Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dae7ef63b4 | ||
|
|
5460f467b0 | ||
|
|
6af0b94158 | ||
|
|
8f8b212de8 | ||
|
|
8068356288 | ||
|
|
1f6589ec3a | ||
|
|
414f0513c3 | ||
|
|
ded32878c0 | ||
|
|
69f8484704 | ||
|
|
b17c61b618 | ||
|
|
f361ead047 | ||
|
|
d38495c906 | ||
|
|
4c800e9aea | ||
|
|
23953c0c87 | ||
|
|
4ed3d1b320 | ||
|
|
d44b67eb55 | ||
|
|
74c56d5ffb | ||
|
|
d64b9ad4bf | ||
|
|
6f66281a1d | ||
|
|
6f205ff422 | ||
|
|
661970d171 | ||
|
|
fbd0eb3732 | ||
|
|
1190afd14f | ||
|
|
e50e594529 | ||
|
|
3be097d450 | ||
|
|
a634611dd1 | ||
|
|
c4367f231b | ||
|
|
cd90742ed9 | ||
|
|
6e83187b8f | ||
|
|
84d10f0be8 |
81
.github/AI_POLICY.md
vendored
Normal file
81
.github/AI_POLICY.md
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
# Generative AI / LLM Policy
|
||||
|
||||
We appreciate that we can't realistically police how you author your pull requests, which includes whether you employ large-language model (LLM)-based development tools.
|
||||
So, we don't.
|
||||
|
||||
However, due to both legal and human reasons, we have to establish boundaries.
|
||||
|
||||
> [!CAUTION]
|
||||
> **TL;DR:**
|
||||
> - We take the responsibility for this project very seriously and we expect you to take your responsibility for your contributions seriously, too.
|
||||
> This used to be a given, but it changed now that a pull request is just one prompt away.
|
||||
>
|
||||
> - Every contribution has to be backed by a human who unequivocally owns the copyright for all changes.
|
||||
> No LLM bots in `Co-authored-by:`s.
|
||||
>
|
||||
> - DoS-by-slop leads to a permanent ban.
|
||||
>
|
||||
> - Absolutely **no** unsupervised agentic tools like OpenClaw.
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> By submitting a pull request, you certify that:
|
||||
>
|
||||
> - You are the author of the contribution or have the legal right to submit it.
|
||||
> - You either hold the copyright to the changes or have explicit legal authorization to contribute them under this project's license.
|
||||
> - You understand the code.
|
||||
> - You accept full responsibility for it.
|
||||
|
||||
|
||||
## Legal
|
||||
|
||||
There is ongoing legal uncertainty regarding the copyright status of LLM-generated works and their provenance.
|
||||
Since we do not have a formal [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_license_agreement) (CLA), you retain your copyright to your changes to this project.
|
||||
|
||||
Therefore, allowing contributions by LLMs has unpredictable consequences for the copyright status of this project – even when leaving aside possible copyright violations due to plagiarism.
|
||||
|
||||
|
||||
## Human
|
||||
|
||||
As the makers of software that is used by millions of people worldwide and with a reputation for high-quality maintenance, we take our responsibility to our users very seriously.
|
||||
No matter what LLM vendors or boosters on LinkedIn tell you, we have to manually review every change before merging, because it's **our responsibility** to keep the project stable.
|
||||
|
||||
Please understand that by opening low-quality pull requests you're not helping anyone.
|
||||
Worse, you're [poisoning the open source ecosystem](https://lwn.net/Articles/1058266/) that was precarious even before the arrival of LLM tools.
|
||||
Having to wade through plausible-looking-but-low-quality pull requests and trying to determine which ones are legit is extremely demoralizing and has already burned out many good maintainers.
|
||||
|
||||
Put bluntly, we have no time or interest to become part of your vibe coding loop where you drop LLM slop at our door, we spend time and energy to review it, and you just feed it back into the LLM for another iteration.
|
||||
|
||||
This dynamic is especially pernicious because it poisons the well for mentoring new contributors which we are committed to.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
In practice, this means:
|
||||
|
||||
- Pull requests that have an LLM product listed as co-author can't be merged and will be closed without further discussion.
|
||||
We cannot risk the copyright status of this project.
|
||||
|
||||
If you used LLM tools during development, you may still submit – but you must remove any LLM co-author tags and take full ownership of every line.
|
||||
|
||||
- By submitting a pull request, **you** take full **technical and legal** responsibility for the contents of the pull request and promise that **you** hold the copyright for the changes submitted.
|
||||
|
||||
"An LLM wrote it" is **not** an acceptable response to questions or critique.
|
||||
**If you cannot explain and defend the changes you submit, do not submit them** and open a high-quality bug report/feature request instead.
|
||||
|
||||
- Accounts that exercise bot-like behavior – like automated mass pull requests – will be permanently banned, whether they belong to a human or not.
|
||||
|
||||
- Do **not** post LLM-generated review comments – we can prompt LLMs ourselves should we desire their wisdom.
|
||||
Do **not** post summaries unless you've fact-checked them and take responsibility for 100% of their content.
|
||||
Remember that *all* LLM output *looks* **plausible**.
|
||||
When using these tools, it's **your** responsibility to ensure that it's also **correct** and has a reasonable signal-to-noise ratio.
|
||||
|
||||
---
|
||||
|
||||
# Credits, Attribution
|
||||
|
||||
The original version of this can be found in [pyca/service-identity][] and is
|
||||
used with permission from Hynek Schlawack.
|
||||
|
||||
[pyca/service-identity]:
|
||||
https://github.com/pyca/service-identity/blob/fa91bf55cfda64145aa3d202cc84059befb98af4/.github/AI_POLICY.md
|
||||
14
.github/CONTRIBUTING.md
vendored
14
.github/CONTRIBUTING.md
vendored
@@ -1,7 +1,8 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
Before opening any issues or proposing any pull requests, please read
|
||||
our [Contributor's Guide](https://requests.readthedocs.io/en/latest/dev/contributing/).
|
||||
our [Contributor's Guide](https://requests.readthedocs.io/en/latest/dev/contributing/)
|
||||
as well as our [AI Policy](/.github/AI_POLICY.md).
|
||||
|
||||
To get the greatest chance of helpful responses, please also observe the
|
||||
following additional notes.
|
||||
@@ -52,3 +53,14 @@ Please be aware of the following things when filing bug reports:
|
||||
If you do not provide all of these things, it will take us much longer to
|
||||
fix your problem. If we ask you to clarify these and you never respond, we
|
||||
will close your issue without fixing it.
|
||||
|
||||
## Good Pull Requests
|
||||
|
||||
1. Avoid raising duplicate pull requests. *Please* use the GitHub pull request
|
||||
search feature to check whether someone else has already filed a pull
|
||||
request.
|
||||
2. When filing a pull request, always include tests that fail without your
|
||||
changes thus preventing the fix from regressing.
|
||||
3. Always run the test suite locally
|
||||
4. Write commit messages that explain _why_ a change was made beyond "Fixes
|
||||
#NNNN". Why is this the right fix? What other options were ruled out? etc.
|
||||
|
||||
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -19,6 +19,9 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-patch"]
|
||||
cooldown:
|
||||
default-days: 7
|
||||
groups:
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
languages: "python"
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -71,4 +71,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
|
||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -11,12 +11,14 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run pre-commit
|
||||
uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507 # v3.0.0
|
||||
run: |
|
||||
python -m pip install pre-commit==4.6.0
|
||||
pre-commit run --show-diff-on-failure --color=always --all-files
|
||||
|
||||
2
.github/workflows/lock-issues.yml
vendored
2
.github/workflows/lock-issues.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
if: github.repository_owner == 'psf'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
|
||||
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
|
||||
with:
|
||||
issue-lock-inactive-days: 90
|
||||
pr-lock-inactive-days: 90
|
||||
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -23,12 +23,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout repository"
|
||||
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
|
||||
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Setup Python"
|
||||
uses: "actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405" # v6.2.0
|
||||
uses: "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97" # v7.0.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
|
||||
12
.github/workflows/run-tests.yml
vendored
12
.github/workflows/run-tests.yml
vendored
@@ -20,11 +20,11 @@ jobs:
|
||||
- { python-version: "pypy-3.11", os: "windows-latest" }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
@@ -45,11 +45,11 @@ jobs:
|
||||
fail-fast: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: 'Set up Python 3.10'
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
@@ -67,11 +67,11 @@ jobs:
|
||||
fail-fast: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: 'Set up Python 3.10'
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
|
||||
4
.github/workflows/typecheck.yml
vendored
4
.github/workflows/typecheck.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
python-version: ["3.10", "3.14"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
4
.github/workflows/zizmor.yml
vendored
4
.github/workflows/zizmor.yml
vendored
@@ -16,9 +16,9 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0
|
||||
|
||||
@@ -13,7 +13,7 @@ repos:
|
||||
args: [--fix=auto]
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.12
|
||||
rev: v0.16.4
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--fix]
|
||||
|
||||
@@ -6,6 +6,14 @@ dev
|
||||
|
||||
- \[Short description of non-trivial change.\]
|
||||
|
||||
|
||||
2.34.2 (2026-05-14)
|
||||
-------------------
|
||||
- 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)
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ test = [
|
||||
typecheck = [
|
||||
"pyright",
|
||||
"typing_extensions",
|
||||
"chardet",
|
||||
"cryptography",
|
||||
"PyOpenSSL",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
@@ -115,3 +118,8 @@ testpaths = ["tests"]
|
||||
[tool.pyright]
|
||||
include = ["src/requests"]
|
||||
typeCheckingMode = "strict"
|
||||
# linting rules unrelated to type checking
|
||||
reportPrivateUsage = false
|
||||
reportPrivateImportUsage = false
|
||||
reportUnnecessaryIsInstance = false
|
||||
reportUnusedImport = false # duplicate of Ruff's F401 rule
|
||||
|
||||
@@ -52,7 +52,7 @@ except ImportError:
|
||||
charset_normalizer_version = None
|
||||
|
||||
try:
|
||||
from chardet import __version__ as chardet_version # type: ignore[import-not-found]
|
||||
from chardet import __version__ as chardet_version
|
||||
except ImportError:
|
||||
chardet_version = None
|
||||
|
||||
@@ -111,13 +111,13 @@ def _check_cryptography(cryptography_version: str) -> None:
|
||||
# Check imported dependencies for compatibility.
|
||||
try:
|
||||
check_compatibility(
|
||||
urllib3.__version__, # type: ignore[reportPrivateImportUsage]
|
||||
chardet_version, # type: ignore[reportUnknownArgumentType]
|
||||
urllib3.__version__,
|
||||
chardet_version,
|
||||
charset_normalizer_version,
|
||||
)
|
||||
except (AssertionError, ValueError):
|
||||
warnings.warn(
|
||||
f"urllib3 ({urllib3.__version__}) or chardet " # type: ignore[reportPrivateImportUsage]
|
||||
f"urllib3 ({urllib3.__version__}) or chardet "
|
||||
f"({chardet_version})/charset_normalizer ({charset_normalizer_version}) "
|
||||
"doesn't match a supported version!",
|
||||
RequestsDependencyWarning,
|
||||
@@ -138,11 +138,11 @@ try:
|
||||
pyopenssl.inject_into_urllib3()
|
||||
|
||||
# Check cryptography version
|
||||
from cryptography import ( # type: ignore[reportMissingImports]
|
||||
__version__ as cryptography_version, # type: ignore[reportUnknownVariableType]
|
||||
from cryptography import (
|
||||
__version__ as cryptography_version,
|
||||
)
|
||||
|
||||
_check_cryptography(cryptography_version) # type: ignore[reportUnknownArgumentType]
|
||||
_check_cryptography(cryptography_version)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
__title__ = "requests"
|
||||
__description__ = "Python HTTP for Humans."
|
||||
__url__ = "https://requests.readthedocs.io"
|
||||
__version__ = "2.34.1"
|
||||
__build__ = 0x023401
|
||||
__version__ = "2.34.2"
|
||||
__build__ = 0x023402
|
||||
__author__ = "Kenneth Reitz"
|
||||
__author_email__ = "me@kennethreitz.org"
|
||||
__license__ = "Apache-2.0"
|
||||
|
||||
@@ -29,6 +29,11 @@ class SupportsRead(Protocol[_T_co]):
|
||||
def read(self, length: int = ..., /) -> _T_co: ...
|
||||
|
||||
|
||||
def has_read(obj: Any) -> TypeIs[SupportsRead[str | bytes]]:
|
||||
"""Check if obj supports read, including __getattr__ based proxies."""
|
||||
return isinstance(obj, SupportsRead) or hasattr(obj, "read")
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SupportsItems(Protocol[_KT_co, _VT_co]):
|
||||
def items(self) -> Iterable[tuple[_KT_co, _VT_co]]: ...
|
||||
@@ -109,7 +114,7 @@ if TYPE_CHECKING:
|
||||
bytes | str | Iterable[bytes | str] | SupportsRead[bytes | str] | None
|
||||
)
|
||||
|
||||
HeadersType: TypeAlias = MutableMapping[str, str | bytes] | None
|
||||
HeadersType: TypeAlias = Mapping[str, str | bytes] | None
|
||||
|
||||
CookiesType: TypeAlias = RequestsCookieJar | Mapping[str, str]
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ and maintain connections.
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import socket # noqa: F401 # type: ignore[reportUnusedImport]
|
||||
import socket # noqa: F401
|
||||
import typing
|
||||
import warnings
|
||||
from typing import Any
|
||||
@@ -33,7 +33,7 @@ from urllib3.util import Timeout as TimeoutSauce
|
||||
from urllib3.util import parse_url
|
||||
from urllib3.util.retry import Retry
|
||||
|
||||
from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]
|
||||
from .auth import _basic_auth_str
|
||||
from .compat import basestring, urlparse
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import (
|
||||
|
||||
@@ -41,7 +41,7 @@ def _basic_auth_str(username: bytes | str, password: bytes | str) -> str:
|
||||
#
|
||||
# These are here solely to maintain backwards compatibility
|
||||
# for things like ints. This will be removed in 3.0.0.
|
||||
if not isinstance(username, basestring): # type: ignore[reportUnnecessaryIsInstance] # runtime guard for non-str/bytes
|
||||
if not isinstance(username, basestring): # runtime guard for non-str/bytes
|
||||
warnings.warn(
|
||||
"Non-string usernames will no longer be supported in Requests "
|
||||
f"3.0.0. Please convert the object you've passed in ({username!r}) to "
|
||||
@@ -51,7 +51,7 @@ def _basic_auth_str(username: bytes | str, password: bytes | str) -> str:
|
||||
)
|
||||
username = str(username)
|
||||
|
||||
if not isinstance(password, basestring): # type: ignore[reportUnnecessaryIsInstance] # runtime guard for non-str/bytes
|
||||
if not isinstance(password, basestring): # runtime guard for non-str/bytes
|
||||
warnings.warn(
|
||||
"Non-string passwords will no longer be supported in Requests "
|
||||
f"3.0.0. Please convert the object you've passed in ({type(password)!r}) to "
|
||||
@@ -300,7 +300,7 @@ class HTTPDigestAuth(AuthBase):
|
||||
r.content
|
||||
r.close()
|
||||
prep = r.request.copy()
|
||||
cookie_jar = cast("CookieJar", prep._cookies) # type: ignore[reportPrivateUsage]
|
||||
cookie_jar = cast("CookieJar", prep._cookies)
|
||||
extract_cookies_to_jar(cookie_jar, r.request, r.raw)
|
||||
prep.prepare_cookies(cookie_jar)
|
||||
|
||||
|
||||
@@ -14,13 +14,12 @@ from __future__ import annotations
|
||||
import importlib
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# -------
|
||||
# urllib3
|
||||
# -------
|
||||
from urllib3 import (
|
||||
__version__ as urllib3_version, # type: ignore[reportPrivateImportUsage]
|
||||
)
|
||||
from urllib3 import __version__ as urllib3_version
|
||||
|
||||
# Detect which major version of urllib3 is being used.
|
||||
try:
|
||||
@@ -46,7 +45,10 @@ def _resolve_char_detection() -> ModuleType | None:
|
||||
return chardet
|
||||
|
||||
|
||||
chardet = _resolve_char_detection()
|
||||
if TYPE_CHECKING:
|
||||
import chardet
|
||||
else:
|
||||
chardet = _resolve_char_detection()
|
||||
|
||||
# -------
|
||||
# Pythons
|
||||
|
||||
@@ -610,7 +610,7 @@ def merge_cookies(
|
||||
:param cookies: Dictionary or CookieJar object to be added.
|
||||
:rtype: CookieJar
|
||||
"""
|
||||
if not isinstance(cookiejar, cookielib.CookieJar): # type: ignore[reportUnnecessaryIsInstance] # runtime guard
|
||||
if not isinstance(cookiejar, cookielib.CookieJar): # runtime guard
|
||||
raise ValueError("You can only merge into CookieJar")
|
||||
|
||||
if isinstance(cookies, dict):
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Module containing bug report helper(s)."""
|
||||
|
||||
# pyright: reportUnknownMemberType=false
|
||||
|
||||
import json
|
||||
import platform
|
||||
import ssl
|
||||
@@ -19,7 +17,7 @@ except ImportError:
|
||||
charset_normalizer = None
|
||||
|
||||
try:
|
||||
import chardet # type: ignore[import-not-found]
|
||||
import chardet
|
||||
except ImportError:
|
||||
chardet = None
|
||||
|
||||
@@ -30,8 +28,8 @@ except ImportError:
|
||||
OpenSSL = None
|
||||
cryptography = None
|
||||
else:
|
||||
import cryptography # type: ignore[import-not-found]
|
||||
import OpenSSL # type: ignore[import-not-found]
|
||||
import cryptography
|
||||
import OpenSSL
|
||||
|
||||
|
||||
def _implementation():
|
||||
@@ -51,7 +49,7 @@ def _implementation():
|
||||
implementation_version = platform.python_version()
|
||||
elif implementation == "PyPy":
|
||||
pypy = sys.pypy_version_info # type: ignore[attr-defined]
|
||||
implementation_version = f"{pypy.major}.{pypy.minor}.{pypy.micro}"
|
||||
implementation_version = f"{pypy.major}.{pypy.minor}.{pypy.micro}" # pyright: ignore[reportUnknownMemberType]
|
||||
if sys.pypy_version_info.releaselevel != "final": # type: ignore[attr-defined]
|
||||
implementation_version = "".join(
|
||||
[implementation_version, sys.pypy_version_info.releaselevel] # type: ignore[attr-defined]
|
||||
@@ -80,7 +78,7 @@ def info() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
implementation_info = _implementation()
|
||||
urllib3_info = {"version": urllib3.__version__} # type: ignore[reportPrivateImportUsage]
|
||||
urllib3_info = {"version": urllib3.__version__}
|
||||
charset_normalizer_info = {"version": None}
|
||||
chardet_info: dict[str, str | None] = {"version": None}
|
||||
if charset_normalizer:
|
||||
|
||||
@@ -12,7 +12,7 @@ import datetime
|
||||
# Import encoding now, to avoid implicit import later.
|
||||
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
|
||||
# such as in Embedded Python. See https://github.com/psf/requests/issues/3578.
|
||||
import encodings.idna # noqa: F401 # type: ignore[reportUnusedImport]
|
||||
import encodings.idna # noqa: F401
|
||||
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping
|
||||
from io import UnsupportedOperation
|
||||
from typing import (
|
||||
@@ -35,8 +35,8 @@ from urllib3.fields import RequestField
|
||||
from urllib3.filepost import encode_multipart_formdata
|
||||
from urllib3.util import parse_url
|
||||
|
||||
from . import _types as _t
|
||||
from ._internal_utils import to_native_string, unicode_is_ascii
|
||||
from ._types import SupportsRead as _SupportsRead
|
||||
from .auth import HTTPBasicAuth
|
||||
from .compat import (
|
||||
JSONDecodeError,
|
||||
@@ -50,7 +50,7 @@ from .compat import (
|
||||
)
|
||||
from .compat import json as complexjson
|
||||
from .cookies import (
|
||||
_copy_cookie_jar, # type: ignore[reportPrivateUsage]
|
||||
_copy_cookie_jar,
|
||||
cookiejar_from_dict,
|
||||
get_cookie_header,
|
||||
)
|
||||
@@ -83,12 +83,10 @@ from .utils import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import MutableMapping
|
||||
from http.cookiejar import CookieJar
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from . import _types as _t
|
||||
from .adapters import HTTPAdapter
|
||||
from .cookies import RequestsCookieJar
|
||||
|
||||
@@ -162,7 +160,7 @@ class RequestEncodingMixin:
|
||||
|
||||
if isinstance(data, (str, bytes)):
|
||||
return data
|
||||
elif isinstance(data, _SupportsRead):
|
||||
elif _t.has_read(data):
|
||||
return data
|
||||
elif hasattr(data, "__iter__"):
|
||||
result: list[tuple[bytes, bytes]] = []
|
||||
@@ -237,9 +235,9 @@ class RequestEncodingMixin:
|
||||
|
||||
if isinstance(fp, (str, bytes, bytearray)):
|
||||
fdata = fp
|
||||
elif isinstance(fp, _SupportsRead): # type: ignore[reportUnnecessaryIsInstance] # defensive check for untyped callers
|
||||
elif _t.has_read(fp):
|
||||
fdata = fp.read()
|
||||
elif fp is None: # type: ignore[reportUnnecessaryComparison] # defensive check for untyped callers
|
||||
elif fp is None: # defensive check for untyped callers
|
||||
continue
|
||||
else:
|
||||
fdata = fp
|
||||
@@ -267,7 +265,9 @@ class RequestHooksMixin:
|
||||
if isinstance(hook, Callable):
|
||||
self.hooks[event].append(hook)
|
||||
elif hasattr(hook, "__iter__"):
|
||||
self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) # type: ignore[reportUnnecessaryIsInstance] # defensive runtime filter
|
||||
self.hooks[event].extend(
|
||||
h for h in hook if isinstance(h, Callable)
|
||||
) # defensive runtime filter
|
||||
|
||||
def deregister_hook(self, event: str, hook: _t.HookType) -> bool:
|
||||
"""Deregister a previously registered hook.
|
||||
@@ -309,9 +309,10 @@ class Request(RequestHooksMixin):
|
||||
<PreparedRequest [GET]>
|
||||
"""
|
||||
|
||||
hooks: dict[str, list[_t.HookType]]
|
||||
method: str | None
|
||||
url: _t.UriType | None
|
||||
headers: MutableMapping[str, str | bytes]
|
||||
headers: Mapping[str, str | bytes]
|
||||
files: _t.FilesType
|
||||
data: _t.DataType
|
||||
json: _t.JsonType
|
||||
@@ -637,7 +638,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
if raw_data:
|
||||
body = self._encode_params(raw_data)
|
||||
if isinstance(data, basestring) or isinstance(data, _SupportsRead):
|
||||
if isinstance(data, basestring) or _t.has_read(data):
|
||||
content_type = None
|
||||
else:
|
||||
content_type = "application/x-www-form-urlencoded"
|
||||
@@ -956,7 +957,9 @@ class Response:
|
||||
|
||||
if self._content_consumed and isinstance(self._content, bool):
|
||||
raise StreamConsumedError()
|
||||
elif chunk_size is not None and not isinstance(chunk_size, int): # type: ignore[reportUnnecessaryIsInstance] # runtime guard for untyped callers
|
||||
elif chunk_size is not None and not isinstance(
|
||||
chunk_size, int
|
||||
): # runtime guard for untyped callers
|
||||
raise TypeError(
|
||||
f"chunk_size must be an int, it is instead a {type(chunk_size)}."
|
||||
)
|
||||
@@ -1011,7 +1014,7 @@ class Response:
|
||||
):
|
||||
if pending is not None:
|
||||
# TODO: remove cast after iter_lines rewrite
|
||||
chunk = cast("str | bytes", pending + chunk) # type: ignore[operator]
|
||||
chunk = cast("str | bytes", pending + chunk)
|
||||
|
||||
if delimiter:
|
||||
lines = chunk.split(delimiter) # type: ignore[arg-type]
|
||||
|
||||
@@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, Any, cast
|
||||
from ._internal_utils import to_native_string
|
||||
from ._types import is_prepared as _is_prepared
|
||||
from .adapters import HTTPAdapter
|
||||
from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]
|
||||
from .auth import _basic_auth_str
|
||||
from .compat import cookielib, urljoin, urlparse
|
||||
from .cookies import (
|
||||
RequestsCookieJar,
|
||||
@@ -38,7 +38,7 @@ from .hooks import default_hooks, dispatch_hook
|
||||
# formerly defined here, reexposed here for backward compatibility
|
||||
from .models import ( # noqa: F401
|
||||
DEFAULT_REDIRECT_LIMIT,
|
||||
REDIRECT_STATI, # type: ignore[reportUnusedImport]
|
||||
REDIRECT_STATI,
|
||||
PreparedRequest,
|
||||
Request,
|
||||
Response,
|
||||
@@ -54,7 +54,7 @@ from .utils import ( # noqa: F401
|
||||
requote_uri,
|
||||
resolve_proxies,
|
||||
rewind_body,
|
||||
should_bypass_proxies, # type: ignore[reportUnusedImport] # re-export for external consumers
|
||||
should_bypass_proxies, # re-export for external consumers
|
||||
to_key_val_list,
|
||||
)
|
||||
|
||||
@@ -263,7 +263,7 @@ class SessionRedirectMixin:
|
||||
# Extract any cookies sent on the response to the cookiejar
|
||||
# in the new request. Because we've mutated our copied prepared
|
||||
# request, use the old one that we haven't yet touched.
|
||||
cookie_jar = cast("CookieJar", prepared_request._cookies) # type: ignore[reportPrivateUsage]
|
||||
cookie_jar = cast("CookieJar", prepared_request._cookies)
|
||||
extract_cookies_to_jar(cookie_jar, req, resp.raw)
|
||||
merge_cookies(cookie_jar, self.cookies)
|
||||
prepared_request.prepare_cookies(cookie_jar)
|
||||
@@ -275,7 +275,7 @@ class SessionRedirectMixin:
|
||||
# A failed tell() sets `_body_position` to `object()`. This non-None
|
||||
# value ensures `rewindable` will be True, allowing us to raise an
|
||||
# UnrewindableBodyError, instead of hanging the connection.
|
||||
rewindable = prepared_request._body_position is not None and ( # type: ignore[reportPrivateUsage]
|
||||
rewindable = prepared_request._body_position is not None and (
|
||||
"Content-Length" in headers or "Transfer-Encoding" in headers
|
||||
)
|
||||
|
||||
@@ -662,7 +662,7 @@ class Session(SessionRedirectMixin):
|
||||
|
||||
: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`.
|
||||
in the query string for the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:rtype: requests.Response
|
||||
"""
|
||||
|
||||
@@ -37,10 +37,10 @@ from .__version__ import __version__
|
||||
|
||||
# to_native_string is unused here, but imported here for backwards compatibility
|
||||
from ._internal_utils import ( # noqa: F401
|
||||
_HEADER_VALIDATORS_BYTE, # type: ignore[reportPrivateUsage]
|
||||
_HEADER_VALIDATORS_STR, # type: ignore[reportPrivateUsage]
|
||||
HEADER_VALIDATORS, # type: ignore[reportUnusedImport]
|
||||
to_native_string, # type: ignore[reportUnusedImport]
|
||||
_HEADER_VALIDATORS_BYTE,
|
||||
_HEADER_VALIDATORS_STR,
|
||||
HEADER_VALIDATORS,
|
||||
to_native_string,
|
||||
)
|
||||
from ._types import SupportsItems as _SupportsItems
|
||||
from .compat import (
|
||||
@@ -1102,7 +1102,7 @@ def _validate_header_part(
|
||||
) -> None:
|
||||
if isinstance(header_part, str):
|
||||
validator = _HEADER_VALIDATORS_STR[header_validator_index]
|
||||
elif isinstance(header_part, bytes): # type: ignore[reportUnnecessaryIsInstance]
|
||||
elif isinstance(header_part, bytes):
|
||||
# runtime guard for non-str/bytes input
|
||||
validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
|
||||
else:
|
||||
@@ -1142,11 +1142,11 @@ def rewind_body(prepared_request: PreparedRequest) -> None:
|
||||
"""
|
||||
body_seek = getattr(prepared_request.body, "seek", None)
|
||||
if body_seek is not None and isinstance(
|
||||
prepared_request._body_position, # type: ignore[reportPrivateUsage]
|
||||
prepared_request._body_position,
|
||||
integer_types,
|
||||
):
|
||||
try:
|
||||
body_seek(prepared_request._body_position) # type: ignore[reportPrivateUsage]
|
||||
body_seek(prepared_request._body_position)
|
||||
except OSError:
|
||||
raise UnrewindableBodyError(
|
||||
"An error occurred when rewinding request body for redirect."
|
||||
|
||||
@@ -1087,6 +1087,32 @@ class TestRequests:
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
def test_post_named_tempfile(self, httpbin):
|
||||
with tempfile.NamedTemporaryFile(mode="w+") as f:
|
||||
f.write("named temp file contents\n")
|
||||
f.seek(0)
|
||||
r = requests.post(
|
||||
httpbin("post"),
|
||||
files={"file": f},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
assert r.json()["files"]["file"] == "named temp file contents\n"
|
||||
|
||||
def test_post_getattr_proxy_read_only(self, httpbin):
|
||||
|
||||
class ReadProxy:
|
||||
def __init__(self):
|
||||
self._file = io.BytesIO(b"streamed body")
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == "__iter__":
|
||||
raise AttributeError(name)
|
||||
return getattr(self._file, name)
|
||||
|
||||
r = requests.post(httpbin("post"), data=ReadProxy())
|
||||
assert r.status_code == 200
|
||||
assert r.json()["data"] == "streamed body"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data",
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user