Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5061145f5c | ||
|
|
122c638bae | ||
|
|
bd74bcab74 | ||
|
|
5cd3ace516 | ||
|
|
da32f23e35 | ||
|
|
0641466faa | ||
|
|
dd3fc20507 | ||
|
|
46963ba464 | ||
|
|
1a8db51cb3 | ||
|
|
d903c42370 | ||
|
|
8b4ebbd953 | ||
|
|
a82ed55f56 | ||
|
|
ac87f0542f |
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -2,10 +2,10 @@ name: build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
lint:
|
||||
name: lint
|
||||
|
||||
12
CHANGES.md
12
CHANGES.md
@@ -1,5 +1,17 @@
|
||||
# Microdot change log
|
||||
|
||||
**Release 0.6.0** - 2021-08-11
|
||||
|
||||
- Better handling of content types in form and json methods [#24](https://github.com/miguelgrinberg/microdot/issues/24) ([commit](https://github.com/miguelgrinberg/microdot/commit/da32f23e35f871470a40638e7000e84b0ff6d17f))
|
||||
- Accept a custom reason phrase for the HTTP response [#25](https://github.com/miguelgrinberg/microdot/issues/25) ([commit](https://github.com/miguelgrinberg/microdot/commit/bd74bcab74f283c89aadffc8f9c20d6ff0f771ce))
|
||||
- Make mime type check for form submissions more robust ([commit](https://github.com/miguelgrinberg/microdot/commit/dd3fc20507715a23d0fa6fa3aae3715c8fbc0351))
|
||||
- Copy client headers to avoid write back [#23](https://github.com/miguelgrinberg/microdot/issues/23) ([commit](https://github.com/miguelgrinberg/microdot/commit/0641466faa9dda0c54f78939ac05993c0812e84a)) (thanks **Mark Blakeney**!)
|
||||
- Work around a bug in uasyncio's create_server() function ([commit](https://github.com/miguelgrinberg/microdot/commit/46963ba4644d7abc8dc653c99bc76222af526964))
|
||||
- More unit tests ([commit](https://github.com/miguelgrinberg/microdot/commit/5cd3ace5166ec549579b0b1149ae3d7be195974a))
|
||||
- Installation instructions ([commit](https://github.com/miguelgrinberg/microdot/commit/1a8db51cb3754308da6dcc227512dcdeb4ce4557))
|
||||
- Run tests with pytest ([commit](https://github.com/miguelgrinberg/microdot/commit/8b4ebbd9535b3c083fb2a955284609acba07f05e))
|
||||
- Deprecated the microdot-asyncio package ([commit](https://github.com/miguelgrinberg/microdot/commit/a82ed55f56e14fbcea93e8171af86ab42657fa96))
|
||||
|
||||
**Release 0.5.0** - 2021-06-06
|
||||
|
||||
- [Documentation](https://microdot.readthedocs.io/en/latest/) site ([commit](https://github.com/miguelgrinberg/microdot/commit/12cd60305b7b48ab151da52661fc5988684dbcd8))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# microdot
|
||||
[](https://github.com/miguelgrinberg/microdot/actions) [](https://codecov.io/gh/miguelgrinberg/microdot)
|
||||
[](https://github.com/miguelgrinberg/microdot/actions) [](https://codecov.io/gh/miguelgrinberg/microdot)
|
||||
|
||||
A minimalistic Python web framework for microcontrollers inspired by Flask
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../microdot'))
|
||||
sys.path.insert(1, os.path.abspath('../microdot-asyncio'))
|
||||
sys.path.insert(0, os.path.abspath('../src'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
Microdot
|
||||
========
|
||||
|
||||
Microdot is a minimalistic Python web framework for microcontrollers inspired
|
||||
by `Flask <https://flask.palletsprojects.com/>`_, and designed to run on
|
||||
Microdot is a minimalistic Python web framework inspired by
|
||||
`Flask <https://flask.palletsprojects.com/>`_, and designed to run on
|
||||
systems with limited resources such as microcontrollers. It runs on standard
|
||||
Python and on `MicroPython <https://micropython.org>`_.
|
||||
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
Installation
|
||||
------------
|
||||
|
||||
Microdot can be installed with ``pip``::
|
||||
|
||||
pip install microdot
|
||||
|
||||
For platforms that do not support or cannot run ``pip``, you can also manually
|
||||
copy and install the ``microdot.py`` and ``microdot_asyncio.py`` source files.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = microdot-asyncio
|
||||
version = 0.4.0
|
||||
version = 0.5.0
|
||||
author = Miguel Grinberg
|
||||
author_email = miguel.grinberg@gmail.com
|
||||
description = AsyncIO support for the Microdot web framework'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, 'microdot')
|
||||
sys.path.insert(1, 'microdot-asyncio')
|
||||
sys.path.insert(0, 'src')
|
||||
sys.path.insert(2, 'tests/libs')
|
||||
|
||||
import unittest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = microdot
|
||||
version = 0.5.0
|
||||
version = 0.6.0
|
||||
author = Miguel Grinberg
|
||||
author_email = miguel.grinberg@gmail.com
|
||||
description = The impossibly small web framework for MicroPython
|
||||
|
||||
@@ -235,7 +235,7 @@ class Request():
|
||||
"""
|
||||
# request line
|
||||
line = client_stream.readline().strip().decode()
|
||||
if not line: # pragma: no cover
|
||||
if not line:
|
||||
return None
|
||||
method, url, http_version = line.split()
|
||||
http_version = http_version.split('/', 1)[1]
|
||||
@@ -267,17 +267,23 @@ class Request():
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
if self.content_type != 'application/json':
|
||||
return None
|
||||
if self._json is None:
|
||||
if self.content_type is None:
|
||||
return None
|
||||
mime_type = self.content_type.split(';')[0]
|
||||
if mime_type != 'application/json':
|
||||
return None
|
||||
self._json = json.loads(self.body.decode())
|
||||
return self._json
|
||||
|
||||
@property
|
||||
def form(self):
|
||||
if self.content_type != 'application/x-www-form-urlencoded':
|
||||
return None
|
||||
if self._form is None:
|
||||
if self.content_type is None:
|
||||
return None
|
||||
mime_type = self.content_type.split(';')[0]
|
||||
if mime_type != 'application/x-www-form-urlencoded':
|
||||
return None
|
||||
self._form = self._parse_urlencoded(self.body.decode())
|
||||
return self._form
|
||||
|
||||
@@ -303,9 +309,10 @@ class Response():
|
||||
}
|
||||
send_file_buffer_size = 1024
|
||||
|
||||
def __init__(self, body='', status_code=200, headers=None):
|
||||
def __init__(self, body='', status_code=200, headers=None, reason=None):
|
||||
self.status_code = status_code
|
||||
self.headers = headers or {}
|
||||
self.headers = headers.copy() if headers else {}
|
||||
self.reason = reason
|
||||
if isinstance(body, (dict, list)):
|
||||
self.body = json.dumps(body).encode()
|
||||
self.headers['Content-Type'] = 'application/json'
|
||||
@@ -358,9 +365,10 @@ class Response():
|
||||
self.complete()
|
||||
|
||||
# status code
|
||||
reason = self.reason if self.reason is not None else \
|
||||
('OK' if self.status_code == 200 else 'N/A')
|
||||
stream.write('HTTP/1.0 {status_code} {reason}\r\n'.format(
|
||||
status_code=self.status_code,
|
||||
reason='OK' if self.status_code == 200 else 'N/A').encode())
|
||||
status_code=self.status_code, reason=reason).encode())
|
||||
|
||||
# headers
|
||||
for header, value in self.headers.items():
|
||||
|
||||
@@ -74,9 +74,10 @@ class Response(BaseResponse):
|
||||
self.complete()
|
||||
|
||||
# status code
|
||||
reason = self.reason if self.reason is not None else \
|
||||
('OK' if self.status_code == 200 else 'N/A')
|
||||
await stream.awrite('HTTP/1.0 {status_code} {reason}\r\n'.format(
|
||||
status_code=self.status_code,
|
||||
reason='OK' if self.status_code == 200 else 'N/A').encode())
|
||||
status_code=self.status_code, reason=reason).encode())
|
||||
|
||||
# headers
|
||||
for header, value in self.headers.items():
|
||||
@@ -95,7 +96,7 @@ class Response(BaseResponse):
|
||||
await stream.awrite(buf)
|
||||
if len(buf) < self.send_file_buffer_size:
|
||||
break
|
||||
if hasattr(self.body, 'close'):
|
||||
if hasattr(self.body, 'close'): # pragma: no cover
|
||||
self.body.close()
|
||||
else:
|
||||
await stream.awrite(self.body)
|
||||
@@ -162,7 +163,14 @@ class Microdot(BaseMicrodot):
|
||||
host=host, port=port))
|
||||
|
||||
self.server = await asyncio.start_server(serve, host, port)
|
||||
while True:
|
||||
try:
|
||||
await self.server.wait_closed()
|
||||
break
|
||||
except AttributeError: # pragma: no cover
|
||||
# the task hasn't been initialized in the server object yet
|
||||
# wait a bit and try again
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
def run(self, host='0.0.0.0', port=5000, debug=False):
|
||||
"""Start the web server. This function does not normally return, as
|
||||
|
||||
@@ -65,6 +65,16 @@ class TestMicrodot(unittest.TestCase):
|
||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nbar'))
|
||||
|
||||
def test_empty_request(self):
|
||||
app = Microdot()
|
||||
|
||||
mock_socket.clear_requests()
|
||||
fd = mock_socket.FakeStream(b'\n')
|
||||
mock_socket._requests.append(fd)
|
||||
self._add_shutdown(app)
|
||||
app.run()
|
||||
assert fd.response == b''
|
||||
|
||||
def test_method_decorators(self):
|
||||
app = Microdot()
|
||||
|
||||
|
||||
@@ -112,6 +112,28 @@ class TestResponse(unittest.TestCase):
|
||||
self.assertEqual(res.headers, {'X-Test': 'Foo'})
|
||||
self.assertEqual(res.body, b'foo')
|
||||
|
||||
def test_create_with_reason(self):
|
||||
res = Response('foo', reason='ALL GOOD!')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.headers, {})
|
||||
self.assertEqual(res.reason, 'ALL GOOD!')
|
||||
self.assertEqual(res.body, b'foo')
|
||||
fd = io.BytesIO()
|
||||
res.write(fd)
|
||||
response = fd.getvalue()
|
||||
self.assertIn(b'HTTP/1.0 200 ALL GOOD!\r\n', response)
|
||||
|
||||
def test_create_with_status_and_reason(self):
|
||||
res = Response('not found', 404, reason='NOT FOUND')
|
||||
self.assertEqual(res.status_code, 404)
|
||||
self.assertEqual(res.headers, {})
|
||||
self.assertEqual(res.reason, 'NOT FOUND')
|
||||
self.assertEqual(res.body, b'not found')
|
||||
fd = io.BytesIO()
|
||||
res.write(fd)
|
||||
response = fd.getvalue()
|
||||
self.assertIn(b'HTTP/1.0 404 NOT FOUND\r\n', response)
|
||||
|
||||
def test_cookies(self):
|
||||
res = Response('ok')
|
||||
res.set_cookie('foo1', 'bar1')
|
||||
|
||||
@@ -76,6 +76,16 @@ class TestMicrodotAsync(unittest.TestCase):
|
||||
self.assertIn(b'Content-Type: text/plain\r\n', fd2.response)
|
||||
self.assertTrue(fd2.response.endswith(b'\r\n\r\nbar-async'))
|
||||
|
||||
def test_empty_request(self):
|
||||
app = Microdot()
|
||||
|
||||
mock_socket.clear_requests()
|
||||
fd = mock_socket.FakeStream(b'\n')
|
||||
mock_socket._requests.append(fd)
|
||||
self._add_shutdown(app)
|
||||
app.run()
|
||||
assert fd.response == b''
|
||||
|
||||
def test_before_after_request(self):
|
||||
app = Microdot()
|
||||
|
||||
|
||||
@@ -85,6 +85,26 @@ class TestResponseAsync(unittest.TestCase):
|
||||
self.assertIn(b'Content-Type: application/json\r\n', fd.response)
|
||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n[1, "2"]'))
|
||||
|
||||
def test_create_with_reason(self):
|
||||
res = Response('foo', reason='ALL GOOD!')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res.headers, {})
|
||||
self.assertEqual(res.reason, 'ALL GOOD!')
|
||||
self.assertEqual(res.body, b'foo')
|
||||
fd = FakeStreamAsync()
|
||||
_run(res.write(fd))
|
||||
self.assertIn(b'HTTP/1.0 200 ALL GOOD!\r\n', fd.response)
|
||||
|
||||
def test_create_with_status_and_reason(self):
|
||||
res = Response('not found', 404, reason='NOT FOUND')
|
||||
self.assertEqual(res.status_code, 404)
|
||||
self.assertEqual(res.headers, {})
|
||||
self.assertEqual(res.reason, 'NOT FOUND')
|
||||
self.assertEqual(res.body, b'not found')
|
||||
fd = FakeStreamAsync()
|
||||
_run(res.write(fd))
|
||||
self.assertIn(b'HTTP/1.0 404 NOT FOUND\r\n', fd.response)
|
||||
|
||||
def test_send_file(self):
|
||||
res = Response.send_file('tests/files/test.txt',
|
||||
content_type='text/html')
|
||||
|
||||
12
tox.ini
12
tox.ini
@@ -13,17 +13,17 @@ python =
|
||||
|
||||
[testenv]
|
||||
commands=
|
||||
pip install -e microdot
|
||||
pip install -e microdot-asyncio
|
||||
coverage run --branch --include="microdot*.py" -m unittest tests
|
||||
coverage report --show-missing
|
||||
deps=coverage
|
||||
pip install -e .
|
||||
pytest -p no:logging --cov=src --cov-branch --cov-report=term-missing
|
||||
deps=
|
||||
pytest
|
||||
pytest-cov
|
||||
|
||||
[testenv:flake8]
|
||||
deps=
|
||||
flake8
|
||||
commands=
|
||||
flake8 --ignore=W503 --exclude tests/libs microdot microdot-asyncio tests
|
||||
flake8 --ignore=W503 --exclude tests/libs src tests
|
||||
|
||||
[testenv:upy]
|
||||
whitelist_externals=sh
|
||||
|
||||
Reference in New Issue
Block a user