anyquart 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {anyquart-0.2.1 → anyquart-0.3.0}/PKG-INFO +42 -11
  2. {anyquart-0.2.1 → anyquart-0.3.0}/README.md +37 -8
  3. {anyquart-0.2.1 → anyquart-0.3.0}/pyproject.toml +3 -2
  4. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/__init__.py +1 -0
  5. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/app.py +73 -64
  6. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/asgi.py +27 -26
  7. anyquart-0.3.0/src/anyquart/di.py +263 -0
  8. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/app.py +5 -4
  9. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/connections.py +9 -6
  10. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/utils.py +5 -4
  11. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/typing.py +241 -15
  12. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/base.py +2 -1
  13. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/request.py +2 -1
  14. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/websocket.py +3 -2
  15. {anyquart-0.2.1 → anyquart-0.3.0}/LICENSE.txt +0 -0
  16. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/__main__.py +0 -0
  17. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/blueprints.py +0 -0
  18. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/cli.py +0 -0
  19. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/config.py +0 -0
  20. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/ctx.py +0 -0
  21. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/datastructures.py +0 -0
  22. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/debug.py +0 -0
  23. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/formparser.py +0 -0
  24. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/globals.py +0 -0
  25. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/helpers.py +0 -0
  26. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/json/__init__.py +0 -0
  27. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/json/provider.py +0 -0
  28. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/json/tag.py +0 -0
  29. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/logging.py +0 -0
  30. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/py.typed +0 -0
  31. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/routing.py +0 -0
  32. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/sessions.py +0 -0
  33. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/signals.py +0 -0
  34. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/templating.py +0 -0
  35. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/__init__.py +0 -0
  36. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/client.py +0 -0
  37. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/utils.py +0 -0
  38. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/views.py +0 -0
  39. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/__init__.py +0 -0
  40. {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/response.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: anyquart
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: A fork of Quart that uses AnyIO
5
5
  Author-email: pgjones <philip.graham.jones@googlemail.com>, NIYONSHUTI Emmanuel <emmanuelniyonshuti13@gmail.com>
6
6
  Maintainer-email: NIYONSHUTI Emmanuel <emmanuelniyonshuti13@gmail.com>
@@ -27,18 +27,20 @@ Requires-Dist: anyio>=4.14.0,<5
27
27
  Requires-Dist: blinker>=1.6
28
28
  Requires-Dist: click>=8.0
29
29
  Requires-Dist: flask>=3.0
30
- Requires-Dist: anycorn>=0.11.2
31
30
  Requires-Dist: importlib-metadata; python_version < '3.13'
32
31
  Requires-Dist: itsdangerous
33
32
  Requires-Dist: jinja2
34
33
  Requires-Dist: markupsafe
35
34
  Requires-Dist: typing-extensions; python_version < '3.13'
36
35
  Requires-Dist: werkzeug>=3.0
36
+ Requires-Dist: anycorn>=0.11.2 ; extra == "anycorn"
37
37
  Requires-Dist: python-dotenv ; extra == "dotenv"
38
38
  Project-URL: Documentation, https://quart.palletsprojects.com
39
39
  Project-URL: Donate, https://palletsprojects.com/donate
40
40
  Project-URL: Source, https://github.com/EmmanuelNiyonshuti/anyquart
41
+ Provides-Extra: anycorn
41
42
  Provides-Extra: dotenv
43
+ Import-Name: anyquart
42
44
 
43
45
  # AnyQuart
44
46
 
@@ -55,11 +57,35 @@ AnyQuart is [Quart](https://github.com/pallets/quart) running on [AnyIO](https:/
55
57
  ## Differences from Quart
56
58
  `AnyQuart` and `Quart` are essentially the same thing. The only difference is the name and the internals(Asyncio replaced with AnyIO). This also means the testing setup changes, which is explained in the [Testing](#testing) section below.
57
59
 
58
- - Works with both asyncio and Trio code via AnyIO, giving you structured concurrency out of the box.
59
- - Uses [Anycorn](https://github.com/davidbrochart/anycorn) instead of Hypercorn as the development server.
60
- - [aiofiles](https://github.com/Tinche/aiofiles) dropped, AnyIO's file I/O is used instead.
61
- - [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) replaced by [AnyIO pytest plugin](https://anyio.readthedocs.io/en/stable/testing.html).
62
- - Runs on Python 3.10+ .
60
+ 1. Works with both asyncio and Trio code via AnyIO, giving you structured concurrency out of the box.
61
+ 2. [aiofiles](https://github.com/Tinche/aiofiles) dropped, AnyIO's file I/O is used instead.
62
+ 3. [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) replaced by [AnyIO pytest plugin](https://anyio.readthedocs.io/en/stable/testing.html).
63
+ 4. As of 0.3.0, [Anycorn](https://github.com/davidbrochart/anycorn) is an optional dependency for the development server, while Quart installs [Hypercorn](https://hypercorn.readthedocs.io) as a required dependency for the same purpose.
64
+ 5. As of 0.3.0, AnyQuart provides a Request-scoped dependency injection for route handlers.
65
+ Request handlers may mark parameters with :class:`Needs` to resolve a request-scoped value.
66
+ e.g:
67
+ ```python
68
+ from anyquart import Needs
69
+
70
+ async def get_db() -> AsyncGenerator[AsyncSession, None]:
71
+ async with async_session() as session:
72
+ yield session
73
+
74
+ @app.route("/users")
75
+ async def get_users(db: AsyncSession = Needs(get_db)) -> None:
76
+ ...
77
+
78
+ # Or:
79
+ DBSession = Annotated[AsyncSession, Needs(get_db)]
80
+
81
+ @app.route("/users")
82
+ async def get_users(db: DBSession) -> None:
83
+ ...
84
+
85
+ # Tests can use `app.dependency_overrides` dictionary to replace route handler's
86
+ # dependency with test dependency
87
+ ```
88
+ 6. Runs on Python 3.10+
63
89
 
64
90
  ## Usage
65
91
  You will have to replace `quart` with `anyquart` and `Quart` with `AnyQuart`.
@@ -91,11 +117,16 @@ async def ws():
91
117
  await websocket.send("hello")
92
118
  await websocket.send_json({"hello": "world"})
93
119
  ```
94
-
120
+ Install an ASGI web server, pick one from [this list](https://asgi.readthedocs.io/en/latest/implementations.html), or install anyquart with Anycorn directly:
121
+ ```bash
122
+ $ uv add anyquart[anycorn]
95
123
  ```
96
- $ anyquart run
97
- * Running on http://127.0.0.1:5000 (CTRL + C to quit)
124
+ run the application:
125
+ ```bash
126
+ $ anycorn app:app
127
+ * [2026-08-01 11:49:46 +0200] [30809] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit)
98
128
  ```
129
+ The built-in `anyquart run` command is only available on anyquart <= 0.2.1 — from 0.3.0 onward, run the ASGI server directly as shown above.
99
130
 
100
131
  # Testing
101
132
  Pytest requires a plugin to run asynchronous test functions and fixtures.
@@ -13,11 +13,35 @@ AnyQuart is [Quart](https://github.com/pallets/quart) running on [AnyIO](https:/
13
13
  ## Differences from Quart
14
14
  `AnyQuart` and `Quart` are essentially the same thing. The only difference is the name and the internals(Asyncio replaced with AnyIO). This also means the testing setup changes, which is explained in the [Testing](#testing) section below.
15
15
 
16
- - Works with both asyncio and Trio code via AnyIO, giving you structured concurrency out of the box.
17
- - Uses [Anycorn](https://github.com/davidbrochart/anycorn) instead of Hypercorn as the development server.
18
- - [aiofiles](https://github.com/Tinche/aiofiles) dropped, AnyIO's file I/O is used instead.
19
- - [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) replaced by [AnyIO pytest plugin](https://anyio.readthedocs.io/en/stable/testing.html).
20
- - Runs on Python 3.10+ .
16
+ 1. Works with both asyncio and Trio code via AnyIO, giving you structured concurrency out of the box.
17
+ 2. [aiofiles](https://github.com/Tinche/aiofiles) dropped, AnyIO's file I/O is used instead.
18
+ 3. [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) replaced by [AnyIO pytest plugin](https://anyio.readthedocs.io/en/stable/testing.html).
19
+ 4. As of 0.3.0, [Anycorn](https://github.com/davidbrochart/anycorn) is an optional dependency for the development server, while Quart installs [Hypercorn](https://hypercorn.readthedocs.io) as a required dependency for the same purpose.
20
+ 5. As of 0.3.0, AnyQuart provides a Request-scoped dependency injection for route handlers.
21
+ Request handlers may mark parameters with :class:`Needs` to resolve a request-scoped value.
22
+ e.g:
23
+ ```python
24
+ from anyquart import Needs
25
+
26
+ async def get_db() -> AsyncGenerator[AsyncSession, None]:
27
+ async with async_session() as session:
28
+ yield session
29
+
30
+ @app.route("/users")
31
+ async def get_users(db: AsyncSession = Needs(get_db)) -> None:
32
+ ...
33
+
34
+ # Or:
35
+ DBSession = Annotated[AsyncSession, Needs(get_db)]
36
+
37
+ @app.route("/users")
38
+ async def get_users(db: DBSession) -> None:
39
+ ...
40
+
41
+ # Tests can use `app.dependency_overrides` dictionary to replace route handler's
42
+ # dependency with test dependency
43
+ ```
44
+ 6. Runs on Python 3.10+
21
45
 
22
46
  ## Usage
23
47
  You will have to replace `quart` with `anyquart` and `Quart` with `AnyQuart`.
@@ -49,11 +73,16 @@ async def ws():
49
73
  await websocket.send("hello")
50
74
  await websocket.send_json({"hello": "world"})
51
75
  ```
52
-
76
+ Install an ASGI web server, pick one from [this list](https://asgi.readthedocs.io/en/latest/implementations.html), or install anyquart with Anycorn directly:
77
+ ```bash
78
+ $ uv add anyquart[anycorn]
53
79
  ```
54
- $ anyquart run
55
- * Running on http://127.0.0.1:5000 (CTRL + C to quit)
80
+ run the application:
81
+ ```bash
82
+ $ anycorn app:app
83
+ * [2026-08-01 11:49:46 +0200] [30809] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit)
56
84
  ```
85
+ The built-in `anyquart run` command is only available on anyquart <= 0.2.1 — from 0.3.0 onward, run the ASGI server directly as shown above.
57
86
 
58
87
  # Testing
59
88
  Pytest requires a plugin to run asynchronous test functions and fixtures.
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "anyquart"
7
- version = "0.2.1"
7
+ version = "0.3.0"
8
8
  description = "A fork of Quart that uses AnyIO"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -37,7 +37,6 @@ dependencies = [
37
37
  "blinker>=1.6",
38
38
  "click>=8.0",
39
39
  "flask>=3.0",
40
- "anycorn>=0.11.2",
41
40
  "importlib-metadata; python_version < '3.13'",
42
41
  "itsdangerous",
43
42
  "jinja2",
@@ -53,6 +52,7 @@ Source = "https://github.com/EmmanuelNiyonshuti/anyquart"
53
52
 
54
53
  [project.optional-dependencies]
55
54
  dotenv = ["python-dotenv"]
55
+ anycorn = ["anycorn>=0.11.2"]
56
56
 
57
57
  [project.scripts]
58
58
  anyquart = "anyquart.cli:main"
@@ -78,6 +78,7 @@ tests = [
78
78
  "pytest-cov",
79
79
  "pytest-sugar",
80
80
  "python-dotenv",
81
+ "anycorn>=0.11.2" # need in typing ci job
81
82
  ]
82
83
  typing = [
83
84
  "mypy",
@@ -13,6 +13,7 @@ from .ctx import copy_current_websocket_context as copy_current_websocket_contex
13
13
  from .ctx import has_app_context as has_app_context
14
14
  from .ctx import has_request_context as has_request_context
15
15
  from .ctx import has_websocket_context as has_websocket_context
16
+ from .di import Needs as Needs
16
17
  from .globals import current_app as current_app
17
18
  from .globals import g as g
18
19
  from .globals import request as request
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  import os
5
4
  import signal
6
5
  import sys
@@ -12,7 +11,7 @@ from collections.abc import Callable
12
11
  from contextlib import AbstractAsyncContextManager
13
12
  from datetime import timedelta
14
13
  from inspect import isasyncgen
15
- from inspect import iscoroutinefunction as _inspect_iscoroutinefunction
14
+ from inspect import iscoroutinefunction
16
15
  from inspect import isgenerator
17
16
  from types import TracebackType
18
17
  from typing import Any
@@ -25,11 +24,6 @@ from typing import TypeVar
25
24
  from urllib.parse import quote
26
25
 
27
26
  import anyio
28
- from anycorn import serve
29
- from anycorn.config import Config as HyperConfig
30
- from anycorn.typing import ASGIReceiveCallable
31
- from anycorn.typing import ASGISendCallable
32
- from anycorn.typing import Scope
33
27
  from anyio import AsyncFile
34
28
  from anyio import create_task_group
35
29
  from anyio import current_time
@@ -52,6 +46,10 @@ from werkzeug.routing import MapAdapter
52
46
  from werkzeug.routing import RoutingException
53
47
  from werkzeug.wrappers import Response as WerkzeugResponse
54
48
 
49
+ from anyquart.typing import ASGIReceiveCallable
50
+ from anyquart.typing import ASGISendCallable
51
+ from anyquart.typing import Scope
52
+
55
53
  from .asgi import ASGIHTTPConnection
56
54
  from .asgi import ASGILifespan
57
55
  from .asgi import ASGIWebsocketConnection
@@ -63,6 +61,8 @@ from .ctx import has_request_context
63
61
  from .ctx import has_websocket_context
64
62
  from .ctx import RequestContext
65
63
  from .ctx import WebsocketContext
64
+ from .di import check_name_conflicts
65
+ from .di import invoke_with_di
66
66
  from .globals import _cv_app
67
67
  from .globals import _cv_request
68
68
  from .globals import _cv_websocket
@@ -127,15 +127,6 @@ from .wrappers import Request
127
127
  from .wrappers import Response
128
128
  from .wrappers import Websocket
129
129
 
130
- # Python 3.14 deprecated asyncio.iscoroutinefunction, but suggested
131
- # inspect.iscoroutinefunction does not work correctly in some Python
132
- # versions before 3.12.
133
- # See https://github.com/python/cpython/issues/122858#issuecomment-2466239748
134
- if sys.version_info >= (3, 12):
135
- iscoroutinefunction = _inspect_iscoroutinefunction
136
- else:
137
- iscoroutinefunction = asyncio.iscoroutinefunction
138
-
139
130
  AppOrBlueprintKey = str | None # The App key is None, whereas blueprints are named
140
131
  T_after_serving = TypeVar("T_after_serving", bound=AfterServingCallable)
141
132
  T_after_websocket = TypeVar("T_after_websocket", bound=AfterWebsocketCallable)
@@ -345,6 +336,10 @@ class AnyQuart(App):
345
336
  self.cli = AppGroup()
346
337
  self.cli.name = self.name
347
338
 
339
+ # overrides route handler's dependencies, useful for tests for replacing
340
+ # expensive dependency
341
+ self.dependency_overrides: dict[Callable[..., Any], Callable[..., Any]] = {}
342
+
348
343
  if self.has_static_folder:
349
344
  assert bool(static_host) == host_matching, (
350
345
  "Invalid static_host/host_matching combination"
@@ -573,6 +568,42 @@ class AnyQuart(App):
573
568
  return self.url_map.bind(self.config["SERVER_NAME"], url_scheme=scheme)
574
569
  return None
575
570
 
571
+ @setupmethod
572
+ def add_url_rule(
573
+ self,
574
+ rule: str,
575
+ endpoint: str | None = None,
576
+ view_func: Callable[..., Any] | None = None,
577
+ provide_automatic_options: bool | None = None,
578
+ **options: Any,
579
+ ) -> None:
580
+ """Register a rule for routing incoming requests and building URLs.
581
+
582
+ In addition to the default behaviour this raises
583
+ :exc:`ValueError` at registration time if any parameter of the view
584
+ function is marked as a :class:`~anyquart.di.Needs` dependency but its
585
+ name also appears as a URL rule converter in ``rule``, since the two
586
+ sources would otherwise clobber one another when dispatching.
587
+
588
+ Arguments:
589
+ rule: The URL rule string.
590
+ endpoint: The endpoint name to associate with the rule
591
+ and view function.
592
+ view_func: The view function to associate with the
593
+ endpoint name.
594
+ provide_automatic_options: Add the ``OPTIONS`` method and
595
+ respond to ``OPTIONS`` requests automatically.
596
+ """
597
+ if view_func is not None:
598
+ check_name_conflicts(view_func, rule, options.get("defaults"))
599
+ super().add_url_rule(
600
+ rule,
601
+ endpoint,
602
+ view_func,
603
+ provide_automatic_options,
604
+ **options,
605
+ )
606
+
576
607
  def websocket(
577
608
  self,
578
609
  rule: str,
@@ -785,10 +816,9 @@ class AnyQuart(App):
785
816
  app_import_path: str | None = None,
786
817
  **kwargs: Any,
787
818
  ) -> None:
788
- """Run this application.
789
-
790
- This is best used for development only, see Hypercorn for
791
- production servers.
819
+ """Run this application. This is best used for development only,
820
+ See from the list: https://asgi.readthedocs.io/en/latest/implementations.html
821
+ for production servers.
792
822
 
793
823
  Arguments:
794
824
  host: Hostname to listen on. By default this is loopback
@@ -800,6 +830,13 @@ class AnyQuart(App):
800
830
  certfile: Path to the SSL certificate file.
801
831
  keyfile: Path to the SSL key file.
802
832
  """
833
+ try:
834
+ from anycorn.config import Config as HyperConfig
835
+ except ImportError:
836
+ raise RuntimeError("""Install ASGI webserver to run in development mode
837
+ e.g: `uv add anyquart[anycorn]
838
+ or choose one from this list https://asgi.readthedocs.io/en/latest/implementations.html
839
+ """) from None
803
840
  if kwargs:
804
841
  warnings.warn(
805
842
  f"Additional arguments, {','.join(kwargs.keys())}, are not supported.\n"
@@ -830,25 +867,6 @@ class AnyQuart(App):
830
867
  scheme = "https" if certfile is not None and keyfile is not None else "http"
831
868
  print(f" * Running on {scheme}://{host}:{port} (CTRL + C to quit)")
832
869
 
833
- if use_reloader:
834
- from anycorn.run import run as anycorn_run
835
-
836
- config = self._build_config(host, port, debug, ca_certs, certfile, keyfile)
837
- config.use_reloader = True
838
- config.application_path = app_import_path
839
- anycorn_run(config)
840
- else:
841
- anyio.run(self._run_serve, host, port, debug, ca_certs, certfile, keyfile)
842
-
843
- def _build_config(
844
- self,
845
- host: str,
846
- port: int,
847
- debug: bool | None,
848
- ca_certs: str | None,
849
- certfile: str | None,
850
- keyfile: str | None,
851
- ) -> HyperConfig:
852
870
  config = HyperConfig()
853
871
  config.access_log_format = "%(h)s %(r)s %(s)s %(b)s %(D)s"
854
872
  config.accesslog = "-"
@@ -860,32 +878,20 @@ class AnyQuart(App):
860
878
  config.debug = debug
861
879
  config.errorlog = config.accesslog
862
880
  config.keyfile = keyfile
863
- return config
864
881
 
865
- async def _run_serve(
866
- self,
867
- host: str = "127.0.0.1",
868
- port: int = 5000,
869
- debug: bool = False,
870
- ca_certs: str | None = None,
871
- certfile: str | None = None,
872
- keyfile: str | None = None,
873
- ) -> None:
874
- """runs this application.
882
+ if use_reloader:
883
+ # run directly with anycorn when in reload mode.
884
+ from anycorn.run import run as anycorn_run
875
885
 
876
- This is best used for development only, see Hypercorn for
877
- production servers.
886
+ config.use_reloader = True
887
+ config.application_path = app_import_path
888
+ anycorn_run(config)
889
+ else:
890
+ anyio.run(self._run_serve, config)
878
891
 
879
- Arguments:
880
- host: Hostname to listen on. By default this is loopback
881
- only, use 0.0.0.0 to have the server listen externally.
882
- port: Port number to listen on.
883
- debug: If set enable (or disable) debug mode and debug output.
884
- ca_certs: Path to the SSL CA certificate file.
885
- certfile: Path to the SSL certificate file.
886
- keyfile: Path to the SSL key file.
892
+ async def _run_serve(self, config) -> None: # type: ignore
893
+ from anycorn import serve
887
894
 
888
- """
889
895
  shutdown_event = Event()
890
896
 
891
897
  async def watch_signals() -> None:
@@ -894,10 +900,13 @@ class AnyQuart(App):
894
900
  shutdown_event.set()
895
901
  return
896
902
 
897
- config = self._build_config(host, port, debug, ca_certs, certfile, keyfile)
898
903
  async with create_task_group() as tg:
899
904
  tg.start_soon(watch_signals)
900
- await serve(self, config, shutdown_trigger=shutdown_event.wait)
905
+ await serve(
906
+ self, # type: ignore[arg-type]
907
+ config,
908
+ shutdown_trigger=shutdown_event.wait,
909
+ )
901
910
  tg.cancel_scope.cancel()
902
911
 
903
912
  def test_client(
@@ -1574,7 +1583,7 @@ class AnyQuart(App):
1574
1583
  return await self.make_default_options_response()
1575
1584
 
1576
1585
  handler = self.view_functions[request_.url_rule.endpoint]
1577
- return await self.ensure_async(handler)(**request_.view_args) # type: ignore[return-value]
1586
+ return await invoke_with_di(self, handler, request_.view_args)
1578
1587
 
1579
1588
  async def dispatch_websocket(
1580
1589
  self, websocket_context: WebsocketContext | None = None
@@ -7,21 +7,6 @@ from typing import cast
7
7
  from typing import TYPE_CHECKING
8
8
  from urllib.parse import urlparse
9
9
 
10
- from anycorn.typing import ASGIReceiveCallable
11
- from anycorn.typing import ASGISendCallable
12
- from anycorn.typing import HTTPResponseBodyEvent
13
- from anycorn.typing import HTTPResponseStartEvent
14
- from anycorn.typing import HTTPScope
15
- from anycorn.typing import LifespanScope
16
- from anycorn.typing import LifespanShutdownCompleteEvent
17
- from anycorn.typing import LifespanShutdownFailedEvent
18
- from anycorn.typing import LifespanStartupCompleteEvent
19
- from anycorn.typing import LifespanStartupFailedEvent
20
- from anycorn.typing import WebsocketAcceptEvent
21
- from anycorn.typing import WebsocketCloseEvent
22
- from anycorn.typing import WebsocketResponseBodyEvent
23
- from anycorn.typing import WebsocketResponseStartEvent
24
- from anycorn.typing import WebsocketScope
25
10
  from anyio import create_memory_object_stream
26
11
  from anyio import create_task_group
27
12
  from anyio import fail_after
@@ -31,14 +16,30 @@ from anyio.abc import TaskGroup
31
16
  from werkzeug.datastructures import Headers
32
17
  from werkzeug.wrappers import Response as WerkzeugResponse
33
18
 
19
+ from anyquart.typing import ASGIReceiveCallable
20
+ from anyquart.typing import ASGISendCallable
21
+ from anyquart.typing import HTTPResponseBodyEvent
22
+ from anyquart.typing import HTTPResponseStartEvent
23
+ from anyquart.typing import HTTPScope
24
+ from anyquart.typing import LifespanScope
25
+ from anyquart.typing import LifespanShutdownCompleteEvent
26
+ from anyquart.typing import LifespanShutdownFailedEvent
27
+ from anyquart.typing import LifespanStartupCompleteEvent
28
+ from anyquart.typing import LifespanStartupFailedEvent
29
+ from anyquart.typing import WebSocketAcceptEvent
30
+ from anyquart.typing import WebSocketCloseEvent
31
+ from anyquart.typing import WebSocketResponseBodyEvent
32
+ from anyquart.typing import WebSocketResponseStartEvent
33
+ from anyquart.typing import WebSocketScope
34
+
34
35
  from .debug import traceback_response
35
36
  from .signals import websocket_received
36
37
  from .signals import websocket_sent
37
38
  from .typing import ResponseTypes
38
39
  from .utils import encode_headers
39
- from .wrappers import Request # noqa: F401
40
- from .wrappers import Response # noqa: F401
41
- from .wrappers import Websocket # noqa: F401
40
+ from .wrappers import Request
41
+ from .wrappers import Response
42
+ from .wrappers import Websocket
42
43
 
43
44
  if TYPE_CHECKING:
44
45
  from .app import AnyQuart # noqa: F401
@@ -180,7 +181,7 @@ class ASGIHTTPConnection:
180
181
 
181
182
 
182
183
  class ASGIWebsocketConnection:
183
- def __init__(self, app: AnyQuart, scope: WebsocketScope) -> None:
184
+ def __init__(self, app: AnyQuart, scope: WebSocketScope) -> None:
184
185
  self.app = app
185
186
  self.scope = scope
186
187
  self._accepted = False
@@ -270,7 +271,7 @@ class ASGIWebsocketConnection:
270
271
  ]
271
272
  await send(
272
273
  cast(
273
- WebsocketResponseStartEvent,
274
+ WebSocketResponseStartEvent,
274
275
  {
275
276
  "type": "websocket.http.response.start",
276
277
  "status": response.status_code,
@@ -282,7 +283,7 @@ class ASGIWebsocketConnection:
282
283
  for data in response.response:
283
284
  await send(
284
285
  cast(
285
- WebsocketResponseBodyEvent,
286
+ WebSocketResponseBodyEvent,
286
287
  {
287
288
  "type": "websocket.http.response.body",
288
289
  "body": data,
@@ -295,7 +296,7 @@ class ASGIWebsocketConnection:
295
296
  async for data in body:
296
297
  await send(
297
298
  cast(
298
- WebsocketResponseBodyEvent,
299
+ WebSocketResponseBodyEvent,
299
300
  {
300
301
  "type": "websocket.http.response.body",
301
302
  "body": data,
@@ -305,7 +306,7 @@ class ASGIWebsocketConnection:
305
306
  )
306
307
  await send(
307
308
  cast(
308
- WebsocketResponseBodyEvent,
309
+ WebSocketResponseBodyEvent,
309
310
  {
310
311
  "type": "websocket.http.response.body",
311
312
  "body": b"",
@@ -315,11 +316,11 @@ class ASGIWebsocketConnection:
315
316
  )
316
317
  elif not self._closed:
317
318
  await send(
318
- cast(WebsocketCloseEvent, {"type": "websocket.close", "code": 1000})
319
+ cast(WebSocketCloseEvent, {"type": "websocket.close", "code": 1000})
319
320
  )
320
321
  elif self._accepted and not self._closed:
321
322
  await send(
322
- cast(WebsocketCloseEvent, {"type": "websocket.close", "code": 1000})
323
+ cast(WebSocketCloseEvent, {"type": "websocket.close", "code": 1000})
323
324
  )
324
325
 
325
326
  tg.cancel_scope.cancel()
@@ -335,7 +336,7 @@ class ASGIWebsocketConnection:
335
336
  self, send: ASGISendCallable, headers: Headers, subprotocol: str | None
336
337
  ) -> None:
337
338
  if not self._accepted:
338
- message: WebsocketAcceptEvent = {
339
+ message: WebSocketAcceptEvent = {
339
340
  "headers": [],
340
341
  "subprotocol": subprotocol,
341
342
  "type": "websocket.accept",