microdot 2.6.1__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 (124) hide show
  1. {microdot-2.6.1/src/microdot.egg-info → microdot-2.7.0}/PKG-INFO +31 -11
  2. {microdot-2.6.1 → microdot-2.7.0}/README.md +24 -8
  3. {microdot-2.6.1 → microdot-2.7.0}/pyproject.toml +7 -3
  4. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/__init__.py +1 -1
  5. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/asgi.pyi +10 -10
  6. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/auth.pyi +9 -9
  7. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/cors.py +7 -6
  8. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/cors.pyi +4 -4
  9. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/csrf.py +5 -0
  10. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/csrf.pyi +3 -3
  11. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/jinja.pyi +5 -5
  12. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/login.pyi +6 -6
  13. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/microdot.py +34 -6
  14. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/microdot.pyi +66 -63
  15. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/multipart.py +2 -1
  16. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/multipart.pyi +8 -8
  17. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/session.py +3 -0
  18. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/session.pyi +10 -11
  19. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/sse.pyi +2 -2
  20. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/test_client.py +15 -1
  21. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/test_client.pyi +15 -12
  22. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/utemplate.pyi +5 -5
  23. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/websocket.pyi +5 -5
  24. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/wsgi.pyi +4 -4
  25. {microdot-2.6.1 → microdot-2.7.0/src/microdot.egg-info}/PKG-INFO +31 -11
  26. {microdot-2.6.1 → microdot-2.7.0}/src/microdot.egg-info/SOURCES.txt +0 -1
  27. {microdot-2.6.1 → microdot-2.7.0}/src/microdot.egg-info/dependency_links.txt +0 -0
  28. {microdot-2.6.1 → microdot-2.7.0}/src/microdot.egg-info/not-zip-safe +0 -0
  29. {microdot-2.6.1 → microdot-2.7.0}/src/microdot.egg-info/requires.txt +0 -0
  30. {microdot-2.6.1 → microdot-2.7.0}/src/microdot.egg-info/top_level.txt +0 -0
  31. {microdot-2.6.1 → microdot-2.7.0}/tests/test_cors.py +7 -1
  32. {microdot-2.6.1 → microdot-2.7.0}/tests/test_csrf.py +6 -0
  33. {microdot-2.6.1 → microdot-2.7.0}/tests/test_microdot.py +70 -0
  34. {microdot-2.6.1 → microdot-2.7.0}/tests/test_multipart.py +14 -0
  35. {microdot-2.6.1 → microdot-2.7.0}/tests/test_response.py +2 -0
  36. {microdot-2.6.1 → microdot-2.7.0}/tests/test_session.py +20 -0
  37. {microdot-2.6.1 → microdot-2.7.0}/tox.ini +26 -3
  38. microdot-2.6.1/docs/implementation/type_hints.rst +0 -16
  39. {microdot-2.6.1 → microdot-2.7.0}/LICENSE +0 -0
  40. {microdot-2.6.1 → microdot-2.7.0}/MANIFEST.in +0 -0
  41. {microdot-2.6.1 → microdot-2.7.0}/docs/Makefile +0 -0
  42. {microdot-2.6.1 → microdot-2.7.0}/docs/_static/css/custom.css +0 -0
  43. {microdot-2.6.1 → microdot-2.7.0}/docs/api/asgi.rst +0 -0
  44. {microdot-2.6.1 → microdot-2.7.0}/docs/api/auth.rst +0 -0
  45. {microdot-2.6.1 → microdot-2.7.0}/docs/api/cors.rst +0 -0
  46. {microdot-2.6.1 → microdot-2.7.0}/docs/api/csrf.rst +0 -0
  47. {microdot-2.6.1 → microdot-2.7.0}/docs/api/index.rst +0 -0
  48. {microdot-2.6.1 → microdot-2.7.0}/docs/api/jinja.rst +0 -0
  49. {microdot-2.6.1 → microdot-2.7.0}/docs/api/login.rst +0 -0
  50. {microdot-2.6.1 → microdot-2.7.0}/docs/api/microdot.rst +0 -0
  51. {microdot-2.6.1 → microdot-2.7.0}/docs/api/multipart.rst +0 -0
  52. {microdot-2.6.1 → microdot-2.7.0}/docs/api/sessions.rst +0 -0
  53. {microdot-2.6.1 → microdot-2.7.0}/docs/api/sse.rst +0 -0
  54. {microdot-2.6.1 → microdot-2.7.0}/docs/api/test_client.rst +0 -0
  55. {microdot-2.6.1 → microdot-2.7.0}/docs/api/utemplate.rst +0 -0
  56. {microdot-2.6.1 → microdot-2.7.0}/docs/api/websocket.rst +0 -0
  57. {microdot-2.6.1 → microdot-2.7.0}/docs/api/wsgi.rst +0 -0
  58. {microdot-2.6.1 → microdot-2.7.0}/docs/conf.py +0 -0
  59. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/auth.rst +0 -0
  60. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/cors.rst +0 -0
  61. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/csrf.rst +0 -0
  62. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/index.rst +0 -0
  63. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/login.rst +0 -0
  64. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/multipart.rst +0 -0
  65. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/production.rst +0 -0
  66. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/sessions.rst +0 -0
  67. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/sse.rst +0 -0
  68. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/templates.rst +0 -0
  69. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/test_client.rst +0 -0
  70. {microdot-2.6.1 → microdot-2.7.0}/docs/extensions/websocket.rst +0 -0
  71. {microdot-2.6.1 → microdot-2.7.0}/docs/implementation/freezing.rst +0 -0
  72. {microdot-2.6.1 → microdot-2.7.0}/docs/implementation/index.rst +0 -0
  73. {microdot-2.6.1 → microdot-2.7.0}/docs/implementation/migrating.rst +0 -0
  74. {microdot-2.6.1 → microdot-2.7.0}/docs/index.rst +0 -0
  75. {microdot-2.6.1 → microdot-2.7.0}/docs/intro.rst +0 -0
  76. {microdot-2.6.1 → microdot-2.7.0}/docs/make.bat +0 -0
  77. {microdot-2.6.1 → microdot-2.7.0}/docs/users-guide/concurrency.rst +0 -0
  78. {microdot-2.6.1 → microdot-2.7.0}/docs/users-guide/defining-routes.rst +0 -0
  79. {microdot-2.6.1 → microdot-2.7.0}/docs/users-guide/index.rst +0 -0
  80. {microdot-2.6.1 → microdot-2.7.0}/docs/users-guide/intro.rst +0 -0
  81. {microdot-2.6.1 → microdot-2.7.0}/docs/users-guide/request-object.rst +0 -0
  82. {microdot-2.6.1 → microdot-2.7.0}/docs/users-guide/responses.rst +0 -0
  83. {microdot-2.6.1 → microdot-2.7.0}/setup.cfg +0 -0
  84. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/__init__.pyi +0 -0
  85. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/asgi.py +0 -0
  86. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/auth.py +0 -0
  87. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/helpers.py +0 -0
  88. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/helpers.pyi +0 -0
  89. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/jinja.py +0 -0
  90. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/login.py +0 -0
  91. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/py.typed +0 -0
  92. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/sse.py +0 -0
  93. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/utemplate.py +0 -0
  94. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/websocket.py +0 -0
  95. {microdot-2.6.1 → microdot-2.7.0}/src/microdot/wsgi.py +0 -0
  96. {microdot-2.6.1 → microdot-2.7.0}/tests/__init__.py +0 -0
  97. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.bin +0 -0
  98. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.css +0 -0
  99. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.gif +0 -0
  100. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.gz +0 -0
  101. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.html +0 -0
  102. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.jpg +0 -0
  103. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.js +0 -0
  104. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.json +0 -0
  105. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.png +0 -0
  106. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.txt +0 -0
  107. {microdot-2.6.1 → microdot-2.7.0}/tests/files/test.txt.gz +0 -0
  108. {microdot-2.6.1 → microdot-2.7.0}/tests/mock_socket.py +0 -0
  109. {microdot-2.6.1 → microdot-2.7.0}/tests/templates/hello.jinja.txt +0 -0
  110. {microdot-2.6.1 → microdot-2.7.0}/tests/templates/hello.utemplate.txt +0 -0
  111. {microdot-2.6.1 → microdot-2.7.0}/tests/templates/hello_utemplate_txt.py +0 -0
  112. {microdot-2.6.1 → microdot-2.7.0}/tests/test_asgi.py +0 -0
  113. {microdot-2.6.1 → microdot-2.7.0}/tests/test_auth.py +0 -0
  114. {microdot-2.6.1 → microdot-2.7.0}/tests/test_end2end.py +0 -0
  115. {microdot-2.6.1 → microdot-2.7.0}/tests/test_jinja.py +0 -0
  116. {microdot-2.6.1 → microdot-2.7.0}/tests/test_login.py +0 -0
  117. {microdot-2.6.1 → microdot-2.7.0}/tests/test_multidict.py +0 -0
  118. {microdot-2.6.1 → microdot-2.7.0}/tests/test_request.py +0 -0
  119. {microdot-2.6.1 → microdot-2.7.0}/tests/test_sse.py +0 -0
  120. {microdot-2.6.1 → microdot-2.7.0}/tests/test_url_pattern.py +0 -0
  121. {microdot-2.6.1 → microdot-2.7.0}/tests/test_urlencode.py +0 -0
  122. {microdot-2.6.1 → microdot-2.7.0}/tests/test_utemplate.py +0 -0
  123. {microdot-2.6.1 → microdot-2.7.0}/tests/test_websocket.py +0 -0
  124. {microdot-2.6.1 → 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.1
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.1"
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.1'
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
  ...
@@ -611,7 +611,9 @@ class Response:
611
611
  :param http_only: The cookie's ``HttpOnly`` flag.
612
612
  :param partitioned: Whether the cookie is partitioned.
613
613
  """
614
- http_cookie = '{cookie}={value}'.format(cookie=cookie, value=value)
614
+ if ';' in value:
615
+ raise ValueError('invalid cookie value')
616
+ http_cookie = f'{cookie}={value}'
615
617
  if path:
616
618
  http_cookie += '; Path=' + path
617
619
  if domain:
@@ -817,7 +819,7 @@ class Response:
817
819
  if max_age is None:
818
820
  max_age = cls.default_send_file_max_age
819
821
  if max_age is not None:
820
- headers['Cache-Control'] = 'max-age={}'.format(max_age)
822
+ headers['Cache-Control'] = f'max-age={max_age}'
821
823
 
822
824
  if compressed:
823
825
  headers['Content-Encoding'] = compressed \
@@ -860,7 +862,7 @@ class URLPattern():
860
862
  value of the segment. If omitted, the value is returned
861
863
  as a string.
862
864
  """
863
- cls.segment_patterns[type_name] = '/({})'.format(pattern)
865
+ cls.segment_patterns[type_name] = f'/({pattern})'
864
866
  cls.segment_parsers[type_name] = parser
865
867
 
866
868
  def __init__(self, url_pattern):
@@ -887,7 +889,7 @@ class URLPattern():
887
889
  name = segment
888
890
  parser = None
889
891
  if type_.startswith('re:'):
890
- pattern += '/({pattern})'.format(pattern=type_[3:])
892
+ pattern += f'/({type_[3:]})'
891
893
  else:
892
894
  if type_ not in self.segment_patterns:
893
895
  raise ValueError('invalid URL segment type')
@@ -925,7 +927,7 @@ class URLPattern():
925
927
  return args
926
928
 
927
929
  def __repr__(self): # pragma: no cover
928
- return 'URLPattern: {}'.format(self.url_pattern)
930
+ return f'URLPattern: {self.url_pattern}'
929
931
 
930
932
 
931
933
  class HTTPException(Exception):
@@ -934,7 +936,7 @@ class HTTPException(Exception):
934
936
  self.reason = reason or str(status_code) + ' error'
935
937
 
936
938
  def __repr__(self): # pragma: no cover
937
- return 'HTTPException: {}'.format(self.status_code)
939
+ return f'HTTPException: {self.status_code}'
938
940
 
939
941
 
940
942
  class Microdot:
@@ -1089,6 +1091,24 @@ class Microdot:
1089
1091
  """
1090
1092
  return self.route(url_pattern, methods=['DELETE'])
1091
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
+
1092
1112
  def before_request(self, f):
1093
1113
  """Decorator to register a function to run before each request is
1094
1114
  handled. The decorated function must take a single argument, the
@@ -1558,6 +1578,14 @@ class Microdot:
1558
1578
  res = await invoke_handler(
1559
1579
  handler, req, res) or res
1560
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))
1561
1589
  return res
1562
1590
 
1563
1591