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.
Files changed (61) hide show
  1. {yhttp-8.2.0 → yhttp-8.3.0}/PKG-INFO +1 -1
  2. yhttp-8.3.0/tests/test_middleware.py +48 -0
  3. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_request.py +1 -1
  4. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/application.py +17 -9
  5. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/request.py +21 -15
  6. yhttp-8.3.0/yhttp/core/version.py +1 -0
  7. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/PKG-INFO +1 -1
  8. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/SOURCES.txt +1 -0
  9. yhttp-8.2.0/yhttp/core/version.py +0 -1
  10. {yhttp-8.2.0 → yhttp-8.3.0}/LICENSE +0 -0
  11. {yhttp-8.2.0 → yhttp-8.3.0}/README.md +0 -0
  12. {yhttp-8.2.0 → yhttp-8.3.0}/setup.cfg +0 -0
  13. {yhttp-8.2.0 → yhttp-8.3.0}/setup.py +0 -0
  14. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_application.py +0 -0
  15. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_applicationcli.py +0 -0
  16. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_builtincli_serve.py +0 -0
  17. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_builtincli_version.py +0 -0
  18. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_builtincli_yhttp_version.py +0 -0
  19. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_cookie.py +0 -0
  20. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_cookieset.py +0 -0
  21. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_extension.py +0 -0
  22. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form.py +0 -0
  23. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form_json.py +0 -0
  24. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form_multipart.py +0 -0
  25. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_form_urlencoded.py +0 -0
  26. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_fswatcher.py +0 -0
  27. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard.py +0 -0
  28. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard_body.py +0 -0
  29. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard_contenttypes.py +0 -0
  30. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_guard_query.py +0 -0
  31. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_headerset.py +0 -0
  32. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_jsonencoding.py +0 -0
  33. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_locale.py +0 -0
  34. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_multidict.py +0 -0
  35. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_multipart.py +0 -0
  36. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_querystring.py +0 -0
  37. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_responseheaders.py +0 -0
  38. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_rewrite.py +0 -0
  39. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_rewritecli.py +0 -0
  40. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_routing.py +0 -0
  41. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_static.py +0 -0
  42. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_statuses.py +0 -0
  43. {yhttp-8.2.0 → yhttp-8.3.0}/tests/test_statushandler.py +0 -0
  44. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/__init__.py +0 -0
  45. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/cli.py +0 -0
  46. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/contenttypes.py +0 -0
  47. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/cookieset.py +0 -0
  48. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/fswatcher.py +0 -0
  49. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/guard.py +0 -0
  50. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/headerset.py +0 -0
  51. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/main.py +0 -0
  52. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/multidict.py +0 -0
  53. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/multipart.py +0 -0
  54. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/response.py +0 -0
  55. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/rewrite.py +0 -0
  56. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/static.py +0 -0
  57. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp/core/statuses.py +0 -0
  58. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/dependency_links.txt +0 -0
  59. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/entry_points.txt +0 -0
  60. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/requires.txt +0 -0
  61. {yhttp-8.2.0 → yhttp-8.3.0}/yhttp.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yhttp
3
- Version: 8.2.0
3
+ Version: 8.3.0
4
4
  Summary: A very micro http framework.
5
5
  Home-page: http://github.com/yhttp/yhttp
6
6
  Author: Vahid Mardani
@@ -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 == 'http://bddrest-interceptor/foo?bar=baz'
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[k] for k in info_['kwonly']
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 isinstance(body, statuses.HTTPStatus):
307
- raise body
304
+ # execute middlewares if any
305
+ for middleware in self.middlewares:
306
+ body = middleware(request)
307
+ if body:
308
+ break
308
309
 
309
- if isinstance(body, types.GeneratorType):
310
- response.stream_firstchunk = next(body)
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
- @cached_property
101
+ @property
87
102
  def fullpath(self):
88
103
  """Request full URI including query string."""
89
- return wsgiutil.request_uri(self.environ, include_query=True)
104
+ result = self.path
105
+ query = urlencode(self.query, doseq=True)
106
+ if query:
107
+ result += f'?{query}'
90
108
 
91
- @cached_property
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yhttp
3
- Version: 8.2.0
3
+ Version: 8.3.0
4
4
  Summary: A very micro http framework.
5
5
  Home-page: http://github.com/yhttp/yhttp
6
6
  Author: Vahid Mardani
@@ -21,6 +21,7 @@ tests/test_guard_query.py
21
21
  tests/test_headerset.py
22
22
  tests/test_jsonencoding.py
23
23
  tests/test_locale.py
24
+ tests/test_middleware.py
24
25
  tests/test_multidict.py
25
26
  tests/test_multipart.py
26
27
  tests/test_querystring.py
@@ -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