Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
300f8563ed | ||
|
|
1fc11193da | ||
|
|
79452a4699 | ||
|
|
84842e39c3 | ||
|
|
2a3c889717 | ||
|
|
ad368be993 | ||
|
|
3df56c6ffe | ||
|
|
c2e18004f7 | ||
|
|
bd18ceb442 | ||
|
|
f38d6d760a | ||
|
|
dee4914bdd |
20
CHANGES.md
20
CHANGES.md
@@ -1,8 +1,26 @@
|
|||||||
# Microdot change log
|
# Microdot change log
|
||||||
|
|
||||||
|
**Release 2.0.2** - 2023-12-28
|
||||||
|
|
||||||
|
- Support binary data in the SSE extension ([commit](https://github.com/miguelgrinberg/microdot/commit/1fc11193da0d298f5539e2ad218836910a13efb2))
|
||||||
|
- Upgrade micropython tests to use v1.22 + initial CircuitPython testing work ([commit](https://github.com/miguelgrinberg/microdot/commit/79452a46992351ccad2c0317c20bf50be0d76641))
|
||||||
|
- Improvements to migration guide ([commit](https://github.com/miguelgrinberg/microdot/commit/84842e39c360a8b3ddf36feac8af201fb19bbb0b))
|
||||||
|
- Remove spurious async in documentation example [#187](https://github.com/miguelgrinberg/microdot/issues/187) ([commit](https://github.com/miguelgrinberg/microdot/commit/ad368be993e2e3007579f1d3880e36d60c71da92)) (thanks **Tak Tran**!)
|
||||||
|
|
||||||
|
**Release 2.0.1** - 2023-12-23
|
||||||
|
|
||||||
|
- Addressed some inadvertent mistakes in the template extensions ([commit](https://github.com/miguelgrinberg/microdot/commit/bd18ceb4424e9dfb52b1e6d498edd260aa24fc53))
|
||||||
|
|
||||||
**Release 2.0.0** - 2023-12-22
|
**Release 2.0.0** - 2023-12-22
|
||||||
|
|
||||||
- Major redesign switching to asyncio as the base implementation (See the [Migration Guide](https://microdot.readthedocs.io/en/stable/migrating.html) in the docs for details) [#186](https://github.com/miguelgrinberg/microdot/issues/186) ([commit](https://github.com/miguelgrinberg/microdot/commit/20ea305fe793eb206b52af9eb5c5f3c1e9f57dbb))
|
- Major redesign [#186](https://github.com/miguelgrinberg/microdot/issues/186) ([commit](https://github.com/miguelgrinberg/microdot/commit/20ea305fe793eb206b52af9eb5c5f3c1e9f57dbb))
|
||||||
|
- Code reorganization as a `microdot` package
|
||||||
|
- Asyncio is now the core implementation
|
||||||
|
- New support for Server-Sent Events (SSE)
|
||||||
|
- Several extensions redesigned
|
||||||
|
- Support for "partitioned" cookies
|
||||||
|
- [Cross-compiling and freezing](https://microdot.readthedocs.io/en/stable/freezing.html) guidance
|
||||||
|
- A [Migration Guide](https://microdot.readthedocs.io/en/stable/migrating.html) to help transition to version 2 from older releases
|
||||||
|
|
||||||
**Release 1.3.4** - 2023-11-08
|
**Release 1.3.4** - 2023-11-08
|
||||||
|
|
||||||
|
|||||||
@@ -32,5 +32,10 @@ describes the backwards incompatible changes that were made.
|
|||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- Documentation: [Latest](https://microdot.readthedocs.io/en/latest/) [Stable](https://microdot.readthedocs.io/en/stable/) [Legacy v1.x](https://microdot.readthedocs.io/en/v1/)
|
- Documentation
|
||||||
|
- [Stable](https://microdot.readthedocs.io/en/stable/)
|
||||||
|
- [Latest](https://microdot.readthedocs.io/en/latest/)
|
||||||
|
- Still using version 1?
|
||||||
|
- [Code](https://github.com/miguelgrinberg/microdot/tree/v1)
|
||||||
|
- [Documentation](https://microdot.readthedocs.io/en/v1/)
|
||||||
- [Change Log](https://github.com/miguelgrinberg/microdot/blob/main/CHANGES.md)
|
- [Change Log](https://github.com/miguelgrinberg/microdot/blob/main/CHANGES.md)
|
||||||
|
|||||||
BIN
bin/micropython
BIN
bin/micropython
Binary file not shown.
@@ -129,11 +129,10 @@ are the asynchronous versions of these two methods.
|
|||||||
|
|
||||||
The default location from where templates are loaded is the *templates*
|
The default location from where templates are loaded is the *templates*
|
||||||
subdirectory. This location can be changed with the
|
subdirectory. This location can be changed with the
|
||||||
:func:`init_templates <microdot.utemplate.init_templates>` function::
|
:func:`Template.initialize <microdot.utemplate.Template.initialize>` class
|
||||||
|
method::
|
||||||
|
|
||||||
from microdot.utemplate import init_templates
|
Template.initialize('my_templates')
|
||||||
|
|
||||||
init_templates('my_templates')
|
|
||||||
|
|
||||||
Using the Jinja Engine
|
Using the Jinja Engine
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -174,17 +173,15 @@ method, which returns a generator instead of a string.
|
|||||||
|
|
||||||
The default location from where templates are loaded is the *templates*
|
The default location from where templates are loaded is the *templates*
|
||||||
subdirectory. This location can be changed with the
|
subdirectory. This location can be changed with the
|
||||||
:func:`init_templates <microdot.utemplate.init_templates>` function::
|
:func:`Template.initialize <microdot.jinja.Template.initialize>` class method::
|
||||||
|
|
||||||
from microdot.jinja import init_templates
|
Template.initialize('my_templates')
|
||||||
|
|
||||||
init_templates('my_templates')
|
The ``initialize()`` method also accepts ``enable_async`` argument, which
|
||||||
|
|
||||||
The ``init_templates()`` function also accepts ``enable_async`` argument, which
|
|
||||||
can be set to ``True`` if asynchronous rendering of templates is desired. If
|
can be set to ``True`` if asynchronous rendering of templates is desired. If
|
||||||
this option is enabled, then the
|
this option is enabled, then the
|
||||||
:func:`render_async() <microdot.utemplate.Template.render_async>` and
|
:func:`render_async() <microdot.jinja.Template.render_async>` and
|
||||||
:func:`generate_async() <microdot.utemplate.Template.generate_async>` methods
|
:func:`generate_async() <microdot.jinja.Template.generate_async>` methods
|
||||||
must be used.
|
must be used.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ match and the route will not be called.
|
|||||||
A special type ``path`` can be used to capture the remainder of the path as a
|
A special type ``path`` can be used to capture the remainder of the path as a
|
||||||
single argument. The difference between an argument of type ``path`` and one of
|
single argument. The difference between an argument of type ``path`` and one of
|
||||||
type ``string`` is that the latter stops capturing when a ``/`` appears in the
|
type ``string`` is that the latter stops capturing when a ``/`` appears in the
|
||||||
URL.
|
URL::
|
||||||
|
|
||||||
@app.get('/tests/<path:path>')
|
@app.get('/tests/<path:path>')
|
||||||
async def get_test(request, path):
|
async def get_test(request, path):
|
||||||
@@ -462,7 +462,7 @@ the sub-applications to build the larger combined application::
|
|||||||
from customers import customers_app
|
from customers import customers_app
|
||||||
from orders import orders_app
|
from orders import orders_app
|
||||||
|
|
||||||
async def create_app():
|
def create_app():
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
app.mount(customers_app, url_prefix='/customers')
|
app.mount(customers_app, url_prefix='/customers')
|
||||||
app.mount(orders_app, url_prefix='/orders')
|
app.mount(orders_app, url_prefix='/orders')
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ extension.
|
|||||||
Any applications built using the asyncio extension will need to update their
|
Any applications built using the asyncio extension will need to update their
|
||||||
imports from this::
|
imports from this::
|
||||||
|
|
||||||
from microdot.asyncio import Microdot
|
from microdot_asyncio import Microdot
|
||||||
|
|
||||||
to this::
|
to this::
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ as a single string::
|
|||||||
|
|
||||||
Streamed templates also have an asynchronous version::
|
Streamed templates also have an asynchronous version::
|
||||||
|
|
||||||
return await Template('index.html').generate_async(title='Home')
|
return Template('index.html').generate_async(title='Home')
|
||||||
|
|
||||||
Class-based user sessions
|
Class-based user sessions
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@@ -138,5 +138,8 @@ deployed with standard WSGI servers such as Gunicorn.
|
|||||||
|
|
||||||
WebSocket support when using the WSGI extension is enabled when using a
|
WebSocket support when using the WSGI extension is enabled when using a
|
||||||
compatible web server. At this time only Gunicorn is supported for WebSocket.
|
compatible web server. At this time only Gunicorn is supported for WebSocket.
|
||||||
|
Given that WebSocket support is asynchronous, it would be better to switch to
|
||||||
|
the ASGI extension, which has full support for WebSocket as defined in the ASGI
|
||||||
|
specification.
|
||||||
|
|
||||||
As before, the WSGI extension is not available under MicroPython.
|
As before, the WSGI extension is not available under MicroPython.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.jinja import template, init_templates
|
from microdot.jinja import Template
|
||||||
|
|
||||||
init_templates('templates', enable_async=True)
|
Template.initialize('templates', enable_async=True)
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return await template('index.html').render_async(name=name)
|
return await Template('index.html').render_async(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.jinja import template
|
from microdot.jinja import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -7,12 +7,12 @@ Response.default_content_type = 'text/html'
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def index(req):
|
async def index(req):
|
||||||
return template('page1.html').render(page='Page 1')
|
return Template('page1.html').render(page='Page 1')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/page2')
|
@app.route('/page2')
|
||||||
async def page2(req):
|
async def page2(req):
|
||||||
return template('page2.html').render(page='Page 2')
|
return Template('page2.html').render(page='Page 2')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.jinja import template
|
from microdot.jinja import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').render(name=name)
|
return Template('index.html').render(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot.asgi import Microdot, Response
|
from microdot.asgi import Microdot, Response
|
||||||
from microdot.jinja import template
|
from microdot.jinja import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').render(name=name)
|
return Template('index.html').render(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot.wsgi import Microdot, Response
|
from microdot.wsgi import Microdot, Response
|
||||||
from microdot.jinja import template
|
from microdot.jinja import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').render(name=name)
|
return Template('index.html').render(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.jinja import template
|
from microdot.jinja import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').generate(name=name)
|
return Template('index.html').generate(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.utemplate import template
|
from microdot.utemplate import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return await template('index.html').render_async(name=name)
|
return await Template('index.html').render_async(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.utemplate import template
|
from microdot.utemplate import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -7,12 +7,12 @@ Response.default_content_type = 'text/html'
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def index(req):
|
async def index(req):
|
||||||
return template('page1.html').render(page='Page 1')
|
return Template('page1.html').render(page='Page 1')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/page2')
|
@app.route('/page2')
|
||||||
async def page2(req):
|
async def page2(req):
|
||||||
return template('page2.html').render(page='Page 2')
|
return Template('page2.html').render(page='Page 2')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.utemplate import template
|
from microdot.utemplate import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').render(name=name)
|
return Template('index.html').render(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot.asgi import Microdot, Response
|
from microdot.asgi import Microdot, Response
|
||||||
from microdot.utemplate import template
|
from microdot.utemplate import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').render(name=name)
|
return Template('index.html').render(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot.wsgi import Microdot, Response
|
from microdot.wsgi import Microdot, Response
|
||||||
from microdot.utemplate import template
|
from microdot.utemplate import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').render(name=name)
|
return Template('index.html').render(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from microdot import Microdot, Response
|
from microdot import Microdot, Response
|
||||||
from microdot.utemplate import template
|
from microdot.utemplate import Template
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
Response.default_content_type = 'text/html'
|
Response.default_content_type = 'text/html'
|
||||||
@@ -10,7 +10,7 @@ async def index(req):
|
|||||||
name = None
|
name = None
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
name = req.form.get('name')
|
name = req.form.get('name')
|
||||||
return template('index.html').generate(name=name)
|
return Template('index.html').generate(name=name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
from utime import *
|
|
||||||
from micropython import const
|
|
||||||
|
|
||||||
_TS_YEAR = const(0)
|
|
||||||
_TS_MON = const(1)
|
|
||||||
_TS_MDAY = const(2)
|
|
||||||
_TS_HOUR = const(3)
|
|
||||||
_TS_MIN = const(4)
|
|
||||||
_TS_SEC = const(5)
|
|
||||||
_TS_WDAY = const(6)
|
|
||||||
_TS_YDAY = const(7)
|
|
||||||
_TS_ISDST = const(8)
|
|
||||||
|
|
||||||
_WDAY = const(("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
|
|
||||||
_MDAY = const(
|
|
||||||
(
|
|
||||||
"January",
|
|
||||||
"February",
|
|
||||||
"March",
|
|
||||||
"April",
|
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def strftime(datefmt, ts):
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
fmtsp = False
|
|
||||||
ftime = StringIO()
|
|
||||||
for k in datefmt:
|
|
||||||
if fmtsp:
|
|
||||||
if k == "a":
|
|
||||||
ftime.write(_WDAY[ts[_TS_WDAY]][0:3])
|
|
||||||
elif k == "A":
|
|
||||||
ftime.write(_WDAY[ts[_TS_WDAY]])
|
|
||||||
elif k == "b":
|
|
||||||
ftime.write(_MDAY[ts[_TS_MON] - 1][0:3])
|
|
||||||
elif k == "B":
|
|
||||||
ftime.write(_MDAY[ts[_TS_MON] - 1])
|
|
||||||
elif k == "d":
|
|
||||||
ftime.write("%02d" % ts[_TS_MDAY])
|
|
||||||
elif k == "H":
|
|
||||||
ftime.write("%02d" % ts[_TS_HOUR])
|
|
||||||
elif k == "I":
|
|
||||||
ftime.write("%02d" % (ts[_TS_HOUR] % 12))
|
|
||||||
elif k == "j":
|
|
||||||
ftime.write("%03d" % ts[_TS_YDAY])
|
|
||||||
elif k == "m":
|
|
||||||
ftime.write("%02d" % ts[_TS_MON])
|
|
||||||
elif k == "M":
|
|
||||||
ftime.write("%02d" % ts[_TS_MIN])
|
|
||||||
elif k == "P":
|
|
||||||
ftime.write("AM" if ts[_TS_HOUR] < 12 else "PM")
|
|
||||||
elif k == "S":
|
|
||||||
ftime.write("%02d" % ts[_TS_SEC])
|
|
||||||
elif k == "w":
|
|
||||||
ftime.write(str(ts[_TS_WDAY]))
|
|
||||||
elif k == "y":
|
|
||||||
ftime.write("%02d" % (ts[_TS_YEAR] % 100))
|
|
||||||
elif k == "Y":
|
|
||||||
ftime.write(str(ts[_TS_YEAR]))
|
|
||||||
else:
|
|
||||||
ftime.write(k)
|
|
||||||
fmtsp = False
|
|
||||||
elif k == "%":
|
|
||||||
fmtsp = True
|
|
||||||
else:
|
|
||||||
ftime.write(k)
|
|
||||||
val = ftime.getvalue()
|
|
||||||
ftime.close()
|
|
||||||
return val
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "microdot"
|
name = "microdot"
|
||||||
version = "2.0.0"
|
version = "2.0.2"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },
|
{ name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,36 +3,37 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|||||||
_jinja_env = None
|
_jinja_env = None
|
||||||
|
|
||||||
|
|
||||||
def init_templates(template_dir='templates', enable_async=False, **kwargs):
|
|
||||||
"""Initialize the templating subsystem.
|
|
||||||
|
|
||||||
:param template_dir: the directory where templates are stored. This
|
|
||||||
argument is optional. The default is to load templates
|
|
||||||
from a *templates* subdirectory.
|
|
||||||
:param enable_async: set to ``True`` to enable the async rendering engine
|
|
||||||
in Jinja, and the ``render_async()`` and
|
|
||||||
``generate_async()`` methods.
|
|
||||||
:param kwargs: any additional options to be passed to Jinja's
|
|
||||||
``Environment`` class.
|
|
||||||
"""
|
|
||||||
global _jinja_env
|
|
||||||
_jinja_env = Environment(
|
|
||||||
loader=FileSystemLoader(template_dir),
|
|
||||||
autoescape=select_autoescape(),
|
|
||||||
enable_async=enable_async,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Template:
|
class Template:
|
||||||
"""A template object.
|
"""A template object.
|
||||||
|
|
||||||
:param template: The filename of the template to render, relative to the
|
:param template: The filename of the template to render, relative to the
|
||||||
configured template directory.
|
configured template directory.
|
||||||
"""
|
"""
|
||||||
|
@classmethod
|
||||||
|
def initialize(cls, template_dir='templates', enable_async=False,
|
||||||
|
**kwargs):
|
||||||
|
"""Initialize the templating subsystem.
|
||||||
|
|
||||||
|
:param template_dir: the directory where templates are stored. This
|
||||||
|
argument is optional. The default is to load
|
||||||
|
templates from a *templates* subdirectory.
|
||||||
|
:param enable_async: set to ``True`` to enable the async rendering
|
||||||
|
engine in Jinja, and the ``render_async()`` and
|
||||||
|
``generate_async()`` methods.
|
||||||
|
:param kwargs: any additional options to be passed to Jinja's
|
||||||
|
``Environment`` class.
|
||||||
|
"""
|
||||||
|
global _jinja_env
|
||||||
|
_jinja_env = Environment(
|
||||||
|
loader=FileSystemLoader(template_dir),
|
||||||
|
autoescape=select_autoescape(),
|
||||||
|
enable_async=enable_async,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, template):
|
def __init__(self, template):
|
||||||
if _jinja_env is None: # pragma: no cover
|
if _jinja_env is None: # pragma: no cover
|
||||||
init_templates()
|
self.initialize()
|
||||||
#: The name of the template
|
#: The name of the template
|
||||||
self.name = template
|
self.name = template
|
||||||
self.template = _jinja_env.get_template(template)
|
self.template = _jinja_env.get_template(template)
|
||||||
|
|||||||
@@ -595,7 +595,7 @@ class Response:
|
|||||||
if expires:
|
if expires:
|
||||||
if isinstance(expires, str):
|
if isinstance(expires, str):
|
||||||
http_cookie += '; Expires=' + expires
|
http_cookie += '; Expires=' + expires
|
||||||
else:
|
else: # pragma: no cover
|
||||||
http_cookie += '; Expires=' + time.strftime(
|
http_cookie += '; Expires=' + time.strftime(
|
||||||
'%a, %d %b %Y %H:%M:%S GMT', expires.timetuple())
|
'%a, %d %b %Y %H:%M:%S GMT', expires.timetuple())
|
||||||
if max_age:
|
if max_age:
|
||||||
|
|||||||
@@ -8,13 +8,23 @@ class SSE:
|
|||||||
self.queue = []
|
self.queue = []
|
||||||
|
|
||||||
async def send(self, data, event=None):
|
async def send(self, data, event=None):
|
||||||
|
"""Send an event to the client.
|
||||||
|
|
||||||
|
:param data: the data to send. It can be given as a string, bytes, dict
|
||||||
|
or list. Dictionaries and lists are serialized to JSON.
|
||||||
|
Any other types are converted to string before sending.
|
||||||
|
:param event: an optional event name, to send along with the data. If
|
||||||
|
given, it must be a string.
|
||||||
|
"""
|
||||||
if isinstance(data, (dict, list)):
|
if isinstance(data, (dict, list)):
|
||||||
data = json.dumps(data)
|
data = json.dumps(data).encode()
|
||||||
elif not isinstance(data, str):
|
elif isinstance(data, str):
|
||||||
data = str(data)
|
data = data.encode()
|
||||||
data = f'data: {data}\n\n'
|
elif not isinstance(data, bytes):
|
||||||
|
data = str(data).encode()
|
||||||
|
data = b'data: ' + data + b'\n\n'
|
||||||
if event:
|
if event:
|
||||||
data = f'event: {event}\n{data}'
|
data = b'event: ' + event.encode() + b'\n' + data
|
||||||
self.queue.append(data)
|
self.queue.append(data)
|
||||||
self.event.set()
|
self.event.set()
|
||||||
|
|
||||||
|
|||||||
@@ -3,30 +3,32 @@ from utemplate import recompile
|
|||||||
_loader = None
|
_loader = None
|
||||||
|
|
||||||
|
|
||||||
def init_templates(template_dir='templates', loader_class=recompile.Loader):
|
|
||||||
"""Initialize the templating subsystem.
|
|
||||||
|
|
||||||
:param template_dir: the directory where templates are stored. This
|
|
||||||
argument is optional. The default is to load templates
|
|
||||||
from a *templates* subdirectory.
|
|
||||||
:param loader_class: the ``utemplate.Loader`` class to use when loading
|
|
||||||
templates. This argument is optional. The default is
|
|
||||||
the ``recompile.Loader`` class, which automatically
|
|
||||||
recompiles templates when they change.
|
|
||||||
"""
|
|
||||||
global _loader
|
|
||||||
_loader = loader_class(None, template_dir)
|
|
||||||
|
|
||||||
|
|
||||||
class Template:
|
class Template:
|
||||||
"""A template object.
|
"""A template object.
|
||||||
|
|
||||||
:param template: The filename of the template to render, relative to the
|
:param template: The filename of the template to render, relative to the
|
||||||
configured template directory.
|
configured template directory.
|
||||||
"""
|
"""
|
||||||
|
@classmethod
|
||||||
|
def initialize(cls, template_dir='templates',
|
||||||
|
loader_class=recompile.Loader):
|
||||||
|
"""Initialize the templating subsystem.
|
||||||
|
|
||||||
|
:param template_dir: the directory where templates are stored. This
|
||||||
|
argument is optional. The default is to load
|
||||||
|
templates from a *templates* subdirectory.
|
||||||
|
:param loader_class: the ``utemplate.Loader`` class to use when loading
|
||||||
|
templates. This argument is optional. The default
|
||||||
|
is the ``recompile.Loader`` class, which
|
||||||
|
automatically recompiles templates when they
|
||||||
|
change.
|
||||||
|
"""
|
||||||
|
global _loader
|
||||||
|
_loader = loader_class(None, template_dir)
|
||||||
|
|
||||||
def __init__(self, template):
|
def __init__(self, template):
|
||||||
if _loader is None: # pragma: no cover
|
if _loader is None: # pragma: no cover
|
||||||
init_templates()
|
self.initialize()
|
||||||
#: The name of the template
|
#: The name of the template
|
||||||
self.name = template
|
self.name = template
|
||||||
self.template = _loader.load(template)
|
self.template = _loader.load(template)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import asyncio
|
|||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from microdot import Microdot
|
from microdot import Microdot
|
||||||
from microdot.jinja import Template, init_templates
|
from microdot.jinja import Template
|
||||||
from microdot.test_client import TestClient
|
from microdot.test_client import TestClient
|
||||||
|
|
||||||
init_templates('tests/templates')
|
Template.initialize('tests/templates')
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.implementation.name == 'micropython',
|
@unittest.skipIf(sys.implementation.name == 'micropython',
|
||||||
@@ -49,7 +49,7 @@ class TestJinja(unittest.TestCase):
|
|||||||
self.assertEqual(res.body, b'Hello, foo!')
|
self.assertEqual(res.body, b'Hello, foo!')
|
||||||
|
|
||||||
def test_render_async_template_in_app(self):
|
def test_render_async_template_in_app(self):
|
||||||
init_templates('tests/templates', enable_async=True)
|
Template.initialize('tests/templates', enable_async=True)
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
|
|
||||||
@@ -62,10 +62,10 @@ class TestJinja(unittest.TestCase):
|
|||||||
self.assertEqual(res.status_code, 200)
|
self.assertEqual(res.status_code, 200)
|
||||||
self.assertEqual(res.body, b'Hello, foo!')
|
self.assertEqual(res.body, b'Hello, foo!')
|
||||||
|
|
||||||
init_templates('tests/templates')
|
Template.initialize('tests/templates')
|
||||||
|
|
||||||
def test_generate_async_template_in_app(self):
|
def test_generate_async_template_in_app(self):
|
||||||
init_templates('tests/templates', enable_async=True)
|
Template.initialize('tests/templates', enable_async=True)
|
||||||
|
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
|
|
||||||
@@ -78,4 +78,4 @@ class TestJinja(unittest.TestCase):
|
|||||||
self.assertEqual(res.status_code, 200)
|
self.assertEqual(res.status_code, 200)
|
||||||
self.assertEqual(res.body, b'Hello, foo!')
|
self.assertEqual(res.body, b'Hello, foo!')
|
||||||
|
|
||||||
init_templates('tests/templates')
|
Template.initialize('tests/templates')
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime
|
|
||||||
import unittest
|
import unittest
|
||||||
from microdot import Response
|
from microdot import Response
|
||||||
from tests.mock_socket import FakeStreamAsync
|
from tests.mock_socket import FakeStreamAsync
|
||||||
@@ -186,12 +185,12 @@ class TestResponse(unittest.TestCase):
|
|||||||
res.set_cookie('foo2', 'bar2', path='/', partitioned=True)
|
res.set_cookie('foo2', 'bar2', path='/', partitioned=True)
|
||||||
res.set_cookie('foo3', 'bar3', domain='example.com:1234')
|
res.set_cookie('foo3', 'bar3', domain='example.com:1234')
|
||||||
res.set_cookie('foo4', 'bar4',
|
res.set_cookie('foo4', 'bar4',
|
||||||
expires=datetime(2019, 11, 5, 2, 23, 54))
|
expires='Tue, 05 Nov 2019 02:23:54 GMT')
|
||||||
res.set_cookie('foo5', 'bar5', max_age=123,
|
res.set_cookie('foo5', 'bar5', max_age=123,
|
||||||
expires='Thu, 01 Jan 1970 00:00:00 GMT')
|
expires='Thu, 01 Jan 1970 00:00:00 GMT')
|
||||||
res.set_cookie('foo6', 'bar6', secure=True, http_only=True)
|
res.set_cookie('foo6', 'bar6', secure=True, http_only=True)
|
||||||
res.set_cookie('foo7', 'bar7', path='/foo', domain='example.com:1234',
|
res.set_cookie('foo7', 'bar7', path='/foo', domain='example.com:1234',
|
||||||
expires=datetime(2019, 11, 5, 2, 23, 54), max_age=123,
|
expires='Tue, 05 Nov 2019 02:23:54 GMT', max_age=123,
|
||||||
secure=True, http_only=True)
|
secure=True, http_only=True)
|
||||||
res.delete_cookie('foo8', http_only=True)
|
res.delete_cookie('foo8', http_only=True)
|
||||||
self.assertEqual(res.headers, {'Set-Cookie': [
|
self.assertEqual(res.headers, {'Set-Cookie': [
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class TestWebSocket(unittest.TestCase):
|
|||||||
await sse.send({'foo': 'bar'})
|
await sse.send({'foo': 'bar'})
|
||||||
await sse.send([42, 'foo', 'bar'])
|
await sse.send([42, 'foo', 'bar'])
|
||||||
await sse.send(ValueError('foo'))
|
await sse.send(ValueError('foo'))
|
||||||
|
await sse.send(b'foo')
|
||||||
|
|
||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
response = self._run(client.get('/sse'))
|
response = self._run(client.get('/sse'))
|
||||||
@@ -35,4 +36,5 @@ class TestWebSocket(unittest.TestCase):
|
|||||||
'event: test\ndata: bar\n\n'
|
'event: test\ndata: bar\n\n'
|
||||||
'data: {"foo": "bar"}\n\n'
|
'data: {"foo": "bar"}\n\n'
|
||||||
'data: [42, "foo", "bar"]\n\n'
|
'data: [42, "foo", "bar"]\n\n'
|
||||||
|
'data: foo\n\n'
|
||||||
'data: foo\n\n'))
|
'data: foo\n\n'))
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import asyncio
|
|||||||
import unittest
|
import unittest
|
||||||
from microdot import Microdot
|
from microdot import Microdot
|
||||||
from microdot.test_client import TestClient
|
from microdot.test_client import TestClient
|
||||||
from microdot.utemplate import Template, init_templates
|
from microdot.utemplate import Template
|
||||||
|
|
||||||
init_templates('tests/templates')
|
Template.initialize('tests/templates')
|
||||||
|
|
||||||
|
|
||||||
class TestUTemplate(unittest.TestCase):
|
class TestUTemplate(unittest.TestCase):
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG VERSION=master
|
||||||
|
ENV VERSION=$VERSION
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y build-essential libffi-dev git pkg-config python3 && \
|
apt-get install -y build-essential libffi-dev git pkg-config python3 && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
git clone https://github.com/micropython/micropython.git && \
|
git clone https://github.com/micropython/micropython.git && \
|
||||||
cd micropython && \
|
cd micropython && \
|
||||||
|
git checkout $VERSION && \
|
||||||
git submodule update --init && \
|
git submodule update --init && \
|
||||||
cd mpy-cross && \
|
cd mpy-cross && \
|
||||||
make && \
|
make && \
|
||||||
cd .. && \
|
cd .. && \
|
||||||
cd ports/unix && \
|
cd ports/unix && \
|
||||||
make && \
|
make && \
|
||||||
make test && \
|
|
||||||
make install && \
|
make install && \
|
||||||
apt-get purge --auto-remove -y build-essential libffi-dev git pkg-config python3 && \
|
apt-get purge --auto-remove -y build-essential libffi-dev git pkg-config python3 && \
|
||||||
cd ../../.. && \
|
cd ../../.. && \
|
||||||
rm -rf micropython
|
rm -rf micropython
|
||||||
|
|
||||||
CMD ["/usr/local/bin/micropython"]
|
CMD ["/usr/local/bin/micropython"]
|
||||||
|
|
||||||
|
|||||||
24
tools/Dockerfile.circuitpython
Normal file
24
tools/Dockerfile.circuitpython
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG VERSION=main
|
||||||
|
ENV VERSION=$VERSION
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y build-essential libffi-dev git pkg-config python3 && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
git clone https://github.com/adafruit/circuitpython.git && \
|
||||||
|
cd circuitpython && \
|
||||||
|
git checkout $VERSION && \
|
||||||
|
git submodule update --init lib tools frozen && \
|
||||||
|
cd mpy-cross && \
|
||||||
|
make && \
|
||||||
|
cd .. && \
|
||||||
|
cd ports/unix && \
|
||||||
|
make && \
|
||||||
|
make install && \
|
||||||
|
apt-get purge --auto-remove -y build-essential libffi-dev git pkg-config python3 && \
|
||||||
|
cd ../../.. && \
|
||||||
|
rm -rf circuitpython
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/micropython"]
|
||||||
11
tools/update-circuitpython.sh
Executable file
11
tools/update-circuitpython.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# this script updates the micropython binary in the /bin directory that is
|
||||||
|
# used to run unit tests under GitHub Actions builds
|
||||||
|
|
||||||
|
DOCKER=${DOCKER:-docker}
|
||||||
|
VERSION=${1:-main}
|
||||||
|
|
||||||
|
$DOCKER build -f Dockerfile.circuitpython --build-arg VERSION=$VERSION -t circuitpython .
|
||||||
|
$DOCKER create -t --name dummy-circuitpython circuitpython
|
||||||
|
$DOCKER cp dummy-circuitpython:/usr/local/bin/micropython ../bin/circuitpython
|
||||||
|
$DOCKER rm dummy-circuitpython
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
# used to run unit tests under GitHub Actions builds
|
# used to run unit tests under GitHub Actions builds
|
||||||
|
|
||||||
DOCKER=${DOCKER:-docker}
|
DOCKER=${DOCKER:-docker}
|
||||||
|
VERSION=${1:-master}
|
||||||
|
|
||||||
$DOCKER build -t micropython .
|
$DOCKER build --build-arg VERSION=$VERSION -t micropython .
|
||||||
$DOCKER create -it --name dummy-micropython micropython
|
$DOCKER create -it --name dummy-micropython micropython
|
||||||
$DOCKER cp dummy-micropython:/usr/local/bin/micropython ../bin/micropython
|
$DOCKER cp dummy-micropython:/usr/local/bin/micropython ../bin/micropython
|
||||||
$DOCKER rm dummy-micropython
|
$DOCKER rm dummy-micropython
|
||||||
|
|||||||
6
tox.ini
6
tox.ini
@@ -1,5 +1,5 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist=flake8,py38,py39,py310,py311,py312,upy,benchmark
|
envlist=flake8,py38,py39,py310,py311,py312,upy,cpy,benchmark
|
||||||
skipsdist=True
|
skipsdist=True
|
||||||
skip_missing_interpreters=True
|
skip_missing_interpreters=True
|
||||||
|
|
||||||
@@ -29,6 +29,10 @@ setenv=
|
|||||||
allowlist_externals=sh
|
allowlist_externals=sh
|
||||||
commands=sh -c "bin/micropython run_tests.py"
|
commands=sh -c "bin/micropython run_tests.py"
|
||||||
|
|
||||||
|
[testenv:cpy]
|
||||||
|
allowlist_externals=sh
|
||||||
|
commands=sh -c "bin/circuitpython run_tests.py"
|
||||||
|
|
||||||
[testenv:upy-mac]
|
[testenv:upy-mac]
|
||||||
allowlist_externals=micropython
|
allowlist_externals=micropython
|
||||||
commands=micropython run_tests.py
|
commands=micropython run_tests.py
|
||||||
|
|||||||
Reference in New Issue
Block a user