Skip to content

Commit 2aefde0

Browse files
author
Gabriel Shaar
authored
Initial draft rsocket python guide (for future version 0.3) (#64)
1 parent ac4c29f commit 2aefde0

12 files changed

+13835
-17
lines changed

content-docs/guides/index.mdx

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ In this section you will find guides related to working with and consuming the v
99
## Language Specific Guides
1010

1111
- [`rsocket-js`](./rsocket-js/index.mdx)
12+
- [`rsocket-py`](./rsocket-py/index.mdx)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
slug: /guides/rsocket-py/client
3+
title: RSocketClient - rsocket-py
4+
sidebar_label: Introduction
5+
---
6+
7+
An `rsocket-py` client can be used to communicate with any RSocket Server implemented against the same protocol version as the client,
8+
and which implements the same transport as the client.
9+
10+
Available network transports for `rsocket-py` client include:
11+
12+
- TCP - available by default
13+
- Websocket (aiohttp)
14+
15+
The `RSocketClient` class should be passed an instance of one of the available transports.
16+
17+
To get started creating an RSocket client, you will need to install the [rsocket](https://pypi.org/project/rsocket/) package,
18+
and at least one transport protocol implementation (TCP available by default).
19+
20+
### Client Quick Start Example
21+
22+
```
23+
npm install rsocket-core rsocket-websocket-client
24+
```
25+
26+
```py
27+
import asyncio
28+
from rsocket.rsocket_client import RSocketClient
29+
from rsocket.transports.tcp import TransportTCP
30+
31+
async def main():
32+
connection = await asyncio.open_connection('localhost', 6565)
33+
34+
async with RSocketClient(TransportTCP(*connection)) as client:
35+
... # Execute requests
36+
37+
if __name__ == '__main__':
38+
asyncio.run(main())
39+
```
40+
41+
## Client API
42+
43+
The `rsocket-py` package exposes the following types:
44+
45+
## RSocketClient (class)
46+
47+
`RSocketClient` is used to create an instance of a client. The clients' connection does not initialize until
48+
the `connect` method is invoked, or it is used as a context-manager.
49+
50+
### constructor (function)
51+
52+
53+
#### serializers (property)
54+
55+
The `data` and `metadata` of each payload are passed through to the
56+
transport layer as-is. This is appropriate for sending/receiving strings/binary.
57+
58+
59+
#### transport (property)
60+
61+
This will typically be an instance conforming to the API of the `Transport` class.
62+
63+
64+
### connect() (method)
65+
66+
This method opens the connection to the peer. Internally this calls `connect()` on the
67+
transport client. See below for the `RSocket` interface.
68+
69+
## RSocket (interface)
70+
71+
This interface represents an instance of a rsocket peer-to-peer connection, providing the five
72+
core interactions (fire/forget, request/response, etc.):
73+
74+
### fire_and_forget() (method)
75+
76+
This method sends data/metadata to the server without waiting for a response. The data is
77+
sent immediately.
78+
79+
```py
80+
from rsocket.payload import Payload
81+
82+
def fire_and_forget(self, payload: Payload):
83+
...
84+
```
85+
86+
### request_response() (method)
87+
88+
This method sends data/metadata to the server, which returns a single response. The data is
89+
sent lazily when the returned `Future` is resolved.
90+
91+
```py
92+
from rsocket.payload import Payload
93+
from asyncio import Future
94+
95+
def request_response(self, payload: Payload) -> Future:
96+
...
97+
```
98+
99+
### request_stream() (method)
100+
101+
This method sends data/metadata to the server, which returns a stream of responses. The semantics
102+
of the stream are application-specific. For example, the stream may represent
103+
updates to a single conceptual value over time, items in an incrementally loaded
104+
list, events, etc. The data is sent to the peer lazily when the returned
105+
`Publisher` is subscribed to and `request(n)` is called to signal demand.
106+
107+
```py
108+
from typing import Union
109+
110+
from reactivestreams.publisher import Publisher
111+
from rsocket.payload import Payload
112+
from rsocket.streams.stream import Stream
113+
114+
def request_stream(self, payload: Payload) -> Union[Stream, Publisher]:
115+
...
116+
```
117+
118+
### requestChannel() (method)
119+
120+
This method establishes an understanding between a client and a server where each intends to send and receive streams
121+
of data from the other. Each actor in this relationship is responsible for signaling to the other that they are ready
122+
to receive data by invoking `request(n)`, where `n` is the max number of payloads the actor is comfortable handling.
123+
Conceptually, `request_channel` can be thought of as two entities 'polling' from each other by signaling to the others
124+
that they are ready to accept `n` number of messages. Inversely, `request_channel` can be leveraged to facilitate
125+
a consistent stream of data transfer payloads between client and server by each (or either)
126+
invoking `request(0x7fffffff)`, where `0x7fffffff` is the max integer value for `int32`.
127+
128+
```py
129+
from typing import Union, Optional
130+
131+
from reactivestreams.publisher import Publisher
132+
from rsocket.payload import Payload
133+
from rsocket.streams.stream import Stream
134+
135+
def request_channel(self, payload: Payload, publisher: Optional[Publisher] = None) -> Union[Stream, Publisher]:
136+
...
137+
```
138+
139+
### metadata_push() (method)
140+
141+
This method sends metadata only to the server without waiting for a response. The payload is
142+
sent immediately. This method is not for the direct application usage and should be used to exchange some service level information
143+
144+
```python
145+
def metadata_push(self, metadata: bytes):
146+
...
147+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
slug: /guides/rsocket-py/client/rsocket-tcp-client
3+
title: RSocketTCPClient - rsocket-py
4+
sidebar_label: TCP Client
5+
---
6+
7+
The `TransportTCP` implements the RSocket protocol using the TCP network transport protocol,
8+
and is suitable for Server to Server, and other scenarios where raw TCP is available.
9+
10+
11+
### TCP Client Quick Start Example
12+
13+
```
14+
npm install rsocket-core rsocket-tcp-client
15+
```
16+
17+
```py
18+
import asyncio
19+
from rsocket.rsocket_client import RSocketClient
20+
from rsocket.transports.tcp import TransportTCP
21+
22+
async def main():
23+
connection = await asyncio.open_connection('localhost', 6565)
24+
25+
async with RSocketClient(TransportTCP(*connection)) as client:
26+
... # Execute requests
27+
28+
if __name__ == '__main__':
29+
asyncio.run(main())
30+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
slug: /guides/rsocket-py/client/rsocket-websocket-client
3+
title: RSocketWebsocketClient - rsocket-py
4+
sidebar_label: WebSocket Client
5+
---
6+
7+
The `RSocketWebsocketClient` implements the RSocket protocol using the WebSocket network transport protocol, and is suitable for Server to Server, and Client to Server scenarios where raw TCP is not available, such as in a web browser.
8+
9+
### WebSocket Client Quick Start Example
10+
11+
12+
```py
13+
import asyncio
14+
15+
from rsocket.payload import Payload
16+
from rsocket.transports.aiohttp_websocket import websocket_client
17+
18+
19+
async def application():
20+
async with websocket_client('http://localhost:6565') as client:
21+
result = await client.request_response(Payload(b'ping'))
22+
print(result)
23+
24+
25+
if __name__ == '__main__':
26+
asyncio.run(application())
27+
```
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
slug: /guides/rsocket-py
3+
title: rsocket-py
4+
sidebar_label: Introduction
5+
---
6+
7+
:::important
8+
This documentation is for version 0.3.0 of `rsocket` (currently only available via git-hub)
9+
:::
10+
11+
:::caution
12+
The python package API is not stable. There may be changes until version 1.0.0
13+
:::
14+
15+
The python `rsocket` package implements the 1.0 version of the [RSocket protocol](https://rsocket.io/about/protocol)
16+
and is designed for use in python >= 3.8 using asyncio.
17+
18+
## Packages
19+
20+
A pypi package is available at [rsocket](https://pypi.org/project/rsocket/)
21+
22+
## Status
23+
24+
The following are currently implemented:
25+
26+
- RSocketClient / RSocketServer
27+
- TCP/WebSocket server/client transport (Websocket integration with Quart and aiohttp)
28+
- Minimal integration with RxPy >= 3.x
29+
30+
## WebSocket Client & Server example
31+
32+
### Client Example
33+
34+
The client sends a `request/response` message to the server on an interval, with the requested date-time format, and exits after a certain amount of time has elapsed.
35+
36+
```py
37+
# client.py
38+
import asyncio
39+
import logging
40+
41+
from reactivestreams.subscriber import Subscriber
42+
from rsocket.payload import Payload
43+
from rsocket.rsocket_client import RSocketClient
44+
from rsocket.transports.tcp import TransportTCP
45+
46+
47+
class StreamSubscriber(Subscriber):
48+
49+
async def on_next(self, value, is_complete=False):
50+
await self.subscription.request(1)
51+
52+
def on_subscribe(self, subscription):
53+
self.subscription = subscription
54+
55+
56+
async def main():
57+
connection = await asyncio.open_connection('localhost', 6565)
58+
59+
async with RSocketClient(TransportTCP(*connection)) as client:
60+
payload = Payload(b'%Y-%m-%d %H:%M:%S')
61+
62+
async def run_request_response():
63+
try:
64+
while True:
65+
result = await client.request_response(payload)
66+
logging.info('Response: {}'.format(result.data))
67+
await asyncio.sleep(1)
68+
except asyncio.CancelledError:
69+
pass
70+
71+
task = asyncio.create_task(run_request_response())
72+
73+
await asyncio.sleep(5)
74+
task.cancel()
75+
await task
76+
77+
78+
if __name__ == '__main__':
79+
logging.basicConfig(level=logging.DEBUG)
80+
asyncio.run(main())
81+
```
82+
83+
### Server Example
84+
85+
The server responds to `request/response` messages with the current formatted date-time.
86+
87+
```py
88+
# server.py
89+
import asyncio
90+
import logging
91+
from datetime import datetime
92+
93+
from rsocket.payload import Payload
94+
from rsocket.request_handler import BaseRequestHandler
95+
from rsocket.rsocket_server import RSocketServer
96+
from rsocket.transports.tcp import TransportTCP
97+
98+
99+
class Handler(BaseRequestHandler):
100+
async def request_response(self, payload: Payload) -> asyncio.Future:
101+
await asyncio.sleep(0.1) # Simulate not immediate process
102+
future = asyncio.Future()
103+
date_time_format = payload.data.decode('utf-8')
104+
formatted_date_time = datetime.now().strftime(date_time_format)
105+
future.set_result(Payload(formatted_date_time.encode('utf-8')))
106+
return future
107+
108+
109+
async def run_server():
110+
def session(*connection):
111+
RSocketServer(TransportTCP(*connection), handler_factory=Handler)
112+
113+
server = await asyncio.start_server(session, 'localhost', 6565)
114+
115+
async with server:
116+
await server.serve_forever()
117+
118+
119+
if __name__ == '__main__':
120+
logging.basicConfig(level=logging.DEBUG)
121+
asyncio.run(run_server())
122+
```
123+
124+
## More Examples
125+
126+
Browse the following repositories for more `rsocket-py` examples:
127+
128+
- [rsocket-py/examples](https://github.com/rsocket/rsocket-py/tree/master/examples)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
slug: /guides/rsocket-py/rxpy
3+
title: RxPy integration
4+
sidebar_label: Introduction
5+
---
6+
7+
## Reactive Streams
8+
9+
The `rsocket-py` implementation doesn't use [RxPy](https://pypi.org/project/Rx/) by default. A wrapper class `RxRSocketClient`
10+
can be used to interact with [RxPy](https://pypi.org/project/Rx/) (>= 3.2.0) entities (`Observable`, `Observer`)
11+

0 commit comments

Comments
 (0)