diff --git a/.audit/bblommers_websocket-server-typing.md b/.audit/bblommers_websocket-server-typing.md new file mode 100644 index 000000000..4467667d9 --- /dev/null +++ b/.audit/bblommers_websocket-server-typing.md @@ -0,0 +1,8 @@ +- [x] I did **not** use any AI-assistance tools to help create this pull request. +- [ ] I **did** use AI-assistance tools to *help* create this pull request. +- [x] I have read, understood and followed the projects' [AI Policy](https://github.com/crossbario/autobahn-python/blob/main/AI_POLICY.md) when creating code, documentation etc. for this pull request. + +Submitted by: @bblommers +Date: 2026-01-07 +Related issue(s): #1839 +Branch: bblommers:websocket-server-typing diff --git a/justfile b/justfile index 127bf55d4..69703a056 100644 --- a/justfile +++ b/justfile @@ -702,17 +702,15 @@ check-typing venv="": (install venv) --ignore unresolved-attribute \ --ignore unresolved-reference \ --ignore possibly-missing-attribute \ - --ignore possibly-missing-import \ --ignore call-non-callable \ --ignore invalid-assignment \ --ignore invalid-argument-type \ - --ignore invalid-return-type \ --ignore invalid-method-override \ --ignore invalid-type-form \ --ignore unsupported-operator \ --ignore too-many-positional-arguments \ --ignore unknown-argument \ - --ignore non-subscriptable \ + --ignore not-subscriptable \ --ignore not-iterable \ --ignore no-matching-overload \ --ignore conflicting-declarations \ diff --git a/src/autobahn/twisted/websocket.py b/src/autobahn/twisted/websocket.py index ce826356d..2c7f4cae1 100644 --- a/src/autobahn/twisted/websocket.py +++ b/src/autobahn/twisted/websocket.py @@ -24,8 +24,10 @@ # ############################################################################### +from __future__ import annotations + from base64 import b64decode, b64encode -from typing import Optional +from typing import Any import txaio from zope.interface import implementer @@ -79,14 +81,14 @@ ) -def create_client_agent(reactor): +def create_client_agent(reactor) -> "_TwistedWebSocketClientAgent": """ :returns: an instance implementing IWebSocketClientAgent """ return _TwistedWebSocketClientAgent(reactor) -def check_transport_config(transport_config): +def check_transport_config(transport_config: str) -> None: """ raises a ValueError if `transport_config` is invalid """ @@ -107,7 +109,7 @@ def check_transport_config(transport_config): return None -def check_client_options(options): +def check_client_options(options: dict[str, Any]) -> None: """ raises a ValueError if `options` is invalid """ @@ -261,10 +263,10 @@ class WebSocketAdapterProtocol(twisted.internet.protocol.Protocol): log = txaio.make_logger() - peer: Optional[str] = None - is_server: Optional[bool] = None + peer: str | None = None + is_server: bool | None = None - def connectionMade(self): + def connectionMade(self) -> None: # Twisted networking framework entry point, called by Twisted # when the connection is established (either a client or a server) @@ -296,7 +298,7 @@ def connectionMade(self): peer=hlval(self.peer), ) - def connectionLost(self, reason: Failure = connectionDone): + def connectionLost(self, reason: Failure = connectionDone) -> None: # Twisted networking framework entry point, called by Twisted # when the connection is lost (either a client or a server) @@ -352,7 +354,7 @@ def connectionLost(self, reason: Failure = connectionDone): reason=reason, ) - def dataReceived(self, data: bytes): + def dataReceived(self, data: bytes) -> None: self.log.debug( '{func} received {data_len} bytes for peer="{peer}"', func=hltype(self.dataReceived), @@ -363,14 +365,14 @@ def dataReceived(self, data: bytes): # bytes received from Twisted, forward to the networking framework independent code for websocket self._dataReceived(data) - def _closeConnection(self, abort=False): + def _closeConnection(self, abort: bool=False) -> None: if abort and hasattr(self.transport, "abortConnection"): self.transport.abortConnection() else: # e.g. ProcessProtocol lacks abortConnection() self.transport.loseConnection() - def _onOpen(self): + def _onOpen(self) -> None: if self._transport_details.is_secure: # now that the TLS opening handshake is complete, the actual TLS channel ID # will be available. make sure to set it! @@ -383,37 +385,37 @@ def _onOpen(self): self.onOpen() - def _onMessageBegin(self, isBinary): + def _onMessageBegin(self, isBinary: bool) -> None: self.onMessageBegin(isBinary) - def _onMessageFrameBegin(self, length): + def _onMessageFrameBegin(self, length: int) -> None: self.onMessageFrameBegin(length) - def _onMessageFrameData(self, payload): + def _onMessageFrameData(self, payload) -> None: self.onMessageFrameData(payload) - def _onMessageFrameEnd(self): + def _onMessageFrameEnd(self) -> None: self.onMessageFrameEnd() - def _onMessageFrame(self, payload): + def _onMessageFrame(self, payload) -> None: self.onMessageFrame(payload) - def _onMessageEnd(self): + def _onMessageEnd(self) -> None: self.onMessageEnd() - def _onMessage(self, payload, isBinary): + def _onMessage(self, payload, isBinary: bool) -> None: self.onMessage(payload, isBinary) - def _onPing(self, payload): + def _onPing(self, payload) -> None: self.onPing(payload) - def _onPong(self, payload): + def _onPong(self, payload) -> None: self.onPong(payload) - def _onClose(self, wasClean, code, reason): + def _onClose(self, wasClean: bool, code, reason) -> None: self.onClose(wasClean, code, reason) - def registerProducer(self, producer, streaming): + def registerProducer(self, producer, streaming) -> None: """ Register a Twisted producer with this protocol. @@ -424,7 +426,7 @@ def registerProducer(self, producer, streaming): """ self.transport.registerProducer(producer, streaming) - def unregisterProducer(self): + def unregisterProducer(self) -> None: """ Unregister Twisted producer with this protocol. """ @@ -608,10 +610,10 @@ def onConnect(self, requestOrResponse): # should not arrive here raise Exception("logic error") - def onOpen(self): + def onOpen(self) -> None: self._proto.connectionMade() - def onMessage(self, payload, isBinary): + def onMessage(self, payload: bytes, isBinary: bool) -> None: if isBinary != self._binaryMode: self._fail_connection( protocol.WebSocketProtocol.CLOSE_STATUS_CODE_UNSUPPORTED_DATA, @@ -632,7 +634,7 @@ def onMessage(self, payload, isBinary): def onClose(self, wasClean, code, reason): self._proto.connectionLost(None) - def write(self, data): + def write(self, data: bytes) -> None: # part of ITransport assert type(data) == bytes if self._binaryMode: @@ -641,12 +643,12 @@ def write(self, data): data = b64encode(data) self.sendMessage(data, isBinary=False) - def writeSequence(self, data): + def writeSequence(self, data: bytes) -> None: # part of ITransport for d in data: self.write(d) - def loseConnection(self): + def loseConnection(self) -> None: # part of ITransport self.sendClose() diff --git a/src/autobahn/wamp/message.py b/src/autobahn/wamp/message.py index 907a2ae07..254f58488 100644 --- a/src/autobahn/wamp/message.py +++ b/src/autobahn/wamp/message.py @@ -24,11 +24,13 @@ # ############################################################################### +from __future__ import annotations + import binascii import re import textwrap from pprint import pformat -from typing import Any, Dict, Optional +from typing import Any, Literal, overload import autobahn from autobahn.util import hlval @@ -243,7 +245,7 @@ def b2a(data, max_len=40): return s -def identify_realm_name_category(value: Any) -> Optional[str]: +def identify_realm_name_category(value: Any) -> str | None: """ Identify the real name category of the given value: @@ -272,14 +274,38 @@ def identify_realm_name_category(value: Any) -> Optional[str]: return None +@overload +def check_or_raise_uri( + value: Any, + message: str, + strict: bool, + allow_empty_components: bool, + allow_last_empty: bool, + allow_none: Literal[True], +) -> str | None: + pass + + +@overload def check_or_raise_uri( value: Any, message: str = "WAMP message invalid", strict: bool = False, allow_empty_components: bool = False, allow_last_empty: bool = False, - allow_none: bool = False, + allow_none: Literal[False] = False, ) -> str: + pass + + +def check_or_raise_uri( + value: Any, + message: str = "WAMP message invalid", + strict: bool = False, + allow_empty_components: bool = False, + allow_last_empty: bool = False, + allow_none: bool = False, +) -> str | None: """ Check a value for being a valid WAMP URI. @@ -408,7 +434,7 @@ def check_or_raise_id(value: Any, message: str = "WAMP message invalid") -> int: def check_or_raise_extra( value: Any, message: str = "WAMP message invalid" -) -> Dict[str, Any]: +) -> dict[str, Any]: """ Check a value for being a valid WAMP extra dictionary. diff --git a/src/autobahn/wamp/types.py b/src/autobahn/wamp/types.py index ab2867fa6..f6012f1ce 100644 --- a/src/autobahn/wamp/types.py +++ b/src/autobahn/wamp/types.py @@ -23,9 +23,12 @@ # THE SOFTWARE. # ############################################################################### + +from __future__ import annotations + from binascii import a2b_hex from pprint import pformat -from typing import Any, Dict, List, Optional +from typing import Any from autobahn.util import public from autobahn.wamp.request import Publication, Registration, Subscription @@ -160,12 +163,12 @@ class Accept(HelloReturn): def __init__( self, - realm: Optional[str] = None, - authid: Optional[str] = None, - authrole: Optional[str] = None, - authmethod: Optional[str] = None, - authprovider: Optional[str] = None, - authextra: Optional[Dict[str, Any]] = None, + realm: str | None = None, + authid: str | None = None, + authrole: str | None = None, + authmethod: str | None = None, + authprovider: str | None = None, + authextra: dict[str, Any] | None = None, ): """ @@ -1682,23 +1685,23 @@ class TransportDetails(object): def __init__( self, - channel_type: Optional[int] = None, - channel_framing: Optional[int] = None, - channel_serializer: Optional[int] = None, - own: Optional[str] = None, - peer: Optional[str] = None, - is_server: Optional[bool] = None, - own_pid: Optional[int] = None, - own_tid: Optional[int] = None, - own_fd: Optional[int] = None, - is_secure: Optional[bool] = None, - channel_id: Optional[Dict[str, bytes]] = None, - peer_cert: Optional[Dict[str, Any]] = None, - websocket_protocol: Optional[str] = None, - websocket_extensions_in_use: Optional[List[str]] = None, - http_headers_received: Optional[Dict[str, Any]] = None, - http_headers_sent: Optional[Dict[str, Any]] = None, - http_cbtid: Optional[str] = None, + channel_type: int | None = None, + channel_framing: int | None = None, + channel_serializer: int | None = None, + own: str | None = None, + peer: str | None = None, + is_server: bool | None = None, + own_pid: int | None = None, + own_tid: int | None = None, + own_fd: int | None = None, + is_secure: bool | None = None, + channel_id: dict[str, bytes] | None = None, + peer_cert: dict[str, Any] | None = None, + websocket_protocol: str | None = None, + websocket_extensions_in_use: list[str] | None = None, + http_headers_received: dict[str, Any] | None = None, + http_headers_sent: dict[str, Any] | None = None, + http_cbtid: str | None = None, ): self._channel_type = channel_type self._channel_framing = channel_framing @@ -1761,7 +1764,7 @@ def __ne__(self, other): return not self.__eq__(other) @staticmethod - def parse(data: Dict[str, Any]) -> "TransportDetails": + def parse(data: dict[str, Any]) -> "TransportDetails": assert type(data) == dict obj = TransportDetails() @@ -1921,7 +1924,7 @@ def parse(data: Dict[str, Any]) -> "TransportDetails": obj.http_cbtid = data["http_cbtid"] return obj - def marshal(self) -> Dict[str, Any]: + def marshal(self) -> dict[str, Any]: return { "channel_type": self.CHANNEL_TYPE_TO_STR.get(self._channel_type, None), "channel_framing": self.CHANNEL_FRAMING_TO_STR.get( @@ -1970,40 +1973,40 @@ def channel_typeid(self): ) @property - def channel_type(self) -> Optional[int]: + def channel_type(self) -> int | None: """ The underlying transport type, e.g. TCP. """ return self._channel_type @channel_type.setter - def channel_type(self, value: Optional[int]): + def channel_type(self, value: int | None): self._channel_type = value @property - def channel_framing(self) -> Optional[int]: + def channel_framing(self) -> int | None: """ The message framing used on this transport, e.g. WebSocket. """ return self._channel_framing @channel_framing.setter - def channel_framing(self, value: Optional[int]): + def channel_framing(self, value: int | None): self._channel_framing = value @property - def channel_serializer(self) -> Optional[int]: + def channel_serializer(self) -> int | None: """ The message serializer used on this transport, e.g. CBOR (batched or unbatched). """ return self._channel_serializer @channel_serializer.setter - def channel_serializer(self, value: Optional[int]): + def channel_serializer(self, value: int | None): self._channel_serializer = value @property - def own(self) -> Optional[str]: + def own(self) -> str | None: """ https://github.com/crossbario/autobahn-python/blob/master/autobahn/websocket/test/test_websocket_url.py @@ -2026,11 +2029,11 @@ def own(self) -> Optional[str]: return self._own @own.setter - def own(self, value: Optional[str]): + def own(self, value: str | None): self._own = value @property - def peer(self) -> Optional[str]: + def peer(self) -> str | None: """ The peer this transport is connected to. @@ -2045,11 +2048,11 @@ def peer(self) -> Optional[str]: return self._peer @peer.setter - def peer(self, value: Optional[str]): + def peer(self, value: str | None): self._peer = value @property - def is_server(self) -> Optional[bool]: + def is_server(self) -> bool | None: """ Flag indicating whether this side of the peer is a "server" (on underlying transports that follows a client-server approach). @@ -2057,22 +2060,22 @@ def is_server(self) -> Optional[bool]: return self._is_server @is_server.setter - def is_server(self, value: Optional[bool]): + def is_server(self, value: bool | None): self._is_server = value @property - def own_pid(self) -> Optional[int]: + def own_pid(self) -> int | None: """ The process ID (PID) of this end of the connection. """ return self._own_pid @own_pid.setter - def own_pid(self, value: Optional[int]): + def own_pid(self, value: int | None): self._own_pid = value @property - def own_tid(self) -> Optional[int]: + def own_tid(self) -> int | None: """ The native thread ID of this end of the connection. @@ -2086,22 +2089,22 @@ def own_tid(self) -> Optional[int]: return self._own_tid @own_tid.setter - def own_tid(self, value: Optional[int]): + def own_tid(self, value: int | None): self._own_tid = value @property - def own_fd(self) -> Optional[int]: + def own_fd(self) -> int | None: """ The file descriptor (FD) at this end of the connection. """ return self._own_fd @own_fd.setter - def own_fd(self, value: Optional[int]): + def own_fd(self, value: int | None): self._own_fd = value @property - def is_secure(self) -> Optional[bool]: + def is_secure(self) -> bool | None: """ Flag indicating whether this transport runs over TLS (or similar), and hence is encrypting at the byte stream or datagram transport level (beneath WAMP payload encryption). @@ -2109,11 +2112,11 @@ def is_secure(self) -> Optional[bool]: return self._is_secure @is_secure.setter - def is_secure(self, value: Optional[bool]): + def is_secure(self, value: bool | None): self._is_secure = value @property - def channel_id(self) -> Dict[str, bytes]: + def channel_id(self) -> dict[str, bytes] | None: """ If this transport runs over a secure underlying connection, e.g. TLS, return a map of channel binding by binding type. @@ -2157,11 +2160,11 @@ def channel_id(self) -> Dict[str, bytes]: return self._channel_id @channel_id.setter - def channel_id(self, value: Dict[str, bytes]): + def channel_id(self, value: dict[str, bytes]): self._channel_id = value @property - def peer_cert(self) -> Dict[str, Any]: + def peer_cert(self) -> dict[str, Any] | None: """ If this transport is using TLS and the TLS peer has provided a valid certificate, this attribute returns the peer certificate. @@ -2172,11 +2175,11 @@ def peer_cert(self) -> Dict[str, Any]: return self._peer_cert @peer_cert.setter - def peer_cert(self, value: Dict[str, Any]): + def peer_cert(self, value: dict[str, Any]): self._peer_cert = value @property - def websocket_protocol(self) -> Optional[str]: + def websocket_protocol(self) -> str | None: """ If the underlying connection uses a regular HTTP based WebSocket opening handshake, the WebSocket subprotocol negotiated, e.g. ``"wamp.2.cbor.batched"``. @@ -2184,11 +2187,11 @@ def websocket_protocol(self) -> Optional[str]: return self._websocket_protocol @websocket_protocol.setter - def websocket_protocol(self, value: Optional[str]): + def websocket_protocol(self, value: str | None): self._websocket_protocol = value @property - def websocket_extensions_in_use(self) -> Optional[List[str]]: + def websocket_extensions_in_use(self) -> list[str] | None: """ If the underlying connection uses a regular HTTP based WebSocket opening handshake, the WebSocket extensions negotiated, e.g. ``["permessage-deflate", "client_no_context_takeover", "client_max_window_bits"]``. @@ -2196,11 +2199,11 @@ def websocket_extensions_in_use(self) -> Optional[List[str]]: return self._websocket_extensions_in_use @websocket_extensions_in_use.setter - def websocket_extensions_in_use(self, value: Optional[List[str]]): + def websocket_extensions_in_use(self, value: list[str] | None): self._websocket_extensions_in_use = value @property - def http_headers_received(self) -> Dict[str, Any]: + def http_headers_received(self) -> dict[str, Any] | None: """ If the underlying connection uses a regular HTTP based WebSocket opening handshake, the HTTP request headers as received from the client on this connection. @@ -2208,11 +2211,11 @@ def http_headers_received(self) -> Dict[str, Any]: return self._http_headers_received @http_headers_received.setter - def http_headers_received(self, value: Dict[str, Any]): + def http_headers_received(self, value: dict[str, Any]): self._http_headers_received = value @property - def http_headers_sent(self) -> Dict[str, Any]: + def http_headers_sent(self) -> dict[str, Any] | None: """ If the underlying connection uses a regular HTTP based WebSocket opening handshake, the HTTP response headers as sent from the server on this connection. @@ -2220,11 +2223,11 @@ def http_headers_sent(self) -> Dict[str, Any]: return self._http_headers_sent @http_headers_sent.setter - def http_headers_sent(self, value: Dict[str, Any]): + def http_headers_sent(self, value: dict[str, Any]): self._http_headers_sent = value @property - def http_cbtid(self) -> Optional[str]: + def http_cbtid(self) -> str | None: """ If the underlying connection uses a regular HTTP based WebSocket opening handshake, the HTTP cookie value of the WAMP tracking cookie if any is associated with this @@ -2233,7 +2236,7 @@ def http_cbtid(self) -> Optional[str]: return self._http_cbtid @http_cbtid.setter - def http_cbtid(self, value: Optional[str]): + def http_cbtid(self, value: str | None): self._http_cbtid = value @@ -2262,18 +2265,18 @@ class SessionDetails(object): def __init__( self, - realm: Optional[str] = None, - session: Optional[int] = None, - authid: Optional[str] = None, - authrole: Optional[str] = None, - authmethod: Optional[str] = None, - authprovider: Optional[str] = None, - authextra: Optional[Dict[str, Any]] = None, - serializer: Optional[str] = None, - transport: Optional[TransportDetails] = None, - resumed: Optional[bool] = None, - resumable: Optional[bool] = None, - resume_token: Optional[str] = None, + realm: str | None = None, + session: int | None = None, + authid: str | None = None, + authrole: str | None = None, + authmethod: str | None = None, + authprovider: str | None = None, + authextra: dict[str, Any] | None = None, + serializer: str | None = None, + transport: TransportDetails | None = None, + resumed: bool | None = None, + resumable: bool | None = None, + resume_token: str | None = None, ): """ @@ -2349,7 +2352,7 @@ def __ne__(self, other): return not self.__eq__(other) @staticmethod - def parse(data: Dict[str, Any]) -> "SessionDetails": + def parse(data: dict[str, Any]) -> "SessionDetails": """ :param data: @@ -2456,7 +2459,7 @@ def parse(data: Dict[str, Any]) -> "SessionDetails": return obj - def marshal(self) -> Dict[str, Any]: + def marshal(self) -> dict[str, Any]: """ :return: @@ -2481,51 +2484,51 @@ def __str__(self) -> str: return pformat(self.marshal()) @property - def realm(self) -> Optional[str]: + def realm(self) -> str | None: """ The WAMP realm this session is attached to, e.g. ``"realm1"``. """ return self._realm @realm.setter - def realm(self, value: Optional[str]): + def realm(self, value: str | None): self._realm = value @property - def session(self) -> Optional[int]: + def session(self) -> int | None: """ WAMP session ID of this session, e.g. ``7069739155960584``. """ return self._session @session.setter - def session(self, value: Optional[int]): + def session(self, value: int | None): self._session = value @property - def authid(self) -> Optional[str]: + def authid(self) -> str | None: """ The WAMP authid this session is joined as, e.g. ``"joe89"`` """ return self._authid @authid.setter - def authid(self, value: Optional[str]): + def authid(self, value: str | None): self._authid = value @property - def authrole(self) -> Optional[str]: + def authrole(self) -> str | None: """ The WAMP authrole this session is joined as, e.g. ``"user"``. """ return self._authrole @authrole.setter - def authrole(self, value: Optional[str]): + def authrole(self, value: str | None): self._authrole = value @property - def authmethod(self) -> Optional[str]: + def authmethod(self) -> str | None: """ The WAMP authentication method the session is authenticated under, e.g. ``"anonymous"`` or ``"wampcra"``. @@ -2533,11 +2536,11 @@ def authmethod(self) -> Optional[str]: return self._authmethod @authmethod.setter - def authmethod(self, value: Optional[str]): + def authmethod(self, value: str | None): self._authmethod = value @property - def authprovider(self) -> Optional[str]: + def authprovider(self) -> str | None: """ The WAMP authentication provider that handled the session authentication, e.g. ``"static"`` or ``"dynamic"``. @@ -2545,71 +2548,71 @@ def authprovider(self) -> Optional[str]: return self._authprovider @authprovider.setter - def authprovider(self, value: Optional[str]): + def authprovider(self, value: str | None): self._authprovider = value @property - def authextra(self) -> Optional[Dict[str, Any]]: + def authextra(self) -> dict[str, Any] | None: """ The (optional) WAMP authentication extra that was provided to the authenticating session. """ return self._authextra @authextra.setter - def authextra(self, value: Optional[Dict[str, Any]]): + def authextra(self, value: dict[str, Any] | None): self._authextra = value @property - def serializer(self) -> Optional[str]: + def serializer(self) -> str | None: """ The WAMP serializer (variant) this session is using, e.g. ``"json"`` or ``"cbor.batched"``. """ return self._serializer @serializer.setter - def serializer(self, value: Optional[str]): + def serializer(self, value: str | None): self._serializer = value @property - def transport(self) -> Optional[TransportDetails]: + def transport(self) -> TransportDetails | None: """ The details of the WAMP transport this session is hosted on (communicates over). """ return self._transport @transport.setter - def transport(self, value: Optional[TransportDetails]): + def transport(self, value: TransportDetails | None): self._transport = value @property - def resumed(self) -> Optional[bool]: + def resumed(self) -> bool | None: """ Whether the session is a resumed one. """ return self._resumed @resumed.setter - def resumed(self, value: Optional[bool]): + def resumed(self, value: bool | None): self._resumed = value @property - def resumable(self) -> Optional[bool]: + def resumable(self) -> bool | None: """ Whether this session can be resumed later. """ return self._resumable @resumable.setter - def resumable(self, value: Optional[bool]): + def resumable(self, value: bool | None): self._resumable = value @property - def resume_token(self) -> Optional[str]: + def resume_token(self) -> str | None: """ The secure authorization token to resume the session. """ return self._resume_token @resume_token.setter - def resume_token(self, value: Optional[str]): + def resume_token(self, value: str | None): self._resume_token = value diff --git a/src/autobahn/websocket/protocol.py b/src/autobahn/websocket/protocol.py index 4368fe1de..638a10575 100755 --- a/src/autobahn/websocket/protocol.py +++ b/src/autobahn/websocket/protocol.py @@ -24,6 +24,8 @@ # ############################################################################### +from __future__ import annotations + import base64 import binascii import copy @@ -36,7 +38,7 @@ import time from collections import deque from pprint import pformat -from typing import Dict, Optional, Tuple, Union +from typing import Literal, Iterator, overload from urllib import parse import hyperlink @@ -249,20 +251,15 @@ class FrameHeader(object): FOR INTERNAL USE ONLY! """ - def __init__(self, opcode, fin, rsv, length, mask): + def __init__(self, opcode: int, fin: bool, rsv: int, length: int, mask: str): """ Constructor. :param opcode: Frame opcode (0-15). - :type opcode: int :param fin: Frame FIN flag. - :type fin: bool :param rsv: Frame reserved flags (0-7). - :type rsv: int :param length: Frame payload length. - :type length: int :param mask: Frame mask (binary string) or None. - :type mask: str """ self.opcode = opcode self.fin = fin @@ -271,7 +268,7 @@ def __init__(self, opcode, fin, rsv, length, mask): self.mask = mask -def parseHttpHeader(data): +def parseHttpHeader(data: bytes) -> tuple[str, dict[str, str], dict[str, int]]: """ Parses the beginning of a HTTP request header (the data up to the \n\n line) into a pair of status line and HTTP headers dictionary. @@ -280,7 +277,6 @@ def parseHttpHeader(data): FOR INTERNAL USE ONLY! :param data: The HTTP header data up to the \n\n line. - :type data: bytes :returns: Tuple of HTTP status line, headers and headers count. """ @@ -294,8 +290,8 @@ def parseHttpHeader(data): # raw = data.decode("iso-8859-1").splitlines() http_status_line = raw[0].strip() - http_headers = {} - http_headers_cnt = {} + http_headers: dict[str, str] = {} + http_headers_cnt: dict[str, int] = {} for h in raw[1:]: i = h.find(":") if i > 0: @@ -324,27 +320,31 @@ class Timings(object): def __init__(self): self._stopwatch = Stopwatch() - self._timings = {} + self._timings: dict[str, float] = {} - def track(self, key): + def track(self, key: str) -> None: """ Track elapsed for key. :param key: Key under which to track the timing. - :type key: str """ self._timings[key] = self._stopwatch.elapsed() - def diff(self, startKey, endKey, formatted=True): + @overload + def diff(self, startKey: str, endKey: str, formatted: Literal[True]) -> str | None: + ... + + @overload + def diff(self, startKey: str, endKey: str, formatted: Literal[False]) -> float | None: + ... + + def diff(self, startKey: str, endKey: str, formatted: bool=True) -> str | float | None: """ Get elapsed difference between two previously tracked keys. :param startKey: First key for interval (older timestamp). - :type startKey: str :param endKey: Second key for interval (younger timestamp). - :type endKey: str :param formatted: If ``True``, format computed time period and return string. - :type formatted: bool :returns: Computed time period in seconds (or formatted string). """ @@ -368,13 +368,13 @@ def diff(self, startKey, endKey, formatted=True): else: return None - def __getitem__(self, key): + def __getitem__(self, key: str) -> float | None: return self._timings.get(key, None) - def __iter__(self): - return self._timings.__iter__() + def __iter__(self) -> Iterator[str]: + return iter(self._timings) - def __str__(self): + def __str__(self) -> str: return pformat(self._timings) @@ -586,7 +586,7 @@ class WebSocketProtocol(ObservableMixin): Configuration attributes specific to clients. """ - def __init__(self): + def __init__(self) -> None: #: a Future/Deferred that fires when we hit STATE_CLOSED self.is_closed = txaio.create_future() self.is_open = txaio.create_future() @@ -601,30 +601,30 @@ def __init__(self): # set in # * autobahn.twisted.websocket.WebSocketAdapterProtocol.connectionMade # * autobahn.asyncio.websocket.WebSocketAdapterProtocol. - self._transport_details: Optional[TransportDetails] = TransportDetails() + self._transport_details: TransportDetails | None = TransportDetails() @property - def transport_details(self) -> Optional[TransportDetails]: + def transport_details(self) -> TransportDetails | None: """ Implements :class:`autobahn.wamp.interfaces.ITransport.transport_details`. """ return self._transport_details - def onOpen(self): + def onOpen(self) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen` """ self.log.debug("WebSocketProtocol.onOpen") - def onMessageBegin(self, isBinary): + def onMessageBegin(self, isBinary: bool) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageBegin` """ self.message_is_binary = isBinary - self.message_data = [] + self.message_data: list[bytes] = [] self.message_data_total_length = 0 - def onMessageFrameBegin(self, length): + def onMessageFrameBegin(self, length: int) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrameBegin` """ @@ -651,7 +651,7 @@ def onMessageFrameBegin(self, length): ), ) - def onMessageFrameData(self, payload): + def onMessageFrameData(self, payload: bytes) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrameData` """ @@ -671,7 +671,7 @@ def onMessageFrameData(self, payload): else: self.frame_data.append(payload) - def onMessageFrameEnd(self): + def onMessageFrameEnd(self) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrameEnd` """ @@ -680,14 +680,14 @@ def onMessageFrameEnd(self): self.frame_data = None - def onMessageFrame(self, payload): + def onMessageFrame(self, payload: bytes) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageFrame` """ if not self.failedByMe: self.message_data.extend(payload) - def onMessageEnd(self): + def onMessageEnd(self) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessageEnd` """ @@ -712,7 +712,7 @@ def error(f): self.message_data = None - def onMessage(self, payload, isBinary): + def onMessage(self, payload: bytes, isBinary: bool) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onMessage` """ @@ -722,7 +722,7 @@ def onMessage(self, payload, isBinary): isBinary=isBinary, ) - def onPing(self, payload): + def onPing(self, payload: bytes) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onPing` """ @@ -733,7 +733,7 @@ def onPing(self, payload): if self.state == WebSocketProtocol.STATE_OPEN: self.sendPong(payload) - def onPong(self, payload): + def onPong(self, payload: bytes) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onPong` """ @@ -742,7 +742,7 @@ def onPong(self, payload): payload_len=(len(payload) if payload else 0), ) - def onClose(self, wasClean, code, reason): + def onClose(self, wasClean: bool, code, reason) -> None: """ Implements :meth:`autobahn.websocket.interfaces.IWebSocketChannel.onClose` """ @@ -753,7 +753,7 @@ def onClose(self, wasClean, code, reason): reason=reason, ) - def onCloseFrame(self, code, reasonRaw): + def onCloseFrame(self, code: int, reasonRaw: bytes) -> bool | None: """ Callback when a Close frame was received. The default implementation answers by sending a Close when no Close was sent before. Otherwise it drops @@ -761,9 +761,7 @@ def onCloseFrame(self, code, reasonRaw): (when we are a client and expect the server to drop the TCP). :param code: Close status code, if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*). - :type code: int :param reasonRaw: Close reason (when present, a status code MUST have been also be present). - :type reasonRaw: bytes """ self.remoteCloseCode = None self.remoteCloseReason = None @@ -867,7 +865,7 @@ def onCloseFrame(self, code, reasonRaw): # STATE_PROXY_CONNECTING, STATE_CONNECTING raise Exception("logic error") - def onServerConnectionDropTimeout(self): + def onServerConnectionDropTimeout(self) -> None: """ We (a client) expected the peer (a server) to drop the connection, but it didn't (in time self.serverConnectionDropTimeout). @@ -885,7 +883,7 @@ def onServerConnectionDropTimeout(self): "skipping closing handshake timeout: server did indeed drop the connection in time" ) - def onOpenHandshakeTimeout(self): + def onOpenHandshakeTimeout(self) -> None: """ We expected the peer to complete the opening handshake with to us. It didn't do so (in time self.openHandshakeTimeout). @@ -917,7 +915,7 @@ def onOpenHandshakeTimeout(self): # should not arrive here raise Exception("logic error") - def onCloseHandshakeTimeout(self): + def onCloseHandshakeTimeout(self) -> None: """ We expected the peer to respond to us initiating a close handshake. It didn't respond (in time self.closeHandshakeTimeout) with a close response frame though. @@ -935,7 +933,7 @@ def onCloseHandshakeTimeout(self): "skipping closing handshake timeout: WebSocket connection is already closed" ) - def onAutoPong(self, ping_sent, ping_seq, pong_received, pong_rtt, payload): + def onAutoPong(self, ping_sent, ping_seq, pong_received, pong_rtt, payload: bytes) -> None: """ When doing automatic ping/pongs, this is called upon a successful pong. @@ -955,7 +953,7 @@ def onAutoPong(self, ping_sent, ping_seq, pong_received, pong_rtt, payload): rtt=pong_rtt, ) - def onAutoPingTimeout(self): + def onAutoPingTimeout(self) -> None: """ When doing automatic ping/pongs to detect broken connection, the peer did not reply in time to our ping. We drop the connection. @@ -967,7 +965,7 @@ def onAutoPingTimeout(self): self.autoPingTimeoutCall = None self.dropConnection(abort=True) - def dropConnection(self, abort=False): + def dropConnection(self, abort: bool=False) -> None: """ Drop the underlying TCP connection. """ @@ -1002,7 +1000,7 @@ def dropConnection(self, abort=False): peer=self.peer, ) - def _max_message_size_exceeded(self, msg_size, max_msg_size, reason): + def _max_message_size_exceeded(self, msg_size, max_msg_size, reason) -> None: # hook that is fired when a message is (to be) received that is larger than what is configured to be handled if True: self._fail_connection( @@ -1011,7 +1009,7 @@ def _max_message_size_exceeded(self, msg_size, max_msg_size, reason): else: raise PayloadExceededError(reason) - def _fail_connection(self, code=CLOSE_STATUS_CODE_GOING_AWAY, reason="going away"): + def _fail_connection(self, code: int=CLOSE_STATUS_CODE_GOING_AWAY, reason: str="going away") -> None: """ Fails the WebSocket connection. """ @@ -1041,7 +1039,7 @@ def _fail_connection(self, code=CLOSE_STATUS_CODE_GOING_AWAY, reason="going away isReply=False, ) else: - # already performing closing handshake .. we now drop the TCP + # already performing closing handshake .. we now drop theprotocol TCP # (this can happen e.g. if we encounter a 2nd protocol violation during closing HS) self.dropConnection(abort=False) @@ -1050,12 +1048,11 @@ def _fail_connection(self, code=CLOSE_STATUS_CODE_GOING_AWAY, reason="going away "skip failing of connection since connection is already closed" ) - def _protocol_violation(self, reason): + def _protocol_violation(self, reason: str) -> bool: """ Fired when a WebSocket protocol violation/error occurs. :param reason: Protocol violation that was encountered (human readable). - :type reason: str :returns: True, when any further processing should be discontinued. """ @@ -1072,14 +1069,13 @@ def _protocol_violation(self, reason): # to continue to later receive the closing handshake reply return False - def _invalid_payload(self, reason): + def _invalid_payload(self, reason: str) -> bool: """ Fired when invalid payload is encountered. Currently, this only happens for text message when payload is invalid UTF-8 or close frames with close reason that is invalid UTF-8. :param reason: What was invalid for the payload (human readable). - :type reason: str :returns: True, when any further processing should be discontinued. """ @@ -1096,12 +1092,11 @@ def _invalid_payload(self, reason): # to continue to later receive the closing handshake reply return False - def setTrackTimings(self, enable): + def setTrackTimings(self, enable: bool) -> None: """ Enable/disable tracking of detailed timings. :param enable: Turn time tracking on/off. - :type enable: bool """ if not hasattr(self, "trackTimings") or self.trackTimings != enable: self.trackTimings = enable @@ -1110,7 +1105,7 @@ def setTrackTimings(self, enable): else: self.trackedTimings = None - def _connectionMade(self): + def _connectionMade(self) -> None: """ This is called by network framework when a new TCP connection has been established and handed over to a Protocol instance (an instance of this class). @@ -1230,7 +1225,7 @@ def _connectionMade(self): self.onOpenHandshakeTimeout, ) - def _connectionLost(self, reason): + def _connectionLost(self, reason: str) -> None: """ This is called by network framework when a transport connection was lost. @@ -1300,7 +1295,7 @@ def _connectionLost(self, reason): ) # XXX could self.fire("close", ...) here if we want? - def logRxOctets(self, data): + def logRxOctets(self, data: bytes) -> None: """ Hook fired right after raw octets have been received, but only when self.logOctets == True. @@ -1311,7 +1306,7 @@ def logRxOctets(self, data): octets=_LazyHexFormatter(data), ) - def logTxOctets(self, data, sync): + def logTxOctets(self, data: bytes, sync: bool) -> None: """ Hook fired right after raw octets have been sent, but only when self.logOctets == True. @@ -1323,7 +1318,7 @@ def logTxOctets(self, data, sync): octets=_LazyHexFormatter(data), ) - def logRxFrame(self, frameHeader, payload): + def logRxFrame(self, frameHeader: FrameHeader, payload: bytes) -> None: """ Hook fired right after WebSocket frame has been received and decoded, but only when self.logFrames == True. @@ -1341,7 +1336,7 @@ def logRxFrame(self, frameHeader, payload): payload=repr(data) if frameHeader.opcode == 1 else _LazyHexFormatter(data), ) - def logTxFrame(self, frameHeader, payload, repeatLength, chopsize, sync): + def logTxFrame(self, frameHeader: FrameHeader, payload: bytes, repeatLength: int, chopsize: int, sync: bool) -> None: """ Hook fired right after WebSocket frame has been encoded and sent, but only when self.logFrames == True. @@ -1364,7 +1359,7 @@ def logTxFrame(self, frameHeader, payload, repeatLength, chopsize, sync): ), ) - def _dataReceived(self, data): + def _dataReceived(self, data: bytes) -> None: """ This is called by network framework upon receiving data on transport connection. @@ -1382,7 +1377,7 @@ def _dataReceived(self, data): self.data += data self.consumeData() - def consumeData(self): + def consumeData(self) -> None: """ Consume buffered (incoming) data. """ @@ -1423,7 +1418,7 @@ def consumeData(self): else: raise Exception("invalid state") - def processProxyConnect(self): + def processProxyConnect(self) -> None: """ Process proxy connect. """ @@ -1431,13 +1426,13 @@ def processProxyConnect(self): "must implement proxy connect (client or server) in derived class" ) - def processHandshake(self): + def processHandshake(self) -> None: """ Process WebSocket handshake. """ raise Exception("must implement handshake (client or server) in derived class") - def _trigger(self): + def _trigger(self) -> None: """ Trigger sending stuff from send queue (which is only used for chopped/synched writes). @@ -1446,7 +1441,7 @@ def _trigger(self): self.triggered = True self._send() - def _send(self): + def _send(self) -> None: """ Send out stuff from send queue. For details how this works, see test/trickling in the repo. @@ -1479,7 +1474,7 @@ def _send(self): else: self.triggered = False - def sendData(self, data, sync=False, chopsize=None): + def sendData(self, data: bytes, sync: bool=False, chopsize: int | None=None) -> None: """ Wrapper for self.transport.write which allows to give a chopsize. When asked to chop up writing to TCP stream, we write only chopsize @@ -1525,7 +1520,7 @@ def sendData(self, data, sync=False, chopsize=None): if self.logOctets: self.logTxOctets(data, False) - def sendPreparedMessage(self, preparedMsg): + def sendPreparedMessage(self, preparedMsg: "PreparedMessage") -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPreparedMessage` """ @@ -1534,7 +1529,7 @@ def sendPreparedMessage(self, preparedMsg): else: self.sendMessage(preparedMsg.payload, preparedMsg.binary) - def processData(self): + def processData(self) -> bool: """ After WebSocket handshake has been completed, this procedure will do all subsequent processing of incoming bytes. @@ -1813,7 +1808,7 @@ def processData(self): # return len(self.data) > 0 - def onFrameBegin(self): + def onFrameBegin(self) -> None: """ Begin of receive new frame. """ @@ -1858,7 +1853,7 @@ def onFrameBegin(self): self._onMessageFrameBegin(self.current_frame.length) - def onFrameData(self, payload): + def onFrameData(self, payload: bytes) -> bool | None: """ New data received within frame. """ @@ -1901,7 +1896,7 @@ def onFrameData(self, payload): self._onMessageFrameData(payload) - def onFrameEnd(self): + def onFrameEnd(self) -> bool | None: """ End of frame received. """ @@ -1948,7 +1943,7 @@ def onFrameEnd(self): self.current_frame = None - def processControlFrame(self): + def processControlFrame(self) -> bool: """ Process a completely received control frame. """ @@ -2027,15 +2022,15 @@ def processControlFrame(self): def sendFrame( self, - opcode, - payload=b"", - fin=True, - rsv=0, - mask=None, - payload_len=None, - chopsize=None, - sync=False, - ): + opcode: int, + payload: bytes=b"", + fin: bool=True, + rsv: int=0, + mask: bytes | None=None, + payload_len: int | None=None, + chopsize: int | None=None, + sync: bool=False, + ) -> None: """ Send out frame. Normally only used internally via sendMessage(), sendPing(), sendPong() and sendClose(). @@ -2133,7 +2128,7 @@ def sendFrame( # self.sendData(raw, sync, chopsize) - def sendPing(self, payload=None): + def sendPing(self, payload: bytes | None=None) -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendPing` """ @@ -2151,7 +2146,7 @@ def sendPing(self, payload=None): else: self.sendFrame(opcode=9) - def _sendAutoPing(self): + def _sendAutoPing(self) -> None: # Sends an automatic ping and sets up a timeout. self.log.debug("Auto ping/pong: sending ping auto-ping/pong") @@ -2221,7 +2216,7 @@ def sendPong(self, payload=None): else: self.sendFrame(opcode=10) - def sendCloseFrame(self, code=None, reasonUtf8=None, isReply=False): + def sendCloseFrame(self, code: int | None=None, reasonUtf8: bytes | None=None, isReply: bool=False) -> None: """ Send a close frame and update protocol state. Note, that this is an internal method which deliberately allows not send close @@ -2266,7 +2261,7 @@ def sendCloseFrame(self, code=None, reasonUtf8=None, isReply=False): else: raise Exception("logic error") - def sendClose(self, code=None, reason=None): + def sendClose(self, code: int | None=None, reason: str | None=None) -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendClose` """ @@ -2305,7 +2300,7 @@ def sendClose(self, code=None, reason=None): self.sendCloseFrame(code=code, reasonUtf8=reasonUtf8, isReply=False) - def beginMessage(self, isBinary=False, doNotCompress=False): + def beginMessage(self, isBinary: bool=False, doNotCompress: bool=False) -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.beginMessage` """ @@ -2336,7 +2331,7 @@ def beginMessage(self, isBinary=False, doNotCompress=False): self.trafficStats.outgoingWebSocketMessages += 1 - def beginMessageFrame(self, length): + def beginMessageFrame(self, length: int) -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.beginMessageFrame` """ @@ -2431,7 +2426,7 @@ def beginMessageFrame(self, length): # self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME - def sendMessageFrameData(self, payload, sync=False): + def sendMessageFrameData(self, payload: bytes, sync: bool=False) -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessageFrameData` """ @@ -2486,7 +2481,7 @@ def sendMessageFrameData(self, payload, sync=False): # return rest - def endMessage(self): + def endMessage(self) -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.endMessage` """ @@ -2508,7 +2503,7 @@ def endMessage(self): self.send_state = WebSocketProtocol.SEND_STATE_GROUND - def sendMessageFrame(self, payload, sync=False): + def sendMessageFrame(self, payload: bytes, sync: bool=False) -> None: """ Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.sendMessageFrame` """ @@ -2691,21 +2686,17 @@ class PreparedMessage(object): This can be used for optimizing Broadcast/PubSub. """ - def __init__(self, payload, isBinary, applyMask, doNotCompress): + def __init__(self, payload: bytes, isBinary: bool, applyMask: bool, doNotCompress: bool): """ Ctor for a prepared message. :param payload: The message payload. - :type payload: str :param isBinary: Provide `True` for binary payload. - :type isBinary: bool :param applyMask: Provide `True` if WebSocket message is to be masked (required for client to server WebSocket messages). - :type applyMask: bool :param doNotCompress: Iff `True`, never compress this message. This only applies when WebSocket compression has been negotiated on the WebSocket connection. Use when you know the payload incompressible (e.g. encrypted or already compressed). - :type doNotCompress: bool """ if not doNotCompress: # we need to store original payload for compressed WS @@ -2764,7 +2755,7 @@ class WebSocketFactory(object): :class:`autobahn.websocket.protocol.WebSocketServerFactory`. """ - def prepareMessage(self, payload, isBinary=False, doNotCompress=False): + def prepareMessage(self, payload: bytes, isBinary: bool=False, doNotCompress: bool=False): """ Prepare a WebSocket message. This can be later sent on multiple instances of :class:`autobahn.websocket.WebSocketProtocol` using @@ -2775,15 +2766,12 @@ def prepareMessage(self, payload, isBinary=False, doNotCompress=False): same payload is to be sent out on multiple connections. :param payload: The message payload. - :type payload: bytes :param isBinary: `True` iff payload is binary, else the payload must be UTF-8 encoded text. - :type isBinary: bool :param doNotCompress: Iff `True`, never compress this message. This only applies when WebSocket compression has been negotiated on the WebSocket connection. Use when you know the payload incompressible (e.g. encrypted or already compressed). - :type doNotCompress: bool :returns: An instance of :class:`autobahn.websocket.protocol.PreparedMessage`. """ @@ -2838,7 +2826,7 @@ class WebSocketServerProtocol(WebSocketProtocol): def onConnect( self, request: ConnectionRequest - ) -> Union[Optional[str], Tuple[Optional[str], Dict[str, str]]]: + ) -> str | tuple[str | None, dict[str, str]] | None: """ Callback fired during WebSocket opening handshake when new WebSocket client connection is about to be established. @@ -2865,7 +2853,7 @@ def onConnect( ) return None - def _connectionMade(self): + def _connectionMade(self) -> None: """ Called by network framework when new transport connection from client was accepted. Default implementation will prepare for initial WebSocket opening @@ -2880,7 +2868,7 @@ def _connectionMade(self): WebSocketProtocol._connectionMade(self) self.factory.countConnections += 1 - def _connectionLost(self, reason): + def _connectionLost(self, reason: str) -> None: """ Called by network framework when established transport connection from client was lost. Default implementation will tear down all state properly. @@ -2896,10 +2884,10 @@ def _connectionLost(self, reason): WebSocketProtocol._connectionLost(self, reason) self.factory.countConnections -= 1 - def processProxyConnect(self): + def processProxyConnect(self) -> None: raise Exception("Autobahn isn't a proxy server") - def processHandshake(self): + def processHandshake(self) -> None: """ Process WebSocket opening handshake request from client. """ @@ -3372,7 +3360,7 @@ def forward_error(err): "WebSocketServerFactory.flashSocketPolicy" ) - def succeedHandshake(self, res): + def succeedHandshake(self, res) -> None: """ Callback after onConnect() returns successfully. Generates the response for the handshake. """ @@ -3555,7 +3543,7 @@ def succeedHandshake(self, res): if len(self.data) > 0: self.consumeData() - def failHandshake(self, reason, code=400, responseHeaders=None): + def failHandshake(self, reason: str, code: int=400, responseHeaders=None): """ During opening handshake the client request was invalid, we send a HTTP error response and then drop the connection. @@ -3576,7 +3564,7 @@ def sendHttpErrorResponse(self, code, reason, responseHeaders=None): response += "\x0d\x0a" self.sendData(response.encode("utf8")) - def sendHtml(self, html): + def sendHtml(self, html: str) -> None: """ Send HTML page HTTP response. """ @@ -3590,7 +3578,7 @@ def sendHtml(self, html): self.sendData(response.encode("utf8")) self.sendData(responseBody) - def sendRedirect(self, url): + def sendRedirect(self, url: str) -> None: """ Send HTTP Redirect (303) response. """ @@ -3601,7 +3589,7 @@ def sendRedirect(self, url): response += "\x0d\x0a" self.sendData(response.encode("utf8")) - def sendServerStatus(self, redirectUrl=None, redirectAfter=0): + def sendServerStatus(self, redirectUrl: str | None=None, redirectAfter: int=0) -> None: """ Used to send out server status/version upon receiving a HTTP/GET without upgrade to WebSocket header (and option serverStatus is True). @@ -3921,7 +3909,7 @@ class WebSocketClientProtocol(WebSocketProtocol): def onConnecting( self, transport_details: TransportDetails - ) -> Optional[ConnectingRequest]: + ) -> ConnectingRequest | None: """ Callback fired after the connection is established, but before the handshake has started. This may return a