Skip to content

Commit d00a79b

Browse files
authored
🔖 Release 2.8.904 (#136)
- Relaxed h11 constraint around "pending proposal" and coming server event about upgrade. This is made to ensure near perfect compatibility against the legacy urllib3 which is based on http.client. - Fixed h11 yielding bytearray instead of bytes in rare circumstances. - Added ``docker-py`` in our CI/integration pipeline. <!--- Hello! If this is your first PR to urllib3.future please review the Contributing Guide: https://urllib3future.readthedocs.io/en/latest/contributing.html Adhering to the Contributing Guide means we can review, merge, and release your change faster! :) --->
1 parent 9d3eb0e commit d00a79b

File tree

8 files changed

+128
-14
lines changed

8 files changed

+128
-14
lines changed

.github/workflows/integration.yml

+8-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
downstream: [botocore, niquests, requests, boto3, sphinx]
16+
downstream: [botocore, niquests, requests, boto3, sphinx, docker]
1717
runs-on: ubuntu-latest
1818
timeout-minutes: 30
1919

@@ -24,10 +24,16 @@ jobs:
2424
- name: "Setup Python"
2525
uses: "actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1"
2626
with:
27-
python-version: "3.x"
27+
python-version: "3.11"
2828

2929
- name: "Install dependencies"
3030
run: python -m pip install --upgrade nox
3131

32+
- name: "Undo Docker Config: docker-py"
33+
if: matrix.downstream == 'docker'
34+
run: |
35+
docker logout
36+
rm -rf ~/.docker
37+
3238
- name: "Run downstream tests"
3339
run: nox -s downstream_${{ matrix.downstream }}

CHANGES.rst

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2.8.904 (2024-07-18)
2+
====================
3+
4+
- Relaxed h11 constraint around "pending proposal" and coming server event about upgrade.
5+
This is made to ensure near perfect compatibility against the legacy urllib3 which is based on http.client.
6+
- Fixed h11 yielding bytearray instead of bytes in rare circumstances.
7+
- Added ``docker-py`` in our CI/integration pipeline.
8+
19
2.8.903 (2024-07-17)
210
====================
311

README.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ less forgiven in case of bugs than the original urllib3. For good~ish reasons, w
129129

130130
The matter is taken with utmost seriousness and everyone can inspect this package at will.
131131

132-
We regularly test this fork against the most used packages (that depend on urllib3).
132+
We regularly test this fork against the most used packages (that depend on urllib3, especially those who plunged deep into urllib3 internals).
133133

134134
Finally, rare is someone "fully aware" of their transitive dependencies. And "urllib3" is forced
135135
into your environments regardless of your preferences.
@@ -151,6 +151,17 @@ Here are some of the reasons (not exhaustive) we choose to work this way:
151151
- D) Some of our partners started noticing that HTTP/1 started to be disabled by some webservices in favor of HTTP/2+
152152
So, this fork can unblock them at (almost) zero cost.
153153

154+
**OK... then what do I gain from this?**
155+
156+
- It is faster than its counterpart, we measured gain up to 2X faster in a multithreaded environment using a http2 endpoint.
157+
- It works well with gevent / does not conflict. We do not use the standard queue class from stdlib as it does not fit http2+ constraints.
158+
- Leveraging recent protocols like http2 and http3 transparently. Code and behaviors does not change one bit.
159+
- You do not depend on the standard library to emit http/1 requests, and that is actually a good news. http.client
160+
has numerous known flaws but cannot be fixed as we speak. (e.g. urllib3 is based on http.client)
161+
- There a ton of other improvement you may leverage, but for that you will need to migrate to Niquests or update your code
162+
to enable specific capabilities, like but not limited to: "DNS over QUIC, HTTP" / "Happy Eyeballs" / "Native Asyncio" / "Advanced Multiplexing".
163+
- Non-blocking IO with concurrent streams/requests. And yes, transparently.
164+
154165
- **Is this funded?**
155166

156167
Yes! We have some funds coming in regularly to ensure its sustainability.

ci/0005-DockerPy-FixBadChunk.patch

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
diff --git a/tests/unit/api_test.py b/tests/unit/api_test.py
2+
index 3ce127b3..cea350ee 100644
3+
--- a/tests/unit/api_test.py
4+
+++ b/tests/unit/api_test.py
5+
@@ -330,6 +330,7 @@ class DockerApiTest(BaseAPIClientTest):
6+
content_str = json.dumps(content)
7+
content_str = content_str.encode('utf-8')
8+
body = io.BytesIO(content_str)
9+
+ body.close = lambda: None # necessary because get closed after initial preloading.
10+
11+
# mock a stream interface
12+
raw_resp = urllib3.HTTPResponse(body=body)
13+
@@ -445,7 +446,7 @@ class UnixSocketStreamTest(unittest.TestCase):
14+
b'HTTP/1.1 200 OK\r\n'
15+
b'Transfer-Encoding: chunked\r\n'
16+
b'\r\n'
17+
- ) + b'\r\n'.join(lines)
18+
+ ) + b'\r\n'.join(lines) + b'\r\n' # fix invalid chunked: missing extraneous RC+LF
19+
20+
with APIClient(
21+
base_url=f"http+unix://{self.socket_file}",
22+
@@ -460,9 +461,11 @@ class UnixSocketStreamTest(unittest.TestCase):
23+
if i == 4:
24+
raise e
25+
26+
- assert list(stream) == [
27+
+ # assert assume that sock will yield on each chunk
28+
+ # but not necessarily true.
29+
+ assert b"".join(list(stream)) == b"".join([
30+
str(i).encode() for i in range(50)
31+
- ]
32+
+ ])
33+
34+
35+
class TCPSocketStreamTest(unittest.TestCase):

noxfile.py

+34
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ def downstream_botocore(session: nox.Session) -> None:
282282
session.run("python", "scripts/ci/install")
283283

284284
session.cd(root)
285+
session.install("setuptools<71")
286+
285287
session.install(".", silent=False)
286288
session.cd(f"{tmp_dir}/botocore")
287289

@@ -401,6 +403,38 @@ def downstream_sphinx(session: nox.Session) -> None:
401403
)
402404

