microdot 2.6.2__tar.gz → 2.7.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 (123) hide show
  1. {microdot-2.6.2/src/microdot.egg-info → microdot-2.7.0}/PKG-INFO +31 -11
  2. {microdot-2.6.2 → microdot-2.7.0}/README.md +24 -8
  3. {microdot-2.6.2 → microdot-2.7.0}/pyproject.toml +7 -3
  4. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/__init__.py +1 -1
  5. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/asgi.pyi +10 -10
  6. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/auth.pyi +9 -9
  7. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/cors.py +7 -6
  8. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/cors.pyi +4 -4
  9. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/csrf.py +5 -0
  10. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/csrf.pyi +3 -3
  11. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/jinja.pyi +5 -5
  12. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/login.pyi +6 -6
  13. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/microdot.py +32 -6
  14. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/microdot.pyi +66 -63
  15. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/multipart.pyi +8 -8
  16. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/session.py +3 -0
  17. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/session.pyi +10 -11
  18. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/sse.pyi +2 -2
  19. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/test_client.py +15 -1
  20. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/test_client.pyi +15 -12
  21. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/utemplate.pyi +5 -5
  22. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/websocket.pyi +5 -5
  23. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/wsgi.pyi +4 -4
  24. {microdot-2.6.2 → microdot-2.7.0/src/microdot.egg-info}/PKG-INFO +31 -11
  25. {microdot-2.6.2 → microdot-2.7.0}/tests/test_cors.py +7 -1
  26. {microdot-2.6.2 → microdot-2.7.0}/tests/test_csrf.py +6 -0
  27. {microdot-2.6.2 → microdot-2.7.0}/tests/test_microdot.py +70 -0
  28. {microdot-2.6.2 → microdot-2.7.0}/tests/test_session.py +20 -0
  29. {microdot-2.6.2 → microdot-2.7.0}/tox.ini +26 -3
  30. {microdot-2.6.2 → microdot-2.7.0}/LICENSE +0 -0
  31. {microdot-2.6.2 → microdot-2.7.0}/MANIFEST.in +0 -0
  32. {microdot-2.6.2 → microdot-2.7.0}/docs/Makefile +0 -0
  33. {microdot-2.6.2 → microdot-2.7.0}/docs/_static/css/custom.css +0 -0
  34. {microdot-2.6.2 → microdot-2.7.0}/docs/api/asgi.rst +0 -0
  35. {microdot-2.6.2 → microdot-2.7.0}/docs/api/auth.rst +0 -0
  36. {microdot-2.6.2 → microdot-2.7.0}/docs/api/cors.rst +0 -0
  37. {microdot-2.6.2 → microdot-2.7.0}/docs/api/csrf.rst +0 -0
  38. {microdot-2.6.2 → microdot-2.7.0}/docs/api/index.rst +0 -0
  39. {microdot-2.6.2 → microdot-2.7.0}/docs/api/jinja.rst +0 -0
  40. {microdot-2.6.2 → microdot-2.7.0}/docs/api/login.rst +0 -0
  41. {microdot-2.6.2 → microdot-2.7.0}/docs/api/microdot.rst +0 -0
  42. {microdot-2.6.2 → microdot-2.7.0}/docs/api/multipart.rst +0 -0
  43. {microdot-2.6.2 → microdot-2.7.0}/docs/api/sessions.rst +0 -0
  44. {microdot-2.6.2 → microdot-2.7.0}/docs/api/sse.rst +0 -0
  45. {microdot-2.6.2 → microdot-2.7.0}/docs/api/test_client.rst +0 -0
  46. {microdot-2.6.2 → microdot-2.7.0}/docs/api/utemplate.rst +0 -0
  47. {microdot-2.6.2 → microdot-2.7.0}/docs/api/websocket.rst +0 -0
  48. {microdot-2.6.2 → microdot-2.7.0}/docs/api/wsgi.rst +0 -0
  49. {microdot-2.6.2 → microdot-2.7.0}/docs/conf.py +0 -0
  50. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/auth.rst +0 -0
  51. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/cors.rst +0 -0
  52. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/csrf.rst +0 -0
  53. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/index.rst +0 -0
  54. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/login.rst +0 -0
  55. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/multipart.rst +0 -0
  56. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/production.rst +0 -0
  57. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/sessions.rst +0 -0
  58. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/sse.rst +0 -0
  59. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/templates.rst +0 -0
  60. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/test_client.rst +0 -0
  61. {microdot-2.6.2 → microdot-2.7.0}/docs/extensions/websocket.rst +0 -0
  62. {microdot-2.6.2 → microdot-2.7.0}/docs/implementation/freezing.rst +0 -0
  63. {microdot-2.6.2 → microdot-2.7.0}/docs/implementation/index.rst +0 -0
  64. {microdot-2.6.2 → microdot-2.7.0}/docs/implementation/migrating.rst +0 -0
  65. {microdot-2.6.2 → microdot-2.7.0}/docs/index.rst +0 -0
  66. {microdot-2.6.2 → microdot-2.7.0}/docs/intro.rst +0 -0
  67. {microdot-2.6.2 → microdot-2.7.0}/docs/make.bat +0 -0
  68. {microdot-2.6.2 → microdot-2.7.0}/docs/users-guide/concurrency.rst +0 -0
  69. {microdot-2.6.2 → microdot-2.7.0}/docs/users-guide/defining-routes.rst +0 -0
  70. {microdot-2.6.2 → microdot-2.7.0}/docs/users-guide/index.rst +0 -0
  71. {microdot-2.6.2 → microdot-2.7.0}/docs/users-guide/intro.rst +0 -0
  72. {microdot-2.6.2 → microdot-2.7.0}/docs/users-guide/request-object.rst +0 -0
  73. {microdot-2.6.2 → microdot-2.7.0}/docs/users-guide/responses.rst +0 -0
  74. {microdot-2.6.2 → microdot-2.7.0}/setup.cfg +0 -0
  75. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/__init__.pyi +0 -0
  76. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/asgi.py +0 -0
  77. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/auth.py +0 -0
  78. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/helpers.py +0 -0
  79. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/helpers.pyi +0 -0
  80. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/jinja.py +0 -0
  81. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/login.py +0 -0
  82. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/multipart.py +0 -0
  83. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/py.typed +0 -0
  84. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/sse.py +0 -0
  85. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/utemplate.py +0 -0
  86. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/websocket.py +0 -0
  87. {microdot-2.6.2 → microdot-2.7.0}/src/microdot/wsgi.py +0 -0
  88. {microdot-2.6.2 → microdot-2.7.0}/src/microdot.egg-info/SOURCES.txt +0 -0
  89. {microdot-2.6.2 → microdot-2.7.0}/src/microdot.egg-info/dependency_links.txt +0 -0
  90. {microdot-2.6.2 → microdot-2.7.0}/src/microdot.egg-info/not-zip-safe +0 -0
  91. {microdot-2.6.2 → microdot-2.7.0}/src/microdot.egg-info/requires.txt +0 -0
  92. {microdot-2.6.2 → microdot-2.7.0}/src/microdot.egg-info/top_level.txt +0 -0
  93. {microdot-2.6.2 → microdot-2.7.0}/tests/__init__.py +0 -0
  94. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.bin +0 -0
  95. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.css +0 -0
  96. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.gif +0 -0
  97. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.gz +0 -0
  98. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.html +0 -0
  99. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.jpg +0 -0
  100. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.js +0 -0
  101. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.json +0 -0
  102. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.png +0 -0
  103. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.txt +0 -0
  104. {microdot-2.6.2 → microdot-2.7.0}/tests/files/test.txt.gz +0 -0
  105. {microdot-2.6.2 → microdot-2.7.0}/tests/mock_socket.py +0 -0
  106. {microdot-2.6.2 → microdot-2.7.0}/tests/templates/hello.jinja.txt +0 -0
  107. {microdot-2.6.2 → microdot-2.7.0}/tests/templates/hello.utemplate.txt +0 -0
  108. {microdot-2.6.2 → microdot-2.7.0}/tests/templates/hello_utemplate_txt.py +0 -0
  109. {microdot-2.6.2 → microdot-2.7.0}/tests/test_asgi.py +0 -0
  110. {microdot-2.6.2 → microdot-2.7.0}/tests/test_auth.py +0 -0
  111. {microdot-2.6.2 → microdot-2.7.0}/tests/test_end2end.py +0 -0
  112. {microdot-2.6.2 → microdot-2.7.0}/tests/test_jinja.py +0 -0
  113. {microdot-2.6.2 → microdot-2.7.0}/tests/test_login.py +0 -0
  114. {microdot-2.6.2 → microdot-2.7.0}/tests/test_multidict.py +0 -0
  115. {microdot-2.6.2 → microdot-2.7.0}/tests/test_multipart.py +0 -0
  116. {microdot-2.6.2 → microdot-2.7.0}/tests/test_request.py +0 -0
  117. {microdot-2.6.2 → microdot-2.7.0}/tests/test_response.py +0 -0
  118. {microdot-2.6.2 → microdot-2.7.0}/tests/test_sse.py +0 -0
  119. {microdot-2.6.2 → microdot-2.7.0}/tests/test_url_pattern.py +0 -0
  120. {microdot-2.6.2 → microdot-2.7.0}/tests/test_urlencode.py +0 -0
  121. {microdot-2.6.2 → microdot-2.7.0}/tests/test_utemplate.py +0 -0
  122. {microdot-2.6.2 → microdot-2.7.0}/tests/test_websocket.py +0 -0
  123. {microdot-2.6.2 → microdot-2.7.0}/tests/test_wsgi.py +0 -0
