microdot 2.5.0__tar.gz → 2.6.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.
- {microdot-2.5.0/src/microdot.egg-info → microdot-2.6.0}/PKG-INFO +13 -5
- {microdot-2.5.0 → microdot-2.6.0}/README.md +12 -4
- {microdot-2.5.0 → microdot-2.6.0}/docs/implementation/migrating.rst +20 -23
- {microdot-2.5.0 → microdot-2.6.0}/docs/index.rst +0 -1
- {microdot-2.5.0 → microdot-2.6.0}/pyproject.toml +1 -1
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/__init__.py +1 -1
- microdot-2.6.0/src/microdot/__init__.pyi +7 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/asgi.py +1 -1
- microdot-2.6.0/src/microdot/asgi.pyi +50 -0
- microdot-2.6.0/src/microdot/auth.pyi +52 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/cors.py +1 -1
- microdot-2.6.0/src/microdot/cors.pyi +29 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/csrf.py +7 -9
- microdot-2.6.0/src/microdot/csrf.pyi +22 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/helpers.py +1 -1
- microdot-2.6.0/src/microdot/helpers.pyi +4 -0
- microdot-2.6.0/src/microdot/jinja.pyi +25 -0
- microdot-2.6.0/src/microdot/login.pyi +26 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/microdot.py +24 -9
- microdot-2.6.0/src/microdot/microdot.pyi +307 -0
- microdot-2.6.0/src/microdot/multipart.pyi +43 -0
- microdot-2.6.0/src/microdot/py.typed +0 -0
- microdot-2.6.0/src/microdot/session.pyi +45 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/sse.py +1 -1
- microdot-2.6.0/src/microdot/sse.pyi +20 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/test_client.py +2 -2
- microdot-2.6.0/src/microdot/test_client.pyi +51 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/utemplate.py +1 -1
- microdot-2.6.0/src/microdot/utemplate.pyi +23 -0
- microdot-2.6.0/src/microdot/websocket.pyi +42 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/wsgi.py +1 -1
- microdot-2.6.0/src/microdot/wsgi.pyi +22 -0
- {microdot-2.5.0 → microdot-2.6.0/src/microdot.egg-info}/PKG-INFO +13 -5
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot.egg-info/SOURCES.txt +17 -1
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_csrf.py +20 -8
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_end2end.py +63 -7
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_login.py +4 -4
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_session.py +8 -6
- {microdot-2.5.0 → microdot-2.6.0}/tox.ini +12 -13
- microdot-2.5.0/docs/contributing.rst +0 -7
- {microdot-2.5.0 → microdot-2.6.0}/LICENSE +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/MANIFEST.in +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/Makefile +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/_static/css/custom.css +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/asgi.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/auth.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/cors.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/csrf.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/index.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/jinja.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/login.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/microdot.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/multipart.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/sessions.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/sse.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/test_client.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/utemplate.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/websocket.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/api/wsgi.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/conf.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/auth.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/cors.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/csrf.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/index.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/login.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/multipart.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/production.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/sessions.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/sse.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/templates.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/test_client.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/extensions/websocket.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/implementation/freezing.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/implementation/index.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/intro.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/make.bat +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/users-guide/concurrency.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/users-guide/defining-routes.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/users-guide/index.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/users-guide/intro.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/users-guide/request-object.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/docs/users-guide/responses.rst +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/setup.cfg +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/auth.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/jinja.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/login.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/multipart.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/session.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot/websocket.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot.egg-info/dependency_links.txt +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot.egg-info/not-zip-safe +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot.egg-info/requires.txt +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/src/microdot.egg-info/top_level.txt +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/__init__.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.bin +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.css +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.gif +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.gz +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.html +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.jpg +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.js +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.json +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.png +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.txt +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/files/test.txt.gz +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/mock_socket.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/templates/hello.jinja.txt +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/templates/hello.utemplate.txt +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/templates/hello_utemplate_txt.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_asgi.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_auth.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_cors.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_jinja.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_microdot.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_multidict.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_multipart.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_request.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_response.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_sse.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_url_pattern.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_urlencode.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_utemplate.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_websocket.py +0 -0
- {microdot-2.5.0 → microdot-2.6.0}/tests/test_wsgi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microdot
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: The impossibly small web framework for MicroPython
|
|
5
5
|
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/miguelgrinberg/microdot
|
|
@@ -49,9 +49,16 @@ app.run()
|
|
|
49
49
|
- Documentation
|
|
50
50
|
- [Latest](https://microdot.readthedocs.io/en/latest/)
|
|
51
51
|
- [Stable (v2)](https://microdot.readthedocs.io/en/stable/)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
|
|
53
|
+
The following links are for version 1 of Microdot:
|
|
54
|
+
|
|
55
|
+
- [Code](https://github.com/miguelgrinberg/microdot/tree/v1)
|
|
56
|
+
- [Documentation](https://microdot.readthedocs.io/en/v1/)
|
|
57
|
+
|
|
58
|
+
Note that version 1 is no longer maintained, so you should consider migrating
|
|
59
|
+
to version 2. See the
|
|
60
|
+
[migration notes](https://microdot.readthedocs.io/en/latest/implementation/migrating.html)
|
|
61
|
+
for help.
|
|
55
62
|
|
|
56
63
|
## Roadmap
|
|
57
64
|
|
|
@@ -60,7 +67,8 @@ MicroPython and CPython:
|
|
|
60
67
|
|
|
61
68
|
- Authentication support, similar to [Flask-Login](https://github.com/maxcountryman/flask-login) for Flask (**Added in version 2.1**)
|
|
62
69
|
- Support for forms encoded in `multipart/form-data` format (**Added in version 2.2**)
|
|
63
|
-
- CSRF protection extension
|
|
70
|
+
- CSRF protection extension (**Added in version 2.5**)
|
|
71
|
+
- Type hints (**Added in version 2.6**)
|
|
64
72
|
- Pub/sub mini-framework for WebSocket and SSE
|
|
65
73
|
- OpenAPI integration, similar to [APIFairy](https://github.com/miguelgrinberg/apifairy) for Flask
|
|
66
74
|
|
|
@@ -25,9 +25,16 @@ app.run()
|
|
|
25
25
|
- Documentation
|
|
26
26
|
- [Latest](https://microdot.readthedocs.io/en/latest/)
|
|
27
27
|
- [Stable (v2)](https://microdot.readthedocs.io/en/stable/)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
|
|
29
|
+
The following links are for version 1 of Microdot:
|
|
30
|
+
|
|
31
|
+
- [Code](https://github.com/miguelgrinberg/microdot/tree/v1)
|
|
32
|
+
- [Documentation](https://microdot.readthedocs.io/en/v1/)
|
|
33
|
+
|
|
34
|
+
Note that version 1 is no longer maintained, so you should consider migrating
|
|
35
|
+
to version 2. See the
|
|
36
|
+
[migration notes](https://microdot.readthedocs.io/en/latest/implementation/migrating.html)
|
|
37
|
+
for help.
|
|
31
38
|
|
|
32
39
|
## Roadmap
|
|
33
40
|
|
|
@@ -36,7 +43,8 @@ MicroPython and CPython:
|
|
|
36
43
|
|
|
37
44
|
- Authentication support, similar to [Flask-Login](https://github.com/maxcountryman/flask-login) for Flask (**Added in version 2.1**)
|
|
38
45
|
- Support for forms encoded in `multipart/form-data` format (**Added in version 2.2**)
|
|
39
|
-
- CSRF protection extension
|
|
46
|
+
- CSRF protection extension (**Added in version 2.5**)
|
|
47
|
+
- Type hints (**Added in version 2.6**)
|
|
40
48
|
- Pub/sub mini-framework for WebSocket and SSE
|
|
41
49
|
- OpenAPI integration, similar to [APIFairy](https://github.com/miguelgrinberg/apifairy) for Flask
|
|
42
50
|
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
Migrating to Microdot 2.x from Older Releases
|
|
2
2
|
---------------------------------------------
|
|
3
3
|
|
|
4
|
-
Version 2 of Microdot
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
For this reason most applications built for earlier versions will need to be
|
|
9
|
-
updated to work correctly with Microdot 2. This section describes the backwards
|
|
10
|
-
incompatible changes that were made.
|
|
4
|
+
Version 2 of Microdot introduces backwards incompatible changes, so most
|
|
5
|
+
applications built for earlier versions will need to be updated to work
|
|
6
|
+
correctly with Microdot 2. This section describes the changes that were made.
|
|
11
7
|
|
|
12
8
|
Code reorganization
|
|
13
9
|
~~~~~~~~~~~~~~~~~~~
|
|
14
10
|
|
|
15
|
-
The Microdot source code has been moved into a ``microdot`` package,
|
|
11
|
+
The Microdot source code has been moved into a ``microdot`` package in v2,
|
|
16
12
|
eliminating the need for each extension to be named with a *microdot_* prefix.
|
|
17
13
|
|
|
18
14
|
As a result of this change, all extensions have been renamed to shorter names.
|
|
@@ -33,11 +29,11 @@ In earlier releases of Microdot the core web server was built on synchronous
|
|
|
33
29
|
Python, and asynchronous support was enabled with the asyncio extension.
|
|
34
30
|
|
|
35
31
|
Microdot 2 eliminates the synchronous web server, and implements the core
|
|
36
|
-
server logic directly with asyncio
|
|
37
|
-
|
|
32
|
+
server logic directly with asyncio. The asyncio extension does not exist
|
|
33
|
+
anymore.
|
|
38
34
|
|
|
39
|
-
Any applications built using the asyncio extension will need to update
|
|
40
|
-
imports from this::
|
|
35
|
+
Any applications built using the legacy asyncio extension will need to update
|
|
36
|
+
their imports from this::
|
|
41
37
|
|
|
42
38
|
from microdot_asyncio import Microdot
|
|
43
39
|
|
|
@@ -46,7 +42,7 @@ to this::
|
|
|
46
42
|
from microdot import Microdot
|
|
47
43
|
|
|
48
44
|
Applications that were built using the synchronous web server do not need to
|
|
49
|
-
change their imports, but will now
|
|
45
|
+
change their imports, but will now use an asynchronous web server. Review the
|
|
50
46
|
:ref:`Concurrency` section to learn about the potential issues when using
|
|
51
47
|
``def`` function handlers, and the benefits of transitioning to ``async def``
|
|
52
48
|
handlers.
|
|
@@ -64,9 +60,9 @@ extensions:
|
|
|
64
60
|
- *microdot_asgi_websocket.py*: the functionality in this extension is now
|
|
65
61
|
available in the ASGI extension.
|
|
66
62
|
- *microdot_ssl.py*: this extension was only used with the synchronous web
|
|
67
|
-
server, so it is not needed anymore.
|
|
63
|
+
server, so it is not needed anymore and has been removed.
|
|
68
64
|
- *microdot_websocket_alt.py*: this extension was only used with the
|
|
69
|
-
synchronous web server, so it is not needed anymore.
|
|
65
|
+
synchronous web server, so it is not needed anymore and has been removed.
|
|
70
66
|
|
|
71
67
|
No more ``render_template()`` function
|
|
72
68
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
@@ -87,8 +83,8 @@ As a result of this change, it is now possible to use asynchronous rendering::
|
|
|
87
83
|
|
|
88
84
|
return await Template('index.html').render_async(title='Home')
|
|
89
85
|
|
|
90
|
-
Also thanks to this redesign, the template can be streamed
|
|
91
|
-
|
|
86
|
+
Also thanks to this redesign, the template can be streamed, which saves memory
|
|
87
|
+
for larger templates::
|
|
92
88
|
|
|
93
89
|
return Template('index.html').generate(title='Home')
|
|
94
90
|
|
|
@@ -96,6 +92,8 @@ Streamed templates also have an asynchronous version::
|
|
|
96
92
|
|
|
97
93
|
return Template('index.html').generate_async(title='Home')
|
|
98
94
|
|
|
95
|
+
Note that templates that are streamed asynchronously do not use an ``await``.
|
|
96
|
+
|
|
99
97
|
Class-based user sessions
|
|
100
98
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
101
99
|
|
|
@@ -136,10 +134,9 @@ Applications using the WSGI extension continue to run under an asynchronous
|
|
|
136
134
|
loop and should try to use the recommended ``async def`` handlers, but can be
|
|
137
135
|
deployed with standard WSGI servers such as Gunicorn.
|
|
138
136
|
|
|
139
|
-
WebSocket support when using the WSGI extension is enabled when using a
|
|
140
|
-
compatible web server. At this time only Gunicorn is supported for WebSocket.
|
|
141
|
-
Given that WebSocket support is asynchronous, it would be better to switch to
|
|
142
|
-
the ASGI extension, which has full support for WebSocket as defined in the ASGI
|
|
143
|
-
specification.
|
|
144
|
-
|
|
145
137
|
As before, the WSGI extension is not available under MicroPython.
|
|
138
|
+
|
|
139
|
+
Given that Microdot version 2 is asynchronous, the WSGI extension should be
|
|
140
|
+
considered deprecated. Users of this extension are encouraged to migrate to the
|
|
141
|
+
ASGI extension, which works with compatible ASGI web servers such as Uvicorn
|
|
142
|
+
and Gunicorn.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This type stub file was generated by pyright.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from microdot.microdot import AsyncBytesIO as AsyncBytesIO, Microdot as Microdot, Request as Request, Response as Response, URLPattern as URLPattern, abort as abort, redirect as redirect, send_file as send_file
|
|
6
|
+
|
|
7
|
+
__version__: str
|
|
@@ -44,7 +44,7 @@ class _BodyStream: # pragma: no cover
|
|
|
44
44
|
return data
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
class Microdot(BaseMicrodot):
|
|
47
|
+
class Microdot(BaseMicrodot): # type: ignore[no-redef]
|
|
48
48
|
"""A subclass of the core :class:`Microdot <microdot.Microdot>` class that
|
|
49
49
|
implements the ASGI protocol.
|
|
50
50
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Any, Awaitable, Callable
|
|
2
|
+
from microdot import * # type: ignore
|
|
3
|
+
from microdot.microdot import Microdot as BaseMicrodot, Request
|
|
4
|
+
from microdot.websocket import WebSocket as BaseWebSocket
|
|
5
|
+
|
|
6
|
+
class Microdot(BaseMicrodot): # type: ignore[no-redef]
|
|
7
|
+
lifespan_startup: Callable[[dict[str, Any]], Awaitable[None]]
|
|
8
|
+
lifespan_shutdown: Callable[[dict[str, Any]], Awaitable[None]]
|
|
9
|
+
embedded_server: bool
|
|
10
|
+
def __init__(self, lifespan_startup: Callable[[dict[str, Any]], Awaitable[None]] = ..., lifespan_shutdown: Callable[[dict[str, Any]], Awaitable[None]] = ...) -> None:
|
|
11
|
+
...
|
|
12
|
+
|
|
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
|
+
...
|
|
15
|
+
|
|
16
|
+
async def asgi_app(self, scope: dict[str, Any], receive: Callable[[], Awaitable[dict[str, Any]]], send: Callable[[dict[str, Any]], Awaitable[None]]):
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
async def __call__(self, scope: dict[str, Any], receive: Callable[[], Awaitable[dict[str, Any]]], send: Callable[[dict[str, Any]], Awaitable[None]]):
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def shutdown(self) -> None:
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def run(self, host: str = ..., port: int = ..., debug: bool = ..., **options: Any) -> None: # type: ignore[override]
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class WebSocket(BaseWebSocket):
|
|
31
|
+
closed: bool
|
|
32
|
+
async def handshake(self) -> None:
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
async def receive(self) -> bytes | str:
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
async def send(self, data: bytes | str) -> None: # type: ignore[override]
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
async def close(self) -> None:
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def websocket_upgrade(request: Request) -> BaseWebSocket:
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def with_websocket(f: Callable[[Request, BaseWebSocket], Awaitable[None]]) -> None:
|
|
50
|
+
...
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Any, Awaitable, Callable
|
|
2
|
+
from microdot import Request
|
|
3
|
+
|
|
4
|
+
class BaseAuth:
|
|
5
|
+
auth_callback: Callable[..., Any | Awaitable[Any]]
|
|
6
|
+
error_callback: Callable[[Request], Any | Awaitable[Any]]
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
...
|
|
9
|
+
|
|
10
|
+
def __call__(self, f: Callable[..., Any]):
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
def optional(self, f: Callable[..., Any]):
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BasicAuth(BaseAuth):
|
|
19
|
+
realm: str
|
|
20
|
+
charset: str
|
|
21
|
+
scheme: str
|
|
22
|
+
error_status: int
|
|
23
|
+
error_callback: Callable[[Request], Any | Awaitable[Any]]
|
|
24
|
+
def __init__(self, realm: str = ..., charset: str = ..., scheme: str = ..., error_status: int = ...) -> None:
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
async def authentication_error(self, request):
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
auth_callback: Callable[[Request, str, str], Any | Awaitable[Any]]
|
|
31
|
+
def authenticate(self, f: Callable[[Request, str, str], Any | Awaitable[Any]]) -> None:
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TokenAuth(BaseAuth):
|
|
37
|
+
header: str
|
|
38
|
+
scheme: str
|
|
39
|
+
error_status: int
|
|
40
|
+
error_callback: Callable[[Request], Any | Awaitable[Any]]
|
|
41
|
+
def __init__(self, header: str = ..., scheme: str = ..., error_status: int = ...) -> None:
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
auth_callback: Callable[[Request, str], Any | Awaitable[Any]]
|
|
45
|
+
def authenticate(self, f: Callable[..., Any | Awaitable[Any]]) -> None:
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
def errorhandler(self, f: Callable[[Request], Any | Awaitable[Any]]) -> None:
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
async def authentication_error(self, request) -> None:
|
|
52
|
+
...
|
|
@@ -14,7 +14,7 @@ class CORS:
|
|
|
14
14
|
making cross-site requests. If not set, all methods
|
|
15
15
|
are allowed.
|
|
16
16
|
:param expose_headers: A list of headers that the browser is allowed to
|
|
17
|
-
|
|
17
|
+
expose.
|
|
18
18
|
:param allowed_headers: A list of headers that are allowed to be used when
|
|
19
19
|
making cross-site requests. If not set, all headers
|
|
20
20
|
are allowed.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This type stub file was generated by pyright.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Callable, Literal
|
|
6
|
+
from microdot import Microdot, Request, Response
|
|
7
|
+
|
|
8
|
+
class CORS:
|
|
9
|
+
allowed_origins: list[str] | Literal['*'] | None
|
|
10
|
+
allow_credentials: bool
|
|
11
|
+
allowed_methods: list[str] | None
|
|
12
|
+
expose_headers: list[str] | None
|
|
13
|
+
allowed_headers: list[str] | None
|
|
14
|
+
max_age: int | None
|
|
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
|
+
...
|
|
17
|
+
|
|
18
|
+
default_options_handler: Callable[[Request], dict[str, str]]
|
|
19
|
+
def initialize(self, app: Microdot, handle_cors: bool = ...) -> None:
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def options_handler(self, request: Request) -> dict[str, str]:
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def get_cors_headers(self, request: Request) -> dict[str, str]:
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def after_request(self, request: Request, response: Response) -> None:
|
|
29
|
+
...
|
|
@@ -56,6 +56,7 @@ class CSRF:
|
|
|
56
56
|
) or request.route in self.protected_routes:
|
|
57
57
|
allow = False
|
|
58
58
|
sfs = request.headers.get('Sec-Fetch-Site')
|
|
59
|
+
origin = request.headers.get('Origin')
|
|
59
60
|
if sfs:
|
|
60
61
|
# if the Sec-Fetch-Site header was given, ensure it is not
|
|
61
62
|
# cross-site
|
|
@@ -63,14 +64,11 @@ class CSRF:
|
|
|
63
64
|
allow = True
|
|
64
65
|
elif sfs == 'same-site' and self.allow_subdomains:
|
|
65
66
|
allow = True
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# of allowed origins, then we can
|
|
69
|
-
|
|
70
|
-
if
|
|
71
|
-
# origin wasn't given so this isn't a browser
|
|
72
|
-
allow = True
|
|
73
|
-
elif not self.allow_subdomains:
|
|
67
|
+
if not allow and origin and self.cors and \
|
|
68
|
+
self.cors.allowed_origins != '*':
|
|
69
|
+
# if we have a list of allowed origins, then we can
|
|
70
|
+
# validate the origin
|
|
71
|
+
if not self.allow_subdomains:
|
|
74
72
|
allow = origin in self.cors.allowed_origins
|
|
75
73
|
else:
|
|
76
74
|
origin_scheme, origin_host = origin.split('://', 1)
|
|
@@ -83,7 +81,7 @@ class CSRF:
|
|
|
83
81
|
):
|
|
84
82
|
allow = True
|
|
85
83
|
break
|
|
86
|
-
|
|
84
|
+
if not allow and not sfs and not origin:
|
|
87
85
|
allow = True # no headers to check
|
|
88
86
|
|
|
89
87
|
if not allow:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any, Awaitable, Callable
|
|
2
|
+
from microdot import Microdot
|
|
3
|
+
from microdot.cors import CORS
|
|
4
|
+
|
|
5
|
+
class CSRF:
|
|
6
|
+
SAFE_METHODS: list[str]
|
|
7
|
+
cors: CORS | None
|
|
8
|
+
protect_all: bool
|
|
9
|
+
allow_subdomains: bool
|
|
10
|
+
exempt_routes: list[Callable[..., Any | Awaitable[Any]]]
|
|
11
|
+
protected_routes: list[Callable[..., Any | Awaitable[Any]]]
|
|
12
|
+
def __init__(self, app: Microdot | None = ..., cors: CORS | None = ..., protect_all: bool = ..., allow_subdomains: bool = ...) -> None:
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def initialize(self, app: Microdot, cors: CORS | None = ...) -> None:
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
def exempt(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def protect(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
|
|
22
|
+
...
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Any, AsyncIterator, Awaitable, Iterator
|
|
2
|
+
from jinja2 import Environment, Template as JinjaTemplate
|
|
3
|
+
|
|
4
|
+
class Template:
|
|
5
|
+
jinja_env: Environment
|
|
6
|
+
@classmethod
|
|
7
|
+
def initialize(cls, template_dir: str = ..., enable_async: bool = ..., **kwargs) -> None:
|
|
8
|
+
...
|
|
9
|
+
|
|
10
|
+
name: str
|
|
11
|
+
template: JinjaTemplate
|
|
12
|
+
def __init__(self, template: str, **kwargs: Any) -> None:
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def generate(self, *args: Any, **kwargs: Any) -> Iterator[str]:
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
def render(self, *args: Any, **kwargs: Any) -> str:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def generate_async(self, *args: Any, **kwargs: Any) -> AsyncIterator[str]:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
async def render_async(self, *args: Any, **kwargs: Any) -> Awaitable[str]:
|
|
25
|
+
...
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Any, Awaitable, Callable
|
|
2
|
+
from microdot import Request, Response
|
|
3
|
+
|
|
4
|
+
class Login:
|
|
5
|
+
login_url: str
|
|
6
|
+
user_loader_callback: Callable[[str], Any | Awaitable[Any]]
|
|
7
|
+
def __init__(self, login_url: str = ...) -> None:
|
|
8
|
+
...
|
|
9
|
+
|
|
10
|
+
def user_loader(self, f: Callable[[str], Any | Awaitable[Any]]) -> None:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
async def login_user(self, request: Request, user: Any, remember: bool = ..., redirect_url: str = ...) -> Response:
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
async def logout_user(self, request: Request) -> None:
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
async def get_current_user(self, request: Request):
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def __call__(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def fresh(self, f: Callable[..., Any | Awaitable[Any]]) -> Callable[..., Any | Awaitable[Any]]:
|
|
26
|
+
...
|
|
@@ -11,7 +11,7 @@ import re
|
|
|
11
11
|
import time
|
|
12
12
|
|
|
13
13
|
try:
|
|
14
|
-
import orjson as json
|
|
14
|
+
import orjson as json # type: ignore[import-not-found]
|
|
15
15
|
except ImportError:
|
|
16
16
|
import json
|
|
17
17
|
|
|
@@ -31,7 +31,7 @@ try:
|
|
|
31
31
|
None, partial(handler, *args, **kwargs))
|
|
32
32
|
return ret
|
|
33
33
|
except ImportError: # pragma: no cover
|
|
34
|
-
def iscoroutine(coro):
|
|
34
|
+
def iscoroutine(coro): # type: ignore[misc]
|
|
35
35
|
return hasattr(coro, 'send') and hasattr(coro, 'throw')
|
|
36
36
|
|
|
37
37
|
async def invoke_handler(handler, *args, **kwargs):
|
|
@@ -46,7 +46,7 @@ except ImportError: # pragma: no cover
|
|
|
46
46
|
return ret
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
|
-
from sys import print_exception
|
|
49
|
+
from sys import print_exception # type: ignore[attr-defined]
|
|
50
50
|
except ImportError: # pragma: no cover
|
|
51
51
|
import traceback
|
|
52
52
|
|
|
@@ -578,7 +578,7 @@ class Response:
|
|
|
578
578
|
#: written to the client. Used to exit WebSocket connections cleanly.
|
|
579
579
|
already_handled = None
|
|
580
580
|
|
|
581
|
-
def __init__(self, body='', status_code=200, headers=None, reason=None):
|
|
581
|
+
def __init__(self, body=b'', status_code=200, headers=None, reason=None):
|
|
582
582
|
if body is None and status_code == 200:
|
|
583
583
|
body = ''
|
|
584
584
|
status_code = 204
|
|
@@ -1212,7 +1212,7 @@ class Microdot:
|
|
|
1212
1212
|
raise HTTPException(status_code, reason)
|
|
1213
1213
|
|
|
1214
1214
|
async def start_server(self, host='0.0.0.0', port=5000, debug=False,
|
|
1215
|
-
ssl=None):
|
|
1215
|
+
ssl=None, start_serving=True):
|
|
1216
1216
|
"""Start the Microdot web server as a coroutine. This coroutine does
|
|
1217
1217
|
not normally return, as the server enters an endless listening loop.
|
|
1218
1218
|
The :func:`shutdown` function provides a method for terminating the
|
|
@@ -1231,6 +1231,13 @@ class Microdot:
|
|
|
1231
1231
|
default is ``False``.
|
|
1232
1232
|
:param ssl: An ``SSLContext`` instance or ``None`` if the server should
|
|
1233
1233
|
not use TLS. The default is ``None``.
|
|
1234
|
+
:param start_serving: If ``True``, the server starts accepting
|
|
1235
|
+
connections immediately. When set to ``False``,
|
|
1236
|
+
this method returns a ``Server`` object. To
|
|
1237
|
+
accept connections, the
|
|
1238
|
+
``Server.serve_forever()`` method should be
|
|
1239
|
+
called. The default is ``True``. A value of
|
|
1240
|
+
``False`` is only supported in CPython.
|
|
1234
1241
|
|
|
1235
1242
|
This method is a coroutine.
|
|
1236
1243
|
|
|
@@ -1275,10 +1282,18 @@ class Microdot:
|
|
|
1275
1282
|
host=host, port=port))
|
|
1276
1283
|
|
|
1277
1284
|
try:
|
|
1278
|
-
self.server = await asyncio.start_server(
|
|
1279
|
-
|
|
1285
|
+
self.server = await asyncio.start_server(
|
|
1286
|
+
serve, host, port, ssl=ssl, start_serving=start_serving)
|
|
1287
|
+
if not start_serving:
|
|
1288
|
+
return self.server
|
|
1280
1289
|
except TypeError: # pragma: no cover
|
|
1281
|
-
|
|
1290
|
+
if not start_serving:
|
|
1291
|
+
raise ValueError('start_serving must be True')
|
|
1292
|
+
try:
|
|
1293
|
+
self.server = await asyncio.start_server(serve, host, port,
|
|
1294
|
+
ssl=ssl)
|
|
1295
|
+
except TypeError: # pragma: no cover
|
|
1296
|
+
self.server = await asyncio.start_server(serve, host, port)
|
|
1282
1297
|
|
|
1283
1298
|
while True:
|
|
1284
1299
|
try:
|
|
@@ -1544,7 +1559,7 @@ class Microdot:
|
|
|
1544
1559
|
return res
|
|
1545
1560
|
|
|
1546
1561
|
|
|
1547
|
-
Response.already_handled = Response()
|
|
1562
|
+
Response.already_handled = Response() # type: ignore[assignment]
|
|
1548
1563
|
|
|
1549
1564
|
abort = Microdot.abort
|
|
1550
1565
|
redirect = Response.redirect
|