fastapi-ws-rpc 0.1.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.
- fastapi_ws_rpc-0.1.0/LICENSE +15 -0
- fastapi_ws_rpc-0.1.0/PKG-INFO +196 -0
- fastapi_ws_rpc-0.1.0/README.md +165 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/__init__.py +11 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/connection_manager.py +15 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/logger.py +113 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/rpc_channel.py +482 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/rpc_methods.py +98 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/schemas.py +44 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/simplewebsocket.py +65 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/utils.py +89 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/websocket_rpc_client.py +318 -0
- fastapi_ws_rpc-0.1.0/fastapi_ws_rpc/websocket_rpc_endpoint.py +141 -0
- fastapi_ws_rpc-0.1.0/pyproject.toml +32 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright (c) <2020> <Or Weis, Asaf Cohen>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
4
|
+
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
5
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
6
|
+
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
7
|
+
subject to the following conditions:
|
|
8
|
+
The above copyright notice and this permission notice
|
|
9
|
+
shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
11
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
12
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
13
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
14
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
15
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fastapi_ws_rpc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fast and durable bidirectional JSON RPC channel over Websockets and FastApi.
|
|
5
|
+
Home-page: https://github.com/permitio/fastapi_websocket_rpc
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: fastapi,websockets,rpc,json,bidirectional
|
|
8
|
+
Author: Or Weis
|
|
9
|
+
Author-email: or@permit.io
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
|
23
|
+
Requires-Dist: fastapi (>=0.78.0,<1)
|
|
24
|
+
Requires-Dist: packaging (>=20.4)
|
|
25
|
+
Requires-Dist: pydantic (>=1.9.1)
|
|
26
|
+
Requires-Dist: tenacity (>=8.0.1,<9)
|
|
27
|
+
Requires-Dist: uvicorn (>=0.17.6,<1)
|
|
28
|
+
Requires-Dist: websockets (>=10.3,<11)
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img src="https://i.ibb.co/m8jL6Zd/RPC.png" alt="RPC" border="0" width="50%" />
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
|
|
37
|
+
# ⚡ FASTAPI Websocket RPC
|
|
38
|
+
RPC over Websockets made easy, robust, and production ready
|
|
39
|
+
|
|
40
|
+
<a href="https://github.com/permitio/fastapi_websocket_rpc/actions?query=workflow%3ATests" target="_blank">
|
|
41
|
+
<img src="https://github.com/permitio/fastapi_websocket_rpc/workflows/Tests/badge.svg" alt="Tests">
|
|
42
|
+
</a>
|
|
43
|
+
|
|
44
|
+
<a href="https://pypi.org/project/fastapi-websocket-rpc/" target="_blank">
|
|
45
|
+
<img src="https://img.shields.io/pypi/v/fastapi-websocket-rpc?color=%2331C654&label=PyPi%20package" alt="Package">
|
|
46
|
+
</a>
|
|
47
|
+
|
|
48
|
+
<a href="https://pepy.tech/project/fastapi-websocket-rpc" target="_blank">
|
|
49
|
+
<img src="https://static.pepy.tech/personalized-badge/fastapi-websocket-rpc?period=total&units=international_system&left_color=black&right_color=blue&left_text=Downloads" alt="Downloads">
|
|
50
|
+
</a>
|
|
51
|
+
|
|
52
|
+
A fast and durable bidirectional JSON RPC channel over Websockets.
|
|
53
|
+
The easiest way to create a live async channel between two nodes via Python (or other clients).
|
|
54
|
+
|
|
55
|
+
- Both server and clients can easily expose Python methods that can be called by the other side.
|
|
56
|
+
Method return values are sent back as RPC responses, which the other side can wait on.
|
|
57
|
+
- Remote methods are easily called via the ```.other.method()``` wrapper
|
|
58
|
+
- Connections are kept alive with a configurable retry mechanism (using Tenacity)
|
|
59
|
+
|
|
60
|
+
- As seen at <a href="https://www.youtube.com/watch?v=KP7tPeKhT3o" target="_blank">PyCon IL 2021</a> and <a href="https://www.youtube.com/watch?v=IuMZVWEUvGs" target="_blank">EuroPython 2021</a>
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
Supports and tested on Python >= 3.7
|
|
64
|
+
## Installation 🛠️
|
|
65
|
+
```
|
|
66
|
+
pip install fastapi_ws_rpc
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## RPC call example:
|
|
71
|
+
|
|
72
|
+
Say the server exposes an "add" method, e.g. :
|
|
73
|
+
```python
|
|
74
|
+
class RpcCalculator(RpcMethodsBase):
|
|
75
|
+
async def add(self, a, b):
|
|
76
|
+
return a + b
|
|
77
|
+
```
|
|
78
|
+
Calling it is as easy as calling the method under the client's "other" property:
|
|
79
|
+
```python
|
|
80
|
+
response = await client.other.add(a=1,b=2)
|
|
81
|
+
print(response.result) # 3
|
|
82
|
+
```
|
|
83
|
+
getting the response with the return value.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Usage example:
|
|
89
|
+
|
|
90
|
+
### Server:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import uvicorn
|
|
94
|
+
from fastapi import FastAPI
|
|
95
|
+
from fastapi_ws_rpc import RpcMethodsBase, WebsocketRPCEndpoint
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Methods to expose to the clients
|
|
99
|
+
class ConcatServer(RpcMethodsBase):
|
|
100
|
+
async def concat(self, a="", b=""):
|
|
101
|
+
return a + b
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Init the FAST-API app
|
|
105
|
+
app = FastAPI()
|
|
106
|
+
# Create an endpoint and load it with the methods to expose
|
|
107
|
+
endpoint = WebsocketRPCEndpoint(ConcatServer())
|
|
108
|
+
# add the endpoint to the app
|
|
109
|
+
endpoint.register_route(app, "/ws")
|
|
110
|
+
|
|
111
|
+
# Start the server itself
|
|
112
|
+
uvicorn.run(app, host="0.0.0.0", port=9000)
|
|
113
|
+
```
|
|
114
|
+
### Client
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import asyncio
|
|
118
|
+
from fastapi_ws_rpc import RpcMethodsBase, WebSocketRpcClient
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def run_client(uri):
|
|
122
|
+
async with WebSocketRpcClient(uri, RpcMethodsBase()) as client:
|
|
123
|
+
# call concat on the other side
|
|
124
|
+
response = await client.other.concat(a="hello", b=" world")
|
|
125
|
+
# print result
|
|
126
|
+
print(response.result) # will print "hello world"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# run the client until it completes interaction with server
|
|
130
|
+
asyncio.get_event_loop().run_until_complete(
|
|
131
|
+
run_client("ws://localhost:9000/ws")
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
See the [examples](/examples) and [tests](/tests) folders for more server and client examples
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## Server calling client example:
|
|
139
|
+
- Clients can call ```client.other.method()```
|
|
140
|
+
- which is a shortcut for ```channel.other.method()```
|
|
141
|
+
- Servers also get the channel object and can call remote methods via ```channel.other.method()```
|
|
142
|
+
- See the [bidirectional call example](examples/bidirectional_server_example.py) for calling client from server and server events (e.g. ```on_connect```).
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## What can I do with this?
|
|
146
|
+
Websockets are ideal to create bi-directional realtime connections over the web.
|
|
147
|
+
- Push updates
|
|
148
|
+
- Remote control mechanism
|
|
149
|
+
- Pub / Sub (see [fastapi_websocket_pubsub](https://github.com/permitio/fastapi_websocket_pubsub))
|
|
150
|
+
- Trigger events (see "tests/trigger_flow_test.py")
|
|
151
|
+
- Node negotiations (see "tests/advanced_rpc_test.py :: test_recursive_rpc_calls")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
## Concepts
|
|
155
|
+
- [RpcChannel](fastapi_ws_rpc/rpc_channel.py) - implements the RPC-protocol over the websocket
|
|
156
|
+
- Sending RpcRequests per method call
|
|
157
|
+
- Creating promises to track them (via unique call ids), and allow waiting for responses
|
|
158
|
+
- Executing methods on the remote side and serializing return values as
|
|
159
|
+
- Receiving RpcResponses and delivering them to waiting callers
|
|
160
|
+
- [RpcMethods](fastapi_ws_rpc/rpc_methods.py) - classes passed to both client and server-endpoint inits to expose callable methods to the other side.
|
|
161
|
+
- Simply derive from RpcMethodsBase and add your own async methods
|
|
162
|
+
- Note currently only key-word arguments are supported
|
|
163
|
+
- Checkout RpcUtilityMethods for example methods, which are also useful debugging utilities
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
- Foundations:
|
|
167
|
+
|
|
168
|
+
- Based on [asyncio](https://docs.python.org/3/library/asyncio.html) for the power of Python coroutines
|
|
169
|
+
|
|
170
|
+
- Server Endpoint:
|
|
171
|
+
- Based on [FAST-API](https://github.com/tiangolo/fastapi): enjoy all the benefits of a full ASGI platform, including Async-io and dependency injections (for example to authenticate connections)
|
|
172
|
+
|
|
173
|
+
- Based on [Pydantic](https://pydantic-docs.helpmanual.io/): easily serialize structured data as part of RPC requests and responses (see 'tests/basic_rpc_test.py :: test_structured_response' for an example)
|
|
174
|
+
|
|
175
|
+
- Client :
|
|
176
|
+
- Based on [Tenacity](https://tenacity.readthedocs.io/en/latest/index.html): allowing configurable retries to keep to connection alive
|
|
177
|
+
- see WebSocketRpcClient.__init__'s retry_config
|
|
178
|
+
- Based on python [websockets](https://websockets.readthedocs.io/en/stable/intro.html) - a more comprehensive client than the one offered by Fast-api
|
|
179
|
+
|
|
180
|
+
## Logging
|
|
181
|
+
fastapi-websocket-rpc provides a helper logging module to control how it produces logs for you.
|
|
182
|
+
See [fastapi_websocket_rpc/logger.py](fastapi_ws_rpc/logger.py).
|
|
183
|
+
Use ```logging_config.set_mode``` or the 'WS_RPC_LOGGING' environment variable to choose the logging method you prefer or override completely via default logging config.
|
|
184
|
+
|
|
185
|
+
example:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# set RPC to log like UVICORN
|
|
189
|
+
from fastapi_ws_rpc.logger import logging_config, LoggingModes
|
|
190
|
+
|
|
191
|
+
logging_config.set_mode(LoggingModes.UVICORN)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Pull requests - welcome!
|
|
195
|
+
- Please include tests for new features
|
|
196
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://i.ibb.co/m8jL6Zd/RPC.png" alt="RPC" border="0" width="50%" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
# ⚡ FASTAPI Websocket RPC
|
|
8
|
+
RPC over Websockets made easy, robust, and production ready
|
|
9
|
+
|
|
10
|
+
<a href="https://github.com/permitio/fastapi_websocket_rpc/actions?query=workflow%3ATests" target="_blank">
|
|
11
|
+
<img src="https://github.com/permitio/fastapi_websocket_rpc/workflows/Tests/badge.svg" alt="Tests">
|
|
12
|
+
</a>
|
|
13
|
+
|
|
14
|
+
<a href="https://pypi.org/project/fastapi-websocket-rpc/" target="_blank">
|
|
15
|
+
<img src="https://img.shields.io/pypi/v/fastapi-websocket-rpc?color=%2331C654&label=PyPi%20package" alt="Package">
|
|
16
|
+
</a>
|
|
17
|
+
|
|
18
|
+
<a href="https://pepy.tech/project/fastapi-websocket-rpc" target="_blank">
|
|
19
|
+
<img src="https://static.pepy.tech/personalized-badge/fastapi-websocket-rpc?period=total&units=international_system&left_color=black&right_color=blue&left_text=Downloads" alt="Downloads">
|
|
20
|
+
</a>
|
|
21
|
+
|
|
22
|
+
A fast and durable bidirectional JSON RPC channel over Websockets.
|
|
23
|
+
The easiest way to create a live async channel between two nodes via Python (or other clients).
|
|
24
|
+
|
|
25
|
+
- Both server and clients can easily expose Python methods that can be called by the other side.
|
|
26
|
+
Method return values are sent back as RPC responses, which the other side can wait on.
|
|
27
|
+
- Remote methods are easily called via the ```.other.method()``` wrapper
|
|
28
|
+
- Connections are kept alive with a configurable retry mechanism (using Tenacity)
|
|
29
|
+
|
|
30
|
+
- As seen at <a href="https://www.youtube.com/watch?v=KP7tPeKhT3o" target="_blank">PyCon IL 2021</a> and <a href="https://www.youtube.com/watch?v=IuMZVWEUvGs" target="_blank">EuroPython 2021</a>
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Supports and tested on Python >= 3.7
|
|
34
|
+
## Installation 🛠️
|
|
35
|
+
```
|
|
36
|
+
pip install fastapi_ws_rpc
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## RPC call example:
|
|
41
|
+
|
|
42
|
+
Say the server exposes an "add" method, e.g. :
|
|
43
|
+
```python
|
|
44
|
+
class RpcCalculator(RpcMethodsBase):
|
|
45
|
+
async def add(self, a, b):
|
|
46
|
+
return a + b
|
|
47
|
+
```
|
|
48
|
+
Calling it is as easy as calling the method under the client's "other" property:
|
|
49
|
+
```python
|
|
50
|
+
response = await client.other.add(a=1,b=2)
|
|
51
|
+
print(response.result) # 3
|
|
52
|
+
```
|
|
53
|
+
getting the response with the return value.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Usage example:
|
|
59
|
+
|
|
60
|
+
### Server:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import uvicorn
|
|
64
|
+
from fastapi import FastAPI
|
|
65
|
+
from fastapi_ws_rpc import RpcMethodsBase, WebsocketRPCEndpoint
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Methods to expose to the clients
|
|
69
|
+
class ConcatServer(RpcMethodsBase):
|
|
70
|
+
async def concat(self, a="", b=""):
|
|
71
|
+
return a + b
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Init the FAST-API app
|
|
75
|
+
app = FastAPI()
|
|
76
|
+
# Create an endpoint and load it with the methods to expose
|
|
77
|
+
endpoint = WebsocketRPCEndpoint(ConcatServer())
|
|
78
|
+
# add the endpoint to the app
|
|
79
|
+
endpoint.register_route(app, "/ws")
|
|
80
|
+
|
|
81
|
+
# Start the server itself
|
|
82
|
+
uvicorn.run(app, host="0.0.0.0", port=9000)
|
|
83
|
+
```
|
|
84
|
+
### Client
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import asyncio
|
|
88
|
+
from fastapi_ws_rpc import RpcMethodsBase, WebSocketRpcClient
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def run_client(uri):
|
|
92
|
+
async with WebSocketRpcClient(uri, RpcMethodsBase()) as client:
|
|
93
|
+
# call concat on the other side
|
|
94
|
+
response = await client.other.concat(a="hello", b=" world")
|
|
95
|
+
# print result
|
|
96
|
+
print(response.result) # will print "hello world"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# run the client until it completes interaction with server
|
|
100
|
+
asyncio.get_event_loop().run_until_complete(
|
|
101
|
+
run_client("ws://localhost:9000/ws")
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
See the [examples](/examples) and [tests](/tests) folders for more server and client examples
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## Server calling client example:
|
|
109
|
+
- Clients can call ```client.other.method()```
|
|
110
|
+
- which is a shortcut for ```channel.other.method()```
|
|
111
|
+
- Servers also get the channel object and can call remote methods via ```channel.other.method()```
|
|
112
|
+
- See the [bidirectional call example](examples/bidirectional_server_example.py) for calling client from server and server events (e.g. ```on_connect```).
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## What can I do with this?
|
|
116
|
+
Websockets are ideal to create bi-directional realtime connections over the web.
|
|
117
|
+
- Push updates
|
|
118
|
+
- Remote control mechanism
|
|
119
|
+
- Pub / Sub (see [fastapi_websocket_pubsub](https://github.com/permitio/fastapi_websocket_pubsub))
|
|
120
|
+
- Trigger events (see "tests/trigger_flow_test.py")
|
|
121
|
+
- Node negotiations (see "tests/advanced_rpc_test.py :: test_recursive_rpc_calls")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## Concepts
|
|
125
|
+
- [RpcChannel](fastapi_ws_rpc/rpc_channel.py) - implements the RPC-protocol over the websocket
|
|
126
|
+
- Sending RpcRequests per method call
|
|
127
|
+
- Creating promises to track them (via unique call ids), and allow waiting for responses
|
|
128
|
+
- Executing methods on the remote side and serializing return values as
|
|
129
|
+
- Receiving RpcResponses and delivering them to waiting callers
|
|
130
|
+
- [RpcMethods](fastapi_ws_rpc/rpc_methods.py) - classes passed to both client and server-endpoint inits to expose callable methods to the other side.
|
|
131
|
+
- Simply derive from RpcMethodsBase and add your own async methods
|
|
132
|
+
- Note currently only key-word arguments are supported
|
|
133
|
+
- Checkout RpcUtilityMethods for example methods, which are also useful debugging utilities
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
- Foundations:
|
|
137
|
+
|
|
138
|
+
- Based on [asyncio](https://docs.python.org/3/library/asyncio.html) for the power of Python coroutines
|
|
139
|
+
|
|
140
|
+
- Server Endpoint:
|
|
141
|
+
- Based on [FAST-API](https://github.com/tiangolo/fastapi): enjoy all the benefits of a full ASGI platform, including Async-io and dependency injections (for example to authenticate connections)
|
|
142
|
+
|
|
143
|
+
- Based on [Pydantic](https://pydantic-docs.helpmanual.io/): easily serialize structured data as part of RPC requests and responses (see 'tests/basic_rpc_test.py :: test_structured_response' for an example)
|
|
144
|
+
|
|
145
|
+
- Client :
|
|
146
|
+
- Based on [Tenacity](https://tenacity.readthedocs.io/en/latest/index.html): allowing configurable retries to keep to connection alive
|
|
147
|
+
- see WebSocketRpcClient.__init__'s retry_config
|
|
148
|
+
- Based on python [websockets](https://websockets.readthedocs.io/en/stable/intro.html) - a more comprehensive client than the one offered by Fast-api
|
|
149
|
+
|
|
150
|
+
## Logging
|
|
151
|
+
fastapi-websocket-rpc provides a helper logging module to control how it produces logs for you.
|
|
152
|
+
See [fastapi_websocket_rpc/logger.py](fastapi_ws_rpc/logger.py).
|
|
153
|
+
Use ```logging_config.set_mode``` or the 'WS_RPC_LOGGING' environment variable to choose the logging method you prefer or override completely via default logging config.
|
|
154
|
+
|
|
155
|
+
example:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# set RPC to log like UVICORN
|
|
159
|
+
from fastapi_ws_rpc.logger import logging_config, LoggingModes
|
|
160
|
+
|
|
161
|
+
logging_config.set_mode(LoggingModes.UVICORN)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Pull requests - welcome!
|
|
165
|
+
- Please include tests for new features
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from fastapi_ws_rpc.rpc_methods import RpcMethodsBase
|
|
2
|
+
from fastapi_ws_rpc.schemas import WebSocketFrameType
|
|
3
|
+
from fastapi_ws_rpc.websocket_rpc_client import WebSocketRpcClient
|
|
4
|
+
from fastapi_ws_rpc.websocket_rpc_endpoint import WebsocketRPCEndpoint
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"RpcMethodsBase",
|
|
8
|
+
"WebSocketRpcClient",
|
|
9
|
+
"WebsocketRPCEndpoint",
|
|
10
|
+
"WebSocketFrameType",
|
|
11
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from fastapi import WebSocket
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConnectionManager:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.active_connections: List[WebSocket] = []
|
|
9
|
+
|
|
10
|
+
async def connect(self, websocket: WebSocket):
|
|
11
|
+
await websocket.accept()
|
|
12
|
+
self.active_connections.append(websocket)
|
|
13
|
+
|
|
14
|
+
def disconnect(self, websocket: WebSocket):
|
|
15
|
+
self.active_connections.remove(websocket)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from logging.config import dictConfig
|
|
5
|
+
from typing import NewType
|
|
6
|
+
|
|
7
|
+
ENV_VAR = "WS_RPC_LOGGING"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoggingModes(Enum):
|
|
11
|
+
# don't produce logs
|
|
12
|
+
NO_LOGS = 0
|
|
13
|
+
# Log alongside uvicorn
|
|
14
|
+
UVICORN = 1
|
|
15
|
+
# Simple log calls (no config)
|
|
16
|
+
SIMPLE = 2
|
|
17
|
+
# log via the loguru module
|
|
18
|
+
LOGURU = 3
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
LoggingMode = NewType("LoggingMode", LoggingModes)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LoggingConfig:
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
self._mode = None
|
|
27
|
+
|
|
28
|
+
config_template = {
|
|
29
|
+
"version": 1,
|
|
30
|
+
"disable_existing_loggers": True,
|
|
31
|
+
"formatters": {
|
|
32
|
+
"default": {
|
|
33
|
+
"()": "uvicorn.logging.DefaultFormatter",
|
|
34
|
+
"fmt": "%(levelprefix)s %(asctime)s %(message)s",
|
|
35
|
+
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
"handlers": {
|
|
39
|
+
"default": {
|
|
40
|
+
"formatter": "default",
|
|
41
|
+
"class": "logging.StreamHandler",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
"loggers": {},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
UVICORN_LOGGERS = {
|
|
48
|
+
"uvicorn.error": {
|
|
49
|
+
"propagate": False,
|
|
50
|
+
"handlers": ["default"],
|
|
51
|
+
},
|
|
52
|
+
"fastapi_ws_rpc": {
|
|
53
|
+
"handlers": ["default"],
|
|
54
|
+
"propagate": False,
|
|
55
|
+
"level": logging.INFO,
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def get_mode(self):
|
|
60
|
+
# if no one set the mode - set default from ENV or hardcoded default
|
|
61
|
+
if self._mode is None:
|
|
62
|
+
mode = LoggingModes.__members__.get(
|
|
63
|
+
os.environ.get(ENV_VAR, "").upper(), LoggingModes.SIMPLE
|
|
64
|
+
)
|
|
65
|
+
self.set_mode(mode)
|
|
66
|
+
return self._mode
|
|
67
|
+
|
|
68
|
+
def set_mode(self, mode: LoggingMode = LoggingModes.UVICORN, level=logging.INFO):
|
|
69
|
+
"""
|
|
70
|
+
Configure logging. this method calls 'logging.config.dictConfig()' to enable
|
|
71
|
+
quick setup of logging. Call this method before starting the app.
|
|
72
|
+
For more advanced cases use 'logging.config' directly (loggers used by this
|
|
73
|
+
library are all nested under "fastapi_ws_rpc" logger name)
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
mode (LoggingMode, optional): The mode to set logging to. Defaults to
|
|
77
|
+
LoggingModes.UVICORN.
|
|
78
|
+
"""
|
|
79
|
+
self._mode = mode
|
|
80
|
+
logging_config = self.config_template.copy()
|
|
81
|
+
# add logs beside uvicorn
|
|
82
|
+
if mode == LoggingModes.UVICORN:
|
|
83
|
+
logging_config["loggers"] = self.UVICORN_LOGGERS.copy()
|
|
84
|
+
logging_config["loggers"]["fastapi_ws_rpc"]["level"] = level
|
|
85
|
+
dictConfig(logging_config)
|
|
86
|
+
elif mode == LoggingModes.SIMPLE:
|
|
87
|
+
pass
|
|
88
|
+
elif mode == LoggingModes.LOGURU:
|
|
89
|
+
pass
|
|
90
|
+
# no logs
|
|
91
|
+
else:
|
|
92
|
+
logging_config["loggers"] = {}
|
|
93
|
+
dictConfig(logging_config)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Singelton for logging configuration
|
|
97
|
+
logging_config = LoggingConfig()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_logger(name):
|
|
101
|
+
"""
|
|
102
|
+
Get a logger object to log with.
|
|
103
|
+
Called by inner modules for logging.
|
|
104
|
+
"""
|
|
105
|
+
mode = logging_config.get_mode()
|
|
106
|
+
# logging through loguru
|
|
107
|
+
if mode == LoggingModes.LOGURU:
|
|
108
|
+
from loguru import logger
|
|
109
|
+
else:
|
|
110
|
+
# regular python logging
|
|
111
|
+
logger = logging.getLogger(f"fastapi_ws_rpc.{name}")
|
|
112
|
+
|
|
113
|
+
return logger
|