diff options
| author | robot-piglet <[email protected]> | 2026-06-03 16:47:44 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2026-06-03 17:44:32 +0300 |
| commit | f253db69975b87e0efb99cee1e57f69ff8cccc10 (patch) | |
| tree | 7430eaeabf337137af1bbe7b6e939dadba3da082 /contrib/python/Twisted/py3/twisted | |
| parent | 3b137bd05f166eb07dfaa42e6f63dd610597a69b (diff) | |
Intermediate changes
commit_hash:7321e1c01f99561516cbd12d4eda7442dbe4a679
Diffstat (limited to 'contrib/python/Twisted/py3/twisted')
184 files changed, 3046 insertions, 2208 deletions
diff --git a/contrib/python/Twisted/py3/twisted/_threads/_pool.py b/contrib/python/Twisted/py3/twisted/_threads/_pool.py index 99c055d2404..3dff48bafda 100644 --- a/contrib/python/Twisted/py3/twisted/_threads/_pool.py +++ b/contrib/python/Twisted/py3/twisted/_threads/_pool.py @@ -7,12 +7,11 @@ Top level thread pool interface, used to implement L{twisted.python.threadpool}. """ +from __future__ import annotations from queue import Queue from threading import Lock, Thread, local as LocalStorage -from typing import Callable, Optional - -from typing_extensions import Protocol +from typing import Callable, Protocol from twisted.python.log import err from ._ithreads import IWorker @@ -59,7 +58,7 @@ def pool( def startThread(target: Callable[..., object]) -> None: return threadFactory(target=target).start() - def limitedWorkerCreator() -> Optional[IWorker]: + def limitedWorkerCreator() -> IWorker | None: stats = team.statistics() if stats.busyWorkerCount + stats.idleWorkerCount >= currentLimit(): return None diff --git a/contrib/python/Twisted/py3/twisted/_threads/_team.py b/contrib/python/Twisted/py3/twisted/_threads/_team.py index 95e40cffa95..2d34fd37dbf 100644 --- a/contrib/python/Twisted/py3/twisted/_threads/_team.py +++ b/contrib/python/Twisted/py3/twisted/_threads/_team.py @@ -9,7 +9,7 @@ workers. from __future__ import annotations from collections import deque -from typing import Callable, Optional, Set +from typing import Callable from zope.interface import implementer @@ -77,7 +77,7 @@ class Team: def __init__( self, coordinator: IExclusiveWorker, - createWorker: Callable[[], Optional[IWorker]], + createWorker: Callable[[], IWorker | None], logException: Callable[[], None], ): """ @@ -100,9 +100,9 @@ class Team: self._logException = logException # Don't touch these except from the coordinator. - self._idle: Set[IWorker] = set() + self._idle: set[IWorker] = set() self._busyCount = 0 - self._pending: "deque[Callable[..., object]]" = deque() + self._pending: deque[Callable[..., object]] = deque() self._shouldQuitCoordinator = False self._toShrink = 0 @@ -131,7 +131,7 @@ class Team: return self._recycleWorker(worker) - def shrink(self, n: Optional[int] = None) -> None: + def shrink(self, n: int | None = None) -> None: """ Decrease the number of idle workers by C{n}. @@ -142,7 +142,7 @@ class Team: self._quit.check() self._coordinator.do(lambda: self._quitIdlers(n)) - def _quitIdlers(self, n: Optional[int] = None) -> None: + def _quitIdlers(self, n: int | None = None) -> None: """ The implmentation of C{shrink}, performed by the coordinator worker. diff --git a/contrib/python/Twisted/py3/twisted/_threads/_threadworker.py b/contrib/python/Twisted/py3/twisted/_threads/_threadworker.py index a4617a1974c..673b6b66bf2 100644 --- a/contrib/python/Twisted/py3/twisted/_threads/_threadworker.py +++ b/contrib/python/Twisted/py3/twisted/_threads/_threadworker.py @@ -7,8 +7,9 @@ Implementation of an L{IWorker} based on native threads and queues. """ from __future__ import annotations +from collections.abc import Iterator from enum import Enum, auto -from typing import TYPE_CHECKING, Callable, Iterator, Literal, Protocol, TypeVar +from typing import TYPE_CHECKING, Callable, Literal, Protocol, TypeVar if TYPE_CHECKING: import threading diff --git a/contrib/python/Twisted/py3/twisted/_version.py b/contrib/python/Twisted/py3/twisted/_version.py index 02290f83ab6..f6bce32271a 100644 --- a/contrib/python/Twisted/py3/twisted/_version.py +++ b/contrib/python/Twisted/py3/twisted/_version.py @@ -7,5 +7,5 @@ Provides Twisted version information. from incremental import Version -__version__ = Version("Twisted", 25, 5, 0) +__version__ = Version("Twisted", 26, 4, 0) __all__ = ["__version__"] diff --git a/contrib/python/Twisted/py3/twisted/application/internet.py b/contrib/python/Twisted/py3/twisted/application/internet.py index 8bcc9722a0a..ab4e6b142ff 100644 --- a/contrib/python/Twisted/py3/twisted/application/internet.py +++ b/contrib/python/Twisted/py3/twisted/application/internet.py @@ -37,8 +37,7 @@ C{TCPServer(8080, server.Site(r))}. See the documentation for the reactor.listen/connect* methods for more information. """ - -from typing import List +from __future__ import annotations from twisted.application import service from twisted.internet import task @@ -48,7 +47,7 @@ from ._client_service import ClientService, _maybeGlobalReactor, backoffPolicy class _VolatileDataService(service.Service): - volatile: List[str] = [] + volatile: list[str] = [] def __getstate__(self): d = service.Service.__getstate__(self) diff --git a/contrib/python/Twisted/py3/twisted/application/reactors.py b/contrib/python/Twisted/py3/twisted/application/reactors.py index a476ca98b79..04d516c44b8 100644 --- a/contrib/python/Twisted/py3/twisted/application/reactors.py +++ b/contrib/python/Twisted/py3/twisted/application/reactors.py @@ -6,7 +6,8 @@ Plugin-based system for enumerating available reactors and installing one of them. """ -from typing import Iterable, cast +from collections.abc import Iterable +from typing import cast from zope.interface import Attribute, Interface, implementer diff --git a/contrib/python/Twisted/py3/twisted/application/runner/_exit.py b/contrib/python/Twisted/py3/twisted/application/runner/_exit.py index 3d6bc10f6f9..ae2df3efbba 100644 --- a/contrib/python/Twisted/py3/twisted/application/runner/_exit.py +++ b/contrib/python/Twisted/py3/twisted/application/runner/_exit.py @@ -6,10 +6,11 @@ System exit support. """ -import typing +from __future__ import annotations + from enum import IntEnum from sys import exit as sysexit, stderr, stdout -from typing import Union +from typing import NoReturn try: import posix as Status @@ -80,7 +81,7 @@ class ExitStatus(IntEnum): EX_CONFIG = Status.EX_CONFIG -def exit(status: Union[int, ExitStatus], message: str = "") -> "typing.NoReturn": +def exit(status: int | ExitStatus, message: str = "") -> NoReturn: """ Exit the python interpreter with the given status and an optional message. diff --git a/contrib/python/Twisted/py3/twisted/application/runner/_pidfile.py b/contrib/python/Twisted/py3/twisted/application/runner/_pidfile.py index b6aab1499fb..8b27b24428a 100644 --- a/contrib/python/Twisted/py3/twisted/application/runner/_pidfile.py +++ b/contrib/python/Twisted/py3/twisted/application/runner/_pidfile.py @@ -10,7 +10,7 @@ from __future__ import annotations import errno from os import getpid, kill, name as SYSTEM_NAME from types import TracebackType -from typing import Any, Optional, Type +from typing import Any from zope.interface import Interface, implementer @@ -62,7 +62,7 @@ class IPIDFile(Interface): for which there is no corresponding running process. """ - def __enter__() -> "IPIDFile": + def __enter__() -> IPIDFile: """ Enter a context using this PIDFile. @@ -73,10 +73,10 @@ class IPIDFile(Interface): """ def __exit__( - excType: Optional[Type[BaseException]], - excValue: Optional[BaseException], - traceback: Optional[TracebackType], - ) -> Optional[bool]: + excType: type[BaseException] | None, + excValue: BaseException | None, + traceback: TracebackType | None, + ) -> bool | None: """ Exit a context using this PIDFile. @@ -187,7 +187,7 @@ class PIDFile: else: return True - def __enter__(self) -> "PIDFile": + def __enter__(self) -> PIDFile: try: if self.isRunning(): raise AlreadyRunningError() @@ -198,9 +198,9 @@ class PIDFile: def __exit__( self, - excType: Optional[Type[BaseException]], - excValue: Optional[BaseException], - traceback: Optional[TracebackType], + excType: type[BaseException] | None, + excValue: BaseException | None, + traceback: TracebackType | None, ) -> None: self.remove() return None @@ -242,14 +242,14 @@ class NonePIDFile: def isRunning(self) -> bool: return False - def __enter__(self) -> "NonePIDFile": + def __enter__(self) -> NonePIDFile: return self def __exit__( self, - excType: Optional[Type[BaseException]], - excValue: Optional[BaseException], - traceback: Optional[TracebackType], + excType: type[BaseException] | None, + excValue: BaseException | None, + traceback: TracebackType | None, ) -> None: return None diff --git a/contrib/python/Twisted/py3/twisted/application/runner/_runner.py b/contrib/python/Twisted/py3/twisted/application/runner/_runner.py index 01bbaeba1bd..79913912175 100644 --- a/contrib/python/Twisted/py3/twisted/application/runner/_runner.py +++ b/contrib/python/Twisted/py3/twisted/application/runner/_runner.py @@ -6,10 +6,11 @@ Twisted application runner. """ +from collections.abc import Mapping from os import kill from signal import SIGTERM from sys import stderr -from typing import Any, Callable, Mapping, TextIO +from typing import Any, Callable, TextIO from attr import Factory, attrib, attrs from constantly import NamedConstant diff --git a/contrib/python/Twisted/py3/twisted/application/strports.py b/contrib/python/Twisted/py3/twisted/application/strports.py index 3c96ee335d4..088e8d4311a 100644 --- a/contrib/python/Twisted/py3/twisted/application/strports.py +++ b/contrib/python/Twisted/py3/twisted/application/strports.py @@ -8,7 +8,9 @@ Construct listening port services from a simple string description. @see: L{twisted.internet.endpoints.serverFromString} @see: L{twisted.internet.endpoints.clientFromString} """ -from typing import Optional, cast +from __future__ import annotations + +from typing import cast from twisted.application.internet import StreamServerEndpointService from twisted.internet import endpoints, interfaces @@ -23,7 +25,7 @@ def _getReactor() -> interfaces.IReactorCore: def service( description: str, factory: interfaces.IProtocolFactory, - reactor: Optional[interfaces.IReactorCore] = None, + reactor: interfaces.IReactorCore | None = None, ) -> StreamServerEndpointService: """ Return the service corresponding to a description. diff --git a/contrib/python/Twisted/py3/twisted/application/twist/_options.py b/contrib/python/Twisted/py3/twisted/application/twist/_options.py index 2bcd207bf4c..2d30d39cd1e 100644 --- a/contrib/python/Twisted/py3/twisted/application/twist/_options.py +++ b/contrib/python/Twisted/py3/twisted/application/twist/_options.py @@ -6,10 +6,12 @@ Command line options for C{twist}. """ -import typing +from __future__ import annotations + +from collections.abc import Iterable, Mapping, Sequence from sys import stderr, stdout from textwrap import dedent -from typing import Callable, Iterable, Mapping, Optional, Sequence, Tuple, cast +from typing import Callable, NoReturn, cast from twisted.copyright import version from twisted.internet.interfaces import IReactorCore @@ -28,7 +30,7 @@ from ..service import IServiceMaker openFile = open -def _update_doc(opt: Callable[["TwistOptions", str], None], **kwargs: str) -> None: +def _update_doc(opt: Callable[[TwistOptions, str], None], **kwargs: str) -> None: """ Update the docstring of a method that implements an option. The string is dedented and the given keyword arguments are substituted. @@ -58,7 +60,7 @@ class TwistOptions(Options): def getSynopsis(self) -> str: return f"{Options.getSynopsis(self)} plugin [plugin_options]" - def opt_version(self) -> "typing.NoReturn": + def opt_version(self) -> NoReturn: """ Print version and exit. """ @@ -166,7 +168,7 @@ class TwistOptions(Options): self["fileLogObserverFactory"] = jsonFileLogObserver self["logFormat"] = "json" - def parseOptions(self, options: Optional[Sequence[str]] = None) -> None: + def parseOptions(self, options: Sequence[str] | None = None) -> None: self.selectDefaultLogObserver() Options.parseOptions(self, options=options) @@ -187,7 +189,7 @@ class TwistOptions(Options): @property def subCommands( self, - ) -> Iterable[Tuple[str, None, Callable[[IServiceMaker], Options], str]]: + ) -> Iterable[tuple[str, None, Callable[[IServiceMaker], Options], str]]: plugins = self.plugins for name in sorted(plugins): plugin = plugins[name] diff --git a/contrib/python/Twisted/py3/twisted/application/twist/_twist.py b/contrib/python/Twisted/py3/twisted/application/twist/_twist.py index 80cf4470f11..f6f19737dfa 100644 --- a/contrib/python/Twisted/py3/twisted/application/twist/_twist.py +++ b/contrib/python/Twisted/py3/twisted/application/twist/_twist.py @@ -7,7 +7,7 @@ Run a Twisted application. """ import sys -from typing import Sequence +from collections.abc import Sequence from twisted.application.app import _exitWithSignal from twisted.internet.interfaces import IReactorCore, _ISupportsExitSignalCapturing diff --git a/contrib/python/Twisted/py3/twisted/conch/checkers.py b/contrib/python/Twisted/py3/twisted/conch/checkers.py index 3ade2d8eeb8..ed596c52a5f 100644 --- a/contrib/python/Twisted/py3/twisted/conch/checkers.py +++ b/contrib/python/Twisted/py3/twisted/conch/checkers.py @@ -6,17 +6,18 @@ Provide L{ICredentialsChecker} implementations to be used in Conch protocols. """ +from __future__ import annotations import binascii import errno import sys from base64 import decodebytes -from typing import IO, Any, Callable, Iterable, Iterator, Mapping, Optional, Tuple, cast +from collections.abc import Iterable, Iterator, Mapping +from typing import IO, Any, Callable, Literal, Protocol, cast from zope.interface import Interface, implementer, providedBy from incremental import Version -from typing_extensions import Literal, Protocol from twisted.conch import error from twisted.conch.ssh import keys @@ -34,12 +35,12 @@ from twisted.python.util import runAsEffectiveUser _log = Logger() -class UserRecord(Tuple[str, str, int, int, str, str, str]): +class UserRecord(tuple[str, str, int, int, str, str, str]): """ - A record in a UNIX-style password database. See L{pwd} for field details. + A record in a UNIX-style password database. See L{pwd} for field details. - This corresponds to the undocumented type L{pwd.struct_passwd}, but lacks named - field accessors. + This corresponds to the undocumented type C{pwd.struct_passwd}, but lacks + named field accessors. """ @property @@ -62,7 +63,7 @@ class UserDB(Protocol): """ -pwd: Optional[UserDB] +pwd: UserDB | None try: import pwd as _pwd except ImportError: @@ -83,7 +84,7 @@ class CryptedPasswordRecord(Protocol): """ A sequence where the item at index 1 may be a crypted password. - Both L{pwd.struct_passwd} and L{spwd.struct_spwd} conform to this protocol. + Both C{pwd.struct_passwd} and C{spwd.struct_spwd} conform to this protocol. """ def __getitem__(self, index: Literal[1]) -> str: @@ -106,7 +107,7 @@ def _lookupUser(userdb: UserDB, username: bytes) -> UserRecord: return userdb.getpwnam(username.decode(sys.getfilesystemencoding())) -def _pwdGetByName(username: str) -> Optional[CryptedPasswordRecord]: +def _pwdGetByName(username: str) -> CryptedPasswordRecord | None: """ Look up a user in the /etc/passwd database using the pwd module. If the pwd module is not available, return None. @@ -114,7 +115,7 @@ def _pwdGetByName(username: str) -> Optional[CryptedPasswordRecord]: @param username: the username of the user to return the passwd database information for. - @returns: A L{pwd.struct_passwd}, where field 1 may contain a crypted + @returns: A C{pwd.struct_passwd}, where field 1 may contain a crypted password, or L{None} when the L{pwd} database is unavailable. @raises KeyError: when no such user exists @@ -124,16 +125,16 @@ def _pwdGetByName(username: str) -> Optional[CryptedPasswordRecord]: return cast(CryptedPasswordRecord, pwd.getpwnam(username)) -def _shadowGetByName(username: str) -> Optional[CryptedPasswordRecord]: +def _shadowGetByName(username: str) -> CryptedPasswordRecord | None: """ - Look up a user in the /etc/shadow database using the spwd module. If it is + Look up a user in the /etc/shadow database using the spwd module. If it is not available, return L{None}. @param username: the username of the user to return the shadow database information for. @type username: L{str} - @returns: A L{spwd.struct_spwd}, where field 1 may contain a crypted + @returns: A C{spwd.struct_spwd}, where field 1 may contain a crypted password, or L{None} when the L{spwd} database is unavailable. @raises KeyError: when no such user exists @@ -506,7 +507,7 @@ class UNIXAuthorizedKeysFiles: def __init__( self, - userdb: Optional[UserDB] = None, + userdb: UserDB | None = None, parseKey: Callable[[bytes], keys.Key] = keys.Key.fromString, ): """ diff --git a/contrib/python/Twisted/py3/twisted/conch/client/direct.py b/contrib/python/Twisted/py3/twisted/conch/client/direct.py index 33fd1d2df46..b38fb43e4e2 100644 --- a/contrib/python/Twisted/py3/twisted/conch/client/direct.py +++ b/contrib/python/Twisted/py3/twisted/conch/client/direct.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: from twisted.conch.ssh.userauth import SSHUserAuthClient -class SSHClientFactory(protocol.ClientFactory): +class SSHClientFactory(protocol.ClientFactory["transport.SSHClientTransport"]): def __init__( self, d: Deferred[None], @@ -46,7 +46,7 @@ class SSHClientFactory(protocol.ClientFactory): d, self.d = self.d, None d.errback(reason) - def buildProtocol(self, addr: IAddress) -> SSHClientTransport: + def buildProtocol(self, addr: IAddress | None) -> SSHClientTransport: trans = SSHClientTransport(self) if self.options["ciphers"]: trans.supportedCiphers = self.options["ciphers"] @@ -61,7 +61,7 @@ class SSHClientFactory(protocol.ClientFactory): class SSHClientTransport(transport.SSHClientTransport): # pre-mypy LSP violation - factory: SSHClientFactory # type:ignore[assignment] + factory: SSHClientFactory def __init__(self, factory: SSHClientFactory) -> None: self.factory = factory @@ -140,6 +140,12 @@ def connect( userAuthObject: SSHUserAuthClient, ) -> Deferred[None]: d: Deferred[None] = defer.Deferred() - factory = SSHClientFactory(d, options, verifyHostKey, userAuthObject) + factory: protocol.ClientFactory[transport.SSHClientTransport] = SSHClientFactory( + d, options, verifyHostKey, userAuthObject + ) + # this is just broken, right? very straightforwardly this is inference + # giving up because there's a cycle in the type graph + # (_ProtoWithFactory.factory -> Factory[Self] -> Factory.protocol: + # "Optional[Callable[..., P]]" -> P < _ProtoWithFactory) IReactorTCP(reactor).connectTCP(host, port, factory) return d diff --git a/contrib/python/Twisted/py3/twisted/conch/client/knownhosts.py b/contrib/python/Twisted/py3/twisted/conch/client/knownhosts.py index 1aa4b477c27..87944c2862b 100644 --- a/contrib/python/Twisted/py3/twisted/conch/client/knownhosts.py +++ b/contrib/python/Twisted/py3/twisted/conch/client/knownhosts.py @@ -13,9 +13,10 @@ from __future__ import annotations import hmac import sys from binascii import Error as DecodeError, a2b_base64, b2a_base64 +from collections.abc import Iterable from contextlib import closing from hashlib import sha1 -from typing import IO, Callable, Iterable, Literal +from typing import IO, Callable, Literal from zope.interface import implementer diff --git a/contrib/python/Twisted/py3/twisted/conch/client/options.py b/contrib/python/Twisted/py3/twisted/conch/client/options.py index 2ab2455099a..72e8f38ec2e 100644 --- a/contrib/python/Twisted/py3/twisted/conch/client/options.py +++ b/contrib/python/Twisted/py3/twisted/conch/client/options.py @@ -1,8 +1,9 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +from __future__ import annotations + import sys -from typing import List, Optional, Union # from twisted.conch.ssh.transport import SSHCiphers, SSHClientTransport @@ -10,7 +11,7 @@ from twisted.python import usage class ConchOptions(usage.Options): - optParameters: List[List[Optional[Union[str, int]]]] = [ + optParameters: list[list[str | int | None]] = [ ["user", "l", None, "Log in using this user name."], ["identity", "i", None], ["ciphers", "c", None], diff --git a/contrib/python/Twisted/py3/twisted/conch/manhole.py b/contrib/python/Twisted/py3/twisted/conch/manhole.py index 670ac0480ec..ab758b5f565 100644 --- a/contrib/python/Twisted/py3/twisted/conch/manhole.py +++ b/contrib/python/Twisted/py3/twisted/conch/manhole.py @@ -19,7 +19,6 @@ import tokenize from io import BytesIO from traceback import format_exception from types import TracebackType -from typing import Type from twisted.conch import recvline from twisted.internet import defer @@ -117,7 +116,7 @@ class ManholeInterpreter(code.InteractiveInterpreter): def excepthook( self, - excType: Type[BaseException], + excType: type[BaseException], excValue: BaseException, excTraceback: TracebackType, ) -> None: diff --git a/contrib/python/Twisted/py3/twisted/conch/manhole_ssh.py b/contrib/python/Twisted/py3/twisted/conch/manhole_ssh.py index 8ac3b6d4be1..977e947d227 100644 --- a/contrib/python/Twisted/py3/twisted/conch/manhole_ssh.py +++ b/contrib/python/Twisted/py3/twisted/conch/manhole_ssh.py @@ -8,8 +8,6 @@ insults/SSH integration support. @author: Jp Calderone """ -from typing import Dict - from zope.interface import implementer from twisted.conch import avatar, error as econch, interfaces as iconch @@ -141,8 +139,8 @@ class TerminalRealm: class ConchFactory(factory.SSHFactory): - publicKeys: Dict[bytes, bytes] = {} - privateKeys: Dict[bytes, bytes] = {} + publicKeys: dict[bytes, bytes] = {} + privateKeys: dict[bytes, bytes] = {} def __init__(self, portal): self.portal = portal diff --git a/contrib/python/Twisted/py3/twisted/conch/openssh_compat/factory.py b/contrib/python/Twisted/py3/twisted/conch/openssh_compat/factory.py index 20051fc89f7..f92b2cbe156 100644 --- a/contrib/python/Twisted/py3/twisted/conch/openssh_compat/factory.py +++ b/contrib/python/Twisted/py3/twisted/conch/openssh_compat/factory.py @@ -7,9 +7,10 @@ Factory for reading openssh configuration files: public keys, private keys, and moduli file. """ +from __future__ import annotations + import errno import os -from typing import Dict, List, Optional, Tuple from twisted.conch.openssh_compat import primes from twisted.conch.ssh import common, factory, keys @@ -67,7 +68,7 @@ class OpenSSHFactory(factory.SSHFactory): privateKeys[key.sshType()] = key return privateKeys - def getPrimes(self) -> Optional[Dict[int, List[Tuple[int, int]]]]: + def getPrimes(self) -> dict[int, list[tuple[int, int]]] | None: try: return primes.parseModuliFile(self.moduliRoot + "/moduli") except OSError: diff --git a/contrib/python/Twisted/py3/twisted/conch/openssh_compat/primes.py b/contrib/python/Twisted/py3/twisted/conch/openssh_compat/primes.py index 9e2070e19aa..3c3bbc139d5 100644 --- a/contrib/python/Twisted/py3/twisted/conch/openssh_compat/primes.py +++ b/contrib/python/Twisted/py3/twisted/conch/openssh_compat/primes.py @@ -10,13 +10,10 @@ Maintainer: Paul Swartz """ -from typing import Dict, List, Tuple - - -def parseModuliFile(filename: str) -> Dict[int, List[Tuple[int, int]]]: +def parseModuliFile(filename: str) -> dict[int, list[tuple[int, int]]]: with open(filename) as f: lines = f.readlines() - primes: Dict[int, List[Tuple[int, int]]] = {} + primes: dict[int, list[tuple[int, int]]] = {} for l in lines: l = l.strip() if not l or l[0] == "#": diff --git a/contrib/python/Twisted/py3/twisted/conch/recvline.py b/contrib/python/Twisted/py3/twisted/conch/recvline.py index aa8115bdc7a..6a495ff5392 100644 --- a/contrib/python/Twisted/py3/twisted/conch/recvline.py +++ b/contrib/python/Twisted/py3/twisted/conch/recvline.py @@ -9,7 +9,6 @@ Basic line editing support. """ import string -from typing import Dict from zope.interface import implementer @@ -18,7 +17,7 @@ from twisted.logger import Logger from twisted.python import reflect from twisted.python.compat import iterbytes -_counters: Dict[str, int] = {} +_counters: dict[str, int] = {} class Logging: diff --git a/contrib/python/Twisted/py3/twisted/conch/scripts/cftp.py b/contrib/python/Twisted/py3/twisted/conch/scripts/cftp.py index e8241fdc3ad..8f81aa9542c 100644 --- a/contrib/python/Twisted/py3/twisted/conch/scripts/cftp.py +++ b/contrib/python/Twisted/py3/twisted/conch/scripts/cftp.py @@ -5,6 +5,9 @@ """ Implementation module for the I{cftp} command. """ + +from __future__ import annotations + import fcntl import fnmatch import getpass @@ -15,7 +18,7 @@ import stat import struct import sys import tty -from typing import List, Optional, TextIO, Union +from typing import TextIO from twisted.conch.client import connect, default, options from twisted.conch.ssh import channel, common, connection, filetransfer @@ -35,7 +38,7 @@ class ClientOptions(options.ConchOptions): "executing commands to send and receive file information" ) - optParameters: List[List[Optional[Union[str, int]]]] = [ + optParameters: list[list[str | int | None]] = [ ["buffersize", "B", 32768, "Size of the buffer to use for sending/receiving."], ["batchfile", "b", None, "File to read commands from, or '-' for stdin."], ["requests", "R", 5, "Number of requests to make before waiting for a reply."], diff --git a/contrib/python/Twisted/py3/twisted/conch/scripts/ckeygen.py b/contrib/python/Twisted/py3/twisted/conch/scripts/ckeygen.py index 728dc6430c7..bac2ba41ffd 100644 --- a/contrib/python/Twisted/py3/twisted/conch/scripts/ckeygen.py +++ b/contrib/python/Twisted/py3/twisted/conch/scripts/ckeygen.py @@ -15,7 +15,7 @@ import sys from collections.abc import Callable from functools import wraps from importlib import reload -from typing import Any, Dict, Optional +from typing import Any from twisted.conch.ssh import keys from twisted.python import failure, filepath, log, usage @@ -206,8 +206,8 @@ def _defaultPrivateKeySubtype(keyType): def _getKeyOrDefault( - options: Dict[Any, Any], - inputCollector: Optional[Callable[[str], str]] = None, + options: dict[Any, Any], + inputCollector: Callable[[str], str] | None = None, keyTypeName: str = "rsa", ) -> str: """ @@ -231,7 +231,7 @@ def _getKeyOrDefault( return str(filename) -def printFingerprint(options: Dict[Any, Any]) -> None: +def printFingerprint(options: dict[Any, Any]) -> None: filename = _getKeyOrDefault(options) if os.path.exists(filename + ".pub"): filename += ".pub" @@ -328,8 +328,8 @@ def _inputSaveFile(prompt: str) -> str: def _saveKey( key: keys.Key, - options: Dict[Any, Any], - inputCollector: Optional[Callable[[str], str]] = None, + options: dict[Any, Any], + inputCollector: Callable[[str], str] | None = None, ) -> None: """ Persist a SSH key on local filesystem. diff --git a/contrib/python/Twisted/py3/twisted/conch/scripts/conch.py b/contrib/python/Twisted/py3/twisted/conch/scripts/conch.py index 74e3a9f9fe0..a0caec95640 100644 --- a/contrib/python/Twisted/py3/twisted/conch/scripts/conch.py +++ b/contrib/python/Twisted/py3/twisted/conch/scripts/conch.py @@ -16,7 +16,7 @@ import signal import struct import sys import tty -from typing import Any, List, Tuple +from typing import Any from twisted.conch.client import connect, default from twisted.conch.client.options import ConchOptions @@ -73,8 +73,8 @@ class ClientOptions(ConchOptions): ], ) - localForwards: List[Tuple[int, Tuple[int, int]]] = [] - remoteForwards: List[Tuple[int, Tuple[int, int]]] = [] + localForwards: list[tuple[int, tuple[int, int]]] = [] + remoteForwards: list[tuple[int, tuple[int, int]]] = [] def opt_escape(self, esc): """ diff --git a/contrib/python/Twisted/py3/twisted/conch/scripts/tkconch.py b/contrib/python/Twisted/py3/twisted/conch/scripts/tkconch.py index e6738403daa..ffd47c5cc44 100644 --- a/contrib/python/Twisted/py3/twisted/conch/scripts/tkconch.py +++ b/contrib/python/Twisted/py3/twisted/conch/scripts/tkconch.py @@ -16,7 +16,6 @@ import sys import tkinter as Tkinter import tkinter.filedialog as tkFileDialog import tkinter.messagebox as tkMessageBox -from typing import List, Tuple from twisted.conch import error from twisted.conch.client.default import isInKnownHosts @@ -278,9 +277,9 @@ class GeneralOptions(usage.Options): ], ) - identitys: List[str] = [] - localForwards: List[Tuple[int, Tuple[int, int]]] = [] - remoteForwards: List[Tuple[int, Tuple[int, int]]] = [] + identitys: list[str] = [] + localForwards: list[tuple[int, tuple[int, int]]] = [] + remoteForwards: list[tuple[int, tuple[int, int]]] = [] def opt_identity(self, i): self.identitys.append(i) @@ -506,7 +505,7 @@ class SSHClientTransport(transport.SSHClientTransport): class SSHUserAuthClient(userauth.SSHUserAuthClient): - usedFiles: List[str] = [] + usedFiles: list[str] = [] def getPassword(self, prompt=None): if not prompt: diff --git a/contrib/python/Twisted/py3/twisted/conch/ssh/common.py b/contrib/python/Twisted/py3/twisted/conch/ssh/common.py index 8d01ab14e50..7df80c7083f 100644 --- a/contrib/python/Twisted/py3/twisted/conch/ssh/common.py +++ b/contrib/python/Twisted/py3/twisted/conch/ssh/common.py @@ -8,7 +8,8 @@ Common functions for the SSH classes. from __future__ import annotations import struct -from typing import Sequence, overload +from collections.abc import Sequence +from typing import overload from cryptography.utils import int_to_bytes diff --git a/contrib/python/Twisted/py3/twisted/conch/ssh/factory.py b/contrib/python/Twisted/py3/twisted/conch/ssh/factory.py index da1c3a8f9ea..89b62d4d169 100644 --- a/contrib/python/Twisted/py3/twisted/conch/ssh/factory.py +++ b/contrib/python/Twisted/py3/twisted/conch/ssh/factory.py @@ -8,27 +8,34 @@ See also L{twisted.conch.openssh_compat.factory} for OpenSSH compatibility. Maintainer: Paul Swartz """ - +from __future__ import annotations import random from itertools import chain -from typing import Dict, List, Optional, Tuple +from typing import Any from twisted.conch import error from twisted.conch.ssh import _kex, connection, transport, userauth from twisted.internet import protocol +from twisted.internet.interfaces import IAddress from twisted.logger import Logger +# Note: type hint here is slightly wonky. Possibly a mypy bug? We ought to be +# able to say SSHServerTransport here, but it doesn't conform to +# _ProtoWithFactory. When we try to investigaate why, its factory attribute +# doesn't supply Factory[SSHServerTransport]. But then when we try to figure +# out its factory attribute, we're back here again... + -class SSHFactory(protocol.Factory): +class SSHFactory(protocol.Factory[Any]): """ A Factory for SSH servers. """ - primes: Optional[Dict[int, List[Tuple[int, int]]]] + primes: dict[int, list[tuple[int, int]]] | None _log = Logger() - protocol = transport.SSHServerTransport + protocol: Any = transport.SSHServerTransport services = { b"ssh-userauth": userauth.SSHUserAuthServer, @@ -48,7 +55,7 @@ class SSHFactory(protocol.Factory): if not hasattr(self, "primes"): self.primes = self.getPrimes() - def buildProtocol(self, addr): + def buildProtocol(self, addr: IAddress | None) -> transport.SSHServerTransport: """ Create an instance of the server side of the SSH protocol. @@ -58,7 +65,8 @@ class SSHFactory(protocol.Factory): @rtype: L{twisted.conch.ssh.transport.SSHServerTransport} @return: The built transport. """ - t = protocol.Factory.buildProtocol(self, addr) + t: transport.SSHServerTransport | None = super().buildProtocol(addr) + assert t is not None t.supportedPublicKeys = list( chain.from_iterable( key.supportedSignatureAlgorithms() for key in self.privateKeys.values() @@ -96,14 +104,14 @@ class SSHFactory(protocol.Factory): """ raise NotImplementedError("getPrivateKeys unimplemented") - def getPrimes(self) -> Optional[Dict[int, List[Tuple[int, int]]]]: + def getPrimes(self) -> dict[int, list[tuple[int, int]]] | None: """ Called when the factory is started to get Diffie-Hellman generators and primes to use. Returns a dictionary mapping number of bits to lists of tuple of (generator, prime). """ - def getDHPrime(self, bits: int) -> Tuple[int, int]: + def getDHPrime(self, bits: int) -> tuple[int, int]: """ Return a tuple of (g, p) for a Diffe-Hellman process, with p being as close to C{bits} bits as possible. diff --git a/contrib/python/Twisted/py3/twisted/conch/ssh/filetransfer.py b/contrib/python/Twisted/py3/twisted/conch/ssh/filetransfer.py index 830ff711ddd..9b0302d9d9e 100644 --- a/contrib/python/Twisted/py3/twisted/conch/ssh/filetransfer.py +++ b/contrib/python/Twisted/py3/twisted/conch/ssh/filetransfer.py @@ -8,7 +8,6 @@ import errno import os import struct import warnings -from typing import Dict from zope.interface import implementer @@ -25,7 +24,7 @@ class FileTransferBase(protocol.Protocol): versions = (3,) - packetTypes: Dict[int, str] = {} + packetTypes: dict[int, str] = {} def __init__(self): self.buf = b"" diff --git a/contrib/python/Twisted/py3/twisted/conch/ssh/keys.py b/contrib/python/Twisted/py3/twisted/conch/ssh/keys.py index e52608df9aa..3d8c93cb0b4 100644 --- a/contrib/python/Twisted/py3/twisted/conch/ssh/keys.py +++ b/contrib/python/Twisted/py3/twisted/conch/ssh/keys.py @@ -13,7 +13,7 @@ import unicodedata import warnings from base64 import b64encode, decodebytes, encodebytes from hashlib import md5, sha256 -from typing import Any +from typing import Any, Literal import bcrypt from constantly import NamedConstant, Names @@ -27,7 +27,6 @@ from cryptography.hazmat.primitives.serialization import ( load_pem_private_key, load_ssh_public_key, ) -from typing_extensions import Literal from twisted.conch.ssh import common, sexpy from twisted.conch.ssh.common import int_to_bytes @@ -235,16 +234,32 @@ class Key: integer y The format of ECDSA-SHA2-* public key blob is:: + string 'ecdsa-sha2-[identifier]' integer x integer y - identifier is the standard NIST curve name. + Where 'identifier' is the standard NIST curve name. The format of an Ed25519 public key blob is:: string 'ssh-ed25519' string a + The format of a [email protected] public key is:: + + string "[email protected]" + string curve name + ec_point Q + string application (user-specified, but typically "ssh:") + + The format of a [email protected] public key is:: + + string "[email protected]" + string public key + string application (user-specified, but typically "ssh:") + + The security key formats are specified at https://github.com/openssh/openssh-portable/blob/80993390bed15bbd1c348f3352e55d0db01ca0fd/PROTOCOL.u2f. + @type blob: L{bytes} @param blob: The key data. @@ -273,18 +288,27 @@ class Key: ) if keyType == b"[email protected]": + _, encodedPoint, application, rest = common.getNS(rest, 3) keyObject = cls._fromECEncodedPoint( - encodedPoint=common.getNS(rest, 2)[1], + encodedPoint=encodedPoint, curve=b"ecdsa-sha2-nistp256", ) keyObject._sk = True + keyObject.application = application + return keyObject + + if keyType == b"[email protected]": + a, application, rest = common.getNS(rest, 2) + keyObject = cls._fromEd25519Components(a) + keyObject._sk = True + keyObject.application = application + return keyObject - if keyType in [b"ssh-ed25519", b"[email protected]"]: + if keyType in [b"ssh-ed25519"]: a, rest = common.getNS(rest) keyObject = cls._fromEd25519Components(a) - if keyType.startswith(b"sk-ssh-"): - keyObject._sk = True + return keyObject raise BadKeyError(f"unknown blob type: {keyType}") @@ -904,7 +928,9 @@ class Key: @type keyObject: C{cryptography.hazmat.primitives.asymmetric} key. """ self._keyObject = keyObject + # Only used for OpenSSH sk ssh keys self._sk = False + self._application = None def __eq__(self, other: object) -> bool: """ @@ -1219,17 +1245,19 @@ class Key: def blob(self): """ - Return the public key blob for this key. The blob is the - over-the-wire format for public keys. + Return the public key blob for this key. The blob is the over-the-wire + format for public keys. SECSH-TRANS RFC 4253 Section 6.6. RSA keys:: + string 'ssh-rsa' integer e integer n DSA keys:: + string 'ssh-dss' integer p integer q @@ -1237,16 +1265,34 @@ class Key: integer y EC keys:: + string 'ecdsa-sha2-[identifier]' integer x integer y - identifier is the standard NIST curve name + Where 'identifier' is the standard NIST curve name. Ed25519 keys:: + string 'ssh-ed25519' string a + [email protected] keys:: + + string "[email protected]" + string curve name + ec_point Q + string application (user-specified, but typically "ssh:") + + [email protected] keys:: + + string "[email protected]" + string public key + string application (user-specified, but typically "ssh:") + + The security key formats are specified at + U{https://github.com/openssh/openssh-portable/blob/80993390bed15bbd1c348f3352e55d0db01ca0fd/PROTOCOL.u2f}. + @rtype: L{bytes} """ type = self.type() @@ -1263,17 +1309,31 @@ class Key: ) elif type == "EC": byteLength = (self._keyObject.curve.key_size + 7) // 8 - return ( - common.NS(data["curve"]) - + common.NS(data["curve"][-8:]) + curve = data["curve"][-8:] + if self._sk: + # We convert the curve name. + # The curve name is only `nistpNNN` part. + # Format example: + # "ecdsa-sha2-nistp256" -> "nistp256" + # "[email protected]" -> "nistp256" + curve = data["curve"][-20:-12] + blob = ( + common.NS(self.sshType()) + + common.NS(curve) + common.NS( b"\x04" + utils.int_to_bytes(data["x"], byteLength) + utils.int_to_bytes(data["y"], byteLength) ) ) + if self._sk: + blob += common.NS(self.application) + return blob elif type == "Ed25519": - return common.NS(b"ssh-ed25519") + common.NS(data["a"]) + blob = common.NS(self.sshType()) + common.NS(data["a"]) + if self._sk: + blob += common.NS(self.application) + return blob else: raise BadKeyError(f"unknown key type: {type}") @@ -1362,8 +1422,8 @@ class Key: @_mutuallyExclusiveArguments( [ - ["extra", "comment"], - ["extra", "passphrase"], + ("extra", "comment"), + ("extra", "passphrase"), ] ) def toString(self, type, extra=None, subtype=None, comment=None, passphrase=None): @@ -1810,6 +1870,30 @@ class Key: else: return True + def isSecurityKey(self): + """ + Return True if key is an OpenSSH security key. + """ + return self.sshType() in [ + b"[email protected]", + b"[email protected]", + ] + + @property + def application(self): + """ + Returns the application value for OpenSSH sk SSH keys + and None for other key types. + """ + return self._application + + @application.setter + def application(self, value): + """ + Modifies the application value for OpenSSH sk SSH key types. + """ + self._application = value + def _getPersistentRSAKey(location, keySize=4096): """ diff --git a/contrib/python/Twisted/py3/twisted/conch/ssh/service.py b/contrib/python/Twisted/py3/twisted/conch/ssh/service.py index acfd40ee6a7..caa806c995c 100644 --- a/contrib/python/Twisted/py3/twisted/conch/ssh/service.py +++ b/contrib/python/Twisted/py3/twisted/conch/ssh/service.py @@ -9,7 +9,7 @@ Maintainer: Paul Swartz """ from __future__ import annotations -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING from twisted.logger import Logger @@ -21,7 +21,7 @@ class SSHService: # this is the ssh name for the service: name: bytes = None # type:ignore[assignment] - protocolMessages: Dict[int, str] = {} # map #'s -> protocol names + protocolMessages: dict[int, str] = {} # map #'s -> protocol names transport: SSHTransportBase | None = None # gets set later _log = Logger() diff --git a/contrib/python/Twisted/py3/twisted/conch/ssh/transport.py b/contrib/python/Twisted/py3/twisted/conch/ssh/transport.py index 323236f4720..2f3f31c2009 100644 --- a/contrib/python/Twisted/py3/twisted/conch/ssh/transport.py +++ b/contrib/python/Twisted/py3/twisted/conch/ssh/transport.py @@ -17,14 +17,19 @@ import struct import types import zlib from hashlib import md5, sha1, sha256, sha384, sha512 -from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Literal, Union from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import dh, ec, x25519 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from typing_extensions import Literal + +try: + from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES +except ImportError: + # Deprecated path, will be removed in cryptography 48.0.0 + from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES from twisted import __version__ as twisted_version from twisted.conch.ssh import _kex, address, keys @@ -62,7 +67,7 @@ _Hash = Any _DigestMod = Union[str, Callable[[], _Hash], types.ModuleType] -class _MACParams(Tuple[_DigestMod, bytes, bytes, int]): +class _MACParams(tuple[_DigestMod, bytes, bytes, int]): """ L{_MACParams} represents the parameters necessary to compute SSH MAC (Message Authenticate Codes). @@ -107,14 +112,14 @@ class SSHCiphers: """ cipherMap = { - b"3des-cbc": (algorithms.TripleDES, 24, modes.CBC), + b"3des-cbc": (TripleDES, 24, modes.CBC), b"aes256-cbc": (algorithms.AES, 32, modes.CBC), b"aes192-cbc": (algorithms.AES, 24, modes.CBC), b"aes128-cbc": (algorithms.AES, 16, modes.CBC), b"aes128-ctr": (algorithms.AES, 16, modes.CTR), b"aes192-ctr": (algorithms.AES, 24, modes.CTR), b"aes256-ctr": (algorithms.AES, 32, modes.CTR), - b"3des-ctr": (algorithms.TripleDES, 24, modes.CTR), + b"3des-ctr": (TripleDES, 24, modes.CTR), b"none": (None, 0, modes.CBC), } macMap = { @@ -509,9 +514,7 @@ class SSHTransportBase(protocol.Protocol): _EXT_INFO_S = b"ext-info-s" _peerSupportsExtensions = False - peerExtensions: Dict[bytes, bytes] = {} - - factory: SSHFactory + peerExtensions: dict[bytes, bytes] = {} # Set by twisted.conch.ssh.userauth.SSHUserAuthServer._cbFinishedAuth avatar: object @@ -1443,6 +1446,7 @@ class SSHServerTransport(SSHTransportBase): @ivar p: the Diffie-Hellman group prime. """ + factory: SSHFactory isClient = False ignoreNextPacket = 0 @@ -1907,7 +1911,7 @@ class SSHClientTransport(SSHTransportBase): d.addErrback( lambda unused: self.sendDisconnect( DISCONNECT_HOST_KEY_NOT_VERIFIABLE, - f"bad host key [ecdh] {unused}".encode("utf-8"), + f"bad host key [ecdh] {unused}".encode(), ) ) return d diff --git a/contrib/python/Twisted/py3/twisted/conch/ssh/userauth.py b/contrib/python/Twisted/py3/twisted/conch/ssh/userauth.py index 0d24df00f92..369550fb13f 100644 --- a/contrib/python/Twisted/py3/twisted/conch/ssh/userauth.py +++ b/contrib/python/Twisted/py3/twisted/conch/ssh/userauth.py @@ -11,7 +11,8 @@ Maintainer: Paul Swartz from __future__ import annotations import struct -from typing import Callable, Tuple, Type +from hashlib import sha256 +from typing import Callable from twisted.conch import error, interfaces from twisted.conch.ssh import keys, service, transport @@ -26,8 +27,8 @@ from twisted.logger import Logger from twisted.python import failure from twisted.python.compat import nativeString -_ConchPortalTuple = Tuple[ - Type[interfaces.IConchUser], interfaces.IConchUser, Callable[[], None] +_ConchPortalTuple = tuple[ + type[interfaces.IConchUser], interfaces.IConchUser, Callable[[], None] ] @@ -277,7 +278,7 @@ class SSHUserAuthServer(service.SSHService): result: Deferred[_ConchPortalTuple] try: - keys.Key.fromString(blob) + pubKey = keys.Key.fromString(blob) except keys.BadKeyError: error = "Unsupported key type {} or bad key".format(algName.decode("ascii")) self._log.error(error) @@ -287,7 +288,7 @@ class SSHUserAuthServer(service.SSHService): if hasSig: assert self.transport is not None, "must have transport for auth" assert self.transport.sessionID is not None, "must have session for auth" - b = ( + messageToBeValidated = ( NS(self.transport.sessionID) + bytes((MSG_USERAUTH_REQUEST,)) + NS(self.user) @@ -297,7 +298,18 @@ class SSHUserAuthServer(service.SSHService): + NS(algName) + NS(blob) ) - c = credentials.SSHPrivateKey(self.user, algName, blob, b, signature) + if pubKey.isSecurityKey(): + try: + application = pubKey.application + messageToBeValidated = self._wrapMessageWithSKMetadata( + signature, messageToBeValidated, application + ) + except ValueError: + return defer.fail(UnauthorizedLogin("Invalid security key format")) + + c = credentials.SSHPrivateKey( + self.user, algName, blob, messageToBeValidated, signature + ) result = self.portal.login(c, None, interfaces.IConchUser) else: c = credentials.SSHPrivateKey(self.user, algName, blob, None, None) @@ -306,6 +318,43 @@ class SSHUserAuthServer(service.SSHService): ) return result + @staticmethod + def _wrapMessageWithSKMetadata(signature, messageToBeValidated, application): + """ + Wraps the SSH user auth message request with security key information. + This blob will be verified against the signature. See + U{https://github.com/openssh/openssh-portable/blob/a4aa090a3d40dddb07d5ebebc501f6457541a501/PROTOCOL.u2f#L176} + + In addition to the message to be signed, the U2F signature operation + requires the key handle and a few additional parameters. The signature + is signed over a blob that consists of:: + + byte[32] SHA256(application) + byte flags (including "user present", extensions present) + uint32 counter + byte[] extensions + byte[32] SHA256(message) + + The signature format used on the wire in SSH2_USERAUTH_REQUEST:: + + string "[email protected]" or "[email protected]" + string signature + byte flags + uint32 counter + """ + _, _, trailing = getNS(signature, 2) + if len(trailing) < 5: + raise ValueError("SK signature missing flags+counter") + flags = trailing[0:1] + counter = trailing[1:5] + + return ( + sha256(application).digest() + + flags + + counter + + sha256(messageToBeValidated).digest() + ) + def _ebCheckKey(self, reason: failure.Failure, packet: bytes) -> failure.Failure: """ Called back if the user did not sent a signature. If reason is diff --git a/contrib/python/Twisted/py3/twisted/conch/unix.py b/contrib/python/Twisted/py3/twisted/conch/unix.py index 0696aeb1ac6..e8e44f57a57 100644 --- a/contrib/python/Twisted/py3/twisted/conch/unix.py +++ b/contrib/python/Twisted/py3/twisted/conch/unix.py @@ -16,7 +16,7 @@ import socket import struct import time import tty -from typing import Callable, Dict, Tuple +from typing import Callable from zope.interface import implementer @@ -52,10 +52,10 @@ except ImportError: class UnixSSHRealm: def requestAvatar( self, - username: bytes | Tuple[()], + username: bytes | tuple[()], mind: object, *interfaces: portal._InterfaceItself, - ) -> Tuple[portal._InterfaceItself, UnixConchUser, Callable[[], None]]: + ) -> tuple[portal._InterfaceItself, UnixConchUser, Callable[[], None]]: if not isinstance(username, bytes): raise LoginDenied("UNIX SSH realm does not authorize anonymous sessions.") user = UnixConchUser(username.decode()) @@ -72,7 +72,7 @@ class UnixConchUser(ConchUser): if username in userlist: l.append(gid) self.otherGroups = l - self.listeners: Dict[ + self.listeners: dict[ str, IListeningPort ] = {} # Dict mapping (interface, port) -> listener self.channelLookup.update( diff --git a/contrib/python/Twisted/py3/twisted/copyright.py b/contrib/python/Twisted/py3/twisted/copyright.py index 8a21533ba74..a28262236b0 100644 --- a/contrib/python/Twisted/py3/twisted/copyright.py +++ b/contrib/python/Twisted/py3/twisted/copyright.py @@ -13,7 +13,7 @@ from twisted import __version__ as version, version as _longversion longversion = str(_longversion) copyright = """\ -Copyright (c) 2001-2025 Twisted Matrix Laboratories. +Copyright (c) 2001-2026 Twisted Matrix Laboratories. See LICENSE for details.""" disclaimer = """ diff --git a/contrib/python/Twisted/py3/twisted/cred/checkers.py b/contrib/python/Twisted/py3/twisted/cred/checkers.py index 24dd40ff173..21697af6acc 100644 --- a/contrib/python/Twisted/py3/twisted/cred/checkers.py +++ b/contrib/python/Twisted/py3/twisted/cred/checkers.py @@ -7,10 +7,10 @@ Basic credential checkers @var ANONYMOUS: An empty tuple used to represent the anonymous avatar ID. """ - +from __future__ import annotations import os -from typing import Any, Dict, Optional, Tuple, Union +from typing import Any from zope.interface import Attribute, Interface, implementer @@ -35,7 +35,7 @@ from twisted.python import failure # username. We do not want an instance of 'object', because that would # create potential problems with persistence. -ANONYMOUS: Tuple[()] = () +ANONYMOUS: tuple[()] = () class ICredentialsChecker(Interface): @@ -48,7 +48,7 @@ class ICredentialsChecker(Interface): "may check." ) - def requestAvatarId(credentials: Any) -> Deferred[Union[bytes, Tuple[()]]]: + def requestAvatarId(credentials: Any) -> Deferred[bytes | tuple[()]]: """ Validate credentials and produce an avatar ID. @@ -168,7 +168,7 @@ class FilePasswordDB: """ cache = False - _credCache: Optional[Dict[bytes, bytes]] = None + _credCache: dict[bytes, bytes] | None = None _cacheTimestamp: float = 0 _log = Logger() @@ -277,7 +277,7 @@ class FilePasswordDB: self._log.error("Unable to load credentials db: {e!r}", e=e) raise error.UnauthorizedLogin() - def getUser(self, username: bytes) -> Tuple[bytes, bytes]: + def getUser(self, username: bytes) -> tuple[bytes, bytes]: """ Look up the credentials for a username. @@ -310,7 +310,7 @@ class FilePasswordDB: def requestAvatarId( self, credentials: IUsernamePassword - ) -> Deferred[Union[bytes, Tuple[()]]]: + ) -> Deferred[bytes | tuple[()]]: try: u, p = self.getUser(credentials.username) except KeyError: diff --git a/contrib/python/Twisted/py3/twisted/cred/credentials.py b/contrib/python/Twisted/py3/twisted/cred/credentials.py index b2e9013c09b..012be122492 100644 --- a/contrib/python/Twisted/py3/twisted/cred/credentials.py +++ b/contrib/python/Twisted/py3/twisted/cred/credentials.py @@ -16,6 +16,7 @@ import re import time from binascii import hexlify from hashlib import md5 +from typing import TYPE_CHECKING from zope.interface import Attribute, Interface, implementer @@ -64,6 +65,18 @@ class IUsernameHashedPassword(ICredentials): appropriate for the particular credentials class. """ + if not TYPE_CHECKING: # pragma: no branch + + def __init__(self) -> None: # type:ignore + """ + IUsernameHashedPassword does not have any particular requirement + upon its constructor. + """ + # This is a workaround for pydoctor bug + # https://github.com/twisted/pydoctor/issues/940 + + del __init__ + username: bytes = Attribute( """ The username associated with these credentials. diff --git a/contrib/python/Twisted/py3/twisted/cred/portal.py b/contrib/python/Twisted/py3/twisted/cred/portal.py index 3e5abde918d..1f6d0b3e239 100644 --- a/contrib/python/Twisted/py3/twisted/cred/portal.py +++ b/contrib/python/Twisted/py3/twisted/cred/portal.py @@ -6,9 +6,10 @@ """ The point of integration of application and authentication. """ +from __future__ import annotations - -from typing import Callable, Dict, Iterable, List, Tuple, Type, Union +from collections.abc import Iterable +from typing import Callable from zope.interface import Interface, providedBy @@ -24,12 +25,12 @@ from twisted.python import failure, reflect # implementation of Interface itself (subclassing it actually instantiates it), # since mypy-zope treats Interface objects *as* types, this is how you have to # treat it. -_InterfaceItself = Type[Interface] +_InterfaceItself = type[Interface] # This is the result shape for both IRealm.requestAvatar and Portal.login, # although the former is optionally allowed to return synchronously and the # latter must be Deferred. -_requestResult = Tuple[_InterfaceItself, object, Callable[[], None]] +_requestResult = tuple[_InterfaceItself, object, Callable[[], None]] class IRealm(Interface): @@ -39,8 +40,8 @@ class IRealm(Interface): """ def requestAvatar( - avatarId: Union[bytes, Tuple[()]], mind: object, *interfaces: _InterfaceItself - ) -> Union[Deferred[_requestResult], _requestResult]: + avatarId: bytes | tuple[()], mind: object, *interfaces: _InterfaceItself + ) -> Deferred[_requestResult] | _requestResult: """ Return avatar which provides one of the given interfaces. @@ -75,7 +76,7 @@ class Portal: in the realm object and in the credentials checker objects. """ - checkers: Dict[Type[Interface], ICredentialsChecker] + checkers: dict[type[Interface], ICredentialsChecker] def __init__( self, realm: IRealm, checkers: Iterable[ICredentialsChecker] = () @@ -88,14 +89,14 @@ class Portal: for checker in checkers: self.registerChecker(checker) - def listCredentialsInterfaces(self) -> List[Type[Interface]]: + def listCredentialsInterfaces(self) -> list[type[Interface]]: """ Return list of credentials interfaces that can be used to login. """ return list(self.checkers.keys()) def registerChecker( - self, checker: ICredentialsChecker, *credentialInterfaces: Type[Interface] + self, checker: ICredentialsChecker, *credentialInterfaces: type[Interface] ) -> None: if not credentialInterfaces: credentialInterfaces = checker.credentialInterfaces @@ -103,7 +104,7 @@ class Portal: self.checkers[credentialInterface] = checker def login( - self, credentials: ICredentials, mind: object, *interfaces: Type[Interface] + self, credentials: ICredentials, mind: object, *interfaces: type[Interface] ) -> Deferred[_requestResult]: """ @param credentials: an implementor of diff --git a/contrib/python/Twisted/py3/twisted/cred/strcred.py b/contrib/python/Twisted/py3/twisted/cred/strcred.py index 574cd8e38e5..1330e47678e 100644 --- a/contrib/python/Twisted/py3/twisted/cred/strcred.py +++ b/contrib/python/Twisted/py3/twisted/cred/strcred.py @@ -13,10 +13,10 @@ Examples: - memory:admin:asdf:user:lkj - unix """ - +from __future__ import annotations import sys -from typing import Optional, Sequence, Type +from collections.abc import Sequence from zope.interface import Attribute, Interface @@ -142,7 +142,7 @@ class AuthOptionMixin: will send all help-related output. Default: L{sys.stdout} """ - supportedInterfaces: Optional[Sequence[Type[Interface]]] = None + supportedInterfaces: Sequence[type[Interface]] | None = None authOutput = sys.stdout def supportsInterface(self, interface): diff --git a/contrib/python/Twisted/py3/twisted/internet/__init__.py b/contrib/python/Twisted/py3/twisted/internet/__init__.py index a3d851d1983..71a01f2afde 100644 --- a/contrib/python/Twisted/py3/twisted/internet/__init__.py +++ b/contrib/python/Twisted/py3/twisted/internet/__init__.py @@ -10,3 +10,30 @@ observers need not care about which event loop is running. Thus, it is possible to use the same code for different loops, from Twisted's basic, yet portable, select-based loop to the loops of various GUI toolkits like GTK+ or Tk. """ + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .interfaces import ( + IReactorCore, + IReactorFDSet, + IReactorProcess, + IReactorTCP, + IReactorTime, + ) + + class _IReactorCommon( + IReactorCore, + IReactorTime, + IReactorFDSet, + IReactorTCP, + IReactorProcess, + ): + """ + A make-believe interface supporting all the commonly-implemented parts + of the reactor. For other interfaces which may or may not be supplied + by the environment, i.e. IReactorSSL, adapt the reactor like + C{IReactorSSL(reactor)}. + """ + + reactor: _IReactorCommon diff --git a/contrib/python/Twisted/py3/twisted/internet/_glibbase.py b/contrib/python/Twisted/py3/twisted/internet/_glibbase.py index 587ec1bd05c..650fdc90dba 100644 --- a/contrib/python/Twisted/py3/twisted/internet/_glibbase.py +++ b/contrib/python/Twisted/py3/twisted/internet/_glibbase.py @@ -13,7 +13,7 @@ or glib2reactor or gtk2reactor for applications using legacy static bindings. import sys -from typing import Any, Callable, Dict, Set +from typing import Any, Callable from zope.interface import implementer @@ -100,23 +100,23 @@ class GlibReactorBase(posixbase.PosixReactorBase, posixbase._PollLikeMixin): Base class for GObject event loop reactors. Notification for I/O events (reads and writes on file descriptors) is done - by the gobject-based event loop. File descriptors are registered with + by the gobject-based event loop. File descriptors are registered with gobject with the appropriate flags for read/write/disconnect notification. Time-based events, the results of C{callLater} and C{callFromThread}, are - handled differently. Rather than registering each event with gobject, a + handled differently. Rather than registering each event with gobject, a single gobject timeout is registered for the earliest scheduled event, the - output of C{reactor.timeout()}. For example, if there are timeouts in 1, 2 - and 3.4 seconds, a single timeout is registered for 1 second in the - future. When this timeout is hit, C{_simulate} is called, which calls the + output of C{reactor.timeout()}. For example, if there are timeouts in 1, 2 + and 3.4 seconds, a single timeout is registered for 1 second in the future. + When this timeout is hit, C{_simulate} is called, which calls the appropriate Twisted-level handlers, and a new timeout is added to gobject by the C{_reschedule} method. To handle C{callFromThread} events, we use a custom waker that calls C{_simulate} whenever it wakes up. - @ivar _sources: A dictionary mapping L{FileDescriptor} instances to - GSource handles. + @ivar _sources: A dictionary mapping L{FileDescriptor} instances to GSource + handles. @ivar _reads: A set of L{FileDescriptor} instances currently monitored for reading. @@ -124,7 +124,8 @@ class GlibReactorBase(posixbase.PosixReactorBase, posixbase._PollLikeMixin): @ivar _writes: A set of L{FileDescriptor} instances currently monitored for writing. - @ivar _simtag: A GSource handle for the next L{simulate} call. + @ivar _simtag: A GSource handle for the next L{GlibReactorBase._simulate} + call. """ # Install a waker that knows it needs to call C{_simulate} in order to run @@ -134,9 +135,9 @@ class GlibReactorBase(posixbase.PosixReactorBase, posixbase._PollLikeMixin): def __init__(self, glib_module: Any, gtk_module: Any, useGtk: bool = False) -> None: self._simtag = None - self._reads: Set[IReadDescriptor] = set() - self._writes: Set[IWriteDescriptor] = set() - self._sources: Dict[FileDescriptor, int] = {} + self._reads: set[IReadDescriptor] = set() + self._writes: set[IWriteDescriptor] = set() + self._sources: dict[FileDescriptor, int] = {} self._glib = glib_module self._POLL_DISCONNECTED = ( diff --git a/contrib/python/Twisted/py3/twisted/internet/_posixstdio.py b/contrib/python/Twisted/py3/twisted/internet/_posixstdio.py index e99920ead3f..8d6c0b73f34 100644 --- a/contrib/python/Twisted/py3/twisted/internet/_posixstdio.py +++ b/contrib/python/Twisted/py3/twisted/internet/_posixstdio.py @@ -47,7 +47,7 @@ class StandardIO: reactor: IReactorFDSet | None = None, ): if reactor is None: - from twisted.internet import reactor # type:ignore[assignment] + from twisted.internet import reactor self.protocol: IProtocol = proto self._writer = process.ProcessWriter(reactor, self, "write", stdout) diff --git a/contrib/python/Twisted/py3/twisted/internet/_producer_helpers.py b/contrib/python/Twisted/py3/twisted/internet/_producer_helpers.py index 7583a9e4594..f7f3103f60f 100644 --- a/contrib/python/Twisted/py3/twisted/internet/_producer_helpers.py +++ b/contrib/python/Twisted/py3/twisted/internet/_producer_helpers.py @@ -6,8 +6,6 @@ Helpers for working with producers. """ -from typing import List - from zope.interface import implementer from twisted.internet.interfaces import IPushProducer @@ -17,7 +15,7 @@ from twisted.logger import Logger _log = Logger() # This module exports nothing public, it's for internal Twisted use only. -__all__: List[str] = [] +__all__: list[str] = [] @implementer(IPushProducer) diff --git a/contrib/python/Twisted/py3/twisted/internet/_resolver.py b/contrib/python/Twisted/py3/twisted/internet/_resolver.py index f4a56b4808f..98eb4ceadb3 100644 --- a/contrib/python/Twisted/py3/twisted/internet/_resolver.py +++ b/contrib/python/Twisted/py3/twisted/internet/_resolver.py @@ -7,8 +7,9 @@ IPv6-aware hostname resolution. @see: L{IHostnameResolver} """ +from __future__ import annotations - +from collections.abc import Sequence from socket import ( AF_INET, AF_INET6, @@ -20,17 +21,7 @@ from socket import ( gaierror, getaddrinfo, ) -from typing import ( - TYPE_CHECKING, - Callable, - List, - NoReturn, - Optional, - Sequence, - Tuple, - Type, - Union, -) +from typing import TYPE_CHECKING, Callable, NoReturn, Protocol from zope.interface import implementer @@ -95,15 +86,29 @@ _socktypeToType = { } -_GETADDRINFO_RESULT = List[ - Tuple[ - AddressFamily, - SocketKind, - int, - str, - Union[Tuple[str, int], Tuple[str, int, int, int]], - ] -] +class _LikeGetAddrInfo(Protocol): + """ + A callable matching the type signature of L{getaddrinfo}. + """ + + def __call__( + self, + host: bytes | str | None, + port: bytes | str | int | None, + family: int = AF_UNSPEC, + type: int = 0, + proto: int = 0, + flags: int = 0, + ) -> list[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + ... @implementer(IHostnameResolver) @@ -116,8 +121,8 @@ class GAIResolver: def __init__( self, reactor: IReactorThreads, - getThreadPool: Optional[Callable[[], "ThreadPool"]] = None, - getaddrinfo: Callable[[str, int, int, int], _GETADDRINFO_RESULT] = getaddrinfo, + getThreadPool: Callable[[], ThreadPool] | None = None, + getaddrinfo: _LikeGetAddrInfo = getaddrinfo, ): """ Create a L{GAIResolver}. @@ -146,7 +151,7 @@ class GAIResolver: resolutionReceiver: IResolutionReceiver, hostName: str, portNumber: int = 0, - addressTypes: Optional[Sequence[Type[IAddress]]] = None, + addressTypes: Sequence[type[IAddress]] | None = None, transportSemantics: str = "TCP", ) -> IHostResolution: """ @@ -170,27 +175,30 @@ class GAIResolver: ] socketType = _transportToSocket[transportSemantics] - def get() -> _GETADDRINFO_RESULT: + resolution = HostResolution(hostName) + + async def resolveAndProcess() -> None: + resolutionReceiver.resolutionBegan(resolution) try: - return self._getaddrinfo( - hostName, portNumber, addressFamily, socketType + names = await deferToThreadPool( + self._reactor, + pool, + self._getaddrinfo, + hostName, + portNumber, + addressFamily, + socketType, ) except gaierror: - return [] - - d = deferToThreadPool(self._reactor, pool, get) - resolution = HostResolution(hostName) - resolutionReceiver.resolutionBegan(resolution) - - @d.addCallback - def deliverResults(result: _GETADDRINFO_RESULT) -> None: - for family, socktype, proto, cannoname, sockaddr in result: + names = [] + for family, socktype, proto, cannoname, sockaddr in names: addrType = _afToType[family] resolutionReceiver.addressResolved( addrType(_socktypeToType.get(socktype, "TCP"), *sockaddr) ) resolutionReceiver.resolutionComplete() + Deferred.fromCoroutine(resolveAndProcess()) return resolution @@ -213,7 +221,7 @@ class SimpleResolverComplexifier: resolutionReceiver: IResolutionReceiver, hostName: str, portNumber: int = 0, - addressTypes: Optional[Sequence[Type[IAddress]]] = None, + addressTypes: Sequence[type[IAddress]] | None = None, transportSemantics: str = "TCP", ) -> IHostResolution: """ @@ -254,13 +262,15 @@ class SimpleResolverComplexifier: ) ) .addErrback( - lambda error: None - if error.check(DNSLookupError) - else self._log.failure( - "while looking up {name} with {resolver}", - error, - name=hostName, - resolver=self._simpleResolver, + lambda error: ( + None + if error.check(DNSLookupError) + else self._log.failure( + "while looking up {name} with {resolver}", + error, + name=hostName, + resolver=self._simpleResolver, + ) ) ) .addCallback(lambda nothing: resolutionReceiver.resolutionComplete()) @@ -274,7 +284,7 @@ class FirstOneWins: An L{IResolutionReceiver} which fires a L{Deferred} with its first result. """ - def __init__(self, deferred: "Deferred[str]"): + def __init__(self, deferred: Deferred[str]): """ @param deferred: The L{Deferred} to fire when the first resolution result arrives. @@ -327,7 +337,7 @@ class ComplexResolverSimplifier: """ self._nameResolver = nameResolver - def getHostByName(self, name: str, timeouts: Sequence[int] = ()) -> "Deferred[str]": + def getHostByName(self, name: str, timeouts: Sequence[int] = ()) -> Deferred[str]: """ See L{IResolverSimple.getHostByName} @@ -337,6 +347,6 @@ class ComplexResolverSimplifier: @return: see L{IResolverSimple.getHostByName} """ - result: "Deferred[str]" = Deferred() + result: Deferred[str] = Deferred() self._nameResolver.resolveHostName(FirstOneWins(result), name, 0, [IPv4Address]) return result diff --git a/contrib/python/Twisted/py3/twisted/internet/_service_identity.py b/contrib/python/Twisted/py3/twisted/internet/_service_identity.py new file mode 100644 index 00000000000..a311fbed72c --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/internet/_service_identity.py @@ -0,0 +1,42 @@ +""" +Conditional imports to enable support for the C{service_identity} module back +to version 18.1.0. +""" + +# imported for the benefit of pydoctor +import service_identity + +VerificationError = service_identity.VerificationError + +try: + __import__("service_identity.hazmat") +except ImportError: + from sys import modules + + for _oldAlias in "common", "_common": + try: + import service_identity + + service_identity.hazmat = modules["service_identity.hazmat"] = getattr( + __import__(f"service_identity.{_oldAlias}"), _oldAlias + ) + except ImportError: + pass +from service_identity.hazmat import DNS_ID, IPAddress_ID, verify_service_identity + +try: + from service_identity.hazmat import ServiceID +except ImportError: + ServiceID = object # type:ignore[assignment,misc] +try: + from service_identity.pyopenssl import extract_patterns +except ImportError: + from service_identity.pyopenssl import extract_ids as extract_patterns +__all__ = [ + "DNS_ID", + "IPAddress_ID", + "extract_patterns", + "ServiceID", + "VerificationError", + "verify_service_identity", +] diff --git a/contrib/python/Twisted/py3/twisted/internet/_signals.py b/contrib/python/Twisted/py3/twisted/internet/_signals.py index 18793bfbba2..62856f01291 100644 --- a/contrib/python/Twisted/py3/twisted/internet/_signals.py +++ b/contrib/python/Twisted/py3/twisted/internet/_signals.py @@ -38,13 +38,14 @@ import errno import os import signal import socket +from collections.abc import Sequence from types import FrameType -from typing import Callable, Optional, Sequence +from typing import Callable, Optional, Protocol from zope.interface import Attribute, Interface, implementer from attrs import define, frozen -from typing_extensions import Protocol, TypeAlias +from typing_extensions import TypeAlias from twisted.internet.interfaces import IReadDescriptor from twisted.python import failure, log, util @@ -213,7 +214,7 @@ class _ChildSignalHandling: _addInternalReader: Callable[[IReadDescriptor], object] _removeInternalReader: Callable[[IReadDescriptor], object] - _childWaker: Optional[_SIGCHLDWaker] = None + _childWaker: _SIGCHLDWaker | None = None def install(self) -> None: """ diff --git a/contrib/python/Twisted/py3/twisted/internet/_sslverify.py b/contrib/python/Twisted/py3/twisted/internet/_sslverify.py index 2095fe69eca..c6dabf6c4c4 100644 --- a/contrib/python/Twisted/py3/twisted/internet/_sslverify.py +++ b/contrib/python/Twisted/py3/twisted/internet/_sslverify.py @@ -6,14 +6,17 @@ from __future__ import annotations import warnings from binascii import hexlify +from collections.abc import Sequence from functools import lru_cache from hashlib import md5 -from typing import Dict +from typing import TYPE_CHECKING, Any, Callable, TypeVar from zope.interface import Interface, implementer from OpenSSL import SSL, crypto from OpenSSL._util import lib as pyOpenSSLlib +from OpenSSL.crypto import X509, PKey +from OpenSSL.SSL import VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_PEER, Connection import attr from constantly import FlagConstant, Flags, NamedConstant, Names @@ -27,6 +30,8 @@ from twisted.internet.interfaces import ( ICipher, IOpenSSLClientConnectionCreator, IOpenSSLContextFactory, + IOpenSSLServerConnectionCreator, + IProtocolNegotiationFactory, ) from twisted.logger import Logger from twisted.python.compat import nativeString @@ -35,6 +40,17 @@ from twisted.python.failure import Failure from twisted.python.randbytes import secureRandom from twisted.python.util import nameToLabel from ._idna import _idnaBytes +from ._service_identity import ( + DNS_ID, + IPAddress_ID, + ServiceID, + VerificationError, + extract_patterns, + verify_service_identity, +) + +if TYPE_CHECKING: + from twisted.protocols.tls import TLSMemoryBIOProtocol _log = Logger() @@ -79,121 +95,24 @@ def _getExcludedTLSProtocols(oldest, newest): @rtype: L{list} of L{TLSVersion} constants. """ versions = list(TLSVersion.iterconstants()) - excludedVersions = [x for x in versions[: versions.index(oldest)]] - - if newest: - excludedVersions.extend([x for x in versions[versions.index(newest) :]]) - + excludedOlder = versions[: versions.index(oldest)] + excludedNewer = versions[versions.index(newest) + 1 :] if newest else [] + excludedVersions = excludedOlder + excludedNewer return excludedVersions -class SimpleVerificationError(Exception): - """ - Not a very useful verification error. - """ - - -def simpleVerifyHostname(connection, hostname): - """ - Check only the common name in the certificate presented by the peer and - only for an exact match. - - This is to provide I{something} in the way of hostname verification to - users who haven't installed C{service_identity}. This check is overly - strict, relies on a deprecated TLS feature (you're supposed to ignore the - commonName if the subjectAlternativeName extensions are present, I - believe), and lots of valid certificates will fail. - - @param connection: the OpenSSL connection to verify. - @type connection: L{OpenSSL.SSL.Connection} - - @param hostname: The hostname expected by the user. - @type hostname: L{unicode} - - @raise twisted.internet.ssl.VerificationError: if the common name and - hostname don't match. - """ - commonName = connection.get_peer_certificate().get_subject().commonName - if commonName != hostname: - raise SimpleVerificationError(repr(commonName) + "!=" + repr(hostname)) - - -def simpleVerifyIPAddress(connection, hostname): - """ - Always fails validation of IP addresses - - @param connection: the OpenSSL connection to verify. - @type connection: L{OpenSSL.SSL.Connection} - - @param hostname: The hostname expected by the user. - @type hostname: L{unicode} - - @raise twisted.internet.ssl.VerificationError: Always raised - """ - raise SimpleVerificationError("Cannot verify certificate IP addresses") - - -def _usablePyOpenSSL(version): - """ - Check pyOpenSSL version string whether we can use it for host verification. - - @param version: A pyOpenSSL version string. - @type version: L{str} - - @rtype: L{bool} - """ - major, minor = (int(part) for part in version.split(".")[:2]) - return (major, minor) >= (0, 12) - - -def _selectVerifyImplementation(): - """ - Determine if C{service_identity} is installed. If so, use it. If not, use - simplistic and incorrect checking as implemented in - L{simpleVerifyHostname}. - - @return: 2-tuple of (C{verify_hostname}, C{VerificationError}) - @rtype: L{tuple} - """ - - whatsWrong = ( - "Without the service_identity module, Twisted can perform only " - "rudimentary TLS client hostname verification. Many valid " - "certificate/hostname mappings may be rejected." - ) - - try: - from service_identity import VerificationError - from service_identity.pyopenssl import verify_hostname, verify_ip_address - - return verify_hostname, verify_ip_address, VerificationError - except ImportError as e: - warnings.warn_explicit( - "You do not have a working installation of the " - "service_identity module: '" + str(e) + "'. " - "Please install it from " - "<https://pypi.python.org/pypi/service_identity> and make " - "sure all of its dependencies are satisfied. " + whatsWrong, - # Unfortunately the lineno is required. - category=UserWarning, - filename="", - lineno=0, - ) - - return simpleVerifyHostname, simpleVerifyIPAddress, SimpleVerificationError - - -verifyHostname, verifyIPAddress, VerificationError = _selectVerifyImplementation() - - class ProtocolNegotiationSupport(Flags): """ L{ProtocolNegotiationSupport} defines flags which are used to indicate the - level of NPN/ALPN support provided by the TLS backend. + level of ALPN support provided by the TLS backend. + + @cvar NOSUPPORT: There is no support for ALPN. This is exclusive with + L{ALPN}. + + @cvar NPN: The implementation supports Next Protocol Negotiation. (This + flag is provided for compatibility only; Twisted no longer supports + Next Protocol Negotiation) - @cvar NOSUPPORT: There is no support for NPN or ALPN. This is exclusive - with both L{NPN} and L{ALPN}. - @cvar NPN: The implementation supports Next Protocol Negotiation. @cvar ALPN: The implementation supports Application Layer Protocol Negotiation. """ @@ -211,28 +130,21 @@ ProtocolNegotiationSupport.NOSUPPORT = ( ) -def protocolNegotiationMechanisms(): +def protocolNegotiationMechanisms() -> FlagConstant: """ - Checks whether your versions of PyOpenSSL and OpenSSL are recent enough to - support protocol negotiation, and if they are, what kind of protocol - negotiation is supported. + Check whether the installed versions of pyOpenSSL and OpenSSL are recent + enough to support ALPN. @return: A combination of flags from L{ProtocolNegotiationSupport} that indicate which mechanisms for protocol negotiation are supported. - @rtype: L{constantly.FlagConstant} """ + # TODO: deprecate this, as it will always return ALPN and only ALPN on all + # supported versions of OpenSSL. support = ProtocolNegotiationSupport.NOSUPPORT ctx = SSL.Context(SSL.SSLv23_METHOD) try: - ctx.set_npn_advertise_callback(lambda c: None) - except (AttributeError, NotImplementedError): - pass - else: - support |= ProtocolNegotiationSupport.NPN - - try: - ctx.set_alpn_select_callback(lambda c: None) + ctx.set_alpn_select_callback(lambda connection, protocols: protocols[0]) except (AttributeError, NotImplementedError): pass else: @@ -258,7 +170,7 @@ _x509names = { } -class DistinguishedName(Dict[str, bytes]): +class DistinguishedName(dict[str, bytes]): """ Identify and describe an entity. @@ -334,13 +246,13 @@ class DistinguishedName(Dict[str, bytes]): value = value.encode("ascii") self[realAttr] = value - def inspect(self): + def inspect(self) -> str: """ Return a multi-line, human-readable representation of this DN. @rtype: L{str} """ - l = [] + lines = [] lablen = 0 def uniqueValues(mapping): @@ -351,11 +263,9 @@ class DistinguishedName(Dict[str, bytes]): lablen = max(len(label), lablen) v = getattr(self, k, None) if v is not None: - l.append((label, nativeString(v))) + lines.append((label, nativeString(v))) lablen += 2 - for n, (label, attrib) in enumerate(l): - l[n] = label.rjust(lablen) + ": " + attrib - return "\n".join(l) + return "\n".join(label.rjust(lablen) + ": " + attrib for label, attrib in lines) DN = DistinguishedName @@ -426,6 +336,9 @@ def _handleattrhelper(Class, transport, methodName): return Class(cert) +_Self = TypeVar("_Self", bound="Certificate") + + class Certificate(CertBase): """ An x509 certificate. @@ -444,7 +357,12 @@ class Certificate(CertBase): return NotImplemented @classmethod - def load(Class, requestData, format=crypto.FILETYPE_ASN1, args=()): + def load( + Class: type[_Self], + requestData: bytes, + format: int = crypto.FILETYPE_ASN1, + args: tuple[Any, ...] = (), + ) -> _Self: """ Load a certificate from an ASN.1- or PEM-format string. @@ -465,7 +383,7 @@ class Certificate(CertBase): return self.dump(crypto.FILETYPE_PEM) @classmethod - def loadPEM(Class, data): + def loadPEM(Class, data: bytes) -> Certificate: """ Load a certificate from a PEM-format data string. @@ -761,7 +679,7 @@ class PublicKey: class KeyPair(PublicKey): @classmethod - def load(Class, data, format=crypto.FILETYPE_ASN1): + def load(Class, data: bytes, format: int = crypto.FILETYPE_ASN1) -> KeyPair: return Class(crypto.load_privatekey(format, data)) def dump(self, format=crypto.FILETYPE_ASN1): @@ -1039,38 +957,6 @@ def platformTrust(): return OpenSSLDefaultPaths() -def _tolerateErrors(wrapped): - """ - Wrap up an C{info_callback} for pyOpenSSL so that if something goes wrong - the error is immediately logged and the connection is dropped if possible. - - This wrapper exists because some versions of pyOpenSSL don't handle errors - from callbacks at I{all}, and those which do write tracebacks directly to - stderr rather than to a supplied logging system. This reports unexpected - errors to the Twisted logging system. - - Also, this terminates the connection immediately if possible because if - you've got bugs in your verification logic it's much safer to just give up. - - @param wrapped: A valid C{info_callback} for pyOpenSSL. - @type wrapped: L{callable} - - @return: A valid C{info_callback} for pyOpenSSL that handles any errors in - C{wrapped}. - @rtype: L{callable} - """ - - def infoCallback(connection: SSL.Connection, where: int, ret: int) -> object: - result = None - with _log.failuresHandled("Error during info_callback") as op: - result = wrapped(connection, where, ret) - if (f := op.failure) is not None: - connection.get_app_data().failVerification(f) - return result - - return infoCallback - - @implementer(IOpenSSLClientConnectionCreator) class ClientTLSOptions: """ @@ -1079,17 +965,16 @@ class ClientTLSOptions: Private implementation type (not exposed to applications) for public L{optionsForClientTLS} API. - @ivar _ctx: The context to use for new connections. - @type _ctx: L{OpenSSL.SSL.Context} + @ivar _createConnection: A callable that creates a mostly-configured + OpenSSL connection, modulo the hostname stuff that L{ClientTLSOptions} + is responsible for. @ivar _hostname: The hostname to verify, as specified by the application, as some human-readable text. - @type _hostname: L{unicode} @ivar _hostnameBytes: The hostname to verify, decoded into IDNA-encoded bytes. This is passed to APIs which think that hostnames are bytes, such as OpenSSL's SNI implementation. - @type _hostnameBytes: L{bytes} @ivar _hostnameASCII: The hostname, as transcoded into IDNA ASCII-range unicode code points. This is pre-transcoded because the @@ -1097,26 +982,44 @@ class ClientTLSOptions: C{idna} package from PyPI for internationalized domain names, rather than working with Python's built-in (but sometimes broken) IDNA encoding. ASCII values, however, will always work. - @type _hostnameASCII: L{unicode} @ivar _hostnameIsDnsName: Whether or not the C{_hostname} is a DNSName. Will be L{False} if C{_hostname} is an IP address or L{True} if C{_hostname} is a DNSName - @type _hostnameIsDnsName: L{bool} + + @ivar _sendServerName: Whether the hostname will be sent via the TLS + U{Server Name Indication + <https://www.rfc-editor.org/rfc/rfc3546#section-3.1>} extension. """ - def __init__(self, hostname, ctx): + _createConnection: Callable[[TLSMemoryBIOProtocol], SSL.Connection] + _hostname: str + _hostnameASCII: str + _hostnameIsDnsName: bool + _hostnameBytes: bytes + _sendServerName: bool + + def __init__( + self, + createConnection: Callable[[TLSMemoryBIOProtocol], SSL.Connection], + hostname: str, + sendServerName: bool | None = None, + ) -> None: """ Initialize L{ClientTLSOptions}. + @param createConnection: A callable which can create a + mostly-configured L{SSL.Connection}, modulo hostname verification. + @param hostname: The hostname to verify as input by a human. - @type hostname: L{unicode} - @param ctx: an L{OpenSSL.SSL.Context} to use for new connections. - @type ctx: L{OpenSSL.SSL.Context}. + @param sendServerName: Should the server name be sent to the peer? + C{None} means "follow the specification", which will send it if + it's a valid DNS name and refrain from sending it if it's an IP + address; C{True} means always send, and C{False} means never send. """ - self._ctx = ctx self._hostname = hostname + self._createConnection = createConnection if isIPAddress(hostname) or isIPv6Address(hostname): self._hostnameBytes = hostname.encode("ascii") @@ -1126,69 +1029,77 @@ class ClientTLSOptions: self._hostnameIsDnsName = True self._hostnameASCII = self._hostnameBytes.decode("ascii") - ctx.set_info_callback(_tolerateErrors(self._identityVerifyingInfoCallback)) + if sendServerName is None: + sendServerName = self._hostnameIsDnsName + self._sendServerName = sendServerName - def clientConnectionForTLS(self, tlsProtocol): + def clientConnectionForTLS(self, tlsProtocol: TLSMemoryBIOProtocol) -> Connection: """ Create a TLS connection for a client. - @note: This will call C{set_app_data} on its connection. If you're - delegating to this implementation of this method, don't ever call - C{set_app_data} or C{set_info_callback} on the returned connection, - or you'll break the implementation of various features of this - class. - @param tlsProtocol: the TLS protocol initiating the connection. - @type tlsProtocol: L{twisted.protocols.tls.TLSMemoryBIOProtocol} @return: the configured client connection. - @rtype: L{OpenSSL.SSL.Connection} """ - context = self._ctx - connection = SSL.Connection(context, None) - connection.set_app_data(tlsProtocol) + connection = self._createConnection(tlsProtocol) + # Literal IPv4 and IPv6 addresses are not permitted as host names + # according to the RFCs + if self._sendServerName: + connection.set_tlsext_host_name(self._hostnameBytes) + callback = _verifyCB(tlsProtocol, self._hostnameIsDnsName, self._hostnameASCII) + connection.set_verify(VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, callback) return connection - def _identityVerifyingInfoCallback(self, connection, where, ret): - """ - U{info_callback - <http://pythonhosted.org/pyOpenSSL/api/ssl.html#OpenSSL.SSL.Context.set_info_callback> - } for pyOpenSSL that verifies the hostname in the presented certificate - matches the one passed to this L{ClientTLSOptions}. - @param connection: the connection which is handshaking. - @type connection: L{OpenSSL.SSL.Connection} +def _verifyCB( + tlsProtocol: TLSMemoryBIOProtocol, hostIsDNS: bool, hostnameASCII: str +) -> Callable[[Connection, X509, int, int, bool], bool]: + svcid: ServiceID + if hostIsDNS: + svcid = DNS_ID(hostnameASCII) + else: + svcid = IPAddress_ID(hostnameASCII) - @param where: flags indicating progress through a TLS handshake. - @type where: L{int} + weakProtoRef: TLSMemoryBIOProtocol | None = tlsProtocol - @param ret: ignored - @type ret: ignored - """ - # Literal IPv4 and IPv6 addresses are not permitted - # as host names according to the RFCs - if where & SSL.SSL_CB_HANDSHAKE_START and self._hostnameIsDnsName: - connection.set_tlsext_host_name(self._hostnameBytes) - elif where & SSL.SSL_CB_HANDSHAKE_DONE: + def verifyCallback( + conn: Connection, cert: X509, err: int, depth: int, ok: bool + ) -> bool: + ourVerifyResult = ok + nonlocal weakProtoRef + try: + if depth != 0: + # We are only verifying the leaf certificate. + return ourVerifyResult try: - if self._hostnameIsDnsName: - verifyHostname(connection, self._hostnameASCII) - else: - verifyIPAddress(connection, self._hostnameASCII) + verify_service_identity(extract_patterns(cert), [svcid], []) except VerificationError: + ourVerifyResult = False f = Failure() - transport = connection.get_app_data() - transport.failVerification(f) + assert weakProtoRef is not None + weakProtoRef.failVerification(f) + except BaseException: + # If we raise an exception *at all* during an OpenSSL callback, at + # best we lose the exception to getting dumped on stderr rather + # than getting logged, at worst it just disappears. So we catch + # *everything* here so we can get it normally logged. + _log.failure("while verifying certificate") + # Ensure that no reference remains to the protocol. + weakProtoRef = None + return ourVerifyResult + + return verifyCallback def optionsForClientTLS( - hostname, - trustRoot=None, - clientCertificate=None, - acceptableProtocols=None, + hostname: str, + trustRoot: IOpenSSLTrustRoot | Certificate | None = None, + clientCertificate: PrivateCertificate | None = None, + acceptableProtocols: Sequence[bytes] | None = None, *, - extraCertificateOptions=None, -): + extraCertificateOptions: dict[str, Any] | None = None, + sendServerName: bool | None = None, +) -> IOpenSSLClientConnectionCreator: """ Create a L{client connection creator <IOpenSSLClientConnectionCreator>} for use with APIs such as L{SSL4ClientEndpoint @@ -1198,44 +1109,43 @@ def optionsForClientTLS( @since: 14.0 - @param hostname: The expected name of the remote host. This serves two + @param hostname: The expected name of the remote host. This serves two purposes: first, and most importantly, it verifies that the certificate received from the server correctly identifies the specified hostname. The second purpose is to use the U{Server Name Indication extension <https://en.wikipedia.org/wiki/Server_Name_Indication>} to indicate to the server which certificate should be used. - @type hostname: L{unicode} - @param trustRoot: Specification of trust requirements of peers. This may be - a L{Certificate} or the result of L{platformTrust}. By default it is - L{platformTrust} and you probably shouldn't adjust it unless you really - know what you're doing. Be aware that clients using this interface - I{must} verify the server; you cannot explicitly pass L{None} since - that just means to use L{platformTrust}. - @type trustRoot: L{IOpenSSLTrustRoot} + @param trustRoot: Specification of trust requirements of peers. This may + be a L{Certificate} or the result of L{platformTrust}. By default it + is L{platformTrust} and you probably shouldn't adjust it unless you + really know what you're doing. Be aware that clients using this + interface I{must} verify the server; you cannot explicitly pass L{None} + since that just means to use L{platformTrust}. @param clientCertificate: The certificate and private key that the client - will use to authenticate to the server. If unspecified, the client will - not authenticate. - @type clientCertificate: L{PrivateCertificate} + will use to authenticate to the server. If unspecified, the client + will not authenticate. @param acceptableProtocols: The protocols this peer is willing to speak - after the TLS negotiation has completed, advertised over both ALPN and - NPN. If this argument is specified, and no overlap can be found with - the other peer, the connection will fail to be established. If the - remote peer does not offer NPN or ALPN, the connection will be - established, but no protocol wil be negotiated. Protocols earlier in - the list are preferred over those later in the list. - @type acceptableProtocols: L{list} of L{bytes} + after the TLS negotiation has completed, advertised over ALPN. If this + argument is specified, and no overlap can be found with the other peer, + the connection will fail to be established. If the remote peer does + not offer ALPN, the connection will be established, but no protocol wil + be negotiated. Protocols earlier in the list are preferred over those + later in the list. + + @param extraCertificateOptions: A dictionary of additional keyword + arguments to be presented to L{CertificateOptions}. Please avoid using + this unless you absolutely need to; any time you need to pass an option + here that is a bug in this interface. - @param extraCertificateOptions: A dictionary of additional keyword arguments - to be presented to L{CertificateOptions}. Please avoid using this unless - you absolutely need to; any time you need to pass an option here that is - a bug in this interface. - @type extraCertificateOptions: L{dict} + @param sendServerName: Should the server name be sent to the peer? C{None} + means "follow the specification", which will send it if it's a valid + DNS name and refrain from sending it if it's an IP address; C{True} + means always send, and C{False} means never send. @return: A client connection creator. - @rtype: L{IOpenSSLClientConnectionCreator} """ if extraCertificateOptions is None: extraCertificateOptions = {} @@ -1251,15 +1161,23 @@ def optionsForClientTLS( privateKey=clientCertificate.privateKey.original, certificate=clientCertificate.original, ) + certificateOptions = OpenSSLCertificateOptions( trustRoot=trustRoot, acceptableProtocols=acceptableProtocols, **extraCertificateOptions, ) - return ClientTLSOptions(hostname, certificateOptions.getContext()) + + return ClientTLSOptions( + certificateOptions._makeTLSConnection, hostname, sendServerName + ) -@implementer(IOpenSSLContextFactory) +@implementer( + IOpenSSLServerConnectionCreator, + IOpenSSLClientConnectionCreator, + IOpenSSLContextFactory, +) class OpenSSLCertificateOptions: """ A L{CertificateOptions <twisted.internet.ssl.CertificateOptions>} specifies @@ -1282,7 +1200,7 @@ class OpenSSLCertificateOptions: # Factory for creating contexts. Configurable for testability. _contextFactory = SSL.Context - _context = None + _context: SSL.Context | None = None _OP_NO_TLSv1_3 = _tlsDisableFlags[TLSVersion.TLSv1_3] @@ -1290,38 +1208,41 @@ class OpenSSLCertificateOptions: @_mutuallyExclusiveArguments( [ - ["trustRoot", "requireCertificate"], - ["trustRoot", "verify"], - ["trustRoot", "caCerts"], - ["method", "insecurelyLowerMinimumTo"], - ["method", "raiseMinimumTo"], - ["raiseMinimumTo", "insecurelyLowerMinimumTo"], - ["method", "lowerMaximumSecurityTo"], + ("trustRoot", "requireCertificate"), + ("trustRoot", "verify"), + ("trustRoot", "caCerts"), + ("method", "insecurelyLowerMinimumTo"), + ("method", "raiseMinimumTo"), + ("raiseMinimumTo", "insecurelyLowerMinimumTo"), + ("method", "lowerMaximumSecurityTo"), ] ) def __init__( self, - privateKey=None, - certificate=None, - method=None, - verify=False, - caCerts=None, - verifyDepth=9, - requireCertificate=True, - verifyOnce=True, - enableSingleUseKeys=True, - enableSessions=False, - fixBrokenPeers=False, - enableSessionTickets=False, - extraCertChain=None, - acceptableCiphers=None, - dhParameters=None, - trustRoot=None, - acceptableProtocols=None, - raiseMinimumTo=None, - insecurelyLowerMinimumTo=None, - lowerMaximumSecurityTo=None, - ): + privateKey: PKey | None = None, + certificate: X509 | None = None, + method: int | None = None, + verify: bool = False, + caCerts: list[X509] | None = None, + verifyDepth: int = 9, + requireCertificate: bool = True, + verifyOnce: bool = True, + enableSingleUseKeys: bool = True, + enableSessions: bool = False, + fixBrokenPeers: bool = False, + enableSessionTickets: bool = False, + extraCertChain: list[X509] | None = None, + acceptableCiphers: IAcceptableCiphers | None = None, + dhParameters: OpenSSLDiffieHellmanParameters | None = None, + trustRoot: IOpenSSLTrustRoot | None = None, + acceptableProtocols: list[bytes] | None = None, + raiseMinimumTo: NamedConstant | None = None, + insecurelyLowerMinimumTo: NamedConstant | None = None, + lowerMaximumSecurityTo: NamedConstant | None = None, + contextForServerName: ( + Callable[[bytes | None], SSL.Context | None] | None + ) = None, + ) -> None: """ Create an OpenSSL context SSL connection context factory. @@ -1395,7 +1316,6 @@ class OpenSSLCertificateOptions: verification chain if the certificate authority that signed your C{certificate} isn't widely supported. Do I{not} add C{certificate} to it. - @type extraCertChain: C{list} of L{OpenSSL.crypto.X509} @param acceptableCiphers: Ciphers that are acceptable for connections. Uses a secure default if left L{None}. @@ -1404,8 +1324,6 @@ class OpenSSLCertificateOptions: @param dhParameters: Key generation parameters that are required for Diffie-Hellman key exchange. If this argument is left L{None}, C{EDH} ciphers are I{disabled} regardless of C{acceptableCiphers}. - @type dhParameters: L{DiffieHellmanParameters - <twisted.internet.ssl.DiffieHellmanParameters>} @param trustRoot: Specification of trust requirements of peers. If this argument is specified, the peer is verified. It requires a @@ -1417,16 +1335,13 @@ class OpenSSLCertificateOptions: those options in combination with this one will raise a L{TypeError}. - @type trustRoot: L{IOpenSSLTrustRoot} - @param acceptableProtocols: The protocols this peer is willing to speak - after the TLS negotiation has completed, advertised over both ALPN - and NPN. If this argument is specified, and no overlap can be - found with the other peer, the connection will fail to be - established. If the remote peer does not offer NPN or ALPN, the - connection will be established, but no protocol wil be negotiated. - Protocols earlier in the list are preferred over those later in the - list. + after the TLS negotiation has completed, advertised over ALPN. If + this argument is specified, and no overlap can be found with the + other peer, the connection will fail to be established. If the + remote peer does not offer ALPN, the connection will be + established, but no protocol wil be negotiated. Protocols earlier + in the list are preferred over those later in the list. @type acceptableProtocols: L{list} of L{bytes} @param raiseMinimumTo: The minimum TLS version that you want to use, or @@ -1453,6 +1368,10 @@ class OpenSSLCertificateOptions: unless you are absolutely sure this is what you want. @type lowerMaximumSecurityTo: L{TLSVersion} constant + @param contextForServerName: A callback to invoke with the server-name + indication field in the client handshake, which returns the other + context to switch to. + @raise ValueError: when C{privateKey} or C{certificate} are set without setting the respective other. @raise ValueError: when C{verify} is L{True} but C{caCerts} doesn't @@ -1605,14 +1524,9 @@ class OpenSSLCertificateOptions: self.verify = True self.requireCertificate = True trustRoot = IOpenSSLTrustRoot(trustRoot) - self.trustRoot = trustRoot - - if acceptableProtocols is not None and not protocolNegotiationMechanisms(): - raise NotImplementedError( - "No support for protocol negotiation on this platform." - ) - + self.trustRoot: IOpenSSLTrustRoot | None = trustRoot self._acceptableProtocols = acceptableProtocols + self._contextForServerName = contextForServerName def __getstate__(self): d = self.__dict__.copy() @@ -1625,15 +1539,51 @@ class OpenSSLCertificateOptions: def __setstate__(self, state): self.__dict__ = state - def getContext(self): + def getContext(self) -> SSL.Context: """ - Return an L{OpenSSL.SSL.Context} object. + Create and cache an L{SSL.Context} based on the parameters in this + L{twisted.internet.ssl.CertificateOptions}. + + This is deprecated because it returns a cached, shared context which + cannot safely be shared, because the acceptable protocols may be + mutated. """ if self._context is None: self._context = self._makeContext() return self._context - def _makeContext(self): + def serverConnectionForTLS(self, protocol: TLSMemoryBIOProtocol) -> SSL.Connection: + """ + Construct a TLS connection for the server. + """ + return self._makeTLSConnection(protocol) + + def clientConnectionForTLS(self, protocol: TLSMemoryBIOProtocol) -> SSL.Connection: + """ + Construct a TLS connection for the client. + """ + return self._makeTLSConnection(protocol) + + def _makeTLSConnection(self, protocol: TLSMemoryBIOProtocol) -> SSL.Connection: + """ + Construct an OpenSSL Connection for either client or server. + """ + ctx = self.getContext() + cxn = Connection(ctx) + if acceptable := protosFromProtocol(protocol, self._acceptableProtocols or ()): + cxn.set_alpn_protos(acceptable) + return cxn + + def _makeContext(self, skipCiphers: bool = False) -> SSL.Context: + """ + Make a new context (with no caching) based on the settings of this + Options. + + @param skipCiphers: Don't set the cipher list, so as to avoid creating + a Connection and preventing further mutation. Just for a few small + tests, per the behavior described U{here + <https://github.com/twisted/twisted/issues/12500#issuecomment-3287771137>}. + """ ctx = self._contextFactory(self.method) ctx.set_options(self._options) ctx.set_mode(self._mode) @@ -1648,6 +1598,9 @@ class OpenSSLCertificateOptions: verifyFlags = SSL.VERIFY_NONE if self.verify: + assert ( + self.trustRoot is not None + ), "when the verify flag is set, trustRoot must be set" verifyFlags = SSL.VERIFY_PEER if self.requireCertificate: verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT @@ -1680,22 +1633,33 @@ class OpenSSLCertificateOptions: if self.dhParameters: ctx.load_tmp_dh(self.dhParameters._dhFile.path) - ctx.set_cipher_list(self._cipherString.encode("ascii")) self._ecChooser.configureECDHCurve(ctx) + if self._contextForServerName is not None: + ctxForName = self._contextForServerName - if self._acceptableProtocols: - # Try to set NPN and ALPN. _acceptableProtocols cannot be set by - # the constructor unless at least one mechanism is supported. - _setAcceptableProtocols(ctx, self._acceptableProtocols) + def contextSelectionCallback(connection: SSL.Connection) -> None: + servername = connection.get_servername() + try: + newContext = ctxForName(servername) + except BaseException: + _log.failure("while looking up SNI context") + connection.get_app_data().abortConnection() + else: + if newContext is not None: + connection.set_context(newContext) + ctx.set_tlsext_servername_callback(contextSelectionCallback) + setupForALPN(ctx, self._acceptableProtocols or ()) + if not skipCiphers: + ctx.set_cipher_list(self._cipherString.encode("ascii")) return ctx -OpenSSLCertificateOptions.__getstate__ = deprecated( +OpenSSLCertificateOptions.__getstate__ = deprecated( # type:ignore[method-assign] Version("Twisted", 15, 0, 0), "a real persistence system" )(OpenSSLCertificateOptions.__getstate__) -OpenSSLCertificateOptions.__setstate__ = deprecated( +OpenSSLCertificateOptions.__setstate__ = deprecated( # type:ignore[method-assign] Version("Twisted", 15, 0, 0), "a real persistence system" )(OpenSSLCertificateOptions.__setstate__) @@ -1960,61 +1924,54 @@ class OpenSSLDiffieHellmanParameters: return cls(filePath) -def _setAcceptableProtocols(context, acceptableProtocols): +def protosFromProtocol( + tlsProto: TLSMemoryBIOProtocol, acceptableProtocols: Sequence[bytes] +) -> Sequence[bytes]: + """ + Get the union of the ALPN protocols from the given TLSMemoryBIOProtocol and + from the static list of acceptable protocols. """ - Called to set up the L{OpenSSL.SSL.Context} for doing NPN and/or ALPN - negotiation. + tlsFactory = tlsProto.factory + assert tlsFactory is not None + ipnf = IProtocolNegotiationFactory(tlsFactory.wrappedFactory, None) + allAcceptableProtocols = tuple(acceptableProtocols or ()) + if ipnf is not None: + allAcceptableProtocols = ( + tuple(ipnf.acceptableProtocols()) + allAcceptableProtocols + ) + return allAcceptableProtocols - @param context: The context which is set up. - @type context: L{OpenSSL.SSL.Context} - @param acceptableProtocols: The protocols this peer is willing to speak - after the TLS negotiation has completed, advertised over both ALPN and - NPN. If this argument is specified, and no overlap can be found with - the other peer, the connection will fail to be established. If the - remote peer does not offer NPN or ALPN, the connection will be - established, but no protocol wil be negotiated. Protocols earlier in - the list are preferred over those later in the list. - @type acceptableProtocols: L{list} of L{bytes} +def setupForALPN(context: SSL.Context, acceptableProtocols: Sequence[bytes]) -> None: """ + Called to set up the L{OpenSSL.SSL.Context} for doing ALPN negotiation. - def protoSelectCallback(conn, protocols): - """ - NPN client-side and ALPN server-side callback used to select - the next protocol. Prefers protocols found earlier in - C{_acceptableProtocols}. + @param context: The context which is being set up. - @param conn: The context which is set up. - @type conn: L{OpenSSL.SSL.Connection} + @param acceptableProtocols: The protocols that the host represented by + C{context} is willing to speak after TLS negotiation has completed, + which will be advertised by connections using this context, over ALPN. - @param conn: Protocols advertised by the other side. - @type conn: L{list} of L{bytes} - """ - overlap = set(protocols) & set(acceptableProtocols) + If this argument is specified, and no overlap can be found with the + peer on a given connection, TLS negotiation of that connection will + fail, and it will not be established. - for p in acceptableProtocols: + If a connection's peer does not offer ALPN, the connection will be + established, but no protocol will be negotiated. Protocols earlier in + the list are preferred over those later in the list. + """ + + def alpnSelect(conn: Connection, clientSentProtocols: Sequence[bytes]) -> bytes: + tlsProto = conn.get_app_data() + allAcceptableProtocols = protosFromProtocol(tlsProto, acceptableProtocols) + overlap = set(clientSentProtocols) & set(allAcceptableProtocols) + for p in allAcceptableProtocols: if p in overlap: return p else: + # TODO: I think this should really be + # OpenSSL.SSL.NO_OVERLAPPING_PROTOCOLS which is a Python-specific + # sentinel object exposed by pyOpenSSL return b"" - # If we don't actually have protocols to negotiate, don't set anything up. - # Depending on OpenSSL version, failing some of the selection callbacks can - # cause the handshake to fail, which is presumably not what was intended - # here. - if not acceptableProtocols: - return - - supported = protocolNegotiationMechanisms() - - if supported & ProtocolNegotiationSupport.NPN: - - def npnAdvertiseCallback(conn): - return acceptableProtocols - - context.set_npn_advertise_callback(npnAdvertiseCallback) - context.set_npn_select_callback(protoSelectCallback) - - if supported & ProtocolNegotiationSupport.ALPN: - context.set_alpn_select_callback(protoSelectCallback) - context.set_alpn_protos(acceptableProtocols) + context.set_alpn_select_callback(alpnSelect) diff --git a/contrib/python/Twisted/py3/twisted/internet/abstract.py b/contrib/python/Twisted/py3/twisted/internet/abstract.py index 09bd751199c..642008a84fe 100644 --- a/contrib/python/Twisted/py3/twisted/internet/abstract.py +++ b/contrib/python/Twisted/py3/twisted/internet/abstract.py @@ -8,8 +8,8 @@ Support for generic select()able objects. from __future__ import annotations +from collections.abc import Iterable from socket import AF_INET, AF_INET6, inet_pton -from typing import Iterable, List, Optional, Union from zope.interface import implementer @@ -182,7 +182,7 @@ class FileDescriptor(_ConsumerMixin, _LogOwner): SEND_LIMIT = 128 * 1024 - def __init__(self, reactor: Optional[interfaces.IReactorFDSet] = None): + def __init__(self, reactor: interfaces.IReactorFDSet | None = None): """ @param reactor: An L{IReactorFDSet} provider which this descriptor will use to get readable and writeable event notifications. If no value @@ -191,10 +191,10 @@ class FileDescriptor(_ConsumerMixin, _LogOwner): if not reactor: from twisted.internet import reactor as _reactor - reactor = _reactor # type: ignore[assignment] + reactor = _reactor self.reactor = reactor # will be added to dataBuffer in doWrite - self._tempDataBuffer: List[bytes] = [] + self._tempDataBuffer: list[bytes] = [] self._tempDataLen = 0 def connectionLost(self, reason): @@ -215,7 +215,7 @@ class FileDescriptor(_ConsumerMixin, _LogOwner): self.stopReading() self.stopWriting() - def writeSomeData(self, data: bytes) -> Union[int, BaseException]: + def writeSomeData(self, data: bytes) -> int | BaseException: """ Write as much as possible of the given data, immediately. diff --git a/contrib/python/Twisted/py3/twisted/internet/address.py b/contrib/python/Twisted/py3/twisted/internet/address.py index d0ab1f69290..b832270b296 100644 --- a/contrib/python/Twisted/py3/twisted/internet/address.py +++ b/contrib/python/Twisted/py3/twisted/internet/address.py @@ -7,13 +7,12 @@ Address objects for network connections. import os -from typing import Optional, Union +from typing import Literal, Optional, Union from warnings import warn from zope.interface import implementer import attr -from typing_extensions import Literal from twisted.internet.interfaces import IAddress from twisted.python.filepath import _asFilesystemBytes, _coerceToFilesystemEncoding diff --git a/contrib/python/Twisted/py3/twisted/internet/asyncioreactor.py b/contrib/python/Twisted/py3/twisted/internet/asyncioreactor.py index cd1cf65f05d..6e06544e6b4 100644 --- a/contrib/python/Twisted/py3/twisted/internet/asyncioreactor.py +++ b/contrib/python/Twisted/py3/twisted/internet/asyncioreactor.py @@ -6,11 +6,11 @@ asyncio-based reactor implementation. """ +from __future__ import annotations import errno import sys -from asyncio import AbstractEventLoop, get_event_loop -from typing import Dict, Optional, Type +from asyncio import AbstractEventLoop, get_running_loop, new_event_loop, set_event_loop from zope.interface import implementer @@ -45,9 +45,13 @@ class AsyncioSelectorReactor(PosixReactorBase): _asyncClosed = False _log = Logger() - def __init__(self, eventloop: Optional[AbstractEventLoop] = None): + def __init__(self, eventloop: AbstractEventLoop | None = None): if eventloop is None: - _eventloop: AbstractEventLoop = get_event_loop() + try: + _eventloop: AbstractEventLoop = get_running_loop() + except RuntimeError: + _eventloop = new_event_loop() + set_event_loop(_eventloop) else: _eventloop = eventloop @@ -63,8 +67,8 @@ class AsyncioSelectorReactor(PosixReactorBase): ) self._asyncioEventloop: AbstractEventLoop = _eventloop - self._writers: Dict[Type[FileDescriptor], int] = {} - self._readers: Dict[Type[FileDescriptor], int] = {} + self._writers: dict[type[FileDescriptor], int] = {} + self._readers: dict[type[FileDescriptor], int] = {} self._continuousPolling = _ContinuousPolling(self) self._scheduledAt = None diff --git a/contrib/python/Twisted/py3/twisted/internet/base.py b/contrib/python/Twisted/py3/twisted/internet/base.py index 2c38b80b0ca..418a7f1559a 100644 --- a/contrib/python/Twisted/py3/twisted/internet/base.py +++ b/contrib/python/Twisted/py3/twisted/internet/base.py @@ -6,28 +6,17 @@ Very basic functionality for a Reactor implementation. """ +from __future__ import annotations import builtins import socket # needed only for sync-dns import warnings from abc import ABC, abstractmethod +from collections.abc import Sequence from heapq import heapify, heappop, heappush from traceback import format_stack from types import FrameType -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - List, - NewType, - Optional, - Sequence, - Set, - Tuple, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, Callable, NewType, cast from zope.interface import classImplements, implementer @@ -55,7 +44,7 @@ from twisted.internet.interfaces import ( IWriteDescriptor, _ISupportsExitSignalCapturing, ) -from twisted.internet.protocol import ClientFactory +from twisted.internet.protocol import ClientFactory, P from twisted.logger import Logger from twisted.python import reflect from twisted.python.failure import Failure @@ -89,19 +78,19 @@ class DelayedCall: # enable .debug to record creator call stack, and it will be logged if # an exception occurs while the function is being run debug = False - _repr: Optional[str] = None + _repr: str | None = None # In debug mode, the call stack at the time of instantiation. - creator: Optional[Sequence[str]] = None + creator: Sequence[str] | None = None def __init__( self, time: float, func: Callable[..., Any], args: Sequence[object], - kw: Dict[str, object], - cancel: Callable[["DelayedCall"], None], - reset: Callable[["DelayedCall"], None], + kw: dict[str, object], + cancel: Callable[[DelayedCall], None], + reset: Callable[[DelayedCall], None], seconds: Callable[[], float] = runtimeSeconds, ) -> None: """ @@ -213,7 +202,7 @@ class DelayedCall: """ return not (self.cancelled or self.called) - def __le__(self, other: "DelayedCall") -> bool: + def __le__(self, other: DelayedCall) -> bool: """ Implement C{<=} operator between two L{DelayedCall} instances. @@ -222,7 +211,7 @@ class DelayedCall: """ return self.time <= other.time - def __lt__(self, other: "DelayedCall") -> bool: + def __lt__(self, other: DelayedCall) -> bool: """ Implement C{<} operator between two L{DelayedCall} instances. @@ -293,10 +282,10 @@ class ThreadedResolver: delivered. """ - def __init__(self, reactor: "ReactorBase") -> None: + def __init__(self, reactor: ReactorBase) -> None: self.reactor = reactor - self._runningQueries: Dict[ - Deferred[str], Tuple[Deferred[str], IDelayedCall] + self._runningQueries: dict[ + Deferred[str], tuple[Deferred[str], IDelayedCall] ] = {} def _fail(self, name: str, err: str) -> Failure: @@ -309,7 +298,7 @@ class ThreadedResolver: userDeferred.errback(self._fail(name, "timeout error")) def _checkTimeout( - self, result: Union[str, Failure], name: str, lookupDeferred: Deferred[str] + self, result: str | Failure, name: str, lookupDeferred: Deferred[str] ) -> None: try: userDeferred, cancelCall = self._runningQueries[lookupDeferred] @@ -371,12 +360,12 @@ class BlockingResolver: _ThreePhaseEventTriggerCallable = Callable[..., Any] -_ThreePhaseEventTrigger = Tuple[ - _ThreePhaseEventTriggerCallable, Tuple[object, ...], Dict[str, object] +_ThreePhaseEventTrigger = tuple[ + _ThreePhaseEventTriggerCallable, tuple[object, ...], dict[str, object] ] _ThreePhaseEventTriggerHandle = NewType( "_ThreePhaseEventTriggerHandle", - Tuple[str, _ThreePhaseEventTriggerCallable, Tuple[object, ...], Dict[str, object]], + tuple[str, _ThreePhaseEventTriggerCallable, tuple[object, ...], dict[str, object]], ) @@ -411,9 +400,9 @@ class _ThreePhaseEvent: """ def __init__(self) -> None: - self.before: List[_ThreePhaseEventTrigger] = [] - self.during: List[_ThreePhaseEventTrigger] = [] - self.after: List[_ThreePhaseEventTrigger] = [] + self.before: list[_ThreePhaseEventTrigger] = [] + self.during: list[_ThreePhaseEventTrigger] = [] + self.after: list[_ThreePhaseEventTrigger] = [] self.state = "BASE" def addTrigger( @@ -494,7 +483,7 @@ class _ThreePhaseEvent: """ self.state = "BEFORE" self.finishedBefore = [] - beforeResults: List[Deferred[object]] = [] + beforeResults: list[Deferred[object]] = [] while self.before: callable, args, kwargs = self.before.pop(0) self.finishedBefore.append((callable, args, kwargs)) @@ -568,8 +557,8 @@ class PluggableResolverMixin: return self._nameResolver -_SystemEventID = NewType("_SystemEventID", Tuple[str, _ThreePhaseEventTriggerHandle]) -_ThreadCall = Tuple[Callable[..., Any], Tuple[object, ...], Dict[str, object]] +_SystemEventID = NewType("_SystemEventID", tuple[str, _ThreePhaseEventTriggerHandle]) +_ThreadCall = tuple[Callable[..., Any], tuple[object, ...], dict[str, object]] _DEFAULT_DELAYED_CALL_LOGGING_HANDLER = _log.failureHandler("while handling timed call") @@ -622,10 +611,10 @@ class ReactorBase(PluggableResolverMixin): def __init__(self) -> None: super().__init__() - self.threadCallQueue: List[_ThreadCall] = [] - self._eventTriggers: Dict[str, _ThreePhaseEvent] = {} - self._pendingTimedCalls: List[DelayedCall] = [] - self._newTimedCalls: List[DelayedCall] = [] + self.threadCallQueue: list[_ThreadCall] = [] + self._eventTriggers: dict[str, _ThreePhaseEvent] = {} + self._pendingTimedCalls: list[DelayedCall] = [] + self._newTimedCalls: list[DelayedCall] = [] self._cancellations = 0 self.running = False self._started = False @@ -633,7 +622,7 @@ class ReactorBase(PluggableResolverMixin): self._startedBefore = False # reactor internal readers, e.g. the waker. # Using Any as the type here… unable to find a suitable defined interface - self._internalReaders: Set[Any] = set() + self._internalReaders: set[Any] = set() self.waker: Any = None # Arrange for the running attribute to change to True at the right time @@ -726,7 +715,7 @@ class ReactorBase(PluggableResolverMixin): # if the waker isn't installed, the reactor isn't running, and # therefore doesn't need to be woken up - def doIteration(self, delay: Optional[float]) -> None: + def doIteration(self, delay: float | None) -> None: """ Do one iteration over the readers and writers which have been added. """ @@ -754,17 +743,17 @@ class ReactorBase(PluggableResolverMixin): reflect.qual(self.__class__) + " did not implement removeWriter" ) - def removeAll(self) -> List[Union[IReadDescriptor, IWriteDescriptor]]: + def removeAll(self) -> list[IReadDescriptor | IWriteDescriptor]: raise NotImplementedError( reflect.qual(self.__class__) + " did not implement removeAll" ) - def getReaders(self) -> List[IReadDescriptor]: + def getReaders(self) -> list[IReadDescriptor]: raise NotImplementedError( reflect.qual(self.__class__) + " did not implement getReaders" ) - def getWriters(self) -> List[IWriteDescriptor]: + def getWriters(self) -> list[IWriteDescriptor]: raise NotImplementedError( reflect.qual(self.__class__) + " did not implement getWriters" ) @@ -804,7 +793,7 @@ class ReactorBase(PluggableResolverMixin): self.running = False self.addSystemEventTrigger("during", "startup", self._reallyStartRunning) - def sigInt(self, number: int, frame: Optional[FrameType] = None) -> None: + def sigInt(self, number: int, frame: FrameType | None = None) -> None: """ Handle a SIGINT interrupt. @@ -815,7 +804,7 @@ class ReactorBase(PluggableResolverMixin): self.callFromThread(self.stop) self._exitSignal = number - def sigBreak(self, number: int, frame: Optional[FrameType] = None) -> None: + def sigBreak(self, number: int, frame: FrameType | None = None) -> None: """ Handle a SIGBREAK interrupt. @@ -826,7 +815,7 @@ class ReactorBase(PluggableResolverMixin): self.callFromThread(self.stop) self._exitSignal = number - def sigTerm(self, number: int, frame: Optional[FrameType] = None) -> None: + def sigTerm(self, number: int, frame: FrameType | None = None) -> None: """ Handle a SIGTERM interrupt. @@ -892,7 +881,7 @@ class ReactorBase(PluggableResolverMixin): def callWhenRunning( self, callable: Callable[..., Any], *args: object, **kwargs: object - ) -> Optional[_SystemEventID]: + ) -> _SystemEventID | None: """ See twisted.internet.interfaces.IReactorCore.callWhenRunning. """ @@ -1021,7 +1010,7 @@ class ReactorBase(PluggableResolverMixin): heappush(self._pendingTimedCalls, call) self._newTimedCalls = [] - def timeout(self) -> Optional[float]: + def timeout(self) -> float | None: """ Determine the longest time the reactor may sleep (waiting on I/O notification, perhaps) before it must wake up to service a time-related @@ -1235,7 +1224,7 @@ class BaseConnector(ABC): factoryStarted = 0 def __init__( - self, factory: ClientFactory, timeout: float, reactor: ReactorBase + self, factory: ClientFactory[P], timeout: float, reactor: ReactorBase ) -> None: self.state = "disconnected" self.reactor = reactor @@ -1251,7 +1240,7 @@ class BaseConnector(ABC): self.transport.loseConnection() @abstractmethod - def _makeTransport(self) -> "Client": + def _makeTransport(self) -> Client: pass def connect(self) -> None: @@ -1263,7 +1252,7 @@ class BaseConnector(ABC): if not self.factoryStarted: self.factory.doStart() self.factoryStarted = 1 - self.transport: Optional[Client] = self._makeTransport() + self.transport: Client | None = self._makeTransport() if self.timeout is not None: self.timeoutID = self.reactor.callLater( self.timeout, self.transport.failIfNotConnected, error.TimeoutError() @@ -1288,7 +1277,7 @@ class BaseConnector(ABC): pass del self.timeoutID - def buildProtocol(self, addr: IAddress) -> Optional[IProtocol]: + def buildProtocol(self, addr: IAddress) -> IProtocol | None: self.state = "connected" self.cancelTimeout() return self.factory.buildProtocol(addr) @@ -1340,9 +1329,9 @@ class BasePort(abstract.FileDescriptor): fdesc._setCloseOnExec(s.fileno()) return s - def doWrite(self) -> Optional[Failure]: + def doWrite(self) -> Failure | None: """Raises a RuntimeError""" raise RuntimeError("doWrite called on a %s" % reflect.qual(self.__class__)) -__all__: List[str] = [] +__all__: list[str] = [] diff --git a/contrib/python/Twisted/py3/twisted/internet/defer.py b/contrib/python/Twisted/py3/twisted/internet/defer.py index 1892eb483ee..d88014215fc 100644 --- a/contrib/python/Twisted/py3/twisted/internet/defer.py +++ b/contrib/python/Twisted/py3/twisted/internet/defer.py @@ -14,27 +14,19 @@ import traceback import warnings from abc import ABC, abstractmethod from asyncio import AbstractEventLoop, Future, iscoroutine +from collections.abc import Awaitable, Coroutine, Generator, Iterable, Mapping, Sequence from contextvars import Context as _Context, copy_context as _copy_context from enum import Enum from functools import wraps -from sys import exc_info, implementation +from sys import exc_info from types import CoroutineType, GeneratorType, MappingProxyType, TracebackType from typing import ( TYPE_CHECKING, Any, - Awaitable, Callable, - Coroutine, - Generator, Generic, - Iterable, - List, - Mapping, + Literal, NoReturn, - Optional, - Sequence, - Tuple, - Type, TypeVar, Union, cast, @@ -43,12 +35,12 @@ from typing import ( import attr from incremental import Version -from typing_extensions import Concatenate, Literal, ParamSpec, Self +from typing_extensions import Concatenate, ParamSpec, Self, TypeAlias from twisted.internet.interfaces import IDelayedCall, IReactorTime from twisted.logger import Logger from twisted.python import lockfile -from twisted.python.compat import _PYPY, cmp, comparable +from twisted.python.compat import cmp, comparable from twisted.python.deprecate import deprecated, deprecatedProperty, warnAboutFunction from twisted.python.failure import Failure, _extraneous @@ -58,9 +50,6 @@ log = Logger() _T = TypeVar("_T") _P = ParamSpec("_P") -# See use in _inlineCallbacks for explanation and removal timeline. -_oldPypyStack = _PYPY and implementation.version < (7, 3, 14) - class AlreadyCalledError(Exception): """ @@ -101,7 +90,7 @@ def logError(err: Failure) -> Failure: return err -def succeed(result: _T) -> "Deferred[_T]": +def succeed(result: _T) -> Deferred[_T]: """ Return a L{Deferred} that has already had C{.callback(result)} called. @@ -125,7 +114,7 @@ def succeed(result: _T) -> "Deferred[_T]": return d -def fail(result: Optional[Union[Failure, BaseException]] = None) -> "Deferred[Any]": +def fail(result: Failure | BaseException | None = None) -> Deferred[Any]: """ Return a L{Deferred} that has already had C{.errback(result)} called. @@ -143,7 +132,7 @@ def fail(result: Optional[Union[Failure, BaseException]] = None) -> "Deferred[An def execute( callable: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs -) -> "Deferred[_T]": +) -> Deferred[_T]: """ Create a L{Deferred} from a callable and arguments. @@ -162,7 +151,7 @@ def execute( @overload def maybeDeferred( f: Callable[_P, Deferred[_T]], *args: _P.args, **kwargs: _P.kwargs -) -> "Deferred[_T]": +) -> Deferred[_T]: ... @@ -171,22 +160,22 @@ def maybeDeferred( f: Callable[_P, Coroutine[Deferred[Any], Any, _T]], *args: _P.args, **kwargs: _P.kwargs, -) -> "Deferred[_T]": +) -> Deferred[_T]: ... @overload def maybeDeferred( f: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs -) -> "Deferred[_T]": +) -> Deferred[_T]: ... def maybeDeferred( - f: Callable[_P, Union[Deferred[_T], Coroutine[Deferred[Any], Any, _T], _T]], + f: Callable[_P, Deferred[_T] | Coroutine[Deferred[Any], Any, _T] | _T], *args: _P.args, **kwargs: _P.kwargs, -) -> "Deferred[_T]": +) -> Deferred[_T]: """ Invoke a function that may or may not return a L{Deferred} or coroutine. @@ -249,7 +238,7 @@ def maybeDeferred( Version("Twisted", 17, 1, 0), replacement="twisted.internet.defer.Deferred.addTimeout", ) -def timeout(deferred: "Deferred[object]") -> None: +def timeout(deferred: Deferred[object]) -> None: deferred.errback(Failure(TimeoutError("Callback timed out"))) @@ -326,15 +315,15 @@ DeferredCallback = Callable[..., object] # Callable[[Failure], object] is next best, but disallows valid callback signatures DeferredErrback = Callable[..., object] -_CallbackOrderedArguments = Tuple[object, ...] -_CallbackKeywordArguments = Mapping[str, object] -_CallbackChain = Tuple[ - Tuple[ +_CallbackOrderedArguments: TypeAlias = tuple[object, ...] +_CallbackKeywordArguments: TypeAlias = Mapping[str, object] +_CallbackChain: TypeAlias = tuple[ + tuple[ Union[DeferredCallback, Literal[_Sentinel._CONTINUE]], _CallbackOrderedArguments, _CallbackKeywordArguments, ], - Tuple[ + tuple[ Union[DeferredErrback, DeferredCallback, Literal[_Sentinel._CONTINUE]], _CallbackOrderedArguments, _CallbackKeywordArguments, @@ -353,9 +342,9 @@ class DebugInfo: Deferred debug helper. """ - failResult: Optional[Failure] = None - creator: Optional[List[str]] = None - invoker: Optional[List[str]] = None + failResult: Failure | None = None + creator: list[str] | None = None + invoker: list[str] | None = None def _getDebugTracebacks(self) -> str: info = "" @@ -429,7 +418,7 @@ class Deferred(Awaitable[_SelfResultT]): called = False paused = 0 - _debugInfo: Optional[DebugInfo] = None + _debugInfo: DebugInfo | None = None _suppressAlreadyCalled = False # Are we currently running a user-installed callback? Meant to prevent @@ -441,10 +430,10 @@ class Deferred(Awaitable[_SelfResultT]): # sets it directly. debug = False - _chainedTo: "Optional[Deferred[Any]]" = None + _chainedTo: Deferred[Any] | None = None def __init__( - self, canceller: Optional[Callable[["Deferred[Any]"], None]] = None + self, canceller: Callable[[Deferred[Any]], None] | None = None ) -> None: """ Initialize a L{Deferred}. @@ -469,42 +458,42 @@ class Deferred(Awaitable[_SelfResultT]): @type canceller: a 1-argument callable which takes a L{Deferred}. The return result is ignored. """ - self._callbacks: List[_CallbackChain] = [] + self._callbacks: list[_CallbackChain] = [] self._canceller = canceller if self.debug: self._debugInfo = DebugInfo() self._debugInfo.creator = traceback.format_stack()[:-1] @deprecatedProperty(Version("Twisted", 25, 5, 0)) - def callbacks(self) -> List[_CallbackChain]: + def callbacks(self) -> list[_CallbackChain]: return self._callbacks def addCallbacks( self, - callback: Union[ - Callable[..., _NextResultT], - Callable[..., Deferred[_NextResultT]], - Callable[..., Failure], - Callable[ + callback: ( + Callable[..., _NextResultT] + | Callable[..., Deferred[_NextResultT]] + | Callable[..., Failure] + | Callable[ ..., - Union[_NextResultT, Deferred[_NextResultT], Failure], - ], - ], - errback: Union[ - Callable[..., _NextResultT], - Callable[..., Deferred[_NextResultT]], - Callable[..., Failure], - Callable[ + _NextResultT | Deferred[_NextResultT] | Failure, + ] + ), + errback: ( + Callable[..., _NextResultT] + | Callable[..., Deferred[_NextResultT]] + | Callable[..., Failure] + | Callable[ ..., - Union[_NextResultT, Deferred[_NextResultT], Failure], - ], - None, - ] = None, - callbackArgs: Tuple[Any, ...] = (), + _NextResultT | Deferred[_NextResultT] | Failure, + ] + | None + ) = None, + callbackArgs: tuple[Any, ...] = (), callbackKeywords: Mapping[str, Any] = _NONE_KWARGS, errbackArgs: _CallbackOrderedArguments = (), errbackKeywords: _CallbackKeywordArguments = _NONE_KWARGS, - ) -> "Deferred[_NextResultT]": + ) -> Deferred[_NextResultT]: """ Add a pair of callbacks (success and error) to this L{Deferred}. @@ -572,7 +561,7 @@ class Deferred(Awaitable[_SelfResultT]): self, callback: Callable[ Concatenate[_SelfResultT, _P], - Union[Failure, Deferred[_NextResultT]], + Failure | Deferred[_NextResultT], ], *args: _P.args, **kwargs: _P.kwargs, @@ -582,7 +571,7 @@ class Deferred(Awaitable[_SelfResultT]): @overload def addCallback( self, - callback: Callable[Concatenate[_SelfResultT, _P], Union[Failure, _NextResultT]], + callback: Callable[Concatenate[_SelfResultT, _P], Failure | _NextResultT], *args: _P.args, **kwargs: _P.kwargs, ) -> Deferred[_NextResultT]: @@ -602,7 +591,7 @@ class Deferred(Awaitable[_SelfResultT]): self, callback: Callable[ Concatenate[_SelfResultT, _P], - Union[Deferred[_NextResultT], _NextResultT], + Deferred[_NextResultT] | _NextResultT, ], *args: _P.args, **kwargs: _P.kwargs, @@ -618,7 +607,7 @@ class Deferred(Awaitable[_SelfResultT]): ) -> Deferred[_NextResultT]: ... - def addCallback(self, callback: Any, *args: Any, **kwargs: Any) -> "Deferred[Any]": + def addCallback(self, callback: Any, *args: Any, **kwargs: Any) -> Deferred[Any]: """ Convenience method for adding just a callback. @@ -639,7 +628,7 @@ class Deferred(Awaitable[_SelfResultT]): errback: Callable[Concatenate[Failure, _P], Deferred[_NextResultT]], *args: _P.args, **kwargs: _P.kwargs, - ) -> "Deferred[Union[_SelfResultT, _NextResultT]]": + ) -> Deferred[_SelfResultT | _NextResultT]: ... @overload @@ -648,7 +637,7 @@ class Deferred(Awaitable[_SelfResultT]): errback: Callable[Concatenate[Failure, _P], Failure], *args: _P.args, **kwargs: _P.kwargs, - ) -> "Deferred[Union[_SelfResultT]]": + ) -> Deferred[_SelfResultT]: ... @overload @@ -657,10 +646,10 @@ class Deferred(Awaitable[_SelfResultT]): errback: Callable[Concatenate[Failure, _P], _NextResultT], *args: _P.args, **kwargs: _P.kwargs, - ) -> "Deferred[Union[_SelfResultT, _NextResultT]]": + ) -> Deferred[_SelfResultT | _NextResultT]: ... - def addErrback(self, errback: Any, *args: Any, **kwargs: Any) -> "Deferred[Any]": + def addErrback(self, errback: Any, *args: Any, **kwargs: Any) -> Deferred[Any]: """ Convenience method for adding just an errback. @@ -678,7 +667,7 @@ class Deferred(Awaitable[_SelfResultT]): @overload def addBoth( self, - callback: Callable[Concatenate[Union[_SelfResultT, Failure], _P], Failure], + callback: Callable[Concatenate[_SelfResultT | Failure, _P], Failure], *args: _P.args, **kwargs: _P.kwargs, ) -> Deferred[_NextResultT]: @@ -688,8 +677,8 @@ class Deferred(Awaitable[_SelfResultT]): def addBoth( self, callback: Callable[ - Concatenate[Union[_SelfResultT, Failure], _P], - Union[Failure, Deferred[_NextResultT]], + Concatenate[_SelfResultT | Failure, _P], + Failure | Deferred[_NextResultT], ], *args: _P.args, **kwargs: _P.kwargs, @@ -700,7 +689,7 @@ class Deferred(Awaitable[_SelfResultT]): def addBoth( self, callback: Callable[ - Concatenate[Union[_SelfResultT, Failure], _P], Union[Failure, _NextResultT] + Concatenate[_SelfResultT | Failure, _P], Failure | _NextResultT ], *args: _P.args, **kwargs: _P.kwargs, @@ -711,7 +700,7 @@ class Deferred(Awaitable[_SelfResultT]): def addBoth( self, callback: Callable[ - Concatenate[Union[_SelfResultT, Failure], _P], Deferred[_NextResultT] + Concatenate[_SelfResultT | Failure, _P], Deferred[_NextResultT] ], *args: _P.args, **kwargs: _P.kwargs, @@ -722,8 +711,8 @@ class Deferred(Awaitable[_SelfResultT]): def addBoth( self, callback: Callable[ - Concatenate[Union[_SelfResultT, Failure], _P], - Union[Deferred[_NextResultT], _NextResultT], + Concatenate[_SelfResultT | Failure, _P], + Deferred[_NextResultT] | _NextResultT, ], *args: _P.args, **kwargs: _P.kwargs, @@ -733,7 +722,7 @@ class Deferred(Awaitable[_SelfResultT]): @overload def addBoth( self, - callback: Callable[Concatenate[Union[_SelfResultT, Failure], _P], _NextResultT], + callback: Callable[Concatenate[_SelfResultT | Failure, _P], _NextResultT], *args: _P.args, **kwargs: _P.kwargs, ) -> Deferred[_NextResultT]: @@ -748,7 +737,7 @@ class Deferred(Awaitable[_SelfResultT]): ) -> Deferred[_SelfResultT]: ... - def addBoth(self, callback: Any, *args: Any, **kwargs: Any) -> "Deferred[Any]": + def addBoth(self, callback: Any, *args: Any, **kwargs: Any) -> Deferred[Any]: """ Convenience method for adding a single callable as both a callback and an errback. @@ -771,13 +760,14 @@ class Deferred(Awaitable[_SelfResultT]): self, timeout: float, clock: IReactorTime, - onTimeoutCancel: Optional[ + onTimeoutCancel: None + | ( Callable[ - [Union[_SelfResultT, Failure], float], - Union[_NextResultT, Failure], + [_SelfResultT | Failure, float], + _NextResultT | Failure, ] - ] = None, - ) -> "Deferred[Union[_SelfResultT, _NextResultT]]": + ) = None, + ) -> Deferred[_SelfResultT | _NextResultT]: """ Time out this L{Deferred} by scheduling it to be cancelled after C{timeout} seconds. @@ -815,8 +805,8 @@ class Deferred(Awaitable[_SelfResultT]): delayedCall = clock.callLater(timeout, timeItOut) def convertCancelled( - result: Union[_SelfResultT, Failure], - ) -> Union[_SelfResultT, _NextResultT, Failure]: + result: _SelfResultT | Failure, + ) -> _SelfResultT | _NextResultT | Failure: # if C{deferred} was timed out, call the translation function, # if provided, otherwise just use L{cancelledToTimedOutError} if timedOut[0]: @@ -833,12 +823,12 @@ class Deferred(Awaitable[_SelfResultT]): # Note: Mypy cannot infer this type, apparently thanks to the ambiguity # of _SelfResultT / _NextResultT both being unbound. Explicitly # annotating it seems to do the trick though. - converted: Deferred[Union[_SelfResultT, _NextResultT]] = self.addBoth( + converted: Deferred[_SelfResultT | _NextResultT] = self.addBoth( convertCancelled ) return converted.addBoth(cancelTimeout) - def chainDeferred(self, d: "Deferred[_SelfResultT]") -> "Deferred[None]": + def chainDeferred(self, d: Deferred[_SelfResultT]) -> Deferred[None]: """ Chain another L{Deferred} to this L{Deferred}. @@ -865,7 +855,7 @@ class Deferred(Awaitable[_SelfResultT]): d._chainedTo = self return self.addCallbacks(d.callback, d.errback) - def callback(self, result: Union[_SelfResultT, Failure]) -> None: + def callback(self, result: _SelfResultT | Failure) -> None: """ Run all success callbacks that have been added to this L{Deferred}. @@ -890,7 +880,7 @@ class Deferred(Awaitable[_SelfResultT]): """ self._startRunCallbacks(result) - def errback(self, fail: Optional[Union[Failure, BaseException]] = None) -> None: + def errback(self, fail: Failure | BaseException | None = None) -> None: """ Run all error callbacks that have been added to this L{Deferred}. @@ -1039,7 +1029,7 @@ class Deferred(Awaitable[_SelfResultT]): # added its _continuation() to the callbacks list of a second Deferred # and then that second Deferred being fired. ie, if ever had _chainedTo # set to something other than None, you might end up on this stack. - chain: List[Deferred[Any]] = [self] + chain: list[Deferred[Any]] = [self] while chain: current = chain[-1] @@ -1198,7 +1188,7 @@ class Deferred(Awaitable[_SelfResultT]): __await__ = __iter__ - def asFuture(self, loop: AbstractEventLoop) -> "Future[_SelfResultT]": + def asFuture(self, loop: AbstractEventLoop) -> Future[_SelfResultT]: """ Adapt this L{Deferred} into a L{Future} which is bound to C{loop}. @@ -1215,7 +1205,7 @@ class Deferred(Awaitable[_SelfResultT]): """ future = loop.create_future() - def checkCancel(futureAgain: "Future[_SelfResultT]") -> None: + def checkCancel(futureAgain: Future[_SelfResultT]) -> None: if futureAgain.cancelled(): self.cancel() @@ -1233,7 +1223,7 @@ class Deferred(Awaitable[_SelfResultT]): return future @classmethod - def fromFuture(cls, future: "Future[_SelfResultT]") -> "Deferred[_SelfResultT]": + def fromFuture(cls, future: Future[_SelfResultT]) -> Deferred[_SelfResultT]: """ Adapt a L{Future} to a L{Deferred}. @@ -1270,7 +1260,7 @@ class Deferred(Awaitable[_SelfResultT]): def uncancel( result: _SelfResultT, - ) -> Union[_SelfResultT, Deferred[_SelfResultT]]: + ) -> _SelfResultT | Deferred[_SelfResultT]: if result is futureCancel: nonlocal actual actual = Deferred() @@ -1285,11 +1275,8 @@ class Deferred(Awaitable[_SelfResultT]): @classmethod def fromCoroutine( cls, - coro: Union[ - Coroutine[Deferred[Any], Any, _T], - Generator[Deferred[Any], Any, _T], - ], - ) -> "Deferred[_T]": + coro: (Coroutine[Deferred[Any], Any, _T] | Generator[Deferred[Any], Any, _T]), + ) -> Deferred[_T]: """ Schedule the execution of a coroutine that awaits on L{Deferred}s, wrapping it in a L{Deferred} that will fire on success/failure of the @@ -1332,7 +1319,7 @@ class Deferred(Awaitable[_SelfResultT]): return _cancellableInlineCallbacks(coro) raise NotACoroutineError(f"{coro!r} is not a coroutine") - def __init_subclass__(cls: Type[Deferred[Any]], **kwargs: Any): + def __init_subclass__(cls: type[Deferred[Any]], **kwargs: Any): # Whenever a subclass is created, record it in L{_DEFERRED_SUBCLASSES} # so we can emulate C{isinstance()} more efficiently. _DEFERRED_SUBCLASSES.append(cls) @@ -1342,11 +1329,11 @@ _DEFERRED_SUBCLASSES = [Deferred] def ensureDeferred( - coro: Union[ - Coroutine[Deferred[Any], Any, _T], - Generator[Deferred[Any], Any, _T], - Deferred[_T], - ] + coro: ( + Coroutine[Deferred[Any], Any, _T] + | Generator[Deferred[Any], Any, _T] + | Deferred[_T] + ), ) -> Deferred[_T]: """ Schedule the execution of a coroutine that awaits/yields from L{Deferred}s, @@ -1413,9 +1400,9 @@ class FirstError(Exception): return -1 -_DeferredListSingleResultT = Tuple[_SelfResultT, int] -_DeferredListResultItemT = Tuple[bool, _SelfResultT] -_DeferredListResultListT = List[_DeferredListResultItemT[_SelfResultT]] +_DeferredListSingleResultT = tuple[_SelfResultT, int] +_DeferredListResultItemT = tuple[bool, _SelfResultT] +_DeferredListResultListT = list[_DeferredListResultItemT[_SelfResultT]] if TYPE_CHECKING: # The result type is different depending on whether fireOnOneCallback @@ -1446,10 +1433,10 @@ if TYPE_CHECKING: fireOnOneCallback: bool = False, fireOnOneErrback: bool = False, consumeErrors: bool = False, - ) -> Union[ - Deferred[_DeferredListSingleResultT[_SelfResultT]], - Deferred[_DeferredListResultListT[_SelfResultT]], - ]: + ) -> ( + Deferred[_DeferredListSingleResultT[_SelfResultT]] + | Deferred[_DeferredListResultListT[_SelfResultT]] + ): ... DeferredList = _DeferredList @@ -1521,7 +1508,7 @@ class DeferredList( # type: ignore[no-redef] # noqa:F811 # Note this contains optional result values as the DeferredList is # processing its results, even though the callback result will not, # which is why we aren't using _DeferredListResultListT here. - self.resultList: List[Optional[_DeferredListResultItemT[Any]]] = [None] * len( + self.resultList: list[_DeferredListResultItemT[Any] | None] = [None] * len( self._deferredList ) """ @@ -1555,7 +1542,7 @@ class DeferredList( # type: ignore[no-redef] # noqa:F811 def _cbDeferred( self, result: _SelfResultT, index: int, succeeded: bool - ) -> Optional[_SelfResultT]: + ) -> _SelfResultT | None: """ (internal) Callback for when one of my deferreds fires. """ @@ -1600,8 +1587,8 @@ class DeferredList( # type: ignore[no-redef] # noqa:F811 def _parseDeferredListResult( - resultList: List[_DeferredListResultItemT[_T]], fireOnOneErrback: bool = False, / -) -> List[_T]: + resultList: list[_DeferredListResultItemT[_T]], fireOnOneErrback: bool = False, / +) -> list[_T]: if __debug__: for result in resultList: assert result is not None @@ -1612,7 +1599,7 @@ def _parseDeferredListResult( def gatherResults( deferredList: Iterable[Deferred[_T]], consumeErrors: bool = False -) -> Deferred[List[_T]]: +) -> Deferred[list[_T]]: """ Returns, via a L{Deferred}, a list with the results of the given L{Deferred}s - in effect, a "join" of multiple deferred operations. @@ -1645,7 +1632,7 @@ class FailureGroup(Exception): """ def __init__(self, failures: Sequence[Failure]) -> None: - super(FailureGroup, self).__init__() + super().__init__() self.failures = failures @@ -1664,7 +1651,7 @@ def race(ds: Sequence[Deferred[_T]]) -> Deferred[tuple[int, _T]]: # shouldn't be. Even though it "completed" it isn't really done - the # caller will still be using it for something. If we cancelled it, # cancellation could propagate down to them. - winner: Optional[Deferred[_T]] = None + winner: Deferred[_T] | None = None # The cancellation function for the Deferred this function returns. def cancel(result: Deferred[_T]) -> None: @@ -1773,16 +1760,13 @@ class _CancellationStatus(Generic[_SelfResultT]): """ deferred: Deferred[_SelfResultT] - waitingOn: Optional[Deferred[_SelfResultT]] = None + waitingOn: Deferred[_SelfResultT] | None = None def _gotResultInlineCallbacks( r: object, - waiting: List[Any], - gen: Union[ - Generator[Deferred[Any], Any, _T], - Coroutine[Deferred[Any], Any, _T], - ], + waiting: list[Any], + gen: (Generator[Deferred[Any], Any, _T] | Coroutine[Deferred[Any], Any, _T]), status: _CancellationStatus[_T], context: _Context, ) -> None: @@ -1806,10 +1790,7 @@ def _gotResultInlineCallbacks( @_extraneous def _inlineCallbacks( result: object, - gen: Union[ - Generator[Deferred[Any], Any, _T], - Coroutine[Deferred[Any], Any, _T], - ], + gen: (Generator[Deferred[Any], Any, _T] | Coroutine[Deferred[Any], Any, _T]), status: _CancellationStatus[_T], context: _Context, ) -> None: @@ -1839,7 +1820,7 @@ def _inlineCallbacks( # recursion. # waiting for result? # result - waiting: List[Any] = [True, None] + waiting: list[Any] = [True, None] stopIteration: bool = False callbackValue: Any = None @@ -1870,27 +1851,18 @@ def _inlineCallbacks( # The traceback starts in this frame (the one for # _inlineCallbacks); the next one down should be the application # code. - excInfo = exc_info() - assert excInfo is not None + appCodeTrace: TracebackType - traceback = excInfo[2] - assert traceback is not None - - appCodeTrace = traceback.tb_next - assert appCodeTrace is not None - - if _oldPypyStack: - # PyPy versions through 7.3.13 add an extra frame; 7.3.14 fixed - # this discrepancy with CPython. This code can be removed once - # we no longer need to support PyPy 7.3.13 or older. - appCodeTrace = appCodeTrace.tb_next - assert appCodeTrace is not None + # Here and everywhere below, we ignore the theoretical possibility + # that this is the end of the traceback (it can effectively never + # be). + appCodeTrace = exc_info()[2].tb_next # type:ignore[union-attr,assignment] if isFailure: # If we invoked this generator frame by throwing an exception # into it, then throwExceptionIntoGenerator will consume an # additional stack frame itself, so we need to skip that too. - appCodeTrace = appCodeTrace.tb_next + appCodeTrace = appCodeTrace.tb_next # type:ignore[assignment] assert appCodeTrace is not None # Now that we've identified the frame being exited by the @@ -2026,10 +1998,7 @@ def _handleCancelInlineCallbacks( def _cancellableInlineCallbacks( - gen: Union[ - Generator[Deferred[Any], object, _T], - Coroutine[Deferred[Any], object, _T], - ] + gen: (Generator[Deferred[Any], object, _T] | Coroutine[Deferred[Any], object, _T]), ) -> Deferred[_T]: """ Make an C{@}L{inlineCallbacks} cancellable. @@ -2056,7 +2025,7 @@ class _InternalInlineCallbacksCancelledError(Exception): def inlineCallbacks( - f: Callable[_P, Generator[Deferred[Any], Any, _T]] + f: Callable[_P, Generator[Deferred[Any], Any, _T]], ) -> Callable[_P, Deferred[_T]]: """ L{inlineCallbacks} helps you write L{Deferred}-using code that looks like a @@ -2143,7 +2112,7 @@ def inlineCallbacks( class _ConcurrencyPrimitive(ABC): def __init__(self: Self) -> None: - self.waiting: List[Deferred[Self]] = [] + self.waiting: list[Deferred[Self]] = [] def _releaseAndReturn(self, r: _T) -> _T: self.release() @@ -2178,7 +2147,7 @@ class _ConcurrencyPrimitive(ABC): def run( self: Self, /, - f: Callable[_P, Union[Deferred[_T], Coroutine[Deferred[Any], Any, _T], _T]], + f: Callable[_P, Deferred[_T] | Coroutine[Deferred[Any], Any, _T] | _T], *args: _P.args, **kwargs: _P.kwargs, ) -> Deferred[_T]: @@ -2213,9 +2182,9 @@ class _ConcurrencyPrimitive(ABC): def __aexit__( self, - __exc_type: Optional[Type[BaseException]], - __exc_value: Optional[BaseException], - __traceback: Optional[TracebackType], + __exc_type: type[BaseException] | None, + __exc_value: BaseException | None, + __traceback: TracebackType | None, ) -> Deferred[Literal[False]]: self.release() # We return False to indicate that we have not consumed the @@ -2388,11 +2357,9 @@ class DeferredQueue(Generic[_T]): for no limit. """ - def __init__( - self, size: Optional[int] = None, backlog: Optional[int] = None - ) -> None: - self.waiting: List[Deferred[_T]] = [] - self.pending: List[_T] = [] + def __init__(self, size: int | None = None, backlog: int | None = None) -> None: + self.waiting: list[Deferred[_T]] = [] + self.pending: list[_T] = [] self.size = size self.backlog = backlog @@ -2466,10 +2433,10 @@ class DeferredFilesystemLock(lockfile.FilesystemLock): """ _interval = 1 - _tryLockCall: Optional[IDelayedCall] = None - _timeoutCall: Optional[IDelayedCall] = None + _tryLockCall: IDelayedCall | None = None + _timeoutCall: IDelayedCall | None = None - def __init__(self, name: str, scheduler: Optional[IReactorTime] = None) -> None: + def __init__(self, name: str, scheduler: IReactorTime | None = None) -> None: """ @param name: The name of the lock to acquire @param scheduler: An object which provides L{IReactorTime} @@ -2483,7 +2450,7 @@ class DeferredFilesystemLock(lockfile.FilesystemLock): self._scheduler = scheduler - def deferUntilLocked(self, timeout: Optional[float] = None) -> Deferred[None]: + def deferUntilLocked(self, timeout: float | None = None) -> Deferred[None]: """ Wait until we acquire this lock. This method is not safe for concurrent use. @@ -2503,7 +2470,7 @@ class DeferredFilesystemLock(lockfile.FilesystemLock): ) ) - def _cancelLock(reason: Union[Failure, Exception]) -> None: + def _cancelLock(reason: Failure | Exception) -> None: """ Cancel a L{DeferredFilesystemLock.deferUntilLocked} call. diff --git a/contrib/python/Twisted/py3/twisted/internet/endpoints.py b/contrib/python/Twisted/py3/twisted/internet/endpoints.py index b73b2acc100..3da186aaae5 100644 --- a/contrib/python/Twisted/py3/twisted/internet/endpoints.py +++ b/contrib/python/Twisted/py3/twisted/internet/endpoints.py @@ -1,4 +1,4 @@ -# -*- test-case-name: twisted.internet.test.test_endpoints.HostnameEndpointMemoryIPv4ReactorTests.test_errorsLogged -*- +# -*- test-case-name: twisted.internet.test.test_endpoints -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. @@ -18,13 +18,15 @@ import os import re import socket import warnings -from typing import Any, Callable, Iterable, List, Optional, Sequence, Tuple, Type, Union +from collections.abc import Iterable, Sequence +from typing import Any, Callable, ClassVar, Protocol as TypingProtocol, TypeVar, Union from unicodedata import normalize from zope.interface import directlyProvides, implementer from constantly import NamedConstant, Names from incremental import Version +from typing_extensions import ParamSpec from twisted.internet import defer, error, fdesc, interfaces, threads from twisted.internet.abstract import isIPv6Address @@ -39,13 +41,17 @@ from twisted.internet.interfaces import ( IHostnameResolver, IHostResolution, IOpenSSLClientConnectionCreator, + IOpenSSLServerConnectionCreator, IProtocol, IProtocolFactory, + IReactorCore, IReactorPluggableNameResolver, IReactorSocket, + IReactorTime, IResolutionReceiver, IStreamClientEndpoint, IStreamClientEndpointStringParserWithReactor, + IStreamServerEndpoint, IStreamServerEndpointStringParser, ) from twisted.internet.protocol import ClientFactory, Factory, ProcessProtocol, Protocol @@ -72,7 +78,9 @@ from ._idna import _idnaBytes, _idnaText try: from OpenSSL.SSL import Error as SSLError - +except ImportError: + TLSMemoryBIOFactory = None +else: from twisted.internet.ssl import ( Certificate, CertificateOptions, @@ -81,10 +89,13 @@ try: optionsForClientTLS, trustRootFromCertificates, ) + from twisted.protocols._sni import ( + SNIConnectionCreator, + TLSServerEndpoint as _TLSServerEndpoint, + autoReloadingDirectoryOfPEMs, + ) from twisted.protocols.tls import TLSMemoryBIOFactory as _TLSMemoryBIOFactory -except ImportError: - TLSMemoryBIOFactory = None -else: + TLSMemoryBIOFactory = _TLSMemoryBIOFactory __all__ = [ @@ -104,10 +115,38 @@ __all__ = [ "HostnameEndpoint", "StandardErrorBehavior", "connectProtocol", + "wrapServerTLS", "wrapClientTLS", ] +_P = ParamSpec("_P") +_R = TypeVar("_R") + + +class _DeferToThreadFunction(TypingProtocol): + def __call__( + self, f: Callable[_P, _R], *args: _P.args, **kwds: _P.kwargs + ) -> defer.Deferred[_R]: + ... + + +def _staticmethod(f: Callable[_P, _R]) -> Callable[_P, _R]: + """ + Wraps generic function as a staticmethod while preserving its signature for mypy. + + Mypy cannot correctly infer the type when a generic function is directly passed to + staticmethod, resulting in an incorrect type annotation like 'staticmethod[Never, Never]'. + + This helper function ensures that mypy understands resulting type as a Callable + with the same signature as original function. + + See follow example on mypy playground to test if the workaround is still necessary: + https://mypy-play.net/?mypy=latest&python=3.12&gist=130fad37723b5d2d4685e6b2fadabe6a + """ + return staticmethod(f) + + class _WrappingProtocol(Protocol): """ Wrap another protocol in order to notify my user when a connection has @@ -204,7 +243,8 @@ class _WrappingFactory(ClientFactory): """ # Type is wrong. See https://twistedmatrix.com/trac/ticket/10005#ticket - protocol = _WrappingProtocol # type: ignore[assignment] + + protocol = _WrappingProtocol def __init__(self, wrappedFactory: IProtocolFactory) -> None: """ @@ -640,7 +680,9 @@ class TCP6ClientEndpoint: """ _getaddrinfo = staticmethod(socket.getaddrinfo) - _deferToThread = staticmethod(threads.deferToThread) + _deferToThread: ClassVar[_DeferToThreadFunction] = _staticmethod( + threads.deferToThread + ) _GAI_ADDRESS = 4 _GAI_ADDRESS_HOST = 0 @@ -699,15 +741,15 @@ class TCP6ClientEndpoint: return defer.fail() -_gairesult = List[ - Tuple[ +_gairesult = list[ + tuple[ socket.AddressFamily, socket.SocketKind, int, str, Union[ - Tuple[str, int], - Tuple[str, int, int, int], + tuple[str, int], + tuple[str, int, int, int], ], ] ] @@ -744,7 +786,7 @@ class _SimpleHostnameResolver: resolutionReceiver: IResolutionReceiver, hostName: str, portNumber: int = 0, - addressTypes: Optional[Sequence[Type[IAddress]]] = None, + addressTypes: Sequence[type[IAddress]] | None = None, transportSemantics: str = "TCP", ) -> IHostResolution: """ @@ -816,7 +858,9 @@ class HostnameEndpoint: """ _getaddrinfo = staticmethod(socket.getaddrinfo) - _deferToThread = staticmethod(threads.deferToThread) + _deferToThread: ClassVar[_DeferToThreadFunction] = _staticmethod( + threads.deferToThread + ) _DEFAULT_ATTEMPT_DELAY = 0.3 def __init__( @@ -1585,8 +1629,8 @@ class _SystemdParser: self, reactor: IReactorSocket, domain: str, - index: Optional[str] = None, - name: Optional[str] = None, + index: str | None = None, + name: str | None = None, ) -> AdoptedStreamServerEndpoint: """ Internal parser function for L{_parseServer} to convert the string @@ -2279,24 +2323,21 @@ class _WrapperServerEndpoint: def wrapClientTLS( connectionCreator: IOpenSSLClientConnectionCreator, wrappedEndpoint: IStreamClientEndpoint, -) -> _WrapperEndpoint: + clock: IReactorTime | None = None, +) -> IStreamClientEndpoint: """ - Wrap an endpoint which upgrades to TLS as soon as the connection is - established. + Wrap a stream client endpoint which such that it upgrades to TLS as soon as + the wrapped connection is established. @since: 16.0 @param connectionCreator: The TLS options to use when connecting; see L{twisted.internet.ssl.optionsForClientTLS} for how to construct this. - @type connectionCreator: - L{twisted.internet.interfaces.IOpenSSLClientConnectionCreator} @param wrappedEndpoint: The endpoint to wrap. - @type wrappedEndpoint: An L{IStreamClientEndpoint} provider. @return: an endpoint that provides transport level encryption layered on top of C{wrappedEndpoint} - @rtype: L{twisted.internet.interfaces.IStreamClientEndpoint} """ if TLSMemoryBIOFactory is None: raise NotImplementedError( @@ -2305,11 +2346,36 @@ def wrapClientTLS( return _WrapperEndpoint( wrappedEndpoint, lambda protocolFactory: TLSMemoryBIOFactory( - connectionCreator, True, protocolFactory + connectionCreator, + True, + protocolFactory, + clock=clock, ), ) +def wrapServerTLS( + connectionCreator: IOpenSSLServerConnectionCreator, + wrappedEndpoint: IStreamServerEndpoint, + clock: IReactorTime | None = None, +) -> IStreamServerEndpoint: + """ + Wrap a server endpoint in a TLS configuration. + + @param connectionCreator: The policy to create server connections. See + L{twisted.internet.ssl.CertificateOptions}. + + @param wrappedEndpoint: The transport server endpoint. See + L{TCP6ServerEndpoint}. + + @param clock: The clock interface used to schedule TLS buffered writes. + + @return: an endpoint that listens with TLS encryption added to + C{wrappedEndpoint} + """ + return _TLSServerEndpoint(wrappedEndpoint, connectionCreator, clock) + + def _parseClientTLS( reactor: Any, host: bytes | str, @@ -2419,3 +2485,38 @@ class _TLSClientEndpointParser: @rtype: L{IStreamClientEndpoint} """ return _parseClientTLS(reactor, *args, **kwargs) + + +@implementer(IPlugin, IStreamServerEndpointStringParser) +class _TLSServerEndpointParser: + """ + TLS server endpoint parser. + """ + + prefix: str = "tls" + + def _actualParseStreamServer( + self, + reactor: IReactorCore, + path: str, + port: str = "443", + backlog: str = "50", + interface: str = "::", + ) -> IStreamServerEndpoint: + """ + Actual parsing method, with detailed signature breaking out all + parameters. + """ + p = FilePath(path) + return wrapServerTLS( + SNIConnectionCreator(autoReloadingDirectoryOfPEMs(p)), + TCP6ServerEndpoint(reactor, int(port), int(backlog), interface), + ) + + def parseStreamServer( + self, reactor: IReactorCore, *args: Any, **kwargs: Any + ) -> IStreamServerEndpoint: + """ + Parse a TLS stream server endpoint. + """ + return self._actualParseStreamServer(reactor, *args, **kwargs) diff --git a/contrib/python/Twisted/py3/twisted/internet/interfaces.py b/contrib/python/Twisted/py3/twisted/internet/interfaces.py index 28ab9bffbd2..6cf273e4600 100644 --- a/contrib/python/Twisted/py3/twisted/internet/interfaces.py +++ b/contrib/python/Twisted/py3/twisted/internet/interfaces.py @@ -8,20 +8,8 @@ Maintainer: Itamar Shtull-Trauring """ from __future__ import annotations -from typing import ( - TYPE_CHECKING, - Any, - AnyStr, - Callable, - Iterable, - List, - Mapping, - Optional, - Sequence, - Tuple, - Type, - Union, -) +from collections.abc import Iterable, Mapping, Sequence +from typing import TYPE_CHECKING, Any, AnyStr, Callable from zope.interface import Attribute, Interface @@ -46,6 +34,7 @@ if TYPE_CHECKING: ConnectedDatagramProtocol, DatagramProtocol, Factory, + P, ServerFactory, ) from twisted.internet.ssl import ClientContextFactory @@ -104,7 +93,7 @@ class IConnector(Interface): class IResolverSimple(Interface): - def getHostByName(name: str, timeout: Sequence[int] = ()) -> "Deferred[str]": + def getHostByName(name: str, timeout: Sequence[int] = ()) -> Deferred[str]: """ Resolve the domain name C{name} into an IP address. @@ -192,7 +181,7 @@ class IHostnameResolver(Interface): resolutionReceiver: IResolutionReceiver, hostName: str, portNumber: int = 0, - addressTypes: Optional[Sequence[Type[IAddress]]] = None, + addressTypes: Sequence[type[IAddress]] | None = None, transportSemantics: str = "TCP", ) -> IHostResolution: """ @@ -222,8 +211,8 @@ class IHostnameResolver(Interface): class IResolver(IResolverSimple): def query( - query: "Query", timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + query: Query, timeout: Sequence[int] + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Dispatch C{query} to the method which can handle its type. @@ -243,7 +232,7 @@ class IResolver(IResolverSimple): def lookupAddress( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an A record lookup. @@ -262,7 +251,7 @@ class IResolver(IResolverSimple): def lookupAddress6( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an A6 record lookup. @@ -281,7 +270,7 @@ class IResolver(IResolverSimple): def lookupIPV6Address( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an AAAA record lookup. @@ -300,7 +289,7 @@ class IResolver(IResolverSimple): def lookupMailExchange( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an MX record lookup. @@ -319,7 +308,7 @@ class IResolver(IResolverSimple): def lookupNameservers( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an NS record lookup. @@ -338,7 +327,7 @@ class IResolver(IResolverSimple): def lookupCanonicalName( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a CNAME record lookup. @@ -357,7 +346,7 @@ class IResolver(IResolverSimple): def lookupMailBox( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an MB record lookup. @@ -376,7 +365,7 @@ class IResolver(IResolverSimple): def lookupMailGroup( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an MG record lookup. @@ -395,7 +384,7 @@ class IResolver(IResolverSimple): def lookupMailRename( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an MR record lookup. @@ -414,7 +403,7 @@ class IResolver(IResolverSimple): def lookupPointer( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a PTR record lookup. @@ -433,7 +422,7 @@ class IResolver(IResolverSimple): def lookupAuthority( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an SOA record lookup. @@ -452,7 +441,7 @@ class IResolver(IResolverSimple): def lookupNull( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a NULL record lookup. @@ -471,7 +460,7 @@ class IResolver(IResolverSimple): def lookupWellKnownServices( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a WKS record lookup. @@ -490,7 +479,7 @@ class IResolver(IResolverSimple): def lookupHostInfo( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a HINFO record lookup. @@ -509,7 +498,7 @@ class IResolver(IResolverSimple): def lookupMailboxInfo( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an MINFO record lookup. @@ -528,7 +517,7 @@ class IResolver(IResolverSimple): def lookupText( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a TXT record lookup. @@ -547,7 +536,7 @@ class IResolver(IResolverSimple): def lookupResponsibility( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an RP record lookup. @@ -566,7 +555,7 @@ class IResolver(IResolverSimple): def lookupAFSDatabase( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an AFSDB record lookup. @@ -585,7 +574,7 @@ class IResolver(IResolverSimple): def lookupService( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an SRV record lookup. @@ -604,7 +593,7 @@ class IResolver(IResolverSimple): def lookupAllRecords( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an ALL_RECORD lookup. @@ -623,7 +612,7 @@ class IResolver(IResolverSimple): def lookupSenderPolicy( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a SPF record lookup. @@ -642,7 +631,7 @@ class IResolver(IResolverSimple): def lookupNamingAuthorityPointer( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform a NAPTR record lookup. @@ -661,7 +650,7 @@ class IResolver(IResolverSimple): def lookupZone( name: str, timeout: Sequence[int] - ) -> "Deferred[Tuple[RRHeader, RRHeader, RRHeader]]": + ) -> Deferred[tuple[RRHeader, RRHeader, RRHeader]]: """ Perform an AXFR record lookup. @@ -689,10 +678,10 @@ class IResolver(IResolverSimple): class IReactorTCP(Interface): def listenTCP( port: int, - factory: "ServerFactory", + factory: ServerFactory, backlog: int = 50, interface: str = "", - ) -> "IListeningPort": + ) -> IListeningPort: """ Connects a given protocol factory to the given numeric TCP/IP port. @@ -714,9 +703,9 @@ class IReactorTCP(Interface): def connectTCP( host: str, port: int, - factory: "ClientFactory", + factory: ClientFactory[P], timeout: float = 30.0, - bindAddress: Optional[Tuple[str, int]] = None, + bindAddress: tuple[str, int] | None = None, ) -> IConnector: """ Connect a TCP client. @@ -741,10 +730,10 @@ class IReactorSSL(Interface): def connectSSL( host: str, port: int, - factory: "ClientFactory", - contextFactory: "ClientContextFactory", + factory: ClientFactory[P], + contextFactory: ClientContextFactory, timeout: float, - bindAddress: Optional[Tuple[str, int]], + bindAddress: tuple[str, int] | None, ) -> IConnector: """ Connect a client Protocol to a remote SSL socket. @@ -763,11 +752,11 @@ class IReactorSSL(Interface): def listenSSL( port: int, - factory: "ServerFactory", - contextFactory: "IOpenSSLContextFactory", - backlog: int, - interface: str, - ) -> "IListeningPort": + factory: ServerFactory[P], + contextFactory: IOpenSSLContextFactory, + backlog: int = 50, + interface: str = "", + ) -> IListeningPort: """ Connects a given protocol factory to the given numeric TCP/IP port. The connection is a SSL one, using contexts created by the context @@ -788,7 +777,7 @@ class IReactorUNIX(Interface): def connectUNIX( address: str, - factory: "ClientFactory", + factory: ClientFactory, timeout: float = 30, checkPID: bool = False, ) -> IConnector: @@ -808,11 +797,11 @@ class IReactorUNIX(Interface): def listenUNIX( address: str, - factory: "Factory", + factory: Factory, backlog: int = 50, mode: int = 0o666, wantPID: bool = False, - ) -> "IListeningPort": + ) -> IListeningPort: """ Listen on a UNIX socket. @@ -836,10 +825,10 @@ class IReactorUNIXDatagram(Interface): def connectUNIXDatagram( address: str, - protocol: "ConnectedDatagramProtocol", + protocol: ConnectedDatagramProtocol, maxPacketSize: int, mode: int, - bindAddress: Optional[Tuple[str, int]], + bindAddress: tuple[str, int] | None, ) -> IConnector: """ Connect a client protocol to a datagram UNIX socket. @@ -857,8 +846,8 @@ class IReactorUNIXDatagram(Interface): """ def listenUNIXDatagram( - address: str, protocol: "DatagramProtocol", maxPacketSize: int, mode: int - ) -> "IListeningPort": + address: str, protocol: DatagramProtocol, maxPacketSize: int, mode: int + ) -> IListeningPort: """ Listen on a datagram UNIX socket. @@ -880,7 +869,7 @@ class IReactorWin32Events(Interface): @since: 10.2 """ - def addEvent(event: object, fd: "FileDescriptor", action: str) -> None: + def addEvent(event: object, fd: FileDescriptor, action: str) -> None: """ Add a new win32 event to the event loop. @@ -906,8 +895,8 @@ class IReactorUDP(Interface): """ def listenUDP( - port: int, protocol: "DatagramProtocol", interface: str, maxPacketSize: int - ) -> "IListeningPort": + port: int, protocol: DatagramProtocol, interface: str, maxPacketSize: int + ) -> IListeningPort: """ Connects a given L{DatagramProtocol} to the given numeric UDP port. @@ -932,7 +921,7 @@ class IReactorMulticast(Interface): def listenMulticast( port: int, - protocol: "DatagramProtocol", + protocol: DatagramProtocol, interface: str = "", maxPacketSize: int = 8192, listenMultiple: bool = False, @@ -1002,8 +991,8 @@ class IReactorSocket(Interface): """ def adoptStreamPort( - fileDescriptor: int, addressFamily: "AddressFamily", factory: "ServerFactory" - ) -> "IListeningPort": + fileDescriptor: int, addressFamily: AddressFamily, factory: ServerFactory + ) -> IListeningPort: """ Add an existing listening I{SOCK_STREAM} socket to the reactor to monitor for new connections to accept and handle. @@ -1030,7 +1019,7 @@ class IReactorSocket(Interface): """ def adoptStreamConnection( - fileDescriptor: int, addressFamily: "AddressFamily", factory: "ServerFactory" + fileDescriptor: int, addressFamily: AddressFamily, factory: ServerFactory ) -> None: """ Add an existing connected I{SOCK_STREAM} socket to the reactor to @@ -1060,10 +1049,10 @@ class IReactorSocket(Interface): def adoptDatagramPort( fileDescriptor: int, - addressFamily: "AddressFamily", - protocol: "DatagramProtocol", + addressFamily: AddressFamily, + protocol: DatagramProtocol, maxPacketSize: int, - ) -> "IListeningPort": + ) -> IListeningPort: """ Add an existing listening I{SOCK_DGRAM} socket to the reactor to monitor for read and write readiness. @@ -1092,16 +1081,16 @@ class IReactorSocket(Interface): class IReactorProcess(Interface): def spawnProcess( - processProtocol: "IProcessProtocol", - executable: Union[bytes, str], - args: Sequence[Union[bytes, str]], - env: Optional[Mapping[AnyStr, AnyStr]] = None, - path: Union[None, bytes, str] = None, - uid: Optional[int] = None, - gid: Optional[int] = None, + processProtocol: IProcessProtocol, + executable: bytes | str, + args: Sequence[bytes | str], + env: Mapping[AnyStr, AnyStr] | None = None, + path: None | bytes | str = None, + uid: int | None = None, + gid: int | None = None, usePTY: bool = False, - childFDs: Optional[Mapping[int, Union[int, str]]] = None, - ) -> "IProcessTransport": + childFDs: Mapping[int, int | str] | None = None, + ) -> IProcessTransport: """ Spawn a process, with a process protocol. @@ -1202,7 +1191,7 @@ class IReactorTime(Interface): def callLater( delay: float, callable: Callable[..., Any], *args: object, **kwargs: object - ) -> "IDelayedCall": + ) -> IDelayedCall: """ Call a function later. @@ -1217,7 +1206,7 @@ class IReactorTime(Interface): C{reset()} methods. """ - def getDelayedCalls() -> Sequence["IDelayedCall"]: + def getDelayedCalls() -> Sequence[IDelayedCall]: """ See L{twisted.internet.interfaces.IReactorTime.getDelayedCalls} """ @@ -1331,7 +1320,7 @@ class IReactorThreads(IReactorFromThreads, IReactorInThreads): Internally, this should use a thread pool and dispatch methods to them. """ - def getThreadPool() -> "ThreadPool": + def getThreadPool() -> ThreadPool: """ Return the threadpool used by L{IReactorInThreads.callInThread}. Create it first if necessary. @@ -1354,7 +1343,7 @@ class IReactorCore(Interface): "I{during shutdown} and C{False} the rest of the time." ) - def resolve(name: str, timeout: Sequence[int] = (1, 3, 11, 45)) -> "Deferred[str]": + def resolve(name: str, timeout: Sequence[int] = (1, 3, 11, 45)) -> Deferred[str]: """ Asynchronously resolve a hostname to a single IPv4 address. @@ -1474,7 +1463,7 @@ class IReactorCore(Interface): def callWhenRunning( callable: Callable[..., Any], *args: object, **kwargs: object - ) -> Optional[Any]: + ) -> Any | None: """ Call a function when the reactor is running. @@ -1566,7 +1555,7 @@ class IReactorFDSet(Interface): (or at least similarly opaque IDs returned from a .fileno() method) """ - def addReader(reader: "IReadDescriptor") -> None: + def addReader(reader: IReadDescriptor) -> None: """ I add reader to the set of file descriptors to get read events for. @@ -1575,7 +1564,7 @@ class IReactorFDSet(Interface): L{removeReader}. """ - def addWriter(writer: "IWriteDescriptor") -> None: + def addWriter(writer: IWriteDescriptor) -> None: """ I add writer to the set of file descriptors to get write events for. @@ -1584,17 +1573,17 @@ class IReactorFDSet(Interface): L{removeWriter}. """ - def removeReader(reader: "IReadDescriptor") -> None: + def removeReader(reader: IReadDescriptor) -> None: """ Removes an object previously added with L{addReader}. """ - def removeWriter(writer: "IWriteDescriptor") -> None: + def removeWriter(writer: IWriteDescriptor) -> None: """ Removes an object previously added with L{addWriter}. """ - def removeAll() -> List[Union["IReadDescriptor", "IWriteDescriptor"]]: + def removeAll() -> list[IReadDescriptor | IWriteDescriptor]: """ Remove all readers and writers. @@ -1604,7 +1593,7 @@ class IReactorFDSet(Interface): which were removed. """ - def getReaders() -> List["IReadDescriptor"]: + def getReaders() -> list[IReadDescriptor]: """ Return the list of file descriptors currently monitored for input events by the reactor. @@ -1612,7 +1601,7 @@ class IReactorFDSet(Interface): @return: the list of file descriptors monitored for input events. """ - def getWriters() -> List["IWriteDescriptor"]: + def getWriters() -> list[IWriteDescriptor]: """ Return the list file descriptors currently monitored for output events by the reactor. @@ -1635,7 +1624,7 @@ class IListeningPort(Interface): port number). """ - def stopListening() -> Optional["Deferred[None]"]: + def stopListening() -> Deferred[None] | None: """ Stop listening on this port. @@ -1703,7 +1692,7 @@ class IReadDescriptor(IFileDescriptor): This interface is generally used in conjunction with L{IReactorFDSet}. """ - def doRead() -> Optional[Failure]: + def doRead() -> Failure | None: """ Some data is available for reading on your descriptor. @@ -1720,7 +1709,7 @@ class IWriteDescriptor(IFileDescriptor): This interface is generally used in conjunction with L{IReactorFDSet}. """ - def doWrite() -> Optional[Failure]: + def doWrite() -> Failure | None: """ Some data can be written to your descriptor. @@ -1775,7 +1764,7 @@ class IConsumer(Interface): A consumer consumes data from a producer. """ - def registerProducer(producer: "IProducer", streaming: bool) -> None: + def registerProducer(producer: IProducer, streaming: bool) -> None: """ Register to receive data from a producer. @@ -1905,7 +1894,7 @@ class IProtocol(Interface): of one of those). """ - def makeConnection(transport: "ITransport") -> None: + def makeConnection(transport: ITransport) -> None: """ Make a connection to a transport and a server. """ @@ -1928,7 +1917,7 @@ class IProcessProtocol(Interface): Interface for process-related event handlers. """ - def makeConnection(process: "IProcessTransport") -> None: + def makeConnection(process: IProcessTransport) -> None: """ Called when the process has been created. @@ -2060,7 +2049,7 @@ class IProtocolFactory(Interface): Interface for protocol factories. """ - def buildProtocol(addr: IAddress) -> Optional[IProtocol]: + def buildProtocol(addr: IAddress) -> IProtocol | None: """ Called when a connection has been established to addr. @@ -2203,12 +2192,12 @@ class ITCPTransport(ITransport): to allow detection of lost peers in a non-infinite amount of time. """ - def getHost() -> Union["IPv4Address", "IPv6Address"]: + def getHost() -> IPv4Address | IPv6Address: """ Returns L{IPv4Address} or L{IPv6Address}. """ - def getPeer() -> Union["IPv4Address", "IPv6Address"]: + def getPeer() -> IPv4Address | IPv6Address: """ Returns L{IPv4Address} or L{IPv6Address}. """ @@ -2255,8 +2244,8 @@ class IOpenSSLServerConnectionCreator(Interface): """ def serverConnectionForTLS( - tlsProtocol: "TLSMemoryBIOProtocol", - ) -> "OpenSSLConnection": + tlsProtocol: TLSMemoryBIOProtocol, + ) -> OpenSSLConnection: """ Create a connection for the given server protocol. @@ -2279,8 +2268,8 @@ class IOpenSSLClientConnectionCreator(Interface): """ def clientConnectionForTLS( - tlsProtocol: "TLSMemoryBIOProtocol", - ) -> "OpenSSLConnection": + tlsProtocol: TLSMemoryBIOProtocol, + ) -> OpenSSLConnection: """ Create a connection for the given client protocol. @@ -2301,7 +2290,7 @@ class IProtocolNegotiationFactory(Interface): @see: L{twisted.internet.ssl} """ - def acceptableProtocols() -> List[bytes]: + def acceptableProtocols() -> list[bytes]: """ Returns a list of protocols that can be spoken by the connection factory in the form of ALPN tokens, as laid out in the IANA registry @@ -2321,7 +2310,7 @@ class IOpenSSLContextFactory(Interface): @see: L{twisted.internet.ssl} """ - def getContext() -> "OpenSSLContext": + def getContext() -> OpenSSLContext: """ Returns a TLS context object, suitable for securing a TLS connection. This context object will be appropriately customized for the connection @@ -2339,9 +2328,9 @@ class ITLSTransport(ITCPTransport): """ def startTLS( - contextFactory: Union[ - IOpenSSLClientConnectionCreator, IOpenSSLServerConnectionCreator - ] + contextFactory: ( + IOpenSSLClientConnectionCreator | IOpenSSLServerConnectionCreator + ), ) -> None: """ Initiate TLS negotiation. @@ -2373,20 +2362,19 @@ class ISSLTransport(ITCPTransport): class INegotiated(ISSLTransport): """ - A TLS based transport that supports using ALPN/NPN to negotiate the - protocol to be used inside the encrypted tunnel. + A TLS based transport that supports using ALPN to negotiate the protocol to + be used inside the encrypted tunnel. """ - negotiatedProtocol = Attribute( + negotiatedProtocol: bytes | None = Attribute( """ - The protocol selected to be spoken using ALPN/NPN. The result from ALPN - is preferred to the result from NPN if both were used. If the remote - peer does not support ALPN or NPN, or neither NPN or ALPN are available - on this machine, will be L{None}. Otherwise, will be the name of the - selected protocol as C{bytes}. Note that until the handshake has - completed this property may incorrectly return L{None}: wait until data - has been received before trusting it (see - https://twistedmatrix.com/trac/ticket/6024). + The protocol selected to be spoken using ALPN. If the remote peer does + not support ALPN, or ALPN is not available via local TLS libraries, + C{negotiatedProtocol} will be L{None}. Otherwise, + C{negotiatedProtocol} will be the name of the selected protocol as + C{bytes}. Until the TLS handshake has completed, this property may + incorrectly return L{None}: wait until data has been received before + trusting it. See U{https://github.com/twisted/twisted/issues/6024}. """ ) @@ -2404,7 +2392,7 @@ class IAcceptableCiphers(Interface): A list of acceptable ciphers for a TLS context. """ - def selectCiphers(availableCiphers: Tuple[ICipher]) -> Tuple[ICipher]: + def selectCiphers(availableCiphers: tuple[ICipher]) -> tuple[ICipher]: """ Choose which ciphers to allow to be negotiated on a TLS connection. @@ -2469,7 +2457,7 @@ class IProcessTransport(ITransport): Close stdin, stderr and stdout. """ - def signalProcess(signalID: Union[str, int]) -> None: + def signalProcess(signalID: str | int) -> None: """ Send a signal to the process. @@ -2516,7 +2504,7 @@ class IUDPTransport(Interface): Transport for UDP DatagramProtocols. """ - def write(packet: bytes, addr: Optional[Tuple[str, int]]) -> None: + def write(packet: bytes, addr: tuple[str, int] | None) -> None: """ Write packet to given address. @@ -2541,14 +2529,14 @@ class IUDPTransport(Interface): @param port: port to connect to. """ - def getHost() -> Union["IPv4Address", "IPv6Address"]: + def getHost() -> IPv4Address | IPv6Address: """ Get this port's host address. @return: an address describing the listening port. """ - def stopListening() -> Optional["Deferred[None]"]: + def stopListening() -> Deferred[None] | None: """ Stop listening on this port. @@ -2581,7 +2569,7 @@ class IUNIXDatagramTransport(Interface): Write packet to given address. """ - def getHost() -> "UNIXAddress": + def getHost() -> UNIXAddress: """ Returns L{UNIXAddress}. """ @@ -2597,12 +2585,12 @@ class IUNIXDatagramConnectedTransport(Interface): Write packet to address we are connected to. """ - def getHost() -> "UNIXAddress": + def getHost() -> UNIXAddress: """ Returns L{UNIXAddress}. """ - def getPeer() -> "UNIXAddress": + def getPeer() -> UNIXAddress: """ Returns L{UNIXAddress}. """ @@ -2649,7 +2637,7 @@ class IMulticastTransport(IUDPTransport): Set time to live on multicast packets. """ - def joinGroup(addr: str, interface: str = "") -> "Deferred[None]": + def joinGroup(addr: str, interface: str = "") -> Deferred[None]: """ Join a multicast group. Returns L{Deferred} of success or failure. @@ -2657,7 +2645,7 @@ class IMulticastTransport(IUDPTransport): L{error.MulticastJoinError}. """ - def leaveGroup(addr: str, interface: str = "") -> "Deferred[None]": + def leaveGroup(addr: str, interface: str = "") -> Deferred[None]: """ Leave multicast group, return L{Deferred} of success. """ @@ -2671,7 +2659,7 @@ class IStreamClientEndpoint(Interface): @since: 10.1 """ - def connect(protocolFactory: IProtocolFactory) -> "Deferred[IProtocol]": + def connect(protocolFactory: IProtocolFactory) -> Deferred[IProtocol]: """ Connect the C{protocolFactory} to the location specified by this L{IStreamClientEndpoint} provider. @@ -2692,7 +2680,7 @@ class IStreamServerEndpoint(Interface): @since: 10.1 """ - def listen(protocolFactory: IProtocolFactory) -> "Deferred[IListeningPort]": + def listen(protocolFactory: IProtocolFactory) -> Deferred[IListeningPort]: """ Listen with C{protocolFactory} at the location specified by this L{IStreamServerEndpoint} provider. diff --git a/contrib/python/Twisted/py3/twisted/internet/iocpreactor/reactor.py b/contrib/python/Twisted/py3/twisted/internet/iocpreactor/reactor.py index 976d1096e72..363b1c9273a 100644 --- a/contrib/python/Twisted/py3/twisted/internet/iocpreactor/reactor.py +++ b/contrib/python/Twisted/py3/twisted/internet/iocpreactor/reactor.py @@ -10,7 +10,6 @@ Reactor that uses IO completion ports import socket import sys import warnings -from typing import Tuple, Type from zope.interface import implementer @@ -27,7 +26,7 @@ except ImportError: TLSMemoryBIOFactory = None # Either pyOpenSSL isn't installed, or it is too old for this code to work. # The reactor won't provide IReactorSSL. - _extraInterfaces: Tuple[Type[interfaces.IReactorSSL], ...] = () + _extraInterfaces: tuple[type[interfaces.IReactorSSL], ...] = () warnings.warn( "pyOpenSSL 0.10 or newer is required for SSL support in iocpreactor. " "It is missing, so the reactor will not support SSL APIs." diff --git a/contrib/python/Twisted/py3/twisted/internet/iocpreactor/tcp.py b/contrib/python/Twisted/py3/twisted/internet/iocpreactor/tcp.py index ef72b7ed7e5..8bc3c1330f7 100644 --- a/contrib/python/Twisted/py3/twisted/internet/iocpreactor/tcp.py +++ b/contrib/python/Twisted/py3/twisted/internet/iocpreactor/tcp.py @@ -10,7 +10,7 @@ from __future__ import annotations import errno import socket import struct -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from zope.interface import classImplements, implementer @@ -356,8 +356,8 @@ class Server(Connection): self, sock: socket.socket, protocol: IProtocol, - clientAddr: Union[IPv4Address, IPv6Address], - serverAddr: Union[IPv4Address, IPv6Address], + clientAddr: IPv4Address | IPv6Address, + serverAddr: IPv4Address | IPv6Address, sessionno: int, reactor: IOCPReactor, ): @@ -422,7 +422,7 @@ class Port(_SocketCloser, _LogOwner): # Actual port number being listened on, only set to a non-None # value when we are actually listening. - _realPortNumber: Optional[int] = None + _realPortNumber: int | None = None # A string describing the connections which will be created by this port. # Normally this is C{"TCP"}, since this is a TCP port, but when the TLS diff --git a/contrib/python/Twisted/py3/twisted/internet/iocpreactor/udp.py b/contrib/python/Twisted/py3/twisted/internet/iocpreactor/udp.py index 744efa03e1b..53c9e9176fd 100644 --- a/contrib/python/Twisted/py3/twisted/internet/iocpreactor/udp.py +++ b/contrib/python/Twisted/py3/twisted/internet/iocpreactor/udp.py @@ -11,7 +11,7 @@ import errno import socket import struct import warnings -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from zope.interface import implementer @@ -53,7 +53,7 @@ class Port(abstract.FileHandle): # Actual port number being listened on, only set to a non-None # value when we are actually listening. - _realPortNumber: Optional[int] = None + _realPortNumber: int | None = None def __init__( self, diff --git a/contrib/python/Twisted/py3/twisted/internet/posixbase.py b/contrib/python/Twisted/py3/twisted/internet/posixbase.py index d9fa8a9a554..19fc69ffb6f 100644 --- a/contrib/python/Twisted/py3/twisted/internet/posixbase.py +++ b/contrib/python/Twisted/py3/twisted/internet/posixbase.py @@ -10,7 +10,7 @@ from __future__ import annotations import socket import sys -from typing import Sequence +from collections.abc import Sequence from zope.interface import classImplements, implementer @@ -30,7 +30,7 @@ from twisted.internet.interfaces import ( IReactorUNIXDatagram, ) from twisted.internet.main import CONNECTION_DONE, CONNECTION_LOST -from twisted.internet.protocol import ClientFactory +from twisted.internet.protocol import ClientFactory, P from twisted.python import failure, log from twisted.python.runtime import platform, platformType from ._signals import ( @@ -370,7 +370,7 @@ class PosixReactorBase(_DisconnectSelectableMixin, ReactorBase): self, host: str, port: int, - factory: "ClientFactory", + factory: ClientFactory[P], timeout: float = 30.0, bindAddress: tuple[str, int] | None = None, ) -> IConnector: diff --git a/contrib/python/Twisted/py3/twisted/internet/process.py b/contrib/python/Twisted/py3/twisted/internet/process.py index ff7684e358b..d47494ab194 100644 --- a/contrib/python/Twisted/py3/twisted/internet/process.py +++ b/contrib/python/Twisted/py3/twisted/internet/process.py @@ -20,7 +20,7 @@ import stat import sys import traceback from collections import defaultdict -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING _PS_CLOSE: int _PS_DUP2: int @@ -66,7 +66,7 @@ else: # here for backwards compatibility: ProcessExitedAlready = error.ProcessExitedAlready -reapProcessHandlers: Dict[int, _BaseProcess] = {} +reapProcessHandlers: dict[int, _BaseProcess] = {} def reapAllProcesses() -> None: @@ -285,7 +285,7 @@ class _BaseProcess(BaseProcess): Base class for Process and PTYProcess. """ - status: Optional[int] = None + status: int | None = None pid = None def reapProcess(self): @@ -632,11 +632,11 @@ def _listOpenFDs(): def _getFileActions( - fdState: List[Tuple[int, bool]], - childToParentFD: Dict[int, int], + fdState: list[tuple[int, bool]], + childToParentFD: dict[int, int], doClose: int, doDup2: int, -) -> List[Tuple[int, ...]]: +) -> list[tuple[int, ...]]: """ Get the C{file_actions} parameter for C{posix_spawn} based on the parameters describing the current process state. @@ -649,7 +649,7 @@ def _getFileActions( @param doDup2: the integer to use for the 'dup2' instruction """ fdStateDict = dict(fdState) - parentToChildren: Dict[int, List[int]] = defaultdict(list) + parentToChildren: dict[int, list[int]] = defaultdict(list) for inChild, inParent in childToParentFD.items(): parentToChildren[inParent].append(inChild) allocated = set(fdStateDict) @@ -664,7 +664,7 @@ def _getFileActions( allocated.add(nextFD) return nextFD - result: List[Tuple[int, ...]] = [] + result: list[tuple[int, ...]] = [] relocations = {} for inChild, inParent in sorted(childToParentFD.items()): # The parent FD will later be reused by a child FD. diff --git a/contrib/python/Twisted/py3/twisted/internet/protocol.py b/contrib/python/Twisted/py3/twisted/internet/protocol.py index bd1e647302b..b2373b656b3 100644 --- a/contrib/python/Twisted/py3/twisted/internet/protocol.py +++ b/contrib/python/Twisted/py3/twisted/internet/protocol.py @@ -11,23 +11,71 @@ Twisted. The Protocol class contains some introductory material. from __future__ import annotations import random -from typing import Any, Callable, Optional +from typing import Any, Callable, Generic, Protocol as TypingProtocol from zope.interface import implementer +from typing_extensions import ParamSpec, Self, TypeVar + from twisted.internet import defer, error, interfaces from twisted.internet.interfaces import ( IAddress, + IConnector, IMulticastTransport, ITransport, IUDPTransport, ) from twisted.logger import _loggerFor from twisted.python import components, failure, log +from twisted.python.failure import Failure + +P = TypeVar("P", bound="_ProtoWithFactory", default="_ProtoWithFactory") +SomeProtocol = TypeVar("SomeProtocol") +_FactoryParams = ParamSpec("_FactoryParams") +R = TypeVar("R", bound="Factory") +_Value = TypeVar("_Value", covariant=True) + + +class _LSPViolationHelper(Generic[_Value]): + def __get__( # type:ignore[empty-body] + self, instance: object, owner: type[object] | None = None + ) -> _Value: + ... + + def __set__(self, instance: object, value: Any) -> None: + ... + + def __delete__(self, instance: object) -> None: + ... + + +@implementer(interfaces.IProtocol) +class _ProtoWithFactory(TypingProtocol): + # factory: _LSPViolationHelper[Factory[Self]] + + @property + def factory(self) -> Factory[Self]: + ... + + @factory.setter + def factory(self, value: Any) -> None: + ... + + def dataReceived(self, data: bytes) -> None: + ... + + def connectionLost(self, reason: Failure) -> None: + ... + + def makeConnection(self, transport: ITransport) -> None: + ... + + def connectionMade(self) -> None: + ... @implementer(interfaces.IProtocolFactory, interfaces.ILoggingContext) -class Factory: +class Factory(Generic[P]): """ This is a factory which produces protocols. @@ -35,13 +83,18 @@ class Factory: self.protocol. """ - protocol: "Optional[Callable[[], Protocol]]" = None + protocol: Callable[..., P] | None = None numPorts = 0 noisy = True @classmethod - def forProtocol(cls, protocol, *args, **kwargs): + def forProtocol( + cls: Callable[_FactoryParams, R], + protocol: Callable[[], P], + *args: _FactoryParams.args, + **kwargs: _FactoryParams.kwargs, + ) -> Factory[P]: """ Create a factory for the given protocol. @@ -58,7 +111,7 @@ class Factory: """ factory = cls(*args, **kwargs) factory.protocol = protocol - return factory + return factory # type:ignore[return-value] def logPrefix(self): """ @@ -118,7 +171,7 @@ class Factory: directly. """ - def buildProtocol(self, addr: IAddress) -> "Optional[Protocol]": + def buildProtocol(self, addr: IAddress | None) -> P | None: """ Create an instance of a subclass of Protocol. @@ -139,7 +192,7 @@ class Factory: return p -class ClientFactory(Factory): +class ClientFactory(Factory[P]): """ A Protocol factory for clients. @@ -147,7 +200,7 @@ class ClientFactory(Factory): reactors. """ - def startedConnecting(self, connector): + def startedConnecting(self, connector: IConnector) -> None: """ Called when a connection has been started. @@ -156,22 +209,18 @@ class ClientFactory(Factory): @param connector: a Connector object. """ - def clientConnectionFailed(self, connector, reason): + def clientConnectionFailed(self, connector: IConnector, reason: Failure) -> None: """ Called when a connection has failed to connect. It may be useful to call connector.connect() - this will reconnect. - - @type reason: L{twisted.python.failure.Failure} """ - def clientConnectionLost(self, connector, reason): + def clientConnectionLost(self, connector: IConnector, reason: Failure) -> None: """ Called when an established connection is lost. It may be useful to call connector.connect() - this will reconnect. - - @type reason: L{twisted.python.failure.Failure} """ @@ -415,7 +464,6 @@ class ReconnectingClientFactory(ClientFactory): log.msg("Abandoning %s after %d retries." % (connector, self.retries)) return - self.delay = min(self.delay * self.factor, self.maxDelay) if self.jitter: self.delay = random.normalvariate(self.delay, self.delay * self.jitter) @@ -438,6 +486,8 @@ class ReconnectingClientFactory(ClientFactory): self.clock = reactor self._callID = self.clock.callLater(self.delay, reconnector) + self.delay = min(self.delay * self.factor, self.maxDelay) + def stopTrying(self): """ Put a stop to any attempt to reconnect in progress. @@ -484,7 +534,7 @@ class ReconnectingClientFactory(ClientFactory): return state -class ServerFactory(Factory): +class ServerFactory(Factory[P]): """ Subclass this to indicate that your protocol.Factory is only usable for servers. """ @@ -500,7 +550,7 @@ class BaseProtocol: """ connected = 0 - transport: Optional[ITransport] = None + transport: ITransport | None = None def makeConnection(self, transport): """ @@ -549,7 +599,7 @@ class Protocol(BaseProtocol): see the L{twisted.protocols.basic} module for a few of them. """ - factory: Optional[Factory] = None + factory: Any = None def logPrefix(self): """ @@ -628,7 +678,7 @@ class ProcessProtocol(BaseProtocol): stdin, stdout, and stderr file descriptors. """ - transport: Optional[interfaces.IProcessTransport] = None + transport: interfaces.IProcessTransport | None = None def childDataReceived(self, childFD: int, data: bytes) -> None: if childFD == 1: diff --git a/contrib/python/Twisted/py3/twisted/internet/reactor.py b/contrib/python/Twisted/py3/twisted/internet/reactor.py index 00f1ef6e012..6be09f5c8f6 100644 --- a/contrib/python/Twisted/py3/twisted/internet/reactor.py +++ b/contrib/python/Twisted/py3/twisted/internet/reactor.py @@ -3,32 +3,93 @@ """ The reactor is the Twisted event loop within Twisted, the loop which drives -applications using Twisted. The reactor provides APIs for networking, +applications using Twisted. The reactor provides APIs for networking, threading, dispatching events, and more. -The default reactor depends on the platform and will be installed if this -module is imported without another reactor being explicitly installed -beforehand. Regardless of which reactor is installed, importing this module is -the correct way to get a reference to it. +This module is the way to access the current global reactor, by doing:: -New application code should prefer to pass and accept the reactor as a -parameter where it is needed, rather than relying on being able to import this -module to get a reference. This simplifies unit testing and may make it easier -to one day support multiple reactors (as a performance enhancement), though -this is not currently possible. + from twisted.internet import reactor + +The specific capabilities of the default reactor depends on your platform, and +will be installed if this module is imported without another reactor being +explicitly installed beforehand. Regardless of which reactor is installed, +importing this module is the way to get a reference to it. + +In order to minimize dependencies on mutable global state, new application code +should prefer to pass and accept the reactor as a parameter where it is needed, +rather than relying on being able to import this module to get a reference. + +To get the reactor at the beginning of your program without using this global +import, use L{twisted.internet.task.react}, like so:: + + # If you need a custom reactor, you can install it at the beginning: + from twisted.internet.custom_reactor import install + install() + + from twisted.internet.task import react + from twisted.internet.defer import Deferred + + async def main(reactor: IReactorTCP) -> None: + reactor.listenTCP(...) + await Deferred() # wait forever + + if __name__ == '__main__': + react(main, ()) + +This simplifies testing and may make it easier to one day support multiple +reactors. + +@note: Another reason to prefer getting the reactor as a parameter is that type + information about the object you get from C{from twisted.internet import + reactor} is always slightly inaccurate. At runtime, interfaces are only + contextually provided depending upon the installed reactor, the installed + libraries, and the capabilities of your platform, so there is no "correct" + type for this object to statically be. Thus, it provides a conglomeration + of many of the most common reactor interfaces which are usually available. + + To be fully correct in new code, it's best to always adapt the reactor to + your desired interface in order to interrogate its capabilities; for + example, if you need to call a method on + L{IReactorSSL<twisted.internet.interfaces.IReactorSSL>} but provide useful + error messages if it's not available, you can do something like this:: + + from twisted.internet import reactor + + if (sslReactor := IReactorSSL(reactor, None)) is not None: + sslReactor.listenSSL(...) + else: + print("TLS support not available.") @see: L{IReactorCore<twisted.internet.interfaces.IReactorCore>} + @see: L{IReactorTime<twisted.internet.interfaces.IReactorTime>} + @see: L{IReactorProcess<twisted.internet.interfaces.IReactorProcess>} + @see: L{IReactorTCP<twisted.internet.interfaces.IReactorTCP>} + @see: L{IReactorSSL<twisted.internet.interfaces.IReactorSSL>} + @see: L{IReactorUDP<twisted.internet.interfaces.IReactorUDP>} + @see: L{IReactorMulticast<twisted.internet.interfaces.IReactorMulticast>} + @see: L{IReactorUNIX<twisted.internet.interfaces.IReactorUNIX>} + @see: L{IReactorUNIXDatagram<twisted.internet.interfaces.IReactorUNIXDatagram>} + @see: L{IReactorFDSet<twisted.internet.interfaces.IReactorFDSet>} + +@see: L{IReactorSocket<twisted.internet.interfaces.IReactorSocket>} + +@see: L{IReactorWin32Events<twisted.internet.interfaces.IReactorWin32Events>} + @see: L{IReactorThreads<twisted.internet.interfaces.IReactorThreads>} -@see: L{IReactorPluggableResolver<twisted.internet.interfaces.IReactorPluggableResolver>} + +@see: + L{IReactorPluggableResolver<twisted.internet.interfaces.IReactorPluggableResolver>} + +@see: L{IReactorDaemonize<twisted.internet.interfaces.IReactorDaemonize>} """ diff --git a/contrib/python/Twisted/py3/twisted/internet/selectreactor.py b/contrib/python/Twisted/py3/twisted/internet/selectreactor.py index ee233f7f10b..0e7235d0f5b 100644 --- a/contrib/python/Twisted/py3/twisted/internet/selectreactor.py +++ b/contrib/python/Twisted/py3/twisted/internet/selectreactor.py @@ -11,7 +11,7 @@ import select import sys from errno import EBADF, EINTR from time import sleep -from typing import Callable, Type, TypeVar +from typing import Callable, TypeVar from zope.interface import implementer @@ -48,7 +48,7 @@ else: try: from twisted.internet.win32eventreactor import _ThreadedWin32EventsMixin except ImportError: - _extraBase: Type[object] = object + _extraBase: type[object] = object else: _extraBase = _ThreadedWin32EventsMixin @@ -153,7 +153,7 @@ class SelectReactor(posixbase.PosixReactorBase, _extraBase): # type: ignore[mis for selectable in selectables: # if this was disconnected in another thread, kill it. # ^^^^ --- what the !@#*? serious! -exarkun - if selectable not in fdset: # type:ignore[operator] + if selectable not in fdset: continue # This for pausing input when we're not ready for more. _logrun(selectable, _drdw, selectable, method) diff --git a/contrib/python/Twisted/py3/twisted/internet/task.py b/contrib/python/Twisted/py3/twisted/internet/task.py index 0319d24a7a4..dba790febff 100644 --- a/contrib/python/Twisted/py3/twisted/internet/task.py +++ b/contrib/python/Twisted/py3/twisted/internet/task.py @@ -5,24 +5,13 @@ """ Scheduling utility methods and classes. """ - +from __future__ import annotations import sys import time import warnings -from typing import ( - Callable, - Coroutine, - Iterable, - Iterator, - List, - NoReturn, - Optional, - Sequence, - TypeVar, - Union, - cast, -) +from collections.abc import Coroutine, Iterable, Iterator, Sequence +from typing import Any, Callable, Generic, NoReturn, TypeVar, cast from zope.interface import implementer @@ -67,13 +56,13 @@ class LoopingCall: to L{LoopingCall.start}. """ - call: Optional[IDelayedCall] = None + call: IDelayedCall | None = None running = False - _deferred: Optional[Deferred["LoopingCall"]] = None - interval: Optional[float] = None + _deferred: Deferred[LoopingCall] | None = None + interval: float | None = None _runAtStart = False - starttime: Optional[float] = None - _realLastTime: Optional[float] = None + starttime: float | None = None + _realLastTime: float | None = None def __init__(self, f: Callable[..., object], *a: object, **kw: object) -> None: self.f = f @@ -84,7 +73,7 @@ class LoopingCall: self.clock = cast(IReactorTime, reactor) @property - def deferred(self) -> Optional[Deferred["LoopingCall"]]: + def deferred(self) -> Deferred[LoopingCall] | None: """ DEPRECATED. L{Deferred} fired when loop stops or fails. @@ -100,7 +89,7 @@ class LoopingCall: return self._deferred @classmethod - def withCount(cls, countCallable: Callable[[int], object]) -> "LoopingCall": + def withCount(cls, countCallable: Callable[[int], object]) -> LoopingCall: """ An alternate constructor for L{LoopingCall} that makes available the number of calls which should have occurred since it was last invoked. @@ -176,7 +165,7 @@ class LoopingCall: intervalNum = int(elapsedTime / self.interval) return intervalNum - def start(self, interval: float, now: bool = True) -> Deferred["LoopingCall"]: + def start(self, interval: float, now: bool = True) -> Deferred[LoopingCall]: """ Start running function every interval seconds. @@ -379,15 +368,18 @@ def _defaultScheduler(callable: Callable[[], None]) -> IDelayedCall: return cast(IReactorTime, reactor).callLater(_EPSILON, callable) -_TaskResultT = TypeVar("_TaskResultT") +_TaskIteratorT = TypeVar("_TaskIteratorT", bound=Iterator[object]) -class CooperativeTask: +class CooperativeTask(Generic[_TaskIteratorT]): """ A L{CooperativeTask} is a task object inside a L{Cooperator}, which can be paused, resumed, and stopped. It can also have its completion (or termination) monitored. + It is generic over a task iterator type, L{_TaskIteratorT}, which is the + type of the iterator passed to it and also the type of its result. + @see: L{Cooperator.cooperate} @ivar _iterator: the iterator to iterate when this L{CooperativeTask} is @@ -411,7 +403,9 @@ class CooperativeTask: """ def __init__( - self, iterator: Iterator[_TaskResultT], cooperator: "Cooperator" + self, + iterator: _TaskIteratorT, + cooperator: Cooperator, ) -> None: """ A private constructor: to create a new L{CooperativeTask}, see @@ -419,13 +413,13 @@ class CooperativeTask: """ self._iterator = iterator self._cooperator = cooperator - self._deferreds: List[Deferred[Iterator[_TaskResultT]]] = [] + self._deferreds: list[Deferred[_TaskIteratorT]] = [] self._pauseCount = 0 - self._completionState: Optional[SchedulerError] = None - self._completionResult: Optional[Union[Iterator[_TaskResultT], Failure]] = None + self._completionState: SchedulerError | None = None + self._completionResult: _TaskIteratorT | Failure | None = None cooperator._addTask(self) - def whenDone(self) -> Deferred[Iterator[_TaskResultT]]: + def whenDone(self) -> Deferred[_TaskIteratorT]: """ Get a L{Deferred} notification of when this task is complete. @@ -437,7 +431,7 @@ class CooperativeTask: @rtype: L{Deferred} """ - d: Deferred[Iterator[_TaskResultT]] = Deferred() + d: Deferred[_TaskIteratorT] = Deferred() if self._completionState is None: self._deferreds.append(d) else: @@ -474,7 +468,7 @@ class CooperativeTask: def _completeWith( self, completionState: SchedulerError, - deferredResult: Union[Iterator[_TaskResultT], Failure], + deferredResult: _TaskIteratorT | Failure, ) -> None: """ @param completionState: a L{SchedulerError} exception or a subclass @@ -594,25 +588,28 @@ class Cooperator: stepped as soon as they are added, or if they will be queued up until L{Cooperator.start} is called. """ - self._tasks: List[CooperativeTask] = [] - self._metarator: Iterator[CooperativeTask] = iter(()) + self._tasks: list[CooperativeTask[Iterator[object]]] = [] + self._metarator: Iterator[CooperativeTask[Iterator[object]]] = iter(()) self._terminationPredicateFactory = terminationPredicateFactory self._scheduler = scheduler - self._delayedCall: Optional[IDelayedCall] = None + self._delayedCall: IDelayedCall | None = None self._stopped = False self._started = started def coiterate( self, - iterator: Iterator[_TaskResultT], - doneDeferred: Optional[Deferred[Iterator[_TaskResultT]]] = None, - ) -> Deferred[Iterator[_TaskResultT]]: + iterator: _TaskIteratorT, + doneDeferred: Deferred[_TaskIteratorT] | None = None, + ) -> Deferred[_TaskIteratorT]: """ Add an iterator to the list of iterators this L{Cooperator} is currently running. - Equivalent to L{cooperate}, but returns a L{Deferred} that will - be fired when the task is done. + Equivalent to L{cooperate}, but returns a L{Deferred} that will be + fired when the task is done. + + @note: The type of value yielded by the given iterator must match that + of the other iterators added to this cooperator. @param doneDeferred: If specified, this will be the Deferred used as the completion deferred. It is suggested that you use the default, @@ -622,13 +619,11 @@ class Cooperator: """ if doneDeferred is None: doneDeferred = Deferred() - whenDone: Deferred[Iterator[_TaskResultT]] = CooperativeTask( - iterator, self - ).whenDone() + whenDone: Deferred[_TaskIteratorT] = CooperativeTask(iterator, self).whenDone() whenDone.chainDeferred(doneDeferred) return doneDeferred - def cooperate(self, iterator: Iterator[_TaskResultT]) -> CooperativeTask: + def cooperate(self, iterator: _TaskIteratorT) -> CooperativeTask[_TaskIteratorT]: """ Start running the given iterator as a long-running cooperative task, by calling next() on it as a periodic timed event. @@ -639,7 +634,7 @@ class Cooperator: """ return CooperativeTask(iterator, self) - def _addTask(self, task: CooperativeTask) -> None: + def _addTask(self, task: CooperativeTask[Any]) -> None: """ Add a L{CooperativeTask} object to this L{Cooperator}. """ @@ -651,7 +646,7 @@ class Cooperator: self._tasks.append(task) self._reschedule() - def _removeTask(self, task: CooperativeTask) -> None: + def _removeTask(self, task: CooperativeTask[Any]) -> None: """ Remove a L{CooperativeTask} from this L{Cooperator}. """ @@ -661,7 +656,7 @@ class Cooperator: self._delayedCall.cancel() self._delayedCall = None - def _tasksWhileNotStopped(self) -> Iterable[CooperativeTask]: + def _tasksWhileNotStopped(self) -> Iterable[CooperativeTask[Iterator[object]]]: """ Yield all L{CooperativeTask} objects in a loop as long as this L{Cooperator}'s termination condition has not been met. @@ -729,7 +724,7 @@ class Cooperator: _theCooperator = Cooperator() -def coiterate(iterator: Iterator[_T]) -> Deferred[Iterator[_T]]: +def coiterate(iterator: _TaskIteratorT) -> Deferred[_TaskIteratorT]: """ Cooperatively iterate over the given iterator, dividing runtime between it and all other iterators which have been passed to this function and not yet @@ -742,7 +737,7 @@ def coiterate(iterator: Iterator[_T]) -> Deferred[Iterator[_T]]: return _theCooperator.coiterate(iterator) -def cooperate(iterator: Iterator[_T]) -> CooperativeTask: +def cooperate(iterator: _TaskIteratorT) -> CooperativeTask[_TaskIteratorT]: """ Start running the given iterator as a long-running cooperative task, by calling next() on it as a periodic timed event. @@ -771,7 +766,7 @@ class Clock: rightNow = 0.0 def __init__(self) -> None: - self.calls: List[DelayedCall] = [] + self.calls: list[DelayedCall] = [] def seconds(self) -> float: """ @@ -841,7 +836,7 @@ class Clock: def deferLater( clock: IReactorTime, delay: float, - callable: Optional[Callable[..., _T]] = None, + callable: Callable[..., _T] | None = None, *args: object, **kw: object, ) -> Deferred[_T]: @@ -880,10 +875,10 @@ def deferLater( def react( main: Callable[ ..., - Union[Deferred[_T], Coroutine["Deferred[_T]", object, _T]], + Deferred[_T] | Coroutine[Deferred[_T], object, _T], ], argv: Iterable[object] = (), - _reactor: Optional[IReactorCore] = None, + _reactor: IReactorCore | None = None, ) -> NoReturn: """ Call C{main} and run the reactor until the L{Deferred} it returns fires or diff --git a/contrib/python/Twisted/py3/twisted/internet/tcp.py b/contrib/python/Twisted/py3/twisted/internet/tcp.py index 7bc4d1eaac8..7a22be2163f 100644 --- a/contrib/python/Twisted/py3/twisted/internet/tcp.py +++ b/contrib/python/Twisted/py3/twisted/internet/tcp.py @@ -14,12 +14,11 @@ import os import socket import struct import sys -from typing import Any, Callable, ClassVar, List, Optional, Union +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Protocol as TypingProtocol from zope.interface import Interface, implementer import attr -import typing_extensions from twisted.internet.interfaces import ( IHalfCloseableProtocol, @@ -29,7 +28,7 @@ from twisted.internet.interfaces import ( ISystemHandle, ITCPTransport, ) -from twisted.internet.protocol import ClientFactory +from twisted.internet.protocol import ClientFactory, P from twisted.logger import ILogObserver, LogEvent, Logger from twisted.python import deprecate, versions from twisted.python.runtime import platformType @@ -57,11 +56,30 @@ except ImportError: pass -if platformType == "win32": - # no such thing as WSAEPERM or error code 10001 +if platformType != "win32": + from errno import ( + EAGAIN, + EALREADY, + ECONNABORTED, + EINPROGRESS, + EINVAL, + EISCONN, + EMFILE, + ENFILE, + ENOBUFS, + ENOMEM, + EPERM, + EWOULDBLOCK, + ) + from os import strerror +elif not TYPE_CHECKING: # pragma: no branch + # Need a coverage annotation here because we will never fall through this + # branch as TYPE_CHECKING is always False. + + # No such thing as WSAEPERM or error code 10001 # according to winsock.h or MSDN - EPERM = object() - from errno import ( # type: ignore[attr-defined] + EPERM = object() # type:ignore[assignment] + from errno import ( # type: ignore[no-redef,attr-defined] WSAEALREADY as EALREADY, WSAEINPROGRESS as EINPROGRESS, WSAEINVAL as EINVAL, @@ -72,28 +90,13 @@ if platformType == "win32": ) # No such thing as WSAENFILE, either. - ENFILE = object() + ENFILE = object() # type:ignore[assignment] # Nor ENOMEM - ENOMEM = object() + ENOMEM = object() # type:ignore[assignment] EAGAIN = EWOULDBLOCK - from errno import WSAECONNRESET as ECONNABORTED # type: ignore[attr-defined] + from errno import WSAECONNRESET as ECONNABORTED # type: ignore[no-redef,attr-defined] from twisted.python.win32 import formatError as strerror -else: - from errno import EPERM - from errno import EINVAL - from errno import EWOULDBLOCK - from errno import EINPROGRESS - from errno import EALREADY - from errno import EISCONN - from errno import ENOBUFS - from errno import EMFILE - from errno import ENFILE - from errno import ENOMEM - from errno import EAGAIN - from errno import ECONNABORTED - - from os import strerror from errno import errorcode @@ -795,9 +798,9 @@ class Server(_TLSServerMixin, Connection): _base = Connection - _addressType: Union[ - type[address.IPv4Address], type[address.IPv6Address] - ] = address.IPv4Address + _addressType: ( + type[address.IPv4Address] | type[address.IPv6Address] + ) = address.IPv4Address def __init__( self, @@ -960,7 +963,7 @@ class _IFileDescriptorReservation(Interface): """ -class _HasClose(typing_extensions.Protocol): +class _HasClose(TypingProtocol): def close(self) -> object: ... @@ -980,7 +983,7 @@ class _FileDescriptorReservation: _log: ClassVar[Logger] = Logger() _fileFactory: Callable[[], _HasClose] - _fileDescriptor: Optional[_HasClose] = attr.ib(init=False, default=None) + _fileDescriptor: _HasClose | None = attr.ib(init=False, default=None) def available(self): """ @@ -1132,7 +1135,7 @@ class _BuffersLogs: _namespace: str _observer: ILogObserver - _logs: List[LogEvent] = attr.ib(default=attr.Factory(list)) + _logs: list[LogEvent] = attr.ib(default=attr.Factory(list)) def __enter__(self): """ @@ -1277,7 +1280,7 @@ class Port(base.BasePort, _SocketCloser): # Actual port number being listened on, only set to a non-None # value when we are actually listening. - _realPortNumber: Optional[int] = None + _realPortNumber: int | None = None # An externally initialized socket that we will use, rather than creating # our own. @@ -1523,7 +1526,7 @@ class Connector(base.BaseConnector): self, host: str, port: int | str, - factory: ClientFactory, + factory: ClientFactory[P], timeout: float, bindAddress: str | tuple[str, int] | None, reactor: Any = None, diff --git a/contrib/python/Twisted/py3/twisted/internet/testing.py b/contrib/python/Twisted/py3/twisted/internet/testing.py index a7e1de67286..0b82638da6d 100644 --- a/contrib/python/Twisted/py3/twisted/internet/testing.py +++ b/contrib/python/Twisted/py3/twisted/internet/testing.py @@ -7,22 +7,12 @@ Assorted functionality which is commonly useful when writing unit tests. """ from __future__ import annotations -import typing +from collections.abc import Coroutine, Generator, Iterator, Sequence from dataclasses import dataclass from io import BytesIO from socket import AF_INET, AF_INET6 from time import time -from typing import ( - Any, - Callable, - Coroutine, - Generator, - Iterator, - Sequence, - TypeVar, - Union, - overload, -) +from typing import Any, Callable, Protocol, TypeVar, overload from zope.interface import implementedBy, implementer from zope.interface.verify import verifyClass @@ -42,6 +32,7 @@ from twisted.internet.interfaces import ( IHostResolution, IListeningPort, IProtocol, + IProtocolFactory, IPushProducer, IReactorCore, IReactorFDSet, @@ -53,6 +44,7 @@ from twisted.internet.interfaces import ( IResolutionReceiver, ITransport, ) +from twisted.internet.protocol import ClientFactory from twisted.internet.task import Clock from twisted.logger import ILogObserver, LogEvent, LogPublisher from twisted.protocols import basic @@ -79,7 +71,7 @@ __all__ = [ _P = ParamSpec("_P") -class _ProtocolConnectionMadeHaver(typing.Protocol): +class _ProtocolConnectionMadeHaver(Protocol): """ Explicit stipulation of the implicit requirement of L{AccumulatingProtocol}'s factory. """ @@ -539,6 +531,8 @@ class MemoryReactor: """ nameResolver: IHostnameResolver + tcpServers: list[tuple[int, IProtocolFactory, int, str]] + tcpClients: list[tuple[str, int, ClientFactory, int, str | None]] def __init__(self): """ @@ -547,9 +541,9 @@ class MemoryReactor: self.hasInstalled = False self.running = False - self.hasRun = True - self.hasStopped = True - self.hasCrashed = True + self.hasRun = False + self.hasStopped = False + self.hasCrashed = False self.whenRunningHooks = [] self.triggers = {} @@ -665,8 +659,20 @@ class MemoryReactor: """ Fake L{IReactorCore.callWhenRunning}. Keeps a list of invocations to make in C{self.whenRunningHooks}. + + If the reactor has not started, the callable will be scheduled + to run when it does start. Otherwise, the callable will be invoked + immediately. """ - self.whenRunningHooks.append((callable, args, kw)) + # Normally, we would only key off of `self.running`, but the `MemoryReactor` is + # a bit unique in the fact that it stops itself immediately after calling + # `MemoryReactor.run()`. We're going to consider it good enough if the reactor + # has ever been started (`self.hasRun`). + if self.running or self.hasRun: + callable(*args, **kw) + return None + else: + self.whenRunningHooks.append((callable, args, kw)) def adoptStreamPort(self, fileno, addressFamily, factory): """ @@ -1079,11 +1085,11 @@ _T = TypeVar("_T") def _benchmarkWithReactor( test_target: Callable[ [], - Union[ - Coroutine[Deferred[Any], Any, _T], - Generator[Deferred[Any], Any, _T], - Deferred[_T], - ], + ( + Coroutine[Deferred[Any], Any, _T] + | Generator[Deferred[Any], Any, _T] + | Deferred[_T] + ), ], ) -> Callable[[Any], None]: # pragma: no cover """ @@ -1135,8 +1141,9 @@ def _runReactor(callback: Callable[[], Deferred[_T]]) -> None: # pragma: no cov deferred = callback() deferred.addErrback(errors.append) - deferred.addBoth(lambda _: reactor.callLater(0, _stopReactor, reactor)) # type: ignore[attr-defined] - reactor.run(installSignalHandlers=False) # type: ignore[attr-defined] + deferred.addBoth(lambda _: reactor.callLater(0, _stopReactor, reactor)) + # installSignalHandlers is technically not in IReactorCore + reactor.run(installSignalHandlers=False) # type:ignore[call-arg] if errors: # pragma: no cover # Make sure the test fails in a visible way: diff --git a/contrib/python/Twisted/py3/twisted/internet/threads.py b/contrib/python/Twisted/py3/twisted/internet/threads.py index e9a49cbea81..296b066f4cc 100644 --- a/contrib/python/Twisted/py3/twisted/internet/threads.py +++ b/contrib/python/Twisted/py3/twisted/internet/threads.py @@ -10,12 +10,12 @@ For basic support see reactor threading API docs. from __future__ import annotations import queue as Queue -from typing import Callable, TypeVar +from typing import Callable, TypeVar, cast from typing_extensions import ParamSpec from twisted.internet import defer -from twisted.internet.interfaces import IReactorFromThreads +from twisted.internet.interfaces import IReactorFromThreads, IReactorThreads from twisted.python import failure from twisted.python.threadpool import ThreadPool @@ -65,7 +65,11 @@ def deferToThreadPool( return d -def deferToThread(f, *args, **kwargs): +def deferToThread( + f: Callable[_P, _R], + *args: _P.args, + **kwargs: _P.kwargs, +) -> defer.Deferred[_R]: """ Run a function in a thread and return the result as a Deferred. @@ -79,7 +83,9 @@ def deferToThread(f, *args, **kwargs): """ from twisted.internet import reactor - return deferToThreadPool(reactor, reactor.getThreadPool(), f, *args, **kwargs) + reactor_ = cast(IReactorThreads, reactor) + + return deferToThreadPool(reactor_, reactor_.getThreadPool(), f, *args, **kwargs) def _runMultiple(tupleList): @@ -101,7 +107,12 @@ def callMultipleInThread(tupleList): reactor.callInThread(_runMultiple, tupleList) -def blockingCallFromThread(reactor, f, *a, **kw): +def blockingCallFromThread( + reactor: IReactorFromThreads, + f: Callable[_P, _R] | Callable[_P, defer.Deferred[_R]], + *a: _P.args, + **kw: _P.kwargs, +) -> _R: """ Run a function in the reactor from a thread, and wait for the result synchronously. If the function returns a L{Deferred}, wait for its @@ -123,7 +134,7 @@ def blockingCallFromThread(reactor, f, *a, **kw): C{blockingCallFromThread} will raise that failure's exception (see L{Failure.raiseException}). """ - queue = Queue.Queue() + queue: Queue.Queue[_R] = Queue.Queue() def _callFromThread(): result = defer.maybeDeferred(f, *a, **kw) diff --git a/contrib/python/Twisted/py3/twisted/internet/udp.py b/contrib/python/Twisted/py3/twisted/internet/udp.py index 6ef67f323ce..bb96f278711 100644 --- a/contrib/python/Twisted/py3/twisted/internet/udp.py +++ b/contrib/python/Twisted/py3/twisted/internet/udp.py @@ -19,7 +19,6 @@ from __future__ import annotations # System Imports import socket import warnings -from typing import Optional from zope.interface import implementer @@ -87,7 +86,7 @@ class Port(base.BasePort): socketType: socket.SocketKind = socket.SOCK_DGRAM maxThroughput = 256 * 1024 - _realPortNumber: Optional[int] = None + _realPortNumber: int | None = None _preexistingSocket = None def __init__(self, port, proto, interface="", maxPacketSize=8192, reactor=None): @@ -466,7 +465,7 @@ class MulticastPort(MulticastMixin, Port): def createInternetSocket(self) -> socket.socket: """ Override L{Port.createInternetSocket} to configure the socket to honor - the C{listenMultiple} argument to L{IReactorMulticast.listenMultiple}. + the C{listenMultiple} argument to L{IReactorMulticast.listenMulticast}. """ skt = Port.createInternetSocket(self) if self.listenMultiple: diff --git a/contrib/python/Twisted/py3/twisted/internet/unix.py b/contrib/python/Twisted/py3/twisted/internet/unix.py index b33ef299747..e89b4862867 100644 --- a/contrib/python/Twisted/py3/twisted/internet/unix.py +++ b/contrib/python/Twisted/py3/twisted/internet/unix.py @@ -10,13 +10,13 @@ End users shouldn't use this module directly - use the reactor APIs instead. Maintainer: Itamar Shtull-Trauring """ +from __future__ import annotations import os import socket import stat import struct from errno import EAGAIN, ECONNREFUSED, EINTR, EMSGSIZE, ENOBUFS, EWOULDBLOCK -from typing import Optional, Type from zope.interface import implementedBy, implementer, implementer_only @@ -66,7 +66,7 @@ class _SendmsgMixin: registered producer, if there is one. """ - _writeSomeDataBase: Optional[Type[FileDescriptor]] = None + _writeSomeDataBase: type[FileDescriptor] | None = None _fileDescriptorBufferSize = 64 def __init__(self): diff --git a/contrib/python/Twisted/py3/twisted/logger/_buffer.py b/contrib/python/Twisted/py3/twisted/logger/_buffer.py index d5e514f18be..5dc4c3970f6 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_buffer.py +++ b/contrib/python/Twisted/py3/twisted/logger/_buffer.py @@ -6,8 +6,10 @@ Log observer that maintains a buffer. """ +from __future__ import annotations + from collections import deque -from typing import Deque, Optional +from typing import Deque from zope.interface import implementer @@ -34,7 +36,7 @@ class LimitedHistoryLogObserver: >>> """ - def __init__(self, size: Optional[int] = _DEFAULT_BUFFER_MAXIMUM) -> None: + def __init__(self, size: int | None = _DEFAULT_BUFFER_MAXIMUM) -> None: """ @param size: The maximum number of events to buffer. If L{None}, the buffer is unbounded. diff --git a/contrib/python/Twisted/py3/twisted/logger/_capture.py b/contrib/python/Twisted/py3/twisted/logger/_capture.py index 9d3ce0e3abf..a40e6799aef 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_capture.py +++ b/contrib/python/Twisted/py3/twisted/logger/_capture.py @@ -6,8 +6,9 @@ Context manager for capturing logs. """ +from collections.abc import Iterator, Sequence from contextlib import contextmanager -from typing import Iterator, List, Sequence, cast +from typing import cast from twisted.logger import globalLogPublisher from ._interfaces import ILogObserver, LogEvent @@ -15,7 +16,7 @@ from ._interfaces import ILogObserver, LogEvent @contextmanager def capturedLogs() -> Iterator[Sequence[LogEvent]]: - events: List[LogEvent] = [] + events: list[LogEvent] = [] observer = cast(ILogObserver, events.append) globalLogPublisher.addObserver(observer) diff --git a/contrib/python/Twisted/py3/twisted/logger/_file.py b/contrib/python/Twisted/py3/twisted/logger/_file.py index 43ae32cd29a..e44c6332330 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_file.py +++ b/contrib/python/Twisted/py3/twisted/logger/_file.py @@ -6,7 +6,9 @@ File log observer. """ -from typing import IO, Any, Callable, Optional +from __future__ import annotations + +from typing import IO, Any, Callable from zope.interface import implementer @@ -22,7 +24,7 @@ class FileLogObserver: """ def __init__( - self, outFile: IO[Any], formatEvent: Callable[[LogEvent], Optional[str]] + self, outFile: IO[Any], formatEvent: Callable[[LogEvent], str | None] ) -> None: """ @param outFile: A file-like object. Ideally one should be passed which @@ -30,7 +32,7 @@ class FileLogObserver: @param formatEvent: A callable that formats an event. """ if ioType(outFile) is not str: - self._encoding: Optional[str] = "utf-8" + self._encoding: str | None = "utf-8" else: self._encoding = None @@ -54,7 +56,7 @@ class FileLogObserver: def textFileLogObserver( - outFile: IO[Any], timeFormat: Optional[str] = timeFormatRFC3339 + outFile: IO[Any], timeFormat: str | None = timeFormatRFC3339 ) -> FileLogObserver: """ Create a L{FileLogObserver} that emits text to a specified (writable) @@ -69,7 +71,7 @@ def textFileLogObserver( @return: A file log observer. """ - def formatEvent(event: LogEvent) -> Optional[str]: + def formatEvent(event: LogEvent) -> str | None: return formatEventAsClassicLogText( event, formatTime=lambda e: formatTime(e, timeFormat) ) diff --git a/contrib/python/Twisted/py3/twisted/logger/_filter.py b/contrib/python/Twisted/py3/twisted/logger/_filter.py index 07e443849a1..1b32e87f70d 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_filter.py +++ b/contrib/python/Twisted/py3/twisted/logger/_filter.py @@ -6,8 +6,8 @@ Filtering log observer. """ +from collections.abc import Iterable from functools import partial -from typing import Dict, Iterable from zope.interface import Interface, implementer @@ -138,7 +138,7 @@ class LogLevelFilterPredicate: """ @param defaultLogLevel: The default minimum log level. """ - self._logLevelsByNamespace: Dict[str, NamedConstant] = {} + self._logLevelsByNamespace: dict[str, NamedConstant] = {} self.defaultLogLevel = defaultLogLevel self.clearLogLevels() diff --git a/contrib/python/Twisted/py3/twisted/logger/_flatten.py b/contrib/python/Twisted/py3/twisted/logger/_flatten.py index b79476aa248..68a6f5fa7c0 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_flatten.py +++ b/contrib/python/Twisted/py3/twisted/logger/_flatten.py @@ -8,9 +8,11 @@ relevant fields from the format string and persisting them for later examination. """ +from __future__ import annotations + from collections import defaultdict from string import Formatter -from typing import Any, Dict, Optional +from typing import Any from ._interfaces import LogEvent @@ -27,10 +29,10 @@ class KeyFlattener: """ Initialize a L{KeyFlattener}. """ - self.keys: Dict[str, int] = defaultdict(lambda: 0) + self.keys: dict[str, int] = defaultdict(int) def flatKey( - self, fieldName: str, formatSpec: Optional[str], conversion: Optional[str] + self, fieldName: str, formatSpec: str | None, conversion: str | None ) -> str: """ Compute a string key for a given field/format/conversion. diff --git a/contrib/python/Twisted/py3/twisted/logger/_format.py b/contrib/python/Twisted/py3/twisted/logger/_format.py index 57d8c3b1e60..a9ef82f2606 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_format.py +++ b/contrib/python/Twisted/py3/twisted/logger/_format.py @@ -8,10 +8,9 @@ Tools for formatting logging events. from __future__ import annotations +from collections.abc import Iterator, Mapping from datetime import datetime as DateTime -from typing import Any, Callable, Iterator, Mapping, Optional, Union, cast - -from constantly import NamedConstant +from typing import Any, Callable, Optional, Union, cast from twisted.python._tzhelper import FixedOffsetTimeZone from twisted.python.failure import Failure @@ -79,8 +78,8 @@ def formatUnformattableEvent(event: LogEvent, error: BaseException) -> str: def formatTime( - when: Optional[float], - timeFormat: Optional[str] = timeFormatRFC3339, + when: float | None, + timeFormat: str | None = timeFormatRFC3339, default: str = "-", ) -> str: """ @@ -113,8 +112,8 @@ def formatTime( def formatEventAsClassicLogText( - event: LogEvent, formatTime: Callable[[Optional[float]], str] = formatTime -) -> Optional[str]: + event: LogEvent, formatTime: Callable[[float | None], str] = formatTime +) -> str | None: """ Format an event as a line of human-readable text for, e.g. traditional log file output. @@ -173,7 +172,7 @@ def keycall(key: str, getter: Callable[[str], Any]) -> PotentialCallWrapper: of C{get} first, before wrapping it up. @param key: The last dotted segment of a formatting key, as parsed by - L{Formatter.vformat}, which may end in C{()}. + L{string.Formatter.vformat}, which may end in C{()}. @param getter: A function which takes a string and returns some other object, to be formatted and stringified for a log. @@ -189,7 +188,7 @@ def keycall(key: str, getter: Callable[[str], Any]) -> PotentialCallWrapper: return PotentialCallWrapper(value) -class PotentialCallWrapper(object): +class PotentialCallWrapper: """ Object wrapper that wraps C{getattr()} so as to process call-parentheses C{"()"} after a dotted attribute access. @@ -337,7 +336,7 @@ def _formatSystem(event: LogEvent) -> str: """ system = cast(Optional[str], event.get("log_system", None)) if system is None: - level = cast(Optional[NamedConstant], event.get("log_level", None)) + level = event.get("log_level", None) if level is None: levelName = "-" else: diff --git a/contrib/python/Twisted/py3/twisted/logger/_global.py b/contrib/python/Twisted/py3/twisted/logger/_global.py index 8ae89baf728..6cbcdbe42b2 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_global.py +++ b/contrib/python/Twisted/py3/twisted/logger/_global.py @@ -7,9 +7,12 @@ This module includes process-global state associated with the logging system, and implementation of logic for managing that global state. """ +from __future__ import annotations + import sys import warnings -from typing import IO, Any, Iterable, Optional, Type +from collections.abc import Iterable +from typing import IO, Any from twisted.python.compat import currentframe from twisted.python.reflect import qual @@ -73,7 +76,7 @@ class LogBeginner: errorStream: IO[Any], stdio: object, warningsModule: Any, - initialBufferSize: Optional[int] = None, + initialBufferSize: int | None = None, ) -> None: """ Initialize this L{LogBeginner}. @@ -89,7 +92,7 @@ class LogBeginner: self._log = Logger(observer=publisher) self._stdio = stdio self._warningsModule = warningsModule - self._temporaryObserver: Optional[ILogObserver] = LogPublisher( + self._temporaryObserver: ILogObserver | None = LogPublisher( self._initialBuffer, FilteringLogObserver( FileLogObserver( @@ -185,11 +188,11 @@ class LogBeginner: def showwarning( self, message: str, - category: Type[Warning], + category: type[Warning], filename: str, lineno: int, - file: Optional[IO[Any]] = None, - line: Optional[str] = None, + file: IO[Any] | None = None, + line: str | None = None, ) -> None: """ Twisted-enabled wrapper around L{warnings.showwarning}. diff --git a/contrib/python/Twisted/py3/twisted/logger/_interfaces.py b/contrib/python/Twisted/py3/twisted/logger/_interfaces.py index 496de1de540..c1182448704 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_interfaces.py +++ b/contrib/python/Twisted/py3/twisted/logger/_interfaces.py @@ -5,16 +5,18 @@ Logger interfaces. """ -from typing import TYPE_CHECKING, Any, Dict, List, Tuple +from typing import TYPE_CHECKING, Any from zope.interface import Interface +from typing_extensions import TypeAlias + if TYPE_CHECKING: from ._logger import Logger -LogEvent = Dict[str, Any] -LogTrace = List[Tuple["Logger", "ILogObserver"]] +LogEvent: TypeAlias = dict[str, Any] +LogTrace: TypeAlias = list[tuple["Logger", "ILogObserver"]] class ILogObserver(Interface): diff --git a/contrib/python/Twisted/py3/twisted/logger/_io.py b/contrib/python/Twisted/py3/twisted/logger/_io.py index 96634212201..bf98ee562df 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_io.py +++ b/contrib/python/Twisted/py3/twisted/logger/_io.py @@ -6,8 +6,11 @@ File-like object that logs. """ +from __future__ import annotations + import sys -from typing import AnyStr, Iterable, Optional +from collections.abc import Iterable +from typing import AnyStr from constantly import NamedConstant from incremental import Version @@ -29,6 +32,7 @@ class LoggingFile: """ _softspace = 0 + _encoding: str @deprecatedProperty(Version("Twisted", 21, 2, 0)) def softspace(self): @@ -42,7 +46,7 @@ class LoggingFile: self, logger: Logger, level: NamedConstant = LogLevel.info, - encoding: Optional[str] = None, + encoding: str | None = None, ) -> None: """ @param logger: the logger to log through. diff --git a/contrib/python/Twisted/py3/twisted/logger/_json.py b/contrib/python/Twisted/py3/twisted/logger/_json.py index aa837ded75a..0d353d840d1 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_json.py +++ b/contrib/python/Twisted/py3/twisted/logger/_json.py @@ -6,8 +6,11 @@ Tools for saving and loading log events in a structured format. """ +from __future__ import annotations + +from collections.abc import Iterable from json import dumps, loads -from typing import IO, Any, AnyStr, Dict, Iterable, Optional, Union, cast +from typing import IO, Any, AnyStr, cast from uuid import UUID from constantly import NamedConstant @@ -22,7 +25,7 @@ from ._logger import Logger log = Logger() -JSONDict = Dict[str, Any] +JSONDict = dict[str, Any] def failureAsJSON(failure: Failure) -> JSONDict: @@ -133,7 +136,7 @@ def eventAsJSON(event: LogEvent) -> str: file. """ - def default(unencodable: object) -> Union[JSONDict, str]: + def default(unencodable: object) -> JSONDict | str: """ Serialize an object not otherwise serializable by L{dumps}. @@ -189,7 +192,7 @@ def jsonFileLogObserver( def eventsFromJSONLogFile( inFile: IO[Any], - recordSeparator: Optional[str] = None, + recordSeparator: str | None = None, bufferSize: int = 4096, ) -> Iterable[LogEvent]: """ @@ -213,7 +216,7 @@ def eventsFromJSONLogFile( else: return s.encode("utf-8") - def eventFromBytearray(record: bytearray) -> Optional[LogEvent]: + def eventFromBytearray(record: bytearray) -> LogEvent | None: try: text = bytes(record).decode("utf-8") except UnicodeDecodeError: @@ -251,7 +254,7 @@ def eventsFromJSONLogFile( else: - def eventFromRecord(record: bytearray) -> Optional[LogEvent]: + def eventFromRecord(record: bytearray) -> LogEvent | None: if record[-1] == ord("\n"): return eventFromBytearray(record) else: diff --git a/contrib/python/Twisted/py3/twisted/logger/_legacy.py b/contrib/python/Twisted/py3/twisted/logger/_legacy.py index 2847bc7a406..c8ec2e40137 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_legacy.py +++ b/contrib/python/Twisted/py3/twisted/logger/_legacy.py @@ -6,7 +6,9 @@ Integration with L{twisted.python.log}. """ -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Callable from zope.interface import implementer @@ -29,7 +31,7 @@ class LegacyLogObserverWrapper: expect legacy events. """ - def __init__(self, legacyObserver: "ILegacyLogObserver") -> None: + def __init__(self, legacyObserver: ILegacyLogObserver) -> None: """ @param legacyObserver: a legacy observer to which this observer will forward events. @@ -92,8 +94,8 @@ class LegacyLogObserverWrapper: def publishToNewObserver( observer: ILogObserver, - eventDict: Dict[str, Any], - textFromEventDict: Callable[[Dict[str, Any]], Optional[str]], + eventDict: dict[str, Any], + textFromEventDict: Callable[[dict[str, Any]], str | None], ) -> None: """ Publish an old-style (L{twisted.python.log}) event to a new-style diff --git a/contrib/python/Twisted/py3/twisted/logger/_logger.py b/contrib/python/Twisted/py3/twisted/logger/_logger.py index b7f3e2e1213..a84ad79eb6c 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_logger.py +++ b/contrib/python/Twisted/py3/twisted/logger/_logger.py @@ -10,7 +10,7 @@ from __future__ import annotations from time import time from types import TracebackType -from typing import Any, Callable, ContextManager, Optional, Protocol, cast +from typing import Any, Callable, ContextManager, Protocol, cast from twisted.python.compat import currentframe from twisted.python.failure import Failure @@ -124,9 +124,9 @@ class Logger: def __init__( self, - namespace: Optional[str] = None, - source: Optional[object] = None, - observer: Optional["ILogObserver"] = None, + namespace: str | None = None, + source: object | None = None, + observer: ILogObserver | None = None, ) -> None: """ @param namespace: The namespace for this logger. Uses a dotted @@ -151,7 +151,7 @@ class Logger: else: self.observer = observer - def __get__(self, instance: object, owner: Optional[type] = None) -> "Logger": + def __get__(self, instance: object, owner: type | None = None) -> Logger: """ When used as a descriptor, i.e.:: @@ -187,7 +187,7 @@ class Logger: return f"<{self.__class__.__name__} {self.namespace!r}>" def emit( - self, level: LogLevel, format: Optional[str] = None, **kwargs: object + self, level: LogLevel, format: str | None = None, **kwargs: object ) -> None: """ Emit a log event to all log observers at the given level. @@ -228,7 +228,7 @@ class Logger: def failure( self, format: str, - failure: Optional[Failure] = None, + failure: Failure | None = None, level: LogLevel = LogLevel.critical, **kwargs: object, ) -> None: @@ -280,7 +280,7 @@ class Logger: self.emit(level, format, log_failure=failure, **kwargs) - def debug(self, format: Optional[str] = None, **kwargs: object) -> None: + def debug(self, format: str | None = None, **kwargs: object) -> None: """ Emit a log event at log level L{LogLevel.debug}. @@ -295,7 +295,7 @@ class Logger: """ self.emit(LogLevel.debug, format, **kwargs) - def info(self, format: Optional[str] = None, **kwargs: object) -> None: + def info(self, format: str | None = None, **kwargs: object) -> None: """ Emit a log event at log level L{LogLevel.info}. @@ -310,7 +310,7 @@ class Logger: """ self.emit(LogLevel.info, format, **kwargs) - def warn(self, format: Optional[str] = None, **kwargs: object) -> None: + def warn(self, format: str | None = None, **kwargs: object) -> None: """ Emit a log event at log level L{LogLevel.warn}. @@ -325,7 +325,7 @@ class Logger: """ self.emit(LogLevel.warn, format, **kwargs) - def error(self, format: Optional[str] = None, **kwargs: object) -> None: + def error(self, format: str | None = None, **kwargs: object) -> None: """ Emit a log event at log level L{LogLevel.error}. @@ -340,7 +340,7 @@ class Logger: """ self.emit(LogLevel.error, format, **kwargs) - def critical(self, format: Optional[str] = None, **kwargs: object) -> None: + def critical(self, format: str | None = None, **kwargs: object) -> None: """ Emit a log event at log level L{LogLevel.critical}. @@ -431,9 +431,9 @@ class Logger: corrective guidance can be offered to an user/administrator, and the impact of the condition is unknown. - @param format: a message format using new-style (PEP 3101) formatting. - The logging event (which is a L{dict}) is used to render this - format string. + @param staticMessage: a message format using new-style (PEP 3101) + formatting. The logging event (which is a L{dict}) is used to + render this format string. @param level: a L{LogLevel} to use. diff --git a/contrib/python/Twisted/py3/twisted/logger/_observer.py b/contrib/python/Twisted/py3/twisted/logger/_observer.py index 86f89c37b45..36bfa278e63 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_observer.py +++ b/contrib/python/Twisted/py3/twisted/logger/_observer.py @@ -6,7 +6,9 @@ Basic log observers. """ -from typing import Callable, Optional +from __future__ import annotations + +from typing import Callable from zope.interface import implementer @@ -59,7 +61,7 @@ class LogPublisher: Forward events to contained observers. """ if "log_trace" not in event: - trace: Optional[Callable[[ILogObserver], None]] = None + trace: Callable[[ILogObserver], None] | None = None else: diff --git a/contrib/python/Twisted/py3/twisted/logger/_stdlib.py b/contrib/python/Twisted/py3/twisted/logger/_stdlib.py index abc707e4a82..5216e6739d3 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_stdlib.py +++ b/contrib/python/Twisted/py3/twisted/logger/_stdlib.py @@ -6,8 +6,10 @@ Integration with Python standard library logging. """ +from __future__ import annotations + import logging as stdlibLogging -from typing import Mapping, Tuple +from collections.abc import Mapping from zope.interface import implementer @@ -28,13 +30,14 @@ toStdlibLogLevelMapping: Mapping[NamedConstant, int] = { } -def _reverseLogLevelMapping() -> Mapping[int, NamedConstant]: +def _reverseLogLevelMapping() -> Mapping[str | int, NamedConstant]: """ Reverse the above mapping, adding both the numerical keys used above and the corresponding string keys also used by python logging. + @return: the reversed mapping """ - mapping = {} + mapping: dict[str | int, NamedConstant] = {} for logLevel, pyLogLevel in toStdlibLogLevelMapping.items(): mapping[pyLogLevel] = logLevel mapping[stdlibLogging.getLevelName(pyLogLevel)] = logLevel @@ -80,7 +83,7 @@ class STDLibLogObserver: def _findCaller( self, stackInfo: bool = False, stackLevel: int = 1 - ) -> Tuple[str, int, str, None]: + ) -> tuple[str, int, str, None]: """ Based on the stack depth passed to this L{STDLibLogObserver}, identify the calling function. diff --git a/contrib/python/Twisted/py3/twisted/logger/_util.py b/contrib/python/Twisted/py3/twisted/logger/_util.py index e8f02ddd225..f8c626e7136 100644 --- a/contrib/python/Twisted/py3/twisted/logger/_util.py +++ b/contrib/python/Twisted/py3/twisted/logger/_util.py @@ -6,8 +6,6 @@ Logging utilities. """ -from typing import List - from ._interfaces import LogTrace from ._logger import Logger @@ -31,7 +29,7 @@ def formatTrace(trace: LogTrace) -> str: return f"{obj}" result = [] - lineage: List[Logger] = [] + lineage: list[Logger] = [] for parent, child in trace: if not lineage or lineage[-1] is not parent: diff --git a/contrib/python/Twisted/py3/twisted/mail/_pop3client.py b/contrib/python/Twisted/py3/twisted/mail/_pop3client.py index 08efe1ec545..14bd07836b9 100644 --- a/contrib/python/Twisted/py3/twisted/mail/_pop3client.py +++ b/contrib/python/Twisted/py3/twisted/mail/_pop3client.py @@ -13,7 +13,6 @@ Don't use this module directly. Use twisted.mail.pop3 instead. import re from hashlib import md5 -from typing import List from twisted.internet import defer, error, interfaces from twisted.mail._except import ( @@ -1232,4 +1231,4 @@ class POP3Client(basic.LineOnlyReceiver, policies.TimeoutMixin): return self.sendShort(b"QUIT", None) -__all__: List[str] = [] +__all__: list[str] = [] diff --git a/contrib/python/Twisted/py3/twisted/mail/imap4.py b/contrib/python/Twisted/py3/twisted/mail/imap4.py index f38d094f896..bf8e3afeacd 100644 --- a/contrib/python/Twisted/py3/twisted/mail/imap4.py +++ b/contrib/python/Twisted/py3/twisted/mail/imap4.py @@ -14,6 +14,7 @@ To do:: Clarify some API docs (Query, etc) Make APPEND recognize (again) non-existent mailboxes before accepting the literal """ +from __future__ import annotations import binascii import codecs @@ -28,7 +29,7 @@ import uuid from base64 import decodebytes, encodebytes from io import BytesIO from itertools import chain -from typing import Any, List, Optional, cast +from typing import Any, cast from zope.interface import implementer @@ -184,7 +185,7 @@ class MessageSet: that it will not be called out-of-order). """ - _empty: List[Any] = [] + _empty: list[Any] = [] _infinity = float("inf") def __init__(self, start=_empty, end=_empty): @@ -5691,7 +5692,7 @@ class _FetchParser: def __str__(self) -> str: return self.__bytes__().decode("ascii") - def getBytes(self, length: Optional[int] = None) -> bytes: + def getBytes(self, length: int | None = None) -> bytes: """ Prepare the initial command response for a Fetch BODY request. Interpret the Fetch request from the client and return the diff --git a/contrib/python/Twisted/py3/twisted/mail/interfaces.py b/contrib/python/Twisted/py3/twisted/mail/interfaces.py index 0bb78e1bfa1..027bc6ffbc0 100644 --- a/contrib/python/Twisted/py3/twisted/mail/interfaces.py +++ b/contrib/python/Twisted/py3/twisted/mail/interfaces.py @@ -155,8 +155,7 @@ class IMailboxPOP3(Interface): Retrieve the size of a message, or, if none is specified, the size of each message in the mailbox. - @type index: L{int} or L{None} - @param index: The 0-based index of the message. + @param i: The 0-based index of the message. @rtype: L{int}, sequence of L{int}, or L{Deferred <defer.Deferred>} @return: The number of octets in the specified message, or, if an diff --git a/contrib/python/Twisted/py3/twisted/mail/pop3.py b/contrib/python/Twisted/py3/twisted/mail/pop3.py index 8b4405cb3b7..a564124e3d4 100644 --- a/contrib/python/Twisted/py3/twisted/mail/pop3.py +++ b/contrib/python/Twisted/py3/twisted/mail/pop3.py @@ -16,7 +16,7 @@ import base64 import binascii import warnings from hashlib import md5 -from typing import IO, Optional, overload +from typing import IO, overload from zope.interface import implementer @@ -432,7 +432,7 @@ class POP3(basic.LineOnlyReceiver, policies.TimeoutMixin): @ivar _auth: Authorization credentials. """ - magic: Optional[bytes] = None + magic: bytes | None = None _userIs = None _onLogout = None diff --git a/contrib/python/Twisted/py3/twisted/mail/pop3client.py b/contrib/python/Twisted/py3/twisted/mail/pop3client.py index 72676a69a81..1ab47366fa8 100644 --- a/contrib/python/Twisted/py3/twisted/mail/pop3client.py +++ b/contrib/python/Twisted/py3/twisted/mail/pop3client.py @@ -4,7 +4,6 @@ Deprecated POP3 client protocol implementation. Don't use this module directly. Use twisted.mail.pop3 instead. """ import warnings -from typing import List from twisted.mail._pop3client import ERR, OK, POP3Client @@ -19,4 +18,4 @@ OK ERR POP3Client -__all__: List[str] = [] +__all__: list[str] = [] diff --git a/contrib/python/Twisted/py3/twisted/mail/protocols.py b/contrib/python/Twisted/py3/twisted/mail/protocols.py index 035a1b0c12b..b78e380da04 100644 --- a/contrib/python/Twisted/py3/twisted/mail/protocols.py +++ b/contrib/python/Twisted/py3/twisted/mail/protocols.py @@ -365,7 +365,7 @@ class POP3Factory(protocol.ServerFactory): L{VirtualPOP3}. """ - protocol = VirtualPOP3 + protocol = VirtualPOP3 # type:ignore[assignment] service = None def __init__(self, service): diff --git a/contrib/python/Twisted/py3/twisted/mail/relaymanager.py b/contrib/python/Twisted/py3/twisted/mail/relaymanager.py index 18cc2878331..f7944a519a2 100644 --- a/contrib/python/Twisted/py3/twisted/mail/relaymanager.py +++ b/contrib/python/Twisted/py3/twisted/mail/relaymanager.py @@ -10,12 +10,12 @@ configurations. Instead of sending mail directly to the recipient, a sender sends mail to a smart host. The smart host finds the mail exchange server for the recipient and sends on the message. """ +from __future__ import annotations import email.utils import os import pickle import time -from typing import Type from twisted.application import internet from twisted.internet import protocol @@ -69,7 +69,7 @@ class ManagedRelayerMixin: self.manager.notifyDone(self.factory) -class SMTPManagedRelayer(ManagedRelayerMixin, relay.SMTPRelayer): # type: ignore[misc] +class SMTPManagedRelayer(ManagedRelayerMixin, relay.SMTPRelayer): """ An SMTP managed relayer. @@ -100,7 +100,7 @@ class SMTPManagedRelayer(ManagedRelayerMixin, relay.SMTPRelayer): # type: ignor relay.SMTPRelayer.__init__(self, messages, *args, **kw) -class ESMTPManagedRelayer(ManagedRelayerMixin, relay.ESMTPRelayer): # type: ignore[misc] +class ESMTPManagedRelayer(ManagedRelayerMixin, relay.ESMTPRelayer): """ An ESMTP managed relayer. @@ -154,7 +154,7 @@ class SMTPManagedRelayerFactory(protocol.ClientFactory): @ivar pKwArgs: Keyword arguments for L{SMTPClient.__init__} """ - protocol: "Type[protocol.Protocol]" = SMTPManagedRelayer + protocol: type[protocol.Protocol] = SMTPManagedRelayer def __init__(self, messages, manager, *args, **kw): """ @@ -674,7 +674,7 @@ class SmartHostSMTPRelayingManager: filenames of messages the managed relayer is responsible for. """ - factory: Type[protocol.ClientFactory] = SMTPManagedRelayerFactory + factory: type[protocol.ClientFactory] = SMTPManagedRelayerFactory PORT = 25 diff --git a/contrib/python/Twisted/py3/twisted/mail/smtp.py b/contrib/python/Twisted/py3/twisted/mail/smtp.py index 55511647e66..058f4e24783 100644 --- a/contrib/python/Twisted/py3/twisted/mail/smtp.py +++ b/contrib/python/Twisted/py3/twisted/mail/smtp.py @@ -19,13 +19,12 @@ import time import warnings from email.utils import parseaddr from io import BytesIO -from typing import Type from zope.interface import implementer from twisted import cred from twisted.copyright import longversion -from twisted.internet import defer, error, protocol, reactor +from twisted.internet import defer, protocol, reactor from twisted.internet._idna import _idnaText from twisted.internet.interfaces import ISSLTransport, ITLSTransport from twisted.mail._cred import ( @@ -950,6 +949,10 @@ class SMTPClient(basic.LineReceiver, policies.TimeoutMixin): self.code = -1 self.log = util.LineLog(logsize) + # A `Failure` giving the reason we were unable to successfully send the message, + # if any. Otherwise, `None`. + self._failureReason = None + def sendLine(self, line): # Log sendLine only if you are in debug mode for performance if self.debug: @@ -968,6 +971,7 @@ class SMTPClient(basic.LineReceiver, policies.TimeoutMixin): """ We are no longer connected """ + self._failureReason = reason self.setTimeout(None) self.mailFile = None @@ -1862,7 +1866,7 @@ class SMTPSenderFactory(protocol.ClientFactory): """ domain = DNSNAME - protocol: Type[SMTPClient] = SMTPSender + protocol: type[SMTPClient] = SMTPSender def __init__(self, fromEmail, toEmail, file, deferred, retries=5, timeout=None): """ @@ -1922,8 +1926,22 @@ class SMTPSenderFactory(protocol.ClientFactory): self._processConnectionError(connector, err) def _processConnectionError(self, connector, err): + # If the initial connection succeeded, but there was a later error + # (such as TLS validation failure), we're more interested in that than + # "Connection was closed cleanly" or whatever. + if ( + self.currentProtocol is not None + and self.currentProtocol._failureReason is not None + ): + err = self.currentProtocol._failureReason + self.currentProtocol = None - if (self.retries < 0) and (not self.sendFinished): + if self.sendFinished: + # we already completed the send, and the deferred has been called. + # There is nothing more we can do. + return + + if self.retries < 0: log.msg("SMTP Client retrying server. Retry: %s" % -self.retries) # Rewind the file in case part of it was read while attempting to @@ -1931,12 +1949,14 @@ class SMTPSenderFactory(protocol.ClientFactory): self.file.seek(0, 0) connector.connect() self.retries += 1 - elif not self.sendFinished: - # If we were unable to communicate with the SMTP server a ConnectionDone will be - # returned. We want a more clear error message for debugging - if err.check(error.ConnectionDone): - err.value = SMTPConnectError(-1, "Unable to connect to server.") - self.result.errback(err.value) + return + + # do our best to wrap the error into something more meaningful. + exn = SMTPConnectError( + -1, "Unable to connect to SMTP server: " + str(err.value) + ) + exn.__cause__ = err.value + self.result.errback(exn) def buildProtocol(self, addr): p = self.protocol(self.domain, self.nEmails * 2 + 2) diff --git a/contrib/python/Twisted/py3/twisted/names/authority.py b/contrib/python/Twisted/py3/twisted/names/authority.py index 33df6c00686..d6043d92a7f 100644 --- a/contrib/python/Twisted/py3/twisted/names/authority.py +++ b/contrib/python/Twisted/py3/twisted/names/authority.py @@ -307,12 +307,11 @@ class BindAuthority(FileAuthority): Load records from C{filename}. @param filename: file to read from - @type filename: L{bytes} """ fp = FilePath(filename) # Not the best way to set an origin. It can be set using $ORIGIN # though. - self.origin = nativeString(fp.basename() + b".") + self.origin = fp.asTextMode().basename() + "." lines = fp.getContent().splitlines(True) lines = self.stripComments(lines) diff --git a/contrib/python/Twisted/py3/twisted/names/dns.py b/contrib/python/Twisted/py3/twisted/names/dns.py index c7644ef50d6..257efc93122 100644 --- a/contrib/python/Twisted/py3/twisted/names/dns.py +++ b/contrib/python/Twisted/py3/twisted/names/dns.py @@ -11,13 +11,16 @@ Future Plans: from __future__ import annotations # System imports +import contextvars import inspect import random import socket import struct +from collections.abc import Generator, Sequence +from contextlib import contextmanager from io import BytesIO from itertools import chain -from typing import Optional, Sequence, SupportsInt, Union, overload +from typing import SupportsInt, overload from zope.interface import Attribute, Interface, implementer @@ -126,6 +129,7 @@ __all__ = [ "OP_UPDATE", "PORT", "AuthoritativeDomainError", + "DNSDecodeError", "DNSQueryTimeoutError", "DomainError", ] @@ -402,7 +406,7 @@ def _str2time(s: str) -> int: @overload -def str2time(s: Union[str, bytes, int]) -> int: +def str2time(s: str | bytes | int) -> int: ... @@ -411,7 +415,7 @@ def str2time(s: None) -> None: ... -def str2time(s: Union[str, bytes, int, None]) -> Union[int, None]: +def str2time(s: str | bytes | int | None) -> int | None: """ Parse a string description of an interval into an integer number of seconds. @@ -444,6 +448,87 @@ def readPrecisely(file, l): return buff +class DNSDecodeError(ValueError): + """ + Raised when a DNS message cannot be decoded because it violates a + protocol-level safety limit. + """ + + +class _DecodeContext: + """ + Mutable state shared between the L{IEncodable} decoders invoked while + reading a single DNS message. + + The primary purpose is to bound the total number of compression-pointer + jumps taken across every name in the message, defending against packets + that fan out thousands of records pointing to deeply chained pointers. + + This class is private. External callers must not rely on it; the + per-message scope is installed and torn down by L{Message.decode} + through L{_decodeContextVar}. + + @ivar jumps: The number of compression pointers followed so far. + @ivar maxJumps: The inclusive upper bound on L{jumps}. Exceeding it + causes L{registerJump} to raise L{DNSDecodeError}. + """ + + __slots__ = ("jumps", "maxJumps") + + def __init__(self, maxJumps: int = 1000) -> None: + self.jumps = 0 + self.maxJumps = maxJumps + + def registerJump(self) -> None: + """ + Record that a compression pointer has been followed. + + The check is performed before any further bytes are read so the + caller fails fast as soon as the aggregate limit is breached, even + if additional records remain in the buffer. + + @raise DNSDecodeError: if the cumulative number of jumps exceeds + L{maxJumps}. + """ + self.jumps += 1 + if self.jumps > self.maxJumps: + raise DNSDecodeError( + "Too many compression pointers while decoding DNS message " + f"(limit is {self.maxJumps})" + ) + + +# Private module-level L{contextvars.ContextVar} used to share a single +# L{_DecodeContext} across the re-entrant calls performed while decoding one +# DNS message. L{contextvars} (rather than a plain module attribute) is used +# on purpose: although Twisted's reactor is single-threaded, message decoding +# is re-entrant across many records in a single pass and L{ContextVar} +# guarantees the scope is restored correctly on exit -- and remains isolated +# per-task should a future caller decode messages from multiple +# L{asyncio}-style contexts concurrently. +_decodeContextVar: contextvars.ContextVar[ + _DecodeContext | None +] = contextvars.ContextVar("_dnsDecodeContext", default=None) + + +@contextmanager +def _installDecodeContext(context: _DecodeContext) -> Generator[_DecodeContext]: + """ + Install C{context} on L{_decodeContextVar} for the duration of the + C{with} block and restore the previous value on exit. + + This wraps the L{contextvars.ContextVar.set} / L{contextvars.ContextVar.reset} + token dance so call sites can use a plain C{with} statement. + + @param context: The L{_DecodeContext} to install as the active context. + """ + token = _decodeContextVar.set(context) + try: + yield context + finally: + _decodeContextVar.reset(token) + + class IEncodable(Interface): """ Interface for something which can be encoded to and decoded @@ -549,8 +634,17 @@ class Name: @ivar name: A byte string giving the name. @type name: L{bytes} + + @ivar maxCompressionPointers: Per-message cap on the total number of + compression-pointer dereferences L{decode} will follow before + raising L{DNSDecodeError}. Defaults to C{1000}. Override it on + a subclass or individual instance to tune the trade-off between + tolerance for legitimately verbose messages and resistance to + denial-of-service attacks. """ + maxCompressionPointers: int = 1000 + def __init__(self, name: bytes | str = b""): """ @param name: A name. @@ -595,16 +689,33 @@ class Name: """ Decode a byte string into this Name. + When invoked from L{Message.decode}, a shared compression-pointer + counter is picked up transparently from the private + L{_decodeContextVar}. Standalone callers get a fresh per-call + counter seeded from L{maxCompressionPointers}, so existing code + keeps working unchanged while still being protected against + pathological inputs. + @type strio: file @param strio: Bytes will be read from this file until the full Name - is decoded. + is decoded. + + @type length: L{int} or L{None} + @param length: Present for compatibility with the L{IEncodable} + interface; ignored by this decoder. @raise EOFError: Raised when there are not enough bytes available - from C{strio}. + from C{strio}. + + @raise ValueError: Raised when the name cannot be decoded because + it contains a compression loop. - @raise ValueError: Raised when the name cannot be decoded (for example, - because it contains a loop). + @raise DNSDecodeError: Raised when the cumulative number of + compression-pointer jumps exceeds the configured limit. """ + context = _decodeContextVar.get() + if context is None: + context = _DecodeContext(maxJumps=self.maxCompressionPointers) visited = set() self.name = b"" off = 0 @@ -616,6 +727,7 @@ class Name: return if (l >> 6) == 3: new_off = (l & 63) << 8 | ord(readPrecisely(strio, 1)) + context.registerJump() if new_off in visited: raise ValueError("Compression loop in encoded name") visited.add(new_off) @@ -660,7 +772,7 @@ class Query: @type cls: L{int} """ - def __init__(self, name: Union[bytes, str] = b"", type: int = A, cls: int = IN): + def __init__(self, name: bytes | str = b"", type: int = A, cls: int = IN): """ @type name: L{bytes} or L{str} @param name: See L{Query.name} @@ -989,11 +1101,11 @@ class RRHeader(tputil.FancyEqMixin): def __init__( self, - name: Union[bytes, str] = b"", + name: bytes | str = b"", type: int = A, cls: int = IN, ttl: SupportsInt = 0, - payload: Optional[IEncodableRecord] = None, + payload: IEncodableRecord | None = None, auth: bool = False, ): """ @@ -1093,7 +1205,7 @@ class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin): showAttributes = (("name", "name", "%s"), "ttl") compareAttributes = ("name", "ttl") - TYPE: Optional[int] = None + TYPE: int | None = None name = None def __init__(self, name=b"", ttl=None): @@ -1568,7 +1680,7 @@ class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin): prefixLen: int = 0, suffix: bytes | str = "::", prefix: bytes | str = b"", - ttl: Union[str, bytes, int, None] = None, + ttl: str | bytes | int | None = None, ): """ @param suffix: An IPv6 address suffix in in RFC 2373 format. @@ -1943,7 +2055,7 @@ class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin): self, cpu: bytes = b"", os: bytes = b"", - ttl: Union[str, bytes, int, None] = None, + ttl: str | bytes | int | None = None, ): self.cpu, self.os = cpu, os self.ttl = str2time(ttl) @@ -2488,8 +2600,17 @@ class Message(tputil.FancyEqMixin): header fields. @ivar _sectionNames: The names of attributes representing the record sections of this message. + + @ivar maxCompressionPointers: Per-message cap on the total number of + compression-pointer dereferences L{decode} will follow across every + name in the message before raising L{DNSDecodeError}. Defaults to + C{1000}. Override it on a subclass or individual instance to tune + the trade-off between tolerance for legitimately verbose messages + and resistance to denial-of-service attacks. """ + maxCompressionPointers: int = 1000 + compareAttributes = ( "id", "answer", @@ -2704,19 +2825,29 @@ class Message(tputil.FancyEqMixin): self.checkingDisabled = (byte4 >> 4) & 1 self.rCode = byte4 & 0xF - self.queries = [] - for i in range(nqueries): - q = Query() - try: - q.decode(strio) - except EOFError: - return - self.queries.append(q) + # A single shared counter bounds the total compression-pointer work + # performed across every name in this message. It is installed on + # the private context variable so nested record decoders pick it up + # without needing to thread it through each signature. + decodeContext = _DecodeContext(maxJumps=self.maxCompressionPointers) + with _installDecodeContext(decodeContext): + self.queries = [] + for i in range(nqueries): + q = Query() + try: + q.decode(strio) + except EOFError: + return + self.queries.append(q) - items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd)) + items = ( + (self.answers, nans), + (self.authority, nns), + (self.additional, nadd), + ) - for l, n in items: - self.parseRecords(l, n, strio) + for l, n in items: + self.parseRecords(l, n, strio) def parseRecords(self, list, num, strio): for i in range(num): diff --git a/contrib/python/Twisted/py3/twisted/names/server.py b/contrib/python/Twisted/py3/twisted/names/server.py index 63fff7a2778..6ff64fcb6e6 100644 --- a/contrib/python/Twisted/py3/twisted/names/server.py +++ b/contrib/python/Twisted/py3/twisted/names/server.py @@ -62,7 +62,7 @@ class DNSServerFactory(protocol.ServerFactory): """ # Type is wrong. See: https://twistedmatrix.com/trac/ticket/10004#ticket - protocol = dns.DNSProtocol # type: ignore[assignment] + protocol = dns.DNSProtocol cache = None _messageFactory = dns.Message diff --git a/contrib/python/Twisted/py3/twisted/pair/ip.py b/contrib/python/Twisted/py3/twisted/pair/ip.py index 3606abf2721..da1bb8af67c 100644 --- a/contrib/python/Twisted/py3/twisted/pair/ip.py +++ b/contrib/python/Twisted/py3/twisted/pair/ip.py @@ -57,7 +57,15 @@ class IPProtocol(protocol.AbstractDatagramProtocol): self.ipProtos[num] = [] self.ipProtos[num].append(proto) - def datagramReceived(self, data, partial, dest, source, protocol): + # this never should have subclassed AbstractDatagramProtocol + def datagramReceived( # type:ignore[override] + self, + data, + partial, + dest, + source, + protocol, + ): header = IPHeader(data) for proto in self.ipProtos.get(header.protocol, ()): proto.datagramReceived( diff --git a/contrib/python/Twisted/py3/twisted/pair/rawudp.py b/contrib/python/Twisted/py3/twisted/pair/rawudp.py index c4f2d97ef4e..ff3ddfdb0eb 100644 --- a/contrib/python/Twisted/py3/twisted/pair/rawudp.py +++ b/contrib/python/Twisted/py3/twisted/pair/rawudp.py @@ -37,7 +37,8 @@ class RawUDPProtocol(protocol.AbstractDatagramProtocol): self.udpProtos[num] = [] self.udpProtos[num].append(proto) - def datagramReceived( + # This never really should have subclassed AbstractDatagramProtocol + def datagramReceived( # type:ignore[override] self, data, partial, diff --git a/contrib/python/Twisted/py3/twisted/pair/tuntap.py b/contrib/python/Twisted/py3/twisted/pair/tuntap.py index 2564257966b..85caa47e15f 100644 --- a/contrib/python/Twisted/py3/twisted/pair/tuntap.py +++ b/contrib/python/Twisted/py3/twisted/pair/tuntap.py @@ -15,7 +15,6 @@ import platform import struct import warnings from collections import namedtuple -from typing import Tuple from zope.interface import Attribute, Interface, implementer @@ -257,7 +256,7 @@ class TuntapPort(abstract.FileDescriptor): self.logstr = f"{logPrefix} ({self._mode.name})" def __repr__(self) -> str: - args: Tuple[str, ...] = (fullyQualifiedName(self.protocol.__class__),) + args: tuple[str, ...] = (fullyQualifiedName(self.protocol.__class__),) if self.connected: args = args + ("",) else: diff --git a/contrib/python/Twisted/py3/twisted/persisted/dirdbm.py b/contrib/python/Twisted/py3/twisted/persisted/dirdbm.py index e9bdc560f55..69894d136f6 100644 --- a/contrib/python/Twisted/py3/twisted/persisted/dirdbm.py +++ b/contrib/python/Twisted/py3/twisted/persisted/dirdbm.py @@ -22,7 +22,8 @@ import base64 import glob import os import pickle -from typing import AnyStr, Iterable, Mapping, TypeVar, overload +from collections.abc import Iterable, Mapping +from typing import AnyStr, TypeVar, overload from twisted.python.filepath import FilePath @@ -258,7 +259,7 @@ class DirDBM: Add all the key/value pairs in L{dict} to this dirdbm. Any conflicting keys will be overwritten with the values from L{dict}. - @param dict: A mapping of key/value pairs to add to this dirdbm. + @param other: A mapping of key/value pairs to add to this dirdbm. """ for key, val in other.items(): self[key] = val diff --git a/contrib/python/Twisted/py3/twisted/persisted/styles.py b/contrib/python/Twisted/py3/twisted/persisted/styles.py index 2d014dc8477..1e1f8406b93 100644 --- a/contrib/python/Twisted/py3/twisted/persisted/styles.py +++ b/contrib/python/Twisted/py3/twisted/persisted/styles.py @@ -12,12 +12,11 @@ import inspect import pickle import types from io import StringIO as _cStringIO -from typing import Dict from twisted.python import log, reflect from twisted.python.compat import _PYPY -oldModules: Dict[str, types.ModuleType] = {} +oldModules: dict[str, types.ModuleType] = {} _UniversalPicklingError = pickle.PicklingError @@ -241,7 +240,7 @@ class Ephemeral: self.__class__ = Ephemeral -versionedsToUpgrade: Dict[int, "Versioned"] = {} +versionedsToUpgrade: dict[int, "Versioned"] = {} upgraded = {} diff --git a/contrib/python/Twisted/py3/twisted/plugin.py b/contrib/python/Twisted/py3/twisted/plugin.py index 45180014cdc..ecda72ae02d 100644 --- a/contrib/python/Twisted/py3/twisted/plugin.py +++ b/contrib/python/Twisted/py3/twisted/plugin.py @@ -10,12 +10,14 @@ Plugin system for Twisted. @author: Glyph Lefkowitz """ +from __future__ import annotations import os import pickle import sys import types -from typing import Iterable, Optional, Type, TypeVar +from collections.abc import Iterable +from typing import TypeVar from zope.interface import Interface, providedBy @@ -196,7 +198,7 @@ _TInterface = TypeVar("_TInterface", bound=Interface) def getPlugins( - interface: Type[_TInterface], package: Optional[types.ModuleType] = None + interface: type[_TInterface], package: types.ModuleType | None = None ) -> Iterable[_TInterface]: """ Retrieve all plugins implementing the given interface beneath the given module. diff --git a/contrib/python/Twisted/py3/twisted/plugins/__init__.py b/contrib/python/Twisted/py3/twisted/plugins/__init__.py index d4b94bbca0d..e0346962538 100644 --- a/contrib/python/Twisted/py3/twisted/plugins/__init__.py +++ b/contrib/python/Twisted/py3/twisted/plugins/__init__.py @@ -14,9 +14,7 @@ the __path__ variable. @author: Glyph Lefkowitz """ -from typing import List - from twisted.plugin import pluginPackagePaths __path__.extend(pluginPackagePaths(__name__)) -__all__: List[str] = [] # nothing to see here, move along, move along +__all__: list[str] = [] # nothing to see here, move along, move along diff --git a/contrib/python/Twisted/py3/twisted/plugins/cred_unix.py b/contrib/python/Twisted/py3/twisted/plugins/cred_unix.py index 3d929f75cce..4bb156a7249 100644 --- a/contrib/python/Twisted/py3/twisted/plugins/cred_unix.py +++ b/contrib/python/Twisted/py3/twisted/plugins/cred_unix.py @@ -93,16 +93,19 @@ class UNIXChecker: def checkSpwd(self, spwd, username, password): """ - Obtain the encrypted password for C{username} from the - Unix shadow password database using L{spwd.getspnam}, - and see if it it matches it matches C{password}. + Obtain the encrypted password for C{username} from the Unix shadow + password database using C{spwd.getspnam}, and see if it it matches it + matches C{password}. - @param spwd: Module which provides functions which - access to the Unix shadow password database. + @param spwd: Module which provides functions which access to the Unix + shadow password database. @type spwd: C{module} + @param username: The user to look up in the Unix password database. @type username: L{unicode}/L{str} or L{bytes} + @param password: The password to compare. + @type username: L{unicode}/L{str} or L{bytes} """ try: diff --git a/contrib/python/Twisted/py3/twisted/plugins/twisted_core.py b/contrib/python/Twisted/py3/twisted/plugins/twisted_core.py index 140ac918bdc..c8b9509f4c0 100644 --- a/contrib/python/Twisted/py3/twisted/plugins/twisted_core.py +++ b/contrib/python/Twisted/py3/twisted/plugins/twisted_core.py @@ -7,6 +7,7 @@ from twisted.internet.endpoints import ( _SystemdParser, _TCP6ServerParser, _TLSClientEndpointParser, + _TLSServerEndpointParser, ) from twisted.protocols.haproxy._parser import ( HAProxyServerParser as _HAProxyServerParser, @@ -16,4 +17,5 @@ systemdEndpointParser = _SystemdParser() tcp6ServerEndpointParser = _TCP6ServerParser() stdioEndpointParser = _StandardIOParser() tlsClientEndpointParser = _TLSClientEndpointParser() +tlsServerEndpointParser = _TLSServerEndpointParser() _haProxyServerEndpointParser = _HAProxyServerParser() diff --git a/contrib/python/Twisted/py3/twisted/positioning/_sentence.py b/contrib/python/Twisted/py3/twisted/positioning/_sentence.py index b40dd060ed8..ee69bbe7706 100644 --- a/contrib/python/Twisted/py3/twisted/positioning/_sentence.py +++ b/contrib/python/Twisted/py3/twisted/positioning/_sentence.py @@ -3,7 +3,6 @@ """ Generic sentence handling tools: hopefully reusable. """ -from typing import Set class _BaseSentence: @@ -35,7 +34,7 @@ class _BaseSentence: @type ALLOWED_ATTRIBUTES: C{set} of C{str} """ - ALLOWED_ATTRIBUTES: Set[str] = set() + ALLOWED_ATTRIBUTES: set[str] = set() def __init__(self, sentenceData): """ diff --git a/contrib/python/Twisted/py3/twisted/positioning/base.py b/contrib/python/Twisted/py3/twisted/positioning/base.py index 460f5b4ff20..220b07eaacd 100644 --- a/contrib/python/Twisted/py3/twisted/positioning/base.py +++ b/contrib/python/Twisted/py3/twisted/positioning/base.py @@ -8,9 +8,10 @@ Generic positioning base classes. """ +from collections.abc import Sequence from functools import partial from operator import attrgetter -from typing import ClassVar, Sequence +from typing import ClassVar from zope.interface import implementer diff --git a/contrib/python/Twisted/py3/twisted/protocols/_sni.py b/contrib/python/Twisted/py3/twisted/protocols/_sni.py new file mode 100644 index 00000000000..ce8b5bf9ffb --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/_sni.py @@ -0,0 +1,347 @@ +# -*- test-case-name: twisted.internet.test.test_endpoints -*- + +from __future__ import annotations + +from dataclasses import dataclass +from functools import cached_property, partial +from typing import Callable + +from zope.interface import implementer + +from OpenSSL.crypto import FILETYPE_PEM +from OpenSSL.SSL import Connection, Context + +from cryptography.x509 import DNSName, ExtensionOID, load_pem_x509_certificate + +from twisted.internet.defer import Deferred +from twisted.internet.interfaces import ( + IListeningPort, + IOpenSSLServerConnectionCreator, + IProtocolFactory, + IReactorTime, + IStreamServerEndpoint, +) +from twisted.internet.ssl import ( + DN, + Certificate, + CertificateOptions, + KeyPair, + PrivateCertificate, +) +from twisted.logger import Logger +from twisted.protocols._tls_legacy import SomeConnectionCreator +from twisted.protocols.tls import TLSMemoryBIOFactory, TLSMemoryBIOProtocol +from twisted.python.filepath import FilePath + +log = Logger() + + +def lookupWithWildcard( + flatLookup: Callable[[bytes | None], Context | None], name: bytes | None +) -> Context | None: + """ + Look up an OpenSSL context for the given domain name, or construct a + default one suitable for bootstrapping the connection. + """ + candidate = flatLookup(name) + if candidate is None: + if name is not None: + segments = name.split(b".") + segments[0] = b"*" + wildcardName = b".".join(segments) + candidate = flatLookup(wildcardName) + if candidate is None: + log.warn("no server certificate for name {name!r}", name=name) + return candidate + + +@implementer(IOpenSSLServerConnectionCreator) +@dataclass +class SNIConnectionCreator: + """ + (Private) L{IOpenSSLServerConnectionCreator} implementation that creates an + OpenSSL connection with a context that will switch to the appropriate one. + """ + + _contextLookup: Callable[[bytes | None], Context | None] + """ + This method should look up an OpenSSL Context object for the given DNS + name, or one that is suitable for unidentified clients. The lookup may + fail and return None. + """ + + @cached_property + def defaultContext(self) -> Context: + """ + Create and cache the OpenSSL context that connections will initially be + using. This constructs a default context which doesn't know its domain + name by delegating to C{self._contextLookup} with None, then sets the + TLS extension servername callback to get invoked to I{switch} contexts + by doing another lookup when the client sends its servername. + + @note: The client I{might} never send a servername at all, in which + case it will be stuck. This edge case is not handled particularly + well right now. Handling it better would involve some changes in + this code (to hook the handshake completion callback rather than + just the servername callback) as well as better ability to + customize which certificate produces the default context in the + implementation of C{_contextLookup}, which is to say, mostly + L{PEMObjects}. + """ + lookedUp = lookupWithWildcard(self._contextLookup, None) + if lookedUp is None: + blankOptions = CertificateOptions( + contextForServerName=partial( + lookupWithWildcard, + self._contextLookup, + ) + ) + return blankOptions.getContext() + + return lookedUp + + def serverConnectionForTLS(self, protocol: TLSMemoryBIOProtocol) -> Connection: + """ + Construct an OpenSSL server connection that can react to the TLS + servername callback to select an appropriate certificate based on a + mapping. + + @param protocol: The protocol initiating a TLS connection. + + @return: a newly-created connection. + """ + return Connection(self.defaultContext) + + +@implementer(IStreamServerEndpoint) +class TLSServerEndpoint: + """ + A wrapper L{IStreamServerEndpoint} that can run TLS over an arbitrary other + L{IStreamServerEndpoint} (most commonly, TCP). + """ + + def __init__( + self, + endpoint: IStreamServerEndpoint, + connectionCreator: SomeConnectionCreator, + clock: IReactorTime | None = None, + ) -> None: + """ + @param endpoint: the endpoint to run over. + + @param connectionCreator: The object that will construct OpenSSL + connections (or Contexts). + + @param clock: The clock which will be used to schedule buffer flushes. + """ + self.endpoint = endpoint + self.connectionCreator = connectionCreator + self.clock = clock + + def listen(self, factory: IProtocolFactory) -> Deferred[IListeningPort]: + """ + Begin listening with the given factory. + """ + return self.endpoint.listen( + TLSMemoryBIOFactory( + self.connectionCreator, False, factory, clock=self.clock + ) + ) + + +def _getSubjectAltNames(c: Certificate) -> list[str]: + """ + Get all the DNSName SANs for a given certificate. + """ + return [ + value + for extension in load_pem_x509_certificate(c.dumpPEM()).extensions + if extension.oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME + for value in extension.value.get_values_for_type(DNSName) + ] + + +def autoReloadingDirectoryOfPEMs( + path: FilePath[str], +) -> Callable[[bytes | None], Context | None]: + """ + Construct a callable that can look up a HTTPS certificate based on their + DNS names, by inspecting a directory full of PEM objects. When + encountering a lookup failure, the directory will be reloaded, so that if + new certificates are added they will be picked up. + """ + # TODO: some flaws with this approach + + """ + 1. too much re-scanning; re-reading full file contents for every single + certificate even if only one has changed. a mtime/length cache + would be a good place to start with this + + 2. too trusting; we get a network request for a billion certificate + names per second, we go ahead and do a bunch of work every single + time (and, see point 1, re-scan and re-parse every single file) + + 3. not *enough* re-scanning on the happy path; if certificates go + stale, we just let them sit there until we get an unknown hostname + + 4. we don't look at notAfter/notBefore, so if we find multiple certs, + we may end up using the wrong one + + """ + + certMap: dict[str, CertificateOptions] + + def doReload() -> None: + nonlocal certMap + certMap = PEMObjects.fromDirectory(path).inferDomainMapping() + + def lookup(name: bytes | None, shouldReload: bool = True) -> Context | None: + name = next(iter(certMap.keys()), "").encode() if name in (None, b"") else name + assert name is not None + if (options := certMap.get(name.decode())) is not None: + return options.getContext() + if not shouldReload: + return None + msg = "could not find domain {name}, re-loading {path}" + log.warn(msg, name=name, path=path) + doReload() + return lookup(name, False) + + doReload() + return lookup + + +@dataclass +class PEMObjects: + """ + A collection of objects loaded from a collection of PEM-encoded files. + """ + + _certificates: list[tuple[FilePath[str], Certificate]] + """ + A list of pairs of (FilePath, Certificate) that indicates what files + contain what certificates. + """ + _keyPairs: list[tuple[FilePath[str], KeyPair]] + """ + A list of pairs of (FilePath, KeyPair) that indicates what pairs contain + what certificates. + """ + + @classmethod + def fromDirectory(cls, directory: FilePath[str]) -> PEMObjects: + """ + Walk through the given directory looking for files with a `.pem` + extension, and instantiate a L{PEMObjects} containing all certificates + and key pairs from those files. + + @param directory: a L{FilePath} pointing at a directory in the + filesystem which may contain some PEM files. + """ + self = PEMObjects([], []) + for fp in directory.walk(): + if fp.basename().endswith(".pem") and fp.isfile(): + subself = cls.fromFile(fp) + self._certificates.extend(subself._certificates) + self._keyPairs.extend(subself._keyPairs) + return self + + @classmethod + def fromFile(cls, fp: FilePath[str]) -> PEMObjects: + """ + Load some objects from the lines of a single PEM file. + + @param fp: A L{FilePath} pointing at a file on the filesystem whose + contents should be PEM data. + """ + certBlobs: list[bytes] = [] + keyBlobs: list[bytes] = [] + blobs = [b""] + with fp.open() as pemlines: + for line in pemlines: + if line.startswith(b"-----BEGIN"): + blobs = certBlobs if b"CERTIFICATE" in line else keyBlobs + blobs.append(b"") + blobs[-1] += line + return cls( + _keyPairs=[ + (fp, KeyPair.load(keyBlob, FILETYPE_PEM)) for keyBlob in keyBlobs + ], + _certificates=[ + (fp, Certificate.loadPEM(certBlob)) for certBlob in certBlobs + ], + ) + + def inferDomainMapping(self) -> dict[str, CertificateOptions]: + """ + Return a mapping of DNS name to L{CertificateOptions}. + """ + + privateCerts = [] + + certificatesByFingerprint = { + certificate.getPublicKey().keyHash(): certificate + for (_, certificate) in self._certificates + } + + for pairPath, keyPair in self._keyPairs: + keyHash = keyPair.keyHash() + matchingCertificate = certificatesByFingerprint.pop(keyHash, None) + if matchingCertificate is None: + # log something? + log.warn( + "unused private key at {path} with hash {hash}", + path=pairPath.path, + hash=keyHash, + ) + continue + privateCerts.append( + ( + _getSubjectAltNames(matchingCertificate), + PrivateCertificate.fromCertificateAndKeyPair( + matchingCertificate, keyPair + ), + ) + ) + + noPrivateKeys = [ + Certificate.load(dumped) + for dumped in {each.dump() for each in certificatesByFingerprint.values()} + ] + + def hashDN(dn: DN) -> tuple[tuple[str, bytes], ...]: + return tuple(sorted(dn.items())) + + bySubject = { + hashDN(eachIntermediate.getSubject()): eachIntermediate + for eachIntermediate in noPrivateKeys + } + + result: dict[str, CertificateOptions] = {} + + def flatLookup(servername: bytes | None) -> Context | None: + if servername is None: + return None + options = result.get(servername.decode("ascii")) + if options is not None: + return options.getContext() + return None + + def nameToContext(servername: bytes | None) -> Context | None: + return lookupWithWildcard(flatLookup, servername) + + for names, privateCert in privateCerts: + chain = [] + chained = privateCert + while hashDN(chained.getIssuer()) in bySubject: + chained = bySubject[hashDN(chained.getIssuer())] + chain.append(chained.original) + options = CertificateOptions( + certificate=privateCert.original, + privateKey=privateCert.privateKey.original, + extraCertChain=chain, + contextForServerName=nameToContext, + ) + for dnsName in names: + result[dnsName] = options + return result diff --git a/contrib/python/Twisted/py3/twisted/protocols/_tls_legacy.py b/contrib/python/Twisted/py3/twisted/protocols/_tls_legacy.py new file mode 100644 index 00000000000..072d8b8c722 --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/_tls_legacy.py @@ -0,0 +1,110 @@ +# -*- test-case-name: twisted.internet.test.test_endpoints.TLSEndpointsTests -*- +""" +Handler for the various legacy things that a C{contextFactory} can be. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Union +from warnings import warn + +from OpenSSL.SSL import Connection, Context + +from twisted.internet.interfaces import ( + IOpenSSLClientConnectionCreator, + IOpenSSLContextFactory, + IOpenSSLServerConnectionCreator, +) + +if TYPE_CHECKING: + # Circular import. + from twisted.protocols.tls import TLSMemoryBIOProtocol + +SomeConnectionCreator = Union[ + IOpenSSLContextFactory, + IOpenSSLClientConnectionCreator, + IOpenSSLServerConnectionCreator, +] + + +SingleArgFactory = Callable[["TLSMemoryBIOProtocol"], Connection] + + +class LegacyContextFactoryWarning(Warning): + """ + You should be using a newer TLS context configuration interface. + """ + + +def older(olderMethod: Callable[[], Context]) -> SingleArgFactory: + """ + Compatibility shim for L{IOpenSSLContextFactory.getContext}-style method to + create(Client/Server)Creator. + """ + + def convert(p: TLSMemoryBIOProtocol) -> Connection: + context = olderMethod() + connection = Connection(context, None) + return connection + + return convert + + +def oldest(isClient: bool, creator: object) -> SingleArgFactory: + """ + Comptibility shim that does largely the same thing as L{older} but for + things that don't even properly implement the old-style interface; check + explicitly for the method and try to provide a useful assert if the object + is just the wrong type rather than simply using an older API. + """ + itype = "Client" if isClient else "Server" + warn( + f"{creator} does not explicitly provide any OpenSSL connection-" + f"creator {itype} interface; neither IOpenSSL{itype}ConnectionCreator," + f" nor IOpenSSLContextFactory.", + LegacyContextFactoryWarning, + stacklevel=4, + ) + getContext = getattr(creator, "getContext", None) + if getContext is None: + raise TypeError(f"{creator} does not even have a `getContext` method") + if not isinstance(getContext(), Context): + raise TypeError(f"{creator}'s `getContext` method doesn't return a `Context`") + return older(getContext) + + +def _convertToAppropriateFactory( + isClient: bool, creator: SomeConnectionCreator +) -> SingleArgFactory: + """ + Upgrade a connection creator / context-factory-ish object into something + with a signature like the most recent interface for building OpenSSL + connection objects (i.e. like the methods on + L{IOpenSSLClientConnectionCreator} and L{IOpenSSLServerConnectionCreator}), + accounting for all the various interfaces older versions of Twisted used + for context configuration. + """ + baseCallable = ( + creator.clientConnectionForTLS + if (isClient and IOpenSSLClientConnectionCreator.providedBy(creator)) + else ( + creator.serverConnectionForTLS + if ((not isClient) and IOpenSSLServerConnectionCreator.providedBy(creator)) + else ( + older(creator.getContext) + if IOpenSSLContextFactory.providedBy(creator) + else oldest(isClient, creator) + ) + ) + ) + + def connectionFactory(protocol: TLSMemoryBIOProtocol) -> Connection: + connection = baseCallable(protocol) + if isClient: + connection.set_connect_state() + else: + connection.set_accept_state() + connection.set_app_data(protocol) + return connection + + return connectionFactory diff --git a/contrib/python/Twisted/py3/twisted/protocols/amp.py b/contrib/python/Twisted/py3/twisted/protocols/amp.py index 8b80982d2ac..70a4ef6f4b0 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/amp.py +++ b/contrib/python/Twisted/py3/twisted/protocols/amp.py @@ -24,17 +24,11 @@ implementation of Deferreds. AMP provides the following base-level features: - Command dispatching (like HTTP Verbs): the protocol is extensible, and multiple AMP sub-protocols can be grouped together easily. -The protocol implementation also provides a few additional features which are -not part of the core wire protocol, but are nevertheless very useful: - - - Tight TLS integration, with an included StartTLS command. - - - Handshaking to other protocols: because AMP has well-defined message - boundaries and maintains all incoming and outgoing requests for you, you - can start a connection over AMP and then switch to another protocol. - This makes it ideal for firewall-traversal applications where you may - have only one forwarded port but multiple applications that want to use - it. +You can also use AMP to tunnel other protocols: because AMP has well-defined +message boundaries and maintains all incoming and outgoing requests for you, +you can start a connection over AMP and then switch to another protocol. This +makes it ideal for firewall-traversal applications where you may have only one +forwarded port but multiple applications that want to use it. Using AMP with Twisted is simple. Each message is a command, with a response. You begin by defining a command type. Commands specify their input and output @@ -65,8 +59,8 @@ a L{Deferred} which will fire with the result:: lambda p: p.callRemote(Sum, a=13, b=81)).addCallback( lambda result: result['total']) -Command responders may also return Deferreds, causing the response to be -sent only once the Deferred fires:: +Command responders may also return Deferreds, causing the response to be sent +only once the Deferred fires:: class DelayedSum(amp.AMP): def slowSum(self, a, b): @@ -202,18 +196,7 @@ from io import BytesIO from itertools import count from struct import pack from types import MethodType -from typing import ( - Any, - Callable, - ClassVar, - Dict, - List, - Optional, - Tuple, - Type, - TypeVar, - Union, -) +from typing import Any, Callable, ClassVar, TypeVar from zope.interface import Interface, implementer @@ -622,7 +605,7 @@ class IncompatibleVersions(AmpError): PROTOCOL_ERRORS = {UNHANDLED_ERROR_CODE: UnhandledCommand} -class AmpBox(Dict[bytes, bytes]): +class AmpBox(dict[bytes, bytes]): """ I am a packet in the AMP protocol, much like a regular bytes:bytes dictionary. @@ -630,7 +613,7 @@ class AmpBox(Dict[bytes, bytes]): # be like a regular dictionary don't magically # acquire a __dict__... - __slots__: List[str] = [] + __slots__: list[str] = [] def __init__(self, *args, **kw): """ @@ -726,7 +709,7 @@ class QuitBox(AmpBox): I am an AmpBox that, upon being sent, terminates the connection. """ - __slots__: List[str] = [] + __slots__: list[str] = [] def __repr__(self) -> str: return f"QuitBox(**{super().__repr__()})" @@ -1097,7 +1080,7 @@ class _CommandLocatorMeta(type): metaclass. """ - _currentClassCommands: "list[tuple[type[Command], Callable[..., Any]]]" = [] + _currentClassCommands: list[tuple[type[Command], Callable[..., Any]]] = [] def __new__(cls, name, bases, attrs): commands = cls._currentClassCommands[:] @@ -1710,12 +1693,12 @@ class _CommandMeta(type): def __new__( cls: type[_Self], name: str, bases: tuple[type], attrs: dict[str, object] - ) -> Type[Command]: + ) -> type[Command]: reverseErrors = attrs["reverseErrors"] = {} er = attrs["allErrors"] = {} if "commandName" not in attrs: attrs["commandName"] = name.encode("ascii") - newtype: Type[Command] = type.__new__(cls, name, bases, attrs) # type:ignore + newtype: type[Command] = type.__new__(cls, name, bases, attrs) # type:ignore if not isinstance(newtype.commandName, bytes): raise TypeError( @@ -1730,8 +1713,8 @@ class _CommandMeta(type): if not isinstance(bname, bytes): raise TypeError(f"Response names must be byte strings, got: {bname!r}") - errors: Dict[Type[Exception], bytes] = {} - fatalErrors: Dict[Type[Exception], bytes] = {} + errors: dict[type[Exception], bytes] = {} + fatalErrors: dict[type[Exception], bytes] = {} accumulateClassDict(newtype, "errors", errors) accumulateClassDict(newtype, "fatalErrors", fatalErrors) @@ -1803,14 +1786,14 @@ class Command(metaclass=_CommandMeta): """ commandName: ClassVar[bytes] - arguments: ClassVar[List[Tuple[bytes, Argument]]] = [] - response: ClassVar[List[Tuple[bytes, Argument]]] = [] - extra: ClassVar[List[Any]] = [] - errors: ClassVar[Dict[Type[Exception], bytes]] = {} - fatalErrors: ClassVar[Dict[Type[Exception], bytes]] = {} + arguments: ClassVar[list[tuple[bytes, Argument]]] = [] + response: ClassVar[list[tuple[bytes, Argument]]] = [] + extra: ClassVar[list[Any]] = [] + errors: ClassVar[dict[type[Exception], bytes]] = {} + fatalErrors: ClassVar[dict[type[Exception], bytes]] = {} - commandType: "ClassVar[Union[Type[Command], Type[Box]]]" = Box - responseType: ClassVar[Type[AmpBox]] = Box + commandType: ClassVar[type[Command] | type[Box]] = Box + responseType: ClassVar[type[AmpBox]] = Box requiresAnswer = True @@ -2044,7 +2027,7 @@ class _TLSBox(AmpBox): I am an AmpBox that, upon being sent, initiates a TLS connection. """ - __slots__: List[str] = [] + __slots__: list[str] = [] def __init__(self): if ssl is None: @@ -2083,18 +2066,28 @@ class _LocalArgument(String): class StartTLS(Command): """ - Use, or subclass, me to implement a command that starts TLS. + If your protocol requires a complex plaintext preamble to begin a secure + connection, and you are I{ABSOLUTELY SURE} that you understand the + consequences of sending that data insecurely, you can use, or subclass, + L{StartTLS} to define a command that switches from an unencrypted + connection to a TLS connection. + + In general, you should prefer using TLS endpoints as defined by + L{twisted.internet.endpoints.wrapClientTLS} and + L{twisted.internet.endpoints.wrapServerTLS}, and using server hostname + indication and/or application layer protocol negotiation to negotiate the + parameters of the TLS connection. Callers of StartTLS may pass several special arguments, which affect the TLS negotiation: - tls_localCertificate: This is a - twisted.internet.ssl.PrivateCertificate which will be used to secure - the side of the connection it is returned on. + twisted.internet.ssl.PrivateCertificate which will be used to secure + the side of the connection it is returned on. - tls_verifyAuthorities: This is a list of - twisted.internet.ssl.Certificate objects that will be used as the - certificate authorities to verify our peer's certificate. + twisted.internet.ssl.Certificate objects that will be used as the + certificate authorities to verify our peer's certificate. Each of those special parameters may also be present as a key in the response dictionary. @@ -2298,7 +2291,7 @@ class BinaryBoxProtocol( hostCertificate = None noPeerCertificate = False # for tests - innerProtocol: Optional[Protocol] = None + innerProtocol: Protocol | None = None innerProtocolClientFactory = None def __init__(self, boxReceiver): diff --git a/contrib/python/Twisted/py3/twisted/protocols/ftp.py b/contrib/python/Twisted/py3/twisted/protocols/ftp.py index 8ac115c7fab..6fdde084b2e 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/ftp.py +++ b/contrib/python/Twisted/py3/twisted/protocols/ftp.py @@ -794,7 +794,7 @@ class FTP(basic.LineReceiver, policies.TimeoutMixin): passivePortRange = range(0, 1) - listenFactory = reactor.listenTCP # type: ignore[attr-defined] + listenFactory = reactor.listenTCP _encoding = "latin-1" def reply(self, key, *args): @@ -2885,7 +2885,7 @@ class FTPClient(FTPClientBasic): @ivar passive: See description in __init__. """ - connectFactory = reactor.connectTCP # type: ignore[attr-defined] + connectFactory = reactor.connectTCP def __init__( self, username="anonymous", password="[email protected]", passive=1 diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_exceptions.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_exceptions.py index 9a521ea2495..7d5e5508690 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_exceptions.py +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_exceptions.py @@ -7,7 +7,8 @@ HAProxy specific exceptions. """ import contextlib -from typing import Callable, Generator, Type +from collections.abc import Generator +from typing import Callable class InvalidProxyHeader(Exception): @@ -30,7 +31,7 @@ class MissingAddressData(InvalidProxyHeader): @contextlib.contextmanager def convertError( - sourceType: Type[BaseException], targetType: Callable[[], BaseException] + sourceType: type[BaseException], targetType: Callable[[], BaseException] ) -> Generator[None, None, None]: """ Convert an error into a different error type. diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_interfaces.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_interfaces.py index 8fe90ea37ab..ab7dad9afc7 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_interfaces.py +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_interfaces.py @@ -5,7 +5,7 @@ """ Interfaces used by the PROXY protocol modules. """ -from typing import Tuple, Union +from __future__ import annotations import zope.interface @@ -33,7 +33,7 @@ class IProxyParser(zope.interface.Interface): Streaming parser that handles PROXY protocol headers. """ - def feed(data: bytes) -> Union[Tuple[IProxyInfo, bytes], Tuple[None, None]]: + def feed(data: bytes) -> tuple[IProxyInfo, bytes] | tuple[None, None]: """ Consume a chunk of data and attempt to parse it. diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_parser.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_parser.py index 834ccb73547..0cb32186e53 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_parser.py +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_parser.py @@ -5,7 +5,7 @@ """ Parser for 'haproxy:' string endpoint. """ -from typing import Mapping, Tuple +from collections.abc import Mapping from zope.interface import implementer @@ -20,7 +20,7 @@ from twisted.plugin import IPlugin from . import proxyEndpoint -def unparseEndpoint(args: Tuple[object, ...], kwargs: Mapping[str, object]) -> str: +def unparseEndpoint(args: tuple[object, ...], kwargs: Mapping[str, object]) -> str: """ Un-parse the already-parsed args and kwargs back into endpoint syntax. diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v1parser.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v1parser.py index fed987c33af..7401664fe09 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v1parser.py +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v1parser.py @@ -6,7 +6,7 @@ """ IProxyParser implementation for version one of the PROXY protocol. """ -from typing import Tuple, Union +from __future__ import annotations from zope.interface import implementer @@ -44,9 +44,7 @@ class V1Parser: def __init__(self) -> None: self.buffer = b"" - def feed( - self, data: bytes - ) -> Union[Tuple[_info.ProxyInfo, bytes], Tuple[None, None]]: + def feed(self, data: bytes) -> tuple[_info.ProxyInfo, bytes] | tuple[None, None]: """ Consume a chunk of data and attempt to parse it. diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v2parser.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v2parser.py index cfcf7c99bcc..ac22a8a0003 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v2parser.py +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v2parser.py @@ -6,15 +6,15 @@ """ IProxyParser implementation for version two of the PROXY protocol. """ +from __future__ import annotations import binascii import struct -from typing import Callable, Tuple, Type, Union +from typing import Callable, Literal from zope.interface import implementer from constantly import ValueConstant, Values -from typing_extensions import Literal from twisted.internet import address from twisted.python import compat @@ -80,9 +80,7 @@ class V2Parser: def __init__(self) -> None: self.buffer = b"" - def feed( - self, data: bytes - ) -> Union[Tuple[_info.ProxyInfo, bytes], Tuple[None, None]]: + def feed(self, data: bytes) -> tuple[_info.ProxyInfo, bytes] | tuple[None, None]: """ Consume a chunk of data and attempt to parse it. @@ -195,12 +193,12 @@ class V2Parser: address.UNIXAddress(dest.rstrip(b"\x00")), ) - addrType: Union[Literal["TCP"], Literal["UDP"]] = "TCP" + addrType: Literal["TCP"] | Literal["UDP"] = "TCP" if netproto is NetProtocol.DGRAM: addrType = "UDP" - addrCls: Union[ - Type[address.IPv4Address], Type[address.IPv6Address] - ] = address.IPv4Address + addrCls: ( + type[address.IPv4Address] | type[address.IPv6Address] + ) = address.IPv4Address addrParser: Callable[[bytes], bytes] = cls._bytesToIPv4 if family is NetFamily.INET6: addrCls = address.IPv6Address diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_wrapper.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_wrapper.py index 935dbfa9e20..3f39a35ac3d 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_wrapper.py +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_wrapper.py @@ -8,6 +8,8 @@ Protocol wrapper that provides HAProxy PROXY protocol support. """ from typing import Optional, Union +from typing_extensions import Self + from twisted.internet import interfaces from twisted.internet.endpoints import _WrapperServerEndpoint from twisted.protocols import policies @@ -27,7 +29,9 @@ class HAProxyProtocolWrapper(policies.ProtocolWrapper): """ def __init__( - self, factory: policies.WrappingFactory, wrappedProtocol: interfaces.IProtocol + self, + factory: policies.WrappingFactory[Self], + wrappedProtocol: interfaces.IProtocol, ): super().__init__(factory, wrappedProtocol) self._proxyInfo: Optional[_info.ProxyInfo] = None diff --git a/contrib/python/Twisted/py3/twisted/protocols/policies.py b/contrib/python/Twisted/py3/twisted/protocols/policies.py index a89d4f8a8d1..a6abe372db8 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/policies.py +++ b/contrib/python/Twisted/py3/twisted/protocols/policies.py @@ -7,18 +7,18 @@ Resource limiting policies. @seealso: See also L{twisted.protocols.htb} for rate limiting. """ - +from __future__ import annotations # system imports import sys -from typing import Optional, Type +from typing import Callable from zope.interface import directlyProvides, providedBy -from twisted.internet import error, interfaces -from twisted.internet.interfaces import ILoggingContext +from typing_extensions import Self, TypeVar -# twisted imports +from twisted.internet import error, interfaces +from twisted.internet.interfaces import IAddress, ILoggingContext from twisted.internet.protocol import ClientFactory, Protocol, ServerFactory from twisted.python import log @@ -51,7 +51,7 @@ class ProtocolWrapper(Protocol): disconnecting = 0 def __init__( - self, factory: "WrappingFactory", wrappedProtocol: interfaces.IProtocol + self, factory: WrappingFactory[Self], wrappedProtocol: interfaces.IProtocol ): self.wrappedProtocol = wrappedProtocol self.factory = factory @@ -117,12 +117,15 @@ class ProtocolWrapper(Protocol): self.wrappedProtocol = None -class WrappingFactory(ClientFactory): +WP = TypeVar("WP", bound=ProtocolWrapper, default=ProtocolWrapper) + + +class WrappingFactory(ClientFactory[WP]): """ Wraps a factory and its protocols, and keeps track of them. """ - protocol: Type[Protocol] = ProtocolWrapper + protocol: Callable[..., WP] = ProtocolWrapper # type:ignore[assignment] def __init__(self, wrappedFactory): self.wrappedFactory = wrappedFactory @@ -151,7 +154,7 @@ class WrappingFactory(ClientFactory): def clientConnectionLost(self, connector, reason): self.wrappedFactory.clientConnectionLost(connector, reason) - def buildProtocol(self, addr): + def buildProtocol(self, addr: IAddress | None) -> WP: return self.protocol(self, self.wrappedFactory.buildProtocol(addr)) def registerProtocol(self, p): @@ -395,7 +398,7 @@ class LimitTotalConnectionsFactory(ServerFactory): connectionCount = 0 connectionLimit = None - overflowProtocol: Optional[Type[Protocol]] = None + overflowProtocol: type[Protocol] | None = None def buildProtocol(self, addr): if self.connectionLimit is None or self.connectionCount < self.connectionLimit: @@ -628,7 +631,7 @@ class TimeoutMixin: @cvar timeOut: The number of seconds after which to timeout the connection. """ - timeOut: Optional[int] = None + timeOut: int | None = None __timeoutCall = None diff --git a/contrib/python/Twisted/py3/twisted/protocols/portforward.py b/contrib/python/Twisted/py3/twisted/protocols/portforward.py index bf3a894dfaa..bd420c2a952 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/portforward.py +++ b/contrib/python/Twisted/py3/twisted/protocols/portforward.py @@ -1,57 +1,66 @@ +# -*- test-case-name: twisted.test.test_protocols.PortforwardingTests -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ A simple port forwarder. """ +from __future__ import annotations -# Twisted imports from twisted.internet import protocol -from twisted.python import log +from twisted.internet.interfaces import ( + IAddress, + IConsumer, + IPushProducer, + IStreamServerEndpoint, + ITransport, +) class Proxy(protocol.Protocol): noisy = True + peer: Proxy | None = None + factory: protocol.Factory[Proxy] - peer = None - - def setPeer(self, peer): + def setPeer(self, peer: Proxy | None) -> None: self.peer = peer def connectionLost(self, reason): if self.peer is not None: self.peer.transport.loseConnection() self.peer = None - elif self.noisy: - log.msg(f"Unable to connect to peer: {reason}") def dataReceived(self, data): self.peer.transport.write(data) class ProxyClient(Proxy): - def connectionMade(self): + factory: protocol.Factory[ProxyClient] # type:ignore + + def connectionMade(self) -> None: + assert self.peer is not None self.peer.setPeer(self) # Wire this and the peer transport together to enable # flow control (this stops connections from filling # this proxy memory when one side produces data at a # higher rate than the other can consume). - self.transport.registerProducer(self.peer.transport, True) - self.peer.transport.registerProducer(self.transport, True) + self.transport.registerProducer(self.peer.transport, True) # type:ignore + self.peer.transport.registerProducer(self.transport, True) # type:ignore # We're connected, everybody can read to their hearts content. - self.peer.transport.resumeProducing() + self.peer.transport.resumeProducing() # type:ignore -class ProxyClientFactory(protocol.ClientFactory): +class ProxyClientFactory(protocol.ClientFactory[ProxyClient]): protocol = ProxyClient def setServer(self, server): self.server = server - def buildProtocol(self, *args, **kw): - prot = protocol.ClientFactory.buildProtocol(self, *args, **kw) + def buildProtocol(self, addr: IAddress | None) -> ProxyClient: + prot = super().buildProtocol(addr) + assert prot is not None, "peer must build protocol" prot.setPeer(self.server) return prot @@ -59,11 +68,21 @@ class ProxyClientFactory(protocol.ClientFactory): self.server.transport.loseConnection() +class _MakeTypesHappy(IPushProducer, IConsumer, ITransport): + """ + L{ProxyServer}'s transport is implicitly assumed to provide several + interfaces so include them all here. + """ + + class ProxyServer(Proxy): clientProtocolFactory = ProxyClientFactory reactor = None + transport: _MakeTypesHappy + factory: ProxyFactory # type:ignore[assignment] + endpoint: IStreamServerEndpoint | None - def connectionMade(self): + def connectionMade(self) -> None: # Don't read anything from the connecting client until we have # somewhere to send it to. self.transport.pauseProducing() @@ -78,13 +97,13 @@ class ProxyServer(Proxy): self.reactor.connectTCP(self.factory.host, self.factory.port, client) -class ProxyFactory(protocol.Factory): +class ProxyFactory(protocol.Factory[ProxyServer]): """ Factory for port forwarder. """ protocol = ProxyServer - def __init__(self, host, port): + def __init__(self, host: str, port: int) -> None: self.host = host self.port = port diff --git a/contrib/python/Twisted/py3/twisted/protocols/postfix.py b/contrib/python/Twisted/py3/twisted/protocols/postfix.py index 5860887cb00..cddc2069545 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/postfix.py +++ b/contrib/python/Twisted/py3/twisted/protocols/postfix.py @@ -9,7 +9,7 @@ from __future__ import annotations import sys from collections import UserDict -from typing import TYPE_CHECKING, Union +from typing import Union from urllib.parse import quote as _quote, unquote as _unquote from twisted.internet import defer, protocol @@ -107,10 +107,7 @@ class PostfixTCPMapServer(basic.LineReceiver, policies.TimeoutMixin): self.sendCode(500, b"put is not implemented yet.") -if TYPE_CHECKING or sys.version_info >= (3, 9): - _PostfixTCPMapDict = UserDict[bytes, Union[str, bytes]] -else: - _PostfixTCPMapDict = UserDict +_PostfixTCPMapDict = UserDict[bytes, Union[str, bytes]] class PostfixTCPMapDictServerFactory(_PostfixTCPMapDict, protocol.ServerFactory): diff --git a/contrib/python/Twisted/py3/twisted/protocols/sip.py b/contrib/python/Twisted/py3/twisted/protocols/sip.py index 51cac143d86..c41d32654ca 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/sip.py +++ b/contrib/python/Twisted/py3/twisted/protocols/sip.py @@ -13,7 +13,6 @@ import socket import time import warnings from collections import OrderedDict -from typing import Dict, List from zope.interface import Interface, implementer @@ -340,7 +339,7 @@ class URL: self.headers = headers def toString(self) -> str: - l: List[str] = [] + l: list[str] = [] w = l.append w("sip:") if self.username != None: @@ -1057,7 +1056,7 @@ class RegisterProxy(Proxy): registry = None # Should implement IRegistry - authorizers: Dict[str, IAuthorizer] = {} + authorizers: dict[str, IAuthorizer] = {} def __init__(self, *args, **kw): Proxy.__init__(self, *args, **kw) diff --git a/contrib/python/Twisted/py3/twisted/protocols/tls.py b/contrib/python/Twisted/py3/twisted/protocols/tls.py index be7a58ce4d3..154d1c13d49 100644 --- a/contrib/python/Twisted/py3/twisted/protocols/tls.py +++ b/contrib/python/Twisted/py3/twisted/protocols/tls.py @@ -38,23 +38,21 @@ transports, such as UNIX sockets and stdio. from __future__ import annotations -from typing import Callable, Iterable, Optional, cast +from collections.abc import Iterable +from typing import Callable, cast from zope.interface import directlyProvides, implementer, providedBy -from OpenSSL.SSL import Connection, Error, SysCallError, WantReadError, ZeroReturnError +from OpenSSL.SSL import Error, SysCallError, WantReadError, ZeroReturnError from twisted.internet._producer_helpers import _PullToPush -from twisted.internet._sslverify import _setAcceptableProtocols from twisted.internet.interfaces import ( IDelayedCall, IHandshakeListener, ILoggingContext, INegotiated, - IOpenSSLClientConnectionCreator, - IOpenSSLServerConnectionCreator, IProtocol, - IProtocolNegotiationFactory, + IProtocolFactory, IPushProducer, IReactorTime, ISystemHandle, @@ -64,6 +62,7 @@ from twisted.internet.main import CONNECTION_LOST from twisted.internet.protocol import Protocol from twisted.protocols.policies import ProtocolWrapper, WrappingFactory from twisted.python.failure import Failure +from ._tls_legacy import SomeConnectionCreator, _convertToAppropriateFactory @implementer(IPushProducer) @@ -119,6 +118,9 @@ def _representsEOF(exceptionObject: Error) -> bool: _, reasonString = exceptionObject.args else: errorQueue = exceptionObject.args[0] + if not errorQueue: + # This appears to be the behavior in OpenSSL 4. + return True _, _, reasonString = errorQueue[-1] return reasonString.casefold().startswith("unexpected eof") @@ -188,6 +190,15 @@ class TLSMemoryBIOProtocol(ProtocolWrapper): _producer = None _aborted = False + # we are a ProtocolWrapper and thus ->Protocol. in order LSP substitute to + # Protocol, self.factory must be (invariant: the attribute is read/write!) + # Factory[Self] | None. Thus, constraining it to TLSMemoryBIOFactory like + # this is invalid; a client of Protocol looking at an instance of + # TLSMemoryBIOProtocol might want to assign some arbitrary .factory and + # that would be wrong. + + factory: TLSMemoryBIOFactory + def __init__(self, factory, wrappedProtocol, _connectWrapped=True): ProtocolWrapper.__init__(self, factory, wrappedProtocol) self._connectWrapped = _connectWrapped @@ -209,7 +220,7 @@ class TLSMemoryBIOProtocol(ProtocolWrapper): Connect this wrapper to the given transport and initialize the necessary L{OpenSSL.SSL.Connection} with a memory BIO. """ - self._tlsConnection = self.factory._createConnection(self) + self._tlsConnection = self.factory._creatorCallable(self) self._appSendBuffer = [] # Add interfaces provided by the transport we are wrapping: @@ -554,31 +565,19 @@ class TLSMemoryBIOProtocol(ProtocolWrapper): return self._tlsConnection.get_peer_certificate() @property - def negotiatedProtocol(self): + def negotiatedProtocol(self) -> bytes | None: """ @see: L{INegotiated.negotiatedProtocol} """ - protocolName = None - - try: - # If ALPN is not implemented that's ok, NPN might be. - protocolName = self._tlsConnection.get_alpn_proto_negotiated() - except (NotImplementedError, AttributeError): - pass - - if protocolName not in (b"", None): - # A protocol was selected using ALPN. - return protocolName - - try: - protocolName = self._tlsConnection.get_next_proto_negotiated() - except (NotImplementedError, AttributeError): - pass - - if protocolName != b"": - return protocolName - - return None + tc = self._tlsConnection + if tc is None: + # We have not yet established a TLS connection, so no application + # layer protocol has been negotiated. + return None + protocolName: bytes | None = tc.get_alpn_proto_negotiated() + if protocolName == b"": + return None + return protocolName def registerProducer(self, producer, streaming): # If we've already disconnected, nothing to do here: @@ -615,81 +614,6 @@ class TLSMemoryBIOProtocol(ProtocolWrapper): self._shutdownTLS() -@implementer(IOpenSSLClientConnectionCreator, IOpenSSLServerConnectionCreator) -class _ContextFactoryToConnectionFactory: - """ - Adapter wrapping a L{twisted.internet.interfaces.IOpenSSLContextFactory} - into a L{IOpenSSLClientConnectionCreator} or - L{IOpenSSLServerConnectionCreator}. - - See U{https://twistedmatrix.com/trac/ticket/7215} for work that should make - this unnecessary. - """ - - def __init__(self, oldStyleContextFactory): - """ - Construct a L{_ContextFactoryToConnectionFactory} with a - L{twisted.internet.interfaces.IOpenSSLContextFactory}. - - Immediately call C{getContext} on C{oldStyleContextFactory} in order to - force advance parameter checking, since old-style context factories - don't actually check that their arguments to L{OpenSSL} are correct. - - @param oldStyleContextFactory: A factory that can produce contexts. - @type oldStyleContextFactory: - L{twisted.internet.interfaces.IOpenSSLContextFactory} - """ - oldStyleContextFactory.getContext() - self._oldStyleContextFactory = oldStyleContextFactory - - def _connectionForTLS(self, protocol): - """ - Create an L{OpenSSL.SSL.Connection} object. - - @param protocol: The protocol initiating a TLS connection. - @type protocol: L{TLSMemoryBIOProtocol} - - @return: a connection - @rtype: L{OpenSSL.SSL.Connection} - """ - context = self._oldStyleContextFactory.getContext() - return Connection(context, None) - - def serverConnectionForTLS(self, protocol): - """ - Construct an OpenSSL server connection from the wrapped old-style - context factory. - - @note: Since old-style context factories don't distinguish between - clients and servers, this is exactly the same as - L{_ContextFactoryToConnectionFactory.clientConnectionForTLS}. - - @param protocol: The protocol initiating a TLS connection. - @type protocol: L{TLSMemoryBIOProtocol} - - @return: a connection - @rtype: L{OpenSSL.SSL.Connection} - """ - return self._connectionForTLS(protocol) - - def clientConnectionForTLS(self, protocol): - """ - Construct an OpenSSL server connection from the wrapped old-style - context factory. - - @note: Since old-style context factories don't distinguish between - clients and servers, this is exactly the same as - L{_ContextFactoryToConnectionFactory.serverConnectionForTLS}. - - @param protocol: The protocol initiating a TLS connection. - @type protocol: L{TLSMemoryBIOProtocol} - - @return: a connection - @rtype: L{OpenSSL.SSL.Connection} - """ - return self._connectionForTLS(protocol) - - class _AggregateSmallWrites: """ Aggregate small writes so they get written in large batches. @@ -709,7 +633,7 @@ class _AggregateSmallWrites: self._clock = clock self._buffer: list[bytes] = [] self._bufferLeft = self.MAX_BUFFER_SIZE - self._scheduled: Optional[IDelayedCall] = None + self._scheduled: IDelayedCall | None = None def write(self, data: bytes) -> None: """ @@ -801,31 +725,25 @@ class BufferingTLSTransport(TLSMemoryBIOProtocol): super().loseConnection() -class TLSMemoryBIOFactory(WrappingFactory): +class TLSMemoryBIOFactory(WrappingFactory[TLSMemoryBIOProtocol]): """ L{TLSMemoryBIOFactory} adds TLS to connections. - @ivar _creatorInterface: the interface which L{_connectionCreator} is - expected to implement. - @type _creatorInterface: L{zope.interface.interfaces.IInterface} - - @ivar _connectionCreator: a callable which creates an OpenSSL Connection - object. - @type _connectionCreator: 1-argument callable taking - L{TLSMemoryBIOProtocol} and returning L{OpenSSL.SSL.Connection}. + @ivar _creatorCallable: A callable for creating an + L{OpenSSL.SSL.Connection} from a L{TLSMemoryBIOProtocol}. """ - protocol = BufferingTLSTransport + protocol: type[TLSMemoryBIOProtocol] = BufferingTLSTransport noisy = False # disable unnecessary logging. def __init__( self, - contextFactory, - isClient, - wrappedFactory, - clock=None, - ): + contextFactory: SomeConnectionCreator, + isClient: bool, + wrappedFactory: IProtocolFactory, + clock: IReactorTime | None = None, + ) -> None: """ Create a L{TLSMemoryBIOFactory}. @@ -868,15 +786,9 @@ class TLSMemoryBIOFactory(WrappingFactory): application-level protocol. @type wrappedFactory: L{twisted.internet.interfaces.IProtocolFactory} """ - WrappingFactory.__init__(self, wrappedFactory) - if isClient: - creatorInterface = IOpenSSLClientConnectionCreator - else: - creatorInterface = IOpenSSLServerConnectionCreator - self._creatorInterface = creatorInterface - if not creatorInterface.providedBy(contextFactory): - contextFactory = _ContextFactoryToConnectionFactory(contextFactory) - self._connectionCreator = contextFactory + super().__init__(wrappedFactory) + + self._creatorCallable = _convertToAppropriateFactory(isClient, contextFactory) if clock is None: clock = _get_default_clock() @@ -894,43 +806,3 @@ class TLSMemoryBIOFactory(WrappingFactory): else: logPrefix = self.wrappedFactory.__class__.__name__ return f"{logPrefix} (TLS)" - - def _applyProtocolNegotiation(self, connection): - """ - Applies ALPN/NPN protocol neogitation to the connection, if the factory - supports it. - - @param connection: The OpenSSL connection object to have ALPN/NPN added - to it. - @type connection: L{OpenSSL.SSL.Connection} - - @return: Nothing - @rtype: L{None} - """ - if IProtocolNegotiationFactory.providedBy(self.wrappedFactory): - protocols = self.wrappedFactory.acceptableProtocols() - context = connection.get_context() - _setAcceptableProtocols(context, protocols) - - return - - def _createConnection(self, tlsProtocol): - """ - Create an OpenSSL connection and set it up good. - - @param tlsProtocol: The protocol which is establishing the connection. - @type tlsProtocol: L{TLSMemoryBIOProtocol} - - @return: an OpenSSL connection object for C{tlsProtocol} to use - @rtype: L{OpenSSL.SSL.Connection} - """ - connectionCreator = self._connectionCreator - if self._creatorInterface is IOpenSSLClientConnectionCreator: - connection = connectionCreator.clientConnectionForTLS(tlsProtocol) - self._applyProtocolNegotiation(connection) - connection.set_connect_state() - else: - connection = connectionCreator.serverConnectionForTLS(tlsProtocol) - self._applyProtocolNegotiation(connection) - connection.set_accept_state() - return connection diff --git a/contrib/python/Twisted/py3/twisted/python/_release.py b/contrib/python/Twisted/py3/twisted/python/_release.py index 35220c31953..6c72f8ae41b 100644 --- a/contrib/python/Twisted/py3/twisted/python/_release.py +++ b/contrib/python/Twisted/py3/twisted/python/_release.py @@ -14,7 +14,6 @@ which must run on multiple platforms (eg the setup.py script). import os from subprocess import STDOUT, CalledProcessError, check_output -from typing import Dict from zope.interface import Interface, implementer @@ -198,7 +197,7 @@ class Project: @return: A L{incremental.Version} specifying the version number of the project based on live python modules. """ - namespace: Dict[str, object] = {} + namespace: dict[str, object] = {} directory = self.directory while not namespace: if directory.path == "/": diff --git a/contrib/python/Twisted/py3/twisted/python/_shellcomp.py b/contrib/python/Twisted/py3/twisted/python/_shellcomp.py index 9c9a46a8d42..bc72951e506 100644 --- a/contrib/python/Twisted/py3/twisted/python/_shellcomp.py +++ b/contrib/python/Twisted/py3/twisted/python/_shellcomp.py @@ -30,7 +30,6 @@ import getopt import inspect import itertools from types import MethodType -from typing import Dict, List, Set from twisted.python import reflect, usage, util from twisted.python.compat import ioType @@ -211,7 +210,7 @@ class ZshSubcommandBuilder(ZshBuilder): self.subOptions = subOptions ZshBuilder.__init__(self, *args) - def write(self): + def write(self, genSubs=True): """ Generate the completion function and write it to the output file @return: L{None} @@ -305,8 +304,8 @@ class ZshArgumentsGenerator: aCL = reflect.accumulateClassList - optFlags: List[List[object]] = [] - optParams: List[List[object]] = [] + optFlags: list[list[object]] = [] + optParams: list[list[object]] = [] aCL(options.__class__, "optFlags", optFlags) aCL(options.__class__, "optParameters", optParams) @@ -468,7 +467,7 @@ class ZshArgumentsGenerator: strings.sort() # need deterministic order for reliable unit-tests return "(%s)" % " ".join(strings) - def makeExcludesDict(self) -> Dict[str, Set[str]]: + def makeExcludesDict(self) -> dict[str, set[str]]: """ @return: A C{dict} that maps each option name appearing in self.mutuallyExclusive to a set of those option names that is it @@ -481,7 +480,7 @@ class ZshArgumentsGenerator: if optList[1] != None: longToShort[optList[0]] = optList[1] - excludes: Dict[str, Set[str]] = {} + excludes: dict[str, set[str]] = {} for lst in self.mutuallyExclusive: for i, longname in enumerate(lst): tmp = set(lst[:i] + lst[i + 1 :]) @@ -624,7 +623,7 @@ class ZshArgumentsGenerator: These will be defined by 'opt_foo' methods of the Options subclass @return: L{None} """ - methodsDict: Dict[str, MethodType] = {} + methodsDict: dict[str, MethodType] = {} reflect.accumulateMethods(self.options, methodsDict, "opt_") methodToShort = {} for name in methodsDict.copy(): diff --git a/contrib/python/Twisted/py3/twisted/python/_textattributes.py b/contrib/python/Twisted/py3/twisted/python/_textattributes.py index 36403094ed4..58ecaabb4b9 100644 --- a/contrib/python/Twisted/py3/twisted/python/_textattributes.py +++ b/contrib/python/Twisted/py3/twisted/python/_textattributes.py @@ -21,7 +21,8 @@ Serializing a formatting structure is done with L{flatten}. """ -from typing import ClassVar, List, Sequence +from collections.abc import Sequence +from typing import ClassVar from twisted.python.util import FancyEqMixin @@ -85,7 +86,7 @@ class _NormalAttr(_Attribute): A text attribute for normal text. """ - def serialize(self, write, attrs, attributeRenderer): + def serialize(self, write, attrs=None, attributeRenderer="toVT102"): attrs.__init__() _Attribute.serialize(self, write, attrs, attributeRenderer) @@ -115,7 +116,7 @@ class _OtherAttr(_Attribute): result.children.extend(self.children) return result - def serialize(self, write, attrs, attributeRenderer): + def serialize(self, write, attrs=None, attributeRenderer="toVT102"): attrs = attrs._withAttribute(self.attrname, self.attrvalue) _Attribute.serialize(self, write, attrs, attributeRenderer) @@ -136,7 +137,7 @@ class _ColorAttr(_Attribute): self.color = color self.ground = ground - def serialize(self, write, attrs, attributeRenderer): + def serialize(self, write, attrs=None, attributeRenderer="toVT102"): attrs = attrs._withAttribute(self.ground, self.color) _Attribute.serialize(self, write, attrs, attributeRenderer) @@ -296,7 +297,7 @@ def flatten(output, attrs, attributeRenderer="toVT102"): @return: A string expressing the text and display attributes specified by L{output}. """ - flattened: List[str] = [] + flattened: list[str] = [] output.serialize(flattened.append, attrs, attributeRenderer) return "".join(flattened) diff --git a/contrib/python/Twisted/py3/twisted/python/_tzhelper.py b/contrib/python/Twisted/py3/twisted/python/_tzhelper.py index 2b841889d75..1b83b53bd9e 100644 --- a/contrib/python/Twisted/py3/twisted/python/_tzhelper.py +++ b/contrib/python/Twisted/py3/twisted/python/_tzhelper.py @@ -6,13 +6,14 @@ Time zone utilities. """ +from __future__ import annotations + from datetime import ( datetime as DateTime, timedelta as TimeDelta, timezone, tzinfo as TZInfo, ) -from typing import Optional __all__ = [ "FixedOffsetTimeZone", @@ -31,7 +32,7 @@ class FixedOffsetTimeZone(TZInfo): offset. """ - def __init__(self, offset: TimeDelta, name: Optional[str] = None) -> None: + def __init__(self, offset: TimeDelta, name: str | None = None) -> None: """ Construct a L{FixedOffsetTimeZone} with a fixed offset. @@ -44,7 +45,7 @@ class FixedOffsetTimeZone(TZInfo): @classmethod def fromSignHoursMinutes( cls, sign: str, hours: int, minutes: int - ) -> "FixedOffsetTimeZone": + ) -> FixedOffsetTimeZone: """ Construct a L{FixedOffsetTimeZone} from an offset described by sign ('+' or '-'), hours, and minutes. @@ -68,7 +69,7 @@ class FixedOffsetTimeZone(TZInfo): return cls(TimeDelta(hours=hours, minutes=minutes), name) @classmethod - def fromLocalTimeStamp(cls, timeStamp: float) -> "FixedOffsetTimeZone": + def fromLocalTimeStamp(cls, timeStamp: float) -> FixedOffsetTimeZone: """ Create a time zone with a fixed offset corresponding to a time stamp in the system's locally configured time zone. @@ -78,20 +79,20 @@ class FixedOffsetTimeZone(TZInfo): ).replace(tzinfo=None) return cls(offset) - def utcoffset(self, dt: Optional[DateTime]) -> TimeDelta: + def utcoffset(self, dt: DateTime | None) -> TimeDelta: """ Return the given timezone's offset from UTC. """ return self.offset - def dst(self, dt: Optional[DateTime]) -> TimeDelta: + def dst(self, dt: DateTime | None) -> TimeDelta: """ Return a zero L{TimeDelta} for the daylight saving time offset, since there is never one. """ return TimeDelta(0) - def tzname(self, dt: Optional[DateTime]) -> str: + def tzname(self, dt: DateTime | None) -> str: """ Return a string describing this timezone. """ diff --git a/contrib/python/Twisted/py3/twisted/python/components.py b/contrib/python/Twisted/py3/twisted/python/components.py index b6a6015dcc6..7ec71309de8 100644 --- a/contrib/python/Twisted/py3/twisted/python/components.py +++ b/contrib/python/Twisted/py3/twisted/python/components.py @@ -31,7 +31,6 @@ interface. from io import StringIO -from typing import Dict # zope3 imports from zope.interface import declarations, interface @@ -332,7 +331,7 @@ def proxyForInterface(iface, originalAttribute="original"): def __init__(self, original): setattr(self, originalAttribute, original) - contents: Dict[str, object] = {"__init__": __init__} + contents: dict[str, object] = {"__init__": __init__} for name in iface: contents[name] = _ProxyDescriptor(name, originalAttribute) proxy = type(f"(Proxy for {reflect.qual(iface)})", (object,), contents) diff --git a/contrib/python/Twisted/py3/twisted/python/context.py b/contrib/python/Twisted/py3/twisted/python/context.py index f2d3bd82e9a..3afdf4c95a2 100644 --- a/contrib/python/Twisted/py3/twisted/python/context.py +++ b/contrib/python/Twisted/py3/twisted/python/context.py @@ -14,9 +14,8 @@ This is thread-safe. from threading import local -from typing import Dict, Type -defaultContextDict: Dict[Type[object], Dict[str, str]] = {} +defaultContextDict: dict[type[object], dict[str, str]] = {} setDefault = defaultContextDict.__setitem__ diff --git a/contrib/python/Twisted/py3/twisted/python/deprecate.py b/contrib/python/Twisted/py3/twisted/python/deprecate.py index 22b0d162aa9..83e119e27f6 100644 --- a/contrib/python/Twisted/py3/twisted/python/deprecate.py +++ b/contrib/python/Twisted/py3/twisted/python/deprecate.py @@ -94,10 +94,11 @@ __all__ = [ import inspect import sys +from collections.abc import Sequence from dis import findlinestarts from functools import wraps from types import ModuleType -from typing import Any, Callable, Dict, Optional, TypeVar, cast +from typing import Any, Callable, TypeVar, cast from warnings import warn, warn_explicit from incremental import Version, getVersionString @@ -628,75 +629,32 @@ def warnAboutFunction(offender, warningString): ) -def _passedArgSpec(argspec, positional, keyword): - """ - Take an I{inspect.ArgSpec}, a tuple of positional arguments, and a dict of - keyword arguments, and return a mapping of arguments that were actually - passed to their passed values. - - @param argspec: The argument specification for the function to inspect. - @type argspec: I{inspect.ArgSpec} - - @param positional: The positional arguments that were passed. - @type positional: L{tuple} - - @param keyword: The keyword arguments that were passed. - @type keyword: L{dict} - - @return: A dictionary mapping argument names (those declared in C{argspec}) - to values that were passed explicitly by the user. - @rtype: L{dict} mapping L{str} to L{object} - """ - result: Dict[str, object] = {} - unpassed = len(argspec.args) - len(positional) - if argspec.keywords is not None: - kwargs = result[argspec.keywords] = {} - if unpassed < 0: - if argspec.varargs is None: - raise TypeError("Too many arguments.") - else: - result[argspec.varargs] = positional[len(argspec.args) :] - for name, value in zip(argspec.args, positional): - result[name] = value - for name, value in keyword.items(): - if name in argspec.args: - if name in result: - raise TypeError("Already passed.") - result[name] = value - elif argspec.keywords is not None: - kwargs[name] = value - else: - raise TypeError("no such param") - return result - - -def _passedSignature(signature, positional, keyword): +def _passedSignature( + signature: inspect.Signature, + positional: tuple[object, ...], + keyword: dict[str, object], +) -> dict[str, object]: """ Take an L{inspect.Signature}, a tuple of positional arguments, and a dict of keyword arguments, and return a mapping of arguments that were actually passed to their passed values. @param signature: The signature of the function to inspect. - @type signature: L{inspect.Signature} - @param positional: The positional arguments that were passed. - @type positional: L{tuple} - @param keyword: The keyword arguments that were passed. - @type keyword: L{dict} @return: A dictionary mapping argument names (those declared in C{signature}) to values that were passed explicitly by the user. - @rtype: L{dict} mapping L{str} to L{object} """ - result = {} - kwargs = None - numPositional = 0 + result: dict[str, object] = {} + kwargs: dict[str, object] | None = None + numPositional: int = 0 for n, (name, param) in enumerate(signature.parameters.items()): if param.kind == inspect.Parameter.VAR_POSITIONAL: # Varargs, for example: *args - result[name] = positional[n:] - numPositional = len(result[name]) + 1 + varargs: tuple[object, ...] = positional[n:] + result[name] = varargs + numPositional = len(varargs) + 1 elif param.kind == inspect.Parameter.VAR_KEYWORD: # Variable keyword args, for example: **my_kwargs kwargs = result[name] = {} @@ -730,30 +688,29 @@ def _passedSignature(signature, positional, keyword): return result -def _mutuallyExclusiveArguments(argumentPairs): +def _mutuallyExclusiveArguments( + argumentPairs: Sequence[tuple[str, str]] +) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: """ Decorator which causes its decoratee to raise a L{TypeError} if two of the given arguments are passed at the same time. @param argumentPairs: pairs of argument identifiers, each pair indicating an argument that may not be passed in conjunction with another. - @type argumentPairs: sequence of 2-sequences of L{str} @return: A decorator, used like so:: @_mutuallyExclusiveArguments([["tweedledum", "tweedledee"]]) def function(tweedledum=1, tweedledee=2): "Don't pass tweedledum and tweedledee at the same time." - - @rtype: 1-argument callable taking a callable and returning a callable. """ - def wrapper(wrappee): + def wrapper(wrappee: Callable[_P, _R]) -> Callable[_P, _R]: spec = inspect.signature(wrappee) _passed = _passedSignature @wraps(wrappee) - def wrapped(*args, **kwargs): + def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _R: arguments = _passed(spec, args, kwargs) for this, that in argumentPairs: if this in arguments and that in arguments: @@ -772,7 +729,7 @@ _Tc = TypeVar("_Tc", bound=Callable[..., Any]) def deprecatedKeywordParameter( - version: Version, name: str, replacement: Optional[str] = None + version: Version, name: str, replacement: str | None = None ) -> Callable[[_Tc], _Tc]: """ Return a decorator that marks a keyword parameter of a callable diff --git a/contrib/python/Twisted/py3/twisted/python/fakepwd.py b/contrib/python/Twisted/py3/twisted/python/fakepwd.py index 39c11008214..c040b37f98e 100644 --- a/contrib/python/Twisted/py3/twisted/python/fakepwd.py +++ b/contrib/python/Twisted/py3/twisted/python/fakepwd.py @@ -6,7 +6,7 @@ L{twisted.python.fakepwd} provides a fake implementation of the L{pwd} API. """ -from typing import List, Optional +from __future__ import annotations __all__ = ["UserDatabase", "ShadowDatabase"] @@ -60,7 +60,7 @@ class UserDatabase: added to this database. """ - _users: List[_UserRecord] + _users: list[_UserRecord] _lastUID: int = 10101 _lastGID: int = 20202 @@ -71,8 +71,8 @@ class UserDatabase: self, username: str, password: str = "password", - uid: Optional[int] = None, - gid: Optional[int] = None, + uid: int | None = None, + gid: int | None = None, gecos: str = "", home: str = "", shell: str = "/bin/sh", @@ -130,7 +130,7 @@ class UserDatabase: return entry raise KeyError() - def getpwall(self) -> List[_UserRecord]: + def getpwall(self) -> list[_UserRecord]: """ Return a list of all user records. """ @@ -194,7 +194,7 @@ class ShadowDatabase: @since: 12.0 """ - _users: List[_ShadowRecord] + _users: list[_ShadowRecord] def __init__(self) -> None: self._users = [] diff --git a/contrib/python/Twisted/py3/twisted/python/filepath.py b/contrib/python/Twisted/py3/twisted/python/filepath.py index c5feb2f3f42..1a0cca5c0bb 100644 --- a/contrib/python/Twisted/py3/twisted/python/filepath.py +++ b/contrib/python/Twisted/py3/twisted/python/filepath.py @@ -12,6 +12,7 @@ import base64 import errno import os import sys +from collections.abc import Iterable, Sequence from os import listdir, stat, utime from os.path import ( abspath, @@ -46,22 +47,17 @@ from typing import ( Any, AnyStr, Callable, - Dict, Generic, - Iterable, - List, - Optional, - Sequence, - Tuple, + Literal, TypeVar, - Union, cast, overload, ) -from zope.interface import Attribute, Interface, implementer +if TYPE_CHECKING: + from types import NotImplementedType -from typing_extensions import Literal +from zope.interface import Attribute, Interface, implementer from twisted.python.compat import cmp, comparable from twisted.python.runtime import platform @@ -208,7 +204,7 @@ class IFilePath(Interface): @raise Exception: if the file at this file path is not a directory. """ - def basename() -> Union[str, bytes]: + def basename() -> str | bytes: """ Retrieve the final component of the file path's path (everything after the final path separator). @@ -352,7 +348,7 @@ class AbstractFilePath(Generic[AnyStr]): """ raise NotImplementedError() - def listdir(self) -> List[AnyStr]: + def listdir(self) -> list[AnyStr]: """ Subclasses must implement this. """ @@ -405,7 +401,7 @@ class AbstractFilePath(Generic[AnyStr]): @return: an iterable of all currently-existing children of this object. """ try: - subnames: List[AnyStr] = self.listdir() + subnames: list[AnyStr] = self.listdir() except OSError as ose: # Under Python 3.3 and higher on Windows, WindowsError is an # alias for OSError. OSError has a winerror attribute and an @@ -441,7 +437,7 @@ class AbstractFilePath(Generic[AnyStr]): def walk( self: _Self, - descend: Optional[Callable[[_Self], bool]] = None, + descend: Callable[[_Self], bool] | None = None, ) -> Iterable[_Self]: """ Yield myself, then each of my children, and each of those children's @@ -513,7 +509,7 @@ class AbstractFilePath(Generic[AnyStr]): path = path.child(name) return path - def segmentsFrom(self: _Self, ancestor: _Self) -> List[AnyStr]: + def segmentsFrom(self: _Self, ancestor: _Self) -> list[AnyStr]: """ Return a list of segments between a child and its ancestor. @@ -534,7 +530,7 @@ class AbstractFilePath(Generic[AnyStr]): # obvious fast implemenation does the right thing too f = self p: _Self = f.parent() # type:ignore[assignment] - segments: List[AnyStr] = [] + segments: list[AnyStr] = [] while f != ancestor and p != f: segments[0:0] = [f.basename()] f = p @@ -664,7 +660,7 @@ class Permissions(FancyEqMixin): return "".join([x.shorthand() for x in (self.user, self.group, self.other)]) -def _asFilesystemBytes(path: Union[bytes, str], encoding: Optional[str] = "") -> bytes: +def _asFilesystemBytes(path: bytes | str, encoding: str | None = "") -> bytes: """ Return C{path} as a string of L{bytes} suitable for use on this system's filesystem. @@ -684,7 +680,7 @@ def _asFilesystemBytes(path: Union[bytes, str], encoding: Optional[str] = "") -> return path.encode(encoding, errors="surrogateescape") -def _asFilesystemText(path: Union[bytes, str], encoding: Optional[str] = None) -> str: +def _asFilesystemText(path: bytes | str, encoding: str | None = None) -> str: """ Return C{path} as a string of L{unicode} suitable for use on this system's filesystem. @@ -706,7 +702,7 @@ def _asFilesystemText(path: Union[bytes, str], encoding: Optional[str] = None) - def _coerceToFilesystemEncoding( - path: AnyStr, newpath: Union[bytes, str], encoding: Optional[str] = None + path: AnyStr, newpath: bytes | str, encoding: str | None = None ) -> AnyStr: """ Return a C{newpath} that is suitable for joining to C{path}. @@ -807,7 +803,7 @@ class FilePath(AbstractFilePath[AnyStr]): """ return FilePath(path) - def __getstate__(self) -> Dict[str, object]: + def __getstate__(self) -> dict[str, object]: """ Support serialization by discarding cached L{os.stat} results and returning everything else. @@ -827,7 +823,7 @@ class FilePath(AbstractFilePath[AnyStr]): """ return _coerceToFilesystemEncoding(self.path, os.sep) - def _asBytesPath(self, encoding: Optional[str] = None) -> bytes: + def _asBytesPath(self, encoding: str | None = None) -> bytes: """ Return the path of this L{FilePath} as bytes. @@ -838,7 +834,7 @@ class FilePath(AbstractFilePath[AnyStr]): """ return _asFilesystemBytes(self.path, encoding=encoding) - def _asTextPath(self, encoding: Optional[str] = None) -> str: + def _asTextPath(self, encoding: str | None = None) -> str: """ Return the path of this L{FilePath} as text. @@ -849,7 +845,7 @@ class FilePath(AbstractFilePath[AnyStr]): """ return _asFilesystemText(self.path, encoding=encoding) - def asBytesMode(self, encoding: Optional[str] = None) -> FilePath[bytes]: + def asBytesMode(self, encoding: str | None = None) -> FilePath[bytes]: """ Return this L{FilePath} in L{bytes}-mode. @@ -862,7 +858,7 @@ class FilePath(AbstractFilePath[AnyStr]): return self.clonePath(self._asBytesPath(encoding=encoding)) return self - def asTextMode(self, encoding: Optional[str] = None) -> FilePath[str]: + def asTextMode(self, encoding: str | None = None) -> FilePath[str]: """ Return this L{FilePath} in L{unicode}-mode. @@ -938,9 +934,7 @@ class FilePath(AbstractFilePath[AnyStr]): raise InsecurePath(f"{newpath!r} is not a child of {ourPath!r}") return self.clonePath(newpath) - def childSearchPreauth( - self, *paths: OtherAnyStr - ) -> Optional[FilePath[OtherAnyStr]]: + def childSearchPreauth(self, *paths: OtherAnyStr) -> FilePath[OtherAnyStr] | None: """ Return my first existing child with a name in C{paths}. @@ -962,7 +956,7 @@ class FilePath(AbstractFilePath[AnyStr]): def siblingExtensionSearch( self, *exts: OtherAnyStr - ) -> Optional[FilePath[OtherAnyStr]]: + ) -> FilePath[OtherAnyStr] | None: """ Attempt to return a path with my name, given multiple possible extensions. @@ -1397,7 +1391,7 @@ class FilePath(AbstractFilePath[AnyStr]): """ return isabs(self.path) - def listdir(self) -> List[AnyStr]: + def listdir(self) -> list[AnyStr]: """ List the base names of the direct children of this L{FilePath}. @@ -1411,7 +1405,7 @@ class FilePath(AbstractFilePath[AnyStr]): """ return listdir(self.path) - def splitext(self) -> Tuple[AnyStr, AnyStr]: + def splitext(self) -> tuple[AnyStr, AnyStr]: """ Split the file path into a pair C{(root, ext)} such that C{root + ext == path}. @@ -1473,7 +1467,7 @@ class FilePath(AbstractFilePath[AnyStr]): ): raise - def globChildren(self, pattern: OtherAnyStr) -> List[FilePath[OtherAnyStr]]: + def globChildren(self, pattern: OtherAnyStr) -> list[FilePath[OtherAnyStr]]: """ Assuming I am representing a directory, return a list of FilePaths representing my children that match the given pattern. @@ -1524,7 +1518,7 @@ class FilePath(AbstractFilePath[AnyStr]): """ return self.clonePath(self.dirname()) - def setContent(self, content: bytes, ext: Union[str, bytes] = ".new") -> None: + def setContent(self, content: bytes, ext: str | bytes = ".new") -> None: """ Replace the file at this path with a new file that contains the given bytes, trying to avoid data-loss in the meanwhile. @@ -1576,9 +1570,10 @@ class FilePath(AbstractFilePath[AnyStr]): os.unlink(self.path) os.rename(sib.path, self.asBytesMode().path) - def __cmp__(self, other: object) -> int: + def __cmp__(self, other: object) -> int | NotImplementedType: if not isinstance(other, FilePath): - return NotImplemented + # https://github.com/python/mypy/issues/18914 + return NotImplemented # type:ignore[no-any-return] return cmp(self.path, other.path) def createDirectory(self) -> None: @@ -1622,13 +1617,11 @@ class FilePath(AbstractFilePath[AnyStr]): ... @overload - def temporarySibling( - self, extension: Optional[OtherAnyStr] - ) -> FilePath[OtherAnyStr]: + def temporarySibling(self, extension: OtherAnyStr | None) -> FilePath[OtherAnyStr]: ... def temporarySibling( - self, extension: Optional[OtherAnyStr] = None + self, extension: OtherAnyStr | None = None ) -> FilePath[OtherAnyStr]: """ Construct a path referring to a sibling of this path. diff --git a/contrib/python/Twisted/py3/twisted/python/formmethod.py b/contrib/python/Twisted/py3/twisted/python/formmethod.py index aa5fb97a2d3..4394f51eec7 100644 --- a/contrib/python/Twisted/py3/twisted/python/formmethod.py +++ b/contrib/python/Twisted/py3/twisted/python/formmethod.py @@ -10,8 +10,10 @@ This module contains support for descriptive method signatures that can be used to format methods. """ +from __future__ import annotations + import calendar -from typing import Any, Optional, Tuple +from typing import Any class FormException(Exception): @@ -136,7 +138,7 @@ class Hidden(String): class Integer(Argument): """A single integer.""" - defaultDefault: Optional[int] = None + defaultDefault: int | None = None def __init__( self, name, allowNone=1, default=None, shortDesc=None, longDesc=None, hints=None @@ -203,7 +205,7 @@ class IntegerRange(Integer): class Float(Argument): - defaultDefault: Optional[float] = None + defaultDefault: float | None = None def __init__( self, name, allowNone=1, default=None, shortDesc=None, longDesc=None, hints=None @@ -343,7 +345,7 @@ def positiveInt(x): class Date(Argument): """A date -- (year, month, day) tuple.""" - defaultDefault: Optional[Tuple[int, int, int]] = None + defaultDefault: tuple[int, int, int] | None = None def __init__( self, name, allowNone=1, default=None, shortDesc=None, longDesc=None, hints=None diff --git a/contrib/python/Twisted/py3/twisted/python/htmlizer.py b/contrib/python/Twisted/py3/twisted/python/htmlizer.py index c1d4e43a378..2e959f5d103 100644 --- a/contrib/python/Twisted/py3/twisted/python/htmlizer.py +++ b/contrib/python/Twisted/py3/twisted/python/htmlizer.py @@ -9,7 +9,6 @@ HTML rendering of Python source. import keyword import tokenize from html import escape -from typing import List from . import reflect @@ -74,11 +73,11 @@ class HTMLWriter: tokens as HTML spans. """ - noSpan: List[str] = [] + noSpan: list[str] = [] def __init__(self, writer): self.writer = writer - noSpan: List[str] = [] + noSpan: list[str] = [] reflect.accumulateClassList(self.__class__, "noSpan", noSpan) self.noSpan = noSpan diff --git a/contrib/python/Twisted/py3/twisted/python/log.py b/contrib/python/Twisted/py3/twisted/python/log.py index 4392486bbee..82db7fc9f87 100644 --- a/contrib/python/Twisted/py3/twisted/python/log.py +++ b/contrib/python/Twisted/py3/twisted/python/log.py @@ -6,13 +6,14 @@ Logging and metrics infrastructure. """ +from __future__ import annotations import sys import time import warnings from abc import ABC, abstractmethod from datetime import datetime, timezone -from typing import Any, BinaryIO, Dict, Optional, cast +from typing import Any, BinaryIO, cast from zope.interface import Interface @@ -31,7 +32,7 @@ from twisted.logger._legacy import publishToNewObserver as _publishNew from twisted.python import context, failure, reflect, util from twisted.python.threadable import synchronize -EventDict = Dict[str, Any] +EventDict = dict[str, Any] class ILogContext: @@ -353,7 +354,7 @@ if "theLogPublisher" not in globals(): """ -def _safeFormat(fmtString: str, fmtDict: Dict[str, Any]) -> str: +def _safeFormat(fmtString: str, fmtDict: dict[str, Any]) -> str: """ Try to format a string, swallowing all errors to always return a string. @@ -397,7 +398,7 @@ def _safeFormat(fmtString: str, fmtDict: Dict[str, Any]) -> str: return text -def textFromEventDict(eventDict: EventDict) -> Optional[str]: +def textFromEventDict(eventDict: EventDict) -> str | None: """ Extract text from an event dict passed to a log observer. If it cannot handle the dict, it returns None. @@ -472,7 +473,7 @@ class FileLogObserver(_GlobalStartStopObserver): @ivar timeFormat: If not L{None}, the format string passed to strftime(). """ - timeFormat: Optional[str] = None + timeFormat: str | None = None def __init__(self, f): # Compatibility diff --git a/contrib/python/Twisted/py3/twisted/python/logfile.py b/contrib/python/Twisted/py3/twisted/python/logfile.py index 33971bab08e..8f079a715b2 100644 --- a/contrib/python/Twisted/py3/twisted/python/logfile.py +++ b/contrib/python/Twisted/py3/twisted/python/logfile.py @@ -7,13 +7,14 @@ A rotating, browsable log file. """ +from __future__ import annotations # System Imports import glob import os import stat import time -from typing import BinaryIO, Optional, cast +from typing import BinaryIO, cast from twisted.python import threadable @@ -26,7 +27,7 @@ class BaseLogFile: synchronized = ["write", "rotate"] def __init__( - self, name: str, directory: str, defaultMode: Optional[int] = None + self, name: str, directory: str, defaultMode: int | None = None ) -> None: """ Create a log file. @@ -40,7 +41,7 @@ class BaseLogFile: self.name = name self.path = os.path.join(directory, name) if defaultMode is None and os.path.exists(self.path): - self.defaultMode: Optional[int] = stat.S_IMODE( + self.defaultMode: int | None = stat.S_IMODE( os.stat(self.path)[stat.ST_MODE] ) else: diff --git a/contrib/python/Twisted/py3/twisted/python/rebuild.py b/contrib/python/Twisted/py3/twisted/python/rebuild.py index e82beec1b6a..9df41c94e30 100644 --- a/contrib/python/Twisted/py3/twisted/python/rebuild.py +++ b/contrib/python/Twisted/py3/twisted/python/rebuild.py @@ -15,7 +15,6 @@ import time import types from importlib import reload from types import ModuleType -from typing import Dict # Sibling Imports from twisted.python import log, reflect @@ -60,7 +59,7 @@ class Sensitive: return anObject -_modDictIDMap: Dict[int, ModuleType] = {} +_modDictIDMap: dict[int, ModuleType] = {} def latestFunction(oldFunc): @@ -210,7 +209,10 @@ def rebuild(module, doLog=1): log.msg("") log.msg(f" (fixing {str(module.__name__)}): ") modcount = 0 - for mk, mod in sys.modules.items(): + # note: sys.modules can change throughout iteration + # https://github.com/twisted/twisted/issues/12458 + for mk in list(sys.modules): + mod = sys.modules.get(mk) modcount = modcount + 1 if mod == module or mod is None: continue diff --git a/contrib/python/Twisted/py3/twisted/python/reflect.py b/contrib/python/Twisted/py3/twisted/python/reflect.py index 9991b4eb63a..70096fbabf8 100644 --- a/contrib/python/Twisted/py3/twisted/python/reflect.py +++ b/contrib/python/Twisted/py3/twisted/python/reflect.py @@ -7,6 +7,7 @@ Standardized versions of various cool and/or strange things that you can do with Python's reflection capabilities. """ +from __future__ import annotations import os import pickle @@ -17,7 +18,6 @@ import types import weakref from collections import deque from io import IOBase, StringIO -from typing import Type, Union from twisted.python.compat import nativeString from twisted.python.deprecate import _fullyQualifiedName as fullyQualifiedName @@ -348,7 +348,7 @@ def filenameToModuleName(fn): return modName -def qual(clazz: Type[object]) -> str: +def qual(clazz: type[object]) -> str: """ Return full import path of a class. """ @@ -373,7 +373,7 @@ def _determineClassName(x): return "<BROKEN CLASS AT 0x%x>" % id(c) -def _safeFormat(formatter: Union[types.FunctionType, Type[str]], o: object) -> str: +def _safeFormat(formatter: types.FunctionType | type[str], o: object) -> str: """ Helper function for L{safe_repr} and L{safe_str}. diff --git a/contrib/python/Twisted/py3/twisted/python/runtime.py b/contrib/python/Twisted/py3/twisted/python/runtime.py index 5e9c71357e5..59fd0a67888 100644 --- a/contrib/python/Twisted/py3/twisted/python/runtime.py +++ b/contrib/python/Twisted/py3/twisted/python/runtime.py @@ -2,6 +2,8 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +from __future__ import annotations + __all__ = [ "seconds", "shortPythonVersion", @@ -13,7 +15,6 @@ import os import sys import warnings from time import time as seconds -from typing import Optional def shortPythonVersion() -> str: @@ -37,13 +38,11 @@ class Platform: Gives us information about the platform we're running on. """ - type: Optional[str] = knownPlatforms.get(os.name) + type: str | None = knownPlatforms.get(os.name) seconds = staticmethod(seconds) _platform = sys.platform - def __init__( - self, name: Optional[str] = None, platform: Optional[str] = None - ) -> None: + def __init__(self, name: str | None = None, platform: str | None = None) -> None: if name is not None: self.type = knownPlatforms.get(name) if platform is not None: @@ -57,7 +56,7 @@ class Platform: """ return self.type != None - def getType(self) -> Optional[str]: + def getType(self) -> str | None: """ Get platform type. diff --git a/contrib/python/Twisted/py3/twisted/python/sendmsg.py b/contrib/python/Twisted/py3/twisted/python/sendmsg.py index ace9a5231c5..4f987204230 100644 --- a/contrib/python/Twisted/py3/twisted/python/sendmsg.py +++ b/contrib/python/Twisted/py3/twisted/python/sendmsg.py @@ -9,7 +9,6 @@ sendmsg(2) and recvmsg(2) support for Python. from collections import namedtuple from socket import CMSG_SPACE, SCM_RIGHTS, socket as Socket -from typing import List, Tuple __all__ = ["sendmsg", "recvmsg", "getSocketFamily", "SCM_RIGHTS"] @@ -20,7 +19,7 @@ ReceivedMessage = namedtuple("ReceivedMessage", ["data", "ancillary", "flags"]) def sendmsg( socket: Socket, data: bytes, - ancillary: List[Tuple[int, int, bytes]] = [], + ancillary: list[tuple[int, int, bytes]] = [], flags: int = 0, ) -> int: """ diff --git a/contrib/python/Twisted/py3/twisted/python/systemd.py b/contrib/python/Twisted/py3/twisted/python/systemd.py index 51911160eb9..627f0177ad9 100644 --- a/contrib/python/Twisted/py3/twisted/python/systemd.py +++ b/contrib/python/Twisted/py3/twisted/python/systemd.py @@ -9,11 +9,12 @@ Currently only the minimum APIs necessary for using systemd's socket activation feature are supported. """ +from __future__ import annotations __all__ = ["ListenFDs"] +from collections.abc import Mapping, Sequence from os import getpid -from typing import Dict, List, Mapping, Optional, Sequence from attrs import Factory, define @@ -46,9 +47,9 @@ class ListenFDs: @classmethod def fromEnvironment( cls, - environ: Optional[Mapping[str, str]] = None, - start: Optional[int] = None, - ) -> "ListenFDs": + environ: Mapping[str, str] | None = None, + start: int | None = None, + ) -> ListenFDs: """ @param environ: A dictionary-like object to inspect to discover inherited descriptors. By default, L{None}, indicating that the @@ -71,7 +72,7 @@ class ListenFDs: start = cls._START if str(getpid()) == environ.get("LISTEN_PID"): - descriptors: List[int] = _parseDescriptors(start, environ) + descriptors: list[int] = _parseDescriptors(start, environ) names: Sequence[str] = _parseNames(environ) else: descriptors = [] @@ -89,13 +90,13 @@ class ListenFDs: return cls(descriptors, names) - def inheritedDescriptors(self) -> List[int]: + def inheritedDescriptors(self) -> list[int]: """ @return: The configured descriptors. """ return list(self._descriptors) - def inheritedNamedDescriptors(self) -> Dict[str, int]: + def inheritedNamedDescriptors(self) -> dict[str, int]: """ @return: A mapping from the names of configured descriptors to their integer values. @@ -103,7 +104,7 @@ class ListenFDs: return dict(zip(self._names, self._descriptors)) -def _parseDescriptors(start: int, environ: Mapping[str, str]) -> List[int]: +def _parseDescriptors(start: int, environ: Mapping[str, str]) -> list[int]: """ Parse the I{LISTEN_FDS} environment variable supplied by systemd. diff --git a/contrib/python/Twisted/py3/twisted/python/threadpool.py b/contrib/python/Twisted/py3/twisted/python/threadpool.py index ab5c0f1e67f..58f008085f2 100644 --- a/contrib/python/Twisted/py3/twisted/python/threadpool.py +++ b/contrib/python/Twisted/py3/twisted/python/threadpool.py @@ -12,9 +12,9 @@ instead of creating a thread pool directly. from __future__ import annotations from threading import Thread, current_thread -from typing import Any, Callable, List, Optional, TypeVar +from typing import Any, Callable, Protocol, TypeVar -from typing_extensions import ParamSpec, Protocol, TypedDict +from typing_extensions import ParamSpec, TypedDict from twisted._threads import pool as _pool from twisted.python import context, log @@ -72,7 +72,7 @@ class ThreadPool: _pool = staticmethod(_pool) def __init__( - self, minthreads: int = 5, maxthreads: int = 20, name: Optional[str] = None + self, minthreads: int = 5, maxthreads: int = 20, name: str | None = None ): """ Create a new threadpool. @@ -91,7 +91,7 @@ class ThreadPool: self.min = minthreads self.max = maxthreads self.name = name - self.threads: List[Thread] = [] + self.threads: list[Thread] = [] def trackingThreadFactory(*a: Any, **kw: Any) -> Thread: thread = self.threadFactory( # type: ignore[misc] @@ -226,7 +226,7 @@ class ThreadPool: def callInThreadWithCallback( self, - onResult: Optional[Callable[[bool, _R], object]], + onResult: Callable[[bool, _R], object] | None, func: Callable[_P, _R], *args: _P.args, **kw: _P.kwargs, @@ -300,7 +300,7 @@ class ThreadPool: thread.join() def adjustPoolsize( - self, minthreads: Optional[int] = None, maxthreads: Optional[int] = None + self, minthreads: int | None = None, maxthreads: int | None = None ) -> None: """ Adjust the number of available threads by setting C{min} and C{max} to diff --git a/contrib/python/Twisted/py3/twisted/python/usage.py b/contrib/python/Twisted/py3/twisted/python/usage.py index 32f074c7318..750197b4c20 100644 --- a/contrib/python/Twisted/py3/twisted/python/usage.py +++ b/contrib/python/Twisted/py3/twisted/python/usage.py @@ -21,7 +21,7 @@ import os import sys import textwrap from os import path -from typing import Any, Dict, Optional, cast +from typing import Any, cast # Sibling Imports from twisted.python import reflect, util @@ -63,7 +63,7 @@ class CoerceParameter: self.options.opts[parameterName] = value -class Options(Dict[str, Any]): +class Options(dict[str, Any]): """ An option list parser class @@ -150,9 +150,9 @@ class Options(Dict[str, Any]): or doc/core/howto/options.xhtml in your Twisted directory. """ - subCommand: Optional[str] = None - defaultSubCommand: Optional[str] = None - parent: "Optional[Options]" = None + subCommand: str | None = None + defaultSubCommand: str | None = None + parent: Options | None = None completionData = None _shellCompFile = sys.stdout # file to use if shell completion is requested @@ -477,7 +477,7 @@ class Options(Dict[str, Any]): ) return synopsis - def getUsage(self, width: Optional[int] = None) -> str: + def getUsage(self, width: int | None = None) -> str: # If subOptions exists by now, then there was probably an error while # parsing its options. if hasattr(self, "subOptions"): @@ -569,7 +569,7 @@ class Completer: subclasses for specific completion functionality. """ - _descr: Optional[str] = None + _descr: str | None = None def __init__(self, descr=None, repeat=False): """ diff --git a/contrib/python/Twisted/py3/twisted/python/util.py b/contrib/python/Twisted/py3/twisted/python/util.py index 6047f7eda53..4a196012a92 100644 --- a/contrib/python/Twisted/py3/twisted/python/util.py +++ b/contrib/python/Twisted/py3/twisted/python/util.py @@ -30,17 +30,8 @@ else: # For backwards compatibility, some things import this, so just link it from collections import OrderedDict -from typing import ( - Any, - Callable, - ClassVar, - Mapping, - MutableMapping, - Sequence, - Tuple, - TypeVar, - Union, -) +from collections.abc import Mapping, MutableMapping, Sequence +from typing import Any, Callable, ClassVar, TypeVar from incremental import Version @@ -615,7 +606,7 @@ class FancyStrMixin: # Override in subclasses: showAttributes: Sequence[ - Union[str, Tuple[str, str, str], Tuple[str, Callable[[Any], str]]] + str | tuple[str, str, str] | tuple[str, Callable[[Any], str]] ] = () def __str__(self) -> str: diff --git a/contrib/python/Twisted/py3/twisted/python/zippath.py b/contrib/python/Twisted/py3/twisted/python/zippath.py index 9aa9c7c6b88..472194ddad6 100644 --- a/contrib/python/Twisted/py3/twisted/python/zippath.py +++ b/contrib/python/Twisted/py3/twisted/python/zippath.py @@ -12,24 +12,13 @@ from __future__ import annotations import errno import os import time -from typing import ( - IO, - TYPE_CHECKING, - Any, - AnyStr, - Dict, - Generic, - Iterable, - List, - Tuple, - TypeVar, - Union, -) +from collections.abc import Iterable +from typing import IO, TYPE_CHECKING, Any, AnyStr, Generic, Literal, TypeVar from zipfile import ZipFile from zope.interface import implementer -from typing_extensions import Literal, Self +from typing_extensions import Self from twisted.python.compat import cmp, comparable from twisted.python.filepath import ( @@ -80,19 +69,20 @@ class ZipPath(Generic[_ZipStr, _ArchiveStr], AbstractFilePath[_ZipStr]): archiveFilename: _ZipStr = _coerceToFilesystemEncoding( pathInArchive, archive._zipfileFilename ) - segments: List[_ZipStr] = self.pathInArchive.split(sep) + segments: list[_ZipStr] = self.pathInArchive.split(sep) fakePath: _ZipStr = os.path.join(archiveFilename, *segments) self.path: _ZipStr = fakePath def __cmp__(self, other: object) -> int: if not isinstance(other, ZipPath): - return NotImplemented + # https://github.com/python/mypy/issues/18914 + return NotImplemented # type:ignore[no-any-return] return cmp( (self.archive, self.pathInArchive), (other.archive, other.pathInArchive) ) def __repr__(self) -> str: - parts: List[_ZipStr] + parts: list[_ZipStr] parts = [ _coerceToFilesystemEncoding(self.sep, os.path.abspath(self.archive.path)) ] @@ -112,7 +102,7 @@ class ZipPath(Generic[_ZipStr, _ArchiveStr], AbstractFilePath[_ZipStr]): def _nativeParent( self, - ) -> Union[ZipPath[_ZipStr, _ArchiveStr], ZipArchive[_ArchiveStr]]: + ) -> ZipPath[_ZipStr, _ArchiveStr] | ZipArchive[_ArchiveStr]: """ Return parent, discarding our own encoding in favor of whatever the archive's is. @@ -122,7 +112,7 @@ class ZipPath(Generic[_ZipStr, _ArchiveStr], AbstractFilePath[_ZipStr]): return self.archive return ZipPath(self.archive, self.sep.join(splitup[:-1])) - def parent(self) -> Union[ZipPath[_ZipStr, _ArchiveStr], ZipArchive[_ZipStr]]: + def parent(self) -> ZipPath[_ZipStr, _ArchiveStr] | ZipArchive[_ZipStr]: parent = self._nativeParent() if isinstance(parent, ZipArchive): return ZipArchive( @@ -134,7 +124,7 @@ class ZipPath(Generic[_ZipStr, _ArchiveStr], AbstractFilePath[_ZipStr]): def parents( self, - ) -> Iterable[Union[ZipPath[_ZipStr, _ArchiveStr], ZipArchive[_ZipStr]]]: + ) -> Iterable[ZipPath[_ZipStr, _ArchiveStr] | ZipArchive[_ZipStr]]: ... def child(self, path: OtherAnyStr) -> ZipPath[OtherAnyStr, _ArchiveStr]: @@ -154,8 +144,8 @@ class ZipPath(Generic[_ZipStr, _ArchiveStr], AbstractFilePath[_ZipStr]): return ZipPath(self.archive, joiner.join([pathInArchive, path])) def sibling(self, path: OtherAnyStr) -> ZipPath[OtherAnyStr, _ArchiveStr]: - parent: Union[ZipPath[_ZipStr, _ArchiveStr], ZipArchive[_ZipStr]] - rightTypedParent: Union[ZipPath[_ZipStr, _ArchiveStr], ZipArchive[_ArchiveStr]] + parent: ZipPath[_ZipStr, _ArchiveStr] | ZipArchive[_ZipStr] + rightTypedParent: ZipPath[_ZipStr, _ArchiveStr] | ZipArchive[_ArchiveStr] parent = self.parent() rightTypedParent = self.archive if isinstance(parent, ZipArchive) else parent @@ -174,7 +164,7 @@ class ZipPath(Generic[_ZipStr, _ArchiveStr], AbstractFilePath[_ZipStr]): def islink(self) -> bool: return False - def listdir(self) -> List[_ZipStr]: + def listdir(self) -> list[_ZipStr]: if self.exists(): if self.isdir(): parentArchivePath: _ArchiveStr = _coerceToFilesystemEncoding( @@ -191,7 +181,7 @@ class ZipPath(Generic[_ZipStr, _ArchiveStr], AbstractFilePath[_ZipStr]): OSError(errno.ENOENT, "Non-existent zip entry listed") ) - def splitext(self) -> Tuple[_ZipStr, _ZipStr]: + def splitext(self) -> tuple[_ZipStr, _ZipStr]: """ Return a value similar to that returned by C{os.path.splitext}. """ @@ -295,7 +285,7 @@ class ZipArchive(ZipPath[AnyStr, AnyStr]): self.pathInArchive = _coerceToFilesystemEncoding(archivePathname, "") # zipfile is already wasting O(N) memory on cached ZipInfo instances, # so there's no sense in trying to do this lazily or intelligently - self.childmap: Dict[AnyStr, Dict[AnyStr, int]] = {} + self.childmap: dict[AnyStr, dict[AnyStr, int]] = {} for name in self.zipfile.namelist(): splitName = _coerceToFilesystemEncoding(self.path, name).split(self.sep) @@ -309,7 +299,8 @@ class ZipArchive(ZipPath[AnyStr, AnyStr]): def __cmp__(self, other: object) -> int: if not isinstance(other, ZipArchive): - return NotImplemented + # https://github.com/python/mypy/issues/18914 + return NotImplemented # type:ignore[no-any-return] return cmp(self.path, other.path) def child(self, path: OtherAnyStr) -> ZipPath[OtherAnyStr, AnyStr]: diff --git a/contrib/python/Twisted/py3/twisted/runner/procmon.py b/contrib/python/Twisted/py3/twisted/runner/procmon.py index 865ce00624b..c4278a0168c 100644 --- a/contrib/python/Twisted/py3/twisted/runner/procmon.py +++ b/contrib/python/Twisted/py3/twisted/runner/procmon.py @@ -7,7 +7,7 @@ Support for starting, monitoring, and restarting child process. """ from __future__ import annotations -from typing import Any, Dict, List, Optional +from typing import Any import attr import incremental @@ -32,27 +32,22 @@ class _Process: The parameters of a process to be restarted. @ivar args: command-line arguments (including name of command as first one) - @type args: C{list} @ivar uid: user-id to run process as, or None (which means inherit uid) - @type uid: C{int} @ivar gid: group-id to run process as, or None (which means inherit gid) - @type gid: C{int} @ivar env: environment for process - @type env: C{dict} @ivar cwd: initial working directory for process or None (which means inherit cwd) - @type cwd: C{str} """ - args: List[str] - uid: Optional[int] = None - gid: Optional[int] = None - env: Dict[str, str] = attr.ib(default=attr.Factory(dict)) - cwd: Optional[str] = None + args: list[str] + uid: int | None = None + gid: int | None = None + env: dict[str, str] = attr.ib(default=attr.Factory(dict)) + cwd: str | None = None @deprecate.deprecated(incremental.Version("Twisted", 18, 7, 0)) def toTuple(self) -> tuple[list[str], int | None, int | None, dict[str, str]]: @@ -206,7 +201,7 @@ class ProcessMonitor(service.Service): def __init__( self, - reactor: IReactorProcess = _reactor, # type:ignore + reactor: IReactorProcess = _reactor, ) -> None: self._reactor = reactor self._clock = IReactorTime(reactor) @@ -318,7 +313,7 @@ class ProcessMonitor(service.Service): for name in list(self._processes): self.stopProcess(name) - @deprecate.deprecatedProperty(incremental.Version("Twisted", 25, 5, 0)) + @deprecate.deprecated(incremental.Version("Twisted", 25, 5, 0)) def connectionLost(self, name: str) -> None: """ Called when a monitored processes exits. If diff --git a/contrib/python/Twisted/py3/twisted/runner/procmontap.py b/contrib/python/Twisted/py3/twisted/runner/procmontap.py index 03205783b31..071471ce682 100644 --- a/contrib/python/Twisted/py3/twisted/runner/procmontap.py +++ b/contrib/python/Twisted/py3/twisted/runner/procmontap.py @@ -6,7 +6,7 @@ Support for creating a service which runs a process monitor. """ -from typing import List, Sequence +from collections.abc import Sequence from twisted.python import usage from twisted.runner.procmon import ProcessMonitor @@ -57,7 +57,7 @@ class Options(usage.Options): ], ] - optFlags: List[Sequence[str]] = [] + optFlags: list[Sequence[str]] = [] longdesc = """\ procmon runs processes, monitors their progress, and restarts them when they diff --git a/contrib/python/Twisted/py3/twisted/scripts/_twistd_unix.py b/contrib/python/Twisted/py3/twisted/scripts/_twistd_unix.py index 295a32fa01e..f33c5959c4c 100644 --- a/contrib/python/Twisted/py3/twisted/scripts/_twistd_unix.py +++ b/contrib/python/Twisted/py3/twisted/scripts/_twistd_unix.py @@ -390,7 +390,9 @@ class UnixApplicationRunner(app.ApplicationRunner): "An error has occurred: {}\nPlease look at log " "file for more information.\n".format(dataRepr) ) - untilConcludes(sys.__stderr__.write, msg) + origstderr = sys.__stderr__ + assert origstderr is not None + untilConcludes(origstderr.write, msg) return 1 return 0 diff --git a/contrib/python/Twisted/py3/twisted/scripts/htmlizer.py b/contrib/python/Twisted/py3/twisted/scripts/htmlizer.py index 9c588ae11fc..fa820b33bde 100644 --- a/contrib/python/Twisted/py3/twisted/scripts/htmlizer.py +++ b/contrib/python/Twisted/py3/twisted/scripts/htmlizer.py @@ -44,7 +44,7 @@ class Options(usage.Options): extraActions=[usage.CompleteFiles("*.py", descr="source python file")] ) - def parseArgs(self, filename): + def parseArgs(self, filename): # type:ignore[override] self["filename"] = filename diff --git a/contrib/python/Twisted/py3/twisted/scripts/trial.py b/contrib/python/Twisted/py3/twisted/scripts/trial.py index f3259ed21b7..f48bbdd39cf 100644 --- a/contrib/python/Twisted/py3/twisted/scripts/trial.py +++ b/contrib/python/Twisted/py3/twisted/scripts/trial.py @@ -2,6 +2,7 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +from __future__ import annotations import gc import inspect @@ -12,7 +13,7 @@ import sys import time import trace import warnings -from typing import NoReturn, Optional, Type +from typing import Callable, NoReturn from twisted import plugin from twisted.application import app @@ -57,11 +58,13 @@ def _autoJobs() -> int: @returns: A strictly positive integer. """ - number: Optional[int] - if getattr(os, "process_cpu_count", None) is not None: - number = os.process_cpu_count() # type: ignore[attr-defined] - elif getattr(os, "sched_getaffinity", None) is not None: - number = len(os.sched_getaffinity(0)) + number: int | None + process_cpu_count: Callable[[], int | None] | None + sched_getaffinity: Callable[[int], set[int]] | None + if process_cpu_count := getattr(os, "process_cpu_count", None): + number = process_cpu_count() + elif sched_getaffinity := getattr(os, "sched_getaffinity", None): + number = len(sched_getaffinity(0)) else: number = os.cpu_count() if number is None or number < 1: @@ -268,7 +271,7 @@ class _BasicOptions: ], ) - tracer: Optional[trace.Trace] = None + tracer: trace.Trace | None = None def __init__(self): self["tests"] = [] @@ -625,7 +628,7 @@ def _makeRunner(config: Options) -> runner._Runner: @return: A trial runner instance. """ - cls: Type[runner._Runner] = runner.TrialRunner + cls: type[runner._Runner] = runner.TrialRunner args = { "reporterFactory": config["reporter"], "tracebackFormat": config["tbformat"], diff --git a/contrib/python/Twisted/py3/twisted/spread/jelly.py b/contrib/python/Twisted/py3/twisted/spread/jelly.py index 46cda178448..dccf8f3aace 100644 --- a/contrib/python/Twisted/py3/twisted/spread/jelly.py +++ b/contrib/python/Twisted/py3/twisted/spread/jelly.py @@ -203,14 +203,14 @@ def setUnjellyableFactoryForClass(classname, copyFactory): """ Set the factory to construct a remote instance of a type:: - jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory) + jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory) Call this at the module level immediately after its class definition. C{copyFactory} should return an instance or subclass of L{RemoteCopy<pb.RemoteCopy>}. - Similar to L{setUnjellyableForClass} except it uses a factory instead - of creating an instance. + Similar to L{twisted.spread.jelly.setUnjellyableForClass} except it uses a + factory instead of creating an instance. """ global unjellyableFactoryRegistry diff --git a/contrib/python/Twisted/py3/twisted/spread/pb.py b/contrib/python/Twisted/py3/twisted/spread/pb.py index 031292e318c..bb85a63bcbc 100644 --- a/contrib/python/Twisted/py3/twisted/spread/pb.py +++ b/contrib/python/Twisted/py3/twisted/spread/pb.py @@ -1277,7 +1277,7 @@ def challenge(): return crap -class PBClientFactory(protocol.ClientFactory): +class PBClientFactory(protocol.ClientFactory[Broker]): """ Client factory for PB brokers. @@ -1420,7 +1420,7 @@ class PBClientFactory(protocol.ClientFactory): return d -class PBServerFactory(protocol.ServerFactory): +class PBServerFactory(protocol.ServerFactory[Broker]): """ Server factory for perspective broker. diff --git a/contrib/python/Twisted/py3/twisted/tap/portforward.py b/contrib/python/Twisted/py3/twisted/tap/portforward.py index 3fc01460941..70d5c6ad347 100644 --- a/contrib/python/Twisted/py3/twisted/tap/portforward.py +++ b/contrib/python/Twisted/py3/twisted/tap/portforward.py @@ -11,16 +11,19 @@ from twisted.python import usage class Options(usage.Options): synopsis = "[options]" - longdesc = "Port Forwarder." + longdesc = "(Deprecated) Port Forwarder. See 'twist forward' instead." optParameters = [ - ["port", "p", "6666", "Set the port number."], - ["host", "h", "localhost", "Set the host."], - ["dest_port", "d", 6665, "Set the destination port."], + ["port", "p", "tcp:6666", "The string endpoint to listen on."], + ["host", "h", "localhost", "The destination host to connect to."], + ["dest_port", "d", 6665, "Set the destination port to connect to."], ] compData = usage.Completions(optActions={"host": usage.CompleteHostnames()}) def makeService(config): + """ + Create a port-forwarding service. + """ f = portforward.ProxyFactory(config["host"], int(config["dest_port"])) return strports.service(config["port"], f) diff --git a/contrib/python/Twisted/py3/twisted/trial/_asyncrunner.py b/contrib/python/Twisted/py3/twisted/trial/_asyncrunner.py index 12a8342ffae..12058f26ebc 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_asyncrunner.py +++ b/contrib/python/Twisted/py3/twisted/trial/_asyncrunner.py @@ -5,12 +5,12 @@ """ Infrastructure for test running and suites. """ - +from __future__ import annotations import doctest import gc import unittest as pyunit -from typing import Iterator, Union +from collections.abc import Iterator from zope.interface import implementer @@ -25,7 +25,7 @@ class TestSuite(pyunit.TestSuite): C{run} method. """ - def run(self, result): + def run(self, result: pyunit.TestResult, debug: bool = False) -> pyunit.TestResult: """ Call C{run} on every member of the suite. """ @@ -162,7 +162,7 @@ if _docTestCase: def _iterateTests( - testSuiteOrCase: Union[pyunit.TestCase, pyunit.TestSuite] + testSuiteOrCase: pyunit.TestCase | pyunit.TestSuite, ) -> Iterator[itrial.ITestCase]: """ Iterate through all of the test cases in C{testSuiteOrCase}. diff --git a/contrib/python/Twisted/py3/twisted/trial/_asynctest.py b/contrib/python/Twisted/py3/twisted/trial/_asynctest.py index 6765c703f66..b9edfdfd8d5 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_asynctest.py +++ b/contrib/python/Twisted/py3/twisted/trial/_asynctest.py @@ -10,7 +10,7 @@ from __future__ import annotations import inspect import warnings -from typing import Callable, List +from typing import Callable from zope.interface import implementer @@ -27,7 +27,7 @@ from twisted.trial._synctest import FailTest, SkipTest, SynchronousTestCase _P = ParamSpec("_P") -_wait_is_running: List[None] = [] +_wait_is_running: list[None] = [] @implementer(itrial.ITestCase) @@ -127,7 +127,7 @@ class TestCase(SynchronousTestCase): ) if inspect.isgeneratorfunction(func): exc = TypeError( - "{!r} is a generator function and therefore will never run".format(func) + f"{func!r} is a generator function and therefore will never run" ) return defer.fail(exc) d = defer.maybeDeferred( diff --git a/contrib/python/Twisted/py3/twisted/trial/_dist/distreporter.py b/contrib/python/Twisted/py3/twisted/trial/_dist/distreporter.py index 3a45cc48067..798d2181320 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_dist/distreporter.py +++ b/contrib/python/Twisted/py3/twisted/trial/_dist/distreporter.py @@ -10,21 +10,24 @@ test is over. @since: 12.3 """ +from __future__ import annotations from types import TracebackType -from typing import Optional, Tuple, Union +from typing import Union from zope.interface import implementer +from typing_extensions import TypeAlias + from twisted.python.components import proxyForInterface from twisted.python.failure import Failure -from ..itrial import IReporter, ITestCase +from ..itrial import IReporterWithDurations, ITestCase -ReporterFailure = Union[Failure, Tuple[type, Exception, TracebackType]] +ReporterFailure: TypeAlias = Union[Failure, tuple[type, Exception, TracebackType]] -@implementer(IReporter) -class DistReporter(proxyForInterface(IReporter)): # type: ignore[misc] +@implementer(IReporterWithDurations) +class DistReporter(proxyForInterface(IReporterWithDurations)): # type: ignore[misc] """ See module docstring. """ @@ -65,7 +68,7 @@ class DistReporter(proxyForInterface(IReporter)): # type: ignore[misc] self.running[test.id()].append((self.original.addUnexpectedSuccess, test, todo)) def addExpectedFailure( - self, test: ITestCase, error: ReporterFailure, todo: Optional[str] = None + self, test: ITestCase, error: ReporterFailure, todo: str | None = None ) -> None: """ Queue adding an expected failure. diff --git a/contrib/python/Twisted/py3/twisted/trial/_dist/disttrial.py b/contrib/python/Twisted/py3/twisted/trial/_dist/disttrial.py index bad82a88148..bb7355a28a9 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_dist/disttrial.py +++ b/contrib/python/Twisted/py3/twisted/trial/_dist/disttrial.py @@ -8,23 +8,14 @@ responsible for coordinating all of trial's behavior at the highest level. @since: 12.3 """ +from __future__ import annotations import os import sys +from collections.abc import Awaitable, Iterable, Sequence from functools import partial from os.path import isabs -from typing import ( - Any, - Awaitable, - Callable, - Iterable, - List, - Optional, - Sequence, - TextIO, - Union, - cast, -) +from typing import Any, Callable, TextIO, cast from unittest import TestCase, TestSuite from attrs import define, field, frozen @@ -119,8 +110,8 @@ class StartedWorkerPool: workingDirectory: FilePath[Any] testDirLock: FilesystemLock testLog: TextIO - workers: List[LocalWorker] - ampWorkers: List[LocalWorkerAMP] + workers: list[LocalWorker] + ampWorkers: list[LocalWorkerAMP] _logger = Logger() @@ -168,7 +159,7 @@ class WorkerPool: protocols: Iterable[LocalWorkerAMP], workingDirectory: FilePath[Any], logFile: TextIO, - ) -> List[LocalWorker]: + ) -> list[LocalWorker]: """ Create local worker protocol instances and return them. @@ -273,7 +264,7 @@ async def runTests( testCases: Iterable[ITestCase], result: DistReporter, driveWorker: Callable[ - [DistReporter, Sequence[ITestCase], LocalWorkerAMP], Awaitable[None] + [DistReporter, Iterable[ITestCase], LocalWorkerAMP], Awaitable[None] ], ) -> None: try: @@ -310,7 +301,7 @@ class DistTrialRunner: # on the argument annotation _reporterFactory: Callable[..., IReporter] _maxWorkers: int - _workerArguments: List[str] + _workerArguments: list[str] _exitFirst: bool = False _reactor: IDistTrialReactor = field( # mypy doesn't understand the converter @@ -349,7 +340,7 @@ class DistTrialRunner: async def _driveWorker( self, result: DistReporter, - testCases: Sequence[ITestCase], + testCases: Iterable[ITestCase], worker: LocalWorkerAMP, ) -> None: """ @@ -377,7 +368,7 @@ class DistTrialRunner: async def runAsync( self, - suite: Union[TestCase, TestSuite], + suite: TestCase | TestSuite, untilFailure: bool = False, ) -> DistReporter: """ @@ -451,16 +442,16 @@ class DistTrialRunner: # Shut down the worker pool. await startedPool.join() - def _run(self, test: Union[TestCase, TestSuite], untilFailure: bool) -> IReporter: - result: Union[Failure, DistReporter, None] = None + def _run(self, test: TestCase | TestSuite, untilFailure: bool) -> IReporter: + result: Failure | DistReporter | None = None reactorStopping: bool = False testsInProgress: Deferred[object] - def capture(r: Union[Failure, DistReporter]) -> None: + def capture(r: Failure | DistReporter) -> None: nonlocal result result = r - def maybeStopTests() -> Optional[Deferred[object]]: + def maybeStopTests() -> Deferred[object] | None: nonlocal reactorStopping reactorStopping = True if result is None: @@ -495,7 +486,7 @@ class DistTrialRunner: # object. DistReporter isn't type annotated correctly so fix it here. return cast(IReporter, result.original) - def run(self, test: Union[TestCase, TestSuite]) -> IReporter: + def run(self, test: TestCase | TestSuite) -> IReporter: """ Run a reactor and a test suite. @@ -503,7 +494,7 @@ class DistTrialRunner: """ return self._run(test, untilFailure=False) - def runUntilFailure(self, test: Union[TestCase, TestSuite]) -> IReporter: + def runUntilFailure(self, test: TestCase | TestSuite) -> IReporter: """ Run the tests with local worker processes until they fail. diff --git a/contrib/python/Twisted/py3/twisted/trial/_dist/functional.py b/contrib/python/Twisted/py3/twisted/trial/_dist/functional.py index f0258bf44c2..f799032b63f 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_dist/functional.py +++ b/contrib/python/Twisted/py3/twisted/trial/_dist/functional.py @@ -4,9 +4,11 @@ """ General functional-style helpers for disttrial. """ +from __future__ import annotations +from collections.abc import Awaitable, Iterable from functools import partial, wraps -from typing import Awaitable, Callable, Iterable, Optional, TypeVar +from typing import Callable, TypeVar from twisted.internet.defer import Deferred, succeed @@ -15,7 +17,7 @@ _B = TypeVar("_B") _C = TypeVar("_C") -def fromOptional(default: _A, optional: Optional[_A]) -> _A: +def fromOptional(default: _A, optional: _A | None) -> _A: """ Get a definite value from an optional value. diff --git a/contrib/python/Twisted/py3/twisted/trial/_dist/stream.py b/contrib/python/Twisted/py3/twisted/trial/_dist/stream.py index a53fd4ab214..5a23cc867b5 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_dist/stream.py +++ b/contrib/python/Twisted/py3/twisted/trial/_dist/stream.py @@ -2,8 +2,9 @@ Buffer byte streams. """ +from collections.abc import Iterator from itertools import count -from typing import Dict, Iterator, List, TypeVar +from typing import TypeVar from attrs import Factory, define @@ -38,7 +39,7 @@ class StreamReceiver: """ _counter: Iterator[int] = count() - _streams: Dict[int, List[bytes]] = Factory(dict) + _streams: dict[int, list[bytes]] = Factory(dict) def open(self) -> int: """ @@ -56,7 +57,7 @@ class StreamReceiver: """ self._streams[streamId].append(chunk) - def finish(self, streamId: int) -> List[bytes]: + def finish(self, streamId: int) -> list[bytes]: """ Indicate an open stream may receive no further data and return all of its current contents. diff --git a/contrib/python/Twisted/py3/twisted/trial/_dist/worker.py b/contrib/python/Twisted/py3/twisted/trial/_dist/worker.py index 77e502173ad..89a810af18a 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_dist/worker.py +++ b/contrib/python/Twisted/py3/twisted/trial/_dist/worker.py @@ -8,15 +8,17 @@ This module implements the worker classes. @since: 12.3 """ +from __future__ import annotations import os -from typing import Any, Awaitable, Callable, Dict, List, Optional, TextIO, TypeVar +from collections.abc import Awaitable +from typing import Any, Callable, Protocol, TextIO, TypeVar from unittest import TestCase from zope.interface import implementer from attrs import frozen -from typing_extensions import Protocol, TypedDict +from typing_extensions import TypedDict from twisted.internet.defer import Deferred, DeferredList from twisted.internet.error import ProcessDone @@ -171,7 +173,7 @@ class LocalWorkerAMP(AMP): self, error: WorkerException, errorClass: str, - frames: List[str], + frames: list[str], ) -> Failure: """ Helper to build a C{Failure} with some traceback. @@ -201,18 +203,16 @@ class LocalWorkerAMP(AMP): errorClass: str, errorStreamId: int, framesStreamId: int, - ) -> Dict[str, bool]: + ) -> dict[str, bool]: """ Add an error to the reporter. - @param errorStreamId: The identifier of a stream over which the text - of this error was previously completely sent to the peer. + @param errorStreamId: The identifier of a stream over which the text of + this error was previously completely sent to the peer. @param framesStreamId: The identifier of a stream over which the lines of the traceback for this error were previously completely sent to the peer. - - @param error: A message describing the error. """ error = b"".join(self._streams.finish(errorStreamId)).decode("utf-8") frames = [ @@ -233,7 +233,7 @@ class LocalWorkerAMP(AMP): failStreamId: int, failClass: str, framesStreamId: int, - ) -> Dict[str, bool]: + ) -> dict[str, bool]: """ Add a failure to the reporter. @@ -263,8 +263,8 @@ class LocalWorkerAMP(AMP): @managercommands.AddExpectedFailure.responder def addExpectedFailure( - self, testName: str, errorStreamId: int, todo: Optional[str] - ) -> Dict[str, bool]: + self, testName: str, errorStreamId: int, todo: str | None + ) -> dict[str, bool]: """ Add an expected failure to the reporter. diff --git a/contrib/python/Twisted/py3/twisted/trial/_dist/workerreporter.py b/contrib/python/Twisted/py3/twisted/trial/_dist/workerreporter.py index f266d727e36..b5e459ae95e 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_dist/workerreporter.py +++ b/contrib/python/Twisted/py3/twisted/trial/_dist/workerreporter.py @@ -8,13 +8,14 @@ Test reporter forwarding test results over trial distributed AMP commands. @since: 12.3 """ +from __future__ import annotations +from collections.abc import Sequence from types import TracebackType -from typing import Callable, List, Optional, Sequence, Type, TypeVar +from typing import Callable, Literal, TypeVar from unittest import TestCase as PyUnitTestCase from attrs import Factory, define -from typing_extensions import Literal from twisted.internet.defer import Deferred, maybeDeferred from twisted.protocols.amp import AMP, MAX_VALUE_LENGTH @@ -29,7 +30,7 @@ T = TypeVar("T") async def addError( - amp: AMP, testName: str, errorClass: str, error: str, frames: List[str] + amp: AMP, testName: str, errorClass: str, error: str, frames: list[str] ) -> None: """ Send an error to the worker manager over an AMP connection. @@ -58,7 +59,7 @@ async def addError( async def addFailure( - amp: AMP, testName: str, fail: str, failClass: str, frames: List[str] + amp: AMP, testName: str, fail: str, failClass: str, frames: list[str] ) -> None: """ Like L{addError} but for failures. @@ -123,8 +124,8 @@ class ReportingResults: interface. """ - _reporter: "WorkerReporter" - _results: List[Deferred[object]] = Factory(list) + _reporter: WorkerReporter + _results: list[Deferred[object]] = Factory(list) def __enter__(self) -> Sequence[Deferred[object]]: """ @@ -140,7 +141,7 @@ class ReportingResults: def __exit__( self, - excType: Type[BaseException], + excType: type[BaseException], excValue: BaseException, excTraceback: TracebackType, ) -> Literal[False]: @@ -173,7 +174,7 @@ class WorkerReporter(TestResult): _DEFAULT_TODO = "Test expected to fail" ampProtocol: AMP - _reporting: Optional[ReportingResults] = None + _reporting: ReportingResults | None = None def __init__(self, ampProtocol): """ @@ -202,11 +203,11 @@ class WorkerReporter(TestResult): return Failure(error[1], error[0], error[2]) return error - def _getFrames(self, failure: Failure) -> List[str]: + def _getFrames(self, failure: Failure) -> list[str]: """ Extract frames from a C{Failure} instance. """ - frames: List[str] = [] + frames: list[str] = [] for frame in failure.frames: # The code object's name, the code object's filename, and the line # number. @@ -215,7 +216,7 @@ class WorkerReporter(TestResult): def _call(self, f: Callable[[], T]) -> None: """ - Call L{f} if and only if a "result reporting" context is active. + Call C{f} if and only if a "result reporting" context is active. @param f: A function to call. Its result is accumulated into the result reporting context. It may return a L{Deferred} or a diff --git a/contrib/python/Twisted/py3/twisted/trial/_synctest.py b/contrib/python/Twisted/py3/twisted/trial/_synctest.py index 6b1f43dc89d..569100d025d 100644 --- a/contrib/python/Twisted/py3/twisted/trial/_synctest.py +++ b/contrib/python/Twisted/py3/twisted/trial/_synctest.py @@ -7,7 +7,7 @@ Things likely to be used by writers of unit tests. Maintainer: Jonathan Lange """ - +from __future__ import annotations import inspect import os @@ -16,21 +16,9 @@ import tempfile import types import unittest as pyunit import warnings +from collections.abc import Coroutine, Generator, Iterable from dis import findlinestarts as _findlinestarts -from typing import ( - Any, - Callable, - Coroutine, - Generator, - Iterable, - List, - NoReturn, - Optional, - Tuple, - Type, - TypeVar, - Union, -) +from typing import Any, Callable, NoReturn, TypeVar # Python 2.7 and higher has skip support built-in from unittest import SkipTest @@ -78,7 +66,7 @@ class Todo: """ reason: str - errors: Optional[Iterable[Type[BaseException]]] = None + errors: Iterable[type[BaseException]] | None = None def __repr__(self) -> str: return f"<Todo reason={self.reason!r} errors={self.errors!r}>" @@ -98,9 +86,7 @@ class Todo: def makeTodo( - value: Union[ - str, Tuple[Union[Type[BaseException], Iterable[Type[BaseException]]], str] - ] + value: (str | tuple[type[BaseException] | Iterable[type[BaseException]], str]) ) -> Todo: """ Return a L{Todo} object built from C{value}. @@ -119,7 +105,7 @@ def makeTodo( if isinstance(value, tuple): errors, reason = value if isinstance(errors, type): - iterableErrors: Iterable[Type[BaseException]] = [errors] + iterableErrors: Iterable[type[BaseException]] = [errors] else: iterableErrors = errors return Todo(reason=reason, errors=iterableErrors) @@ -371,7 +357,7 @@ class _Assertions(pyunit.TestCase): callbacks. """ - def fail(self, msg: Optional[object] = None) -> NoReturn: + def fail(self, msg: object | None = None) -> NoReturn: """ Absolutely fail the test. Do not pass go, do not collect $200. @@ -429,10 +415,13 @@ class _Assertions(pyunit.TestCase): return context._handle(lambda: f(*args, **kwargs)) - # unittest.TestCase.assertRaises() is defined with 4 arguments - # but we define it with 5 arguments. So we need to tell mypy - # to ignore the following assignment to failUnlessRaises - failUnlessRaises = assertRaises # type: ignore[assignment] + # The type-ignore below is present to address the evolving incompatible + # signature between assertRaises and failUnlessRaises in the stdlib. + # Depending on which version of Python you are developing with you might + # get a spurious error here *or not* which is why there's also the + # unused-ignore ignore here; in upstream unittest this method is now + # entirely removed, since Python 3.12, so there's nothing to conflict with. + failUnlessRaises = assertRaises # type:ignore[assignment,unused-ignore] def assertEqual(self, first, second, msg=None): """ @@ -444,9 +433,10 @@ class _Assertions(pyunit.TestCase): super().assertEqual(first, second, msg) return first + # We keep all these aliases for backward compatibility. + assertEquals = assertEqual failUnlessEqual = assertEqual failUnlessEquals = assertEqual - assertEquals = assertEqual def assertIs(self, first, second, msg=None): """ @@ -491,6 +481,7 @@ class _Assertions(pyunit.TestCase): raise self.failureException(msg or f"{first!r} == {second!r}") return first + # We keep all these aliases for backward compatibility. assertNotEquals = assertNotEqual failIfEquals = assertNotEqual failIfEqual = assertNotEqual @@ -545,8 +536,8 @@ class _Assertions(pyunit.TestCase): ) return first - assertNotAlmostEquals = assertNotAlmostEqual # type:ignore[assignment] - failIfAlmostEqual = assertNotAlmostEqual # type:ignore[assignment] + assertNotAlmostEquals = assertNotAlmostEqual + failIfAlmostEqual = assertNotAlmostEqual failIfAlmostEquals = assertNotAlmostEqual def assertAlmostEqual(self, first, second, places=7, msg=None, delta=None): @@ -567,8 +558,8 @@ class _Assertions(pyunit.TestCase): ) return first - assertAlmostEquals = assertAlmostEqual # type:ignore[assignment] - failUnlessAlmostEqual = assertAlmostEqual # type:ignore[assignment] + assertAlmostEquals = assertAlmostEqual + failUnlessAlmostEqual = assertAlmostEqual def assertApproximates(self, first, second, tolerance, msg=None): """ @@ -686,11 +677,11 @@ class _Assertions(pyunit.TestCase): def successResultOf( self, - deferred: Union[ - Coroutine[Deferred[T], Any, T], - Generator[Deferred[T], Any, T], - Deferred[T], - ], + deferred: ( + Coroutine[Deferred[T], Any, T] + | Generator[Deferred[T], Any, T] + | Deferred[T] + ), ) -> T: """ Return the current success result of C{deferred} or raise @@ -716,7 +707,7 @@ class _Assertions(pyunit.TestCase): @return: The result of C{deferred}. """ deferred = ensureDeferred(deferred) - results: List[Union[T, failure.Failure]] = [] + results: list[T | failure.Failure] = [] deferred.addBoth(results.append) if not results: @@ -992,7 +983,7 @@ class SynchronousTestCase(_Assertions): return self._testMethodName return desc - def getSkip(self) -> Tuple[bool, Optional[str]]: + def getSkip(self) -> tuple[bool, str | None]: """ Return the skip reason set on this test, if any is set. Checks on the instance first, then the class, then the module, then packages. As @@ -1260,7 +1251,7 @@ class SynchronousTestCase(_Assertions): } if message is not None: expectedWarning = expectedWarning + ": " + message - self.assert_( + self.assertTrue( observedWarning.startswith(expectedWarning), f"Expected {observedWarning!r} to start with {expectedWarning!r}", ) diff --git a/contrib/python/Twisted/py3/twisted/trial/itrial.py b/contrib/python/Twisted/py3/twisted/trial/itrial.py index 840c6e61eeb..8b74fd758b5 100644 --- a/contrib/python/Twisted/py3/twisted/trial/itrial.py +++ b/contrib/python/Twisted/py3/twisted/trial/itrial.py @@ -3,10 +3,9 @@ """ Interfaces for Trial. - -Maintainer: Jonathan Lange """ +from unittest import TestCase import zope.interface as zi @@ -155,3 +154,21 @@ class IReporter(zi.Interface): L{IReporter} object, you should assume that the L{IReporter} object is no longer usable. """ + + +class IReporterWithDurations(IReporter): + """ + The L{IReporter} interface with the 'durations' additions added to the + standard library in 3.12. + """ + + collectedDurations: list[tuple[str, float]] = zi.Attribute( + """ + The collected durations of the tests reported. + """ + ) + + def addDuration(test: TestCase, elapsed: float) -> None: + """ + Collect a duration for C{test} into L{IReporterWithDurations.collectedDurations}. + """ diff --git a/contrib/python/Twisted/py3/twisted/trial/reporter.py b/contrib/python/Twisted/py3/twisted/trial/reporter.py index 2034a83e261..fda7a60c9c9 100644 --- a/contrib/python/Twisted/py3/twisted/trial/reporter.py +++ b/contrib/python/Twisted/py3/twisted/trial/reporter.py @@ -16,7 +16,7 @@ import unittest as pyunit import warnings from collections import OrderedDict from types import TracebackType -from typing import TYPE_CHECKING, List, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Union from zope.interface import implementer @@ -36,12 +36,12 @@ try: except ImportError: TestProtocolClient = None -ExcInfo: TypeAlias = Tuple[Type[BaseException], BaseException, TracebackType] -XUnitFailure = Union[ExcInfo, Tuple[None, None, None]] +ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType] +XUnitFailure = Union[ExcInfo, tuple[None, None, None]] TrialFailure = Union[XUnitFailure, Failure] -def _makeTodo(value: str) -> "Todo": +def _makeTodo(value: str) -> Todo: """ Return a L{Todo} object built from C{value}. @@ -97,13 +97,16 @@ class TestResult(pyunit.TestResult): # Used when no todo provided to addExpectedFailure or addUnexpectedSuccess. _DEFAULT_TODO = "Test expected to fail" - skips: List[Tuple[itrial.ITestCase, str]] - expectedFailures: List[Tuple[itrial.ITestCase, str, "Todo"]] # type: ignore[assignment] - unexpectedSuccesses: List[Tuple[itrial.ITestCase, str]] # type: ignore[assignment] + errors: list[ + tuple[itrial.ITestCase | pyunit.TestCase, str | Failure] + ] # type:ignore[assignment] + skips: list[tuple[itrial.ITestCase, str]] + expectedFailures: list[tuple[itrial.ITestCase, str | Failure, Todo]] # type: ignore[assignment] + unexpectedSuccesses: list[tuple[itrial.ITestCase, str]] # type: ignore[assignment] successes: int - _testStarted: Optional[int] + _testStarted: int | None # The duration of the test. It is None until the test completes. - _lastTime: Optional[int] + _lastTime: int | None # Make pytest not think this is test class __test__ = False @@ -171,7 +174,13 @@ class TestResult(pyunit.TestResult): """ self.failures.append((test, self._getFailure(fail))) - def addError(self, test, error): + def addError( + self, + test: pyunit.TestCase, + error: Failure + | tuple[type[BaseException], BaseException, TracebackType] + | tuple[None, None, None], + ) -> None: """ Report an error that occurred while running the given test. @@ -253,9 +262,9 @@ class TestResult(pyunit.TestResult): """ -@implementer(itrial.IReporter) +@implementer(itrial.IReporterWithDurations) class TestResultDecorator( - proxyForInterface(itrial.IReporter, "_originalReporter") # type: ignore[misc] + proxyForInterface(itrial.IReporterWithDurations, "_originalReporter") # type: ignore[misc] ): """ Base class for TestResult decorators. diff --git a/contrib/python/Twisted/py3/twisted/trial/runner.py b/contrib/python/Twisted/py3/twisted/trial/runner.py index ffc554e25c8..83b58e42fe4 100644 --- a/contrib/python/Twisted/py3/twisted/trial/runner.py +++ b/contrib/python/Twisted/py3/twisted/trial/runner.py @@ -7,7 +7,7 @@ A miscellany of code used to run Trial tests. Maintainer: Jonathan Lange """ - +from __future__ import annotations __all__ = [ "TestSuite", @@ -35,14 +35,15 @@ import sys import types import unittest as pyunit import warnings +from collections.abc import Generator from contextlib import contextmanager from importlib.machinery import SourceFileLoader -from typing import Callable, Generator, List, Optional, TextIO, Type, Union +from typing import Callable, Protocol, TextIO, Union from zope.interface import implementer from attrs import define -from typing_extensions import ParamSpec, Protocol, TypeAlias, TypeGuard +from typing_extensions import ParamSpec, TypeAlias, TypeGuard from twisted.internet import defer from twisted.python import failure, filepath, log, modules, reflect @@ -275,7 +276,7 @@ _Loadable: TypeAlias = Union[ modules.PythonAttribute, modules.PythonModule, pyunit.TestCase, - Type[pyunit.TestCase], + type[pyunit.TestCase], ] @@ -301,7 +302,7 @@ def name(thing: _Loadable) -> str: raise TypeError(f"Cannot name {thing!r}") -def isTestCase(obj: type) -> TypeGuard[Type[pyunit.TestCase]]: +def isTestCase(obj: type) -> TypeGuard[type[pyunit.TestCase]]: """ @return: C{True} if C{obj} is a class that contains test cases, C{False} otherwise. Used to find all the tests in a module. @@ -413,7 +414,7 @@ class TestLoader: methodPrefix = "test" modulePrefix = "test_" - suiteFactory: Type[TestSuite] = TestSuite + suiteFactory: type[TestSuite] = TestSuite sorter: Callable[[_Loadable], object] = name def sort(self, xs): @@ -714,7 +715,7 @@ class TestLoader: loadTestsFromName = loadByName - def loadByNames(self, names: List[str], recurse: bool = False) -> TestSuite: + def loadByNames(self, names: list[str], recurse: bool = False) -> TestSuite: """ Load some tests by a list of names. @@ -789,7 +790,7 @@ def _qualNameWalker(qualName): @contextmanager -def _testDirectory(workingDirectory: str) -> Generator[None, None, None]: +def _testDirectory(workingDirectory: str) -> Generator[None]: """ A context manager which obtains a lock on a trial working directory and enters (L{os.chdir}) it and then reverses these things. @@ -809,7 +810,7 @@ def _testDirectory(workingDirectory: str) -> Generator[None, None, None]: @contextmanager -def _logFile(logfile: str) -> Generator[None, None, None]: +def _logFile(logfile: str) -> Generator[None]: """ A context manager which adds a log observer and then removes it. @@ -834,11 +835,11 @@ def _logFile(logfile: str) -> Generator[None, None, None]: class _Runner(Protocol): stream: TextIO - def run(self, test: Union[pyunit.TestCase, pyunit.TestSuite]) -> itrial.IReporter: + def run(self, test: pyunit.TestCase | pyunit.TestSuite) -> itrial.IReporter: ... def runUntilFailure( - self, test: Union[pyunit.TestCase, pyunit.TestSuite] + self, test: pyunit.TestCase | pyunit.TestSuite ) -> itrial.IReporter: ... @@ -889,7 +890,7 @@ class TrialRunner: DRY_RUN = "dry-run" reporterFactory: Callable[[TextIO, str, bool, log.LogPublisher], itrial.IReporter] - mode: Optional[str] = None + mode: str | None = None logfile: str = "test.log" stream: TextIO = sys.stdout profile: bool = False @@ -898,7 +899,7 @@ class TrialRunner: uncleanWarnings: bool = False workingDirectory: str = "_trial_temp" _forceGarbageCollection: bool = False - debugger: Optional[_Debugger] = None + debugger: _Debugger | None = None _exitFirst: bool = False _log: log.LogPublisher = log # type: ignore[assignment] @@ -921,7 +922,7 @@ class TrialRunner: def rterrors(self) -> bool: return self._realTimeErrors - def run(self, test: Union[pyunit.TestCase, pyunit.TestSuite]) -> itrial.IReporter: + def run(self, test: pyunit.TestCase | pyunit.TestSuite) -> itrial.IReporter: """ Run the test or suite and return a result object. """ @@ -934,7 +935,7 @@ class TrialRunner: def _runWithoutDecoration( self, - test: Union[pyunit.TestCase, pyunit.TestSuite], + test: pyunit.TestCase | pyunit.TestSuite, forceGarbageCollection: bool = False, ) -> itrial.IReporter: """ @@ -964,7 +965,7 @@ class TrialRunner: return result def runUntilFailure( - self, test: Union[pyunit.TestCase, pyunit.TestSuite] + self, test: pyunit.TestCase | pyunit.TestSuite ) -> itrial.IReporter: """ Repeatedly run C{test} until it fails. diff --git a/contrib/python/Twisted/py3/twisted/web/_abnf.py b/contrib/python/Twisted/py3/twisted/web/_abnf.py index 8029943beab..50822417bca 100644 --- a/contrib/python/Twisted/py3/twisted/web/_abnf.py +++ b/contrib/python/Twisted/py3/twisted/web/_abnf.py @@ -5,6 +5,7 @@ """ Tools for pedantically processing the HTTP protocol. """ +from __future__ import annotations def _istoken(b: bytes) -> bool: @@ -42,7 +43,7 @@ def _decint(data: bytes) -> int: return int(data) -def _ishexdigits(b: bytes) -> bool: +def _ishexdigits(b: bytes | bytearray) -> bool: """ Is the string case-insensitively hexidecimal? @@ -55,7 +56,7 @@ def _ishexdigits(b: bytes) -> bool: return b != b"" -def _hexint(b: bytes) -> int: +def _hexint(b: bytes | bytearray) -> int: """ Decode a hexadecimal integer. diff --git a/contrib/python/Twisted/py3/twisted/web/_element.py b/contrib/python/Twisted/py3/twisted/web/_element.py index 81d724071e4..369a27df3ab 100644 --- a/contrib/python/Twisted/py3/twisted/web/_element.py +++ b/contrib/python/Twisted/py3/twisted/web/_element.py @@ -2,17 +2,10 @@ # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. +from __future__ import annotations + import itertools -from typing import ( - TYPE_CHECKING, - Any, - Callable, - List, - Optional, - TypeVar, - Union, - overload, -) +from typing import TYPE_CHECKING, Any, Callable, TypeVar, overload from zope.interface import implementer @@ -70,7 +63,7 @@ class Expose: @return: The first of C{funcObjs}. """ for fObj in itertools.chain([f], funcObjs): - exposedThrough: List[Expose] = getattr(fObj, "exposedThrough", []) + exposedThrough: list[Expose] = getattr(fObj, "exposedThrough", []) exposedThrough.append(self) setattr(fObj, "exposedThrough", exposedThrough) return f @@ -84,7 +77,7 @@ class Expose: @overload def get( self, instance: object, methodName: str, default: T - ) -> Union[Callable[..., Any], T]: + ) -> Callable[..., Any] | T: ... def get( @@ -166,15 +159,15 @@ class Element: return from C{render}. """ - loader: Optional[ITemplateLoader] = None + loader: ITemplateLoader | None = None - def __init__(self, loader: Optional[ITemplateLoader] = None): + def __init__(self, loader: ITemplateLoader | None = None): if loader is not None: self.loader = loader def lookupRenderMethod( self, name: str - ) -> Callable[[Optional[IRequest], "Tag"], "Flattenable"]: + ) -> Callable[[IRequest | None, Tag], Flattenable]: """ Look up and return the named render method. """ @@ -183,7 +176,7 @@ class Element: raise MissingRenderMethod(self, name) return method - def render(self, request: Optional[IRequest]) -> "Flattenable": + def render(self, request: IRequest | None) -> Flattenable: """ Implement L{IRenderable} to allow one L{Element} to be embedded in another's template or rendering output. diff --git a/contrib/python/Twisted/py3/twisted/web/_flatten.py b/contrib/python/Twisted/py3/twisted/web/_flatten.py index 12691b87fa8..5ba9fc42c33 100644 --- a/contrib/python/Twisted/py3/twisted/web/_flatten.py +++ b/contrib/python/Twisted/py3/twisted/web/_flatten.py @@ -8,25 +8,13 @@ complex or arbitrarily nested, as strings. """ from __future__ import annotations +from collections.abc import Coroutine, Generator, Mapping, Sequence from inspect import iscoroutine from io import BytesIO from sys import exc_info from traceback import extract_tb -from types import GeneratorType -from typing import ( - Any, - Callable, - Coroutine, - Generator, - List, - Mapping, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - cast, -) +from types import FrameType, GeneratorType +from typing import Any, Callable, TypeVar, Union, cast from twisted.internet.defer import Deferred, ensureDeferred from twisted.python.compat import nativeString @@ -51,8 +39,8 @@ Flattenable = Union[ CDATA, Comment, Tag, - Tuple[FlattenableRecursive, ...], - List[FlattenableRecursive], + tuple[FlattenableRecursive, ...], + list[FlattenableRecursive], Generator[FlattenableRecursive, None, None], CharRef, Deferred[FlattenableRecursive], @@ -68,7 +56,7 @@ Type alias containing all types that can be flattened by L{flatten()}. BUFFER_SIZE = 2**16 -def escapeForContent(data: Union[bytes, str]) -> bytes: +def escapeForContent(data: bytes | str) -> bytes: """ Escape some character or UTF-8 byte data for inclusion in an HTML or XML document, by replacing metacharacters (C{&<>}) with their entity @@ -87,7 +75,7 @@ def escapeForContent(data: Union[bytes, str]) -> bytes: return data -def attributeEscapingDoneOutside(data: Union[bytes, str]) -> bytes: +def attributeEscapingDoneOutside(data: bytes | str) -> bytes: """ Escape some character or UTF-8 byte data for inclusion in the top level of an attribute. L{attributeEscapingDoneOutside} actually passes the data @@ -107,7 +95,7 @@ def attributeEscapingDoneOutside(data: Union[bytes, str]) -> bytes: def writeWithAttributeEscaping( - write: Callable[[bytes], object] + write: Callable[[bytes], object], ) -> Callable[[bytes], None]: """ Decorate a C{write} callable so that all output written is properly quoted @@ -153,7 +141,7 @@ def writeWithAttributeEscaping( return _write -def escapedCDATA(data: Union[bytes, str]) -> bytes: +def escapedCDATA(data: bytes | str) -> bytes: """ Escape CDATA for inclusion in a document. @@ -167,7 +155,7 @@ def escapedCDATA(data: Union[bytes, str]) -> bytes: return data.replace(b"]]>", b"]]]]><![CDATA[>") -def escapedComment(data: Union[bytes, str]) -> bytes: +def escapedComment(data: bytes | str) -> bytes: """ Within comments the sequence C{-->} can be mistaken as the end of the comment. To ensure consistent parsing and valid output the sequence is replaced with C{-->}. @@ -189,8 +177,8 @@ def escapedComment(data: Union[bytes, str]) -> bytes: def _getSlotValue( name: str, - slotData: Sequence[Optional[Mapping[str, Flattenable]]], - default: Optional[Flattenable] = None, + slotData: Sequence[Mapping[str, Flattenable] | None], + default: Flattenable | None = None, ) -> Flattenable: """ Find the value of the named slot in the given stack of slot data. @@ -224,16 +212,16 @@ def _fork(d: Deferred[T]) -> Deferred[T]: def _flattenElement( - request: Optional[IRequest], + request: IRequest | None, root: Flattenable, write: Callable[[bytes], object], - slotData: List[Optional[Mapping[str, Flattenable]]], - renderFactory: Optional[IRenderable], - dataEscaper: Callable[[Union[bytes, str]], bytes], + slotData: list[Mapping[str, Flattenable] | None], + renderFactory: IRenderable | None, + dataEscaper: Callable[[bytes | str], bytes], # This is annotated as Generator[T, None, None] instead of Iterator[T] # because mypy does not consider an Iterator to be an instance of # GeneratorType. -) -> Generator[Union[Generator[Any, Any, Any], Deferred[Flattenable]], None, None]: +) -> Generator[Generator[Any, Any, Any] | Deferred[Flattenable], None, None]: """ Make C{root} slightly more flat by yielding all its immediate contents as strings, deferreds or generators that are recursive calls to itself. @@ -272,10 +260,10 @@ def _flattenElement( def keepGoing( newRoot: Flattenable, - dataEscaper: Callable[[Union[bytes, str]], bytes] = dataEscaper, - renderFactory: Optional[IRenderable] = renderFactory, + dataEscaper: Callable[[bytes | str], bytes] = dataEscaper, + renderFactory: IRenderable | None = renderFactory, write: Callable[[bytes], object] = write, - ) -> Generator[Union[Flattenable, Deferred[Flattenable]], None, None]: + ) -> Generator[Flattenable | Deferred[Flattenable], None, None]: return _flattenElement( request, newRoot, write, slotData, renderFactory, dataEscaper ) @@ -369,7 +357,7 @@ def _flattenElement( async def _flattenTree( - request: Optional[IRequest], root: Flattenable, write: Callable[[bytes], object] + request: IRequest | None, root: Flattenable, write: Callable[[bytes], object] ) -> None: """ Make C{root} into an iterable of L{bytes} and L{Deferred} by doing a depth @@ -412,7 +400,7 @@ async def _flattenTree( del buf[:] bufSize = 0 - stack: List[Generator[Any, Any, Any]] = [ + stack: list[Generator[Any, Any, Any]] = [ _flattenElement(request, root, bufferedWrite, [], None, escapeForContent) ] @@ -429,8 +417,12 @@ async def _flattenTree( except Exception as e: roots = [] for generator in stack: - if generator.gi_frame is not None: - roots.append(generator.gi_frame.f_locals["root"]) + generatorFrame: FrameType = ( + # FIXME: typeshed bug? + generator.gi_frame # type:ignore[attr-defined] + ) + if generatorFrame is not None: + roots.append(generatorFrame.f_locals["root"]) stack.pop() raise FlattenerError(e, roots, extract_tb(exc_info()[2])) else: @@ -441,7 +433,7 @@ async def _flattenTree( def flatten( - request: Optional[IRequest], root: Flattenable, write: Callable[[bytes], object] + request: IRequest | None, root: Flattenable, write: Callable[[bytes], object] ) -> Deferred[None]: """ Incrementally write out a string representation of C{root} using C{write}. @@ -468,7 +460,7 @@ def flatten( return ensureDeferred(_flattenTree(request, root, write)) -def flattenString(request: Optional[IRequest], root: Flattenable) -> Deferred[bytes]: +def flattenString(request: IRequest | None, root: Flattenable) -> Deferred[bytes]: """ Collate a string representation of C{root} into a single string. diff --git a/contrib/python/Twisted/py3/twisted/web/_http2.py b/contrib/python/Twisted/py3/twisted/web/_http2.py index 301e9ea196b..c42f2da5b2d 100644 --- a/contrib/python/Twisted/py3/twisted/web/_http2.py +++ b/contrib/python/Twisted/py3/twisted/web/_http2.py @@ -16,7 +16,6 @@ it has stabilised, it'll be made public. import io from collections import deque -from typing import List from zope.interface import implementer @@ -45,7 +44,7 @@ from twisted.python.failure import Failure from twisted.web.error import ExcessiveBufferingError # This API is currently considered private. -__all__: List[str] = [] +__all__: list[str] = [] _END_STREAM_SENTINEL = object() @@ -63,7 +62,7 @@ class H2Connection(Protocol, TimeoutMixin): interface between the two objects that allows them to work hand-in-hand here. @ivar conn: The HTTP/2 connection state machine. - @type conn: L{h2.connection.H2Connection} + @type conn: C{h2.connection.H2Connection} @ivar streams: A mapping of stream IDs to L{H2Stream} objects, used to call specific methods on streams when events occur. @@ -437,7 +436,7 @@ class H2Connection(Protocol, TimeoutMixin): @param event: The Hyper-h2 event that encodes information about the received request. - @type event: L{h2.events.RequestReceived} + @type event: C{h2.events.RequestReceived} """ stream = H2Stream( event.stream_id, @@ -469,7 +468,7 @@ class H2Connection(Protocol, TimeoutMixin): @param event: The Hyper-h2 event that encodes information about the received data. - @type event: L{h2.events.DataReceived} + @type event: C{h2.events.DataReceived} """ stream = self.streams[event.stream_id] stream.receiveDataChunk(event.data, event.flow_controlled_length) @@ -481,7 +480,7 @@ class H2Connection(Protocol, TimeoutMixin): @param event: The Hyper-h2 event that encodes information about the completed stream. - @type event: L{h2.events.StreamEnded} + @type event: C{h2.events.StreamEnded} """ stream = self.streams[event.stream_id] stream.requestComplete() @@ -492,7 +491,7 @@ class H2Connection(Protocol, TimeoutMixin): @param event: The Hyper-h2 event that encodes information about the reset stream. - @type event: L{h2.events.StreamReset} + @type event: C{h2.events.StreamReset} """ stream = self.streams[event.stream_id] stream.connectionLost( @@ -506,7 +505,7 @@ class H2Connection(Protocol, TimeoutMixin): @param event: The Hyper-h2 event that encodes information about the stream reprioritization. - @type event: L{h2.events.PriorityUpdated} + @type event: C{h2.events.PriorityUpdated} """ try: self.priority.reprioritize( @@ -659,7 +658,7 @@ class H2Connection(Protocol, TimeoutMixin): @param event: The Hyper-h2 event that encodes information about the flow control window change. - @type event: L{h2.events.WindowUpdated} + @type event: C{h2.events.WindowUpdated} """ streamID = event.stream_id diff --git a/contrib/python/Twisted/py3/twisted/web/_newclient.py b/contrib/python/Twisted/py3/twisted/web/_newclient.py index 9ae0a0c2ecd..e9fa5673ee7 100644 --- a/contrib/python/Twisted/py3/twisted/web/_newclient.py +++ b/contrib/python/Twisted/py3/twisted/web/_newclient.py @@ -29,7 +29,7 @@ Various other classes in this module support this usage: from __future__ import annotations import re -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from zope.interface import implementer @@ -606,7 +606,7 @@ def _ensureValidURI(uri): raise ValueError(f"Invalid URI {uri!r}") -def _contentLength(connHeaders: Headers) -> Optional[int]: +def _contentLength(connHeaders: Headers) -> int | None: """ Parse the I{Content-Length} connection header. diff --git a/contrib/python/Twisted/py3/twisted/web/_stan.py b/contrib/python/Twisted/py3/twisted/web/_stan.py index b165bdb6fda..4075e1dd349 100644 --- a/contrib/python/Twisted/py3/twisted/web/_stan.py +++ b/contrib/python/Twisted/py3/twisted/web/_stan.py @@ -21,9 +21,10 @@ cumbersome. output. """ +from __future__ import annotations from inspect import iscoroutine, isgenerator -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING from warnings import warn import attr @@ -45,12 +46,12 @@ class slot: The key which must be used in L{Tag.fillSlots} to fill it. """ - children: List["Tag"] = attr.ib(init=False, factory=list) + children: list[Tag] = attr.ib(init=False, factory=list) """ The L{Tag} objects included in this L{slot}'s template. """ - default: Optional["Flattenable"] = None + default: Flattenable | None = None """ The default contents of this slot, if it is left unfilled. @@ -58,14 +59,14 @@ class slot: L{None} actually being used. """ - filename: Optional[str] = None + filename: str | None = None """ The name of the XML file from which this tag was parsed. If it was not parsed from an XML file, L{None}. """ - lineNumber: Optional[int] = None + lineNumber: int | None = None """ The line number on which this tag was encountered in the XML file from which it was parsed. @@ -73,7 +74,7 @@ class slot: If it was not parsed from an XML file, L{None}. """ - columnNumber: Optional[int] = None + columnNumber: int | None = None """ The column number at which this tag was encountered in the XML file from which it was parsed. @@ -92,20 +93,20 @@ class Tag: using pure python syntax. """ - tagName: Union[bytes, str] + tagName: bytes | str """ The name of the represented element. For a tag like C{<div></div>}, this would be C{"div"}. """ - attributes: Dict[Union[bytes, str], "Flattenable"] = attr.ib(factory=dict) + attributes: dict[bytes | str, Flattenable] = attr.ib(factory=dict) """The attributes of the element.""" - children: List["Flattenable"] = attr.ib(factory=list) + children: list[Flattenable] = attr.ib(factory=list) """The contents of this C{Tag}.""" - render: Optional[str] = None + render: str | None = None """ The name of the render method to use for this L{Tag}. @@ -115,14 +116,14 @@ class Tag: to determine which method to call. """ - filename: Optional[str] = None + filename: str | None = None """ The name of the XML file from which this tag was parsed. If it was not parsed from an XML file, L{None}. """ - lineNumber: Optional[int] = None + lineNumber: int | None = None """ The line number on which this tag was encountered in the XML file from which it was parsed. @@ -130,7 +131,7 @@ class Tag: If it was not parsed from an XML file, L{None}. """ - columnNumber: Optional[int] = None + columnNumber: int | None = None """ The column number at which this tag was encountered in the XML file from which it was parsed. @@ -138,7 +139,7 @@ class Tag: If it was not parsed from an XML file, L{None}. """ - slotData: Optional[Dict[str, "Flattenable"]] = attr.ib(init=False, default=None) + slotData: dict[str, Flattenable] | None = attr.ib(init=False, default=None) """ The data which can fill slots. @@ -147,7 +148,7 @@ class Tag: the child of a L{Tag}: strings, lists, L{Tag}s, generators, etc. """ - def fillSlots(self, **slots: "Flattenable") -> "Tag": + def fillSlots(self, **slots: Flattenable) -> Tag: """ Remember the slots provided at this position in the DOM. @@ -162,7 +163,7 @@ class Tag: self.slotData.update(slots) return self - def __call__(self, *children: "Flattenable", **kw: "Flattenable") -> "Tag": + def __call__(self, *children: Flattenable, **kw: Flattenable) -> Tag: """ Add children and change attributes on this tag. @@ -203,7 +204,7 @@ class Tag: self.attributes[k] = v return self - def _clone(self, obj: "Flattenable", deep: bool) -> "Flattenable": + def _clone(self, obj: Flattenable, deep: bool) -> Flattenable: """ Clone a C{Flattenable} object; used by L{Tag.clone}. @@ -242,7 +243,7 @@ class Tag: else: return obj - def clone(self, deep: bool = True) -> "Tag": + def clone(self, deep: bool = True) -> Tag: """ Return a clone of this tag. If deep is True, clone all of this tag's children. Otherwise, just shallow copy the children list without copying @@ -275,7 +276,7 @@ class Tag: return newtag - def clear(self) -> "Tag": + def clear(self) -> Tag: """ Clear any existing children from this tag. """ diff --git a/contrib/python/Twisted/py3/twisted/web/_template_util.py b/contrib/python/Twisted/py3/twisted/web/_template_util.py index 501941ad121..89b2289e413 100644 --- a/contrib/python/Twisted/py3/twisted/web/_template_util.py +++ b/contrib/python/Twisted/py3/twisted/web/_template_util.py @@ -5,24 +5,15 @@ twisted.web.util and twisted.web.template merged to avoid cyclic deps """ +from __future__ import annotations + import io import linecache import warnings from collections import OrderedDict +from collections.abc import Mapping from html import escape -from typing import ( - IO, - Any, - AnyStr, - Callable, - Dict, - List, - Mapping, - Optional, - Tuple, - Union, - cast, -) +from typing import IO, Any, AnyStr, Callable, Union, cast from xml.sax import handler, make_parser from xml.sax.xmlreader import AttributesNSImpl, Locator @@ -321,18 +312,18 @@ class _NSContext: A mapping from XML namespaces onto their prefixes in the document. """ - def __init__(self, parent: Optional["_NSContext"] = None): + def __init__(self, parent: _NSContext | None = None): """ Pull out the parent's namespaces, if there's no parent then default to XML. """ self.parent = parent if parent is not None: - self.nss: Dict[Optional[str], Optional[str]] = OrderedDict(parent.nss) + self.nss: dict[str | None, str | None] = OrderedDict(parent.nss) else: self.nss = {"http://www.w3.org/XML/1998/namespace": "xml"} - def get(self, k: Optional[str], d: Optional[str] = None) -> Optional[str]: + def get(self, k: str | None, d: str | None = None) -> str | None: """ Get a prefix for a namespace. @@ -340,13 +331,13 @@ class _NSContext: """ return self.nss.get(k, d) - def __setitem__(self, k: Optional[str], v: Optional[str]) -> None: + def __setitem__(self, k: str | None, v: str | None) -> None: """ Proxy through to setting the prefix for the namespace. """ self.nss.__setitem__(k, v) - def __getitem__(self, k: Optional[str]) -> Optional[str]: + def __getitem__(self, k: str | None) -> str | None: """ Proxy through to getting the prefix for the namespace. """ @@ -362,7 +353,7 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): Document Object Model. """ - def __init__(self, sourceFilename: Optional[str]): + def __init__(self, sourceFilename: str | None): """ @param sourceFilename: the filename the XML was loaded out of. """ @@ -383,10 +374,10 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): # Depending on our active context, the element type can be Tag, slot # or str. Since mypy doesn't understand that context, it would be # a pain to not use Any here. - self.document: List[Any] = [] + self.document: list[Any] = [] self.current = self.document - self.stack: List[Any] = [] - self.xmlnsAttrs: List[Tuple[str, str]] = [] + self.stack: list[Any] = [] + self.xmlnsAttrs: list[tuple[str, str]] = [] def endDocument(self) -> None: """ @@ -398,7 +389,7 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): Processing instructions are ignored. """ - def startPrefixMapping(self, prefix: Optional[str], uri: str) -> None: + def startPrefixMapping(self, prefix: str | None, uri: str) -> None: """ Set up the prefix mapping, which maps fully qualified namespace URIs onto namespace prefixes. @@ -420,7 +411,7 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): else: self.xmlnsAttrs.append(("xmlns:%s" % prefix, uri)) - def endPrefixMapping(self, prefix: Optional[str]) -> None: + def endPrefixMapping(self, prefix: str | None) -> None: """ "Pops the stack" on the prefix mapping. @@ -432,8 +423,8 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): def startElementNS( self, - namespaceAndName: Tuple[str, str], - qname: Optional[str], + namespaceAndName: tuple[str | None, str], + qname: str | None, attrs: AttributesNSImpl, ) -> None: """ @@ -455,7 +446,7 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): if name == "transparent": name = "" elif name == "slot": - default: Optional[str] + default: str | None try: # Try to get the default value for the slot default = attrs[(None, "default")] @@ -560,7 +551,7 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): return self.current.append(ch) - def endElementNS(self, name: Tuple[str, str], qname: Optional[str]) -> None: + def endElementNS(self, name: tuple[str | None, str], qname: str | None) -> None: """ A namespace tag is closed. Pop the stack, if there's anything left in it, otherwise return to the document's namespace. @@ -604,7 +595,7 @@ class _ToStan(handler.ContentHandler, handler.EntityResolver): self.current.append(Comment(content)) -def _flatsaxParse(fl: Union[IO[AnyStr], str]) -> List["Flattenable"]: +def _flatsaxParse(fl: IO[AnyStr] | str) -> list[Flattenable]: """ Perform a SAX parse of an XML document with the _ToStan class. @@ -634,7 +625,7 @@ class XMLString: An L{ITemplateLoader} that loads and parses XML from a string. """ - def __init__(self, s: Union[str, bytes]): + def __init__(self, s: str | bytes): """ Run the parser on a L{io.StringIO} copy of the string. @@ -644,10 +635,10 @@ class XMLString: if not isinstance(s, str): s = s.decode("utf8") - self._loadedTemplate: List["Flattenable"] = _flatsaxParse(io.StringIO(s)) + self._loadedTemplate: list[Flattenable] = _flatsaxParse(io.StringIO(s)) """The loaded document.""" - def load(self) -> List["Flattenable"]: + def load(self) -> list[Flattenable]: """ Return the document. @@ -810,15 +801,15 @@ class TagLoader: An L{ITemplateLoader} that loads an existing flattenable object. """ - def __init__(self, tag: "Flattenable"): + def __init__(self, tag: Flattenable): """ @param tag: The object which will be loaded. """ - self.tag: "Flattenable" = tag + self.tag: Flattenable = tag """The object which will be loaded.""" - def load(self) -> List["Flattenable"]: + def load(self) -> list[Flattenable]: return [self.tag] @@ -842,13 +833,13 @@ class XMLFile: stacklevel=2, ) - self._loadedTemplate: Optional[List["Flattenable"]] = None + self._loadedTemplate: list[Flattenable] | None = None """The loaded document, or L{None}, if not loaded.""" self._path: FilePath[Any] = path """The file that is being loaded from.""" - def _loadDoc(self) -> List["Flattenable"]: + def _loadDoc(self) -> list[Flattenable]: """ Read and parse the XML. @@ -863,7 +854,7 @@ class XMLFile: def __repr__(self) -> str: return f"<XMLFile of {self._path!r}>" - def load(self) -> List["Flattenable"]: + def load(self) -> list[Flattenable]: """ Return the document, first loading it if necessary. @@ -1032,8 +1023,8 @@ tags = _TagFactory() def renderElement( request: IRequest, element: IRenderable, - doctype: Optional[bytes] = b"<!DOCTYPE html>", - _failElement: Optional[Callable[[Failure], "Element"]] = None, + doctype: bytes | None = b"<!DOCTYPE html>", + _failElement: Callable[[Failure], Element] | None = None, ) -> object: """ Render an element or other L{IRenderable}. @@ -1058,7 +1049,7 @@ def renderElement( d = flatten(request, element, request.write) - def eb(failure: Failure) -> Optional[Deferred[None]]: + def eb(failure: Failure) -> Deferred[None] | None: _moduleLog.failure( "An error occurred while rendering the response.", failure=failure ) diff --git a/contrib/python/Twisted/py3/twisted/web/_websocket_impl.py b/contrib/python/Twisted/py3/twisted/web/_websocket_impl.py index 17ca57f7bf7..692fdd1efe8 100644 --- a/contrib/python/Twisted/py3/twisted/web/_websocket_impl.py +++ b/contrib/python/Twisted/py3/twisted/web/_websocket_impl.py @@ -169,7 +169,7 @@ class WebSocketProtocol(typing.Protocol): A text message was received from the peer. """ - def bytesMessageReceived(self, data: bytes) -> None: + def bytesMessageReceived(self, data: bytes | bytearray) -> None: """ A bytes message was received from the peer. """ @@ -317,6 +317,8 @@ class _WebSocketWireProtocol(Generic[_WSP]): _bootstrap: _Bootstrap _wsp: _WSP + factory: ProtocolFactory[_WebSocketWireProtocol[_WSP]] = field(init=False) + # Public attribute. transport: ITransport = field(init=False) @@ -354,7 +356,8 @@ class _WebSocketWireProtocol(Generic[_WSP]): def sendBytesMessage(self, data: bytes) -> None: t = self.transport assert t is not None - t.write(self._wsconn.send(BytesMessage(data))) + message = BytesMessage(data) + t.write(self._wsconn.send(message)) def ping(self, payload: bytes = b"") -> None: t = self.transport diff --git a/contrib/python/Twisted/py3/twisted/web/client.py b/contrib/python/Twisted/py3/twisted/web/client.py index d3cd11fb845..d3f25eb84ab 100644 --- a/contrib/python/Twisted/py3/twisted/web/client.py +++ b/contrib/python/Twisted/py3/twisted/web/client.py @@ -12,15 +12,17 @@ import collections import os import warnings import zlib +from collections.abc import Hashable, Iterable from dataclasses import dataclass from functools import wraps from http.cookiejar import CookieJar -from typing import TYPE_CHECKING, Hashable, Iterable, Optional +from typing import TYPE_CHECKING, Callable, TypeVar from urllib.parse import urldefrag, urljoin, urlunparse as _urlunparse from zope.interface import implementer from incremental import Version +from typing_extensions import ParamSpec from twisted.internet import defer, protocol, task from twisted.internet.abstract import isIPv6Address @@ -28,6 +30,7 @@ from twisted.internet.defer import Deferred from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS from twisted.internet.interfaces import ( IAddress, + IOpenSSLClientConnectionCreator, IOpenSSLContextFactory, IProtocol, IReactorTime, @@ -220,7 +223,10 @@ def _urljoin(base, url): """ base, baseFrag = urldefrag(base) url, urlFrag = urldefrag(urljoin(base, url)) - return urljoin(url, b"#" + (urlFrag or baseFrag)) + # We strip the hash to get test pass on Python 3.14 + # Looks like a regression in 3.14 + # See https://github.com/twisted/twisted/issues/12427 + return urljoin(url, b"#" + (urlFrag or baseFrag)).strip(b"#") def _makeGetterFactory(url, factoryFactory, contextFactory=None, *args, **kwargs): @@ -283,8 +289,11 @@ else: platformTrust, ) +_P = ParamSpec("_P") +_T = TypeVar("_T") -def _requireSSL(decoratee): + +def _requireSSL(decoratee: Callable[_P, _T]) -> Callable[_P, _T]: """ The decorated method requires pyOpenSSL to be present, or it raises L{NotImplementedError}. @@ -298,8 +307,8 @@ def _requireSSL(decoratee): """ if SSL is None: - @wraps(decoratee) - def raiseNotImplemented(*a, **kw): + @wraps(decoratee) # type:ignore[unreachable] + def raiseNotImplemented(*a: _P.args, **kw: _P.kwargs) -> _T: """ pyOpenSSL is not available. @@ -363,7 +372,9 @@ class BrowserLikePolicyForHTTPS: self._trustRoot = trustRoot @_requireSSL - def creatorForNetloc(self, hostname, port): + def creatorForNetloc( + self, hostname: bytes, port: int + ) -> IOpenSSLClientConnectionCreator: """ Create a L{client connection creator <twisted.internet.interfaces.IOpenSSLClientConnectionCreator>} for a @@ -418,19 +429,21 @@ class HostnameCachingHTTPSPolicy: @since: Twisted 19.2.0 """ - def __init__(self, policyforHTTPS, cacheSize=20): + def __init__(self, policyforHTTPS: IPolicyForHTTPS, cacheSize: int = 20) -> None: """ @param policyforHTTPS: The IPolicyForHTTPS to wrap. - @type policyforHTTPS: L{IPolicyForHTTPS} @param cacheSize: The maximum size of the hostname cache. - @type cacheSize: L{int} """ self._policyForHTTPS = policyforHTTPS - self._cache = collections.OrderedDict() + self._cache: collections.OrderedDict[ + str, IOpenSSLClientConnectionCreator + ] = collections.OrderedDict() self._cacheSize = cacheSize - def creatorForNetloc(self, hostname, port): + def creatorForNetloc( + self, hostname: bytes, port: int + ) -> IOpenSSLClientConnectionCreator: """ Create a L{client connection creator <twisted.internet.interfaces.IOpenSSLClientConnectionCreator>} for a @@ -664,7 +677,7 @@ class _HTTP11ClientFactory(protocol.Factory): self._quiescentCallback, self._metadata ) - def buildProtocol(self, addr: IAddress) -> HTTP11ClientProtocol: + def buildProtocol(self, addr: IAddress | None) -> HTTP11ClientProtocol: return HTTP11ClientProtocol(self._quiescentCallback) @@ -1171,8 +1184,8 @@ class Agent(_AgentBase): self, method: bytes, uri: bytes, - headers: Optional[Headers] = None, - bodyProducer: Optional[IBodyProducer] = None, + headers: Headers | None = None, + bodyProducer: IBodyProducer | None = None, ) -> Deferred[IResponse]: """ Issue a request to the server indicated by the given C{uri}. @@ -1244,7 +1257,7 @@ class ProxyAgent(_AgentBase): class _FakeStdlibRequest(_RequestBase): """ - A fake L{urllib.request.Request} object for L{cookiejar} to work with. + A fake L{urllib.request.Request} object for L{http.cookiejar} to work with. @see: U{urllib.request.Request <https://docs.python.org/3/library/urllib.request.html#urllib.request.Request>} @@ -1325,7 +1338,7 @@ class _FakeUrllibResponseInfo(_InfoType): class _FakeStdlibResponse(_ResponseBase): """ - A fake L{urllib.response.Response} object for L{http.cookiejar} to work + A fake L{urllib.response.addinfourl} object for L{http.cookiejar} to work with. @ivar response: Underlying Twisted Web response. @@ -1372,8 +1385,8 @@ class CookieAgent: self, method: bytes, uri: bytes, - headers: Optional[Headers] = None, - bodyProducer: Optional[IBodyProducer] = None, + headers: Headers | None = None, + bodyProducer: IBodyProducer | None = None, ) -> Deferred[IResponse]: """ Issue a new request to the wrapped L{Agent}. diff --git a/contrib/python/Twisted/py3/twisted/web/http.py b/contrib/python/Twisted/py3/twisted/web/http.py index c2894fbd253..29ac7db00ea 100644 --- a/contrib/python/Twisted/py3/twisted/web/http.py +++ b/contrib/python/Twisted/py3/twisted/web/http.py @@ -113,15 +113,7 @@ from email import message_from_bytes from email.message import EmailMessage, Message from io import BufferedIOBase, BytesIO, TextIOWrapper from time import gmtime, time -from typing import ( - AnyStr, - Callable, - Dict, - List, - Optional, - Protocol as TypingProtocol, - Tuple, -) +from typing import AnyStr, Callable, Protocol as TypingProtocol from urllib.parse import ( ParseResultBytes, unquote_to_bytes as unquote, @@ -142,7 +134,6 @@ from twisted.internet.interfaces import ( IReactorTime, ITCPTransport, ) -from twisted.internet.protocol import Protocol from twisted.logger import Logger from twisted.protocols import basic, policies from twisted.python import log @@ -527,7 +518,7 @@ def toChunk(data): return (networkString(f"{len(data):x}"), b"\r\n", data, b"\r\n") -def fromChunk(data: bytes) -> Tuple[bytes, bytes]: +def fromChunk(data: bytes) -> tuple[bytes, bytes]: """ Convert chunk to string. @@ -943,7 +934,7 @@ class Request: URL-encoded body uploads into C{request.args}. This can use large amounts of memory for large uploads. """ - self.notifications: List[Deferred[None]] = [] + self.notifications: list[Deferred[None]] = [] self.channel = channel # Cache the client and server information, we'll need this @@ -953,9 +944,9 @@ class Request: self.host = self.channel.getHost() self.requestHeaders: Headers = Headers() - self.received_cookies: Dict[bytes, bytes] = {} + self.received_cookies: dict[bytes, bytes] = {} self.responseHeaders: Headers = Headers() - self.cookies: List[bytes] = [] # outgoing cookies + self.cookies: list[bytes] = [] # outgoing cookies self.transport = self.channel.transport if queued is _QUEUED_SENTINEL: @@ -1153,7 +1144,7 @@ class Request: # The following is the public interface that people should be # writing to. - def getHeader(self, key: AnyStr) -> Optional[AnyStr]: + def getHeader(self, key: AnyStr) -> AnyStr | None: """ Get an HTTP request header. @@ -1444,7 +1435,7 @@ class Request: cookie += b"; SameSite=" + sameSite self.cookies.append(cookie) - def setResponseCode(self, code: int, message: Optional[bytes] = None) -> None: + def setResponseCode(self, code: int, message: bytes | None = None) -> None: """ Set the HTTP response code. @@ -2001,7 +1992,7 @@ class _ChunkedTransferDecoder: self.finishCallback = finishCallback self._buffer = bytearray() self._start = 0 - self._trailerHeaders: List[bytearray] = [] + self._trailerHeaders: list[bytearray] = [] self._maxTrailerHeadersSize = 2**16 self._receivedTrailerHeadersSize = 0 @@ -2328,7 +2319,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): totalHeadersSize = 16384 abortTimeout = 15 - length: Optional[int] = 0 + length: int | None = 0 persistent = 1 __header = b"" __first_line = 1 @@ -2598,7 +2589,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): L{HTTPChannel} from a custom data source, call C{dataReceived} on it directly. - @see: L{LineReceive.rawDataReceived} + @see: L{LineReceiver.rawDataReceived} """ if self._handlingRequest: self._dataBuffer.append(data) @@ -3115,16 +3106,15 @@ class _GenericHTTPChannelProtocol(proxyForInterface(IProtocol, "_channel")): # A proxy object that wraps one of the HTTP protocol objects, and switches between them depending on TLS negotiated protocol. - @ivar _negotiatedProtocol: The protocol negotiated with ALPN or NPN, if - any. + @ivar _negotiatedProtocol: The protocol negotiated with ALPN, if any. @type _negotiatedProtocol: Either a bytestring containing the ALPN token for the negotiated protocol, or L{None} if no protocol has yet been negotiated. @ivar _channel: The object capable of behaving like a L{HTTPChannel} that - is backing this object. By default this is a L{HTTPChannel}, but if a + is backing this object. By default this is a L{HTTPChannel}, but if a HTTP protocol upgrade takes place this may be a different channel - object. Must implement L{IProtocol}. + object. Must implement L{IProtocol}. @type _channel: L{HTTPChannel} @ivar _requestFactory: A callable to use to build L{IRequest} objects. @@ -3299,7 +3289,9 @@ class _GenericHTTPChannelProtocol(proxyForInterface(IProtocol, "_channel")): # return self._channel.dataReceived(data) -def _genericHTTPChannelProtocolFactory(self): +def _genericHTTPChannelProtocolFactory( + self: HTTPFactory, +) -> _GenericHTTPChannelProtocol: """ Returns an appropriately initialized _GenericHTTPChannelProtocol. """ @@ -3321,7 +3313,7 @@ class _MinimalLogFile(TypingProtocol): value: type[_MinimalLogFile] = TextIOWrapper -class HTTPFactory(protocol.ServerFactory): +class HTTPFactory(protocol.ServerFactory[_GenericHTTPChannelProtocol]): """ Factory for HTTP server. @@ -3348,7 +3340,7 @@ class HTTPFactory(protocol.ServerFactory): # _genericHTTPChannelProtocolFactory is a callable which returns a proxy # to a Protocol, instead of a concrete Protocol object, as expected in # the protocol.Factory interface - protocol = _genericHTTPChannelProtocolFactory # type: ignore[assignment] + protocol = _genericHTTPChannelProtocolFactory logPath = None _logFile: _MinimalLogFile | None = None @@ -3381,7 +3373,7 @@ class HTTPFactory(protocol.ServerFactory): reactor. """ if reactor is None: - from twisted.internet import reactor # type:ignore[assignment] + from twisted.internet import reactor self.reactor: IReactorTime = reactor # type:ignore[assignment] if logPath is not None: @@ -3418,7 +3410,7 @@ class HTTPFactory(protocol.ServerFactory): def _set_logFile(self, newLogFile: BufferedIOBase | _MinimalLogFile) -> None: if isinstance(newLogFile, BufferedIOBase): newLogFile = TextIOWrapper( - newLogFile, # type:ignore[arg-type] + newLogFile, # type:ignore[type-var] "utf-8", write_through=True, newline="\n", @@ -3434,8 +3426,10 @@ class HTTPFactory(protocol.ServerFactory): self._logDateTime = datetimeToLogString(self.reactor.seconds()) self._logDateTimeCall = self.reactor.callLater(1, self._updateLogDateTime) - def buildProtocol(self, addr: IAddress) -> Protocol | None: - p = protocol.ServerFactory.buildProtocol(self, addr) + def buildProtocol( + self, addr: IAddress | None + ) -> _GenericHTTPChannelProtocol | None: + p = super().buildProtocol(addr) # This is a bit of a hack to ensure that the HTTPChannel timeouts # occur on the same reactor as the one we're using here. This could diff --git a/contrib/python/Twisted/py3/twisted/web/http_headers.py b/contrib/python/Twisted/py3/twisted/web/http_headers.py index 49e945744b4..0fa7b37646f 100644 --- a/contrib/python/Twisted/py3/twisted/web/http_headers.py +++ b/contrib/python/Twisted/py3/twisted/web/http_headers.py @@ -7,20 +7,8 @@ An API for storing HTTP header names and values. """ from __future__ import annotations -from typing import ( - AnyStr, - ClassVar, - Dict, - Iterator, - List, - Mapping, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - overload, -) +from collections.abc import Iterator, Mapping, Sequence +from typing import AnyStr, ClassVar, TypeVar, overload from twisted.python.compat import cmp, comparable from twisted.web._abnf import _istoken @@ -70,9 +58,9 @@ class Headers: def __init__( self, - rawHeaders: Optional[Mapping[AnyStr, Sequence[AnyStr]]] = None, + rawHeaders: Mapping[AnyStr, Sequence[AnyStr]] | None = None, ) -> None: - self._rawHeaders: Dict[bytes, List[bytes]] = {} + self._rawHeaders: dict[bytes, list[bytes]] = {} if rawHeaders is not None: for name, values in rawHeaders.items(): self.setRawHeaders(name, values) @@ -127,9 +115,7 @@ class Headers: """ self._rawHeaders.pop(_nameEncoder.encode(name), None) - def setRawHeaders( - self, name: Union[str, bytes], values: Sequence[Union[str, bytes]] - ) -> None: + def setRawHeaders(self, name: str | bytes, values: Sequence[str | bytes]) -> None: """ Sets the raw representation of the given header. @@ -144,7 +130,7 @@ class Headers: @return: L{None} """ _name = _nameEncoder.encode(name) - encodedValues: List[bytes] = [] + encodedValues: list[bytes] = [] for v in values: if isinstance(v, str): _v = v.encode("utf8") @@ -154,7 +140,7 @@ class Headers: self._rawHeaders[_name] = encodedValues - def addRawHeader(self, name: Union[str, bytes], value: Union[str, bytes]) -> None: + def addRawHeader(self, name: str | bytes, value: str | bytes) -> None: """ Add a new raw value for the given header. @@ -169,16 +155,16 @@ class Headers: ) @overload - def getRawHeaders(self, name: AnyStr) -> Optional[Sequence[AnyStr]]: + def getRawHeaders(self, name: AnyStr) -> Sequence[AnyStr] | None: ... @overload - def getRawHeaders(self, name: AnyStr, default: _T) -> Union[Sequence[AnyStr], _T]: + def getRawHeaders(self, name: AnyStr, default: _T) -> Sequence[AnyStr] | _T: ... def getRawHeaders( - self, name: AnyStr, default: Optional[_T] = None - ) -> Union[Sequence[AnyStr], Optional[_T]]: + self, name: AnyStr, default: _T | None = None + ) -> Sequence[AnyStr] | _T | None: """ Returns a sequence of headers matching the given name as the raw string given. @@ -200,7 +186,7 @@ class Headers: return [v.decode("utf8") for v in values] return values - def getAllRawHeaders(self) -> Iterator[Tuple[bytes, Sequence[bytes]]]: + def getAllRawHeaders(self) -> Iterator[tuple[bytes, Sequence[bytes]]]: """ Return an iterator of key, value pairs of all headers contained in this object, as L{bytes}. The keys are capitalized in canonical @@ -223,9 +209,9 @@ class _NameEncoder: """ __slots__ = ("_canonicalHeaderCache",) - _canonicalHeaderCache: Dict[Union[bytes, str], bytes] + _canonicalHeaderCache: dict[bytes | str, bytes] - _caseMappings: ClassVar[Dict[bytes, bytes]] = { + _caseMappings: ClassVar[dict[bytes, bytes]] = { b"Content-Md5": b"Content-MD5", b"Dnt": b"DNT", b"Etag": b"ETag", @@ -240,7 +226,7 @@ class _NameEncoder: def __init__(self): self._canonicalHeaderCache = {} - def encode(self, name: Union[str, bytes]) -> bytes: + def encode(self, name: str | bytes) -> bytes: """ Encode the name of a header (eg 'Content-Type') to an ISO-8859-1 bytestring if required. It will be canonicalized to Http-Header-Case. diff --git a/contrib/python/Twisted/py3/twisted/web/iweb.py b/contrib/python/Twisted/py3/twisted/web/iweb.py index 040b916c738..93b5ac99ba8 100644 --- a/contrib/python/Twisted/py3/twisted/web/iweb.py +++ b/contrib/python/Twisted/py3/twisted/web/iweb.py @@ -10,13 +10,15 @@ Interface definitions for L{twisted.web}. body is not known in advance. """ -from typing import TYPE_CHECKING, Callable, List, Optional +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable from zope.interface import Attribute, Interface from twisted.cred.credentials import IUsernameDigestHash from twisted.internet.defer import Deferred -from twisted.internet.interfaces import IPushProducer +from twisted.internet.interfaces import IOpenSSLClientConnectionCreator, IPushProducer from twisted.web.http_headers import Headers if TYPE_CHECKING: @@ -514,7 +516,7 @@ class IRenderable(Interface): def lookupRenderMethod( name: str, - ) -> Callable[[Optional[IRequest], "Tag"], "Flattenable"]: + ) -> Callable[[IRequest | None, Tag], Flattenable]: """ Look up and return the render method associated with the given name. @@ -526,7 +528,7 @@ class IRenderable(Interface): was encountered. """ - def render(request: Optional[IRequest]) -> "Flattenable": + def render(request: IRequest | None) -> Flattenable: """ Get the document for this L{IRenderable}. @@ -543,7 +545,7 @@ class ITemplateLoader(Interface): L{twisted.web.template.Element}'s C{loader} attribute. """ - def load() -> List["Flattenable"]: + def load() -> list[Flattenable]: """ Load a template suitable for rendering. @@ -724,11 +726,23 @@ class IAgent(Interface): doSomeRequests(cache) """ + if not TYPE_CHECKING: # pragma: no branch + + def __init__(self) -> None: # type:ignore + """ + IAgent does not have any particular requirement upon its + constructor. + """ + # This is a workaround for pydoctor bug + # https://github.com/twisted/pydoctor/issues/940 + + del __init__ + def request( method: bytes, uri: bytes, - headers: Optional[Headers] = None, - bodyProducer: Optional[IBodyProducer] = None, + headers: Headers | None = None, + bodyProducer: IBodyProducer | None = None, ) -> Deferred[IResponse]: """ Request the resource at the given location. @@ -769,7 +783,7 @@ class IPolicyForHTTPS(Interface): @since: 14.0 """ - def creatorForNetloc(hostname, port): + def creatorForNetloc(hostname: bytes, port: int) -> IOpenSSLClientConnectionCreator: """ Create a L{client connection creator <twisted.internet.interfaces.IOpenSSLClientConnectionCreator>} @@ -777,15 +791,11 @@ class IPolicyForHTTPS(Interface): pair. @param hostname: The name of the requested remote host. - @type hostname: L{bytes} @param port: The number of the requested remote port. - @type port: L{int} @return: A client connection creator expressing the security requirements for the given remote host. - @rtype: L{client connection creator - <twisted.internet.interfaces.IOpenSSLClientConnectionCreator>} """ diff --git a/contrib/python/Twisted/py3/twisted/web/proxy.py b/contrib/python/Twisted/py3/twisted/web/proxy.py index e31ec7a65d3..8b41478ab75 100644 --- a/contrib/python/Twisted/py3/twisted/web/proxy.py +++ b/contrib/python/Twisted/py3/twisted/web/proxy.py @@ -89,7 +89,7 @@ class ProxyClientFactory(ClientFactory): """ # Type is wrong. See: https://twistedmatrix.com/trac/ticket/10006 - protocol = ProxyClient # type: ignore[assignment] + protocol = ProxyClient def __init__(self, command, rest, version, headers, data, father): self.father = father diff --git a/contrib/python/Twisted/py3/twisted/web/resource.py b/contrib/python/Twisted/py3/twisted/web/resource.py index 56595d2995b..9c0e9de4ae5 100644 --- a/contrib/python/Twisted/py3/twisted/web/resource.py +++ b/contrib/python/Twisted/py3/twisted/web/resource.py @@ -20,7 +20,7 @@ __all__ = [ ] import warnings -from typing import Sequence +from collections.abc import Sequence from zope.interface import Attribute, Interface, implementer @@ -65,7 +65,7 @@ class IResource(Interface): @type request: L{twisted.web.server.Request} """ - def putChild(path: bytes, child: "IResource") -> None: + def putChild(path: bytes, child: IResource) -> None: """ Put a child L{IResource} implementor at the given path. diff --git a/contrib/python/Twisted/py3/twisted/web/server.py b/contrib/python/Twisted/py3/twisted/web/server.py index 75344a07588..a1261ea283a 100644 --- a/contrib/python/Twisted/py3/twisted/web/server.py +++ b/contrib/python/Twisted/py3/twisted/web/server.py @@ -13,13 +13,14 @@ This is a web server which integrates with the twisted.internet infrastructure. value. """ +from __future__ import annotations + import copy import os import re import zlib from binascii import hexlify from html import escape -from typing import List, Optional from urllib.parse import quote as _quote from zope.interface import implementer @@ -87,11 +88,11 @@ class Request(Copyable, http.Request, components.Componentized): will be transmitted only over HTTPS. """ - defaultContentType: Optional[bytes] = b"text/html" + defaultContentType: bytes | None = b"text/html" site = None appRootURL = None - prepath: Optional[List[bytes]] = None - postpath: Optional[List[bytes]] = None + prepath: list[bytes] | None = None + postpath: list[bytes] | None = None __pychecker__ = "unusednames=issuer" _inFakeHead = False _encoder = None diff --git a/contrib/python/Twisted/py3/twisted/web/static.py b/contrib/python/Twisted/py3/twisted/web/static.py index aeffd03fb16..dbbd730b0c8 100644 --- a/contrib/python/Twisted/py3/twisted/web/static.py +++ b/contrib/python/Twisted/py3/twisted/web/static.py @@ -13,14 +13,14 @@ import mimetypes import os import time import warnings +from collections.abc import Sequence from html import escape -from typing import Any, Callable, Dict, Sequence +from typing import Any, Callable, Literal from urllib.parse import quote, unquote from zope.interface import implementer from incremental import Version -from typing_extensions import Literal from twisted.internet import abstract, interfaces from twisted.python import components, filepath, log @@ -200,7 +200,7 @@ class File(resource.Resource, filepath.FilePath[str]): contentEncodings = {".gz": "gzip", ".bz2": "bzip2"} - processors: Dict[str, Callable[[str, Any], Data]] = {} + processors: dict[str, Callable[[str, Any], Data]] = {} indexNames = ["index", "index.html", "index.htm", "index.rpy"] @@ -283,8 +283,7 @@ class File(resource.Resource, filepath.FilePath[str]): If this L{File}"s path refers to a directory, return a L{File} referring to the file named C{path} in that directory. - If C{path} is the empty string, return a L{DirectoryLister} - instead. + If C{path} is the empty string, return a L{DirectoryLister} instead. @param path: The current path segment. @type path: L{bytes} @@ -292,9 +291,9 @@ class File(resource.Resource, filepath.FilePath[str]): @param request: The incoming request. @type request: An that provides L{twisted.web.iweb.IRequest}. - @return: A resource representing the requested file or - directory, or L{NoResource} if the path cannot be - accessed. + @return: A resource representing the requested file or directory, or a + resource returning a NOT_FOUND error to clients if the path cannot + be accessed. @rtype: An object that provides L{resource.IResource}. """ if isinstance(path, bytes): diff --git a/contrib/python/Twisted/py3/twisted/web/test/requesthelper.py b/contrib/python/Twisted/py3/twisted/web/test/requesthelper.py index d5c8918b302..74f1e4bb5ef 100644 --- a/contrib/python/Twisted/py3/twisted/web/test/requesthelper.py +++ b/contrib/python/Twisted/py3/twisted/web/test/requesthelper.py @@ -10,7 +10,6 @@ from __future__ import annotations __all__ = ["DummyChannel", "DummyRequest"] from io import BytesIO -from typing import Dict, List, Optional from zope.interface import implementer, verify @@ -210,12 +209,12 @@ class DummyRequest: uri = b"http://dummy/" method = b"GET" - client: Optional[IAddress] = None - sitepath: List[bytes] - written: List[bytes] - prepath: List[bytes] - args: Dict[bytes, List[bytes]] - _finishedDeferreds: List[Deferred[None]] + client: IAddress | None = None + sitepath: list[bytes] + written: list[bytes] + prepath: list[bytes] + args: dict[bytes, list[bytes]] + _finishedDeferreds: list[Deferred[None]] def registerProducer(self, prod, s): """ @@ -238,8 +237,8 @@ class DummyRequest: def __init__( self, postpath: list[bytes], - session: Optional[Session] = None, - client: Optional[IAddress] = None, + session: Session | None = None, + client: IAddress | None = None, ) -> None: self.sitepath = [] self.written = [] diff --git a/contrib/python/Twisted/py3/twisted/web/wsgi.py b/contrib/python/Twisted/py3/twisted/web/wsgi.py index e979d30416e..0a3223f982b 100644 --- a/contrib/python/Twisted/py3/twisted/web/wsgi.py +++ b/contrib/python/Twisted/py3/twisted/web/wsgi.py @@ -6,9 +6,10 @@ An implementation of U{Python Web Server Gateway Interface v1.0.1<http://www.python.org/dev/peps/pep-3333/>}. """ +from __future__ import annotations + from collections.abc import Sequence from sys import exc_info -from typing import List, Union from warnings import warn from zope.interface import implementer @@ -34,7 +35,7 @@ from twisted.web.server import NOT_DONE_YET # # The following pair of functions -- _wsgiString() and _wsgiStringToBytes() -- # are used to make Twisted's WSGI support compliant with the standard. -def _wsgiString(string: Union[str, bytes]) -> str: +def _wsgiString(string: str | bytes) -> str: """ Convert C{string} to a WSGI "bytes-as-unicode" string. @@ -100,7 +101,7 @@ class _ErrorStream: # will overwrite this value if it is not properly formatted here. self._log.error(data, system="wsgi", isError=True, message=(data,)) - def writelines(self, iovec: List[str]) -> None: + def writelines(self, iovec: list[str]) -> None: """ Join the given lines and pass them to C{write} to be handled in the usual way. diff --git a/contrib/python/Twisted/py3/twisted/words/im/basesupport.py b/contrib/python/Twisted/py3/twisted/words/im/basesupport.py index 55519193d4e..8dee4f47df6 100644 --- a/contrib/python/Twisted/py3/twisted/words/im/basesupport.py +++ b/contrib/python/Twisted/py3/twisted/words/im/basesupport.py @@ -7,8 +7,6 @@ You will find these useful if you're adding a new protocol to IM. """ -from typing import Type - from twisted.internet import error from twisted.internet.protocol import Protocol, connectionDone from twisted.persisted import styles @@ -95,7 +93,7 @@ class AbstractClientMixin: @ivar _logonDeferred: Fired when I am done logging in. """ - _protoBase: Type[Protocol] = None # type: ignore[assignment] + _protoBase: type[Protocol] = None # type: ignore[assignment] def __init__(self, account, chatui, logonDeferred): for base in self.__class__.__bases__: diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/irc.py b/contrib/python/Twisted/py3/twisted/words/protocols/irc.py index 2906fa5627a..f31f69cd4de 100644 --- a/contrib/python/Twisted/py3/twisted/words/protocols/irc.py +++ b/contrib/python/Twisted/py3/twisted/words/protocols/irc.py @@ -2851,7 +2851,7 @@ class DccSendProtocol(protocol.Protocol, styles.Ephemeral): class DccSendFactory(protocol.Factory): - protocol = DccSendProtocol # type: ignore[assignment] + protocol = DccSendProtocol def __init__(self, file): self.file = file @@ -2963,7 +2963,7 @@ class DccChat(basic.LineReceiver, styles.Ephemeral): class DccChatFactory(protocol.ClientFactory): - protocol = DccChat # type: ignore[assignment] + protocol = DccChat noisy = False def __init__(self, client, queryData): diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py index 52e154fee4f..13e10a6449f 100644 --- a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py +++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/jid.py @@ -11,8 +11,7 @@ parse string representations into them with proper checking for illegal characters, case folding and canonicalisation through L{stringprep<twisted.words.protocols.jabber.xmpp_stringprep>}. """ - -from typing import Dict, Tuple, Union +from __future__ import annotations from twisted.words.protocols.jabber.xmpp_stringprep import ( nameprep, @@ -27,7 +26,7 @@ class InvalidFormat(Exception): """ -def parse(jidstring: str) -> Tuple[Union[str, None], str, Union[str, None]]: +def parse(jidstring: str) -> tuple[str | None, str, str | None]: """ Parse given JID string into its respective parts and apply stringprep. @@ -75,8 +74,8 @@ def parse(jidstring: str) -> Tuple[Union[str, None], str, Union[str, None]]: def prep( - user: Union[str, None], host: str, resource: Union[str, None] -) -> Tuple[Union[str, None], str, Union[str, None]]: + user: str | None, host: str, resource: str | None +) -> tuple[str | None, str, str | None]: """ Perform stringprep on all JID fragments. @@ -117,7 +116,7 @@ def prep( return (user, host, resource) -__internJIDs: Dict[str, "JID"] = {} +__internJIDs: dict[str, JID] = {} def internJID(jidstring): @@ -145,8 +144,8 @@ class JID: def __init__( self, - str: Union[str, None] = None, - tuple: Union[Tuple[Union[str, None], str, Union[str, None]], None] = None, + str: str | None = None, + tuple: tuple[str | None, str, str | None] | None = None, ): if str: user, host, res = parse(str) diff --git a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py index 601a879aa8b..a96e51e21af 100644 --- a/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py +++ b/contrib/python/Twisted/py3/twisted/words/protocols/jabber/xmlstream.py @@ -21,12 +21,11 @@ Stanzas. @var Reset: Token to signal that the XML stream has been reset. @type Reset: Basic object. """ - +from __future__ import annotations from binascii import hexlify from hashlib import sha1 from sys import intern -from typing import Optional, Tuple from zope.interface import directlyProvides, implementer @@ -166,7 +165,7 @@ class ConnectAuthenticator(Authenticator): Authenticator for initiating entities. """ - namespace: Optional[str] = None + namespace: str | None = None def __init__(self, otherHost): self.otherHost = otherHost @@ -262,7 +261,7 @@ class ListenAuthenticator(Authenticator): Authenticator for receiving entities. """ - namespace: Optional[str] = None + namespace: str | None = None def associateWithStream(self, xmlstream): """ @@ -318,7 +317,7 @@ class BaseFeatureInitiatingInitializer: @type required: C{bool} """ - feature: Optional[Tuple[str, str]] = None + feature: tuple[str, str] | None = None def __init__(self, xs, required=False): self.xmlstream = xs @@ -678,7 +677,9 @@ class XmlStreamFactory(xmlstream.XmlStreamFactory): self.authenticator = authenticator -class XmlStreamServerFactory(xmlstream.BootstrapMixin, protocol.ServerFactory): +class XmlStreamServerFactory( + xmlstream.BootstrapMixin, protocol.ServerFactory[XmlStream] +): """ Factory for Jabber XmlStream objects as a server. @@ -688,8 +689,7 @@ class XmlStreamServerFactory(xmlstream.BootstrapMixin, protocol.ServerFactory): with the XmlStream. """ - # Type is wrong. See: https://twistedmatrix.com/trac/ticket/10007#ticket - protocol = XmlStream # type: ignore[assignment] + protocol = XmlStream def __init__(self, authenticatorFactory): xmlstream.BootstrapMixin.__init__(self) diff --git a/contrib/python/Twisted/py3/twisted/words/tap.py b/contrib/python/Twisted/py3/twisted/words/tap.py index d83db71b76e..84018611ef5 100644 --- a/contrib/python/Twisted/py3/twisted/words/tap.py +++ b/contrib/python/Twisted/py3/twisted/words/tap.py @@ -4,10 +4,11 @@ """ Shiny new words service maker """ +from __future__ import annotations import socket import sys -from typing import List, Optional, Sequence +from collections.abc import Sequence from twisted import plugin from twisted.application import strports @@ -19,7 +20,7 @@ from twisted.words import iwords, service class Options(usage.Options, strcred.AuthOptionMixin): supportedInterfaces = [credentials.IUsernamePassword] - optParameters: List[Sequence[Optional[str]]] = [ + optParameters: list[Sequence[str | None]] = [ ( "hostname", None, |