@@ -1,10 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microdot
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: The impossibly small web framework for MicroPython
5
5
  Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
6
- Project-URL: Homepage, https://github.com/miguelgrinberg/microdot
7
- Project-URL: Bug Tracker, https://github.com/miguelgrinberg/microdot/issues
6
+ Project-URL: Homepage, https://code.miguelgrinberg.com/miguelgrinberg/microdot
7
+ Project-URL: Documentation, https://microdot.readthedocs.io/
8
+ Project-URL: Source Code, https://code.miguelgrinberg.com/miguelgrinberg/microdot
9
+ Project-URL: Issue Tracker, https://code.miguelgrinberg.com/miguelgrinberg/microdot/issues
10
+ Project-URL: Changelog, https://code.miguelgrinberg.com/miguelgrinberg/microdot/src/branch/main/CHANGES.md
11
+ Project-URL: Funding, https://code.miguelgrinberg.com/miguelgrinberg/microdot#sponsor-this-project
8
12
  Classifier: Environment :: Web Environment
9
13
  Classifier: Intended Audience :: Developers
10
14
  Classifier: Programming Language :: Python :: 3
@@ -22,8 +26,9 @@ Requires-Dist: furo; extra == "docs"
22
26
  Requires-Dist: pyjwt; extra == "docs"
