Skip to content

Commit 919d791

Browse files
authored
Merge pull request #51 from mberdyshev/reformat-optional-dependency
Refactor websockets dependencies: move the optional dependency to extras and unite imports
2 parents 1b19728 + 05b3301 commit 919d791

File tree

5 files changed

+34
-31
lines changed

5 files changed

+34
-31
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ["3.7","3.8", "3.9", "3.10.8", "3.11"]
18+
python-version: ["3.9", "3.10.8", "3.11", "3.12", "3.13"]
1919

2020
steps:
2121
- uses: actions/checkout@v2

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Method return values are sent back as RPC responses, which the other side can wa
3030
- 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>
3131

3232

33-
Supports and tested on Python >= 3.7
33+
Supports and tested on Python >= 3.9
3434
## Installation 🛠️
3535
```
3636
pip install fastapi_websocket_rpc
@@ -157,7 +157,7 @@ logging_config.set_mode(LoggingModes.UVICORN)
157157
By default, fastapi-websocket-rpc uses websockets module as websocket client handler. This does not support HTTP(S) Proxy, see https://github.com/python-websockets/websockets/issues/364 . If the ability to use a proxy is important to, another websocket client implementation can be used, e.g. websocket-client (https://websocket-client.readthedocs.io). Here is how to use it. Installation:
158158

159159
```
160-
pip install websocket-client
160+
pip install fastapi_websocket_rpc[websocket-client]
161161
```
162162

163163
Then use websocket_client_handler_cls parameter:

fastapi_websocket_rpc/websocket_rpc_client.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import asyncio
22
import logging
33
from typing import Coroutine, Dict, List, Type
4-
from tenacity import retry, wait
5-
import tenacity
6-
from tenacity.retry import retry_if_exception
74

8-
import websockets
9-
from websockets.exceptions import InvalidStatusCode, WebSocketException, ConnectionClosedError, ConnectionClosedOK
5+
from tenacity import retry, RetryCallState, wait
6+
from tenacity.retry import retry_if_exception
107

118
from .rpc_methods import PING_RESPONSE, RpcMethodsBase
129
from .rpc_channel import RpcChannel, OnConnectCallback, OnDisconnectCallback
@@ -15,11 +12,16 @@
1512

1613
logger = get_logger("RPC_CLIENT")
1714

15+
try:
16+
import websockets
17+
except ImportError:
18+
websockets = None
19+
1820
try:
1921
import websocket
2022
except ImportError:
21-
# Websocket-client optional module not installed.
22-
pass
23+
# Websocket-client optional module is not installed.
24+
websocket = None
2325

2426
class ProxyEnabledWebSocketClientHandler(SimpleWebSocket):
2527
"""
@@ -33,6 +35,8 @@ class ProxyEnabledWebSocketClientHandler(SimpleWebSocket):
3335
Note: the connect timeout, if not specified, is the default socket connect timeout, which could be around 2min, so a bit longer than WebSocketsClientHandler.
3436
"""
3537
def __init__(self):
38+
if websocket is None:
39+
raise RuntimeError("Proxy handler requires websocket-client library")
3640
self._websocket = None
3741

3842
"""
@@ -101,6 +105,8 @@ class WebSocketsClientHandler(SimpleWebSocket):
101105
This implementation does not support HTTP proxy (see https://github.com/python-websockets/websockets/issues/364).
102106
"""
103107
def __init__(self):
108+
if websockets is None:
109+
raise RuntimeError("Default handler requires websockets library")
104110
self._websocket = None
105111

106112
"""
@@ -114,17 +120,17 @@ async def connect(self, uri: str, **connect_kwargs):
114120
except ConnectionRefusedError:
115121
logger.info("RPC connection was refused by server")
116122
raise
117-
except ConnectionClosedError:
123+
except websockets.ConnectionClosedError:
118124
logger.info("RPC connection lost")
119125
raise
120-
except ConnectionClosedOK:
126+
except websockets.ConnectionClosedOK:
121127
logger.info("RPC connection closed")
122128
raise
123-
except InvalidStatusCode as err:
129+
except websockets.InvalidStatusCode as err:
124130
logger.info(
125131
f"RPC Websocket failed - with invalid status code {err.status_code}")
126132
raise
127-
except WebSocketException as err:
133+
except websockets.WebSocketException as err:
128134
logger.info(f"RPC Websocket failed - with {err}")
129135
raise
130136
except OSError as err:
@@ -156,16 +162,14 @@ async def close(self, code: int = 1000):
156162
# Case opened, we have something to close.
157163
await self._websocket.close(code)
158164

159-
def isNotInvalidStatusCode(value):
160-
return not isinstance(value, InvalidStatusCode)
161-
162165

163166
def isNotForbbiden(value) -> bool:
164167
"""
165168
Returns:
166-
bool: Returns True as long as the given exception value is not InvalidStatusCode with 401 or 403
169+
bool: Returns True as long as the given exception value doesn't hold HTTP status codes 401 or 403
167170
"""
168-
return not (isinstance(value, InvalidStatusCode) and (value.status_code == 401 or value.status_code == 403))
171+
value = getattr(value, "response", value) # `websockets.InvalidStatus` exception contains a status code inside the `response` property
172+
return not (hasattr(value, "status_code") and value.status_code in (401, 403))
169173

170174

171175
class WebSocketRpcClient:
@@ -175,7 +179,7 @@ class WebSocketRpcClient:
175179
Exposes methods that the server can call
176180
"""
177181

178-
def logerror(retry_state: tenacity.RetryCallState):
182+
def logerror(retry_state: RetryCallState):
179183
logger.exception(retry_state.outcome.exception())
180184

181185
DEFAULT_RETRY_CONFIG = {

setup.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ def get_requirements(env=""):
55
if env:
66
env = "-{}".format(env)
77
with open("requirements{}.txt".format(env)) as fp:
8-
requirements = [x.strip() for x in fp.read().split("\n") if not x.startswith("#")]
9-
withWebsocketClient = os.environ.get("WITH_WEBSOCKET_CLIENT", "False")
10-
if bool(withWebsocketClient):
11-
requirements.append("websocket-client>=1.1.0")
12-
return requirements
8+
return [x.strip() for x in fp.read().split("\n") if not x.startswith("#")]
139

1410
with open("README.md", "r", encoding="utf-8") as fh:
1511
long_description = fh.read()
@@ -31,6 +27,9 @@ def get_requirements(env=""):
3127
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
3228
"Topic :: Internet :: WWW/HTTP :: WSGI",
3329
],
34-
python_requires=">=3.7",
30+
python_requires=">=3.9",
3531
install_requires=get_requirements(),
32+
extras_require={
33+
"websocket-client": ["websocket-client>=1.1.0"],
34+
},
3635
)

tests/fast_api_depends_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import sys
33

4-
from websockets.exceptions import InvalidStatusCode
4+
from websockets.exceptions import InvalidStatus
55

66
from multiprocessing import Process
77

@@ -54,7 +54,7 @@ async def test_valid_token(server):
5454
"""
5555
Test basic RPC with a simple echo
5656
"""
57-
async with WebSocketRpcClient(uri, RpcUtilityMethods(), default_response_timeout=4, extra_headers=[("X-TOKEN", SECRET_TOKEN)]) as client:
57+
async with WebSocketRpcClient(uri, RpcUtilityMethods(), default_response_timeout=4, additional_headers=[("X-TOKEN", SECRET_TOKEN)]) as client:
5858
text = "Hello World!"
5959
response = await client.other.echo(text=text)
6060
assert response.result == text
@@ -66,9 +66,9 @@ async def test_invalid_token(server):
6666
Test basic RPC with a simple echo
6767
"""
6868
try:
69-
async with WebSocketRpcClient(uri, RpcUtilityMethods(), default_response_timeout=4, extra_headers=[("X-TOKEN", "bad-token")]) as client:
69+
async with WebSocketRpcClient(uri, RpcUtilityMethods(), default_response_timeout=4, additional_headers=[("X-TOKEN", "bad-token")]) as client:
7070
assert client is not None
7171
# if we got here - the server didn't reject us
7272
assert False
73-
except InvalidStatusCode as e:
74-
assert e.status_code == 403
73+
except InvalidStatus as e:
74+
assert e.response.status_code == 403

0 commit comments

Comments
 (0)