]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Drop 'adapters' 55/head
authorTom Christie <tom@tomchristie.com>
Mon, 13 May 2019 10:54:51 +0000 (11:54 +0100)
committerTom Christie <tom@tomchristie.com>
Mon, 13 May 2019 10:54:51 +0000 (11:54 +0100)
16 files changed:
httpcore/__init__.py
httpcore/adapters/__init__.py [deleted file]
httpcore/adapters/authentication.py [deleted file]
httpcore/adapters/cookies.py [deleted file]
httpcore/adapters/environment.py [deleted file]
httpcore/adapters/redirects.py [deleted file]
httpcore/client.py
httpcore/dispatch/connection.py
httpcore/dispatch/connection_pool.py
httpcore/dispatch/http11.py
httpcore/dispatch/http2.py
httpcore/interfaces.py
tests/client/test_client.py [moved from tests/test_client.py with 100% similarity]
tests/client/test_redirects.py [moved from tests/adapters/test_redirects.py with 87% similarity]
tests/dispatch/test_connections.py
tests/dispatch/test_http2.py

index 8ab2b3c9199852d8add3e1406bb8953ef644c4a4..19987176c1fd3c3f65d8137d36f0e6a74122d9df 100644 (file)
@@ -1,4 +1,3 @@
-from .adapters.redirects import RedirectAdapter
 from .backends.sync import SyncClient
 from .client import Client
 from .config import PoolLimits, SSLConfig, TimeoutConfig
@@ -22,7 +21,7 @@ from .exceptions import (
     Timeout,
     TooManyRedirects,
 )
-from .interfaces import Adapter, BaseReader, BaseWriter
+from .interfaces import BaseReader, BaseWriter, Dispatcher
 from .models import URL, Headers, Origin, QueryParams, Request, Response
 
 __version__ = "0.2.1"
