anyquart 0.2.1__tar.gz → 0.3.1__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.1}/PKG-INFO +58 -18
- {anyquart-0.2.1 → anyquart-0.3.1}/README.md +50 -12
- {anyquart-0.2.1 → anyquart-0.3.1}/pyproject.toml +9 -9
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/__init__.py +1 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/app.py +80 -63
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/asgi.py +27 -26
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/cli.py +32 -15
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/ctx.py +7 -4
- anyquart-0.3.1/src/anyquart/di.py +260 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/helpers.py +2 -2
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/sessions.py +3 -5
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/templating.py +1 -3
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/testing/__init__.py +11 -14
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/testing/app.py +5 -4
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/testing/connections.py +9 -6
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/testing/utils.py +15 -9
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/typing.py +237 -15
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/wrappers/base.py +2 -1
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/wrappers/request.py +2 -1
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/wrappers/response.py +1 -1
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/wrappers/websocket.py +3 -2
- {anyquart-0.2.1 → anyquart-0.3.1}/LICENSE.txt +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/__main__.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/blueprints.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/config.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/datastructures.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/debug.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/formparser.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/globals.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/json/__init__.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/json/provider.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/json/tag.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/logging.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/py.typed +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/routing.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/signals.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/testing/client.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/utils.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/views.py +0 -0
- {anyquart-0.2.1 → anyquart-0.3.1}/src/anyquart/wrappers/__init__.py +6 -6
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: anyquart
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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>
|
|
7
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -13,12 +13,12 @@ Classifier: Framework :: Flask
|
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.15
|
|
21
|
+
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
|
|
22
22
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
24
24
|
Classifier: Typing :: Typed
|
|
@@ -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
|
-
Requires-Dist: typing-extensions; python_version < '3.
|
|
34
|
+
Requires-Dist: typing-extensions >= 4.16.0; python_version < '3.15'
|
|
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,18 +57,45 @@ 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
|
+
|
|
71
|
+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
72
|
+
async with async_session() as session:
|
|
73
|
+
yield session
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.route("/users")
|
|
77
|
+
async def get_users(db: AsyncSession = Needs(get_db)) -> None: ...
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Or:
|
|
81
|
+
DBSession = Annotated[AsyncSession, Needs(get_db)]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.route("/users")
|
|
85
|
+
async def get_users(db: DBSession) -> None: ...
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Tests can use `app.dependency_overrides` dictionary to replace route handler's
|
|
89
|
+
# dependency with test dependency
|
|
90
|
+
```
|
|
91
|
+
6. Runs on Python 3.11+
|
|
63
92
|
|
|
64
93
|
## Usage
|
|
65
94
|
You will have to replace `quart` with `anyquart` and `Quart` with `AnyQuart`.
|
|
66
95
|
|
|
67
|
-
Install from PyPI using an installer such as pip. Requires Python 3.
|
|
96
|
+
Install from PyPI using an installer such as pip. Requires Python 3.11+.
|
|
68
97
|
|
|
69
|
-
```
|
|
98
|
+
```python
|
|
70
99
|
$ pip install anyquart
|
|
71
100
|
```
|
|
72
101
|
|
|
@@ -77,26 +106,35 @@ from anyquart import AnyQuart, websocket, render_template
|
|
|
77
106
|
|
|
78
107
|
app = AnyQuart(__name__)
|
|
79
108
|
|
|
109
|
+
|
|
80
110
|
@app.route("/")
|
|
81
111
|
async def hello():
|
|
82
112
|
return await render_template("index.html")
|
|
83
113
|
|
|
114
|
+
|
|
84
115
|
@app.route("/api")
|
|
85
116
|
async def json():
|
|
86
117
|
return {"hello": "world"}
|
|
87
118
|
|
|
119
|
+
|
|
88
120
|
@app.websocket("/ws")
|
|
89
121
|
async def ws():
|
|
90
122
|
while True:
|
|
91
123
|
await websocket.send("hello")
|
|
92
124
|
await websocket.send_json({"hello": "world"})
|
|
93
125
|
```
|
|
94
|
-
|
|
126
|
+
Install an ASGI web server, pick one from [this list](https://asgi.readthedocs.io/en/latest/implementations.html), or install anyquart with Anycorn directly:
|
|
127
|
+
```bash
|
|
128
|
+
$ pip install anyquart[anycorn]
|
|
95
129
|
```
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
run the application:
|
|
131
|
+
```bash
|
|
132
|
+
$ anycorn app:app
|
|
133
|
+
* [2026-08-01 11:49:46 +0200] [30809] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit)
|
|
98
134
|
```
|
|
99
135
|
|
|
136
|
+
**Note**: As of 0.3.0, `anyquart run` requires an ASGI web server to be installed. If no ASGI web server is available, running the application in development mode will raise a `RuntimeError`. Install AnyQuart with Anycorn using the command above, or install another ASGI web server from the list above.
|
|
137
|
+
|
|
100
138
|
# Testing
|
|
101
139
|
Pytest requires a plugin to run asynchronous test functions and fixtures.
|
|
102
140
|
Quart uses pytest-asyncio, while AnyQuart uses AnyIO's pytest plugin. You will need to specify which backend your tests run on via the `anyio_backend` fixture and decorate your asynchronous tests with `@pytest.mark.anyio`.
|
|
@@ -106,20 +144,22 @@ import pytest
|
|
|
106
144
|
|
|
107
145
|
from app import app
|
|
108
146
|
|
|
147
|
+
|
|
109
148
|
@pytest.fixture()
|
|
110
149
|
def anyio_backend():
|
|
111
|
-
return "trio"
|
|
150
|
+
return "trio" # you can replace with "asyncio"
|
|
151
|
+
|
|
112
152
|
|
|
113
153
|
@pytest.fixture()
|
|
114
154
|
def test_client():
|
|
115
155
|
return app.test_client()
|
|
116
156
|
|
|
157
|
+
|
|
117
158
|
@pytest.mark.anyio
|
|
118
159
|
async def test_do_something(test_client) -> None:
|
|
119
160
|
response = await test_client.get("/")
|
|
120
161
|
assert response.status_code == 200
|
|
121
162
|
assert await response.json == {"hello": "world"}
|
|
122
|
-
|
|
123
163
|
```
|
|
124
164
|
|
|
125
165
|
Refer to the [Quart documentation](https://quart.palletsprojects.com) for more details.
|
|
@@ -13,18 +13,45 @@ 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
|
+
|
|
27
|
+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
28
|
+
async with async_session() as session:
|
|
29
|
+
yield session
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.route("/users")
|
|
33
|
+
async def get_users(db: AsyncSession = Needs(get_db)) -> None: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Or:
|
|
37
|
+
DBSession = Annotated[AsyncSession, Needs(get_db)]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.route("/users")
|
|
41
|
+
async def get_users(db: DBSession) -> None: ...
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Tests can use `app.dependency_overrides` dictionary to replace route handler's
|
|
45
|
+
# dependency with test dependency
|
|
46
|
+
```
|
|
47
|
+
6. Runs on Python 3.11+
|
|
21
48
|
|
|
22
49
|
## Usage
|
|
23
50
|
You will have to replace `quart` with `anyquart` and `Quart` with `AnyQuart`.
|
|
24
51
|
|
|
25
|
-
Install from PyPI using an installer such as pip. Requires Python 3.
|
|
52
|
+
Install from PyPI using an installer such as pip. Requires Python 3.11+.
|
|
26
53
|
|
|
27
|
-
```
|
|
54
|
+
```python
|
|
28
55
|
$ pip install anyquart
|
|
29
56
|
```
|
|
30
57
|
|
|
@@ -35,26 +62,35 @@ from anyquart import AnyQuart, websocket, render_template
|
|
|
35
62
|
|
|
36
63
|
app = AnyQuart(__name__)
|
|
37
64
|
|
|
65
|
+
|
|
38
66
|
@app.route("/")
|
|
39
67
|
async def hello():
|
|
40
68
|
return await render_template("index.html")
|
|
41
69
|
|
|
70
|
+
|
|
42
71
|
@app.route("/api")
|
|
43
72
|
async def json():
|
|
44
73
|
return {"hello": "world"}
|
|
45
74
|
|
|
75
|
+
|
|
46
76
|
@app.websocket("/ws")
|
|
47
77
|
async def ws():
|
|
48
78
|
while True:
|
|
49
79
|
await websocket.send("hello")
|
|
50
80
|
await websocket.send_json({"hello": "world"})
|
|
51
81
|
```
|
|
52
|
-
|
|
82
|
+
Install an ASGI web server, pick one from [this list](https://asgi.readthedocs.io/en/latest/implementations.html), or install anyquart with Anycorn directly:
|
|
83
|
+
```bash
|
|
84
|
+
$ pip install anyquart[anycorn]
|
|
53
85
|
```
|
|
54
|
-
|
|
55
|
-
|
|
86
|
+
run the application:
|
|
87
|
+
```bash
|
|
88
|
+
$ anycorn app:app
|
|
89
|
+
* [2026-08-01 11:49:46 +0200] [30809] [INFO] Running on http://127.0.0.1:8000 (CTRL + C to quit)
|
|
56
90
|
```
|
|
57
91
|
|
|
92
|
+
**Note**: As of 0.3.0, `anyquart run` requires an ASGI web server to be installed. If no ASGI web server is available, running the application in development mode will raise a `RuntimeError`. Install AnyQuart with Anycorn using the command above, or install another ASGI web server from the list above.
|
|
93
|
+
|
|
58
94
|
# Testing
|
|
59
95
|
Pytest requires a plugin to run asynchronous test functions and fixtures.
|
|
60
96
|
Quart uses pytest-asyncio, while AnyQuart uses AnyIO's pytest plugin. You will need to specify which backend your tests run on via the `anyio_backend` fixture and decorate your asynchronous tests with `@pytest.mark.anyio`.
|
|
@@ -64,20 +100,22 @@ import pytest
|
|
|
64
100
|
|
|
65
101
|
from app import app
|
|
66
102
|
|
|
103
|
+
|
|
67
104
|
@pytest.fixture()
|
|
68
105
|
def anyio_backend():
|
|
69
|
-
return "trio"
|
|
106
|
+
return "trio" # you can replace with "asyncio"
|
|
107
|
+
|
|
70
108
|
|
|
71
109
|
@pytest.fixture()
|
|
72
110
|
def test_client():
|
|
73
111
|
return app.test_client()
|
|
74
112
|
|
|
113
|
+
|
|
75
114
|
@pytest.mark.anyio
|
|
76
115
|
async def test_do_something(test_client) -> None:
|
|
77
116
|
response = await test_client.get("/")
|
|
78
117
|
assert response.status_code == 200
|
|
79
118
|
assert await response.json == {"hello": "world"}
|
|
80
|
-
|
|
81
119
|
```
|
|
82
120
|
|
|
83
121
|
Refer to the [Quart documentation](https://quart.palletsprojects.com) for more details.
|
|
@@ -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.1"
|
|
8
8
|
description = "A fork of Quart that uses AnyIO"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -21,28 +21,27 @@ classifiers = [
|
|
|
21
21
|
"Intended Audience :: Developers",
|
|
22
22
|
"Operating System :: OS Independent",
|
|
23
23
|
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
-
"Programming Language :: Python :: 3.10",
|
|
25
24
|
"Programming Language :: Python :: 3.11",
|
|
26
25
|
"Programming Language :: Python :: 3.12",
|
|
27
26
|
"Programming Language :: Python :: 3.13",
|
|
28
27
|
"Programming Language :: Python :: 3.14",
|
|
29
28
|
"Programming Language :: Python :: 3.15",
|
|
29
|
+
"Programming Language :: Python :: Free Threading :: 2 - Beta",
|
|
30
30
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
31
31
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
32
32
|
"Typing :: Typed",
|
|
33
33
|
]
|
|
34
|
-
requires-python = ">=3.
|
|
34
|
+
requires-python = ">=3.11"
|
|
35
35
|
dependencies = [
|
|
36
36
|
"anyio>=4.14.0,<5",
|
|
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",
|
|
44
43
|
"markupsafe",
|
|
45
|
-
"typing-extensions; python_version < '3.
|
|
44
|
+
"typing-extensions >= 4.16.0; python_version < '3.15'",
|
|
46
45
|
"werkzeug>=3.0",
|
|
47
46
|
]
|
|
48
47
|
|
|
@@ -53,13 +52,14 @@ 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"
|
|
59
59
|
|
|
60
60
|
[dependency-groups]
|
|
61
61
|
dev = [
|
|
62
|
-
"trio==0.
|
|
62
|
+
"trio==0.35.0",
|
|
63
63
|
"ruff",
|
|
64
64
|
"tox",
|
|
65
65
|
"tox-uv",
|
|
@@ -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",
|
|
@@ -122,7 +123,7 @@ warn_return_any = false
|
|
|
122
123
|
files = ["src", "tests"]
|
|
123
124
|
|
|
124
125
|
[tool.pyright]
|
|
125
|
-
pythonVersion = "3.
|
|
126
|
+
pythonVersion = "3.11"
|
|
126
127
|
include = ["src", "tests"]
|
|
127
128
|
typeCheckingMode = "basic"
|
|
128
129
|
|
|
@@ -144,7 +145,6 @@ select = [
|
|
|
144
145
|
"W", # pycodestyle warning
|
|
145
146
|
]
|
|
146
147
|
ignore = [
|
|
147
|
-
"UP038", # keep isinstance tuple
|
|
148
148
|
"UP007", # Union[] is needed for forward-referenced type aliases
|
|
149
149
|
"UP045"
|
|
150
150
|
]
|
|
@@ -155,7 +155,7 @@ order-by-type = false
|
|
|
155
155
|
|
|
156
156
|
[tool.tox]
|
|
157
157
|
env_list = [
|
|
158
|
-
"py3.15", "py3.14", "py3.
|
|
158
|
+
"py3.15", "py3.14", "py3.14t", "py3.13", "py3.12", "py3.11",
|
|
159
159
|
"style",
|
|
160
160
|
"typing",
|
|
161
161
|
"docs",
|
|
@@ -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,6 @@ 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
|
|
16
14
|
from inspect import isgenerator
|
|
17
15
|
from types import TracebackType
|
|
18
16
|
from typing import Any
|
|
@@ -25,11 +23,6 @@ from typing import TypeVar
|
|
|
25
23
|
from urllib.parse import quote
|
|
26
24
|
|
|
27
25
|
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
26
|
from anyio import AsyncFile
|
|
34
27
|
from anyio import create_task_group
|
|
35
28
|
from anyio import current_time
|
|
@@ -52,6 +45,10 @@ from werkzeug.routing import MapAdapter
|
|
|
52
45
|
from werkzeug.routing import RoutingException
|
|
53
46
|
from werkzeug.wrappers import Response as WerkzeugResponse
|
|
54
47
|
|
|
48
|
+
from anyquart.typing import ASGIReceiveCallable
|
|
49
|
+
from anyquart.typing import ASGISendCallable
|
|
50
|
+
from anyquart.typing import Scope
|
|
51
|
+
|
|
55
52
|
from .asgi import ASGIHTTPConnection
|
|
56
53
|
from .asgi import ASGILifespan
|
|
57
54
|
from .asgi import ASGIWebsocketConnection
|
|
@@ -63,6 +60,8 @@ from .ctx import has_request_context
|
|
|
63
60
|
from .ctx import has_websocket_context
|
|
64
61
|
from .ctx import RequestContext
|
|
65
62
|
from .ctx import WebsocketContext
|
|
63
|
+
from .di import check_name_conflicts
|
|
64
|
+
from .di import invoke_with_di
|
|
66
65
|
from .globals import _cv_app
|
|
67
66
|
from .globals import _cv_request
|
|
68
67
|
from .globals import _cv_websocket
|
|
@@ -97,8 +96,8 @@ from .testing import make_test_body_with_headers
|
|
|
97
96
|
from .testing import make_test_headers_path_and_query_string
|
|
98
97
|
from .testing import make_test_scope
|
|
99
98
|
from .testing import no_op_push
|
|
100
|
-
from .testing import sentinel
|
|
101
99
|
from .testing import TestApp
|
|
100
|
+
from .testing.utils import sentinel
|
|
102
101
|
from .typing import AfterServingCallable
|
|
103
102
|
from .typing import AfterWebsocketCallable
|
|
104
103
|
from .typing import ASGIHTTPProtocol
|
|
@@ -127,14 +126,12 @@ from .wrappers import Request
|
|
|
127
126
|
from .wrappers import Response
|
|
128
127
|
from .wrappers import Websocket
|
|
129
128
|
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if sys.version_info >= (3, 12):
|
|
135
|
-
iscoroutinefunction = _inspect_iscoroutinefunction
|
|
129
|
+
# inspect.iscoroutinefunction behaves differently on python < 3.13
|
|
130
|
+
# See: https://github.com/python/cpython/issues/94924
|
|
131
|
+
if sys.version_info >= (3, 13):
|
|
132
|
+
from inspect import iscoroutinefunction
|
|
136
133
|
else:
|
|
137
|
-
|
|
134
|
+
from asyncio import iscoroutinefunction
|
|
138
135
|
|
|
139
136
|
AppOrBlueprintKey = str | None # The App key is None, whereas blueprints are named
|
|
140
137
|
T_after_serving = TypeVar("T_after_serving", bound=AfterServingCallable)
|
|
@@ -345,6 +342,10 @@ class AnyQuart(App):
|
|
|
345
342
|
self.cli = AppGroup()
|
|
346
343
|
self.cli.name = self.name
|
|
347
344
|
|
|
345
|
+
# overrides route handler's dependencies, useful for tests for replacing
|
|
346
|
+
# expensive dependency
|
|
347
|
+
self.dependency_overrides: dict[Callable[..., Any], Callable[..., Any]] = {}
|
|
348
|
+
|
|
348
349
|
if self.has_static_folder:
|
|
349
350
|
assert bool(static_host) == host_matching, (
|
|
350
351
|
"Invalid static_host/host_matching combination"
|
|
@@ -573,6 +574,42 @@ class AnyQuart(App):
|
|
|
573
574
|
return self.url_map.bind(self.config["SERVER_NAME"], url_scheme=scheme)
|
|
574
575
|
return None
|
|
575
576
|
|
|
577
|
+
@setupmethod
|
|
578
|
+
def add_url_rule(
|
|
579
|
+
self,
|
|
580
|
+
rule: str,
|
|
581
|
+
endpoint: str | None = None,
|
|
582
|
+
view_func: Callable[..., Any] | None = None,
|
|
583
|
+
provide_automatic_options: bool | None = None,
|
|
584
|
+
**options: Any,
|
|
585
|
+
) -> None:
|
|
586
|
+
"""Register a rule for routing incoming requests and building URLs.
|
|
587
|
+
|
|
588
|
+
In addition to the default behaviour this raises
|
|
589
|
+
:exc:`ValueError` at registration time if any parameter of the view
|
|
590
|
+
function is marked as a :class:`~anyquart.di.Needs` dependency but its
|
|
591
|
+
name also appears as a URL rule converter in ``rule``, since the two
|
|
592
|
+
sources would otherwise clobber one another when dispatching.
|
|
593
|
+
|
|
594
|
+
Arguments:
|
|
595
|
+
rule: The URL rule string.
|
|
596
|
+
endpoint: The endpoint name to associate with the rule
|
|
597
|
+
and view function.
|
|
598
|
+
view_func: The view function to associate with the
|
|
599
|
+
endpoint name.
|
|
600
|
+
provide_automatic_options: Add the ``OPTIONS`` method and
|
|
601
|
+
respond to ``OPTIONS`` requests automatically.
|
|
602
|
+
"""
|
|
603
|
+
if view_func is not None:
|
|
604
|
+
check_name_conflicts(view_func, rule, options.get("defaults"))
|
|
605
|
+
super().add_url_rule(
|
|
606
|
+
rule,
|
|
607
|
+
endpoint,
|
|
608
|
+
view_func,
|
|
609
|
+
provide_automatic_options,
|
|
610
|
+
**options,
|
|
611
|
+
)
|
|
612
|
+
|
|
576
613
|
def websocket(
|
|
577
614
|
self,
|
|
578
615
|
rule: str,
|
|
@@ -785,10 +822,9 @@ class AnyQuart(App):
|
|
|
785
822
|
app_import_path: str | None = None,
|
|
786
823
|
**kwargs: Any,
|
|
787
824
|
) -> None:
|
|
788
|
-
"""Run this application.
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
production servers.
|
|
825
|
+
"""Run this application. This is best used for development only,
|
|
826
|
+
See from the list: https://asgi.readthedocs.io/en/latest/implementations.html
|
|
827
|
+
for production servers.
|
|
792
828
|
|
|
793
829
|
Arguments:
|
|
794
830
|
host: Hostname to listen on. By default this is loopback
|
|
@@ -800,6 +836,15 @@ class AnyQuart(App):
|
|
|
800
836
|
certfile: Path to the SSL certificate file.
|
|
801
837
|
keyfile: Path to the SSL key file.
|
|
802
838
|
"""
|
|
839
|
+
try:
|
|
840
|
+
from anycorn.config import Config as HyperConfig
|
|
841
|
+
except ImportError:
|
|
842
|
+
raise RuntimeError(
|
|
843
|
+
"Running anyquart requires an ASGI web server to be installed\n"
|
|
844
|
+
"You can install one from this list https://asgi.readthedocs.io/en/latest/implementations.html\n"
|
|
845
|
+
"e.g:\n"
|
|
846
|
+
" $ pip install anycorn\n"
|
|
847
|
+
) from None
|
|
803
848
|
if kwargs:
|
|
804
849
|
warnings.warn(
|
|
805
850
|
f"Additional arguments, {','.join(kwargs.keys())}, are not supported.\n"
|
|
@@ -830,25 +875,6 @@ class AnyQuart(App):
|
|
|
830
875
|
scheme = "https" if certfile is not None and keyfile is not None else "http"
|
|
831
876
|
print(f" * Running on {scheme}://{host}:{port} (CTRL + C to quit)")
|
|
832
877
|
|
|
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
878
|
config = HyperConfig()
|
|
853
879
|
config.access_log_format = "%(h)s %(r)s %(s)s %(b)s %(D)s"
|
|
854
880
|
config.accesslog = "-"
|
|
@@ -860,32 +886,20 @@ class AnyQuart(App):
|
|
|
860
886
|
config.debug = debug
|
|
861
887
|
config.errorlog = config.accesslog
|
|
862
888
|
config.keyfile = keyfile
|
|
863
|
-
return config
|
|
864
889
|
|
|
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.
|
|
890
|
+
if use_reloader:
|
|
891
|
+
# run directly with anycorn when in reload mode.
|
|
892
|
+
from anycorn.run import run as anycorn_run
|
|
875
893
|
|
|
876
|
-
|
|
877
|
-
|
|
894
|
+
config.use_reloader = True
|
|
895
|
+
config.application_path = app_import_path
|
|
896
|
+
anycorn_run(config)
|
|
897
|
+
else:
|
|
898
|
+
anyio.run(self._run_serve, config)
|
|
878
899
|
|
|
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.
|
|
900
|
+
async def _run_serve(self, config) -> None: # type: ignore
|
|
901
|
+
from anycorn import serve
|
|
887
902
|
|
|
888
|
-
"""
|
|
889
903
|
shutdown_event = Event()
|
|
890
904
|
|
|
891
905
|
async def watch_signals() -> None:
|
|
@@ -894,10 +908,13 @@ class AnyQuart(App):
|
|
|
894
908
|
shutdown_event.set()
|
|
895
909
|
return
|
|
896
910
|
|
|
897
|
-
config = self._build_config(host, port, debug, ca_certs, certfile, keyfile)
|
|
898
911
|
async with create_task_group() as tg:
|
|
899
912
|
tg.start_soon(watch_signals)
|
|
900
|
-
await serve(
|
|
913
|
+
await serve(
|
|
914
|
+
self, # type: ignore[arg-type]
|
|
915
|
+
config,
|
|
916
|
+
shutdown_trigger=shutdown_event.wait,
|
|
917
|
+
)
|
|
901
918
|
tg.cancel_scope.cancel()
|
|
902
919
|
|
|
903
920
|
def test_client(
|
|
@@ -1574,7 +1591,7 @@ class AnyQuart(App):
|
|
|
1574
1591
|
return await self.make_default_options_response()
|
|
1575
1592
|
|
|
1576
1593
|
handler = self.view_functions[request_.url_rule.endpoint]
|
|
1577
|
-
return await self
|
|
1594
|
+
return await invoke_with_di(self, handler, request_.view_args)
|
|
1578
1595
|
|
|
1579
1596
|
async def dispatch_websocket(
|
|
1580
1597
|
self, websocket_context: WebsocketContext | None = None
|