Compare commits
1 Commits
main
...
init_level
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35ba878a72 |
81
.github/AI_POLICY.md
vendored
81
.github/AI_POLICY.md
vendored
@@ -1,81 +0,0 @@
|
||||
# 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,8 +1,7 @@
|
||||
# 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/)
|
||||
as well as our [AI Policy](/.github/AI_POLICY.md).
|
||||
our [Contributor's Guide](https://requests.readthedocs.io/en/latest/dev/contributing/).
|
||||
|
||||
To get the greatest chance of helpful responses, please also observe the
|
||||
following additional notes.
|
||||
@@ -53,14 +52,3 @@ 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,9 +19,6 @@ 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
|
||||
# ℹ️ 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@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
|
||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -11,14 +11,12 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Run pre-commit
|
||||
run: |
|
||||
python -m pip install pre-commit==4.6.0
|
||||
pre-commit run --show-diff-on-failure --color=always --all-files
|
||||
uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507 # v3.0.0
|
||||
|
||||
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@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
|
||||
- uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
|
||||
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
|
||||
uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Setup Python"
|
||||
uses: "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97" # v7.0.0
|
||||
uses: "actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405" # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
|
||||
15
.github/workflows/run-tests.yml
vendored
15
.github/workflows/run-tests.yml
vendored
@@ -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.
|
||||
@@ -20,11 +21,11 @@ jobs:
|
||||
- { python-version: "pypy-3.11", os: "windows-latest" }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
@@ -45,11 +46,11 @@ jobs:
|
||||
fail-fast: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: 'Set up Python 3.10'
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
@@ -67,11 +68,11 @@ jobs:
|
||||
fail-fast: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: 'Set up Python 3.10'
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
|
||||
@@ -13,7 +13,7 @@ repos:
|
||||
args: [--fix=auto]
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.16.4
|
||||
rev: v0.15.12
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--fix]
|
||||
|
||||
49
HISTORY.md
49
HISTORY.md
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>`_
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
@@ -69,9 +67,6 @@ test = [
|
||||
typecheck = [
|
||||
"pyright",
|
||||
"typing_extensions",
|
||||
"chardet",
|
||||
"cryptography",
|
||||
"PyOpenSSL",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
@@ -118,8 +113,3 @@ 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
|
||||
from chardet import __version__ as chardet_version # type: ignore[import-not-found]
|
||||
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__,
|
||||
chardet_version,
|
||||
urllib3.__version__, # type: ignore[reportPrivateImportUsage]
|
||||
chardet_version, # type: ignore[reportUnknownArgumentType]
|
||||
charset_normalizer_version,
|
||||
)
|
||||
except (AssertionError, ValueError):
|
||||
warnings.warn(
|
||||
f"urllib3 ({urllib3.__version__}) or chardet "
|
||||
f"urllib3 ({urllib3.__version__}) or chardet " # type: ignore[reportPrivateImportUsage]
|
||||
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 (
|
||||
__version__ as cryptography_version,
|
||||
from cryptography import ( # type: ignore[reportMissingImports]
|
||||
__version__ as cryptography_version, # type: ignore[reportUnknownVariableType]
|
||||
)
|
||||
|
||||
_check_cryptography(cryptography_version)
|
||||
_check_cryptography(cryptography_version) # type: ignore[reportUnknownArgumentType]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -29,14 +27,9 @@ 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]]: ...
|
||||
class SupportsItems(Protocol):
|
||||
def items(self) -> Iterable[tuple[Any, Any]]: ...
|
||||
|
||||
|
||||
# These are needed at runtime for default_hooks() return type
|
||||
@@ -86,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
|
||||
@@ -94,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]
|
||||
@@ -114,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
|
||||
@@ -143,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
|
||||
|
||||
@@ -9,7 +9,7 @@ and maintain connections.
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import socket # noqa: F401
|
||||
import socket # noqa: F401 # type: ignore[reportUnusedImport]
|
||||
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
|
||||
from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]
|
||||
from .compat import basestring, urlparse
|
||||
from .cookies import extract_cookies_to_jar
|
||||
from .exceptions import (
|
||||
@@ -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)
|
||||
|
||||
@@ -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): # runtime guard for non-str/bytes
|
||||
if not isinstance(username, basestring): # type: ignore[reportUnnecessaryIsInstance] # 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): # runtime guard for non-str/bytes
|
||||
if not isinstance(password, basestring): # type: ignore[reportUnnecessaryIsInstance] # 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 "
|
||||
@@ -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
|
||||
@@ -300,7 +288,7 @@ class HTTPDigestAuth(AuthBase):
|
||||
r.content
|
||||
r.close()
|
||||
prep = r.request.copy()
|
||||
cookie_jar = cast("CookieJar", prep._cookies)
|
||||
cookie_jar = cast("CookieJar", prep._cookies) # type: ignore[reportPrivateUsage]
|
||||
extract_cookies_to_jar(cookie_jar, r.request, r.raw)
|
||||
prep.prepare_cookies(cookie_jar)
|
||||
|
||||
|
||||
@@ -14,12 +14,13 @@ 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
|
||||
from urllib3 import (
|
||||
__version__ as urllib3_version, # type: ignore[reportPrivateImportUsage]
|
||||
)
|
||||
|
||||
# Detect which major version of urllib3 is being used.
|
||||
try:
|
||||
@@ -45,10 +46,7 @@ def _resolve_char_detection() -> ModuleType | None:
|
||||
return chardet
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import chardet
|
||||
else:
|
||||
chardet = _resolve_char_detection()
|
||||
chardet = _resolve_char_detection()
|
||||
|
||||
# -------
|
||||
# Pythons
|
||||
|
||||
@@ -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
|
||||
@@ -610,7 +608,7 @@ def merge_cookies(
|
||||
:param cookies: Dictionary or CookieJar object to be added.
|
||||
:rtype: CookieJar
|
||||
"""
|
||||
if not isinstance(cookiejar, cookielib.CookieJar): # runtime guard
|
||||
if not isinstance(cookiejar, cookielib.CookieJar): # type: ignore[reportUnnecessaryIsInstance] # runtime guard
|
||||
raise ValueError("You can only merge into CookieJar")
|
||||
|
||||
if isinstance(cookies, dict):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Module containing bug report helper(s)."""
|
||||
|
||||
# pyright: reportUnknownMemberType=false
|
||||
|
||||
import json
|
||||
import platform
|
||||
import ssl
|
||||
@@ -17,7 +19,7 @@ except ImportError:
|
||||
charset_normalizer = None
|
||||
|
||||
try:
|
||||
import chardet
|
||||
import chardet # type: ignore[import-not-found]
|
||||
except ImportError:
|
||||
chardet = None
|
||||
|
||||
@@ -28,8 +30,8 @@ except ImportError:
|
||||
OpenSSL = None
|
||||
cryptography = None
|
||||
else:
|
||||
import cryptography
|
||||
import OpenSSL
|
||||
import cryptography # type: ignore[import-not-found]
|
||||
import OpenSSL # type: ignore[import-not-found]
|
||||
|
||||
|
||||
def _implementation():
|
||||
@@ -49,7 +51,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}" # pyright: ignore[reportUnknownMemberType]
|
||||
implementation_version = f"{pypy.major}.{pypy.minor}.{pypy.micro}"
|
||||
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]
|
||||
@@ -78,7 +80,7 @@ def info() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
implementation_info = _implementation()
|
||||
urllib3_info = {"version": urllib3.__version__}
|
||||
urllib3_info = {"version": urllib3.__version__} # type: ignore[reportPrivateImportUsage]
|
||||
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
|
||||
import encodings.idna # noqa: F401 # type: ignore[reportUnusedImport]
|
||||
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,
|
||||
_copy_cookie_jar, # type: ignore[reportPrivateUsage]
|
||||
cookiejar_from_dict,
|
||||
get_cookie_header,
|
||||
)
|
||||
@@ -87,6 +87,7 @@ if TYPE_CHECKING:
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from . import _types as _t
|
||||
from .adapters import HTTPAdapter
|
||||
from .cookies import RequestsCookieJar
|
||||
|
||||
@@ -160,7 +161,7 @@ class RequestEncodingMixin:
|
||||
|
||||
if isinstance(data, (str, bytes)):
|
||||
return data
|
||||
elif _t.has_read(data):
|
||||
elif isinstance(data, _SupportsRead):
|
||||
return data
|
||||
elif hasattr(data, "__iter__"):
|
||||
result: list[tuple[bytes, bytes]] = []
|
||||
@@ -235,9 +236,9 @@ class RequestEncodingMixin:
|
||||
|
||||
if isinstance(fp, (str, bytes, bytearray)):
|
||||
fdata = fp
|
||||
elif _t.has_read(fp):
|
||||
elif isinstance(fp, _SupportsRead): # type: ignore[reportUnnecessaryIsInstance] # defensive check for untyped callers
|
||||
fdata = fp.read()
|
||||
elif fp is None: # defensive check for untyped callers
|
||||
elif fp is None: # type: ignore[reportUnnecessaryComparison] # defensive check for untyped callers
|
||||
continue
|
||||
else:
|
||||
fdata = fp
|
||||
@@ -265,9 +266,7 @@ 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)
|
||||
) # defensive runtime filter
|
||||
self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) # type: ignore[reportUnnecessaryIsInstance] # defensive runtime filter
|
||||
|
||||
def deregister_hook(self, event: str, hook: _t.HookType) -> bool:
|
||||
"""Deregister a previously registered hook.
|
||||
@@ -309,22 +308,11 @@ class Request(RequestHooksMixin):
|
||||
<PreparedRequest [GET]>
|
||||
"""
|
||||
|
||||
hooks: dict[str, list[_t.HookType]]
|
||||
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,
|
||||
@@ -340,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}]>"
|
||||
@@ -396,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,
|
||||
@@ -598,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):
|
||||
@@ -638,7 +618,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||
else:
|
||||
if raw_data:
|
||||
body = self._encode_params(raw_data)
|
||||
if isinstance(data, basestring) or _t.has_read(data):
|
||||
if isinstance(data, basestring) or isinstance(data, _SupportsRead):
|
||||
content_type = None
|
||||
else:
|
||||
content_type = "application/x-www-form-urlencoded"
|
||||
@@ -734,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] = [
|
||||
@@ -763,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).
|
||||
@@ -803,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
|
||||
@@ -957,9 +924,7 @@ 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
|
||||
): # runtime guard for untyped callers
|
||||
elif chunk_size is not None and not isinstance(chunk_size, int): # type: ignore[reportUnnecessaryIsInstance] # runtime guard for untyped callers
|
||||
raise TypeError(
|
||||
f"chunk_size must be an int, it is instead a {type(chunk_size)}."
|
||||
)
|
||||
@@ -1014,7 +979,7 @@ class Response:
|
||||
):
|
||||
if pending is not None:
|
||||
# TODO: remove cast after iter_lines rewrite
|
||||
chunk = cast("str | bytes", pending + chunk)
|
||||
chunk = cast("str | bytes", pending + chunk) # type: ignore[operator]
|
||||
|
||||
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
|
||||
from .auth import _basic_auth_str # type: ignore[reportPrivateUsage]
|
||||
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,
|
||||
REDIRECT_STATI, # type: ignore[reportUnusedImport]
|
||||
PreparedRequest,
|
||||
Request,
|
||||
Response,
|
||||
@@ -54,7 +54,7 @@ from .utils import ( # noqa: F401
|
||||
requote_uri,
|
||||
resolve_proxies,
|
||||
rewind_body,
|
||||
should_bypass_proxies, # re-export for external consumers
|
||||
should_bypass_proxies, # type: ignore[reportUnusedImport] # 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)
|
||||
cookie_jar = cast("CookieJar", prepared_request._cookies) # type: ignore[reportPrivateUsage]
|
||||
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 (
|
||||
rewindable = prepared_request._body_position is not None and ( # type: ignore[reportPrivateUsage]
|
||||
"Content-Length" in headers or "Transfer-Encoding" in headers
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
_HEADER_VALIDATORS_STR,
|
||||
HEADER_VALIDATORS,
|
||||
to_native_string,
|
||||
_HEADER_VALIDATORS_BYTE, # type: ignore[reportPrivateUsage]
|
||||
_HEADER_VALIDATORS_STR, # type: ignore[reportPrivateUsage]
|
||||
HEADER_VALIDATORS, # type: ignore[reportUnusedImport]
|
||||
to_native_string, # type: ignore[reportUnusedImport]
|
||||
)
|
||||
from ._types import SupportsItems as _SupportsItems
|
||||
from .compat import (
|
||||
@@ -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):
|
||||
@@ -1102,7 +1100,7 @@ def _validate_header_part(
|
||||
) -> None:
|
||||
if isinstance(header_part, str):
|
||||
validator = _HEADER_VALIDATORS_STR[header_validator_index]
|
||||
elif isinstance(header_part, bytes):
|
||||
elif isinstance(header_part, bytes): # type: ignore[reportUnnecessaryIsInstance]
|
||||
# runtime guard for non-str/bytes input
|
||||
validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
|
||||
else:
|
||||
@@ -1142,11 +1140,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,
|
||||
prepared_request._body_position, # type: ignore[reportPrivateUsage]
|
||||
integer_types,
|
||||
):
|
||||
try:
|
||||
body_seek(prepared_request._body_position)
|
||||
body_seek(prepared_request._body_position) # type: ignore[reportPrivateUsage]
|
||||
except OSError:
|
||||
raise UnrewindableBodyError(
|
||||
"An error occurred when rewinding request body for redirect."
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, {})
|
||||
|
||||
@@ -1087,32 +1087,6 @@ 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",
|
||||
(
|
||||
@@ -2099,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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user