diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-11-30 12:58:42 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-11-30 13:09:50 +0300 |
commit | 9dc6b2fddb37a98c860a013c641c112d5030c7a7 (patch) | |
tree | 6678c90833d76fbed73b10363f1b0ec668eacafe /contrib/python/httpcore | |
parent | df64d4599e151698c173343d73e12328aa9ce1ef (diff) | |
download | ydb-9dc6b2fddb37a98c860a013c641c112d5030c7a7.tar.gz |
Intermediate changes
commit_hash:f785655b6e4979e4b61af2cb8227296a279f7ab6
Diffstat (limited to 'contrib/python/httpcore')
29 files changed, 683 insertions, 593 deletions
diff --git a/contrib/python/httpcore/.dist-info/METADATA b/contrib/python/httpcore/.dist-info/METADATA index 6be8e4bb68..99be2236cd 100644 --- a/contrib/python/httpcore/.dist-info/METADATA +++ b/contrib/python/httpcore/.dist-info/METADATA @@ -1,13 +1,12 @@ Metadata-Version: 2.3 Name: httpcore -Version: 1.0.6 +Version: 1.0.7 Summary: A minimal low-level HTTP client. Project-URL: Documentation, https://www.encode.io/httpcore Project-URL: Homepage, https://www.encode.io/httpcore/ Project-URL: Source, https://github.com/encode/httpcore Author-email: Tom Christie <tom@tomchristie.com> -License-Expression: BSD-3-Clause -License-File: LICENSE.md +License: BSD-3-Clause Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Web Environment Classifier: Framework :: AsyncIO @@ -153,6 +152,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Version 1.0.7 (November 15th, 2024) + +- Support `proxy=…` configuration on `ConnectionPool()`. (#974) + ## Version 1.0.6 (October 1st, 2024) - Relax `trio` dependency pinning. (#956) diff --git a/contrib/python/httpcore/httpcore/__init__.py b/contrib/python/httpcore/httpcore/__init__.py index 330745a5dc..662b1563a1 100644 --- a/contrib/python/httpcore/httpcore/__init__.py +++ b/contrib/python/httpcore/httpcore/__init__.py @@ -34,7 +34,7 @@ from ._exceptions import ( WriteError, WriteTimeout, ) -from ._models import URL, Origin, Request, Response +from ._models import URL, Origin, Proxy, Request, Response from ._ssl import default_ssl_context from ._sync import ( ConnectionInterface, @@ -79,6 +79,7 @@ __all__ = [ "URL", "Request", "Response", + "Proxy", # async "AsyncHTTPConnection", "AsyncConnectionPool", @@ -130,7 +131,7 @@ __all__ = [ "WriteError", ] -__version__ = "1.0.6" +__version__ = "1.0.7" __locals = locals() diff --git a/contrib/python/httpcore/httpcore/_api.py b/contrib/python/httpcore/httpcore/_api.py index 854235f5f6..38b961d10d 100644 --- a/contrib/python/httpcore/httpcore/_api.py +++ b/contrib/python/httpcore/httpcore/_api.py @@ -1,17 +1,19 @@ -from contextlib import contextmanager -from typing import Iterator, Optional, Union +from __future__ import annotations + +import contextlib +import typing from ._models import URL, Extensions, HeaderTypes, Response from ._sync.connection_pool import ConnectionPool def request( - method: Union[bytes, str], - url: Union[URL, bytes, str], + method: bytes | str, + url: URL | bytes | str, *, headers: HeaderTypes = None, - content: Union[bytes, Iterator[bytes], None] = None, - extensions: Optional[Extensions] = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, ) -> Response: """ Sends an HTTP request, returning the response. @@ -45,15 +47,15 @@ def request( ) -@contextmanager +@contextlib.contextmanager def stream( - method: Union[bytes, str], - url: Union[URL, bytes, str], + method: bytes | str, + url: URL | bytes | str, *, headers: HeaderTypes = None, - content: Union[bytes, Iterator[bytes], None] = None, - extensions: Optional[Extensions] = None, -) -> Iterator[Response]: + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, +) -> typing.Iterator[Response]: """ Sends an HTTP request, returning the response within a content manager. diff --git a/contrib/python/httpcore/httpcore/_async/connection.py b/contrib/python/httpcore/httpcore/_async/connection.py index 2f439cf09c..b42581dff8 100644 --- a/contrib/python/httpcore/httpcore/_async/connection.py +++ b/contrib/python/httpcore/httpcore/_async/connection.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import itertools import logging import ssl -from types import TracebackType -from typing import Iterable, Iterator, Optional, Type +import types +import typing from .._backends.auto import AutoBackend from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream @@ -20,7 +22,7 @@ RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc. logger = logging.getLogger("httpcore.connection") -def exponential_backoff(factor: float) -> Iterator[float]: +def exponential_backoff(factor: float) -> typing.Iterator[float]: """ Generate a geometric sequence that has a ratio of 2 and starts with 0. @@ -37,15 +39,15 @@ class AsyncHTTPConnection(AsyncConnectionInterface): def __init__( self, origin: Origin, - ssl_context: Optional[ssl.SSLContext] = None, - keepalive_expiry: Optional[float] = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - local_address: Optional[str] = None, - uds: Optional[str] = None, - network_backend: Optional[AsyncNetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: self._origin = origin self._ssl_context = ssl_context @@ -59,7 +61,7 @@ class AsyncHTTPConnection(AsyncConnectionInterface): self._network_backend: AsyncNetworkBackend = ( AutoBackend() if network_backend is None else network_backend ) - self._connection: Optional[AsyncConnectionInterface] = None + self._connection: AsyncConnectionInterface | None = None self._connect_failed: bool = False self._request_lock = AsyncLock() self._socket_options = socket_options @@ -208,13 +210,13 @@ class AsyncHTTPConnection(AsyncConnectionInterface): # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. - async def __aenter__(self) -> "AsyncHTTPConnection": + async def __aenter__(self) -> AsyncHTTPConnection: return self async def __aexit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: await self.aclose() diff --git a/contrib/python/httpcore/httpcore/_async/connection_pool.py b/contrib/python/httpcore/httpcore/_async/connection_pool.py index 214dfc4be4..96e973d0ce 100644 --- a/contrib/python/httpcore/httpcore/_async/connection_pool.py +++ b/contrib/python/httpcore/httpcore/_async/connection_pool.py @@ -1,12 +1,14 @@ +from __future__ import annotations + import ssl import sys -from types import TracebackType -from typing import AsyncIterable, AsyncIterator, Iterable, List, Optional, Type +import types +import typing from .._backends.auto import AutoBackend from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol -from .._models import Origin, Request, Response +from .._models import Origin, Proxy, Request, Response from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock from .connection import AsyncHTTPConnection from .interfaces import AsyncConnectionInterface, AsyncRequestInterface @@ -15,12 +17,10 @@ from .interfaces import AsyncConnectionInterface, AsyncRequestInterface class AsyncPoolRequest: def __init__(self, request: Request) -> None: self.request = request - self.connection: Optional[AsyncConnectionInterface] = None + self.connection: AsyncConnectionInterface | None = None self._connection_acquired = AsyncEvent() - def assign_to_connection( - self, connection: Optional[AsyncConnectionInterface] - ) -> None: + def assign_to_connection(self, connection: AsyncConnectionInterface | None) -> None: self.connection = connection self._connection_acquired.set() @@ -29,7 +29,7 @@ class AsyncPoolRequest: self._connection_acquired = AsyncEvent() async def wait_for_connection( - self, timeout: Optional[float] = None + self, timeout: float | None = None ) -> AsyncConnectionInterface: if self.connection is None: await self._connection_acquired.wait(timeout=timeout) @@ -47,17 +47,18 @@ class AsyncConnectionPool(AsyncRequestInterface): def __init__( self, - ssl_context: Optional[ssl.SSLContext] = None, - max_connections: Optional[int] = 10, - max_keepalive_connections: Optional[int] = None, - keepalive_expiry: Optional[float] = None, + ssl_context: ssl.SSLContext | None = None, + proxy: Proxy | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - local_address: Optional[str] = None, - uds: Optional[str] = None, - network_backend: Optional[AsyncNetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: """ A connection pool for making HTTP requests. @@ -89,7 +90,7 @@ class AsyncConnectionPool(AsyncRequestInterface): in the TCP socket when the connection was established. """ self._ssl_context = ssl_context - + self._proxy = proxy self._max_connections = ( sys.maxsize if max_connections is None else max_connections ) @@ -116,8 +117,8 @@ class AsyncConnectionPool(AsyncRequestInterface): # The mutable state on a connection pool is the queue of incoming requests, # and the set of connections that are servicing those requests. - self._connections: List[AsyncConnectionInterface] = [] - self._requests: List[AsyncPoolRequest] = [] + self._connections: list[AsyncConnectionInterface] = [] + self._requests: list[AsyncPoolRequest] = [] # We only mutate the state of the connection pool within an 'optional_thread_lock' # context. This holds a threading lock unless we're running in async mode, @@ -125,6 +126,45 @@ class AsyncConnectionPool(AsyncRequestInterface): self._optional_thread_lock = AsyncThreadLock() def create_connection(self, origin: Origin) -> AsyncConnectionInterface: + if self._proxy is not None: + if self._proxy.url.scheme in (b"socks5", b"socks5h"): + from .socks_proxy import AsyncSocks5Connection + + return AsyncSocks5Connection( + proxy_origin=self._proxy.url.origin, + proxy_auth=self._proxy.auth, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + elif origin.scheme == b"http": + from .http_proxy import AsyncForwardHTTPConnection + + return AsyncForwardHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + ) + from .http_proxy import AsyncTunnelHTTPConnection + + return AsyncTunnelHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + return AsyncHTTPConnection( origin=origin, ssl_context=self._ssl_context, @@ -139,7 +179,7 @@ class AsyncConnectionPool(AsyncRequestInterface): ) @property - def connections(self) -> List[AsyncConnectionInterface]: + def connections(self) -> list[AsyncConnectionInterface]: """ Return a list of the connections currently in the pool. @@ -217,7 +257,7 @@ class AsyncConnectionPool(AsyncRequestInterface): # Return the response. Note that in this case we still have to manage # the point at which the response is closed. - assert isinstance(response.stream, AsyncIterable) + assert isinstance(response.stream, typing.AsyncIterable) return Response( status=response.status, headers=response.headers, @@ -227,7 +267,7 @@ class AsyncConnectionPool(AsyncRequestInterface): extensions=response.extensions, ) - def _assign_requests_to_connections(self) -> List[AsyncConnectionInterface]: + def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]: """ Manage the state of the connection pool, assigning incoming requests to connections as available. @@ -298,7 +338,7 @@ class AsyncConnectionPool(AsyncRequestInterface): return closing_connections - async def _close_connections(self, closing: List[AsyncConnectionInterface]) -> None: + async def _close_connections(self, closing: list[AsyncConnectionInterface]) -> None: # Close connections which have been removed from the pool. with AsyncShieldCancellation(): for connection in closing: @@ -312,14 +352,14 @@ class AsyncConnectionPool(AsyncRequestInterface): self._connections = [] await self._close_connections(closing_connections) - async def __aenter__(self) -> "AsyncConnectionPool": + async def __aenter__(self) -> AsyncConnectionPool: return self async def __aexit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: await self.aclose() @@ -349,7 +389,7 @@ class AsyncConnectionPool(AsyncRequestInterface): class PoolByteStream: def __init__( self, - stream: AsyncIterable[bytes], + stream: typing.AsyncIterable[bytes], pool_request: AsyncPoolRequest, pool: AsyncConnectionPool, ) -> None: @@ -358,7 +398,7 @@ class PoolByteStream: self._pool = pool self._closed = False - async def __aiter__(self) -> AsyncIterator[bytes]: + async def __aiter__(self) -> typing.AsyncIterator[bytes]: try: async for part in self._stream: yield part diff --git a/contrib/python/httpcore/httpcore/_async/http11.py b/contrib/python/httpcore/httpcore/_async/http11.py index 0493a923dc..e6d6d70985 100644 --- a/contrib/python/httpcore/httpcore/_async/http11.py +++ b/contrib/python/httpcore/httpcore/_async/http11.py @@ -1,18 +1,11 @@ +from __future__ import annotations + import enum import logging import ssl import time -from types import TracebackType -from typing import ( - Any, - AsyncIterable, - AsyncIterator, - List, - Optional, - Tuple, - Type, - Union, -) +import types +import typing import h11 @@ -33,7 +26,7 @@ logger = logging.getLogger("httpcore.http11") # A subset of `h11.Event` types supported by `_send_event` -H11SendEvent = Union[ +H11SendEvent = typing.Union[ h11.Request, h11.Data, h11.EndOfMessage, @@ -55,12 +48,12 @@ class AsyncHTTP11Connection(AsyncConnectionInterface): self, origin: Origin, stream: AsyncNetworkStream, - keepalive_expiry: Optional[float] = None, + keepalive_expiry: float | None = None, ) -> None: self._origin = origin self._network_stream = stream - self._keepalive_expiry: Optional[float] = keepalive_expiry - self._expire_at: Optional[float] = None + self._keepalive_expiry: float | None = keepalive_expiry + self._expire_at: float | None = None self._state = HTTPConnectionState.NEW self._state_lock = AsyncLock() self._request_count = 0 @@ -160,16 +153,14 @@ class AsyncHTTP11Connection(AsyncConnectionInterface): timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("write", None) - assert isinstance(request.stream, AsyncIterable) + assert isinstance(request.stream, typing.AsyncIterable) async for chunk in request.stream: event = h11.Data(data=chunk) await self._send_event(event, timeout=timeout) await self._send_event(h11.EndOfMessage(), timeout=timeout) - async def _send_event( - self, event: h11.Event, timeout: Optional[float] = None - ) -> None: + async def _send_event(self, event: h11.Event, timeout: float | None = None) -> None: bytes_to_send = self._h11_state.send(event) if bytes_to_send is not None: await self._network_stream.write(bytes_to_send, timeout=timeout) @@ -178,7 +169,7 @@ class AsyncHTTP11Connection(AsyncConnectionInterface): async def _receive_response_headers( self, request: Request - ) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], bytes]: + ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("read", None) @@ -202,7 +193,9 @@ class AsyncHTTP11Connection(AsyncConnectionInterface): return http_version, event.status_code, event.reason, headers, trailing_data - async def _receive_response_body(self, request: Request) -> AsyncIterator[bytes]: + async def _receive_response_body( + self, request: Request + ) -> typing.AsyncIterator[bytes]: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("read", None) @@ -214,8 +207,8 @@ class AsyncHTTP11Connection(AsyncConnectionInterface): break async def _receive_event( - self, timeout: Optional[float] = None - ) -> Union[h11.Event, Type[h11.PAUSED]]: + self, timeout: float | None = None + ) -> h11.Event | type[h11.PAUSED]: while True: with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): event = self._h11_state.next_event() @@ -316,14 +309,14 @@ class AsyncHTTP11Connection(AsyncConnectionInterface): # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. - async def __aenter__(self) -> "AsyncHTTP11Connection": + async def __aenter__(self) -> AsyncHTTP11Connection: return self async def __aexit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: await self.aclose() @@ -334,7 +327,7 @@ class HTTP11ConnectionByteStream: self._request = request self._closed = False - async def __aiter__(self) -> AsyncIterator[bytes]: + async def __aiter__(self) -> typing.AsyncIterator[bytes]: kwargs = {"request": self._request} try: async with Trace("receive_response_body", logger, self._request, kwargs): @@ -360,7 +353,7 @@ class AsyncHTTP11UpgradeStream(AsyncNetworkStream): self._stream = stream self._leading_data = leading_data - async def read(self, max_bytes: int, timeout: Optional[float] = None) -> bytes: + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: if self._leading_data: buffer = self._leading_data[:max_bytes] self._leading_data = self._leading_data[max_bytes:] @@ -368,7 +361,7 @@ class AsyncHTTP11UpgradeStream(AsyncNetworkStream): else: return await self._stream.read(max_bytes, timeout) - async def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + async def write(self, buffer: bytes, timeout: float | None = None) -> None: await self._stream.write(buffer, timeout) async def aclose(self) -> None: @@ -377,10 +370,10 @@ class AsyncHTTP11UpgradeStream(AsyncNetworkStream): async def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: Optional[str] = None, - timeout: Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ) -> AsyncNetworkStream: return await self._stream.start_tls(ssl_context, server_hostname, timeout) - def get_extra_info(self, info: str) -> Any: + def get_extra_info(self, info: str) -> typing.Any: return self._stream.get_extra_info(info) diff --git a/contrib/python/httpcore/httpcore/_async/http2.py b/contrib/python/httpcore/httpcore/_async/http2.py index c201ee4cbc..c6434a0496 100644 --- a/contrib/python/httpcore/httpcore/_async/http2.py +++ b/contrib/python/httpcore/httpcore/_async/http2.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import enum import logging import time @@ -45,14 +47,14 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): self, origin: Origin, stream: AsyncNetworkStream, - keepalive_expiry: typing.Optional[float] = None, + keepalive_expiry: float | None = None, ): self._origin = origin self._network_stream = stream - self._keepalive_expiry: typing.Optional[float] = keepalive_expiry + self._keepalive_expiry: float | None = keepalive_expiry self._h2_state = h2.connection.H2Connection(config=self.CONFIG) self._state = HTTPConnectionState.IDLE - self._expire_at: typing.Optional[float] = None + self._expire_at: float | None = None self._request_count = 0 self._init_lock = AsyncLock() self._state_lock = AsyncLock() @@ -63,24 +65,20 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): self._connection_error = False # Mapping from stream ID to response stream events. - self._events: typing.Dict[ + self._events: dict[ int, - typing.Union[ - h2.events.ResponseReceived, - h2.events.DataReceived, - h2.events.StreamEnded, - h2.events.StreamReset, - ], + h2.events.ResponseReceived + | h2.events.DataReceived + | h2.events.StreamEnded + | h2.events.StreamReset, ] = {} # Connection terminated events are stored as state since # we need to handle them for all streams. - self._connection_terminated: typing.Optional[h2.events.ConnectionTerminated] = ( - None - ) + self._connection_terminated: h2.events.ConnectionTerminated | None = None - self._read_exception: typing.Optional[Exception] = None - self._write_exception: typing.Optional[Exception] = None + self._read_exception: Exception | None = None + self._write_exception: Exception | None = None async def handle_async_request(self, request: Request) -> Response: if not self.can_handle_request(request.url.origin): @@ -284,7 +282,7 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): async def _receive_response( self, request: Request, stream_id: int - ) -> typing.Tuple[int, typing.List[typing.Tuple[bytes, bytes]]]: + ) -> tuple[int, list[tuple[bytes, bytes]]]: """ Return the response status code and headers for a given stream ID. """ @@ -321,9 +319,7 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): async def _receive_stream_event( self, request: Request, stream_id: int - ) -> typing.Union[ - h2.events.ResponseReceived, h2.events.DataReceived, h2.events.StreamEnded - ]: + ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded: """ Return the next available event for a given stream ID. @@ -337,7 +333,7 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): return event async def _receive_events( - self, request: Request, stream_id: typing.Optional[int] = None + self, request: Request, stream_id: int | None = None ) -> None: """ Read some data from the network until we see one or more events @@ -425,9 +421,7 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): # Wrappers around network read/write operations... - async def _read_incoming_data( - self, request: Request - ) -> typing.List[h2.events.Event]: + async def _read_incoming_data(self, request: Request) -> list[h2.events.Event]: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("read", None) @@ -451,7 +445,7 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): self._connection_error = True raise exc - events: typing.List[h2.events.Event] = self._h2_state.receive_data(data) + events: list[h2.events.Event] = self._h2_state.receive_data(data) return events @@ -544,14 +538,14 @@ class AsyncHTTP2Connection(AsyncConnectionInterface): # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. - async def __aenter__(self) -> "AsyncHTTP2Connection": + async def __aenter__(self) -> AsyncHTTP2Connection: return self async def __aexit__( self, - exc_type: typing.Optional[typing.Type[BaseException]] = None, - exc_value: typing.Optional[BaseException] = None, - traceback: typing.Optional[types.TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: await self.aclose() diff --git a/contrib/python/httpcore/httpcore/_async/http_proxy.py b/contrib/python/httpcore/httpcore/_async/http_proxy.py index 4aa7d8741a..cc9d92066e 100644 --- a/contrib/python/httpcore/httpcore/_async/http_proxy.py +++ b/contrib/python/httpcore/httpcore/_async/http_proxy.py @@ -1,7 +1,9 @@ +from __future__ import annotations + +import base64 import logging import ssl -from base64 import b64encode -from typing import Iterable, List, Mapping, Optional, Sequence, Tuple, Union +import typing from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend from .._exceptions import ProxyError @@ -22,17 +24,18 @@ from .connection_pool import AsyncConnectionPool from .http11 import AsyncHTTP11Connection from .interfaces import AsyncConnectionInterface -HeadersAsSequence = Sequence[Tuple[Union[bytes, str], Union[bytes, str]]] -HeadersAsMapping = Mapping[Union[bytes, str], Union[bytes, str]] +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] logger = logging.getLogger("httpcore.proxy") def merge_headers( - default_headers: Optional[Sequence[Tuple[bytes, bytes]]] = None, - override_headers: Optional[Sequence[Tuple[bytes, bytes]]] = None, -) -> List[Tuple[bytes, bytes]]: + default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, +) -> list[tuple[bytes, bytes]]: """ Append default_headers and override_headers, de-duplicating if a key exists in both cases. @@ -48,33 +51,28 @@ def merge_headers( return default_headers + override_headers -def build_auth_header(username: bytes, password: bytes) -> bytes: - userpass = username + b":" + password - return b"Basic " + b64encode(userpass) - - -class AsyncHTTPProxy(AsyncConnectionPool): +class AsyncHTTPProxy(AsyncConnectionPool): # pragma: nocover """ A connection pool that sends requests via an HTTP proxy. """ def __init__( self, - proxy_url: Union[URL, bytes, str], - proxy_auth: Optional[Tuple[Union[bytes, str], Union[bytes, str]]] = None, - proxy_headers: Union[HeadersAsMapping, HeadersAsSequence, None] = None, - ssl_context: Optional[ssl.SSLContext] = None, - proxy_ssl_context: Optional[ssl.SSLContext] = None, - max_connections: Optional[int] = 10, - max_keepalive_connections: Optional[int] = None, - keepalive_expiry: Optional[float] = None, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - local_address: Optional[str] = None, - uds: Optional[str] = None, - network_backend: Optional[AsyncNetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: """ A connection pool for making HTTP requests. @@ -139,7 +137,8 @@ class AsyncHTTPProxy(AsyncConnectionPool): if proxy_auth is not None: username = enforce_bytes(proxy_auth[0], name="proxy_auth") password = enforce_bytes(proxy_auth[1], name="proxy_auth") - authorization = build_auth_header(username, password) + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) self._proxy_headers = [ (b"Proxy-Authorization", authorization) ] + self._proxy_headers @@ -172,11 +171,11 @@ class AsyncForwardHTTPConnection(AsyncConnectionInterface): self, proxy_origin: Origin, remote_origin: Origin, - proxy_headers: Union[HeadersAsMapping, HeadersAsSequence, None] = None, - keepalive_expiry: Optional[float] = None, - network_backend: Optional[AsyncNetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, - proxy_ssl_context: Optional[ssl.SSLContext] = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + keepalive_expiry: float | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, ) -> None: self._connection = AsyncHTTPConnection( origin=proxy_origin, @@ -236,14 +235,14 @@ class AsyncTunnelHTTPConnection(AsyncConnectionInterface): self, proxy_origin: Origin, remote_origin: Origin, - ssl_context: Optional[ssl.SSLContext] = None, - proxy_ssl_context: Optional[ssl.SSLContext] = None, - proxy_headers: Optional[Sequence[Tuple[bytes, bytes]]] = None, - keepalive_expiry: Optional[float] = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, - network_backend: Optional[AsyncNetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: self._connection: AsyncConnectionInterface = AsyncHTTPConnection( origin=proxy_origin, diff --git a/contrib/python/httpcore/httpcore/_async/interfaces.py b/contrib/python/httpcore/httpcore/_async/interfaces.py index c998dd2763..361583bede 100644 --- a/contrib/python/httpcore/httpcore/_async/interfaces.py +++ b/contrib/python/httpcore/httpcore/_async/interfaces.py @@ -1,5 +1,7 @@ -from contextlib import asynccontextmanager -from typing import AsyncIterator, Optional, Union +from __future__ import annotations + +import contextlib +import typing from .._models import ( URL, @@ -18,12 +20,12 @@ from .._models import ( class AsyncRequestInterface: async def request( self, - method: Union[bytes, str], - url: Union[URL, bytes, str], + method: bytes | str, + url: URL | bytes | str, *, headers: HeaderTypes = None, - content: Union[bytes, AsyncIterator[bytes], None] = None, - extensions: Optional[Extensions] = None, + content: bytes | typing.AsyncIterator[bytes] | None = None, + extensions: Extensions | None = None, ) -> Response: # Strict type checking on our parameters. method = enforce_bytes(method, name="method") @@ -47,16 +49,16 @@ class AsyncRequestInterface: await response.aclose() return response - @asynccontextmanager + @contextlib.asynccontextmanager async def stream( self, - method: Union[bytes, str], - url: Union[URL, bytes, str], + method: bytes | str, + url: URL | bytes | str, *, headers: HeaderTypes = None, - content: Union[bytes, AsyncIterator[bytes], None] = None, - extensions: Optional[Extensions] = None, - ) -> AsyncIterator[Response]: + content: bytes | typing.AsyncIterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> typing.AsyncIterator[Response]: # Strict type checking on our parameters. method = enforce_bytes(method, name="method") url = enforce_url(url, name="url") diff --git a/contrib/python/httpcore/httpcore/_async/socks_proxy.py b/contrib/python/httpcore/httpcore/_async/socks_proxy.py index f839603fe5..b363f55a0b 100644 --- a/contrib/python/httpcore/httpcore/_async/socks_proxy.py +++ b/contrib/python/httpcore/httpcore/_async/socks_proxy.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import logging import ssl -import typing -from socksio import socks5 +import socksio from .._backends.auto import AutoBackend from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream @@ -43,24 +44,24 @@ async def _init_socks5_connection( *, host: bytes, port: int, - auth: typing.Optional[typing.Tuple[bytes, bytes]] = None, + auth: tuple[bytes, bytes] | None = None, ) -> None: - conn = socks5.SOCKS5Connection() + conn = socksio.socks5.SOCKS5Connection() # Auth method request auth_method = ( - socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED + socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED if auth is None - else socks5.SOCKS5AuthMethod.USERNAME_PASSWORD + else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD ) - conn.send(socks5.SOCKS5AuthMethodsRequest([auth_method])) + conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method])) outgoing_bytes = conn.data_to_send() await stream.write(outgoing_bytes) # Auth method response incoming_bytes = await stream.read(max_bytes=4096) response = conn.receive_data(incoming_bytes) - assert isinstance(response, socks5.SOCKS5AuthReply) + assert isinstance(response, socksio.socks5.SOCKS5AuthReply) if response.method != auth_method: requested = AUTH_METHODS.get(auth_method, "UNKNOWN") responded = AUTH_METHODS.get(response.method, "UNKNOWN") @@ -68,25 +69,25 @@ async def _init_socks5_connection( f"Requested {requested} from proxy server, but got {responded}." ) - if response.method == socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: + if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: # Username/password request assert auth is not None username, password = auth - conn.send(socks5.SOCKS5UsernamePasswordRequest(username, password)) + conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password)) outgoing_bytes = conn.data_to_send() await stream.write(outgoing_bytes) # Username/password response incoming_bytes = await stream.read(max_bytes=4096) response = conn.receive_data(incoming_bytes) - assert isinstance(response, socks5.SOCKS5UsernamePasswordReply) + assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply) if not response.success: raise ProxyError("Invalid username/password") # Connect request conn.send( - socks5.SOCKS5CommandRequest.from_address( - socks5.SOCKS5Command.CONNECT, (host, port) + socksio.socks5.SOCKS5CommandRequest.from_address( + socksio.socks5.SOCKS5Command.CONNECT, (host, port) ) ) outgoing_bytes = conn.data_to_send() @@ -95,31 +96,29 @@ async def _init_socks5_connection( # Connect response incoming_bytes = await stream.read(max_bytes=4096) response = conn.receive_data(incoming_bytes) - assert isinstance(response, socks5.SOCKS5Reply) - if response.reply_code != socks5.SOCKS5ReplyCode.SUCCEEDED: + assert isinstance(response, socksio.socks5.SOCKS5Reply) + if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED: reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN") raise ProxyError(f"Proxy Server could not connect: {reply_code}.") -class AsyncSOCKSProxy(AsyncConnectionPool): +class AsyncSOCKSProxy(AsyncConnectionPool): # pragma: nocover """ A connection pool that sends requests via an HTTP proxy. """ def __init__( self, - proxy_url: typing.Union[URL, bytes, str], - proxy_auth: typing.Optional[ - typing.Tuple[typing.Union[bytes, str], typing.Union[bytes, str]] - ] = None, - ssl_context: typing.Optional[ssl.SSLContext] = None, - max_connections: typing.Optional[int] = 10, - max_keepalive_connections: typing.Optional[int] = None, - keepalive_expiry: typing.Optional[float] = None, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - network_backend: typing.Optional[AsyncNetworkBackend] = None, + network_backend: AsyncNetworkBackend | None = None, ) -> None: """ A connection pool for making HTTP requests. @@ -167,7 +166,7 @@ class AsyncSOCKSProxy(AsyncConnectionPool): username, password = proxy_auth username_bytes = enforce_bytes(username, name="proxy_auth") password_bytes = enforce_bytes(password, name="proxy_auth") - self._proxy_auth: typing.Optional[typing.Tuple[bytes, bytes]] = ( + self._proxy_auth: tuple[bytes, bytes] | None = ( username_bytes, password_bytes, ) @@ -192,12 +191,12 @@ class AsyncSocks5Connection(AsyncConnectionInterface): self, proxy_origin: Origin, remote_origin: Origin, - proxy_auth: typing.Optional[typing.Tuple[bytes, bytes]] = None, - ssl_context: typing.Optional[ssl.SSLContext] = None, - keepalive_expiry: typing.Optional[float] = None, + proxy_auth: tuple[bytes, bytes] | None = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, - network_backend: typing.Optional[AsyncNetworkBackend] = None, + network_backend: AsyncNetworkBackend | None = None, ) -> None: self._proxy_origin = proxy_origin self._remote_origin = remote_origin @@ -211,7 +210,7 @@ class AsyncSocks5Connection(AsyncConnectionInterface): AutoBackend() if network_backend is None else network_backend ) self._connect_lock = AsyncLock() - self._connection: typing.Optional[AsyncConnectionInterface] = None + self._connection: AsyncConnectionInterface | None = None self._connect_failed = False async def handle_async_request(self, request: Request) -> Response: diff --git a/contrib/python/httpcore/httpcore/_backends/anyio.py b/contrib/python/httpcore/httpcore/_backends/anyio.py index d469e0084c..a140095e1b 100644 --- a/contrib/python/httpcore/httpcore/_backends/anyio.py +++ b/contrib/python/httpcore/httpcore/_backends/anyio.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ssl import typing @@ -20,9 +22,7 @@ class AnyIOStream(AsyncNetworkStream): def __init__(self, stream: anyio.abc.ByteStream) -> None: self._stream = stream - async def read( - self, max_bytes: int, timeout: typing.Optional[float] = None - ) -> bytes: + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: exc_map = { TimeoutError: ReadTimeout, anyio.BrokenResourceError: ReadError, @@ -36,9 +36,7 @@ class AnyIOStream(AsyncNetworkStream): except anyio.EndOfStream: # pragma: nocover return b"" - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: float | None = None) -> None: if not buffer: return @@ -57,8 +55,8 @@ class AnyIOStream(AsyncNetworkStream): async def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: typing.Optional[str] = None, - timeout: typing.Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ) -> AsyncNetworkStream: exc_map = { TimeoutError: ConnectTimeout, @@ -101,9 +99,9 @@ class AnyIOBackend(AsyncNetworkBackend): self, host: str, port: int, - timeout: typing.Optional[float] = None, - local_address: typing.Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: # pragma: nocover if socket_options is None: socket_options = [] @@ -127,8 +125,8 @@ class AnyIOBackend(AsyncNetworkBackend): async def connect_unix_socket( self, path: str, - timeout: typing.Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: # pragma: nocover if socket_options is None: socket_options = [] diff --git a/contrib/python/httpcore/httpcore/_backends/auto.py b/contrib/python/httpcore/httpcore/_backends/auto.py index 3ac05f4da0..49f0e698c9 100644 --- a/contrib/python/httpcore/httpcore/_backends/auto.py +++ b/contrib/python/httpcore/httpcore/_backends/auto.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import typing -from typing import Optional from .._synchronization import current_async_library from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream @@ -22,9 +23,9 @@ class AutoBackend(AsyncNetworkBackend): self, host: str, port: int, - timeout: Optional[float] = None, - local_address: Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: await self._init_backend() return await self._backend.connect_tcp( @@ -38,8 +39,8 @@ class AutoBackend(AsyncNetworkBackend): async def connect_unix_socket( self, path: str, - timeout: Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: # pragma: nocover await self._init_backend() return await self._backend.connect_unix_socket( diff --git a/contrib/python/httpcore/httpcore/_backends/base.py b/contrib/python/httpcore/httpcore/_backends/base.py index 6cadedb5f9..cf55c8b10e 100644 --- a/contrib/python/httpcore/httpcore/_backends/base.py +++ b/contrib/python/httpcore/httpcore/_backends/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ssl import time import typing @@ -10,10 +12,10 @@ SOCKET_OPTION = typing.Union[ class NetworkStream: - def read(self, max_bytes: int, timeout: typing.Optional[float] = None) -> bytes: + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: raise NotImplementedError() # pragma: nocover - def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: + def write(self, buffer: bytes, timeout: float | None = None) -> None: raise NotImplementedError() # pragma: nocover def close(self) -> None: @@ -22,9 +24,9 @@ class NetworkStream: def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: typing.Optional[str] = None, - timeout: typing.Optional[float] = None, - ) -> "NetworkStream": + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: raise NotImplementedError() # pragma: nocover def get_extra_info(self, info: str) -> typing.Any: @@ -36,17 +38,17 @@ class NetworkBackend: self, host: str, port: int, - timeout: typing.Optional[float] = None, - local_address: typing.Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> NetworkStream: raise NotImplementedError() # pragma: nocover def connect_unix_socket( self, path: str, - timeout: typing.Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> NetworkStream: raise NotImplementedError() # pragma: nocover @@ -55,14 +57,10 @@ class NetworkBackend: class AsyncNetworkStream: - async def read( - self, max_bytes: int, timeout: typing.Optional[float] = None - ) -> bytes: + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: raise NotImplementedError() # pragma: nocover - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: float | None = None) -> None: raise NotImplementedError() # pragma: nocover async def aclose(self) -> None: @@ -71,9 +69,9 @@ class AsyncNetworkStream: async def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: typing.Optional[str] = None, - timeout: typing.Optional[float] = None, - ) -> "AsyncNetworkStream": + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: raise NotImplementedError() # pragma: nocover def get_extra_info(self, info: str) -> typing.Any: @@ -85,17 +83,17 @@ class AsyncNetworkBackend: self, host: str, port: int, - timeout: typing.Optional[float] = None, - local_address: typing.Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: raise NotImplementedError() # pragma: nocover async def connect_unix_socket( self, path: str, - timeout: typing.Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: raise NotImplementedError() # pragma: nocover diff --git a/contrib/python/httpcore/httpcore/_backends/mock.py b/contrib/python/httpcore/httpcore/_backends/mock.py index f7aefebf51..9b6edca03d 100644 --- a/contrib/python/httpcore/httpcore/_backends/mock.py +++ b/contrib/python/httpcore/httpcore/_backends/mock.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import ssl import typing -from typing import Optional from .._exceptions import ReadError from .base import ( @@ -21,19 +22,19 @@ class MockSSLObject: class MockStream(NetworkStream): - def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: self._buffer = buffer self._http2 = http2 self._closed = False - def read(self, max_bytes: int, timeout: Optional[float] = None) -> bytes: + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: if self._closed: raise ReadError("Connection closed") if not self._buffer: return b"" return self._buffer.pop(0) - def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + def write(self, buffer: bytes, timeout: float | None = None) -> None: pass def close(self) -> None: @@ -42,8 +43,8 @@ class MockStream(NetworkStream): def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: Optional[str] = None, - timeout: Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ) -> NetworkStream: return self @@ -55,7 +56,7 @@ class MockStream(NetworkStream): class MockBackend(NetworkBackend): - def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: self._buffer = buffer self._http2 = http2 @@ -63,17 +64,17 @@ class MockBackend(NetworkBackend): self, host: str, port: int, - timeout: Optional[float] = None, - local_address: Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> NetworkStream: return MockStream(list(self._buffer), http2=self._http2) def connect_unix_socket( self, path: str, - timeout: Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> NetworkStream: return MockStream(list(self._buffer), http2=self._http2) @@ -82,19 +83,19 @@ class MockBackend(NetworkBackend): class AsyncMockStream(AsyncNetworkStream): - def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: self._buffer = buffer self._http2 = http2 self._closed = False - async def read(self, max_bytes: int, timeout: Optional[float] = None) -> bytes: + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: if self._closed: raise ReadError("Connection closed") if not self._buffer: return b"" return self._buffer.pop(0) - async def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + async def write(self, buffer: bytes, timeout: float | None = None) -> None: pass async def aclose(self) -> None: @@ -103,8 +104,8 @@ class AsyncMockStream(AsyncNetworkStream): async def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: Optional[str] = None, - timeout: Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ) -> AsyncNetworkStream: return self @@ -116,7 +117,7 @@ class AsyncMockStream(AsyncNetworkStream): class AsyncMockBackend(AsyncNetworkBackend): - def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: self._buffer = buffer self._http2 = http2 @@ -124,17 +125,17 @@ class AsyncMockBackend(AsyncNetworkBackend): self, host: str, port: int, - timeout: Optional[float] = None, - local_address: Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: return AsyncMockStream(list(self._buffer), http2=self._http2) async def connect_unix_socket( self, path: str, - timeout: Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: return AsyncMockStream(list(self._buffer), http2=self._http2) diff --git a/contrib/python/httpcore/httpcore/_backends/sync.py b/contrib/python/httpcore/httpcore/_backends/sync.py index 7b7b417dc1..4018a09c6f 100644 --- a/contrib/python/httpcore/httpcore/_backends/sync.py +++ b/contrib/python/httpcore/httpcore/_backends/sync.py @@ -1,8 +1,10 @@ +from __future__ import annotations + +import functools import socket import ssl import sys import typing -from functools import partial from .._exceptions import ( ConnectError, @@ -33,8 +35,8 @@ class TLSinTLSStream(NetworkStream): # pragma: no cover self, sock: socket.socket, ssl_context: ssl.SSLContext, - server_hostname: typing.Optional[str] = None, - timeout: typing.Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ): self._sock = sock self._incoming = ssl.MemoryBIO() @@ -74,20 +76,20 @@ class TLSinTLSStream(NetworkStream): # pragma: no cover if errno is None: return ret - def read(self, max_bytes: int, timeout: typing.Optional[float] = None) -> bytes: + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError} with map_exceptions(exc_map): self._sock.settimeout(timeout) return typing.cast( - bytes, self._perform_io(partial(self.ssl_obj.read, max_bytes)) + bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes)) ) - def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: + def write(self, buffer: bytes, timeout: float | None = None) -> None: exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError} with map_exceptions(exc_map): self._sock.settimeout(timeout) while buffer: - nsent = self._perform_io(partial(self.ssl_obj.write, buffer)) + nsent = self._perform_io(functools.partial(self.ssl_obj.write, buffer)) buffer = buffer[nsent:] def close(self) -> None: @@ -96,9 +98,9 @@ class TLSinTLSStream(NetworkStream): # pragma: no cover def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: typing.Optional[str] = None, - timeout: typing.Optional[float] = None, - ) -> "NetworkStream": + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: raise NotImplementedError() def get_extra_info(self, info: str) -> typing.Any: @@ -119,13 +121,13 @@ class SyncStream(NetworkStream): def __init__(self, sock: socket.socket) -> None: self._sock = sock - def read(self, max_bytes: int, timeout: typing.Optional[float] = None) -> bytes: + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError} with map_exceptions(exc_map): self._sock.settimeout(timeout) return self._sock.recv(max_bytes) - def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: + def write(self, buffer: bytes, timeout: float | None = None) -> None: if not buffer: return @@ -142,8 +144,8 @@ class SyncStream(NetworkStream): def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: typing.Optional[str] = None, - timeout: typing.Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ) -> NetworkStream: exc_map: ExceptionMapping = { socket.timeout: ConnectTimeout, @@ -187,9 +189,9 @@ class SyncBackend(NetworkBackend): self, host: str, port: int, - timeout: typing.Optional[float] = None, - local_address: typing.Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> NetworkStream: # Note that we automatically include `TCP_NODELAY` # in addition to any other custom socket options. @@ -216,8 +218,8 @@ class SyncBackend(NetworkBackend): def connect_unix_socket( self, path: str, - timeout: typing.Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> NetworkStream: # pragma: nocover if sys.platform == "win32": raise RuntimeError( diff --git a/contrib/python/httpcore/httpcore/_backends/trio.py b/contrib/python/httpcore/httpcore/_backends/trio.py index b1626d28e2..6f53f5f2a0 100644 --- a/contrib/python/httpcore/httpcore/_backends/trio.py +++ b/contrib/python/httpcore/httpcore/_backends/trio.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ssl import typing @@ -20,9 +22,7 @@ class TrioStream(AsyncNetworkStream): def __init__(self, stream: trio.abc.Stream) -> None: self._stream = stream - async def read( - self, max_bytes: int, timeout: typing.Optional[float] = None - ) -> bytes: + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: timeout_or_inf = float("inf") if timeout is None else timeout exc_map: ExceptionMapping = { trio.TooSlowError: ReadTimeout, @@ -34,9 +34,7 @@ class TrioStream(AsyncNetworkStream): data: bytes = await self._stream.receive_some(max_bytes=max_bytes) return data - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: float | None = None) -> None: if not buffer: return @@ -56,8 +54,8 @@ class TrioStream(AsyncNetworkStream): async def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: typing.Optional[str] = None, - timeout: typing.Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ) -> AsyncNetworkStream: timeout_or_inf = float("inf") if timeout is None else timeout exc_map: ExceptionMapping = { @@ -113,9 +111,9 @@ class TrioBackend(AsyncNetworkBackend): self, host: str, port: int, - timeout: typing.Optional[float] = None, - local_address: typing.Optional[str] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: # By default for TCP sockets, trio enables TCP_NODELAY. # https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream @@ -139,8 +137,8 @@ class TrioBackend(AsyncNetworkBackend): async def connect_unix_socket( self, path: str, - timeout: typing.Optional[float] = None, - socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: # pragma: nocover if socket_options is None: socket_options = [] diff --git a/contrib/python/httpcore/httpcore/_exceptions.py b/contrib/python/httpcore/httpcore/_exceptions.py index 81e7fc61dd..bc28d44f55 100644 --- a/contrib/python/httpcore/httpcore/_exceptions.py +++ b/contrib/python/httpcore/httpcore/_exceptions.py @@ -1,11 +1,11 @@ import contextlib -from typing import Iterator, Mapping, Type +import typing -ExceptionMapping = Mapping[Type[Exception], Type[Exception]] +ExceptionMapping = typing.Mapping[typing.Type[Exception], typing.Type[Exception]] @contextlib.contextmanager -def map_exceptions(map: ExceptionMapping) -> Iterator[None]: +def map_exceptions(map: ExceptionMapping) -> typing.Iterator[None]: try: yield except Exception as exc: # noqa: PIE786 diff --git a/contrib/python/httpcore/httpcore/_models.py b/contrib/python/httpcore/httpcore/_models.py index dadee79f69..8a65f13347 100644 --- a/contrib/python/httpcore/httpcore/_models.py +++ b/contrib/python/httpcore/httpcore/_models.py @@ -1,30 +1,22 @@ -from typing import ( - Any, - AsyncIterable, - AsyncIterator, - Iterable, - Iterator, - List, - Mapping, - MutableMapping, - Optional, - Sequence, - Tuple, - Union, -) -from urllib.parse import urlparse +from __future__ import annotations + +import base64 +import ssl +import typing +import urllib.parse # Functions for typechecking... -HeadersAsSequence = Sequence[Tuple[Union[bytes, str], Union[bytes, str]]] -HeadersAsMapping = Mapping[Union[bytes, str], Union[bytes, str]] -HeaderTypes = Union[HeadersAsSequence, HeadersAsMapping, None] +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] +HeaderTypes = typing.Union[HeadersAsSequence, HeadersAsMapping, None] -Extensions = MutableMapping[str, Any] +Extensions = typing.MutableMapping[str, typing.Any] -def enforce_bytes(value: Union[bytes, str], *, name: str) -> bytes: +def enforce_bytes(value: bytes | str, *, name: str) -> bytes: """ Any arguments that are ultimately represented as bytes can be specified either as bytes or as strings. @@ -45,7 +37,7 @@ def enforce_bytes(value: Union[bytes, str], *, name: str) -> bytes: raise TypeError(f"{name} must be bytes or str, but got {seen_type}.") -def enforce_url(value: Union["URL", bytes, str], *, name: str) -> "URL": +def enforce_url(value: URL | bytes | str, *, name: str) -> URL: """ Type check for URL parameters. """ @@ -59,15 +51,15 @@ def enforce_url(value: Union["URL", bytes, str], *, name: str) -> "URL": def enforce_headers( - value: Union[HeadersAsMapping, HeadersAsSequence, None] = None, *, name: str -) -> List[Tuple[bytes, bytes]]: + value: HeadersAsMapping | HeadersAsSequence | None = None, *, name: str +) -> list[tuple[bytes, bytes]]: """ Convienence function that ensure all items in request or response headers are either bytes or strings in the plain ASCII range. """ if value is None: return [] - elif isinstance(value, Mapping): + elif isinstance(value, typing.Mapping): return [ ( enforce_bytes(k, name="header name"), @@ -75,7 +67,7 @@ def enforce_headers( ) for k, v in value.items() ] - elif isinstance(value, Sequence): + elif isinstance(value, typing.Sequence): return [ ( enforce_bytes(k, name="header name"), @@ -91,8 +83,10 @@ def enforce_headers( def enforce_stream( - value: Union[bytes, Iterable[bytes], AsyncIterable[bytes], None], *, name: str -) -> Union[Iterable[bytes], AsyncIterable[bytes]]: + value: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None, + *, + name: str, +) -> typing.Iterable[bytes] | typing.AsyncIterable[bytes]: if value is None: return ByteStream(b"") elif isinstance(value, bytes): @@ -113,11 +107,11 @@ DEFAULT_PORTS = { def include_request_headers( - headers: List[Tuple[bytes, bytes]], + headers: list[tuple[bytes, bytes]], *, url: "URL", - content: Union[None, bytes, Iterable[bytes], AsyncIterable[bytes]], -) -> List[Tuple[bytes, bytes]]: + content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes], +) -> list[tuple[bytes, bytes]]: headers_set = set(k.lower() for k, v in headers) if b"host" not in headers_set: @@ -154,10 +148,10 @@ class ByteStream: def __init__(self, content: bytes) -> None: self._content = content - def __iter__(self) -> Iterator[bytes]: + def __iter__(self) -> typing.Iterator[bytes]: yield self._content - async def __aiter__(self) -> AsyncIterator[bytes]: + async def __aiter__(self) -> typing.AsyncIterator[bytes]: yield self._content def __repr__(self) -> str: @@ -170,7 +164,7 @@ class Origin: self.host = host self.port = port - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: typing.Any) -> bool: return ( isinstance(other, Origin) and self.scheme == other.scheme @@ -254,12 +248,12 @@ class URL: def __init__( self, - url: Union[bytes, str] = "", + url: bytes | str = "", *, - scheme: Union[bytes, str] = b"", - host: Union[bytes, str] = b"", - port: Optional[int] = None, - target: Union[bytes, str] = b"", + scheme: bytes | str = b"", + host: bytes | str = b"", + port: int | None = None, + target: bytes | str = b"", ) -> None: """ Parameters: @@ -271,7 +265,7 @@ class URL: target: The target of the HTTP request. Such as `"/items?search=red"`. """ if url: - parsed = urlparse(enforce_bytes(url, name="url")) + parsed = urllib.parse.urlparse(enforce_bytes(url, name="url")) self.scheme = parsed.scheme self.host = parsed.hostname or b"" self.port = parsed.port @@ -292,12 +286,13 @@ class URL: b"ws": 80, b"wss": 443, b"socks5": 1080, + b"socks5h": 1080, }[self.scheme] return Origin( scheme=self.scheme, host=self.host, port=self.port or default_port ) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: typing.Any) -> bool: return ( isinstance(other, URL) and other.scheme == self.scheme @@ -325,12 +320,15 @@ class Request: def __init__( self, - method: Union[bytes, str], - url: Union[URL, bytes, str], + method: bytes | str, + url: URL | bytes | str, *, headers: HeaderTypes = None, - content: Union[bytes, Iterable[bytes], AsyncIterable[bytes], None] = None, - extensions: Optional[Extensions] = None, + content: bytes + | typing.Iterable[bytes] + | typing.AsyncIterable[bytes] + | None = None, + extensions: Extensions | None = None, ) -> None: """ Parameters: @@ -345,11 +343,11 @@ class Request: """ self.method: bytes = enforce_bytes(method, name="method") self.url: URL = enforce_url(url, name="url") - self.headers: List[Tuple[bytes, bytes]] = enforce_headers( + self.headers: list[tuple[bytes, bytes]] = enforce_headers( headers, name="headers" ) - self.stream: Union[Iterable[bytes], AsyncIterable[bytes]] = enforce_stream( - content, name="content" + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( + enforce_stream(content, name="content") ) self.extensions = {} if extensions is None else extensions @@ -375,8 +373,11 @@ class Response: status: int, *, headers: HeaderTypes = None, - content: Union[bytes, Iterable[bytes], AsyncIterable[bytes], None] = None, - extensions: Optional[Extensions] = None, + content: bytes + | typing.Iterable[bytes] + | typing.AsyncIterable[bytes] + | None = None, + extensions: Extensions | None = None, ) -> None: """ Parameters: @@ -388,11 +389,11 @@ class Response: `"reason_phrase"`, and `"network_stream"`. """ self.status: int = status - self.headers: List[Tuple[bytes, bytes]] = enforce_headers( + self.headers: list[tuple[bytes, bytes]] = enforce_headers( headers, name="headers" ) - self.stream: Union[Iterable[bytes], AsyncIterable[bytes]] = enforce_stream( - content, name="content" + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( + enforce_stream(content, name="content") ) self.extensions = {} if extensions is None else extensions @@ -401,7 +402,7 @@ class Response: @property def content(self) -> bytes: if not hasattr(self, "_content"): - if isinstance(self.stream, Iterable): + if isinstance(self.stream, typing.Iterable): raise RuntimeError( "Attempted to access 'response.content' on a streaming response. " "Call 'response.read()' first." @@ -419,7 +420,7 @@ class Response: # Sync interface... def read(self) -> bytes: - if not isinstance(self.stream, Iterable): # pragma: nocover + if not isinstance(self.stream, typing.Iterable): # pragma: nocover raise RuntimeError( "Attempted to read an asynchronous response using 'response.read()'. " "You should use 'await response.aread()' instead." @@ -428,8 +429,8 @@ class Response: self._content = b"".join([part for part in self.iter_stream()]) return self._content - def iter_stream(self) -> Iterator[bytes]: - if not isinstance(self.stream, Iterable): # pragma: nocover + def iter_stream(self) -> typing.Iterator[bytes]: + if not isinstance(self.stream, typing.Iterable): # pragma: nocover raise RuntimeError( "Attempted to stream an asynchronous response using 'for ... in " "response.iter_stream()'. " @@ -444,7 +445,7 @@ class Response: yield chunk def close(self) -> None: - if not isinstance(self.stream, Iterable): # pragma: nocover + if not isinstance(self.stream, typing.Iterable): # pragma: nocover raise RuntimeError( "Attempted to close an asynchronous response using 'response.close()'. " "You should use 'await response.aclose()' instead." @@ -455,7 +456,7 @@ class Response: # Async interface... async def aread(self) -> bytes: - if not isinstance(self.stream, AsyncIterable): # pragma: nocover + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover raise RuntimeError( "Attempted to read an synchronous response using " "'await response.aread()'. " @@ -465,8 +466,8 @@ class Response: self._content = b"".join([part async for part in self.aiter_stream()]) return self._content - async def aiter_stream(self) -> AsyncIterator[bytes]: - if not isinstance(self.stream, AsyncIterable): # pragma: nocover + async def aiter_stream(self) -> typing.AsyncIterator[bytes]: + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover raise RuntimeError( "Attempted to stream an synchronous response using 'async for ... in " "response.aiter_stream()'. " @@ -482,7 +483,7 @@ class Response: yield chunk async def aclose(self) -> None: - if not isinstance(self.stream, AsyncIterable): # pragma: nocover + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover raise RuntimeError( "Attempted to close a synchronous response using " "'await response.aclose()'. " @@ -490,3 +491,26 @@ class Response: ) if hasattr(self.stream, "aclose"): await self.stream.aclose() + + +class Proxy: + def __init__( + self, + url: URL | bytes | str, + auth: tuple[bytes | str, bytes | str] | None = None, + headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + ): + self.url = enforce_url(url, name="url") + self.headers = enforce_headers(headers, name="headers") + self.ssl_context = ssl_context + + if auth is not None: + username = enforce_bytes(auth[0], name="auth") + password = enforce_bytes(auth[1], name="auth") + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) + self.auth: tuple[bytes, bytes] | None = (username, password) + self.headers = [(b"Proxy-Authorization", authorization)] + self.headers + else: + self.auth = None diff --git a/contrib/python/httpcore/httpcore/_sync/connection.py b/contrib/python/httpcore/httpcore/_sync/connection.py index c3890f340c..363f8be819 100644 --- a/contrib/python/httpcore/httpcore/_sync/connection.py +++ b/contrib/python/httpcore/httpcore/_sync/connection.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import itertools import logging import ssl -from types import TracebackType -from typing import Iterable, Iterator, Optional, Type +import types +import typing from .._backends.sync import SyncBackend from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream @@ -20,7 +22,7 @@ RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc. logger = logging.getLogger("httpcore.connection") -def exponential_backoff(factor: float) -> Iterator[float]: +def exponential_backoff(factor: float) -> typing.Iterator[float]: """ Generate a geometric sequence that has a ratio of 2 and starts with 0. @@ -37,15 +39,15 @@ class HTTPConnection(ConnectionInterface): def __init__( self, origin: Origin, - ssl_context: Optional[ssl.SSLContext] = None, - keepalive_expiry: Optional[float] = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - local_address: Optional[str] = None, - uds: Optional[str] = None, - network_backend: Optional[NetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: self._origin = origin self._ssl_context = ssl_context @@ -59,7 +61,7 @@ class HTTPConnection(ConnectionInterface): self._network_backend: NetworkBackend = ( SyncBackend() if network_backend is None else network_backend ) - self._connection: Optional[ConnectionInterface] = None + self._connection: ConnectionInterface | None = None self._connect_failed: bool = False self._request_lock = Lock() self._socket_options = socket_options @@ -208,13 +210,13 @@ class HTTPConnection(ConnectionInterface): # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. - def __enter__(self) -> "HTTPConnection": + def __enter__(self) -> HTTPConnection: return self def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: self.close() diff --git a/contrib/python/httpcore/httpcore/_sync/connection_pool.py b/contrib/python/httpcore/httpcore/_sync/connection_pool.py index 01bec59e88..9ccfa53e59 100644 --- a/contrib/python/httpcore/httpcore/_sync/connection_pool.py +++ b/contrib/python/httpcore/httpcore/_sync/connection_pool.py @@ -1,12 +1,14 @@ +from __future__ import annotations + import ssl import sys -from types import TracebackType -from typing import Iterable, Iterator, Iterable, List, Optional, Type +import types +import typing from .._backends.sync import SyncBackend from .._backends.base import SOCKET_OPTION, NetworkBackend from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol -from .._models import Origin, Request, Response +from .._models import Origin, Proxy, Request, Response from .._synchronization import Event, ShieldCancellation, ThreadLock from .connection import HTTPConnection from .interfaces import ConnectionInterface, RequestInterface @@ -15,12 +17,10 @@ from .interfaces import ConnectionInterface, RequestInterface class PoolRequest: def __init__(self, request: Request) -> None: self.request = request - self.connection: Optional[ConnectionInterface] = None + self.connection: ConnectionInterface | None = None self._connection_acquired = Event() - def assign_to_connection( - self, connection: Optional[ConnectionInterface] - ) -> None: + def assign_to_connection(self, connection: ConnectionInterface | None) -> None: self.connection = connection self._connection_acquired.set() @@ -29,7 +29,7 @@ class PoolRequest: self._connection_acquired = Event() def wait_for_connection( - self, timeout: Optional[float] = None + self, timeout: float | None = None ) -> ConnectionInterface: if self.connection is None: self._connection_acquired.wait(timeout=timeout) @@ -47,17 +47,18 @@ class ConnectionPool(RequestInterface): def __init__( self, - ssl_context: Optional[ssl.SSLContext] = None, - max_connections: Optional[int] = 10, - max_keepalive_connections: Optional[int] = None, - keepalive_expiry: Optional[float] = None, + ssl_context: ssl.SSLContext | None = None, + proxy: Proxy | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - local_address: Optional[str] = None, - uds: Optional[str] = None, - network_backend: Optional[NetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: """ A connection pool for making HTTP requests. @@ -89,7 +90,7 @@ class ConnectionPool(RequestInterface): in the TCP socket when the connection was established. """ self._ssl_context = ssl_context - + self._proxy = proxy self._max_connections = ( sys.maxsize if max_connections is None else max_connections ) @@ -116,8 +117,8 @@ class ConnectionPool(RequestInterface): # The mutable state on a connection pool is the queue of incoming requests, # and the set of connections that are servicing those requests. - self._connections: List[ConnectionInterface] = [] - self._requests: List[PoolRequest] = [] + self._connections: list[ConnectionInterface] = [] + self._requests: list[PoolRequest] = [] # We only mutate the state of the connection pool within an 'optional_thread_lock' # context. This holds a threading lock unless we're running in async mode, @@ -125,6 +126,45 @@ class ConnectionPool(RequestInterface): self._optional_thread_lock = ThreadLock() def create_connection(self, origin: Origin) -> ConnectionInterface: + if self._proxy is not None: + if self._proxy.url.scheme in (b"socks5", b"socks5h"): + from .socks_proxy import Socks5Connection + + return Socks5Connection( + proxy_origin=self._proxy.url.origin, + proxy_auth=self._proxy.auth, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + elif origin.scheme == b"http": + from .http_proxy import ForwardHTTPConnection + + return ForwardHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + ) + from .http_proxy import TunnelHTTPConnection + + return TunnelHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + return HTTPConnection( origin=origin, ssl_context=self._ssl_context, @@ -139,7 +179,7 @@ class ConnectionPool(RequestInterface): ) @property - def connections(self) -> List[ConnectionInterface]: + def connections(self) -> list[ConnectionInterface]: """ Return a list of the connections currently in the pool. @@ -217,7 +257,7 @@ class ConnectionPool(RequestInterface): # Return the response. Note that in this case we still have to manage # the point at which the response is closed. - assert isinstance(response.stream, Iterable) + assert isinstance(response.stream, typing.Iterable) return Response( status=response.status, headers=response.headers, @@ -227,7 +267,7 @@ class ConnectionPool(RequestInterface): extensions=response.extensions, ) - def _assign_requests_to_connections(self) -> List[ConnectionInterface]: + def _assign_requests_to_connections(self) -> list[ConnectionInterface]: """ Manage the state of the connection pool, assigning incoming requests to connections as available. @@ -298,7 +338,7 @@ class ConnectionPool(RequestInterface): return closing_connections - def _close_connections(self, closing: List[ConnectionInterface]) -> None: + def _close_connections(self, closing: list[ConnectionInterface]) -> None: # Close connections which have been removed from the pool. with ShieldCancellation(): for connection in closing: @@ -312,14 +352,14 @@ class ConnectionPool(RequestInterface): self._connections = [] self._close_connections(closing_connections) - def __enter__(self) -> "ConnectionPool": + def __enter__(self) -> ConnectionPool: return self def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: self.close() @@ -349,7 +389,7 @@ class ConnectionPool(RequestInterface): class PoolByteStream: def __init__( self, - stream: Iterable[bytes], + stream: typing.Iterable[bytes], pool_request: PoolRequest, pool: ConnectionPool, ) -> None: @@ -358,7 +398,7 @@ class PoolByteStream: self._pool = pool self._closed = False - def __iter__(self) -> Iterator[bytes]: + def __iter__(self) -> typing.Iterator[bytes]: try: for part in self._stream: yield part diff --git a/contrib/python/httpcore/httpcore/_sync/http11.py b/contrib/python/httpcore/httpcore/_sync/http11.py index a74ff8e809..ebd3a97480 100644 --- a/contrib/python/httpcore/httpcore/_sync/http11.py +++ b/contrib/python/httpcore/httpcore/_sync/http11.py @@ -1,18 +1,11 @@ +from __future__ import annotations + import enum import logging import ssl import time -from types import TracebackType -from typing import ( - Any, - Iterable, - Iterator, - List, - Optional, - Tuple, - Type, - Union, -) +import types +import typing import h11 @@ -33,7 +26,7 @@ logger = logging.getLogger("httpcore.http11") # A subset of `h11.Event` types supported by `_send_event` -H11SendEvent = Union[ +H11SendEvent = typing.Union[ h11.Request, h11.Data, h11.EndOfMessage, @@ -55,12 +48,12 @@ class HTTP11Connection(ConnectionInterface): self, origin: Origin, stream: NetworkStream, - keepalive_expiry: Optional[float] = None, + keepalive_expiry: float | None = None, ) -> None: self._origin = origin self._network_stream = stream - self._keepalive_expiry: Optional[float] = keepalive_expiry - self._expire_at: Optional[float] = None + self._keepalive_expiry: float | None = keepalive_expiry + self._expire_at: float | None = None self._state = HTTPConnectionState.NEW self._state_lock = Lock() self._request_count = 0 @@ -160,16 +153,14 @@ class HTTP11Connection(ConnectionInterface): timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("write", None) - assert isinstance(request.stream, Iterable) + assert isinstance(request.stream, typing.Iterable) for chunk in request.stream: event = h11.Data(data=chunk) self._send_event(event, timeout=timeout) self._send_event(h11.EndOfMessage(), timeout=timeout) - def _send_event( - self, event: h11.Event, timeout: Optional[float] = None - ) -> None: + def _send_event(self, event: h11.Event, timeout: float | None = None) -> None: bytes_to_send = self._h11_state.send(event) if bytes_to_send is not None: self._network_stream.write(bytes_to_send, timeout=timeout) @@ -178,7 +169,7 @@ class HTTP11Connection(ConnectionInterface): def _receive_response_headers( self, request: Request - ) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], bytes]: + ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("read", None) @@ -202,7 +193,9 @@ class HTTP11Connection(ConnectionInterface): return http_version, event.status_code, event.reason, headers, trailing_data - def _receive_response_body(self, request: Request) -> Iterator[bytes]: + def _receive_response_body( + self, request: Request + ) -> typing.Iterator[bytes]: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("read", None) @@ -214,8 +207,8 @@ class HTTP11Connection(ConnectionInterface): break def _receive_event( - self, timeout: Optional[float] = None - ) -> Union[h11.Event, Type[h11.PAUSED]]: + self, timeout: float | None = None + ) -> h11.Event | type[h11.PAUSED]: while True: with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): event = self._h11_state.next_event() @@ -316,14 +309,14 @@ class HTTP11Connection(ConnectionInterface): # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. - def __enter__(self) -> "HTTP11Connection": + def __enter__(self) -> HTTP11Connection: return self def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: self.close() @@ -334,7 +327,7 @@ class HTTP11ConnectionByteStream: self._request = request self._closed = False - def __iter__(self) -> Iterator[bytes]: + def __iter__(self) -> typing.Iterator[bytes]: kwargs = {"request": self._request} try: with Trace("receive_response_body", logger, self._request, kwargs): @@ -360,7 +353,7 @@ class HTTP11UpgradeStream(NetworkStream): self._stream = stream self._leading_data = leading_data - def read(self, max_bytes: int, timeout: Optional[float] = None) -> bytes: + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: if self._leading_data: buffer = self._leading_data[:max_bytes] self._leading_data = self._leading_data[max_bytes:] @@ -368,7 +361,7 @@ class HTTP11UpgradeStream(NetworkStream): else: return self._stream.read(max_bytes, timeout) - def write(self, buffer: bytes, timeout: Optional[float] = None) -> None: + def write(self, buffer: bytes, timeout: float | None = None) -> None: self._stream.write(buffer, timeout) def close(self) -> None: @@ -377,10 +370,10 @@ class HTTP11UpgradeStream(NetworkStream): def start_tls( self, ssl_context: ssl.SSLContext, - server_hostname: Optional[str] = None, - timeout: Optional[float] = None, + server_hostname: str | None = None, + timeout: float | None = None, ) -> NetworkStream: return self._stream.start_tls(ssl_context, server_hostname, timeout) - def get_extra_info(self, info: str) -> Any: + def get_extra_info(self, info: str) -> typing.Any: return self._stream.get_extra_info(info) diff --git a/contrib/python/httpcore/httpcore/_sync/http2.py b/contrib/python/httpcore/httpcore/_sync/http2.py index 1ee4bbb34f..ca4dd72432 100644 --- a/contrib/python/httpcore/httpcore/_sync/http2.py +++ b/contrib/python/httpcore/httpcore/_sync/http2.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import enum import logging import time @@ -45,14 +47,14 @@ class HTTP2Connection(ConnectionInterface): self, origin: Origin, stream: NetworkStream, - keepalive_expiry: typing.Optional[float] = None, + keepalive_expiry: float | None = None, ): self._origin = origin self._network_stream = stream - self._keepalive_expiry: typing.Optional[float] = keepalive_expiry + self._keepalive_expiry: float | None = keepalive_expiry self._h2_state = h2.connection.H2Connection(config=self.CONFIG) self._state = HTTPConnectionState.IDLE - self._expire_at: typing.Optional[float] = None + self._expire_at: float | None = None self._request_count = 0 self._init_lock = Lock() self._state_lock = Lock() @@ -63,24 +65,20 @@ class HTTP2Connection(ConnectionInterface): self._connection_error = False # Mapping from stream ID to response stream events. - self._events: typing.Dict[ + self._events: dict[ int, - typing.Union[ - h2.events.ResponseReceived, - h2.events.DataReceived, - h2.events.StreamEnded, - h2.events.StreamReset, - ], + h2.events.ResponseReceived + | h2.events.DataReceived + | h2.events.StreamEnded + | h2.events.StreamReset, ] = {} # Connection terminated events are stored as state since # we need to handle them for all streams. - self._connection_terminated: typing.Optional[h2.events.ConnectionTerminated] = ( - None - ) + self._connection_terminated: h2.events.ConnectionTerminated | None = None - self._read_exception: typing.Optional[Exception] = None - self._write_exception: typing.Optional[Exception] = None + self._read_exception: Exception | None = None + self._write_exception: Exception | None = None def handle_request(self, request: Request) -> Response: if not self.can_handle_request(request.url.origin): @@ -284,7 +282,7 @@ class HTTP2Connection(ConnectionInterface): def _receive_response( self, request: Request, stream_id: int - ) -> typing.Tuple[int, typing.List[typing.Tuple[bytes, bytes]]]: + ) -> tuple[int, list[tuple[bytes, bytes]]]: """ Return the response status code and headers for a given stream ID. """ @@ -321,9 +319,7 @@ class HTTP2Connection(ConnectionInterface): def _receive_stream_event( self, request: Request, stream_id: int - ) -> typing.Union[ - h2.events.ResponseReceived, h2.events.DataReceived, h2.events.StreamEnded - ]: + ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded: """ Return the next available event for a given stream ID. @@ -337,7 +333,7 @@ class HTTP2Connection(ConnectionInterface): return event def _receive_events( - self, request: Request, stream_id: typing.Optional[int] = None + self, request: Request, stream_id: int | None = None ) -> None: """ Read some data from the network until we see one or more events @@ -425,9 +421,7 @@ class HTTP2Connection(ConnectionInterface): # Wrappers around network read/write operations... - def _read_incoming_data( - self, request: Request - ) -> typing.List[h2.events.Event]: + def _read_incoming_data(self, request: Request) -> list[h2.events.Event]: timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("read", None) @@ -451,7 +445,7 @@ class HTTP2Connection(ConnectionInterface): self._connection_error = True raise exc - events: typing.List[h2.events.Event] = self._h2_state.receive_data(data) + events: list[h2.events.Event] = self._h2_state.receive_data(data) return events @@ -544,14 +538,14 @@ class HTTP2Connection(ConnectionInterface): # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. - def __enter__(self) -> "HTTP2Connection": + def __enter__(self) -> HTTP2Connection: return self def __exit__( self, - exc_type: typing.Optional[typing.Type[BaseException]] = None, - exc_value: typing.Optional[BaseException] = None, - traceback: typing.Optional[types.TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: self.close() diff --git a/contrib/python/httpcore/httpcore/_sync/http_proxy.py b/contrib/python/httpcore/httpcore/_sync/http_proxy.py index 6acac9a7cd..ecca88f7dc 100644 --- a/contrib/python/httpcore/httpcore/_sync/http_proxy.py +++ b/contrib/python/httpcore/httpcore/_sync/http_proxy.py @@ -1,7 +1,9 @@ +from __future__ import annotations + +import base64 import logging import ssl -from base64 import b64encode -from typing import Iterable, List, Mapping, Optional, Sequence, Tuple, Union +import typing from .._backends.base import SOCKET_OPTION, NetworkBackend from .._exceptions import ProxyError @@ -22,17 +24,18 @@ from .connection_pool import ConnectionPool from .http11 import HTTP11Connection from .interfaces import ConnectionInterface -HeadersAsSequence = Sequence[Tuple[Union[bytes, str], Union[bytes, str]]] -HeadersAsMapping = Mapping[Union[bytes, str], Union[bytes, str]] +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] logger = logging.getLogger("httpcore.proxy") def merge_headers( - default_headers: Optional[Sequence[Tuple[bytes, bytes]]] = None, - override_headers: Optional[Sequence[Tuple[bytes, bytes]]] = None, -) -> List[Tuple[bytes, bytes]]: + default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, +) -> list[tuple[bytes, bytes]]: """ Append default_headers and override_headers, de-duplicating if a key exists in both cases. @@ -48,33 +51,28 @@ def merge_headers( return default_headers + override_headers -def build_auth_header(username: bytes, password: bytes) -> bytes: - userpass = username + b":" + password - return b"Basic " + b64encode(userpass) - - -class HTTPProxy(ConnectionPool): +class HTTPProxy(ConnectionPool): # pragma: nocover """ A connection pool that sends requests via an HTTP proxy. """ def __init__( self, - proxy_url: Union[URL, bytes, str], - proxy_auth: Optional[Tuple[Union[bytes, str], Union[bytes, str]]] = None, - proxy_headers: Union[HeadersAsMapping, HeadersAsSequence, None] = None, - ssl_context: Optional[ssl.SSLContext] = None, - proxy_ssl_context: Optional[ssl.SSLContext] = None, - max_connections: Optional[int] = 10, - max_keepalive_connections: Optional[int] = None, - keepalive_expiry: Optional[float] = None, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - local_address: Optional[str] = None, - uds: Optional[str] = None, - network_backend: Optional[NetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: """ A connection pool for making HTTP requests. @@ -139,7 +137,8 @@ class HTTPProxy(ConnectionPool): if proxy_auth is not None: username = enforce_bytes(proxy_auth[0], name="proxy_auth") password = enforce_bytes(proxy_auth[1], name="proxy_auth") - authorization = build_auth_header(username, password) + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) self._proxy_headers = [ (b"Proxy-Authorization", authorization) ] + self._proxy_headers @@ -172,11 +171,11 @@ class ForwardHTTPConnection(ConnectionInterface): self, proxy_origin: Origin, remote_origin: Origin, - proxy_headers: Union[HeadersAsMapping, HeadersAsSequence, None] = None, - keepalive_expiry: Optional[float] = None, - network_backend: Optional[NetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, - proxy_ssl_context: Optional[ssl.SSLContext] = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + keepalive_expiry: float | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, ) -> None: self._connection = HTTPConnection( origin=proxy_origin, @@ -236,14 +235,14 @@ class TunnelHTTPConnection(ConnectionInterface): self, proxy_origin: Origin, remote_origin: Origin, - ssl_context: Optional[ssl.SSLContext] = None, - proxy_ssl_context: Optional[ssl.SSLContext] = None, - proxy_headers: Optional[Sequence[Tuple[bytes, bytes]]] = None, - keepalive_expiry: Optional[float] = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, - network_backend: Optional[NetworkBackend] = None, - socket_options: Optional[Iterable[SOCKET_OPTION]] = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> None: self._connection: ConnectionInterface = HTTPConnection( origin=proxy_origin, diff --git a/contrib/python/httpcore/httpcore/_sync/interfaces.py b/contrib/python/httpcore/httpcore/_sync/interfaces.py index 5e95be1ec7..e673d4cc1b 100644 --- a/contrib/python/httpcore/httpcore/_sync/interfaces.py +++ b/contrib/python/httpcore/httpcore/_sync/interfaces.py @@ -1,5 +1,7 @@ -from contextlib import contextmanager -from typing import Iterator, Optional, Union +from __future__ import annotations + +import contextlib +import typing from .._models import ( URL, @@ -18,12 +20,12 @@ from .._models import ( class RequestInterface: def request( self, - method: Union[bytes, str], - url: Union[URL, bytes, str], + method: bytes | str, + url: URL | bytes | str, *, headers: HeaderTypes = None, - content: Union[bytes, Iterator[bytes], None] = None, - extensions: Optional[Extensions] = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, ) -> Response: # Strict type checking on our parameters. method = enforce_bytes(method, name="method") @@ -47,16 +49,16 @@ class RequestInterface: response.close() return response - @contextmanager + @contextlib.contextmanager def stream( self, - method: Union[bytes, str], - url: Union[URL, bytes, str], + method: bytes | str, + url: URL | bytes | str, *, headers: HeaderTypes = None, - content: Union[bytes, Iterator[bytes], None] = None, - extensions: Optional[Extensions] = None, - ) -> Iterator[Response]: + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> typing.Iterator[Response]: # Strict type checking on our parameters. method = enforce_bytes(method, name="method") url = enforce_url(url, name="url") diff --git a/contrib/python/httpcore/httpcore/_sync/socks_proxy.py b/contrib/python/httpcore/httpcore/_sync/socks_proxy.py index 502e4d7fef..0ca96ddfb5 100644 --- a/contrib/python/httpcore/httpcore/_sync/socks_proxy.py +++ b/contrib/python/httpcore/httpcore/_sync/socks_proxy.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import logging import ssl -import typing -from socksio import socks5 +import socksio from .._backends.sync import SyncBackend from .._backends.base import NetworkBackend, NetworkStream @@ -43,24 +44,24 @@ def _init_socks5_connection( *, host: bytes, port: int, - auth: typing.Optional[typing.Tuple[bytes, bytes]] = None, + auth: tuple[bytes, bytes] | None = None, ) -> None: - conn = socks5.SOCKS5Connection() + conn = socksio.socks5.SOCKS5Connection() # Auth method request auth_method = ( - socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED + socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED if auth is None - else socks5.SOCKS5AuthMethod.USERNAME_PASSWORD + else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD ) - conn.send(socks5.SOCKS5AuthMethodsRequest([auth_method])) + conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method])) outgoing_bytes = conn.data_to_send() stream.write(outgoing_bytes) # Auth method response incoming_bytes = stream.read(max_bytes=4096) response = conn.receive_data(incoming_bytes) - assert isinstance(response, socks5.SOCKS5AuthReply) + assert isinstance(response, socksio.socks5.SOCKS5AuthReply) if response.method != auth_method: requested = AUTH_METHODS.get(auth_method, "UNKNOWN") responded = AUTH_METHODS.get(response.method, "UNKNOWN") @@ -68,25 +69,25 @@ def _init_socks5_connection( f"Requested {requested} from proxy server, but got {responded}." ) - if response.method == socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: + if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: # Username/password request assert auth is not None username, password = auth - conn.send(socks5.SOCKS5UsernamePasswordRequest(username, password)) + conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password)) outgoing_bytes = conn.data_to_send() stream.write(outgoing_bytes) # Username/password response incoming_bytes = stream.read(max_bytes=4096) response = conn.receive_data(incoming_bytes) - assert isinstance(response, socks5.SOCKS5UsernamePasswordReply) + assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply) if not response.success: raise ProxyError("Invalid username/password") # Connect request conn.send( - socks5.SOCKS5CommandRequest.from_address( - socks5.SOCKS5Command.CONNECT, (host, port) + socksio.socks5.SOCKS5CommandRequest.from_address( + socksio.socks5.SOCKS5Command.CONNECT, (host, port) ) ) outgoing_bytes = conn.data_to_send() @@ -95,31 +96,29 @@ def _init_socks5_connection( # Connect response incoming_bytes = stream.read(max_bytes=4096) response = conn.receive_data(incoming_bytes) - assert isinstance(response, socks5.SOCKS5Reply) - if response.reply_code != socks5.SOCKS5ReplyCode.SUCCEEDED: + assert isinstance(response, socksio.socks5.SOCKS5Reply) + if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED: reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN") raise ProxyError(f"Proxy Server could not connect: {reply_code}.") -class SOCKSProxy(ConnectionPool): +class SOCKSProxy(ConnectionPool): # pragma: nocover """ A connection pool that sends requests via an HTTP proxy. """ def __init__( self, - proxy_url: typing.Union[URL, bytes, str], - proxy_auth: typing.Optional[ - typing.Tuple[typing.Union[bytes, str], typing.Union[bytes, str]] - ] = None, - ssl_context: typing.Optional[ssl.SSLContext] = None, - max_connections: typing.Optional[int] = 10, - max_keepalive_connections: typing.Optional[int] = None, - keepalive_expiry: typing.Optional[float] = None, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, retries: int = 0, - network_backend: typing.Optional[NetworkBackend] = None, + network_backend: NetworkBackend | None = None, ) -> None: """ A connection pool for making HTTP requests. @@ -167,7 +166,7 @@ class SOCKSProxy(ConnectionPool): username, password = proxy_auth username_bytes = enforce_bytes(username, name="proxy_auth") password_bytes = enforce_bytes(password, name="proxy_auth") - self._proxy_auth: typing.Optional[typing.Tuple[bytes, bytes]] = ( + self._proxy_auth: tuple[bytes, bytes] | None = ( username_bytes, password_bytes, ) @@ -192,12 +191,12 @@ class Socks5Connection(ConnectionInterface): self, proxy_origin: Origin, remote_origin: Origin, - proxy_auth: typing.Optional[typing.Tuple[bytes, bytes]] = None, - ssl_context: typing.Optional[ssl.SSLContext] = None, - keepalive_expiry: typing.Optional[float] = None, + proxy_auth: tuple[bytes, bytes] | None = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, http1: bool = True, http2: bool = False, - network_backend: typing.Optional[NetworkBackend] = None, + network_backend: NetworkBackend | None = None, ) -> None: self._proxy_origin = proxy_origin self._remote_origin = remote_origin @@ -211,7 +210,7 @@ class Socks5Connection(ConnectionInterface): SyncBackend() if network_backend is None else network_backend ) self._connect_lock = Lock() - self._connection: typing.Optional[ConnectionInterface] = None + self._connection: ConnectionInterface | None = None self._connect_failed = False def handle_request(self, request: Request) -> Response: diff --git a/contrib/python/httpcore/httpcore/_synchronization.py b/contrib/python/httpcore/httpcore/_synchronization.py index 50cfefe0a2..2ecc9e9c36 100644 --- a/contrib/python/httpcore/httpcore/_synchronization.py +++ b/contrib/python/httpcore/httpcore/_synchronization.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import threading -from types import TracebackType -from typing import Optional, Type +import types from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions @@ -66,7 +67,7 @@ class AsyncLock: elif self._backend == "asyncio": self._anyio_lock = anyio.Lock() - async def __aenter__(self) -> "AsyncLock": + async def __aenter__(self) -> AsyncLock: if not self._backend: self.setup() @@ -79,9 +80,9 @@ class AsyncLock: async def __aexit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: if self._backend == "trio": self._trio_lock.release() @@ -97,14 +98,14 @@ class AsyncThreadLock: In the async case `AsyncThreadLock` is a no-op. """ - def __enter__(self) -> "AsyncThreadLock": + def __enter__(self) -> AsyncThreadLock: return self def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: pass @@ -133,7 +134,7 @@ class AsyncEvent: elif self._backend == "asyncio": self._anyio_event.set() - async def wait(self, timeout: Optional[float] = None) -> None: + async def wait(self, timeout: float | None = None) -> None: if not self._backend: self.setup() @@ -206,7 +207,7 @@ class AsyncShieldCancellation: elif self._backend == "asyncio": self._anyio_shield = anyio.CancelScope(shield=True) - def __enter__(self) -> "AsyncShieldCancellation": + def __enter__(self) -> AsyncShieldCancellation: if self._backend == "trio": self._trio_shield.__enter__() elif self._backend == "asyncio": @@ -215,9 +216,9 @@ class AsyncShieldCancellation: def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: if self._backend == "trio": self._trio_shield.__exit__(exc_type, exc_value, traceback) @@ -239,15 +240,15 @@ class Lock: def __init__(self) -> None: self._lock = threading.Lock() - def __enter__(self) -> "Lock": + def __enter__(self) -> Lock: self._lock.acquire() return self def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: self._lock.release() @@ -263,15 +264,15 @@ class ThreadLock: def __init__(self) -> None: self._lock = threading.Lock() - def __enter__(self) -> "ThreadLock": + def __enter__(self) -> ThreadLock: self._lock.acquire() return self def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: self._lock.release() @@ -283,7 +284,7 @@ class Event: def set(self) -> None: self._event.set() - def wait(self, timeout: Optional[float] = None) -> None: + def wait(self, timeout: float | None = None) -> None: if timeout == float("inf"): # pragma: no cover timeout = None if not self._event.wait(timeout=timeout): @@ -305,13 +306,13 @@ class ShieldCancellation: # Thread-synchronous codebases don't support cancellation semantics. # We have this class because we need to mirror the async and sync # cases within our package, but it's just a no-op. - def __enter__(self) -> "ShieldCancellation": + def __enter__(self) -> ShieldCancellation: return self def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: pass diff --git a/contrib/python/httpcore/httpcore/_trace.py b/contrib/python/httpcore/httpcore/_trace.py index b122a53e88..5f1cd7c478 100644 --- a/contrib/python/httpcore/httpcore/_trace.py +++ b/contrib/python/httpcore/httpcore/_trace.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import inspect import logging -from types import TracebackType -from typing import Any, Dict, Optional, Type +import types +import typing from ._models import Request @@ -11,8 +13,8 @@ class Trace: self, name: str, logger: logging.Logger, - request: Optional[Request] = None, - kwargs: Optional[Dict[str, Any]] = None, + request: Request | None = None, + kwargs: dict[str, typing.Any] | None = None, ) -> None: self.name = name self.logger = logger @@ -21,11 +23,11 @@ class Trace: ) self.debug = self.logger.isEnabledFor(logging.DEBUG) self.kwargs = kwargs or {} - self.return_value: Any = None + self.return_value: typing.Any = None self.should_trace = self.debug or self.trace_extension is not None self.prefix = self.logger.name.split(".")[-1] - def trace(self, name: str, info: Dict[str, Any]) -> None: + def trace(self, name: str, info: dict[str, typing.Any]) -> None: if self.trace_extension is not None: prefix_and_name = f"{self.prefix}.{name}" ret = self.trace_extension(prefix_and_name, info) @@ -44,7 +46,7 @@ class Trace: message = f"{name} {args}" self.logger.debug(message) - def __enter__(self) -> "Trace": + def __enter__(self) -> Trace: if self.should_trace: info = self.kwargs self.trace(f"{self.name}.started", info) @@ -52,9 +54,9 @@ class Trace: def __exit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: if self.should_trace: if exc_value is None: @@ -64,7 +66,7 @@ class Trace: info = {"exception": exc_value} self.trace(f"{self.name}.failed", info) - async def atrace(self, name: str, info: Dict[str, Any]) -> None: + async def atrace(self, name: str, info: dict[str, typing.Any]) -> None: if self.trace_extension is not None: prefix_and_name = f"{self.prefix}.{name}" coro = self.trace_extension(prefix_and_name, info) @@ -84,7 +86,7 @@ class Trace: message = f"{name} {args}" self.logger.debug(message) - async def __aenter__(self) -> "Trace": + async def __aenter__(self) -> Trace: if self.should_trace: info = self.kwargs await self.atrace(f"{self.name}.started", info) @@ -92,9 +94,9 @@ class Trace: async def __aexit__( self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, ) -> None: if self.should_trace: if exc_value is None: diff --git a/contrib/python/httpcore/httpcore/_utils.py b/contrib/python/httpcore/httpcore/_utils.py index df5dea8fe4..c44ff93cb2 100644 --- a/contrib/python/httpcore/httpcore/_utils.py +++ b/contrib/python/httpcore/httpcore/_utils.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import select import socket import sys -import typing -def is_socket_readable(sock: typing.Optional[socket.socket]) -> bool: +def is_socket_readable(sock: socket.socket | None) -> bool: """ Return whether a socket, as identifed by its file descriptor, is readable. "A socket is readable" means that the read buffer isn't empty, i.e. that calling diff --git a/contrib/python/httpcore/ya.make b/contrib/python/httpcore/ya.make index 30d63fe784..6d4f3507ca 100644 --- a/contrib/python/httpcore/ya.make +++ b/contrib/python/httpcore/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(1.0.6) +VERSION(1.0.7) LICENSE(BSD-3-Clause) |