Compare commits

..

1 Commits

Author SHA1 Message Date
Nate Prewitt
35ba878a72 Move class types to init where possible
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
Type Check / typecheck (3.10) (push) Has been cancelled
Type Check / typecheck (3.14) (push) Has been cancelled
2026-05-07 18:14:26 -06:00
23 changed files with 105 additions and 277 deletions

View File

@@ -8,11 +8,12 @@ permissions:
jobs:
build:
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.python-version == '3.15-dev' }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15-dev", "pypy-3.11"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15-dev", "pypy-3.11"]
os: [ubuntu-22.04, macOS-latest, windows-latest]
# Pypy-3.11 can't install openssl-sys with rust
# which prevents us from testing in GHA.

View File

@@ -6,56 +6,19 @@ dev
- \[Short description of non-trivial change.\]
2.34.2 (2026-05-14)
2.34.0 (2026-05-??)
-------------------
- Moved `headers` input type back to `Mapping` to avoid invariance issues
with `MutableMapping` and inferred dict types. Users calling
`Request.headers.update()` may need to narrow typing in their code. (#7441)
2.34.1 (2026-05-13)
-------------------
**Bugfixes**
- Widened `json` input type from `dict` and `list` to `Mapping`
and `Sequence`. (#7436)
- Changed `headers` input type to MutableMapping and removed `None` from
`Request.headers` typing to improve handling for users. (#7431)
- `Response.reason` moved from `str | None` to `str` to improve handling
for users. (#7437)
- Fixed a bug where some bodies with custom `__getattr__` implementations
weren't being properly detected as Iterables. (#7433)
2.34.0 (2026-05-11)
-------------------
**Announcements**
- Requests 2.34.0 introduces inline types, replacing those provided by
typeshed. Public API types should be fully compatible with mypy, pyright,
and ty. We believe types are comprehensive but if you find issues, please
report them to the pinned tracking issue.
Special thanks to @bastimeyer, @cthoyt, @edgarrmondragon, and @srittau for
helping review and test the types ahead of the release. (#7272)
**Improvements**
- Digest Auth hashing algorithms have added `usedforsecurity=False` to clarify
* Requests 2.34.0 introduces inline types, replacing those provided by
typeshed. Public API types should be fully compatible with mypy, pyright,
and ty. (#7272)
* Digest Auth hashing algorithms have added `usedforsecurity=False` to clarify
security considerations. (#7310)
- Requests added support for Python 3.15 based on beta1. Downstream projects
should be able to start testing prior to its release in October. (#7422)
- Requests added support for Python 3.14t. (#7419)
**Bugfixes**
- ``Response.history`` no longer contains a reference to itself, preventing
* ``Response.history`` no longer contains a reference to itself, preventing
accidental looping when traversing the history list. (#7328)
- Requests no longer performs greedy matching on no_proxy domains. The
proxy_bypass implementation has been updated with CPython's fix from
bpo-39057. (#7427)
- Requests no longer incorrectly strips duplicate leading slashes in
URI paths. This should address user issues with specific presigned
URLs. Note the full fix requires urllib3 2.7.0+. (#7315)
2.33.1 (2026-03-30)

View File

@@ -9,7 +9,7 @@ BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://www.sphinx-doc.org/)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.

View File

@@ -30,7 +30,7 @@ Why not Httplib2?
-----------------
Chris Adams gave an excellent summary on
`Hacker News <https://news.ycombinator.com/item?id=2884406>`_:
`Hacker News <http://news.ycombinator.com/item?id=2884406>`_:
httplib2 is part of why you should use requests: it's far more respectable
as a client but not as well documented and it still takes way too much code
@@ -44,7 +44,7 @@ Chris Adams gave an excellent summary on
Disclosure: I'm listed in the requests AUTHORS file but can claim credit
for, oh, about 0.0001% of the awesomeness.
1. https://code.google.com/p/httplib2/issues/detail?id=96 is a good example:
1. http://code.google.com/p/httplib2/issues/detail?id=96 is a good example:
an annoying bug that affected many people, there was a fix available for
months, which worked great when I applied it in a fork and pounded a couple
TB of data through it, but it took over a year to make it into trunk and

View File

@@ -7,4 +7,4 @@ Articles & Talks
- `Issac Kelly's 'Consuming Web APIs' talk <https://issackelly.github.io/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
- `Blog post about Requests via Yum <https://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
- `Russian blog post introducing Requests <https://habr.com/post/126262/>`_
- `Sending JSON in Requests <https://www.coglib.com/~icordasc/blog/2014/11/sending-json-in-requests.html>`_
- `Sending JSON in Requests <http://www.coglib.com/~icordasc/blog/2014/11/sending-json-in-requests.html>`_

View File

@@ -14,7 +14,7 @@ getting a feel for how contributing to this project works. If you have any
questions, feel free to reach out to either `Nate Prewitt`_, `Ian Cordasco`_,
or `Seth Michael Larson`_, the primary maintainers.
.. _Ian Cordasco: https://www.coglib.com/~icordasc/
.. _Ian Cordasco: http://www.coglib.com/~icordasc/
.. _Nate Prewitt: https://www.nateprewitt.com/
.. _Seth Michael Larson: https://sethmlarson.dev/
@@ -132,8 +132,8 @@ files and a semi-formal, yet friendly and approachable, prose style.
When presenting Python code, use single-quoted strings (``'hello'`` instead of
``"hello"``).
.. _reStructuredText: https://docutils.sourceforge.net/rst.html
.. _Sphinx: https://www.sphinx-doc.org/en/master/
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _Sphinx: http://sphinx-doc.org/index.html
.. _bug-reports:

View File

@@ -65,7 +65,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
echo.http://sphinx-doc.org/
exit /b 1
)

View File

@@ -467,7 +467,6 @@ Let's print some request method arguments at runtime::
You can add multiple hooks to a single request. Let's call two hooks at once::
>>> r = requests.get('https://httpbin.org/', hooks={'response': [print_url, record_hook]})
https://httpbin.org/
>>> r.hook_called
True

View File

@@ -37,11 +37,9 @@ classifiers = [
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: Free Threading :: 2 - Beta",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries"
]

View File

@@ -5,8 +5,8 @@
__title__ = "requests"
__description__ = "Python HTTP for Humans."
__url__ = "https://requests.readthedocs.io"
__version__ = "2.34.2"
__build__ = 0x023402
__version__ = "2.34.0.dev1"
__build__ = 0x023400
__author__ = "Kenneth Reitz"
__author_email__ = "me@kennethreitz.org"
__license__ = "Apache-2.0"

View File

@@ -9,7 +9,7 @@ by external code.
from __future__ import annotations
from collections.abc import Callable, Iterable, Mapping, MutableMapping, Sequence
from collections.abc import Callable, Iterable, Mapping, MutableMapping
from typing import (
TYPE_CHECKING,
Any,
@@ -20,8 +20,6 @@ from typing import (
)
_T_co = TypeVar("_T_co", covariant=True)
_KT_co = TypeVar("_KT_co", covariant=True)
_VT_co = TypeVar("_VT_co", covariant=True)
@runtime_checkable
@@ -30,8 +28,8 @@ class SupportsRead(Protocol[_T_co]):
@runtime_checkable
class SupportsItems(Protocol[_KT_co, _VT_co]):
def items(self) -> Iterable[tuple[_KT_co, _VT_co]]: ...
class SupportsItems(Protocol):
def items(self) -> Iterable[tuple[Any, Any]]: ...
# These are needed at runtime for default_hooks() return type
@@ -81,7 +79,7 @@ if TYPE_CHECKING:
str | bytes | int | float | Iterable[str | bytes | int | float] | None
)
ParamsType: TypeAlias = (
SupportsItems[_ParamsMappingKeyType, _ParamsMappingValueType]
Mapping[_ParamsMappingKeyType, _ParamsMappingValueType]
| tuple[tuple[_ParamsMappingKeyType, _ParamsMappingValueType], ...]
| Iterable[tuple[_ParamsMappingKeyType, _ParamsMappingValueType]]
| str
@@ -89,7 +87,7 @@ if TYPE_CHECKING:
| None
)
KVDataType: TypeAlias = Iterable[tuple[Any, Any]] | SupportsItems[Any, Any]
KVDataType: TypeAlias = Iterable[tuple[Any, Any]] | Mapping[Any, Any]
RawDataType: TypeAlias = KVDataType | str | bytes
StreamDataType: TypeAlias = SupportsRead[str | bytes]
@@ -109,9 +107,10 @@ if TYPE_CHECKING:
bytes | str | Iterable[bytes | str] | SupportsRead[bytes | str] | None
)
HeadersType: TypeAlias = Mapping[str, str | bytes] | None
HeadersType: TypeAlias = CaseInsensitiveDict[str] | Mapping[str, str | bytes]
HeadersUpdateType: TypeAlias = Mapping[str, str | bytes | None]
CookiesType: TypeAlias = RequestsCookieJar | Mapping[str, str]
CookiesType: TypeAlias = RequestsCookieJar | CookieJar | None
# Building blocks for FilesType
_FileName: TypeAlias = str | None
@@ -138,26 +137,20 @@ if TYPE_CHECKING:
VerifyType: TypeAlias = bool | str
CertType: TypeAlias = str | tuple[str, str] | None
JsonType: TypeAlias = (
None
| bool
| int
| float
| str
| Sequence["JsonType"]
| Mapping[str, "JsonType"]
None | bool | int | float | str | list["JsonType"] | dict[str, "JsonType"]
)
# TypedDicts for Unpack kwargs (PEP 692)
class BaseRequestKwargs(TypedDict, total=False):
headers: HeadersType
headers: Mapping[str, str | bytes] | None
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None
files: FilesType
auth: AuthType
timeout: TimeoutType
allow_redirects: bool
proxies: dict[str, str] | None
hooks: HooksInputType | None
hooks: HooksType
stream: bool | None
verify: VerifyType | None
cert: CertType

View File

@@ -190,14 +190,6 @@ class HTTPAdapter(BaseAdapter):
"_pool_block",
]
max_retries: Retry
config: dict[str, Any]
proxy_manager: dict[str, Any]
_pool_connections: int
_pool_maxsize: int
_pool_block: bool
poolmanager: _PoolManager
def __init__(
self,
pool_connections: int = DEFAULT_POOLSIZE,
@@ -206,18 +198,19 @@ class HTTPAdapter(BaseAdapter):
pool_block: bool = DEFAULT_POOLBLOCK,
) -> None:
if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False)
self.max_retries: Retry = Retry(0, read=False)
else:
self.max_retries = Retry.from_int(max_retries)
self.config = {}
self.proxy_manager = {}
self.config: dict[str, Any] = {}
self.proxy_manager: dict[str, Any] = {}
super().__init__()
self._pool_connections = pool_connections
self._pool_maxsize = pool_maxsize
self._pool_block = pool_block
self._pool_connections: int = pool_connections
self._pool_maxsize: int = pool_maxsize
self._pool_block: bool = pool_block
self.poolmanager: _PoolManager
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
def __getstate__(self) -> dict[str, Any]:
@@ -590,6 +583,8 @@ class HTTPAdapter(BaseAdapter):
using_socks_proxy = proxy_scheme.startswith("socks")
url = request.path_url
if url.startswith("//"): # Don't confuse urllib3
url = f"/{url.lstrip('/')}"
if is_proxied_http_request and not using_socks_proxy:
url = urldefragauth(request.url)

View File

@@ -85,17 +85,14 @@ class AuthBase:
class HTTPBasicAuth(AuthBase):
"""Attaches HTTP Basic Authentication to the given Request object."""
username: bytes | str
password: bytes | str
@overload
def __init__(self, username: str, password: str) -> None: ...
@overload
def __init__(self, username: bytes, password: bytes) -> None: ...
def __init__(self, username: bytes | str, password: bytes | str) -> None:
self.username = username
self.password = password
self.username: bytes | str = username
self.password: bytes | str = password
def __eq__(self, other: object) -> bool:
return all(
@@ -124,25 +121,16 @@ class HTTPProxyAuth(HTTPBasicAuth):
class HTTPDigestAuth(AuthBase):
"""Attaches HTTP Digest Authentication to the given Request object."""
username: bytes | str
password: bytes | str
_thread_local: threading.local
last_nonce: str
nonce_count: int
chal: dict[str, str]
pos: int | None
num_401_calls: int | None
@overload
def __init__(self, username: str, password: str) -> None: ...
@overload
def __init__(self, username: bytes, password: bytes) -> None: ...
def __init__(self, username: bytes | str, password: bytes | str) -> None:
self.username = username
self.password = password
self.username: bytes | str = username
self.password: bytes | str = password
# Keep state in per-thread local storage
self._thread_local = threading.local()
self._thread_local: threading.local = threading.local()
def init_per_thread_state(self) -> None:
# Ensure state is initialized just once per-thread

View File

@@ -40,13 +40,11 @@ class MockRequest:
probably want `get_cookie_header`, defined below.
"""
type: str
def __init__(self, request: PreparedRequest) -> None:
assert _is_prepared(request)
self._r = request
self._new_headers: dict[str, str] = {}
self.type = urlparse(self._r.url).scheme
self.type: str = urlparse(self._r.url).scheme
def get_type(self) -> str:
return self.type

View File

@@ -22,14 +22,11 @@ class RequestException(IOError):
request.
"""
response: Response | None
request: Request | PreparedRequest | None
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize RequestException with `request` and `response` objects."""
response: Response | None = kwargs.pop("response", None)
self.response = response
self.request = kwargs.pop("request", None)
self.response: Response | None = response
self.request: Request | PreparedRequest | None = kwargs.pop("request", None)
if response is not None and not self.request and hasattr(response, "request"):
self.request = response.request
super().__init__(*args, **kwargs)

View File

@@ -308,21 +308,11 @@ class Request(RequestHooksMixin):
<PreparedRequest [GET]>
"""
method: str | None
url: _t.UriType | None
headers: Mapping[str, str | bytes]
files: _t.FilesType
data: _t.DataType
json: _t.JsonType
params: _t.ParamsType
auth: _t.AuthType
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None
def __init__(
self,
method: str | None = None,
url: _t.UriType | None = None,
headers: _t.HeadersType = None,
headers: Mapping[str, str | bytes] | None = None,
files: _t.FilesType = None,
data: _t.DataType = None,
params: _t.ParamsType = None,
@@ -338,19 +328,19 @@ class Request(RequestHooksMixin):
params = {} if params is None else params
hooks = {} if hooks is None else hooks
self.hooks = default_hooks()
self.hooks: dict[str, list[_t.HookType]] = default_hooks()
for k, v in list(hooks.items()):
self.register_hook(event=k, hook=v)
self.method = method
self.url = url
self.headers = headers
self.files = files
self.data = data
self.json = json
self.params = params
self.auth = auth
self.cookies = cookies
self.method: str | None = method
self.url: _t.UriType | None = url
self.headers: _t.HeadersType | None = headers
self.files: _t.FilesType = files
self.data: _t.DataType = data
self.json: _t.JsonType = json
self.params: _t.ParamsType = params
self.auth: _t.AuthType = auth
self.cookies: _t.CookiesType | dict[str, str] = cookies
def __repr__(self) -> str:
return f"<Request [{self.method}]>"
@@ -394,30 +384,22 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
<Response [200]>
"""
method: str | None
url: str | None
headers: CaseInsensitiveDict[str | bytes]
_cookies: RequestsCookieJar | CookieJar | None
body: _t.BodyType
hooks: dict[str, list[_t.HookType]]
_body_position: int | object | None
def __init__(self) -> None:
#: HTTP verb to send to the server.
self.method = None
self.method: str | None = None
#: HTTP URL to send the request to.
self.url = None
self.url: str | None = None
#: dictionary of HTTP headers.
self.headers = None # type: ignore[assignment]
self.headers: CaseInsensitiveDict[str | bytes] = None # type: ignore[assignment]
# The `CookieJar` used to create the Cookie header will be stored here
# after prepare_cookies is called
self._cookies = None
self._cookies: _t.CookiesType = None
#: request body to send to the server.
self.body = None
self.body: _t.BodyType = None
#: dictionary of callback hooks, for internal usage.
self.hooks = default_hooks()
self.hooks: dict[str, list[_t.HookType]] = default_hooks()
#: integer denoting starting position of a readable file-like body.
self._body_position = None
self._body_position: int | object | None = None
def prepare(
self,
@@ -596,9 +578,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if not isinstance(body, bytes):
body = body.encode("utf-8")
# data that proxies attributes to underlying objects needs hasattr
is_iterable = isinstance(data, Iterable) or hasattr(data, "__iter__")
if is_iterable and not isinstance(data, (str, bytes, list, tuple, Mapping)):
if isinstance(data, Iterable) and not isinstance(
data, (str, bytes, list, tuple, Mapping)
):
try:
length = super_len(data)
except (TypeError, AttributeError, UnsupportedOperation):
@@ -732,19 +714,6 @@ class Response:
server's response to an HTTP request.
"""
_content: bytes | Literal[False] | None
_content_consumed: bool
_next: PreparedRequest | None
status_code: int
headers: CaseInsensitiveDict[str]
raw: Any
url: str
encoding: str | None
history: list[Response]
reason: str
cookies: RequestsCookieJar
elapsed: datetime.timedelta
request: PreparedRequest
connection: HTTPAdapter
__attrs__: list[str] = [
@@ -761,39 +730,39 @@ class Response:
]
def __init__(self) -> None:
self._content = False
self._content_consumed = False
self._next = None
self._content: bytes | Literal[False] | None = False
self._content_consumed: bool = False
self._next: PreparedRequest | None = None
#: Integer Code of responded HTTP Status, e.g. 404 or 200.
self.status_code = None # type: ignore[assignment]
self.status_code: int = None # type: ignore[assignment]
#: Case-insensitive Dictionary of Response Headers.
#: For example, ``headers['content-encoding']`` will return the
#: value of a ``'Content-Encoding'`` response header.
self.headers = CaseInsensitiveDict()
self.headers: CaseInsensitiveDict[str] = CaseInsensitiveDict()
#: File-like object representation of response (for advanced usage).
#: Use of ``raw`` requires that ``stream=True`` be set on the request.
#: This requirement does not apply for use internally to Requests.
self.raw = None
self.raw: Any = None
#: Final URL location of Response.
self.url = None # type: ignore[assignment]
self.url: str = None # type: ignore[assignment]
#: Encoding to decode with when accessing r.text.
self.encoding = None
self.encoding: str | None = None
#: A list of :class:`Response <Response>` objects from
#: the history of the Request. Any redirect responses will end
#: up here. The list is sorted from the oldest to the most recent request.
self.history = []
self.history: list[Response] = []
#: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".
self.reason = None # type: ignore[assignment]
self.reason: str | None = None
#: A CookieJar of Cookies the server sent back.
self.cookies = cookiejar_from_dict({})
self.cookies: RequestsCookieJar = cookiejar_from_dict({})
#: The amount of time elapsed between sending the request
#: and the arrival of the response (as a timedelta).
@@ -801,11 +770,11 @@ class Response:
#: the first byte of the request and finishing parsing the headers. It
#: is therefore unaffected by consuming the response content or the
#: value of the ``stream`` keyword argument.
self.elapsed = datetime.timedelta(0)
self.elapsed: datetime.timedelta = datetime.timedelta(0)
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
#: is a response.
self.request = None # type: ignore[assignment]
self.request: PreparedRequest = None # type: ignore[assignment]
def __enter__(self) -> Self:
return self

View File

@@ -411,19 +411,6 @@ class Session(SessionRedirectMixin):
<Response [200]>
"""
headers: CaseInsensitiveDict[str]
auth: _t.AuthType
proxies: dict[str, str]
hooks: dict[str, list[_t.HookType]]
params: MutableMapping[str, Any]
stream: bool
verify: _t.VerifyType
cert: _t.CertType
max_redirects: int
trust_env: bool
cookies: RequestsCookieJar
adapters: MutableMapping[str, BaseAdapter]
__attrs__: list[str] = [
"headers",
"cookies",
@@ -443,27 +430,27 @@ class Session(SessionRedirectMixin):
#: A case-insensitive dictionary of headers to be sent on each
#: :class:`Request <Request>` sent from this
#: :class:`Session <Session>`.
self.headers = default_headers()
self.headers: CaseInsensitiveDict[str] = default_headers()
#: Default Authentication tuple or object to attach to
#: :class:`Request <Request>`.
self.auth = None
self.auth: _t.AuthType = None
#: Dictionary mapping protocol or protocol and host to the URL of the proxy
#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
#: be used on each :class:`Request <Request>`.
self.proxies = {}
self.proxies: dict[str, str] = {}
#: Event-handling hooks.
self.hooks = default_hooks()
self.hooks: dict[str, list[_t.HookType]] = default_hooks()
#: Dictionary of querystring data to attach to each
#: :class:`Request <Request>`. The dictionary values may be lists for
#: representing multivalued query parameters.
self.params = {}
self.params: MutableMapping[str, Any] = {}
#: Stream response content default.
self.stream = False
self.stream: bool = False
#: SSL Verification default.
#: Defaults to `True`, requiring requests to verify the TLS certificate at the
@@ -475,30 +462,30 @@ class Session(SessionRedirectMixin):
#: Only set this to `False` for testing.
#: If verify is set to a string, it must be the path to a CA bundle file
#: that will be used to verify the TLS certificate.
self.verify = True
self.verify: _t.VerifyType = True
#: SSL client certificate default, if String, path to ssl client
#: cert file (.pem). If Tuple, ('cert', 'key') pair.
self.cert = None
self.cert: _t.CertType = None
#: Maximum number of redirects allowed. If the request exceeds this
#: limit, a :class:`TooManyRedirects` exception is raised.
#: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
#: 30.
self.max_redirects = DEFAULT_REDIRECT_LIMIT
self.max_redirects: int = DEFAULT_REDIRECT_LIMIT
#: Trust environment settings for proxy configuration, default
#: authentication and similar.
self.trust_env = True
self.trust_env: bool = True
#: A CookieJar containing all currently outstanding cookies set on this
#: session. By default it is a
#: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
#: may be any other ``cookielib.CookieJar`` compatible object.
self.cookies = cookiejar_from_dict({})
self.cookies: RequestsCookieJar = cookiejar_from_dict({})
# Default connection adapters.
self.adapters = OrderedDict()
self.adapters: MutableMapping[str, BaseAdapter] = OrderedDict()
self.mount("https://", HTTPAdapter())
self.mount("http://", HTTPAdapter())
@@ -560,14 +547,14 @@ class Session(SessionRedirectMixin):
url: _t.UriType,
params: _t.ParamsType = None,
data: _t.DataType = None,
headers: _t.HeadersType = None,
headers: Mapping[str, str | bytes] | None = None,
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
files: _t.FilesType = None,
auth: _t.AuthType = None,
timeout: _t.TimeoutType = None,
allow_redirects: bool = True,
proxies: dict[str, str] | None = None,
hooks: _t.HooksInputType | None = None,
hooks: _t.HooksType = None,
stream: bool | None = None,
verify: _t.VerifyType | None = None,
cert: _t.CertType = None,
@@ -652,23 +639,16 @@ class Session(SessionRedirectMixin):
return resp
def get(
self,
url: _t.UriType,
params: _t.ParamsType = None,
**kwargs: Unpack[_t.GetKwargs],
) -> Response:
def get(self, url: _t.UriType, **kwargs: Unpack[_t.GetKwargs]) -> Response:
r"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault("allow_redirects", True)
return self.request("GET", url, params=params, **kwargs)
return self.request("GET", url, **kwargs)
def options(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
r"""Sends a OPTIONS request. Returns :class:`Response` object.

View File

@@ -44,14 +44,12 @@ class CaseInsensitiveDict(MutableMapping[str, _VT], Generic[_VT]):
behavior is undefined.
"""
_store: OrderedDict[str, tuple[str, _VT]]
def __init__(
self,
data: Mapping[str, _VT] | Iterable[tuple[str, _VT]] | None = None,
**kwargs: _VT,
) -> None:
self._store = OrderedDict()
self._store: OrderedDict[str, tuple[str, _VT]] = OrderedDict()
if data is None:
data = {}
self.update(data, **kwargs)
@@ -96,10 +94,8 @@ class CaseInsensitiveDict(MutableMapping[str, _VT], Generic[_VT]):
class LookupDict(dict[str, _VT]):
"""Dictionary lookup object."""
name: Any
def __init__(self, name: Any = None) -> None:
self.name = name
self.name: bytes | str = name
super().__init__()
def __repr__(self) -> str:

View File

@@ -147,7 +147,7 @@ if sys.platform == "win32":
def dict_to_sequence(
d: _t.SupportsItems[Any, Any] | Iterable[tuple[Any, Any]],
d: _t.SupportsItems | Iterable[tuple[Any, Any]],
) -> Iterable[tuple[Any, Any]]:
"""Returns an internal sequence dictionary update."""
@@ -371,10 +371,10 @@ def from_key_val_list(
def to_key_val_list(value: None) -> None: ...
@overload
def to_key_val_list(
value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]],
value: Mapping[_KT, _VT] | Iterable[tuple[_KT, _VT]],
) -> list[tuple[_KT, _VT]]: ...
def to_key_val_list(
value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]] | None,
value: Mapping[_KT, _VT] | Iterable[tuple[_KT, _VT]] | None,
) -> list[tuple[_KT, _VT]] | None:
"""Take an object and test to see if it can be represented as a
dictionary. If it can be, return a list of tuples, e.g.,
@@ -851,11 +851,9 @@ def should_bypass_proxies(url: str, no_proxy: str | None) -> bool:
host_with_port += f":{parsed.port}"
for host in no_proxy_hosts:
host = host.lstrip(".")
if hostname == host or host_with_port == host:
return True
host = "." + host
if hostname.endswith(host) or host_with_port.endswith(host):
# The URL does match something in no_proxy, so we don't want
# to apply the proxies on this URL.
return True
with set_environ("no_proxy", no_proxy_arg):

View File

@@ -22,15 +22,6 @@ def prepare_url(value):
return inner
@pytest.fixture(autouse=True)
def clean_proxy_environ(monkeypatch):
"""Remove proxy related environment variables for every test."""
proxy_vars = ("http_proxy", "https_proxy", "no_proxy", "ftp_proxy", "all_proxy")
for var in proxy_vars:
monkeypatch.delenv(var, raising=False)
monkeypatch.delenv(var.upper(), raising=False)
@pytest.fixture
def httpbin(httpbin):
return prepare_url(httpbin)

View File

@@ -1,8 +1,8 @@
import requests.adapters
def test_request_url_handles_leading_path_separators():
def test_request_url_trims_leading_path_separators():
"""See also https://github.com/psf/requests/issues/6643."""
a = requests.adapters.HTTPAdapter()
p = requests.Request(method="GET", url="http://127.0.0.1:10000//v:h").prepare()
assert "//v:h" == a.request_url(p, {})
assert "/v:h" == a.request_url(p, {})

View File

@@ -2073,21 +2073,6 @@ class TestRequests:
assert "Unable to rewind request body" in str(e)
def test_getattr_proxy_stream_follows_redirect(self, httpbin):
"""Ensure stream wrappers that don't implement __iter__ directly are still detected."""
class AttrProxy:
def __init__(self):
self._file = io.BytesIO(b"data")
def __getattr__(self, name):
return getattr(self._file, name)
r = requests.post(
httpbin("redirect-to?url=/post&status_code=307"), data=AttrProxy()
)
assert r.json()["data"] == "data"
def _patch_adapter_gzipped_redirect(self, session, url):
adapter = session.get_adapter(url=url)
org_build_response = adapter.build_response

View File

@@ -844,29 +844,6 @@ def test_should_bypass_proxies_no_proxy(url, expected, monkeypatch):
assert should_bypass_proxies(url, no_proxy=no_proxy) == expected
@pytest.mark.parametrize(
"url, expected",
(
("http://localhost/", True),
("http://anotherdomain.com:8888/", True),
("http://newdomain.com:1234/", True),
("http://www.newdomain.com:1234/", True),
("http://foo.d.o.t/", True),
("http://d.o.t/", True),
("http://prelocalhost/", False),
("http://newdomain.com/", False),
("http://newdomain.com:1235/", False),
),
)
def test_should_bypass_proxies_no_proxy_domain_boundary(url, expected):
"""Ensure no_proxy matching respects domain boundaries and does not
greedily match domains that merely endswith the no_proxy entry.
See CPython bpo-39057.
"""
no_proxy = "localhost, anotherdomain.com, newdomain.com:1234, .d.o.t"
assert should_bypass_proxies(url, no_proxy=no_proxy) == expected
@pytest.mark.skipif(os.name != "nt", reason="Test only on Windows")
@pytest.mark.parametrize(
"url, expected, override",