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.
@@ -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