Skip to content

Commit 12834e3

Browse files
Version 1.0 (#809)
* Version 1.0 * Keep Trio support at trio==0.21. * Add note in 'trace' extension docs to make it clear that trace events could change across package versions. * Fix installation docs * Fix installation messages * Update trio, anyio versions * Update CHANGELOG * Remove sniffio as a direct dependency --------- Co-authored-by: Kar Petrosyan <[email protected]>
1 parent 221a507 commit 12834e3

File tree

11 files changed

+96
-38
lines changed

11 files changed

+96
-38
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7-
## Unreleased
7+
## 1.0.0 (...)
88

9+
From version 1.0 our async support is now optional, as the package has minimal dependencies by default.
10+
11+
For async support use either `pip install 'httpcore[asyncio]'` or `pip install 'httpcore[trio]'`.
12+
13+
The project versioning policy is now explicitly governed by SEMVER. See https://semver.org/.
14+
15+
- Async support becomes fully optional. (#809)
916
- Add support for Python 3.12. (#807)
1017

1118
## 0.18.0 (September 8th, 2023)

README.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,10 @@ For HTTP/1.1 only support, install with:
3535
$ pip install httpcore
3636
```
3737

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

4040
```shell
41-
$ pip install httpcore[http2]
42-
```
43-
44-
For SOCKS proxy support, install with:
45-
46-
```shell
47-
$ pip install httpcore[socks]
41+
$ pip install httpcore['asyncio,trio,http2,socks']
4842
```
4943

5044
# Sending requests
@@ -89,3 +83,29 @@ The motivation for `httpcore` is:
8983
* To provide a reusable low-level client library, that other packages can then build on top of.
9084
* To provide a *really clear interface split* between the networking code and client logic,
9185
so that each is easier to understand and reason about in isolation.
86+
87+
## Dependencies
88+
89+
The `httpcore` package has the following dependencies...
90+
91+
* `h11`
92+
* `certifi`
93+
94+
And the following optional extras...
95+
96+
* `anyio` - Required by `pip install httpcore['asyncio']`.
97+
* `trio` - Required by `pip install httpcore['trio']`.
98+
* `h2` - Required by `pip install httpcore['http2']`.
99+
* `socksio` - Required by `pip install httpcore['socks']`.
100+
101+
## Versioning
102+
103+
We use [SEMVER for our versioning policy](https://semver.org/).
104+
105+
For changes between package versions please see our [project changelog](CHANGELOG.md).
106+
107+
We recommend pinning your requirements either the most current major version, or a more specific version range:
108+
109+
```python
110+
pip install 'httpcore==1.*'
111+
```

docs/async.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ If you're working with an async web framework then you'll also want to use an as
88

99
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.
1010

11+
## Enabling Async support
12+
13+
If you're using async with [Python's stdlib `asyncio` support](https://docs.python.org/3/library/asyncio.html), install the optional dependencies using:
14+
15+
```shell
16+
$ pip install 'httpcore[asyncio]'
17+
```
18+
19+
Alternatively, if you're working with [the Python `trio` package](https://trio.readthedocs.io/en/stable/):
20+
21+
```shell
22+
$ pip install 'httpcore[trio]'
23+
```
24+
25+
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.
26+
1127
## API differences
1228

1329
When using async support, you need make sure to use an async connection pool class:

docs/extensions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ The following event types are currently exposed...
147147
* `"http2.receive_response_body"`
148148
* `"http2.response_closed"`
149149

150+
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.
151+
150152
### `"sni_hostname"`
151153

152154
The server's hostname, which is used to confirm the hostname supplied by the SSL certificate.

docs/http2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ When using the `httpcore` client, HTTP/2 support is not enabled by default, beca
1515
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...
1616

1717
```shell
18-
$ pip install httpcore[http2]
18+
$ pip install 'httpcore[http2]'
1919
```
2020

2121
And then instantiating a connection pool with HTTP/2 support enabled:

docs/proxies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ If you use proxies, keep in mind that the `httpcore` package only supports proxi
8383

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

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

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

httpcore/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def __init__(self, *args, **kwargs): # type: ignore
130130
"WriteError",
131131
]
132132

133-
__version__ = "0.18.0"
133+
__version__ = "1.0.0"
134134

135135

136136
__locals = locals()

httpcore/_backends/auto.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import typing
22
from typing import Optional
33

4-
import sniffio
5-
4+
from .._synchronization import current_async_library
65
from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
76

87

98
class AutoBackend(AsyncNetworkBackend):
109
async def _init_backend(self) -> None:
1110
if not (hasattr(self, "_backend")):
12-
backend = sniffio.current_async_library()
11+
backend = current_async_library()
1312
if backend == "trio":
1413
from .trio import TrioBackend
1514

httpcore/_synchronization.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
from types import TracebackType
33
from typing import Optional, Type
44

5-
import sniffio
6-
75
from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions
86

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

2220

21+
def current_async_library() -> str:
22+
# Determine if we're running under trio or asyncio.
23+
# See https://sniffio.readthedocs.io/en/latest/
24+
try:
25+
import sniffio
26+
except ImportError: # pragma: nocover
27+
return "asyncio"
28+
29+
environment = sniffio.current_async_library()
30+
31+
if environment not in ("asyncio", "trio"): # pragma: nocover
32+
raise RuntimeError("Running under an unsupported async environment.")
33+
34+
return environment
35+
36+
2337
class AsyncLock:
2438
def __init__(self) -> None:
2539
self._backend = ""
@@ -29,17 +43,17 @@ def setup(self) -> None:
2943
Detect if we're running under 'asyncio' or 'trio' and create
3044
a lock with the correct implementation.
3145
"""
32-
self._backend = sniffio.current_async_library()
46+
self._backend = current_async_library()
3347
if self._backend == "trio":
3448
if trio is None: # pragma: nocover
3549
raise RuntimeError(
36-
"Running under trio, requires the 'trio' package to be installed."
50+
"Running with trio requires installation of 'httpcore[trio]'."
3751
)
3852
self._trio_lock = trio.Lock()
3953
else:
4054
if anyio is None: # pragma: nocover
4155
raise RuntimeError(
42-
"Running under asyncio requires the 'anyio' package to be installed."
56+
"Running with asyncio requires installation of 'httpcore[asyncio]'."
4357
)
4458
self._anyio_lock = anyio.Lock()
4559

@@ -75,17 +89,17 @@ def setup(self) -> None:
7589
Detect if we're running under 'asyncio' or 'trio' and create
7690
a lock with the correct implementation.
7791
"""
78-
self._backend = sniffio.current_async_library()
92+
self._backend = current_async_library()
7993
if self._backend == "trio":
8094
if trio is None: # pragma: nocover
8195
raise RuntimeError(
82-
"Running under trio requires the 'trio' package to be installed."
96+
"Running with trio requires installation of 'httpcore[trio]'."
8397
)
8498
self._trio_event = trio.Event()
8599
else:
86100
if anyio is None: # pragma: nocover
87101
raise RuntimeError(
88-
"Running under asyncio requires the 'anyio' package to be installed."
102+
"Running with asyncio requires installation of 'httpcore[asyncio]'."
89103
)
90104
self._anyio_event = anyio.Event()
91105

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

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

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

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

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

189203
if self._backend == "trio":
190204
if trio is None: # pragma: nocover
191205
raise RuntimeError(
192-
"Running under trio requires the 'trio' package to be installed."
206+
"Running with trio requires installation of 'httpcore[trio]'."
193207
)
194208

195209
self._trio_shield = trio.CancelScope(shield=True)
196210
else:
197211
if anyio is None: # pragma: nocover
198212
raise RuntimeError(
199-
"Running under asyncio requires the 'anyio' package to be installed."
213+
"Running with asyncio requires installation of 'httpcore[asyncio]'."
200214
)
201215

202216
self._anyio_shield = anyio.CancelScope(shield=True)

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ classifiers = [
2929
"Topic :: Internet :: WWW/HTTP",
3030
]
3131
dependencies = [
32-
"anyio>=3.0,<5.0",
3332
"certifi",
3433
"h11>=0.13,<0.15",
35-
"sniffio==1.*",
3634
]
3735

3836
[project.optional-dependencies]
@@ -42,6 +40,12 @@ http2 = [
4240
socks = [
4341
"socksio==1.*",
4442
]
43+
trio = [
44+
"trio>=0.22.0,<0.23.0",
45+
]
46+
asyncio = [
47+
"anyio>=4.0,<5.0",
48+
]
4549

4650
[project.urls]
4751
Documentation = "https://www.encode.io/httpcore"

0 commit comments

Comments
 (0)