403405

406+
@nox.session()
407+
def downstream_docker(session: nox.Session) -> None:
408+
root = os.getcwd()
409+
tmp_dir = session.create_tmp()
410+
411+
session.cd(tmp_dir)
412+
git_clone(session, "https://github.com/docker/docker-py")
413+
session.chdir("docker-py")
414+
415+
for patch in [
416+
"0005-DockerPy-FixBadChunk.patch",
417+
]:
418+
session.run("git", "apply", f"{root}/ci/{patch}", external=True)
419+
420+
session.run("git", "rev-parse", "HEAD", external=True)
421+
session.install(".[ssh,dev]", silent=False)
422+
423+
session.cd(root)
424+
session.install(".", silent=False)
425+
session.cd(f"{tmp_dir}/docker-py")
426+
427+
session.run("python", "-c", "import urllib3; print(urllib3.__version__)")
428+
session.run(
429+
"python",
430+
"-m",
431+
"pytest",
432+
"-v",
433+
f"--color={'yes' if 'GITHUB_ACTIONS' in os.environ else 'auto'}",
434+
*(session.posargs or ("tests/unit",)),
435+
)
436+
437+
404438
@nox.session()
405439
def format(session: nox.Session) -> None:
406440
"""Run code formatters."""

src/urllib3/_collections.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import typing
4-
import warnings
54
from collections import OrderedDict
65
from enum import Enum, auto
76
from functools import lru_cache
@@ -72,7 +71,9 @@ class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT,
7271
"""
7372
Provides a thread-safe dict-like container which maintains up to
7473
``maxsize`` keys while throwing away the least-recently-used keys beyond
75-
``maxsize``.
74+
``maxsize``. Caution: RecentlyUsedContainer is deprecated and scheduled for
75+
removal in a next major of urllib3.future. It has been replaced by a more
76+
suitable implementation in ``urllib3.util.traffic_police``.
7677
7778
:param maxsize:
7879
Maximum number of recent elements to retain.
@@ -98,13 +99,6 @@ def __init__(
9899
self._container = OrderedDict()
99100
self.lock = RLock()
100101

101-
warnings.warn(
102-
"RecentlyUsedContainer is deprecated and scheduled for removal in urllib3.future v3. "
103-
"It has been replaced by a more suitable implementation in urllib3.util.traffic_police.",
104-
DeprecationWarning,
105-
stacklevel=2,
106-
)
107-
108102
def __getitem__(self, key: _KT) -> _VT:
109103
# Re-insert the item, moving it to the end of the eviction line.
110104
with self.lock:

src/urllib3/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# This file is protected via CODEOWNERS
22
from __future__ import annotations
33

4-
__version__ = "2.8.903"
4+
__version__ = "2.8.904"

src/urllib3/contrib/hface/protocols/http1/_h11.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
from __future__ import annotations
1818

19+
import warnings
1920
from functools import lru_cache
2021

2122
import h11
23+
from h11._state import _SWITCH_UPGRADE, ConnectionState
2224

2325
from ..._stream_matrix import StreamMatrix
2426
from ..._typing import HeadersType
@@ -100,11 +102,35 @@ def headers_from_response(
100102
] + response.headers.raw_items()
101103

102104

105+
class RelaxConnectionState(ConnectionState):
106+
def process_event( # type: ignore[no-untyped-def]
107+
self,
108+
role,
109+
event_type,
110+
server_switch_event=None,
111+
) -> None:
112+
if server_switch_event is not None:
113+
if server_switch_event not in self.pending_switch_proposals:
114+
if server_switch_event is _SWITCH_UPGRADE:
115+
warnings.warn(
116+
f"Received server {server_switch_event} event without a pending proposal. "
117+
"This will raise an exception in a future version. It is temporarily relaxed to match the "
118+
"legacy http.client standard library.",
119+
DeprecationWarning,
120+
stacklevel=2,
121+
)
122+
self.pending_switch_proposals.add(_SWITCH_UPGRADE)
123+
124+
return super().process_event(role, event_type, server_switch_event)
125+
126+
103127
class HTTP1ProtocolHyperImpl(HTTP1Protocol):
104128
implementation: str = "h11"
105129

106130
def __init__(self) -> None:
107131
self._connection: h11.Connection = h11.Connection(h11.CLIENT)
132+
self._connection._cstate = RelaxConnectionState()
133+
108134
self._data_buffer: list[bytes] = []
109135
self._events: StreamMatrix = StreamMatrix()
110136
self._terminated: bool = False
@@ -255,7 +281,7 @@ def _headers_from_h11_response(
255281
)
256282

257283
def _data_from_h11(self, h11_event: h11.Data) -> Event:
258-
return DataReceived(self._current_stream_id, h11_event.data)
284+
return DataReceived(self._current_stream_id, bytes(h11_event.data))
259285

260286
def _connection_terminated(
261287
self, error_code: int = 0, message: str | None = None

0 commit comments

Comments
 (0)