diff --git a/httpcore/adapters/__init__.py b/httpcore/adapters/__init__.py
deleted file mode 100644 (file)
index 8d4629f..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-"""
-Adapter classes layer additional behavior over the raw dispatching of the
-HTTP request/response.
-"""
diff --git a/httpcore/adapters/authentication.py b/httpcore/adapters/authentication.py
deleted file mode 100644 (file)
index cb5ae99..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-import typing
-
-from ..interfaces import Adapter
-from ..models import Request, Response
-
-
-class AuthenticationAdapter(Adapter):
-    def __init__(self, dispatch: Adapter):
-        self.dispatch = dispatch
-
-    def prepare_request(self, request: Request) -> None:
-        self.dispatch.prepare_request(request)
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
-        return await self.dispatch.send(request, **options)
-
-    async def close(self) -> None:
-        await self.dispatch.close()
diff --git a/httpcore/adapters/cookies.py b/httpcore/adapters/cookies.py
deleted file mode 100644 (file)
index 1105152..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-import typing
-
-from ..interfaces import Adapter
-from ..models import Request, Response
-
-
-class CookieAdapter(Adapter):
-    def __init__(self, dispatch: Adapter):
-        self.dispatch = dispatch
-
-    def prepare_request(self, request: Request) -> None:
-        self.dispatch.prepare_request(request)
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
-        return await self.dispatch.send(request, **options)
-
-    async def close(self) -> None:
-        await self.dispatch.close()
diff --git a/httpcore/adapters/environment.py b/httpcore/adapters/environment.py
deleted file mode 100644 (file)
index 9840a92..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import typing
-
-from ..interfaces import Adapter
-from ..models import Request, Response
-
-
-class EnvironmentAdapter(Adapter):
-    def __init__(self, dispatch: Adapter, trust_env: bool = True):
-        self.dispatch = dispatch
-        self.trust_env = trust_env
-
-    def prepare_request(self, request: Request) -> None:
-        self.dispatch.prepare_request(request)
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
-        if self.trust_env:
-            self.merge_environment_options(options)
-        return await self.dispatch.send(request, **options)
-
-    async def close(self) -> None:
-        await self.dispatch.close()
-
-    def merge_environment_options(self, options: dict) -> None:
-        """
-        Add environment options.
-        """
-        #  TODO
diff --git a/httpcore/adapters/redirects.py b/httpcore/adapters/redirects.py
deleted file mode 100644 (file)
index 08639cd..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-import typing
-
-from ..config import DEFAULT_MAX_REDIRECTS
-from ..constants import codes
-from ..exceptions import RedirectBodyUnavailable, RedirectLoop, TooManyRedirects
-from ..interfaces import Adapter
-from ..models import URL, Headers, Request, Response
-
-
-class RedirectAdapter(Adapter):
-    def __init__(self, dispatch: Adapter, max_redirects: int = DEFAULT_MAX_REDIRECTS):
-        self.dispatch = dispatch
-        self.max_redirects = max_redirects
-
-    def prepare_request(self, request: Request) -> None:
-        self.dispatch.prepare_request(request)
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
-        allow_redirects = options.pop("allow_redirects", True)  # type: bool
-
-        # The following will not typically be specified by the end-user developer,
-        # but are included in `response.next()` calls.
-        history = options.pop("history", [])  # type: typing.List[Response]
-        seen_urls = options.pop("seen_urls", set())  # type: typing.Set[URL]
-
-        while True:
-            # We perform these checks here, so that calls to `response.next()`
-            # will raise redirect errors if appropriate.
-            if len(history) > self.max_redirects:
-                raise TooManyRedirects()
-            if request.url in seen_urls:
-                raise RedirectLoop()
-
-            response = await self.dispatch.send(request, **options)
-            response.history = list(history)
-            if not response.is_redirect:
-                break
-
-            history.insert(0, response)
-            seen_urls.add(request.url)
-
-            if allow_redirects:
-                request = self.build_redirect_request(request, response)
-            else:
-                next_options = dict(options)
-                next_options["seen_urls"] = seen_urls
-                next_options["history"] = history
-
-                async def send_next() -> Response:
-                    nonlocal request, response, next_options
-                    request = self.build_redirect_request(request, response)
-                    response = await self.send(request, **next_options)
-                    return response
-
-                response.next = send_next  # type: ignore
-                break
-
-        return response
-
-    async def close(self) -> None:
-        await self.dispatch.close()
-
-    def build_redirect_request(self, request: Request, response: Response) -> Request:
-        method = self.redirect_method(request, response)
-        url = self.redirect_url(request, response)
-        headers = self.redirect_headers(request, url)
-        content = self.redirect_content(request, method)
-        return Request(method=method, url=url, headers=headers, data=content)
-
-    def redirect_method(self, request: Request, response: Response) -> str:
-        """
-        When being redirected we may want to change the method of the request
-        based on certain specs or browser behavior.
-        """
-        method = request.method
-
-        # https://tools.ietf.org/html/rfc7231#section-6.4.4
-        if response.status_code == codes.see_other and method != "HEAD":
-            method = "GET"
-
-        # Do what the browsers do, despite standards...
-        # Turn 302s into GETs.
-        if response.status_code == codes.found and method != "HEAD":
-            method = "GET"
-
-        # If a POST is responded to with a 301, turn it into a GET.
-        # This bizarre behaviour is explained in 'requests' issue 1704.
-        if response.status_code == codes.moved_permanently and method == "POST":
-            method = "GET"
-
-        return method
-
-    def redirect_url(self, request: Request, response: Response) -> URL:
-        """
-        Return the URL for the redirect to follow.
-        """
-        location = response.headers["Location"]
-
-        url = URL(location, allow_relative=True)
-
-        # Facilitate relative 'Location' headers, as allowed by RFC 7231.
-        # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
-        if url.is_relative_url:
-            url = url.resolve_with(request.url)
-
-        # Attach previous fragment if needed (RFC 7231 7.1.2)
-        if request.url.fragment and not url.fragment:
-            url = url.copy_with(fragment=request.url.fragment)
-
-        return url
-
-    def redirect_headers(self, request: Request, url: URL) -> Headers:
-        """
-        Strip Authorization headers when responses are redirected away from
-        the origin.
-        """
-        headers = Headers(request.headers)
-        if url.origin != request.url.origin:
-            del headers["Authorization"]
-        return headers
-
-    def redirect_content(self, request: Request, method: str) -> bytes:
-        """
-        Return the body that should be used for the redirect request.
-        """
-        if method != request.method and method == "GET":
-            return b""
-        if request.is_streaming:
-            raise RedirectBodyUnavailable()
-        return request.content
index 16925b3101f949bbb7c0d3443e4faf34eca23ddd..04be87e28ecefe55b7233190e12822fea474805c 100644 (file)
@@ -1,10 +1,6 @@
 import typing
 from types import TracebackType
 
