serverwamp 1.2.0__tar.gz → 2.0.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.
- {serverwamp-1.2.0 → serverwamp-2.0.0}/PKG-INFO +18 -12
- {serverwamp-1.2.0 → serverwamp-2.0.0}/README.md +11 -3
- serverwamp-2.0.0/docs/changelog.rst +264 -0
- {serverwamp-1.2.0 → serverwamp-2.0.0}/pyproject.toml +29 -12
- serverwamp-2.0.0/serverwamp/__init__.py +17 -0
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/adapters/aiohttp.py +59 -36
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/adapters/anyio.py +7 -10
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/adapters/asgi.py +96 -56
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/adapters/async_base.py +3 -7
- serverwamp-2.0.0/serverwamp/adapters/asyncio.py +57 -0
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/adapters/trio.py +4 -4
- serverwamp-2.0.0/serverwamp/application.py +888 -0
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/connection.py +19 -4
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/events.py +16 -6
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/helpers.py +19 -2
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/json.py +21 -5
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/protocol.py +107 -70
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/routing.py +7 -7
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/rpc.py +109 -54
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/session.py +92 -26
- serverwamp-1.2.0/CHANGELOG.md +0 -89
- serverwamp-1.2.0/serverwamp/__init__.py +0 -15
- serverwamp-1.2.0/serverwamp/adapters/asyncio.py +0 -52
- serverwamp-1.2.0/serverwamp/application.py +0 -667
- serverwamp-1.2.0/serverwamp/context.py +0 -145
- {serverwamp-1.2.0 → serverwamp-2.0.0}/.gitignore +0 -0
- {serverwamp-1.2.0 → serverwamp-2.0.0}/LICENSE.md +0 -0
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/adapters/__init__.py +0 -0
- {serverwamp-1.2.0 → serverwamp-2.0.0}/serverwamp/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: serverwamp
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Components that add Web Application Messaging Protocol features to WebSocket servers.
|
|
5
5
|
Project-URL: Homepage, https://github.com/JustinTArthur/serverwamp
|
|
6
6
|
Project-URL: Documentation, https://serverwamp.readthedocs.io/
|
|
@@ -14,11 +14,6 @@ Classifier: Framework :: Trio
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Programming Language :: Python
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
22
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -26,11 +21,14 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
26
21
|
Classifier: Programming Language :: Python :: 3.15
|
|
27
22
|
Classifier: Topic :: Internet
|
|
28
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
-
Requires-Python: >=3.
|
|
30
|
-
Requires-Dist: dataclasses~=0.6; python_version < '3.7'
|
|
24
|
+
Requires-Python: >=3.11
|
|
31
25
|
Requires-Dist: msgpack<2,>=1.0.0
|
|
26
|
+
Provides-Extra: anyio
|
|
27
|
+
Requires-Dist: anyio>=4.0; extra == 'anyio'
|
|
32
28
|
Provides-Extra: docs
|
|
33
|
-
Requires-Dist: sphinx<10,>=
|
|
29
|
+
Requires-Dist: sphinx<10,>=5.3; extra == 'docs'
|
|
30
|
+
Provides-Extra: trio
|
|
31
|
+
Requires-Dist: trio>=0.25.0; extra == 'trio'
|
|
34
32
|
Description-Content-Type: text/markdown
|
|
35
33
|
|
|
36
34
|
# serverwamp
|
|
@@ -70,7 +68,6 @@ async def delete_doc(document_id):
|
|
|
70
68
|
@docs_events.route('docs.changes')
|
|
71
69
|
async def subscribe_to_doc_changes(session, changes_subscribers):
|
|
72
70
|
changes_subscribers.add(session)
|
|
73
|
-
session.send_event(args=('Thanks for subscribing!',))
|
|
74
71
|
yield
|
|
75
72
|
changes_subscribers.remove(session)
|
|
76
73
|
|
|
@@ -79,8 +76,12 @@ async def server_context(app):
|
|
|
79
76
|
|
|
80
77
|
async def watch_for_docs_changes():
|
|
81
78
|
async for event in my_db.watch_changes():
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
# A copy, as sessions can unsubscribe while events are sent.
|
|
80
|
+
for session in list(app['changes_subscribers']):
|
|
81
|
+
await session.send_event(
|
|
82
|
+
'docs.changes',
|
|
83
|
+
args=(f'Document changed: {event}',)
|
|
84
|
+
)
|
|
84
85
|
|
|
85
86
|
watcher = asyncio.create_task(watch_for_docs_changes())
|
|
86
87
|
try:
|
|
@@ -113,3 +114,8 @@ Unit tests can be run with:
|
|
|
113
114
|
|
|
114
115
|
pip install -e . --group ci
|
|
115
116
|
pytest
|
|
117
|
+
|
|
118
|
+
Linting and type checking can be run with:
|
|
119
|
+
|
|
120
|
+
ruff check serverwamp tests examples
|
|
121
|
+
mypy serverwamp
|
|
@@ -35,7 +35,6 @@ async def delete_doc(document_id):
|
|
|
35
35
|
@docs_events.route('docs.changes')
|
|
36
36
|
async def subscribe_to_doc_changes(session, changes_subscribers):
|
|
37
37
|
changes_subscribers.add(session)
|
|
38
|
-
session.send_event(args=('Thanks for subscribing!',))
|
|
39
38
|
yield
|
|
40
39
|
changes_subscribers.remove(session)
|
|
41
40
|
|
|
@@ -44,8 +43,12 @@ async def server_context(app):
|
|
|
44
43
|
|
|
45
44
|
async def watch_for_docs_changes():
|
|
46
45
|
async for event in my_db.watch_changes():
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
# A copy, as sessions can unsubscribe while events are sent.
|
|
47
|
+
for session in list(app['changes_subscribers']):
|
|
48
|
+
await session.send_event(
|
|
49
|
+
'docs.changes',
|
|
50
|
+
args=(f'Document changed: {event}',)
|
|
51
|
+
)
|
|
49
52
|
|
|
50
53
|
watcher = asyncio.create_task(watch_for_docs_changes())
|
|
51
54
|
try:
|
|
@@ -78,3 +81,8 @@ Unit tests can be run with:
|
|
|
78
81
|
|
|
79
82
|
pip install -e . --group ci
|
|
80
83
|
pytest
|
|
84
|
+
|
|
85
|
+
Linting and type checking can be run with:
|
|
86
|
+
|
|
87
|
+
ruff check serverwamp tests examples
|
|
88
|
+
mypy serverwamp
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Changelog
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
.. _serverwamp-changelog-2.0.0:
|
|
5
|
+
|
|
6
|
+
2.0.0
|
|
7
|
+
-----
|
|
8
|
+
|
|
9
|
+
* Rename session state handlers to session lifespan handlers
|
|
10
|
+
(``add_session_lifespan_handler()``, ``SessionLifespanHandler``,
|
|
11
|
+
``Realm.session_lifespan_handlers`` and
|
|
12
|
+
``authed_session_lifespan_handlers``). The old names are deprecated.
|
|
13
|
+
* Fix session lifespan and subscription handlers getting arguments in the wrong
|
|
14
|
+
parameters when one isn't ``session``, a default arg, or a subscription
|
|
15
|
+
handler's ``topic_uri``. Such a parameter now keeps its default value, or the
|
|
16
|
+
handler fails with a ``TypeError`` naming it.
|
|
17
|
+
* Answer subscription handler errors with ``wamp.error.runtime_error``, and end
|
|
18
|
+
sessions whose session lifespan handlers fail to start with that reason,
|
|
19
|
+
instead of failing the connection without a reply. Errors are logged, and no
|
|
20
|
+
longer stop other teardown.
|
|
21
|
+
* Send events for a subscription only after its SUBSCRIBED. Events sent while
|
|
22
|
+
its handler runs, such as a first event from the handler, are held until
|
|
23
|
+
then.
|
|
24
|
+
* Add ``serverwamp.SubscribeError`` for subscription handlers to raise to
|
|
25
|
+
answer a SUBSCRIBE with an error, such as ``wamp.error.not_authorized``.
|
|
26
|
+
* Require Python 3.11 or newer. ``serverwamp.context``, a backport of
|
|
27
|
+
``contextlib.asynccontextmanager`` for older Pythons, is removed.
|
|
28
|
+
* Build the asyncio support on ``asyncio.TaskGroup``. As with Trio and AnyIO,
|
|
29
|
+
task group failures, including unhandled errors while handling a connection,
|
|
30
|
+
are raised as an ``ExceptionGroup`` instead of the first error alone, and a
|
|
31
|
+
failing task interrupts the code that started the group.
|
|
32
|
+
* Support current AnyIO and Trio releases, now requiring AnyIO 4 or newer and
|
|
33
|
+
Trio 0.25 or newer. They can be installed with serverwamp's ``anyio`` and
|
|
34
|
+
``trio`` extras.
|
|
35
|
+
* Abort sessions for protocol errors with ``wamp.error.protocol_violation``,
|
|
36
|
+
per WAMP spec, instead of ``wamp.error.protocol_error``.
|
|
37
|
+
* Handle these client mistakes instead of failing in the connection handler:
|
|
38
|
+
|
|
39
|
+
* WebSocket messages that aren't valid JSON or MessagePack, WAMP messages
|
|
40
|
+
that are too short or have fields of the wrong type, and HELLO after the
|
|
41
|
+
session is established abort the session with a protocol violation. Message
|
|
42
|
+
types must be integers, so ``1.0`` and ``true`` are no longer accepted as
|
|
43
|
+
HELLO.
|
|
44
|
+
* UNSUBSCRIBE for a subscription the session doesn't have is answered with
|
|
45
|
+
``wamp.error.no_such_subscription``.
|
|
46
|
+
* SUBSCRIBE for a topic without a route is answered with
|
|
47
|
+
``wamp.error.invalid_uri``.
|
|
48
|
+
* HELLO with a null ``authmethods`` that no transport authenticator
|
|
49
|
+
identifies is aborted with ``wamp.error.authentication_failed``.
|
|
50
|
+
|
|
51
|
+
* Fix GOODBYE details and reason being read from the wrong fields.
|
|
52
|
+
``WAMPGoodbyeRequest`` no longer has a ``request_id``.
|
|
53
|
+
* Give GOODBYE messages a reason instead of none. Replies to a client's GOODBYE
|
|
54
|
+
and ``WAMPSession.close()`` without a reason use
|
|
55
|
+
``wamp.close.goodbye_and_out``, and ``Application.close_sessions()`` uses
|
|
56
|
+
``wamp.close.system_shutdown``.
|
|
57
|
+
* End sessions once GOODBYE has been exchanged, instead of when the client
|
|
58
|
+
disconnects. Subscription and session lifespan handler teardown run then, and
|
|
59
|
+
the client can start a new session on the same connection with HELLO, as the
|
|
60
|
+
WAMP spec allows.
|
|
61
|
+
|
|
62
|
+
* After the session sends GOODBYE, messages from the client other than its
|
|
63
|
+
GOODBYE in reply are ignored, per WAMP spec.
|
|
64
|
+
* Sending for a session that has ended raises
|
|
65
|
+
``serverwamp.ConnectionClosed``, including from call handlers still running
|
|
66
|
+
and from ``send_event()``.
|
|
67
|
+
* ``Application.handle_session()`` returns whether the session ended with
|
|
68
|
+
GOODBYE.
|
|
69
|
+
|
|
70
|
+
* Fix prefix and regex routes handling URIs they don't match. A call or
|
|
71
|
+
subscription for a URI without an exact route went to the first prefix or
|
|
72
|
+
regex route added.
|
|
73
|
+
|
|
74
|
+
* Applications relying on this should add a catch-all route such as
|
|
75
|
+
``prefix_route('')`` after their other prefix and regex routes.
|
|
76
|
+
|
|
77
|
+
* Answer SUBSCRIBE for a topic the session is already subscribed to with the
|
|
78
|
+
existing subscription, without running the subscription handler again.
|
|
79
|
+
* Fix RPCs that finish after their client disconnected logging errors,
|
|
80
|
+
cancelling the session's other calls, and keeping the session in memory.
|
|
81
|
+
|
|
82
|
+
* Sends to a disconnected client raise ``serverwamp.ConnectionClosed``, a
|
|
83
|
+
``ConnectionError``, instead of the transport's own exception.
|
|
84
|
+
* ``WAMPSession.close()`` and ``abort()`` succeed when the client already
|
|
85
|
+
left.
|
|
86
|
+
* Unhandled exceptions from a session's tasks are logged. With Trio or AnyIO,
|
|
87
|
+
they no longer end the session.
|
|
88
|
+
|
|
89
|
+
* Run progressive RPC handlers' ``finally`` blocks as soon as their call is
|
|
90
|
+
handled, instead of at garbage collection.
|
|
91
|
+
* Wait for tasks started with ``WAMPSession.spawn_task()`` while a session
|
|
92
|
+
ends, instead of cancelling them.
|
|
93
|
+
* Fix sessions never being cleaned up with Trio. Teardown after ``yield`` in
|
|
94
|
+
subscription and session lifespan handlers now runs, and ended sessions are
|
|
95
|
+
no longer kept in memory.
|
|
96
|
+
* Fix ASGI apps failing on anything other than a WebSocket. HTTP requests are
|
|
97
|
+
answered with 426 Upgrade Required without an error, and lifespan startup and
|
|
98
|
+
shutdown are acknowledged, so an app from ``asgi_application()`` can be
|
|
99
|
+
served directly.
|
|
100
|
+
* Rename ``serverwamp.adapters.asgi.ASGIWebSocketConection`` to
|
|
101
|
+
``ASGIWebSocketConnection``.
|
|
102
|
+
* Reject WebSocket connections that offer none of the supported WAMP
|
|
103
|
+
subprotocols, with 400 Bad Request with aiohttp and 403 Forbidden from ASGI
|
|
104
|
+
servers. aiohttp upgraded them and then failed in the request handler, and
|
|
105
|
+
ASGI accepted them with ``wamp.2.msgpack.batched``, which the client hadn't
|
|
106
|
+
offered.
|
|
107
|
+
* Fix how RPC handlers' parameters are filled:
|
|
108
|
+
|
|
109
|
+
* Handlers given to ``set_rpc_handler()`` failed on every call unless their
|
|
110
|
+
parameter was named ``rpc_request``.
|
|
111
|
+
* Route handlers with keyword-only parameters failed on every call. ``*args``
|
|
112
|
+
and ``**kwargs`` now get the call's leftover arguments.
|
|
113
|
+
* Callers could override default args. Parameters named for a default arg now
|
|
114
|
+
always get its value.
|
|
115
|
+
|
|
116
|
+
* Answer RPC arguments that can't be converted to their parameter's annotated
|
|
117
|
+
type with ``wamp.error.invalid_argument``, per WAMP spec, instead of
|
|
118
|
+
``wamp.error.runtime_error``. Only class annotations are converted to, so
|
|
119
|
+
parameters annotated like ``int | None`` get arguments as they are instead of
|
|
120
|
+
failing, and parameters' own default values are no longer converted.
|
|
121
|
+
|
|
122
|
+
.. _serverwamp-changelog-1.2.0:
|
|
123
|
+
|
|
124
|
+
1.2.0
|
|
125
|
+
-----
|
|
126
|
+
|
|
127
|
+
* Fix the default realm never being used. Sessions requesting a realm that
|
|
128
|
+
wasn't added with ``add_realm`` now join the default realm as documented
|
|
129
|
+
instead of being aborted with ``wamp.error.no_such_realm``.
|
|
130
|
+
|
|
131
|
+
* To reject realms that weren't added, use
|
|
132
|
+
``Application(allow_default_realm=False)``. Configuring the default realm
|
|
133
|
+
through the application (e.g. ``add_rpc_routes``) then raises a
|
|
134
|
+
``RuntimeError`` instead of an ``AttributeError``.
|
|
135
|
+
|
|
136
|
+
* Fix single RPC return values (e.g. ``return 'hello'``) being sent as the
|
|
137
|
+
result arguments themselves instead of as a single-element argument list.
|
|
138
|
+
|
|
139
|
+
* Returning ``None`` still sends a result without arguments. Return ``None,``
|
|
140
|
+
to send ``None`` as the single argument.
|
|
141
|
+
|
|
142
|
+
* Support Python 3.14 and 3.15, and allow msgpack 1.x releases newer than 1.0.
|
|
143
|
+
* Package with pyproject.toml and hatchling.
|
|
144
|
+
* Fix aiohttp logging request handler errors when the server ended a
|
|
145
|
+
connection.
|
|
146
|
+
* Fix ``wamp.2.json.batched`` sessions being aborted with a protocol error, and
|
|
147
|
+
batched JSON messages containing line breaks being dropped.
|
|
148
|
+
* Fix ASGI ``wamp.2.json`` sessions failing on the first message sent to the
|
|
149
|
+
client.
|
|
150
|
+
* Fix ``Application(synchronize_requests=True)`` failing every session on its
|
|
151
|
+
first request after the welcome.
|
|
152
|
+
* Fix messages batched in the same WebSocket message as a client's HELLO being
|
|
153
|
+
dropped.
|
|
154
|
+
* Declare public names in ``__all__`` so strict type checkers accept imports
|
|
155
|
+
such as ``serverwamp.Application``.
|
|
156
|
+
|
|
157
|
+
.. _serverwamp-changelog-1.1.2:
|
|
158
|
+
|
|
159
|
+
1.1.2
|
|
160
|
+
-----
|
|
161
|
+
|
|
162
|
+
* Fix standard library json support.
|
|
163
|
+
|
|
164
|
+
.. _serverwamp-changelog-1.1.1:
|
|
165
|
+
|
|
166
|
+
1.1.1
|
|
167
|
+
-----
|
|
168
|
+
|
|
169
|
+
* Support session close goodbye message.
|
|
170
|
+
|
|
171
|
+
.. _serverwamp-changelog-1.0.7:
|
|
172
|
+
|
|
173
|
+
1.0.7
|
|
174
|
+
-----
|
|
175
|
+
|
|
176
|
+
* Fix using deprecated collections.Sequence.
|
|
177
|
+
* WAMP-compliant msgpack batch processing
|
|
178
|
+
|
|
179
|
+
* Fixes Autobahn|Python out-of-box compatibility.
|
|
180
|
+
|
|
181
|
+
* Allow unauthenticated sessions if no authenticators configured.
|
|
182
|
+
|
|
183
|
+
* ``session.identity`` is ``None`` in this case.
|
|
184
|
+
|
|
185
|
+
.. _serverwamp-changelog-1.0.6:
|
|
186
|
+
|
|
187
|
+
1.0.6
|
|
188
|
+
-----
|
|
189
|
+
|
|
190
|
+
* Server now responds with error when unsubscribing from non-subscribed topic.
|
|
191
|
+
* Temporarily synchronize subscribe/unsubscribe messages.
|
|
192
|
+
|
|
193
|
+
* Synchronization will be made configurable by request type in a future
|
|
194
|
+
version.
|
|
195
|
+
|
|
196
|
+
* Fix for exception raised in trying to process protocol messages from a
|
|
197
|
+
canceled connection.
|
|
198
|
+
|
|
199
|
+
.. _serverwamp-changelog-1.0.5:
|
|
200
|
+
|
|
201
|
+
1.0.5
|
|
202
|
+
-----
|
|
203
|
+
|
|
204
|
+
* Fix error in SCRAM nonce generation
|
|
205
|
+
* Fix lack of response to clients' unsubscribe requests
|
|
206
|
+
* Fix for exception raised in trying to process protocol messages from a
|
|
207
|
+
canceled connection.
|
|
208
|
+
|
|
209
|
+
.. _serverwamp-changelog-1.0.4:
|
|
210
|
+
|
|
211
|
+
1.0.4
|
|
212
|
+
-----
|
|
213
|
+
|
|
214
|
+
* aiohttp sessions now cancellable by server shutdown
|
|
215
|
+
* Session cleanup operations specifically shielded from cancellation
|
|
216
|
+
* Added Application.close_sessions method (undocumented)
|
|
217
|
+
|
|
218
|
+
.. _serverwamp-changelog-1.0.3:
|
|
219
|
+
|
|
220
|
+
1.0.3
|
|
221
|
+
-----
|
|
222
|
+
|
|
223
|
+
* Fix for plain topic routes raising exception during registration. `Issue #4
|
|
224
|
+
<https://github.com/JustinTArthur/serverwamp/issues/4>`_
|
|
225
|
+
* Exceptions in user-supplied RPC code no longer crash the app, but instead
|
|
226
|
+
return a wamp.error.runtime_error to the caller like Crossbar.io's toolchain.
|
|
227
|
+
|
|
228
|
+
.. _serverwamp-changelog-1.0.2:
|
|
229
|
+
|
|
230
|
+
1.0.2
|
|
231
|
+
-----
|
|
232
|
+
|
|
233
|
+
* Add "http_headers_raw" transport_info item for aiohttp and ASGI connections.
|
|
234
|
+
|
|
235
|
+
.. _serverwamp-changelog-1.0.1:
|
|
236
|
+
|
|
237
|
+
1.0.1
|
|
238
|
+
-----
|
|
239
|
+
|
|
240
|
+
* Fix stopasynciteration exception on session close with outstanding
|
|
241
|
+
subscriptions.
|
|
242
|
+
|
|
243
|
+
.. _serverwamp-changelog-1.0.0:
|
|
244
|
+
|
|
245
|
+
1.0.0
|
|
246
|
+
-----
|
|
247
|
+
|
|
248
|
+
Brand new API with…
|
|
249
|
+
|
|
250
|
+
* Support for multiple realms w/ separate handlers
|
|
251
|
+
* Context-manager-friendly subscription handlers
|
|
252
|
+
* Session state handlers.
|
|
253
|
+
* Routes configuration for topics similar to RPC.
|
|
254
|
+
* Slightly more structured concurrency on the asyncio side.
|
|
255
|
+
* Transport authenticators, ticket authenticators, and CRA auth.
|
|
256
|
+
|
|
257
|
+
Released 2020-05-07 alongside new documentation.
|
|
258
|
+
|
|
259
|
+
.. _serverwamp-changelog-0.2.3:
|
|
260
|
+
|
|
261
|
+
0.2.3
|
|
262
|
+
-----
|
|
263
|
+
|
|
264
|
+
Transport auth, RPC and subscriptions now working.
|
|
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "serverwamp"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description = "Components that add Web Application Messaging Protocol features to WebSocket servers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Justin Turner Arthur", email = "justinarthur@gmail.com" },
|
|
14
14
|
]
|
|
@@ -19,11 +19,6 @@ keywords = [
|
|
|
19
19
|
classifiers = [
|
|
20
20
|
"Programming Language :: Python",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
|
-
"Programming Language :: Python :: 3.6",
|
|
23
|
-
"Programming Language :: Python :: 3.7",
|
|
24
|
-
"Programming Language :: Python :: 3.8",
|
|
25
|
-
"Programming Language :: Python :: 3.9",
|
|
26
|
-
"Programming Language :: Python :: 3.10",
|
|
27
22
|
"Programming Language :: Python :: 3.11",
|
|
28
23
|
"Programming Language :: Python :: 3.12",
|
|
29
24
|
"Programming Language :: Python :: 3.13",
|
|
@@ -37,13 +32,18 @@ classifiers = [
|
|
|
37
32
|
"Framework :: Trio",
|
|
38
33
|
]
|
|
39
34
|
dependencies = [
|
|
40
|
-
"dataclasses~=0.6;python_version<'3.7'",
|
|
41
35
|
"msgpack>=1.0.0,<2",
|
|
42
36
|
]
|
|
43
37
|
|
|
44
38
|
[project.optional-dependencies]
|
|
39
|
+
anyio = [
|
|
40
|
+
"anyio>=4.0",
|
|
41
|
+
]
|
|
42
|
+
trio = [
|
|
43
|
+
"trio>=0.25.0",
|
|
44
|
+
]
|
|
45
45
|
docs = [
|
|
46
|
-
"Sphinx>=
|
|
46
|
+
"Sphinx>=5.3,<10",
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
[project.urls]
|
|
@@ -53,15 +53,32 @@ Documentation = "https://serverwamp.readthedocs.io/"
|
|
|
53
53
|
[dependency-groups]
|
|
54
54
|
ci = [
|
|
55
55
|
"aiohttp>=3.0.0",
|
|
56
|
+
"anyio>=4.0",
|
|
57
|
+
"msgpack-types>=0.8.0",
|
|
56
58
|
"mypy>=0.770",
|
|
57
|
-
"pytest>=
|
|
58
|
-
"
|
|
59
|
+
"pytest>=8.0",
|
|
60
|
+
"ruff>=0.17.1,<0.18",
|
|
61
|
+
"trio>=0.25.0",
|
|
59
62
|
]
|
|
60
63
|
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 99
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
# Ruff's default rules, as of 0.17, are far broader than these.
|
|
69
|
+
select = ["E4", "E7", "E9", "F", "E501", "I", "Q", "UP"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint.flake8-quotes]
|
|
72
|
+
inline-quotes = "single"
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint.isort]
|
|
75
|
+
# Helper modules the tests import from their own directory.
|
|
76
|
+
known-local-folder = ["memory_connection", "stream_connection"]
|
|
77
|
+
|
|
61
78
|
[tool.hatch.build.targets.sdist]
|
|
62
79
|
include = [
|
|
63
80
|
"/serverwamp",
|
|
64
|
-
"/
|
|
81
|
+
"/docs/changelog.rst",
|
|
65
82
|
"/LICENSE.md",
|
|
66
83
|
"/README.md",
|
|
67
84
|
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from serverwamp.application import Application, Realm
|
|
2
|
+
from serverwamp.connection import ConnectionClosed
|
|
3
|
+
from serverwamp.events import SubscribeError, TopicRouteSet
|
|
4
|
+
from serverwamp.rpc import RPCErrorResult, RPCProgressReport, RPCRequest, RPCResult, RPCRouteSet
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
'Application',
|
|
8
|
+
'ConnectionClosed',
|
|
9
|
+
'Realm',
|
|
10
|
+
'RPCErrorResult',
|
|
11
|
+
'RPCProgressReport',
|
|
12
|
+
'RPCRequest',
|
|
13
|
+
'RPCResult',
|
|
14
|
+
'RPCRouteSet',
|
|
15
|
+
'SubscribeError',
|
|
16
|
+
'TopicRouteSet',
|
|
17
|
+
)
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
from abc import ABCMeta
|
|
2
|
+
from collections.abc import Mapping
|
|
2
3
|
from ssl import SSLObject
|
|
3
|
-
from typing import Optional
|
|
4
4
|
|
|
5
5
|
import aiohttp
|
|
6
6
|
import msgpack
|
|
7
7
|
from aiohttp import WSMsgType, web
|
|
8
8
|
|
|
9
|
-
from serverwamp.connection import Connection
|
|
10
|
-
from serverwamp.helpers import objects_from_msgpack_batch, pack_uint32_be
|
|
11
|
-
from serverwamp.json import JSON_BATCH_SPLITTER
|
|
12
|
-
from serverwamp.json import
|
|
13
|
-
from serverwamp.json import objects_from_json_batch
|
|
9
|
+
from serverwamp.connection import Connection, ConnectionClosed
|
|
10
|
+
from serverwamp.helpers import object_from_msgpack, objects_from_msgpack_batch, pack_uint32_be
|
|
11
|
+
from serverwamp.json import JSON_BATCH_SPLITTER, objects_from_json_batch
|
|
12
|
+
from serverwamp.json import deserialize_msg as deserialize_json
|
|
14
13
|
from serverwamp.json import serialize as serialize_json
|
|
15
14
|
|
|
16
15
|
SUPPORTED_WS_PROTOCOLS = (
|
|
@@ -23,9 +22,15 @@ SUPPORTED_WS_PROTOCOLS = (
|
|
|
23
22
|
|
|
24
23
|
async def connection_for_aiohttp_request(request: aiohttp.web.Request):
|
|
25
24
|
ws = web.WebSocketResponse(protocols=SUPPORTED_WS_PROTOCOLS)
|
|
25
|
+
ws_protocol = ws.can_prepare(request).protocol
|
|
26
|
+
if ws_protocol is None:
|
|
27
|
+
# WAMP must be negotiated as a WebSocket subprotocol.
|
|
28
|
+
raise web.HTTPBadRequest(
|
|
29
|
+
text='A supported WAMP WebSocket subprotocol is required.'
|
|
30
|
+
)
|
|
26
31
|
await ws.prepare(request)
|
|
27
32
|
|
|
28
|
-
construct_connection = ws_protocol_connection_classes[
|
|
33
|
+
construct_connection = ws_protocol_connection_classes[ws_protocol]
|
|
29
34
|
connection = construct_connection(ws, request)
|
|
30
35
|
return connection
|
|
31
36
|
|
|
@@ -51,7 +56,7 @@ class AiohttpWebSocketConnection(Connection, metaclass=ABCMeta):
|
|
|
51
56
|
self.transport_info['peer_certificate'] = (
|
|
52
57
|
request.transport.get_extra_info('peercert')
|
|
53
58
|
)
|
|
54
|
-
ssl_obj:
|
|
59
|
+
ssl_obj: SSLObject | None = (
|
|
55
60
|
request.transport.get_extra_info('ssl_object')
|
|
56
61
|
)
|
|
57
62
|
if ssl_obj:
|
|
@@ -75,10 +80,11 @@ class AiohttpJSONWebSocketConnection(AiohttpWebSocketConnection):
|
|
|
75
80
|
yield deserialize_json(ws_msg.data)
|
|
76
81
|
|
|
77
82
|
async def send_msg(self, msg):
|
|
78
|
-
|
|
79
|
-
msg,
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
try:
|
|
84
|
+
await self._ws.send_json(msg, compress=self._compress_outbound)
|
|
85
|
+
except ConnectionResetError:
|
|
86
|
+
# aiohttp raises this when writing to a closing transport.
|
|
87
|
+
raise ConnectionClosed() from None
|
|
82
88
|
|
|
83
89
|
|
|
84
90
|
class AiohttpBatchedJSONWebSocketConnection(AiohttpWebSocketConnection):
|
|
@@ -89,32 +95,41 @@ class AiohttpBatchedJSONWebSocketConnection(AiohttpWebSocketConnection):
|
|
|
89
95
|
yield msg
|
|
90
96
|
|
|
91
97
|
async def send_msg(self, msg):
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
try:
|
|
99
|
+
await self._ws.send_str(
|
|
100
|
+
serialize_json(msg) + JSON_BATCH_SPLITTER,
|
|
101
|
+
compress=self._compress_outbound
|
|
102
|
+
)
|
|
103
|
+
except ConnectionResetError:
|
|
104
|
+
raise ConnectionClosed() from None
|
|
96
105
|
|
|
97
106
|
async def send_msgs(self, msgs):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
try:
|
|
108
|
+
await self._ws.send_str(
|
|
109
|
+
JSON_BATCH_SPLITTER.join(
|
|
110
|
+
[serialize_json(msg) for msg in msgs]
|
|
111
|
+
) + JSON_BATCH_SPLITTER,
|
|
112
|
+
compress=self._compress_outbound
|
|
113
|
+
)
|
|
114
|
+
except ConnectionResetError:
|
|
115
|
+
raise ConnectionClosed() from None
|
|
104
116
|
|
|
105
117
|
|
|
106
118
|
class AiohttpMsgPackWebSocketConnection(AiohttpWebSocketConnection):
|
|
107
119
|
async def iterate_msgs(self):
|
|
108
120
|
async for ws_msg in self._ws:
|
|
109
121
|
if ws_msg.type == WSMsgType.BINARY:
|
|
110
|
-
yield
|
|
122
|
+
yield object_from_msgpack(ws_msg.data)
|
|
111
123
|
|
|
112
124
|
async def send_msg(self, msg):
|
|
113
125
|
msg_bytes = msgpack.packb(msg)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
try:
|
|
127
|
+
await self._ws.send_bytes(
|
|
128
|
+
msg_bytes,
|
|
129
|
+
compress=self._compress_outbound
|
|
130
|
+
)
|
|
131
|
+
except ConnectionResetError:
|
|
132
|
+
raise ConnectionClosed() from None
|
|
118
133
|
|
|
119
134
|
|
|
120
135
|
class AiohttpBatchedMsgPackWebSocketConnection(AiohttpWebSocketConnection):
|
|
@@ -129,10 +144,13 @@ class AiohttpBatchedMsgPackWebSocketConnection(AiohttpWebSocketConnection):
|
|
|
129
144
|
async def send_msg(self, msg):
|
|
130
145
|
msg_bytes = msgpack.packb(msg)
|
|
131
146
|
msg_len_bytes = pack_uint32_be(len(msg_bytes))
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
try:
|
|
148
|
+
await self._ws.send_bytes(
|
|
149
|
+
msg_len_bytes + msg_bytes,
|
|
150
|
+
compress=self._compress_outbound
|
|
151
|
+
)
|
|
152
|
+
except ConnectionResetError:
|
|
153
|
+
raise ConnectionClosed() from None
|
|
136
154
|
|
|
137
155
|
async def send_msgs(self, msgs):
|
|
138
156
|
batch_bytes = bytearray()
|
|
@@ -141,13 +159,18 @@ class AiohttpBatchedMsgPackWebSocketConnection(AiohttpWebSocketConnection):
|
|
|
141
159
|
msg_len_bytes = pack_uint32_be(len(msg_bytes))
|
|
142
160
|
batch_bytes += msg_len_bytes
|
|
143
161
|
batch_bytes += msg_bytes
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
try:
|
|
163
|
+
await self._ws.send_bytes(
|
|
164
|
+
batch_bytes,
|
|
165
|
+
compress=self._compress_outbound
|
|
166
|
+
)
|
|
167
|
+
except ConnectionResetError:
|
|
168
|
+
raise ConnectionClosed() from None
|
|
148
169
|
|
|
149
170
|
|
|
150
|
-
ws_protocol_connection_classes
|
|
171
|
+
ws_protocol_connection_classes: Mapping[
|
|
172
|
+
str, type[AiohttpWebSocketConnection]
|
|
173
|
+
] = {
|
|
151
174
|
'wamp.2.msgpack': AiohttpMsgPackWebSocketConnection,
|
|
152
175
|
'wamp.2.msgpack.batched': AiohttpBatchedMsgPackWebSocketConnection,
|
|
153
176
|
'wamp.2.json': AiohttpJSONWebSocketConnection,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
1
2
|
from functools import partial
|
|
2
3
|
|
|
3
4
|
import anyio
|
|
5
|
+
import anyio.abc
|
|
4
6
|
|
|
5
7
|
from serverwamp.adapters.async_base import AsyncSupport, AsyncTaskGroup
|
|
6
|
-
from serverwamp.context import asynccontextmanager
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class AnyioAsyncSupport(AsyncSupport):
|
|
@@ -15,30 +16,26 @@ class AnyioAsyncSupport(AsyncSupport):
|
|
|
15
16
|
yield task_group
|
|
16
17
|
|
|
17
18
|
@classmethod
|
|
18
|
-
@asynccontextmanager
|
|
19
19
|
async def shield(
|
|
20
20
|
cls,
|
|
21
21
|
callback,
|
|
22
22
|
*callback_args,
|
|
23
23
|
**callback_kwargs
|
|
24
24
|
):
|
|
25
|
-
|
|
26
|
-
await callback(*callback_args, **callback_kwargs)
|
|
25
|
+
with anyio.CancelScope(shield=True):
|
|
26
|
+
return await callback(*callback_args, **callback_kwargs)
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class AnyioTaskGroup(AsyncTaskGroup):
|
|
30
30
|
"""In anyio's case, this is just a light wrapper around its own TaskGroup.
|
|
31
31
|
"""
|
|
32
|
-
def __init__(self, anyio_task_group: anyio.TaskGroup):
|
|
32
|
+
def __init__(self, anyio_task_group: anyio.abc.TaskGroup):
|
|
33
33
|
self._anyio_task_group = anyio_task_group
|
|
34
34
|
|
|
35
35
|
async def spawn(self, callback, *callback_args, **callback_kwargs):
|
|
36
36
|
if callback_kwargs:
|
|
37
37
|
callback = partial(callback, **callback_kwargs)
|
|
38
|
-
|
|
39
|
-
await self._anyio_task_group.spawn(
|
|
40
|
-
callback, *callback_args, **callback_kwargs
|
|
41
|
-
)
|
|
38
|
+
self._anyio_task_group.start_soon(callback, *callback_args)
|
|
42
39
|
|
|
43
40
|
async def cancel(self):
|
|
44
|
-
|
|
41
|
+
self._anyio_task_group.cancel_scope.cancel()
|