microdot 2.0.3__tar.gz → 2.0.5__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.0.3/src/microdot.egg-info → microdot-2.0.5}/PKG-INFO +1 -1
- {microdot-2.0.3 → microdot-2.0.5}/docs/extensions.rst +15 -0
- {microdot-2.0.3 → microdot-2.0.5}/pyproject.toml +1 -1
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/microdot.py +67 -26
- {microdot-2.0.3 → microdot-2.0.5/src/microdot.egg-info}/PKG-INFO +1 -1
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_url_pattern.py +24 -0
- {microdot-2.0.3 → microdot-2.0.5}/LICENSE +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/MANIFEST.in +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/README.md +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/Makefile +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/_static/css/custom.css +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/api.rst +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/conf.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/freezing.rst +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/index.rst +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/intro.rst +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/make.bat +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/docs/migrating.rst +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/setup.cfg +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/__init__.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/asgi.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/cors.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/jinja.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/session.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/sse.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/test_client.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/utemplate.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/websocket.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot/wsgi.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/SOURCES.txt +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/dependency_links.txt +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/not-zip-safe +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/requires.txt +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/top_level.txt +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/__init__.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.bin +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.css +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.gif +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.gz +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.html +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.jpg +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.js +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.json +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.png +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.txt +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/mock_socket.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/templates/hello.jinja.txt +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/templates/hello.utemplate.txt +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/templates/hello_utemplate_txt.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_asgi.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_cors.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_end2end.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_jinja.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_microdot.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_multidict.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_request.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_response.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_session.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_sse.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_urlencode.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_utemplate.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_websocket.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tests/test_wsgi.py +0 -0
- {microdot-2.0.3 → microdot-2.0.5}/tox.ini +0 -0
|
@@ -134,6 +134,21 @@ method::
|
|
|
134
134
|
|
|
135
135
|
Template.initialize('my_templates')
|
|
136
136
|
|
|
137
|
+
By default templates are automatically compiled the first time they are
|
|
138
|
+
rendered, or when their last modified timestamp is more recent than the
|
|
139
|
+
compiledo file's timestamp. This loading behavior can be changed by switching
|
|
140
|
+
to a different template loader. For example, if the templates are pre-compiled,
|
|
141
|
+
the timestamp check and compile steps can be removed by switching to the
|
|
142
|
+
"compiled" template loader::
|
|
143
|
+
|
|
144
|
+
from utemplate import compiled
|
|
145
|
+
from microdot.utemplate import Template
|
|
146
|
+
|
|
147
|
+
Template.initialize(loader_class=compiled.Loader)
|
|
148
|
+
|
|
149
|
+
Consult the `uTemplate documentation <https://github.com/pfalcon/utemplate>`_
|
|
150
|
+
for additional information regarding template loaders.
|
|
151
|
+
|
|
137
152
|
Using the Jinja Engine
|
|
138
153
|
^^^^^^^^^^^^^^^^^^^^^^
|
|
139
154
|
|
|
@@ -8,7 +8,6 @@ servers for MicroPython and standard Python.
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import io
|
|
10
10
|
import json
|
|
11
|
-
import re
|
|
12
11
|
import time
|
|
13
12
|
|
|
14
13
|
try:
|
|
@@ -798,8 +797,9 @@ class Response:
|
|
|
798
797
|
class URLPattern():
|
|
799
798
|
def __init__(self, url_pattern):
|
|
800
799
|
self.url_pattern = url_pattern
|
|
801
|
-
self.
|
|
802
|
-
self.
|
|
800
|
+
self.segments = []
|
|
801
|
+
self.regex = None
|
|
802
|
+
pattern = ''
|
|
803
803
|
use_regex = False
|
|
804
804
|
for segment in url_pattern.lstrip('/').split('/'):
|
|
805
805
|
if segment and segment[0] == '<':
|
|
@@ -811,42 +811,83 @@ class URLPattern():
|
|
|
811
811
|
else:
|
|
812
812
|
type_ = 'string'
|
|
813
813
|
name = segment
|
|
814
|
+
parser = None
|
|
814
815
|
if type_ == 'string':
|
|
815
|
-
|
|
816
|
+
parser = self._string_segment
|
|
817
|
+
pattern += '/([^/]+)'
|
|
816
818
|
elif type_ == 'int':
|
|
817
|
-
|
|
819
|
+
parser = self._int_segment
|
|
820
|
+
pattern += '/(-?\\d+)'
|
|
818
821
|
elif type_ == 'path':
|
|
819
|
-
|
|
822
|
+
use_regex = True
|
|
823
|
+
pattern += '/(.+)'
|
|
820
824
|
elif type_.startswith('re:'):
|
|
821
|
-
|
|
825
|
+
use_regex = True
|
|
826
|
+
pattern += '/({pattern})'.format(pattern=type_[3:])
|
|
822
827
|
else:
|
|
823
828
|
raise ValueError('invalid URL segment type')
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
self.args.append({'type': type_, 'name': name})
|
|
829
|
+
self.segments.append({'parser': parser, 'name': name,
|
|
830
|
+
'type': type_})
|
|
827
831
|
else:
|
|
828
|
-
|
|
832
|
+
pattern += '/' + segment
|
|
833
|
+
self.segments.append({'parser': self._static_segment(segment)})
|
|
829
834
|
if use_regex:
|
|
830
|
-
|
|
835
|
+
import re
|
|
836
|
+
self.regex = re.compile('^' + pattern + '$')
|
|
831
837
|
|
|
832
838
|
def match(self, path):
|
|
833
|
-
if isinstance(self.pattern, str):
|
|
834
|
-
if path != self.pattern:
|
|
835
|
-
return
|
|
836
|
-
return {}
|
|
837
|
-
g = self.pattern.match(path)
|
|
838
|
-
if not g:
|
|
839
|
-
return
|
|
840
839
|
args = {}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
840
|
+
if self.regex:
|
|
841
|
+
g = self.regex.match(path)
|
|
842
|
+
if not g:
|
|
843
|
+
return
|
|
844
|
+
i = 1
|
|
845
|
+
for segment in self.segments:
|
|
846
|
+
if 'name' not in segment:
|
|
847
|
+
continue
|
|
848
|
+
value = g.group(i)
|
|
849
|
+
if segment['type'] == 'int':
|
|
850
|
+
value = int(value)
|
|
851
|
+
args[segment['name']] = value
|
|
852
|
+
i += 1
|
|
853
|
+
else:
|
|
854
|
+
if len(path) == 0 or path[0] != '/':
|
|
855
|
+
return
|
|
856
|
+
path = path[1:]
|
|
857
|
+
args = {}
|
|
858
|
+
for segment in self.segments:
|
|
859
|
+
if path is None:
|
|
860
|
+
return
|
|
861
|
+
arg, path = segment['parser'](path)
|
|
862
|
+
if arg is None:
|
|
863
|
+
return
|
|
864
|
+
if 'name' in segment:
|
|
865
|
+
args[segment['name']] = arg
|
|
866
|
+
if path is not None:
|
|
867
|
+
return
|
|
848
868
|
return args
|
|
849
869
|
|
|
870
|
+
def _static_segment(self, segment):
|
|
871
|
+
def _static(value):
|
|
872
|
+
s = value.split('/', 1)
|
|
873
|
+
if s[0] == segment:
|
|
874
|
+
return '', s[1] if len(s) > 1 else None
|
|
875
|
+
return None, None
|
|
876
|
+
return _static
|
|
877
|
+
|
|
878
|
+
def _string_segment(self, value):
|
|
879
|
+
s = value.split('/', 1)
|
|
880
|
+
if len(s[0]) == 0:
|
|
881
|
+
return None, None
|
|
882
|
+
return s[0], s[1] if len(s) > 1 else None
|
|
883
|
+
|
|
884
|
+
def _int_segment(self, value):
|
|
885
|
+
s = value.split('/', 1)
|
|
886
|
+
try:
|
|
887
|
+
return int(s[0]), s[1] if len(s) > 1 else None
|
|
888
|
+
except ValueError:
|
|
889
|
+
return None, None
|
|
890
|
+
|
|
850
891
|
|
|
851
892
|
class HTTPException(Exception):
|
|
852
893
|
def __init__(self, status_code, reason=None):
|
|
@@ -7,11 +7,14 @@ class TestURLPattern(unittest.TestCase):
|
|
|
7
7
|
p = URLPattern('/')
|
|
8
8
|
self.assertEqual(p.match('/'), {})
|
|
9
9
|
self.assertIsNone(p.match('/foo'))
|
|
10
|
+
self.assertIsNone(p.match('foo'))
|
|
11
|
+
self.assertIsNone(p.match(''))
|
|
10
12
|
|
|
11
13
|
p = URLPattern('/foo/bar')
|
|
12
14
|
self.assertEqual(p.match('/foo/bar'), {})
|
|
13
15
|
self.assertIsNone(p.match('/foo'))
|
|
14
16
|
self.assertIsNone(p.match('/foo/bar/'))
|
|
17
|
+
self.assertIsNone(p.match('/foo/bar/baz'))
|
|
15
18
|
|
|
16
19
|
p = URLPattern('/foo//bar/baz/')
|
|
17
20
|
self.assertEqual(p.match('/foo//bar/baz/'), {})
|
|
@@ -23,32 +26,50 @@ class TestURLPattern(unittest.TestCase):
|
|
|
23
26
|
p = URLPattern('/<arg>')
|
|
24
27
|
self.assertEqual(p.match('/foo'), {'arg': 'foo'})
|
|
25
28
|
self.assertIsNone(p.match('/'))
|
|
29
|
+
self.assertIsNone(p.match('//'))
|
|
30
|
+
self.assertIsNone(p.match(''))
|
|
31
|
+
self.assertIsNone(p.match('foo/'))
|
|
26
32
|
self.assertIsNone(p.match('/foo/'))
|
|
33
|
+
self.assertIsNone(p.match('//foo/'))
|
|
34
|
+
self.assertIsNone(p.match('/foo//'))
|
|
35
|
+
self.assertIsNone(p.match('/foo/bar'))
|
|
36
|
+
self.assertIsNone(p.match('/foo//bar'))
|
|
27
37
|
|
|
28
38
|
p = URLPattern('/<arg>/')
|
|
29
39
|
self.assertEqual(p.match('/foo/'), {'arg': 'foo'})
|
|
30
40
|
self.assertIsNone(p.match('/'))
|
|
31
41
|
self.assertIsNone(p.match('/foo'))
|
|
42
|
+
self.assertIsNone(p.match('/foo/bar'))
|
|
43
|
+
self.assertIsNone(p.match('/foo/bar/'))
|
|
32
44
|
|
|
33
45
|
p = URLPattern('/<string:arg>')
|
|
34
46
|
self.assertEqual(p.match('/foo'), {'arg': 'foo'})
|
|
35
47
|
self.assertIsNone(p.match('/'))
|
|
36
48
|
self.assertIsNone(p.match('/foo/'))
|
|
49
|
+
self.assertIsNone(p.match('/foo/bar'))
|
|
50
|
+
self.assertIsNone(p.match('/foo/bar/'))
|
|
37
51
|
|
|
38
52
|
p = URLPattern('/<string:arg>/')
|
|
39
53
|
self.assertEqual(p.match('/foo/'), {'arg': 'foo'})
|
|
40
54
|
self.assertIsNone(p.match('/'))
|
|
41
55
|
self.assertIsNone(p.match('/foo'))
|
|
56
|
+
self.assertIsNone(p.match('/foo/bar'))
|
|
57
|
+
self.assertIsNone(p.match('/foo/bar/'))
|
|
42
58
|
|
|
43
59
|
p = URLPattern('/foo/<arg1>/bar/<arg2>')
|
|
44
60
|
self.assertEqual(p.match('/foo/one/bar/two'),
|
|
45
61
|
{'arg1': 'one', 'arg2': 'two'})
|
|
46
62
|
self.assertIsNone(p.match('/'))
|
|
47
63
|
self.assertIsNone(p.match('/foo/'))
|
|
64
|
+
self.assertIsNone(p.match('/foo/bar'))
|
|
65
|
+
self.assertIsNone(p.match('/foo//bar/'))
|
|
66
|
+
self.assertIsNone(p.match('/foo//bar//'))
|
|
48
67
|
|
|
49
68
|
def test_int_argument(self):
|
|
50
69
|
p = URLPattern('/users/<int:id>')
|
|
51
70
|
self.assertEqual(p.match('/users/123'), {'id': 123})
|
|
71
|
+
self.assertEqual(p.match('/users/-123'), {'id': -123})
|
|
72
|
+
self.assertEqual(p.match('/users/0'), {'id': 0})
|
|
52
73
|
self.assertIsNone(p.match('/users/'))
|
|
53
74
|
self.assertIsNone(p.match('/users/abc'))
|
|
54
75
|
self.assertIsNone(p.match('/users/123abc'))
|
|
@@ -82,7 +103,10 @@ class TestURLPattern(unittest.TestCase):
|
|
|
82
103
|
p = URLPattern('/users/<re:[a-c]+:id>')
|
|
83
104
|
self.assertEqual(p.match('/users/ab'), {'id': 'ab'})
|
|
84
105
|
self.assertEqual(p.match('/users/bca'), {'id': 'bca'})
|
|
106
|
+
self.assertIsNone(p.match('/users'))
|
|
107
|
+
self.assertIsNone(p.match('/users/'))
|
|
85
108
|
self.assertIsNone(p.match('/users/abcd'))
|
|
109
|
+
self.assertIsNone(p.match('/users/abcdx'))
|
|
86
110
|
|
|
87
111
|
def test_many_arguments(self):
|
|
88
112
|
p = URLPattern('/foo/<path:path>/<int:id>/bar/<name>')
|
|
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
|