microdot 2.3.5__tar.gz → 2.4.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.
- {microdot-2.3.5/src/microdot.egg-info → microdot-2.4.0}/PKG-INFO +1 -1
- {microdot-2.3.5 → microdot-2.4.0}/pyproject.toml +1 -1
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/__init__.py +2 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/microdot.py +11 -2
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/session.py +4 -1
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/sse.py +19 -6
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/test_client.py +6 -2
- {microdot-2.3.5 → microdot-2.4.0/src/microdot.egg-info}/PKG-INFO +1 -1
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot.egg-info/SOURCES.txt +0 -1
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_microdot.py +2 -1
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_sse.py +19 -12
- microdot-2.3.5/src/microdot/pubsub.py +0 -21
- {microdot-2.3.5 → microdot-2.4.0}/LICENSE +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/MANIFEST.in +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/README.md +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/Makefile +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/_static/css/custom.css +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/api.rst +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/conf.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/extensions.rst +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/freezing.rst +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/index.rst +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/intro.rst +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/make.bat +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/docs/migrating.rst +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/setup.cfg +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/asgi.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/auth.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/cors.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/helpers.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/jinja.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/login.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/multipart.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/utemplate.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/websocket.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot/wsgi.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot.egg-info/dependency_links.txt +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot.egg-info/not-zip-safe +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot.egg-info/requires.txt +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/src/microdot.egg-info/top_level.txt +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/__init__.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.bin +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.css +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.gif +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.gz +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.html +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.jpg +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.js +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.json +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.png +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.txt +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/files/test.txt.gz +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/mock_socket.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/templates/hello.jinja.txt +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/templates/hello.utemplate.txt +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/templates/hello_utemplate_txt.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_asgi.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_auth.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_cors.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_end2end.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_jinja.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_login.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_multidict.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_multipart.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_request.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_response.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_session.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_url_pattern.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_urlencode.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_utemplate.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_websocket.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tests/test_wsgi.py +0 -0
- {microdot-2.3.5 → microdot-2.4.0}/tox.ini +0 -0
|
@@ -632,9 +632,13 @@ class Response:
|
|
|
632
632
|
"""Delete a cookie.
|
|
633
633
|
|
|
634
634
|
:param cookie: The cookie's name.
|
|
635
|
-
:param kwargs: Any cookie
|
|
636
|
-
|
|
635
|
+
:param kwargs: Any cookie options and flags supported by
|
|
636
|
+
:meth:`set_cookie() <microdot.Response.set_cookie>`.
|
|
637
|
+
Values given for ``expires`` and ``max_age`` are
|
|
638
|
+
ignored.
|
|
637
639
|
"""
|
|
640
|
+
kwargs.pop('expires', None)
|
|
641
|
+
kwargs.pop('max_age', None)
|
|
638
642
|
self.set_cookie(cookie, '', expires='Thu, 01 Jan 1970 00:00:01 GMT',
|
|
639
643
|
max_age=0, **kwargs)
|
|
640
644
|
|
|
@@ -1367,6 +1371,11 @@ class Microdot:
|
|
|
1367
1371
|
try:
|
|
1368
1372
|
req = await Request.create(self, reader, writer,
|
|
1369
1373
|
writer.get_extra_info('peername'))
|
|
1374
|
+
except OSError as exc: # pragma: no cover
|
|
1375
|
+
if exc.errno in MUTED_SOCKET_ERRORS:
|
|
1376
|
+
pass
|
|
1377
|
+
else:
|
|
1378
|
+
raise
|
|
1370
1379
|
except Exception as exc: # pragma: no cover
|
|
1371
1380
|
print_exception(exc)
|
|
1372
1381
|
|
|
@@ -25,7 +25,10 @@ class SessionDict(dict):
|
|
|
25
25
|
class Session:
|
|
26
26
|
"""
|
|
27
27
|
:param app: The application instance.
|
|
28
|
-
:param
|
|
28
|
+
:param secret_key: The secret key, as a string or bytes object.
|
|
29
|
+
:param cookie_options: A dictionary with cookie options to pass as
|
|
30
|
+
arguments to :meth:`Response.set_cookie()
|
|
31
|
+
<microdot.Response.set_cookie>`.
|
|
29
32
|
"""
|
|
30
33
|
secret_key = None
|
|
31
34
|
|
|
@@ -17,7 +17,8 @@ class SSE:
|
|
|
17
17
|
self.event = asyncio.Event()
|
|
18
18
|
self.queue = []
|
|
19
19
|
|
|
20
|
-
async def send(self, data, event=None, event_id=None
|
|
20
|
+
async def send(self, data, event=None, event_id=None, retry=None,
|
|
21
|
+
comment=False):
|
|
21
22
|
"""Send an event to the client.
|
|
22
23
|
|
|
23
24
|
:param data: the data to send. It can be given as a string, bytes, dict
|
|
@@ -27,6 +28,12 @@ class SSE:
|
|
|
27
28
|
given, it must be a string.
|
|
28
29
|
:param event_id: an optional event id, to send along with the data. If
|
|
29
30
|
given, it must be a string.
|
|
31
|
+
:param retry: an optional reconnection time (in seconds) that the
|
|
32
|
+
client should use when the connection is lost.
|
|
33
|
+
:param comment: when set to ``True``, the data is sent as a comment
|
|
34
|
+
line, and all other parameters are ignored. This is
|
|
35
|
+
useful as a heartbeat mechanism that keeps the
|
|
36
|
+
connection alive.
|
|
30
37
|
"""
|
|
31
38
|
if isinstance(data, (dict, list)):
|
|
32
39
|
data = json.dumps(data)
|
|
@@ -34,11 +41,17 @@ class SSE:
|
|
|
34
41
|
data = data.encode()
|
|
35
42
|
elif not isinstance(data, bytes):
|
|
36
43
|
data = str(data).encode()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
if comment:
|
|
45
|
+
data = b': ' + data + b'\n\n'
|
|
46
|
+
else:
|
|
47
|
+
data = b'data: ' + data + b'\n\n'
|
|
48
|
+
if event_id:
|
|
49
|
+
data = b'id: ' + event_id.encode() + b'\n' + data
|
|
50
|
+
if event:
|
|
51
|
+
data = b'event: ' + event.encode() + b'\n' + data
|
|
52
|
+
if retry:
|
|
53
|
+
data = b'retry: ' + str(int(retry * 1000)).encode() + b'\n' + \
|
|
54
|
+
data
|
|
42
55
|
self.queue.append(data)
|
|
43
56
|
self.event.set()
|
|
44
57
|
|
|
@@ -78,6 +78,7 @@ class TestResponse:
|
|
|
78
78
|
data = None
|
|
79
79
|
event = None
|
|
80
80
|
event_id = None
|
|
81
|
+
retry = None
|
|
81
82
|
for line in sse_event.split(b'\n'):
|
|
82
83
|
if line.startswith(b'data:'):
|
|
83
84
|
data = line[5:].strip()
|
|
@@ -85,6 +86,8 @@ class TestResponse:
|
|
|
85
86
|
event = line[6:].strip().decode()
|
|
86
87
|
elif line.startswith(b'id:'):
|
|
87
88
|
event_id = line[3:].strip().decode()
|
|
89
|
+
elif line.startswith(b'retry:'):
|
|
90
|
+
retry = int(line[7:].strip()) / 1000
|
|
88
91
|
if data:
|
|
89
92
|
data_json = None
|
|
90
93
|
try:
|
|
@@ -92,8 +95,9 @@ class TestResponse:
|
|
|
92
95
|
except ValueError:
|
|
93
96
|
pass
|
|
94
97
|
self.events.append({
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
'data': data, 'data_json': data_json,
|
|
99
|
+
'event': event, 'event_id': event_id,
|
|
100
|
+
'retry': retry})
|
|
97
101
|
|
|
98
102
|
@classmethod
|
|
99
103
|
async def create(cls, res):
|
|
@@ -204,9 +204,10 @@ class TestMicrodot(unittest.TestCase):
|
|
|
204
204
|
res.set_cookie('four', '4')
|
|
205
205
|
res.delete_cookie('two', path='/')
|
|
206
206
|
res.delete_cookie('one', path='/bad')
|
|
207
|
+
res.delete_cookie('five', max_age=123, expires='foo')
|
|
207
208
|
return res
|
|
208
209
|
|
|
209
|
-
client = TestClient(app, cookies={'one': '1', 'two': '2'})
|
|
210
|
+
client = TestClient(app, cookies={'one': '1', 'two': '2', 'five': '5'})
|
|
210
211
|
res = self._run(client.get('/', headers={'Cookie': 'three=3'}))
|
|
211
212
|
self.assertEqual(res.status_code, 200)
|
|
212
213
|
self.assertEqual(res.headers['Content-Type'],
|
|
@@ -25,7 +25,9 @@ class TestWebSocket(unittest.TestCase):
|
|
|
25
25
|
await sse.send('bar', event='test')
|
|
26
26
|
await sse.send('bar', event='test', event_id='id42')
|
|
27
27
|
await sse.send('bar', event_id='id42')
|
|
28
|
+
await sse.send('bar', retry=2.5)
|
|
28
29
|
await sse.send({'foo': 'bar'})
|
|
30
|
+
await sse.send('ping', comment=True)
|
|
29
31
|
await sse.send([42, 'foo', 'bar'])
|
|
30
32
|
await sse.send(ValueError('foo'))
|
|
31
33
|
await sse.send(b'foo')
|
|
@@ -38,35 +40,40 @@ class TestWebSocket(unittest.TestCase):
|
|
|
38
40
|
'event: test\ndata: bar\n\n'
|
|
39
41
|
'event: test\nid: id42\ndata: bar\n\n'
|
|
40
42
|
'id: id42\ndata: bar\n\n'
|
|
43
|
+
'retry: 2500\ndata: bar\n\n'
|
|
41
44
|
'data: {"foo": "bar"}\n\n'
|
|
45
|
+
': ping\n\n'
|
|
42
46
|
'data: [42, "foo", "bar"]\n\n'
|
|
43
47
|
'data: foo\n\n'
|
|
44
48
|
'data: foo\n\n'))
|
|
45
|
-
self.assertEqual(len(response.events),
|
|
49
|
+
self.assertEqual(len(response.events), 9)
|
|
46
50
|
self.assertEqual(response.events[0], {
|
|
47
51
|
'data': b'foo', 'data_json': None, 'event': None,
|
|
48
|
-
'event_id': None})
|
|
52
|
+
'event_id': None, "retry": None})
|
|
49
53
|
self.assertEqual(response.events[1], {
|
|
50
54
|
'data': b'bar', 'data_json': None, 'event': 'test',
|
|
51
|
-
'event_id': None})
|
|
55
|
+
'event_id': None, "retry": None})
|
|
52
56
|
self.assertEqual(response.events[2], {
|
|
53
57
|
'data': b'bar', 'data_json': None, 'event': 'test',
|
|
54
|
-
'event_id': 'id42'})
|
|
58
|
+
'event_id': 'id42', "retry": None})
|
|
55
59
|
self.assertEqual(response.events[3], {
|
|
56
60
|
'data': b'bar', 'data_json': None, 'event': None,
|
|
57
|
-
'event_id': 'id42'})
|
|
61
|
+
'event_id': 'id42', "retry": None})
|
|
58
62
|
self.assertEqual(response.events[4], {
|
|
59
|
-
'data': b'
|
|
60
|
-
'
|
|
63
|
+
'data': b'bar', 'data_json': None, 'event': None, 'event_id': None,
|
|
64
|
+
'retry': 2.5})
|
|
61
65
|
self.assertEqual(response.events[5], {
|
|
62
|
-
'data': b'
|
|
63
|
-
'event': None, 'event_id': None})
|
|
66
|
+
'data': b'{"foo": "bar"}', 'data_json': {'foo': 'bar'},
|
|
67
|
+
'event': None, 'event_id': None, "retry": None})
|
|
64
68
|
self.assertEqual(response.events[6], {
|
|
65
|
-
'data': b'foo', 'data_json':
|
|
66
|
-
'event_id': None})
|
|
69
|
+
'data': b'[42, "foo", "bar"]', 'data_json': [42, 'foo', 'bar'],
|
|
70
|
+
'event': None, 'event_id': None, "retry": None})
|
|
67
71
|
self.assertEqual(response.events[7], {
|
|
68
72
|
'data': b'foo', 'data_json': None, 'event': None,
|
|
69
|
-
'event_id': None})
|
|
73
|
+
'event_id': None, "retry": None})
|
|
74
|
+
self.assertEqual(response.events[8], {
|
|
75
|
+
'data': b'foo', 'data_json': None, 'event': None,
|
|
76
|
+
'event_id': None, "retry": None})
|
|
70
77
|
|
|
71
78
|
def test_sse_exception(self):
|
|
72
79
|
app = Microdot()
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
class PubSub:
|
|
2
|
-
subscriptions = {}
|
|
3
|
-
|
|
4
|
-
def subscribe(self, observer, topic):
|
|
5
|
-
if topic not in self.subscriptions:
|
|
6
|
-
self.subscriptions[topic] = []
|
|
7
|
-
self.subscriptions[topic].append(observer)
|
|
8
|
-
|
|
9
|
-
def unsubscribe(self, observer, topic):
|
|
10
|
-
if topic in self.subscriptions:
|
|
11
|
-
try:
|
|
12
|
-
self.subscriptions[topic].remove(observer)
|
|
13
|
-
if not self.subscriptions[topic]:
|
|
14
|
-
del self.subscriptions[topic]
|
|
15
|
-
except (ValueError, IndexError):
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
@classmethod
|
|
19
|
-
async def publish(cls, topic, data):
|
|
20
|
-
for observer in cls.subscriptions.get(topic, []):
|
|
21
|
-
await observer.send(data, event=topic)
|
|
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
|