diff --git a/CHANGELOG.md b/CHANGELOG.md
index b47a18bbc..d2abd040e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## unreleased
+
+- The networking backend interface has [been added to the public API](https://www.encode.io/httpcore/network-backends). Some classes which were previously private implementation detail are now part of the top-level public API. (#699)
+
## 0.17.2 (May 23th, 2023)
- Add `socket_options` argument to `ConnectionPool` and `HTTProxy` classes. (#668)
diff --git a/docs/extensions.md b/docs/extensions.md
index 231e73f9c..3ac3d0829 100644
--- a/docs/extensions.md
+++ b/docs/extensions.md
@@ -200,6 +200,8 @@ The interface provided by the network stream:
This API can be used as the foundation for working with HTTP proxies, WebSocket upgrades, and other advanced use-cases.
+See the [network backends documentation](network-backends.md) for more information on working directly with network streams.
+
##### `CONNECT` requests
A proxy CONNECT request using the network stream:
diff --git a/docs/network-backends.md b/docs/network-backends.md
index b3783e78d..fbb6bfdbf 100644
--- a/docs/network-backends.md
+++ b/docs/network-backends.md
@@ -1,3 +1,279 @@
# Network Backends
-TODO
+The API layer at which `httpcore` interacts with the network is described as the network backend. Various backend implementations are provided, allowing `httpcore` to handle networking in different runtime contexts.
+
+## Working with network backends
+
+### The default network backend
+
+Typically you won't need to specify a network backend, as a default will automatically be selected. However, understanding how the network backends fit in may be useful if you want to better understand the underlying architecture. Let's start by seeing how we can explicitly select the network backend.
+
+First we're making a standard HTTP request, using a connection pool:
+
+```python
+import httpcore
+
+with httpcore.ConnectionPool() as http:
+ response = http.request('GET', 'https://www.example.com')
+ print(response)
+```
+
+We can also have the same behavior, but be explicit with our selection of the network backend:
+
+```python
+import httpcore
+
+network_backend = httpcore.SyncBackend()
+with httpcore.ConnectionPool(network_backend=network_backend) as http:
+ response = http.request('GET', 'https://www.example.com')
+ print(response)
+```
+
+The `httpcore.SyncBackend()` implementation handles the opening of TCP connections, and operations on the socket stream, such as reading, writing, and closing the connection.
+
+We can get a better understanding of this by using a network backend to send a basic HTTP/1.1 request directly:
+
+```python
+import httpcore
+
+# Create an SSL context using 'certifi' for the certificates.
+ssl_context = httpcore.default_ssl_context()
+
+# A basic HTTP/1.1 request as a plain bytestring.
+request = b'\r\n'.join([
+ b'GET / HTTP/1.1',
+ b'Host: www.example.com',
+ b'Accept: */*',
+ b'Connection: close',
+ b''
+])
+
+# Open a TCP stream and upgrade it to SSL.
+network_backend = httpcore.SyncBackend()
+network_stream = network_backend.connect_tcp("www.example.com", 443)
+network_stream = network_stream.start_tls(ssl_context, server_hostname="www.example.com")
+
+# Send the HTTP request.
+network_stream.write(request)
+
+# Read the HTTP response.
+while True:
+ response = network_stream.read(max_bytes=4096)
+ if response == b'':
+ break
+ print(response)
+
+# The output should look something like this:
+#
+# b'HTTP/1.1 200 OK\r\nAge: 600005\r\n [...] Content-Length: 1256\r\nConnection: close\r\n\r\n'
+# b'\n\n
\n Example Domain [...] \n'
+```
+
+### Async network backends
+
+If we're working with an `async` codebase, then we need to select a different backend.
+
+The `httpcore.AnyIOBackend` is suitable for usage if you're running under `asyncio`. This is a networking backend implemented using [the `anyio` package](https://anyio.readthedocs.io/en/3.x/).
+
+```python
+import httpcore
+import asyncio
+
+async def main():
+ network_backend = httpcore.AnyIOBackend()
+ async with httpcore.AsyncConnectionPool(network_backend=network_backend) as http:
+ response = await http.request('GET', 'https://www.example.com')
+ print(response)
+
+asyncio.run(main())
+```
+
+The `AnyIOBackend` will work when running under either `asyncio` or `trio`. However, if you're working with async using the [`trio` framework](https://trio.readthedocs.io/en/stable/), then we recommend using the `httpcore.TrioBackend`.
+
+This will give you the same kind of networking behavior you'd have using `AnyIOBackend`, but there will be a little less indirection so it will be marginally more efficient and will present cleaner tracebacks in error cases.
+
+```python
+import httpcore
+import trio
+
+async def main():
+ network_backend = httpcore.TrioBackend()
+ async with httpcore.AsyncConnectionPool(network_backend=network_backend) as http:
+ response = await http.request('GET', 'https://www.example.com')
+ print(response)
+
+trio.run(main)
+```
+
+### Mock network backends
+
+There are also mock network backends available that can be useful for testing purposes.
+These backends accept a list of bytes, and return network stream interfaces that return those byte streams.
+
+Here's an example of mocking a simple HTTP/1.1 response...
+
+```python
+import httpcore
+
+network_backend = httpcore.MockBackend([
+ b"HTTP/1.1 200 OK\r\n",
+ b"Content-Type: plain/text\r\n",
+ b"Content-Length: 13\r\n",
+ b"\r\n",
+ b"Hello, world!",
+])
+with httpcore.ConnectionPool(network_backend=network_backend) as http:
+ response = http.request("GET", "https://example.com/")
+ print(response.extensions['http_version'])
+ print(response.status)
+ print(response.content)
+```
+
+Mocking a HTTP/2 response is more complex, since it uses a binary format...
+
+```python
+import hpack
+import hyperframe.frame
+import httpcore
+
+content = [
+ hyperframe.frame.SettingsFrame().serialize(),
+ hyperframe.frame.HeadersFrame(
+ stream_id=1,
+ data=hpack.Encoder().encode(
+ [
+ (b":status", b"200"),
+ (b"content-type", b"plain/text"),
+ ]
+ ),
+ flags=["END_HEADERS"],
+ ).serialize(),
+ hyperframe.frame.DataFrame(
+ stream_id=1, data=b"Hello, world!", flags=["END_STREAM"]
+ ).serialize(),
+]
+# Note that we instantiate the mock backend with an `http2=True` argument.
+# This ensures that the mock network stream acts as if the `h2` ALPN flag has been set,
+# and causes the connection pool to interact with the connection using HTTP/2.
+network_backend = httpcore.MockBackend(content, http2=True)
+with httpcore.ConnectionPool(network_backend=network_backend) as http:
+ response = http.request("GET", "https://example.com/")
+ print(response.extensions['http_version'])
+ print(response.status)
+ print(response.content)
+```
+
+### Custom network backends
+
+The base interface for network backends is provided as public API, allowing you to implement custom networking behavior.
+
+You can use this to provide advanced networking functionality such as:
+
+* Network recording / replay.
+* In-depth debug tooling.
+* Handling non-standard SSL or DNS requirements.
+
+Here's an example that records the network response to a file on disk:
+
+```python
+import httpcore
+
+
+class RecordingNetworkStream(httpcore.NetworkStream):
+ def __init__(self, record_file, stream):
+ self.record_file = record_file
+ self.stream = stream
+
+ def read(self, max_bytes, timeout=None):
+ data = self.stream.read(max_bytes, timeout=timeout)
+ self.record_file.write(data)
+ return data
+
+ def write(self, buffer, timeout=None):
+ self.stream.write(buffer, timeout=timeout)
+
+ def close(self) -> None:
+ self.stream.close()
+
+ def start_tls(
+ self,
+ ssl_context,
+ server_hostname=None,
+ timeout=None,
+ ):
+ self.stream = self.stream.start_tls(
+ ssl_context, server_hostname=server_hostname, timeout=timeout
+ )
+ return self
+
+ def get_extra_info(self, info):
+ return self.stream.get_extra_info(info)
+
+
+class RecordingNetworkBackend(httpcore.NetworkBackend):
+ """
+ A custom network backend that records network responses.
+ """
+ def __init__(self, record_file):
+ self.record_file = record_file
+ self.backend = httpcore.SyncBackend()
+
+ def connect_tcp(
+ self,
+ host,
+ port,
+ timeout=None,
+ local_address=None,
+ socket_options=None,
+ ):
+ # Note that we're only using a single record file here,
+ # so even if multiple connections are opened the network
+ # traffic will all write to the same file.
+
+ # An alternative implementation might automatically use
+ # a new file for each opened connection.
+ stream = self.backend.connect_tcp(
+ host,
+ port,
+ timeout=timeout,
+ local_address=local_address,
+ socket_options=socket_options
+ )
+ return RecordingNetworkStream(self.record_file, stream)
+
+
+# Once you make the request, the raw HTTP/1.1 response will be available
+# in the 'network-recording' file.
+#
+# Try switching to `http2=True` to see the difference when recording HTTP/2 binary network traffic,
+# or add `headers={'Accept-Encoding': 'gzip'}` to see HTTP content compression.
+with open("network-recording", "wb") as record_file:
+ network_backend = RecordingNetworkBackend(record_file)
+ with httpcore.ConnectionPool(network_backend=network_backend) as http:
+ response = http.request("GET", "https://www.example.com/")
+ print(response)
+```
+
+---
+
+## Reference
+
+### Networking Backends
+
+* `httpcore.SyncBackend`
+* `httpcore.AnyIOBackend`
+* `httpcore.TrioBackend`
+
+### Mock Backends
+
+* `httpcore.MockBackend`
+* `httpcore.MockStream`
+* `httpcore.AsyncMockBackend`
+* `httpcore.AsyncMockStream`
+
+### Base Interface
+
+* `httpcore.NetworkBackend`
+* `httpcore.NetworkStream`
+* `httpcore.AsyncNetworkBackend`
+* `httpcore.AsyncNetworkStream`
diff --git a/httpcore/__init__.py b/httpcore/__init__.py
index 0477ec3ed..d3e57ad01 100644
--- a/httpcore/__init__.py
+++ b/httpcore/__init__.py
@@ -8,6 +8,15 @@
AsyncHTTPProxy,
AsyncSOCKSProxy,
)
+from ._backends.base import (
+ SOCKET_OPTION,
+ AsyncNetworkBackend,
+ AsyncNetworkStream,
+ NetworkBackend,
+ NetworkStream,
+)
+from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream
+from ._backends.sync import SyncBackend
from ._exceptions import (
ConnectError,
ConnectionNotAvailable,
@@ -37,6 +46,30 @@
SOCKSProxy,
)
+# The 'httpcore.AnyIOBackend' class is conditional on 'anyio' being installed.
+try:
+ from ._backends.anyio import AnyIOBackend
+except ImportError: # pragma: nocover
+
+ class AnyIOBackend: # type: ignore
+ def __init__(self, *args, **kwargs): # type: ignore
+ msg = (
+ "Attempted to use 'httpcore.AnyIOBackend' but 'anyio' is not installed."
+ )
+ raise RuntimeError(msg)
+
+
+# The 'httpcore.TrioBackend' class is conditional on 'trio' being installed.
+try:
+ from ._backends.trio import TrioBackend
+except ImportError: # pragma: nocover
+
+ class TrioBackend: # type: ignore
+ def __init__(self, *args, **kwargs): # type: ignore
+ msg = "Attempted to use 'httpcore.TrioBackend' but 'trio' is not installed."
+ raise RuntimeError(msg)
+
+
__all__ = [
# top-level requests
"request",
@@ -62,8 +95,23 @@
"HTTP2Connection",
"ConnectionInterface",
"SOCKSProxy",
+ # network backends, implementations
+ "SyncBackend",
+ "AnyIOBackend",
+ "TrioBackend",
+ # network backends, mock implementations
+ "AsyncMockBackend",
+ "AsyncMockStream",
+ "MockBackend",
+ "MockStream",
+ # network backends, interface
+ "AsyncNetworkStream",
+ "AsyncNetworkBackend",
+ "NetworkStream",
+ "NetworkBackend",
# util
"default_ssl_context",
+ "SOCKET_OPTION",
# exceptions
"ConnectionNotAvailable",
"ProxyError",
diff --git a/httpcore/_async/connection.py b/httpcore/_async/connection.py
index ecf2546b9..9014ab957 100644
--- a/httpcore/_async/connection.py
+++ b/httpcore/_async/connection.py
@@ -4,13 +4,13 @@
from types import TracebackType
from typing import Iterable, Iterator, Optional, Type
+from .._backends.auto import AutoBackend
+from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
from .._exceptions import ConnectError, ConnectionNotAvailable, ConnectTimeout
from .._models import Origin, Request, Response
from .._ssl import default_ssl_context
from .._synchronization import AsyncLock
from .._trace import Trace
-from ..backends.auto import AutoBackend
-from ..backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
from .http11 import AsyncHTTP11Connection
from .interfaces import AsyncConnectionInterface
diff --git a/httpcore/_async/connection_pool.py b/httpcore/_async/connection_pool.py
index ed72e9fdb..7c2e457b2 100644
--- a/httpcore/_async/connection_pool.py
+++ b/httpcore/_async/connection_pool.py
@@ -4,12 +4,12 @@
from types import TracebackType
from typing import AsyncIterable, AsyncIterator, Iterable, List, Optional, Type
+from .._backends.auto import AutoBackend
+from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
from .._models import Origin, Request, Response
from .._synchronization import AsyncEvent, AsyncLock
from .._trace import Trace
-from ..backends.auto import AutoBackend
-from ..backends.base import SOCKET_OPTION, AsyncNetworkBackend
from .connection import AsyncHTTPConnection
from .interfaces import AsyncConnectionInterface, AsyncRequestInterface
diff --git a/httpcore/_async/http11.py b/httpcore/_async/http11.py
index 99b356f49..3ef6c80fe 100644
--- a/httpcore/_async/http11.py
+++ b/httpcore/_async/http11.py
@@ -15,6 +15,7 @@
import h11
+from .._backends.base import AsyncNetworkStream
from .._exceptions import (
ConnectionNotAvailable,
LocalProtocolError,
@@ -24,7 +25,6 @@
from .._models import Origin, Request, Response
from .._synchronization import AsyncLock
from .._trace import Trace
-from ..backends.base import AsyncNetworkStream
from .interfaces import AsyncConnectionInterface
logger = logging.getLogger("httpcore.http11")
diff --git a/httpcore/_async/http2.py b/httpcore/_async/http2.py
index 131d932bc..7eb3680bd 100644
--- a/httpcore/_async/http2.py
+++ b/httpcore/_async/http2.py
@@ -10,6 +10,7 @@
import h2.exceptions
import h2.settings
+from .._backends.base import AsyncNetworkStream
from .._exceptions import (
ConnectionNotAvailable,
LocalProtocolError,
@@ -18,7 +19,6 @@
from .._models import Origin, Request, Response
from .._synchronization import AsyncLock, AsyncSemaphore
from .._trace import Trace
-from ..backends.base import AsyncNetworkStream
from .interfaces import AsyncConnectionInterface
logger = logging.getLogger("httpcore.http2")
diff --git a/httpcore/_async/http_proxy.py b/httpcore/_async/http_proxy.py
index 3dd1cb4fe..62f510978 100644
--- a/httpcore/_async/http_proxy.py
+++ b/httpcore/_async/http_proxy.py
@@ -3,6 +3,7 @@
from base64 import b64encode
from typing import Iterable, List, Mapping, Optional, Sequence, Tuple, Union
+from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
from .._exceptions import ProxyError
from .._models import (
URL,
@@ -16,7 +17,6 @@
from .._ssl import default_ssl_context
from .._synchronization import AsyncLock
from .._trace import Trace
-from ..backends.base import SOCKET_OPTION, AsyncNetworkBackend
from .connection import AsyncHTTPConnection
from .connection_pool import AsyncConnectionPool
from .http11 import AsyncHTTP11Connection
diff --git a/httpcore/_async/socks_proxy.py b/httpcore/_async/socks_proxy.py
index b3d7b7620..f12cb373f 100644
--- a/httpcore/_async/socks_proxy.py
+++ b/httpcore/_async/socks_proxy.py
@@ -4,13 +4,13 @@
from socksio import socks5
+from .._backends.auto import AutoBackend
+from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream
from .._exceptions import ConnectionNotAvailable, ProxyError
from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
from .._ssl import default_ssl_context
from .._synchronization import AsyncLock
from .._trace import Trace
-from ..backends.auto import AutoBackend
-from ..backends.base import AsyncNetworkBackend, AsyncNetworkStream
from .connection_pool import AsyncConnectionPool
from .http11 import AsyncHTTP11Connection
from .interfaces import AsyncConnectionInterface
diff --git a/httpcore/backends/__init__.py b/httpcore/_backends/__init__.py
similarity index 100%
rename from httpcore/backends/__init__.py
rename to httpcore/_backends/__init__.py
diff --git a/httpcore/backends/asyncio.py b/httpcore/_backends/anyio.py
similarity index 96%
rename from httpcore/backends/asyncio.py
rename to httpcore/_backends/anyio.py
index 70a220781..1ed5228db 100644
--- a/httpcore/backends/asyncio.py
+++ b/httpcore/_backends/anyio.py
@@ -16,7 +16,7 @@
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
-class AsyncIOStream(AsyncNetworkStream):
+class AnyIOStream(AsyncNetworkStream):
def __init__(self, stream: anyio.abc.ByteStream) -> None:
self._stream = stream
@@ -76,7 +76,7 @@ async def start_tls(
except Exception as exc: # pragma: nocover
await self.aclose()
raise exc
- return AsyncIOStream(ssl_stream)
+ return AnyIOStream(ssl_stream)
def get_extra_info(self, info: str) -> typing.Any:
if info == "ssl_object":
@@ -93,7 +93,7 @@ def get_extra_info(self, info: str) -> typing.Any:
return None
-class AsyncIOBackend(AsyncNetworkBackend):
+class AnyIOBackend(AsyncNetworkBackend):
async def connect_tcp(
self,
host: str,
@@ -119,7 +119,7 @@ async def connect_tcp(
# By default TCP sockets opened in `asyncio` include TCP_NODELAY.
for option in socket_options:
stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
- return AsyncIOStream(stream)
+ return AnyIOStream(stream)
async def connect_unix_socket(
self,
@@ -139,7 +139,7 @@ async def connect_unix_socket(
stream: anyio.abc.ByteStream = await anyio.connect_unix(path)
for option in socket_options:
stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover
- return AsyncIOStream(stream)
+ return AnyIOStream(stream)
async def sleep(self, seconds: float) -> None:
await anyio.sleep(seconds) # pragma: nocover
diff --git a/httpcore/backends/auto.py b/httpcore/_backends/auto.py
similarity index 93%
rename from httpcore/backends/auto.py
rename to httpcore/_backends/auto.py
index f4766ab51..b612ba071 100644
--- a/httpcore/backends/auto.py
+++ b/httpcore/_backends/auto.py
@@ -15,9 +15,9 @@ async def _init_backend(self) -> None:
self._backend: AsyncNetworkBackend = TrioBackend()
else:
- from .asyncio import AsyncIOBackend
+ from .anyio import AnyIOBackend
- self._backend = AsyncIOBackend()
+ self._backend = AnyIOBackend()
async def connect_tcp(
self,
diff --git a/httpcore/backends/base.py b/httpcore/_backends/base.py
similarity index 100%
rename from httpcore/backends/base.py
rename to httpcore/_backends/base.py
diff --git a/httpcore/backends/mock.py b/httpcore/_backends/mock.py
similarity index 100%
rename from httpcore/backends/mock.py
rename to httpcore/_backends/mock.py
diff --git a/httpcore/backends/sync.py b/httpcore/_backends/sync.py
similarity index 100%
rename from httpcore/backends/sync.py
rename to httpcore/_backends/sync.py
diff --git a/httpcore/backends/trio.py b/httpcore/_backends/trio.py
similarity index 100%
rename from httpcore/backends/trio.py
rename to httpcore/_backends/trio.py
diff --git a/httpcore/_sync/connection.py b/httpcore/_sync/connection.py
index d40b155bb..39b8b97e8 100644
--- a/httpcore/_sync/connection.py
+++ b/httpcore/_sync/connection.py
@@ -4,13 +4,13 @@
from types import TracebackType
from typing import Iterable, Iterator, Optional, Type
+from .._backends.sync import SyncBackend
+from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream
from .._exceptions import ConnectError, ConnectionNotAvailable, ConnectTimeout
from .._models import Origin, Request, Response
from .._ssl import default_ssl_context
from .._synchronization import Lock
from .._trace import Trace
-from ..backends.sync import SyncBackend
-from ..backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream
from .http11 import HTTP11Connection
from .interfaces import ConnectionInterface
diff --git a/httpcore/_sync/connection_pool.py b/httpcore/_sync/connection_pool.py
index 9f0bb2481..0a2acc4f3 100644
--- a/httpcore/_sync/connection_pool.py
+++ b/httpcore/_sync/connection_pool.py
@@ -4,12 +4,12 @@
from types import TracebackType
from typing import Iterable, Iterator, Iterable, List, Optional, Type
+from .._backends.sync import SyncBackend
+from .._backends.base import SOCKET_OPTION, NetworkBackend
from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
from .._models import Origin, Request, Response
from .._synchronization import Event, Lock
from .._trace import Trace
-from ..backends.sync import SyncBackend
-from ..backends.base import SOCKET_OPTION, NetworkBackend
from .connection import HTTPConnection
from .interfaces import ConnectionInterface, RequestInterface
diff --git a/httpcore/_sync/http11.py b/httpcore/_sync/http11.py
index 3a430bcf5..448cf8def 100644
--- a/httpcore/_sync/http11.py
+++ b/httpcore/_sync/http11.py
@@ -15,6 +15,7 @@
import h11
+from .._backends.base import NetworkStream
from .._exceptions import (
ConnectionNotAvailable,
LocalProtocolError,
@@ -24,7 +25,6 @@
from .._models import Origin, Request, Response
from .._synchronization import Lock
from .._trace import Trace
-from ..backends.base import NetworkStream
from .interfaces import ConnectionInterface
logger = logging.getLogger("httpcore.http11")
diff --git a/httpcore/_sync/http2.py b/httpcore/_sync/http2.py
index 68482d268..4dc508ab8 100644
--- a/httpcore/_sync/http2.py
+++ b/httpcore/_sync/http2.py
@@ -10,6 +10,7 @@
import h2.exceptions
import h2.settings
+from .._backends.base import NetworkStream
from .._exceptions import (
ConnectionNotAvailable,
LocalProtocolError,
@@ -18,7 +19,6 @@
from .._models import Origin, Request, Response
from .._synchronization import Lock, Semaphore
from .._trace import Trace
-from ..backends.base import NetworkStream
from .interfaces import ConnectionInterface
logger = logging.getLogger("httpcore.http2")
diff --git a/httpcore/_sync/http_proxy.py b/httpcore/_sync/http_proxy.py
index f282f663d..bb368dd42 100644
--- a/httpcore/_sync/http_proxy.py
+++ b/httpcore/_sync/http_proxy.py
@@ -3,6 +3,7 @@
from base64 import b64encode
from typing import Iterable, List, Mapping, Optional, Sequence, Tuple, Union
+from .._backends.base import SOCKET_OPTION, NetworkBackend
from .._exceptions import ProxyError
from .._models import (
URL,
@@ -16,7 +17,6 @@
from .._ssl import default_ssl_context
from .._synchronization import Lock
from .._trace import Trace
-from ..backends.base import SOCKET_OPTION, NetworkBackend
from .connection import HTTPConnection
from .connection_pool import ConnectionPool
from .http11 import HTTP11Connection
diff --git a/httpcore/_sync/socks_proxy.py b/httpcore/_sync/socks_proxy.py
index 7387e731d..407351d06 100644
--- a/httpcore/_sync/socks_proxy.py
+++ b/httpcore/_sync/socks_proxy.py
@@ -4,13 +4,13 @@
from socksio import socks5
+from .._backends.sync import SyncBackend
+from .._backends.base import NetworkBackend, NetworkStream
from .._exceptions import ConnectionNotAvailable, ProxyError
from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url
from .._ssl import default_ssl_context
from .._synchronization import Lock
from .._trace import Trace
-from ..backends.sync import SyncBackend
-from ..backends.base import NetworkBackend, NetworkStream
from .connection_pool import ConnectionPool
from .http11 import HTTP11Connection
from .interfaces import ConnectionInterface
diff --git a/mkdocs.yml b/mkdocs.yml
index 8fd57903d..9f4200bad 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -12,6 +12,7 @@ nav:
- Proxies: 'proxies.md'
- HTTP/2: 'http2.md'
- Async Support: 'async.md'
+ - Network Backends: 'network-backends.md'
- Extensions: 'extensions.md'
- Logging: 'logging.md'
- Exceptions: 'exceptions.md'
diff --git a/requirements.txt b/requirements.txt
index 1dcd763bf..bd4a98e63 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,9 +6,9 @@ trio==0.21.0
# Docs
mkdocs==1.4.2
mkdocs-autorefs==0.3.1
-mkdocs-material==9.1.5
+mkdocs-material==9.1.15
mkdocs-material-extensions==1.1.1
-mkdocstrings[python-legacy]==0.19.1
+mkdocstrings[python-legacy]==0.22.0
jinja2==3.1.2
# Packaging
@@ -18,7 +18,7 @@ wheel
# Tests & Linting
anyio==3.6.2
autoflake==1.7.7
-black==23.1.0
+black==23.3.0
coverage==6.5.0
flake8==3.9.2 # See: https://github.com/PyCQA/flake8/pull/1438
isort==5.11.4
@@ -26,7 +26,7 @@ importlib-metadata==4.13.0
mypy==1.2.0
trio-typing==0.8.0
types-certifi==2021.10.8.3
-pytest==7.2.0
+pytest==7.3.1
pytest-httpbin==2.0.0rc1
pytest-trio==0.7.0
werkzeug<2.1 # See: https://github.com/postmanlabs/httpbin/issues/673
diff --git a/tests/_async/test_connection.py b/tests/_async/test_connection.py
index 77ab3db00..8b29942c2 100644
--- a/tests/_async/test_connection.py
+++ b/tests/_async/test_connection.py
@@ -1,14 +1,19 @@
import ssl
import typing
-from typing import List, Optional
import hpack
import hyperframe.frame
import pytest
-from httpcore import AsyncHTTPConnection, ConnectError, ConnectionNotAvailable, Origin
-from httpcore.backends.base import SOCKET_OPTION, AsyncNetworkStream
-from httpcore.backends.mock import AsyncMockBackend
+from httpcore import (
+ SOCKET_OPTION,
+ AsyncHTTPConnection,
+ AsyncMockBackend,
+ AsyncNetworkStream,
+ ConnectError,
+ ConnectionNotAvailable,
+ Origin,
+)
@pytest.mark.anyio
@@ -128,7 +133,7 @@ async def test_request_to_incorrect_origin():
class NeedsRetryBackend(AsyncMockBackend):
def __init__(
self,
- buffer: List[bytes],
+ buffer: typing.List[bytes],
http2: bool = False,
connect_tcp_failures: int = 2,
start_tls_failures: int = 0,
@@ -141,8 +146,8 @@ async def connect_tcp(
self,
host: str,
port: int,
- timeout: Optional[float] = None,
- local_address: Optional[str] = None,
+ timeout: typing.Optional[float] = None,
+ local_address: typing.Optional[str] = None,
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
) -> AsyncNetworkStream:
if self._connect_tcp_failures > 0:
diff --git a/tests/_async/test_connection_pool.py b/tests/_async/test_connection_pool.py
index 7c9957056..683ad28a5 100644
--- a/tests/_async/test_connection_pool.py
+++ b/tests/_async/test_connection_pool.py
@@ -1,19 +1,10 @@
import logging
import typing
-from typing import List, Optional
import pytest
import trio as concurrency
-from httpcore import (
- AsyncConnectionPool,
- ConnectError,
- PoolTimeout,
- ReadError,
- UnsupportedProtocol,
-)
-from httpcore.backends.base import SOCKET_OPTION, AsyncNetworkStream
-from httpcore.backends.mock import AsyncMockBackend
+import httpcore
@pytest.mark.anyio
@@ -21,7 +12,7 @@ async def test_connection_pool_with_keepalive():
"""
By default HTTP/1.1 requests should be returned to the connection pool.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -36,7 +27,7 @@ async def test_connection_pool_with_keepalive():
]
)
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
network_backend=network_backend,
) as pool:
# Sending an intial request, which once complete will return to the pool, IDLE.
@@ -93,7 +84,7 @@ async def test_connection_pool_with_close():
HTTP/1.1 requests that include a 'Connection: Close' header should
not be returned to the connection pool.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -103,7 +94,7 @@ async def test_connection_pool_with_close():
]
)
- async with AsyncConnectionPool(network_backend=network_backend) as pool:
+ async with httpcore.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"}
@@ -126,7 +117,7 @@ async def test_trace_request():
The 'trace' request extension allows for a callback function to inspect the
internal events that occur while sending a request.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -141,7 +132,7 @@ async def test_trace_request():
async def trace(name, kwargs):
called.append(name)
- async with AsyncConnectionPool(network_backend=network_backend) as pool:
+ async with httpcore.AsyncConnectionPool(network_backend=network_backend) as pool:
await pool.request("GET", "https://example.com/", extensions={"trace": trace})
assert called == [
@@ -172,7 +163,7 @@ async def test_debug_request(caplog):
"""
caplog.set_level(logging.DEBUG)
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -182,7 +173,7 @@ async def test_debug_request(caplog):
]
)
- async with AsyncConnectionPool(network_backend=network_backend) as pool:
+ async with httpcore.AsyncConnectionPool(network_backend=network_backend) as pool:
await pool.request("GET", "http://example.com/")
assert caplog.record_tuples == [
@@ -269,14 +260,14 @@ async def test_connection_pool_with_http_exception():
HTTP/1.1 requests that result in an exception during the connection should
not be returned to the connection pool.
"""
- network_backend = AsyncMockBackend([b"Wait, this isn't valid HTTP!"])
+ network_backend = httpcore.AsyncMockBackend([b"Wait, this isn't valid HTTP!"])
called = []
async def trace(name, kwargs):
called.append(name)
- async with AsyncConnectionPool(network_backend=network_backend) as pool:
+ async with httpcore.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(
@@ -311,16 +302,18 @@ async def test_connection_pool_with_connect_exception():
be returned to the connection pool.
"""
- class FailedConnectBackend(AsyncMockBackend):
+ class FailedConnectBackend(httpcore.AsyncMockBackend):
async def connect_tcp(
self,
host: str,
port: int,
- timeout: Optional[float] = None,
- local_address: Optional[str] = None,
- socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
- ) -> AsyncNetworkStream:
- raise ConnectError("Could not connect")
+ timeout: typing.Optional[float] = None,
+ local_address: typing.Optional[str] = None,
+ socket_options: typing.Optional[
+ typing.Iterable[httpcore.SOCKET_OPTION]
+ ] = None,
+ ) -> httpcore.AsyncNetworkStream:
+ raise httpcore.ConnectError("Could not connect")
network_backend = FailedConnectBackend([])
@@ -329,7 +322,7 @@ async def connect_tcp(
async def trace(name, kwargs):
called.append(name)
- async with AsyncConnectionPool(network_backend=network_backend) as pool:
+ async with httpcore.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(
@@ -353,7 +346,7 @@ async def test_connection_pool_with_immediate_expiry():
Connection pools with keepalive_expiry=0.0 should immediately expire
keep alive connections.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -363,7 +356,7 @@ async def test_connection_pool_with_immediate_expiry():
]
)
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
keepalive_expiry=0.0,
network_backend=network_backend,
) as pool:
@@ -387,7 +380,7 @@ async def test_connection_pool_with_no_keepalive_connections_allowed():
When 'max_keepalive_connections=0' is used, IDLE connections should not
be returned to the pool.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -397,7 +390,7 @@ async def test_connection_pool_with_no_keepalive_connections_allowed():
]
)
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
max_keepalive_connections=0, network_backend=network_backend
) as pool:
# Sending an intial request, which once complete will not return to the pool.
@@ -420,7 +413,7 @@ async def test_connection_pool_concurrency():
HTTP/1.1 requests made in concurrency must not ever exceed the maximum number
of allowable connection in the pool.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -436,10 +429,10 @@ async def fetch(pool, domain, info_list):
info_list.append(info)
await response.aread()
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
max_connections=1, network_backend=network_backend
) as pool:
- info_list: List[str] = []
+ 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"]:
nursery.start_soon(fetch, pool, domain, info_list)
@@ -465,7 +458,7 @@ async def test_connection_pool_concurrency_same_domain_closing():
HTTP/1.1 requests made in concurrency must not ever exceed the maximum number
of allowable connection in the pool.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -482,10 +475,10 @@ async def fetch(pool, domain, info_list):
info_list.append(info)
await response.aread()
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
max_connections=1, network_backend=network_backend, http2=True
) as pool:
- info_list: List[str] = []
+ 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"]:
nursery.start_soon(fetch, pool, domain, info_list)
@@ -507,7 +500,7 @@ async def test_connection_pool_concurrency_same_domain_keepalive():
HTTP/1.1 requests made in concurrency must not ever exceed the maximum number
of allowable connection in the pool.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -524,10 +517,10 @@ async def fetch(pool, domain, info_list):
info_list.append(info)
await response.aread()
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
max_connections=1, network_backend=network_backend, http2=True
) as pool:
- info_list: List[str] = []
+ 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"]:
nursery.start_soon(fetch, pool, domain, info_list)
@@ -548,11 +541,11 @@ async def fetch(pool, domain, info_list):
@pytest.mark.anyio
async def test_unsupported_protocol():
- async with AsyncConnectionPool() as pool:
- with pytest.raises(UnsupportedProtocol):
+ async with httpcore.AsyncConnectionPool() as pool:
+ with pytest.raises(httpcore.UnsupportedProtocol):
await pool.request("GET", "ftp://www.example.com/")
- with pytest.raises(UnsupportedProtocol):
+ with pytest.raises(httpcore.UnsupportedProtocol):
await pool.request("GET", "://www.example.com/")
@@ -562,7 +555,7 @@ async def test_connection_pool_closed_while_request_in_flight():
Closing a connection pool while a request/response is still in-flight
should raise an error.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -572,14 +565,14 @@ async def test_connection_pool_closed_while_request_in_flight():
]
)
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
network_backend=network_backend,
) as pool:
# Send a request, and then close the connection pool while the
# response has not yet been streamed.
async with pool.stream("GET", "https://example.com/") as response:
await pool.aclose()
- with pytest.raises(ReadError):
+ with pytest.raises(httpcore.ReadError):
await response.aread()
@@ -588,7 +581,7 @@ async def test_connection_pool_timeout():
"""
Ensure that exceeding max_connections can cause a request to timeout.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -598,14 +591,14 @@ async def test_connection_pool_timeout():
]
)
- async with AsyncConnectionPool(
+ async with httpcore.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.
async with pool.stream("GET", "https://example.com/"):
- with pytest.raises(PoolTimeout):
+ with pytest.raises(httpcore.PoolTimeout):
extensions = {"timeout": {"pool": 0.0001}}
await pool.request("GET", "https://example.com/", extensions=extensions)
@@ -616,7 +609,7 @@ async def test_connection_pool_timeout_zero():
A pool timeout of 0 shouldn't raise a PoolTimeout if there's
no need to wait on a new connection.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -635,7 +628,7 @@ 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 AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
network_backend=network_backend, max_connections=1
) as pool:
# Two consecutive requests with a pool timeout of zero.
@@ -653,7 +646,7 @@ async def test_connection_pool_timeout_zero():
assert response.content == b"Hello, world!"
# A connection pool configured to allow only one connection at a time.
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
network_backend=network_backend, max_connections=1
) as pool:
# Two concurrent requests with a pool timeout of zero.
@@ -662,7 +655,7 @@ async def test_connection_pool_timeout_zero():
"GET", "https://example.com/", extensions=extensions
) as response:
# The first response hasn't yet completed.
- with pytest.raises(PoolTimeout):
+ with pytest.raises(httpcore.PoolTimeout):
# So a pool timeout occurs.
await pool.request("GET", "https://example.com/", extensions=extensions)
# The first response now completes.
@@ -683,7 +676,7 @@ async def test_http11_upgrade_connection():
https://httpwg.org/specs/rfc9110.html#status.101
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
b"HTTP/1.1 101 Switching Protocols\r\n",
b"Connection: upgrade\r\n",
@@ -692,7 +685,7 @@ async def test_http11_upgrade_connection():
b"...",
]
)
- async with AsyncConnectionPool(
+ async with httpcore.AsyncConnectionPool(
network_backend=network_backend, max_connections=1
) as pool:
async with pool.stream(
diff --git a/tests/_async/test_http11.py b/tests/_async/test_http11.py
index d0a60df7f..489d68b43 100644
--- a/tests/_async/test_http11.py
+++ b/tests/_async/test_http11.py
@@ -1,19 +1,12 @@
import pytest
-from httpcore import (
- AsyncHTTP11Connection,
- ConnectionNotAvailable,
- LocalProtocolError,
- Origin,
- RemoteProtocolError,
-)
-from httpcore.backends.mock import AsyncMockStream
+import httpcore
@pytest.mark.anyio
async def test_http11_connection():
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -22,7 +15,7 @@ async def test_http11_connection():
b"Hello, world!",
]
)
- async with AsyncHTTP11Connection(
+ async with httpcore.AsyncHTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = await conn.request("GET", "https://example.com/")
@@ -45,8 +38,8 @@ async def test_http11_connection_unread_response():
If the client releases the response without reading it to termination,
then the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -55,7 +48,7 @@ async def test_http11_connection_unread_response():
b"Hello, world!",
]
)
- async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
+ async with httpcore.AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
async with conn.stream("GET", "https://example.com/") as response:
assert response.status == 200
@@ -75,10 +68,10 @@ async def test_http11_connection_with_remote_protocol_error():
If a remote protocol error occurs, then no response will be returned,
and the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream([b"Wait, this isn't valid HTTP!", b""])
- async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream([b"Wait, this isn't valid HTTP!", b""])
+ async with httpcore.AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
await conn.request("GET", "https://example.com/")
assert not conn.is_idle()
@@ -96,8 +89,8 @@ async def test_http11_connection_with_incomplete_response():
"""
We should be gracefully handling the case where the connection ends prematurely.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -106,8 +99,8 @@ async def test_http11_connection_with_incomplete_response():
b"Hello, wor",
]
)
- async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ async with httpcore.AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
await conn.request("GET", "https://example.com/")
assert not conn.is_idle()
@@ -126,8 +119,8 @@ async def test_http11_connection_with_local_protocol_error():
If a local protocol error occurs, then no response will be returned,
and the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -136,8 +129,8 @@ async def test_http11_connection_with_local_protocol_error():
b"Hello, world!",
]
)
- async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(LocalProtocolError) as exc_info:
+ async with httpcore.AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.LocalProtocolError) as exc_info:
await conn.request("GET", "https://example.com/", headers={"Host": "\0"})
assert str(exc_info.value) == "Illegal header value b'\\x00'"
@@ -158,8 +151,8 @@ async def test_http11_connection_handles_one_active_request():
Attempting to send a request while one is already in-flight will raise
a ConnectionNotAvailable exception.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -168,9 +161,9 @@ async def test_http11_connection_handles_one_active_request():
b"Hello, world!",
]
)
- async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
+ async with httpcore.AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
async with conn.stream("GET", "https://example.com/"):
- with pytest.raises(ConnectionNotAvailable):
+ with pytest.raises(httpcore.ConnectionNotAvailable):
await conn.request("GET", "https://example.com/")
@@ -179,8 +172,8 @@ async def test_http11_connection_attempt_close():
"""
A connection can only be closed when it is idle.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -189,7 +182,7 @@ async def test_http11_connection_attempt_close():
b"Hello, world!",
]
)
- async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
+ async with httpcore.AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
async with conn.stream("GET", "https://example.com/") as response:
await response.aread()
assert response.status == 200
@@ -201,9 +194,9 @@ async def test_http11_request_to_incorrect_origin():
"""
A connection can only send requests to whichever origin it is connected to.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream([])
- async with AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream([])
+ async with httpcore.AsyncHTTP11Connection(origin=origin, stream=stream) as conn:
with pytest.raises(RuntimeError):
await conn.request("GET", "https://other.com/")
@@ -217,8 +210,8 @@ async def test_http11_expect_continue():
https://httpwg.org/specs/rfc9110.html#status.100
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 100 Continue\r\n",
b"\r\n",
@@ -229,7 +222,7 @@ async def test_http11_expect_continue():
b"Hello, world!",
]
)
- async with AsyncHTTP11Connection(
+ async with httpcore.AsyncHTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = await conn.request(
@@ -252,8 +245,8 @@ async def test_http11_upgrade_connection():
https://httpwg.org/specs/rfc9110.html#status.101
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101
"""
- origin = Origin(b"wss", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"wss", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 101 Switching Protocols\r\n",
b"Connection: upgrade\r\n",
@@ -262,7 +255,7 @@ async def test_http11_upgrade_connection():
b"...",
]
)
- async with AsyncHTTP11Connection(
+ async with httpcore.AsyncHTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
async with conn.stream(
@@ -284,8 +277,8 @@ async def test_http11_early_hints():
https://datatracker.ietf.org/doc/rfc8297/
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 103 Early Hints\r\n",
b"Link: ; rel=preload; as=style\r\n",
@@ -300,7 +293,7 @@ async def test_http11_early_hints():
b"Hello, world! ...",
]
)
- async with AsyncHTTP11Connection(
+ async with httpcore.AsyncHTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = await conn.request(
@@ -317,8 +310,8 @@ async def test_http11_header_sub_100kb():
"""
A connection should be able to handle a http header size up to 100kB.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
b"HTTP/1.1 200 OK\r\n", # 17
b"Content-Type: plain/text\r\n", # 43
@@ -328,7 +321,7 @@ async def test_http11_header_sub_100kb():
b"",
]
)
- async with AsyncHTTP11Connection(
+ async with httpcore.AsyncHTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = await conn.request("GET", "https://example.com/")
diff --git a/tests/_async/test_http2.py b/tests/_async/test_http2.py
index f995465f7..59ba158e9 100644
--- a/tests/_async/test_http2.py
+++ b/tests/_async/test_http2.py
@@ -2,19 +2,13 @@
import hyperframe.frame
import pytest
-from httpcore import (
- AsyncHTTP2Connection,
- ConnectionNotAvailable,
- Origin,
- RemoteProtocolError,
-)
-from httpcore.backends.mock import AsyncMockStream
+import httpcore
@pytest.mark.anyio
async def test_http2_connection():
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -32,7 +26,7 @@ async def test_http2_connection():
).serialize(),
]
)
- async with AsyncHTTP2Connection(
+ async with httpcore.AsyncHTTP2Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = await conn.request("GET", "https://example.com/")
@@ -54,8 +48,8 @@ async def test_http2_connection():
@pytest.mark.anyio
async def test_http2_connection_closed():
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -75,12 +69,12 @@ async def test_http2_connection_closed():
hyperframe.frame.GoAwayFrame(stream_id=0, error_code=0).serialize(),
]
)
- async with AsyncHTTP2Connection(
+ async with httpcore.AsyncHTTP2Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
await conn.request("GET", "https://example.com/")
- with pytest.raises(RemoteProtocolError):
+ with pytest.raises(httpcore.RemoteProtocolError):
await conn.request("GET", "https://example.com/")
assert not conn.is_available()
@@ -88,8 +82,8 @@ async def test_http2_connection_closed():
@pytest.mark.anyio
async def test_http2_connection_post_request():
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -107,7 +101,7 @@ async def test_http2_connection_post_request():
).serialize(),
]
)
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
response = await conn.request(
"POST",
"https://example.com/",
@@ -124,10 +118,10 @@ async def test_http2_connection_with_remote_protocol_error():
If a remote protocol error occurs, then no response will be returned,
and the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream([b"Wait, this isn't valid HTTP!", b""])
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream([b"Wait, this isn't valid HTTP!", b""])
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
await conn.request("GET", "https://example.com/")
@@ -137,8 +131,8 @@ async def test_http2_connection_with_rst_stream():
If a stream reset occurs, then no response will be returned,
but the connection will remain reusable for other requests.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -170,8 +164,8 @@ async def test_http2_connection_with_rst_stream():
b"",
]
)
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
await conn.request("GET", "https://example.com/")
response = await conn.request("GET", "https://example.com/")
assert response.status == 200
@@ -183,8 +177,8 @@ async def test_http2_connection_with_goaway():
If a GoAway frame occurs, then no response will be returned,
and the connection will not be reusable for other requests.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -216,17 +210,17 @@ async def test_http2_connection_with_goaway():
b"",
]
)
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
await conn.request("GET", "https://example.com/")
- with pytest.raises(RemoteProtocolError):
+ with pytest.raises(httpcore.RemoteProtocolError):
await conn.request("GET", "https://example.com/")
@pytest.mark.anyio
async def test_http2_connection_with_flow_control():
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
# Available flow: 65,535
@@ -273,7 +267,7 @@ async def test_http2_connection_with_flow_control():
).serialize(),
]
)
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
response = await conn.request(
"POST",
"https://example.com/",
@@ -288,8 +282,8 @@ async def test_http2_connection_attempt_close():
"""
A connection can only be closed when it is idle.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -307,14 +301,14 @@ async def test_http2_connection_attempt_close():
).serialize(),
]
)
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
async with conn.stream("GET", "https://example.com/") as response:
await response.aread()
assert response.status == 200
assert response.content == b"Hello, world!"
await conn.aclose()
- with pytest.raises(ConnectionNotAvailable):
+ with pytest.raises(httpcore.ConnectionNotAvailable):
await conn.request("GET", "https://example.com/")
@@ -323,9 +317,9 @@ async def test_http2_request_to_incorrect_origin():
"""
A connection can only send requests to whichever origin it is connected to.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream([])
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream([])
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
with pytest.raises(RuntimeError):
await conn.request("GET", "https://other.com/")
@@ -336,8 +330,8 @@ async def test_http2_remote_max_streams_update():
If the remote server updates the maximum concurrent streams value, we should
be adjusting how many streams we will allow.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = AsyncMockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.AsyncMockStream(
[
hyperframe.frame.SettingsFrame(
settings={hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 1000}
@@ -361,7 +355,7 @@ async def test_http2_remote_max_streams_update():
).serialize(),
]
)
- async with AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
+ async with httpcore.AsyncHTTP2Connection(origin=origin, stream=stream) as conn:
async with conn.stream("GET", "https://example.com/") as response:
i = 0
async for chunk in response.aiter_stream():
diff --git a/tests/_async/test_http_proxy.py b/tests/_async/test_http_proxy.py
index 55ac88e1e..b35fc2899 100644
--- a/tests/_async/test_http_proxy.py
+++ b/tests/_async/test_http_proxy.py
@@ -1,14 +1,19 @@
import ssl
import typing
-from typing import Optional
import hpack
import hyperframe.frame
import pytest
-from httpcore import AsyncHTTPProxy, Origin, ProxyError
-from httpcore.backends.base import SOCKET_OPTION, AsyncNetworkStream
-from httpcore.backends.mock import AsyncMockBackend, AsyncMockStream
+from httpcore import (
+ SOCKET_OPTION,
+ AsyncHTTPProxy,
+ AsyncMockBackend,
+ AsyncMockStream,
+ AsyncNetworkStream,
+ Origin,
+ ProxyError,
+)
@pytest.mark.anyio
@@ -127,8 +132,8 @@ class HTTP1ThenHTTP2Stream(AsyncMockStream):
async def start_tls(
self,
ssl_context: ssl.SSLContext,
- server_hostname: Optional[str] = None,
- timeout: Optional[float] = None,
+ server_hostname: typing.Optional[str] = None,
+ timeout: typing.Optional[float] = None,
) -> AsyncNetworkStream:
self._http2 = True
return self
@@ -139,8 +144,8 @@ async def connect_tcp(
self,
host: str,
port: int,
- timeout: Optional[float] = None,
- local_address: Optional[str] = None,
+ timeout: typing.Optional[float] = None,
+ local_address: typing.Optional[str] = None,
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
) -> AsyncNetworkStream:
return HTTP1ThenHTTP2Stream(list(self._buffer))
diff --git a/tests/_async/test_integration.py b/tests/_async/test_integration.py
index ec7cb61b4..1970531d5 100644
--- a/tests/_async/test_integration.py
+++ b/tests/_async/test_integration.py
@@ -2,12 +2,12 @@
import pytest
-from httpcore import AsyncConnectionPool
+import httpcore
@pytest.mark.anyio
async def test_request(httpbin):
- async with AsyncConnectionPool() as pool:
+ async with httpcore.AsyncConnectionPool() as pool:
response = await pool.request("GET", httpbin.url)
assert response.status == 200
@@ -17,7 +17,7 @@ async def test_ssl_request(httpbin_secure):
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
- async with AsyncConnectionPool(ssl_context=ssl_context) as pool:
+ async with httpcore.AsyncConnectionPool(ssl_context=ssl_context) as pool:
response = await pool.request("GET", httpbin_secure.url)
assert response.status == 200
@@ -27,7 +27,7 @@ async def test_extra_info(httpbin_secure):
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
- async with AsyncConnectionPool(ssl_context=ssl_context) as pool:
+ async with httpcore.AsyncConnectionPool(ssl_context=ssl_context) as pool:
async with pool.stream("GET", httpbin_secure.url) as response:
assert response.status == 200
stream = response.extensions["network_stream"]
diff --git a/tests/_async/test_socks_proxy.py b/tests/_async/test_socks_proxy.py
index 168bc21e1..3f5dd1cc0 100644
--- a/tests/_async/test_socks_proxy.py
+++ b/tests/_async/test_socks_proxy.py
@@ -1,7 +1,6 @@
import pytest
-from httpcore import AsyncSOCKSProxy, Origin, ProxyError
-from httpcore.backends.mock import AsyncMockBackend
+import httpcore
@pytest.mark.anyio
@@ -9,7 +8,7 @@ async def test_socks5_request():
"""
Send an HTTP request via a SOCKS proxy.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
# The initial socks CONNECT
# v5 NOAUTH
@@ -25,7 +24,7 @@ async def test_socks5_request():
]
)
- async with AsyncSOCKSProxy(
+ async with httpcore.AsyncSOCKSProxy(
proxy_url="socks5://localhost:8080/",
network_backend=network_backend,
) as proxy:
@@ -49,16 +48,16 @@ async def test_socks5_request():
# 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)
+ httpcore.Origin(b"http", b"example.com", 80)
)
assert not proxy.connections[0].can_handle_request(
- Origin(b"http", b"other.com", 80)
+ httpcore.Origin(b"http", b"other.com", 80)
)
assert proxy.connections[0].can_handle_request(
- Origin(b"https", b"example.com", 443)
+ httpcore.Origin(b"https", b"example.com", 443)
)
assert not proxy.connections[0].can_handle_request(
- Origin(b"https", b"other.com", 443)
+ httpcore.Origin(b"https", b"other.com", 443)
)
@@ -67,7 +66,7 @@ async def test_authenticated_socks5_request():
"""
Send an HTTP request via a SOCKS proxy.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
# The initial socks CONNECT
# v5 USERNAME/PASSWORD
@@ -85,7 +84,7 @@ async def test_authenticated_socks5_request():
]
)
- async with AsyncSOCKSProxy(
+ async with httpcore.AsyncSOCKSProxy(
proxy_url="socks5://localhost:8080/",
proxy_auth=(b"username", b"password"),
network_backend=network_backend,
@@ -114,7 +113,7 @@ async def test_socks5_request_connect_failed():
"""
Attempt to send an HTTP request via a SOCKS proxy, resulting in a connect failure.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
# The initial socks CONNECT
# v5 NOAUTH
@@ -124,12 +123,12 @@ async def test_socks5_request_connect_failed():
]
)
- async with AsyncSOCKSProxy(
+ async with httpcore.AsyncSOCKSProxy(
proxy_url="socks5://localhost:8080/",
network_backend=network_backend,
) as proxy:
# Sending a request, which the proxy rejects
- with pytest.raises(ProxyError) as exc_info:
+ with pytest.raises(httpcore.ProxyError) as exc_info:
await proxy.request("GET", "https://example.com/")
assert (
str(exc_info.value) == "Proxy Server could not connect: Connection refused."
@@ -144,19 +143,19 @@ async def test_socks5_request_failed_to_provide_auth():
Attempt to send an HTTP request via an authenticated SOCKS proxy,
without providing authentication credentials.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
# v5 USERNAME/PASSWORD
b"\x05\x02",
]
)
- async with AsyncSOCKSProxy(
+ async with httpcore.AsyncSOCKSProxy(
proxy_url="socks5://localhost:8080/",
network_backend=network_backend,
) as proxy:
# Sending a request, which the proxy rejects
- with pytest.raises(ProxyError) as exc_info:
+ with pytest.raises(httpcore.ProxyError) as exc_info:
await proxy.request("GET", "https://example.com/")
assert (
str(exc_info.value)
@@ -172,7 +171,7 @@ async def test_socks5_request_incorrect_auth():
Attempt to send an HTTP request via an authenticated SOCKS proxy,
wit incorrect authentication credentials.
"""
- network_backend = AsyncMockBackend(
+ network_backend = httpcore.AsyncMockBackend(
[
# v5 USERNAME/PASSWORD
b"\x05\x02",
@@ -181,13 +180,13 @@ async def test_socks5_request_incorrect_auth():
]
)
- async with AsyncSOCKSProxy(
+ async with httpcore.AsyncSOCKSProxy(
proxy_url="socks5://localhost:8080/",
proxy_auth=(b"invalid", b"invalid"),
network_backend=network_backend,
) as proxy:
# Sending a request, which the proxy rejects
- with pytest.raises(ProxyError) as exc_info:
+ with pytest.raises(httpcore.ProxyError) as exc_info:
await proxy.request("GET", "https://example.com/")
assert str(exc_info.value) == "Invalid username/password"
diff --git a/tests/_sync/test_connection.py b/tests/_sync/test_connection.py
index 40990c302..9e0c40351 100644
--- a/tests/_sync/test_connection.py
+++ b/tests/_sync/test_connection.py
@@ -1,14 +1,19 @@
import ssl
import typing
-from typing import List, Optional
import hpack
import hyperframe.frame
import pytest
-from httpcore import HTTPConnection, ConnectError, ConnectionNotAvailable, Origin
-from httpcore.backends.base import SOCKET_OPTION, NetworkStream
-from httpcore.backends.mock import MockBackend
+from httpcore import (
+ SOCKET_OPTION,
+ HTTPConnection,
+ MockBackend,
+ NetworkStream,
+ ConnectError,
+ ConnectionNotAvailable,
+ Origin,
+)
@@ -128,7 +133,7 @@ def test_request_to_incorrect_origin():
class NeedsRetryBackend(MockBackend):
def __init__(
self,
- buffer: List[bytes],
+ buffer: typing.List[bytes],
http2: bool = False,
connect_tcp_failures: int = 2,
start_tls_failures: int = 0,
@@ -141,8 +146,8 @@ def connect_tcp(
self,
host: str,
port: int,
- timeout: Optional[float] = None,
- local_address: Optional[str] = None,
+ timeout: typing.Optional[float] = None,
+ local_address: typing.Optional[str] = None,
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
) -> NetworkStream:
if self._connect_tcp_failures > 0:
diff --git a/tests/_sync/test_connection_pool.py b/tests/_sync/test_connection_pool.py
index 6de82cbc9..f32cc1f5c 100644
--- a/tests/_sync/test_connection_pool.py
+++ b/tests/_sync/test_connection_pool.py
@@ -1,19 +1,10 @@
import logging
import typing
-from typing import List, Optional
import pytest
from tests import concurrency
-from httpcore import (
- ConnectionPool,
- ConnectError,
- PoolTimeout,
- ReadError,
- UnsupportedProtocol,
-)
-from httpcore.backends.base import SOCKET_OPTION, NetworkStream
-from httpcore.backends.mock import MockBackend
+import httpcore
@@ -21,7 +12,7 @@ def test_connection_pool_with_keepalive():
"""
By default HTTP/1.1 requests should be returned to the connection pool.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -36,7 +27,7 @@ def test_connection_pool_with_keepalive():
]
)
- with ConnectionPool(
+ with httpcore.ConnectionPool(
network_backend=network_backend,
) as pool:
# Sending an intial request, which once complete will return to the pool, IDLE.
@@ -93,7 +84,7 @@ def test_connection_pool_with_close():
HTTP/1.1 requests that include a 'Connection: Close' header should
not be returned to the connection pool.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -103,7 +94,7 @@ def test_connection_pool_with_close():
]
)
- with ConnectionPool(network_backend=network_backend) as pool:
+ with httpcore.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"}
@@ -126,7 +117,7 @@ def test_trace_request():
The 'trace' request extension allows for a callback function to inspect the
internal events that occur while sending a request.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -141,7 +132,7 @@ def test_trace_request():
def trace(name, kwargs):
called.append(name)
- with ConnectionPool(network_backend=network_backend) as pool:
+ with httpcore.ConnectionPool(network_backend=network_backend) as pool:
pool.request("GET", "https://example.com/", extensions={"trace": trace})
assert called == [
@@ -172,7 +163,7 @@ def test_debug_request(caplog):
"""
caplog.set_level(logging.DEBUG)
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -182,7 +173,7 @@ def test_debug_request(caplog):
]
)
- with ConnectionPool(network_backend=network_backend) as pool:
+ with httpcore.ConnectionPool(network_backend=network_backend) as pool:
pool.request("GET", "http://example.com/")
assert caplog.record_tuples == [
@@ -269,14 +260,14 @@ def test_connection_pool_with_http_exception():
HTTP/1.1 requests that result in an exception during the connection should
not be returned to the connection pool.
"""
- network_backend = MockBackend([b"Wait, this isn't valid HTTP!"])
+ network_backend = httpcore.MockBackend([b"Wait, this isn't valid HTTP!"])
called = []
def trace(name, kwargs):
called.append(name)
- with ConnectionPool(network_backend=network_backend) as pool:
+ with httpcore.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(
@@ -311,16 +302,18 @@ def test_connection_pool_with_connect_exception():
be returned to the connection pool.
"""
- class FailedConnectBackend(MockBackend):
+ class FailedConnectBackend(httpcore.MockBackend):
def connect_tcp(
self,
host: str,
port: int,
- timeout: Optional[float] = None,
- local_address: Optional[str] = None,
- socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
- ) -> NetworkStream:
- raise ConnectError("Could not connect")
+ timeout: typing.Optional[float] = None,
+ local_address: typing.Optional[str] = None,
+ socket_options: typing.Optional[
+ typing.Iterable[httpcore.SOCKET_OPTION]
+ ] = None,
+ ) -> httpcore.NetworkStream:
+ raise httpcore.ConnectError("Could not connect")
network_backend = FailedConnectBackend([])
@@ -329,7 +322,7 @@ def connect_tcp(
def trace(name, kwargs):
called.append(name)
- with ConnectionPool(network_backend=network_backend) as pool:
+ with httpcore.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(
@@ -353,7 +346,7 @@ def test_connection_pool_with_immediate_expiry():
Connection pools with keepalive_expiry=0.0 should immediately expire
keep alive connections.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -363,7 +356,7 @@ def test_connection_pool_with_immediate_expiry():
]
)
- with ConnectionPool(
+ with httpcore.ConnectionPool(
keepalive_expiry=0.0,
network_backend=network_backend,
) as pool:
@@ -387,7 +380,7 @@ def test_connection_pool_with_no_keepalive_connections_allowed():
When 'max_keepalive_connections=0' is used, IDLE connections should not
be returned to the pool.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -397,7 +390,7 @@ def test_connection_pool_with_no_keepalive_connections_allowed():
]
)
- with ConnectionPool(
+ with httpcore.ConnectionPool(
max_keepalive_connections=0, network_backend=network_backend
) as pool:
# Sending an intial request, which once complete will not return to the pool.
@@ -420,7 +413,7 @@ def test_connection_pool_concurrency():
HTTP/1.1 requests made in concurrency must not ever exceed the maximum number
of allowable connection in the pool.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -436,10 +429,10 @@ def fetch(pool, domain, info_list):
info_list.append(info)
response.read()
- with ConnectionPool(
+ with httpcore.ConnectionPool(
max_connections=1, network_backend=network_backend
) as pool:
- info_list: List[str] = []
+ info_list: typing.List[str] = []
with concurrency.open_nursery() as nursery:
for domain in ["a.com", "b.com", "c.com", "d.com", "e.com"]:
nursery.start_soon(fetch, pool, domain, info_list)
@@ -465,7 +458,7 @@ def test_connection_pool_concurrency_same_domain_closing():
HTTP/1.1 requests made in concurrency must not ever exceed the maximum number
of allowable connection in the pool.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -482,10 +475,10 @@ def fetch(pool, domain, info_list):
info_list.append(info)
response.read()
- with ConnectionPool(
+ with httpcore.ConnectionPool(
max_connections=1, network_backend=network_backend, http2=True
) as pool:
- info_list: List[str] = []
+ info_list: typing.List[str] = []
with concurrency.open_nursery() as nursery:
for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]:
nursery.start_soon(fetch, pool, domain, info_list)
@@ -507,7 +500,7 @@ def test_connection_pool_concurrency_same_domain_keepalive():
HTTP/1.1 requests made in concurrency must not ever exceed the maximum number
of allowable connection in the pool.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -524,10 +517,10 @@ def fetch(pool, domain, info_list):
info_list.append(info)
response.read()
- with ConnectionPool(
+ with httpcore.ConnectionPool(
max_connections=1, network_backend=network_backend, http2=True
) as pool:
- info_list: List[str] = []
+ info_list: typing.List[str] = []
with concurrency.open_nursery() as nursery:
for domain in ["a.com", "a.com", "a.com", "a.com", "a.com"]:
nursery.start_soon(fetch, pool, domain, info_list)
@@ -548,11 +541,11 @@ def fetch(pool, domain, info_list):
def test_unsupported_protocol():
- with ConnectionPool() as pool:
- with pytest.raises(UnsupportedProtocol):
+ with httpcore.ConnectionPool() as pool:
+ with pytest.raises(httpcore.UnsupportedProtocol):
pool.request("GET", "ftp://www.example.com/")
- with pytest.raises(UnsupportedProtocol):
+ with pytest.raises(httpcore.UnsupportedProtocol):
pool.request("GET", "://www.example.com/")
@@ -562,7 +555,7 @@ def test_connection_pool_closed_while_request_in_flight():
Closing a connection pool while a request/response is still in-flight
should raise an error.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -572,14 +565,14 @@ def test_connection_pool_closed_while_request_in_flight():
]
)
- with ConnectionPool(
+ with httpcore.ConnectionPool(
network_backend=network_backend,
) as pool:
# Send a request, and then close the connection pool while the
# response has not yet been streamed.
with pool.stream("GET", "https://example.com/") as response:
pool.close()
- with pytest.raises(ReadError):
+ with pytest.raises(httpcore.ReadError):
response.read()
@@ -588,7 +581,7 @@ def test_connection_pool_timeout():
"""
Ensure that exceeding max_connections can cause a request to timeout.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -598,14 +591,14 @@ def test_connection_pool_timeout():
]
)
- with ConnectionPool(
+ with httpcore.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.
with pool.stream("GET", "https://example.com/"):
- with pytest.raises(PoolTimeout):
+ with pytest.raises(httpcore.PoolTimeout):
extensions = {"timeout": {"pool": 0.0001}}
pool.request("GET", "https://example.com/", extensions=extensions)
@@ -616,7 +609,7 @@ def test_connection_pool_timeout_zero():
A pool timeout of 0 shouldn't raise a PoolTimeout if there's
no need to wait on a new connection.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -635,7 +628,7 @@ def test_connection_pool_timeout_zero():
extensions = {"timeout": {"pool": 0}}
# A connection pool configured to allow only one connection at a time.
- with ConnectionPool(
+ with httpcore.ConnectionPool(
network_backend=network_backend, max_connections=1
) as pool:
# Two consecutive requests with a pool timeout of zero.
@@ -653,7 +646,7 @@ def test_connection_pool_timeout_zero():
assert response.content == b"Hello, world!"
# A connection pool configured to allow only one connection at a time.
- with ConnectionPool(
+ with httpcore.ConnectionPool(
network_backend=network_backend, max_connections=1
) as pool:
# Two concurrent requests with a pool timeout of zero.
@@ -662,7 +655,7 @@ def test_connection_pool_timeout_zero():
"GET", "https://example.com/", extensions=extensions
) as response:
# The first response hasn't yet completed.
- with pytest.raises(PoolTimeout):
+ with pytest.raises(httpcore.PoolTimeout):
# So a pool timeout occurs.
pool.request("GET", "https://example.com/", extensions=extensions)
# The first response now completes.
@@ -683,7 +676,7 @@ def test_http11_upgrade_connection():
https://httpwg.org/specs/rfc9110.html#status.101
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
b"HTTP/1.1 101 Switching Protocols\r\n",
b"Connection: upgrade\r\n",
@@ -692,7 +685,7 @@ def test_http11_upgrade_connection():
b"...",
]
)
- with ConnectionPool(
+ with httpcore.ConnectionPool(
network_backend=network_backend, max_connections=1
) as pool:
with pool.stream(
diff --git a/tests/_sync/test_http11.py b/tests/_sync/test_http11.py
index 4965654ca..dcd80e842 100644
--- a/tests/_sync/test_http11.py
+++ b/tests/_sync/test_http11.py
@@ -1,19 +1,12 @@
import pytest
-from httpcore import (
- HTTP11Connection,
- ConnectionNotAvailable,
- LocalProtocolError,
- Origin,
- RemoteProtocolError,
-)
-from httpcore.backends.mock import MockStream
+import httpcore
def test_http11_connection():
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -22,7 +15,7 @@ def test_http11_connection():
b"Hello, world!",
]
)
- with HTTP11Connection(
+ with httpcore.HTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = conn.request("GET", "https://example.com/")
@@ -45,8 +38,8 @@ def test_http11_connection_unread_response():
If the client releases the response without reading it to termination,
then the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -55,7 +48,7 @@ def test_http11_connection_unread_response():
b"Hello, world!",
]
)
- with HTTP11Connection(origin=origin, stream=stream) as conn:
+ with httpcore.HTTP11Connection(origin=origin, stream=stream) as conn:
with conn.stream("GET", "https://example.com/") as response:
assert response.status == 200
@@ -75,10 +68,10 @@ def test_http11_connection_with_remote_protocol_error():
If a remote protocol error occurs, then no response will be returned,
and the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream([b"Wait, this isn't valid HTTP!", b""])
- with HTTP11Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream([b"Wait, this isn't valid HTTP!", b""])
+ with httpcore.HTTP11Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
conn.request("GET", "https://example.com/")
assert not conn.is_idle()
@@ -96,8 +89,8 @@ def test_http11_connection_with_incomplete_response():
"""
We should be gracefully handling the case where the connection ends prematurely.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -106,8 +99,8 @@ def test_http11_connection_with_incomplete_response():
b"Hello, wor",
]
)
- with HTTP11Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ with httpcore.HTTP11Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
conn.request("GET", "https://example.com/")
assert not conn.is_idle()
@@ -126,8 +119,8 @@ def test_http11_connection_with_local_protocol_error():
If a local protocol error occurs, then no response will be returned,
and the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -136,8 +129,8 @@ def test_http11_connection_with_local_protocol_error():
b"Hello, world!",
]
)
- with HTTP11Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(LocalProtocolError) as exc_info:
+ with httpcore.HTTP11Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.LocalProtocolError) as exc_info:
conn.request("GET", "https://example.com/", headers={"Host": "\0"})
assert str(exc_info.value) == "Illegal header value b'\\x00'"
@@ -158,8 +151,8 @@ def test_http11_connection_handles_one_active_request():
Attempting to send a request while one is already in-flight will raise
a ConnectionNotAvailable exception.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -168,9 +161,9 @@ def test_http11_connection_handles_one_active_request():
b"Hello, world!",
]
)
- with HTTP11Connection(origin=origin, stream=stream) as conn:
+ with httpcore.HTTP11Connection(origin=origin, stream=stream) as conn:
with conn.stream("GET", "https://example.com/"):
- with pytest.raises(ConnectionNotAvailable):
+ with pytest.raises(httpcore.ConnectionNotAvailable):
conn.request("GET", "https://example.com/")
@@ -179,8 +172,8 @@ def test_http11_connection_attempt_close():
"""
A connection can only be closed when it is idle.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 200 OK\r\n",
b"Content-Type: plain/text\r\n",
@@ -189,7 +182,7 @@ def test_http11_connection_attempt_close():
b"Hello, world!",
]
)
- with HTTP11Connection(origin=origin, stream=stream) as conn:
+ with httpcore.HTTP11Connection(origin=origin, stream=stream) as conn:
with conn.stream("GET", "https://example.com/") as response:
response.read()
assert response.status == 200
@@ -201,9 +194,9 @@ def test_http11_request_to_incorrect_origin():
"""
A connection can only send requests to whichever origin it is connected to.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream([])
- with HTTP11Connection(origin=origin, stream=stream) as conn:
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream([])
+ with httpcore.HTTP11Connection(origin=origin, stream=stream) as conn:
with pytest.raises(RuntimeError):
conn.request("GET", "https://other.com/")
@@ -217,8 +210,8 @@ def test_http11_expect_continue():
https://httpwg.org/specs/rfc9110.html#status.100
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 100 Continue\r\n",
b"\r\n",
@@ -229,7 +222,7 @@ def test_http11_expect_continue():
b"Hello, world!",
]
)
- with HTTP11Connection(
+ with httpcore.HTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = conn.request(
@@ -252,8 +245,8 @@ def test_http11_upgrade_connection():
https://httpwg.org/specs/rfc9110.html#status.101
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101
"""
- origin = Origin(b"wss", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"wss", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 101 Switching Protocols\r\n",
b"Connection: upgrade\r\n",
@@ -262,7 +255,7 @@ def test_http11_upgrade_connection():
b"...",
]
)
- with HTTP11Connection(
+ with httpcore.HTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
with conn.stream(
@@ -284,8 +277,8 @@ def test_http11_early_hints():
https://datatracker.ietf.org/doc/rfc8297/
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 103 Early Hints\r\n",
b"Link: ; rel=preload; as=style\r\n",
@@ -300,7 +293,7 @@ def test_http11_early_hints():
b"Hello, world! ...",
]
)
- with HTTP11Connection(
+ with httpcore.HTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = conn.request(
@@ -317,8 +310,8 @@ def test_http11_header_sub_100kb():
"""
A connection should be able to handle a http header size up to 100kB.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
b"HTTP/1.1 200 OK\r\n", # 17
b"Content-Type: plain/text\r\n", # 43
@@ -328,7 +321,7 @@ def test_http11_header_sub_100kb():
b"",
]
)
- with HTTP11Connection(
+ with httpcore.HTTP11Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = conn.request("GET", "https://example.com/")
diff --git a/tests/_sync/test_http2.py b/tests/_sync/test_http2.py
index 0adb96ef5..2cb353e10 100644
--- a/tests/_sync/test_http2.py
+++ b/tests/_sync/test_http2.py
@@ -2,19 +2,13 @@
import hyperframe.frame
import pytest
-from httpcore import (
- HTTP2Connection,
- ConnectionNotAvailable,
- Origin,
- RemoteProtocolError,
-)
-from httpcore.backends.mock import MockStream
+import httpcore
def test_http2_connection():
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -32,7 +26,7 @@ def test_http2_connection():
).serialize(),
]
)
- with HTTP2Connection(
+ with httpcore.HTTP2Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
response = conn.request("GET", "https://example.com/")
@@ -54,8 +48,8 @@ def test_http2_connection():
def test_http2_connection_closed():
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -75,12 +69,12 @@ def test_http2_connection_closed():
hyperframe.frame.GoAwayFrame(stream_id=0, error_code=0).serialize(),
]
)
- with HTTP2Connection(
+ with httpcore.HTTP2Connection(
origin=origin, stream=stream, keepalive_expiry=5.0
) as conn:
conn.request("GET", "https://example.com/")
- with pytest.raises(RemoteProtocolError):
+ with pytest.raises(httpcore.RemoteProtocolError):
conn.request("GET", "https://example.com/")
assert not conn.is_available()
@@ -88,8 +82,8 @@ def test_http2_connection_closed():
def test_http2_connection_post_request():
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -107,7 +101,7 @@ def test_http2_connection_post_request():
).serialize(),
]
)
- with HTTP2Connection(origin=origin, stream=stream) as conn:
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
response = conn.request(
"POST",
"https://example.com/",
@@ -124,10 +118,10 @@ def test_http2_connection_with_remote_protocol_error():
If a remote protocol error occurs, then no response will be returned,
and the connection will not be reusable.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream([b"Wait, this isn't valid HTTP!", b""])
- with HTTP2Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream([b"Wait, this isn't valid HTTP!", b""])
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
conn.request("GET", "https://example.com/")
@@ -137,8 +131,8 @@ def test_http2_connection_with_rst_stream():
If a stream reset occurs, then no response will be returned,
but the connection will remain reusable for other requests.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -170,8 +164,8 @@ def test_http2_connection_with_rst_stream():
b"",
]
)
- with HTTP2Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
conn.request("GET", "https://example.com/")
response = conn.request("GET", "https://example.com/")
assert response.status == 200
@@ -183,8 +177,8 @@ def test_http2_connection_with_goaway():
If a GoAway frame occurs, then no response will be returned,
and the connection will not be reusable for other requests.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -216,17 +210,17 @@ def test_http2_connection_with_goaway():
b"",
]
)
- with HTTP2Connection(origin=origin, stream=stream) as conn:
- with pytest.raises(RemoteProtocolError):
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
+ with pytest.raises(httpcore.RemoteProtocolError):
conn.request("GET", "https://example.com/")
- with pytest.raises(RemoteProtocolError):
+ with pytest.raises(httpcore.RemoteProtocolError):
conn.request("GET", "https://example.com/")
def test_http2_connection_with_flow_control():
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
# Available flow: 65,535
@@ -273,7 +267,7 @@ def test_http2_connection_with_flow_control():
).serialize(),
]
)
- with HTTP2Connection(origin=origin, stream=stream) as conn:
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
response = conn.request(
"POST",
"https://example.com/",
@@ -288,8 +282,8 @@ def test_http2_connection_attempt_close():
"""
A connection can only be closed when it is idle.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame().serialize(),
hyperframe.frame.HeadersFrame(
@@ -307,14 +301,14 @@ def test_http2_connection_attempt_close():
).serialize(),
]
)
- with HTTP2Connection(origin=origin, stream=stream) as conn:
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
with conn.stream("GET", "https://example.com/") as response:
response.read()
assert response.status == 200
assert response.content == b"Hello, world!"
conn.close()
- with pytest.raises(ConnectionNotAvailable):
+ with pytest.raises(httpcore.ConnectionNotAvailable):
conn.request("GET", "https://example.com/")
@@ -323,9 +317,9 @@ def test_http2_request_to_incorrect_origin():
"""
A connection can only send requests to whichever origin it is connected to.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream([])
- with HTTP2Connection(origin=origin, stream=stream) as conn:
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream([])
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
with pytest.raises(RuntimeError):
conn.request("GET", "https://other.com/")
@@ -336,8 +330,8 @@ def test_http2_remote_max_streams_update():
If the remote server updates the maximum concurrent streams value, we should
be adjusting how many streams we will allow.
"""
- origin = Origin(b"https", b"example.com", 443)
- stream = MockStream(
+ origin = httpcore.Origin(b"https", b"example.com", 443)
+ stream = httpcore.MockStream(
[
hyperframe.frame.SettingsFrame(
settings={hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 1000}
@@ -361,7 +355,7 @@ def test_http2_remote_max_streams_update():
).serialize(),
]
)
- with HTTP2Connection(origin=origin, stream=stream) as conn:
+ with httpcore.HTTP2Connection(origin=origin, stream=stream) as conn:
with conn.stream("GET", "https://example.com/") as response:
i = 0
for chunk in response.iter_stream():
diff --git a/tests/_sync/test_http_proxy.py b/tests/_sync/test_http_proxy.py
index f00995e21..2d66578e2 100644
--- a/tests/_sync/test_http_proxy.py
+++ b/tests/_sync/test_http_proxy.py
@@ -1,14 +1,19 @@
import ssl
import typing
-from typing import Optional
import hpack
import hyperframe.frame
import pytest
-from httpcore import HTTPProxy, Origin, ProxyError
-from httpcore.backends.base import SOCKET_OPTION, NetworkStream
-from httpcore.backends.mock import MockBackend, MockStream
+from httpcore import (
+ SOCKET_OPTION,
+ HTTPProxy,
+ MockBackend,
+ MockStream,
+ NetworkStream,
+ Origin,
+ ProxyError,
+)
@@ -127,8 +132,8 @@ class HTTP1ThenHTTP2Stream(MockStream):
def start_tls(
self,
ssl_context: ssl.SSLContext,
- server_hostname: Optional[str] = None,
- timeout: Optional[float] = None,
+ server_hostname: typing.Optional[str] = None,
+ timeout: typing.Optional[float] = None,
) -> NetworkStream:
self._http2 = True
return self
@@ -139,8 +144,8 @@ def connect_tcp(
self,
host: str,
port: int,
- timeout: Optional[float] = None,
- local_address: Optional[str] = None,
+ timeout: typing.Optional[float] = None,
+ local_address: typing.Optional[str] = None,
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
) -> NetworkStream:
return HTTP1ThenHTTP2Stream(list(self._buffer))
diff --git a/tests/_sync/test_integration.py b/tests/_sync/test_integration.py
index 42bf70b08..e3327e696 100644
--- a/tests/_sync/test_integration.py
+++ b/tests/_sync/test_integration.py
@@ -2,12 +2,12 @@
import pytest
-from httpcore import ConnectionPool
+import httpcore
def test_request(httpbin):
- with ConnectionPool() as pool:
+ with httpcore.ConnectionPool() as pool:
response = pool.request("GET", httpbin.url)
assert response.status == 200
@@ -17,7 +17,7 @@ def test_ssl_request(httpbin_secure):
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
- with ConnectionPool(ssl_context=ssl_context) as pool:
+ with httpcore.ConnectionPool(ssl_context=ssl_context) as pool:
response = pool.request("GET", httpbin_secure.url)
assert response.status == 200
@@ -27,7 +27,7 @@ def test_extra_info(httpbin_secure):
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
- with ConnectionPool(ssl_context=ssl_context) as pool:
+ with httpcore.ConnectionPool(ssl_context=ssl_context) as pool:
with pool.stream("GET", httpbin_secure.url) as response:
assert response.status == 200
stream = response.extensions["network_stream"]
diff --git a/tests/_sync/test_socks_proxy.py b/tests/_sync/test_socks_proxy.py
index 39108c8f2..2d39bb97a 100644
--- a/tests/_sync/test_socks_proxy.py
+++ b/tests/_sync/test_socks_proxy.py
@@ -1,7 +1,6 @@
import pytest
-from httpcore import SOCKSProxy, Origin, ProxyError
-from httpcore.backends.mock import MockBackend
+import httpcore
@@ -9,7 +8,7 @@ def test_socks5_request():
"""
Send an HTTP request via a SOCKS proxy.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
# The initial socks CONNECT
# v5 NOAUTH
@@ -25,7 +24,7 @@ def test_socks5_request():
]
)
- with SOCKSProxy(
+ with httpcore.SOCKSProxy(
proxy_url="socks5://localhost:8080/",
network_backend=network_backend,
) as proxy:
@@ -49,16 +48,16 @@ def test_socks5_request():
# 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)
+ httpcore.Origin(b"http", b"example.com", 80)
)
assert not proxy.connections[0].can_handle_request(
- Origin(b"http", b"other.com", 80)
+ httpcore.Origin(b"http", b"other.com", 80)
)
assert proxy.connections[0].can_handle_request(
- Origin(b"https", b"example.com", 443)
+ httpcore.Origin(b"https", b"example.com", 443)
)
assert not proxy.connections[0].can_handle_request(
- Origin(b"https", b"other.com", 443)
+ httpcore.Origin(b"https", b"other.com", 443)
)
@@ -67,7 +66,7 @@ def test_authenticated_socks5_request():
"""
Send an HTTP request via a SOCKS proxy.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
# The initial socks CONNECT
# v5 USERNAME/PASSWORD
@@ -85,7 +84,7 @@ def test_authenticated_socks5_request():
]
)
- with SOCKSProxy(
+ with httpcore.SOCKSProxy(
proxy_url="socks5://localhost:8080/",
proxy_auth=(b"username", b"password"),
network_backend=network_backend,
@@ -114,7 +113,7 @@ def test_socks5_request_connect_failed():
"""
Attempt to send an HTTP request via a SOCKS proxy, resulting in a connect failure.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
# The initial socks CONNECT
# v5 NOAUTH
@@ -124,12 +123,12 @@ def test_socks5_request_connect_failed():
]
)
- with SOCKSProxy(
+ with httpcore.SOCKSProxy(
proxy_url="socks5://localhost:8080/",
network_backend=network_backend,
) as proxy:
# Sending a request, which the proxy rejects
- with pytest.raises(ProxyError) as exc_info:
+ with pytest.raises(httpcore.ProxyError) as exc_info:
proxy.request("GET", "https://example.com/")
assert (
str(exc_info.value) == "Proxy Server could not connect: Connection refused."
@@ -144,19 +143,19 @@ def test_socks5_request_failed_to_provide_auth():
Attempt to send an HTTP request via an authenticated SOCKS proxy,
without providing authentication credentials.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
# v5 USERNAME/PASSWORD
b"\x05\x02",
]
)
- with SOCKSProxy(
+ with httpcore.SOCKSProxy(
proxy_url="socks5://localhost:8080/",
network_backend=network_backend,
) as proxy:
# Sending a request, which the proxy rejects
- with pytest.raises(ProxyError) as exc_info:
+ with pytest.raises(httpcore.ProxyError) as exc_info:
proxy.request("GET", "https://example.com/")
assert (
str(exc_info.value)
@@ -172,7 +171,7 @@ def test_socks5_request_incorrect_auth():
Attempt to send an HTTP request via an authenticated SOCKS proxy,
wit incorrect authentication credentials.
"""
- network_backend = MockBackend(
+ network_backend = httpcore.MockBackend(
[
# v5 USERNAME/PASSWORD
b"\x05\x02",
@@ -181,13 +180,13 @@ def test_socks5_request_incorrect_auth():
]
)
- with SOCKSProxy(
+ with httpcore.SOCKSProxy(
proxy_url="socks5://localhost:8080/",
proxy_auth=(b"invalid", b"invalid"),
network_backend=network_backend,
) as proxy:
# Sending a request, which the proxy rejects
- with pytest.raises(ProxyError) as exc_info:
+ with pytest.raises(httpcore.ProxyError) as exc_info:
proxy.request("GET", "https://example.com/")
assert str(exc_info.value) == "Invalid username/password"
diff --git a/tests/test_models.py b/tests/test_models.py
index 056197547..104da310a 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,4 +1,4 @@
-from typing import AsyncIterator, Iterator, List
+import typing
import pytest
@@ -95,10 +95,10 @@ def test_response():
class ByteIterator:
- def __init__(self, chunks: List[bytes]) -> None:
+ def __init__(self, chunks: typing.List[bytes]) -> None:
self._chunks = chunks
- def __iter__(self) -> Iterator[bytes]:
+ def __iter__(self) -> typing.Iterator[bytes]:
for chunk in self._chunks:
yield chunk
@@ -130,10 +130,10 @@ def test_response_sync_streaming():
class AsyncByteIterator:
- def __init__(self, chunks: List[bytes]) -> None:
+ def __init__(self, chunks: typing.List[bytes]) -> None:
self._chunks = chunks
- async def __aiter__(self) -> AsyncIterator[bytes]:
+ async def __aiter__(self) -> typing.AsyncIterator[bytes]:
for chunk in self._chunks:
yield chunk
diff --git a/unasync.py b/unasync.py
index 60888596b..d3607cd4e 100755
--- a/unasync.py
+++ b/unasync.py
@@ -4,7 +4,7 @@
import sys
SUBS = [
- ('from ..backends.auto import AutoBackend', 'from ..backends.sync import SyncBackend'),
+ ('from .._backends.auto import AutoBackend', 'from .._backends.sync import SyncBackend'),
('import trio as concurrency', 'from tests import concurrency'),
('AsyncByteStream', 'SyncByteStream'),
('AsyncIterator', 'Iterator'),