Skip to content

Commit bf7d216

Browse files
Merge branch 'master' into move-bench
2 parents 4644255 + 38f277c commit bf7d216

36 files changed

+479
-329
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ 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+
## Version 1.0.7 (November 15th, 2024)
8+
9+
- Support `proxy=…` configuration on `ConnectionPool()`. (#974)
10+
711
## Version 1.0.6 (October 1st, 2024)
812

913
- Relax `trio` dependency pinning. (#956)

docs/async.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,6 @@ async with httpcore.AsyncConnectionPool() as http:
3434
...
3535
```
3636

37-
Or if connecting via a proxy:
38-
39-
```python
40-
# The async variation of `httpcore.HTTPProxy`
41-
async with httpcore.AsyncHTTPProxy() as proxy:
42-
...
43-
```
44-
4537
### Sending requests
4638

4739
Sending requests with the async version of `httpcore` requires the `await` keyword:
@@ -221,10 +213,3 @@ anyio.run(main)
221213
handler: python
222214
rendering:
223215
show_source: False
224-
225-
## `httpcore.AsyncHTTPProxy`
226-
227-
::: httpcore.AsyncHTTPProxy
228-
handler: python
229-
rendering:
230-
show_source: False

docs/proxies.md

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ Sending requests via a proxy is very similar to sending requests using a standar
77
```python
88
import httpcore
99

10-
proxy = httpcore.HTTPProxy(proxy_url="http://127.0.0.1:8080/")
10+
proxy = httpcore.Proxy("http://127.0.0.1:8080/")
11+
pool = httpcore.ConnectionPool(proxy=proxy)
1112
r = proxy.request("GET", "https://www.example.com/")
1213

1314
print(r)
@@ -31,10 +32,11 @@ Proxy authentication can be included in the initial configuration:
3132
import httpcore
3233

3334
# A `Proxy-Authorization` header will be included on the initial proxy connection.
34-
proxy = httpcore.HTTPProxy(
35-
proxy_url="http://127.0.0.1:8080/",
36-
proxy_auth=("<username>", "<password>")
35+
proxy = httpcore.Proxy(
36+
url="http://127.0.0.1:8080/",
37+
auth=("<username>", "<password>")
3738
)
39+
pool = httpcore.ConnectionPool(proxy=proxy)
3840
```
3941

4042
Custom headers can also be included:
@@ -45,10 +47,11 @@ import base64
4547

4648
# Construct and include a `Proxy-Authorization` header.
4749
auth = base64.b64encode(b"<username>:<password>")
48-
proxy = httpcore.HTTPProxy(
49-
proxy_url="http://127.0.0.1:8080/",
50-
proxy_headers={"Proxy-Authorization": b"Basic " + auth}
50+
proxy = httpcore.Proxy(
51+
url="http://127.0.0.1:8080/",
52+
headers={"Proxy-Authorization": b"Basic " + auth}
5153
)
54+
pool = httpcore.ConnectionPool(proxy=proxy)
5255
```
5356

5457
## Proxy SSL
@@ -58,10 +61,10 @@ The `httpcore` package also supports HTTPS proxies for http and https destinatio
5861
HTTPS proxies can be used in the same way that HTTP proxies are.
5962

6063
```python
61-
proxy = httpcore.HTTPProxy(proxy_url="https://127.0.0.1:8080/")
64+
proxy = httpcore.Proxy(url="https://127.0.0.1:8080/")
6265
```
6366

64-
Also, when using HTTPS proxies, you may need to configure the SSL context, which you can do with the `proxy_ssl_context` argument.
67+
Also, when using HTTPS proxies, you may need to configure the SSL context, which you can do with the `ssl_context` argument.
6568

6669
```python
6770
import ssl
@@ -70,11 +73,13 @@ import httpcore
7073
proxy_ssl_context = ssl.create_default_context()
7174
proxy_ssl_context.check_hostname = False
7275

73-
proxy = httpcore.HTTPProxy('https://127.0.0.1:8080/', proxy_ssl_context=proxy_ssl_context)
76+
proxy = httpcore.Proxy(
77+
url='https://127.0.0.1:8080/',
78+
ssl_context=proxy_ssl_context
79+
)
80+
pool = httpcore.ConnectionPool(proxy=proxy)
7481
```
7582

76-
It is important to note that the `ssl_context` argument is always used for the remote connection, and the `proxy_ssl_context` argument is always used for the proxy connection.
77-
7883
## HTTP Versions
7984

8085
If you use proxies, keep in mind that the `httpcore` package only supports proxies to HTTP/1.1 servers.
@@ -91,29 +96,31 @@ The `SOCKSProxy` class should be using instead of a standard connection pool:
9196
import httpcore
9297

9398
# Note that the SOCKS port is 1080.
94-
proxy = httpcore.SOCKSProxy(proxy_url="socks5://127.0.0.1:1080/")
95-
r = proxy.request("GET", "https://www.example.com/")
99+
proxy = httpcore.Proxy(url="socks5://127.0.0.1:1080/")
100+
pool = httpcore.ConnectionPool(proxy=proxy)
101+
r = pool.request("GET", "https://www.example.com/")
96102
```
97103

98104
Authentication via SOCKS is also supported:
99105

100106
```python
101107
import httpcore
102108

103-
proxy = httpcore.SOCKSProxy(
104-
proxy_url="socks5://127.0.0.1:8080/",
105-
proxy_auth=("<username>", "<password>")
109+
proxy = httpcore.Proxy(
110+
url="socks5://127.0.0.1:1080/",
111+
auth=("<username>", "<password>"),
106112
)
107-
r = proxy.request("GET", "https://www.example.com/")
113+
pool = httpcore.ConnectionPool(proxy=proxy)
114+
r = pool.request("GET", "https://www.example.com/")
108115
```
109116

110117
---
111118

112119
# Reference
113120

114-
## `httpcore.HTTPProxy`
121+
## `httpcore.Proxy`
115122

116-
::: httpcore.HTTPProxy
123+
::: httpcore.Proxy
117124
handler: python
118125
rendering:
119126
show_source: False

docs/table-of-contents.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@
1010
* Connection Pools
1111
* `httpcore.ConnectionPool`
1212
* Proxies
13-
* `httpcore.HTTPProxy`
13+
* `httpcore.Proxy`
1414
* Connections
1515
* `httpcore.HTTPConnection`
1616
* `httpcore.HTTP11Connection`
1717
* `httpcore.HTTP2Connection`
1818
* Async Support
1919
* `httpcore.AsyncConnectionPool`
20-
* `httpcore.AsyncHTTPProxy`
2120
* `httpcore.AsyncHTTPConnection`
2221
* `httpcore.AsyncHTTP11Connection`
2322
* `httpcore.AsyncHTTP2Connection`

httpcore/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
WriteError,
3535
WriteTimeout,
3636
)
37-
from ._models import URL, Origin, Request, Response
37+
from ._models import URL, Origin, Proxy, Request, Response
3838
from ._ssl import default_ssl_context
3939
from ._sync import (
4040
ConnectionInterface,
@@ -79,6 +79,7 @@ def __init__(self, *args, **kwargs): # type: ignore
7979
"URL",
8080
"Request",
8181
"Response",
82+
"Proxy",
8283
# async
8384
"AsyncHTTPConnection",
8485
"AsyncConnectionPool",
@@ -130,7 +131,7 @@ def __init__(self, *args, **kwargs): # type: ignore
130131
"WriteError",
131132
]
132133

133-
__version__ = "1.0.6"
134+
__version__ = "1.0.7"
134135

135136

136137
__locals = locals()

httpcore/_api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

3-
from contextlib import contextmanager
4-
from typing import Iterator
3+
import contextlib
4+
import typing
55

66
from ._models import URL, Extensions, HeaderTypes, Response
77
from ._sync.connection_pool import ConnectionPool
@@ -12,7 +12,7 @@ def request(
1212
url: URL | bytes | str,
1313
*,
1414
headers: HeaderTypes = None,
15-
content: bytes | Iterator[bytes] | None = None,
15+
content: bytes | typing.Iterator[bytes] | None = None,
1616
extensions: Extensions | None = None,
1717
) -> Response:
1818
"""
@@ -47,15 +47,15 @@ def request(
4747
)
4848

4949

50-
@contextmanager
50+
@contextlib.contextmanager
5151
def stream(
5252
method: bytes | str,
5353
url: URL | bytes | str,
5454
*,
5555
headers: HeaderTypes = None,
56-
content: bytes | Iterator[bytes] | None = None,
56+
content: bytes | typing.Iterator[bytes] | None = None,
5757
extensions: Extensions | None = None,
58-
) -> Iterator[Response]:
58+
) -> typing.Iterator[Response]:
5959
"""
6060
Sends an HTTP request, returning the response within a content manager.
6161

httpcore/_async/connection.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import itertools
44
import logging
55
import ssl
6-
from types import TracebackType
7-
from typing import Iterable, Iterator
6+
import types
7+
import typing
88

99
from .._backends.auto import AutoBackend
1010
from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream
@@ -22,7 +22,7 @@
2222
logger = logging.getLogger("httpcore.connection")
2323

2424

25-
def exponential_backoff(factor: float) -> Iterator[float]:
25+
def exponential_backoff(factor: float) -> typing.Iterator[float]:
2626
"""
2727
Generate a geometric sequence that has a ratio of 2 and starts with 0.
2828
@@ -47,7 +47,7 @@ def __init__(
4747
local_address: str | None = None,
4848
uds: str | None = None,
4949
network_backend: AsyncNetworkBackend | None = None,
50-
socket_options: Iterable[SOCKET_OPTION] | None = None,
50+
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
5151
) -> None:
5252
self._origin = origin
5353
self._ssl_context = ssl_context
@@ -217,6 +217,6 @@ async def __aexit__(
217217
self,
218218
exc_type: type[BaseException] | None = None,
219219
exc_value: BaseException | None = None,
220-
traceback: TracebackType | None = None,
220+
traceback: types.TracebackType | None = None,
221221
) -> None:
222222
await self.aclose()

httpcore/_async/connection_pool.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import ssl
44
import sys
5-
from types import TracebackType
6-
from typing import AsyncIterable, AsyncIterator, Iterable
5+
import types
6+
import typing
77

88
from .._backends.auto import AutoBackend
99
from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend
1010
from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol
11-
from .._models import Origin, Request, Response
11+
from .._models import Origin, Proxy, Request, Response
1212
from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock
1313
from .connection import AsyncHTTPConnection
1414
from .interfaces import AsyncConnectionInterface, AsyncRequestInterface
@@ -48,6 +48,7 @@ class AsyncConnectionPool(AsyncRequestInterface):
4848
def __init__(
4949
self,
5050
ssl_context: ssl.SSLContext | None = None,
51+
proxy: Proxy | None = None,
5152
max_connections: int | None = 10,
5253
max_keepalive_connections: int | None = None,
5354
keepalive_expiry: float | None = None,
@@ -57,7 +58,7 @@ def __init__(
5758
local_address: str | None = None,
5859
uds: str | None = None,
5960
network_backend: AsyncNetworkBackend | None = None,
60-
socket_options: Iterable[SOCKET_OPTION] | None = None,
61+
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
6162
) -> None:
6263
"""
6364
A connection pool for making HTTP requests.
@@ -89,7 +90,7 @@ def __init__(
8990
in the TCP socket when the connection was established.
9091
"""
9192
self._ssl_context = ssl_context
92-
93+
self._proxy = proxy
9394
self._max_connections = (
9495
sys.maxsize if max_connections is None else max_connections
9596
)
@@ -125,6 +126,45 @@ def __init__(
125126
self._optional_thread_lock = AsyncThreadLock()
126127

127128
def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
129+
if self._proxy is not None:
130+
if self._proxy.url.scheme in (b"socks5", b"socks5h"):
131+
from .socks_proxy import AsyncSocks5Connection
132+
133+
return AsyncSocks5Connection(
134+
proxy_origin=self._proxy.url.origin,
135+
proxy_auth=self._proxy.auth,
136+
remote_origin=origin,
137+
ssl_context=self._ssl_context,
138+
keepalive_expiry=self._keepalive_expiry,
139+
http1=self._http1,
140+
http2=self._http2,
141+
network_backend=self._network_backend,
142+
)
143+
elif origin.scheme == b"http":
144+
from .http_proxy import AsyncForwardHTTPConnection
145+
146+
return AsyncForwardHTTPConnection(
147+
proxy_origin=self._proxy.url.origin,
148+
proxy_headers=self._proxy.headers,
149+
proxy_ssl_context=self._proxy.ssl_context,
150+
remote_origin=origin,
151+
keepalive_expiry=self._keepalive_expiry,
152+
network_backend=self._network_backend,
153+
)
154+
from .http_proxy import AsyncTunnelHTTPConnection
155+
156+
return AsyncTunnelHTTPConnection(
157+
proxy_origin=self._proxy.url.origin,
158+
proxy_headers=self._proxy.headers,
159+
proxy_ssl_context=self._proxy.ssl_context,
160+
remote_origin=origin,
161+
ssl_context=self._ssl_context,
162+
keepalive_expiry=self._keepalive_expiry,
163+
http1=self._http1,
164+
http2=self._http2,
165+
network_backend=self._network_backend,
166+
)
167+
128168
return AsyncHTTPConnection(
129169
origin=origin,
130170
ssl_context=self._ssl_context,
@@ -217,7 +257,7 @@ async def handle_async_request(self, request: Request) -> Response:
217257

218258
# Return the response. Note that in this case we still have to manage
219259
# the point at which the response is closed.
220-
assert isinstance(response.stream, AsyncIterable)
260+
assert isinstance(response.stream, typing.AsyncIterable)
221261
return Response(
222262
status=response.status,
223263
headers=response.headers,
@@ -319,7 +359,7 @@ async def __aexit__(
319359
self,
320360
exc_type: type[BaseException] | None = None,
321361
exc_value: BaseException | None = None,
322-
traceback: TracebackType | None = None,
362+
traceback: types.TracebackType | None = None,
323363
) -> None:
324364
await self.aclose()
325365

@@ -349,7 +389,7 @@ def __repr__(self) -> str:
349389
class PoolByteStream:
350390
def __init__(
351391
self,
352-
stream: AsyncIterable[bytes],
392+
stream: typing.AsyncIterable[bytes],
353393
pool_request: AsyncPoolRequest,
354394
pool: AsyncConnectionPool,
355395
) -> None:
@@ -358,7 +398,7 @@ def __init__(
358398
self._pool = pool
359399
self._closed = False
360400

361-
async def __aiter__(self) -> AsyncIterator[bytes]:
401+
async def __aiter__(self) -> typing.AsyncIterator[bytes]:
362402
try:
363403
async for part in self._stream:
364404
yield part

0 commit comments

Comments
 (0)