microdot 2.3.3__tar.gz → 2.3.4__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.3.3/src/microdot.egg-info → microdot-2.3.4}/PKG-INFO +1 -1
- {microdot-2.3.3 → microdot-2.3.4}/pyproject.toml +1 -1
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/asgi.py +5 -7
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/cors.py +2 -1
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/microdot.py +2 -2
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/multipart.py +8 -1
- {microdot-2.3.3 → microdot-2.3.4/src/microdot.egg-info}/PKG-INFO +1 -1
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_multipart.py +31 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_request.py +4 -3
- {microdot-2.3.3 → microdot-2.3.4}/tox.ini +3 -2
- {microdot-2.3.3 → microdot-2.3.4}/LICENSE +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/MANIFEST.in +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/README.md +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/Makefile +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/_static/css/custom.css +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/api.rst +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/conf.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/extensions.rst +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/freezing.rst +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/index.rst +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/intro.rst +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/make.bat +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/docs/migrating.rst +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/setup.cfg +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/__init__.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/auth.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/helpers.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/jinja.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/login.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/session.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/sse.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/test_client.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/utemplate.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/websocket.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot/wsgi.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot.egg-info/SOURCES.txt +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot.egg-info/dependency_links.txt +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot.egg-info/not-zip-safe +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot.egg-info/requires.txt +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/src/microdot.egg-info/top_level.txt +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/__init__.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.bin +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.css +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.gif +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.gz +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.html +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.jpg +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.js +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.json +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.png +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.txt +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/files/test.txt.gz +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/mock_socket.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/templates/hello.jinja.txt +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/templates/hello.utemplate.txt +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/templates/hello_utemplate_txt.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_asgi.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_auth.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_cors.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_end2end.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_jinja.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_login.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_microdot.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_multidict.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_response.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_session.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_sse.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_url_pattern.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_urlencode.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_utemplate.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_websocket.py +0 -0
- {microdot-2.3.3 → microdot-2.3.4}/tests/test_wsgi.py +0 -0
|
@@ -127,19 +127,17 @@ class Microdot(BaseMicrodot):
|
|
|
127
127
|
monitor_task = asyncio.ensure_future(cancel_monitor())
|
|
128
128
|
|
|
129
129
|
body_iter = res.body_iter().__aiter__()
|
|
130
|
-
res_body = b''
|
|
131
130
|
try:
|
|
132
|
-
res_body = await body_iter.__anext__()
|
|
133
131
|
while not cancelled: # pragma: no branch
|
|
134
|
-
|
|
132
|
+
res_body = await body_iter.__anext__()
|
|
135
133
|
await send({'type': 'http.response.body',
|
|
136
134
|
'body': res_body,
|
|
137
135
|
'more_body': True})
|
|
138
|
-
res_body = next_body
|
|
139
136
|
except StopAsyncIteration:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
pass
|
|
138
|
+
await send({'type': 'http.response.body',
|
|
139
|
+
'body': b'',
|
|
140
|
+
'more_body': False})
|
|
143
141
|
if hasattr(body_iter, 'aclose'): # pragma: no branch
|
|
144
142
|
await body_iter.aclose()
|
|
145
143
|
cancelled = True
|
|
@@ -104,7 +104,8 @@ class CORS:
|
|
|
104
104
|
|
|
105
105
|
def after_request(self, request, response):
|
|
106
106
|
saved_vary = response.headers.get('Vary')
|
|
107
|
-
|
|
107
|
+
if request: # pragma: no branch
|
|
108
|
+
response.headers.update(self.get_cors_headers(request))
|
|
108
109
|
if saved_vary and saved_vary != response.headers.get('Vary'):
|
|
109
110
|
response.headers['Vary'] = (
|
|
110
111
|
saved_vary + ', ' + response.headers['Vary'])
|
|
@@ -366,8 +366,8 @@ class Request:
|
|
|
366
366
|
self.content_type = self.headers['Content-Type']
|
|
367
367
|
if 'Cookie' in self.headers:
|
|
368
368
|
for cookie in self.headers['Cookie'].split(';'):
|
|
369
|
-
|
|
370
|
-
self.cookies[
|
|
369
|
+
c = cookie.strip().split('=', 1)
|
|
370
|
+
self.cookies[c[0]] = c[1] if len(c) > 1 else ''
|
|
371
371
|
|
|
372
372
|
self._body = body
|
|
373
373
|
self.body_used = False
|
|
@@ -31,7 +31,10 @@ class FormDataIter:
|
|
|
31
31
|
the next iteration, as the internal stream stored in ``FileUpload``
|
|
32
32
|
instances is invalidated at the end of the iteration.
|
|
33
33
|
"""
|
|
34
|
-
#: The size of the buffer used to read chunks of the request body.
|
|
34
|
+
#: The size of the buffer used to read chunks of the request body. This
|
|
35
|
+
#: size must be large enough to hold at least one complete header or
|
|
36
|
+
#: boundary line, so it is not recommended to lower it, but it can be made
|
|
37
|
+
#: higher to improve performance at the expense of RAM.
|
|
35
38
|
buffer_size = 256
|
|
36
39
|
|
|
37
40
|
def __init__(self, request):
|
|
@@ -59,6 +62,7 @@ class FormDataIter:
|
|
|
59
62
|
pass
|
|
60
63
|
|
|
61
64
|
# make sure we are at a boundary
|
|
65
|
+
await self._fill_buffer()
|
|
62
66
|
s = self.buffer.split(self.boundary, 1)
|
|
63
67
|
if len(s) != 2 or s[0] != b'':
|
|
64
68
|
abort(400) # pragma: no cover
|
|
@@ -111,6 +115,9 @@ class FormDataIter:
|
|
|
111
115
|
return name, FileUpload(filename, content_type, self._read_buffer)
|
|
112
116
|
|
|
113
117
|
async def _fill_buffer(self):
|
|
118
|
+
if self.buffer[-len(self.boundary) - 4:] == self.boundary + b'--\r\n':
|
|
119
|
+
# we have reached the end of the body
|
|
120
|
+
return
|
|
114
121
|
self.buffer += await self.request.stream.read(
|
|
115
122
|
self.buffer_size + self.extra_size - len(self.buffer))
|
|
116
123
|
|
|
@@ -99,6 +99,37 @@ class TestMultipart(unittest.TestCase):
|
|
|
99
99
|
'g': 'g|text/html|<p>hello</p>'})
|
|
100
100
|
FileUpload.max_memory_size = saved_max_memory_size
|
|
101
101
|
|
|
102
|
+
def test_large_file_upload(self):
|
|
103
|
+
saved_buffer_size = FormDataIter.buffer_size
|
|
104
|
+
FormDataIter.buffer_size = 100
|
|
105
|
+
saved_max_memory_size = FileUpload.max_memory_size
|
|
106
|
+
FileUpload.max_memory_size = 200
|
|
107
|
+
|
|
108
|
+
app = Microdot()
|
|
109
|
+
|
|
110
|
+
@app.post('/')
|
|
111
|
+
@with_form_data
|
|
112
|
+
async def index(req):
|
|
113
|
+
return {"len": len(await req.files['f'].read())}
|
|
114
|
+
|
|
115
|
+
client = TestClient(app)
|
|
116
|
+
|
|
117
|
+
res = self._run(client.post(
|
|
118
|
+
'/', headers={
|
|
119
|
+
'Content-Type': 'multipart/form-data; boundary=boundary',
|
|
120
|
+
},
|
|
121
|
+
body=(
|
|
122
|
+
b'--boundary\r\n'
|
|
123
|
+
b'Content-Disposition: form-data; name="f"; filename="f"\r\n'
|
|
124
|
+
b'Content-Type: text/plain\r\n\r\n' + b'*' * 398 + b'\r\n'
|
|
125
|
+
b'--boundary--\r\n')
|
|
126
|
+
))
|
|
127
|
+
self.assertEqual(res.status_code, 200)
|
|
128
|
+
self.assertEqual(res.json, {'len': 398})
|
|
129
|
+
|
|
130
|
+
FormDataIter.buffer_size = saved_buffer_size
|
|
131
|
+
FileUpload.max_memory_size = saved_max_memory_size
|
|
132
|
+
|
|
102
133
|
def test_file_save(self):
|
|
103
134
|
app = Microdot()
|
|
104
135
|
|
|
@@ -35,16 +35,17 @@ class TestRequest(unittest.TestCase):
|
|
|
35
35
|
def test_headers(self):
|
|
36
36
|
fd = get_async_request_fd('GET', '/foo', headers={
|
|
37
37
|
'Content-Type': 'application/json',
|
|
38
|
-
'Cookie': 'foo=bar;abc=def',
|
|
38
|
+
'Cookie': 'foo=bar;nothing;abc=def;',
|
|
39
39
|
'Content-Length': '3'}, body='aaa')
|
|
40
40
|
req = self._run(Request.create('app', fd, 'writer', 'addr'))
|
|
41
41
|
self.assertEqual(req.headers, {
|
|
42
42
|
'Host': 'example.com:1234',
|
|
43
43
|
'Content-Type': 'application/json',
|
|
44
|
-
'Cookie': 'foo=bar;abc=def',
|
|
44
|
+
'Cookie': 'foo=bar;nothing;abc=def;',
|
|
45
45
|
'Content-Length': '3'})
|
|
46
46
|
self.assertEqual(req.content_type, 'application/json')
|
|
47
|
-
self.assertEqual(req.cookies, {'foo': 'bar', '
|
|
47
|
+
self.assertEqual(req.cookies, {'foo': 'bar', 'nothing': '',
|
|
48
|
+
'abc': 'def', '': ''})
|
|
48
49
|
self.assertEqual(req.content_length, 3)
|
|
49
50
|
self.assertEqual(req.body, b'aaa')
|
|
50
51
|
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
[tox]
|
|
2
|
-
envlist=flake8,py38,py39,py310,py311,py312,
|
|
2
|
+
envlist=flake8,py38,py39,py310,py311,py312,py313,py314upy,cpy,benchmark,docs
|
|
3
3
|
skipsdist=True
|
|
4
4
|
skip_missing_interpreters=True
|
|
5
5
|
|
|
6
6
|
[gh-actions]
|
|
7
7
|
python =
|
|
8
|
-
3.7: py37
|
|
9
8
|
3.8: py38
|
|
10
9
|
3.9: py39
|
|
11
10
|
3.10: py310
|
|
12
11
|
3.11: py311
|
|
13
12
|
3.12: py312
|
|
13
|
+
3.13: py313
|
|
14
|
+
3.14: py314
|
|
14
15
|
pypy3: pypy3
|
|
15
16
|
|
|
16
17
|
[testenv]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|