-from .adapters.authentication import AuthenticationAdapter
-from .adapters.cookies import CookieAdapter
-from .adapters.environment import EnvironmentAdapter
-from .adapters.redirects import RedirectAdapter
 from .config import (
     DEFAULT_MAX_REDIRECTS,
     DEFAULT_POOL_LIMITS,
@@ -14,9 +10,13 @@ from .config import (
     SSLConfig,
     TimeoutConfig,
 )
+from .constants import codes
 from .dispatch.connection_pool import ConnectionPool
+from .exceptions import RedirectBodyUnavailable, RedirectLoop, TooManyRedirects
+from .interfaces import Dispatcher
 from .models import (
     URL,
+    Headers,
     HeaderTypes,
     QueryParamTypes,
     Request,
@@ -33,42 +33,13 @@ class Client:
         timeout: TimeoutConfig = DEFAULT_TIMEOUT_CONFIG,
         pool_limits: PoolLimits = DEFAULT_POOL_LIMITS,
         max_redirects: int = DEFAULT_MAX_REDIRECTS,
+        dispatch: Dispatcher = None,
     ):
-        connection_pool = ConnectionPool(
-            ssl=ssl, timeout=timeout, pool_limits=pool_limits
-        )
-        cookie_adapter = CookieAdapter(dispatch=connection_pool)
-        auth_adapter = AuthenticationAdapter(dispatch=cookie_adapter)
-        redirect_adapter = RedirectAdapter(
-            dispatch=auth_adapter, max_redirects=max_redirects
-        )
-        self.adapter = EnvironmentAdapter(dispatch=redirect_adapter)
+        if dispatch is None:
+            dispatch = ConnectionPool(ssl=ssl, timeout=timeout, pool_limits=pool_limits)
 
-    async def request(
-        self,
-        method: str,
-        url: URLTypes,
-        *,
-        data: RequestData = b"",
-        query_params: QueryParamTypes = None,
-        headers: HeaderTypes = None,
-        stream: bool = False,
-        allow_redirects: bool = True,
-        ssl: SSLConfig = None,
-        timeout: TimeoutConfig = None,
-    ) -> Response:
-        request = Request(
-            method, url, data=data, query_params=query_params, headers=headers
-        )
-        self.prepare_request(request)
-        response = await self.send(
-            request,
-            stream=stream,
-            allow_redirects=allow_redirects,
-            ssl=ssl,
-            timeout=timeout,
-        )
-        return response
+        self.max_redirects = max_redirects
+        self.dispatch = dispatch
 
     async def get(
         self,
@@ -232,32 +203,157 @@ class Client:
             timeout=timeout,
         )
 
+    async def request(
+        self,
+        method: str,
+        url: URLTypes,
+        *,
+        data: RequestData = b"",
+        query_params: QueryParamTypes = None,
+        headers: HeaderTypes = None,
+        stream: bool = False,
+        allow_redirects: bool = True,
+        ssl: SSLConfig = None,
+        timeout: TimeoutConfig = None,
+    ) -> Response:
+        request = Request(
+            method, url, data=data, query_params=query_params, headers=headers
+        )
+        self.prepare_request(request)
+        response = await self.send(
+            request,
+            stream=stream,
+            allow_redirects=allow_redirects,
+            ssl=ssl,
+            timeout=timeout,
+        )
+        return response
+
     def prepare_request(self, request: Request) -> None:
-        self.adapter.prepare_request(request)
+        request.prepare()
 
     async def send(
         self,
         request: Request,
         *,
         stream: bool = False,
-        allow_redirects: bool = True,
         ssl: SSLConfig = None,
         timeout: TimeoutConfig = None,
+        allow_redirects: bool = True,
+        history: typing.List[Response] = None,
     ) -> Response:
-        options = {
-            "stream": stream,
-            "allow_redirects": allow_redirects,
-        }  # type: typing.Dict[str, typing.Any]
+        if history is None:
+            history = []
+
+        while True:
+            # We perform these checks here, so that calls to `response.next()`
+            # will raise redirect errors if appropriate.
+            if len(history) > self.max_redirects:
+                raise TooManyRedirects()
+            if request.url in [response.url for response in history]:
+                raise RedirectLoop()
+
+            response = await self.dispatch.send(
+                request, stream=stream, ssl=ssl, timeout=timeout
+            )
+            response.history = list(history)
+            history = [response] + history
+            if not response.is_redirect:
+                break
+
+            if allow_redirects:
+                request = self.build_redirect_request(request, response)
+            else:
+
+                async def send_next() -> Response:
+                    nonlocal request, response, ssl, allow_redirects, timeout, history
+                    request = self.build_redirect_request(request, response)
+                    response = await self.send(
+                        request,
+                        stream=stream,
+                        allow_redirects=allow_redirects,
+                        ssl=ssl,
+                        timeout=timeout,
+                        history=history,
+                    )
+                    return response
+
+                response.next = send_next  # type: ignore
+                break
+
+        return response
+
+    def build_redirect_request(self, request: Request, response: Response) -> Request:
+        method = self.redirect_method(request, response)
+        url = self.redirect_url(request, response)
+        headers = self.redirect_headers(request, url)
+        content = self.redirect_content(request, method)
+        return Request(method=method, url=url, headers=headers, data=content)
+
+    def redirect_method(self, request: Request, response: Response) -> str:
+        """
+        When being redirected we may want to change the method of the request
+        based on certain specs or browser behavior.
+        """
+        method = request.method
+
+        # https://tools.ietf.org/html/rfc7231#section-6.4.4
+        if response.status_code == codes.see_other and method != "HEAD":
+            method = "GET"
+
+        # Do what the browsers do, despite standards...
+        # Turn 302s into GETs.
+        if response.status_code == codes.found and method != "HEAD":
+            method = "GET"
+
+        # If a POST is responded to with a 301, turn it into a GET.
+        # This bizarre behaviour is explained in 'requests' issue 1704.
+        if response.status_code == codes.moved_permanently and method == "POST":
+            method = "GET"
+
+        return method
+
+    def redirect_url(self, request: Request, response: Response) -> URL:
+        """
+        Return the URL for the redirect to follow.
+        """
+        location = response.headers["Location"]
+
+        url = URL(location, allow_relative=True)
+
+        # Facilitate relative 'Location' headers, as allowed by RFC 7231.
+        # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
+        if url.is_relative_url:
+            url = url.resolve_with(request.url)
+
+        # Attach previous fragment if needed (RFC 7231 7.1.2)
+        if request.url.fragment and not url.fragment:
+            url = url.copy_with(fragment=request.url.fragment)
+
+        return url
 
-        if ssl is not None:
-            options["ssl"] = ssl
-        if timeout is not None:
-            options["timeout"] = timeout
+    def redirect_headers(self, request: Request, url: URL) -> Headers:
+        """
+        Strip Authorization headers when responses are redirected away from
+        the origin.
+        """
+        headers = Headers(request.headers)
+        if url.origin != request.url.origin:
+            del headers["Authorization"]
+        return headers
 
-        return await self.adapter.send(request, **options)
+    def redirect_content(self, request: Request, method: str) -> bytes:
+        """
+        Return the body that should be used for the redirect request.
+        """
+        if method != request.method and method == "GET":
+            return b""
+        if request.is_streaming:
+            raise RedirectBodyUnavailable()
+        return request.content
 
     async def close(self) -> None:
-        await self.adapter.close()
+        await self.dispatch.close()
 
     async def __aenter__(self) -> "Client":
         return self
index f1a63f80382c6fcfd4b4c253198ad9b81c7a5c1f..e5622d1d6a92719a990f19eec1afd2dc1d0c9d38 100644 (file)
@@ -13,7 +13,7 @@ from ..config import (
 )
 from ..constants import Protocol
 from ..exceptions import ConnectTimeout
-from ..interfaces import Adapter
+from ..interfaces import Dispatcher
 from ..models import Origin, Request, Response
 from .http2 import HTTP2Connection
 from .http11 import HTTP11Connection
@@ -22,7 +22,7 @@ from .http11 import HTTP11Connection
 ReleaseCallback = typing.Callable[["HTTPConnection"], typing.Awaitable[None]]
 
 
-class HTTPConnection(Adapter):
+class HTTPConnection(Dispatcher):
     def __init__(
         self,
         origin: typing.Union[str, Origin],
@@ -37,26 +37,35 @@ class HTTPConnection(Adapter):
         self.h11_connection = None  # type: typing.Optional[HTTP11Connection]
         self.h2_connection = None  # type: typing.Optional[HTTP2Connection]
 
-    def prepare_request(self, request: Request) -> None:
-        request.prepare()
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
+    async def send(
+        self,
+        request: Request,
+        stream: bool = False,
+        ssl: SSLConfig = None,
+        timeout: TimeoutConfig = None,
+    ) -> Response:
         if self.h11_connection is None and self.h2_connection is None:
-            await self.connect(**options)
+            await self.connect(ssl=ssl, timeout=timeout)
 
         if self.h2_connection is not None:
-            response = await self.h2_connection.send(request, **options)
+            response = await self.h2_connection.send(
+                request, stream=stream, timeout=timeout
+            )
         else:
             assert self.h11_connection is not None
-            response = await self.h11_connection.send(request, **options)
+            response = await self.h11_connection.send(
+                request, stream=stream, timeout=timeout
+            )
 
         return response
 
-    async def connect(self, **options: typing.Any) -> None:
-        ssl = options.get("ssl", self.ssl)
-        timeout = options.get("timeout", self.timeout)
-        assert isinstance(ssl, SSLConfig)
-        assert isinstance(timeout, TimeoutConfig)
+    async def connect(
+        self, ssl: SSLConfig = None, timeout: TimeoutConfig = None
+    ) -> None:
+        if ssl is None:
+            ssl = self.ssl
+        if timeout is None:
+            timeout = self.timeout
 
         host = self.origin.host
         port = self.origin.port
index f10b14ce6b391c67288f0b5f3bcb41c3a4de1c62..88bb36ca1f79f4a94fe6d841c3dd68540c371bf2 100644 (file)
@@ -13,7 +13,7 @@ from ..config import (
 )
 from ..decoders import ACCEPT_ENCODING
 from ..exceptions import PoolTimeout
-from ..interfaces import Adapter
+from ..interfaces import Dispatcher
 from ..models import Origin, Request, Response
 from .connection import HTTPConnection
 
@@ -83,7 +83,7 @@ class ConnectionStore(collections.abc.Sequence):
         return len(self.all)
 
 
-class ConnectionPool(Adapter):
+class ConnectionPool(Dispatcher):
     def __init__(
         self,
         *,
@@ -104,13 +104,18 @@ class ConnectionPool(Adapter):
     def num_connections(self) -> int:
         return len(self.keepalive_connections) + len(self.active_connections)
 
-    def prepare_request(self, request: Request) -> None:
-        request.prepare()
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
+    async def send(
+        self,
+        request: Request,
+        stream: bool = False,
+        ssl: SSLConfig = None,
+        timeout: TimeoutConfig = None,
+    ) -> Response:
         connection = await self.acquire_connection(request.url.origin)
         try:
-            response = await connection.send(request, **options)
+            response = await connection.send(
+                request, stream=stream, ssl=ssl, timeout=timeout
+            )
         except BaseException as exc:
             self.active_connections.remove(connection)
             self.max_connections.release()
index 107fa06b03cc3519d15b1830779dc18cd2af4b58..39f72db42afa9c82208d2fc9507e9469768123d3 100644 (file)
@@ -9,7 +9,7 @@ from ..config import (
     TimeoutConfig,
 )
 from ..exceptions import ConnectTimeout, ReadTimeout
-from ..interfaces import Adapter, BaseReader, BaseWriter
+from ..interfaces import BaseReader, BaseWriter, Dispatcher
 from ..models import Request, Response
 
 H11Event = typing.Union[
@@ -30,7 +30,7 @@ OptionalTimeout = typing.Optional[TimeoutConfig]
 OnReleaseCallback = typing.Callable[[], typing.Awaitable[None]]
 
 
-class HTTP11Connection(Adapter):
+class HTTP11Connection:
     READ_NUM_BYTES = 4096
 
     def __init__(
@@ -44,14 +44,9 @@ class HTTP11Connection(Adapter):
         self.on_release = on_release
         self.h11_state = h11.Connection(our_role=h11.CLIENT)
 
-    def prepare_request(self, request: Request) -> None:
-        request.prepare()
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
-        timeout = options.get("timeout")
-        stream = options.get("stream", False)
-        assert timeout is None or isinstance(timeout, TimeoutConfig)
-
+    async def send(
+        self, request: Request, stream: bool = False, timeout: TimeoutConfig = None
+    ) -> Response:
         #  Start sending the request.
         method = request.method.encode("ascii")
         target = request.url.full_path.encode("ascii")
index 3cd15d5a607b59c38ad04d1f015d98eb2906ec7e..301a36c4df1be14a89556b8a351797af7ab5bcab 100644 (file)
@@ -11,13 +11,13 @@ from ..config import (
     TimeoutConfig,
 )
 from ..exceptions import ConnectTimeout, ReadTimeout
-from ..interfaces import Adapter, BaseReader, BaseWriter
+from ..interfaces import BaseReader, BaseWriter, Dispatcher
 from ..models import Request, Response
 
 OptionalTimeout = typing.Optional[TimeoutConfig]
 
 
-class HTTP2Connection(Adapter):
+class HTTP2Connection:
     READ_NUM_BYTES = 4096
 
     def __init__(
@@ -30,14 +30,9 @@ class HTTP2Connection(Adapter):
         self.events = {}  # type: typing.Dict[int, typing.List[h2.events.Event]]
         self.initialized = False
 
-    def prepare_request(self, request: Request) -> None:
-        request.prepare()
-
-    async def send(self, request: Request, **options: typing.Any) -> Response:
-        timeout = options.get("timeout")
-        stream = options.get("stream", False)
-        assert timeout is None or isinstance(timeout, TimeoutConfig)
-
+    async def send(
+        self, request: Request, stream: bool = False, timeout: TimeoutConfig = None
+    ) -> Response:
         #  Start sending the request.
         if not self.initialized:
             self.initiate_connection()
index 5903c454566d6eae01e8b0b1f3196d209c35473b..b530ab49b1436b628ef64e612b4e2715e62c166c 100644 (file)
@@ -1,9 +1,10 @@
 import typing
 from types import TracebackType
 
-from .config import TimeoutConfig
+from .config import SSLConfig, TimeoutConfig
 from .models import (
     URL,
+    Headers,
     HeaderTypes,
     QueryParamTypes,
     Request,
@@ -15,7 +16,7 @@ from .models import (
 OptionalTimeout = typing.Optional[TimeoutConfig]
 
 
-class Adapter:
+class Dispatcher:
     """
     The base class for all adapter or dispatcher classes.
 
@@ -32,25 +33,33 @@ class Adapter:
         data: RequestData = b"",
         query_params: QueryParamTypes = None,
         headers: HeaderTypes = None,
-        **options: typing.Any,
+        stream: bool = False,
+        ssl: SSLConfig = None,
+        timeout: TimeoutConfig = None
     ) -> Response:
         request = Request(
             method, url, data=data, query_params=query_params, headers=headers
         )
         self.prepare_request(request)
-        response = await self.send(request, **options)
+        response = await self.send(request, stream=stream, ssl=ssl, timeout=timeout)
         return response
 
     def prepare_request(self, request: Request) -> None:
-        raise NotImplementedError()  # pragma: nocover
+        request.prepare()
 
-    async def send(self, request: Request, **options: typing.Any) -> Response:
+    async def send(
+        self,
+        request: Request,
+        stream: bool = False,
+        ssl: SSLConfig = None,
+        timeout: TimeoutConfig = None,
+    ) -> Response:
         raise NotImplementedError()  # pragma: nocover
 
     async def close(self) -> None:
-        raise NotImplementedError()  # pragma: nocover
+        pass  # pragma: nocover
 
-    async def __aenter__(self) -> "Adapter":
+    async def __aenter__(self) -> "Dispatcher":
         return self
 
     async def __aexit__(
similarity index 87%
rename from tests/adapters/test_redirects.py
rename to tests/client/test_redirects.py
index 94e5a745db77de995f8af1df62a917b382a9cbae..6f915366f96462499223dde01848d3d92a8fde4d 100644 (file)
@@ -5,22 +5,27 @@ import pytest
 
 from httpcore import (
     URL,
-    Adapter,
-    RedirectAdapter,
+    Client,
+    Dispatcher,
     RedirectBodyUnavailable,
     RedirectLoop,
     Request,
     Response,
+    SSLConfig,
+    TimeoutConfig,
     TooManyRedirects,
     codes,
 )
 
 
-class MockDispatch(Adapter):
-    def prepare_request(self, request: Request) -> None:
-        pass
-
-    async def send(self, request: Request, **options) -> Response:
+class MockDispatch(Dispatcher):
+    async def send(
+        self,
+        request: Request,
+        stream: bool = False,
+        ssl: SSLConfig = None,
+        timeout: TimeoutConfig = None,
+    ) -> Response:
         if request.url.path == "/redirect_301":
             status_code = codes.moved_permanently
             headers = {"location": "https://example.org/"}
@@ -83,7 +88,7 @@ class MockDispatch(Adapter):
 
 @pytest.mark.asyncio
 async def test_redirect_301():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     response = await client.request("POST", "https://example.org/redirect_301")
     assert response.status_code == codes.ok
     assert response.url == URL("https://example.org/")
@@ -92,7 +97,7 @@ async def test_redirect_301():
 
 @pytest.mark.asyncio
 async def test_redirect_302():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     response = await client.request("POST", "https://example.org/redirect_302")
     assert response.status_code == codes.ok
     assert response.url == URL("https://example.org/")
@@ -101,7 +106,7 @@ async def test_redirect_302():
 
 @pytest.mark.asyncio
 async def test_redirect_303():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     response = await client.request("GET", "https://example.org/redirect_303")
     assert response.status_code == codes.ok
     assert response.url == URL("https://example.org/")
@@ -110,7 +115,7 @@ async def test_redirect_303():
 
 @pytest.mark.asyncio
 async def test_disallow_redirects():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     response = await client.request(
         "POST", "https://example.org/redirect_303", allow_redirects=False
     )
@@ -126,7 +131,7 @@ async def test_disallow_redirects():
 
 @pytest.mark.asyncio
 async def test_relative_redirect():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     response = await client.request("GET", "https://example.org/relative_redirect")
     assert response.status_code == codes.ok
     assert response.url == URL("https://example.org/")
@@ -135,7 +140,7 @@ async def test_relative_redirect():
 
 @pytest.mark.asyncio
 async def test_no_scheme_redirect():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     response = await client.request("GET", "https://example.org/no_scheme_redirect")
     assert response.status_code == codes.ok
     assert response.url == URL("https://example.org/")
@@ -144,7 +149,7 @@ async def test_no_scheme_redirect():
 
 @pytest.mark.asyncio
 async def test_fragment_redirect():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.org/relative_redirect#fragment"
     response = await client.request("GET", url)
     assert response.status_code == codes.ok
@@ -154,7 +159,7 @@ async def test_fragment_redirect():
 
 @pytest.mark.asyncio
 async def test_multiple_redirects():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.org/multiple_redirects?count=20"
     response = await client.request("GET", url)
     assert response.status_code == codes.ok
@@ -164,14 +169,14 @@ async def test_multiple_redirects():
 
 @pytest.mark.asyncio
 async def test_too_many_redirects():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     with pytest.raises(TooManyRedirects):
         await client.request("GET", "https://example.org/multiple_redirects?count=21")
 
 
 @pytest.mark.asyncio
 async def test_too_many_redirects_calling_next():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.org/multiple_redirects?count=21"
     response = await client.request("GET", url, allow_redirects=False)
     with pytest.raises(TooManyRedirects):
@@ -181,14 +186,14 @@ async def test_too_many_redirects_calling_next():
 
 @pytest.mark.asyncio
 async def test_redirect_loop():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     with pytest.raises(RedirectLoop):
         await client.request("GET", "https://example.org/redirect_loop")
 
 
 @pytest.mark.asyncio
 async def test_redirect_loop_calling_next():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.org/redirect_loop"
     response = await client.request("GET", url, allow_redirects=False)
     with pytest.raises(RedirectLoop):
@@ -198,29 +203,29 @@ async def test_redirect_loop_calling_next():
 
 @pytest.mark.asyncio
 async def test_cross_domain_redirect():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.com/cross_domain"
     headers = {"Authorization": "abc"}
     response = await client.request("GET", url, headers=headers)
     data = json.loads(response.content.decode())
     assert response.url == URL("https://example.org/cross_domain_target")
-    assert data == {"headers": {}}
+    assert "authorization" not in data["headers"]
 
 
 @pytest.mark.asyncio
 async def test_same_domain_redirect():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.org/cross_domain"
     headers = {"Authorization": "abc"}
     response = await client.request("GET", url, headers=headers)
     data = json.loads(response.content.decode())
     assert response.url == URL("https://example.org/cross_domain_target")
-    assert data == {"headers": {"authorization": "abc"}}
+    assert data["headers"]["authorization"] == "abc"
 
 
 @pytest.mark.asyncio
 async def test_body_redirect():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.org/redirect_body"
     data = b"Example request body"
     response = await client.request("POST", url, data=data)
@@ -231,7 +236,7 @@ async def test_body_redirect():
 
 @pytest.mark.asyncio
 async def test_cannot_redirect_streaming_body():
-    client = RedirectAdapter(MockDispatch())
+    client = Client(dispatch=MockDispatch())
     url = "https://example.org/redirect_body"
 
     async def streaming_body():
index cf1192f2acbea329a182084d0c502b5286936387..a2bac09c4ff60db8907645c34ad4a711065811eb 100644 (file)
@@ -1,20 +1,22 @@
 import pytest
 
-import httpcore
+from httpcore import HTTPConnection, Request
 
 
 @pytest.mark.asyncio
 async def test_get(server):
-    http = httpcore.HTTPConnection(origin="http://127.0.0.1:8000/")
-    response = await http.request("GET", "http://127.0.0.1:8000/")
+    conn = HTTPConnection(origin="http://127.0.0.1:8000/")
+    request = Request("GET", "http://127.0.0.1:8000/")
+    request.prepare()
+    response = await conn.send(request)
     assert response.status_code == 200
     assert response.content == b"Hello, world!"
 
 
 @pytest.mark.asyncio
 async def test_post(server):
-    http = httpcore.HTTPConnection(origin="http://127.0.0.1:8000/")
-    response = await http.request(
-        "POST", "http://127.0.0.1:8000/", body=b"Hello, world!"
-    )
+    conn = HTTPConnection(origin="http://127.0.0.1:8000/")
+    request = Request("GET", "http://127.0.0.1:8000/", data=b"Hello, world!")
+    request.prepare()
+    response = await conn.send(request)
     assert response.status_code == 200
index b9bf8ccf49c0ea8029f7bdf4915167d513cab351..940c3ee11f7875523bd55c8545432e11288a671f 100644 (file)
@@ -5,10 +5,10 @@ import h2.connection
 import h2.events
 import pytest
 
-import httpcore
+from httpcore import BaseReader, BaseWriter, HTTP2Connection, Request
 
 
-class MockServer(httpcore.BaseReader, httpcore.BaseWriter):
+class MockServer(BaseReader, BaseWriter):
     """
     This class exposes Reader and Writer style interfaces
     """
@@ -82,8 +82,12 @@ class MockServer(httpcore.BaseReader, httpcore.BaseWriter):
 @pytest.mark.asyncio
 async def test_http2_get_request():
     server = MockServer()
-    async with httpcore.HTTP2Connection(reader=server, writer=server) as conn:
-        response = await conn.request("GET", "http://example.org")
+    conn = HTTP2Connection(reader=server, writer=server)
+    request = Request("GET", "http://example.org")
+    request.prepare()
+
+    response = await conn.send(request)
+
     assert response.status_code == 200
     assert json.loads(response.content) == {"method": "GET", "path": "/", "body": ""}
 
@@ -91,8 +95,12 @@ async def test_http2_get_request():
 @pytest.mark.asyncio
 async def test_http2_post_request():
     server = MockServer()
-    async with httpcore.HTTP2Connection(reader=server, writer=server) as conn:
-        response = await conn.request("POST", "http://example.org", data=b"<data>")
+    conn = HTTP2Connection(reader=server, writer=server)
+    request = Request("POST", "http://example.org", data=b"<data>")
+    request.prepare()
+
+    response = await conn.send(request)
+
     assert response.status_code == 200
     assert json.loads(response.content) == {
         "method": "POST",
@@ -104,10 +112,18 @@ async def test_http2_post_request():
 @pytest.mark.asyncio
 async def test_http2_multiple_requests():
     server = MockServer()
-    async with httpcore.HTTP2Connection(reader=server, writer=server) as conn:
-        response_1 = await conn.request("GET", "http://example.org/1")
-        response_2 = await conn.request("GET", "http://example.org/2")
-        response_3 = await conn.request("GET", "http://example.org/3")
+    conn = HTTP2Connection(reader=server, writer=server)
+    request_1 = Request("GET", "http://example.org/1")
+    request_2 = Request("GET", "http://example.org/2")
+    request_3 = Request("GET", "http://example.org/3")
+
+    request_1.prepare()
+    request_2.prepare()
+    request_3.prepare()
+
+    response_1 = await conn.send(request_1)
+    response_2 = await conn.send(request_2)
+    response_3 = await conn.send(request_3)
 
     assert response_1.status_code == 200
     assert json.loads(response_1.content) == {"method": "GET", "path": "/1", "body": ""}
@@ -117,3 +133,5 @@ async def test_http2_multiple_requests():
 
     assert response_3.status_code == 200
     assert json.loads(response_3.content) == {"method": "GET", "path": "/3", "body": ""}
+
+    await conn.close()