Skip to content

Commit 785d980

Browse files
authored
Merge pull request #98 from rsocket/routing_improvements
routing improvements
2 parents 39c9be1 + 0e0e277 commit 785d980

31 files changed

+522
-141
lines changed

CHANGELOG.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ v0.4.6
55
======
66
- fire_and_forget now only removes the stream id when the future denoting the frame was sent, is done
77
- API documentation auto generated at rsocket.readthedocs.io
8+
- Request router changes:
9+
- Raise error on empty or None route specified in request router
10+
- Added the following methods to RequestRouter to allow specifying handlers of unknown routes:
11+
- response_unknown
12+
- stream_unknown
13+
- channel_unknown
14+
- fire_and_forget_unknown
15+
- metadata_push_unknown
16+
- Officially support route aliases by using the decorator multiple times on the same method
17+
- Fix value mapping in request router:
18+
-A parameter of any name (not just *payload*) specified on a routed method with a type-hint other than Payload will use the payload_mapper to decode the value
19+
- Any parameter with the type CompositeMetadata will receive the composite metadata
820

921
v0.4.5
1022
======

docs/api.rst

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
Core API Reference
22
==================
33

4+
Controls
5+
--------
6+
47
Server
5-
------
8+
~~~~~~
69

710
.. automodule:: rsocket.rsocket_server
811
:members:
912
:inherited-members:
1013

1114
Client
12-
------
15+
~~~~~~
1316

1417
.. automodule:: rsocket.rsocket_client
1518
:members:
1619
:inherited-members:
1720

1821
Handler
19-
-------
22+
~~~~~~~
2023

2124
.. automodule:: rsocket.request_handler
2225
:members:
2326

2427

28+
Models
29+
------
30+
31+
.. automodule:: rsocket.payload
32+
:members:
33+
34+
2535
Interfaces
2636
----------
2737

docs/quickstart.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ Quick start
44
.. autosummary::
55
:toctree: generated
66

7-
87
A quick getting started guide is available at https://rsocket.io/guides/rsocket-py/simple

examples/tutorial/reactivex/chat_client.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from reactivex import operators
99

10-
from examples.tutorial.reactivex.models import (Message, chat_filename_mimetype, ServerStatistics, ClientStatistics,
10+
from examples.tutorial.reactivex.shared import (Message, chat_filename_mimetype, ServerStatistics, ClientStatistics,
1111
ServerStatisticsRequest, encode_dataclass, dataclass_to_payload,
1212
decode_dataclass)
1313
from rsocket.extensions.helpers import composite, route, metadata_item
@@ -147,6 +147,12 @@ async def list_channels(self) -> List[str]:
147147
).pipe(operators.map(lambda _: utf8_decode(_.data)),
148148
operators.to_list())
149149

150+
async def list_channel_users(self, channel_name: str) -> List[str]:
151+
request = Payload(ensure_bytes(channel_name), composite(route('channel.users')))
152+
return await ReactiveXClient(self._rsocket).request_stream(
153+
request
154+
).pipe(operators.map(lambda _: utf8_decode(_.data)),
155+
operators.to_list())
150156

151157
async def main():
152158
connection1 = await asyncio.open_connection('localhost', 6565)
@@ -174,16 +180,21 @@ async def messaging_example(user1, user2):
174180
user1.listen_for_messages()
175181
user2.listen_for_messages()
176182

177-
await user1.join('channel1')
178-
await user2.join('channel1')
183+
channel_name = 'channel1'
184+
await user1.join(channel_name)
185+
await user2.join(channel_name)
179186

180187
print(f'Channels: {await user1.list_channels()}')
188+
print(f'Channel {channel_name} users: {await user1.list_channel_users(channel_name)}')
181189

182190
await user1.private_message('user2', 'private message from user1')
183-
await user1.channel_message('channel1', 'channel message from user1')
191+
await user1.channel_message(channel_name, 'channel message from user1')
184192

185193
await asyncio.sleep(1)
186194

195+
await user1.leave(channel_name)
196+
print(f'Channel {channel_name} users: {await user1.list_channel_users(channel_name)}')
197+
187198
user1.stop_listening_for_messages()
188199
user2.stop_listening_for_messages()
189200

examples/tutorial/reactivex/chat_server.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
from more_itertools import first
1212
from reactivex import Observable, operators, Subject, Observer
1313

