yhttp 8.2.0__tar.gz → 8.3.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.
- {yhttp-8.2.0 → yhttp-8.3.0}/PKG-INFO +1 -1
- yhttp-8.3.0/tests/test_middleware.py +48 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_request.py +1 -1
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/application.py +17 -9
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/request.py +21 -15
- yhttp-8.3.0/yhttp/core/version.py +1 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/PKG-INFO +1 -1
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/SOURCES.txt +1 -0
- yhttp-8.2.0/yhttp/core/version.py +0 -1
- {yhttp-8.2.0 → yhttp-8.3.0}/LICENSE +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/README.md +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/setup.cfg +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/setup.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_application.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_applicationcli.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_builtincli_serve.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_builtincli_version.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_builtincli_yhttp_version.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_cookie.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_cookieset.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_extension.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form_json.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form_multipart.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form_urlencoded.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_fswatcher.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard_body.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard_contenttypes.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard_query.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_headerset.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_jsonencoding.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_locale.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_multidict.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_multipart.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_querystring.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_responseheaders.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_rewrite.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_rewritecli.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_routing.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_static.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_statuses.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_statushandler.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/__init__.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/cli.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/contenttypes.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/cookieset.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/fswatcher.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/guard.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/headerset.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/main.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/multidict.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/multipart.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/response.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/rewrite.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/static.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/statuses.py +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/dependency_links.txt +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/entry_points.txt +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/requires.txt +0 -0
- {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from bddrest import status, response
|
|
2
|
+
|
|
3
|
+
from yhttp.core import statuses
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_middleware_returnnone(app, httpreq):
|
|
7
|
+
def middleware(request):
|
|
8
|
+
request.query['baz'] = 'qux'
|
|
9
|
+
|
|
10
|
+
app.middlewares.append(middleware)
|
|
11
|
+
|
|
12
|
+
@app.route()
|
|
13
|
+
def get(req):
|
|
14
|
+
return '&'.join([f'{k}={v}' for k, v in req.query.items()])
|
|
15
|
+
|
|
16
|
+
with httpreq('?foo=bar'):
|
|
17
|
+
assert status == 200
|
|
18
|
+
assert response.text == 'foo=bar&baz=qux'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_middleware_returnbody(app, httpreq):
|
|
22
|
+
def middleware(request):
|
|
23
|
+
return 'something else'
|
|
24
|
+
|
|
25
|
+
app.middlewares.append(middleware)
|
|
26
|
+
|
|
27
|
+
@app.route()
|
|
28
|
+
def get(req):
|
|
29
|
+
return 'foobar'
|
|
30
|
+
|
|
31
|
+
with httpreq('?foo=bar'):
|
|
32
|
+
assert status == 200
|
|
33
|
+
assert response.text == 'something else'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_middleware_raise(app, httpreq):
|
|
37
|
+
def middleware(request):
|
|
38
|
+
raise statuses.found('/foo')
|
|
39
|
+
|
|
40
|
+
app.middlewares.append(middleware)
|
|
41
|
+
|
|
42
|
+
@app.route()
|
|
43
|
+
def get(req):
|
|
44
|
+
return 'foobar'
|
|
45
|
+
|
|
46
|
+
with httpreq('?foo=bar'):
|
|
47
|
+
assert status == 302
|
|
48
|
+
assert response.headers['location'] == '/foo'
|
|
@@ -4,7 +4,7 @@ from bddrest import status
|
|
|
4
4
|
def test_request(app, httpreq):
|
|
5
5
|
@app.route('/foo')
|
|
6
6
|
def get(req):
|
|
7
|
-
assert req.fullpath == '
|
|
7
|
+
assert req.fullpath == '/foo?bar=baz'
|
|
8
8
|
assert req.scheme == 'http'
|
|
9
9
|
assert req.headers.get('foo-bar') == 'baz'
|
|
10
10
|
assert req.contenttype is None
|
|
@@ -62,6 +62,7 @@ class BaseApplication:
|
|
|
62
62
|
self.events = {}
|
|
63
63
|
self.cliarguments = []
|
|
64
64
|
self.settings = pymlconf.Root(self._builtinsettings)
|
|
65
|
+
self.middlewares = []
|
|
65
66
|
|
|
66
67
|
def when(self, func):
|
|
67
68
|
"""Return decorator to registers the ``func`` into :attr:`events` by
|
|
@@ -245,8 +246,7 @@ class Application(BaseApplication):
|
|
|
245
246
|
pathparams = [a for a in match.groups() if a is not None]
|
|
246
247
|
info = info_.copy()
|
|
247
248
|
info['kwonly'] = {
|
|
248
|
-
k: request.query
|
|
249
|
-
if k in request.query
|
|
249
|
+
k: request.query.get(k, v) for k, v in info_['kwonly'].items()
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
return handler, pathparams, info
|
|
@@ -297,17 +297,25 @@ class Application(BaseApplication):
|
|
|
297
297
|
|
|
298
298
|
"""
|
|
299
299
|
response = self.response_factory(self, environ, startresponse)
|
|
300
|
+
request = self.request_factory(self, environ, response)
|
|
300
301
|
|
|
301
302
|
try:
|
|
302
|
-
request = self.request_factory(self, environ, response)
|
|
303
|
-
handler, pathparams, info = self._findhandler(request)
|
|
304
|
-
body = handler(request, *pathparams, **info['kwonly'])
|
|
305
303
|
|
|
306
|
-
if
|
|
307
|
-
|
|
304
|
+
# execute middlewares if any
|
|
305
|
+
for middleware in self.middlewares:
|
|
306
|
+
body = middleware(request)
|
|
307
|
+
if body:
|
|
308
|
+
break
|
|
308
309
|
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
else:
|
|
311
|
+
handler, pathparams, info = self._findhandler(request)
|
|
312
|
+
body = handler(request, *pathparams, **info['kwonly'])
|
|
313
|
+
|
|
314
|
+
if isinstance(body, statuses.HTTPStatus):
|
|
315
|
+
raise body
|
|
316
|
+
|
|
317
|
+
if isinstance(body, types.GeneratorType):
|
|
318
|
+
response.stream_firstchunk = next(body)
|
|
311
319
|
|
|
312
320
|
response.body = body
|
|
313
321
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import wsgiref.util as wsgiutil
|
|
2
|
+
from urllib.parse import urlencode
|
|
2
3
|
from http import cookies
|
|
3
4
|
from functools import partial, cached_property
|
|
4
5
|
from urllib.parse import parse_qs, unquote, quote
|
|
@@ -72,6 +73,20 @@ class Request:
|
|
|
72
73
|
"""HTTP method."""
|
|
73
74
|
return self.environ['REQUEST_METHOD'].lower()
|
|
74
75
|
|
|
76
|
+
@cached_property
|
|
77
|
+
def contentlength(self):
|
|
78
|
+
"""HTTP Request ``Content-Length`` header value."""
|
|
79
|
+
v = self.environ.get('CONTENT_LENGTH')
|
|
80
|
+
return None if not v or not v.strip() else int(v)
|
|
81
|
+
|
|
82
|
+
@cached_property
|
|
83
|
+
def contenttype(self):
|
|
84
|
+
"""HTTP Request ``Content-Type`` header value without encoding."""
|
|
85
|
+
contenttype = self.environ.get('CONTENT_TYPE')
|
|
86
|
+
if contenttype:
|
|
87
|
+
return contenttype.split(';')[0]
|
|
88
|
+
return None
|
|
89
|
+
|
|
75
90
|
@cached_property
|
|
76
91
|
def path(self):
|
|
77
92
|
"""Request URL without query string and ``scheme://domain.ext``."""
|
|
@@ -83,24 +98,15 @@ class Request:
|
|
|
83
98
|
|
|
84
99
|
return p
|
|
85
100
|
|
|
86
|
-
@
|
|
101
|
+
@property
|
|
87
102
|
def fullpath(self):
|
|
88
103
|
"""Request full URI including query string."""
|
|
89
|
-
|
|
104
|
+
result = self.path
|
|
105
|
+
query = urlencode(self.query, doseq=True)
|
|
106
|
+
if query:
|
|
107
|
+
result += f'?{query}'
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
def contentlength(self):
|
|
93
|
-
"""HTTP Request ``Content-Length`` header value."""
|
|
94
|
-
v = self.environ.get('CONTENT_LENGTH')
|
|
95
|
-
return None if not v or not v.strip() else int(v)
|
|
96
|
-
|
|
97
|
-
@cached_property
|
|
98
|
-
def contenttype(self):
|
|
99
|
-
"""HTTP Request ``Content-Type`` header value without encoding."""
|
|
100
|
-
contenttype = self.environ.get('CONTENT_TYPE')
|
|
101
|
-
if contenttype:
|
|
102
|
-
return contenttype.split(';')[0]
|
|
103
|
-
return None
|
|
109
|
+
return result
|
|
104
110
|
|
|
105
111
|
@cached_property
|
|
106
112
|
def query(self):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '8.3.0'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '8.2.0'
|
|
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
|