From 08b3eee9ea45f8d325f287d546db768c2ed21ce3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 2 Nov 2023 14:24:08 +0000 Subject: [PATCH] Raise a neater RuntimeError when the correct async deps are not installed. (#826) * Raise a neater RuntimeError when the correct async deps are not installed * Run scripts/unasync * Update CHANGELOG --- CHANGELOG.md | 3 +- httpcore/_async/connection_pool.py | 4 ++ httpcore/_sync/connection_pool.py | 4 ++ httpcore/_synchronization.py | 86 +++++++++--------------------- 4 files changed, 35 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f11dc97..b6b5adfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased - Fix pool timeout to account for the total time spent retrying. (#823) -- Fix synchronous TLS-in-TLS streams. (#840) +- Raise a neater RuntimeError when the correct async deps are not installed. (#826) +- Add support for synchronous TLS-in-TLS streams. (#840) ## 1.0.0 (October 6th, 2023) diff --git a/httpcore/_async/connection_pool.py b/httpcore/_async/connection_pool.py index a8a3d00c..0320c6d8 100644 --- a/httpcore/_async/connection_pool.py +++ b/httpcore/_async/connection_pool.py @@ -326,6 +326,10 @@ async def aclose(self) -> None: self._requests = [] async def __aenter__(self) -> "AsyncConnectionPool": + # Acquiring the pool lock here ensures that we have the + # correct dependencies installed as early as possible. + async with self._pool_lock: + pass return self async def __aexit__( diff --git a/httpcore/_sync/connection_pool.py b/httpcore/_sync/connection_pool.py index b586abf1..ccfb8d22 100644 --- a/httpcore/_sync/connection_pool.py +++ b/httpcore/_sync/connection_pool.py @@ -326,6 +326,10 @@ def close(self) -> None: self._requests = [] def __enter__(self) -> "ConnectionPool": + # Acquiring the pool lock here ensures that we have the + # correct dependencies installed as early as possible. + with self._pool_lock: + pass return self def __exit__( diff --git a/httpcore/_synchronization.py b/httpcore/_synchronization.py index 58e06160..2fbf961f 100644 --- a/httpcore/_synchronization.py +++ b/httpcore/_synchronization.py @@ -24,13 +24,23 @@ def current_async_library() -> str: try: import sniffio except ImportError: # pragma: nocover - return "asyncio" - - environment = sniffio.current_async_library() + environment = "asyncio" + else: + environment = sniffio.current_async_library() if environment not in ("asyncio", "trio"): # pragma: nocover raise RuntimeError("Running under an unsupported async environment.") + if environment == "asyncio" and anyio is None: # pragma: nocover + raise RuntimeError( + "Running with asyncio requires installation of 'httpcore[asyncio]'." + ) + + if environment == "trio" and trio is None: # pragma: nocover + raise RuntimeError( + "Running with trio requires installation of 'httpcore[trio]'." + ) + return environment @@ -45,16 +55,8 @@ def setup(self) -> None: """ self._backend = current_async_library() if self._backend == "trio": - if trio is None: # pragma: nocover - raise RuntimeError( - "Running with trio requires installation of 'httpcore[trio]'." - ) self._trio_lock = trio.Lock() - else: - if anyio is None: # pragma: nocover - raise RuntimeError( - "Running with asyncio requires installation of 'httpcore[asyncio]'." - ) + elif self._backend == "asyncio": self._anyio_lock = anyio.Lock() async def __aenter__(self) -> "AsyncLock": @@ -63,7 +65,7 @@ async def __aenter__(self) -> "AsyncLock": if self._backend == "trio": await self._trio_lock.acquire() - else: + elif self._backend == "asyncio": await self._anyio_lock.acquire() return self @@ -76,7 +78,7 @@ async def __aexit__( ) -> None: if self._backend == "trio": self._trio_lock.release() - else: + elif self._backend == "asyncio": self._anyio_lock.release() @@ -91,16 +93,8 @@ def setup(self) -> None: """ self._backend = current_async_library() if self._backend == "trio": - if trio is None: # pragma: nocover - raise RuntimeError( - "Running with trio requires installation of 'httpcore[trio]'." - ) self._trio_event = trio.Event() - else: - if anyio is None: # pragma: nocover - raise RuntimeError( - "Running with asyncio requires installation of 'httpcore[asyncio]'." - ) + elif self._backend == "asyncio": self._anyio_event = anyio.Event() def set(self) -> None: @@ -109,7 +103,7 @@ def set(self) -> None: if self._backend == "trio": self._trio_event.set() - else: + elif self._backend == "asyncio": self._anyio_event.set() async def wait(self, timeout: Optional[float] = None) -> None: @@ -117,22 +111,12 @@ async def wait(self, timeout: Optional[float] = None) -> None: self.setup() if self._backend == "trio": - if trio is None: # pragma: nocover - raise RuntimeError( - "Running with trio requires installation of 'httpcore[trio]'." - ) - trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout} timeout_or_inf = float("inf") if timeout is None else timeout with map_exceptions(trio_exc_map): with trio.fail_after(timeout_or_inf): await self._trio_event.wait() - else: - if anyio is None: # pragma: nocover - raise RuntimeError( - "Running with asyncio requires installation of 'httpcore[asyncio]'." - ) - + elif self._backend == "asyncio": anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout} with map_exceptions(anyio_exc_map): with anyio.fail_after(timeout): @@ -151,20 +135,10 @@ def setup(self) -> None: """ self._backend = current_async_library() if self._backend == "trio": - if trio is None: # pragma: nocover - raise RuntimeError( - "Running with trio requires installation of 'httpcore[trio]'." - ) - self._trio_semaphore = trio.Semaphore( initial_value=self._bound, max_value=self._bound ) - else: - if anyio is None: # pragma: nocover - raise RuntimeError( - "Running with asyncio requires installation of 'httpcore[asyncio]'." - ) - + elif self._backend == "asyncio": self._anyio_semaphore = anyio.Semaphore( initial_value=self._bound, max_value=self._bound ) @@ -175,13 +149,13 @@ async def acquire(self) -> None: if self._backend == "trio": await self._trio_semaphore.acquire() - else: + elif self._backend == "asyncio": await self._anyio_semaphore.acquire() async def release(self) -> None: if self._backend == "trio": self._trio_semaphore.release() - else: + elif self._backend == "asyncio": self._anyio_semaphore.release() @@ -201,24 +175,14 @@ def __init__(self) -> None: self._backend = current_async_library() if self._backend == "trio": - if trio is None: # pragma: nocover - raise RuntimeError( - "Running with trio requires installation of 'httpcore[trio]'." - ) - self._trio_shield = trio.CancelScope(shield=True) - else: - if anyio is None: # pragma: nocover - raise RuntimeError( - "Running with asyncio requires installation of 'httpcore[asyncio]'." - ) - + elif self._backend == "asyncio": self._anyio_shield = anyio.CancelScope(shield=True) def __enter__(self) -> "AsyncShieldCancellation": if self._backend == "trio": self._trio_shield.__enter__() - else: + elif self._backend == "asyncio": self._anyio_shield.__enter__() return self @@ -230,7 +194,7 @@ def __exit__( ) -> None: if self._backend == "trio": self._trio_shield.__exit__(exc_type, exc_value, traceback) - else: + elif self._backend == "asyncio": self._anyio_shield.__exit__(exc_type, exc_value, traceback)