Skip to content

Commit ee4f225

Browse files
authored
Release 3.12.2 (#206)
3.12.2 (2025-01-22) ------------------- **Fixed** - Parsing of special scheme that exceed 9 characters on rare custom adapters. **Changed** - Default `Content-Type` for json payloads changed from `application/json; charset="utf-8"` to `application/json;charset=utf-8`. While the previous default was valid, this is the preferred value according to RFC9110. (#204) **Misc** - Removed a useless hasattr control to support older version of urllib3-future (<2.5). - Updated our pre-commit configuration and reformatted files accordingly.
2 parents 42176d9 + 16a2a08 commit ee4f225

35 files changed

+707
-1275
lines changed

.pre-commit-config.yaml

+4-9
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,23 @@ exclude: 'docs/|ext/'
22

33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v4.4.0
5+
rev: v5.0.0
66
hooks:
77
- id: check-yaml
88
- id: debug-statements
99
- id: end-of-file-fixer
1010
- id: trailing-whitespace
11-
- repo: https://github.com/asottile/pyupgrade
12-
rev: v3.15.1
13-
hooks:
14-
- id: pyupgrade
15-
args: [--py37-plus]
1611
- repo: https://github.com/astral-sh/ruff-pre-commit
1712
# Ruff version.
18-
rev: v0.3.2
13+
rev: v0.9.1
1914
hooks:
2015
# Run the linter.
2116
- id: ruff
22-
args: [ --fix ]
17+
args: [ --fix, --target-version=py37 ]
2318
# Run the formatter.
2419
- id: ruff-format
2520
- repo: https://github.com/pre-commit/mirrors-mypy
26-
rev: v1.12.1
21+
rev: v1.14.1
2722
hooks:
2823
- id: mypy
2924
args: [--check-untyped-defs]

HISTORY.md

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
Release History
22
===============
33

4+
3.12.2 (2025-01-22)
5+
-------------------
6+
7+
**Fixed**
8+
- Parsing of special scheme that exceed 9 characters on rare custom adapters.
9+
10+
**Changed**
11+
- Default `Content-Type` for json payloads changed from `application/json; charset="utf-8"` to `application/json;charset=utf-8`.
12+
While the previous default was valid, this is the preferred value according to RFC9110. (#204)
13+
14+
**Misc**
15+
- Removed a useless hasattr control to support older version of urllib3-future (<2.5).
16+
- Updated our pre-commit configuration and reformatted files accordingly.
17+
418
3.12.1 (2025-01-03)
519
-------------------
620

pyproject.toml

+14-5
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,20 @@ packages = [
9494
"src/niquests",
9595
]
9696

97-
[tool.isort]
98-
profile = "black"
99-
src_paths = ["src/niquests", "tests"]
100-
honor_noqa = true
101-
add_imports = "from __future__ import annotations"
97+
[tool.ruff]
98+
line-length = 128
99+
100+
[tool.ruff.lint]
101+
select = [
102+
"E", # pycodestyle
103+
"F", # Pyflakes
104+
"W", # pycodestyle
105+
"I", # isort
106+
"U", # pyupgrade
107+
]
108+
109+
[tool.ruff.lint.isort]
110+
required-imports = ["from __future__ import annotations"]
102111

103112
[tool.pytest.ini_options]
104113
addopts = "--doctest-modules"

src/niquests/__init__.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,17 @@
4848
from ._compat import HAS_LEGACY_URLLIB3
4949

5050
if HAS_LEGACY_URLLIB3 is False:
51+
from urllib3 import Retry as RetryConfiguration
52+
from urllib3 import Timeout as TimeoutConfiguration
5153
from urllib3.exceptions import DependencyWarning
52-
from urllib3 import Timeout as TimeoutConfiguration, Retry as RetryConfiguration
5354
else:
54-
from urllib3_future.exceptions import DependencyWarning # type: ignore[assignment]
5555
from urllib3_future import ( # type: ignore[assignment]
56-
Timeout as TimeoutConfiguration,
5756
Retry as RetryConfiguration,
5857
)
58+
from urllib3_future import ( # type: ignore[assignment]
59+
Timeout as TimeoutConfiguration,
60+
)
61+
from urllib3_future.exceptions import DependencyWarning # type: ignore[assignment]
5962

6063
# urllib3's DependencyWarnings should be silenced.
6164
warnings.simplefilter("ignore", DependencyWarning)
@@ -89,7 +92,7 @@
8992
TooManyRedirects,
9093
URLRequired,
9194
)
92-
from .models import PreparedRequest, Request, Response, AsyncResponse
95+
from .models import AsyncResponse, PreparedRequest, Request, Response
9396
from .sessions import Session
9497
from .status_codes import codes
9598

src/niquests/__version__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
__url__: str = "https://niquests.readthedocs.io"
1010

1111
__version__: str
12-
__version__ = "3.12.1"
12+
__version__ = "3.12.2"
1313

14-
__build__: int = 0x031201
14+
__build__: int = 0x031202
1515
__author__: str = "Kenneth Reitz"
1616
__author_email__: str = "me@kennethreitz.org"
1717
__license__: str = "Apache-2.0"

src/niquests/_async.py

+50-75
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from __future__ import annotations
22

33
import os
4-
import typing
5-
import warnings
64
import sys
75
import time
8-
from http.cookiejar import CookieJar
6+
import typing
7+
import warnings
98
from collections import OrderedDict
109
from datetime import timedelta
10+
from http.cookiejar import CookieJar
1111
from urllib.parse import urljoin, urlparse
12+
1213
from .status_codes import codes
1314

1415
if typing.TYPE_CHECKING:
@@ -22,17 +23,26 @@
2223
from urllib3.contrib.webextensions._async import load_extension
2324
else: # Defensive: tested in separate/isolated CI
2425
from urllib3_future import ConnectionInfo # type: ignore[assignment]
25-
from urllib3_future.contrib.resolver._async import AsyncBaseResolver # type: ignore[assignment]
26-
from urllib3_future.contrib.webextensions._async import load_extension # type: ignore[assignment]
26+
from urllib3_future.contrib.resolver._async import ( # type: ignore[assignment]
27+
AsyncBaseResolver,
28+
)
29+
from urllib3_future.contrib.webextensions._async import ( # type: ignore[assignment]
30+
load_extension,
31+
)
2732

2833
from ._constant import (
34+
DEFAULT_POOLSIZE,
35+
DEFAULT_RETRIES,
2936
READ_DEFAULT_TIMEOUT,
3037
WRITE_DEFAULT_TIMEOUT,
31-
DEFAULT_RETRIES,
32-
DEFAULT_POOLSIZE,
3338
)
3439
from ._typing import (
40+
AsyncBodyType,
41+
AsyncHookType,
42+
AsyncHttpAuthenticationType,
43+
AsyncResolverType,
3544
BodyType,
45+
CacheLayerAltSvcType,
3646
CookiesType,
3747
HeadersType,
3848
HookType,
@@ -42,51 +52,46 @@
4252
MultiPartFilesType,
4353
ProxyType,
4454
QueryParameterType,
55+
RetryType,
4556
TimeoutType,
4657
TLSClientCertType,
4758
TLSVerifyType,
48-
AsyncResolverType,
49-
CacheLayerAltSvcType,
50-
RetryType,
51-
AsyncHookType,
52-
AsyncBodyType,
53-
AsyncHttpAuthenticationType,
59+
)
60+
from .adapters import AsyncBaseAdapter, AsyncHTTPAdapter
61+
from .cookies import (
62+
RequestsCookieJar,
63+
cookiejar_from_dict,
64+
extract_cookies_to_jar,
65+
merge_cookies,
5466
)
5567
from .exceptions import (
5668
ChunkedEncodingError,
5769
ContentDecodingError,
58-
TooManyRedirects,
5970
InvalidSchema,
71+
TooManyRedirects,
6072
)
6173
from .hooks import async_dispatch_hook, default_hooks
6274
from .models import (
75+
DEFAULT_REDIRECT_LIMIT,
76+
AsyncResponse,
6377
PreparedRequest,
6478
Request,
6579
Response,
66-
DEFAULT_REDIRECT_LIMIT,
6780
TransferProgress,
68-
AsyncResponse,
6981
)
7082
from .sessions import Session
83+
from .structures import AsyncQuicSharedCache
7184
from .utils import (
85+
_deepcopy_ci,
86+
_swap_context,
7287
create_async_resolver,
7388
default_headers,
89+
is_ocsp_capable,
90+
parse_scheme,
91+
requote_uri,
7492
resolve_proxies,
7593
rewind_body,
76-
requote_uri,
77-
_swap_context,
78-
_deepcopy_ci,
79-
parse_scheme,
80-
is_ocsp_capable,
81-
)
82-
from .cookies import (
83-
RequestsCookieJar,
84-
cookiejar_from_dict,
85-
extract_cookies_to_jar,
86-
merge_cookies,
8794
)
88-
from .structures import AsyncQuicSharedCache
89-
from .adapters import AsyncBaseAdapter, AsyncHTTPAdapter
9095

9196
# Preferred clock, based on which one is more accurate on a given system.
9297
if sys.platform == "win32":
@@ -163,9 +168,7 @@ def __init__(
163168
self.proxies: ProxyType = {}
164169

165170
#: Event-handling hooks.
166-
self.hooks: AsyncHookType[PreparedRequest | Response | AsyncResponse] = (
167-
default_hooks() # type: ignore[assignment]
168-
)
171+
self.hooks: AsyncHookType[PreparedRequest | Response | AsyncResponse] = default_hooks() # type: ignore[assignment]
169172

170173
#: Dictionary of querystring data to attach to each
171174
#: :class:`Request <Request>`. The dictionary values may be lists for
@@ -232,18 +235,12 @@ def __init__(
232235
#: session. By default it is a
233236
#: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
234237
#: may be any other ``cookielib.CookieJar`` compatible object.
235-
self.cookies: RequestsCookieJar | CookieJar = cookiejar_from_dict(
236-
{}, thread_free=True
237-
)
238+
self.cookies: RequestsCookieJar | CookieJar = cookiejar_from_dict({}, thread_free=True)
238239

239240
#: A simple dict that allows us to persist which server support QUIC
240241
#: It is simply forwarded to urllib3.future that handle the caching logic.
241242
#: Can be any mutable mapping.
242-
self.quic_cache_layer = (
243-
quic_cache_layer
244-
if quic_cache_layer is not None
245-
else AsyncQuicSharedCache(max_size=12_288)
246-
)
243+
self.quic_cache_layer = quic_cache_layer if quic_cache_layer is not None else AsyncQuicSharedCache(max_size=12_288)
247244

248245
#: Don't try to manipulate this object.
249246
#: It cannot be pickled and accessing this object may cause
@@ -291,9 +288,7 @@ def __init__(
291288
)
292289

293290
def __enter__(self) -> typing.NoReturn:
294-
raise SyntaxError(
295-
'You probably meant "async with". Did you forget to prepend the "async" keyword?'
296-
)
291+
raise SyntaxError('You probably meant "async with". Did you forget to prepend the "async" keyword?')
297292

298293
async def __aenter__(self) -> AsyncSession:
299294
return self
@@ -367,10 +362,7 @@ def get_adapter(self, url: str) -> AsyncBaseAdapter: # type: ignore[override]
367362
try:
368363
extension = load_extension(scheme, implementation=implementation)
369364
for prefix, adapter in self.adapters.items():
370-
if (
371-
scheme in extension.supported_schemes()
372-
and extension.scheme_to_http_scheme(scheme) == parse_scheme(prefix)
373-
):
365+
if scheme in extension.supported_schemes() and extension.scheme_to_http_scheme(scheme) == parse_scheme(prefix):
374366
return adapter
375367
except ImportError:
376368
pass
@@ -383,9 +375,7 @@ def get_adapter(self, url: str) -> AsyncBaseAdapter: # type: ignore[override]
383375
additional_hint = ""
384376

385377
# Nothing matches :-/
386-
raise InvalidSchema(
387-
f"No connection adapters were found for {url!r}{additional_hint}"
388-
)
378+
raise InvalidSchema(f"No connection adapters were found for {url!r}{additional_hint}")
389379

390380
async def send( # type: ignore[override]
391381
self, request: PreparedRequest, **kwargs: typing.Any
@@ -427,21 +417,16 @@ async def on_post_connection(conn_info: ConnectionInfo) -> None:
427417
nonlocal ptr_request, request, kwargs
428418
ptr_request.conn_info = conn_info
429419

430-
if (
431-
ptr_request.url
432-
and ptr_request.url.startswith("https://")
433-
and kwargs["verify"]
434-
and is_ocsp_capable(conn_info)
435-
):
436-
strict_ocsp_enabled: bool = (
437-
os.environ.get("NIQUESTS_STRICT_OCSP", "0") != "0"
438-
)
420+
if ptr_request.url and ptr_request.url.startswith("https://") and kwargs["verify"] and is_ocsp_capable(conn_info):
421+
strict_ocsp_enabled: bool = os.environ.get("NIQUESTS_STRICT_OCSP", "0") != "0"
439422

440423
try:
441424
from .extensions._async_ocsp import (
442-
verify as ocsp_verify,
443425
InMemoryRevocationStatus,
444426
)
427+
from .extensions._async_ocsp import (
428+
verify as ocsp_verify,
429+
)
445430
except ImportError:
446431
pass
447432
else:
@@ -608,9 +593,7 @@ async def _redirect_method_ref(x, y):
608593
# Resolve redirects if allowed.
609594
if allow_redirects:
610595
# Redirect resolving generator.
611-
gen = self.resolve_redirects(
612-
r, request, yield_requests_trail=True, **kwargs
613-
)
596+
gen = self.resolve_redirects(r, request, yield_requests_trail=True, **kwargs)
614597
history = []
615598

616599
async for resp_or_req in gen:
@@ -693,9 +676,7 @@ async def resolve_redirects( # type: ignore[override]
693676
await resp.raw.read(decode_content=False)
694677

695678
if len(resp.history) >= self.max_redirects:
696-
raise TooManyRedirects(
697-
f"Exceeded {self.max_redirects} redirects.", response=resp
698-
)
679+
raise TooManyRedirects(f"Exceeded {self.max_redirects} redirects.", response=resp)
699680

700681
# Release the connection back into the pool.
701682
if isinstance(resp, AsyncResponse):
@@ -715,9 +696,7 @@ async def resolve_redirects( # type: ignore[override]
715696
parsed = urlparse(url)
716697
if parsed.fragment == "" and previous_fragment:
717698
parsed = parsed._replace(
718-
fragment=previous_fragment
719-
if isinstance(previous_fragment, str)
720-
else previous_fragment.decode("utf-8")
699+
fragment=previous_fragment if isinstance(previous_fragment, str) else previous_fragment.decode("utf-8")
721700
)
722701
elif parsed.fragment:
723702
previous_fragment = parsed.fragment
@@ -728,9 +707,7 @@ async def resolve_redirects( # type: ignore[override]
728707
# Compliant with RFC3986, we percent encode the url.
729708
if not parsed.netloc:
730709
url = urljoin(resp.url, requote_uri(url)) # type: ignore[type-var]
731-
assert isinstance(
732-
url, str
733-
), f"urljoin produced {type(url)} instead of str"
710+
assert isinstance(url, str), f"urljoin produced {type(url)} instead of str"
734711
else:
735712
url = requote_uri(url)
736713

@@ -910,9 +887,7 @@ async def request( # type: ignore[override]
910887

911888
proxies = proxies or {}
912889

913-
settings = self.merge_environment_settings(
914-
prep.url, proxies, stream, verify, cert
915-
)
890+
settings = self.merge_environment_settings(prep.url, proxies, stream, verify, cert)
916891

917892
# Send the request.
918893
send_kwargs = {

src/niquests/_compat.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
urllib3 = None # type: ignore[assignment]
2929

3030

31-
if (urllib3 is None and urllib3_future is None) or (
32-
HAS_LEGACY_URLLIB3 and urllib3_future is None
33-
):
31+
if (urllib3 is None and urllib3_future is None) or (HAS_LEGACY_URLLIB3 and urllib3_future is None):
3432
raise RuntimeError( # Defensive: tested in separate CI
3533
"This is awkward but your environment is missing urllib3-future. "
3634
"Your environment seems broken. "

0 commit comments

Comments
 (0)