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.
Files changed (39) hide show
  1. {anyquart-0.1.0 → anyquart-0.2.0}/PKG-INFO +28 -11
  2. {anyquart-0.1.0 → anyquart-0.2.0}/README.md +27 -10
  3. {anyquart-0.1.0 → anyquart-0.2.0}/pyproject.toml +3 -10
  4. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/app.py +56 -76
  5. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/blueprints.py +0 -1
  6. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/cli.py +23 -8
  7. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/helpers.py +1 -29
  8. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/connections.py +7 -8
  9. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/utils.py +0 -76
  10. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/views.py +1 -1
  11. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/response.py +2 -5
  12. {anyquart-0.1.0 → anyquart-0.2.0}/LICENSE.txt +0 -0
  13. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/__init__.py +0 -0
  14. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/__main__.py +0 -0
  15. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/asgi.py +0 -0
  16. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/config.py +0 -0
  17. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/ctx.py +0 -0
  18. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/datastructures.py +0 -0
  19. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/debug.py +0 -0
  20. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/formparser.py +0 -0
  21. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/globals.py +0 -0
  22. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/json/__init__.py +0 -0
  23. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/json/provider.py +0 -0
  24. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/json/tag.py +0 -0
  25. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/logging.py +0 -0
  26. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/py.typed +0 -0
  27. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/routing.py +0 -0
  28. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/sessions.py +0 -0
  29. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/signals.py +0 -0
  30. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/templating.py +0 -0
  31. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/__init__.py +0 -0
  32. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/app.py +0 -0
  33. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/client.py +0 -0
  34. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/testing/utils.py +0 -0
  35. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/typing.py +0 -0
  36. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/__init__.py +0 -0
  37. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/base.py +0 -0
  38. {anyquart-0.1.0 → anyquart-0.2.0}/src/anyquart/wrappers/request.py +0 -0
  39. {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.1.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
- All credit for Quart goes to the [Pallets](https://palletsprojects.com) team.
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 does not natively support async test functions hence we need an async framework for async tests and fixtures.
83
- Quart uses pytest-asyncio, here we use 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`.
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
- All credit for Quart goes to the [Pallets](https://palletsprojects.com) team.
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 does not natively support async test functions hence we need an async framework for async tests and fixtures.
47
- Quart uses pytest-asyncio, here we use 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`.
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.1.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.10"
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 | None = None,
785
- port: int | None = None,
786
- debug: bool | None = None,
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. Hypercorn) directly in production")
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
- reload_ = False
839
- try:
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
- if reload_:
854
- restart()
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
- async def _run_serve(
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
- use_reloader: bool,
865
- ) -> None:
866
- shutdown_event = Event()
867
-
868
- async def watch_signals() -> None:
869
- with open_signal_receiver(signal.SIGINT, signal.SIGTERM) as signals:
870
- async for _ in signals:
871
- shutdown_event.set()
872
- return
873
-
874
- try:
875
- async with create_task_group() as tg:
876
- tg.start_soon(watch_signals)
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 run_task(
865
+ async def _run_serve(
893
866
  self,
894
867
  host: str = "127.0.0.1",
895
868
  port: int = 5000,
896
- debug: bool | None = None,
869
+ debug: bool = False,
897
870
  ca_certs: str | None = None,
898
871
  certfile: str | None = None,
899
872
  keyfile: str | None = None,
900
- shutdown_trigger: Callable[..., Awaitable[None]] | None = None,
901
- ) -> Coroutine[None, None, None]:
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
- config = HyperConfig()
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
- return serve(self, config, shutdown_trigger=shutdown_trigger)
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 anyquart 0.11 this did not run the synchronous code
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(self, func: Callable, *args: Any, **kwargs: Any) -> None:
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
- response = value.get_response() # type: ignore
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))
@@ -85,7 +85,6 @@ class Blueprint(SansioBlueprint):
85
85
  return int(value.total_seconds())
86
86
 
87
87
  return value
88
- return None
89
88
 
90
89
  async def send_static_file(self, filename: str) -> Response:
91
90
  if not self.has_static_folder:
@@ -648,14 +648,29 @@ def run_command(
648
648
  if reload is None:
649
649
  reload = debug
650
650
 
651
- app.run(
652
- debug=debug,
653
- host=host,
654
- port=port,
655
- certfile=certfile,
656
- keyfile=keyfile,
657
- use_reloader=reload,
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
- await self._tg_cm.__aexit__(exc_type, exc_value, tb)
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
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._client_send.aclose()
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.file_manager = await async_open(self.file_path, mode="rb")
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.file_manager.__aexit__(exc_type, exc_value, tb)
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