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.
Files changed (64) hide show
  1. {microdot-2.0.3/src/microdot.egg-info → microdot-2.0.5}/PKG-INFO +1 -1
  2. {microdot-2.0.3 → microdot-2.0.5}/docs/extensions.rst +15 -0
  3. {microdot-2.0.3 → microdot-2.0.5}/pyproject.toml +1 -1
  4. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/microdot.py +67 -26
  5. {microdot-2.0.3 → microdot-2.0.5/src/microdot.egg-info}/PKG-INFO +1 -1
  6. {microdot-2.0.3 → microdot-2.0.5}/tests/test_url_pattern.py +24 -0
  7. {microdot-2.0.3 → microdot-2.0.5}/LICENSE +0 -0
  8. {microdot-2.0.3 → microdot-2.0.5}/MANIFEST.in +0 -0
  9. {microdot-2.0.3 → microdot-2.0.5}/README.md +0 -0
  10. {microdot-2.0.3 → microdot-2.0.5}/docs/Makefile +0 -0
  11. {microdot-2.0.3 → microdot-2.0.5}/docs/_static/css/custom.css +0 -0
  12. {microdot-2.0.3 → microdot-2.0.5}/docs/api.rst +0 -0
  13. {microdot-2.0.3 → microdot-2.0.5}/docs/conf.py +0 -0
  14. {microdot-2.0.3 → microdot-2.0.5}/docs/freezing.rst +0 -0
  15. {microdot-2.0.3 → microdot-2.0.5}/docs/index.rst +0 -0
  16. {microdot-2.0.3 → microdot-2.0.5}/docs/intro.rst +0 -0
  17. {microdot-2.0.3 → microdot-2.0.5}/docs/make.bat +0 -0
  18. {microdot-2.0.3 → microdot-2.0.5}/docs/migrating.rst +0 -0
  19. {microdot-2.0.3 → microdot-2.0.5}/setup.cfg +0 -0
  20. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/__init__.py +0 -0
  21. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/asgi.py +0 -0
  22. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/cors.py +0 -0
  23. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/jinja.py +0 -0
  24. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/session.py +0 -0
  25. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/sse.py +0 -0
  26. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/test_client.py +0 -0
  27. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/utemplate.py +0 -0
  28. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/websocket.py +0 -0
  29. {microdot-2.0.3 → microdot-2.0.5}/src/microdot/wsgi.py +0 -0
  30. {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/SOURCES.txt +0 -0
  31. {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/dependency_links.txt +0 -0
  32. {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/not-zip-safe +0 -0
  33. {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/requires.txt +0 -0
  34. {microdot-2.0.3 → microdot-2.0.5}/src/microdot.egg-info/top_level.txt +0 -0
  35. {microdot-2.0.3 → microdot-2.0.5}/tests/__init__.py +0 -0
  36. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.bin +0 -0
  37. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.css +0 -0
  38. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.gif +0 -0
  39. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.gz +0 -0
  40. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.html +0 -0
  41. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.jpg +0 -0
  42. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.js +0 -0
  43. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.json +0 -0
  44. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.png +0 -0
  45. {microdot-2.0.3 → microdot-2.0.5}/tests/files/test.txt +0 -0
  46. {microdot-2.0.3 → microdot-2.0.5}/tests/mock_socket.py +0 -0
  47. {microdot-2.0.3 → microdot-2.0.5}/tests/templates/hello.jinja.txt +0 -0
  48. {microdot-2.0.3 → microdot-2.0.5}/tests/templates/hello.utemplate.txt +0 -0
  49. {microdot-2.0.3 → microdot-2.0.5}/tests/templates/hello_utemplate_txt.py +0 -0
  50. {microdot-2.0.3 → microdot-2.0.5}/tests/test_asgi.py +0 -0
  51. {microdot-2.0.3 → microdot-2.0.5}/tests/test_cors.py +0 -0
  52. {microdot-2.0.3 → microdot-2.0.5}/tests/test_end2end.py +0 -0
  53. {microdot-2.0.3 → microdot-2.0.5}/tests/test_jinja.py +0 -0
  54. {microdot-2.0.3 → microdot-2.0.5}/tests/test_microdot.py +0 -0
  55. {microdot-2.0.3 → microdot-2.0.5}/tests/test_multidict.py +0 -0
  56. {microdot-2.0.3 → microdot-2.0.5}/tests/test_request.py +0 -0
  57. {microdot-2.0.3 → microdot-2.0.5}/tests/test_response.py +0 -0
  58. {microdot-2.0.3 → microdot-2.0.5}/tests/test_session.py +0 -0
  59. {microdot-2.0.3 → microdot-2.0.5}/tests/test_sse.py +0 -0
  60. {microdot-2.0.3 → microdot-2.0.5}/tests/test_urlencode.py +0 -0
  61. {microdot-2.0.3 → microdot-2.0.5}/tests/test_utemplate.py +0 -0
  62. {microdot-2.0.3 → microdot-2.0.5}/tests/test_websocket.py +0 -0
  63. {microdot-2.0.3 → microdot-2.0.5}/tests/test_wsgi.py +0 -0
  64. {microdot-2.0.3 → microdot-2.0.5}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: microdot
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: The impossibly small web framework for MicroPython
5
5
  Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/miguelgrinberg/microdot
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "microdot"
3
- version = "2.0.3"
3
+ version = "2.0.5"
4
4
  authors = [
5
5
  { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },
6
6
  ]
@@ -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.pattern = ''
802
- self.args = []
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
- pattern = '[^/]+'
816
+ parser = self._string_segment
817
+ pattern += '/([^/]+)'
816
818
  elif type_ == 'int':
817
- pattern = '-?\\d+'
819
+ parser = self._int_segment
820
+ pattern += '/(-?\\d+)'
818
821
  elif type_ == 'path':
819
- pattern = '.+'
822
+ use_regex = True
823
+ pattern += '/(.+)'
820
824
  elif type_.startswith('re:'):
821
- pattern = type_[3:]
825
+ use_regex = True
826
+ pattern += '/({pattern})'.format(pattern=type_[3:])
822
827
  else:
823
828
  raise ValueError('invalid URL segment type')
824
- use_regex = True
825
- self.pattern += '/({pattern})'.format(pattern=pattern)
826
- self.args.append({'type': type_, 'name': name})
829
+ self.segments.append({'parser': parser, 'name': name,
830
+ 'type': type_})
827
831
  else:
828
- self.pattern += '/{segment}'.format(segment=segment)
832
+ pattern += '/' + segment
833
+ self.segments.append({'parser': self._static_segment(segment)})
829
834
  if use_regex:
830
- self.pattern = re.compile('^' + self.pattern + '$')
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
- i = 1
842
- for arg in self.args:
843
- value = g.group(i)
844
- if arg['type'] == 'int':
845
- value = int(value)
846
- args[arg['name']] = value
847
- i += 1
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: microdot
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary: The impossibly small web framework for MicroPython
5
5
  Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/miguelgrinberg/microdot
@@ -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