14-
from examples.tutorial.reactivex.models import (Message, chat_filename_mimetype, ClientStatistics,
14+
from examples.tutorial.reactivex.shared import (Message, chat_filename_mimetype, ClientStatistics,
1515
ServerStatisticsRequest, ServerStatistics, dataclass_to_payload,
16-
decode_dataclass)
16+
decode_dataclass, decode_payload)
1717
from rsocket.extensions.composite_metadata import CompositeMetadata
1818
from rsocket.extensions.helpers import composite, metadata_item
1919
from rsocket.frame_helpers import ensure_bytes
@@ -36,15 +36,15 @@ class SessionId(str): # allow weak reference
3636
@dataclass()
3737
class UserSessionData:
3838
username: str
39-
session_id: str
39+
session_id: SessionId
4040
messages: Queue = field(default_factory=Queue)
4141
statistics: Optional[ClientStatistics] = None
4242
requested_statistics: ServerStatisticsRequest = field(default_factory=ServerStatisticsRequest)
4343

4444

4545
@dataclass(frozen=True)
4646
class ChatData:
47-
channel_users: Dict[str, Set[str]] = field(default_factory=lambda: defaultdict(WeakSet))
47+
channel_users: Dict[str, Set[SessionId]] = field(default_factory=lambda: defaultdict(WeakSet))
4848
files: Dict[str, bytes] = field(default_factory=dict)
4949
channel_messages: Dict[str, Queue] = field(default_factory=lambda: defaultdict(Queue))
5050
user_session_by_id: Dict[str, UserSessionData] = field(default_factory=WeakValueDictionary)
@@ -95,6 +95,13 @@ def new_statistics_data(requested_statistics: ServerStatisticsRequest):
9595
return ServerStatistics(**statistics_data)
9696

9797

98+
def find_username_by_session(session_id: SessionId) -> Optional[str]:
99+
session = chat_data.user_session_by_id.get(session_id)
100+
if session is None:
101+
return None
102+
return session.username
103+
104+
98105
class ChatUserSession:
99106

100107
def __init__(self):
@@ -105,11 +112,10 @@ def remove(self):
105112
del chat_data.user_session_by_id[self._session.session_id]
106113

107114
def router_factory(self):
108-
router = RequestRouter()
115+
router = RequestRouter(payload_mapper=decode_payload)
109116

110117
@router.response('login')
111-
async def login(payload: Payload) -> Observable:
112-
username = utf8_decode(payload.data)
118+
async def login(username: str) -> Observable:
113119
logging.info(f'New user: {username}')
114120
session_id = SessionId(uuid.uuid4())
115121
self._session = UserSessionData(username, session_id)
@@ -118,15 +124,13 @@ async def login(payload: Payload) -> Observable:
118124
return reactivex.just(Payload(ensure_bytes(session_id)))
119125

120126
@router.response('channel.join')
121-
async def join_channel(payload: Payload) -> Observable:
122-
channel_name = utf8_decode(payload.data)
127+
async def join_channel(channel_name: str) -> Observable:
123128
ensure_channel_exists(channel_name)
124129
chat_data.channel_users[channel_name].add(self._session.session_id)
125130
return reactivex.empty()
126131

127132
@router.response('channel.leave')
128-
async def leave_channel(payload: Payload) -> Observable:
129-
channel_name = utf8_decode(payload.data)
133+
async def leave_channel(channel_name: str) -> Observable:
130134
chat_data.channel_users[channel_name].discard(self._session.session_id)
131135
return reactivex.empty()
132136

@@ -157,10 +161,17 @@ async def get_channels() -> Observable:
157161
return reactivex.from_iterable(
158162
(Payload(ensure_bytes(channel)) for channel in chat_data.channel_messages.keys()))
159163

160-
@router.fire_and_forget('statistics')
161-
async def receive_statistics(payload: Payload):
162-
statistics = decode_dataclass(payload.data, ClientStatistics)
164+
@router.stream('channel.users')
165+
async def get_channel_users(channel_name: str) -> Observable:
166+
if channel_name not in chat_data.channel_users:
167+
return reactivex.empty()
163168

169+
return reactivex.from_iterable(Payload(ensure_bytes(find_username_by_session(session_id))) for
170+
session_id in
171+
chat_data.channel_users[channel_name])
172+
173+
@router.fire_and_forget('statistics')
174+
async def receive_statistics(statistics: ClientStatistics):
164175
logging.info('Received client statistics. memory usage: %s', statistics.memory_usage)
165176

