Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 1.0 #809

Merged
merged 14 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ 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
## 1.0.0 (...)

From version 1.0 our async support is now optional, as the package has minimal dependencies by default.

For async support use either `pip install 'httpcore[asyncio]'` or `pip install 'httpcore[trio]'`.

The project versioning policy is now explicitly governed by SEMVER. See https://semver.org/.

- Async support becomes fully optional. (#809)
- Add support for Python 3.12. (#807)

## 0.18.0 (September 8th, 2023)
Expand Down
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,10 @@ For HTTP/1.1 only support, install with:
$ pip install httpcore
```

For HTTP/1.1 and HTTP/2 support, install with:
There are also a number of optional extras available...

```shell
$ pip install httpcore[http2]
```

For SOCKS proxy support, install with:

```shell
$ pip install httpcore[socks]
$ pip install httpcore['asyncio,trio,http2,socks']
```

# Sending requests
Expand Down Expand Up @@ -89,3 +83,29 @@ The motivation for `httpcore` is:
* To provide a reusable low-level client library, that other packages can then build on top of.
* To provide a *really clear interface split* between the networking code and client logic,
so that each is easier to understand and reason about in isolation.

## Dependencies

The `httpcore` package has the following dependencies...

* `h11`
* `certifi`

And the following optional extras...

* `anyio` - Required by `pip install httpcore['asyncio']`.
* `trio` - Required by `pip install httpcore['trio']`.
* `h2` - Required by `pip install httpcore['http2']`.
* `socksio` - Required by `pip install httpcore['socks']`.

## Versioning

We use [SEMVER for our versioning policy](https://semver.org/).

For changes between package versions please see our [project changelog](CHANGELOG.md).

We recommend pinning your requirements either the most current major version, or a more specific version range:

```python
pip install 'httpcore==1.*'
```
16 changes: 16 additions & 0 deletions docs/async.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ If you're working with an async web framework then you'll also want to use an as

Launching concurrent async tasks is far more resource efficient than spawning multiple threads. The Python interpreter should be able to comfortably handle switching between over 1000 concurrent tasks, while a sensible number of threads in a thread pool might be to enable around 10 or 20 concurrent threads.

## Enabling Async support

If you're using async with [Python's stdlib `asyncio` support](https://docs.python.org/3/library/asyncio.html), install the optional dependencies using:

```shell
$ pip install 'httpcore[asyncio]'
```

Alternatively, if you're working with [the Python `trio` package](https://trio.readthedocs.io/en/stable/):

```shell
$ pip install 'httpcore[trio]'
```

We highly recommend `trio` for async support. The `trio` project [pioneered the principles of structured concurrency](https://en.wikipedia.org/wiki/Structured_concurrency), and has a more carefully constrained API against which to work from.

## API differences

When using async support, you need make sure to use an async connection pool class:
Expand Down
2 changes: 2 additions & 0 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ The following event types are currently exposed...
* `"http2.receive_response_body"`
* `"http2.response_closed"`

The exact set of trace events may be subject to change across different versions of `httpcore`. If you need to rely on a particular set of events it is recommended that you pin installation of the package to a fixed version.

### `"sni_hostname"`

The server's hostname, which is used to confirm the hostname supplied by the SSL certificate.
Expand Down
2 changes: 1 addition & 1 deletion docs/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ When using the `httpcore` client, HTTP/2 support is not enabled by default, beca
If you're issuing highly concurrent requests you might want to consider trying out our HTTP/2 support. You can do so by first making sure to install the optional HTTP/2 dependencies...

```shell
$ pip install httpcore[http2]
$ pip install 'httpcore[http2]'
```

And then instantiating a connection pool with HTTP/2 support enabled:
Expand Down
2 changes: 1 addition & 1 deletion docs/proxies.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ If you use proxies, keep in mind that the `httpcore` package only supports proxi

The `httpcore` package also supports proxies using the SOCKS5 protocol.

Make sure to install the optional dependancy using `pip install httpcore[socks]`.
Make sure to install the optional dependancy using `pip install 'httpcore[socks]'`.

The `SOCKSProxy` class should be using instead of a standard connection pool:

Expand Down
2 changes: 1 addition & 1 deletion httpcore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def __init__(self, *args, **kwargs): # type: ignore
"WriteError",
]

__version__ = "0.18.0"
__version__ = "1.0.0"


__locals = locals()
Expand Down
5 changes: 2 additions & 3 deletions httpcore/_backends/auto.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import typing
from typing import Optional

import sniffio

from .._synchronization import current_async_library
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream


class AutoBackend(AsyncNetworkBackend):
async def _init_backend(self) -> None:
if not (hasattr(self, "_backend")):
backend = sniffio.current_async_library()
backend = current_async_library()
if backend == "trio":
from .trio import TrioBackend

Expand Down
46 changes: 30 additions & 16 deletions httpcore/_synchronization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
from types import TracebackType
from typing import Optional, Type

import sniffio

from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions

# Our async synchronization primatives use either 'anyio' or 'trio' depending
Expand All @@ -20,6 +18,22 @@
anyio = None # type: ignore


def current_async_library() -> str:
# Determine if we're running under trio or asyncio.
# See https://sniffio.readthedocs.io/en/latest/
try:
import sniffio
except ImportError: # pragma: nocover
return "asyncio"

environment = sniffio.current_async_library()

if environment not in ("asyncio", "trio"): # pragma: nocover
raise RuntimeError("Running under an unsupported async environment.")

return environment


class AsyncLock:
def __init__(self) -> None:
self._backend = ""
Expand All @@ -29,17 +43,17 @@ def setup(self) -> None:
Detect if we're running under 'asyncio' or 'trio' and create
a lock with the correct implementation.
"""
self._backend = sniffio.current_async_library()
self._backend = current_async_library()
if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio, requires the 'trio' package to be installed."
"Running with trio requires installation of 'httpcore[trio]'."
)
self._trio_lock = trio.Lock()
else:
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
"Running with asyncio requires installation of 'httpcore[asyncio]'."
)
self._anyio_lock = anyio.Lock()

Expand Down Expand Up @@ -75,17 +89,17 @@ def setup(self) -> None:
Detect if we're running under 'asyncio' or 'trio' and create
a lock with the correct implementation.
"""
self._backend = sniffio.current_async_library()
self._backend = current_async_library()
if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
"Running with trio requires installation of 'httpcore[trio]'."
)
self._trio_event = trio.Event()
else:
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
"Running with asyncio requires installation of 'httpcore[asyncio]'."
)
self._anyio_event = anyio.Event()

Expand All @@ -105,7 +119,7 @@ async def wait(self, timeout: Optional[float] = None) -> None:
if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
"Running with trio requires installation of 'httpcore[trio]'."
)

trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout}
Expand All @@ -116,7 +130,7 @@ async def wait(self, timeout: Optional[float] = None) -> None:
else:
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
"Running with asyncio requires installation of 'httpcore[asyncio]'."
)

anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout}
Expand All @@ -135,11 +149,11 @@ def setup(self) -> None:
Detect if we're running under 'asyncio' or 'trio' and create
a semaphore with the correct implementation.
"""
self._backend = sniffio.current_async_library()
self._backend = current_async_library()
if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
"Running with trio requires installation of 'httpcore[trio]'."
)

self._trio_semaphore = trio.Semaphore(
Expand All @@ -148,7 +162,7 @@ def setup(self) -> None:
else:
if anyio is None: # pragma: nocover
raise RuntimeError(
"Running under asyncio requires the 'anyio' package to be installed."
"Running with asyncio requires installation of 'httpcore[asyncio]'."
)

self._anyio_semaphore = anyio.Semaphore(
Expand Down Expand Up @@ -184,19 +198,19 @@ def __init__(self) -> None:
Detect if we're running under 'asyncio' or 'trio' and create
a shielded scope with the correct implementation.
"""
self._backend = sniffio.current_async_library()
self._backend = current_async_library()

if self._backend == "trio":
if trio is None: # pragma: nocover
raise RuntimeError(
"Running under trio requires the 'trio' package to be installed."
"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 under asyncio requires the 'anyio' package to be installed."
"Running with asyncio requires installation of 'httpcore[asyncio]'."
)

self._anyio_shield = anyio.CancelScope(shield=True)
Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
"anyio>=3.0,<5.0",
"certifi",
"h11>=0.13,<0.15",
"sniffio==1.*",
]

[project.optional-dependencies]
Expand All @@ -42,6 +40,12 @@ http2 = [
socks = [
"socksio==1.*",
]
trio = [
"trio>=0.22.0,<0.23.0",
]
asyncio = [
"anyio>=4.0,<5.0",
]

[project.urls]
Documentation = "https://www.encode.io/httpcore"
Expand Down
6 changes: 1 addition & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
-e .[http2,socks]

# Optionals
trio==0.21.0
-e .[asyncio,trio,http2,socks]

# Docs
mkdocs==1.5.3
Expand All @@ -16,7 +13,6 @@ build==1.0.3
twine

# Tests & Linting
anyio==4.0.0
black==23.7.0
coverage[toml]==7.3.0
ruff==0.0.291
Expand Down