23
27
  Dynamic: license-file
24
28
 
25
- # microdot
26
- [![Build status](https://github.com/miguelgrinberg/microdot/workflows/build/badge.svg)](https://github.com/miguelgrinberg/microdot/actions) [![codecov](https://codecov.io/gh/miguelgrinberg/microdot/branch/main/graph/badge.svg)](https://codecov.io/gh/miguelgrinberg/microdot)
29
+ # Microdot
30
+
31
+ [![tests](https://code.miguelgrinberg.com/miguelgrinberg/microdot/badges/workflows/tests.yml/badge.svg)](https://code.miguelgrinberg.com/miguelgrinberg/microdot/actions)
27
32
 
28
33
  *“The impossibly small web framework for Python and MicroPython”*
29
34
 
@@ -45,14 +50,16 @@ app.run()
45
50
 
46
51
  ## Resources
47
52
 
48
- - [Change Log](https://github.com/miguelgrinberg/microdot/blob/main/CHANGES.md)
49
- - Documentation
50
- - [Latest](https://microdot.readthedocs.io/en/latest/)
51
- - [Stable (v2)](https://microdot.readthedocs.io/en/stable/)
53
+ - [git](https://code.miguelgrinberg.com/miguelgrinberg/microdot)
54
+ - [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/microdot/src/branch/main/CHANGES.md)
55
+ - [Documentation](https://microdot.readthedocs.io/)
56
+ - [PyPI](https://pypi.python.org/pypi/microdot)
57
+ - [Contributor's guide](CONTRIBUTING.md)
58
+ - [Security policy](SECURITY.md)
52
59
 
53
- The following links are for version 1 of Microdot:
60
+ The following links are for legacy version 1 of Microdot:
54
61
 
55
- - [Code](https://github.com/miguelgrinberg/microdot/tree/v1)
62
+ - [git](https://github.com/miguelgrinberg/microdot/tree/v1)
56
63
  - [Documentation](https://microdot.readthedocs.io/en/v1/)
57
64
 
58
65
  Note that version 1 is no longer maintained, so you should consider migrating
@@ -73,3 +80,16 @@ MicroPython and CPython:
73
80
  - OpenAPI integration, similar to [APIFairy](https://github.com/miguelgrinberg/apifairy) for Flask
74
81
 
75
82
  Do you have other ideas to propose? Let's [discuss them](https://github.com/:miguelgrinberg/microdot/discussions/new?category=ideas)!
83
+
84
+ Sponsor this project
85
+ --------------------
86
+
87
+ This project relies on contributions from its users. If you benefit from it please consider making a single or ongoing monetary contribution in one of the following platforms:
88
+
89
+ - [Github Sponsors](https://github.com/sponsors/miguelgrinberg)
90
+ - [Patreon](https://patreon.com/miguelgrinberg)
91
+ - [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg)
92
+ - [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg)
93
+ - [PayPal](https://paypal.me/miguelgrinberg)
94
+
95
+ Thank you!
@@ -1,5 +1,6 @@
1
- # microdot
2
- [![Build status](https://github.com/miguelgrinberg/microdot/workflows/build/badge.svg)](https://github.com/miguelgrinberg/microdot/actions) [![codecov](https://codecov.io/gh/miguelgrinberg/microdot/branch/main/graph/badge.svg)](https://codecov.io/gh/miguelgrinberg/microdot)
1
+ # Microdot
2
+
3
+ [![tests](https://code.miguelgrinberg.com/miguelgrinberg/microdot/badges/workflows/tests.yml/badge.svg)](https://code.miguelgrinberg.com/miguelgrinberg/microdot/actions)
3
4
 
4
5
  *“The impossibly small web framework for Python and MicroPython”*
5
6
 
@@ -21,14 +22,16 @@ app.run()
21
22
 
22
23
  ## Resources
23
24
 
24
- - [Change Log](https://github.com/miguelgrinberg/microdot/blob/main/CHANGES.md)
25
- - Documentation
26
- - [Latest](https://microdot.readthedocs.io/en/latest/)
27
- - [Stable (v2)](https://microdot.readthedocs.io/en/stable/)
25
+ - [git](https://code.miguelgrinberg.com/miguelgrinberg/microdot)
26
+ - [Change Log](https://code.miguelgrinberg.com/miguelgrinberg/microdot/src/branch/main/CHANGES.md)
27
+ - [Documentation](https://microdot.readthedocs.io/)
28
+ - [PyPI](https://pypi.python.org/pypi/microdot)
29
+ - [Contributor's guide](CONTRIBUTING.md)
30
+ - [Security policy](SECURITY.md)
28
31
 
29
- The following links are for version 1 of Microdot:
32
+ The following links are for legacy version 1 of Microdot:
30
33
 
31
- - [Code](https://github.com/miguelgrinberg/microdot/tree/v1)
34
+ - [git](https://github.com/miguelgrinberg/microdot/tree/v1)
32
35
  - [Documentation](https://microdot.readthedocs.io/en/v1/)
33
36
 
34
37
  Note that version 1 is no longer maintained, so you should consider migrating
@@ -49,3 +52,16 @@ MicroPython and CPython:
49
52
  - OpenAPI integration, similar to [APIFairy](https://github.com/miguelgrinberg/apifairy) for Flask
50
53
 
51
54
  Do you have other ideas to propose? Let's [discuss them](https://github.com/:miguelgrinberg/microdot/discussions/new?category=ideas)!
55
+
56
+ Sponsor this project
57
+ --------------------
58
+
59
+ This project relies on contributions from its users. If you benefit from it please consider making a single or ongoing monetary contribution in one of the following platforms:
60
+
61
+ - [Github Sponsors](https://github.com/sponsors/miguelgrinberg)
62
+ - [Patreon](https://patreon.com/miguelgrinberg)
63
+ - [Buy me a Coffee](https://buymeacoffee.com/miguelgrinberg)
64
+ - [thanks.dev](https://thanks.dev/u/gh/miguelgrinberg)
65
+ - [PayPal](https://paypal.me/miguelgrinberg)
66
+
67
+ Thank you!
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "microdot"
3
- version = "2.6.2"
3
+ version = "2.7.0"
4
4
  authors = [
5
5
  { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },
6
6
  ]
@@ -22,8 +22,12 @@ file = "README.md"
22
22
  content-type = "text/markdown"
23
23
 
24
24
  [project.urls]
25
- Homepage = "https://github.com/miguelgrinberg/microdot"
26
- "Bug Tracker" = "https://github.com/miguelgrinberg/microdot/issues"
25
+ Homepage = "https://code.miguelgrinberg.com/miguelgrinberg/microdot"
26
+ Documentation = "https://microdot.readthedocs.io/"
27
+ "Source Code" = "https://code.miguelgrinberg.com/miguelgrinberg/microdot"
28
+ "Issue Tracker" = "https://code.miguelgrinberg.com/miguelgrinberg/microdot/issues"
29
+ Changelog = "https://code.miguelgrinberg.com/miguelgrinberg/microdot/src/branch/main/CHANGES.md"
30
+ Funding = "https://code.miguelgrinberg.com/miguelgrinberg/microdot#sponsor-this-project"
27
31
 
28
32
  [project.optional-dependencies]
29
33
  dev = [
@@ -1,4 +1,4 @@
1
1
  from microdot.microdot import Microdot, Request, Response, abort, redirect, \
2
2
  send_file, URLPattern, AsyncBytesIO, iscoroutine # noqa: F401
3
3
 
4
- __version__ = '2.6.2'
4
+ __version__ = '2.7.0'
@@ -9,38 +9,38 @@ class Microdot(BaseMicrodot): # type: ignore[no-redef]
9
9
  embedded_server: bool
10
10
  def __init__(self, lifespan_startup: Callable[[dict[str, Any]], Awaitable[None]] = ..., lifespan_shutdown: Callable[[dict[str, Any]], Awaitable[None]] = ...) -> None:
11
11
  ...
12
-
12
+
13
13
  async def handle_lifespan(self, scope: dict[str, Any], receive: Callable[[], Awaitable[dict[str, Any]]], send: Callable[[dict[str, Any]], Awaitable[None]]) -> None:
14
14
  ...
15
-
15
+
16
16
  async def asgi_app(self, scope: dict[str, Any], receive: Callable[[], Awaitable[dict[str, Any]]], send: Callable[[dict[str, Any]], Awaitable[None]]):
17
17
  ...
18
-
18
+
19
19
  async def __call__(self, scope: dict[str, Any], receive: Callable[[], Awaitable[dict[str, Any]]], send: Callable[[dict[str, Any]], Awaitable[None]]):
20
20
  ...
21
-
21
+
22
22
  def shutdown(self) -> None:
23
23
  ...
24
-
24
+
25
25
  def run(self, host: str = ..., port: int = ..., debug: bool = ..., **options: Any) -> None: # type: ignore[override]
26
26
  ...
27
-
27
+
28
28
 
29
29
 
30
30
  class WebSocket(BaseWebSocket):
31
31
  closed: bool
32
32
  async def handshake(self) -> None:
33
33
  ...
34
-
34
+
35
35
  async def receive(self) -> bytes | str:
36
36
  ...
37
-
37
+
38
38
  async def send(self, data: bytes | str) -> None: # type: ignore[override]
39
39
  ...
40
-
40
+
41
41
  async def close(self) -> None:
42
42
  ...
43
-
43
+
44
44
 
45
45
 
46
46
  async def websocket_upgrade(request: Request) -> BaseWebSocket:
@@ -6,13 +6,13 @@ class BaseAuth:
6
6
  error_callback: Callable[[Request], Any | Awaitable[Any]]
7
7
  def __init__(self) -> None:
8
8
  ...
9
-
9
+
10
10
  def __call__(self, f: Callable[..., Any]):
11
11
  ...
12
-
12
+
13
13
  def optional(self, f: Callable[..., Any]):
14
14
  ...
15
-
15
+
16
16
 
17
17
 
18
18
  class BasicAuth(BaseAuth):
@@ -23,14 +23,14 @@ class BasicAuth(BaseAuth):
23
23
  error_callback: Callable[[Request], Any | Awaitable[Any]]
24
24
  def __init__(self, realm: str = ..., charset: str = ..., scheme: str = ..., error_status: int = ...) -> None:
25
25
  ...
26
-
26
+
27
27
  async def authentication_error(self, request):
28
28
  ...
29
-
29
+
30
30
  auth_callback: Callable[[Request, str, str], Any | Awaitable[Any]]
31
31
  def authenticate(self, f: Callable[[Request, str, str], Any | Awaitable[Any]]) -> None:
32
32
  ...
33
-
33
+
34
34
 
35
35
 
36
36
  class TokenAuth(BaseAuth):
@@ -40,13 +40,13 @@ class TokenAuth(BaseAuth):
40
40
  error_callback: Callable[[Request], Any | Awaitable[Any]]
41
41
  def __init__(self, header: str = ..., scheme: str = ..., error_status: int = ...) -> None:
42
42
  ...
43
-
43
+
44
44
  auth_callback: Callable[[Request, str], Any | Awaitable[Any]]
45
45
  def authenticate(self, f: Callable[..., Any | Awaitable[Any]]) -> None:
46
46
  ...
47
-
47
+
48
48
  def errorhandler(self, f: Callable[[Request], Any | Awaitable[Any]]) -> None:
49
49
  ...
50
-
50
+
51
51
  async def authentication_error(self, request) -> None:
52
52
  ...
@@ -63,13 +63,18 @@ class CORS:
63
63
  """
64
64
  cors_headers = {}
65
65
  origin = request.headers.get('Origin')
66
+ vary_origin = False
66
67
  if self.allowed_origins == '*':
67
68
  cors_headers['Access-Control-Allow-Origin'] = origin or '*'
68
69
  if origin:
69
- cors_headers['Vary'] = 'Origin'
70
+ vary_origin = True
70
71
  elif origin in (self.allowed_origins or []):
71
72
  cors_headers['Access-Control-Allow-Origin'] = origin
72
- cors_headers['Vary'] = 'Origin'
73
+ vary_origin = True
74
+ if vary_origin:
75
+ if not hasattr(request.g, '_vary'):
76
+ request.g._vary = set()
77
+ request.g._vary.add('Origin')
73
78
  if self.allow_credentials and \
74
79
  'Access-Control-Allow-Origin' in cors_headers:
75
80
  cors_headers['Access-Control-Allow-Credentials'] = 'true'
@@ -103,9 +108,5 @@ class CORS:
103
108
  return cors_headers
104
109
 
105
110
  def after_request(self, request, response):
106
- saved_vary = response.headers.get('Vary')
107
111
  if request: # pragma: no branch
108
112
  response.headers.update(self.get_cors_headers(request))
109
- if saved_vary and saved_vary != response.headers.get('Vary'):
110
- response.headers['Vary'] = (
111
- saved_vary + ', ' + response.headers['Vary'])
@@ -14,16 +14,16 @@ class CORS:
14
14
  max_age: int | None
15
15
  def __init__(self, app: Microdot | None = ..., allowed_origins: list[str] | Literal['*'] | None = ..., allow_credentials: bool = ..., allowed_methods: list[str] | None = ..., expose_headers: list[str] | None = ..., allowed_headers: list[str] | None = ..., max_age: int | None = ..., handle_cors: bool = ...) -> None:
16
16
  ...
17
-
17
+
18
18
  default_options_handler: Callable[[Request], dict[str, str]]
19
19
  def initialize(self, app: Microdot, handle_cors: bool = ...) -> None:
20
20
  ...
21
-
21
+
22
22
  def options_handler(self, request: Request) -> dict[str, str]:
23
23
  ...
24
-
24
+
25
25
  def get_cors_headers(self, request: Request) -> dict[str, str]:
26
26
  ...
27
-
27
+
28
28
  def after_request(self, request: Request, response: Response) -> None:
29
29
  ...
@@ -64,6 +64,11 @@ class CSRF:
64
64
  allow = True
65
65
  elif sfs == 'same-site' and self.allow_subdomains:
66
66
  allow = True
67
+ if allow:
68
+ if not hasattr(
69
+ request.g, '_vary'): # pragma: no branch
70
+ request.g._vary = set()
71
+ request.g._vary.add('Sec-Fetch-Site')
67
72
  if not allow and origin and self.cors and \
68
73
  self.cors.allowed_origins != '*':
69
74
  # if we have a list of allowed origins, then we can
@@ -11,12 +11,12 @@ class CSRF:
11
11
  protected_routes: list[Callable[..., Any | Awaitable[Any]]]
12
12
  def __init__(self, app: Microdot | None = ..., cors: CORS | None = ..., protect_all: bool = ..., allow_subdomains: bool = ...) -> None:
13
13
  ...
14
-
14
+
15
15
  def initialize(self, app: Microdot, cors: CORS | None = ...) -> None:
16
16
  ...
17
-
17
+
18
18
  def exempt(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
19
19
  ...
20
-
20
+
21
21
  def protect(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
22
22
  ...
@@ -6,20 +6,20 @@ class Template:
6
6
  @classmethod
7
7
  def initialize(cls, template_dir: str = ..., enable_async: bool = ..., **kwargs) -> None:
8
8
  ...
9
-
9
+
10
10
  name: str
11
11
  template: JinjaTemplate
12
12
  def __init__(self, template: str, **kwargs: Any) -> None:
13
13
  ...
14
-
14
+
15
15
  def generate(self, *args: Any, **kwargs: Any) -> Iterator[str]:
16
16
  ...
17
-
17
+
18
18
  def render(self, *args: Any, **kwargs: Any) -> str:
19
19
  ...
20
-
20
+
21
21
  def generate_async(self, *args: Any, **kwargs: Any) -> AsyncIterator[str]:
22
22
  ...
23
-
23
+
24
24
  async def render_async(self, *args: Any, **kwargs: Any) -> Awaitable[str]:
25
25
  ...
@@ -6,21 +6,21 @@ class Login:
6
6
  user_loader_callback: Callable[[str], Any | Awaitable[Any]]
7
7
  def __init__(self, login_url: str = ...) -> None:
8
8
  ...
9
-
9
+
10
10
  def user_loader(self, f: Callable[[str], Any | Awaitable[Any]]) -> None:
11
11
  ...
12
-
12
+
13
13
  async def login_user(self, request: Request, user: Any, remember: bool = ..., redirect_url: str = ...) -> Response:
14
14
  ...
15
-
15
+
16
16
  async def logout_user(self, request: Request) -> None:
17
17
  ...
18
-
18
+
19
19
  async def get_current_user(self, request: Request):
20
20
  ...
21
-
21
+
22
22
  def __call__(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
23
23
  ...
24
-
24
+
25
25
  def fresh(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
26
26
  ...
@@ -613,7 +613,7 @@ class Response:
613
613
  """
614
614
  if ';' in value:
615
615
  raise ValueError('invalid cookie value')
616
- http_cookie = '{cookie}={value}'.format(cookie=cookie, value=value)
616
+ http_cookie = f'{cookie}={value}'
617
617
  if path:
618
618
  http_cookie += '; Path=' + path
619
619
  if domain:
@@ -819,7 +819,7 @@ class Response:
819
819
  if max_age is None:
820
820
  max_age = cls.default_send_file_max_age
821
821
  if max_age is not None:
822
- headers['Cache-Control'] = 'max-age={}'.format(max_age)
822
+ headers['Cache-Control'] = f'max-age={max_age}'
823
823
 
824
824
  if compressed:
825
825
  headers['Content-Encoding'] = compressed \
@@ -862,7 +862,7 @@ class URLPattern():
862
862
  value of the segment. If omitted, the value is returned
863
863
  as a string.
864
864
  """
865
- cls.segment_patterns[type_name] = '/({})'.format(pattern)
865
+ cls.segment_patterns[type_name] = f'/({pattern})'
866
866
  cls.segment_parsers[type_name] = parser
867
867
 
868
868
  def __init__(self, url_pattern):
@@ -889,7 +889,7 @@ class URLPattern():
889
889
  name = segment
890
890
  parser = None
891
891
  if type_.startswith('re:'):
892
- pattern += '/({pattern})'.format(pattern=type_[3:])
892
+ pattern += f'/({type_[3:]})'
893
893
  else:
894
894
  if type_ not in self.segment_patterns:
895
895
  raise ValueError('invalid URL segment type')
@@ -927,7 +927,7 @@ class URLPattern():
927
927
  return args
928
928
 
929
929
  def __repr__(self): # pragma: no cover
930
- return 'URLPattern: {}'.format(self.url_pattern)
930
+ return f'URLPattern: {self.url_pattern}'
931
931
 
932
932
 
933
933
  class HTTPException(Exception):
@@ -936,7 +936,7 @@ class HTTPException(Exception):
936
936
  self.reason = reason or str(status_code) + ' error'
937
937
 
938
938
  def __repr__(self): # pragma: no cover
939
- return 'HTTPException: {}'.format(self.status_code)
939
+ return f'HTTPException: {self.status_code}'
940
940
 
941
941
 
942
942
  class Microdot:
@@ -1091,6 +1091,24 @@ class Microdot:
1091
1091
  """
1092
1092
  return self.route(url_pattern, methods=['DELETE'])
1093
1093
 
1094
+ def query(self, url_pattern):
1095
+ """Decorator that is used to register a function as a ``QUERY``
1096
+ request handler for a given URL.
1097
+
1098
+ :param url_pattern: The URL pattern that will be compared against
1099
+ incoming requests.
1100
+
1101
+ This decorator can be used as an alias to the ``route`` decorator with
1102
+ ``methods=['QUERY']``.
1103
+
1104
+ Example::
1105
+
1106
+ @app.query('/users/<int:id>')
1107
+ def query_user(request, id):
1108
+ # ...
1109
+ """
1110
+ return self.route(url_pattern, methods=['QUERY'])
1111
+
1094
1112
  def before_request(self, f):
1095
1113
  """Decorator to register a function to run before each request is
1096
1114
  handled. The decorated function must take a single argument, the
@@ -1560,6 +1578,14 @@ class Microdot:
1560
1578
  res = await invoke_handler(
1561
1579
  handler, req, res) or res
1562
1580
  res.is_head = (req and req.method == 'HEAD')
1581
+ if req and hasattr(req.g, '_vary'):
1582
+ if res.headers.get('Vary'):
1583
+ vary = set(
1584
+ [h.strip() for h in res.headers['Vary'].split(',')])
1585
+ vary.update(req.g._vary)
1586
+ else:
1587
+ vary = req.g._vary
1588
+ res.headers['Vary'] = ', '.join(list(vary))
1563
1589
  return res
1564
1590
 
1565
1591