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.
- {anyquart-0.2.1 → anyquart-0.3.0}/PKG-INFO +42 -11
- {anyquart-0.2.1 → anyquart-0.3.0}/README.md +37 -8
- {anyquart-0.2.1 → anyquart-0.3.0}/pyproject.toml +3 -2
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/__init__.py +1 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/app.py +73 -64
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/asgi.py +27 -26
- anyquart-0.3.0/src/anyquart/di.py +263 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/app.py +5 -4
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/connections.py +9 -6
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/utils.py +5 -4
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/typing.py +241 -15
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/base.py +2 -1
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/request.py +2 -1
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/websocket.py +3 -2
- {anyquart-0.2.1 → anyquart-0.3.0}/LICENSE.txt +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/__main__.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/blueprints.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/cli.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/config.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/ctx.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/datastructures.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/debug.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/formparser.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/globals.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/helpers.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/json/__init__.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/json/provider.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/json/tag.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/logging.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/py.typed +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/routing.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/sessions.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/signals.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/templating.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/__init__.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/testing/client.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/utils.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/views.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/__init__.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.0}/src/anyquart/wrappers/response.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: anyquart
|
|
3
|
-
Version: 0.
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
-
|
|
877
|
-
|
|
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
|
-
|
|
880
|
-
|
|
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(
|
|
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
|
|
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
|
|
40
|
-
from .wrappers import Response
|
|
41
|
-
from .wrappers import Websocket
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
339
|
+
message: WebSocketAcceptEvent = {
|
|
339
340
|
"headers": [],
|
|
340
341
|
"subprotocol": subprotocol,
|
|
341
342
|
"type": "websocket.accept",
|