166177
self._session.statistics = statistics
@@ -198,9 +209,7 @@ def on_next(payload: Payload):
198209
limit_rate=2)
199210

200211
@router.response('message')
201-
async def send_message(payload: Payload) -> Observable:
202-
message = decode_dataclass(payload.data, Message)
203-
212+
async def send_message(message: Message) -> Observable:
204213
logging.info('Received message for user: %s, channel: %s', message.user, message.channel)
205214

206215
target_message = Message(self._session.username, message.content, message.channel)

examples/tutorial/reactivex/models.py renamed to examples/tutorial/reactivex/shared.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,14 @@ def dataclass_to_payload(obj) -> Payload:
4747

4848
def decode_dataclass(data: bytes, cls: Type[T]) -> T:
4949
return cls(**json.loads(utf8_decode(data)))
50+
51+
52+
def decode_payload(cls, payload: Payload):
53+
data = payload.data
54+
55+
if cls is bytes:
56+
return data
57+
if cls is str:
58+
return utf8_decode(data)
59+
60+
return decode_dataclass(data, cls)

examples/tutorial/step3/chat_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from typing import Optional
44

5-
from examples.tutorial.step3.models import Message, encode_dataclass, decode_dataclass
5+
from examples.tutorial.step3.shared import Message, encode_dataclass, decode_dataclass
66
from reactivestreams.subscriber import DefaultSubscriber
77
from reactivestreams.subscription import DefaultSubscription
88
from rsocket.extensions.helpers import composite, route

examples/tutorial/step3/chat_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from more_itertools import first
1010

11-
from examples.tutorial.step3.models import Message, dataclass_to_payload, decode_dataclass
11+
from examples.tutorial.step3.shared import Message, dataclass_to_payload, decode_dataclass
1212
from reactivestreams.publisher import DefaultPublisher, Publisher
1313
from reactivestreams.subscriber import Subscriber
1414
from reactivestreams.subscription import DefaultSubscription
File renamed without changes.

examples/tutorial/step4/chat_client.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from typing import List, Optional
44

5-
from examples.tutorial.step4.models import Message, encode_dataclass, decode_dataclass
5+
from examples.tutorial.step4.shared import Message, encode_dataclass, decode_dataclass
66
from reactivestreams.subscriber import DefaultSubscriber
77
from reactivestreams.subscription import DefaultSubscription
88
from rsocket.awaitable.awaitable_rsocket import AwaitableRSocket
@@ -77,6 +77,11 @@ async def list_channels(self) -> List[str]:
7777
response = await AwaitableRSocket(self._rsocket).request_stream(request)
7878
return list(map(lambda _: utf8_decode(_.data), response))
7979

80+
async def list_channel_users(self, channel_name: str):
81+
request = Payload(ensure_bytes(channel_name), composite(route('channel.users')))
82+
response = await AwaitableRSocket(self._rsocket).request_stream(request)
83+
return list(map(lambda _: utf8_decode(_.data), response))
84+
8085

8186
async def main():
8287
connection1 = await asyncio.open_connection('localhost', 6565)
@@ -100,16 +105,22 @@ async def messaging_example(user1: ChatClient, user2: ChatClient):
100105
user1.listen_for_messages()
101106
user2.listen_for_messages()
102107

103-
await user1.join('channel1')
104-
await user2.join('channel1')
108+
channel_name = 'channel1'
109+
110+
await user1.join(channel_name)
111+
await user2.join(channel_name)
105112

106113
print(f'Channels: {await user1.list_channels()}')
114+
print(f'Channel {channel_name} users: {await user1.list_channel_users(channel_name)}')
107115

108116
await user1.private_message('user2', 'private message from user1')
109-
await user1.channel_message('channel1', 'channel message from user1')
117+
await user1.channel_message(channel_name, 'channel message from user1')
110118

111119
await asyncio.sleep(1)
112120

121+
await user1.leave(channel_name)
122+
print(f'Channel {channel_name} users: {await user1.list_channel_users(channel_name)}')
123+
113124
user1.stop_listening_for_messages()
114125
user2.stop_listening_for_messages()
115126

0 commit comments

Comments
 (0)