Repository: litestar-org/litestar

Issue:
Bug: Issue with ChannelsPlugin and __future__.annotations
### Description

While working with the new Channels feature, I found a bug that affects Litestar's ability to generate the route argument signature when passing `channels: ChannelsPlugin` to a route handler while `from __future__ import annotations` is active.

### URL to code causing the issue

_No response_

### MCVE

_No response_

### Steps to reproduce

```bash
1. Copy script from https://blog.litestar.dev/creating-a-websockets-chat-in-just-30-lines-with-litestar-2ca0f3767a47
2. Add `from __future__ import annotations` to the top
3. Start the app
```


### Screenshots

_No response_

### Logs

```bash
Traceback (most recent call last):                                                                                                                                                            File "/home/ksmith/.pyenv/versions/3.8.16/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap                                                                                  self.run()                                                                                                                                                                                File "/home/ksmith/.pyenv/versions/3.8.16/lib/python3.8/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)                                                                                                                                                 File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/uvicorn/_subprocess.py", line 76, in subprocess_started                                                                             target(sockets=sockets)                                                                                                                                                                   File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/uvicorn/server.py", line 61, in run
    return asyncio.run(self.serve(sockets=sockets))
  File "/home/ksmith/.pyenv/versions/3.8.16/lib/python3.8/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "uvloop/loop.pyx", line 1517, in uvloop.loop.Loop.run_until_complete
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/uvicorn/server.py", line 68, in serve
    config.load()
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/uvicorn/config.py", line 473, in load
    self.loaded_app = import_from_string(self.app)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/uvicorn/importer.py", line 21, in import_from_string
    module = importlib.import_module(module_str)
  File "/home/ksmith/.pyenv/versions/3.8.16/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 843, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/tmp/litestar-bug/app.py", line 28, in <module>
    app = Litestar(
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/app.py", line 415, in __init__
    self.register(route_handler)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/app.py", line 553, in register
    route_handler.on_registration(self)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/handlers/websocket_handlers/listener.py", line 274, in on_registration
    super().on_registration(app)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/handlers/base.py", line 396, in on_registration
    self._create_signature_model(app)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/handlers/websocket_handlers/listener.py", line 286, in _create_signature_model
    parsed_signature=ParsedSignature.from_signature(new_signature, self.resolve_signature_namespace()),
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/utils/signature.py", line 369, in from_signature
    return cls.from_fn(fn, signature_namespace)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/utils/signature.py", line 336, in from_fn
    fn_type_hints = get_fn_type_hints(fn, namespace=signature_namespace)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/litestar/utils/signature.py", line 78, in get_fn_type_hints
    return get_type_hints(fn_to_inspect, globalns=namespace, include_extras=True)
  File "/tmp/litestar-bug/.venv/lib/python3.8/site-packages/typing_extensions.py", line 940, in get_type_hints
    hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
  File "/home/ksmith/.pyenv/versions/3.8.16/lib/python3.8/typing.py", line 1264, in get_type_hints
    value = _eval_type(value, globalns, localns)
  File "/home/ksmith/.pyenv/versions/3.8.16/lib/python3.8/typing.py", line 270, in _eval_type
    return t._evaluate(globalns, localns)
  File "/home/ksmith/.pyenv/versions/3.8.16/lib/python3.8/typing.py", line 518, in _evaluate
    eval(self.__forward_code__, globalns, localns),
  File "<string>", line 1, in <module>
NameError: name 'ChannelsPlugin' is not defined
```


### Litestar Version

2.0.0a7

### Platform

- [X] Linux
- [ ] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)


Discussion:
This is because when you try to resolve the annotation, the object referred to `ChannelsPlugin` isn't in the namespace. We need to explicitly add it to the signature namespace.

As a workaround, you can add it to the signature namespace manually by adding `signature_namespace={"ChannelsPlugin": ChannelsPlugin}` on any layer above the handler (or on the handler itself).

The workaround works great!

I did attempt to add `ChannelsPlugin` to `litestar/utils/signature.py` and add it to [_GLOBAL_NAMES](https://github.com/litestar-org/litestar/blob/1cb42dc491a644e763f095eda723e75212abdca3/litestar/utils/signature.py#L21) the but importing it triggers a circular import error. I have not had time to spend any more time on it.

Thanks!
Kyle

Resolving patch:
diff --git a/litestar/channels/plugin.py b/litestar/channels/plugin.py
--- a/litestar/channels/plugin.py
+++ b/litestar/channels/plugin.py
@@ -111,6 +111,7 @@ def on_app_init(self, app_config: AppConfig) -> AppConfig:
         """Plugin hook. Set up a ``channels`` dependency, add route handlers and register application hooks"""
         app_config.dependencies["channels"] = Provide(lambda: self, use_cache=True, sync_to_thread=False)
         app_config.lifespan.append(self)
+        app_config.signature_namespace.update(ChannelsPlugin=ChannelsPlugin)
 
         if self._create_route_handlers:
             if self._arbitrary_channels_allowed:


Test patch:
diff --git a/tests/channels/test_plugin.py b/tests/channels/test_plugin.py
--- a/tests/channels/test_plugin.py
+++ b/tests/channels/test_plugin.py
@@ -69,6 +69,12 @@ def handler(channels: ChannelsPlugin) -> None:
     assert mock.call_args[0][0] is channels_plugin
 
 
+def test_plugin_dependency_signature_namespace(memory_backend: MemoryChannelsBackend) -> None:
+    channels_plugin = ChannelsPlugin(backend=memory_backend, arbitrary_channels_allowed=True)
+    app = Litestar(plugins=[channels_plugin])
+    assert app.signature_namespace["ChannelsPlugin"] is ChannelsPlugin
+
+
 @pytest.mark.flaky(reruns=5)
 async def test_pub_sub_wait_published(channels_backend: ChannelsBackend) -> None:
     async with ChannelsPlugin(backend=channels_backend, channels=["something"]) as plugin: