anyquart 0.1.0__tar.gz → 0.2.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.1.0 → anyquart-0.2.0}/PKG-INFO +28 -11
- {anyquart-0.1.0 → anyquart-0.2.0}/README.md +27 -10
- {anyquart-0.1.0 → anyquart-0.2.0}/pyproject.toml +3 -10
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/app.py +56 -76
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/blueprints.py +0 -1
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/cli.py +23 -8
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/helpers.py +1 -29
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/connections.py +7 -8
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/utils.py +0 -76
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/views.py +1 -1
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/response.py +2 -5
- {anyquart-0.1.0 → anyquart-0.2.0}/LICENSE.txt +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/__init__.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/__main__.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/asgi.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/config.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/ctx.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/datastructures.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/debug.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/formparser.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/globals.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/json/__init__.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/json/provider.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/json/tag.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/logging.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/py.typed +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/routing.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/sessions.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/signals.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/templating.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/__init__.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/app.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/client.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/utils.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/typing.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/__init__.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/base.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/request.py +0 -0
- {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anyquart
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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>
|
|
@@ -40,7 +40,16 @@ Quart runs on Asyncio and when you want to run it on Trio event loop you use [qu
|
|
|
40
40
|
|
|
41
41
|
AnyQuart is [Quart](https://github.com/pallets/quart) running on [AnyIO](https://github.com/agronholm/anyio). It is a fork of Quart 0.20.1.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Differences from Quart
|
|
46
|
+
`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.
|
|
47
|
+
|
|
48
|
+
- Works with both asyncio and Trio code via AnyIO, giving you structured concurrency out of the box.
|
|
49
|
+
- Uses [Anycorn](https://github.com/davidbrochart/anycorn) instead of Hypercorn as the development server.
|
|
50
|
+
- [aiofiles](https://github.com/Tinche/aiofiles) dropped, AnyIO's file I/O is used instead.
|
|
51
|
+
- [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) replaced by [AnyIO pytest plugin](https://anyio.readthedocs.io/en/stable/testing.html).
|
|
52
|
+
- Runs on Python 3.10+ .
|
|
44
53
|
|
|
45
54
|
## Usage
|
|
46
55
|
You will have to replace `quart` with `anyquart` and `Quart` with `AnyQuart`.
|
|
@@ -79,8 +88,8 @@ $ anyquart run
|
|
|
79
88
|
```
|
|
80
89
|
|
|
81
90
|
# Testing
|
|
82
|
-
Pytest
|
|
83
|
-
Quart uses pytest-asyncio,
|
|
91
|
+
Pytest requires a plugin to run asynchronous test functions and fixtures.
|
|
92
|
+
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`.
|
|
84
93
|
|
|
85
94
|
```python
|
|
86
95
|
import pytest
|
|
@@ -103,12 +112,20 @@ async def test_do_something(test_client) -> None:
|
|
|
103
112
|
|
|
104
113
|
```
|
|
105
114
|
|
|
106
|
-
## Differences from Quart
|
|
107
|
-
- Uses [Anycorn](https://github.com/davidbrochart/anycorn) instead of Hypercorn as the development server
|
|
108
|
-
- Works with both asyncio and Trio via AnyIO
|
|
109
|
-
- Uses AnyIO's file I/O instead of aiofiles
|
|
110
|
-
- AnyIO primitives work freely in route handlers, giving you structured concurrency out of the box
|
|
111
|
-
- Tests need `@pytest.mark.anyio` and the `anyio_backend` fixture instead of pytest-asyncio
|
|
112
|
-
|
|
113
115
|
Refer to the [Quart documentation](https://quart.palletsprojects.com) for more details.
|
|
114
116
|
|
|
117
|
+
## Contributing
|
|
118
|
+
Issues and Pull Requests are welcome.
|
|
119
|
+
|
|
120
|
+
## Contributors ✨
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary>See All Contributors</summary>
|
|
124
|
+
|
|
125
|
+
<div align="center">
|
|
126
|
+
<a href="https://github.com/EmmanuelNiyonshuti/anyquart/graphs/contributors?all=1">
|
|
127
|
+
<img src="https://contrib.rocks/image?repo=EmmanuelNiyonshuti/anyquart"/>
|
|
128
|
+
</a>
|
|
129
|
+
</div>
|
|
130
|
+
</details>
|
|
131
|
+
|
|
@@ -4,7 +4,16 @@ Quart runs on Asyncio and when you want to run it on Trio event loop you use [qu
|
|
|
4
4
|
|
|
5
5
|
AnyQuart is [Quart](https://github.com/pallets/quart) running on [AnyIO](https://github.com/agronholm/anyio). It is a fork of Quart 0.20.1.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Differences from Quart
|
|
10
|
+
`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.
|
|
11
|
+
|
|
12
|
+
- Works with both asyncio and Trio code via AnyIO, giving you structured concurrency out of the box.
|
|
13
|
+
- Uses [Anycorn](https://github.com/davidbrochart/anycorn) instead of Hypercorn as the development server.
|
|
14
|
+
- [aiofiles](https://github.com/Tinche/aiofiles) dropped, AnyIO's file I/O is used instead.
|
|
15
|
+
- [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) replaced by [AnyIO pytest plugin](https://anyio.readthedocs.io/en/stable/testing.html).
|
|
16
|
+
- Runs on Python 3.10+ .
|
|
8
17
|
|
|
9
18
|
## Usage
|
|
10
19
|
You will have to replace `quart` with `anyquart` and `Quart` with `AnyQuart`.
|
|
@@ -43,8 +52,8 @@ $ anyquart run
|
|
|
43
52
|
```
|
|
44
53
|
|
|
45
54
|
# Testing
|
|
46
|
-
Pytest
|
|
47
|
-
Quart uses pytest-asyncio,
|
|
55
|
+
Pytest requires a plugin to run asynchronous test functions and fixtures.
|
|
56
|
+
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`.
|
|
48
57
|
|
|
49
58
|
```python
|
|
50
59
|
import pytest
|
|
@@ -67,11 +76,19 @@ async def test_do_something(test_client) -> None:
|
|
|
67
76
|
|
|
68
77
|
```
|
|
69
78
|
|
|
70
|
-
## Differences from Quart
|
|
71
|
-
- Uses [Anycorn](https://github.com/davidbrochart/anycorn) instead of Hypercorn as the development server
|
|
72
|
-
- Works with both asyncio and Trio via AnyIO
|
|
73
|
-
- Uses AnyIO's file I/O instead of aiofiles
|
|
74
|
-
- AnyIO primitives work freely in route handlers, giving you structured concurrency out of the box
|
|
75
|
-
- Tests need `@pytest.mark.anyio` and the `anyio_backend` fixture instead of pytest-asyncio
|
|
76
|
-
|
|
77
79
|
Refer to the [Quart documentation](https://quart.palletsprojects.com) for more details.
|
|
80
|
+
|
|
81
|
+
## Contributing
|
|
82
|
+
Issues and Pull Requests are welcome.
|
|
83
|
+
|
|
84
|
+
## Contributors ✨
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary>See All Contributors</summary>
|
|
88
|
+
|
|
89
|
+
<div align="center">
|
|
90
|
+
<a href="https://github.com/EmmanuelNiyonshuti/anyquart/graphs/contributors?all=1">
|
|
91
|
+
<img src="https://contrib.rocks/image?repo=EmmanuelNiyonshuti/anyquart"/>
|
|
92
|
+
</a>
|
|
93
|
+
</div>
|
|
94
|
+
</details>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "anyquart"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "A fork of Quart that uses AnyIO"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -107,19 +107,12 @@ exclude_also = [
|
|
|
107
107
|
]
|
|
108
108
|
|
|
109
109
|
[tool.mypy]
|
|
110
|
-
python_version = "3.
|
|
111
|
-
files = ["src", "tests"]
|
|
112
|
-
show_error_codes = true
|
|
113
|
-
pretty = true
|
|
110
|
+
python_version = "3.14"
|
|
114
111
|
strict = true
|
|
115
|
-
# TODO fully satisfy strict mode and remove these customizations
|
|
116
|
-
allow_redefinition = true
|
|
117
112
|
disallow_any_generics = false
|
|
118
|
-
disallow_untyped_calls = false
|
|
119
|
-
implicit_reexport = true
|
|
120
|
-
no_implicit_optional = true
|
|
121
113
|
strict_optional = false
|
|
122
114
|
warn_return_any = false
|
|
115
|
+
files = ["src", "tests"]
|
|
123
116
|
|
|
124
117
|
[tool.pyright]
|
|
125
118
|
pythonVersion = "3.10"
|
|
@@ -9,7 +9,6 @@ from collections import defaultdict
|
|
|
9
9
|
from collections.abc import AsyncGenerator
|
|
10
10
|
from collections.abc import Awaitable
|
|
11
11
|
from collections.abc import Callable
|
|
12
|
-
from collections.abc import Coroutine
|
|
13
12
|
from contextlib import AbstractAsyncContextManager
|
|
14
13
|
from datetime import timedelta
|
|
15
14
|
from inspect import isasyncgen
|
|
@@ -25,6 +24,7 @@ from typing import ParamSpec
|
|
|
25
24
|
from typing import TypeVar
|
|
26
25
|
from urllib.parse import quote
|
|
27
26
|
|
|
27
|
+
import anyio
|
|
28
28
|
from anycorn import serve
|
|
29
29
|
from anycorn.config import Config as HyperConfig
|
|
30
30
|
from anycorn.typing import ASGIReceiveCallable
|
|
@@ -37,8 +37,6 @@ from anyio import Event
|
|
|
37
37
|
from anyio import Lock
|
|
38
38
|
from anyio import open_file as async_open
|
|
39
39
|
from anyio import open_signal_receiver
|
|
40
|
-
from anyio import run
|
|
41
|
-
from anyio import sleep
|
|
42
40
|
from anyio.abc import TaskGroup
|
|
43
41
|
from flask.sansio.app import App
|
|
44
42
|
from flask.sansio.scaffold import setupmethod
|
|
@@ -123,9 +121,6 @@ from .typing import TestClientProtocol
|
|
|
123
121
|
from .typing import WebsocketCallable
|
|
124
122
|
from .typing import WhileServingCallable
|
|
125
123
|
from .utils import file_path_to_path
|
|
126
|
-
from .utils import MustReloadError
|
|
127
|
-
from .utils import observe_changes
|
|
128
|
-
from .utils import restart
|
|
129
124
|
from .utils import run_sync
|
|
130
125
|
from .wrappers import BaseRequestWebsocket
|
|
131
126
|
from .wrappers import Request
|
|
@@ -384,7 +379,6 @@ class AnyQuart(App):
|
|
|
384
379
|
return int(value.total_seconds())
|
|
385
380
|
|
|
386
381
|
return value
|
|
387
|
-
return None
|
|
388
382
|
|
|
389
383
|
async def send_static_file(self, filename: str) -> Response:
|
|
390
384
|
if not self.has_static_folder:
|
|
@@ -781,13 +775,14 @@ class AnyQuart(App):
|
|
|
781
775
|
|
|
782
776
|
def run(
|
|
783
777
|
self,
|
|
784
|
-
host: str
|
|
785
|
-
port: int
|
|
786
|
-
debug: bool
|
|
787
|
-
use_reloader: bool = True,
|
|
778
|
+
host: str = "127.0.0.1",
|
|
779
|
+
port: int = 5000,
|
|
780
|
+
debug: bool = False,
|
|
788
781
|
ca_certs: str | None = None,
|
|
789
782
|
certfile: str | None = None,
|
|
790
783
|
keyfile: str | None = None,
|
|
784
|
+
use_reloader: bool | None = False,
|
|
785
|
+
app_import_path: str | None = None,
|
|
791
786
|
**kwargs: Any,
|
|
792
787
|
) -> None:
|
|
793
788
|
"""Run this application.
|
|
@@ -819,7 +814,6 @@ class AnyQuart(App):
|
|
|
819
814
|
if debug is not None:
|
|
820
815
|
self.debug = debug
|
|
821
816
|
|
|
822
|
-
scheme = "https" if certfile is not None and keyfile is not None else "http"
|
|
823
817
|
if host is None:
|
|
824
818
|
server_name = self.config.get("SERVER_NAME")
|
|
825
819
|
host = (
|
|
@@ -832,28 +826,21 @@ class AnyQuart(App):
|
|
|
832
826
|
|
|
833
827
|
print(f" * Serving AnyQuart app '{self.name}'")
|
|
834
828
|
print(f" * Debug mode: {self.debug or False}")
|
|
835
|
-
print(" * Please use an ASGI server (e.g.
|
|
829
|
+
print(" * Please use an ASGI server (e.g. Anycorn) directly in production")
|
|
830
|
+
scheme = "https" if certfile is not None and keyfile is not None else "http"
|
|
836
831
|
print(f" * Running on {scheme}://{host}:{port} (CTRL + C to quit)")
|
|
837
832
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
run(
|
|
841
|
-
self._run_serve,
|
|
842
|
-
host,
|
|
843
|
-
port,
|
|
844
|
-
debug,
|
|
845
|
-
ca_certs,
|
|
846
|
-
certfile,
|
|
847
|
-
keyfile,
|
|
848
|
-
use_reloader,
|
|
849
|
-
)
|
|
850
|
-
except MustReloadError:
|
|
851
|
-
reload_ = True
|
|
833
|
+
if use_reloader:
|
|
834
|
+
from anycorn.run import run as anycorn_run
|
|
852
835
|
|
|
853
|
-
|
|
854
|
-
|
|
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)
|
|
855
842
|
|
|
856
|
-
|
|
843
|
+
def _build_config(
|
|
857
844
|
self,
|
|
858
845
|
host: str,
|
|
859
846
|
port: int,
|
|
@@ -861,45 +848,30 @@ class AnyQuart(App):
|
|
|
861
848
|
ca_certs: str | None,
|
|
862
849
|
certfile: str | None,
|
|
863
850
|
keyfile: str | None,
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
tg.start_soon(
|
|
878
|
-
self.run_task,
|
|
879
|
-
host,
|
|
880
|
-
port,
|
|
881
|
-
debug,
|
|
882
|
-
ca_certs,
|
|
883
|
-
certfile,
|
|
884
|
-
keyfile,
|
|
885
|
-
shutdown_event.wait,
|
|
886
|
-
)
|
|
887
|
-
if use_reloader:
|
|
888
|
-
tg.start_soon(observe_changes, sleep, shutdown_event)
|
|
889
|
-
except MustReloadError:
|
|
890
|
-
raise
|
|
851
|
+
) -> HyperConfig:
|
|
852
|
+
config = HyperConfig()
|
|
853
|
+
config.access_log_format = "%(h)s %(r)s %(s)s %(b)s %(D)s"
|
|
854
|
+
config.accesslog = "-"
|
|
855
|
+
config.bind = [f"{host}:{port}"]
|
|
856
|
+
config.ca_certs = ca_certs
|
|
857
|
+
config.certfile = certfile
|
|
858
|
+
if debug is not None:
|
|
859
|
+
self.debug = debug
|
|
860
|
+
config.debug = debug
|
|
861
|
+
config.errorlog = config.accesslog
|
|
862
|
+
config.keyfile = keyfile
|
|
863
|
+
return config
|
|
891
864
|
|
|
892
|
-
def
|
|
865
|
+
async def _run_serve(
|
|
893
866
|
self,
|
|
894
867
|
host: str = "127.0.0.1",
|
|
895
868
|
port: int = 5000,
|
|
896
|
-
debug: bool
|
|
869
|
+
debug: bool = False,
|
|
897
870
|
ca_certs: str | None = None,
|
|
898
871
|
certfile: str | None = None,
|
|
899
872
|
keyfile: str | None = None,
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
"""Return a task that when awaited runs this application.
|
|
873
|
+
) -> None:
|
|
874
|
+
"""runs this application.
|
|
903
875
|
|
|
904
876
|
This is best used for development only, see Hypercorn for
|
|
905
877
|
production servers.
|
|
@@ -914,18 +886,19 @@ class AnyQuart(App):
|
|
|
914
886
|
keyfile: Path to the SSL key file.
|
|
915
887
|
|
|
916
888
|
"""
|
|
917
|
-
|
|
918
|
-
config.access_log_format = "%(h)s %(r)s %(s)s %(b)s %(D)s"
|
|
919
|
-
config.accesslog = "-"
|
|
920
|
-
config.bind = [f"{host}:{port}"]
|
|
921
|
-
config.ca_certs = ca_certs
|
|
922
|
-
config.certfile = certfile
|
|
923
|
-
if debug is not None:
|
|
924
|
-
self.debug = debug
|
|
925
|
-
config.errorlog = config.accesslog
|
|
926
|
-
config.keyfile = keyfile
|
|
889
|
+
shutdown_event = Event()
|
|
927
890
|
|
|
928
|
-
|
|
891
|
+
async def watch_signals() -> None:
|
|
892
|
+
with open_signal_receiver(signal.SIGINT, signal.SIGTERM) as signals:
|
|
893
|
+
async for _ in signals:
|
|
894
|
+
shutdown_event.set()
|
|
895
|
+
return
|
|
896
|
+
|
|
897
|
+
config = self._build_config(host, port, debug, ca_certs, certfile, keyfile)
|
|
898
|
+
async with create_task_group() as tg:
|
|
899
|
+
tg.start_soon(watch_signals)
|
|
900
|
+
await serve(self, config, shutdown_trigger=shutdown_event.wait)
|
|
901
|
+
tg.cancel_scope.cancel()
|
|
929
902
|
|
|
930
903
|
def test_client(
|
|
931
904
|
self, use_cookies: bool = True, **kwargs: Any
|
|
@@ -1170,7 +1143,7 @@ class AnyQuart(App):
|
|
|
1170
1143
|
.. versionadded:: 0.11
|
|
1171
1144
|
|
|
1172
1145
|
Override if you wish to change how synchronous functions are
|
|
1173
|
-
run. Before
|
|
1146
|
+
run. Before quart 0.11 this did not run the synchronous code
|
|
1174
1147
|
in an executor.
|
|
1175
1148
|
"""
|
|
1176
1149
|
if iscoroutinefunction(func):
|
|
@@ -1367,7 +1340,12 @@ class AnyQuart(App):
|
|
|
1367
1340
|
request.body.set_result(request_body)
|
|
1368
1341
|
return self.request_context(request)
|
|
1369
1342
|
|
|
1370
|
-
def add_background_task(
|
|
1343
|
+
def add_background_task(
|
|
1344
|
+
self,
|
|
1345
|
+
func: Callable[P, Awaitable[T]],
|
|
1346
|
+
*args: P.args,
|
|
1347
|
+
**kwargs: P.kwargs,
|
|
1348
|
+
) -> None:
|
|
1371
1349
|
if self.background_tasks_tg is None:
|
|
1372
1350
|
raise RuntimeError(
|
|
1373
1351
|
"Can not add background tasks before the app has started."
|
|
@@ -1436,7 +1414,9 @@ class AnyQuart(App):
|
|
|
1436
1414
|
|
|
1437
1415
|
response: ResponseTypes
|
|
1438
1416
|
if isinstance(value, HTTPException):
|
|
1439
|
-
|
|
1417
|
+
werkzeug_response = value.get_response()
|
|
1418
|
+
assert isinstance(werkzeug_response, (Response, WerkzeugResponse))
|
|
1419
|
+
response = werkzeug_response
|
|
1440
1420
|
elif not isinstance(value, (Response, WerkzeugResponse)):
|
|
1441
1421
|
if (
|
|
1442
1422
|
isinstance(value, (str, bytes, bytearray))
|
|
@@ -648,14 +648,29 @@ def run_command(
|
|
|
648
648
|
if reload is None:
|
|
649
649
|
reload = debug
|
|
650
650
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
651
|
+
if reload:
|
|
652
|
+
"""
|
|
653
|
+
Delegating Development server reloading to Anycorn.
|
|
654
|
+
We need to provide the application path to Anycorn Config.
|
|
655
|
+
"""
|
|
656
|
+
app.run(
|
|
657
|
+
debug=debug,
|
|
658
|
+
host=host,
|
|
659
|
+
port=port,
|
|
660
|
+
certfile=certfile,
|
|
661
|
+
keyfile=keyfile,
|
|
662
|
+
use_reloader=reload,
|
|
663
|
+
app_import_path=info.app_import_path,
|
|
664
|
+
)
|
|
665
|
+
else:
|
|
666
|
+
app.run(
|
|
667
|
+
debug=debug,
|
|
668
|
+
host=host,
|
|
669
|
+
port=port,
|
|
670
|
+
certfile=certfile,
|
|
671
|
+
keyfile=keyfile,
|
|
672
|
+
use_reloader=False,
|
|
673
|
+
)
|
|
659
674
|
|
|
660
675
|
|
|
661
676
|
@click.command("shell", short_help="Run a shell in the app context.")
|
|
@@ -2,8 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import mimetypes
|
|
4
4
|
import os
|
|
5
|
-
import pkgutil
|
|
6
|
-
import sys
|
|
7
5
|
from collections.abc import Callable
|
|
8
6
|
from collections.abc import Iterable
|
|
9
7
|
from datetime import datetime
|
|
@@ -21,8 +19,8 @@ from zlib import adler32
|
|
|
21
19
|
from flask.helpers import get_root_path as get_root_path # noqa: F401
|
|
22
20
|
from werkzeug.exceptions import abort as werkzeug_abort
|
|
23
21
|
from werkzeug.exceptions import NotFound
|
|
22
|
+
from werkzeug.security import safe_join
|
|
24
23
|
from werkzeug.utils import redirect as werkzeug_redirect
|
|
25
|
-
from werkzeug.utils import safe_join
|
|
26
24
|
from werkzeug.wrappers import Response as WerkzeugResponse
|
|
27
25
|
|
|
28
26
|
from .globals import _cv_request
|
|
@@ -232,32 +230,6 @@ def stream_with_context(func: Callable) -> Callable:
|
|
|
232
230
|
return generator
|
|
233
231
|
|
|
234
232
|
|
|
235
|
-
def find_package(name: str) -> tuple[Path | None, Path]:
|
|
236
|
-
"""Finds packages install prefix (or None) and it's containing Folder"""
|
|
237
|
-
module = name.split(".")[0]
|
|
238
|
-
loader = pkgutil.get_loader(module)
|
|
239
|
-
if name == "__main__" or loader is None:
|
|
240
|
-
package_path = Path.cwd()
|
|
241
|
-
else:
|
|
242
|
-
if hasattr(loader, "get_filename"):
|
|
243
|
-
filename = loader.get_filename(module)
|
|
244
|
-
else:
|
|
245
|
-
__import__(name)
|
|
246
|
-
filename = sys.modules[name].__file__
|
|
247
|
-
package_path = Path(filename).resolve().parent
|
|
248
|
-
if hasattr(loader, "is_package"):
|
|
249
|
-
is_package = loader.is_package(module)
|
|
250
|
-
if is_package:
|
|
251
|
-
package_path = Path(package_path).resolve().parent
|
|
252
|
-
sys_prefix = Path(sys.prefix).resolve()
|
|
253
|
-
try:
|
|
254
|
-
package_path.relative_to(sys_prefix)
|
|
255
|
-
except ValueError:
|
|
256
|
-
return None, package_path
|
|
257
|
-
else:
|
|
258
|
-
return sys_prefix, package_path
|
|
259
|
-
|
|
260
|
-
|
|
261
233
|
async def send_from_directory(
|
|
262
234
|
directory: FilePath,
|
|
263
235
|
file_name: str,
|
|
@@ -92,16 +92,15 @@ class TestHTTPConnection:
|
|
|
92
92
|
if exc_type is not None:
|
|
93
93
|
await self.disconnect()
|
|
94
94
|
try:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
with self._client_receive, self._client_send:
|
|
96
|
+
async for data in self._client_receive:
|
|
97
|
+
if isinstance(data, bytes):
|
|
98
|
+
self.response_data.extend(data)
|
|
99
|
+
elif not isinstance(data, HTTPDisconnectError):
|
|
100
|
+
raise data
|
|
101
101
|
finally:
|
|
102
|
-
await self._client_receive.aclose()
|
|
103
102
|
await self._server_receive.aclose()
|
|
104
|
-
await self.
|
|
103
|
+
await self._tg_cm.__aexit__(exc_type, exc_value, tb)
|
|
105
104
|
|
|
106
105
|
async def as_response(self) -> Response:
|
|
107
106
|
return self.app.response_class(
|
|
@@ -2,10 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import os
|
|
5
|
-
import platform
|
|
6
|
-
import sys
|
|
7
5
|
from collections.abc import AsyncIterator
|
|
8
|
-
from collections.abc import Awaitable
|
|
9
6
|
from collections.abc import Callable
|
|
10
7
|
from collections.abc import Coroutine
|
|
11
8
|
from collections.abc import Iterable
|
|
@@ -20,17 +17,12 @@ from typing import TypeVar
|
|
|
20
17
|
import anyio
|
|
21
18
|
from werkzeug.datastructures import Headers
|
|
22
19
|
|
|
23
|
-
from .typing import Event
|
|
24
20
|
from .typing import FilePath
|
|
25
21
|
|
|
26
22
|
if TYPE_CHECKING:
|
|
27
23
|
from .wrappers.response import Response # noqa: F401
|
|
28
24
|
|
|
29
25
|
|
|
30
|
-
class MustReloadError(Exception):
|
|
31
|
-
pass
|
|
32
|
-
|
|
33
|
-
|
|
34
26
|
def file_path_to_path(*paths: FilePath) -> Path:
|
|
35
27
|
# Flask supports bytes paths
|
|
36
28
|
safe_paths: list[str | os.PathLike] = []
|
|
@@ -96,71 +88,3 @@ def encode_headers(headers: Headers) -> list[tuple[bytes, bytes]]:
|
|
|
96
88
|
|
|
97
89
|
def decode_headers(headers: Iterable[tuple[bytes, bytes]]) -> Headers:
|
|
98
90
|
return Headers([(key.decode(), value.decode()) for key, value in headers])
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
async def observe_changes(
|
|
102
|
-
sleep: Callable[[float], Awaitable[Any]], shutdown_event: Event
|
|
103
|
-
) -> None:
|
|
104
|
-
last_updates: dict[Path, float] = {}
|
|
105
|
-
for module in list(sys.modules.values()):
|
|
106
|
-
filename = getattr(module, "__file__", None)
|
|
107
|
-
if filename is None:
|
|
108
|
-
continue
|
|
109
|
-
path = Path(filename)
|
|
110
|
-
try:
|
|
111
|
-
last_updates[Path(filename)] = path.stat().st_mtime
|
|
112
|
-
except (FileNotFoundError, NotADirectoryError):
|
|
113
|
-
pass
|
|
114
|
-
|
|
115
|
-
while not shutdown_event.is_set():
|
|
116
|
-
await sleep(1)
|
|
117
|
-
|
|
118
|
-
for index, (path, last_mtime) in enumerate(last_updates.items()):
|
|
119
|
-
if index % 10 == 0:
|
|
120
|
-
# Yield to the event loop
|
|
121
|
-
await sleep(0)
|
|
122
|
-
|
|
123
|
-
try:
|
|
124
|
-
mtime = path.stat().st_mtime
|
|
125
|
-
except FileNotFoundError as e:
|
|
126
|
-
# File deleted
|
|
127
|
-
raise MustReloadError() from e
|
|
128
|
-
else:
|
|
129
|
-
if mtime > last_mtime:
|
|
130
|
-
raise MustReloadError()
|
|
131
|
-
else:
|
|
132
|
-
last_updates[path] = mtime
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def restart() -> None:
|
|
136
|
-
# Restart this process (only safe for dev/debug)
|
|
137
|
-
executable = sys.executable
|
|
138
|
-
script_path = Path(sys.argv[0]).resolve()
|
|
139
|
-
args = sys.argv[1:]
|
|
140
|
-
main_package = sys.modules["__main__"].__package__
|
|
141
|
-
|
|
142
|
-
if main_package is None:
|
|
143
|
-
# Executed by filename
|
|
144
|
-
if platform.system() == "Windows":
|
|
145
|
-
if not script_path.exists() and script_path.with_suffix(".exe").exists():
|
|
146
|
-
# anyquart run
|
|
147
|
-
executable = str(script_path.with_suffix(".exe"))
|
|
148
|
-
else:
|
|
149
|
-
# python run.py
|
|
150
|
-
args = [str(script_path), *args]
|
|
151
|
-
else:
|
|
152
|
-
if script_path.is_file() and os.access(script_path, os.X_OK):
|
|
153
|
-
# anycorn run:app --reload
|
|
154
|
-
executable = str(script_path)
|
|
155
|
-
else:
|
|
156
|
-
# python run.py
|
|
157
|
-
args = [str(script_path), *args]
|
|
158
|
-
else:
|
|
159
|
-
# Executed as a module e.g. python -m run
|
|
160
|
-
module = script_path.stem
|
|
161
|
-
import_name = main_package
|
|
162
|
-
if module != "__main__":
|
|
163
|
-
import_name = f"{main_package}.{module}"
|
|
164
|
-
args[:0] = ["-m", import_name.lstrip(".")]
|
|
165
|
-
|
|
166
|
-
os.execv(executable, [executable] + args)
|
|
@@ -72,7 +72,7 @@ class View:
|
|
|
72
72
|
self = cls(*class_args, **class_kwargs)
|
|
73
73
|
|
|
74
74
|
async def view(**kwargs: Any) -> ResponseReturnValue:
|
|
75
|
-
return await current_app.ensure_async(self.dispatch_request)(**kwargs)
|
|
75
|
+
return await current_app.ensure_async(self.dispatch_request)(**kwargs) # type: ignore
|
|
76
76
|
|
|
77
77
|
if cls.decorators:
|
|
78
78
|
view.__name__ = name
|
|
@@ -6,7 +6,6 @@ from collections.abc import AsyncGenerator
|
|
|
6
6
|
from collections.abc import AsyncIterable
|
|
7
7
|
from collections.abc import AsyncIterator
|
|
8
8
|
from collections.abc import Iterable
|
|
9
|
-
from contextlib import AbstractAsyncContextManager
|
|
10
9
|
from hashlib import md5
|
|
11
10
|
from io import BytesIO
|
|
12
11
|
from os import PathLike
|
|
@@ -145,18 +144,16 @@ class FileBody(ResponseBody):
|
|
|
145
144
|
if buffer_size is not None:
|
|
146
145
|
self.buffer_size = buffer_size
|
|
147
146
|
self.file: AsyncFile[bytes] | None = None
|
|
148
|
-
self.file_manager: AbstractAsyncContextManager | None = None
|
|
149
147
|
|
|
150
148
|
async def __aenter__(self) -> FileBody:
|
|
151
|
-
self.
|
|
152
|
-
self.file = await self.file_manager.__aenter__()
|
|
149
|
+
self.file = await async_open(self.file_path, mode="rb")
|
|
153
150
|
await self.file.seek(self.begin)
|
|
154
151
|
return self
|
|
155
152
|
|
|
156
153
|
async def __aexit__(
|
|
157
154
|
self, exc_type: type, exc_value: BaseException, tb: TracebackType
|
|
158
155
|
) -> None:
|
|
159
|
-
await self.
|
|
156
|
+
await self.file.aclose()
|
|
160
157
|
|
|
161
158
|
def __aiter__(self) -> FileBody:
|
|
162
159
|
return self
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|