From 2800309026cc5ce915db8e2e01197d9fa03b227c Mon Sep 17 00:00:00 2001 From: Lev Gorodetskiy Date: Wed, 27 Dec 2023 16:18:51 -0300 Subject: [PATCH] Sync CI with DipDup one --- .github/workflows/release.yml | 13 ++++---- .github/workflows/test.yml | 26 +++++++++++++--- Makefile | 33 +++++--------------- example.py | 6 ++-- poetry.lock | 16 +--------- pyproject.toml | 25 ++++++++------- requirements.txt | 11 ------- src/pysignalr/client.py | 35 ++++++++++----------- src/pysignalr/exceptions.py | 5 +-- src/pysignalr/messages.py | 45 +++++++++++++-------------- src/pysignalr/protocol/abstract.py | 10 +++--- src/pysignalr/protocol/json.py | 24 +++++++------- src/pysignalr/protocol/messagepack.py | 16 +++++----- src/pysignalr/transport/websocket.py | 17 +++++----- src/pysignalr/utils.py | 5 +-- 15 files changed, 128 insertions(+), 159 deletions(-) delete mode 100644 requirements.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fef11e2..b0e6a0f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,19 +15,17 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Set up Poetry uses: snok/install-poetry@v1 - with: - version: '1.3.2' - name: Install project - run: make install + run: poetry install - name: Run lint - run: make lint + run: poetry run make lint - name: Run tests - run: make test + run: poetry run make test - name: Publish package on PyPi run: | @@ -35,6 +33,7 @@ jobs: poetry build poetry publish + # FIXME: Fails on prereleases; https://github.com/mindsers/changelog-reader-action/pull/39 - name: Parse changelog id: changelog uses: mindsers/changelog-reader-action@v2 @@ -53,5 +52,5 @@ jobs: ## ${{ steps.changelog.outputs.version }} - ${{ steps.changelog.outputs.date }} ${{ steps.changelog.outputs.changes }} - draft: true + draft: false prerelease: ${{ steps.changelog.outputs.status == 'prereleased' }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82a5ddb..af8ecae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,14 +18,32 @@ jobs: strategy: matrix: include: + # Architectures - os: ubuntu-latest arch: amd64 + python-version: '3.12' - os: ubuntu-latest arch: arm64 + python-version: '3.12' - os: macos-latest arch: amd64 + python-version: '3.12' - os: macos-latest arch: arm64 + python-version: '3.12' + # Python versions + - os: ubuntu-latest + arch: amd64 + python-version: '3.8' + - os: ubuntu-latest + arch: amd64 + python-version: '3.9' + - os: ubuntu-latest + arch: amd64 + python-version: '3.10' + - os: ubuntu-latest + arch: amd64 + python-version: '3.11' steps: - name: Check out the repo uses: actions/checkout@v3 @@ -36,12 +54,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: ${{ matrix.python-version }} cache: 'poetry' - name: Install project - run: make install + run: poetry install - name: Run lint - run: make lint + run: poetry run make lint - name: Run tests - run: make test + run: poetry run make test diff --git a/Makefile b/Makefile index ea33cb3..e893c8d 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,6 @@ ## ## 🚧 pysignalr developer tools ## -## DEV=1 Install dev dependencies -DEV=1 ## @@ -12,46 +10,29 @@ help: ## Show this help (default) @grep -F -h "##" $(MAKEFILE_LIST) | grep -F -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' all: ## Run a whole CI pipeline: formatters, linters and tests - make install lint test docs - -install: ## Install project dependencies - poetry install \ - `if [ "${DEV}" = "0" ]; then echo "--without dev"; fi` + make lint test docs lint: ## Lint with all tools - make isort black ruff mypy + make black ruff mypy test: ## Run test suite - poetry run pytest --cov-report=term-missing --cov=pysignalr --cov-report=xml -s -v tests + pytest --cov-report=term-missing --cov=pysignalr --cov-report=xml -s -v tests ## -isort: ## Format with isort - poetry run isort src tests example.py - black: ## Format with black - poetry run black src tests example.py + black src tests example.py ruff: ## Lint with ruff - poetry run ruff check src tests example.py + ruff check --fix --unsafe-fixes src tests example.py mypy: ## Lint with mypy - poetry run mypy --strict src tests example.py + mypy --strict src tests example.py cover: ## Print coverage for the current branch - poetry run diff-cover --compare-branch `git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'` coverage.xml - -build: ## Build Python wheel package - poetry build + diff-cover --compare-branch `git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'` coverage.xml ## clean: ## Remove all files from .gitignore except for `.venv` git clean -xdf --exclude=".venv" - rm -r ~/.cache/flakeheaven - -update: ## Update dependencies, export requirements.txt - rm requirements.* poetry.lock - make install - poetry export --without-hashes -o requirements.txt - diff --git a/example.py b/example.py index 958b2ee..61303c5 100644 --- a/example.py +++ b/example.py @@ -1,8 +1,8 @@ +from __future__ import annotations + import asyncio from contextlib import suppress from typing import Any -from typing import Dict -from typing import List from pysignalr.client import SignalRClient from pysignalr.messages import CompletionMessage @@ -16,7 +16,7 @@ async def on_close() -> None: print('Disconnected from the server') -async def on_message(message: List[Dict[str, Any]]) -> None: +async def on_message(message: list[dict[str, Any]]) -> None: print(f'Received message: {message}') diff --git a/poetry.lock b/poetry.lock index e4bc4df..f48a23d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -561,20 +561,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - [[package]] name = "jinja2" version = "3.1.2" @@ -1346,4 +1332,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "7afa6e910c392087f70ff2bfbc514504d348e20c0bf5e40cf95d4e5b0fcf8eb6" +content-hash = "0471e14650c4f9dd1e3f35c1825c72877fe869d6b4d24acd32f14c9a2439b93b" diff --git a/pyproject.toml b/pyproject.toml index e1213f9..6cc2ccf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,16 +47,11 @@ black = "*" diff-cover = "*" docker = "*" ruff = "*" -isort = "*" mypy = "*" pytest = "*" pytest-asyncio = "*" pytest-cov = "*" -[tool.isort] -line_length = 120 -force_single_line = true - [tool.black] line-length = 120 target-version = ["py38", "py39", "py310", "py311", "py312"] @@ -64,26 +59,34 @@ skip-string-normalization = true [tool.ruff] line-length = 120 -ignore = ["E501", "B905"] -target-version = "py312" -extend-select = ["B", "C4", "Q"] # todo: G, PTH, RET, RUF, S, TCH -flake8-quotes = {inline-quotes = "single", multiline-quotes = "double"} +ignore = [ + "E402", # module level import not at top of file + "E501", # line too long + "TCH001", # breaks our runtime Pydantic magic +] +target-version = "py38" +extend-select = ["B", "C4", "FA", "G", "I", "PTH", "Q", "RUF", "TCH", "UP"] +flake8-quotes = { inline-quotes = "single", multiline-quotes = "double" } +isort = { force-single-line = true, known-first-party = ["pysignalr"] } [tool.mypy] -python_version = "3.11" +python_version = "3.8" +strict = true [tool.pytest.ini_options] asyncio_mode = "auto" +log_cli_level = "WARNING" [tool.coverage.report] +precision = 2 exclude_lines = [ "pragma: no cover", "def __repr__", - "raise FrameworkError", "raise NotImplementedError", "if __name__ == .__main__.:", "class .*\\bProtocol\\):", "@(abc\\.)?abstractmethod", + "if TYPE_CHECKING:", ] [build-system] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b4a7f09..0000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -aiohttp==3.9.1 ; python_version >= "3.8" and python_version < "4" -aiosignal==1.3.1 ; python_version >= "3.8" and python_version < "4" -async-timeout==4.0.3 ; python_version >= "3.8" and python_version < "3.11" -attrs==23.1.0 ; python_version >= "3.8" and python_version < "4" -frozenlist==1.4.0 ; python_version >= "3.8" and python_version < "4" -idna==3.6 ; python_version >= "3.8" and python_version < "4" -msgpack==1.0.7 ; python_version >= "3.8" and python_version < "4" -multidict==6.0.4 ; python_version >= "3.8" and python_version < "4" -orjson==3.9.10 ; python_version >= "3.8" and python_version < "4" -websockets==12.0 ; python_version >= "3.8" and python_version < "4" -yarl==1.9.3 ; python_version >= "3.8" and python_version < "4" diff --git a/src/pysignalr/client.py b/src/pysignalr/client.py index 8d62412..bb580cf 100644 --- a/src/pysignalr/client.py +++ b/src/pysignalr/client.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import uuid from collections import defaultdict from contextlib import asynccontextmanager @@ -5,11 +7,6 @@ from typing import AsyncIterator from typing import Awaitable from typing import Callable -from typing import DefaultDict -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple from pysignalr.exceptions import ServerError from pysignalr.messages import CancelInvocationMessage @@ -64,21 +61,21 @@ class SignalRClient: def __init__( self, url: str, - protocol: Optional[Protocol] = None, - headers: Optional[Dict[str, str]] = None, + protocol: Protocol | None = None, + headers: dict[str, str] | None = None, ping_interval: int = DEFAULT_PING_INTERVAL, connection_timeout: int = DEFAULT_CONNECTION_TIMEOUT, - max_size: Optional[int] = DEFAULT_MAX_SIZE, + max_size: int | None = DEFAULT_MAX_SIZE, ) -> None: self._url = url self._protocol = protocol or JSONProtocol() self._headers = headers or {} - self._message_handlers: DefaultDict[str, List[Optional[MessageCallback]]] = defaultdict(list) - self._stream_handlers: Dict[ - str, Tuple[Optional[MessageCallback], Optional[MessageCallback], Optional[CompletionMessageCallback]] + self._message_handlers: defaultdict[str, list[MessageCallback | None]] = defaultdict(list) + self._stream_handlers: dict[ + str, tuple[MessageCallback | None, MessageCallback | None, CompletionMessageCallback | None] ] = {} - self._invocation_handlers: Dict[str, Optional[MessageCallback]] = {} + self._invocation_handlers: dict[str, MessageCallback | None] = {} self._transport = WebsocketTransport( url=self._url, @@ -89,7 +86,7 @@ def __init__( connection_timeout=connection_timeout, max_size=max_size, ) - self._error_callback: Optional[CompletionMessageCallback] = None + self._error_callback: CompletionMessageCallback | None = None async def run(self) -> None: await self._transport.run() @@ -113,8 +110,8 @@ def on_error(self, callback: CompletionMessageCallback) -> None: async def send( self, method: str, - arguments: List[Dict[str, Any]], - on_invocation: Optional[MessageCallback] = None, + arguments: list[dict[str, Any]], + on_invocation: MessageCallback | None = None, ) -> None: """Send a message to the server""" invocation_id = str(uuid.uuid4()) @@ -125,10 +122,10 @@ async def send( async def stream( self, event: str, - event_params: List[str], - on_next: Optional[MessageCallback] = None, - on_complete: Optional[MessageCallback] = None, - on_error: Optional[CompletionMessageCallback] = None, + event_params: list[str], + on_next: MessageCallback | None = None, + on_complete: MessageCallback | None = None, + on_error: CompletionMessageCallback | None = None, ) -> None: """Invoke stream on the specified event""" invocation_id = str(uuid.uuid4()) diff --git a/src/pysignalr/exceptions.py b/src/pysignalr/exceptions.py index 3ebcf47..882184b 100644 --- a/src/pysignalr/exceptions.py +++ b/src/pysignalr/exceptions.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from dataclasses import dataclass -from typing import Optional @dataclass(frozen=True) @@ -19,4 +20,4 @@ class ConnectionError(HubError): @dataclass(frozen=True) class ServerError(HubError): - message: Optional[str] + message: str | None diff --git a/src/pysignalr/messages.py b/src/pysignalr/messages.py index 03779d8..7d964ff 100644 --- a/src/pysignalr/messages.py +++ b/src/pysignalr/messages.py @@ -1,14 +1,13 @@ +from __future__ import annotations + from dataclasses import dataclass from enum import IntEnum from typing import Any -from typing import Dict -from typing import List -from typing import Optional @dataclass class HandshakeMessage: - def dump(self) -> Dict[str, Any]: + def dump(self) -> dict[str, Any]: return self.__dict__ @@ -20,7 +19,7 @@ class HandshakeRequestMessage(HandshakeMessage): @dataclass class HandshakeResponseMessage(HandshakeMessage): - error: Optional[str] + error: str | None class MessageType(IntEnum): @@ -40,7 +39,7 @@ class Message: def __init_subclass__(cls, type_: MessageType) -> None: cls.type = type_ # type: ignore[attr-defined] - def dump(self) -> Dict[str, Any]: + def dump(self) -> dict[str, Any]: data = self.__dict__ invocation_id = data.pop('invocation_id', None) @@ -57,8 +56,8 @@ def dump(self) -> Dict[str, Any]: @dataclass class ResponseMessage(Message, type_=MessageType._): - error: Optional[str] - result: Optional[Any] + error: str | None + result: Any | None """ @@ -80,7 +79,7 @@ class ResponseMessage(Message, type_=MessageType._): @dataclass class CancelInvocationMessage(Message, type_=MessageType.cancel_invocation): invocation_id: str - headers: Optional[Dict[str, Any]] = None + headers: dict[str, Any] | None = None """ @@ -109,9 +108,9 @@ class CancelInvocationMessage(Message, type_=MessageType.cancel_invocation): @dataclass class CloseMessage(Message, type_=MessageType.close): - error: Optional[str] = None - allow_reconnect: Optional[bool] = None - headers: Optional[Dict[str, Any]] = None + error: str | None = None + allow_reconnect: bool | None = None + headers: dict[str, Any] | None = None """ @@ -175,15 +174,15 @@ class CloseMessage(Message, type_=MessageType.close): @dataclass class CompletionClientStreamMessage(Message, type_=MessageType.stream_item): invocation_id: str - headers: Optional[Dict[str, Any]] = None + headers: dict[str, Any] | None = None @dataclass class CompletionMessage(Message, type_=MessageType.completion): invocation_id: str - result: Optional[Any] = None - error: Optional[str] = None - headers: Optional[Dict[str, Any]] = None + result: Any | None = None + error: str | None = None + headers: dict[str, Any] | None = None """ @@ -234,15 +233,15 @@ class InvocationMessage(Message, type_=MessageType.invocation): invocation_id: str target: str arguments: Any - headers: Optional[Dict[str, Any]] = None + headers: dict[str, Any] | None = None @dataclass class InvocationClientStreamMessage(Message, type_=MessageType.invocation): - stream_ids: List[str] + stream_ids: list[str] target: str arguments: Any - headers: Optional[Dict[str, Any]] = None + headers: dict[str, Any] | None = None """ @@ -299,7 +298,7 @@ class StreamInvocationMessage(Message, type_=MessageType.stream_invocation): invocation_id: str target: str arguments: Any - headers: Optional[Dict[str, Any]] = None + headers: dict[str, Any] | None = None """ @@ -327,14 +326,14 @@ class StreamInvocationMessage(Message, type_=MessageType.stream_invocation): class StreamItemMessage(Message, type_=MessageType.stream_item): invocation_id: str item: Any - headers: Optional[Dict[str, Any]] = None + headers: dict[str, Any] | None = None class JSONMessage(Message, type_=MessageType._): """Not a real message type; used in BaseJSONProtocol to skip pysignalr-specific things""" - def __init__(self, data: Dict[str, Any]) -> None: + def __init__(self, data: dict[str, Any]) -> None: self.data = data - def dump(self) -> Dict[str, Any]: + def dump(self) -> dict[str, Any]: return self.data diff --git a/src/pysignalr/protocol/abstract.py b/src/pysignalr/protocol/abstract.py index 23f234a..392cf23 100644 --- a/src/pysignalr/protocol/abstract.py +++ b/src/pysignalr/protocol/abstract.py @@ -1,8 +1,8 @@ +from __future__ import annotations + from abc import ABC from abc import abstractmethod from typing import Iterable -from typing import Tuple -from typing import Union from pysignalr.messages import HandshakeRequestMessage from pysignalr.messages import HandshakeResponseMessage @@ -16,15 +16,15 @@ def __init__(self, protocol: str, version: int, record_separator: str) -> None: self.record_separator = record_separator @abstractmethod - def decode(self, raw_message: Union[str, bytes]) -> Iterable[Message]: + def decode(self, raw_message: str | bytes) -> Iterable[Message]: ... @abstractmethod - def encode(self, message: Union[Message, HandshakeRequestMessage]) -> Union[str, bytes]: + def encode(self, message: Message | HandshakeRequestMessage) -> str | bytes: ... @abstractmethod - def decode_handshake(self, raw_message: Union[str, bytes]) -> Tuple[HandshakeResponseMessage, Iterable[Message]]: + def decode_handshake(self, raw_message: str | bytes) -> tuple[HandshakeResponseMessage, Iterable[Message]]: ... def handshake_message(self) -> HandshakeRequestMessage: diff --git a/src/pysignalr/protocol/json.py b/src/pysignalr/protocol/json.py index 188ef01..886bb62 100644 --- a/src/pysignalr/protocol/json.py +++ b/src/pysignalr/protocol/json.py @@ -1,10 +1,8 @@ +from __future__ import annotations + from json import JSONEncoder from typing import Any -from typing import Dict from typing import Iterable -from typing import List -from typing import Tuple -from typing import Union import orjson @@ -25,7 +23,7 @@ class MessageEncoder(JSONEncoder): - def default(self, obj: Union[Message, MessageType]) -> Union[str, int, Dict[str, Any]]: + def default(self, obj: Message | MessageType) -> str | int | dict[str, Any]: if isinstance(obj, MessageType): return obj.value return obj.dump() @@ -38,14 +36,14 @@ class BaseJSONProtocol(Protocol): def __init__(self) -> None: pass - def decode(self, raw_message: Union[str, bytes]) -> Tuple[JSONMessage]: + def decode(self, raw_message: str | bytes) -> tuple[JSONMessage]: json_message = orjson.loads(raw_message) return (JSONMessage(data=json_message),) - def encode(self, message: Union[Message, HandshakeRequestMessage]) -> Union[str, bytes]: + def encode(self, message: Message | HandshakeRequestMessage) -> str | bytes: return orjson.dumps(message.dump()) - def decode_handshake(self, raw_message: Union[str, bytes]) -> Tuple[HandshakeResponseMessage, Iterable[Message]]: + def decode_handshake(self, raw_message: str | bytes) -> tuple[HandshakeResponseMessage, Iterable[Message]]: raise NotImplementedError @@ -57,12 +55,12 @@ def __init__(self) -> None: record_separator=chr(0x1E), ) - def decode(self, raw_message: Union[str, bytes]) -> List[Message]: + def decode(self, raw_message: str | bytes) -> list[Message]: if isinstance(raw_message, bytes): raw_message = raw_message.decode() raw_messages = raw_message.split(self.record_separator) - messages: List[Message] = [] + messages: list[Message] = [] for item in raw_messages: if item in ('', self.record_separator): @@ -74,10 +72,10 @@ def decode(self, raw_message: Union[str, bytes]) -> List[Message]: return messages - def encode(self, message: Union[Message, HandshakeMessage]) -> str: + def encode(self, message: Message | HandshakeMessage) -> str: return message_encoder.encode(message) + self.record_separator - def decode_handshake(self, raw_message: Union[str, bytes]) -> Tuple[HandshakeResponseMessage, Iterable[Message]]: + def decode_handshake(self, raw_message: str | bytes) -> tuple[HandshakeResponseMessage, Iterable[Message]]: if isinstance(raw_message, bytes): raw_message = raw_message.decode() @@ -92,7 +90,7 @@ def decode_handshake(self, raw_message: Union[str, bytes]) -> Tuple[HandshakeRes ) @staticmethod - def parse_message(dict_message: Dict[str, Any]) -> Message: + def parse_message(dict_message: dict[str, Any]) -> Message: message_type = MessageType(dict_message.pop('type', 'close')) if message_type is MessageType.invocation: diff --git a/src/pysignalr/protocol/messagepack.py b/src/pysignalr/protocol/messagepack.py index 4671365..0b99b44 100644 --- a/src/pysignalr/protocol/messagepack.py +++ b/src/pysignalr/protocol/messagepack.py @@ -1,12 +1,10 @@ +from __future__ import annotations + # TODO: Refactor this module from collections import deque from typing import Any -from typing import Deque from typing import Iterable -from typing import List from typing import Sequence -from typing import Tuple -from typing import Union from typing import cast import msgpack # type: ignore[import-untyped] @@ -49,8 +47,8 @@ def __init__(self) -> None: record_separator=chr(0x1E), ) - def decode(self, raw_message: Union[str, bytes]) -> List[Message]: - messages: List[Message] = [] + def decode(self, raw_message: str | bytes) -> list[Message]: + messages: list[Message] = [] offset = 0 while offset < len(raw_message): length = msgpack.unpackb(raw_message[offset : offset + 1]) @@ -60,8 +58,8 @@ def decode(self, raw_message: Union[str, bytes]) -> List[Message]: messages.append(message) return messages - def encode(self, message: Union[Message, HandshakeRequestMessage]) -> bytes: - raw_message: Deque[Any] = deque() + def encode(self, message: Message | HandshakeRequestMessage) -> bytes: + raw_message: deque[Any] = deque() for attr in _attribute_priority: if hasattr(message, attr): @@ -74,7 +72,7 @@ def encode(self, message: Union[Message, HandshakeRequestMessage]) -> bytes: varint_length = self._to_varint(len(encoded_message)) return varint_length + encoded_message - def decode_handshake(self, raw_message: Union[str, bytes]) -> Tuple[HandshakeResponseMessage, Iterable[Message]]: + def decode_handshake(self, raw_message: str | bytes) -> tuple[HandshakeResponseMessage, Iterable[Message]]: if isinstance(raw_message, str): raw_message = raw_message.encode() diff --git a/src/pysignalr/transport/websocket.py b/src/pysignalr/transport/websocket.py index e4a2f84..4c59361 100644 --- a/src/pysignalr/transport/websocket.py +++ b/src/pysignalr/transport/websocket.py @@ -1,12 +1,11 @@ +from __future__ import annotations + import asyncio import logging from contextlib import suppress from http import HTTPStatus from typing import Awaitable from typing import Callable -from typing import Dict -from typing import Optional -from typing import Union from aiohttp import ClientSession from aiohttp import ClientTimeout @@ -41,11 +40,11 @@ def __init__( url: str, protocol: Protocol, callback: Callable[[Message], Awaitable[None]], - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, skip_negotiation: bool = False, ping_interval: int = DEFAULT_PING_INTERVAL, connection_timeout: int = DEFAULT_CONNECTION_TIMEOUT, - max_size: Optional[int] = DEFAULT_MAX_SIZE, + max_size: int | None = DEFAULT_MAX_SIZE, ): super().__init__() self._url = url @@ -59,9 +58,9 @@ def __init__( self._state = ConnectionState.disconnected self._connected = asyncio.Event() - self._ws: Optional[WebSocketClientProtocol] = None - self._open_callback: Optional[Callable[[], Awaitable[None]]] = None - self._close_callback: Optional[Callable[[], Awaitable[None]]] = None + self._ws: WebSocketClientProtocol | None = None + self._open_callback: Callable[[], Awaitable[None]] | None = None + self._close_callback: Callable[[], Awaitable[None]] | None = None def on_open(self, callback: Callable[[], Awaitable[None]]) -> None: self._open_callback = callback @@ -209,7 +208,7 @@ async def _negotiate(self) -> None: else: raise exceptions.ServerError(str(data)) - async def _on_raw_message(self, raw_message: Union[str, bytes]) -> None: + async def _on_raw_message(self, raw_message: str | bytes) -> None: for message in self._protocol.decode(raw_message): await self._on_message(message) diff --git a/src/pysignalr/utils.py b/src/pysignalr/utils.py index f064188..35d98e8 100644 --- a/src/pysignalr/utils.py +++ b/src/pysignalr/utils.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import urllib.parse as parse from contextlib import suppress -from typing import List http_schemas = ('http', 'https') websocket_schemas = ('ws', 'wss') @@ -28,7 +29,7 @@ def get_negotiate_url(url: str) -> str: return parse.urlunsplit((scheme, netloc, path, query, fragment)) -def get_connection_url(url: str, id: List[str]) -> str: +def get_connection_url(url: str, id: list[str]) -> str: scheme, netloc, path, query, fragment = parse.urlsplit(url) parsed_query = parse.parse_qs(query)