diff --git a/pyproject.toml b/pyproject.toml index 3bb94228..76de1795 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,40 +14,41 @@ httpx2 = { workspace = true } [dependency-groups] dev = [ - "httpx2[brotli,cli,http2,socks,zstd]", - "httpcore2[asyncio,trio,http2,socks]", - # Tests - "chardet==5.2.0", - "coverage[toml]==7.10.6", - "cryptography==46.0.7", - "pytest>=9.0.3", - "pytest-httpbin==2.0.0", - "pytest-trio==0.8.0", - "trio==0.31.0", - "trio-typing==0.10.0", - "trustme==1.2.1", - "uvicorn>=0.35", - "werkzeug>=3.1.6", - # Linting - "mypy==1.17.1", - "ruff==0.12.11", - # Packaging - "build==1.3.0", - "twine==6.1.0", -] -docs = [ - "zensical>=0.0.41", - "mkdocstrings[python]>=0.27", + "httpx2[brotli,cli,http2,socks,zstd]", + "httpcore2[asyncio,trio,http2,socks]", + # Tests + "chardet==5.2.0", + "coverage[toml]==7.10.6", + "cryptography==46.0.7", + "pytest>=9.0.3", + "pytest-httpbin==2.0.0", + "pytest-trio==0.8.0", + "trio==0.31.0", + "trio-typing==0.10.0", + "trustme==1.2.1", + "uvicorn>=0.35", + "werkzeug>=3.1.6", + # Linting + "mypy==1.17.1", + "ruff==0.12.11", + # Packaging + "build==1.3.0", + "twine==6.1.0", ] +docs = ["zensical>=0.0.41", "mkdocstrings[python]>=0.27"] bench = [ - "aiohttp>=3.10.2", - "matplotlib>=3.9", - "pyinstrument>=4.6.2", - "urllib3>=2.2.2", + "aiohttp>=3.10.2", + "matplotlib>=3.9", + "pyinstrument>=4.6.2", + "urllib3>=2.2.2", ] +[tool.ruff] +line-length = 120 +exclude = ["src/httpcore2/httpcore2/_sync", "tests/httpcore2/_sync"] + [tool.ruff.lint] -select = ["E", "F", "I", "B", "PIE"] +select = ["E", "F", "W", "I", "B", "PIE"] ignore = ["B904", "B028"] [tool.ruff.lint.isort] @@ -72,12 +73,13 @@ filterwarnings = [ "error", "ignore: You seem to already have a custom sys.excepthook handler installed. I'll skip installing Trio's custom handler, but this means MultiErrors will not show full tracebacks.:RuntimeWarning", # See: https://github.com/agronholm/anyio/issues/508 - "ignore: trio.MultiError is deprecated since Trio 0.22.0:trio.TrioDeprecationWarning" + "ignore: trio.MultiError is deprecated since Trio 0.22.0:trio.TrioDeprecationWarning", ] markers = [ "copied_from(source, changes=None): mark test as copied from somewhere else, along with a description of changes made to accodomate e.g. our test setup", - "network: marks tests which require network connection. Used in 3rd-party build environments that have network disabled." + "network: marks tests which require network connection. Used in 3rd-party build environments that have network disabled.", ] [tool.coverage.run] -source_pkgs = ["httpx2", "tests"] +source_pkgs = ["httpx2", "httpcore2", "tests"] +omit = ["src/httpcore2/httpcore2/_sync/*"] diff --git a/scripts/check b/scripts/check index c52173d2..aa541202 100755 --- a/scripts/check +++ b/scripts/check @@ -1,6 +1,6 @@ #!/bin/sh -e -export SOURCE_FILES="src/httpx2 tests/httpx2" +export SOURCE_FILES="src/httpx2 tests/httpx2 src/httpcore2 tests/httpcore2" set -x diff --git a/scripts/lint b/scripts/lint index 0f3ceeff..77e8192c 100755 --- a/scripts/lint +++ b/scripts/lint @@ -1,6 +1,6 @@ #!/bin/sh -e -export SOURCE_FILES="src/httpx2 tests/httpx2" +export SOURCE_FILES="src/httpx2 tests/httpx2 src/httpcore2 tests/httpcore2" set -x diff --git a/src/httpcore2/httpcore2/__init__.py b/src/httpcore2/httpcore2/__init__.py index deaa2401..409a7aae 100644 --- a/src/httpcore2/httpcore2/__init__.py +++ b/src/httpcore2/httpcore2/__init__.py @@ -55,9 +55,7 @@ class AnyIOBackend: # type: ignore def __init__(self, *args, **kwargs): # type: ignore - msg = ( - "Attempted to use 'httpcore2.AnyIOBackend' but 'anyio' is not installed." - ) + msg = "Attempted to use 'httpcore2.AnyIOBackend' but 'anyio' is not installed." raise RuntimeError(msg) diff --git a/src/httpcore2/httpcore2/_async/connection.py b/src/httpcore2/httpcore2/_async/connection.py index 5e7cf294..8123afd3 100644 --- a/src/httpcore2/httpcore2/_async/connection.py +++ b/src/httpcore2/httpcore2/_async/connection.py @@ -58,9 +58,7 @@ def __init__( self._local_address = local_address self._uds = uds - self._network_backend: AsyncNetworkBackend = ( - AutoBackend() if network_backend is None else network_backend - ) + self._network_backend: AsyncNetworkBackend = AutoBackend() if network_backend is None else network_backend self._connection: AsyncConnectionInterface | None = None self._connect_failed: bool = False self._request_lock = AsyncLock() @@ -68,9 +66,7 @@ def __init__( async def handle_async_request(self, request: Request) -> Response: if not self.can_handle_request(request.url.origin): - raise RuntimeError( - f"Attempted to send request to {request.url.origin} on connection to {self._origin}" - ) + raise RuntimeError(f"Attempted to send request to {request.url.origin} on connection to {self._origin}") try: async with self._request_lock: @@ -78,10 +74,7 @@ async def handle_async_request(self, request: Request) -> Response: stream = await self._connect(request) ssl_object = stream.get_extra_info("ssl_object") - http2_negotiated = ( - ssl_object is not None - and ssl_object.selected_alpn_protocol() == "h2" - ) + http2_negotiated = ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2" if http2_negotiated or (self._http2 and not self._http1): from .http2 import AsyncHTTP2Connection @@ -129,27 +122,18 @@ async def _connect(self, request: Request) -> AsyncNetworkStream: "timeout": timeout, "socket_options": self._socket_options, } - async with Trace( - "connect_unix_socket", logger, request, kwargs - ) as trace: - stream = await self._network_backend.connect_unix_socket( - **kwargs - ) + async with Trace("connect_unix_socket", logger, request, kwargs) as trace: + stream = await self._network_backend.connect_unix_socket(**kwargs) trace.return_value = stream if self._origin.scheme in (b"https", b"wss"): - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) + ssl_context = default_ssl_context() if self._ssl_context is None else self._ssl_context alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] ssl_context.set_alpn_protocols(alpn_protocols) kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._origin.host.decode("ascii"), + "server_hostname": sni_hostname or self._origin.host.decode("ascii"), "timeout": timeout, } async with Trace("start_tls", logger, request, kwargs) as trace: @@ -177,11 +161,7 @@ def is_available(self) -> bool: # If HTTP/2 support is enabled, and the resulting connection could # end up as HTTP/2 then we should indicate the connection as being # available to service multiple requests. - return ( - self._http2 - and (self._origin.scheme == b"https" or not self._http1) - and not self._connect_failed - ) + return self._http2 and (self._origin.scheme == b"https" or not self._http1) and not self._connect_failed return self._connection.is_available() def has_expired(self) -> bool: diff --git a/src/httpcore2/httpcore2/_async/connection_pool.py b/src/httpcore2/httpcore2/_async/connection_pool.py index cf3943d9..bf10e28a 100644 --- a/src/httpcore2/httpcore2/_async/connection_pool.py +++ b/src/httpcore2/httpcore2/_async/connection_pool.py @@ -30,9 +30,7 @@ def clear_connection(self) -> None: self.connection = None self._connection_acquired = AsyncEvent() - async def wait_for_connection( - self, timeout: float | None = None - ) -> AsyncConnectionInterface: + async def wait_for_connection(self, timeout: float | None = None) -> AsyncConnectionInterface: if self.connection is None: await self._connection_acquired.wait(timeout=timeout) assert self.connection is not None @@ -93,17 +91,11 @@ def __init__( """ self._ssl_context = ssl_context self._proxy = proxy - self._max_connections = ( - sys.maxsize if max_connections is None else max_connections - ) + self._max_connections = sys.maxsize if max_connections is None else max_connections self._max_keepalive_connections = ( - sys.maxsize - if max_keepalive_connections is None - else max_keepalive_connections - ) - self._max_keepalive_connections = min( - self._max_connections, self._max_keepalive_connections + sys.maxsize if max_keepalive_connections is None else max_keepalive_connections ) + self._max_keepalive_connections = min(self._max_connections, self._max_keepalive_connections) self._keepalive_expiry = keepalive_expiry self._http1 = http1 @@ -112,9 +104,7 @@ def __init__( self._local_address = local_address self._uds = uds - self._network_backend = ( - AutoBackend() if network_backend is None else network_backend - ) + self._network_backend = AutoBackend() if network_backend is None else network_backend self._socket_options = socket_options # The mutable state on a connection pool is the queue of incoming requests, @@ -206,13 +196,9 @@ async def handle_async_request(self, request: Request) -> Response: """ scheme = request.url.scheme.decode() if scheme == "": - raise UnsupportedProtocol( - "Request URL is missing an 'http://' or 'https://' protocol." - ) + raise UnsupportedProtocol("Request URL is missing an 'http://' or 'https://' protocol.") if scheme not in ("http", "https", "ws", "wss"): - raise UnsupportedProtocol( - f"Request URL has an unsupported protocol '{scheme}://'." - ) + raise UnsupportedProtocol(f"Request URL has an unsupported protocol '{scheme}://'.") timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("pool", None) @@ -235,9 +221,7 @@ async def handle_async_request(self, request: Request) -> Response: try: # Send the request on the assigned connection. - response = await connection.handle_async_request( - pool_request.request - ) + response = await connection.handle_async_request(pool_request.request) except ConnectionNotAvailable: # In some cases a connection may initially be available to # handle a request, but then become unavailable. @@ -263,9 +247,7 @@ async def handle_async_request(self, request: Request) -> Response: return Response( status=response.status, headers=response.headers, - content=PoolByteStream( - stream=response.stream, pool_request=pool_request, pool=self - ), + content=PoolByteStream(stream=response.stream, pool_request=pool_request, pool=self), extensions=response.extensions, ) @@ -293,8 +275,7 @@ def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]: closing_connections.append(connection) elif ( connection.is_idle() - and sum(connection.is_idle() for connection in self._connections) - > self._max_keepalive_connections + and sum(connection.is_idle() for connection in self._connections) > self._max_keepalive_connections ): # log: "closing idle connection" self._connections.remove(connection) @@ -309,9 +290,7 @@ def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]: for connection in self._connections if connection.can_handle_request(origin) and connection.is_available() ] - idle_connections = [ - connection for connection in self._connections if connection.is_idle() - ] + idle_connections = [connection for connection in self._connections if connection.is_idle()] # There are three cases for how we may be able to handle the request: # @@ -369,21 +348,15 @@ def __repr__(self) -> str: class_name = self.__class__.__name__ with self._optional_thread_lock: request_is_queued = [request.is_queued() for request in self._requests] - connection_is_idle = [ - connection.is_idle() for connection in self._connections - ] + connection_is_idle = [connection.is_idle() for connection in self._connections] num_active_requests = request_is_queued.count(False) num_queued_requests = request_is_queued.count(True) num_active_connections = connection_is_idle.count(False) num_idle_connections = connection_is_idle.count(True) - requests_info = ( - f"Requests: {num_active_requests} active, {num_queued_requests} queued" - ) - connection_info = ( - f"Connections: {num_active_connections} active, {num_idle_connections} idle" - ) + requests_info = f"Requests: {num_active_requests} active, {num_queued_requests} queued" + connection_info = f"Connections: {num_active_connections} active, {num_idle_connections} idle" return f"<{class_name} [{requests_info} | {connection_info}]>" diff --git a/src/httpcore2/httpcore2/_async/http11.py b/src/httpcore2/httpcore2/_async/http11.py index 9a9ee771..d774a8fc 100644 --- a/src/httpcore2/httpcore2/_async/http11.py +++ b/src/httpcore2/httpcore2/_async/http11.py @@ -66,10 +66,7 @@ def __init__( async def handle_async_request(self, request: Request) -> Response: if not self.can_handle_request(request.url.origin): - raise RuntimeError( - f"Attempted to send request to {request.url.origin} on connection " - f"to {self._origin}" - ) + raise RuntimeError(f"Attempted to send request to {request.url.origin} on connection to {self._origin}") async with self._state_lock: if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE): @@ -82,9 +79,7 @@ async def handle_async_request(self, request: Request) -> Response: try: kwargs = {"request": request} try: - async with Trace( - "send_request_headers", logger, request, kwargs - ) as trace: + async with Trace("send_request_headers", logger, request, kwargs) as trace: await self._send_request_headers(**kwargs) async with Trace("send_request_body", logger, request, kwargs) as trace: await self._send_request_body(**kwargs) @@ -96,9 +91,7 @@ async def handle_async_request(self, request: Request) -> Response: # error response. pass - async with Trace( - "receive_response_headers", logger, request, kwargs - ) as trace: + async with Trace("receive_response_headers", logger, request, kwargs) as trace: ( http_version, status, @@ -116,9 +109,7 @@ async def handle_async_request(self, request: Request) -> Response: network_stream = self._network_stream # CONNECT or Upgrade request - if (status == 101) or ( - (request.method == b"CONNECT") and (200 <= status < 300) - ): + if (status == 101) or ((request.method == b"CONNECT") and (200 <= status < 300)): network_stream = AsyncHTTP11UpgradeStream(network_stream, trailing_data) return Response( @@ -180,10 +171,7 @@ async def _receive_response_headers( event = await self._receive_event(timeout=timeout) if isinstance(event, h11.Response): break - if ( - isinstance(event, h11.InformationalResponse) - and event.status_code == 101 - ): + if isinstance(event, h11.InformationalResponse) and event.status_code == 101: break http_version = b"HTTP/" + event.http_version @@ -207,17 +195,13 @@ async def _receive_response_body(self, request: Request) -> AsyncGenerator[bytes elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)): break - async def _receive_event( - self, timeout: float | None = None - ) -> h11.Event | type[h11.PAUSED]: + async def _receive_event(self, timeout: float | None = None) -> h11.Event | type[h11.PAUSED]: while True: with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): event = self._h11_state.next_event() if event is h11.NEED_DATA: - data = await self._network_stream.read( - self.READ_NUM_BYTES, timeout=timeout - ) + data = await self._network_stream.read(self.READ_NUM_BYTES, timeout=timeout) # If we feed this case through h11 we'll raise an exception like: # @@ -238,10 +222,7 @@ async def _receive_event( async def _response_closed(self) -> None: async with self._state_lock: - if ( - self._h11_state.our_state is h11.DONE - and self._h11_state.their_state is h11.DONE - ): + if self._h11_state.our_state is h11.DONE and self._h11_state.their_state is h11.DONE: self._state = HTTPConnectionState.IDLE self._h11_state.start_next_cycle() if self._keepalive_expiry is not None: @@ -279,9 +260,8 @@ def has_expired(self) -> bool: # If the HTTP connection is idle but the socket is readable, then the # only valid state is that the socket is about to return b"", indicating # a server-initiated disconnect. - server_disconnected = ( - self._state == HTTPConnectionState.IDLE - and self._network_stream.get_extra_info("is_readable") + server_disconnected = self._state == HTTPConnectionState.IDLE and self._network_stream.get_extra_info( + "is_readable" ) return keepalive_expired or server_disconnected @@ -294,18 +274,12 @@ def is_closed(self) -> bool: def info(self) -> str: origin = str(self._origin) - return ( - f"{origin!r}, HTTP/1.1, {self._state.name}, " - f"Request Count: {self._request_count}" - ) + return f"{origin!r}, HTTP/1.1, {self._state.name}, Request Count: {self._request_count}" def __repr__(self) -> str: class_name = self.__class__.__name__ origin = str(self._origin) - return ( - f"<{class_name} [{origin!r}, {self._state.name}, " - f"Request Count: {self._request_count}]>" - ) + return f"<{class_name} [{origin!r}, {self._state.name}, Request Count: {self._request_count}]>" # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. @@ -332,9 +306,7 @@ async def __aiter__(self) -> AsyncGenerator[bytes]: kwargs = {"request": self._request} try: async with Trace("receive_response_body", logger, self._request, kwargs): - async with safe_async_iterate( - self._connection._receive_response_body(**kwargs) - ) as iterator: + async with safe_async_iterate(self._connection._receive_response_body(**kwargs)) as iterator: async for chunk in iterator: yield chunk except BaseException as exc: diff --git a/src/httpcore2/httpcore2/_async/http2.py b/src/httpcore2/httpcore2/_async/http2.py index 75a86078..dd9a9062 100644 --- a/src/httpcore2/httpcore2/_async/http2.py +++ b/src/httpcore2/httpcore2/_async/http2.py @@ -29,10 +29,7 @@ def has_body_headers(request: Request) -> bool: - return any( - k.lower() == b"content-length" or k.lower() == b"transfer-encoding" - for k, v in request.headers - ) + return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, v in request.headers) class HTTPConnectionState(enum.IntEnum): @@ -69,12 +66,7 @@ def __init__( # Mapping from stream ID to response stream events. self._events: dict[ int, - list[ - h2.events.ResponseReceived - | h2.events.DataReceived - | h2.events.StreamEnded - | h2.events.StreamReset, - ], + list[h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded | h2.events.StreamReset,], ] = {} # Connection terminated events are stored as state since @@ -90,10 +82,7 @@ async def handle_async_request(self, request: Request) -> Response: # will only send requests on connections that handle them. # It's in place simply for resilience as a guard against incorrect # usage, for anyone working directly with httpcore connections. - raise RuntimeError( - f"Attempted to send request to {request.url.origin} on connection " - f"to {self._origin}" - ) + raise RuntimeError(f"Attempted to send request to {request.url.origin} on connection to {self._origin}") async with self._state_lock: if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE): @@ -107,9 +96,7 @@ async def handle_async_request(self, request: Request) -> Response: if not self._sent_connection_init: try: sci_kwargs = {"request": request} - async with Trace( - "send_connection_init", logger, request, sci_kwargs - ): + async with Trace("send_connection_init", logger, request, sci_kwargs): await self._send_connection_init(**sci_kwargs) except BaseException as exc: with AsyncShieldCancellation(): @@ -122,9 +109,7 @@ async def handle_async_request(self, request: Request) -> Response: # its max_concurrent_streams value self._max_streams = 1 - local_settings_max_streams = ( - self._h2_state.local_settings.max_concurrent_streams - ) + local_settings_max_streams = self._h2_state.local_settings.max_concurrent_streams self._max_streams_semaphore = AsyncSemaphore(local_settings_max_streams) for _ in range(local_settings_max_streams - self._max_streams): @@ -146,12 +131,8 @@ async def handle_async_request(self, request: Request) -> Response: await self._send_request_headers(request=request, stream_id=stream_id) async with Trace("send_request_body", logger, request, kwargs): await self._send_request_body(request=request, stream_id=stream_id) - async with Trace( - "receive_response_headers", logger, request, kwargs - ) as trace: - status, headers = await self._receive_response( - request=request, stream_id=stream_id - ) + async with Trace("receive_response_headers", logger, request, kwargs) as trace: + status, headers = await self._receive_response(request=request, stream_id=stream_id) trace.return_value = (status, headers) return Response( @@ -211,9 +192,7 @@ async def _send_connection_init(self, request: Request) -> None: # Some websites (*cough* Yahoo *cough*) balk at this setting being # present in the initial handshake since it's not defined in the original # RFC despite the RFC mandating ignoring settings you don't know about. - del self._h2_state.local_settings[ - h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL - ] + del self._h2_state.local_settings[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] self._h2_state.initiate_connection() self._h2_state.increment_flow_control_window(2**24) @@ -266,9 +245,7 @@ async def _send_request_body(self, request: Request, stream_id: int) -> None: await self._send_end_stream(request, stream_id) - async def _send_stream_data( - self, request: Request, stream_id: int, data: bytes - ) -> None: + async def _send_stream_data(self, request: Request, stream_id: int, data: bytes) -> None: """ Send a single chunk of data in one or more data frames. """ @@ -288,9 +265,7 @@ async def _send_end_stream(self, request: Request, stream_id: int) -> None: # Receiving the response... - async def _receive_response( - self, request: Request, stream_id: int - ) -> tuple[int, list[tuple[bytes, bytes]]]: + async def _receive_response(self, request: Request, stream_id: int) -> tuple[int, list[tuple[bytes, bytes]]]: """ Return the response status code and headers for a given stream ID. """ @@ -310,9 +285,7 @@ async def _receive_response( return (status_code, headers) - async def _receive_response_body( - self, request: Request, stream_id: int - ) -> AsyncGenerator[bytes]: + async def _receive_response_body(self, request: Request, stream_id: int) -> AsyncGenerator[bytes]: """ Iterator that returns the bytes of the response body for a given stream ID. """ @@ -343,9 +316,7 @@ async def _receive_stream_event( raise RemoteProtocolError(event) return event - async def _receive_events( - self, request: Request, stream_id: int | None = None - ) -> None: + async def _receive_events(self, request: Request, stream_id: int | None = None) -> None: """ Read some data from the network until we see one or more events for a given stream ID. @@ -368,9 +339,7 @@ async def _receive_events( events = await self._read_incoming_data(request) for event in events: if isinstance(event, h2.events.RemoteSettingsChanged): - async with Trace( - "receive_remote_settings", logger, request - ) as trace: + async with Trace("receive_remote_settings", logger, request) as trace: await self._receive_remote_settings_change(event) trace.return_value = event @@ -391,12 +360,8 @@ async def _receive_events( await self._write_outgoing_data(request) - async def _receive_remote_settings_change( - self, event: h2.events.RemoteSettingsChanged - ) -> None: - max_concurrent_streams = event.changed_settings.get( - h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS - ) + async def _receive_remote_settings_change(self, event: h2.events.RemoteSettingsChanged) -> None: + max_concurrent_streams = event.changed_settings.get(h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS) if max_concurrent_streams: new_max_streams = min( max_concurrent_streams.new_value, @@ -517,10 +482,7 @@ def is_available(self) -> bool: self._state != HTTPConnectionState.CLOSED and not self._connection_error and not self._used_all_stream_ids - and not ( - self._h2_state.state_machine.state - == h2.connection.ConnectionState.CLOSED - ) + and not (self._h2_state.state_machine.state == h2.connection.ConnectionState.CLOSED) ) def has_expired(self) -> bool: @@ -535,18 +497,12 @@ def is_closed(self) -> bool: def info(self) -> str: origin = str(self._origin) - return ( - f"{origin!r}, HTTP/2, {self._state.name}, " - f"Request Count: {self._request_count}" - ) + return f"{origin!r}, HTTP/2, {self._state.name}, Request Count: {self._request_count}" def __repr__(self) -> str: class_name = self.__class__.__name__ origin = str(self._origin) - return ( - f"<{class_name} [{origin!r}, {self._state.name}, " - f"Request Count: {self._request_count}]>" - ) + return f"<{class_name} [{origin!r}, {self._state.name}, Request Count: {self._request_count}]>" # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. @@ -564,9 +520,7 @@ async def __aexit__( class HTTP2ConnectionByteStream: - def __init__( - self, connection: AsyncHTTP2Connection, request: Request, stream_id: int - ) -> None: + def __init__(self, connection: AsyncHTTP2Connection, request: Request, stream_id: int) -> None: self._connection = connection self._request = request self._stream_id = stream_id @@ -577,9 +531,7 @@ async def __aiter__(self) -> AsyncGenerator[bytes]: try: async with Trace("receive_response_body", logger, self._request, kwargs): async with safe_async_iterate( - self._connection._receive_response_body( - request=self._request, stream_id=self._stream_id - ) + self._connection._receive_response_body(request=self._request, stream_id=self._stream_id) ) as iterator: async for chunk in iterator: yield chunk diff --git a/src/httpcore2/httpcore2/_async/http_proxy.py b/src/httpcore2/httpcore2/_async/http_proxy.py index 5cf7c987..533bf278 100644 --- a/src/httpcore2/httpcore2/_async/http_proxy.py +++ b/src/httpcore2/httpcore2/_async/http_proxy.py @@ -43,11 +43,7 @@ def merge_headers( default_headers = [] if default_headers is None else list(default_headers) override_headers = [] if override_headers is None else list(override_headers) has_override = set(key.lower() for key, value in override_headers) - default_headers = [ - (key, value) - for key, value in default_headers - if key.lower() not in has_override - ] + default_headers = [(key, value) for key, value in default_headers if key.lower() not in has_override] return default_headers + override_headers @@ -124,12 +120,8 @@ def __init__( ) self._proxy_url = enforce_url(proxy_url, name="proxy_url") - if ( - self._proxy_url.scheme == b"http" and proxy_ssl_context is not None - ): # pragma: no cover - raise RuntimeError( - "The `proxy_ssl_context` argument is not allowed for the http scheme" - ) + if self._proxy_url.scheme == b"http" and proxy_ssl_context is not None: # pragma: no cover + raise RuntimeError("The `proxy_ssl_context` argument is not allowed for the http scheme") self._ssl_context = ssl_context self._proxy_ssl_context = proxy_ssl_context @@ -139,9 +131,7 @@ def __init__( password = enforce_bytes(proxy_auth[1], name="proxy_auth") userpass = username + b":" + password authorization = b"Basic " + base64.b64encode(userpass) - self._proxy_headers = [ - (b"Proxy-Authorization", authorization) - ] + self._proxy_headers + self._proxy_headers = [(b"Proxy-Authorization", authorization)] + self._proxy_headers def create_connection(self, origin: Origin) -> AsyncConnectionInterface: if origin.scheme == b"http": @@ -276,18 +266,14 @@ async def handle_async_request(self, request: Request) -> Response: port=self._proxy_origin.port, target=target, ) - connect_headers = merge_headers( - [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers - ) + connect_headers = merge_headers([(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers) connect_request = Request( method=b"CONNECT", url=connect_url, headers=connect_headers, extensions=request.extensions, ) - connect_response = await self._connection.handle_async_request( - connect_request - ) + connect_response = await self._connection.handle_async_request(connect_request) if connect_response.status < 200 or connect_response.status > 299: reason_bytes = connect_response.extensions.get("reason_phrase", b"") @@ -299,11 +285,7 @@ async def handle_async_request(self, request: Request) -> Response: stream = connect_response.extensions["network_stream"] # Upgrade the stream to SSL - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) + ssl_context = default_ssl_context() if self._ssl_context is None else self._ssl_context alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] ssl_context.set_alpn_protocols(alpn_protocols) @@ -318,10 +300,7 @@ async def handle_async_request(self, request: Request) -> Response: # Determine if we should be using HTTP/1.1 or HTTP/2 ssl_object = stream.get_extra_info("ssl_object") - http2_negotiated = ( - ssl_object is not None - and ssl_object.selected_alpn_protocol() == "h2" - ) + http2_negotiated = ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2" # Create the HTTP/1.1 or HTTP/2 connection if http2_negotiated or (self._http2 and not self._http1): diff --git a/src/httpcore2/httpcore2/_async/socks_proxy.py b/src/httpcore2/httpcore2/_async/socks_proxy.py index 5ab2c32c..7a364eca 100644 --- a/src/httpcore2/httpcore2/_async/socks_proxy.py +++ b/src/httpcore2/httpcore2/_async/socks_proxy.py @@ -65,9 +65,7 @@ async def _init_socks5_connection( if response.method != auth_method: requested = AUTH_METHODS.get(auth_method, "UNKNOWN") responded = AUTH_METHODS.get(response.method, "UNKNOWN") - raise ProxyError( - f"Requested {requested} from proxy server, but got {responded}." - ) + raise ProxyError(f"Requested {requested} from proxy server, but got {responded}.") if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: # Username/password request @@ -85,11 +83,7 @@ async def _init_socks5_connection( raise ProxyError("Invalid username/password") # Connect request - conn.send( - socksio.socks5.SOCKS5CommandRequest.from_address( - socksio.socks5.SOCKS5Command.CONNECT, (host, port) - ) - ) + conn.send(socksio.socks5.SOCKS5CommandRequest.from_address(socksio.socks5.SOCKS5Command.CONNECT, (host, port))) outgoing_bytes = conn.data_to_send() await stream.write(outgoing_bytes) @@ -206,9 +200,7 @@ def __init__( self._http1 = http1 self._http2 = http2 - self._network_backend: AsyncNetworkBackend = ( - AutoBackend() if network_backend is None else network_backend - ) + self._network_backend: AsyncNetworkBackend = AutoBackend() if network_backend is None else network_backend self._connect_lock = AsyncLock() self._connection: AsyncConnectionInterface | None = None self._connect_failed = False @@ -238,28 +230,19 @@ async def handle_async_request(self, request: Request) -> Response: "port": self._remote_origin.port, "auth": self._proxy_auth, } - async with Trace( - "setup_socks5_connection", logger, request, kwargs - ) as trace: + async with Trace("setup_socks5_connection", logger, request, kwargs) as trace: await _init_socks5_connection(**kwargs) trace.return_value = stream # Upgrade the stream to SSL if self._remote_origin.scheme == b"https": - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) - alpn_protocols = ( - ["http/1.1", "h2"] if self._http2 else ["http/1.1"] - ) + ssl_context = default_ssl_context() if self._ssl_context is None else self._ssl_context + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] ssl_context.set_alpn_protocols(alpn_protocols) kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._remote_origin.host.decode("ascii"), + "server_hostname": sni_hostname or self._remote_origin.host.decode("ascii"), "timeout": timeout, } async with Trace("start_tls", logger, request, kwargs) as trace: @@ -268,15 +251,10 @@ async def handle_async_request(self, request: Request) -> Response: # Determine if we should be using HTTP/1.1 or HTTP/2 ssl_object = stream.get_extra_info("ssl_object") - http2_negotiated = ( - ssl_object is not None - and ssl_object.selected_alpn_protocol() == "h2" - ) + http2_negotiated = ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2" # Create the HTTP/1.1 or HTTP/2 connection - if http2_negotiated or ( - self._http2 and not self._http1 - ): # pragma: nocover + if http2_negotiated or (self._http2 and not self._http1): # pragma: nocover from .http2 import AsyncHTTP2Connection self._connection = AsyncHTTP2Connection( @@ -311,9 +289,7 @@ def is_available(self) -> bool: # end up as HTTP/2 then we should indicate the connection as being # available to service multiple requests. return ( - self._http2 - and (self._remote_origin.scheme == b"https" or not self._http1) - and not self._connect_failed + self._http2 and (self._remote_origin.scheme == b"https" or not self._http1) and not self._connect_failed ) return self._connection.is_available() diff --git a/src/httpcore2/httpcore2/_backends/auto.py b/src/httpcore2/httpcore2/_backends/auto.py index 49f0e698..33140ddf 100644 --- a/src/httpcore2/httpcore2/_backends/auto.py +++ b/src/httpcore2/httpcore2/_backends/auto.py @@ -43,9 +43,7 @@ async def connect_unix_socket( socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> AsyncNetworkStream: # pragma: nocover await self._init_backend() - return await self._backend.connect_unix_socket( - path, timeout=timeout, socket_options=socket_options - ) + return await self._backend.connect_unix_socket(path, timeout=timeout, socket_options=socket_options) async def sleep(self, seconds: float) -> None: # pragma: nocover await self._init_backend() diff --git a/src/httpcore2/httpcore2/_backends/sync.py b/src/httpcore2/httpcore2/_backends/sync.py index 4018a09c..f18721bf 100644 --- a/src/httpcore2/httpcore2/_backends/sync.py +++ b/src/httpcore2/httpcore2/_backends/sync.py @@ -80,9 +80,7 @@ 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(functools.partial(self.ssl_obj.read, max_bytes)) - ) + return typing.cast(bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes))) def write(self, buffer: bytes, timeout: float | None = None) -> None: exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError} @@ -157,14 +155,10 @@ def start_tls( # If the underlying socket has already been upgraded # to the TLS layer (i.e. is an instance of SSLSocket), # we need some additional smarts to support TLS-in-TLS. - return TLSinTLSStream( - self._sock, ssl_context, server_hostname, timeout - ) + return TLSinTLSStream(self._sock, ssl_context, server_hostname, timeout) else: self._sock.settimeout(timeout) - sock = ssl_context.wrap_socket( - self._sock, server_hostname=server_hostname - ) + sock = ssl_context.wrap_socket(self._sock, server_hostname=server_hostname) except Exception as exc: # pragma: nocover self.close() raise exc @@ -222,9 +216,7 @@ def connect_unix_socket( socket_options: typing.Iterable[SOCKET_OPTION] | None = None, ) -> NetworkStream: # pragma: nocover if sys.platform == "win32": - raise RuntimeError( - "Attempted to connect to a UNIX socket on a Windows system." - ) + raise RuntimeError("Attempted to connect to a UNIX socket on a Windows system.") if socket_options is None: socket_options = [] diff --git a/src/httpcore2/httpcore2/_backends/trio.py b/src/httpcore2/httpcore2/_backends/trio.py index 6f53f5f2..13b55acd 100644 --- a/src/httpcore2/httpcore2/_backends/trio.py +++ b/src/httpcore2/httpcore2/_backends/trio.py @@ -127,9 +127,7 @@ async def connect_tcp( } with map_exceptions(exc_map): with trio.fail_after(timeout_or_inf): - stream: trio.abc.Stream = await trio.open_tcp_stream( - host=host, port=port, local_address=local_address - ) + stream: trio.abc.Stream = await trio.open_tcp_stream(host=host, port=port, local_address=local_address) for option in socket_options: stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover return TrioStream(stream) diff --git a/src/httpcore2/httpcore2/_models.py b/src/httpcore2/httpcore2/_models.py index a850b2a6..fa611af6 100644 --- a/src/httpcore2/httpcore2/_models.py +++ b/src/httpcore2/httpcore2/_models.py @@ -80,9 +80,7 @@ def enforce_headers( ] seen_type = type(value).__name__ - raise TypeError( - f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}." - ) + raise TypeError(f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}.") def enforce_stream( @@ -125,11 +123,7 @@ def include_request_headers( header_value = b"%b:%d" % (url.host, url.port) headers = [(b"Host", header_value)] + headers - if ( - content is not None - and b"content-length" not in headers_set - and b"transfer-encoding" not in headers_set - ): + if content is not None and b"content-length" not in headers_set and b"transfer-encoding" not in headers_set: if isinstance(content, bytes): content_length = str(len(content)).encode("ascii") headers += [(b"Content-Length", content_length)] @@ -272,9 +266,7 @@ def __init__( self.scheme = parsed.scheme self.host = parsed.hostname or b"" self.port = parsed.port - self.target = (parsed.path or b"/") + ( - b"?" + parsed.query if parsed.query else b"" - ) + self.target = (parsed.path or b"/") + (b"?" + parsed.query if parsed.query else b"") else: self.scheme = enforce_bytes(scheme, name="scheme") self.host = enforce_bytes(host, name="host") @@ -291,9 +283,7 @@ def origin(self) -> Origin: b"socks5": 1080, b"socks5h": 1080, }[self.scheme] - return Origin( - scheme=self.scheme, host=self.host, port=self.port or default_port - ) + return Origin(scheme=self.scheme, host=self.host, port=self.port or default_port) def __eq__(self, other: typing.Any) -> bool: return ( @@ -327,10 +317,7 @@ def __init__( url: URL | bytes | str, *, headers: HeaderTypes = None, - content: bytes - | typing.Iterable[bytes] - | typing.AsyncIterable[bytes] - | None = None, + content: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None = None, extensions: Extensions | None = None, ) -> None: """ @@ -346,12 +333,8 @@ def __init__( """ self.method: bytes = enforce_bytes(method, name="method") self.url: URL = enforce_url(url, name="url") - self.headers: list[tuple[bytes, bytes]] = enforce_headers( - headers, name="headers" - ) - self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( - enforce_stream(content, name="content") - ) + self.headers: list[tuple[bytes, bytes]] = enforce_headers(headers, name="headers") + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = enforce_stream(content, name="content") self.extensions = {} if extensions is None else extensions if "target" in self.extensions: @@ -376,10 +359,7 @@ def __init__( status: int, *, headers: HeaderTypes = None, - content: bytes - | typing.Iterable[bytes] - | typing.AsyncIterable[bytes] - | None = None, + content: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None = None, extensions: Extensions | None = None, ) -> None: """ @@ -392,12 +372,8 @@ def __init__( `"reason_phrase"`, and `"network_stream"`. """ self.status: int = status - self.headers: list[tuple[bytes, bytes]] = enforce_headers( - headers, name="headers" - ) - self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( - enforce_stream(content, name="content") - ) + self.headers: list[tuple[bytes, bytes]] = enforce_headers(headers, name="headers") + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = enforce_stream(content, name="content") self.extensions = {} if extensions is None else extensions self._stream_consumed = False @@ -407,8 +383,7 @@ def content(self) -> bytes: if not hasattr(self, "_content"): if isinstance(self.stream, typing.Iterable): raise RuntimeError( - "Attempted to access 'response.content' on a streaming response. " - "Call 'response.read()' first." + "Attempted to access 'response.content' on a streaming response. Call 'response.read()' first." ) else: raise RuntimeError( @@ -440,9 +415,7 @@ def iter_stream(self) -> typing.Iterator[bytes]: "You should use 'async for ... in response.aiter_stream()' instead." ) if self._stream_consumed: - raise RuntimeError( - "Attempted to call 'for ... in response.iter_stream()' more than once." - ) + raise RuntimeError("Attempted to call 'for ... in response.iter_stream()' more than once.") self._stream_consumed = True for chunk in self.stream: yield chunk @@ -478,10 +451,7 @@ async def aiter_stream(self) -> AsyncGenerator[bytes]: "You should use 'for ... in response.iter_stream()' instead." ) if self._stream_consumed: - raise RuntimeError( - "Attempted to call 'async for ... in response.aiter_stream()' " - "more than once." - ) + raise RuntimeError("Attempted to call 'async for ... in response.aiter_stream()' more than once.") self._stream_consumed = True async with safe_async_iterate(self.stream) as iterator: async for chunk in iterator: diff --git a/src/httpcore2/httpcore2/_sync/connection.py b/src/httpcore2/httpcore2/_sync/connection.py index e5331561..14ef8b8b 100644 --- a/src/httpcore2/httpcore2/_sync/connection.py +++ b/src/httpcore2/httpcore2/_sync/connection.py @@ -58,9 +58,7 @@ def __init__( self._local_address = local_address self._uds = uds - self._network_backend: NetworkBackend = ( - SyncBackend() if network_backend is None else network_backend - ) + self._network_backend: NetworkBackend = SyncBackend() if network_backend is None else network_backend self._connection: ConnectionInterface | None = None self._connect_failed: bool = False self._request_lock = Lock() @@ -68,9 +66,7 @@ def __init__( def handle_request(self, request: Request) -> Response: if not self.can_handle_request(request.url.origin): - raise RuntimeError( - f"Attempted to send request to {request.url.origin} on connection to {self._origin}" - ) + raise RuntimeError(f"Attempted to send request to {request.url.origin} on connection to {self._origin}") try: with self._request_lock: @@ -78,10 +74,7 @@ def handle_request(self, request: Request) -> Response: stream = self._connect(request) ssl_object = stream.get_extra_info("ssl_object") - http2_negotiated = ( - ssl_object is not None - and ssl_object.selected_alpn_protocol() == "h2" - ) + http2_negotiated = ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2" if http2_negotiated or (self._http2 and not self._http1): from .http2 import HTTP2Connection @@ -129,27 +122,18 @@ def _connect(self, request: Request) -> NetworkStream: "timeout": timeout, "socket_options": self._socket_options, } - with Trace( - "connect_unix_socket", logger, request, kwargs - ) as trace: - stream = self._network_backend.connect_unix_socket( - **kwargs - ) + with Trace("connect_unix_socket", logger, request, kwargs) as trace: + stream = self._network_backend.connect_unix_socket(**kwargs) trace.return_value = stream if self._origin.scheme in (b"https", b"wss"): - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) + ssl_context = default_ssl_context() if self._ssl_context is None else self._ssl_context alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] ssl_context.set_alpn_protocols(alpn_protocols) kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._origin.host.decode("ascii"), + "server_hostname": sni_hostname or self._origin.host.decode("ascii"), "timeout": timeout, } with Trace("start_tls", logger, request, kwargs) as trace: @@ -177,11 +161,7 @@ def is_available(self) -> bool: # If HTTP/2 support is enabled, and the resulting connection could # end up as HTTP/2 then we should indicate the connection as being # available to service multiple requests. - return ( - self._http2 - and (self._origin.scheme == b"https" or not self._http1) - and not self._connect_failed - ) + return self._http2 and (self._origin.scheme == b"https" or not self._http1) and not self._connect_failed return self._connection.is_available() def has_expired(self) -> bool: diff --git a/src/httpcore2/httpcore2/_sync/connection_pool.py b/src/httpcore2/httpcore2/_sync/connection_pool.py index 0b4d4587..17fc9ae6 100644 --- a/src/httpcore2/httpcore2/_sync/connection_pool.py +++ b/src/httpcore2/httpcore2/_sync/connection_pool.py @@ -30,9 +30,7 @@ def clear_connection(self) -> None: self.connection = None self._connection_acquired = Event() - def wait_for_connection( - self, timeout: float | None = None - ) -> ConnectionInterface: + def wait_for_connection(self, timeout: float | None = None) -> ConnectionInterface: if self.connection is None: self._connection_acquired.wait(timeout=timeout) assert self.connection is not None @@ -93,17 +91,11 @@ def __init__( """ self._ssl_context = ssl_context self._proxy = proxy - self._max_connections = ( - sys.maxsize if max_connections is None else max_connections - ) + self._max_connections = sys.maxsize if max_connections is None else max_connections self._max_keepalive_connections = ( - sys.maxsize - if max_keepalive_connections is None - else max_keepalive_connections - ) - self._max_keepalive_connections = min( - self._max_connections, self._max_keepalive_connections + sys.maxsize if max_keepalive_connections is None else max_keepalive_connections ) + self._max_keepalive_connections = min(self._max_connections, self._max_keepalive_connections) self._keepalive_expiry = keepalive_expiry self._http1 = http1 @@ -112,9 +104,7 @@ def __init__( self._local_address = local_address self._uds = uds - self._network_backend = ( - SyncBackend() if network_backend is None else network_backend - ) + self._network_backend = SyncBackend() if network_backend is None else network_backend self._socket_options = socket_options # The mutable state on a connection pool is the queue of incoming requests, @@ -206,13 +196,9 @@ def handle_request(self, request: Request) -> Response: """ scheme = request.url.scheme.decode() if scheme == "": - raise UnsupportedProtocol( - "Request URL is missing an 'http://' or 'https://' protocol." - ) + raise UnsupportedProtocol("Request URL is missing an 'http://' or 'https://' protocol.") if scheme not in ("http", "https", "ws", "wss"): - raise UnsupportedProtocol( - f"Request URL has an unsupported protocol '{scheme}://'." - ) + raise UnsupportedProtocol(f"Request URL has an unsupported protocol '{scheme}://'.") timeouts = request.extensions.get("timeout", {}) timeout = timeouts.get("pool", None) @@ -235,9 +221,7 @@ def handle_request(self, request: Request) -> Response: try: # Send the request on the assigned connection. - response = connection.handle_request( - pool_request.request - ) + response = connection.handle_request(pool_request.request) except ConnectionNotAvailable: # In some cases a connection may initially be available to # handle a request, but then become unavailable. @@ -263,9 +247,7 @@ def handle_request(self, request: Request) -> Response: return Response( status=response.status, headers=response.headers, - content=PoolByteStream( - stream=response.stream, pool_request=pool_request, pool=self - ), + content=PoolByteStream(stream=response.stream, pool_request=pool_request, pool=self), extensions=response.extensions, ) @@ -293,8 +275,7 @@ def _assign_requests_to_connections(self) -> list[ConnectionInterface]: closing_connections.append(connection) elif ( connection.is_idle() - and sum(connection.is_idle() for connection in self._connections) - > self._max_keepalive_connections + and sum(connection.is_idle() for connection in self._connections) > self._max_keepalive_connections ): # log: "closing idle connection" self._connections.remove(connection) @@ -309,9 +290,7 @@ def _assign_requests_to_connections(self) -> list[ConnectionInterface]: for connection in self._connections if connection.can_handle_request(origin) and connection.is_available() ] - idle_connections = [ - connection for connection in self._connections if connection.is_idle() - ] + idle_connections = [connection for connection in self._connections if connection.is_idle()] # There are three cases for how we may be able to handle the request: # @@ -369,21 +348,15 @@ def __repr__(self) -> str: class_name = self.__class__.__name__ with self._optional_thread_lock: request_is_queued = [request.is_queued() for request in self._requests] - connection_is_idle = [ - connection.is_idle() for connection in self._connections - ] + connection_is_idle = [connection.is_idle() for connection in self._connections] num_active_requests = request_is_queued.count(False) num_queued_requests = request_is_queued.count(True) num_active_connections = connection_is_idle.count(False) num_idle_connections = connection_is_idle.count(True) - requests_info = ( - f"Requests: {num_active_requests} active, {num_queued_requests} queued" - ) - connection_info = ( - f"Connections: {num_active_connections} active, {num_idle_connections} idle" - ) + requests_info = f"Requests: {num_active_requests} active, {num_queued_requests} queued" + connection_info = f"Connections: {num_active_connections} active, {num_idle_connections} idle" return f"<{class_name} [{requests_info} | {connection_info}]>" diff --git a/src/httpcore2/httpcore2/_sync/http11.py b/src/httpcore2/httpcore2/_sync/http11.py index 8a41e9e2..39554924 100644 --- a/src/httpcore2/httpcore2/_sync/http11.py +++ b/src/httpcore2/httpcore2/_sync/http11.py @@ -66,10 +66,7 @@ def __init__( def handle_request(self, request: Request) -> Response: if not self.can_handle_request(request.url.origin): - raise RuntimeError( - f"Attempted to send request to {request.url.origin} on connection " - f"to {self._origin}" - ) + raise RuntimeError(f"Attempted to send request to {request.url.origin} on connection to {self._origin}") with self._state_lock: if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE): @@ -82,9 +79,7 @@ def handle_request(self, request: Request) -> Response: try: kwargs = {"request": request} try: - with Trace( - "send_request_headers", logger, request, kwargs - ) as trace: + with Trace("send_request_headers", logger, request, kwargs) as trace: self._send_request_headers(**kwargs) with Trace("send_request_body", logger, request, kwargs) as trace: self._send_request_body(**kwargs) @@ -96,9 +91,7 @@ def handle_request(self, request: Request) -> Response: # error response. pass - with Trace( - "receive_response_headers", logger, request, kwargs - ) as trace: + with Trace("receive_response_headers", logger, request, kwargs) as trace: ( http_version, status, @@ -116,9 +109,7 @@ def handle_request(self, request: Request) -> Response: network_stream = self._network_stream # CONNECT or Upgrade request - if (status == 101) or ( - (request.method == b"CONNECT") and (200 <= status < 300) - ): + if (status == 101) or ((request.method == b"CONNECT") and (200 <= status < 300)): network_stream = HTTP11UpgradeStream(network_stream, trailing_data) return Response( @@ -180,10 +171,7 @@ def _receive_response_headers( event = self._receive_event(timeout=timeout) if isinstance(event, h11.Response): break - if ( - isinstance(event, h11.InformationalResponse) - and event.status_code == 101 - ): + if isinstance(event, h11.InformationalResponse) and event.status_code == 101: break http_version = b"HTTP/" + event.http_version @@ -207,17 +195,13 @@ def _receive_response_body(self, request: Request) -> Generator[bytes]: elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)): break - def _receive_event( - self, timeout: float | None = None - ) -> h11.Event | type[h11.PAUSED]: + def _receive_event(self, timeout: float | None = None) -> h11.Event | type[h11.PAUSED]: while True: with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): event = self._h11_state.next_event() if event is h11.NEED_DATA: - data = self._network_stream.read( - self.READ_NUM_BYTES, timeout=timeout - ) + data = self._network_stream.read(self.READ_NUM_BYTES, timeout=timeout) # If we feed this case through h11 we'll raise an exception like: # @@ -238,10 +222,7 @@ def _receive_event( def _response_closed(self) -> None: with self._state_lock: - if ( - self._h11_state.our_state is h11.DONE - and self._h11_state.their_state is h11.DONE - ): + if self._h11_state.our_state is h11.DONE and self._h11_state.their_state is h11.DONE: self._state = HTTPConnectionState.IDLE self._h11_state.start_next_cycle() if self._keepalive_expiry is not None: @@ -279,9 +260,8 @@ def has_expired(self) -> bool: # If the HTTP connection is idle but the socket is readable, then the # only valid state is that the socket is about to return b"", indicating # a server-initiated disconnect. - server_disconnected = ( - self._state == HTTPConnectionState.IDLE - and self._network_stream.get_extra_info("is_readable") + server_disconnected = self._state == HTTPConnectionState.IDLE and self._network_stream.get_extra_info( + "is_readable" ) return keepalive_expired or server_disconnected @@ -294,18 +274,12 @@ def is_closed(self) -> bool: def info(self) -> str: origin = str(self._origin) - return ( - f"{origin!r}, HTTP/1.1, {self._state.name}, " - f"Request Count: {self._request_count}" - ) + return f"{origin!r}, HTTP/1.1, {self._state.name}, Request Count: {self._request_count}" def __repr__(self) -> str: class_name = self.__class__.__name__ origin = str(self._origin) - return ( - f"<{class_name} [{origin!r}, {self._state.name}, " - f"Request Count: {self._request_count}]>" - ) + return f"<{class_name} [{origin!r}, {self._state.name}, Request Count: {self._request_count}]>" # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. @@ -332,9 +306,7 @@ def __iter__(self) -> Generator[bytes]: kwargs = {"request": self._request} try: with Trace("receive_response_body", logger, self._request, kwargs): - with safe_iterate( - self._connection._receive_response_body(**kwargs) - ) as iterator: + with safe_iterate(self._connection._receive_response_body(**kwargs)) as iterator: for chunk in iterator: yield chunk except BaseException as exc: diff --git a/src/httpcore2/httpcore2/_sync/http2.py b/src/httpcore2/httpcore2/_sync/http2.py index 8d9ed998..f15c35fe 100644 --- a/src/httpcore2/httpcore2/_sync/http2.py +++ b/src/httpcore2/httpcore2/_sync/http2.py @@ -29,10 +29,7 @@ def has_body_headers(request: Request) -> bool: - return any( - k.lower() == b"content-length" or k.lower() == b"transfer-encoding" - for k, v in request.headers - ) + return any(k.lower() == b"content-length" or k.lower() == b"transfer-encoding" for k, v in request.headers) class HTTPConnectionState(enum.IntEnum): @@ -69,12 +66,7 @@ def __init__( # Mapping from stream ID to response stream events. self._events: dict[ int, - list[ - h2.events.ResponseReceived - | h2.events.DataReceived - | h2.events.StreamEnded - | h2.events.StreamReset, - ], + list[h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded | h2.events.StreamReset,], ] = {} # Connection terminated events are stored as state since @@ -90,10 +82,7 @@ def handle_request(self, request: Request) -> Response: # will only send requests on connections that handle them. # It's in place simply for resilience as a guard against incorrect # usage, for anyone working directly with httpcore connections. - raise RuntimeError( - f"Attempted to send request to {request.url.origin} on connection " - f"to {self._origin}" - ) + raise RuntimeError(f"Attempted to send request to {request.url.origin} on connection to {self._origin}") with self._state_lock: if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE): @@ -107,9 +96,7 @@ def handle_request(self, request: Request) -> Response: if not self._sent_connection_init: try: sci_kwargs = {"request": request} - with Trace( - "send_connection_init", logger, request, sci_kwargs - ): + with Trace("send_connection_init", logger, request, sci_kwargs): self._send_connection_init(**sci_kwargs) except BaseException as exc: with ShieldCancellation(): @@ -122,9 +109,7 @@ def handle_request(self, request: Request) -> Response: # its max_concurrent_streams value self._max_streams = 1 - local_settings_max_streams = ( - self._h2_state.local_settings.max_concurrent_streams - ) + local_settings_max_streams = self._h2_state.local_settings.max_concurrent_streams self._max_streams_semaphore = Semaphore(local_settings_max_streams) for _ in range(local_settings_max_streams - self._max_streams): @@ -146,12 +131,8 @@ def handle_request(self, request: Request) -> Response: self._send_request_headers(request=request, stream_id=stream_id) with Trace("send_request_body", logger, request, kwargs): self._send_request_body(request=request, stream_id=stream_id) - with Trace( - "receive_response_headers", logger, request, kwargs - ) as trace: - status, headers = self._receive_response( - request=request, stream_id=stream_id - ) + with Trace("receive_response_headers", logger, request, kwargs) as trace: + status, headers = self._receive_response(request=request, stream_id=stream_id) trace.return_value = (status, headers) return Response( @@ -211,9 +192,7 @@ def _send_connection_init(self, request: Request) -> None: # Some websites (*cough* Yahoo *cough*) balk at this setting being # present in the initial handshake since it's not defined in the original # RFC despite the RFC mandating ignoring settings you don't know about. - del self._h2_state.local_settings[ - h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL - ] + del self._h2_state.local_settings[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] self._h2_state.initiate_connection() self._h2_state.increment_flow_control_window(2**24) @@ -266,9 +245,7 @@ def _send_request_body(self, request: Request, stream_id: int) -> None: self._send_end_stream(request, stream_id) - def _send_stream_data( - self, request: Request, stream_id: int, data: bytes - ) -> None: + def _send_stream_data(self, request: Request, stream_id: int, data: bytes) -> None: """ Send a single chunk of data in one or more data frames. """ @@ -288,9 +265,7 @@ def _send_end_stream(self, request: Request, stream_id: int) -> None: # Receiving the response... - def _receive_response( - self, request: Request, stream_id: int - ) -> tuple[int, list[tuple[bytes, bytes]]]: + def _receive_response(self, request: Request, stream_id: int) -> tuple[int, list[tuple[bytes, bytes]]]: """ Return the response status code and headers for a given stream ID. """ @@ -310,9 +285,7 @@ def _receive_response( return (status_code, headers) - def _receive_response_body( - self, request: Request, stream_id: int - ) -> Generator[bytes]: + def _receive_response_body(self, request: Request, stream_id: int) -> Generator[bytes]: """ Iterator that returns the bytes of the response body for a given stream ID. """ @@ -343,9 +316,7 @@ def _receive_stream_event( raise RemoteProtocolError(event) return event - def _receive_events( - self, request: Request, stream_id: int | None = None - ) -> None: + def _receive_events(self, request: Request, stream_id: int | None = None) -> None: """ Read some data from the network until we see one or more events for a given stream ID. @@ -368,9 +339,7 @@ def _receive_events( events = self._read_incoming_data(request) for event in events: if isinstance(event, h2.events.RemoteSettingsChanged): - with Trace( - "receive_remote_settings", logger, request - ) as trace: + with Trace("receive_remote_settings", logger, request) as trace: self._receive_remote_settings_change(event) trace.return_value = event @@ -391,12 +360,8 @@ def _receive_events( self._write_outgoing_data(request) - def _receive_remote_settings_change( - self, event: h2.events.RemoteSettingsChanged - ) -> None: - max_concurrent_streams = event.changed_settings.get( - h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS - ) + def _receive_remote_settings_change(self, event: h2.events.RemoteSettingsChanged) -> None: + max_concurrent_streams = event.changed_settings.get(h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS) if max_concurrent_streams: new_max_streams = min( max_concurrent_streams.new_value, @@ -517,10 +482,7 @@ def is_available(self) -> bool: self._state != HTTPConnectionState.CLOSED and not self._connection_error and not self._used_all_stream_ids - and not ( - self._h2_state.state_machine.state - == h2.connection.ConnectionState.CLOSED - ) + and not (self._h2_state.state_machine.state == h2.connection.ConnectionState.CLOSED) ) def has_expired(self) -> bool: @@ -535,18 +497,12 @@ def is_closed(self) -> bool: def info(self) -> str: origin = str(self._origin) - return ( - f"{origin!r}, HTTP/2, {self._state.name}, " - f"Request Count: {self._request_count}" - ) + return f"{origin!r}, HTTP/2, {self._state.name}, Request Count: {self._request_count}" def __repr__(self) -> str: class_name = self.__class__.__name__ origin = str(self._origin) - return ( - f"<{class_name} [{origin!r}, {self._state.name}, " - f"Request Count: {self._request_count}]>" - ) + return f"<{class_name} [{origin!r}, {self._state.name}, Request Count: {self._request_count}]>" # These context managers are not used in the standard flow, but are # useful for testing or working with connection instances directly. @@ -564,9 +520,7 @@ def __exit__( class HTTP2ConnectionByteStream: - def __init__( - self, connection: HTTP2Connection, request: Request, stream_id: int - ) -> None: + def __init__(self, connection: HTTP2Connection, request: Request, stream_id: int) -> None: self._connection = connection self._request = request self._stream_id = stream_id @@ -577,9 +531,7 @@ def __iter__(self) -> Generator[bytes]: try: with Trace("receive_response_body", logger, self._request, kwargs): with safe_iterate( - self._connection._receive_response_body( - request=self._request, stream_id=self._stream_id - ) + self._connection._receive_response_body(request=self._request, stream_id=self._stream_id) ) as iterator: for chunk in iterator: yield chunk diff --git a/src/httpcore2/httpcore2/_sync/http_proxy.py b/src/httpcore2/httpcore2/_sync/http_proxy.py index bbc5317a..8c6f8976 100644 --- a/src/httpcore2/httpcore2/_sync/http_proxy.py +++ b/src/httpcore2/httpcore2/_sync/http_proxy.py @@ -43,11 +43,7 @@ def merge_headers( default_headers = [] if default_headers is None else list(default_headers) override_headers = [] if override_headers is None else list(override_headers) has_override = set(key.lower() for key, value in override_headers) - default_headers = [ - (key, value) - for key, value in default_headers - if key.lower() not in has_override - ] + default_headers = [(key, value) for key, value in default_headers if key.lower() not in has_override] return default_headers + override_headers @@ -124,12 +120,8 @@ def __init__( ) self._proxy_url = enforce_url(proxy_url, name="proxy_url") - if ( - self._proxy_url.scheme == b"http" and proxy_ssl_context is not None - ): # pragma: no cover - raise RuntimeError( - "The `proxy_ssl_context` argument is not allowed for the http scheme" - ) + if self._proxy_url.scheme == b"http" and proxy_ssl_context is not None: # pragma: no cover + raise RuntimeError("The `proxy_ssl_context` argument is not allowed for the http scheme") self._ssl_context = ssl_context self._proxy_ssl_context = proxy_ssl_context @@ -139,9 +131,7 @@ def __init__( password = enforce_bytes(proxy_auth[1], name="proxy_auth") userpass = username + b":" + password authorization = b"Basic " + base64.b64encode(userpass) - self._proxy_headers = [ - (b"Proxy-Authorization", authorization) - ] + self._proxy_headers + self._proxy_headers = [(b"Proxy-Authorization", authorization)] + self._proxy_headers def create_connection(self, origin: Origin) -> ConnectionInterface: if origin.scheme == b"http": @@ -276,18 +266,14 @@ def handle_request(self, request: Request) -> Response: port=self._proxy_origin.port, target=target, ) - connect_headers = merge_headers( - [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers - ) + connect_headers = merge_headers([(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers) connect_request = Request( method=b"CONNECT", url=connect_url, headers=connect_headers, extensions=request.extensions, ) - connect_response = self._connection.handle_request( - connect_request - ) + connect_response = self._connection.handle_request(connect_request) if connect_response.status < 200 or connect_response.status > 299: reason_bytes = connect_response.extensions.get("reason_phrase", b"") @@ -299,11 +285,7 @@ def handle_request(self, request: Request) -> Response: stream = connect_response.extensions["network_stream"] # Upgrade the stream to SSL - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) + ssl_context = default_ssl_context() if self._ssl_context is None else self._ssl_context alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] ssl_context.set_alpn_protocols(alpn_protocols) @@ -318,10 +300,7 @@ def handle_request(self, request: Request) -> Response: # Determine if we should be using HTTP/1.1 or HTTP/2 ssl_object = stream.get_extra_info("ssl_object") - http2_negotiated = ( - ssl_object is not None - and ssl_object.selected_alpn_protocol() == "h2" - ) + http2_negotiated = ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2" # Create the HTTP/1.1 or HTTP/2 connection if http2_negotiated or (self._http2 and not self._http1): diff --git a/src/httpcore2/httpcore2/_sync/socks_proxy.py b/src/httpcore2/httpcore2/_sync/socks_proxy.py index 9c91af2e..516def0b 100644 --- a/src/httpcore2/httpcore2/_sync/socks_proxy.py +++ b/src/httpcore2/httpcore2/_sync/socks_proxy.py @@ -65,9 +65,7 @@ def _init_socks5_connection( if response.method != auth_method: requested = AUTH_METHODS.get(auth_method, "UNKNOWN") responded = AUTH_METHODS.get(response.method, "UNKNOWN") - raise ProxyError( - f"Requested {requested} from proxy server, but got {responded}." - ) + raise ProxyError(f"Requested {requested} from proxy server, but got {responded}.") if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: # Username/password request @@ -85,11 +83,7 @@ def _init_socks5_connection( raise ProxyError("Invalid username/password") # Connect request - conn.send( - socksio.socks5.SOCKS5CommandRequest.from_address( - socksio.socks5.SOCKS5Command.CONNECT, (host, port) - ) - ) + conn.send(socksio.socks5.SOCKS5CommandRequest.from_address(socksio.socks5.SOCKS5Command.CONNECT, (host, port))) outgoing_bytes = conn.data_to_send() stream.write(outgoing_bytes) @@ -206,9 +200,7 @@ def __init__( self._http1 = http1 self._http2 = http2 - self._network_backend: NetworkBackend = ( - SyncBackend() if network_backend is None else network_backend - ) + self._network_backend: NetworkBackend = SyncBackend() if network_backend is None else network_backend self._connect_lock = Lock() self._connection: ConnectionInterface | None = None self._connect_failed = False @@ -238,28 +230,19 @@ def handle_request(self, request: Request) -> Response: "port": self._remote_origin.port, "auth": self._proxy_auth, } - with Trace( - "setup_socks5_connection", logger, request, kwargs - ) as trace: + with Trace("setup_socks5_connection", logger, request, kwargs) as trace: _init_socks5_connection(**kwargs) trace.return_value = stream # Upgrade the stream to SSL if self._remote_origin.scheme == b"https": - ssl_context = ( - default_ssl_context() - if self._ssl_context is None - else self._ssl_context - ) - alpn_protocols = ( - ["http/1.1", "h2"] if self._http2 else ["http/1.1"] - ) + ssl_context = default_ssl_context() if self._ssl_context is None else self._ssl_context + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] ssl_context.set_alpn_protocols(alpn_protocols) kwargs = { "ssl_context": ssl_context, - "server_hostname": sni_hostname - or self._remote_origin.host.decode("ascii"), + "server_hostname": sni_hostname or self._remote_origin.host.decode("ascii"), "timeout": timeout, } with Trace("start_tls", logger, request, kwargs) as trace: @@ -268,15 +251,10 @@ def handle_request(self, request: Request) -> Response: # Determine if we should be using HTTP/1.1 or HTTP/2 ssl_object = stream.get_extra_info("ssl_object") - http2_negotiated = ( - ssl_object is not None - and ssl_object.selected_alpn_protocol() == "h2" - ) + http2_negotiated = ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2" # Create the HTTP/1.1 or HTTP/2 connection - if http2_negotiated or ( - self._http2 and not self._http1 - ): # pragma: nocover + if http2_negotiated or (self._http2 and not self._http1): # pragma: nocover from .http2 import HTTP2Connection self._connection = HTTP2Connection( @@ -311,9 +289,7 @@ def is_available(self) -> bool: # end up as HTTP/2 then we should indicate the connection as being # available to service multiple requests. return ( - self._http2 - and (self._remote_origin.scheme == b"https" or not self._http1) - and not self._connect_failed + self._http2 and (self._remote_origin.scheme == b"https" or not self._http1) and not self._connect_failed ) return self._connection.is_available() diff --git a/src/httpcore2/httpcore2/_synchronization.py b/src/httpcore2/httpcore2/_synchronization.py index 2ecc9e9c..f13b4920 100644 --- a/src/httpcore2/httpcore2/_synchronization.py +++ b/src/httpcore2/httpcore2/_synchronization.py @@ -33,14 +33,10 @@ def current_async_library() -> str: raise RuntimeError("Running under an unsupported async environment.") if environment == "asyncio" and anyio is None: # pragma: nocover - raise RuntimeError( - "Running with asyncio requires installation of 'httpcore[asyncio]'." - ) + raise RuntimeError("Running with asyncio requires installation of 'httpcore[asyncio]'.") if environment == "trio" and trio is None: # pragma: nocover - raise RuntimeError( - "Running with trio requires installation of 'httpcore[trio]'." - ) + raise RuntimeError("Running with trio requires installation of 'httpcore[trio]'.") return environment @@ -163,13 +159,9 @@ def setup(self) -> None: """ self._backend = current_async_library() if self._backend == "trio": - self._trio_semaphore = trio.Semaphore( - initial_value=self._bound, max_value=self._bound - ) + self._trio_semaphore = trio.Semaphore(initial_value=self._bound, max_value=self._bound) elif self._backend == "asyncio": - self._anyio_semaphore = anyio.Semaphore( - initial_value=self._bound, max_value=self._bound - ) + self._anyio_semaphore = anyio.Semaphore(initial_value=self._bound, max_value=self._bound) async def acquire(self) -> None: if not self._backend: diff --git a/src/httpcore2/httpcore2/_trace.py b/src/httpcore2/httpcore2/_trace.py index 5f1cd7c4..8e33ec70 100644 --- a/src/httpcore2/httpcore2/_trace.py +++ b/src/httpcore2/httpcore2/_trace.py @@ -18,9 +18,7 @@ def __init__( ) -> None: self.name = name self.logger = logger - self.trace_extension = ( - None if request is None else request.extensions.get("trace") - ) + self.trace_extension = None if request is None else request.extensions.get("trace") self.debug = self.logger.isEnabledFor(logging.DEBUG) self.kwargs = kwargs or {} self.return_value: typing.Any = None diff --git a/src/httpcore2/httpcore2/_utils.py b/src/httpcore2/httpcore2/_utils.py index 6951457d..2227300c 100644 --- a/src/httpcore2/httpcore2/_utils.py +++ b/src/httpcore2/httpcore2/_utils.py @@ -40,9 +40,7 @@ def is_socket_readable(sock: socket.socket | None) -> bool: # Use select.select on Windows, and when poll is unavailable and select.poll # everywhere else. (E.g. When eventlet is in use. See #327) - if ( - sys.platform == "win32" or getattr(select, "poll", None) is None - ): # pragma: nocover + if sys.platform == "win32" or getattr(select, "poll", None) is None: # pragma: nocover rready, _, _ = select.select([sock_fd], [], [], 0) return bool(rready) p = select.poll() @@ -55,9 +53,7 @@ async def safe_async_iterate( iterable_or_iterator: AsyncIterable[T] | AsyncIterator[T], / ) -> AsyncGenerator[AsyncIterator[T]]: iterator = ( - iterable_or_iterator - if isinstance(iterable_or_iterator, AsyncIterator) - else iterable_or_iterator.__aiter__() + iterable_or_iterator if isinstance(iterable_or_iterator, AsyncIterator) else iterable_or_iterator.__aiter__() ) try: yield iterator @@ -67,13 +63,7 @@ async def safe_async_iterate( @contextmanager -def safe_iterate( - iterable_or_iterator: Iterable[T] | Iterator[T], / -) -> Generator[Iterator[T], None, None]: +def safe_iterate(iterable_or_iterator: Iterable[T] | Iterator[T], /) -> Generator[Iterator[T], None, None]: # This is boilerplate code, only needed to make unasync happy - iterator = ( - iterable_or_iterator - if isinstance(iterable_or_iterator, Iterator) - else iterable_or_iterator.__iter__() - ) + iterator = iterable_or_iterator if isinstance(iterable_or_iterator, Iterator) else iterable_or_iterator.__iter__() yield iterator diff --git a/src/httpcore2/pyproject.toml b/src/httpcore2/pyproject.toml index 4d3c325e..dcc25f2f 100644 --- a/src/httpcore2/pyproject.toml +++ b/src/httpcore2/pyproject.toml @@ -1,5 +1,9 @@ [build-system] -requires = ["hatchling", "hatch-fancy-pypi-readme", "uv-dynamic-versioning>=0.7.0"] +requires = [ + "hatchling", + "hatch-fancy-pypi-readme", + "uv-dynamic-versioning>=0.7.0", +] build-backend = "hatchling.build" [tool.hatch.version] @@ -16,11 +20,9 @@ dynamic = ["readme", "version"] description = "A minimal low-level HTTP client." license = "BSD-3-Clause" requires-python = ">=3.10" -authors = [ - { name = "Tom Christie", email = "tom@tomchristie.com" }, -] +authors = [{ name = "Tom Christie", email = "tom@tomchristie.com" }] maintainers = [ - { name = "Pydantic Services Inc.", email = "engineering@pydantic.dev" } + { name = "Pydantic Services Inc.", email = "engineering@pydantic.dev" }, ] classifiers = [ "Development Status :: 3 - Alpha", @@ -39,24 +41,13 @@ classifiers = [ "Programming Language :: Python :: 3.14", "Topic :: Internet :: WWW/HTTP", ] -dependencies = [ - "certifi", - "h11>=0.16", -] +dependencies = ["certifi", "h11>=0.16"] [project.optional-dependencies] -http2 = [ - "h2>=3,<5", -] -socks = [ - "socksio==1.*", -] -trio = [ - "trio>=0.22.0,<1.0", -] -asyncio = [ - "anyio>=4.0,<5.0", -] +http2 = ["h2>=3,<5"] +socks = ["socksio==1.*"] +trio = ["trio>=0.22.0,<1.0"] +asyncio = ["anyio>=4.0,<5.0"] [project.urls] Documentation = "https://www.encode.io/httpcore" @@ -64,13 +55,7 @@ Homepage = "https://www.encode.io/httpcore/" Source = "https://github.com/encode/httpcore" [tool.hatch.build.targets.sdist] -include = [ - "/httpcore2", - "/CHANGELOG.md", - "/LICENSE.md", - "/README.md", - "/tests" -] +include = ["/httpcore2", "/CHANGELOG.md", "/LICENSE.md", "/README.md", "/tests"] [tool.hatch.metadata.hooks.fancy-pypi-readme] content-type = "text/markdown" @@ -80,44 +65,3 @@ path = "README.md" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "CHANGELOG.md" - -[tool.mypy] -strict = true -show_error_codes = true - -[[tool.mypy.overrides]] -module = "tests.*" -disallow_untyped_defs = false -check_untyped_defs = true - -[[tool.mypy.overrides]] -module = "h2.*" -ignore_missing_imports = true - -[[tool.mypy.overrides]] -module = "hpack.*" -ignore_missing_imports = true - -[tool.pytest.ini_options] -addopts = ["-rxXs", "--strict-config", "--strict-markers"] -markers = ["copied_from(source, changes=None): mark test as copied from somewhere else, along with a description of changes made to accodomate e.g. our test setup"] -filterwarnings = ["error"] - -[tool.coverage.run] -omit = ["httpcore2/_sync/*"] -source_pkgs = ["httpcore2", "tests"] - -[tool.ruff] -exclude = [ - "httpcore2/_sync", - "tests/_sync", -] - -[tool.ruff.lint] -select = ["E", "F", "W", "I"] - -[tool.ruff.lint.pycodestyle] -max-line-length = 120 - -[tool.ruff.lint.isort] -combine-as-imports = true diff --git a/src/httpx2/httpx2/_auth.py b/src/httpx2/httpx2/_auth.py index 9d24faed..8ac028be 100644 --- a/src/httpx2/httpx2/_auth.py +++ b/src/httpx2/httpx2/_auth.py @@ -59,9 +59,7 @@ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, Non """ yield request - def sync_auth_flow( - self, request: Request - ) -> typing.Generator[Request, Response, None]: + def sync_auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: """ Execute the authentication flow synchronously. @@ -84,9 +82,7 @@ def sync_auth_flow( except StopIteration: break - async def async_auth_flow( - self, request: Request - ) -> typing.AsyncGenerator[Request, Response]: + async def async_auth_flow(self, request: Request) -> typing.AsyncGenerator[Request, Response]: """ Execute the authentication flow asynchronously. @@ -161,9 +157,7 @@ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, Non yield request else: # Build a basic auth header with credentials from the netrc file. - request.headers["Authorization"] = self._build_auth_header( - username=auth_info[0], password=auth_info[2] - ) + request.headers["Authorization"] = self._build_auth_header(username=auth_info[0], password=auth_info[2]) yield request def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str: @@ -192,9 +186,7 @@ def __init__(self, username: str | bytes, password: str | bytes) -> None: def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: if self._last_challenge: - request.headers["Authorization"] = self._build_auth_header( - request, self._last_challenge - ) + request.headers["Authorization"] = self._build_auth_header(request, self._last_challenge) response = yield request @@ -214,16 +206,12 @@ def auth_flow(self, request: Request) -> typing.Generator[Request, Response, Non self._last_challenge = self._parse_challenge(request, response, auth_header) self._nonce_count = 1 - request.headers["Authorization"] = self._build_auth_header( - request, self._last_challenge - ) + request.headers["Authorization"] = self._build_auth_header(request, self._last_challenge) if response.cookies: Cookies(response.cookies).set_cookie_header(request=request) yield request - def _parse_challenge( - self, request: Request, response: Response, auth_header: str - ) -> _DigestAuthChallenge: + def _parse_challenge(self, request: Request, response: Response, auth_header: str) -> _DigestAuthChallenge: """ Returns a challenge from a Digest WWW-Authenticate header. These take the form of: @@ -245,16 +233,12 @@ def _parse_challenge( algorithm = header_dict.get("algorithm", "MD5") opaque = header_dict["opaque"].encode() if "opaque" in header_dict else None qop = header_dict["qop"].encode() if "qop" in header_dict else None - return _DigestAuthChallenge( - realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop - ) + return _DigestAuthChallenge(realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop) except KeyError as exc: message = "Malformed Digest WWW-Authenticate header" raise ProtocolError(message, request=request) from exc - def _build_auth_header( - self, request: Request, challenge: _DigestAuthChallenge - ) -> str: + def _build_auth_header(self, request: Request, challenge: _DigestAuthChallenge) -> str: hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()] def digest(data: bytes) -> bytes: @@ -317,11 +301,7 @@ def _get_header_value(self, header_fields: dict[str, bytes]) -> str: for i, (field, value) in enumerate(header_fields.items()): if i > 0: header_value += ", " - template = ( - QUOTED_TEMPLATE - if field not in NON_QUOTED_FIELDS - else NON_QUOTED_TEMPLATE - ) + template = QUOTED_TEMPLATE if field not in NON_QUOTED_FIELDS else NON_QUOTED_TEMPLATE header_value += template.format(field, to_str(value)) return header_value diff --git a/src/httpx2/httpx2/_client.py b/src/httpx2/httpx2/_client.py index 868f7e05..3c950679 100644 --- a/src/httpx2/httpx2/_client.py +++ b/src/httpx2/httpx2/_client.py @@ -84,11 +84,7 @@ def _same_origin(url: URL, other: URL) -> bool: """ Return 'True' if the given URLs share the same origin. """ - return ( - url.scheme == other.scheme - and url.host == other.host - and _port_or_default(url) == _port_or_default(other) - ) + return url.scheme == other.scheme and url.host == other.host and _port_or_default(url) == _port_or_default(other) class UseClientDefault: @@ -117,9 +113,7 @@ class UseClientDefault: logger = logging.getLogger("httpx2") USER_AGENT = f"python-httpx2/{__version__}" -ACCEPT_ENCODING = ", ".join( - [key for key in SUPPORTED_DECODERS.keys() if key != "identity"] -) +ACCEPT_ENCODING = ", ".join([key for key in SUPPORTED_DECODERS.keys() if key != "identity"]) class ClientState(enum.Enum): @@ -142,9 +136,7 @@ class BoundSyncStream(SyncByteStream): ensures the `response.elapsed` is set once the response is closed. """ - def __init__( - self, stream: SyncByteStream, response: Response, start: float - ) -> None: + def __init__(self, stream: SyncByteStream, response: Response, start: float) -> None: self._stream = stream self._response = response self._start = start @@ -165,9 +157,7 @@ class BoundAsyncStream(AsyncByteStream): ensures the `response.elapsed` is set once the response is closed. """ - def __init__( - self, stream: AsyncByteStream, response: Response, start: float - ) -> None: + def __init__(self, stream: AsyncByteStream, response: Response, start: float) -> None: self._stream = stream self._response = response self._start = start @@ -236,15 +226,10 @@ def _enforce_trailing_slash(self, url: URL) -> URL: return url return url.copy_with(raw_path=url.raw_path + b"/") - def _get_proxy_map( - self, proxy: ProxyTypes | None, allow_env_proxies: bool - ) -> dict[str, Proxy | None]: + def _get_proxy_map(self, proxy: ProxyTypes | None, allow_env_proxies: bool) -> dict[str, Proxy | None]: if proxy is None: if allow_env_proxies: - return { - key: None if url is None else Proxy(url=url) - for key, url in get_environment_proxies().items() - } + return {key: None if url is None else Proxy(url=url) for key, url in get_environment_proxies().items()} return {} else: proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy @@ -369,11 +354,7 @@ def build_request( params = self._merge_queryparams(params) extensions = {} if extensions is None else extensions if "timeout" not in extensions: - timeout = ( - self.timeout - if isinstance(timeout, UseClientDefault) - else Timeout(timeout) - ) + timeout = self.timeout if isinstance(timeout, UseClientDefault) else Timeout(timeout) extensions = dict(**extensions, timeout=timeout.as_dict()) return Request( method, @@ -430,9 +411,7 @@ def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | No merged_headers.update(headers) return merged_headers - def _merge_queryparams( - self, params: QueryParamTypes | None = None - ) -> QueryParamTypes | None: + def _merge_queryparams(self, params: QueryParamTypes | None = None) -> QueryParamTypes | None: """ Merge a queryparams argument together with any queryparams on the client, to create the queryparams used for the outgoing request. @@ -459,9 +438,7 @@ def _build_request_auth( request: Request, auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, ) -> Auth: - auth = ( - self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth) - ) + auth = self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth) if auth is not None: return auth @@ -523,9 +500,7 @@ def _redirect_url(self, request: Request, response: Response) -> URL: try: url = URL(location) except InvalidURL as exc: - raise RemoteProtocolError( - f"Invalid URL in location header: {exc}.", request=request - ) from None + raise RemoteProtocolError(f"Invalid URL in location header: {exc}.", request=request) from None # Handle malformed 'Location' headers that are "absolute" form, have no host. # See: https://github.com/encode/httpx/issues/771 @@ -570,9 +545,7 @@ def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers: return headers - def _redirect_stream( - self, request: Request, method: str - ) -> SyncByteStream | AsyncByteStream | None: + def _redirect_stream(self, request: Request, method: str) -> SyncByteStream | AsyncByteStream | None: """ Return the body that should be used for the redirect request. """ @@ -583,11 +556,7 @@ def _redirect_stream( def _set_timeout(self, request: Request) -> None: if "timeout" not in request.extensions: - timeout = ( - self.timeout - if isinstance(self.timeout, UseClientDefault) - else Timeout(self.timeout) - ) + timeout = self.timeout if isinstance(self.timeout, UseClientDefault) else Timeout(self.timeout) request.extensions = dict(**request.extensions, timeout=timeout.as_dict()) @@ -711,9 +680,7 @@ def __init__( for key, proxy in proxy_map.items() } if mounts is not None: - self._mounts.update( - {URLPattern(key): transport for key, transport in mounts.items()} - ) + self._mounts.update({URLPattern(key): transport for key, transport in mounts.items()}) self._mounts = dict(sorted(self._mounts.items())) @@ -903,11 +870,7 @@ def send( raise RuntimeError("Cannot send a request, as the client has been closed.") self._state = ClientState.OPENED - follow_redirects = ( - self.follow_redirects - if isinstance(follow_redirects, UseClientDefault) - else follow_redirects - ) + follow_redirects = self.follow_redirects if isinstance(follow_redirects, UseClientDefault) else follow_redirects self._set_timeout(request) @@ -971,9 +934,7 @@ def _send_handling_redirects( ) -> Response: while True: if len(history) > self.max_redirects: - raise TooManyRedirects( - "Exceeded maximum allowed redirects.", request=request - ) + raise TooManyRedirects("Exceeded maximum allowed redirects.", request=request) for hook in self._event_hooks["request"]: hook(request) @@ -1008,9 +969,7 @@ def _send_single_request(self, request: Request) -> Response: start = time.perf_counter() if not isinstance(request.stream, SyncByteStream): - raise RuntimeError( - "Attempted to send an async request with a sync Client instance." - ) + raise RuntimeError("Attempted to send an async request with a sync Client instance.") with request_context(request=request): response = transport.handle_request(request) @@ -1018,9 +977,7 @@ def _send_single_request(self, request: Request) -> Response: assert isinstance(response.stream, SyncByteStream) response.request = request - response.stream = BoundSyncStream( - response.stream, response=response, start=start - ) + response.stream = BoundSyncStream(response.stream, response=response, start=start) self.cookies.extract_cookies(response) response.default_encoding = self._default_encoding @@ -1278,9 +1235,7 @@ def __enter__(self: T) -> T: if self._state != ClientState.UNOPENED: msg = { ClientState.OPENED: "Cannot open a client instance more than once.", - ClientState.CLOSED: ( - "Cannot reopen a client instance, once it has been closed." - ), + ClientState.CLOSED: ("Cannot reopen a client instance, once it has been closed."), }[self._state] raise RuntimeError(msg) @@ -1428,9 +1383,7 @@ def __init__( for key, proxy in proxy_map.items() } if mounts is not None: - self._mounts.update( - {URLPattern(key): transport for key, transport in mounts.items()} - ) + self._mounts.update({URLPattern(key): transport for key, transport in mounts.items()}) self._mounts = dict(sorted(self._mounts.items())) def _init_transport( @@ -1620,11 +1573,7 @@ async def send( raise RuntimeError("Cannot send a request, as the client has been closed.") self._state = ClientState.OPENED - follow_redirects = ( - self.follow_redirects - if isinstance(follow_redirects, UseClientDefault) - else follow_redirects - ) + follow_redirects = self.follow_redirects if isinstance(follow_redirects, UseClientDefault) else follow_redirects self._set_timeout(request) @@ -1688,9 +1637,7 @@ async def _send_handling_redirects( ) -> Response: while True: if len(history) > self.max_redirects: - raise TooManyRedirects( - "Exceeded maximum allowed redirects.", request=request - ) + raise TooManyRedirects("Exceeded maximum allowed redirects.", request=request) for hook in self._event_hooks["request"]: await hook(request) @@ -1726,18 +1673,14 @@ async def _send_single_request(self, request: Request) -> Response: start = time.perf_counter() if not isinstance(request.stream, AsyncByteStream): - raise RuntimeError( - "Attempted to send a sync request with an AsyncClient instance." - ) + raise RuntimeError("Attempted to send a sync request with an AsyncClient instance.") with request_context(request=request): response = await transport.handle_async_request(request) assert isinstance(response.stream, AsyncByteStream) response.request = request - response.stream = BoundAsyncStream( - response.stream, response=response, start=start - ) + response.stream = BoundAsyncStream(response.stream, response=response, start=start) self.cookies.extract_cookies(response) response.default_encoding = self._default_encoding @@ -1995,9 +1938,7 @@ async def __aenter__(self: U) -> U: if self._state != ClientState.UNOPENED: msg = { ClientState.OPENED: "Cannot open a client instance more than once.", - ClientState.CLOSED: ( - "Cannot reopen a client instance, once it has been closed." - ), + ClientState.CLOSED: ("Cannot reopen a client instance, once it has been closed."), }[self._state] raise RuntimeError(msg) diff --git a/src/httpx2/httpx2/_config.py b/src/httpx2/httpx2/_config.py index 2209e122..46a6e6ec 100644 --- a/src/httpx2/httpx2/_config.py +++ b/src/httpx2/httpx2/_config.py @@ -120,10 +120,7 @@ def __init__( self.pool = pool else: if isinstance(timeout, UnsetType): - raise ValueError( - "httpx2.Timeout must either include a default, or set all " - "four parameters explicitly." - ) + raise ValueError("httpx2.Timeout must either include a default, or set all four parameters explicitly.") self.connect = timeout if isinstance(connect, UnsetType) else connect self.read = timeout if isinstance(read, UnsetType) else read self.write = timeout if isinstance(write, UnsetType) else write @@ -150,10 +147,7 @@ def __repr__(self) -> str: class_name = self.__class__.__name__ if len({self.connect, self.read, self.write, self.pool}) == 1: return f"{class_name}(timeout={self.connect})" - return ( - f"{class_name}(connect={self.connect}, " - f"read={self.read}, write={self.write}, pool={self.pool})" - ) + return f"{class_name}(connect={self.connect}, read={self.read}, write={self.write}, pool={self.pool})" class Limits: @@ -226,11 +220,7 @@ def __init__( @property def raw_auth(self) -> tuple[bytes, bytes] | None: # The proxy authentication as raw bytes. - return ( - None - if self.auth is None - else (self.auth[0].encode("utf-8"), self.auth[1].encode("utf-8")) - ) + return None if self.auth is None else (self.auth[0].encode("utf-8"), self.auth[1].encode("utf-8")) def __repr__(self) -> str: # The authentication is represented with the password component masked. diff --git a/src/httpx2/httpx2/_content.py b/src/httpx2/httpx2/_content.py index 21edadce..71969ecc 100644 --- a/src/httpx2/httpx2/_content.py +++ b/src/httpx2/httpx2/_content.py @@ -174,9 +174,7 @@ def encode_html(html: str) -> tuple[dict[str, str], ByteStream]: def encode_json(json: Any) -> tuple[dict[str, str], ByteStream]: - body = json_dumps( - json, ensure_ascii=False, separators=(",", ":"), allow_nan=False - ).encode("utf-8") + body = json_dumps(json, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8") content_length = str(len(body)) content_type = "application/json" headers = {"Content-Length": content_length, "Content-Type": content_type} diff --git a/src/httpx2/httpx2/_decoders.py b/src/httpx2/httpx2/_decoders.py index 3a535c6d..7c1f98cc 100644 --- a/src/httpx2/httpx2/_decoders.py +++ b/src/httpx2/httpx2/_decoders.py @@ -192,8 +192,7 @@ class ZStandardDecoder(ContentDecoder): def __init__(self) -> None: if not _zstandard_installed: # pragma: no cover raise ImportError( - "Using 'ZStandardDecoder', ..." - "Make sure to install httpx using `pip install httpx[zstd]`." + "Using 'ZStandardDecoder', ...Make sure to install httpx using `pip install httpx[zstd]`." ) from None self.decompressor = ZstdDecompressor() @@ -261,10 +260,7 @@ def decode(self, content: bytes) -> list[bytes]: self._buffer.write(content) if self._buffer.tell() >= self._chunk_size: value = self._buffer.getvalue() - chunks = [ - value[i : i + self._chunk_size] - for i in range(0, len(value), self._chunk_size) - ] + chunks = [value[i : i + self._chunk_size] for i in range(0, len(value), self._chunk_size)] if len(chunks[-1]) == self._chunk_size: self._buffer.seek(0) self._buffer.truncate() @@ -300,10 +296,7 @@ def decode(self, content: str) -> list[str]: self._buffer.write(content) if self._buffer.tell() >= self._chunk_size: value = self._buffer.getvalue() - chunks = [ - value[i : i + self._chunk_size] - for i in range(0, len(value), self._chunk_size) - ] + chunks = [value[i : i + self._chunk_size] for i in range(0, len(value), self._chunk_size)] if len(chunks[-1]) == self._chunk_size: self._buffer.seek(0) self._buffer.truncate() diff --git a/src/httpx2/httpx2/_exceptions.py b/src/httpx2/httpx2/_exceptions.py index ff734c67..ca561853 100644 --- a/src/httpx2/httpx2/_exceptions.py +++ b/src/httpx2/httpx2/_exceptions.py @@ -341,10 +341,7 @@ class ResponseNotRead(StreamError): """ def __init__(self) -> None: - message = ( - "Attempted to access streaming response content," - " without having called `read()`." - ) + message = "Attempted to access streaming response content, without having called `read()`." super().__init__(message) @@ -354,10 +351,7 @@ class RequestNotRead(StreamError): """ def __init__(self) -> None: - message = ( - "Attempted to access streaming request content," - " without having called `read()`." - ) + message = "Attempted to access streaming request content, without having called `read()`." super().__init__(message) diff --git a/src/httpx2/httpx2/_main.py b/src/httpx2/httpx2/_main.py index a18323ec..aefda18d 100644 --- a/src/httpx2/httpx2/_main.py +++ b/src/httpx2/httpx2/_main.py @@ -30,9 +30,7 @@ def print_help() -> None: console.print() console.print("A next generation HTTP client.", justify="center") console.print() - console.print( - "Usage: [bold]httpx2[/bold] [cyan] [OPTIONS][/cyan] ", justify="left" - ) + console.print("Usage: [bold]httpx2[/bold] [cyan] [OPTIONS][/cyan] ", justify="left") console.print() table = rich.table.Table.grid(padding=1, pad_edge=True) @@ -47,12 +45,8 @@ def print_help() -> None: "-p, --params [cyan] ...", "Query parameters to include in the request URL.", ) - table.add_row( - "-c, --content [cyan]TEXT", "Byte content to include in the request body." - ) - table.add_row( - "-d, --data [cyan] ...", "Form data to include in the request body." - ) + table.add_row("-c, --content [cyan]TEXT", "Byte content to include in the request body.") + table.add_row("-d, --data [cyan] ...", "Form data to include in the request body.") table.add_row( "-f, --files [cyan] ...", "Form files to include in the request body.", @@ -62,9 +56,7 @@ def print_help() -> None: "-h, --headers [cyan] ...", "Include additional HTTP headers in the request.", ) - table.add_row( - "--cookies [cyan] ...", "Cookies to include in the request." - ) + table.add_row("--cookies [cyan] ...", "Cookies to include in the request.") table.add_row( "--auth [cyan]", "Username and password to include in the request. Specify '-' for the password" @@ -86,9 +78,7 @@ def print_help() -> None: table.add_row("--follow-redirects", "Automatically follow redirects.") table.add_row("--no-verify", "Disable SSL verification.") - table.add_row( - "--http2", "Send the request using HTTP/2, if the remote server supports it." - ) + table.add_row("--http2", "Send the request using HTTP/2, if the remote server supports it.") table.add_row( "--download [cyan]FILE", @@ -105,9 +95,7 @@ def get_lexer_for_response(response: Response) -> str: if content_type is not None: mime_type, _, _ = content_type.partition(";") try: - return typing.cast( - str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name - ) + return typing.cast(str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name) except pygments.util.ClassNotFound: # pragma: no cover pass return "" # pragma: no cover @@ -115,9 +103,7 @@ def get_lexer_for_response(response: Response) -> str: def format_request_headers(request: httpcore2.Request, http2: bool = False) -> str: version = "HTTP/2" if http2 else "HTTP/1.1" - headers = [ - (name.lower() if http2 else name, value) for name, value in request.headers - ] + headers = [(name.lower() if http2 else name, value) for name, value in request.headers] method = request.method.decode("ascii") target = request.url.target.decode("ascii") lines = [f"{method} {target} {version}"] + [ @@ -133,11 +119,7 @@ def format_response_headers( headers: list[tuple[bytes, bytes]], ) -> str: version = http_version.decode("ascii") - reason = ( - codes.get_reason_phrase(status) - if reason_phrase is None - else reason_phrase.decode("ascii") - ) + reason = codes.get_reason_phrase(status) if reason_phrase is None else reason_phrase.decode("ascii") lines = [f"{version} {status} {reason}"] + [ f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers ] @@ -209,9 +191,7 @@ def format_certificate(cert: _PeerCertRetDictType) -> str: # pragma: no cover return "\n".join(lines) -def trace( - name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False -) -> None: +def trace(name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False) -> None: console = rich.console.Console() if name == "connection.connect_tcp.started" and verbose: host = info["host"] diff --git a/src/httpx2/httpx2/_models.py b/src/httpx2/httpx2/_models.py index 2cc86321..02baa3eb 100644 --- a/src/httpx2/httpx2/_models.py +++ b/src/httpx2/httpx2/_models.py @@ -234,10 +234,7 @@ def multi_items(self) -> list[tuple[str, str]]: occurrences of the same key without concatenating into a single comma separated value. """ - return [ - (key.decode(self.encoding), value.decode(self.encoding)) - for _, key, value in self._list - ] + return [(key.decode(self.encoding), value.decode(self.encoding)) for _, key, value in self._list] def get(self, key: str, default: typing.Any = None) -> typing.Any: """ @@ -310,11 +307,7 @@ def __setitem__(self, key: str, value: str) -> None: set_value = value.encode(self._encoding or "utf-8") lookup_key = set_key.lower() - found_indexes = [ - idx - for idx, (_, item_key, _) in enumerate(self._list) - if item_key == lookup_key - ] + found_indexes = [idx for idx, (_, item_key, _) in enumerate(self._list) if item_key == lookup_key] for idx in reversed(found_indexes[1:]): del self._list[idx] @@ -331,11 +324,7 @@ def __delitem__(self, key: str) -> None: """ del_key = key.lower().encode(self.encoding) - pop_indexes = [ - idx - for idx, (_, item_key, _) in enumerate(self._list) - if item_key.lower() == del_key - ] + pop_indexes = [idx for idx, (_, item_key, _) in enumerate(self._list) if item_key.lower() == del_key] if not pop_indexes: raise KeyError(key) @@ -411,9 +400,7 @@ def __init__( files=files, json=json, boundary=get_multipart_boundary_from_content_type( - content_type=content_type.encode(self.headers.encoding) - if content_type - else None + content_type=content_type.encode(self.headers.encoding) if content_type else None ), ) self._prepare(headers) @@ -448,9 +435,7 @@ def _prepare(self, default_headers: dict[str, str]) -> None: auto_headers: list[tuple[bytes, bytes]] = [] has_host = "Host" in self.headers - has_content_length = ( - "Content-Length" in self.headers or "Transfer-Encoding" in self.headers - ) + has_content_length = "Content-Length" in self.headers or "Transfer-Encoding" in self.headers if not has_host and self.url.host: auto_headers.append((b"Host", self.url.netloc)) @@ -499,11 +484,7 @@ def __repr__(self) -> str: return f"<{class_name}({self.method!r}, {url!r})>" def __getstate__(self) -> dict[str, typing.Any]: - return { - name: value - for name, value in self.__dict__.items() - if name not in ["extensions", "stream"] - } + return {name: value for name, value in self.__dict__.items() if name not in ["extensions", "stream"]} def __setstate__(self, state: dict[str, typing.Any]) -> None: for name, value in state.items(): @@ -582,10 +563,7 @@ def elapsed(self) -> datetime.timedelta: cycle to complete. """ if not hasattr(self, "_elapsed"): - raise RuntimeError( - "'.elapsed' may only be accessed after the response " - "has been read or closed." - ) + raise RuntimeError("'.elapsed' may only be accessed after the response has been read or closed.") return self._elapsed @elapsed.setter @@ -598,9 +576,7 @@ def request(self) -> Request: Returns the request instance associated to the current response. """ if self._request is None: - raise RuntimeError( - "The request instance has not been set on this response." - ) + raise RuntimeError("The request instance has not been set on this response.") return self._request @request.setter @@ -680,9 +656,7 @@ def encoding(self, value: str) -> None: encoding will throw a ValueError. """ if hasattr(self, "_text"): - raise ValueError( - "Setting encoding after `text` has been accessed is not allowed." - ) + raise ValueError("Setting encoding after `text` has been accessed is not allowed.") self._encoding = value @property @@ -798,8 +772,7 @@ def raise_for_status(self) -> Response: request = self._request if request is None: raise RuntimeError( - "Cannot call `raise_for_status` as the request " - "instance has not been set on this response." + "Cannot call `raise_for_status` as the request instance has not been set on this response." ) if self.is_success: @@ -847,10 +820,7 @@ def links(self) -> dict[str | None, dict[str, str]]: if header is None: return {} - return { - (link.get("rel") or link.get("url")): link - for link in _parse_header_links(header) - } + return {(link.get("rel") or link.get("url")): link for link in _parse_header_links(header)} @property def num_bytes_downloaded(self) -> int: @@ -979,9 +949,7 @@ async def aread(self) -> bytes: self._content = b"".join([part async for part in self.aiter_bytes()]) return self._content - async def aiter_bytes( - self, chunk_size: int | None = None - ) -> typing.AsyncIterator[bytes]: + async def aiter_bytes(self, chunk_size: int | None = None) -> typing.AsyncIterator[bytes]: """ A byte-iterator over the decoded response content. This allows us to handle gzip, deflate, brotli, and zstd encoded responses. @@ -1004,9 +972,7 @@ async def aiter_bytes( for chunk in chunker.flush(): yield chunk - async def aiter_text( - self, chunk_size: int | None = None - ) -> typing.AsyncIterator[str]: + async def aiter_text(self, chunk_size: int | None = None) -> typing.AsyncIterator[str]: """ A str-iterator over the decoded response content that handles both gzip, deflate, etc but also detects the content's @@ -1034,9 +1000,7 @@ async def aiter_lines(self) -> typing.AsyncIterator[str]: for line in decoder.flush(): yield line - async def aiter_raw( - self, chunk_size: int | None = None - ) -> typing.AsyncIterator[bytes]: + async def aiter_raw(self, chunk_size: int | None = None) -> typing.AsyncIterator[bytes]: """ A byte-iterator over the raw response content. """ @@ -1232,10 +1196,7 @@ def __bool__(self) -> bool: def __repr__(self) -> str: cookies_repr = ", ".join( - [ - f"" - for cookie in self.jar - ] + [f"" for cookie in self.jar] ) return f"" diff --git a/src/httpx2/httpx2/_multipart.py b/src/httpx2/httpx2/_multipart.py index b4761af9..7b28fc0b 100644 --- a/src/httpx2/httpx2/_multipart.py +++ b/src/httpx2/httpx2/_multipart.py @@ -22,12 +22,8 @@ ) _HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"} -_HTML5_FORM_ENCODING_REPLACEMENTS.update( - {chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B} -) -_HTML5_FORM_ENCODING_RE = re.compile( - r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()]) -) +_HTML5_FORM_ENCODING_REPLACEMENTS.update({chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B}) +_HTML5_FORM_ENCODING_RE = re.compile(r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()])) def _format_form_param(name: str, value: str) -> bytes: @@ -74,25 +70,16 @@ class DataField: def __init__(self, name: str, value: str | bytes | int | float | None) -> None: if not isinstance(name, str): - raise TypeError( - f"Invalid type for name. Expected str, got {type(name)}: {name!r}" - ) + raise TypeError(f"Invalid type for name. Expected str, got {type(name)}: {name!r}") if value is not None and not isinstance(value, (str, bytes, int, float)): - raise TypeError( - "Invalid type for value. Expected primitive type," - f" got {type(value)}: {value!r}" - ) + raise TypeError(f"Invalid type for value. Expected primitive type, got {type(value)}: {value!r}") self.name = name - self.value: str | bytes = ( - value if isinstance(value, bytes) else primitive_value_to_str(value) - ) + self.value: str | bytes = value if isinstance(value, bytes) else primitive_value_to_str(value) def render_headers(self) -> bytes: if not hasattr(self, "_headers"): name = _format_form_param("name", self.name) - self._headers = b"".join( - [b"Content-Disposition: form-data; ", name, b"\r\n\r\n"] - ) + self._headers = b"".join([b"Content-Disposition: form-data; ", name, b"\r\n\r\n"]) return self._headers @@ -156,13 +143,9 @@ def __init__(self, name: str, value: FileTypes) -> None: headers["Content-Type"] = content_type if isinstance(fileobj, io.StringIO): - raise TypeError( - "Multipart file uploads require 'io.BytesIO', not 'io.StringIO'." - ) + raise TypeError("Multipart file uploads require 'io.BytesIO', not 'io.StringIO'.") if isinstance(fileobj, io.TextIOBase): - raise TypeError( - "Multipart file uploads must be opened in binary mode, not text mode." - ) + raise TypeError("Multipart file uploads must be opened in binary mode, not text mode.") self.filename = filename self.file = fileobj @@ -236,14 +219,10 @@ def __init__( boundary = os.urandom(16).hex().encode("ascii") self.boundary = boundary - self.content_type = "multipart/form-data; boundary=%s" % boundary.decode( - "ascii" - ) + self.content_type = "multipart/form-data; boundary=%s" % boundary.decode("ascii") self.fields = list(self._iter_fields(data, files)) - def _iter_fields( - self, data: RequestData, files: RequestFiles - ) -> typing.Iterator[FileField | DataField]: + def _iter_fields(self, data: RequestData, files: RequestFiles) -> typing.Iterator[FileField | DataField]: for name, value in data.items(): if isinstance(value, (tuple, list)): for item in value: diff --git a/src/httpx2/httpx2/_transports/asgi.py b/src/httpx2/httpx2/_transports/asgi.py index bd6243cb..0dd14670 100644 --- a/src/httpx2/httpx2/_transports/asgi.py +++ b/src/httpx2/httpx2/_transports/asgi.py @@ -16,12 +16,8 @@ _Message = typing.MutableMapping[str, typing.Any] _Receive = typing.Callable[[], typing.Awaitable[_Message]] -_Send = typing.Callable[ - [typing.MutableMapping[str, typing.Any]], typing.Awaitable[None] -] -_ASGIApp = typing.Callable[ - [typing.MutableMapping[str, typing.Any], _Receive, _Send], typing.Awaitable[None] -] +_Send = typing.Callable[[typing.MutableMapping[str, typing.Any]], typing.Awaitable[None]] +_ASGIApp = typing.Callable[[typing.MutableMapping[str, typing.Any], _Receive, _Send], typing.Awaitable[None]] __all__ = ["ASGITransport"] @@ -168,7 +164,7 @@ async def send(message: typing.MutableMapping[str, typing.Any]) -> None: try: await self.app(scope, receive, send) - except Exception: # noqa: PIE-786 + except Exception: if self.raise_app_exceptions: raise diff --git a/src/httpx2/httpx2/_transports/base.py b/src/httpx2/httpx2/_transports/base.py index 8561271c..12bd1158 100644 --- a/src/httpx2/httpx2/_transports/base.py +++ b/src/httpx2/httpx2/_transports/base.py @@ -54,9 +54,7 @@ def handle_request(self, request: Request) -> Response: Returns a `Response` instance. """ - raise NotImplementedError( - "The 'handle_request' method must be implemented." - ) # pragma: no cover + raise NotImplementedError("The 'handle_request' method must be implemented.") # pragma: no cover def close(self) -> None: pass @@ -78,9 +76,7 @@ async def handle_async_request( self, request: Request, ) -> Response: - raise NotImplementedError( - "The 'handle_async_request' method must be implemented." - ) # pragma: no cover + raise NotImplementedError("The 'handle_async_request' method must be implemented.") # pragma: no cover async def aclose(self) -> None: pass diff --git a/src/httpx2/httpx2/_transports/default.py b/src/httpx2/httpx2/_transports/default.py index 0dbb7234..ce5ce90f 100644 --- a/src/httpx2/httpx2/_transports/default.py +++ b/src/httpx2/httpx2/_transports/default.py @@ -210,8 +210,7 @@ def __init__( ) else: # pragma: no cover raise ValueError( - "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h'," - f" but got {proxy.url.scheme!r}." + f"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h', but got {proxy.url.scheme!r}." ) def __enter__(self: T) -> T: # Use generics for subclass support. @@ -354,8 +353,7 @@ def __init__( ) else: # pragma: no cover raise ValueError( - "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h'," - f" but got {proxy.url.scheme!r}." + f"Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h', but got {proxy.url.scheme!r}." ) async def __aenter__(self: A) -> A: # Use generics for subclass support. diff --git a/src/httpx2/httpx2/_transports/wsgi.py b/src/httpx2/httpx2/_transports/wsgi.py index 9e3f7e83..5364612a 100644 --- a/src/httpx2/httpx2/_transports/wsgi.py +++ b/src/httpx2/httpx2/_transports/wsgi.py @@ -141,9 +141,6 @@ def start_response( raise seen_exc_info[1] status_code = int(seen_status.split()[0]) - headers = [ - (key.encode("ascii"), value.encode("ascii")) - for key, value in seen_response_headers - ] + headers = [(key.encode("ascii"), value.encode("ascii")) for key, value in seen_response_headers] return Response(status_code, headers=headers, stream=stream) diff --git a/src/httpx2/httpx2/_types.py b/src/httpx2/httpx2/_types.py index 704dfdff..99a91d3b 100644 --- a/src/httpx2/httpx2/_types.py +++ b/src/httpx2/httpx2/_types.py @@ -91,9 +91,7 @@ class SyncByteStream: def __iter__(self) -> Iterator[bytes]: - raise NotImplementedError( - "The '__iter__' method must be implemented." - ) # pragma: no cover + raise NotImplementedError("The '__iter__' method must be implemented.") # pragma: no cover yield b"" # pragma: no cover def close(self) -> None: @@ -105,9 +103,7 @@ def close(self) -> None: class AsyncByteStream: async def __aiter__(self) -> AsyncIterator[bytes]: - raise NotImplementedError( - "The '__aiter__' method must be implemented." - ) # pragma: no cover + raise NotImplementedError("The '__aiter__' method must be implemented.") # pragma: no cover yield b"" # pragma: no cover async def aclose(self) -> None: diff --git a/src/httpx2/httpx2/_urlparse.py b/src/httpx2/httpx2/_urlparse.py index bf190fd5..9f08d4e1 100644 --- a/src/httpx2/httpx2/_urlparse.py +++ b/src/httpx2/httpx2/_urlparse.py @@ -29,9 +29,7 @@ MAX_URL_LENGTH = 65536 # https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3 -UNRESERVED_CHARACTERS = ( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" -) +UNRESERVED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" SUB_DELIMS = "!$&'()*+,;=" PERCENT_ENCODED_REGEX = re.compile("%[A-Fa-f0-9]{2}") @@ -40,24 +38,16 @@ # The fragment percent-encode set is the C0 control percent-encode set # and U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`). -FRAG_SAFE = "".join( - [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x3C, 0x3E, 0x60)] -) +FRAG_SAFE = "".join([chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x3C, 0x3E, 0x60)]) # The query percent-encode set is the C0 control percent-encode set # and U+0020 SPACE, U+0022 ("), U+0023 (#), U+003C (<), and U+003E (>). -QUERY_SAFE = "".join( - [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E)] -) +QUERY_SAFE = "".join([chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E)]) # The path percent-encode set is the query percent-encode set # and U+003F (?), U+0060 (`), U+007B ({), and U+007D (}). PATH_SAFE = "".join( - [ - chr(i) - for i in range(0x20, 0x7F) - if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + (0x3F, 0x60, 0x7B, 0x7D) - ] + [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + (0x3F, 0x60, 0x7B, 0x7D)] ) # The userinfo percent-encode set is the path percent-encode set @@ -123,9 +113,7 @@ # {host} # :{port} (optional) AUTHORITY_REGEX = re.compile( - ( - r"(?:(?P{userinfo})@)?" r"(?P{host})" r":?(?P{port})?" - ).format( + (r"(?:(?P{userinfo})@)?" r"(?P{host})" r":?(?P{port})?").format( userinfo=".*", # Any character sequence. host="(\\[.*\\]|[^:@]*)", # Either any character sequence excluding ':' or '@', # or an IPv6 address enclosed within square brackets. @@ -223,9 +211,7 @@ def urlparse(url: str = "", **kwargs: str | None) -> ParseResult: if any(char.isascii() and not char.isprintable() for char in url): char = next(char for char in url if char.isascii() and not char.isprintable()) idx = url.find(char) - error = ( - f"Invalid non-printable ASCII character in URL, {char!r} at position {idx}." - ) + error = f"Invalid non-printable ASCII character in URL, {char!r} at position {idx}." raise InvalidURL(error) # Some keyword arguments require special handling. @@ -271,14 +257,9 @@ def urlparse(url: str = "", **kwargs: str | None) -> ParseResult: # If a component includes any ASCII control characters including \t, \r, \n, # then treat it as invalid. if any(char.isascii() and not char.isprintable() for char in value): - char = next( - char for char in value if char.isascii() and not char.isprintable() - ) + char = next(char for char in value if char.isascii() and not char.isprintable()) idx = value.find(char) - error = ( - f"Invalid non-printable ASCII character in URL {key} component, " - f"{char!r} at position {idx}." - ) + error = f"Invalid non-printable ASCII character in URL {key} component, {char!r} at position {idx}." raise InvalidURL(error) # Ensure that keyword arguments match as a valid regex. @@ -321,9 +302,7 @@ def urlparse(url: str = "", **kwargs: str | None) -> ParseResult: parsed_port: int | None = normalize_port(port, scheme) has_scheme = parsed_scheme != "" - has_authority = ( - parsed_userinfo != "" or parsed_host != "" or parsed_port is not None - ) + has_authority = parsed_userinfo != "" or parsed_host != "" or parsed_port is not None validate_path(path, has_scheme=has_scheme, has_authority=has_authority) if has_scheme or has_authority: path = normalize_path(path) @@ -411,9 +390,7 @@ def normalize_port(port: str | int | None, scheme: str) -> int | None: raise InvalidURL(f"Invalid port: {port!r}") # See https://url.spec.whatwg.org/#url-miscellaneous - default_port = {"ftp": 21, "http": 80, "https": 443, "ws": 80, "wss": 443}.get( - scheme - ) + default_port = {"ftp": 21, "http": 80, "https": 443, "ws": 80, "wss": 443}.get(scheme) if port_as_int == default_port: return None return port_as_int @@ -489,9 +466,7 @@ def percent_encoded(string: str, safe: str) -> str: if not string.rstrip(NON_ESCAPED_CHARS): return string - return "".join( - [char if char in NON_ESCAPED_CHARS else PERCENT(char) for char in string] - ) + return "".join([char if char in NON_ESCAPED_CHARS else PERCENT(char) for char in string]) def quote(string: str, safe: str) -> str: diff --git a/src/httpx2/httpx2/_urls.py b/src/httpx2/httpx2/_urls.py index a97257c4..057ca150 100644 --- a/src/httpx2/httpx2/_urls.py +++ b/src/httpx2/httpx2/_urls.py @@ -118,10 +118,7 @@ def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None: elif isinstance(url, URL): self._uri_reference = url._uri_reference.copy_with(**kwargs) else: - raise TypeError( - "Invalid type for url. Expected str or httpx2.URL," - f" got {type(url)}: {url!r}" - ) + raise TypeError(f"Invalid type for url. Expected str or httpx2.URL, got {type(url)}: {url!r}") @property def scheme(self) -> str: @@ -406,9 +403,7 @@ def raw(self) -> tuple[bytes, bytes, int, bytes]: # pragma: nocover import warnings warnings.warn("URL.raw is deprecated.") - RawURL = collections.namedtuple( - "RawURL", ["raw_scheme", "raw_host", "port", "raw_path"] - ) + RawURL = collections.namedtuple("RawURL", ["raw_scheme", "raw_host", "port", "raw_path"]) return RawURL( raw_scheme=self.raw_scheme, raw_host=self.raw_host, @@ -447,18 +442,12 @@ def __init__(self, *args: QueryParamTypes | None, **kwargs: typing.Any) -> None: # {"a": "123", "b": ["456", "789"]} # To dict inputs where values are always lists, like: # {"a": ["123"], "b": ["456", "789"]} - dict_value = { - k: list(v) if isinstance(v, (list, tuple)) else [v] - for k, v in value.items() - } + dict_value = {k: list(v) if isinstance(v, (list, tuple)) else [v] for k, v in value.items()} # Ensure that keys and values are neatly coerced to strings. # We coerce values `True` and `False` to JSON-like "true" and "false" # representations, and coerce `None` values to the empty string. - self._dict = { - str(k): [primitive_value_to_str(item) for item in v] - for k, v in dict_value.items() - } + self._dict = {str(k): [primitive_value_to_str(item) for item in v] for k, v in dict_value.items()} def keys(self) -> typing.KeysView[str]: """ @@ -629,13 +618,9 @@ def __repr__(self) -> str: return f"{class_name}({query_string!r})" def update(self, params: QueryParamTypes | None = None) -> None: - raise RuntimeError( - "QueryParams are immutable since 0.18.0. " - "Use `q = q.merge(...)` to create an updated copy." - ) + raise RuntimeError("QueryParams are immutable since 0.18.0. Use `q = q.merge(...)` to create an updated copy.") def __setitem__(self, key: str, value: str) -> None: raise RuntimeError( - "QueryParams are immutable since 0.18.0. " - "Use `q = q.set(key, value)` to create an updated copy." + "QueryParams are immutable since 0.18.0. Use `q = q.set(key, value)` to create an updated copy." ) diff --git a/src/httpx2/httpx2/_utils.py b/src/httpx2/httpx2/_utils.py index 4776e695..eef7b6dc 100644 --- a/src/httpx2/httpx2/_utils.py +++ b/src/httpx2/httpx2/_utils.py @@ -40,9 +40,7 @@ def get_environment_proxies() -> dict[str, str | None]: for scheme in ("http", "https", "all"): if proxy_info.get(scheme): hostname = proxy_info[scheme] - mounts[f"{scheme}://"] = ( - hostname if "://" in hostname else f"http://{hostname}" - ) + mounts[f"{scheme}://"] = hostname if "://" in hostname else f"http://{hostname}" no_proxy_hosts = [host.strip() for host in proxy_info.get("no", "").split(",")] for hostname in no_proxy_hosts: @@ -192,11 +190,7 @@ def __init__(self, pattern: str) -> None: def matches(self, other: URL) -> bool: if self.scheme and self.scheme != other.scheme: return False - if ( - self.host - and self.host_regex is not None - and not self.host_regex.match(other.host) - ): + if self.host and self.host_regex is not None and not self.host_regex.match(other.host): return False if self.port is not None and self.port != other.port: return False diff --git a/tests/httpcore2/_async/test_connection.py b/tests/httpcore2/_async/test_connection.py index 69f0e1b0..3bd93e61 100644 --- a/tests/httpcore2/_async/test_connection.py +++ b/tests/httpcore2/_async/test_connection.py @@ -32,9 +32,7 @@ async def test_http_connection(): ] ) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: assert not conn.is_idle() assert not conn.is_closed() assert not conn.is_available() @@ -42,10 +40,7 @@ async def test_http_connection(): assert repr(conn) == "" async with conn.stream("GET", "https://example.com/") as response: - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" await response.aread() assert response.status == 200 @@ -55,10 +50,7 @@ async def test_http_connection(): assert not conn.is_closed() assert conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @pytest.mark.anyio @@ -78,9 +70,7 @@ async def test_concurrent_requests_not_available_on_http11_connections(): ] ) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: async with conn.stream("GET", "https://example.com/"): with pytest.raises(ConnectionNotAvailable): await conn.request("GET", "https://example.com/") @@ -102,9 +92,7 @@ def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: self.count += len(buffer) if self.count > 1_000_000: @@ -132,9 +120,7 @@ async def connect_tcp( ] ) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: content = b"x" * 10_000_000 response = await conn.request("POST", "https://example.com/", content=content) assert response.status == 413 @@ -156,9 +142,7 @@ def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: self.count += len(buffer) if self.count > 1_000_000: @@ -178,9 +162,7 @@ async def connect_tcp( origin = Origin(b"https", b"example.com", 443) network_backend = ErrorOnRequestTooLarge([]) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: content = b"x" * 10_000_000 with pytest.raises(RemoteProtocolError) as exc_info: await conn.request("POST", "https://example.com/", content=content) @@ -204,16 +186,12 @@ async def test_http2_connection(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ], http2=True, ) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, http2=True - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, http2=True) as conn: response = await conn.request("GET", "https://example.com/") assert response.status == 200 @@ -228,9 +206,7 @@ async def test_request_to_incorrect_origin(): """ origin = Origin(b"https", b"example.com", 443) network_backend = AsyncMockBackend([]) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend) as conn: with pytest.raises(RuntimeError): await conn.request("GET", "https://other.com/") @@ -259,26 +235,18 @@ async def connect_tcp( self._connect_tcp_failures -= 1 raise ConnectError() - stream = await super().connect_tcp( - host, port, timeout=timeout, local_address=local_address - ) + stream = await super().connect_tcp(host, port, timeout=timeout, local_address=local_address) return self._NeedsRetryAsyncNetworkStream(self, stream) class _NeedsRetryAsyncNetworkStream(AsyncNetworkStream): - def __init__( - self, backend: "NeedsRetryBackend", stream: AsyncNetworkStream - ) -> None: + def __init__(self, backend: "NeedsRetryBackend", stream: AsyncNetworkStream) -> None: self._backend = backend self._stream = stream - async def read( - self, max_bytes: int, timeout: typing.Optional[float] = None - ) -> bytes: + async def read(self, max_bytes: int, timeout: typing.Optional[float] = None) -> bytes: return await self._stream.read(max_bytes, timeout) - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: await self._stream.write(buffer, timeout) async def aclose(self) -> None: @@ -313,9 +281,7 @@ async def test_connection_retries(): ] network_backend = NeedsRetryBackend(content) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, retries=3 - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, retries=3) as conn: response = await conn.request("GET", "https://example.com/") assert response.status == 200 @@ -339,18 +305,12 @@ async def test_connection_retries_tls(): b"Hello, world!", ] - network_backend = NeedsRetryBackend( - content, connect_tcp_failures=0, start_tls_failures=2 - ) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, retries=3 - ) as conn: + network_backend = NeedsRetryBackend(content, connect_tcp_failures=0, start_tls_failures=2) + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, retries=3) as conn: response = await conn.request("GET", "https://example.com/") assert response.status == 200 - network_backend = NeedsRetryBackend( - content, connect_tcp_failures=0, start_tls_failures=2 - ) + network_backend = NeedsRetryBackend(content, connect_tcp_failures=0, start_tls_failures=2) async with AsyncHTTPConnection( origin=origin, network_backend=network_backend, @@ -374,8 +334,6 @@ async def test_uds_connections(): b"Hello, world!", ] ) - async with AsyncHTTPConnection( - origin=origin, network_backend=network_backend, uds="/mock/example" - ) as conn: + async with AsyncHTTPConnection(origin=origin, network_backend=network_backend, uds="/mock/example") as conn: response = await conn.request("GET", "https://example.com/") assert response.status == 200 diff --git a/tests/httpcore2/_async/test_connection_pool.py b/tests/httpcore2/_async/test_connection_pool.py index 757827df..33eedbb6 100644 --- a/tests/httpcore2/_async/test_connection_pool.py +++ b/tests/httpcore2/_async/test_connection_pool.py @@ -29,69 +29,45 @@ async def test_connection_pool_with_keepalive(): ] ) - async with httpcore2.AsyncConnectionPool( - network_backend=network_backend, max_keepalive_connections=1 - ) as pool: + async with httpcore2.AsyncConnectionPool(network_backend=network_backend, max_keepalive_connections=1) as pool: # Sending an intial request, which once complete will return to the pool, IDLE. async with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" await response.aread() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" # Sending a second request to the same origin will reuse the existing IDLE connection. async with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" await response.aread() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" # Sending a request to a different origin will not reuse the existing IDLE connection. - async with pool.stream("GET", "http://example.com/") as response_1, pool.stream( - "GET", "http://example.com/" - ) as response_2: + async with ( + pool.stream("GET", "http://example.com/") as response_1, + pool.stream("GET", "http://example.com/") as response_2, + ): info = [repr(c) for c in pool.connections] assert info == [ "", "", "", ] - assert ( - repr(pool) - == "" - ) + assert repr(pool) == "" await response_1.aread() await response_2.aread() @@ -103,10 +79,7 @@ async def test_connection_pool_with_keepalive(): assert info == [ "", ] - assert ( - repr(pool) - == "" - ) + assert repr(pool) == "" @pytest.mark.anyio @@ -127,13 +100,9 @@ async def test_connection_pool_with_close(): async with httpcore2.AsyncConnectionPool(network_backend=network_backend) as pool: # Sending an intial request, which once complete will not return to the pool. - async with pool.stream( - "GET", "https://example.com/", headers={"Connection": "close"} - ) as response: + async with pool.stream("GET", "https://example.com/", headers={"Connection": "close"}) as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] await response.aread() assert response.status == 200 @@ -160,9 +129,7 @@ async def test_connection_pool_with_http2(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), hyperframe.frame.HeadersFrame( stream_id=3, data=hpack.Encoder().encode( @@ -173,9 +140,7 @@ async def test_connection_pool_with_http2(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=3, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=3, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ], http2=True, ) @@ -189,9 +154,7 @@ async def test_connection_pool_with_http2(): assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] # Sending a second request to the same origin will reuse the existing IDLE connection. response = await pool.request("GET", "https://example.com/") @@ -199,9 +162,7 @@ async def test_connection_pool_with_http2(): assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] @pytest.mark.anyio @@ -223,12 +184,8 @@ async def test_connection_pool_with_http2_goaway(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), - hyperframe.frame.GoAwayFrame( - stream_id=0, error_code=0, last_stream_id=1 - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), + hyperframe.frame.GoAwayFrame(stream_id=0, error_code=0, last_stream_id=1).serialize(), b"", ], http2=True, @@ -243,9 +200,7 @@ async def test_connection_pool_with_http2_goaway(): assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] # Sending a second request to the same origin will require a new connection. # The original connection has now been closed. @@ -384,10 +339,8 @@ async def trace(name, kwargs): async with httpcore2.AsyncConnectionPool(network_backend=network_backend) as pool: # Sending an initial request, which once complete will not return to the pool. - with pytest.raises(Exception): - await pool.request( - "GET", "https://example.com/", extensions={"trace": trace} - ) + with pytest.raises(httpcore2.RemoteProtocolError): + await pool.request("GET", "https://example.com/", extensions={"trace": trace}) info = [repr(c) for c in pool.connections] assert info == [] @@ -422,9 +375,7 @@ async def connect_tcp( port: int, timeout: typing.Optional[float] = None, local_address: typing.Optional[str] = None, - socket_options: typing.Optional[ - typing.Iterable[httpcore2.SOCKET_OPTION] - ] = None, + socket_options: typing.Optional[typing.Iterable[httpcore2.SOCKET_OPTION]] = None, ) -> httpcore2.AsyncNetworkStream: raise httpcore2.ConnectError("Could not connect") @@ -437,10 +388,8 @@ async def trace(name, kwargs): async with httpcore2.AsyncConnectionPool(network_backend=network_backend) as pool: # Sending an initial request, which once complete will not return to the pool. - with pytest.raises(Exception): - await pool.request( - "GET", "https://example.com/", extensions={"trace": trace} - ) + with pytest.raises(httpcore2.ConnectError): + await pool.request("GET", "https://example.com/", extensions={"trace": trace}) info = [repr(c) for c in pool.connections] assert info == [] @@ -474,9 +423,7 @@ async def test_connection_pool_with_immediate_expiry(): # Sending an intial request, which once complete will not return to the pool. async with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] await response.aread() assert response.status == 200 @@ -501,15 +448,11 @@ async def test_connection_pool_with_no_keepalive_connections_allowed(): ] ) - async with httpcore2.AsyncConnectionPool( - max_keepalive_connections=0, network_backend=network_backend - ) as pool: + async with httpcore2.AsyncConnectionPool(max_keepalive_connections=0, network_backend=network_backend) as pool: # Sending an intial request, which once complete will not return to the pool. async with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] await response.aread() assert response.status == 200 @@ -540,9 +483,7 @@ async def fetch(pool, domain, info_list): info_list.append(info) await response.aread() - async with httpcore2.AsyncConnectionPool( - max_connections=1, network_backend=network_backend - ) as pool: + async with httpcore2.AsyncConnectionPool(max_connections=1, network_backend=network_backend) as pool: info_list: typing.List[str] = [] async with concurrency.open_nursery() as nursery: for domain in ["a.com", "b.com", "c.com", "d.com", "e.com"]: @@ -586,9 +527,7 @@ async def fetch(pool, domain, info_list): info_list.append(info) await response.aread() - async with httpcore2.AsyncConnectionPool( - max_connections=1, network_backend=network_backend, http2=True - ) as pool: + async with httpcore2.AsyncConnectionPool(max_connections=1, network_backend=network_backend, http2=True) as pool: info_list: typing.List[str] = [] async with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: @@ -599,10 +538,7 @@ async def fetch(pool, domain, info_list): # single connection was established at any one time. assert len(item) == 1 # Only a single request was sent on each connection. - assert ( - item[0] - == "" - ) + assert item[0] == "" @pytest.mark.trio @@ -628,9 +564,7 @@ async def fetch(pool, domain, info_list): info_list.append(info) await response.aread() - async with httpcore2.AsyncConnectionPool( - max_connections=1, network_backend=network_backend, http2=True - ) as pool: + async with httpcore2.AsyncConnectionPool(max_connections=1, network_backend=network_backend, http2=True) as pool: info_list: typing.List[str] = [] async with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: @@ -649,10 +583,7 @@ async def fetch(pool, domain, info_list): "", ] - assert ( - repr(pool) - == "" - ) + assert repr(pool) == "" @pytest.mark.anyio @@ -707,9 +638,7 @@ async def test_connection_pool_timeout(): ] ) - async with httpcore2.AsyncConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + async with httpcore2.AsyncConnectionPool(network_backend=network_backend, max_connections=1) as pool: # Send a request to a pool that is configured to only support a single # connection, and then ensure that a second concurrent request # fails with a timeout. @@ -744,32 +673,22 @@ async def test_connection_pool_timeout_zero(): extensions = {"timeout": {"pool": 0}} # A connection pool configured to allow only one connection at a time. - async with httpcore2.AsyncConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + async with httpcore2.AsyncConnectionPool(network_backend=network_backend, max_connections=1) as pool: # Two consecutive requests with a pool timeout of zero. # Both succeed without raising a timeout. - response = await pool.request( - "GET", "https://example.com/", extensions=extensions - ) + response = await pool.request("GET", "https://example.com/", extensions=extensions) assert response.status == 200 assert response.content == b"Hello, world!" - response = await pool.request( - "GET", "https://example.com/", extensions=extensions - ) + response = await pool.request("GET", "https://example.com/", extensions=extensions) assert response.status == 200 assert response.content == b"Hello, world!" # A connection pool configured to allow only one connection at a time. - async with httpcore2.AsyncConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + async with httpcore2.AsyncConnectionPool(network_backend=network_backend, max_connections=1) as pool: # Two concurrent requests with a pool timeout of zero. # Only the first will succeed without raising a timeout. - async with pool.stream( - "GET", "https://example.com/", extensions=extensions - ) as response: + async with pool.stream("GET", "https://example.com/", extensions=extensions) as response: # The first response hasn't yet completed. with pytest.raises(httpcore2.PoolTimeout): # So a pool timeout occurs. @@ -807,9 +726,7 @@ async def test_http11_upgrade_connection(): async def trace(name, kwargs): called.append(name) - async with httpcore2.AsyncConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + async with httpcore2.AsyncConnectionPool(network_backend=network_backend, max_connections=1) as pool: async with pool.stream( "GET", "wss://example.com/", diff --git a/tests/httpcore2/_async/test_http11.py b/tests/httpcore2/_async/test_http11.py index f44ef838..8e965f4f 100644 --- a/tests/httpcore2/_async/test_http11.py +++ b/tests/httpcore2/_async/test_http11.py @@ -15,9 +15,7 @@ async def test_http11_connection(): b"Hello, world!", ] ) - async with httpcore2.AsyncHTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = await conn.request("GET", "https://example.com/") assert response.status == 200 assert response.content == b"Hello, world!" @@ -26,10 +24,7 @@ async def test_http11_connection(): assert not conn.is_closed() assert conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @pytest.mark.anyio @@ -56,10 +51,7 @@ async def test_http11_connection_unread_response(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @pytest.mark.anyio @@ -78,10 +70,7 @@ async def test_http11_connection_with_remote_protocol_error(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @pytest.mark.anyio @@ -107,10 +96,7 @@ async def test_http11_connection_with_incomplete_response(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @pytest.mark.anyio @@ -139,10 +125,7 @@ async def test_http11_connection_with_local_protocol_error(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @pytest.mark.anyio @@ -222,9 +205,7 @@ async def test_http11_expect_continue(): b"Hello, world!", ] ) - async with httpcore2.AsyncHTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = await conn.request( "GET", "https://example.com/", @@ -255,9 +236,7 @@ async def test_http11_upgrade_connection(): b"...", ] ) - async with httpcore2.AsyncHTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: async with conn.stream( "GET", "wss://example.com/", @@ -285,19 +264,11 @@ async def test_http11_upgrade_with_trailing_data(): # in which response headers and data are received at once. # This means that "foobar" becomes trailing data. [ - ( - b"HTTP/1.1 101 Switching Protocols\r\n" - b"Connection: upgrade\r\n" - b"Upgrade: custom\r\n" - b"\r\n" - b"foobar" - ), + (b"HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nUpgrade: custom\r\n\r\nfoobar"), b"baz", ] ) - async with httpcore2.AsyncHTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: async with conn.stream( "GET", "wss://example.com/", @@ -344,9 +315,7 @@ async def test_http11_early_hints(): b"Hello, world! ...", ] ) - async with httpcore2.AsyncHTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = await conn.request( "GET", "https://example.com/", @@ -372,9 +341,7 @@ async def test_http11_header_sub_100kb(): b"", ] ) - async with httpcore2.AsyncHTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = await conn.request("GET", "https://example.com/") assert response.status == 200 assert response.content == b"" diff --git a/tests/httpcore2/_async/test_http2.py b/tests/httpcore2/_async/test_http2.py index ab04af4c..258ccdc9 100644 --- a/tests/httpcore2/_async/test_http2.py +++ b/tests/httpcore2/_async/test_http2.py @@ -21,14 +21,10 @@ async def test_http2_connection(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ] ) - async with httpcore2.AsyncHTTP2Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP2Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = await conn.request("GET", "https://example.com/") assert response.status == 200 assert response.content == b"Hello, world!" @@ -37,13 +33,8 @@ async def test_http2_connection(): assert conn.is_available() assert not conn.is_closed() assert not conn.has_expired() - assert ( - conn.info() == "'https://example.com:443', HTTP/2, IDLE, Request Count: 1" - ) - assert ( - repr(conn) - == "" - ) + assert conn.info() == "'https://example.com:443', HTTP/2, IDLE, Request Count: 1" + assert repr(conn) == "" @pytest.mark.anyio @@ -62,18 +53,12 @@ async def test_http2_connection_closed(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), # Connection is closed after the first response - hyperframe.frame.GoAwayFrame( - stream_id=0, error_code=0, last_stream_id=1 - ).serialize(), + hyperframe.frame.GoAwayFrame(stream_id=0, error_code=0, last_stream_id=1).serialize(), ] ) - async with httpcore2.AsyncHTTP2Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + async with httpcore2.AsyncHTTP2Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: await conn.request("GET", "https://example.com/") with pytest.raises(httpcore2.ConnectionNotAvailable): @@ -98,9 +83,7 @@ async def test_http2_connection_post_request(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ] ) async with httpcore2.AsyncHTTP2Connection(origin=origin, stream=stream) as conn: @@ -160,9 +143,7 @@ async def test_http2_connection_with_rst_stream(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=3, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=3, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), b"", ] ) @@ -206,9 +187,7 @@ async def test_http2_connection_with_goaway(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=3, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=3, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), b"", ] ) @@ -230,33 +209,17 @@ async def test_http2_connection_with_flow_control(): [ hyperframe.frame.SettingsFrame().serialize(), # Available flow: 65,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 75,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 85,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 95,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 105,535 hyperframe.frame.HeadersFrame( stream_id=1, @@ -268,9 +231,7 @@ async def test_http2_connection_with_flow_control(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"100,000 bytes received", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"100,000 bytes received", flags=["END_STREAM"]).serialize(), ] ) async with httpcore2.AsyncHTTP2Connection(origin=origin, stream=stream) as conn: @@ -302,9 +263,7 @@ async def test_http2_connection_attempt_close(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ] ) async with httpcore2.AsyncHTTP2Connection(origin=origin, stream=stream) as conn: @@ -356,9 +315,7 @@ async def test_http2_remote_max_streams_update(): hyperframe.frame.SettingsFrame( settings={hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 50} ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world...again!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world...again!", flags=["END_STREAM"]).serialize(), ] ) async with httpcore2.AsyncHTTP2Connection(origin=origin, stream=stream) as conn: diff --git a/tests/httpcore2/_async/test_http_proxy.py b/tests/httpcore2/_async/test_http_proxy.py index 9bf7bb96..37d83dab 100644 --- a/tests/httpcore2/_async/test_http_proxy.py +++ b/tests/httpcore2/_async/test_http_proxy.py @@ -48,26 +48,16 @@ async def test_proxy_forwarding(): assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a forwarding proxy can only handle HTTP requests to the same origin. - assert proxy.connections[0].can_handle_request( - Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"other.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"other.com", 443) - ) + assert proxy.connections[0].can_handle_request(Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"other.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"other.com", 443)) @pytest.mark.anyio @@ -103,26 +93,16 @@ async def test_proxy_tunneling(): assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a tunneled proxy can only handle HTTPS requests to the same origin. - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"other.com", 80) - ) - assert proxy.connections[0].can_handle_request( - Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"other.com", 443) - ) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"other.com", 80)) + assert proxy.connections[0].can_handle_request(Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"other.com", 443)) # We need to adapt the mock backend here slightly in order to deal @@ -173,9 +153,7 @@ async def test_proxy_tunneling_http2(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ], ) @@ -187,34 +165,22 @@ async def test_proxy_tunneling_http2(): # Sending an intial request, which once complete will return to the pool, IDLE. async with proxy.stream("GET", "https://example.com/") as response: info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] await response.aread() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a tunneled proxy can only handle HTTPS requests to the same origin. - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"other.com", 80) - ) - assert proxy.connections[0].can_handle_request( - Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"other.com", 443) - ) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"other.com", 80)) + assert proxy.connections[0].can_handle_request(Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"other.com", 443)) @pytest.mark.anyio @@ -224,7 +190,7 @@ async def test_proxy_tunneling_with_403(): """ network_backend = AsyncMockBackend( [ - b"HTTP/1.1 403 Permission Denied\r\n" b"\r\n", + b"HTTP/1.1 403 Permission Denied\r\n\r\n", ] ) @@ -273,6 +239,4 @@ def test_proxy_headers(): url="http://localhost:8080/", auth=("username", "password"), ) - assert proxy.headers == [ - (b"Proxy-Authorization", b"Basic dXNlcm5hbWU6cGFzc3dvcmQ=") - ] + assert proxy.headers == [(b"Proxy-Authorization", b"Basic dXNlcm5hbWU6cGFzc3dvcmQ=")] diff --git a/tests/httpcore2/_async/test_socks_proxy.py b/tests/httpcore2/_async/test_socks_proxy.py index e6134aa2..ace6f0b0 100644 --- a/tests/httpcore2/_async/test_socks_proxy.py +++ b/tests/httpcore2/_async/test_socks_proxy.py @@ -31,34 +31,22 @@ async def test_socks5_request(): # Sending an intial request, which once complete will return to the pool, IDLE. async with proxy.stream("GET", "https://example.com/") as response: info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] await response.aread() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a tunneled proxy can only handle HTTPS requests to the same origin. - assert not proxy.connections[0].can_handle_request( - httpcore2.Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - httpcore2.Origin(b"http", b"other.com", 80) - ) - assert proxy.connections[0].can_handle_request( - httpcore2.Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - httpcore2.Origin(b"https", b"other.com", 443) - ) + assert not proxy.connections[0].can_handle_request(httpcore2.Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(httpcore2.Origin(b"http", b"other.com", 80)) + assert proxy.connections[0].can_handle_request(httpcore2.Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(httpcore2.Origin(b"https", b"other.com", 443)) @pytest.mark.anyio @@ -94,17 +82,13 @@ async def test_authenticated_socks5_request(): # Sending an intial request, which once complete will return to the pool, IDLE. async with proxy.stream("GET", "https://example.com/") as response: info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] await response.aread() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() @@ -132,9 +116,7 @@ async def test_socks5_request_connect_failed(): # Sending a request, which the proxy rejects with pytest.raises(httpcore2.ProxyError) as exc_info: await proxy.request("GET", "https://example.com/") - assert ( - str(exc_info.value) == "Proxy Server could not connect: Connection refused." - ) + assert str(exc_info.value) == "Proxy Server could not connect: Connection refused." assert not proxy.connections @@ -160,8 +142,7 @@ async def test_socks5_request_failed_to_provide_auth(): with pytest.raises(httpcore2.ProxyError) as exc_info: await proxy.request("GET", "https://example.com/") assert ( - str(exc_info.value) - == "Requested NO AUTHENTICATION REQUIRED from proxy server, but got USERNAME/PASSWORD." + str(exc_info.value) == "Requested NO AUTHENTICATION REQUIRED from proxy server, but got USERNAME/PASSWORD." ) assert not proxy.connections diff --git a/tests/httpcore2/_sync/test_connection.py b/tests/httpcore2/_sync/test_connection.py index e24cc3d6..1b630c41 100644 --- a/tests/httpcore2/_sync/test_connection.py +++ b/tests/httpcore2/_sync/test_connection.py @@ -32,9 +32,7 @@ def test_http_connection(): ] ) - with HTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: assert not conn.is_idle() assert not conn.is_closed() assert not conn.is_available() @@ -42,10 +40,7 @@ def test_http_connection(): assert repr(conn) == "" with conn.stream("GET", "https://example.com/") as response: - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" response.read() assert response.status == 200 @@ -55,10 +50,7 @@ def test_http_connection(): assert not conn.is_closed() assert conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @@ -78,9 +70,7 @@ def test_concurrent_requests_not_available_on_http11_connections(): ] ) - with HTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: with conn.stream("GET", "https://example.com/"): with pytest.raises(ConnectionNotAvailable): conn.request("GET", "https://example.com/") @@ -102,9 +92,7 @@ def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: self.count += len(buffer) if self.count > 1_000_000: @@ -132,9 +120,7 @@ def connect_tcp( ] ) - with HTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: content = b"x" * 10_000_000 response = conn.request("POST", "https://example.com/", content=content) assert response.status == 413 @@ -156,9 +142,7 @@ def __init__(self, buffer: typing.List[bytes], http2: bool = False) -> None: super().__init__(buffer, http2) self.count = 0 - def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: self.count += len(buffer) if self.count > 1_000_000: @@ -178,9 +162,7 @@ def connect_tcp( origin = Origin(b"https", b"example.com", 443) network_backend = ErrorOnRequestTooLarge([]) - with HTTPConnection( - origin=origin, network_backend=network_backend, keepalive_expiry=5.0 - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend, keepalive_expiry=5.0) as conn: content = b"x" * 10_000_000 with pytest.raises(RemoteProtocolError) as exc_info: conn.request("POST", "https://example.com/", content=content) @@ -204,16 +186,12 @@ def test_http2_connection(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ], http2=True, ) - with HTTPConnection( - origin=origin, network_backend=network_backend, http2=True - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend, http2=True) as conn: response = conn.request("GET", "https://example.com/") assert response.status == 200 @@ -228,9 +206,7 @@ def test_request_to_incorrect_origin(): """ origin = Origin(b"https", b"example.com", 443) network_backend = MockBackend([]) - with HTTPConnection( - origin=origin, network_backend=network_backend - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend) as conn: with pytest.raises(RuntimeError): conn.request("GET", "https://other.com/") @@ -259,26 +235,18 @@ def connect_tcp( self._connect_tcp_failures -= 1 raise ConnectError() - stream = super().connect_tcp( - host, port, timeout=timeout, local_address=local_address - ) + stream = super().connect_tcp(host, port, timeout=timeout, local_address=local_address) return self._NeedsRetryAsyncNetworkStream(self, stream) class _NeedsRetryAsyncNetworkStream(NetworkStream): - def __init__( - self, backend: "NeedsRetryBackend", stream: NetworkStream - ) -> None: + def __init__(self, backend: "NeedsRetryBackend", stream: NetworkStream) -> None: self._backend = backend self._stream = stream - def read( - self, max_bytes: int, timeout: typing.Optional[float] = None - ) -> bytes: + def read(self, max_bytes: int, timeout: typing.Optional[float] = None) -> bytes: return self._stream.read(max_bytes, timeout) - def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: self._stream.write(buffer, timeout) def close(self) -> None: @@ -313,9 +281,7 @@ def test_connection_retries(): ] network_backend = NeedsRetryBackend(content) - with HTTPConnection( - origin=origin, network_backend=network_backend, retries=3 - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend, retries=3) as conn: response = conn.request("GET", "https://example.com/") assert response.status == 200 @@ -339,18 +305,12 @@ def test_connection_retries_tls(): b"Hello, world!", ] - network_backend = NeedsRetryBackend( - content, connect_tcp_failures=0, start_tls_failures=2 - ) - with HTTPConnection( - origin=origin, network_backend=network_backend, retries=3 - ) as conn: + network_backend = NeedsRetryBackend(content, connect_tcp_failures=0, start_tls_failures=2) + with HTTPConnection(origin=origin, network_backend=network_backend, retries=3) as conn: response = conn.request("GET", "https://example.com/") assert response.status == 200 - network_backend = NeedsRetryBackend( - content, connect_tcp_failures=0, start_tls_failures=2 - ) + network_backend = NeedsRetryBackend(content, connect_tcp_failures=0, start_tls_failures=2) with HTTPConnection( origin=origin, network_backend=network_backend, @@ -374,8 +334,6 @@ def test_uds_connections(): b"Hello, world!", ] ) - with HTTPConnection( - origin=origin, network_backend=network_backend, uds="/mock/example" - ) as conn: + with HTTPConnection(origin=origin, network_backend=network_backend, uds="/mock/example") as conn: response = conn.request("GET", "https://example.com/") assert response.status == 200 diff --git a/tests/httpcore2/_sync/test_connection_pool.py b/tests/httpcore2/_sync/test_connection_pool.py index babeef44..00dcb232 100644 --- a/tests/httpcore2/_sync/test_connection_pool.py +++ b/tests/httpcore2/_sync/test_connection_pool.py @@ -29,69 +29,45 @@ def test_connection_pool_with_keepalive(): ] ) - with httpcore2.ConnectionPool( - network_backend=network_backend, max_keepalive_connections=1 - ) as pool: + with httpcore2.ConnectionPool(network_backend=network_backend, max_keepalive_connections=1) as pool: # Sending an intial request, which once complete will return to the pool, IDLE. with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" response.read() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" # Sending a second request to the same origin will reuse the existing IDLE connection. with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" response.read() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] - assert ( - repr(pool) - == "" - ) + assert info == [""] + assert repr(pool) == "" # Sending a request to a different origin will not reuse the existing IDLE connection. - with pool.stream("GET", "http://example.com/") as response_1, pool.stream( - "GET", "http://example.com/" - ) as response_2: + with ( + pool.stream("GET", "http://example.com/") as response_1, + pool.stream("GET", "http://example.com/") as response_2, + ): info = [repr(c) for c in pool.connections] assert info == [ "", "", "", ] - assert ( - repr(pool) - == "" - ) + assert repr(pool) == "" response_1.read() response_2.read() @@ -103,10 +79,7 @@ def test_connection_pool_with_keepalive(): assert info == [ "", ] - assert ( - repr(pool) - == "" - ) + assert repr(pool) == "" @@ -127,13 +100,9 @@ def test_connection_pool_with_close(): with httpcore2.ConnectionPool(network_backend=network_backend) as pool: # Sending an intial request, which once complete will not return to the pool. - with pool.stream( - "GET", "https://example.com/", headers={"Connection": "close"} - ) as response: + with pool.stream("GET", "https://example.com/", headers={"Connection": "close"}) as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] response.read() assert response.status == 200 @@ -160,9 +129,7 @@ def test_connection_pool_with_http2(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), hyperframe.frame.HeadersFrame( stream_id=3, data=hpack.Encoder().encode( @@ -173,9 +140,7 @@ def test_connection_pool_with_http2(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=3, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=3, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ], http2=True, ) @@ -189,9 +154,7 @@ def test_connection_pool_with_http2(): assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] # Sending a second request to the same origin will reuse the existing IDLE connection. response = pool.request("GET", "https://example.com/") @@ -199,9 +162,7 @@ def test_connection_pool_with_http2(): assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] @@ -223,12 +184,8 @@ def test_connection_pool_with_http2_goaway(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), - hyperframe.frame.GoAwayFrame( - stream_id=0, error_code=0, last_stream_id=1 - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), + hyperframe.frame.GoAwayFrame(stream_id=0, error_code=0, last_stream_id=1).serialize(), b"", ], http2=True, @@ -243,9 +200,7 @@ def test_connection_pool_with_http2_goaway(): assert response.content == b"Hello, world!" info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] # Sending a second request to the same origin will require a new connection. # The original connection has now been closed. @@ -384,10 +339,8 @@ def trace(name, kwargs): with httpcore2.ConnectionPool(network_backend=network_backend) as pool: # Sending an initial request, which once complete will not return to the pool. - with pytest.raises(Exception): - pool.request( - "GET", "https://example.com/", extensions={"trace": trace} - ) + with pytest.raises(httpcore2.RemoteProtocolError): + pool.request("GET", "https://example.com/", extensions={"trace": trace}) info = [repr(c) for c in pool.connections] assert info == [] @@ -422,9 +375,7 @@ def connect_tcp( port: int, timeout: typing.Optional[float] = None, local_address: typing.Optional[str] = None, - socket_options: typing.Optional[ - typing.Iterable[httpcore2.SOCKET_OPTION] - ] = None, + socket_options: typing.Optional[typing.Iterable[httpcore2.SOCKET_OPTION]] = None, ) -> httpcore2.NetworkStream: raise httpcore2.ConnectError("Could not connect") @@ -437,10 +388,8 @@ def trace(name, kwargs): with httpcore2.ConnectionPool(network_backend=network_backend) as pool: # Sending an initial request, which once complete will not return to the pool. - with pytest.raises(Exception): - pool.request( - "GET", "https://example.com/", extensions={"trace": trace} - ) + with pytest.raises(httpcore2.ConnectError): + pool.request("GET", "https://example.com/", extensions={"trace": trace}) info = [repr(c) for c in pool.connections] assert info == [] @@ -474,9 +423,7 @@ def test_connection_pool_with_immediate_expiry(): # Sending an intial request, which once complete will not return to the pool. with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] response.read() assert response.status == 200 @@ -501,15 +448,11 @@ def test_connection_pool_with_no_keepalive_connections_allowed(): ] ) - with httpcore2.ConnectionPool( - max_keepalive_connections=0, network_backend=network_backend - ) as pool: + with httpcore2.ConnectionPool(max_keepalive_connections=0, network_backend=network_backend) as pool: # Sending an intial request, which once complete will not return to the pool. with pool.stream("GET", "https://example.com/") as response: info = [repr(c) for c in pool.connections] - assert info == [ - "" - ] + assert info == [""] response.read() assert response.status == 200 @@ -540,9 +483,7 @@ def fetch(pool, domain, info_list): info_list.append(info) response.read() - with httpcore2.ConnectionPool( - max_connections=1, network_backend=network_backend - ) as pool: + with httpcore2.ConnectionPool(max_connections=1, network_backend=network_backend) as pool: info_list: typing.List[str] = [] with concurrency.open_nursery() as nursery: for domain in ["a.com", "b.com", "c.com", "d.com", "e.com"]: @@ -586,9 +527,7 @@ def fetch(pool, domain, info_list): info_list.append(info) response.read() - with httpcore2.ConnectionPool( - max_connections=1, network_backend=network_backend, http2=True - ) as pool: + with httpcore2.ConnectionPool(max_connections=1, network_backend=network_backend, http2=True) as pool: info_list: typing.List[str] = [] with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: @@ -599,10 +538,7 @@ def fetch(pool, domain, info_list): # single connection was established at any one time. assert len(item) == 1 # Only a single request was sent on each connection. - assert ( - item[0] - == "" - ) + assert item[0] == "" @@ -628,9 +564,7 @@ def fetch(pool, domain, info_list): info_list.append(info) response.read() - with httpcore2.ConnectionPool( - max_connections=1, network_backend=network_backend, http2=True - ) as pool: + with httpcore2.ConnectionPool(max_connections=1, network_backend=network_backend, http2=True) as pool: info_list: typing.List[str] = [] with concurrency.open_nursery() as nursery: for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]: @@ -649,10 +583,7 @@ def fetch(pool, domain, info_list): "", ] - assert ( - repr(pool) - == "" - ) + assert repr(pool) == "" @@ -707,9 +638,7 @@ def test_connection_pool_timeout(): ] ) - with httpcore2.ConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + with httpcore2.ConnectionPool(network_backend=network_backend, max_connections=1) as pool: # Send a request to a pool that is configured to only support a single # connection, and then ensure that a second concurrent request # fails with a timeout. @@ -744,32 +673,22 @@ def test_connection_pool_timeout_zero(): extensions = {"timeout": {"pool": 0}} # A connection pool configured to allow only one connection at a time. - with httpcore2.ConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + with httpcore2.ConnectionPool(network_backend=network_backend, max_connections=1) as pool: # Two consecutive requests with a pool timeout of zero. # Both succeed without raising a timeout. - response = pool.request( - "GET", "https://example.com/", extensions=extensions - ) + response = pool.request("GET", "https://example.com/", extensions=extensions) assert response.status == 200 assert response.content == b"Hello, world!" - response = pool.request( - "GET", "https://example.com/", extensions=extensions - ) + response = pool.request("GET", "https://example.com/", extensions=extensions) assert response.status == 200 assert response.content == b"Hello, world!" # A connection pool configured to allow only one connection at a time. - with httpcore2.ConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + with httpcore2.ConnectionPool(network_backend=network_backend, max_connections=1) as pool: # Two concurrent requests with a pool timeout of zero. # Only the first will succeed without raising a timeout. - with pool.stream( - "GET", "https://example.com/", extensions=extensions - ) as response: + with pool.stream("GET", "https://example.com/", extensions=extensions) as response: # The first response hasn't yet completed. with pytest.raises(httpcore2.PoolTimeout): # So a pool timeout occurs. @@ -807,9 +726,7 @@ def test_http11_upgrade_connection(): def trace(name, kwargs): called.append(name) - with httpcore2.ConnectionPool( - network_backend=network_backend, max_connections=1 - ) as pool: + with httpcore2.ConnectionPool(network_backend=network_backend, max_connections=1) as pool: with pool.stream( "GET", "wss://example.com/", diff --git a/tests/httpcore2/_sync/test_http11.py b/tests/httpcore2/_sync/test_http11.py index 5012ba28..38c06232 100644 --- a/tests/httpcore2/_sync/test_http11.py +++ b/tests/httpcore2/_sync/test_http11.py @@ -15,9 +15,7 @@ def test_http11_connection(): b"Hello, world!", ] ) - with httpcore2.HTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = conn.request("GET", "https://example.com/") assert response.status == 200 assert response.content == b"Hello, world!" @@ -26,10 +24,7 @@ def test_http11_connection(): assert not conn.is_closed() assert conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @@ -56,10 +51,7 @@ def test_http11_connection_unread_response(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @@ -78,10 +70,7 @@ def test_http11_connection_with_remote_protocol_error(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @@ -107,10 +96,7 @@ def test_http11_connection_with_incomplete_response(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @@ -139,10 +125,7 @@ def test_http11_connection_with_local_protocol_error(): assert conn.is_closed() assert not conn.is_available() assert not conn.has_expired() - assert ( - repr(conn) - == "" - ) + assert repr(conn) == "" @@ -222,9 +205,7 @@ def test_http11_expect_continue(): b"Hello, world!", ] ) - with httpcore2.HTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = conn.request( "GET", "https://example.com/", @@ -255,9 +236,7 @@ def test_http11_upgrade_connection(): b"...", ] ) - with httpcore2.HTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: with conn.stream( "GET", "wss://example.com/", @@ -285,19 +264,11 @@ def test_http11_upgrade_with_trailing_data(): # in which response headers and data are received at once. # This means that "foobar" becomes trailing data. [ - ( - b"HTTP/1.1 101 Switching Protocols\r\n" - b"Connection: upgrade\r\n" - b"Upgrade: custom\r\n" - b"\r\n" - b"foobar" - ), + (b"HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nUpgrade: custom\r\n\r\nfoobar"), b"baz", ] ) - with httpcore2.HTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: with conn.stream( "GET", "wss://example.com/", @@ -344,9 +315,7 @@ def test_http11_early_hints(): b"Hello, world! ...", ] ) - with httpcore2.HTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = conn.request( "GET", "https://example.com/", @@ -372,9 +341,7 @@ def test_http11_header_sub_100kb(): b"", ] ) - with httpcore2.HTTP11Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP11Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = conn.request("GET", "https://example.com/") assert response.status == 200 assert response.content == b"" diff --git a/tests/httpcore2/_sync/test_http2.py b/tests/httpcore2/_sync/test_http2.py index 89aafe10..3b8239a4 100644 --- a/tests/httpcore2/_sync/test_http2.py +++ b/tests/httpcore2/_sync/test_http2.py @@ -21,14 +21,10 @@ def test_http2_connection(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ] ) - with httpcore2.HTTP2Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP2Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: response = conn.request("GET", "https://example.com/") assert response.status == 200 assert response.content == b"Hello, world!" @@ -37,13 +33,8 @@ def test_http2_connection(): assert conn.is_available() assert not conn.is_closed() assert not conn.has_expired() - assert ( - conn.info() == "'https://example.com:443', HTTP/2, IDLE, Request Count: 1" - ) - assert ( - repr(conn) - == "" - ) + assert conn.info() == "'https://example.com:443', HTTP/2, IDLE, Request Count: 1" + assert repr(conn) == "" @@ -62,18 +53,12 @@ def test_http2_connection_closed(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), # Connection is closed after the first response - hyperframe.frame.GoAwayFrame( - stream_id=0, error_code=0, last_stream_id=1 - ).serialize(), + hyperframe.frame.GoAwayFrame(stream_id=0, error_code=0, last_stream_id=1).serialize(), ] ) - with httpcore2.HTTP2Connection( - origin=origin, stream=stream, keepalive_expiry=5.0 - ) as conn: + with httpcore2.HTTP2Connection(origin=origin, stream=stream, keepalive_expiry=5.0) as conn: conn.request("GET", "https://example.com/") with pytest.raises(httpcore2.ConnectionNotAvailable): @@ -98,9 +83,7 @@ def test_http2_connection_post_request(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ] ) with httpcore2.HTTP2Connection(origin=origin, stream=stream) as conn: @@ -160,9 +143,7 @@ def test_http2_connection_with_rst_stream(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=3, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=3, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), b"", ] ) @@ -206,9 +187,7 @@ def test_http2_connection_with_goaway(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=3, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=3, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), b"", ] ) @@ -230,33 +209,17 @@ def test_http2_connection_with_flow_control(): [ hyperframe.frame.SettingsFrame().serialize(), # Available flow: 65,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 75,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 85,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 95,535 - hyperframe.frame.WindowUpdateFrame( - stream_id=0, window_increment=10_000 - ).serialize(), - hyperframe.frame.WindowUpdateFrame( - stream_id=1, window_increment=10_000 - ).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=10_000).serialize(), + hyperframe.frame.WindowUpdateFrame(stream_id=1, window_increment=10_000).serialize(), # Available flow: 105,535 hyperframe.frame.HeadersFrame( stream_id=1, @@ -268,9 +231,7 @@ def test_http2_connection_with_flow_control(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"100,000 bytes received", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"100,000 bytes received", flags=["END_STREAM"]).serialize(), ] ) with httpcore2.HTTP2Connection(origin=origin, stream=stream) as conn: @@ -302,9 +263,7 @@ def test_http2_connection_attempt_close(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ] ) with httpcore2.HTTP2Connection(origin=origin, stream=stream) as conn: @@ -356,9 +315,7 @@ def test_http2_remote_max_streams_update(): hyperframe.frame.SettingsFrame( settings={hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 50} ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world...again!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world...again!", flags=["END_STREAM"]).serialize(), ] ) with httpcore2.HTTP2Connection(origin=origin, stream=stream) as conn: diff --git a/tests/httpcore2/_sync/test_http_proxy.py b/tests/httpcore2/_sync/test_http_proxy.py index 1454ef1b..feff9ac6 100644 --- a/tests/httpcore2/_sync/test_http_proxy.py +++ b/tests/httpcore2/_sync/test_http_proxy.py @@ -48,26 +48,16 @@ def test_proxy_forwarding(): assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a forwarding proxy can only handle HTTP requests to the same origin. - assert proxy.connections[0].can_handle_request( - Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"other.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"other.com", 443) - ) + assert proxy.connections[0].can_handle_request(Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"other.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"other.com", 443)) @@ -103,26 +93,16 @@ def test_proxy_tunneling(): assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a tunneled proxy can only handle HTTPS requests to the same origin. - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"other.com", 80) - ) - assert proxy.connections[0].can_handle_request( - Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"other.com", 443) - ) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"other.com", 80)) + assert proxy.connections[0].can_handle_request(Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"other.com", 443)) # We need to adapt the mock backend here slightly in order to deal @@ -173,9 +153,7 @@ def test_proxy_tunneling_http2(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!", flags=["END_STREAM"] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]).serialize(), ], ) @@ -187,34 +165,22 @@ def test_proxy_tunneling_http2(): # Sending an intial request, which once complete will return to the pool, IDLE. with proxy.stream("GET", "https://example.com/") as response: info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] response.read() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a tunneled proxy can only handle HTTPS requests to the same origin. - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"http", b"other.com", 80) - ) - assert proxy.connections[0].can_handle_request( - Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - Origin(b"https", b"other.com", 443) - ) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(Origin(b"http", b"other.com", 80)) + assert proxy.connections[0].can_handle_request(Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(Origin(b"https", b"other.com", 443)) @@ -224,7 +190,7 @@ def test_proxy_tunneling_with_403(): """ network_backend = MockBackend( [ - b"HTTP/1.1 403 Permission Denied\r\n" b"\r\n", + b"HTTP/1.1 403 Permission Denied\r\n\r\n", ] ) @@ -273,6 +239,4 @@ def test_proxy_headers(): url="http://localhost:8080/", auth=("username", "password"), ) - assert proxy.headers == [ - (b"Proxy-Authorization", b"Basic dXNlcm5hbWU6cGFzc3dvcmQ=") - ] + assert proxy.headers == [(b"Proxy-Authorization", b"Basic dXNlcm5hbWU6cGFzc3dvcmQ=")] diff --git a/tests/httpcore2/_sync/test_socks_proxy.py b/tests/httpcore2/_sync/test_socks_proxy.py index b40d0b28..9ca4a725 100644 --- a/tests/httpcore2/_sync/test_socks_proxy.py +++ b/tests/httpcore2/_sync/test_socks_proxy.py @@ -31,34 +31,22 @@ def test_socks5_request(): # Sending an intial request, which once complete will return to the pool, IDLE. with proxy.stream("GET", "https://example.com/") as response: info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] response.read() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() # A connection on a tunneled proxy can only handle HTTPS requests to the same origin. - assert not proxy.connections[0].can_handle_request( - httpcore2.Origin(b"http", b"example.com", 80) - ) - assert not proxy.connections[0].can_handle_request( - httpcore2.Origin(b"http", b"other.com", 80) - ) - assert proxy.connections[0].can_handle_request( - httpcore2.Origin(b"https", b"example.com", 443) - ) - assert not proxy.connections[0].can_handle_request( - httpcore2.Origin(b"https", b"other.com", 443) - ) + assert not proxy.connections[0].can_handle_request(httpcore2.Origin(b"http", b"example.com", 80)) + assert not proxy.connections[0].can_handle_request(httpcore2.Origin(b"http", b"other.com", 80)) + assert proxy.connections[0].can_handle_request(httpcore2.Origin(b"https", b"example.com", 443)) + assert not proxy.connections[0].can_handle_request(httpcore2.Origin(b"https", b"other.com", 443)) @@ -94,17 +82,13 @@ def test_authenticated_socks5_request(): # Sending an intial request, which once complete will return to the pool, IDLE. with proxy.stream("GET", "https://example.com/") as response: info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] response.read() assert response.status == 200 assert response.content == b"Hello, world!" info = [repr(c) for c in proxy.connections] - assert info == [ - "" - ] + assert info == [""] assert proxy.connections[0].is_idle() assert proxy.connections[0].is_available() assert not proxy.connections[0].is_closed() @@ -132,9 +116,7 @@ def test_socks5_request_connect_failed(): # Sending a request, which the proxy rejects with pytest.raises(httpcore2.ProxyError) as exc_info: proxy.request("GET", "https://example.com/") - assert ( - str(exc_info.value) == "Proxy Server could not connect: Connection refused." - ) + assert str(exc_info.value) == "Proxy Server could not connect: Connection refused." assert not proxy.connections @@ -160,8 +142,7 @@ def test_socks5_request_failed_to_provide_auth(): with pytest.raises(httpcore2.ProxyError) as exc_info: proxy.request("GET", "https://example.com/") assert ( - str(exc_info.value) - == "Requested NO AUTHENTICATION REQUIRED from proxy server, but got USERNAME/PASSWORD." + str(exc_info.value) == "Requested NO AUTHENTICATION REQUIRED from proxy server, but got USERNAME/PASSWORD." ) assert not proxy.connections diff --git a/tests/httpcore2/benchmark/client.py b/tests/httpcore2/benchmark/client.py index bc873a8c..3adbff2e 100644 --- a/tests/httpcore2/benchmark/client.py +++ b/tests/httpcore2/benchmark/client.py @@ -7,10 +7,10 @@ from typing import Any, Callable, Coroutine, Iterator, List import aiohttp -import matplotlib.pyplot as plt # type: ignore[import-untyped] +import matplotlib.pyplot as plt import pyinstrument import urllib3 -from matplotlib.axes import Axes # type: ignore[import-untyped] +from matplotlib.axes import Axes import httpcore2 @@ -50,9 +50,7 @@ async def coro_with_sem(coro: Coroutine[Any, Any, Any]) -> None: await asyncio.gather(*(coro_with_sem(c) for c in coros)) - async def httpcore_get( - pool: httpcore2.AsyncConnectionPool, timings: List[int] - ) -> None: + async def httpcore_get(pool: httpcore2.AsyncConnectionPool, timings: List[int]) -> None: start = time.monotonic() res = await pool.request("GET", URL) assert len(await res.aread()) == 2000 @@ -68,43 +66,29 @@ async def aiohttp_get(session: aiohttp.ClientSession, timings: List[int]) -> Non async with httpcore2.AsyncConnectionPool(max_connections=POOL_LIMIT) as pool: # warmup - await gather_limited_concurrency( - (httpcore_get(pool, []) for _ in range(REQUESTS)), CONCURRENCY * 2 - ) + await gather_limited_concurrency((httpcore_get(pool, []) for _ in range(REQUESTS)), CONCURRENCY * 2) timings: List[int] = [] start = time.monotonic() with profile(): for _ in range(REPEATS): - await gather_limited_concurrency( - (httpcore_get(pool, timings) for _ in range(REQUESTS)) - ) - axis.plot( - [*range(len(timings))], timings, label=f"httpcore (tot={duration(start)}ms)" - ) + await gather_limited_concurrency((httpcore_get(pool, timings) for _ in range(REQUESTS))) + axis.plot([*range(len(timings))], timings, label=f"httpcore (tot={duration(start)}ms)") connector = aiohttp.TCPConnector(limit=POOL_LIMIT) async with aiohttp.ClientSession(connector=connector) as session: # warmup - await gather_limited_concurrency( - (aiohttp_get(session, []) for _ in range(REQUESTS)), CONCURRENCY * 2 - ) + await gather_limited_concurrency((aiohttp_get(session, []) for _ in range(REQUESTS)), CONCURRENCY * 2) timings = [] start = time.monotonic() for _ in range(REPEATS): - await gather_limited_concurrency( - (aiohttp_get(session, timings) for _ in range(REQUESTS)) - ) - axis.plot( - [*range(len(timings))], timings, label=f"aiohttp (tot={duration(start)}ms)" - ) + await gather_limited_concurrency((aiohttp_get(session, timings) for _ in range(REQUESTS))) + axis.plot([*range(len(timings))], timings, label=f"aiohttp (tot={duration(start)}ms)") def run_sync_requests(axis: Axes) -> None: - def run_in_executor( - fns: Iterator[Callable[[], None]], executor: ThreadPoolExecutor - ) -> None: + def run_in_executor(fns: Iterator[Callable[[], None]], executor: ThreadPoolExecutor) -> None: futures = [executor.submit(fn) for fn in fns] for future in futures: future.result() @@ -136,17 +120,11 @@ def urllib3_get(pool: urllib3.HTTPConnectionPool, timings: List[int]) -> None: start = time.monotonic() with profile(): for _ in range(REPEATS): - run_in_executor( - (lambda: httpcore_get(pool, timings) for _ in range(REQUESTS)), exec - ) + run_in_executor((lambda: httpcore_get(pool, timings) for _ in range(REQUESTS)), exec) exec.shutdown(wait=True) - axis.plot( - [*range(len(timings))], timings, label=f"httpcore (tot={duration(start)}ms)" - ) + axis.plot([*range(len(timings))], timings, label=f"httpcore (tot={duration(start)}ms)") - with urllib3.HTTPConnectionPool( - "localhost", PORT, maxsize=POOL_LIMIT - ) as urllib3_pool: + with urllib3.HTTPConnectionPool("localhost", PORT, maxsize=POOL_LIMIT) as urllib3_pool: # warmup with ThreadPoolExecutor(max_workers=CONCURRENCY * 2) as exec: run_in_executor( @@ -163,9 +141,7 @@ def urllib3_get(pool: urllib3.HTTPConnectionPool, timings: List[int]) -> None: exec, ) exec.shutdown(wait=True) - axis.plot( - [*range(len(timings))], timings, label=f"urllib3 (tot={duration(start)}ms)" - ) + axis.plot([*range(len(timings))], timings, label=f"urllib3 (tot={duration(start)}ms)") def main() -> None: diff --git a/tests/httpcore2/test_cancellations.py b/tests/httpcore2/test_cancellations.py index 83a71cae..8bec4b10 100644 --- a/tests/httpcore2/test_cancellations.py +++ b/tests/httpcore2/test_cancellations.py @@ -14,9 +14,7 @@ class SlowWriteStream(httpcore2.AsyncNetworkStream): the request writing. """ - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: await anyio.sleep(999) async def aclose(self) -> None: @@ -33,9 +31,7 @@ class HandshakeThenSlowWriteStream(httpcore2.AsyncNetworkStream): def __init__(self) -> None: self._handshake_complete = False - async def write( - self, buffer: bytes, timeout: typing.Optional[float] = None - ) -> None: + async def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None: if not self._handshake_complete: self._handshake_complete = True else: @@ -57,9 +53,7 @@ def __init__(self, buffer: typing.List[bytes]): async def write(self, buffer, timeout=None): pass - async def read( - self, max_bytes: int, timeout: typing.Optional[float] = None - ) -> bytes: + async def read(self, max_bytes: int, timeout: typing.Optional[float] = None) -> bytes: if not self._buffer: await anyio.sleep(999) return self._buffer.pop(0) @@ -229,9 +223,7 @@ async def test_h2_timeout_during_response(): ), flags=["END_HEADERS"], ).serialize(), - hyperframe.frame.DataFrame( - stream_id=1, data=b"Hello, world!...", flags=[] - ).serialize(), + hyperframe.frame.DataFrame(stream_id=1, data=b"Hello, world!...", flags=[]).serialize(), ] ) async with httpcore2.AsyncHTTP2Connection(origin, stream) as conn: diff --git a/tests/httpcore2/test_models.py b/tests/httpcore2/test_models.py index 5d7c82b7..a09917cf 100644 --- a/tests/httpcore2/test_models.py +++ b/tests/httpcore2/test_models.py @@ -9,17 +9,13 @@ def test_url(): url = httpcore2.URL("https://www.example.com/") - assert url == httpcore2.URL( - scheme="https", host="www.example.com", port=None, target="/" - ) + assert url == httpcore2.URL(scheme="https", host="www.example.com", port=None, target="/") assert bytes(url) == b"https://www.example.com/" def test_url_with_port(): url = httpcore2.URL("https://www.example.com:443/") - assert url == httpcore2.URL( - scheme="https", host="www.example.com", port=443, target="/" - ) + assert url == httpcore2.URL(scheme="https", host="www.example.com", port=443, target="/") assert bytes(url) == b"https://www.example.com:443/" @@ -63,24 +59,17 @@ def test_request(): assert request.headers == [] assert request.extensions == {} assert repr(request) == "" - assert ( - repr(request.url) - == "URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')" - ) + assert repr(request.url) == "URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')" assert repr(request.stream) == "" def test_request_with_target_extension(): extensions = {"target": b"/another_path"} - request = httpcore2.Request( - "GET", "https://www.example.com/path", extensions=extensions - ) + request = httpcore2.Request("GET", "https://www.example.com/path", extensions=extensions) assert request.url.target == b"/another_path" extensions = {"target": b"/unescaped|path"} - request = httpcore2.Request( - "GET", "https://www.example.com/path", extensions=extensions - ) + request = httpcore2.Request("GET", "https://www.example.com/path", extensions=extensions) assert request.url.target == b"/unescaped|path" @@ -99,10 +88,7 @@ def test_request_with_invalid_url(): def test_request_with_invalid_headers(): with pytest.raises(TypeError) as exc_info: httpcore2.Request("GET", "https://www.example.com/", headers=123) # type: ignore - assert ( - str(exc_info.value) - == "headers must be a mapping or sequence of two-tuples, but got int." - ) + assert str(exc_info.value) == "headers must be a mapping or sequence of two-tuples, but got int." # Response @@ -144,7 +130,7 @@ def test_response_sync_streaming(): # We streamed the response rather than reading it, so .content is not available. with pytest.raises(RuntimeError): - response.content + _ = response.content # Once we've streamed the response, we can't access the stream again. with pytest.raises(RuntimeError): @@ -181,9 +167,9 @@ async def test_response_async_streaming(): # We streamed the response rather than reading it, so .content is not available. with pytest.raises(RuntimeError): - response.content + _ = response.content # Once we've streamed the response, we can't access the stream again. with pytest.raises(RuntimeError): - async for chunk in response.aiter_stream(): + async for _chunk in response.aiter_stream(): pass # pragma: nocover diff --git a/tests/httpx2/client/test_async_client.py b/tests/httpx2/client/test_async_client.py index 6510d299..ccc73813 100644 --- a/tests/httpx2/client/test_async_client.py +++ b/tests/httpx2/client/test_async_client.py @@ -115,9 +115,7 @@ def hello_world() -> typing.Iterator[bytes]: # pragma: no cover async def test_raise_for_status(server): async with httpx2.AsyncClient() as client: for status_code in (200, 400, 404, 500, 505): - response = await client.request( - "GET", server.url.copy_with(path=f"/status/{status_code}") - ) + response = await client.request("GET", server.url.copy_with(path=f"/status/{status_code}")) if 400 <= status_code < 600: with pytest.raises(httpx2.HTTPStatusError) as exc_info: @@ -171,9 +169,7 @@ async def test_100_continue(server): content = b"Echo request body" async with httpx2.AsyncClient() as client: - response = await client.post( - server.url.copy_with(path="/echo_body"), headers=headers, content=content - ) + response = await client.post(server.url.copy_with(path="/echo_body"), headers=headers, content=content) assert response.status_code == 200 assert response.content == content @@ -235,9 +231,7 @@ async def __aexit__(self, *args): transport = Transport(name="transport") mounted = Transport(name="mounted") - async with httpx2.AsyncClient( - transport=transport, mounts={"http://www.example.org": mounted} - ): + async with httpx2.AsyncClient(transport=transport, mounts={"http://www.example.org": mounted}): pass assert transport.events == [ @@ -355,9 +349,7 @@ async def aclose(self) -> None: nonlocal stream_was_closed stream_was_closed = True - return httpx2.Response( - 200, headers={"Content-Length": "12"}, stream=CancelledStream() - ) + return httpx2.Response(200, headers={"Content-Length": "12"}, stream=CancelledStream()) transport = httpx2.MockTransport(response_with_cancel_during_stream) diff --git a/tests/httpx2/client/test_auth.py b/tests/httpx2/client/test_auth.py index baa660e8..b2887cc5 100644 --- a/tests/httpx2/client/test_auth.py +++ b/tests/httpx2/client/test_auth.py @@ -66,17 +66,11 @@ def challenge_send(self, request: httpx2.Request) -> httpx2.Response: challenge_data = { "nonce": nonce, "qop": self.qop, - "opaque": ( - "ee6378f3ee14ebfd2fff54b70a91a7c9390518047f242ab2271380db0e14bda1" - ), + "opaque": ("ee6378f3ee14ebfd2fff54b70a91a7c9390518047f242ab2271380db0e14bda1"), "algorithm": self.algorithm, "stale": "FALSE", } - challenge_str = ", ".join( - '{}="{}"'.format(key, value) - for key, value in challenge_data.items() - if value - ) + challenge_str = ", ".join('{}="{}"'.format(key, value) for key, value in challenge_data.items() if value) headers = { "www-authenticate": f'Digest realm="httpx@example.org", {challenge_str}', @@ -97,9 +91,7 @@ class RepeatAuth(httpx2.Auth): def __init__(self, repeat: int) -> None: self.repeat = repeat - def auth_flow( - self, request: httpx2.Request - ) -> typing.Generator[httpx2.Request, httpx2.Response, None]: + def auth_flow(self, request: httpx2.Request) -> typing.Generator[httpx2.Request, httpx2.Response, None]: nonces = [] for index in range(self.repeat): @@ -124,9 +116,7 @@ class ResponseBodyAuth(httpx2.Auth): def __init__(self, token: str) -> None: self.token = token - def auth_flow( - self, request: httpx2.Request - ) -> typing.Generator[httpx2.Request, httpx2.Response, None]: + def auth_flow(self, request: httpx2.Request) -> typing.Generator[httpx2.Request, httpx2.Response, None]: request.headers["Authorization"] = self.token response = yield request data = response.text @@ -144,16 +134,12 @@ def __init__(self) -> None: self._lock = threading.Lock() self._async_lock = anyio.Lock() - def sync_auth_flow( - self, request: httpx2.Request - ) -> typing.Generator[httpx2.Request, httpx2.Response, None]: + def sync_auth_flow(self, request: httpx2.Request) -> typing.Generator[httpx2.Request, httpx2.Response, None]: with self._lock: request.headers["Authorization"] = "sync-auth" yield request - async def async_auth_flow( - self, request: httpx2.Request - ) -> typing.AsyncGenerator[httpx2.Request, httpx2.Response]: + async def async_auth_flow(self, request: httpx2.Request) -> typing.AsyncGenerator[httpx2.Request, httpx2.Response]: async with self._async_lock: request.headers["Authorization"] = "async-auth" yield request @@ -181,9 +167,7 @@ async def test_basic_auth_with_stream() -> None: auth = ("user", "password123") app = App() - async with httpx2.AsyncClient( - transport=httpx2.MockTransport(app), auth=auth - ) as client: + async with httpx2.AsyncClient(transport=httpx2.MockTransport(app), auth=auth) as client: async with client.stream("GET", url) as response: await response.aread() @@ -209,9 +193,7 @@ async def test_basic_auth_on_session() -> None: auth = ("user", "password123") app = App() - async with httpx2.AsyncClient( - transport=httpx2.MockTransport(app), auth=auth - ) as client: + async with httpx2.AsyncClient(transport=httpx2.MockTransport(app), auth=auth) as client: response = await client.get(url) assert response.status_code == 200 @@ -248,9 +230,7 @@ def test_netrc_auth_credentials_exist() -> None: response = client.get(url) assert response.status_code == 200 - assert response.json() == { - "auth": "Basic ZXhhbXBsZS11c2VybmFtZTpleGFtcGxlLXBhc3N3b3Jk" - } + assert response.json() == {"auth": "Basic ZXhhbXBsZS11c2VybmFtZTpleGFtcGxlLXBhc3N3b3Jk"} def test_netrc_auth_credentials_do_not_exist() -> None: @@ -291,9 +271,7 @@ async def test_auth_disable_per_request() -> None: auth = ("user", "password123") app = App() - async with httpx2.AsyncClient( - transport=httpx2.MockTransport(app), auth=auth - ) as client: + async with httpx2.AsyncClient(transport=httpx2.MockTransport(app), auth=auth) as client: response = await client.get(url, auth=None) assert response.status_code == 200 @@ -424,9 +402,7 @@ async def test_digest_auth_401_response_without_digest_auth_header() -> None: ], ) @pytest.mark.anyio -async def test_digest_auth( - algorithm: str, expected_hash_length: int, expected_response_length: int -) -> None: +async def test_digest_auth(algorithm: str, expected_hash_length: int, expected_response_length: int) -> None: url = "https://example.org/" auth = httpx2.DigestAuth(username="user", password="password123") app = DigestApp(algorithm=algorithm) @@ -564,12 +540,8 @@ async def test_digest_auth_resets_nonce_count_after_401() -> None: assert response_1.status_code == 200 assert len(response_1.history) == 1 - first_nonce = parse_keqv_list( - response_1.request.headers["Authorization"].split(", ") - )["nonce"] - first_nc = parse_keqv_list( - response_1.request.headers["Authorization"].split(", ") - )["nc"] + first_nonce = parse_keqv_list(response_1.request.headers["Authorization"].split(", "))["nonce"] + first_nc = parse_keqv_list(response_1.request.headers["Authorization"].split(", "))["nc"] # with this we now force a 401 on a subsequent (but initial) request app.send_response_after_attempt = 2 @@ -580,17 +552,11 @@ async def test_digest_auth_resets_nonce_count_after_401() -> None: assert response_2.status_code == 200 assert len(response_2.history) == 1 - second_nonce = parse_keqv_list( - response_2.request.headers["Authorization"].split(", ") - )["nonce"] - second_nc = parse_keqv_list( - response_2.request.headers["Authorization"].split(", ") - )["nc"] + second_nonce = parse_keqv_list(response_2.request.headers["Authorization"].split(", "))["nonce"] + second_nc = parse_keqv_list(response_2.request.headers["Authorization"].split(", "))["nc"] assert first_nonce != second_nonce # ensures that the auth challenge was reset - assert ( - first_nc == second_nc - ) # ensures the nonce count is reset when the authentication failed + assert first_nc == second_nc # ensures the nonce count is reset when the authentication failed @pytest.mark.parametrize( diff --git a/tests/httpx2/client/test_client.py b/tests/httpx2/client/test_client.py index e86c0eca..e39e17d7 100644 --- a/tests/httpx2/client/test_client.py +++ b/tests/httpx2/client/test_client.py @@ -134,9 +134,7 @@ async def hello_world() -> typing.AsyncIterator[bytes]: # pragma: no cover def test_raise_for_status(server): with httpx2.Client() as client: for status_code in (200, 400, 404, 500, 505): - response = client.request( - "GET", server.url.copy_with(path=f"/status/{status_code}") - ) + response = client.request("GET", server.url.copy_with(path=f"/status/{status_code}")) if 400 <= status_code < 600: with pytest.raises(httpx2.HTTPStatusError) as exc_info: response.raise_for_status() @@ -334,10 +332,7 @@ def test_client_closed_state_using_with_block(): def echo_raw_headers(request: httpx2.Request) -> httpx2.Response: - data = [ - (name.decode("ascii"), value.decode("ascii")) - for name, value in request.headers.raw - ] + data = [(name.decode("ascii"), value.decode("ascii")) for name, value in request.headers.raw] return httpx2.Response(200, json=data) @@ -348,9 +343,7 @@ def test_raw_client_header(): url = "http://example.org/echo_headers" headers = {"Example-Header": "example-value"} - client = httpx2.Client( - transport=httpx2.MockTransport(echo_raw_headers), headers=headers - ) + client = httpx2.Client(transport=httpx2.MockTransport(echo_raw_headers), headers=headers) response = client.get(url) assert response.status_code == 200 diff --git a/tests/httpx2/client/test_cookies.py b/tests/httpx2/client/test_cookies.py index 90858e6c..b95fcff3 100644 --- a/tests/httpx2/client/test_cookies.py +++ b/tests/httpx2/client/test_cookies.py @@ -23,9 +23,7 @@ def test_set_cookie() -> None: url = "http://example.org/echo_cookies" cookies = {"example-name": "example-value"} - client = httpx2.Client( - cookies=cookies, transport=httpx2.MockTransport(get_and_set_cookies) - ) + client = httpx2.Client(cookies=cookies, transport=httpx2.MockTransport(get_and_set_cookies)) response = client.get(url) assert response.status_code == 200 @@ -75,9 +73,7 @@ def test_set_cookie_with_cookiejar() -> None: ) cookies.set_cookie(cookie) - client = httpx2.Client( - cookies=cookies, transport=httpx2.MockTransport(get_and_set_cookies) - ) + client = httpx2.Client(cookies=cookies, transport=httpx2.MockTransport(get_and_set_cookies)) response = client.get(url) assert response.status_code == 200 @@ -112,9 +108,7 @@ def test_setting_client_cookies_to_cookiejar() -> None: ) cookies.set_cookie(cookie) - client = httpx2.Client( - cookies=cookies, transport=httpx2.MockTransport(get_and_set_cookies) - ) + client = httpx2.Client(cookies=cookies, transport=httpx2.MockTransport(get_and_set_cookies)) response = client.get(url) assert response.status_code == 200 diff --git a/tests/httpx2/client/test_event_hooks.py b/tests/httpx2/client/test_event_hooks.py index f56890fa..458b7789 100644 --- a/tests/httpx2/client/test_event_hooks.py +++ b/tests/httpx2/client/test_event_hooks.py @@ -24,9 +24,7 @@ def on_response(response): event_hooks = {"request": [on_request], "response": [on_response]} - with httpx2.Client( - event_hooks=event_hooks, transport=httpx2.MockTransport(app) - ) as http: + with httpx2.Client(event_hooks=event_hooks, transport=httpx2.MockTransport(app)) as http: http.get("http://127.0.0.1:8000/", auth=("username", "password")) assert events == [ @@ -54,9 +52,7 @@ def raise_on_4xx_5xx(response): event_hooks = {"response": [raise_on_4xx_5xx]} - with httpx2.Client( - event_hooks=event_hooks, transport=httpx2.MockTransport(app) - ) as http: + with httpx2.Client(event_hooks=event_hooks, transport=httpx2.MockTransport(app)) as http: try: http.get("http://127.0.0.1:8000/status/400") except httpx2.HTTPStatusError as exc: @@ -75,9 +71,7 @@ async def on_response(response): event_hooks = {"request": [on_request], "response": [on_response]} - async with httpx2.AsyncClient( - event_hooks=event_hooks, transport=httpx2.MockTransport(app) - ) as http: + async with httpx2.AsyncClient(event_hooks=event_hooks, transport=httpx2.MockTransport(app)) as http: await http.get("http://127.0.0.1:8000/", auth=("username", "password")) assert events == [ @@ -106,9 +100,7 @@ async def raise_on_4xx_5xx(response): event_hooks = {"response": [raise_on_4xx_5xx]} - async with httpx2.AsyncClient( - event_hooks=event_hooks, transport=httpx2.MockTransport(app) - ) as http: + async with httpx2.AsyncClient(event_hooks=event_hooks, transport=httpx2.MockTransport(app)) as http: try: await http.get("http://127.0.0.1:8000/status/400") except httpx2.HTTPStatusError as exc: diff --git a/tests/httpx2/client/test_headers.py b/tests/httpx2/client/test_headers.py index 0f97e031..81da74d5 100755 --- a/tests/httpx2/client/test_headers.py +++ b/tests/httpx2/client/test_headers.py @@ -48,9 +48,7 @@ def test_header_merge(): url = "http://example.org/echo_headers" client_headers = {"User-Agent": "python-myclient/0.2.1"} request_headers = {"X-Auth-Token": "FooBarBazToken"} - client = httpx2.Client( - transport=httpx2.MockTransport(echo_headers), headers=client_headers - ) + client = httpx2.Client(transport=httpx2.MockTransport(echo_headers), headers=client_headers) response = client.get(url, headers=request_headers) assert response.status_code == 200 @@ -70,9 +68,7 @@ def test_header_merge_conflicting_headers(): url = "http://example.org/echo_headers" client_headers = {"X-Auth-Token": "FooBar"} request_headers = {"X-Auth-Token": "BazToken"} - client = httpx2.Client( - transport=httpx2.MockTransport(echo_headers), headers=client_headers - ) + client = httpx2.Client(transport=httpx2.MockTransport(echo_headers), headers=client_headers) response = client.get(url, headers=request_headers) assert response.status_code == 200 @@ -92,9 +88,7 @@ def test_header_update(): url = "http://example.org/echo_headers" client = httpx2.Client(transport=httpx2.MockTransport(echo_headers)) first_response = client.get(url) - client.headers.update( - {"User-Agent": "python-myclient/0.2.1", "Another-Header": "AThing"} - ) + client.headers.update({"User-Agent": "python-myclient/0.2.1", "Another-Header": "AThing"}) second_response = client.get(url) assert first_response.status_code == 200 @@ -131,16 +125,12 @@ def test_header_repeated_items(): echoed_headers = response.json()["headers"] # as per RFC 7230, the whitespace after a comma is insignificant # so we split and strip here so that we can do a safe comparison - assert ["x-header", ["1", "2", "3"]] in [ - [k, [subv.lstrip() for subv in v.split(",")]] for k, v in echoed_headers - ] + assert ["x-header", ["1", "2", "3"]] in [[k, [subv.lstrip() for subv in v.split(",")]] for k, v in echoed_headers] def test_header_repeated_multi_items(): url = "http://example.org/echo_headers" - client = httpx2.Client( - transport=httpx2.MockTransport(echo_repeated_headers_multi_items) - ) + client = httpx2.Client(transport=httpx2.MockTransport(echo_repeated_headers_multi_items)) response = client.get(url, headers=[("x-header", "1"), ("x-header", "2,3")]) assert response.status_code == 200 @@ -260,9 +250,7 @@ def test_not_same_origin(): def test_is_https_redirect(): url = httpx2.URL("https://example.com") - request = httpx2.Request( - "GET", "http://example.com", headers={"Authorization": "empty"} - ) + request = httpx2.Request("GET", "http://example.com", headers={"Authorization": "empty"}) client = httpx2.Client() headers = client._redirect_headers(request, url, "GET") @@ -272,9 +260,7 @@ def test_is_https_redirect(): def test_is_not_https_redirect(): url = httpx2.URL("https://www.example.com") - request = httpx2.Request( - "GET", "http://example.com", headers={"Authorization": "empty"} - ) + request = httpx2.Request("GET", "http://example.com", headers={"Authorization": "empty"}) client = httpx2.Client() headers = client._redirect_headers(request, url, "GET") @@ -284,9 +270,7 @@ def test_is_not_https_redirect(): def test_is_not_https_redirect_if_not_default_ports(): url = httpx2.URL("https://example.com:1337") - request = httpx2.Request( - "GET", "http://example.com:9999", headers={"Authorization": "empty"} - ) + request = httpx2.Request("GET", "http://example.com:9999", headers={"Authorization": "empty"}) client = httpx2.Client() headers = client._redirect_headers(request, url, "GET") diff --git a/tests/httpx2/client/test_queryparams.py b/tests/httpx2/client/test_queryparams.py index 2e711910..c46780d3 100644 --- a/tests/httpx2/client/test_queryparams.py +++ b/tests/httpx2/client/test_queryparams.py @@ -26,9 +26,7 @@ def test_client_queryparams_echo(): url = "http://example.org/echo_queryparams" client_queryparams = "first=str" request_queryparams = {"second": "dict"} - client = httpx2.Client( - transport=httpx2.MockTransport(hello_world), params=client_queryparams - ) + client = httpx2.Client(transport=httpx2.MockTransport(hello_world), params=client_queryparams) response = client.get(url, params=request_queryparams) assert response.status_code == 200 diff --git a/tests/httpx2/client/test_redirects.py b/tests/httpx2/client/test_redirects.py index 71842765..a0b25577 100644 --- a/tests/httpx2/client/test_redirects.py +++ b/tests/httpx2/client/test_redirects.py @@ -182,9 +182,7 @@ def test_head_redirect(): def test_relative_redirect(): client = httpx2.Client(transport=httpx2.MockTransport(redirects)) - response = client.get( - "https://example.org/relative_redirect", follow_redirects=True - ) + response = client.get("https://example.org/relative_redirect", follow_redirects=True) assert response.status_code == httpx2.codes.OK assert response.url == "https://example.org/" assert len(response.history) == 1 @@ -193,9 +191,7 @@ def test_relative_redirect(): def test_malformed_redirect(): # https://github.com/encode/httpx/issues/771 client = httpx2.Client(transport=httpx2.MockTransport(redirects)) - response = client.get( - "http://example.org/malformed_redirect", follow_redirects=True - ) + response = client.get("http://example.org/malformed_redirect", follow_redirects=True) assert response.status_code == httpx2.codes.OK assert response.url == "https://example.org:443/" assert len(response.history) == 1 @@ -209,9 +205,7 @@ def test_invalid_redirect(): def test_no_scheme_redirect(): client = httpx2.Client(transport=httpx2.MockTransport(redirects)) - response = client.get( - "https://example.org/no_scheme_redirect", follow_redirects=True - ) + response = client.get("https://example.org/no_scheme_redirect", follow_redirects=True) assert response.status_code == httpx2.codes.OK assert response.url == "https://example.org/" assert len(response.history) == 1 @@ -219,9 +213,7 @@ def test_no_scheme_redirect(): def test_fragment_redirect(): client = httpx2.Client(transport=httpx2.MockTransport(redirects)) - response = client.get( - "https://example.org/relative_redirect#fragment", follow_redirects=True - ) + response = client.get("https://example.org/relative_redirect#fragment", follow_redirects=True) assert response.status_code == httpx2.codes.OK assert response.url == "https://example.org/#fragment" assert len(response.history) == 1 @@ -229,9 +221,7 @@ def test_fragment_redirect(): def test_multiple_redirects(): client = httpx2.Client(transport=httpx2.MockTransport(redirects)) - response = client.get( - "https://example.org/multiple_redirects?count=20", follow_redirects=True - ) + response = client.get("https://example.org/multiple_redirects?count=20", follow_redirects=True) assert response.status_code == httpx2.codes.OK assert response.url == "https://example.org/multiple_redirects" assert len(response.history) == 20 @@ -245,17 +235,13 @@ def test_multiple_redirects(): async def test_async_too_many_redirects(): async with httpx2.AsyncClient(transport=httpx2.MockTransport(redirects)) as client: with pytest.raises(httpx2.TooManyRedirects): - await client.get( - "https://example.org/multiple_redirects?count=21", follow_redirects=True - ) + await client.get("https://example.org/multiple_redirects?count=21", follow_redirects=True) def test_sync_too_many_redirects(): client = httpx2.Client(transport=httpx2.MockTransport(redirects)) with pytest.raises(httpx2.TooManyRedirects): - client.get( - "https://example.org/multiple_redirects?count=21", follow_redirects=True - ) + client.get("https://example.org/multiple_redirects?count=21", follow_redirects=True) def test_redirect_loop(): @@ -381,10 +367,7 @@ def cookie_sessions(request: httpx2.Request) -> httpx2.Response: status_code = httpx2.codes.SEE_OTHER headers = { "location": "/", - "set-cookie": ( - "session=eyJ1c2VybmFtZSI6ICJ0b21; path=/; Max-Age=1209600; " - "httponly; samesite=lax" - ), + "set-cookie": ("session=eyJ1c2VybmFtZSI6ICJ0b21; path=/; Max-Age=1209600; httponly; samesite=lax"), } return httpx2.Response(status_code, headers=headers) @@ -393,18 +376,13 @@ def cookie_sessions(request: httpx2.Request) -> httpx2.Response: status_code = httpx2.codes.SEE_OTHER headers = { "location": "/", - "set-cookie": ( - "session=null; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; " - "httponly; samesite=lax" - ), + "set-cookie": ("session=null; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly; samesite=lax"), } return httpx2.Response(status_code, headers=headers) def test_redirect_cookie_behavior(): - client = httpx2.Client( - transport=httpx2.MockTransport(cookie_sessions), follow_redirects=True - ) + client = httpx2.Client(transport=httpx2.MockTransport(cookie_sessions), follow_redirects=True) # The client is not logged in. response = client.get("https://example.com/") @@ -443,6 +421,4 @@ def test_redirect_custom_scheme(): async def test_async_invalid_redirect(): async with httpx2.AsyncClient(transport=httpx2.MockTransport(redirects)) as client: with pytest.raises(httpx2.RemoteProtocolError): - await client.get( - "http://example.org/invalid_redirect", follow_redirects=True - ) + await client.get("http://example.org/invalid_redirect", follow_redirects=True) diff --git a/tests/httpx2/conftest.py b/tests/httpx2/conftest.py index 159933b0..fe1a6756 100644 --- a/tests/httpx2/conftest.py +++ b/tests/httpx2/conftest.py @@ -50,9 +50,7 @@ def clean_environ(): Message = typing.Dict[str, typing.Any] Receive = typing.Callable[[], typing.Awaitable[Message]] -Send = typing.Callable[ - [typing.Dict[str, typing.Any]], typing.Coroutine[None, None, None] -] +Send = typing.Callable[[typing.Dict[str, typing.Any]], typing.Coroutine[None, None, None]] Scope = typing.Dict[str, typing.Any] @@ -161,10 +159,7 @@ async def echo_binary(scope: Scope, receive: Receive, send: Send) -> None: async def echo_headers(scope: Scope, receive: Receive, send: Send) -> None: - body = { - name.capitalize().decode(): value.decode() - for name, value in scope.get("headers", []) - } + body = {name.capitalize().decode(): value.decode() for name, value in scope.get("headers", [])} await send( { "type": "http.response.start", @@ -176,9 +171,7 @@ async def echo_headers(scope: Scope, receive: Receive, send: Send) -> None: async def redirect_301(scope: Scope, receive: Receive, send: Send) -> None: - await send( - {"type": "http.response.start", "status": 301, "headers": [[b"location", b"/"]]} - ) + await send({"type": "http.response.start", "status": 301, "headers": [[b"location", b"/"]]}) await send({"type": "http.response.body"}) @@ -207,9 +200,7 @@ def cert_private_key_file(localhost_cert): @pytest.fixture(scope="session") def cert_encrypted_private_key_file(localhost_cert): # Deserialize the private key and then reserialize with a password - private_key = load_pem_private_key( - localhost_cert.private_key_pem.bytes(), password=None, backend=default_backend() - ) + private_key = load_pem_private_key(localhost_cert.private_key_pem.bytes(), password=None, backend=default_backend()) encrypted_private_key_pem = trustme.Blob( private_key.private_bytes( Encoding.PEM, diff --git a/tests/httpx2/models/test_cookies.py b/tests/httpx2/models/test_cookies.py index 6fd9f7ae..0ca1e38f 100644 --- a/tests/httpx2/models/test_cookies.py +++ b/tests/httpx2/models/test_cookies.py @@ -57,8 +57,7 @@ def test_multiple_set_cookie(): headers = [ ( b"Set-Cookie", - b"1P_JAR=2020-08-09-18; expires=Tue, 08-Sep-2099 18:33:35 GMT; " - b"path=/; domain=.example.org; Secure", + b"1P_JAR=2020-08-09-18; expires=Tue, 08-Sep-2099 18:33:35 GMT; path=/; domain=.example.org; Secure", ), ( b"Set-Cookie", @@ -93,6 +92,5 @@ def test_cookies_repr(): cookies.set(name="fizz", value="buzz", domain="http://hello.com") assert repr(cookies) == ( - "," - " ]>" + ", ]>" ) diff --git a/tests/httpx2/models/test_headers.py b/tests/httpx2/models/test_headers.py index e1ea80d3..6d618cd4 100644 --- a/tests/httpx2/models/test_headers.py +++ b/tests/httpx2/models/test_headers.py @@ -108,9 +108,7 @@ def test_headers_list_repr(): Headers should display with a list repr if they include multiple identical keys. """ headers = httpx2.Headers([("custom", "example 1"), ("custom", "example 2")]) - assert ( - repr(headers) == "Headers([('custom', 'example 1'), ('custom', 'example 2')])" - ) + assert repr(headers) == "Headers([('custom', 'example 1'), ('custom', 'example 2')])" def test_headers_decode_ascii(): diff --git a/tests/httpx2/models/test_queryparams.py b/tests/httpx2/models/test_queryparams.py index 39a2e5db..2e2a37c7 100644 --- a/tests/httpx2/models/test_queryparams.py +++ b/tests/httpx2/models/test_queryparams.py @@ -31,19 +31,11 @@ def test_queryparams(source): assert dict(q) == {"a": "123", "b": "789"} assert str(q) == "a=123&a=456&b=789" assert repr(q) == "QueryParams('a=123&a=456&b=789')" - assert httpx2.QueryParams({"a": "123", "b": "456"}) == httpx2.QueryParams( - [("a", "123"), ("b", "456")] - ) - assert httpx2.QueryParams({"a": "123", "b": "456"}) == httpx2.QueryParams( - "a=123&b=456" - ) - assert httpx2.QueryParams({"a": "123", "b": "456"}) == httpx2.QueryParams( - {"b": "456", "a": "123"} - ) + assert httpx2.QueryParams({"a": "123", "b": "456"}) == httpx2.QueryParams([("a", "123"), ("b", "456")]) + assert httpx2.QueryParams({"a": "123", "b": "456"}) == httpx2.QueryParams("a=123&b=456") + assert httpx2.QueryParams({"a": "123", "b": "456"}) == httpx2.QueryParams({"b": "456", "a": "123"}) assert httpx2.QueryParams() == httpx2.QueryParams({}) - assert httpx2.QueryParams([("a", "123"), ("a", "456")]) == httpx2.QueryParams( - "a=123&a=456" - ) + assert httpx2.QueryParams([("a", "123"), ("a", "456")]) == httpx2.QueryParams("a=123&a=456") assert httpx2.QueryParams({"a": "123", "b": "456"}) != "invalid" q = httpx2.QueryParams([("a", "123"), ("a", "456")]) diff --git a/tests/httpx2/models/test_requests.py b/tests/httpx2/models/test_requests.py index d1e174e1..08be406a 100644 --- a/tests/httpx2/models/test_requests.py +++ b/tests/httpx2/models/test_requests.py @@ -43,9 +43,7 @@ def content() -> typing.Iterator[bytes]: yield b"test 123" # pragma: no cover headers = {"Content-Length": "8"} - request = httpx2.Request( - "POST", "http://example.org", content=content(), headers=headers - ) + request = httpx2.Request("POST", "http://example.org", content=content(), headers=headers) assert request.headers == {"Host": "example.org", "Content-Length": "8"} @@ -131,9 +129,7 @@ def streaming_body(data: bytes) -> typing.Iterator[bytes]: data = streaming_body(b"abcd") headers = {"Content-Length": "4"} - request = httpx2.Request( - "POST", "http://example.org", content=data, headers=headers - ) + request = httpx2.Request("POST", "http://example.org", content=data, headers=headers) assert "Transfer-Encoding" not in request.headers assert request.headers["Content-Length"] == "4" @@ -159,9 +155,7 @@ async def streaming_body(data: bytes) -> typing.AsyncIterator[bytes]: data = streaming_body(b"test 123") headers = {"Content-Length": "8"} - request = httpx2.Request( - "POST", "http://example.org", content=data, headers=headers - ) + request = httpx2.Request("POST", "http://example.org", content=data, headers=headers) assert request.headers["Content-Length"] == "8" @@ -236,9 +230,7 @@ def test_request_params(): request = httpx2.Request("GET", "http://example.com", params={}) assert str(request.url) == "http://example.com" - request = httpx2.Request( - "GET", "http://example.com?c=3", params={"a": "1", "b": "2"} - ) + request = httpx2.Request("GET", "http://example.com?c=3", params={"a": "1", "b": "2"}) assert str(request.url) == "http://example.com?a=1&b=2" request = httpx2.Request("GET", "http://example.com?a=1", params={}) diff --git a/tests/httpx2/models/test_responses.py b/tests/httpx2/models/test_responses.py index ef360b26..f294b5f7 100644 --- a/tests/httpx2/models/test_responses.py +++ b/tests/httpx2/models/test_responses.py @@ -237,9 +237,7 @@ def test_response_no_charset_with_iso_8859_1_content(): """ content = "Accented: Österreich abcdefghijklmnopqrstuzwxyz".encode("iso-8859-1") headers = {"Content-Type": "text/plain"} - response = httpx2.Response( - 200, content=content, headers=headers, default_encoding=autodetect - ) + response = httpx2.Response(200, content=content, headers=headers, default_encoding=autodetect) assert response.text == "Accented: Österreich abcdefghijklmnopqrstuzwxyz" assert response.charset_encoding is None @@ -251,9 +249,7 @@ def test_response_no_charset_with_cp_1252_content(): """ content = "Euro Currency: € abcdefghijklmnopqrstuzwxyz".encode("cp1252") headers = {"Content-Type": "text/plain"} - response = httpx2.Response( - 200, content=content, headers=headers, default_encoding=autodetect - ) + response = httpx2.Response(200, content=content, headers=headers, default_encoding=autodetect) assert response.text == "Euro Currency: € abcdefghijklmnopqrstuzwxyz" assert response.charset_encoding is None @@ -273,9 +269,7 @@ def test_response_non_text_encoding(): def test_response_set_explicit_encoding(): - headers = { - "Content-Type": "text-plain; charset=utf-8" - } # Deliberately incorrect charset + headers = {"Content-Type": "text-plain; charset=utf-8"} # Deliberately incorrect charset response = httpx2.Response( 200, content="Latin 1: ÿ".encode("latin-1"), diff --git a/tests/httpx2/models/test_url.py b/tests/httpx2/models/test_url.py index 79a73cea..90449cf0 100644 --- a/tests/httpx2/models/test_url.py +++ b/tests/httpx2/models/test_url.py @@ -32,9 +32,7 @@ def test_complete_url(): assert url.fragment == "anchor" assert str(url) == "https://example.org:123/path/to/somewhere?abc=123#anchor" - assert ( - repr(url) == "URL('https://example.org:123/path/to/somewhere?abc=123#anchor')" - ) + assert repr(url) == "URL('https://example.org:123/path/to/somewhere?abc=123#anchor')" def test_url_with_empty_query(): @@ -156,9 +154,7 @@ def test_url_params(): assert str(url) == "https://example.org:123/path/to/somewhere?a=123" assert url.params == httpx2.QueryParams({"a": "123"}) - url = httpx2.URL( - "https://example.org:123/path/to/somewhere?b=456", params={"a": "123"} - ) + url = httpx2.URL("https://example.org:123/path/to/somewhere?b=456", params={"a": "123"}) assert str(url) == "https://example.org:123/path/to/somewhere?a=123" assert url.params == httpx2.QueryParams({"a": "123"}) @@ -363,18 +359,13 @@ def test_url_excessively_long_component(): def test_url_non_printing_character_in_url(): with pytest.raises(httpx2.InvalidURL) as exc: httpx2.URL("https://www.example.com/\n") - assert str(exc.value) == ( - "Invalid non-printable ASCII character in URL, '\\n' at position 24." - ) + assert str(exc.value) == ("Invalid non-printable ASCII character in URL, '\\n' at position 24.") def test_url_non_printing_character_in_component(): with pytest.raises(httpx2.InvalidURL) as exc: httpx2.URL("https://www.example.com", path="/\n") - assert str(exc.value) == ( - "Invalid non-printable ASCII character in URL path component, " - "'\\n' at position 1." - ) + assert str(exc.value) == ("Invalid non-printable ASCII character in URL path component, '\\n' at position 1.") # Test for url components @@ -482,12 +473,8 @@ def test_url_join(): """ url = httpx2.URL("https://example.org:123/path/to/somewhere") assert url.join("/somewhere-else") == "https://example.org:123/somewhere-else" - assert ( - url.join("somewhere-else") == "https://example.org:123/path/to/somewhere-else" - ) - assert ( - url.join("../somewhere-else") == "https://example.org:123/path/somewhere-else" - ) + assert url.join("somewhere-else") == "https://example.org:123/path/to/somewhere-else" + assert url.join("../somewhere-else") == "https://example.org:123/path/somewhere-else" assert url.join("../../somewhere-else") == "https://example.org:123/somewhere-else" diff --git a/tests/httpx2/models/test_whatwg.py b/tests/httpx2/models/test_whatwg.py index 9157b8c6..d48486a8 100644 --- a/tests/httpx2/models/test_whatwg.py +++ b/tests/httpx2/models/test_whatwg.py @@ -12,11 +12,7 @@ # https://github.com/web-platform-tests/wpt/blob/master/url/resources/urltestdata.json with open("tests/httpx2/models/whatwg.json", "r", encoding="utf-8") as input: test_cases = json.load(input) - test_cases = [ - item - for item in test_cases - if not isinstance(item, str) and not item.get("failure") - ] + test_cases = [item for item in test_cases if not isinstance(item, str) and not item.get("failure")] @pytest.mark.parametrize("test_case", test_cases) diff --git a/tests/httpx2/test_asgi.py b/tests/httpx2/test_asgi.py index 813e6103..6cde80fe 100644 --- a/tests/httpx2/test_asgi.py +++ b/tests/httpx2/test_asgi.py @@ -47,9 +47,7 @@ async def echo_body(scope, receive, send): async def echo_headers(scope, receive, send): status = 200 - output = json.dumps( - {"headers": [[k.decode(), v.decode()] for k, v in scope["headers"]]} - ).encode("utf-8") + output = json.dumps({"headers": [[k.decode(), v.decode()] for k, v in scope["headers"]]}).encode("utf-8") headers = [(b"content-type", "text/plain"), (b"content-length", str(len(output)))] await send({"type": "http.response.start", "status": status, "headers": headers}) @@ -190,9 +188,7 @@ async def read_body(scope, receive, send): status = 200 headers = [(b"content-type", "text/plain")] - await send( - {"type": "http.response.start", "status": status, "headers": headers} - ) + await send({"type": "http.response.start", "status": status, "headers": headers}) more_body = True while more_body: message = await receive() diff --git a/tests/httpx2/test_auth.py b/tests/httpx2/test_auth.py index 1fe200b4..127eb83c 100644 --- a/tests/httpx2/test_auth.py +++ b/tests/httpx2/test_auth.py @@ -51,12 +51,8 @@ def test_digest_auth_with_401(): assert "Authorization" not in request.headers # If a 401 response is returned, then a digest auth request is made. - headers = { - "WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."' - } - response = httpx2.Response( - content=b"Auth required", status_code=401, headers=headers, request=request - ) + headers = {"WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."'} + response = httpx2.Response(content=b"Auth required", status_code=401, headers=headers, request=request) request = flow.send(response) assert request.headers["Authorization"].startswith("Digest") @@ -76,12 +72,8 @@ def test_digest_auth_with_401_nonce_counting(): assert "Authorization" not in request.headers # If a 401 response is returned, then a digest auth request is made. - headers = { - "WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."' - } - response = httpx2.Response( - content=b"Auth required", status_code=401, headers=headers, request=request - ) + headers = {"WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."'} + response = httpx2.Response(content=b"Auth required", status_code=401, headers=headers, request=request) first_request = flow.send(response) assert first_request.headers["Authorization"].startswith("Digest") @@ -93,9 +85,7 @@ def test_digest_auth_with_401_nonce_counting(): # ... and the client nonce count (nc) is increased first_nc = parse_keqv_list(first_request.headers["Authorization"].split(", "))["nc"] - second_nc = parse_keqv_list(second_request.headers["Authorization"].split(", "))[ - "nc" - ] + second_nc = parse_keqv_list(second_request.headers["Authorization"].split(", "))["nc"] assert int(first_nc, 16) + 1 == int(second_nc, 16) # No other requests are made. @@ -110,9 +100,7 @@ def set_cookies(request: httpx2.Request) -> httpx2.Response: "WWW-Authenticate": 'Digest realm="...", qop="auth", nonce="...", opaque="..."', } if request.url.path == "/auth": - return httpx2.Response( - content=b"Auth required", status_code=401, headers=headers - ) + return httpx2.Response(content=b"Auth required", status_code=401, headers=headers) else: raise NotImplementedError() # pragma: no cover @@ -136,9 +124,7 @@ def test_digest_auth_setting_cookie_in_request(): assert request.headers["Cookie"] == "session=.session_value..." # No other requests are made. - response = httpx2.Response( - content=b"Hello, world!", status_code=200, request=request - ) + response = httpx2.Response(content=b"Hello, world!", status_code=200, request=request) with pytest.raises(StopIteration): flow.send(response) @@ -163,24 +149,15 @@ def test_digest_auth_rfc_2069(): 'opaque="5ccc069c403ebaf9f0171e9517f40e41"' ) } - response = httpx2.Response( - content=b"Auth required", status_code=401, headers=headers, request=request - ) + response = httpx2.Response(content=b"Auth required", status_code=401, headers=headers, request=request) request = flow.send(response) assert request.headers["Authorization"].startswith("Digest") assert 'username="Mufasa"' in request.headers["Authorization"] assert 'realm="testrealm@host.com"' in request.headers["Authorization"] - assert ( - 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"' in request.headers["Authorization"] - ) + assert 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"' in request.headers["Authorization"] assert 'uri="/dir/index.html"' in request.headers["Authorization"] - assert ( - 'opaque="5ccc069c403ebaf9f0171e9517f40e41"' in request.headers["Authorization"] - ) - assert ( - 'response="1949323746fe6a43ef61f9606e7febea"' - in request.headers["Authorization"] - ) + assert 'opaque="5ccc069c403ebaf9f0171e9517f40e41"' in request.headers["Authorization"] + assert 'response="1949323746fe6a43ef61f9606e7febea"' in request.headers["Authorization"] # No other requests are made. response = httpx2.Response(content=b"Hello, world!", status_code=200) @@ -214,33 +191,19 @@ def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes: 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' ) } - response = httpx2.Response( - content=b"Auth required", status_code=401, headers=headers, request=request - ) + response = httpx2.Response(content=b"Auth required", status_code=401, headers=headers, request=request) request = flow.send(response) assert request.headers["Authorization"].startswith("Digest") assert 'username="Mufasa"' in request.headers["Authorization"] assert 'realm="http-auth@example.org"' in request.headers["Authorization"] assert 'uri="/dir/index.html"' in request.headers["Authorization"] assert "algorithm=MD5" in request.headers["Authorization"] - assert ( - 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"' - in request.headers["Authorization"] - ) + assert 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"' in request.headers["Authorization"] assert "nc=00000001" in request.headers["Authorization"] - assert ( - 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' - in request.headers["Authorization"] - ) + assert 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' in request.headers["Authorization"] assert "qop=auth" in request.headers["Authorization"] - assert ( - 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' - in request.headers["Authorization"] - ) - assert ( - 'response="8ca523f5e9506fed4657c9700eebdbec"' - in request.headers["Authorization"] - ) + assert 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' in request.headers["Authorization"] + assert 'response="8ca523f5e9506fed4657c9700eebdbec"' in request.headers["Authorization"] # No other requests are made. response = httpx2.Response(content=b"Hello, world!", status_code=200) @@ -274,29 +237,18 @@ def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes: 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' ) } - response = httpx2.Response( - content=b"Auth required", status_code=401, headers=headers, request=request - ) + response = httpx2.Response(content=b"Auth required", status_code=401, headers=headers, request=request) request = flow.send(response) assert request.headers["Authorization"].startswith("Digest") assert 'username="Mufasa"' in request.headers["Authorization"] assert 'realm="http-auth@example.org"' in request.headers["Authorization"] assert 'uri="/dir/index.html"' in request.headers["Authorization"] assert "algorithm=SHA-256" in request.headers["Authorization"] - assert ( - 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"' - in request.headers["Authorization"] - ) + assert 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"' in request.headers["Authorization"] assert "nc=00000001" in request.headers["Authorization"] - assert ( - 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' - in request.headers["Authorization"] - ) + assert 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' in request.headers["Authorization"] assert "qop=auth" in request.headers["Authorization"] - assert ( - 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' - in request.headers["Authorization"] - ) + assert 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' in request.headers["Authorization"] assert ( 'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1"' in request.headers["Authorization"] diff --git a/tests/httpx2/test_config.py b/tests/httpx2/test_config.py index ec92755e..fb69545a 100644 --- a/tests/httpx2/test_config.py +++ b/tests/httpx2/test_config.py @@ -49,23 +49,17 @@ def test_load_ssl_config_cert_and_key(cert_pem_file, cert_private_key_file): @pytest.mark.parametrize("password", [b"password", "password"]) -def test_load_ssl_config_cert_and_encrypted_key( - cert_pem_file, cert_encrypted_private_key_file, password -): +def test_load_ssl_config_cert_and_encrypted_key(cert_pem_file, cert_encrypted_private_key_file, password): context = httpx2.create_ssl_context() context.load_cert_chain(cert_pem_file, cert_encrypted_private_key_file, password) assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED assert context.check_hostname is True -def test_load_ssl_config_cert_and_key_invalid_password( - cert_pem_file, cert_encrypted_private_key_file -): +def test_load_ssl_config_cert_and_key_invalid_password(cert_pem_file, cert_encrypted_private_key_file): with pytest.raises(ssl.SSLError): context = httpx2.create_ssl_context() - context.load_cert_chain( - cert_pem_file, cert_encrypted_private_key_file, "password1" - ) + context.load_cert_chain(cert_pem_file, cert_encrypted_private_key_file, "password1") def test_load_ssl_config_cert_without_key_raises(cert_pem_file): @@ -89,10 +83,7 @@ def test_SSLContext_with_get_request(server, cert_pem_file): def test_limits_repr(): limits = httpx2.Limits(max_connections=100) - expected = ( - "Limits(max_connections=100, max_keepalive_connections=None," - " keepalive_expiry=5.0)" - ) + expected = "Limits(max_connections=100, max_keepalive_connections=None, keepalive_expiry=5.0)" assert repr(limits) == expected diff --git a/tests/httpx2/test_content.py b/tests/httpx2/test_content.py index b62abbfa..77a3b7f5 100644 --- a/tests/httpx2/test_content.py +++ b/tests/httpx2/test_content.py @@ -489,9 +489,7 @@ def test_response_invalid_argument(): def test_ensure_ascii_false_with_french_characters(): data = {"greeting": "Bonjour, ça va ?"} response = httpx2.Response(200, json=data) - assert "ça va" in response.text, ( - "ensure_ascii=False should preserve French accented characters" - ) + assert "ça va" in response.text, "ensure_ascii=False should preserve French accented characters" assert response.headers["Content-Type"] == "application/json" @@ -508,11 +506,7 @@ def test_allow_nan_false(): data_with_nan = {"nombre": float("nan")} data_with_inf = {"nombre": float("inf")} - with pytest.raises( - ValueError, match="Out of range float values are not JSON compliant" - ): + with pytest.raises(ValueError, match="Out of range float values are not JSON compliant"): httpx2.Response(200, json=data_with_nan) - with pytest.raises( - ValueError, match="Out of range float values are not JSON compliant" - ): + with pytest.raises(ValueError, match="Out of range float values are not JSON compliant"): httpx2.Response(200, json=data_with_inf) diff --git a/tests/httpx2/test_decoders.py b/tests/httpx2/test_decoders.py index 8c360b40..78208fde 100644 --- a/tests/httpx2/test_decoders.py +++ b/tests/httpx2/test_decoders.py @@ -153,9 +153,7 @@ def test_multi(): compressed_body = deflate_compressor.compress(body) + deflate_compressor.flush() gzip_compressor = zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) - compressed_body = ( - gzip_compressor.compress(compressed_body) + gzip_compressor.flush() - ) + compressed_body = gzip_compressor.compress(compressed_body) + gzip_compressor.flush() headers = [(b"Content-Encoding", b"deflate, gzip")] response = httpx2.Response( @@ -298,9 +296,7 @@ def test_text_decoder_empty_cases(): ["data", "expected"], [((b"Hello,", b" world!"), ["Hello,", " world!"])], ) -def test_streaming_text_decoder( - data: typing.Iterable[bytes], expected: list[str] -) -> None: +def test_streaming_text_decoder(data: typing.Iterable[bytes], expected: list[str]) -> None: response = httpx2.Response(200, content=iter(data)) assert list(response.iter_text()) == expected @@ -313,9 +309,7 @@ def test_line_decoder_nl(): assert list(response.iter_lines()) == ["a", "", "b", "c"] # Issue #1033 - response = httpx2.Response( - 200, content=[b"", b"12345\n", b"foo ", b"bar ", b"baz\n"] - ) + response = httpx2.Response(200, content=[b"", b"12345\n", b"foo ", b"bar ", b"baz\n"]) assert list(response.iter_lines()) == ["12345", "foo bar baz"] @@ -327,9 +321,7 @@ def test_line_decoder_cr(): assert list(response.iter_lines()) == ["a", "", "b", "c"] # Issue #1033 - response = httpx2.Response( - 200, content=[b"", b"12345\r", b"foo ", b"bar ", b"baz\r"] - ) + response = httpx2.Response(200, content=[b"", b"12345\r", b"foo ", b"bar ", b"baz\r"]) assert list(response.iter_lines()) == ["12345", "foo bar baz"] diff --git a/tests/httpx2/test_exceptions.py b/tests/httpx2/test_exceptions.py index 85a01feb..a02824a7 100644 --- a/tests/httpx2/test_exceptions.py +++ b/tests/httpx2/test_exceptions.py @@ -19,15 +19,11 @@ def test_httpcore_all_exceptions_mapped() -> None: expected_mapped_httpcore_exceptions = { value.__name__ for _, value in vars(httpcore2).items() - if isinstance(value, type) - and issubclass(value, Exception) - and value is not httpcore2.ConnectionNotAvailable + if isinstance(value, type) and issubclass(value, Exception) and value is not httpcore2.ConnectionNotAvailable } httpx_exceptions = { - value.__name__ - for _, value in vars(httpx2).items() - if isinstance(value, type) and issubclass(value, Exception) + value.__name__ for _, value in vars(httpx2).items() if isinstance(value, type) and issubclass(value, Exception) } unmapped_exceptions = expected_mapped_httpcore_exceptions - httpx_exceptions diff --git a/tests/httpx2/test_exported_members.py b/tests/httpx2/test_exported_members.py index 4628ce00..afa4d8e0 100644 --- a/tests/httpx2/test_exported_members.py +++ b/tests/httpx2/test_exported_members.py @@ -4,10 +4,6 @@ def test_all_imports_are_exported() -> None: included_private_members = ["__description__", "__title__", "__version__"] assert httpx2.__all__ == sorted( - ( - member - for member in vars(httpx2).keys() - if not member.startswith("_") or member in included_private_members - ), + (member for member in vars(httpx2).keys() if not member.startswith("_") or member in included_private_members), key=str.casefold, ) diff --git a/tests/httpx2/test_multipart.py b/tests/httpx2/test_multipart.py index 752e4f20..b58eac90 100644 --- a/tests/httpx2/test_multipart.py +++ b/tests/httpx2/test_multipart.py @@ -283,9 +283,9 @@ def test_multipart_encode_files_allows_filenames_as_none() -> None: "Content-Length": str(len(request.content)), } assert request.content == ( - '--BOUNDARY\r\nContent-Disposition: form-data; name="file"\r\n\r\n' - "\r\n--BOUNDARY--\r\n" - "".encode("ascii") + '--BOUNDARY\r\nContent-Disposition: form-data; name="file"\r\n\r\n\r\n--BOUNDARY--\r\n'.encode( + "ascii" + ) ) @@ -297,9 +297,7 @@ def test_multipart_encode_files_allows_filenames_as_none() -> None: ("no-extension", "application/octet-stream"), ], ) -def test_multipart_encode_files_guesses_correct_content_type( - file_name: str, expected_content_type: str -) -> None: +def test_multipart_encode_files_guesses_correct_content_type(file_name: str, expected_content_type: str) -> None: url = "https://www.example.com/" headers = {"Content-Type": "multipart/form-data; boundary=BOUNDARY"} files = {"file": (file_name, io.BytesIO(b""))} diff --git a/tests/httpx2/test_timeouts.py b/tests/httpx2/test_timeouts.py index d4e024cc..1bb02bc1 100644 --- a/tests/httpx2/test_timeouts.py +++ b/tests/httpx2/test_timeouts.py @@ -50,6 +50,4 @@ async def test_async_client_new_request_send_timeout(server): async with httpx2.AsyncClient(timeout=timeout) as client: with pytest.raises(httpx2.TimeoutException): - await client.send( - httpx2.Request("GET", server.url.copy_with(path="/slow_response")) - ) + await client.send(httpx2.Request("GET", server.url.copy_with(path="/slow_response"))) diff --git a/tests/httpx2/test_utils.py b/tests/httpx2/test_utils.py index 9870a0bb..f4bbd1fa 100644 --- a/tests/httpx2/test_utils.py +++ b/tests/httpx2/test_utils.py @@ -75,8 +75,7 @@ def test_logging_redirect_chain(server, caplog): ( "httpx2", logging.INFO, - "HTTP Request: GET http://127.0.0.1:8000/redirect_301" - ' "HTTP/1.1 301 Moved Permanently"', + 'HTTP Request: GET http://127.0.0.1:8000/redirect_301 "HTTP/1.1 301 Moved Permanently"', ), ( "httpx2", diff --git a/tests/httpx2/test_wsgi.py b/tests/httpx2/test_wsgi.py index 68be111e..52c15384 100644 --- a/tests/httpx2/test_wsgi.py +++ b/tests/httpx2/test_wsgi.py @@ -30,9 +30,7 @@ def application(environ, start_response): return wsgiref.validate.validator(application) -def echo_body( - environ: WSGIEnvironment, start_response: StartResponse -) -> typing.Iterable[bytes]: +def echo_body(environ: WSGIEnvironment, start_response: StartResponse) -> typing.Iterable[bytes]: status = "200 OK" output = environ["wsgi.input"].read() @@ -45,9 +43,7 @@ def echo_body( return [output] -def echo_body_with_response_stream( - environ: WSGIEnvironment, start_response: StartResponse -) -> typing.Iterable[bytes]: +def echo_body_with_response_stream(environ: WSGIEnvironment, start_response: StartResponse) -> typing.Iterable[bytes]: status = "200 OK" response_headers = [("Content-Type", "text/plain")]