Compare commits

..

276 Commits

Author SHA1 Message Date
Nate Prewitt
970e8cec98 v2.32.1
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.10) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.9) (push) Has been cancelled
Tests / build (macos-13, 3.8) (push) Has been cancelled
Tests / build (macos-13, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.10) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.9) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
2024-05-20 15:02:29 -07:00
Nate Prewitt
d6ebc4a2f1 v2.32.0
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.10) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.9) (push) Has been cancelled
Tests / build (macos-13, 3.8) (push) Has been cancelled
Tests / build (macos-13, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.10) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.9) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
2024-05-20 08:46:49 -07:00
agubelu
9a40d12778 Avoid reloading root certificates to improve concurrent performance (#6667) 2024-05-15 14:07:26 -06:00
Ian Stapleton Cordasco
0c030f78d2 Merge pull request #6702 from nateprewitt/no_char_detection
Allow optional char detection dependencies in post-packaging
2024-05-14 19:15:19 -05:00
Nate Prewitt
555b870eb1 Allow character detection dependencies to be optional in post-packaging steps 2024-05-14 15:26:04 -07:00
Ian Stapleton Cordasco
d6dded3f00 Merge pull request #6700 from franekmagiera/update-redirect-to-invalid-uri-test
Use an invalid URI that will not cause httpbin to throw 500
2024-05-12 16:33:47 -05:00
franekmagiera
bf24b7d8d1 Use an invalid URI that will not cause httpbin to throw 500 2024-05-12 22:08:24 +02:00
Nate Prewitt
2d5f54779a Pin 3.8 and 3.9 runners back to macos-13 (#6688) 2024-04-23 10:50:19 -07:00
Nate Prewitt
f1bb07d39b Merge pull request #6687 from psf/dependabot/github_actions/github/codeql-action-3.25.0
Bump github/codeql-action from 3.24.0 to 3.25.0
2024-04-15 11:01:39 -06:00
dependabot[bot]
60047ade64 Bump github/codeql-action from 3.24.0 to 3.25.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.0 to 3.25.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e8893c57a1...df5a14dc28)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 16:40:35 +00:00
Ian Stapleton Cordasco
31ebb8102c Merge pull request #6682 from frenzymadness/pytest8
Fix compatibility with pytest 8
2024-04-11 14:49:35 -05:00
Lumir Balhar
cc23d1c644 Fix compatibility with pytest 8
`pytest.warns(None)` has been deprecated in pytest 7.0.0
and it's no longer working in pytest 8.

Resolves: https://github.com/psf/requests/issues/6679
2024-04-11 21:10:36 +02:00
Nate Prewitt
0790ea4250 Merge pull request #6680 from mbeijen/rfc9110
Add rfc9110 HTTP status code names
2024-04-11 07:10:57 -07:00
Michiel W. Beijen
e45b428960 Add rfc9110 HTTP status code names
RFC 9110 _HTTP Semantics_ obsoletes some earlier RFCs which defined HTTP
1.1. It adds some status codes that were previously only used for
WebDAV to HTTP _proper_ after making the names somewhat more generic.

See https://www.rfc-editor.org/rfc/rfc9110.html#name-changes-from-rfc-7231

This commit adds the http status code names from that RFC.
2024-04-08 21:48:24 +02:00
Nate Prewitt
2a438c27b5 Merge pull request #6677 from psf/dependabot/github_actions/actions/setup-python-5.1.0
Bump actions/setup-python from 5.0.0 to 5.1.0
2024-04-01 10:36:47 -07:00
dependabot[bot]
2daa7b52a7 Bump actions/setup-python from 5.0.0 to 5.1.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](0a5c615913...82c7e631bb)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 17:00:20 +00:00
Ian Stapleton Cordasco
8dd3b26bf5 Merge pull request #6302 from flysee/main
Fix the proxy_bypass_registry function all returning true in some cases.
2024-03-18 16:38:16 -05:00
flysee
13d892bdbe Additional should_bypass_proxies function test cases 2024-03-18 22:33:17 +08:00
flysee
1604e20fc8 Fix the proxy_bypass_registry function all returning true in some cases. 2024-03-18 15:36:36 +08:00
Ian Stapleton Cordasco
eeafb6ab3a Merge pull request #6662 from sigmavirus24/fix-tls-floppy
Add local TLS server
2024-03-14 06:25:24 -05:00
Ian Stapleton Cordasco
a94e9b5308 Add local TLS server
This also adds certificates for testing purposes and files to make it
easy to generate/regenerate them.

This also replaces an existing test of how we utilize our pool manager
such that we don't connect to badssl.com

Finally, this adds additional context parameters for our pool manager to
account for mTLS certificates used by clients to authenticate to a
server.
2024-03-14 06:06:22 -05:00
Ian Stapleton Cordasco
a58d7f2ffb Merge pull request #6655 from sigmavirus24/fix-tls-floppy
Use TLS settings in selecting connection pool
2024-03-11 06:21:59 -05:00
Ian Stapleton Cordasco
c0813a2d91 Use TLS settings in selecting connection pool
Previously, if someone made a request with `verify=False` then made a
request where they expected verification to be enabled to the same host,
they would potentially reuse a connection where TLS had not been
verified.

This fixes that issue.
2024-03-06 11:28:13 -06:00
Ian Stapleton Cordasco
eea3bbf9ac Merge pull request #6562 from amkarn258/issue-6223
every chardet package maps to requests.packages.chardet.* package respectively
2024-02-23 06:26:38 -06:00
Ian Stapleton Cordasco
f3f2611d83 Merge pull request #6583 from EFord36/fix-broken-links
Fix broken links in docs
2024-02-23 05:30:46 -06:00
Elliot Ford
a0e79bad05 update broken rfc link
ietf seems appropriate here - it's used elsewhere in the requets docs in
several places.
2024-02-23 10:26:21 +00:00
Elliot Ford
0a7f662aa7 Make example URL format a literal rather than an actual link
The rendered docs 'auto-link' this, which might encourage users to click
the link, even though it doesn't go anywhere.
2024-02-23 10:26:21 +00:00
Elliot Ford
5f1c3c22ff remove section with broken link to survey 2024-02-23 10:26:21 +00:00
Elliot Ford
541aa80ca3 update urllib3 docs link 2024-02-23 10:26:21 +00:00
Elliot Ford
d3b3399ece update authors github link
Update account link for original author which has changed.
2024-02-23 10:25:50 +00:00
Elliot Ford
0ec2780c29 update broken github pagination link 2024-02-23 10:25:50 +00:00
Ian Stapleton Cordasco
b8be93a721 Merge pull request #6589 from bruceadams/super_len_str_utf-8
Enhance `super_len` to count encoded bytes for str
2024-02-22 18:53:25 -06:00
Ian Stapleton Cordasco
3587a5f869 Merge pull request #6644 from sigmavirus24/bug/6643
Trim excess leading path separators
2024-02-22 18:49:42 -06:00
Ian Stapleton Cordasco
60389df6d6 Trim excess leading path separators
A URL with excess leading / (path-separator)s would cause urllib3 to
attempt to reparse the request-uri as a full URI with a host and port.
This bypasses that logic in ConnectionPool.urlopen by replacing these
leading /s with just a single /.

Closes #6643
2024-02-22 18:36:44 -06:00
Nate Prewitt
382fc2c0c6 Merge pull request #6629 from Tarty/fix-6628-jsondecode-error-not-deserializable
Fix #6628 - JSONDecodeError are not deserializable
2024-02-22 12:27:37 -08:00
Nate Prewitt
7a13c041db Merge pull request #6642 from nateprewitt/drop_python_37
Drop support for CPython 3.7
2024-02-20 15:44:43 -08:00
Nate Prewitt
58cea7a728 Drop support for CPython 3.7 2024-02-20 15:37:46 -08:00
Ian Stapleton Cordasco
9439fad038 Merge pull request #6641 from nateprewitt/fix_ci
Fix CI
2024-02-20 17:32:57 -06:00
Nate Prewitt
8fa4302322 Update Sphinx to work with latest readthedocs requirements 2024-02-20 15:04:38 -08:00
Nate Prewitt
28855fd43a Update supported copies of PyPy
Dropped support for pypy-3.7 and pypy-3.8 which are no longer maintained by upstream.
Added support for pypy-3.10.
2024-02-20 13:58:26 -08:00
Nate Prewitt
5fc10bf1f5 Fix httpbin pin for test suite 2024-02-20 13:37:25 -08:00
Ian Stapleton Cordasco
46c1a3d8c8 Merge pull request #6640 from nateprewitt/community_docs_cleanup
Cleanup defunct links from community docs page
2024-02-20 14:04:33 -06:00
Nate Prewitt
6106a63eb6 Cleanup defunct links from community docs page 2024-02-20 11:58:35 -08:00
Ian Stapleton Cordasco
4f3f189d6b Merge pull request #6632 from psf/dependabot/github_actions/github/codeql-action-3.24.0
Bump github/codeql-action from 3.23.0 to 3.24.0
2024-02-05 20:54:26 -06:00
dependabot[bot]
a5a0e4b587 Bump github/codeql-action from 3.23.0 to 3.24.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.23.0 to 3.24.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e5f05b81d5...e8893c57a1)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 16:38:01 +00:00
Thomas Dehghani
3ff3ff21dd Fix #6628 - JSONDecodeError are not deserializable
requests.exceptions.JSONDecodeError are not deserializable: calling
`pickle.dumps` followed by `pickle.loads` will trigger an error.

This is particularly a problem in a process pool, as an attempt to
decode json on an invalid json document will result in the entire
process pool crashing.

This is due to the MRO of the `requests.exceptions.JSONDecodeError`
class: the `__reduce__` method called when pickling an instance is not
the one from the JSON library parent: two out of three args expected
for instantiation will be dropped, and the instance can't be
deserialised.

By specifying in the class which parent `__reduce__` method should be
called, the bug is fixed as all args are carried over in the resulting
pickled bytes.
2024-01-31 17:13:53 +01:00
Ian Stapleton Cordasco
96b22fa18c Merge pull request #6619 from psf/dependabot/github_actions/github/codeql-action-3.23.0
Bump github/codeql-action from 3.22.11 to 3.23.0
2024-01-08 11:56:01 -06:00
dependabot[bot]
b0e6c9bf85 Bump github/codeql-action from 3.22.11 to 3.23.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.22.11 to 3.23.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b374143c11...e5f05b81d5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 17:01:37 +00:00
Ian Stapleton Cordasco
72eccc8dd8 Merge pull request #6612 from miketheman/fix-docs-urls
docs: specify sphinx dirhtml builder, use references instead of absolutes
2023-12-23 12:33:48 -06:00
Mike Fiedler
25939d8784 add myself
Signed-off-by: Mike Fiedler <miketheman@gmail.com>
2023-12-23 18:15:33 +00:00
Mike Fiedler
bfba9dc68c docs: replace concrete URLs with references
Any development links will now refer back to the generated docs.

Signed-off-by: Mike Fiedler <miketheman@gmail.com>
2023-12-23 18:09:55 +00:00
Mike Fiedler
f23346a9b8 Revert "Merge pull request #6605 from jaikishpai/fix-#6604"
This reverts commit 1396eb6f7a, reversing
changes made to e4c821a247.
2023-12-23 17:36:34 +00:00
Ian Stapleton Cordasco
f3f9784419 Merge pull request #6613 from miketheman/add-socks-label
docs: add label to socks heading
2023-12-23 11:24:56 -06:00
Mike Fiedler
b5bd0f14cc docs: add label to socks heading
When trying to link via intersphinx, a label must be used.
Otherwise a full URL is required, which is less desirable.

Signed-off-by: Mike Fiedler <miketheman@gmail.com>
2023-12-23 17:17:34 +00:00
Mike Fiedler
242d3113c0 docs: specify sphinx dirhtml builder
With the requirement of a configuration file, the default builder
of `dirhtml` that RTD used to use is no longer specified.
This leads to URLs ending in `.html` now, which
breaks other exisitng references.

Refs: #6603
Refs: https://docs.readthedocs.io/en/stable/config-file/v2.html#sphinx-builder
Refs: https://www.sphinx-doc.org/en/master/usage/builders/index.html#sphinx.builders.dirhtml.DirectoryHTMLBuilder

Signed-off-by: Mike Fiedler <miketheman@gmail.com>
2023-12-23 16:16:50 +00:00
Nate Prewitt
1396eb6f7a Merge pull request #6605 from jaikishpai/fix-#6604
fix for ##6604
2023-12-19 22:17:55 -08:00
Nate Prewitt
e4c821a247 Merge pull request #6607 from psf/dependabot/github_actions/github/codeql-action-3.22.11
Bump github/codeql-action from 2.22.1 to 3.22.11
2023-12-18 09:30:42 -08:00
dependabot[bot]
421b1f1757 Bump github/codeql-action from 2.22.1 to 3.22.11
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.22.1 to 3.22.11.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](fdcae64e14...b374143c11)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 16:39:47 +00:00
Jaikish Pai
1447bccc05 fix for ##6604 2023-12-17 22:34:30 +01:00
Ian Stapleton Cordasco
3b5978fc5d Merge pull request #6592 from atatuzuner61/bug/6584
"TOO_EARLY" type definition for status code 425
2023-12-16 14:24:02 -06:00
Ian Stapleton Cordasco
1ddf014f47 Merge pull request #6600 from MestreLion/MestreLion-connection-timeout-note
Add note on connection timeout being larger than specified. Fix #5773
2023-12-16 07:30:18 -06:00
Ian Stapleton Cordasco
92f9e431c9 Update docs/user/advanced.rst
Add note about wall clock too
2023-12-16 07:29:50 -06:00
Ian Stapleton Cordasco
951dd15fa6 Update docs/user/advanced.rst
fix indentation for note so it renders properly
2023-12-16 07:26:23 -06:00
Ian Stapleton Cordasco
51d0d83eaf Merge pull request #6603 from nsoranzo/rtd_config
Add now mandatory readthedocs config file
2023-12-16 07:24:37 -06:00
Nicola Soranzo
c7c2ebf1a7 Add now mandatory readthedocs config file
Docs builds currently fail with:

```
Problem in your project's configuration. No default configuration file found at repository's root. See https://docs.readthedocs.io/en/stable/config-file/
```

See e.g. https://readthedocs.org/projects/requests/builds/22842479/
2023-12-15 20:59:56 +00:00
Rodrigo Silva
a64f32ba45 Add note on connection timeout being larger than specified. Fix #5773
On servers with multiple IPs, such as IPv4 and IPv6, `urllib3` tries each address sequentially until one successfully connects, using the specified timeout for _each_ attempt, leading to a total connection timeout that is a _multiple_ of the requested time.
2023-12-13 10:05:12 -03:00
Nate Prewitt
a25fde6989 Merge pull request #6599 from psf/dependabot/github_actions/actions/setup-python-5.0.0 2023-12-11 08:11:07 -08:00
dependabot[bot]
ba67dc8dcc Bump actions/setup-python from 4.7.0 to 5.0.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.0 to 5.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](61a6322f88...0a5c615913)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 16:07:15 +00:00
Nate Prewitt
769bc3ae5c Merge pull request #6596 from psf/fix-autoclose-automtaion
Update close-issues.yml
2023-12-01 09:18:02 -08:00
Ian Stapleton Cordasco
d6ffd868ee Update close-issues.yml
I noticed the auto-labeling was working but not the auto-closing. Looking at recent actions runs I see that we need to specify the token even if we're not giving our own special token. See https://github.com/psf/requests/actions/runs/7057701782/job/19211845073#step:2:13 for additional context, namely

```
 gh: To use GitHub CLI in a GitHub Actions workflow, set the GH_TOKEN environment variable. Example:
  env:
    GH_TOKEN: ${{ github.token }}
```
2023-12-01 08:05:59 -06:00
Bruce Adams
3fd309a5c1 Enhance super_len to count encoded bytes for str
This fixes issue #6586
2023-11-29 15:42:57 -05:00
Ata Tuzuner
ec84f2c539 Fixes to test 2023-11-29 12:27:18 -05:00
Ata Tuzuner
889910c77a Added tests for status code 425 definitions. 2023-11-29 12:24:15 -05:00
Ata Tuzuner
b37878d340 Too early definition added to 425 status code type 2023-11-28 18:12:22 -05:00
Bruce Adams
f6707042d8 Unit test for string containing multi-byte UTF-8
There are two tests here. One demonstrating existing, correct
behavior for `data=bytes`, and another, failing, test for the case
where `data=string` and the string contains multi-byte UTF-8.
2023-11-27 17:27:43 -05:00
Ian Stapleton Cordasco
0b4d494192 Merge pull request #6581 from EFord36/typo-fix
fix docstring typo: a -> as
2023-11-22 09:10:47 -06:00
Elliot Ford
15849947ec fix docstring typo: a -> as 2023-11-22 11:34:04 +00:00
Ian Stapleton Cordasco
c6de5a14e5 Merge pull request #6580 from psf/dependabot/github_actions/dessant/lock-threads-5.0.0
Bump dessant/lock-threads from 4.0.1 to 5.0.0
2023-11-20 10:37:07 -06:00
dependabot[bot]
e66a07b286 Bump dessant/lock-threads from 4.0.1 to 5.0.0
Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 4.0.1 to 5.0.0.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md)
- [Commits](be8aa5be94...d42e5f4980)

---
updated-dependencies:
- dependency-name: dessant/lock-threads
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 16:28:17 +00:00
Nate Prewitt
9e98a8749a Merge pull request #6574 from msea1/matthew/fix_space 2023-11-15 23:44:49 -08:00
Matthew Carruth
c32b046243 Fix missing space in error message 2023-11-15 17:00:28 -08:00
mayank
89cde235be checkstyle 2023-11-04 23:20:21 +05:30
amkarn258
d7490c9d2d Update src/requests/packages.py
Co-authored-by: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>
2023-10-31 22:36:39 +05:30
mayank
e9b1217cff added handling for chardet and charset_normalizer imports 2023-10-31 20:37:59 +05:30
mayank
ad761abcdd every chardet package maps to requests.packages.chardet.* package respectively 2023-10-31 00:50:10 +05:30
Nate Prewitt
839a8edec3 Merge pull request #6552 from swims-hjkl/issue/test_header_validation
#6551  - assert statements for test test_header_validation
2023-10-17 23:08:52 -07:00
sumedhrao7
a8e9c1b436 added assert statements into tests/test_requests/test_header_validation in regards to the issue #6551 2023-10-18 07:40:50 +05:30
Nate Prewitt
b55bb15c35 Merge pull request #6527 from sigmavirus24/update-templates
Autoclose specific issue templates
2023-10-11 09:29:20 -07:00
Ian Stapleton Cordasco
8187768622 Update close-issues.yml
Remove references to GITHUB_TOKEN/MY_TOKEN
2023-10-11 08:19:57 -05:00
Ian Stapleton Cordasco
f75b9504fc Update close comment
Co-authored-by: Nate Prewitt <nate.prewitt@gmail.com>
2023-10-11 08:19:14 -05:00
Nate Prewitt
8199a2b389 Merge pull request #6548 from psf/dependabot/github_actions/github/codeql-action-2.22.1
Bump github/codeql-action from 2.21.3 to 2.22.1
2023-10-09 09:49:28 -07:00
dependabot[bot]
42a3b5cce8 Bump github/codeql-action from 2.21.3 to 2.22.1
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.21.3 to 2.22.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](5b6282e01c...fdcae64e14)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 16:29:55 +00:00
Nate Prewitt
f5a7aefc2d Fix urllib3 pin in setup.cfg (#6545) 2023-10-06 16:34:24 -06:00
Nate Prewitt
ee93fac6b2 Merge pull request #6538 from psf/dependabot/github_actions/actions/checkout-4.1.0
Bump actions/checkout from 4.0.0 to 4.1.0
2023-09-25 11:30:05 -07:00
dependabot[bot]
a775435c4b Bump actions/checkout from 4.0.0 to 4.1.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](3df4ab11eb...8ade135a41)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 16:28:35 +00:00
Ian Stapleton Cordasco
6c8d9b1d0f Autoclose specific issue templates
We spend a fair amount of time closing issues because people don't read
the template closely or understand what they're being told. Let's take
advantage of being able to auto-label an issue based on the template and
then trigger a workflow to auto-magically close the issue with a message
and lock the issue.
2023-09-13 09:33:58 -05:00
Ian Stapleton Cordasco
881281250f Merge pull request #6519 from psf/dependabot/github_actions/actions/checkout-4.0.0
Bump actions/checkout from 3.6.0 to 4.0.0
2023-09-04 19:17:08 -05:00
dependabot[bot]
29fc8d1e93 Bump actions/checkout from 3.6.0 to 4.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](f43a0e5ff2...3df4ab11eb)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 16:21:27 +00:00
Ian Stapleton Cordasco
8b560ecb24 Merge pull request #6517 from jnhyperion/remove-pytest-ini
Remove pytest.ini in MANIFEST.in since it does not exist anymore
2023-08-29 09:27:10 -05:00
Johnny.H
4585a7fbda remove pytest.ini since it does not exist anymore 2023-08-29 13:41:49 +08:00
Ian Stapleton Cordasco
16dc70df34 Merge pull request #6516 from psf/dependabot/github_actions/actions/checkout-3.6.0
Bump actions/checkout from 3.5.3 to 3.6.0
2023-08-28 14:05:56 -05:00
dependabot[bot]
bea231b033 Bump actions/checkout from 3.5.3 to 3.6.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](c85c95e3d7...f43a0e5ff2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 16:12:37 +00:00
13steinj
2ee5b0b01c Fix documentation monthly download badge (#6508) 2023-08-18 11:01:26 -06:00
Jonas Schell
89f0eb91b3 fix monthly download badge (#6507) 2023-08-16 08:16:53 -07:00
Nate Prewitt
005571d118 Remove pytest-mock requirement (#6505) 2023-08-13 16:08:21 -07:00
Nate Prewitt
d63e94f552 Move to src directory (#6506) 2023-08-13 14:46:13 -07:00
Nate Prewitt
e9fa2e2da3 Add 3.12 classifier and tox configuration (#6503) 2023-08-13 12:56:18 -07:00
Nate Prewitt
678fca8423 Upgrade to httpbin 0.10.0 (#6496) 2023-08-13 09:56:53 -07:00
Nate Prewitt
d8152769ce Merge pull request #6501 from psf/dependabot/github_actions/actions/checkout-3.5.3 2023-08-12 13:15:39 -07:00
Nate Prewitt
9ff1a25ef5 Merge pull request #6502 from psf/dependabot/github_actions/github/codeql-action-2.21.3
Bump github/codeql-action from 1.1.39 to 2.21.3
2023-08-12 12:50:52 -07:00
dependabot[bot]
4bd06cd325 Bump actions/checkout from 2.7.0 to 3.5.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.7.0 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.7.0...c85c95e3d7251135ab7dc9ce3241c5835cc595a9)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-12 19:37:22 +00:00
Nate Prewitt
09a241f59e Merge pull request #6500 from psf/dependabot/github_actions/actions/setup-python-4.7.0
Bump actions/setup-python from 2.3.4 to 4.7.0
2023-08-12 12:36:48 -07:00
Nate Prewitt
6ad493650c Merge pull request #6499 from psf/dependabot/github_actions/dessant/lock-threads-4.0.1
Bump dessant/lock-threads from 3.0.0 to 4.0.1
2023-08-12 12:24:59 -07:00
dependabot[bot]
9acca90bf6 Bump github/codeql-action from 1.1.39 to 2.21.3
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1.1.39 to 2.21.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](231aa2c8a8...5b6282e01c)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-12 19:03:30 +00:00
dependabot[bot]
c7933453cf Bump actions/setup-python from 2.3.4 to 4.7.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.4 to 4.7.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2.3.4...61a6322f88396a6271a6ee3565807d608ecaddd1)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-12 19:03:22 +00:00
dependabot[bot]
ea49261a27 Bump dessant/lock-threads from 3.0.0 to 4.0.1
Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 3.0.0 to 4.0.1.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md)
- [Commits](e460dfeb36...be8aa5be94)

---
updated-dependencies:
- dependency-name: dessant/lock-threads
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-12 19:03:17 +00:00
Nate Prewitt
8112fcc7be Pre commit update (#6498) 2023-08-12 12:03:10 -07:00
Nate Prewitt
2c193bda0c Pin GHA workflows and add dependabot to keep them up to date (#6497) 2023-08-12 12:02:58 -07:00
Ian Stapleton Cordasco
34a64bc65e Merge pull request #6266 from elprimato/spdx-conform-license
Change to SPDX conform license string
2023-08-12 13:51:42 -05:00
Nate Prewitt
9b6c62e235 Merge branch 'main' into spdx-conform-license 2023-08-12 11:37:15 -07:00
Joren Vrancken
cb7fcd7ebd Specify that Session.headers needs to be set to a OrderedDict in Header Ordering docs (#6475) 2023-08-12 10:53:36 -07:00
Calle Svensson
aa4cc78627 Add note about adapter prefix match to docs (#6465) 2023-07-29 21:05:44 -07:00
Kevin Kirsche
dfe46c1b7f refactor: prefer dictionary comphrension to loop (#6187) 2023-07-29 18:55:18 -07:00
Alexandre Erwin Ittner
2ecdb68561 Update reference to "cookielib" to "cookiejar" in documentation (#6214) 2023-07-29 18:50:43 -07:00
cpzt
fecf3dc472 add docstring parameter hooks (#6456) 2023-07-29 18:01:42 -07:00
Matthew Armand
cdbc2e2715 Add an Example for automatic retries to the Advanced Usage docs (#6258)
- While Requests doesn't automatically retry failures, this ability is a very common advanced use case in real world applications.
- Although there's a mention of this ability on the HTTPAdapter class docs, it's a bit buried and not very specific.
- It makes sense then to have an Example in the HTTPAdapter section of the Advanced Usage docs with a basic template for how this can be accomplished with Requests.
2023-07-03 13:38:22 -07:00
Boris Verkhovskiy
22db55a889 Fix doc typo (#6467) 2023-06-26 11:09:23 -06:00
Nate Prewitt
6e5b15d542 Fix linting issues 2023-05-22 09:36:22 -06:00
Nate Prewitt
147c8511dd v2.31.0
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12-dev) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.8) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.9) (push) Has been cancelled
Tests / build (ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12-dev) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.8) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.9) (push) Has been cancelled
2023-05-22 09:10:32 -06:00
Nate Prewitt
74ea7cf7a6 Merge pull request from GHSA-j8r2-6x86-q33q 2023-05-22 09:08:57 -06:00
Thomas Grainger
3022253346 test on pypy 3.8 and pypy 3.9 on windows and macos (#6424) 2023-05-15 09:04:21 -06:00
Thomas Grainger
b639e66c81 test on py3.12 (#6448)
* test on py3.12

* update to pytest-httpbin==2.0.0
2023-05-13 08:10:56 -06:00
Syed Saifullah
d3d504436e Fixed a small typo (#6452) 2023-05-09 10:55:33 -06:00
Nate Prewitt
2ad18e0e10 v2.30.0
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (false, ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2023-05-03 09:39:44 -06:00
Quentin Pradet
f2629e9e3c Remove strict parameter (#6434) 2023-04-28 13:17:10 -06:00
Nate Prewitt
87d63de873 v2.29.0
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (false, ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2023-04-26 09:19:11 -06:00
Thomas Grainger
51716c4ef3 enable the warnings plugin (#6416) 2023-04-22 18:00:22 -06:00
Thomas Grainger
a7da1ab349 try on ubuntu 22.04 (#6418) 2023-04-22 17:53:09 -06:00
Thomas Grainger
dfc3e24338 pin cryptography<40 on pypy 3.7 (#6421)
* pin cryptography<40 on pypy 3.7
2023-04-22 17:41:27 -06:00
Levi
26bea1e498 Use urllib3 native chunking ability (#6226)
* Fix #3844 
* Use urllib for chunked 

---------

Co-authored-by: James Pickering <james.pickering@xml-solutions.com>
Co-authored-by: Leon Verrall <LVerrall@slb.com>
2023-04-22 17:32:51 -06:00
Seth Michael Larson
7f694b79e1 Allow str/bytes subclasses to be used as header parts 2023-03-03 14:39:02 -06:00
Yifang Zhu
e90852d20c Make json doc strings consistent (#6368) 2023-03-02 21:32:49 -08:00
Nate Prewitt
ec78348c4b Upgrade lint.yml action (#6370) 2023-03-02 20:45:27 -08:00
Boris Verkhovskiy
46188256ee Response.raw.read() returns bytes (#6365) 2023-02-27 08:31:15 -07:00
Quentin Pradet
15585909c3 Support missing SNIMissingWarning in tests (#6336) 2023-01-21 01:44:33 -07:00
Matthew Feickert
16a17a3ca7 fix: Remove '<4' from python_requires (#6333)
* As discussed in https://discuss.python.org/t/use-of-less-than-next-major-version-e-g-4-in-python-requires-setup-py/1066
  and other places by the PyPA, use of upper bounds with python_requires
  for future versions of Python is unintended use of python_requires and
  actively discouraged.
2023-01-18 22:37:24 -07:00
Nate Prewitt
61c324da43 v2.28.2
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (false, ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2023-01-12 09:16:09 -07:00
Ashish Kurmi
eb07385b02 ci: add minimum GitHub token permissions for workflows (#6236)
Signed-off-by: Ashish Kurmi <akurmi@stepsecurity.io>
2023-01-11 15:06:53 -07:00
Bruno Alla
2745db131a Switch to Python 3.11 final on CI and add to tox file (#6325) 2023-01-06 11:32:31 -07:00
ch-iv
ec553c275f Update flake8 repo and version (#6317) 2022-12-25 21:24:16 -07:00
Ian Stapleton Cordasco
ac3be98b19 Merge pull request #6291 from oliviacrain/docs/git-clone-protocol
Replace git protocol URL in docs with an HTTPS URL
2022-11-21 16:06:01 -06:00
Olivia Crain
192083576d Replace git protocol URL in docs with an HTTPS URL 2022-11-21 14:01:02 -06:00
Volker Schaus
53a0562331 change to SPDX conform license string 2022-10-23 18:35:43 +00:00
Ian Stapleton Cordasco
1e62a3ec18 Merge pull request #6262 from winmorre/docs-update/#6260
JSONDecodeError included in the documentation
2022-10-21 11:37:47 -05:00
reeko234
7908ad3af9 JSONDecodeError included in the documentation #6260 2022-10-21 07:42:58 +00:00
deedy5
c57f1f0ca1 Allow charset normalizer >=2 and <4 (#6261) 2022-10-20 12:26:18 -06:00
Ian Stapleton Cordasco
7104ad4b13 Merge pull request #6234 from jaap3/patch-1
Fix CURL_CA_BUNDLE override example
2022-09-09 14:10:14 -05:00
Jaap Roes
da246958b4 Fix CURL_CA_BUNDLE override example
Promote REQUESTS_CA_BUNDLE as it is looked at first
2022-09-07 09:48:59 +02:00
David Cain
177dd90f18 Remove Python 2 mention on chardet behavior (#6204) 2022-07-27 09:22:21 -08:00
Miloslav Pojman
786255613b Remove Python 2 mention from tox (#6200) 2022-07-20 17:13:36 -06:00
Sergey Lyapustin
bda7f0171f Fixed warnings message to suggest using secure protocol. (#6188) 2022-06-29 12:41:49 -06:00
Nate Prewitt
4d394574f5 v2.28.1
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (false, ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11-dev) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.11-dev) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11-dev) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2022-06-29 09:09:11 -06:00
Kevin Kirsche
16b418b44a Use yield from instead of looping yield (#6170) 2022-06-28 20:08:26 -06:00
Lumír 'Frenzy' Balhar
0e28c8f1dc Allow charset normalizer >=2 and <3 (#6169) 2022-06-28 20:01:42 -06:00
Wong Hoi Sing Edison
73793ce43e Update Dependency with chardet>=3.0.2,<6 (#6179)
https://github.com/chardet/chardet/releases/tag/5.0.0 was released on
2022-06-25 with remove of Python 3.6 support, which also align with our
Python > 3.6 requirement.

Signed-off-by: Wong Hoi Sing Edison <hswong3i@pantarei-design.com>
2022-06-27 20:25:58 -06:00
Nate Prewitt
da9996fe4d v2.28.0
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (false, ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11-dev) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.11-dev) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11-dev) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2022-06-09 08:42:10 -06:00
Nate Prewitt
1dcf3b7122 Add GitHub action to automate linting (#6157) 2022-06-08 13:11:11 -06:00
Nate Prewitt
e36f34597c Add valdation for header name (#6154) 2022-06-08 12:03:56 -06:00
Nate Prewitt
60865f21ae Run 3.11 CI on all platforms (#6155) 2022-06-08 10:12:53 -06:00
Olivier Gayot
3af2f456d8 Fix output of test_lowlevel tests in case of timeout (#6136)
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-06-08 10:02:42 -06:00
Nate Prewitt
0f358eabe6 Reduce lock thread runs to daily (#6150) 2022-06-01 11:51:04 -06:00
Nate Prewitt
210095fd08 Tolerate bad registry entries in Windows proxy settings (#6149) 2022-06-01 11:36:26 -06:00
Nate Prewitt
a5e7169e75 Cleanup the docs sidebar (#6144) 2022-05-26 12:45:57 -07:00
davidshivaji
79f2ec3acc Grammar fix (#6133) 2022-05-13 10:36:13 -06:00
Mickaël Schoentgen
cb233a101d Fix several ResourceWarnings in test_requests.py (#4766) 2022-05-10 19:04:17 -06:00
Nate Prewitt
40956723f2 Add .git-blame-ignore-revs 2022-04-29 15:39:22 -06:00
Nate Prewitt
2a6f290bc0 Add automatic code formatting to Requests (#6095) 2022-04-29 13:16:58 -06:00
Nate Prewitt
2d5517682b Fix inconsistent exception for JSONDecode error (#6097)
When calling .json() it was possible to raise a
requests.exceptions.JSONDecodeError or ValueError
depending on whether the encoding is set.
2022-03-28 12:51:27 -06:00
Nate Prewitt
8bce583b95 Farewell, sweet Concorde!
Formally removing any remaining vestiges of Python 2 from Requests.
We'll also leave behind Python 3.6 while we're at it.
2022-03-25 10:50:33 -06:00
Ian Stapleton Cordasco
79f60274f7 Merge pull request #6074 from nateprewitt/curl_ca_bundle
Fix environment CA Bundle resolution
2022-02-26 07:39:28 -06:00
Nate Prewitt
79c4a017fe Fix environment CA Bundle resolution 2022-02-25 11:25:20 -07:00
Ian Stapleton Cordasco
5e749546a2 Merge pull request #6068 from nateprewitt/sess_prox_warn
Reword proxies section and add warning
2022-02-19 17:04:06 -06:00
Ian Stapleton Cordasco
3eed0512a6 Merge pull request #6069 from nateprewitt/jinja_fix
Pin markupsafe for older versions of Jinja2
2022-02-19 07:34:48 -06:00
Nate Prewitt
3ed60078e2 Fix jinja 2022-02-18 18:33:43 -07:00
Nate Prewitt
15fde024f2 Reword proxies section and add warning 2022-02-18 18:18:23 -07:00
Nate Prewitt
95f4567336 Wrap urllib3's SSLError as requests' SSLError (#6057) 2022-02-05 11:56:05 -07:00
Nate Prewitt
7ae38876b1 Merge pull request #5978 from sha016/all-proxy-docs
Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
2022-02-02 11:03:26 -07:00
Seth Michael Larson
2c20474bfe Update advanced.rst 2022-02-02 11:56:27 -06:00
Chavithra
24dfbfde55 Update quickstart.rst : fixing brotlicffi link (#6044) 2022-01-17 11:30:24 -07:00
chyzzqo2
fa1b0a367a Initialize JsonDecodeError before initializing IOError (#6036)
That way we get the formated error message

Co-authored-by: Peter <cHYzZQo@netflix.com>
2022-01-13 08:42:49 -07:00
Ian Stapleton Cordasco
d15a3b6e2e Merge pull request #6042 from nateprewitt/lock_thread_spam
Disable lock-threads on forks
2022-01-11 11:42:00 -06:00
Nate Prewitt
48676caf57 Disable lock-threads on forks 2022-01-11 08:24:39 -07:00
Nate Prewitt
d718e75383 Start testing on 3.11 dev branch (#6037) 2022-01-07 12:07:31 -07:00
Marwan
620ed4fc52 Move socks testing requirement into requirements-dev.txt (#6035) 2022-01-06 10:57:59 -07:00
Nate Prewitt
31a89d9c84 v2.27.1
Some checks failed
Tests / build (macOS-latest, 2.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.6) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-18.04, 2.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.6) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (ubuntu-latest, pypy3) (push) Has been cancelled
Tests / build (windows-latest, 2.7) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.6) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2022-01-05 08:35:04 -07:00
Ian Stapleton Cordasco
8fa9724398 Merge pull request #6028 from nateprewitt/prox_auth_fix
Fix auth parsing for proxies
2022-01-04 10:04:25 -06:00
Nate Prewitt
38f3f8ecb9 Fix auth parsing for proxies
Co-authored-by: adamp01 <@adamp01>
2022-01-04 08:27:23 -07:00
Nate Prewitt
0192aac241 v2.27.0
Some checks failed
Tests / build (macOS-latest, 2.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.6) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-18.04, 2.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.6) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-latest, pypy-3.7) (push) Has been cancelled
Tests / build (ubuntu-latest, pypy3) (push) Has been cancelled
Tests / build (windows-latest, 2.7) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.6) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2022-01-03 07:59:10 -07:00
Nate Prewitt
e50dc12b0f Fix doc link 2022-01-02 22:20:40 -07:00
Nate Prewitt
17e6e27a93 General cleanup for 2.27.0 2021-12-29 16:29:09 -06:00
Greg Dubicki
ab38e2c726 Make the data vs json parameters more clear (#5382)
As https://stackoverflow.com/q/26685248/2693875 question
and answer popularity shows that there is a lot of people who
do not understand the distinction.
2021-12-29 10:46:21 -07:00
Nate Prewitt
77d1e9aa3e Merge pull request #5894 from dbaxa/do-not-re-build-proxies-when-proxies-have-been-provided
Prevent unnecessary should_bypass_proxies checks in rebuild_proxies
2021-12-29 10:06:05 -07:00
Ian Stapleton Cordasco
b0829a8690 Merge pull request #6020 from nateprewitt/pypy_37
Update 3.10-dev to 3.10 and add pypy-3.7 in CI
2021-12-29 07:32:11 -06:00
Ian Stapleton Cordasco
28d537dde3 Merge pull request #5917 from nateprewitt/proxy_scheme_unknown_fix
Move from urlparse to parse_url for prepending schemes
2021-12-29 07:31:40 -06:00
Nate Prewitt
86bbee71b2 Update 3.10-dev to 3.10 and add pypy-3.7 2021-12-28 22:28:30 -07:00
Nate Prewitt
0d5347e628 Only compute should_bypass_proxies if needed
Co-authored-by: David Black <dblack@atlassian.com>
2021-12-28 22:10:51 -07:00
Nate Prewitt
ef59aa0227 Move from urlparse to parse_url for prepending schemes 2021-12-28 21:12:00 -07:00
Ian Stapleton Cordasco
d09659997c Merge pull request #5414 from mondeja/5367
Raise InvalidUrl if host starts with '.' character.
2021-12-28 19:39:36 -06:00
Ian Stapleton Cordasco
2b06a952a9 Merge pull request #6017 from nateprewitt/schema2scheme
Fix erroneous use of 'schema' instead of 'scheme'
2021-12-28 19:39:08 -06:00
Ian Stapleton Cordasco
e2064a2c05 Merge pull request #6018 from nateprewitt/basic_auth_update
Update basic auth example
2021-12-28 19:38:35 -06:00
Nate Prewitt
401e17bbdc Update basic auth example 2021-12-28 18:07:01 -07:00
Nate Prewitt
4246792b81 Fix erroneous use of 'schema' instead of 'scheme' 2021-12-28 17:32:48 -07:00
Álvaro Mondéjar
658c10e84a Raise InvalidURL if hosts starts with '.' as was reported at #5367. 2021-12-28 14:24:24 -07:00
Abhinav Vashisth
39d0fdd909 Update README.md (#5998) 2021-12-05 02:52:10 -08:00
Ian Stapleton Cordasco
c193d9742e Merge pull request #5993 from davisagli/tox-parallel
Run tox in parallel using -p instead of detox
2021-11-30 08:07:59 -06:00
David Glick
8e764f355d Run tox in parallel using -p instead of detox 2021-11-29 22:20:47 -05:00
Nate Prewitt
7556ea4213 Merge pull request #5391 from hodbn/fix-5274
Send only one Host header in chunked request
2021-11-29 07:58:31 -08:00
David Hotham
2d2447e210 Catch AttributeError in utils.super_len (#5239)
* Catch AttributeError in utils.super_len

This allows it to handle files obtained via `Tarfile.extractfile()`.
2021-11-28 12:04:49 -08:00
Ian Stapleton Cordasco
e77dd8de6e Merge pull request #5990 from mgorny/trustme-test-dep
Skip the test requiring trustme when it is not available
2021-11-27 19:20:17 -06:00
Michał Górny
55da533a87 Defer the trustme import until inside the fixture
Move the trustme import from global scope into the nosan_server fixture
in order to make it possible to deselect the test via command-line
when trustme is not available.
2021-11-27 18:47:56 +01:00
Nate Prewitt
99b3b49241 Refactor rebuild_proxies to allow proxy resolution (#5924)
without stripping Proxy-Authorization header
2021-11-24 13:21:01 -06:00
sha016
e2fa8d3654 mention all_proxy env variable in Proxies section 2021-11-13 20:58:30 -06:00
Alan Yee
590350f8d0 Update Bug_report.md (#5965)
Add some highlighting and comment out the instructions so that the submitter can see it but it is not displayed in the GitHub issue
2021-10-21 11:06:33 -07:00
Nate Prewitt
9dd6e47b5b Add doc build requirements for RTD (#5956) 2021-10-09 15:03:33 -07:00
Борис Верховский
94f9f7357e Don't mention pip as the reason for supporting py2 (#5940) 2021-09-21 16:22:12 -07:00
Kevin Kirsche
fc106ab586 fix minor typos (#5934) 2021-09-15 06:43:12 -05:00
Ian Stapleton Cordasco
1e5fad7433 Update lock-issues.yml (#5930)
Run this hourly. I'm not seeing any effects, let's see if
running it more frequently will allow it to avoid the GitHub ratelimit

https://github.com/dessant/lock-threads#why-are-only-some-issues-and-pull-requests-processed
2021-09-07 17:00:31 -07:00
Nate Prewitt
248122a697 Add support for 3.10 (#5928) 2021-09-03 19:03:15 -07:00
Nate Prewitt
9572d6a299 General doc updates (#5923) 2021-09-02 08:53:46 -07:00
Nate Prewitt
4ab2550d66 Add test for default chunked Host header 2021-09-02 01:53:34 -07:00
hodbn
6fbfca90b6 Send only one Host header in chunked request
Closes #5274
2021-09-02 00:53:08 -07:00
Ben Li-Sauerwine
aae78010e9 Time out requests' tests after 10 minutes. (#5916) 2021-08-27 11:49:51 -07:00
Nate Prewitt
10968357a1 Appropriately handle urllib3's InvalidHeader exception (#5914) 2021-08-26 20:11:30 -05:00
Nate Prewitt
b351e906b6 Update codeql branch target (#5913) 2021-08-26 05:58:35 -05:00
Nate Prewitt
70a13e2a26 Fix syntax error in lock action (#5912) 2021-08-25 18:04:43 -07:00
Ian Stapleton Cordasco
8ea90a5ce7 Shorten the window before we lock threads
Per Nate's recommendation, let's lock things after 90 days rather than
360
2021-08-25 11:44:57 -07:00
Ian Stapleton Cordasco
7adb8686ac Add thread locker workflow (#5909) 2021-08-25 13:21:35 -05:00
Борис Верховский
827b87b9be Formatting (#5910) 2021-08-25 09:48:30 -07:00
Ben Li-Sauerwine
2d30aeb7c6 Add a test which verifies that ChunkedEncodingError is emitted if an invalid Transfer-Encoding: chunked response is returned. (#5906) 2021-08-24 19:00:17 -07:00
Israel Tsadok
b0e025ade7 Fix broken link to adapters essay (#5899) 2021-08-03 22:58:51 -07:00
Thomas Lam
d8829f9f24 Fix extract_zipped_paths infinite loop when provided invalid unc path (#5851) 2021-08-02 19:40:34 -05:00
Michał Górny
b227e3cb82 Fix creating non-listening sockets in tests on some platforms (#5890)
Fix the listen() invocation for the test server not to pass a backlog
value of zero.  The value of zero means no backlog which effectively
means that the socket can not accept any connections.  This does not
matter for the majority of platforms since the value is only advisory
and the platform tends to go with a bigger backlog anyway.  However,
a few platforms (e.g. alpha or riscv Linux) do take the value literally,
and therefore the tests fail since they are unable to connect to
the server.
2021-07-28 11:23:08 -05:00
Steve Berdy
db575eeedc Fix inconsistent exception type in response.json() method (#5856) 2021-07-26 08:56:44 -07:00
Jeremy Cline
941a77f2b8 docs: Update the Red Hat vulnerabilities contact (#5881) 2021-07-19 09:44:02 -07:00
Egor
a3d54b83f7 Update advanced.rst (#5821)
* Update advanced.rst

* remove confusing euphemism
2021-07-18 05:59:37 -05:00
Steve Berdy
a5b45edf2f Update api.rst (#5877) 2021-07-16 11:06:42 -07:00
Nate Prewitt
a1a6a549a0 v2.26.0
Some checks failed
Tests / build (macOS-latest, 2.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.6) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-18.04, 2.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.6) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-latest, pypy3) (push) Has been cancelled
Tests / build (windows-latest, 2.7) (push) Has been cancelled
Tests / build (windows-latest, 3.6) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2021-07-13 07:52:02 -07:00
Quentin Pradet
e253eba5ed Stop abusing pytest-httpbin to test commonName support
pytest-httpbin<1.0 ships with a server certificate with a commonName but
no subjectAltName. urllib3 2.0 will stop supporting those in the future,
so we want to upgrade pytest-httpbin.

Unfortunately, `test_https_warnings` was relying on this broken
certificate. With this change, we use `trustme` to create a broken
certificate specifically for this test, so that we can upgrade
pytest-httpbin and make sure that other tests relying on httpbin TLS
support will continue to work with urllib3 2.0.
2021-07-12 09:44:43 -05:00
Nate Prewitt
f6c0619d89 Disable requests[security] and remove 3.5 support references 2021-07-09 12:40:55 -07:00
Naor Livne
33cf965f72 Allow idna 3.x to be installed on Python 3.x
Co-authored-by: Mickaël Schoentgen <contact@tiger-222.fr>
Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
2021-07-07 08:25:20 -05:00
Дилян Палаузов
5351469472 Add support for brotli decoding (#5783)
* Add support for Brotli decoding

When the brotli or brotlicffi packages are installed,
urllib3.util.make_headers() inserts ',br' in the Accept-Encoding header
and decodes br from the answers.

* Create the default Accept-Encoding header once

* Preserve the previous delimiter behavior

* Update prose in quickstart.rst

Co-authored-by: Seth Michael Larson <sethmichaellarson@gmail.com>
2021-07-07 08:16:28 -05:00
Bernát Gábor
24630743ce Avoid zip extract racing condition by using read+write instead extract (#5707)
Extract also creates the folder hierarchy, however we do not need that,
the file itself being extracted to a temporary folder is good enough.
Instead we read the content of the zip and then write it. The write is
not locked but it's OK to update the same file multiple times given the
update operation will not alter the content of the file. By not creating
the folder hierarchy (default via extract) we no longer can run into the
problem of two parallel extracts both trying to create the folder
hierarchy without exists ok flag, and one must fail.

Resolves #5223.

Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
2021-07-06 19:14:51 -05:00
Ash Berlin-Taylor
2ed84f55b2 Switch LGPL'd chardet for MIT licensed charset_normalizer (#5797)
Although using the (non-vendored) chardet library is fine for requests
itself, but using a LGPL dependency the story is a lot less clear
for downstream projects, particularly ones that might like to bundle
requests (and thus chardet) in to a single binary -- think something
similar to what docker-compose is doing. By including an LGPL'd module
it is no longer clear if the resulting artefact must also be LGPL'd.

By changing out this dependency for one under MIT we remove all
license ambiguity.

As an "escape hatch" I have made the code so that it will use chardet
first if it is installed, but we no longer depend upon it directly,
although there is a new extra added, `requests[lgpl]`. This should
minimize the impact to users, and give them an escape hatch if
charset_normalizer turns out to be not as good. (In my non-exhaustive
tests it detects the same encoding as chartdet in every case I threw at
it)

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
2021-07-06 18:55:02 -05:00
TAHRI Ahmed R
33d448eb21 Pin Flask to <2.0 to fix the test suite
httpbin does not limit Flask version and the 2.0 release has broken compatibility with our test suite
2021-07-06 15:39:50 -05:00
Francois Marier
1466ad713c Fix GitHub links (#5835)
All of these links now redirect to a repo under a different GitHub
user account.
2021-05-31 21:23:54 -07:00
Mark Bell
f6d43b03fb Updated to new be-cordial-or-be-on-your-way URL and CoC now references Python Community CoC (#5811)
* Updated references to new be-cordial-or-be-on-your-way URL.

* CoC now references Python Community Code of Conduct
2021-05-07 11:54:37 -05:00
tallalnparis4ev
05a1a21593 Throw value error when serializing JSON object with NaN value (#5810)
* disallow nan values in json serialize

* test nan value in json post

* added exception for invalid json in request

* use invalid json exception

* invalid json test
2021-05-06 13:18:58 -05:00
Coco Mishra
c45a4dfe6b grammar fix (#5791) 2021-04-11 11:25:49 -05:00
Hans96
2c2138e811 Update setup.py (#5780)
Removed `import re` as unnecessary import
2021-03-19 17:09:29 -05:00
laggardkernel
c267415882 Remove call to set default value for allow_redirects
The call to kwargs.setdefault was redundant and unnecessary as the lower-level session defaults to true.
2021-03-19 11:44:19 -05:00
Ian Stapleton Cordasco
5433656878 Merge pull request #5777 from cclauss/patch-1
quickstart.rst: r.json() can raise JSONDecodeError on Py3
2021-03-18 09:36:34 -05:00
Christian Clauss
f79d1ef42f Also update requests.models.Response 2021-03-18 13:19:28 +01:00
Christian Clauss
4193b95db0 simplejson.JSONDecodeError may be raised on Py2 and Py3 2021-03-18 12:27:02 +01:00
Christian Clauss
06462a6e79 quickstart.rst: r.json() can raise JSONDecodeError on Py3
% `python2 -c "import requests ; requests.get('https://github.com').json()"`
% `python3 -c "import requests ; requests.get('https://github.com').json()"`
2021-03-18 10:06:14 +01:00
Andrew M. White
913880c45a Fix typo 2021-02-24 10:19:48 -07:00
Ian Stapleton Cordasco
bdc00eb097 Merge pull request #5755 from pdmccormick/master
Provide directions for cloning repository despite malformed commits
2021-02-17 15:37:02 -06:00
Peter David McCormick
ef6ceffc1c Provide directions for cloning repository despite malformed commits 2021-02-17 15:38:48 -05:00
Ian Stapleton Cordasco
8c211a96cd Merge pull request #5681 from mateusduboli/5677
5677: Rebuild proxies on Session#send
2021-01-24 16:59:46 -06:00
XiaojueGuan
74b7280049 Fix typo request -> requests 2021-01-22 07:41:39 -06:00
Ian Stapleton Cordasco
083aa67a0d Merge pull request #5723 from bbodenmiller/patch-2
Format c_rehash
2021-01-20 08:58:35 -06:00
Ben Bodenmiller
f6072f1e14 Format c_rehash 2021-01-19 17:27:56 -08:00
Nate Prewitt
c2b307dbef v2.25.1
Some checks failed
Tests / build (macOS-latest, 2.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.5) (push) Has been cancelled
Tests / build (macOS-latest, 3.6) (push) Has been cancelled
Tests / build (macOS-latest, 3.7) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (ubuntu-18.04, 2.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.5) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.6) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.7) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-18.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-latest, pypy3) (push) Has been cancelled
Tests / build (windows-latest, 2.7) (push) Has been cancelled
Tests / build (windows-latest, 3.5) (push) Has been cancelled
Tests / build (windows-latest, 3.6) (push) Has been cancelled
Tests / build (windows-latest, 3.7) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
2020-12-16 11:34:26 -08:00
Shouro
6de4f104cd Change docs title to requests.sessions 2020-12-14 17:49:49 -06:00
Nate Prewitt
b223291693 Merge pull request #5688 from dan-blanchard/patch-1
Upgrade to chardet 4.x
2020-12-14 09:29:10 -08:00
Dan Blanchard
516f84f490 Upgrade to chardet 4.x 2020-12-11 10:31:38 -05:00
Mateus Dubiela Oliveira
60ea7f0c2d #5677: Rebuild proxies on Session#send 2020-12-08 18:37:18 +01:00
106 changed files with 5351 additions and 3112 deletions

View File

@@ -1,2 +1,2 @@
[run]
omit = requests/packages/*
omit = requests/packages/*

5
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,5 @@
# You can configure git to automatically use this file with the following config:
# git config --global blame.ignoreRevsFile .git-blame-ignore-revs
# Add automatic code formatting to Requests
2a6f290bc09324406708a4d404a88a45d848ddf9

View File

@@ -1,3 +1,6 @@
Be cordial or be on your way.
# Treat each other well
https://www.kennethreitz.org/essays/be-cordial-or-be-on-your-way
Everyone participating in the _requests_ project, and in particular in the issue tracker,
pull requests, and social media activity, is expected to treat other people with respect
and more generally to follow the guidelines articulated in the
[Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/).

View File

@@ -25,4 +25,4 @@ import requests
This command is only available on Requests v2.16.4 and greater. Otherwise,
please provide some basic information about your system (Python version,
operating system, &c).
operating system, &c).

View File

@@ -4,15 +4,15 @@ about: Create a report to help us improve
---
Summary.
<!-- Summary. -->
## Expected Result
What you expected.
<!-- What you expected. -->
## Actual Result
What happened instead.
<!-- What happened instead. -->
## Reproduction Steps
@@ -25,10 +25,12 @@ import requests
$ python -m requests.help
```
<paste here>
```json
{
"paste": "here"
}
```
This command is only available on Requests v2.16.4 and greater. Otherwise,
<!-- This command is only available on Requests v2.16.4 and greater. Otherwise,
please provide some basic information about your system (Python version,
operating system, &c).
operating system, &c). -->

View File

@@ -1,6 +1,9 @@
---
name: Request for Help
about: Guidance on using Requests.
labels:
- "Question/Not a bug"
- "actions/autoclose-qa"
---

View File

@@ -1,6 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
labels:
- "Feature Request"
- "actions/autoclose-feat"
---

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
ignore:
# Ignore all patch releases as we can manually
# upgrade if we run into a bug and need a fix.
- dependency-name: "*"
update-types: ["version-update:semver-patch"]

35
.github/workflows/close-issues.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: 'Autoclose Issues'
on:
issues:
types:
- labeled
permissions:
issues: write
jobs:
close_qa:
if: github.event.label.name == 'actions/autoclose-qa'
runs-on: ubuntu-latest
steps:
- env:
ISSUE_URL: ${{ github.event.issue.html_url }}
GH_TOKEN: ${{ github.token }}
run: |
gh issue close $ISSUE_URL \
--comment "As described in the template, we won't be able to answer questions on this issue tracker. Please use [Stack Overflow](https://stackoverflow.com/)" \
--reason completed
gh issue lock $ISSUE_URL --reason off_topic
close_feature_request:
if: github.event.label.name == 'actions/autoclose-feat'
runs-on: ubuntu-latest
steps:
- env:
ISSUE_URL: ${{ github.event.issue.html_url }}
GH_TOKEN: ${{ github.token }}
run: |
gh issue close $ISSUE_URL \
--comment "As described in the template, Requests is not accepting feature requests" \
--reason "not planned"
gh issue lock $ISSUE_URL --reason off_topic

View File

@@ -7,15 +7,22 @@ name: "CodeQL"
on:
push:
branches: [master]
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
branches: [main]
schedule:
- cron: '0 23 * * 0'
permissions:
contents: read
jobs:
analyze:
permissions:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report
name: Analyze
runs-on: ubuntu-latest
@@ -25,7 +32,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -38,18 +45,18 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0
with:
languages: "python"
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# 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@v1
uses: github/codeql-action/autobuild@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -63,4 +70,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0

20
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Lint code
on: [push, pull_request]
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-20.04
timeout-minutes: 10
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up Python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:
python-version: "3.x"
- name: Run pre-commit
uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507 # v3.0.0

19
.github/workflows/lock-issues.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: 'Lock Threads'
on:
schedule:
- cron: '0 0 * * *'
permissions:
issues: write
pull-requests: write
jobs:
action:
if: github.repository_owner == 'psf'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@d42e5f49803f3c4e14ffee0378e31481265dda22 # v5.0.0
with:
issue-lock-inactive-days: 90
pr-lock-inactive-days: 90

View File

@@ -2,30 +2,58 @@ name: Tests
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
os: [ubuntu-18.04, macOS-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.9", "pypy-3.10"]
os: [ubuntu-22.04, macOS-latest, windows-latest]
# Python 3.8 and 3.9 do not run on macOS-latest which
# is now using arm64 hardware.
# https://github.com/actions/setup-python/issues/696#issuecomment-1637587760
exclude:
- { python-version: "3.8", os: "macos-latest" }
- { python-version: "3.9", os: "macos-latest" }
include:
# pypy3 on Mac OS currently fails trying to compile
# brotlipy. Moving pypy3 to only test linux.
- python-version: pypy3
os: ubuntu-latest
- { python-version: "3.8", os: "macos-13" }
- { python-version: "3.9", os: "macos-13" }
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
make
- name: Run tests
run: |
make ci
no_chardet:
name: "No Character Detection"
runs-on: ubuntu-latest
strategy:
fail-fast: true
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- name: 'Set up Python 3.8'
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
with:
python-version: '3.8'
- name: Install dependencies
run: |
make
python -m pip uninstall -y "charset_normalizer" "chardet"
- name: Run tests
run: |
make ci

6
.gitignore vendored
View File

@@ -23,6 +23,12 @@ env/
.workon
# in case you work with IntelliJ/PyCharm
.idea
*.iml
.python-version
t.py
t2.py

28
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,28 @@
exclude: 'docs/|ext/'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
exclude: tests/test_lowlevel.py
- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8

29
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,29 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
builder: "dirhtml"
# Optionally build your docs in additional formats such as PDF and ePub
formats:
- pdf
- epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- path: .
- requirements: docs/requirements.txt

View File

@@ -8,7 +8,7 @@ Keepers of the Crystals
Previous Keepers of Crystals
````````````````````````````
- Kenneth Reitz <me@kennethreitz.org> `@ken-reitz <https://github.com/ken-reitz>`_, reluctant Keeper of the Master Crystal.
- Kenneth Reitz <me@kennethreitz.org> `@kennethreitz <https://github.com/kennethreitz>`_, reluctant Keeper of the Master Crystal.
- Cory Benfield <cory@lukasa.co.uk> `@lukasa <https://github.com/lukasa>`_
- Ian Cordasco <graffatcolmingov@gmail.com> `@sigmavirus24 <https://github.com/sigmavirus24>`_.
@@ -191,3 +191,5 @@ Patches and Suggestions
- "Dull Bananas" <dull.bananas0@gmail.com> (`@dullbananas <https://github.com/dullbananas>`_)
- Alessio Izzo (`@aless10 <https://github.com/aless10>`_)
- Sylvain Marié (`@smarie <https://github.com/smarie>`_)
- Hod Bin Noon (`@hodbn <https://github.com/hodbn>`_)
- Mike Fiedler (`@miketheman <https://github.com/miketheman>`_)

View File

@@ -4,11 +4,272 @@ Release History
dev
---
- \[Short description of non-trivial change.\]
- \[Short description of non-trivial change.\]
2.32.1 (2024-05-20)
-------------------
**Bugfixes**
- Add missing test certs to the sdist distributed on PyPI.
2.32.0 (2024-05-20)
-------------------
**Security**
- Fixed an issue where setting `verify=False` on the first request from a
Session will cause subsequent requests to the _same origin_ to also ignore
cert verification, regardless of the value of `verify`.
(https://github.com/psf/requests/security/advisories/GHSA-9wx4-h78v-vm56)
**Improvements**
- `verify=True` now reuses a global SSLContext which should improve
request time variance between first and subsequent requests. It should
also minimize certificate load time on Windows systems when using a Python
version built with OpenSSL 3.x. (#6667)
- Requests now supports optional use of character detection
(`chardet` or `charset_normalizer`) when repackaged or vendored.
This enables `pip` and other projects to minimize their vendoring
surface area. The `Response.text()` and `apparent_encoding` APIs
will default to `utf-8` if neither library is present. (#6702)
**Bugfixes**
- Fixed bug in length detection where emoji length was incorrectly
calculated in the request content-length. (#6589)
- Fixed deserialization bug in JSONDecodeError. (#6629)
- Fixed bug where an extra leading `/` (path separator) could lead
urllib3 to unnecessarily reparse the request URI. (#6644)
**Deprecations**
- Requests has officially added support for CPython 3.12 (#6503)
- Requests has officially added support for PyPy 3.9 and 3.10 (#6641)
- Requests has officially dropped support for CPython 3.7 (#6642)
- Requests has officially dropped support for PyPy 3.7 and 3.8 (#6641)
**Documentation**
- Various typo fixes and doc improvements.
**Packaging**
- Requests has started adopting some modern packaging practices.
The source files for the projects (formerly `requests`) is now located
in `src/requests` in the Requests sdist. (#6506)
- Starting in Requests 2.33.0, Requests will migrate to a PEP 517 build system
using `hatchling`. This should not impact the average user, but extremely old
versions of packaging utilities may have issues with the new packaging format.
2.31.0 (2023-05-22)
-------------------
**Security**
- Versions of Requests between v2.3.0 and v2.30.0 are vulnerable to potential
forwarding of `Proxy-Authorization` headers to destination servers when
following HTTPS redirects.
When proxies are defined with user info (`https://user:pass@proxy:8080`), Requests
will construct a `Proxy-Authorization` header that is attached to the request to
authenticate with the proxy.
In cases where Requests receives a redirect response, it previously reattached
the `Proxy-Authorization` header incorrectly, resulting in the value being
sent through the tunneled connection to the destination server. Users who rely on
defining their proxy credentials in the URL are *strongly* encouraged to upgrade
to Requests 2.31.0+ to prevent unintentional leakage and rotate their proxy
credentials once the change has been fully deployed.
Users who do not use a proxy or do not supply their proxy credentials through
the user information portion of their proxy URL are not subject to this
vulnerability.
Full details can be read in our [Github Security Advisory](https://github.com/psf/requests/security/advisories/GHSA-j8r2-6x86-q33q)
and [CVE-2023-32681](https://nvd.nist.gov/vuln/detail/CVE-2023-32681).
2.30.0 (2023-05-03)
-------------------
**Dependencies**
- ⚠️ Added support for urllib3 2.0. ⚠️
This may contain minor breaking changes so we advise careful testing and
reviewing https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html
prior to upgrading.
Users who wish to stay on urllib3 1.x can pin to `urllib3<2`.
2.29.0 (2023-04-26)
-------------------
**Improvements**
- Requests now defers chunked requests to the urllib3 implementation to improve
standardization. (#6226)
- Requests relaxes header component requirements to support bytes/str subclasses. (#6356)
2.28.2 (2023-01-12)
-------------------
**Dependencies**
- Requests now supports charset\_normalizer 3.x. (#6261)
**Bugfixes**
- Updated MissingSchema exception to suggest https scheme rather than http. (#6188)
2.28.1 (2022-06-29)
-------------------
**Improvements**
- Speed optimization in `iter_content` with transition to `yield from`. (#6170)
**Dependencies**
- Added support for chardet 5.0.0 (#6179)
- Added support for charset-normalizer 2.1.0 (#6169)
2.28.0 (2022-06-09)
-------------------
**Deprecations**
- ⚠️ Requests has officially dropped support for Python 2.7. ⚠️ (#6091)
- Requests has officially dropped support for Python 3.6 (including pypy3.6). (#6091)
**Improvements**
- Wrap JSON parsing issues in Request's JSONDecodeError for payloads without
an encoding to make `json()` API consistent. (#6097)
- Parse header components consistently, raising an InvalidHeader error in
all invalid cases. (#6154)
- Added provisional 3.11 support with current beta build. (#6155)
- Requests got a makeover and we decided to paint it black. (#6095)
**Bugfixes**
- Fixed bug where setting `CURL_CA_BUNDLE` to an empty string would disable
cert verification. All Requests 2.x versions before 2.28.0 are affected. (#6074)
- Fixed urllib3 exception leak, wrapping `urllib3.exceptions.SSLError` with
`requests.exceptions.SSLError` for `content` and `iter_content`. (#6057)
- Fixed issue where invalid Windows registry entries caused proxy resolution
to raise an exception rather than ignoring the entry. (#6149)
- Fixed issue where entire payload could be included in the error message for
JSONDecodeError. (#6036)
2.27.1 (2022-01-05)
-------------------
**Bugfixes**
- Fixed parsing issue that resulted in the `auth` component being
dropped from proxy URLs. (#6028)
2.27.0 (2022-01-03)
-------------------
**Improvements**
- Officially added support for Python 3.10. (#5928)
- Added a `requests.exceptions.JSONDecodeError` to unify JSON exceptions between
Python 2 and 3. This gets raised in the `response.json()` method, and is
backwards compatible as it inherits from previously thrown exceptions.
Can be caught from `requests.exceptions.RequestException` as well. (#5856)
- Improved error text for misnamed `InvalidSchema` and `MissingSchema`
exceptions. This is a temporary fix until exceptions can be renamed
(Schema->Scheme). (#6017)
- Improved proxy parsing for proxy URLs missing a scheme. This will address
recent changes to `urlparse` in Python 3.9+. (#5917)
**Bugfixes**
- Fixed defect in `extract_zipped_paths` which could result in an infinite loop
for some paths. (#5851)
- Fixed handling for `AttributeError` when calculating length of files obtained
by `Tarfile.extractfile()`. (#5239)
- Fixed urllib3 exception leak, wrapping `urllib3.exceptions.InvalidHeader` with
`requests.exceptions.InvalidHeader`. (#5914)
- Fixed bug where two Host headers were sent for chunked requests. (#5391)
- Fixed regression in Requests 2.26.0 where `Proxy-Authorization` was
incorrectly stripped from all requests sent with `Session.send`. (#5924)
- Fixed performance regression in 2.26.0 for hosts with a large number of
proxies available in the environment. (#5924)
- Fixed idna exception leak, wrapping `UnicodeError` with
`requests.exceptions.InvalidURL` for URLs with a leading dot (.) in the
domain. (#5414)
**Deprecations**
- Requests support for Python 2.7 and 3.6 will be ending in 2022. While we
don't have exact dates, Requests 2.27.x is likely to be the last release
series providing support.
2.26.0 (2021-07-13)
-------------------
**Improvements**
- Requests now supports Brotli compression, if either the `brotli` or
`brotlicffi` package is installed. (#5783)
- `Session.send` now correctly resolves proxy configurations from both
the Session and Request. Behavior now matches `Session.request`. (#5681)
**Bugfixes**
- Fixed a race condition in zip extraction when using Requests in parallel
from zip archive. (#5707)
**Dependencies**
- Instead of `chardet`, use the MIT-licensed `charset_normalizer` for Python3
to remove license ambiguity for projects bundling requests. If `chardet`
is already installed on your machine it will be used instead of `charset_normalizer`
to keep backwards compatibility. (#5797)
You can also install `chardet` while installing requests by
specifying `[use_chardet_on_py3]` extra as follows:
```shell
pip install "requests[use_chardet_on_py3]"
```
Python2 still depends upon the `chardet` module.
- Requests now supports `idna` 3.x on Python 3. `idna` 2.x will continue to
be used on Python 2 installations. (#5711)
**Deprecations**
- The `requests[security]` extra has been converted to a no-op install.
PyOpenSSL is no longer the recommended secure option for Requests. (#5867)
- Requests has officially dropped support for Python 3.5. (#5867)
2.25.1 (2020-12-16)
-------------------
**Bugfixes**
- Requests now treats `application/json` as `utf8` by default. Resolving
inconsistencies between `r.text` and `r.json` output. (#5673)
**Dependencies**
- Requests now supports chardet v4.x.
2.25.0 (2020-11-11)
------------------
-------------------
**Improvements**
@@ -55,7 +316,7 @@ dev
**Dependencies**
- Pinning for `chardet` and `idna` now uses major version instead of minor.
This hopefully reduces the need for releases everytime a dependency is updated.
This hopefully reduces the need for releases every time a dependency is updated.
2.22.0 (2019-05-15)
-------------------
@@ -410,7 +671,7 @@ Or, even better:
- Fixed regression from 2.12.2 where non-string types were rejected in
the basic auth parameters. While support for this behaviour has been
readded, the behaviour is deprecated and will be removed in the
re-added, the behaviour is deprecated and will be removed in the
future.
2.12.3 (2016-12-01)
@@ -1644,12 +1905,12 @@ This is not a backwards compatible change.
- Automatic Authentication API Change
- Smarter Query URL Parameterization
- Allow file uploads and POST data together
-
-
New Authentication Manager System
: - Simpler Basic HTTP System
- Supports all build-in urllib2 Auths
- Supports all built-in urllib2 Auths
- Allows for custom Auth Handlers
0.2.4 (2011-02-19)
@@ -1663,7 +1924,7 @@ This is not a backwards compatible change.
0.2.3 (2011-02-15)
------------------
-
-
New HTTPHandling Methods
@@ -1696,4 +1957,3 @@ This is not a backwards compatible change.
- Frustration
- Conception

View File

@@ -1,2 +1,3 @@
include README.md LICENSE NOTICE HISTORY.md pytest.ini requirements-dev.txt
include README.md LICENSE NOTICE HISTORY.md requirements-dev.txt
recursive-include tests *.py
recursive-include tests/certs *

View File

@@ -1,24 +1,23 @@
.PHONY: docs
init:
pip install -e .[socks]
pip install -r requirements-dev.txt
python -m pip install -r requirements-dev.txt
test:
# This runs all of the tests, on both Python 2 and Python 3.
detox
# This runs all of the tests on all supported Python versions.
tox -p
ci:
pytest tests --junitxml=report.xml
python -m pytest tests --junitxml=report.xml
test-readme:
python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"
flake8:
flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
python -m flake8 src/requests
coverage:
pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
python -m pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=src/requests tests
publish:
pip install 'twine>=1.5.0'
python -m pip install 'twine>=1.5.0'
python setup.py sdist bdist_wheel
twine upload dist/*
rm -fr build dist .egg requests.egg-info

View File

@@ -1,10 +1,10 @@
# Requests
**Requests** is a simple, yet elegant HTTP library.
**Requests** is a simple, yet elegant, HTTP library.
```python
>>> import requests
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r = requests.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
@@ -12,16 +12,16 @@
>>> r.encoding
'utf-8'
>>> r.text
'{"type":"User"...'
'{"authenticated": true, ...'
>>> r.json()
{'disk_usage': 368627, 'private_gists': 484, ...}
{'authenticated': True, ...}
```
Requests allows you to send HTTP/1.1 requests extremely easily. Theres no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method!
Requests is one of the most downloaded Python package today, pulling in around `14M downloads / week`— according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `500,000+` repositories. You may certainly put your trust in this code.
Requests is one of the most downloaded Python packages today, pulling in around `30M downloads / week`— according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `1,000,000+` repositories. You may certainly put your trust in this code.
[![Downloads](https://pepy.tech/badge/requests/month)](https://pepy.tech/project/requests/month)
[![Downloads](https://static.pepy.tech/badge/requests/month)](https://pepy.tech/project/requests)
[![Supported Versions](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests)
[![Contributors](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors)
@@ -33,7 +33,7 @@ Requests is available on PyPI:
$ python -m pip install requests
```
Requests officially supports Python 2.7 & 3.5+.
Requests officially supports Python 3.8+.
## Supported Features & BestPractices
@@ -55,8 +55,24 @@ Requests is ready for the demands of building robust and reliable HTTPspeakin
## API Reference and User Guide available on [Read the Docs](https://requests.readthedocs.io)
[![Read the Docs](https://raw.githubusercontent.com/psf/requests/master/ext/ss.png)](https://requests.readthedocs.io)
[![Read the Docs](https://raw.githubusercontent.com/psf/requests/main/ext/ss.png)](https://requests.readthedocs.io)
## Cloning the repository
When cloning the Requests repository, you may need to add the `-c
fetch.fsck.badTimezone=ignore` flag to avoid an error about a bad commit (see
[this issue](https://github.com/psf/requests/issues/2690) for more background):
```shell
git clone -c fetch.fsck.badTimezone=ignore https://github.com/psf/requests.git
```
You can also apply this setting to your global Git config:
```shell
git config --global fetch.fsck.badTimezone ignore
```
---
[![Kenneth Reitz](https://raw.githubusercontent.com/psf/requests/master/ext/kr.png)](https://kennethreitz.org) [![Python Software Foundation](https://raw.githubusercontent.com/psf/requests/master/ext/psf.png)](https://www.python.org/psf)
[![Kenneth Reitz](https://raw.githubusercontent.com/psf/requests/main/ext/kr.png)](https://kennethreitz.org) [![Python Software Foundation](https://raw.githubusercontent.com/psf/requests/main/ext/psf.png)](https://www.python.org/psf)

BIN
docs/_static/requests-sidebar.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

View File

@@ -1,52 +1,30 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/requests-sidebar.png', 1) }}" alt="Requests logo" />
</a>
</p>
<p>
<iframe src="https://ghbtns.com/github-btn.html?user=psf&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
<style>
.algolia-autocomplete {
width: 100%;
height: 1.5em
}
.algolia-autocomplete a {
border-bottom: none !important;
}
#doc_search {
width: 100%;
height: 100%;
}
</style>
<input id="doc_search" placeholder="Search the doc" autofocus />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" onload="docsearch({
apiKey: 'f177061e2354c50a97bfc635e827ffab',
indexName: 'python-requests',
inputSelector: '#doc_search',
debug: false // Set debug to true if you want to inspect the dropdown
})" async></script>
<p>
Requests is an elegant and simple HTTP library for Python, built for
human beings.
</p>
<p>Sponsored by <strong><a href="https://www.govcert.lu">CERT Gouvernemental - GOVCERT.LU</a></strong>.</p>
<script async type="text/javascript"
src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=CKYI5K3E&placement=pythonrequestsorg"
id="_carbonads_js"></script>
<h3>Useful Links</h3>
<ul>
<li><a href="https://requests.readthedocs.io/en/latest/user/quickstart/">Quickstart</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/user/advanced/">Advanced Usage</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/api/">API Reference</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/community/updates/#release-history">Release History</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/dev/contributing/">Contributors Guide</a></li>
<li><a href="{{ pathto('user/quickstart') }}">Quickstart</a></li>
<li><a href="{{ pathto('user/advanced') }}">Advanced Usage</a></li>
<li><a href="{{ pathto('api') }}">API Reference</a></li>
<li><a href="{{ pathto('community/updates') + '#release-history' }}">Release History</a></li>
<li><a href="{{ pathto('dev/contributing') }}">Contributors Guide</a></li>
<p></p>
<li><a href="https://requests.readthedocs.io/en/latest/community/recommended/">Recommended Packages and Extensions</a></li>
<li><a href="{{ pathto('community/recommended') }}">Recommended Packages and Extensions</a></li>
<p></p>
@@ -55,19 +33,5 @@
<li><a href="https://github.com/psf/requests/issues">Issue Tracker</a></li>
</ul>
<h3>Translations</h3>
<ul>
<li><a href="https://requests.readthedocs.io/">English</a></li>
<li><a href="https://fr.python-requests.org/">French</a></li>
<li><a href="https://de.python-requests.org/">German</a></li>
<li><a href="https://jp.python-requests.org/">Japanese</a></li>
<li><a href="https://cn.python-requests.org/">Chinese</a></li>
<li><a href="https://pt.python-requests.org/">Portuguese</a></li>
<li><a href="https://it.python-requests.org/">Italian</a></li>
<li><a href="https://es.python-requests.org/">Spanish</a></li>
</ul>
<div id="native-ribbon">
</div>

View File

@@ -2,29 +2,6 @@
<iframe src="https://ghbtns.com/github-btn.html?user=psf&repo=requests&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
</p>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
<style>
.algolia-autocomplete {
width: 100%;
height: 1.5em
}
.algolia-autocomplete a {
border-bottom: none !important;
}
#doc_search {
width: 100%;
height: 100%;
}
</style>
<input id="doc_search" placeholder="Search the doc" autofocus />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" onload="docsearch({
apiKey: 'f177061e2354c50a97bfc635e827ffab',
indexName: 'python-requests',
inputSelector: '#doc_search',
debug: false // Set debug to true if you want to inspect the dropdown
})" async></script>
<p>
Requests is an elegant and simple HTTP library for Python, built for
@@ -32,23 +9,17 @@
development release.
</p>
<p>Sponsored by <strong><a href="https://www.govcert.lu">CERT Gouvernemental - GOVCERT.LU</a></strong>.</p>
<script async type="text/javascript"
src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=pythonrequestsorg"
id="_carbonads_js"></script>
<h3>Useful Links</h3>
<ul>
<li><a href="https://requests.readthedocs.io/en/latest/user/quickstart/">Quickstart</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/user/advanced/">Advanced Usage</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/api/">API Reference</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/community/updates/#release-history">Release History</a></li>
<li><a href="https://requests.readthedocs.io/en/latest/dev/contributing/">Contributors Guide</a></li>
<li><a href="{{ pathto('user/quickstart') }}">Quickstart</a></li>
<li><a href="{{ pathto('user/advanced') }}">Advanced Usage</a></li>
<li><a href="{{ pathto('api') }}">API Reference</a></li>
<li><a href="{{ pathto('community/updates') + '#release-history' }}">Release History</a></li>
<li><a href="{{ pathto('dev/contributing') }}">Contributors Guide</a></li>
<p></p>
<li><a href="https://requests.readthedocs.io/en/latest/community/recommended/">Recommended Packages and Extensions</a></li>
<li><a href="{{ pathto('community/recommended') }}">Recommended Packages and Extensions</a></li>
<p></p>
@@ -57,15 +28,3 @@
<li><a href="https://github.com/psf/requests/issues">Issue Tracker</a></li>
</ul>
<h3>Translations</h3>
<ul>
<li><a href="https://requests.readthedocs.io/">English</a></li>
<li><a href="https://fr.python-requests.org/">French</a></li>
<li><a href="https://de.python-requests.org/">German</a></li>
<li><a href="https://jp.python-requests.org/">Japanese</a></li>
<li><a href="https://cn.python-requests.org/">Chinese</a></li>
<li><a href="https://pt.python-requests.org/">Portuguese</a></li>
<li><a href="https://it.python-requests.org/">Italian</a></li>
<li><a href="https://es.python-requests.org/">Spanish</a></li>
</ul>

View File

@@ -36,6 +36,7 @@ Exceptions
.. autoexception:: requests.ConnectTimeout
.. autoexception:: requests.ReadTimeout
.. autoexception:: requests.Timeout
.. autoexception:: requests.JSONDecodeError
Request Sessions
@@ -127,7 +128,7 @@ API Changes
::
import requests
r = requests.get('https://github.com/timeline.json')
r = requests.get('https://api.github.com/events')
r.json() # This *call* raises an exception if JSON decoding fails
* The ``Session`` API has changed. Sessions objects no longer take parameters.
@@ -156,7 +157,7 @@ API Changes
::
# in 0.x, passing prefetch=False would accomplish the same thing
r = requests.get('https://github.com/timeline.json', stream=True)
r = requests.get('https://api.github.com/events', stream=True)
for chunk in r.iter_content(8192):
...

View File

@@ -11,6 +11,9 @@ Encoded Data?
Requests automatically decompresses gzip-encoded responses, and does
its best to decode response content to unicode when possible.
When either the `brotli <https://pypi.org/project/Brotli/>`_ or `brotlicffi <https://pypi.org/project/brotlicffi/>`_
package is installed, requests also decodes Brotli-encoded responses.
You can get direct access to the raw response (and even the socket),
if needed as well.
@@ -19,7 +22,7 @@ Custom User-Agents?
-------------------
Requests allows you to easily override User-Agent strings, along with
any other HTTP Header. See `documentation about headers <https://requests.readthedocs.io/en/master/user/quickstart/#custom-headers>`_.
any other HTTP Header. See :ref:`documentation about headers <custom-headers>`.
@@ -52,19 +55,16 @@ Chris Adams gave an excellent summary on
Python 3 Support?
-----------------
Yes! Requests officially supports Python 2.7 & 3.5+ and PyPy.
Yes! Requests officially supports Python 3.8+ and PyPy.
Python 2 Support?
-----------------
Yes! We do not have immediate plans to `sunset
<https://www.python.org/doc/sunset-python-2/>`_ our support for Python
2.7. We understand that we have a large user base with varying needs,
and intend to maintain Python 2.7 support within Requests until `pip
stops supporting Python 2.7 (there's no estimated date on that yet)
<https://pip.pypa.io/en/latest/development/release-process/#python-2-support>`_.
No! As of Requests 2.28.0, Requests no longer supports Python 2.7. Users who
have been unable to migrate should pin to `requests<2.28`. Full information
can be found in `psf/requests#6023 <https://github.com/psf/requests/issues/6023>`_.
That said, it is *highly* recommended users migrate to Python 3.6+ since Python
It is *highly* recommended users migrate to Python 3.8+ now since Python
2.7 is no longer receiving bug fixes or security updates as of January 1, 2020.
What are "hostname doesn't match" errors?
@@ -83,10 +83,7 @@ when servers are using `Virtual Hosting`_. When such servers are hosting
more than one SSL site they need to be able to return the appropriate
certificate based on the hostname the client is connecting to.
Python3 and Python 2.7.9+ include native support for SNI in their SSL modules.
For information on using SNI with Requests on Python < 2.7.9 refer to this
`Stack Overflow answer`_.
Python 3 already includes native support for SNI in their SSL modules.
.. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication
.. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting
.. _`Stack Overflow answer`: https://stackoverflow.com/questions/18578439/using-requests-with-tls-doesnt-give-sni-support/18579484#18579484

View File

@@ -1,22 +1,10 @@
Integrations
============
Python for iOS
--------------
Requests is built into the wonderful `Python for iOS <https://itunes.apple.com/us/app/python-2.7-for-ios/id485729872?mt=Python8>`_ runtime!
To give it a try, simply::
import requests
Articles & Talks
================
- `Python for the Web <https://www.gun.io/blog/python-for-the-web>`_ teaches how to use Python to interact with the web, using Requests.
- `Daniel Greenfeld's Review of Requests <https://pydanny.blogspot.com/2011/05/python-http-requests-for-humans.html>`_
- `My 'Python for Humans' talk <http://python-for-humans.heroku.com>`_ ( `audio <https://codeconf.s3.amazonaws.com/2011/pycodeconf/talks/PyCodeConf2011%20-%20Kenneth%20Reitz.m4a>`_ )
- `Issac Kelly's 'Consuming Web APIs' talk <https://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
- `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 <http://www.coglib.com/~icordasc/blog/2014/11/sending-json-in-requests.html>`_

View File

@@ -59,4 +59,4 @@ Betamax
`Betamax`_ records your HTTP interactions so the NSA does not have to.
A VCR imitation designed only for Python-Requests.
.. _betamax: https://github.com/sigmavirus24/betamax
.. _betamax: https://github.com/betamaxpy/betamax

View File

@@ -72,7 +72,7 @@ time, to ensure that they are able to promptly release their downstream
packages. Currently the list of people we actively contact *ahead of a public
release* is:
- Jeremy Cline, Red Hat (@jeremycline)
- Python Maintenance Team, Red Hat (python-maint@redhat.com)
- Daniele Tricoli, Debian (@eriol)
We will notify these individuals at least a week ahead of our planned release

View File

@@ -34,7 +34,7 @@ including reporting bugs or requesting features. This golden rule is
**All contributions are welcome**, as long as
everyone involved is treated with respect.
.. _be cordial or be on your way: https://www.kennethreitz.org/essays/be-cordial-or-be-on-your-way
.. _be cordial or be on your way: https://kenreitz.org/essays/2013/01/27/be-cordial-or-be-on-your-way
.. _early-feedback:
@@ -78,7 +78,7 @@ When contributing code, you'll want to follow this checklist:
4. Make your change.
5. Run the entire test suite again, confirming that all tests pass *including
the ones you just added*.
6. Send a GitHub Pull Request to the main repository's ``master`` branch.
6. Send a GitHub Pull Request to the main repository's ``main`` branch.
GitHub Pull Requests are the expected method of code collaboration on this
project.
@@ -93,6 +93,21 @@ event that you object to the code review feedback, you should make your case
clearly and calmly. If, after doing so, the feedback is judged to still apply,
you must either apply the feedback or withdraw your contribution.
Code Style
~~~~~~~~~~
Requests uses a collection of tools to ensure the code base has a consistent
style as it grows. We have these orchestrated using a tool called
`pre-commit`_. This can be installed locally and run over your changes prior
to opening a PR, and will also be run as part of the CI approval process
before a change is merged.
You can find the full list of formatting requirements specified in the
`.pre-commit-config.yaml`_ at the top level directory of Requests.
.. _pre-commit: https://pre-commit.com/
.. _.pre-commit-config.yaml: https://github.com/psf/requests/blob/main/.pre-commit-config.yaml
New Contributors
~~~~~~~~~~~~~~~~
@@ -103,62 +118,6 @@ asking for help.
Please also check the :ref:`early-feedback` section.
Kenneth Reitz's Code Style™
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Requests codebase uses the `PEP 8`_ code style.
In addition to the standards outlined in PEP 8, we have a few guidelines:
- Line-length can exceed 79 characters, to 100, when convenient.
- Line-length can exceed 100 characters, when doing otherwise would be *terribly* inconvenient.
- Always use single-quoted strings (e.g. ``'#flatearth'``), unless a single-quote occurs within the string.
Additionally, one of the styles that PEP8 recommends for `line continuations`_
completely lacks all sense of taste, and is not to be permitted within
the Requests codebase::
# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)
No. Just don't. Please. This is much better::
foo = long_function_name(
var_one,
var_two,
var_three,
var_four,
)
Docstrings are to follow the following syntaxes::
def the_earth_is_flat():
"""NASA divided up the seas into thirty-three degrees."""
pass
::
def fibonacci_spiral_tool():
"""With my feet upon the ground I lose myself / between the sounds
and open wide to suck it in. / I feel it move across my skin. / I'm
reaching up and reaching out. / I'm reaching for the random or
whatever will bewilder me. / Whatever will bewilder me. / And
following our will and wind we may just go where no one's been. /
We'll ride the spiral to the end and may just go where no one's
been.
Spiral out. Keep going...
"""
pass
All functions, methods, and classes are to contain docstrings. Object data
model methods (e.g. ``__repr__``) are typically the exception to this rule.
Thanks for helping to make the world a better place!
.. _PEP 8: https://pep8.org/
.. _line continuations: https://www.python.org/dev/peps/pep-0008/#indentation
Documentation Contributions
---------------------------

View File

@@ -9,17 +9,21 @@ Requests: HTTP for Humans™
Release v\ |version|. (:ref:`Installation <install>`)
.. image:: https://pepy.tech/badge/requests
.. image:: https://static.pepy.tech/badge/requests/month
:target: https://pepy.tech/project/requests
:alt: Requests Downloads Per Month Badge
.. image:: https://img.shields.io/pypi/l/requests.svg
:target: https://pypi.org/project/requests/
:alt: License Badge
.. image:: https://img.shields.io/pypi/wheel/requests.svg
:target: https://pypi.org/project/requests/
:alt: Wheel Support Badge
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
:target: https://pypi.org/project/requests/
:alt: Python Version Support Badge
**Requests** is an elegant and simple HTTP library for Python, built for human beings.
@@ -68,7 +72,7 @@ Requests is ready for today's web.
- Chunked Requests
- ``.netrc`` Support
Requests officially supports Python 2.7 & 3.5+, and runs great on PyPy.
Requests officially supports Python 3.8+, and runs great on PyPy.
The User Guide

3
docs/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
# Pinning to avoid unexpected breakages.
# Used by RTD to generate docs.
Sphinx==7.2.6

View File

@@ -233,7 +233,7 @@ or persistent::
s.verify = '/path/to/certfile'
.. note:: If ``verify`` is set to a path to a directory, the directory must have been processed using
the c_rehash utility supplied with OpenSSL.
the ``c_rehash`` utility supplied with OpenSSL.
This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable.
If ``REQUESTS_CA_BUNDLE`` is not set, ``CURL_CA_BUNDLE`` will be used as fallback.
@@ -291,7 +291,7 @@ versions of Requests.
For the sake of security we recommend upgrading certifi frequently!
.. _HTTP persistent connection: https://en.wikipedia.org/wiki/HTTP_persistent_connection
.. _connection pooling: https://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.connectionpool
.. _connection pooling: https://urllib3.readthedocs.io/en/latest/reference/urllib3.connectionpool.html
.. _certifi: https://certifiio.readthedocs.io/
.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt
@@ -305,7 +305,7 @@ immediately. You can override this behaviour and defer downloading the response
body until you access the :attr:`Response.content <requests.Response.content>`
attribute with the ``stream`` parameter::
tarball_url = 'https://github.com/psf/requests/tarball/master'
tarball_url = 'https://github.com/psf/requests/tarball/main'
r = requests.get(tarball_url, stream=True)
At this point only the response headers have been downloaded and the connection
@@ -446,7 +446,7 @@ argument.
def print_url(r, *args, **kwargs):
print(r.url)
If an error occurs while executing your callback, a warning is given.
Your callback function must handle its own exceptions. Any unhandled exception won't be passed silently and thus should be handled by the code calling Requests.
If the callback function returns a value, it is assumed that it is to
replace the data that was passed in. If the function doesn't return
@@ -598,20 +598,30 @@ Alternatively you can configure it once for an entire
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
session = request.Session()
session = requests.Session()
session.proxies.update(proxies)
session.get('http://example.org')
When the proxies configuration is not overridden in python as shown above,
by default Requests relies on the proxy configuration defined by standard
environment variables ``http_proxy``, ``https_proxy``, ``no_proxy`` and
``curl_ca_bundle``. Uppercase variants of these variables are also supported.
.. warning:: Setting ``session.proxies`` may behave differently than expected.
Values provided will be overwritten by environmental proxies
(those returned by `urllib.request.getproxies <https://docs.python.org/3/library/urllib.request.html#urllib.request.getproxies>`_).
To ensure the use of proxies in the presence of environmental proxies,
explicitly specify the ``proxies`` argument on all individual requests as
initially explained above.
See `#2018 <https://github.com/psf/requests/issues/2018>`_ for details.
When the proxies configuration is not overridden per request as shown above,
Requests relies on the proxy configuration defined by standard
environment variables ``http_proxy``, ``https_proxy``, ``no_proxy``,
and ``all_proxy``. Uppercase variants of these variables are also supported.
You can therefore set them to configure Requests (only set the ones relevant
to your needs)::
$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ export ALL_PROXY="socks5://10.10.1.10:3434"
$ python
>>> import requests
@@ -626,7 +636,7 @@ syntax in any of the above configuration entries::
>>> proxies = {'http': 'http://user:pass@10.10.1.10:3128/'}
.. warning:: Storing sensitive username and password information in an
environment variable or a version-controled file is a security risk and is
environment variable or a version-controlled file is a security risk and is
highly discouraged.
To give a proxy for a specific scheme and host, use the
@@ -646,16 +656,18 @@ certificates trusted by Requests can be found with::
from requests.utils import DEFAULT_CA_BUNDLE_PATH
print(DEFAULT_CA_BUNDLE_PATH)
You override this default certificate bundle by setting the standard
``curl_ca_bundle`` environment variable to another file path::
You override this default certificate bundle by setting the ``REQUESTS_CA_BUNDLE``
(or ``CURL_CA_BUNDLE``) environment variable to another file path::
$ export curl_ca_bundle="/usr/local/myproxy_info/cacert.pem"
$ export REQUESTS_CA_BUNDLE="/usr/local/myproxy_info/cacert.pem"
$ export https_proxy="http://10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get('https://example.org')
.. _socks:
SOCKS
^^^^^
@@ -697,10 +709,21 @@ Encodings
When you receive a response, Requests makes a guess at the encoding to
use for decoding the response when you access the :attr:`Response.text
<requests.Response.text>` attribute. Requests will first check for an
encoding in the HTTP header, and if none is present, will use `chardet
<https://pypi.org/project/chardet/>`_ to attempt to guess the encoding.
encoding in the HTTP header, and if none is present, will use
`charset_normalizer <https://pypi.org/project/charset_normalizer/>`_
or `chardet <https://github.com/chardet/chardet>`_ to attempt to
guess the encoding.
The only time Requests will not do this is if no explicit charset
If ``chardet`` is installed, ``requests`` uses it, however for python3
``chardet`` is no longer a mandatory dependency. The ``chardet``
library is an LGPL-licenced dependency and some users of requests
cannot depend on mandatory LGPL-licensed dependencies.
When you install ``requests`` without specifying ``[use_chardet_on_py3]`` extra,
and ``chardet`` is not already installed, ``requests`` uses ``charset-normalizer``
(MIT-licensed) to guess the encoding.
The only time Requests will not guess the encoding is if no explicit charset
is present in the HTTP headers **and** the ``Content-Type``
header contains ``text``. In this situation, `RFC 2616
<https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1>`_ specifies
@@ -925,7 +948,7 @@ Link Headers
Many HTTP APIs feature Link headers. They make APIs more self describing and
discoverable.
GitHub uses these for `pagination <https://developer.github.com/v3/#pagination>`_
GitHub uses these for `pagination <https://docs.github.com/en/rest/guides/using-pagination-in-the-rest-api>`_
in their API, for example::
>>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10'
@@ -973,6 +996,10 @@ The mount call registers a specific instance of a Transport Adapter to a
prefix. Once mounted, any HTTP request made using that session whose URL starts
with the given prefix will use the given Transport Adapter.
.. note:: The adapter will be chosen based on a longest prefix match. Be mindful
prefixes such as ``http://localhost`` will also match ``http://localhost.other.com``
or ``http://localhost@other.com``. It's recommended to terminate full hostnames with a ``/``.
Many of the details of implementing a Transport Adapter are beyond the scope of
this documentation, but take a look at the next example for a simple SSL use-
case. For more than that, you might look at subclassing the
@@ -1005,8 +1032,30 @@ library to use SSLv3::
num_pools=connections, maxsize=maxsize,
block=block, ssl_version=ssl.PROTOCOL_SSLv3)
.. _`described here`: https://www.kennethreitz.org/essays/the-future-of-python-http
.. _`urllib3`: https://github.com/shazow/urllib3
Example: Automatic Retries
^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, Requests does not retry failed connections. However, it is possible
to implement automatic retries with a powerful array of features, including
backoff, within a Requests :class:`Session <requests.Session>` using the
`urllib3.util.Retry`_ class::
from urllib3.util import Retry
from requests import Session
from requests.adapters import HTTPAdapter
s = Session()
retries = Retry(
total=3,
backoff_factor=0.1,
status_forcelist=[502, 503, 504],
allowed_methods={'POST'},
)
s.mount('https://', HTTPAdapter(max_retries=retries))
.. _`described here`: https://kenreitz.org/essays/2012/06/14/the-future-of-python-http
.. _`urllib3`: https://github.com/urllib3/urllib3
.. _`urllib3.util.Retry`: https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Retry
.. _blocking-or-nonblocking:
@@ -1025,7 +1074,7 @@ out there that combine Requests with one of Python's asynchronicity frameworks.
Some excellent examples are `requests-threads`_, `grequests`_, `requests-futures`_, and `httpx`_.
.. _`requests-threads`: https://github.com/requests/requests-threads
.. _`grequests`: https://github.com/kennethreitz/grequests
.. _`grequests`: https://github.com/spyoungtech/grequests
.. _`requests-futures`: https://github.com/ross/requests-futures
.. _`httpx`: https://github.com/encode/httpx
@@ -1034,7 +1083,7 @@ Header Ordering
In unusual circumstances you may want to provide headers in an ordered manner. If you pass an ``OrderedDict`` to the ``headers`` keyword argument, that will provide the headers with an ordering. *However*, the ordering of the default headers used by Requests will be preferred, which means that if you override default headers in the ``headers`` keyword argument, they may appear out of order compared to other headers in that keyword argument.
If this is problematic, users should consider setting the default headers on a :class:`Session <requests.Session>` object, by setting :attr:`Session <requests.Session.headers>` to a custom ``OrderedDict``. That ordering will always be preferred.
If this is problematic, users should consider setting the default headers on a :class:`Session <requests.Session>` object, by setting :attr:`Session.headers <requests.Session.headers>` to a custom ``OrderedDict``. That ordering will always be preferred.
.. _timeouts:
@@ -1050,7 +1099,7 @@ The **connect** timeout is the number of seconds Requests will wait for your
client to establish a connection to a remote machine (corresponding to the
`connect()`_) call on the socket. It's a good practice to set connect timeouts
to slightly larger than a multiple of 3, which is the default `TCP packet
retransmission window <https://www.hjp.at/doc/rfc/rfc2988.txt>`_.
retransmission window <https://datatracker.ietf.org/doc/html/rfc2988>`_.
Once your client has connected to the server and sent the HTTP request, the
**read** timeout is the number of seconds the client will wait for the server
@@ -1075,4 +1124,18 @@ coffee.
r = requests.get('https://github.com', timeout=None)
.. note:: The connect timeout applies to each connection attempt to an IP address.
If multiple addresses exist for a domain name, the underlying ``urllib3`` will
try each address sequentially until one successfully connects.
This may lead to an effective total connection timeout *multiple* times longer
than the specified time, e.g. an unresponsive server having both IPv4 and IPv6
addresses will have its perceived timeout *doubled*, so take that into account
when setting the connection timeout.
.. note:: Neither the connect nor read timeouts are `wall clock`_. This means
that if you start a request, and look at the time, and then look at
the time when the request finishes or times out, the real-world time
may be greater than what you specified.
.. _`wall clock`: https://wiki.php.net/rfc/max_execution_wall_time
.. _`connect()`: https://linux.die.net/man/2/connect

View File

@@ -19,13 +19,14 @@ the simplest kind, and Requests supports it straight out of the box.
Making requests with HTTP Basic Auth is very simple::
>>> from requests.auth import HTTPBasicAuth
>>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass'))
>>> basic = HTTPBasicAuth('user', 'pass')
>>> requests.get('https://httpbin.org/basic-auth/user/pass', auth=basic)
<Response [200]>
In fact, HTTP Basic Auth is so common that Requests provides a handy shorthand
for using it::
>>> requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> requests.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass'))
<Response [200]>
Providing the credentials in a tuple like this is exactly the same as the

View File

@@ -22,11 +22,11 @@ Requests is actively developed on GitHub, where the code is
You can either clone the public repository::
$ git clone git://github.com/psf/requests.git
$ git clone https://github.com/psf/requests.git
Or, download the `tarball <https://github.com/psf/requests/tarball/master>`_::
Or, download the `tarball <https://github.com/psf/requests/tarball/main>`_::
$ curl -OL https://github.com/psf/requests/tarball/master
$ curl -OL https://github.com/psf/requests/tarball/main
# optionally, zipball is also available (for Windows users).
Once you have a copy of the source, you can embed it in your own Python

View File

@@ -37,12 +37,12 @@ get all the information we need from this object.
Requests' simple API means that all forms of HTTP request are as obvious. For
example, this is how you make an HTTP POST request::
>>> r = requests.post('https://httpbin.org/post', data = {'key':'value'})
>>> r = requests.post('https://httpbin.org/post', data={'key': 'value'})
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
OPTIONS? These are all just as simple::
>>> r = requests.put('https://httpbin.org/put', data = {'key':'value'})
>>> r = requests.put('https://httpbin.org/put', data={'key': 'value'})
>>> r = requests.delete('https://httpbin.org/delete')
>>> r = requests.head('https://httpbin.org/get')
>>> r = requests.options('https://httpbin.org/get')
@@ -128,6 +128,9 @@ You can also access the response body as bytes, for non-text requests::
The ``gzip`` and ``deflate`` transfer-encodings are automatically decoded for you.
The ``br`` transfer-encoding is automatically decoded for you if a Brotli library
like `brotli <https://pypi.org/project/brotli>`_ or `brotlicffi <https://pypi.org/project/brotlicffi>`_ is installed.
For example, to create an image from binary data returned by a request, you can
use the following code::
@@ -150,7 +153,9 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data::
In case the JSON decoding fails, ``r.json()`` raises an exception. For example, if
the response gets a 204 (No Content), or if the response contains invalid JSON,
attempting ``r.json()`` raises ``ValueError: No JSON object could be decoded``.
attempting ``r.json()`` raises ``requests.exceptions.JSONDecodeError``. This wrapper exception
provides interoperability for multiple exceptions that may be thrown by different
python versions and json serialization libraries.
It should be noted that the success of the call to ``r.json()`` does **not**
indicate the success of the response. Some servers may return a JSON object in a
@@ -172,7 +177,7 @@ server, you can access ``r.raw``. If you want to do this, make sure you set
<urllib3.response.HTTPResponse object at 0x101194810>
>>> r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
In general, however, you should use a pattern like this to save what is being
streamed to a file::
@@ -196,6 +201,8 @@ may better fit your use cases.
were returned, use ``Response.raw``.
.. _custom-headers:
Custom Headers
--------------
@@ -232,7 +239,7 @@ dictionary of data will automatically be form-encoded when the request is made::
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("https://httpbin.org/post", data=payload)
>>> r = requests.post('https://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
@@ -278,8 +285,12 @@ For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
>>> r = requests.post(url, data=json.dumps(payload))
Instead of encoding the ``dict`` yourself, you can also pass it directly using
the ``json`` parameter (added in version 2.4.2) and it will be encoded automatically::
Please note that the above code will NOT add the ``Content-Type`` header
(so in particular it will NOT set it to ``application/json``).
If you need that header set and you don't want to encode the ``dict`` yourself,
you can also pass it directly using the ``json`` parameter (added in version 2.4.2)
and it will be encoded automatically:
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
@@ -288,8 +299,6 @@ the ``json`` parameter (added in version 2.4.2) and it will be encoded automatic
Note, the ``json`` parameter is ignored if either ``data`` or ``files`` is passed.
Using the ``json`` parameter in the request will change the ``Content-Type`` in the header to ``application/json``.
POST a Multipart-Encoded File
-----------------------------
@@ -559,6 +568,3 @@ All exceptions that Requests explicitly raises inherit from
-----------------------
Ready for more? Check out the :ref:`advanced <advanced>` section.
If you're on the job market, consider taking `this programming quiz <https://triplebyte.com/a/b1i2FB8/requests-docs-1>`_. A substantial donation will be made to this project, if you find a job through this platform.

10
pyproject.toml Normal file
View File

@@ -0,0 +1,10 @@
[tool.isort]
profile = "black"
src_paths = ["src/requests", "test"]
honor_noqa = true
[tool.pytest.ini_options]
addopts = "--doctest-modules"
doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS"
minversion = "6.2"
testpaths = ["tests"]

View File

@@ -1,3 +0,0 @@
[pytest]
addopts = -p no:warnings --doctest-modules
doctest_optionflags= NORMALIZE_WHITESPACE ELLIPSIS

View File

@@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
# __
# /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _)
# /
"""
Requests HTTP Library
~~~~~~~~~~~~~~~~~~~~~
Requests is an HTTP library, written in Python, for human beings.
Basic GET usage:
>>> import requests
>>> r = requests.get('https://www.python.org')
>>> r.status_code
200
>>> b'Python is a programming language' in r.content
True
... or POST:
>>> payload = dict(key1='value1', key2='value2')
>>> r = requests.post('https://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
"form": {
"key1": "value1",
"key2": "value2"
},
...
}
The other HTTP methods are supported - see `requests.api`. Full documentation
is at <https://requests.readthedocs.io>.
:copyright: (c) 2017 by Kenneth Reitz.
:license: Apache 2.0, see LICENSE for more details.
"""
import urllib3
import chardet
import warnings
from .exceptions import RequestsDependencyWarning
def check_compatibility(urllib3_version, chardet_version):
urllib3_version = urllib3_version.split('.')
assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git.
# Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2:
urllib3_version.append('0')
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1, <= 1.26
assert major == 1
assert minor >= 21
assert minor <= 26
# Check chardet for compatibility.
major, minor, patch = chardet_version.split('.')[:3]
major, minor, patch = int(major), int(minor), int(patch)
# chardet >= 3.0.2, < 3.1.0
assert major == 3
assert minor < 1
assert patch >= 2
def _check_cryptography(cryptography_version):
# cryptography < 1.3.4
try:
cryptography_version = list(map(int, cryptography_version.split('.')))
except ValueError:
return
if cryptography_version < [1, 3, 4]:
warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version)
warnings.warn(warning, RequestsDependencyWarning)
# Check imported dependencies for compatibility.
try:
check_compatibility(urllib3.__version__, chardet.__version__)
except (AssertionError, ValueError):
warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "
"version!".format(urllib3.__version__, chardet.__version__),
RequestsDependencyWarning)
# Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the
# 'ssl' library isn't available.
try:
try:
import ssl
except ImportError:
ssl = None
if not getattr(ssl, "HAS_SNI", False):
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
except ImportError:
pass
# urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning
warnings.simplefilter('ignore', DependencyWarning)
from .__version__ import __title__, __description__, __url__, __version__
from .__version__ import __build__, __author__, __author_email__, __license__
from .__version__ import __copyright__, __cake__
from . import utils
from . import packages
from .models import Request, Response, PreparedRequest
from .api import request, get, head, post, patch, put, delete, options
from .sessions import session, Session
from .status_codes import codes
from .exceptions import (
RequestException, Timeout, URLRequired,
TooManyRedirects, HTTPError, ConnectionError,
FileModeWarning, ConnectTimeout, ReadTimeout
)
# Set default logging handler to avoid "No handler found" warnings.
import logging
from logging import NullHandler
logging.getLogger(__name__).addHandler(NullHandler())
# FileModeWarnings go off per the default.
warnings.simplefilter('default', FileModeWarning, append=True)

View File

@@ -1,14 +0,0 @@
# .-. .-. .-. . . .-. .-. .-. .-.
# |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-'
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'https://requests.readthedocs.io'
__version__ = '2.25.0'
__build__ = 0x022500
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2020 Kenneth Reitz'
__cake__ = u'\u2728 \U0001f370 \u2728'

View File

@@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
"""
requests.compat
~~~~~~~~~~~~~~~
This module handles import compatibility issues between Python 2 and
Python 3.
"""
import chardet
import sys
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
#: Python 2.x?
is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)
try:
import simplejson as json
except ImportError:
import json
import urllib3
try:
SKIP_HEADER = urllib3.util.SKIP_HEADER
SKIPPABLE_HEADERS = urllib3.util.SKIPPABLE_HEADERS
except AttributeError:
SKIP_HEADER = None
SKIPPABLE_HEADERS = frozenset([])
# ---------
# Specifics
# ---------
if is_py2:
from urllib import (
quote, unquote, quote_plus, unquote_plus, urlencode, getproxies,
proxy_bypass, proxy_bypass_environment, getproxies_environment)
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag
from urllib2 import parse_http_list
import cookielib
from Cookie import Morsel
from StringIO import StringIO
# Keep OrderedDict for backwards compatibility.
from collections import Callable, Mapping, MutableMapping, OrderedDict
builtin_str = str
bytes = str
str = unicode
basestring = basestring
numeric_types = (int, long, float)
integer_types = (int, long)
elif is_py3:
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
# Keep OrderedDict for backwards compatibility.
from collections import OrderedDict
from collections.abc import Callable, Mapping, MutableMapping
builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)

View File

@@ -1,119 +0,0 @@
"""Module containing bug report helper(s)."""
from __future__ import print_function
import json
import platform
import sys
import ssl
import idna
import urllib3
import chardet
from . import __version__ as requests_version
try:
from urllib3.contrib import pyopenssl
except ImportError:
pyopenssl = None
OpenSSL = None
cryptography = None
else:
import OpenSSL
import cryptography
def _implementation():
"""Return a dict with the Python implementation and version.
Provide both the name and the version of the Python implementation
currently running. For example, on CPython 2.7.5 it will return
{'name': 'CPython', 'version': '2.7.5'}.
This function works best on CPython and PyPy: in particular, it probably
doesn't work for Jython or IronPython. Future investigation should be done
to work out the correct shape of the code for those platforms.
"""
implementation = platform.python_implementation()
if implementation == 'CPython':
implementation_version = platform.python_version()
elif implementation == 'PyPy':
implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
sys.pypy_version_info.minor,
sys.pypy_version_info.micro)
if sys.pypy_version_info.releaselevel != 'final':
implementation_version = ''.join([
implementation_version, sys.pypy_version_info.releaselevel
])
elif implementation == 'Jython':
implementation_version = platform.python_version() # Complete Guess
elif implementation == 'IronPython':
implementation_version = platform.python_version() # Complete Guess
else:
implementation_version = 'Unknown'
return {'name': implementation, 'version': implementation_version}
def info():
"""Generate information for a bug report."""
try:
platform_info = {
'system': platform.system(),
'release': platform.release(),
}
except IOError:
platform_info = {
'system': 'Unknown',
'release': 'Unknown',
}
implementation_info = _implementation()
urllib3_info = {'version': urllib3.__version__}
chardet_info = {'version': chardet.__version__}
pyopenssl_info = {
'version': None,
'openssl_version': '',
}
if OpenSSL:
pyopenssl_info = {
'version': OpenSSL.__version__,
'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER,
}
cryptography_info = {
'version': getattr(cryptography, '__version__', ''),
}
idna_info = {
'version': getattr(idna, '__version__', ''),
}
system_ssl = ssl.OPENSSL_VERSION_NUMBER
system_ssl_info = {
'version': '%x' % system_ssl if system_ssl is not None else ''
}
return {
'platform': platform_info,
'implementation': implementation_info,
'system_ssl': system_ssl_info,
'using_pyopenssl': pyopenssl is not None,
'pyOpenSSL': pyopenssl_info,
'urllib3': urllib3_info,
'chardet': chardet_info,
'cryptography': cryptography_info,
'idna': idna_info,
'requests': {
'version': requests_version,
},
}
def main():
"""Pretty-print the bug information as JSON."""
print(json.dumps(info(), sort_keys=True, indent=2))
if __name__ == '__main__':
main()

View File

@@ -1,14 +0,0 @@
import sys
# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)
for package in ('urllib3', 'idna', 'chardet'):
locals()[package] = __import__(package)
# This traversal is apparently necessary such that the identities are
# preserved (requests.packages.urllib3.* is urllib3.*)
for mod in list(sys.modules):
if mod == package or mod.startswith(package + '.'):
sys.modules['requests.packages.' + mod] = sys.modules[mod]
# Kinda cool, though, right?

View File

@@ -1,123 +0,0 @@
# -*- coding: utf-8 -*-
r"""
The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary
items.
Example::
>>> import requests
>>> requests.codes['temporary_redirect']
307
>>> requests.codes.teapot
418
>>> requests.codes['\o/']
200
Some codes have multiple names, and both upper- and lower-case versions of
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
``codes.okay`` all correspond to the HTTP status code 200.
"""
from .structures import LookupDict
_codes = {
# Informational.
100: ('continue',),
101: ('switching_protocols',),
102: ('processing',),
103: ('checkpoint',),
122: ('uri_too_long', 'request_uri_too_long'),
200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', ''),
201: ('created',),
202: ('accepted',),
203: ('non_authoritative_info', 'non_authoritative_information'),
204: ('no_content',),
205: ('reset_content', 'reset'),
206: ('partial_content', 'partial'),
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
208: ('already_reported',),
226: ('im_used',),
# Redirection.
300: ('multiple_choices',),
301: ('moved_permanently', 'moved', '\\o-'),
302: ('found',),
303: ('see_other', 'other'),
304: ('not_modified',),
305: ('use_proxy',),
306: ('switch_proxy',),
307: ('temporary_redirect', 'temporary_moved', 'temporary'),
308: ('permanent_redirect',
'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
# Client Error.
400: ('bad_request', 'bad'),
401: ('unauthorized',),
402: ('payment_required', 'payment'),
403: ('forbidden',),
404: ('not_found', '-o-'),
405: ('method_not_allowed', 'not_allowed'),
406: ('not_acceptable',),
407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
408: ('request_timeout', 'timeout'),
409: ('conflict',),
410: ('gone',),
411: ('length_required',),
412: ('precondition_failed', 'precondition'),
413: ('request_entity_too_large',),
414: ('request_uri_too_large',),
415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
417: ('expectation_failed',),
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
421: ('misdirected_request',),
422: ('unprocessable_entity', 'unprocessable'),
423: ('locked',),
424: ('failed_dependency', 'dependency'),
425: ('unordered_collection', 'unordered'),
426: ('upgrade_required', 'upgrade'),
428: ('precondition_required', 'precondition'),
429: ('too_many_requests', 'too_many'),
431: ('header_fields_too_large', 'fields_too_large'),
444: ('no_response', 'none'),
449: ('retry_with', 'retry'),
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
451: ('unavailable_for_legal_reasons', 'legal_reasons'),
499: ('client_closed_request',),
# Server Error.
500: ('internal_server_error', 'server_error', '/o\\', ''),
501: ('not_implemented',),
502: ('bad_gateway',),
503: ('service_unavailable', 'unavailable'),
504: ('gateway_timeout',),
505: ('http_version_not_supported', 'http_version'),
506: ('variant_also_negotiates',),
507: ('insufficient_storage',),
509: ('bandwidth_limit_exceeded', 'bandwidth'),
510: ('not_extended',),
511: ('network_authentication_required', 'network_auth', 'network_authentication'),
}
codes = LookupDict(name='status_codes')
def _init():
for code, titles in _codes.items():
for title in titles:
setattr(codes, title, code)
if not title.startswith(('\\', '/')):
setattr(codes, title.upper(), code)
def doc(code):
names = ', '.join('``%s``' % n for n in _codes[code])
return '* %d: %s' % (code, names)
global __doc__
__doc__ = (__doc__ + '\n' +
'\n'.join(doc(code) for code in sorted(_codes))
if __doc__ is not None else None)
_init()

View File

@@ -1,6 +1,7 @@
pytest>=2.8.0,<=3.10.1
-e .[socks]
pytest>=2.8.0,<9
pytest-cov
pytest-httpbin<1.0
pytest-mock==2.0.0
httpbin==0.7.0
pytest-httpbin==2.0.0
httpbin~=0.10.0
trustme
wheel

View File

@@ -1,5 +1,17 @@
[bdist_wheel]
universal = 1
[metadata]
license_file = LICENSE
provides-extra =
socks
use_chardet_on_py3
requires-dist =
certifi>=2017.4.17
charset_normalizer>=2,<4
idna>=2.5,<4
urllib3>=1.21.1,<3
[flake8]
ignore = E203, E501, W503
per-file-ignores =
src/requests/__init__.py:E402, F401
src/requests/compat.py:E402, F401
tests/compat.py:F401

136
setup.py
View File

@@ -1,26 +1,44 @@
#!/usr/bin/env python
# Learn more: https://github.com/kennethreitz/setup.py
import os
import re
import sys
from codecs import open
from setuptools import setup
from setuptools.command.test import test as TestCommand
here = os.path.abspath(os.path.dirname(__file__))
CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 8)
if CURRENT_PYTHON < REQUIRED_PYTHON:
sys.stderr.write(
"""
==========================
Unsupported Python version
==========================
This version of Requests requires at least Python {}.{}, but
you're trying to install it on Python {}.{}. To resolve this,
consider upgrading to a supported Python version.
If you can't upgrade your Python version, you'll need to
pin to an older version of Requests (<2.32.0).
""".format(
*(REQUIRED_PYTHON + CURRENT_PYTHON)
)
)
sys.exit(1)
class PyTest(TestCommand):
user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")]
user_options = [("pytest-args=", "a", "Arguments to pass into py.test")]
def initialize_options(self):
TestCommand.initialize_options(self)
try:
from multiprocessing import cpu_count
self.pytest_args = ['-n', str(cpu_count()), '--boxed']
self.pytest_args = ["-n", str(cpu_count()), "--boxed"]
except (ImportError, NotImplementedError):
self.pytest_args = ['-n', '1', '--boxed']
self.pytest_args = ["-n", "1", "--boxed"]
def finalize_options(self):
TestCommand.finalize_options(self)
@@ -33,80 +51,82 @@ class PyTest(TestCommand):
errno = pytest.main(self.pytest_args)
sys.exit(errno)
# 'setup.py publish' shortcut.
if sys.argv[-1] == 'publish':
os.system('python setup.py sdist bdist_wheel')
os.system('twine upload dist/*')
if sys.argv[-1] == "publish":
os.system("python setup.py sdist bdist_wheel")
os.system("twine upload dist/*")
sys.exit()
packages = ['requests']
requires = [
'chardet>=3.0.2,<4',
'idna>=2.5,<3',
'urllib3>=1.21.1,<1.27',
'certifi>=2017.4.17'
"charset_normalizer>=2,<4",
"idna>=2.5,<4",
"urllib3>=1.21.1,<3",
"certifi>=2017.4.17",
]
test_requirements = [
'pytest-httpbin==0.0.7',
'pytest-cov',
'pytest-mock',
'pytest-xdist',
'PySocks>=1.5.6, !=1.5.7',
'pytest>=3'
"pytest-httpbin==2.0.0",
"pytest-cov",
"pytest-mock",
"pytest-xdist",
"PySocks>=1.5.6, !=1.5.7",
"pytest>=3",
]
about = {}
with open(os.path.join(here, 'requests', '__version__.py'), 'r', 'utf-8') as f:
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, "src", "requests", "__version__.py"), "r", "utf-8") as f:
exec(f.read(), about)
with open('README.md', 'r', 'utf-8') as f:
with open("README.md", "r", "utf-8") as f:
readme = f.read()
setup(
name=about['__title__'],
version=about['__version__'],
description=about['__description__'],
name=about["__title__"],
version=about["__version__"],
description=about["__description__"],
long_description=readme,
long_description_content_type='text/markdown',
author=about['__author__'],
author_email=about['__author_email__'],
url=about['__url__'],
packages=packages,
package_data={'': ['LICENSE', 'NOTICE']},
package_dir={'requests': 'requests'},
long_description_content_type="text/markdown",
author=about["__author__"],
author_email=about["__author_email__"],
url=about["__url__"],
packages=["requests"],
package_data={"": ["LICENSE", "NOTICE"]},
package_dir={"": "src"},
include_package_data=True,
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
python_requires=">=3.8",
install_requires=requires,
license=about['__license__'],
license=about["__license__"],
zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy'
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries",
],
cmdclass={'test': PyTest},
cmdclass={"test": PyTest},
tests_require=test_requirements,
extras_require={
'security': ['pyOpenSSL >= 0.14', 'cryptography>=1.3.4'],
'socks': ['PySocks>=1.5.6, !=1.5.7'],
'socks:sys_platform == "win32" and python_version == "2.7"': ['win_inet_pton'],
"security": [],
"socks": ["PySocks>=1.5.6, !=1.5.7"],
"use_chardet_on_py3": ["chardet>=3.0.2,<6"],
},
project_urls={
'Documentation': 'https://requests.readthedocs.io',
'Source': 'https://github.com/psf/requests',
"Documentation": "https://requests.readthedocs.io",
"Source": "https://github.com/psf/requests",
},
)

184
src/requests/__init__.py Normal file
View File

@@ -0,0 +1,184 @@
# __
# /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _)
# /
"""
Requests HTTP Library
~~~~~~~~~~~~~~~~~~~~~
Requests is an HTTP library, written in Python, for human beings.
Basic GET usage:
>>> import requests
>>> r = requests.get('https://www.python.org')
>>> r.status_code
200
>>> b'Python is a programming language' in r.content
True
... or POST:
>>> payload = dict(key1='value1', key2='value2')
>>> r = requests.post('https://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
"form": {
"key1": "value1",
"key2": "value2"
},
...
}
The other HTTP methods are supported - see `requests.api`. Full documentation
is at <https://requests.readthedocs.io>.
:copyright: (c) 2017 by Kenneth Reitz.
:license: Apache 2.0, see LICENSE for more details.
"""
import warnings
import urllib3
from .exceptions import RequestsDependencyWarning
try:
from charset_normalizer import __version__ as charset_normalizer_version
except ImportError:
charset_normalizer_version = None
try:
from chardet import __version__ as chardet_version
except ImportError:
chardet_version = None
def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version):
urllib3_version = urllib3_version.split(".")
assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git.
# Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2:
urllib3_version.append("0")
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1
assert major >= 1
if major == 1:
assert minor >= 21
# Check charset_normalizer for compatibility.
if chardet_version:
major, minor, patch = chardet_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch)
# chardet_version >= 3.0.2, < 6.0.0
assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0)
elif charset_normalizer_version:
major, minor, patch = charset_normalizer_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch)
# charset_normalizer >= 2.0.0 < 4.0.0
assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0)
else:
warnings.warn(
"Unable to find acceptable character detection dependency "
"(chardet or charset_normalizer).",
RequestsDependencyWarning,
)
def _check_cryptography(cryptography_version):
# cryptography < 1.3.4
try:
cryptography_version = list(map(int, cryptography_version.split(".")))
except ValueError:
return
if cryptography_version < [1, 3, 4]:
warning = "Old version of cryptography ({}) may cause slowdown.".format(
cryptography_version
)
warnings.warn(warning, RequestsDependencyWarning)
# Check imported dependencies for compatibility.
try:
check_compatibility(
urllib3.__version__, chardet_version, charset_normalizer_version
)
except (AssertionError, ValueError):
warnings.warn(
"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
"version!".format(
urllib3.__version__, chardet_version, charset_normalizer_version
),
RequestsDependencyWarning,
)
# Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the
# 'ssl' library isn't available.
try:
try:
import ssl
except ImportError:
ssl = None
if not getattr(ssl, "HAS_SNI", False):
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
except ImportError:
pass
# urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning
warnings.simplefilter("ignore", DependencyWarning)
# Set default logging handler to avoid "No handler found" warnings.
import logging
from logging import NullHandler
from . import packages, utils
from .__version__ import (
__author__,
__author_email__,
__build__,
__cake__,
__copyright__,
__description__,
__license__,
__title__,
__url__,
__version__,
)
from .api import delete, get, head, options, patch, post, put, request
from .exceptions import (
ConnectionError,
ConnectTimeout,
FileModeWarning,
HTTPError,
JSONDecodeError,
ReadTimeout,
RequestException,
Timeout,
TooManyRedirects,
URLRequired,
)
from .models import PreparedRequest, Request, Response
from .sessions import Session, session
from .status_codes import codes
logging.getLogger(__name__).addHandler(NullHandler())
# FileModeWarnings go off per the default.
warnings.simplefilter("default", FileModeWarning, append=True)

View File

@@ -0,0 +1,14 @@
# .-. .-. .-. . . .-. .-. .-. .-.
# |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-'
__title__ = "requests"
__description__ = "Python HTTP for Humans."
__url__ = "https://requests.readthedocs.io"
__version__ = "2.32.1"
__build__ = 0x023201
__author__ = "Kenneth Reitz"
__author_email__ = "me@kennethreitz.org"
__license__ = "Apache-2.0"
__copyright__ = "Copyright Kenneth Reitz"
__cake__ = "\u2728 \U0001f370 \u2728"

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests._internal_utils
~~~~~~~~~~~~~~
@@ -7,11 +5,24 @@ requests._internal_utils
Provides utility functions that are consumed internally by Requests
which depend on extremely few external helpers (such as compat)
"""
import re
from .compat import is_py2, builtin_str, str
from .compat import builtin_str
_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*$")
_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$")
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$")
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$")
_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR)
_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE)
HEADER_VALIDATORS = {
bytes: _HEADER_VALIDATORS_BYTE,
str: _HEADER_VALIDATORS_STR,
}
def to_native_string(string, encoding='ascii'):
def to_native_string(string, encoding="ascii"):
"""Given a string object, regardless of type, returns a representation of
that string in the native string type, encoding and decoding where
necessary. This assumes ASCII unless told otherwise.
@@ -19,10 +30,7 @@ def to_native_string(string, encoding='ascii'):
if isinstance(string, builtin_str):
out = string
else:
if is_py2:
out = string.encode(encoding)
else:
out = string.decode(encoding)
out = string.decode(encoding)
return out
@@ -36,7 +44,7 @@ def unicode_is_ascii(u_string):
"""
assert isinstance(u_string, str)
try:
u_string.encode('ascii')
u_string.encode("ascii")
return True
except UnicodeEncodeError:
return False

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.adapters
~~~~~~~~~~~~~~~~~
@@ -9,57 +7,123 @@ and maintain connections.
"""
import os.path
import socket
import socket # noqa: F401
import typing
from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.response import HTTPResponse
from urllib3.util import parse_url
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util.retry import Retry
from urllib3.exceptions import ClosedPoolError
from urllib3.exceptions import ConnectTimeoutError
from urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
from urllib3.exceptions import HTTPError as _HTTPError
from urllib3.exceptions import MaxRetryError
from urllib3.exceptions import NewConnectionError
from urllib3.exceptions import InvalidHeader as _InvalidHeader
from urllib3.exceptions import (
LocationValueError,
MaxRetryError,
NewConnectionError,
ProtocolError,
)
from urllib3.exceptions import ProxyError as _ProxyError
from urllib3.exceptions import ProtocolError
from urllib3.exceptions import ReadTimeoutError
from urllib3.exceptions import ReadTimeoutError, ResponseError
from urllib3.exceptions import SSLError as _SSLError
from urllib3.exceptions import ResponseError
from urllib3.exceptions import LocationValueError
from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util import parse_url
from urllib3.util.retry import Retry
from urllib3.util.ssl_ import create_urllib3_context
from .models import Response
from .compat import urlparse, basestring
from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths,
get_encoding_from_headers, prepend_scheme_if_needed,
get_auth_from_url, urldefragauth, select_proxy)
from .structures import CaseInsensitiveDict
from .cookies import extract_cookies_to_jar
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
ProxyError, RetryError, InvalidSchema, InvalidProxyURL,
InvalidURL)
from .auth import _basic_auth_str
from .compat import basestring, urlparse
from .cookies import extract_cookies_to_jar
from .exceptions import (
ConnectionError,
ConnectTimeout,
InvalidHeader,
InvalidProxyURL,
InvalidSchema,
InvalidURL,
ProxyError,
ReadTimeout,
RetryError,
SSLError,
)
from .models import Response
from .structures import CaseInsensitiveDict
from .utils import (
DEFAULT_CA_BUNDLE_PATH,
extract_zipped_paths,
get_auth_from_url,
get_encoding_from_headers,
prepend_scheme_if_needed,
select_proxy,
urldefragauth,
)
try:
from urllib3.contrib.socks import SOCKSProxyManager
except ImportError:
def SOCKSProxyManager(*args, **kwargs):
raise InvalidSchema("Missing dependencies for SOCKS support.")
if typing.TYPE_CHECKING:
from .models import PreparedRequest
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None
_preloaded_ssl_context = create_urllib3_context()
_preloaded_ssl_context.load_verify_locations(
extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
)
class BaseAdapter(object):
def _urllib3_request_context(
request: "PreparedRequest",
verify: "bool | str | None",
client_cert: "typing.Tuple[str, str] | str | None",
) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])":
host_params = {}
pool_kwargs = {}
parsed_request_url = urlparse(request.url)
scheme = parsed_request_url.scheme.lower()
port = parsed_request_url.port
cert_reqs = "CERT_REQUIRED"
if verify is False:
cert_reqs = "CERT_NONE"
elif verify is True:
pool_kwargs["ssl_context"] = _preloaded_ssl_context
elif isinstance(verify, str):
if not os.path.isdir(verify):
pool_kwargs["ca_certs"] = verify
else:
pool_kwargs["ca_cert_dir"] = verify
pool_kwargs["cert_reqs"] = cert_reqs
if client_cert is not None:
if isinstance(client_cert, tuple) and len(client_cert) == 2:
pool_kwargs["cert_file"] = client_cert[0]
pool_kwargs["key_file"] = client_cert[1]
else:
# According to our docs, we allow users to specify just the client
# cert path
pool_kwargs["cert_file"] = client_cert
host_params = {
"scheme": scheme,
"host": parsed_request_url.hostname,
"port": port,
}
return host_params, pool_kwargs
class BaseAdapter:
"""The Base Transport Adapter"""
def __init__(self):
super(BaseAdapter, self).__init__()
super().__init__()
def send(self, request, stream=False, timeout=None, verify=True,
cert=None, proxies=None):
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -107,12 +171,22 @@ class HTTPAdapter(BaseAdapter):
>>> a = requests.adapters.HTTPAdapter(max_retries=3)
>>> s.mount('http://', a)
"""
__attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
'_pool_block']
def __init__(self, pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK):
__attrs__ = [
"max_retries",
"config",
"_pool_connections",
"_pool_maxsize",
"_pool_block",
]
def __init__(
self,
pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE,
max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK,
):
if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False)
else:
@@ -120,7 +194,7 @@ class HTTPAdapter(BaseAdapter):
self.config = {}
self.proxy_manager = {}
super(HTTPAdapter, self).__init__()
super().__init__()
self._pool_connections = pool_connections
self._pool_maxsize = pool_maxsize
@@ -140,10 +214,13 @@ class HTTPAdapter(BaseAdapter):
for attr, value in state.items():
setattr(self, attr, value)
self.init_poolmanager(self._pool_connections, self._pool_maxsize,
block=self._pool_block)
self.init_poolmanager(
self._pool_connections, self._pool_maxsize, block=self._pool_block
)
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
def init_poolmanager(
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
):
"""Initializes a urllib3 PoolManager.
This method should not be called from user code, and is only
@@ -160,8 +237,12 @@ class HTTPAdapter(BaseAdapter):
self._pool_maxsize = maxsize
self._pool_block = block
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
block=block, strict=True, **pool_kwargs)
self.poolmanager = PoolManager(
num_pools=connections,
maxsize=maxsize,
block=block,
**pool_kwargs,
)
def proxy_manager_for(self, proxy, **proxy_kwargs):
"""Return urllib3 ProxyManager for the given proxy.
@@ -177,7 +258,7 @@ class HTTPAdapter(BaseAdapter):
"""
if proxy in self.proxy_manager:
manager = self.proxy_manager[proxy]
elif proxy.lower().startswith('socks'):
elif proxy.lower().startswith("socks"):
username, password = get_auth_from_url(proxy)
manager = self.proxy_manager[proxy] = SOCKSProxyManager(
proxy,
@@ -186,7 +267,7 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
**proxy_kwargs
**proxy_kwargs,
)
else:
proxy_headers = self.proxy_headers(proxy)
@@ -196,7 +277,8 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
**proxy_kwargs)
**proxy_kwargs,
)
return manager
@@ -212,29 +294,29 @@ class HTTPAdapter(BaseAdapter):
to a CA bundle to use
:param cert: The SSL certificate to verify.
"""
if url.lower().startswith('https') and verify:
if url.lower().startswith("https") and verify:
conn.cert_reqs = "CERT_REQUIRED"
cert_loc = None
# Allow self-specified cert location.
# Only load the CA certificates if 'verify' is a string indicating the CA bundle to use.
# Otherwise, if verify is a boolean, we don't load anything since
# the connection will be using a context with the default certificates already loaded,
# and this avoids a call to the slow load_verify_locations()
if verify is not True:
# `verify` must be a str with a path then
cert_loc = verify
if not cert_loc:
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
if not os.path.exists(cert_loc):
raise OSError(
f"Could not find a suitable TLS CA certificate bundle, "
f"invalid path: {cert_loc}"
)
if not cert_loc or not os.path.exists(cert_loc):
raise IOError("Could not find a suitable TLS CA certificate bundle, "
"invalid path: {}".format(cert_loc))
conn.cert_reqs = 'CERT_REQUIRED'
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
else:
conn.cert_reqs = 'CERT_NONE'
conn.cert_reqs = "CERT_NONE"
conn.ca_certs = None
conn.ca_cert_dir = None
@@ -246,11 +328,14 @@ class HTTPAdapter(BaseAdapter):
conn.cert_file = cert
conn.key_file = None
if conn.cert_file and not os.path.exists(conn.cert_file):
raise IOError("Could not find the TLS certificate file, "
"invalid path: {}".format(conn.cert_file))
raise OSError(
f"Could not find the TLS certificate file, "
f"invalid path: {conn.cert_file}"
)
if conn.key_file and not os.path.exists(conn.key_file):
raise IOError("Could not find the TLS key file, "
"invalid path: {}".format(conn.key_file))
raise OSError(
f"Could not find the TLS key file, invalid path: {conn.key_file}"
)
def build_response(self, req, resp):
"""Builds a :class:`Response <requests.Response>` object from a urllib3
@@ -265,10 +350,10 @@ class HTTPAdapter(BaseAdapter):
response = Response()
# Fallback to None if there's no status_code, for whatever reason.
response.status_code = getattr(resp, 'status', None)
response.status_code = getattr(resp, "status", None)
# Make headers case-insensitive.
response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {}))
response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))
# Set encoding.
response.encoding = get_encoding_from_headers(response.headers)
@@ -276,7 +361,7 @@ class HTTPAdapter(BaseAdapter):
response.reason = response.raw.reason
if isinstance(req.url, bytes):
response.url = req.url.decode('utf-8')
response.url = req.url.decode("utf-8")
else:
response.url = req.url
@@ -289,6 +374,35 @@ class HTTPAdapter(BaseAdapter):
return response
def _get_connection(self, request, verify, proxies=None, cert=None):
# Replace the existing get_connection without breaking things and
# ensure that TLS settings are considered when we interact with
# urllib3 HTTP Pools
proxy = select_proxy(request.url, proxies)
try:
host_params, pool_kwargs = _urllib3_request_context(request, verify, cert)
except ValueError as e:
raise InvalidURL(e, request=request)
if proxy:
proxy = prepend_scheme_if_needed(proxy, "http")
proxy_url = parse_url(proxy)
if not proxy_url.host:
raise InvalidProxyURL(
"Please check proxy URL. It is malformed "
"and could be missing the host."
)
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_host(
**host_params, pool_kwargs=pool_kwargs
)
else:
# Only scheme should be lower case
conn = self.poolmanager.connection_from_host(
**host_params, pool_kwargs=pool_kwargs
)
return conn
def get_connection(self, url, proxies=None):
"""Returns a urllib3 connection for the given URL. This should not be
called from user code, and is only exposed for use when subclassing the
@@ -301,11 +415,13 @@ class HTTPAdapter(BaseAdapter):
proxy = select_proxy(url, proxies)
if proxy:
proxy = prepend_scheme_if_needed(proxy, 'http')
proxy = prepend_scheme_if_needed(proxy, "http")
proxy_url = parse_url(proxy)
if not proxy_url.host:
raise InvalidProxyURL("Please check proxy URL. It is malformed"
" and could be missing the host.")
raise InvalidProxyURL(
"Please check proxy URL. It is malformed "
"and could be missing the host."
)
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_url(url)
else:
@@ -343,13 +459,16 @@ class HTTPAdapter(BaseAdapter):
proxy = select_proxy(request.url, proxies)
scheme = urlparse(request.url).scheme
is_proxied_http_request = (proxy and scheme != 'https')
is_proxied_http_request = proxy and scheme != "https"
using_socks_proxy = False
if proxy:
proxy_scheme = urlparse(proxy).scheme.lower()
using_socks_proxy = proxy_scheme.startswith('socks')
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)
@@ -386,12 +505,13 @@ class HTTPAdapter(BaseAdapter):
username, password = get_auth_from_url(proxy)
if username:
headers['Proxy-Authorization'] = _basic_auth_str(username,
password)
headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return headers
def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -409,92 +529,53 @@ class HTTPAdapter(BaseAdapter):
"""
try:
conn = self.get_connection(request.url, proxies)
conn = self._get_connection(request, verify, proxies=proxies, cert=cert)
except LocationValueError as e:
raise InvalidURL(e, request=request)
self.cert_verify(conn, request.url, verify, cert)
url = self.request_url(request, proxies)
self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies)
self.add_headers(
request,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies,
)
chunked = not (request.body is None or 'Content-Length' in request.headers)
chunked = not (request.body is None or "Content-Length" in request.headers)
if isinstance(timeout, tuple):
try:
connect, read = timeout
timeout = TimeoutSauce(connect=connect, read=read)
except ValueError as e:
# this may raise a string formatting error.
err = ("Invalid timeout {}. Pass a (connect, read) "
"timeout tuple, or a single float to set "
"both timeouts to the same value".format(timeout))
raise ValueError(err)
except ValueError:
raise ValueError(
f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
f"or a single float to set both timeouts to the same value."
)
elif isinstance(timeout, TimeoutSauce):
pass
else:
timeout = TimeoutSauce(connect=timeout, read=timeout)
try:
if not chunked:
resp = conn.urlopen(
method=request.method,
url=url,
body=request.body,
headers=request.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout
)
resp = conn.urlopen(
method=request.method,
url=url,
body=request.body,
headers=request.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout,
chunked=chunked,
)
# Send the request.
else:
if hasattr(conn, 'proxy_pool'):
conn = conn.proxy_pool
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
try:
low_conn.putrequest(request.method,
url,
skip_accept_encoding=True)
for header, value in request.headers.items():
low_conn.putheader(header, value)
low_conn.endheaders()
for i in request.body:
low_conn.send(hex(len(i))[2:].encode('utf-8'))
low_conn.send(b'\r\n')
low_conn.send(i)
low_conn.send(b'\r\n')
low_conn.send(b'0\r\n\r\n')
# Receive the response from the server
try:
# For Python 2.7, use buffering of HTTP responses
r = low_conn.getresponse(buffering=True)
except TypeError:
# For compatibility with Python 3.3+
r = low_conn.getresponse()
resp = HTTPResponse.from_httplib(
r,
pool=conn,
connection=low_conn,
preload_content=False,
decode_content=False
)
except:
# If we hit any problems here, clean up the connection.
# Then, reraise so that we can handle the actual exception.
low_conn.close()
raise
except (ProtocolError, socket.error) as err:
except (ProtocolError, OSError) as err:
raise ConnectionError(err, request=request)
except MaxRetryError as e:
@@ -527,6 +608,8 @@ class HTTPAdapter(BaseAdapter):
raise SSLError(e, request=request)
elif isinstance(e, ReadTimeoutError):
raise ReadTimeout(e, request=request)
elif isinstance(e, _InvalidHeader):
raise InvalidHeader(e, request=request)
else:
raise

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.api
~~~~~~~~~~~~
@@ -27,7 +25,7 @@ def request(method, url, **kwargs):
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content_type'`` is a string
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
to add for the file.
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
@@ -72,8 +70,7 @@ def get(url, params=None, **kwargs):
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)
return request("get", url, params=params, **kwargs)
def options(url, **kwargs):
@@ -85,8 +82,7 @@ def options(url, **kwargs):
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('options', url, **kwargs)
return request("options", url, **kwargs)
def head(url, **kwargs):
@@ -100,8 +96,8 @@ def head(url, **kwargs):
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', False)
return request('head', url, **kwargs)
kwargs.setdefault("allow_redirects", False)
return request("head", url, **kwargs)
def post(url, data=None, json=None, **kwargs):
@@ -110,13 +106,13 @@ def post(url, data=None, json=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('post', url, data=data, json=json, **kwargs)
return request("post", url, data=data, json=json, **kwargs)
def put(url, data=None, **kwargs):
@@ -125,13 +121,13 @@ def put(url, data=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('put', url, data=data, **kwargs)
return request("put", url, data=data, **kwargs)
def patch(url, data=None, **kwargs):
@@ -140,13 +136,13 @@ def patch(url, data=None, **kwargs):
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('patch', url, data=data, **kwargs)
return request("patch", url, data=data, **kwargs)
def delete(url, **kwargs):
@@ -158,4 +154,4 @@ def delete(url, **kwargs):
:rtype: requests.Response
"""
return request('delete', url, **kwargs)
return request("delete", url, **kwargs)

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.auth
~~~~~~~~~~~~~
@@ -7,22 +5,21 @@ requests.auth
This module contains the authentication handlers for Requests.
"""
import hashlib
import os
import re
import time
import hashlib
import threading
import time
import warnings
from base64 import b64encode
from .compat import urlparse, str, basestring
from .cookies import extract_cookies_to_jar
from ._internal_utils import to_native_string
from .compat import basestring, str, urlparse
from .cookies import extract_cookies_to_jar
from .utils import parse_dict_header
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART = "multipart/form-data"
def _basic_auth_str(username, password):
@@ -57,23 +54,23 @@ def _basic_auth_str(username, password):
# -- End Removal --
if isinstance(username, str):
username = username.encode('latin1')
username = username.encode("latin1")
if isinstance(password, str):
password = password.encode('latin1')
password = password.encode("latin1")
authstr = 'Basic ' + to_native_string(
b64encode(b':'.join((username, password))).strip()
authstr = "Basic " + to_native_string(
b64encode(b":".join((username, password))).strip()
)
return authstr
class AuthBase(object):
class AuthBase:
"""Base class that all auth implementations derive from"""
def __call__(self, r):
raise NotImplementedError('Auth hooks must be callable.')
raise NotImplementedError("Auth hooks must be callable.")
class HTTPBasicAuth(AuthBase):
@@ -84,16 +81,18 @@ class HTTPBasicAuth(AuthBase):
self.password = password
def __eq__(self, other):
return all([
self.username == getattr(other, 'username', None),
self.password == getattr(other, 'password', None)
])
return all(
[
self.username == getattr(other, "username", None),
self.password == getattr(other, "password", None),
]
)
def __ne__(self, other):
return not self == other
def __call__(self, r):
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
return r
@@ -101,7 +100,7 @@ class HTTPProxyAuth(HTTPBasicAuth):
"""Attaches HTTP Proxy Authentication to a given Request object."""
def __call__(self, r):
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
return r
@@ -116,9 +115,9 @@ class HTTPDigestAuth(AuthBase):
def init_per_thread_state(self):
# Ensure state is initialized just once per-thread
if not hasattr(self._thread_local, 'init'):
if not hasattr(self._thread_local, "init"):
self._thread_local.init = True
self._thread_local.last_nonce = ''
self._thread_local.last_nonce = ""
self._thread_local.nonce_count = 0
self._thread_local.chal = {}
self._thread_local.pos = None
@@ -129,44 +128,52 @@ class HTTPDigestAuth(AuthBase):
:rtype: str
"""
realm = self._thread_local.chal['realm']
nonce = self._thread_local.chal['nonce']
qop = self._thread_local.chal.get('qop')
algorithm = self._thread_local.chal.get('algorithm')
opaque = self._thread_local.chal.get('opaque')
realm = self._thread_local.chal["realm"]
nonce = self._thread_local.chal["nonce"]
qop = self._thread_local.chal.get("qop")
algorithm = self._thread_local.chal.get("algorithm")
opaque = self._thread_local.chal.get("opaque")
hash_utf8 = None
if algorithm is None:
_algorithm = 'MD5'
_algorithm = "MD5"
else:
_algorithm = algorithm.upper()
# lambdas assume digest modules are imported at the top level
if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
if _algorithm == "MD5" or _algorithm == "MD5-SESS":
def md5_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
x = x.encode("utf-8")
return hashlib.md5(x).hexdigest()
hash_utf8 = md5_utf8
elif _algorithm == 'SHA':
elif _algorithm == "SHA":
def sha_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
x = x.encode("utf-8")
return hashlib.sha1(x).hexdigest()
hash_utf8 = sha_utf8
elif _algorithm == 'SHA-256':
elif _algorithm == "SHA-256":
def sha256_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
x = x.encode("utf-8")
return hashlib.sha256(x).hexdigest()
hash_utf8 = sha256_utf8
elif _algorithm == 'SHA-512':
elif _algorithm == "SHA-512":
def sha512_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
x = x.encode("utf-8")
return hashlib.sha512(x).hexdigest()
hash_utf8 = sha512_utf8
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731
if hash_utf8 is None:
return None
@@ -177,10 +184,10 @@ class HTTPDigestAuth(AuthBase):
#: path is request-uri defined in RFC 2616 which should not be empty
path = p_parsed.path or "/"
if p_parsed.query:
path += '?' + p_parsed.query
path += f"?{p_parsed.query}"
A1 = '%s:%s:%s' % (self.username, realm, self.password)
A2 = '%s:%s' % (method, path)
A1 = f"{self.username}:{realm}:{self.password}"
A2 = f"{method}:{path}"
HA1 = hash_utf8(A1)
HA2 = hash_utf8(A2)
@@ -189,22 +196,20 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.nonce_count += 1
else:
self._thread_local.nonce_count = 1
ncvalue = '%08x' % self._thread_local.nonce_count
s = str(self._thread_local.nonce_count).encode('utf-8')
s += nonce.encode('utf-8')
s += time.ctime().encode('utf-8')
ncvalue = f"{self._thread_local.nonce_count:08x}"
s = str(self._thread_local.nonce_count).encode("utf-8")
s += nonce.encode("utf-8")
s += time.ctime().encode("utf-8")
s += os.urandom(8)
cnonce = (hashlib.sha1(s).hexdigest()[:16])
if _algorithm == 'MD5-SESS':
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
cnonce = hashlib.sha1(s).hexdigest()[:16]
if _algorithm == "MD5-SESS":
HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")
if not qop:
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
elif qop == 'auth' or 'auth' in qop.split(','):
noncebit = "%s:%s:%s:%s:%s" % (
nonce, ncvalue, cnonce, 'auth', HA2
)
respdig = KD(HA1, f"{nonce}:{HA2}")
elif qop == "auth" or "auth" in qop.split(","):
noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}"
respdig = KD(HA1, noncebit)
else:
# XXX handle auth-int.
@@ -213,18 +218,20 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.last_nonce = nonce
# XXX should the partial digests be encoded too?
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
'response="%s"' % (self.username, realm, nonce, path, respdig)
base = (
f'username="{self.username}", realm="{realm}", nonce="{nonce}", '
f'uri="{path}", response="{respdig}"'
)
if opaque:
base += ', opaque="%s"' % opaque
base += f', opaque="{opaque}"'
if algorithm:
base += ', algorithm="%s"' % algorithm
base += f', algorithm="{algorithm}"'
if entdig:
base += ', digest="%s"' % entdig
base += f', digest="{entdig}"'
if qop:
base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
return 'Digest %s' % (base)
return f"Digest {base}"
def handle_redirect(self, r, **kwargs):
"""Reset num_401_calls counter on redirects."""
@@ -248,13 +255,12 @@ class HTTPDigestAuth(AuthBase):
# Rewind the file position indicator of the body to where
# it was to resend the request.
r.request.body.seek(self._thread_local.pos)
s_auth = r.headers.get('www-authenticate', '')
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
s_auth = r.headers.get("www-authenticate", "")
if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2:
self._thread_local.num_401_calls += 1
pat = re.compile(r'digest ', flags=re.IGNORECASE)
self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))
pat = re.compile(r"digest ", flags=re.IGNORECASE)
self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1))
# Consume content and release the original connection
# to allow our new request to reuse the same one.
@@ -264,8 +270,9 @@ class HTTPDigestAuth(AuthBase):
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
prep.prepare_cookies(prep._cookies)
prep.headers['Authorization'] = self.build_digest_header(
prep.method, prep.url)
prep.headers["Authorization"] = self.build_digest_header(
prep.method, prep.url
)
_r = r.connection.send(prep, **kwargs)
_r.history.append(r)
_r.request = prep
@@ -280,7 +287,7 @@ class HTTPDigestAuth(AuthBase):
self.init_per_thread_state()
# If we have a saved nonce, skip the 401
if self._thread_local.last_nonce:
r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
r.headers["Authorization"] = self.build_digest_header(r.method, r.url)
try:
self._thread_local.pos = r.body.tell()
except AttributeError:
@@ -289,17 +296,19 @@ class HTTPDigestAuth(AuthBase):
# file position of the previous body. Ensure it's set to
# None.
self._thread_local.pos = None
r.register_hook('response', self.handle_401)
r.register_hook('response', self.handle_redirect)
r.register_hook("response", self.handle_401)
r.register_hook("response", self.handle_redirect)
self._thread_local.num_401_calls = 1
return r
def __eq__(self, other):
return all([
self.username == getattr(other, 'username', None),
self.password == getattr(other, 'password', None)
])
return all(
[
self.username == getattr(other, "username", None),
self.password == getattr(other, "password", None),
]
)
def __ne__(self, other):
return not self == other

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
requests.certs
@@ -14,5 +13,5 @@ packaged CA bundle.
"""
from certifi import where
if __name__ == '__main__':
if __name__ == "__main__":
print(where())

94
src/requests/compat.py Normal file
View File

@@ -0,0 +1,94 @@
"""
requests.compat
~~~~~~~~~~~~~~~
This module previously handled import compatibility issues
between Python 2 and Python 3. It remains for backwards
compatibility until the next major version.
"""
import importlib
import sys
# -------------------
# Character Detection
# -------------------
def _resolve_char_detection():
"""Find supported character detection libraries."""
chardet = None
for lib in ("chardet", "charset_normalizer"):
if chardet is None:
try:
chardet = importlib.import_module(lib)
except ImportError:
pass
return chardet
chardet = _resolve_char_detection()
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
#: Python 2.x?
is_py2 = _ver[0] == 2
#: Python 3.x?
is_py3 = _ver[0] == 3
# json/simplejson module import resolution
has_simplejson = False
try:
import simplejson as json
has_simplejson = True
except ImportError:
import json
if has_simplejson:
from simplejson import JSONDecodeError
else:
from json import JSONDecodeError
# Keep OrderedDict for backwards compatibility.
from collections import OrderedDict
from collections.abc import Callable, Mapping, MutableMapping
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
# --------------
# Legacy Imports
# --------------
from urllib.parse import (
quote,
quote_plus,
unquote,
unquote_plus,
urldefrag,
urlencode,
urljoin,
urlparse,
urlsplit,
urlunparse,
)
from urllib.request import (
getproxies,
getproxies_environment,
parse_http_list,
proxy_bypass,
proxy_bypass_environment,
)
builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)

View File

@@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
"""
requests.cookies
~~~~~~~~~~~~~~~~
Compatibility code to be able to use `cookielib.CookieJar` with requests.
Compatibility code to be able to use `http.cookiejar.CookieJar` with requests.
requests.utils imports from here, so be careful with imports.
"""
import calendar
import copy
import time
import calendar
from ._internal_utils import to_native_string
from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping
from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse
try:
import threading
@@ -22,10 +20,10 @@ except ImportError:
import dummy_threading as threading
class MockRequest(object):
class MockRequest:
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
The code in `cookielib.CookieJar` expects this interface in order to correctly
The code in `http.cookiejar.CookieJar` expects this interface in order to correctly
manage cookie policies, i.e., determine whether a cookie can be set, given the
domains of the request and the cookie.
@@ -51,16 +49,22 @@ class MockRequest(object):
def get_full_url(self):
# Only return the response's URL if the user hadn't set the Host
# header
if not self._r.headers.get('Host'):
if not self._r.headers.get("Host"):
return self._r.url
# If they did set it, retrieve it and reconstruct the expected domain
host = to_native_string(self._r.headers['Host'], encoding='utf-8')
host = to_native_string(self._r.headers["Host"], encoding="utf-8")
parsed = urlparse(self._r.url)
# Reconstruct the URL as we expect it
return urlunparse([
parsed.scheme, host, parsed.path, parsed.params, parsed.query,
parsed.fragment
])
return urlunparse(
[
parsed.scheme,
host,
parsed.path,
parsed.params,
parsed.query,
parsed.fragment,
]
)
def is_unverifiable(self):
return True
@@ -72,8 +76,10 @@ class MockRequest(object):
return self._r.headers.get(name, self._new_headers.get(name, default))
def add_header(self, key, val):
"""cookielib has no legitimate use for this method; add it back if you find one."""
raise NotImplementedError("Cookie headers should be added with add_unredirected_header()")
"""cookiejar has no legitimate use for this method; add it back if you find one."""
raise NotImplementedError(
"Cookie headers should be added with add_unredirected_header()"
)
def add_unredirected_header(self, name, value):
self._new_headers[name] = value
@@ -94,15 +100,15 @@ class MockRequest(object):
return self.get_host()
class MockResponse(object):
class MockResponse:
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
...what? Basically, expose the parsed HTTP headers from the server response
the way `cookielib` expects to see them.
the way `http.cookiejar` expects to see them.
"""
def __init__(self, headers):
"""Make a MockResponse for `cookielib` to read.
"""Make a MockResponse for `cookiejar` to read.
:param headers: a httplib.HTTPMessage or analogous carrying the headers
"""
@@ -118,12 +124,11 @@ class MockResponse(object):
def extract_cookies_to_jar(jar, request, response):
"""Extract the cookies from the response into a CookieJar.
:param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
:param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar)
:param request: our own requests.Request object
:param response: urllib3.HTTPResponse object
"""
if not (hasattr(response, '_original_response') and
response._original_response):
if not (hasattr(response, "_original_response") and response._original_response):
return
# the _original_response field is the wrapped httplib.HTTPResponse object,
req = MockRequest(request)
@@ -140,7 +145,7 @@ def get_cookie_header(jar, request):
"""
r = MockRequest(request)
jar.add_cookie_header(r)
return r.get_new_headers().get('Cookie')
return r.get_new_headers().get("Cookie")
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
@@ -169,7 +174,7 @@ class CookieConflictError(RuntimeError):
class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
"""Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict
interface.
This is the CookieJar we create by default for requests and sessions that
@@ -205,7 +210,9 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
# support client code that unsets cookies by assignment of a None value:
if value is None:
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path'))
remove_cookie_by_name(
self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
)
return
if isinstance(value, Morsel):
@@ -305,16 +312,15 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
dictionary = {}
for cookie in iter(self):
if (
(domain is None or cookie.domain == domain) and
(path is None or cookie.path == path)
if (domain is None or cookie.domain == domain) and (
path is None or cookie.path == path
):
dictionary[cookie.name] = cookie.value
return dictionary
def __contains__(self, name):
try:
return super(RequestsCookieJar, self).__contains__(name)
return super().__contains__(name)
except CookieConflictError:
return True
@@ -335,15 +341,19 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
self.set(name, value)
def __delitem__(self, name):
"""Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
"""Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s
``remove_cookie_by_name()``.
"""
remove_cookie_by_name(self, name)
def set_cookie(self, cookie, *args, **kwargs):
if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'):
cookie.value = cookie.value.replace('\\"', '')
return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs)
if (
hasattr(cookie.value, "startswith")
and cookie.value.startswith('"')
and cookie.value.endswith('"')
):
cookie.value = cookie.value.replace('\\"', "")
return super().set_cookie(cookie, *args, **kwargs)
def update(self, other):
"""Updates this jar with cookies from another CookieJar or dict-like"""
@@ -351,7 +361,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
for cookie in other:
self.set_cookie(copy.copy(cookie))
else:
super(RequestsCookieJar, self).update(other)
super().update(other)
def _find(self, name, domain=None, path=None):
"""Requests uses this method internally to get cookie values.
@@ -371,7 +381,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
if path is None or cookie.path == path:
return cookie.value
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def _find_no_duplicates(self, name, domain=None, path=None):
"""Both ``__get_item__`` and ``get`` call this function: it's never
@@ -390,25 +400,29 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
if cookie.name == name:
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
if toReturn is not None: # if there are multiple cookies that meet passed in criteria
raise CookieConflictError('There are multiple cookies with name, %r' % (name))
toReturn = cookie.value # we will eventually return this as long as no cookie conflict
if toReturn is not None:
# if there are multiple cookies that meet passed in criteria
raise CookieConflictError(
f"There are multiple cookies with name, {name!r}"
)
# we will eventually return this as long as no cookie conflict
toReturn = cookie.value
if toReturn:
return toReturn
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def __getstate__(self):
"""Unlike a normal CookieJar, this class is pickleable."""
state = self.__dict__.copy()
# remove the unpickleable RLock object
state.pop('_cookies_lock')
state.pop("_cookies_lock")
return state
def __setstate__(self, state):
"""Unlike a normal CookieJar, this class is pickleable."""
self.__dict__.update(state)
if '_cookies_lock' not in self.__dict__:
if "_cookies_lock" not in self.__dict__:
self._cookies_lock = threading.RLock()
def copy(self):
@@ -427,7 +441,7 @@ def _copy_cookie_jar(jar):
if jar is None:
return None
if hasattr(jar, 'copy'):
if hasattr(jar, "copy"):
# We're dealing with an instance of RequestsCookieJar
return jar.copy()
# We're dealing with a generic CookieJar instance
@@ -445,31 +459,32 @@ def create_cookie(name, value, **kwargs):
and sent on every request (this is sometimes called a "supercookie").
"""
result = {
'version': 0,
'name': name,
'value': value,
'port': None,
'domain': '',
'path': '/',
'secure': False,
'expires': None,
'discard': True,
'comment': None,
'comment_url': None,
'rest': {'HttpOnly': None},
'rfc2109': False,
"version": 0,
"name": name,
"value": value,
"port": None,
"domain": "",
"path": "/",
"secure": False,
"expires": None,
"discard": True,
"comment": None,
"comment_url": None,
"rest": {"HttpOnly": None},
"rfc2109": False,
}
badargs = set(kwargs) - set(result)
if badargs:
err = 'create_cookie() got unexpected keyword arguments: %s'
raise TypeError(err % list(badargs))
raise TypeError(
f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
)
result.update(kwargs)
result['port_specified'] = bool(result['port'])
result['domain_specified'] = bool(result['domain'])
result['domain_initial_dot'] = result['domain'].startswith('.')
result['path_specified'] = bool(result['path'])
result["port_specified"] = bool(result["port"])
result["domain_specified"] = bool(result["domain"])
result["domain_initial_dot"] = result["domain"].startswith(".")
result["path_specified"] = bool(result["path"])
return cookielib.Cookie(**result)
@@ -478,30 +493,28 @@ def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
expires = None
if morsel['max-age']:
if morsel["max-age"]:
try:
expires = int(time.time() + int(morsel['max-age']))
expires = int(time.time() + int(morsel["max-age"]))
except ValueError:
raise TypeError('max-age: %s must be integer' % morsel['max-age'])
elif morsel['expires']:
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
expires = calendar.timegm(
time.strptime(morsel['expires'], time_template)
)
raise TypeError(f"max-age: {morsel['max-age']} must be integer")
elif morsel["expires"]:
time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
return create_cookie(
comment=morsel['comment'],
comment_url=bool(morsel['comment']),
comment=morsel["comment"],
comment_url=bool(morsel["comment"]),
discard=False,
domain=morsel['domain'],
domain=morsel["domain"],
expires=expires,
name=morsel.key,
path=morsel['path'],
path=morsel["path"],
port=None,
rest={'HttpOnly': morsel['httponly']},
rest={"HttpOnly": morsel["httponly"]},
rfc2109=False,
secure=bool(morsel['secure']),
secure=bool(morsel["secure"]),
value=morsel.value,
version=morsel['version'] or 0,
version=morsel["version"] or 0,
)
@@ -534,11 +547,10 @@ def merge_cookies(cookiejar, cookies):
:rtype: CookieJar
"""
if not isinstance(cookiejar, cookielib.CookieJar):
raise ValueError('You can only merge into CookieJar')
raise ValueError("You can only merge into CookieJar")
if isinstance(cookies, dict):
cookiejar = cookiejar_from_dict(
cookies, cookiejar=cookiejar, overwrite=False)
cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
elif isinstance(cookies, cookielib.CookieJar):
try:
cookiejar.update(cookies)

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.exceptions
~~~~~~~~~~~~~~~~~~~
@@ -8,6 +6,8 @@ This module contains the set of Requests' exceptions.
"""
from urllib3.exceptions import HTTPError as BaseHTTPError
from .compat import JSONDecodeError as CompatJSONDecodeError
class RequestException(IOError):
"""There was an ambiguous exception that occurred while handling your
@@ -16,13 +16,40 @@ class RequestException(IOError):
def __init__(self, *args, **kwargs):
"""Initialize RequestException with `request` and `response` objects."""
response = kwargs.pop('response', None)
response = kwargs.pop("response", None)
self.response = response
self.request = kwargs.pop('request', None)
if (response is not None and not self.request and
hasattr(response, 'request')):
self.request = kwargs.pop("request", None)
if response is not None and not self.request and hasattr(response, "request"):
self.request = self.response.request
super(RequestException, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
class InvalidJSONError(RequestException):
"""A JSON error occurred."""
class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
"""Couldn't decode the text into json"""
def __init__(self, *args, **kwargs):
"""
Construct the JSONDecodeError instance first with all
args. Then use it's args to construct the IOError so that
the json specific args aren't used as IOError specific args
and the error message from JSONDecodeError is preserved.
"""
CompatJSONDecodeError.__init__(self, *args)
InvalidJSONError.__init__(self, *self.args, **kwargs)
def __reduce__(self):
"""
The __reduce__ method called when pickling the object must
be the one from the JSONDecodeError (be it json/simplejson)
as it expects all the arguments for instantiation, not just
one like the IOError, and the MRO would by default call the
__reduce__ method from the IOError due to the inheritance order.
"""
return CompatJSONDecodeError.__reduce__(self)
class HTTPError(RequestException):
@@ -70,11 +97,11 @@ class TooManyRedirects(RequestException):
class MissingSchema(RequestException, ValueError):
"""The URL schema (e.g. http or https) is missing."""
"""The URL scheme (e.g. http or https) is missing."""
class InvalidSchema(RequestException, ValueError):
"""See defaults.py for valid schemas."""
"""The URL scheme provided is either invalid or unsupported."""
class InvalidURL(RequestException, ValueError):
@@ -108,6 +135,7 @@ class RetryError(RequestException):
class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body."""
# Warnings

134
src/requests/help.py Normal file
View File

@@ -0,0 +1,134 @@
"""Module containing bug report helper(s)."""
import json
import platform
import ssl
import sys
import idna
import urllib3
from . import __version__ as requests_version
try:
import charset_normalizer
except ImportError:
charset_normalizer = None
try:
import chardet
except ImportError:
chardet = None
try:
from urllib3.contrib import pyopenssl
except ImportError:
pyopenssl = None
OpenSSL = None
cryptography = None
else:
import cryptography
import OpenSSL
def _implementation():
"""Return a dict with the Python implementation and version.
Provide both the name and the version of the Python implementation
currently running. For example, on CPython 3.10.3 it will return
{'name': 'CPython', 'version': '3.10.3'}.
This function works best on CPython and PyPy: in particular, it probably
doesn't work for Jython or IronPython. Future investigation should be done
to work out the correct shape of the code for those platforms.
"""
implementation = platform.python_implementation()
if implementation == "CPython":
implementation_version = platform.python_version()
elif implementation == "PyPy":
implementation_version = "{}.{}.{}".format(
sys.pypy_version_info.major,
sys.pypy_version_info.minor,
sys.pypy_version_info.micro,
)
if sys.pypy_version_info.releaselevel != "final":
implementation_version = "".join(
[implementation_version, sys.pypy_version_info.releaselevel]
)
elif implementation == "Jython":
implementation_version = platform.python_version() # Complete Guess
elif implementation == "IronPython":
implementation_version = platform.python_version() # Complete Guess
else:
implementation_version = "Unknown"
return {"name": implementation, "version": implementation_version}
def info():
"""Generate information for a bug report."""
try:
platform_info = {
"system": platform.system(),
"release": platform.release(),
}
except OSError:
platform_info = {
"system": "Unknown",
"release": "Unknown",
}
implementation_info = _implementation()
urllib3_info = {"version": urllib3.__version__}
charset_normalizer_info = {"version": None}
chardet_info = {"version": None}
if charset_normalizer:
charset_normalizer_info = {"version": charset_normalizer.__version__}
if chardet:
chardet_info = {"version": chardet.__version__}
pyopenssl_info = {
"version": None,
"openssl_version": "",
}
if OpenSSL:
pyopenssl_info = {
"version": OpenSSL.__version__,
"openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}",
}
cryptography_info = {
"version": getattr(cryptography, "__version__", ""),
}
idna_info = {
"version": getattr(idna, "__version__", ""),
}
system_ssl = ssl.OPENSSL_VERSION_NUMBER
system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}
return {
"platform": platform_info,
"implementation": implementation_info,
"system_ssl": system_ssl_info,
"using_pyopenssl": pyopenssl is not None,
"using_charset_normalizer": chardet is None,
"pyOpenSSL": pyopenssl_info,
"urllib3": urllib3_info,
"chardet": chardet_info,
"charset_normalizer": charset_normalizer_info,
"cryptography": cryptography_info,
"idna": idna_info,
"requests": {
"version": requests_version,
},
}
def main():
"""Pretty-print the bug information as JSON."""
print(json.dumps(info(), sort_keys=True, indent=2))
if __name__ == "__main__":
main()

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.hooks
~~~~~~~~~~~~~~
@@ -11,12 +9,13 @@ Available hooks:
``response``:
The response generated from a Request.
"""
HOOKS = ['response']
HOOKS = ["response"]
def default_hooks():
return {event: [] for event in HOOKS}
# TODO: response is the only one
@@ -25,7 +24,7 @@ def dispatch_hook(key, hooks, hook_data, **kwargs):
hooks = hooks or {}
hooks = hooks.get(key)
if hooks:
if hasattr(hooks, '__call__'):
if hasattr(hooks, "__call__"):
hooks = [hooks]
for hook in hooks:
_hook_data = hook(hook_data, **kwargs)

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.models
~~~~~~~~~~~~~~~
@@ -8,59 +6,72 @@ This module contains the primary objects that power Requests.
"""
import datetime
import sys
# 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
import encodings.idna # noqa: F401
from io import UnsupportedOperation
import urllib3
from urllib3.exceptions import (
DecodeError,
LocationParseError,
ProtocolError,
ReadTimeoutError,
SSLError,
)
from urllib3.fields import RequestField
from urllib3.filepost import encode_multipart_formdata
from urllib3.util import parse_url
from urllib3.exceptions import (
DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
from io import UnsupportedOperation
from .hooks import default_hooks
from .structures import CaseInsensitiveDict
from .auth import HTTPBasicAuth
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
from .exceptions import (
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
ContentDecodingError, ConnectionError, StreamConsumedError)
from ._internal_utils import to_native_string, unicode_is_ascii
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
stream_decode_response_unicode, to_key_val_list, parse_header_links,
iter_slices, guess_json_utf, super_len, check_header_validity)
from .auth import HTTPBasicAuth
from .compat import (
SKIP_HEADER,
SKIPPABLE_HEADERS,
Callable,
JSONDecodeError,
Mapping,
cookielib,
urlunparse,
urlsplit,
urlencode,
str,
bytes,
is_py2,
chardet,
builtin_str,
basestring,
builtin_str,
chardet,
cookielib,
)
from .compat import json as complexjson
from .compat import urlencode, urlsplit, urlunparse
from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header
from .exceptions import (
ChunkedEncodingError,
ConnectionError,
ContentDecodingError,
HTTPError,
InvalidJSONError,
InvalidURL,
)
from .exceptions import JSONDecodeError as RequestsJSONDecodeError
from .exceptions import MissingSchema
from .exceptions import SSLError as RequestsSSLError
from .exceptions import StreamConsumedError
from .hooks import default_hooks
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import (
check_header_validity,
get_auth_from_url,
guess_filename,
guess_json_utf,
iter_slices,
parse_header_links,
requote_uri,
stream_decode_response_unicode,
super_len,
to_key_val_list,
)
#: The set of HTTP status codes that indicate an automatically
#: processable redirect.
REDIRECT_STATI = (
codes.moved, # 301
codes.found, # 302
codes.other, # 303
codes.moved, # 301
codes.found, # 302
codes.other, # 303
codes.temporary_redirect, # 307
codes.permanent_redirect, # 308
)
@@ -70,7 +81,7 @@ CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512
class RequestEncodingMixin(object):
class RequestEncodingMixin:
@property
def path_url(self):
"""Build the path URL to use."""
@@ -81,16 +92,16 @@ class RequestEncodingMixin(object):
path = p.path
if not path:
path = '/'
path = "/"
url.append(path)
query = p.query
if query:
url.append('?')
url.append("?")
url.append(query)
return ''.join(url)
return "".join(url)
@staticmethod
def _encode_params(data):
@@ -103,18 +114,21 @@ class RequestEncodingMixin(object):
if isinstance(data, (str, bytes)):
return data
elif hasattr(data, 'read'):
elif hasattr(data, "read"):
return data
elif hasattr(data, '__iter__'):
elif hasattr(data, "__iter__"):
result = []
for k, vs in to_key_val_list(data):
if isinstance(vs, basestring) or not hasattr(vs, '__iter__'):
if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
vs = [vs]
for v in vs:
if v is not None:
result.append(
(k.encode('utf-8') if isinstance(k, str) else k,
v.encode('utf-8') if isinstance(v, str) else v))
(
k.encode("utf-8") if isinstance(k, str) else k,
v.encode("utf-8") if isinstance(v, str) else v,
)
)
return urlencode(result, doseq=True)
else:
return data
@@ -129,7 +143,7 @@ class RequestEncodingMixin(object):
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
or 4-tuples (filename, fileobj, contentype, custom_headers).
"""
if (not files):
if not files:
raise ValueError("Files must be provided.")
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
@@ -139,7 +153,7 @@ class RequestEncodingMixin(object):
files = to_key_val_list(files or {})
for field, val in fields:
if isinstance(val, basestring) or not hasattr(val, '__iter__'):
if isinstance(val, basestring) or not hasattr(val, "__iter__"):
val = [val]
for v in val:
if v is not None:
@@ -148,10 +162,15 @@ class RequestEncodingMixin(object):
v = str(v)
new_fields.append(
(field.decode('utf-8') if isinstance(field, bytes) else field,
v.encode('utf-8') if isinstance(v, str) else v))
(
field.decode("utf-8")
if isinstance(field, bytes)
else field,
v.encode("utf-8") if isinstance(v, str) else v,
)
)
for (k, v) in files:
for k, v in files:
# support for explicit filename
ft = None
fh = None
@@ -168,7 +187,7 @@ class RequestEncodingMixin(object):
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
elif hasattr(fp, 'read'):
elif hasattr(fp, "read"):
fdata = fp.read()
elif fp is None:
continue
@@ -184,16 +203,16 @@ class RequestEncodingMixin(object):
return body, content_type
class RequestHooksMixin(object):
class RequestHooksMixin:
def register_hook(self, event, hook):
"""Properly register a hook."""
if event not in self.hooks:
raise ValueError('Unsupported event specified, with event name "%s"' % (event))
raise ValueError(f'Unsupported event specified, with event name "{event}"')
if isinstance(hook, Callable):
self.hooks[event].append(hook)
elif hasattr(hook, '__iter__'):
elif hasattr(hook, "__iter__"):
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
def deregister_hook(self, event, hook):
@@ -236,10 +255,19 @@ class Request(RequestHooksMixin):
<PreparedRequest [GET]>
"""
def __init__(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
def __init__(
self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
# Default empty dicts for dict params.
data = [] if data is None else data
files = [] if files is None else files
@@ -248,7 +276,7 @@ class Request(RequestHooksMixin):
hooks = {} if hooks is None else hooks
self.hooks = default_hooks()
for (k, v) in list(hooks.items()):
for k, v in list(hooks.items()):
self.register_hook(event=k, hook=v)
self.method = method
@@ -262,7 +290,7 @@ class Request(RequestHooksMixin):
self.cookies = cookies
def __repr__(self):
return '<Request [%s]>' % (self.method)
return f"<Request [{self.method}]>"
def prepare(self):
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
@@ -320,9 +348,19 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
#: integer denoting starting position of a readable file-like body.
self._body_position = None
def prepare(self,
method=None, url=None, headers=None, files=None, data=None,
params=None, auth=None, cookies=None, hooks=None, json=None):
def prepare(
self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
"""Prepares the entire request with the given parameters."""
self.prepare_method(method)
@@ -339,7 +377,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.prepare_hooks(hooks)
def __repr__(self):
return '<PreparedRequest [%s]>' % (self.method)
return f"<PreparedRequest [{self.method}]>"
def copy(self):
p = PreparedRequest()
@@ -363,7 +401,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
import idna
try:
host = idna.encode(host, uts46=True).decode('utf-8')
host = idna.encode(host, uts46=True).decode("utf-8")
except idna.IDNAError:
raise UnicodeError
return host
@@ -376,9 +414,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
#: on python 3.x.
#: https://github.com/psf/requests/pull/2238
if isinstance(url, bytes):
url = url.decode('utf8')
url = url.decode("utf8")
else:
url = unicode(url) if is_py2 else str(url)
url = str(url)
# Remove leading whitespaces from url
url = url.lstrip()
@@ -386,7 +424,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only.
if ':' in url and not url.lower().startswith('http'):
if ":" in url and not url.lower().startswith("http"):
self.url = url
return
@@ -397,13 +435,13 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
raise InvalidURL(*e.args)
if not scheme:
error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
error = error.format(to_native_string(url, 'utf8'))
raise MissingSchema(error)
raise MissingSchema(
f"Invalid URL {url!r}: No scheme supplied. "
f"Perhaps you meant https://{url}?"
)
if not host:
raise InvalidURL("Invalid URL %r: No host supplied" % url)
raise InvalidURL(f"Invalid URL {url!r}: No host supplied")
# In general, we want to try IDNA encoding the hostname if the string contains
# non-ASCII characters. This allows users to automatically get the correct IDNA
@@ -413,33 +451,21 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
try:
host = self._get_idna_encoded_host(host)
except UnicodeError:
raise InvalidURL('URL has an invalid label.')
elif host.startswith(u'*'):
raise InvalidURL('URL has an invalid label.')
raise InvalidURL("URL has an invalid label.")
elif host.startswith(("*", ".")):
raise InvalidURL("URL has an invalid label.")
# Carefully reconstruct the network location
netloc = auth or ''
netloc = auth or ""
if netloc:
netloc += '@'
netloc += "@"
netloc += host
if port:
netloc += ':' + str(port)
netloc += f":{port}"
# Bare domains aren't valid URLs.
if not path:
path = '/'
if is_py2:
if isinstance(scheme, str):
scheme = scheme.encode('utf-8')
if isinstance(netloc, str):
netloc = netloc.encode('utf-8')
if isinstance(path, str):
path = path.encode('utf-8')
if isinstance(query, str):
query = query.encode('utf-8')
if isinstance(fragment, str):
fragment = fragment.encode('utf-8')
path = "/"
if isinstance(params, (str, bytes)):
params = to_native_string(params)
@@ -447,7 +473,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
enc_params = self._encode_params(params)
if enc_params:
if query:
query = '%s&%s' % (query, enc_params)
query = f"{query}&{enc_params}"
else:
query = enc_params
@@ -460,14 +486,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.headers = CaseInsensitiveDict()
if headers:
for header in headers.items():
name, value = header
if value is None:
if name.lower() in SKIPPABLE_HEADERS:
value = SKIP_HEADER
else:
continue
# Raise exception on invalid header value.
check_header_validity(header)
name, value = header
self.headers[to_native_string(name)] = value
def prepare_body(self, data, files, json=None):
@@ -483,15 +504,22 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if not data and json is not None:
# urllib3 requires a bytes-like body. Python 2's json.dumps
# provides this natively, but Python 3 gives a Unicode string.
content_type = 'application/json'
body = complexjson.dumps(json)
if not isinstance(body, bytes):
body = body.encode('utf-8')
content_type = "application/json"
is_stream = all([
hasattr(data, '__iter__'),
not isinstance(data, (basestring, list, tuple, Mapping))
])
try:
body = complexjson.dumps(json, allow_nan=False)
except ValueError as ve:
raise InvalidJSONError(ve, request=self)
if not isinstance(body, bytes):
body = body.encode("utf-8")
is_stream = all(
[
hasattr(data, "__iter__"),
not isinstance(data, (basestring, list, tuple, Mapping)),
]
)
if is_stream:
try:
@@ -501,24 +529,26 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
body = data
if getattr(body, 'tell', None) is not None:
if getattr(body, "tell", None) is not None:
# Record the current file position before reading.
# This will allow us to rewind a file in the event
# of a redirect.
try:
self._body_position = body.tell()
except (IOError, OSError):
except OSError:
# This differentiates from None, allowing us to catch
# a failed `tell()` later when trying to rewind the body
self._body_position = object()
if files:
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
raise NotImplementedError(
"Streamed bodies and files are mutually exclusive."
)
if length:
self.headers['Content-Length'] = builtin_str(length)
self.headers["Content-Length"] = builtin_str(length)
else:
self.headers['Transfer-Encoding'] = 'chunked'
self.headers["Transfer-Encoding"] = "chunked"
else:
# Multi-part file uploads.
if files:
@@ -526,16 +556,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
else:
if data:
body = self._encode_params(data)
if isinstance(data, basestring) or hasattr(data, 'read'):
if isinstance(data, basestring) or hasattr(data, "read"):
content_type = None
else:
content_type = 'application/x-www-form-urlencoded'
content_type = "application/x-www-form-urlencoded"
self.prepare_content_length(body)
# Add content-type if it wasn't explicitly provided.
if content_type and ('content-type' not in self.headers):
self.headers['Content-Type'] = content_type
if content_type and ("content-type" not in self.headers):
self.headers["Content-Type"] = content_type
self.body = body
@@ -546,13 +576,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if length:
# If length exists, set it. Otherwise, we fallback
# to Transfer-Encoding: chunked.
self.headers['Content-Length'] = builtin_str(length)
elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None:
self.headers["Content-Length"] = builtin_str(length)
elif (
self.method not in ("GET", "HEAD")
and self.headers.get("Content-Length") is None
):
# Set Content-Length to 0 for methods that can have a body
# but don't provide one. (i.e. not GET or HEAD)
self.headers['Content-Length'] = '0'
self.headers["Content-Length"] = "0"
def prepare_auth(self, auth, url=''):
def prepare_auth(self, auth, url=""):
"""Prepares the given HTTP auth data."""
# If no Auth is explicitly provided, extract it from the URL first.
@@ -592,7 +625,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
cookie_header = get_cookie_header(self._cookies, self)
if cookie_header is not None:
self.headers['Cookie'] = cookie_header
self.headers["Cookie"] = cookie_header
def prepare_hooks(self, hooks):
"""Prepares the given hooks."""
@@ -604,14 +637,22 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.register_hook(event, hooks[event])
class Response(object):
class Response:
"""The :class:`Response <Response>` object, which contains a
server's response to an HTTP request.
"""
__attrs__ = [
'_content', 'status_code', 'headers', 'url', 'history',
'encoding', 'reason', 'cookies', 'elapsed', 'request'
"_content",
"status_code",
"headers",
"url",
"history",
"encoding",
"reason",
"cookies",
"elapsed",
"request",
]
def __init__(self):
@@ -680,11 +721,11 @@ class Response(object):
setattr(self, name, value)
# pickled objects do not have .raw
setattr(self, '_content_consumed', True)
setattr(self, 'raw', None)
setattr(self, "_content_consumed", True)
setattr(self, "raw", None)
def __repr__(self):
return '<Response [%s]>' % (self.status_code)
return f"<Response [{self.status_code}]>"
def __bool__(self):
"""Returns True if :attr:`status_code` is less than 400.
@@ -730,12 +771,15 @@ class Response(object):
"""True if this Response is a well-formed HTTP redirect that could have
been processed automatically (by :meth:`Session.resolve_redirects`).
"""
return ('location' in self.headers and self.status_code in REDIRECT_STATI)
return "location" in self.headers and self.status_code in REDIRECT_STATI
@property
def is_permanent_redirect(self):
"""True if this Response one of the permanent versions of redirect."""
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
return "location" in self.headers and self.status_code in (
codes.moved_permanently,
codes.permanent_redirect,
)
@property
def next(self):
@@ -744,8 +788,13 @@ class Response(object):
@property
def apparent_encoding(self):
"""The apparent encoding, provided by the chardet library."""
return chardet.detect(self.content)['encoding']
"""The apparent encoding, provided by the charset_normalizer or chardet libraries."""
if chardet is not None:
return chardet.detect(self.content)["encoding"]
else:
# If no character detection library is available, we'll fall back
# to a standard Python utf-8 str.
return "utf-8"
def iter_content(self, chunk_size=1, decode_unicode=False):
"""Iterates over the response data. When stream=True is set on the
@@ -766,16 +815,17 @@ class Response(object):
def generate():
# Special case for urllib3.
if hasattr(self.raw, 'stream'):
if hasattr(self.raw, "stream"):
try:
for chunk in self.raw.stream(chunk_size, decode_content=True):
yield chunk
yield from self.raw.stream(chunk_size, decode_content=True)
except ProtocolError as e:
raise ChunkedEncodingError(e)
except DecodeError as e:
raise ContentDecodingError(e)
except ReadTimeoutError as e:
raise ConnectionError(e)
except SSLError as e:
raise RequestsSSLError(e)
else:
# Standard file-like object.
while True:
@@ -789,7 +839,9 @@ class Response(object):
if self._content_consumed and isinstance(self._content, bool):
raise StreamConsumedError()
elif chunk_size is not None and not isinstance(chunk_size, int):
raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size))
raise TypeError(
f"chunk_size must be an int, it is instead a {type(chunk_size)}."
)
# simulate reading small chunks of the content
reused_chunks = iter_slices(self._content, chunk_size)
@@ -802,7 +854,9 @@ class Response(object):
return chunks
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None):
def iter_lines(
self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None
):
"""Iterates over the response data, one line at a time. When
stream=True is set on the request, this avoids reading the
content at once into memory for large responses.
@@ -812,8 +866,9 @@ class Response(object):
pending = None
for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode):
for chunk in self.iter_content(
chunk_size=chunk_size, decode_unicode=decode_unicode
):
if pending is not None:
chunk = pending + chunk
@@ -827,8 +882,7 @@ class Response(object):
else:
pending = None
for line in lines:
yield line
yield from lines
if pending is not None:
yield pending
@@ -840,13 +894,12 @@ class Response(object):
if self._content is False:
# Read the contents.
if self._content_consumed:
raise RuntimeError(
'The content for this response was already consumed')
raise RuntimeError("The content for this response was already consumed")
if self.status_code == 0 or self.raw is None:
self._content = None
else:
self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3
@@ -858,7 +911,7 @@ class Response(object):
"""Content of the response, in unicode.
If Response.encoding is None, encoding will be guessed using
``chardet``.
``charset_normalizer`` or ``chardet``.
The encoding of the response content is determined based solely on HTTP
headers, following RFC 2616 to the letter. If you can take advantage of
@@ -871,7 +924,7 @@ class Response(object):
encoding = self.encoding
if not self.content:
return str('')
return ""
# Fallback to auto-detected encoding.
if self.encoding is None:
@@ -879,7 +932,7 @@ class Response(object):
# Decode unicode from given encoding.
try:
content = str(self.content, encoding, errors='replace')
content = str(self.content, encoding, errors="replace")
except (LookupError, TypeError):
# A LookupError is raised if the encoding was not found which could
# indicate a misspelling or similar mistake.
@@ -887,7 +940,7 @@ class Response(object):
# A TypeError can be raised if encoding is None
#
# So we try blindly encoding.
content = str(self.content, errors='replace')
content = str(self.content, errors="replace")
return content
@@ -895,67 +948,77 @@ class Response(object):
r"""Returns the json-encoded content of a response, if any.
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises ValueError: If the response body does not contain valid json.
:raises requests.exceptions.JSONDecodeError: If the response body does not
contain valid json.
"""
if not self.encoding and self.content and len(self.content) > 3:
# No encoding set. JSON RFC 4627 section 3 states we should expect
# UTF-8, -16 or -32. Detect which one to use; If the detection or
# decoding fails, fall back to `self.text` (using chardet to make
# decoding fails, fall back to `self.text` (using charset_normalizer to make
# a best guess).
encoding = guess_json_utf(self.content)
if encoding is not None:
try:
return complexjson.loads(
self.content.decode(encoding), **kwargs
)
return complexjson.loads(self.content.decode(encoding), **kwargs)
except UnicodeDecodeError:
# Wrong UTF codec detected; usually because it's not UTF-8
# but some other 8-bit codec. This is an RFC violation,
# and the server didn't bother to tell us what codec *was*
# used.
pass
return complexjson.loads(self.text, **kwargs)
except JSONDecodeError as e:
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
try:
return complexjson.loads(self.text, **kwargs)
except JSONDecodeError as e:
# Catch JSON-related errors and raise as requests.JSONDecodeError
# This aliases json.JSONDecodeError and simplejson.JSONDecodeError
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
@property
def links(self):
"""Returns the parsed header links of the response, if any."""
header = self.headers.get('link')
header = self.headers.get("link")
# l = MultiDict()
l = {}
resolved_links = {}
if header:
links = parse_header_links(header)
for link in links:
key = link.get('rel') or link.get('url')
l[key] = link
key = link.get("rel") or link.get("url")
resolved_links[key] = link
return l
return resolved_links
def raise_for_status(self):
"""Raises :class:`HTTPError`, if one occurred."""
http_error_msg = ''
http_error_msg = ""
if isinstance(self.reason, bytes):
# We attempt to decode utf-8 first because some servers
# choose to localize their reason strings. If the string
# isn't utf-8, we fall back to iso-8859-1 for all other
# encodings. (See PR #3538)
try:
reason = self.reason.decode('utf-8')
reason = self.reason.decode("utf-8")
except UnicodeDecodeError:
reason = self.reason.decode('iso-8859-1')
reason = self.reason.decode("iso-8859-1")
else:
reason = self.reason
if 400 <= self.status_code < 500:
http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url)
http_error_msg = (
f"{self.status_code} Client Error: {reason} for url: {self.url}"
)
elif 500 <= self.status_code < 600:
http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url)
http_error_msg = (
f"{self.status_code} Server Error: {reason} for url: {self.url}"
)
if http_error_msg:
raise HTTPError(http_error_msg, response=self)
@@ -969,6 +1032,6 @@ class Response(object):
if not self._content_consumed:
self.raw.close()
release_conn = getattr(self.raw, 'release_conn', None)
release_conn = getattr(self.raw, "release_conn", None)
if release_conn is not None:
release_conn()

23
src/requests/packages.py Normal file
View File

@@ -0,0 +1,23 @@
import sys
from .compat import chardet
# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)
for package in ("urllib3", "idna"):
locals()[package] = __import__(package)
# This traversal is apparently necessary such that the identities are
# preserved (requests.packages.urllib3.* is urllib3.*)
for mod in list(sys.modules):
if mod == package or mod.startswith(f"{package}."):
sys.modules[f"requests.packages.{mod}"] = sys.modules[mod]
if chardet is not None:
target = chardet.__name__
for mod in list(sys.modules):
if mod == target or mod.startswith(f"{target}."):
imported_mod = sys.modules[mod]
sys.modules[f"requests.packages.{mod}"] = imported_mod
mod = mod.replace(target, "chardet")
sys.modules[f"requests.packages.{mod}"] = imported_mod

View File

@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""
requests.session
~~~~~~~~~~~~~~~~
requests.sessions
~~~~~~~~~~~~~~~~~
This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
@@ -10,46 +8,57 @@ requests (cookies, auth, proxies).
import os
import sys
import time
from datetime import timedelta
from collections import OrderedDict
from datetime import timedelta
from .auth import _basic_auth_str
from .compat import cookielib, is_py3, urljoin, urlparse, Mapping
from .cookies import (
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
from .hooks import default_hooks, dispatch_hook
from ._internal_utils import to_native_string
from .utils import to_key_val_list, default_headers, DEFAULT_PORTS
from .exceptions import (
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
from .structures import CaseInsensitiveDict
from .adapters import HTTPAdapter
from .utils import (
requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
get_auth_from_url, rewind_body
from .auth import _basic_auth_str
from .compat import Mapping, cookielib, urljoin, urlparse
from .cookies import (
RequestsCookieJar,
cookiejar_from_dict,
extract_cookies_to_jar,
merge_cookies,
)
from .status_codes import codes
from .exceptions import (
ChunkedEncodingError,
ContentDecodingError,
InvalidSchema,
TooManyRedirects,
)
from .hooks import default_hooks, dispatch_hook
# formerly defined here, reexposed here for backward compatibility
from .models import REDIRECT_STATI
from .models import ( # noqa: F401
DEFAULT_REDIRECT_LIMIT,
REDIRECT_STATI,
PreparedRequest,
Request,
)
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import ( # noqa: F401
DEFAULT_PORTS,
default_headers,
get_auth_from_url,
get_environ_proxies,
get_netrc_auth,
requote_uri,
resolve_proxies,
rewind_body,
should_bypass_proxies,
to_key_val_list,
)
# Preferred clock, based on which one is more accurate on a given system.
if sys.platform == 'win32':
try: # Python 3.4+
preferred_clock = time.perf_counter
except AttributeError: # Earlier than Python 3.
preferred_clock = time.clock
if sys.platform == "win32":
preferred_clock = time.perf_counter
else:
preferred_clock = time.time
def merge_setting(
request_setting, session_setting, dict_class=OrderedDict, delete_none=True
):
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
"""Determines appropriate setting for a given request, taking into account
the explicit setting on that request, and the setting in the session. If a
setting is a dictionary, they will be merged together using `dict_class`
@@ -63,20 +72,18 @@ def merge_setting(
# Bypass if not a dictionary (e.g. verify)
if not (
isinstance(session_setting, Mapping) and
isinstance(request_setting, Mapping)
isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
):
return request_setting
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
if delete_none:
# Remove keys that are set to None. Extract keys first to avoid altering
# the dictionary during iteration.
none_keys = [k for (k, v) in merged_setting.items() if v is None]
for key in none_keys:
del merged_setting[key]
# Remove keys that are set to None. Extract keys first to avoid altering
# the dictionary during iteration.
none_keys = [k for (k, v) in merged_setting.items() if v is None]
for key in none_keys:
del merged_setting[key]
return merged_setting
@@ -87,17 +94,16 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
This is necessary because when request_hooks == {'response': []}, the
merge breaks Session hooks entirely.
"""
if session_hooks is None or session_hooks.get('response') == []:
if session_hooks is None or session_hooks.get("response") == []:
return request_hooks
if request_hooks is None or request_hooks.get('response') == []:
if request_hooks is None or request_hooks.get("response") == []:
return session_hooks
return merge_setting(request_hooks, session_hooks, dict_class)
class SessionRedirectMixin(object):
class SessionRedirectMixin:
def get_redirect_target(self, resp):
"""Receives a Response. Returns a redirect URI or ``None``"""
# Due to the nature of how requests processes redirects this method will
@@ -107,16 +113,15 @@ class SessionRedirectMixin(object):
# to cache the redirect location onto the response object as a private
# attribute.
if resp.is_redirect:
location = resp.headers['location']
location = resp.headers["location"]
# Currently the underlying http module on py3 decode headers
# in latin1, but empirical evidence suggests that latin1 is very
# rarely used with non-ASCII characters in HTTP headers.
# It is more likely to get UTF8 header rather than latin1.
# This causes incorrect handling of UTF8 encoded location headers.
# To solve this, we re-encode the location in latin1.
if is_py3:
location = location.encode('latin1')
return to_native_string(location, 'utf8')
location = location.encode("latin1")
return to_native_string(location, "utf8")
return None
def should_strip_auth(self, old_url, new_url):
@@ -129,23 +134,40 @@ class SessionRedirectMixin(object):
# ports. This isn't specified by RFC 7235, but is kept to avoid
# breaking backwards compatibility with older versions of requests
# that allowed any redirects on the same host.
if (old_parsed.scheme == 'http' and old_parsed.port in (80, None)
and new_parsed.scheme == 'https' and new_parsed.port in (443, None)):
if (
old_parsed.scheme == "http"
and old_parsed.port in (80, None)
and new_parsed.scheme == "https"
and new_parsed.port in (443, None)
):
return False
# Handle default port usage corresponding to scheme.
changed_port = old_parsed.port != new_parsed.port
changed_scheme = old_parsed.scheme != new_parsed.scheme
default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
if (not changed_scheme and old_parsed.port in default_port
and new_parsed.port in default_port):
if (
not changed_scheme
and old_parsed.port in default_port
and new_parsed.port in default_port
):
return False
# Standard case: root URI must match
return changed_port or changed_scheme
def resolve_redirects(self, resp, req, stream=False, timeout=None,
verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs):
def resolve_redirects(
self,
resp,
req,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
yield_requests=False,
**adapter_kwargs,
):
"""Receives a Response. Returns a generator of Responses or Requests."""
hist = [] # keep track of history
@@ -166,19 +188,21 @@ class SessionRedirectMixin(object):
resp.raw.read(decode_content=False)
if len(resp.history) >= self.max_redirects:
raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp)
raise TooManyRedirects(
f"Exceeded {self.max_redirects} redirects.", response=resp
)
# Release the connection back into the pool.
resp.close()
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith('//'):
if url.startswith("//"):
parsed_rurl = urlparse(resp.url)
url = ':'.join([to_native_string(parsed_rurl.scheme), url])
url = ":".join([to_native_string(parsed_rurl.scheme), url])
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
parsed = urlparse(url)
if parsed.fragment == '' and previous_fragment:
if parsed.fragment == "" and previous_fragment:
parsed = parsed._replace(fragment=previous_fragment)
elif parsed.fragment:
previous_fragment = parsed.fragment
@@ -197,15 +221,18 @@ class SessionRedirectMixin(object):
self.rebuild_method(prepared_request, resp)
# https://github.com/psf/requests/issues/1084
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
if resp.status_code not in (
codes.temporary_redirect,
codes.permanent_redirect,
):
# https://github.com/psf/requests/issues/3490
purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding')
purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
for header in purged_headers:
prepared_request.headers.pop(header, None)
prepared_request.body = None
headers = prepared_request.headers
headers.pop('Cookie', None)
headers.pop("Cookie", None)
# Extract any cookies sent on the response to the cookiejar
# in the new request. Because we've mutated our copied prepared
@@ -221,9 +248,8 @@ class SessionRedirectMixin(object):
# 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
('Content-Length' in headers or 'Transfer-Encoding' in headers)
rewindable = prepared_request._body_position is not None and (
"Content-Length" in headers or "Transfer-Encoding" in headers
)
# Attempt to rewind consumed file-like object.
@@ -236,7 +262,6 @@ class SessionRedirectMixin(object):
if yield_requests:
yield req
else:
resp = self.send(
req,
stream=stream,
@@ -245,7 +270,7 @@ class SessionRedirectMixin(object):
cert=cert,
proxies=proxies,
allow_redirects=False,
**adapter_kwargs
**adapter_kwargs,
)
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
@@ -262,17 +287,18 @@ class SessionRedirectMixin(object):
headers = prepared_request.headers
url = prepared_request.url
if 'Authorization' in headers and self.should_strip_auth(response.request.url, url):
if "Authorization" in headers and self.should_strip_auth(
response.request.url, url
):
# If we get redirected to a new host, we should strip out any
# authentication headers.
del headers['Authorization']
del headers["Authorization"]
# .netrc might have more auth for us on our new host.
new_auth = get_netrc_auth(url) if self.trust_env else None
if new_auth is not None:
prepared_request.prepare_auth(new_auth)
def rebuild_proxies(self, prepared_request, proxies):
"""This method re-evaluates the proxy configuration by considering the
environment variables. If we are redirected to a URL covered by
@@ -285,32 +311,22 @@ class SessionRedirectMixin(object):
:rtype: dict
"""
proxies = proxies if proxies is not None else {}
headers = prepared_request.headers
url = prepared_request.url
scheme = urlparse(url).scheme
new_proxies = proxies.copy()
no_proxy = proxies.get('no_proxy')
scheme = urlparse(prepared_request.url).scheme
new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
if self.trust_env and not bypass_proxy:
environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
if proxy:
new_proxies.setdefault(scheme, proxy)
if 'Proxy-Authorization' in headers:
del headers['Proxy-Authorization']
if "Proxy-Authorization" in headers:
del headers["Proxy-Authorization"]
try:
username, password = get_auth_from_url(new_proxies[scheme])
except KeyError:
username, password = None, None
if username and password:
headers['Proxy-Authorization'] = _basic_auth_str(username, password)
# urllib3 handles proxy authorization for us in the standard adapter.
# Avoid appending this to TLS tunneled requests where it may be leaked.
if not scheme.startswith("https") and username and password:
headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return new_proxies
@@ -321,18 +337,18 @@ class SessionRedirectMixin(object):
method = prepared_request.method
# https://tools.ietf.org/html/rfc7231#section-6.4.4
if response.status_code == codes.see_other and method != 'HEAD':
method = 'GET'
if response.status_code == codes.see_other and method != "HEAD":
method = "GET"
# Do what the browsers do, despite standards...
# First, turn 302s into GETs.
if response.status_code == codes.found and method != 'HEAD':
method = 'GET'
if response.status_code == codes.found and method != "HEAD":
method = "GET"
# Second, if a POST is responded to with a 301, turn it into a GET.
# This bizarre behaviour is explained in Issue 1704.
if response.status_code == codes.moved and method == 'POST':
method = 'GET'
if response.status_code == codes.moved and method == "POST":
method = "GET"
prepared_request.method = method
@@ -357,13 +373,21 @@ class Session(SessionRedirectMixin):
"""
__attrs__ = [
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
'cert', 'adapters', 'stream', 'trust_env',
'max_redirects',
"headers",
"cookies",
"auth",
"proxies",
"hooks",
"params",
"verify",
"cert",
"adapters",
"stream",
"trust_env",
"max_redirects",
]
def __init__(self):
#: A case-insensitive dictionary of headers to be sent on each
#: :class:`Request <Request>` sent from this
#: :class:`Session <Session>`.
@@ -421,8 +445,8 @@ class Session(SessionRedirectMixin):
# Default connection adapters.
self.adapters = OrderedDict()
self.mount('https://', HTTPAdapter())
self.mount('http://', HTTPAdapter())
self.mount("https://", HTTPAdapter())
self.mount("http://", HTTPAdapter())
def __enter__(self):
return self
@@ -448,7 +472,8 @@ class Session(SessionRedirectMixin):
# Merge with session cookies
merged_cookies = merge_cookies(
merge_cookies(RequestsCookieJar(), self.cookies), cookies)
merge_cookies(RequestsCookieJar(), self.cookies), cookies
)
# Set environment's basic authentication if not explicitly set.
auth = request.auth
@@ -463,10 +488,7 @@ class Session(SessionRedirectMixin):
data=request.data,
json=request.json,
headers=merge_setting(
request.headers,
self.headers,
dict_class=CaseInsensitiveDict,
delete_none=False,
request.headers, self.headers, dict_class=CaseInsensitiveDict
),
params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth),
@@ -475,10 +497,25 @@ class Session(SessionRedirectMixin):
)
return p
def request(self, method, url,
params=None, data=None, headers=None, cookies=None, files=None,
auth=None, timeout=None, allow_redirects=True, proxies=None,
hooks=None, stream=None, verify=None, cert=None, json=None):
def request(
self,
method,
url,
params=None,
data=None,
headers=None,
cookies=None,
files=None,
auth=None,
timeout=None,
allow_redirects=True,
proxies=None,
hooks=None,
stream=None,
verify=None,
cert=None,
json=None,
):
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
Returns :class:`Response <Response>` object.
@@ -506,6 +543,8 @@ class Session(SessionRedirectMixin):
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol or protocol and
hostname to the URL of the proxy.
:param hooks: (optional) Dictionary mapping hook name to one event or
list of events, event must be callable.
:param stream: (optional) whether to immediately download the response
content. Defaults to ``False``.
:param verify: (optional) Either a boolean, in which case it controls whether we verify
@@ -514,7 +553,7 @@ class Session(SessionRedirectMixin):
``False``, requests will accept any TLS certificate presented by
the server, and will ignore hostname mismatches and/or expired
certificates, which will make your application vulnerable to
man-in-the-middle (MitM) attacks. Setting verify to ``False``
man-in-the-middle (MitM) attacks. Setting verify to ``False``
may be useful during local development or testing.
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
@@ -543,8 +582,8 @@ class Session(SessionRedirectMixin):
# Send the request.
send_kwargs = {
'timeout': timeout,
'allow_redirects': allow_redirects,
"timeout": timeout,
"allow_redirects": allow_redirects,
}
send_kwargs.update(settings)
resp = self.send(prep, **send_kwargs)
@@ -559,8 +598,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return self.request('GET', url, **kwargs)
kwargs.setdefault("allow_redirects", True)
return self.request("GET", url, **kwargs)
def options(self, url, **kwargs):
r"""Sends a OPTIONS request. Returns :class:`Response` object.
@@ -570,8 +609,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return self.request('OPTIONS', url, **kwargs)
kwargs.setdefault("allow_redirects", True)
return self.request("OPTIONS", url, **kwargs)
def head(self, url, **kwargs):
r"""Sends a HEAD request. Returns :class:`Response` object.
@@ -581,8 +620,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', False)
return self.request('HEAD', url, **kwargs)
kwargs.setdefault("allow_redirects", False)
return self.request("HEAD", url, **kwargs)
def post(self, url, data=None, json=None, **kwargs):
r"""Sends a POST request. Returns :class:`Response` object.
@@ -595,7 +634,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
return self.request('POST', url, data=data, json=json, **kwargs)
return self.request("POST", url, data=data, json=json, **kwargs)
def put(self, url, data=None, **kwargs):
r"""Sends a PUT request. Returns :class:`Response` object.
@@ -607,7 +646,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
return self.request('PUT', url, data=data, **kwargs)
return self.request("PUT", url, data=data, **kwargs)
def patch(self, url, data=None, **kwargs):
r"""Sends a PATCH request. Returns :class:`Response` object.
@@ -619,7 +658,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
return self.request('PATCH', url, data=data, **kwargs)
return self.request("PATCH", url, data=data, **kwargs)
def delete(self, url, **kwargs):
r"""Sends a DELETE request. Returns :class:`Response` object.
@@ -629,7 +668,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response
"""
return self.request('DELETE', url, **kwargs)
return self.request("DELETE", url, **kwargs)
def send(self, request, **kwargs):
"""Send a given PreparedRequest.
@@ -638,19 +677,20 @@ class Session(SessionRedirectMixin):
"""
# Set defaults that the hooks can utilize to ensure they always have
# the correct parameters to reproduce the previous request.
kwargs.setdefault('stream', self.stream)
kwargs.setdefault('verify', self.verify)
kwargs.setdefault('cert', self.cert)
kwargs.setdefault('proxies', self.proxies)
kwargs.setdefault("stream", self.stream)
kwargs.setdefault("verify", self.verify)
kwargs.setdefault("cert", self.cert)
if "proxies" not in kwargs:
kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)
# It's possible that users might accidentally send a Request object.
# Guard against that specific failure case.
if isinstance(request, Request):
raise ValueError('You can only send PreparedRequests.')
raise ValueError("You can only send PreparedRequests.")
# Set up variables needed for resolve_redirects and dispatching of hooks
allow_redirects = kwargs.pop('allow_redirects', True)
stream = kwargs.get('stream')
allow_redirects = kwargs.pop("allow_redirects", True)
stream = kwargs.get("stream")
hooks = request.hooks
# Get the appropriate adapter to use
@@ -667,11 +707,10 @@ class Session(SessionRedirectMixin):
r.elapsed = timedelta(seconds=elapsed)
# Response manipulation hooks
r = dispatch_hook('response', hooks, r, **kwargs)
r = dispatch_hook("response", hooks, r, **kwargs)
# Persist cookies
if r.history:
# If the hooks create history then we want those cookies too
for resp in r.history:
extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
@@ -697,7 +736,9 @@ class Session(SessionRedirectMixin):
# If redirects aren't being followed, store the response on the Request for Response.next().
if not allow_redirects:
try:
r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
r._next = next(
self.resolve_redirects(r, request, yield_requests=True, **kwargs)
)
except StopIteration:
pass
@@ -715,16 +756,19 @@ class Session(SessionRedirectMixin):
# Gather clues from the surrounding environment.
if self.trust_env:
# Set environment's proxies.
no_proxy = proxies.get('no_proxy') if proxies is not None else None
no_proxy = proxies.get("no_proxy") if proxies is not None else None
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
for (k, v) in env_proxies.items():
for k, v in env_proxies.items():
proxies.setdefault(k, v)
# Look for requests environment configuration and be compatible
# with cURL.
# Look for requests environment configuration
# and be compatible with cURL.
if verify is True or verify is None:
verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
os.environ.get('CURL_CA_BUNDLE'))
verify = (
os.environ.get("REQUESTS_CA_BUNDLE")
or os.environ.get("CURL_CA_BUNDLE")
or verify
)
# Merge all the kwargs.
proxies = merge_setting(proxies, self.proxies)
@@ -732,8 +776,7 @@ class Session(SessionRedirectMixin):
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
return {'verify': verify, 'proxies': proxies, 'stream': stream,
'cert': cert}
return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}
def get_adapter(self, url):
"""
@@ -741,13 +784,12 @@ class Session(SessionRedirectMixin):
:rtype: requests.adapters.BaseAdapter
"""
for (prefix, adapter) in self.adapters.items():
for prefix, adapter in self.adapters.items():
if url.lower().startswith(prefix.lower()):
return adapter
# Nothing matches :-/
raise InvalidSchema("No connection adapters were found for {!r}".format(url))
raise InvalidSchema(f"No connection adapters were found for {url!r}")
def close(self):
"""Closes all adapters and as such the session"""

View File

@@ -0,0 +1,128 @@
r"""
The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary
items.
Example::
>>> import requests
>>> requests.codes['temporary_redirect']
307
>>> requests.codes.teapot
418
>>> requests.codes['\o/']
200
Some codes have multiple names, and both upper- and lower-case versions of
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
``codes.okay`` all correspond to the HTTP status code 200.
"""
from .structures import LookupDict
_codes = {
# Informational.
100: ("continue",),
101: ("switching_protocols",),
102: ("processing", "early-hints"),
103: ("checkpoint",),
122: ("uri_too_long", "request_uri_too_long"),
200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", ""),
201: ("created",),
202: ("accepted",),
203: ("non_authoritative_info", "non_authoritative_information"),
204: ("no_content",),
205: ("reset_content", "reset"),
206: ("partial_content", "partial"),
207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"),
208: ("already_reported",),
226: ("im_used",),
# Redirection.
300: ("multiple_choices",),
301: ("moved_permanently", "moved", "\\o-"),
302: ("found",),
303: ("see_other", "other"),
304: ("not_modified",),
305: ("use_proxy",),
306: ("switch_proxy",),
307: ("temporary_redirect", "temporary_moved", "temporary"),
308: (
"permanent_redirect",
"resume_incomplete",
"resume",
), # "resume" and "resume_incomplete" to be removed in 3.0
# Client Error.
400: ("bad_request", "bad"),
401: ("unauthorized",),
402: ("payment_required", "payment"),
403: ("forbidden",),
404: ("not_found", "-o-"),
405: ("method_not_allowed", "not_allowed"),
406: ("not_acceptable",),
407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"),
408: ("request_timeout", "timeout"),
409: ("conflict",),
410: ("gone",),
411: ("length_required",),
412: ("precondition_failed", "precondition"),
413: ("request_entity_too_large", "content_too_large"),
414: ("request_uri_too_large", "uri_too_long"),
415: ("unsupported_media_type", "unsupported_media", "media_type"),
416: (
"requested_range_not_satisfiable",
"requested_range",
"range_not_satisfiable",
),
417: ("expectation_failed",),
418: ("im_a_teapot", "teapot", "i_am_a_teapot"),
421: ("misdirected_request",),
422: ("unprocessable_entity", "unprocessable", "unprocessable_content"),
423: ("locked",),
424: ("failed_dependency", "dependency"),
425: ("unordered_collection", "unordered", "too_early"),
426: ("upgrade_required", "upgrade"),
428: ("precondition_required", "precondition"),
429: ("too_many_requests", "too_many"),
431: ("header_fields_too_large", "fields_too_large"),
444: ("no_response", "none"),
449: ("retry_with", "retry"),
450: ("blocked_by_windows_parental_controls", "parental_controls"),
451: ("unavailable_for_legal_reasons", "legal_reasons"),
499: ("client_closed_request",),
# Server Error.
500: ("internal_server_error", "server_error", "/o\\", ""),
501: ("not_implemented",),
502: ("bad_gateway",),
503: ("service_unavailable", "unavailable"),
504: ("gateway_timeout",),
505: ("http_version_not_supported", "http_version"),
506: ("variant_also_negotiates",),
507: ("insufficient_storage",),
509: ("bandwidth_limit_exceeded", "bandwidth"),
510: ("not_extended",),
511: ("network_authentication_required", "network_auth", "network_authentication"),
}
codes = LookupDict(name="status_codes")
def _init():
for code, titles in _codes.items():
for title in titles:
setattr(codes, title, code)
if not title.startswith(("\\", "/")):
setattr(codes, title.upper(), code)
def doc(code):
names = ", ".join(f"``{n}``" for n in _codes[code])
return "* %d: %s" % (code, names)
global __doc__
__doc__ = (
__doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes))
if __doc__ is not None
else None
)
_init()

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.structures
~~~~~~~~~~~~~~~~~~~
@@ -64,11 +62,7 @@ class CaseInsensitiveDict(MutableMapping):
def lower_items(self):
"""Like iteritems(), but with all lowercase keys."""
return (
(lowerkey, keyval[1])
for (lowerkey, keyval)
in self._store.items()
)
return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
def __eq__(self, other):
if isinstance(other, Mapping):
@@ -91,10 +85,10 @@ class LookupDict(dict):
def __init__(self, name=None):
self.name = name
super(LookupDict, self).__init__()
super().__init__()
def __repr__(self):
return '<lookup \'%s\'>' % (self.name)
return f"<lookup '{self.name}'>"
def __getitem__(self, key):
# We allow fall-through here, so values default to None

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
requests.utils
~~~~~~~~~~~~~~
@@ -21,49 +19,76 @@ import warnings
import zipfile
from collections import OrderedDict
from .__version__ import __version__
from urllib3.util import make_headers, parse_url
from . import certs
from .__version__ import __version__
# to_native_string is unused here, but imported here for backwards compatibility
from ._internal_utils import to_native_string
from ._internal_utils import ( # noqa: F401
_HEADER_VALIDATORS_BYTE,
_HEADER_VALIDATORS_STR,
HEADER_VALIDATORS,
to_native_string,
)
from .compat import (
Mapping,
basestring,
bytes,
getproxies,
getproxies_environment,
integer_types,
)
from .compat import parse_http_list as _parse_list_header
from .compat import (
quote, urlparse, bytes, str, unquote, getproxies,
proxy_bypass, urlunparse, basestring, integer_types, is_py3,
proxy_bypass_environment, getproxies_environment, Mapping)
proxy_bypass,
proxy_bypass_environment,
quote,
str,
unquote,
urlparse,
urlunparse,
)
from .cookies import cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .exceptions import (
InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError)
FileModeWarning,
InvalidHeader,
InvalidURL,
UnrewindableBodyError,
)
from .structures import CaseInsensitiveDict
NETRC_FILES = ('.netrc', '_netrc')
NETRC_FILES = (".netrc", "_netrc")
DEFAULT_CA_BUNDLE_PATH = certs.where()
DEFAULT_PORTS = {'http': 80, 'https': 443}
DEFAULT_PORTS = {"http": 80, "https": 443}
# Ensure that ', ' is used to preserve previous delimiter behavior.
DEFAULT_ACCEPT_ENCODING = ", ".join(
re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"])
)
if sys.platform == 'win32':
if sys.platform == "win32":
# provide a proxy_bypass version on Windows without DNS lookups
def proxy_bypass_registry(host):
try:
if is_py3:
import winreg
else:
import _winreg as winreg
import winreg
except ImportError:
return False
try:
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings')
internetSettings = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
)
# ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
proxyEnable = int(winreg.QueryValueEx(internetSettings,
'ProxyEnable')[0])
proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0])
# ProxyOverride is almost always a string
proxyOverride = winreg.QueryValueEx(internetSettings,
'ProxyOverride')[0]
except OSError:
proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0]
except (OSError, ValueError):
return False
if not proxyEnable or not proxyOverride:
return False
@@ -71,15 +96,17 @@ if sys.platform == 'win32':
# make a check value list from the registry entry: replace the
# '<local>' string by the localhost entry and the corresponding
# canonical entry.
proxyOverride = proxyOverride.split(';')
proxyOverride = proxyOverride.split(";")
# filter out empty strings to avoid re.match return true in the following code.
proxyOverride = filter(None, proxyOverride)
# now check if we match one of the registry values.
for test in proxyOverride:
if test == '<local>':
if '.' not in host:
if test == "<local>":
if "." not in host:
return True
test = test.replace(".", r"\.") # mask dots
test = test.replace("*", r".*") # change glob sequence
test = test.replace("?", r".") # change glob char
test = test.replace(".", r"\.") # mask dots
test = test.replace("*", r".*") # change glob sequence
test = test.replace("?", r".") # change glob char
if re.match(test, host, re.I):
return True
return False
@@ -99,7 +126,7 @@ if sys.platform == 'win32':
def dict_to_sequence(d):
"""Returns an internal sequence dictionary update."""
if hasattr(d, 'items'):
if hasattr(d, "items"):
d = d.items()
return d
@@ -109,37 +136,45 @@ def super_len(o):
total_length = None
current_position = 0
if hasattr(o, '__len__'):
if isinstance(o, str):
o = o.encode("utf-8")
if hasattr(o, "__len__"):
total_length = len(o)
elif hasattr(o, 'len'):
elif hasattr(o, "len"):
total_length = o.len
elif hasattr(o, 'fileno'):
elif hasattr(o, "fileno"):
try:
fileno = o.fileno()
except io.UnsupportedOperation:
except (io.UnsupportedOperation, AttributeError):
# AttributeError is a surprising exception, seeing as how we've just checked
# that `hasattr(o, 'fileno')`. It happens for objects obtained via
# `Tarfile.extractfile()`, per issue 5229.
pass
else:
total_length = os.fstat(fileno).st_size
# Having used fstat to determine the file length, we need to
# confirm that this file was opened up in binary mode.
if 'b' not in o.mode:
warnings.warn((
"Requests has determined the content-length for this "
"request using the binary size of the file: however, the "
"file has been opened in text mode (i.e. without the 'b' "
"flag in the mode). This may lead to an incorrect "
"content-length. In Requests 3.0, support will be removed "
"for files in text mode."),
FileModeWarning
if "b" not in o.mode:
warnings.warn(
(
"Requests has determined the content-length for this "
"request using the binary size of the file: however, the "
"file has been opened in text mode (i.e. without the 'b' "
"flag in the mode). This may lead to an incorrect "
"content-length. In Requests 3.0, support will be removed "
"for files in text mode."
),
FileModeWarning,
)
if hasattr(o, 'tell'):
if hasattr(o, "tell"):
try:
current_position = o.tell()
except (OSError, IOError):
except OSError:
# This can happen in some weird situations, such as when the file
# is actually a special file descriptor like stdin. In this
# instance, we don't know what the length is, so set it to zero and
@@ -147,8 +182,8 @@ def super_len(o):
if total_length is not None:
current_position = total_length
else:
if hasattr(o, 'seek') and total_length is None:
# StringIO and BytesIO have seek but no useable fileno
if hasattr(o, "seek") and total_length is None:
# StringIO and BytesIO have seek but no usable fileno
try:
# seek to end of file
o.seek(0, 2)
@@ -157,7 +192,7 @@ def super_len(o):
# seek back to current position to support
# partially read file-like objects
o.seek(current_position or 0)
except (OSError, IOError):
except OSError:
total_length = 0
if total_length is None:
@@ -169,14 +204,14 @@ def super_len(o):
def get_netrc_auth(url, raise_errors=False):
"""Returns the Requests tuple auth for a given url from netrc."""
netrc_file = os.environ.get('NETRC')
netrc_file = os.environ.get("NETRC")
if netrc_file is not None:
netrc_locations = (netrc_file,)
else:
netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES)
netrc_locations = (f"~/{f}" for f in NETRC_FILES)
try:
from netrc import netrc, NetrcParseError
from netrc import NetrcParseError, netrc
netrc_path = None
@@ -201,18 +236,18 @@ def get_netrc_auth(url, raise_errors=False):
# Strip port numbers from netloc. This weird `if...encode`` dance is
# used for Python 3.2, which doesn't support unicode literals.
splitstr = b':'
splitstr = b":"
if isinstance(url, str):
splitstr = splitstr.decode('ascii')
splitstr = splitstr.decode("ascii")
host = ri.netloc.split(splitstr)[0]
try:
_netrc = netrc(netrc_path).authenticators(host)
if _netrc:
# Return with login / password
login_i = (0 if _netrc[0] else 1)
login_i = 0 if _netrc[0] else 1
return (_netrc[login_i], _netrc[2])
except (NetrcParseError, IOError):
except (NetrcParseError, OSError):
# If there was a parsing error or a permissions issue reading the file,
# we'll just skip netrc auth unless explicitly asked to raise errors.
if raise_errors:
@@ -225,9 +260,8 @@ def get_netrc_auth(url, raise_errors=False):
def guess_filename(obj):
"""Tries to guess the filename of the given object."""
name = getattr(obj, 'name', None)
if (name and isinstance(name, basestring) and name[0] != '<' and
name[-1] != '>'):
name = getattr(obj, "name", None)
if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">":
return os.path.basename(name)
@@ -245,7 +279,11 @@ def extract_zipped_paths(path):
archive, member = os.path.split(path)
while archive and not os.path.exists(archive):
archive, prefix = os.path.split(archive)
member = '/'.join([prefix, member])
if not prefix:
# If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split),
# we _can_ end up in an infinite loop on a rare corner case affecting a small number of users
break
member = "/".join([prefix, member])
if not zipfile.is_zipfile(archive):
return path
@@ -256,13 +294,27 @@ def extract_zipped_paths(path):
# we have a valid zip archive and a valid member of that archive
tmp = tempfile.gettempdir()
extracted_path = os.path.join(tmp, *member.split('/'))
extracted_path = os.path.join(tmp, member.split("/")[-1])
if not os.path.exists(extracted_path):
extracted_path = zip_file.extract(member, path=tmp)
# use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition
with atomic_open(extracted_path) as file_handler:
file_handler.write(zip_file.read(member))
return extracted_path
@contextlib.contextmanager
def atomic_open(filename):
"""Write a file to the disk in an atomic fashion"""
tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
try:
with os.fdopen(tmp_descriptor, "wb") as tmp_handler:
yield tmp_handler
os.replace(tmp_name, filename)
except BaseException:
os.remove(tmp_name)
raise
def from_key_val_list(value):
"""Take an object and test to see if it can be represented as a
dictionary. Unless it can not be represented as such, return an
@@ -285,7 +337,7 @@ def from_key_val_list(value):
return None
if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples')
raise ValueError("cannot encode objects that are not 2-tuples")
return OrderedDict(value)
@@ -311,7 +363,7 @@ def to_key_val_list(value):
return None
if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples')
raise ValueError("cannot encode objects that are not 2-tuples")
if isinstance(value, Mapping):
value = value.items()
@@ -376,10 +428,10 @@ def parse_dict_header(value):
"""
result = {}
for item in _parse_list_header(value):
if '=' not in item:
if "=" not in item:
result[item] = None
continue
name, value = item.split('=', 1)
name, value = item.split("=", 1)
if value[:1] == value[-1:] == '"':
value = unquote_header_value(value[1:-1])
result[name] = value
@@ -407,8 +459,8 @@ def unquote_header_value(value, is_filename=False):
# replace sequence below on a UNC path has the effect of turning
# the leading double slash into a single slash and then
# _fix_ie_filename() doesn't work correctly. See #458.
if not is_filename or value[:2] != '\\\\':
return value.replace('\\\\', '\\').replace('\\"', '"')
if not is_filename or value[:2] != "\\\\":
return value.replace("\\\\", "\\").replace('\\"', '"')
return value
@@ -419,11 +471,7 @@ def dict_from_cookiejar(cj):
:rtype: dict
"""
cookie_dict = {}
for cookie in cj:
cookie_dict[cookie.name] = cookie.value
cookie_dict = {cookie.name: cookie.value for cookie in cj}
return cookie_dict
@@ -443,19 +491,24 @@ def get_encodings_from_content(content):
:param content: bytestring to extract encodings from.
"""
warnings.warn((
'In requests 3.0, get_encodings_from_content will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'),
DeprecationWarning)
warnings.warn(
(
"In requests 3.0, get_encodings_from_content will be removed. For "
"more information, please see the discussion on issue #2266. (This"
" warning should only appear once.)"
),
DeprecationWarning,
)
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
return (charset_re.findall(content) +
pragma_re.findall(content) +
xml_re.findall(content))
return (
charset_re.findall(content)
+ pragma_re.findall(content)
+ xml_re.findall(content)
)
def _parse_content_type_header(header):
@@ -466,7 +519,7 @@ def _parse_content_type_header(header):
parameters
"""
tokens = header.split(';')
tokens = header.split(";")
content_type, params = tokens[0].strip(), tokens[1:]
params_dict = {}
items_to_strip = "\"' "
@@ -478,7 +531,7 @@ def _parse_content_type_header(header):
index_of_equals = param.find("=")
if index_of_equals != -1:
key = param[:index_of_equals].strip(items_to_strip)
value = param[index_of_equals + 1:].strip(items_to_strip)
value = param[index_of_equals + 1 :].strip(items_to_strip)
params_dict[key.lower()] = value
return content_type, params_dict
@@ -490,38 +543,37 @@ def get_encoding_from_headers(headers):
:rtype: str
"""
content_type = headers.get('content-type')
content_type = headers.get("content-type")
if not content_type:
return None
content_type, params = _parse_content_type_header(content_type)
if 'charset' in params:
return params['charset'].strip("'\"")
if "charset" in params:
return params["charset"].strip("'\"")
if 'text' in content_type:
return 'ISO-8859-1'
if "text" in content_type:
return "ISO-8859-1"
if 'application/json' in content_type:
if "application/json" in content_type:
# Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset
return 'utf-8'
return "utf-8"
def stream_decode_response_unicode(iterator, r):
"""Stream decodes a iterator."""
"""Stream decodes an iterator."""
if r.encoding is None:
for item in iterator:
yield item
yield from iterator
return
decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace')
decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace")
for chunk in iterator:
rv = decoder.decode(chunk)
if rv:
yield rv
rv = decoder.decode(b'', final=True)
rv = decoder.decode(b"", final=True)
if rv:
yield rv
@@ -532,7 +584,7 @@ def iter_slices(string, slice_length):
if slice_length is None or slice_length <= 0:
slice_length = len(string)
while pos < len(string):
yield string[pos:pos + slice_length]
yield string[pos : pos + slice_length]
pos += slice_length
@@ -548,11 +600,14 @@ def get_unicode_from_response(r):
:rtype: str
"""
warnings.warn((
'In requests 3.0, get_unicode_from_response will be removed. For '
'more information, please see the discussion on issue #2266. (This'
' warning should only appear once.)'),
DeprecationWarning)
warnings.warn(
(
"In requests 3.0, get_unicode_from_response will be removed. For "
"more information, please see the discussion on issue #2266. (This"
" warning should only appear once.)"
),
DeprecationWarning,
)
tried_encodings = []
@@ -567,14 +622,15 @@ def get_unicode_from_response(r):
# Fall back:
try:
return str(r.content, encoding, errors='replace')
return str(r.content, encoding, errors="replace")
except TypeError:
return r.content
# The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~")
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
)
def unquote_unreserved(uri):
@@ -583,22 +639,22 @@ def unquote_unreserved(uri):
:rtype: str
"""
parts = uri.split('%')
parts = uri.split("%")
for i in range(1, len(parts)):
h = parts[i][0:2]
if len(h) == 2 and h.isalnum():
try:
c = chr(int(h, 16))
except ValueError:
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h)
raise InvalidURL(f"Invalid percent-escape sequence: '{h}'")
if c in UNRESERVED_SET:
parts[i] = c + parts[i][2:]
else:
parts[i] = '%' + parts[i]
parts[i] = f"%{parts[i]}"
else:
parts[i] = '%' + parts[i]
return ''.join(parts)
parts[i] = f"%{parts[i]}"
return "".join(parts)
def requote_uri(uri):
@@ -631,10 +687,10 @@ def address_in_network(ip, net):
:rtype: bool
"""
ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0]
netaddr, bits = net.split('/')
netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0]
network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask
ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0]
netaddr, bits = net.split("/")
netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0]
network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask
return (ipaddr & netmask) == (network & netmask)
@@ -645,8 +701,8 @@ def dotted_netmask(mask):
:rtype: str
"""
bits = 0xffffffff ^ (1 << 32 - mask) - 1
return socket.inet_ntoa(struct.pack('>I', bits))
bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1
return socket.inet_ntoa(struct.pack(">I", bits))
def is_ipv4_address(string_ip):
@@ -655,7 +711,7 @@ def is_ipv4_address(string_ip):
"""
try:
socket.inet_aton(string_ip)
except socket.error:
except OSError:
return False
return True
@@ -666,9 +722,9 @@ def is_valid_cidr(string_network):
:rtype: bool
"""
if string_network.count('/') == 1:
if string_network.count("/") == 1:
try:
mask = int(string_network.split('/')[1])
mask = int(string_network.split("/")[1])
except ValueError:
return False
@@ -676,8 +732,8 @@ def is_valid_cidr(string_network):
return False
try:
socket.inet_aton(string_network.split('/')[0])
except socket.error:
socket.inet_aton(string_network.split("/")[0])
except OSError:
return False
else:
return False
@@ -712,15 +768,17 @@ def should_bypass_proxies(url, no_proxy):
:rtype: bool
"""
# Prioritize lowercase environment variables over uppercase
# to keep a consistent behaviour with other http projects (curl, wget).
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
def get_proxy(key):
return os.environ.get(key) or os.environ.get(key.upper())
# First check whether no_proxy is defined. If it is, check that the URL
# we're getting isn't in the no_proxy list.
no_proxy_arg = no_proxy
if no_proxy is None:
no_proxy = get_proxy('no_proxy')
no_proxy = get_proxy("no_proxy")
parsed = urlparse(url)
if parsed.hostname is None:
@@ -730,9 +788,7 @@ def should_bypass_proxies(url, no_proxy):
if no_proxy:
# We need to check whether we match here. We need to see if we match
# the end of the hostname, both with and without the port.
no_proxy = (
host for host in no_proxy.replace(' ', '').split(',') if host
)
no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host)
if is_ipv4_address(parsed.hostname):
for proxy_ip in no_proxy:
@@ -746,7 +802,7 @@ def should_bypass_proxies(url, no_proxy):
else:
host_with_port = parsed.hostname
if parsed.port:
host_with_port += ':{}'.format(parsed.port)
host_with_port += f":{parsed.port}"
for host in no_proxy:
if parsed.hostname.endswith(host) or host_with_port.endswith(host):
@@ -754,7 +810,7 @@ def should_bypass_proxies(url, no_proxy):
# to apply the proxies on this URL.
return True
with set_environ('no_proxy', no_proxy_arg):
with set_environ("no_proxy", no_proxy_arg):
# parsed.hostname can be `None` in cases such as a file URI.
try:
bypass = proxy_bypass(parsed.hostname)
@@ -788,13 +844,13 @@ def select_proxy(url, proxies):
proxies = proxies or {}
urlparts = urlparse(url)
if urlparts.hostname is None:
return proxies.get(urlparts.scheme, proxies.get('all'))
return proxies.get(urlparts.scheme, proxies.get("all"))
proxy_keys = [
urlparts.scheme + '://' + urlparts.hostname,
urlparts.scheme + "://" + urlparts.hostname,
urlparts.scheme,
'all://' + urlparts.hostname,
'all',
"all://" + urlparts.hostname,
"all",
]
proxy = None
for proxy_key in proxy_keys:
@@ -805,25 +861,54 @@ def select_proxy(url, proxies):
return proxy
def resolve_proxies(request, proxies, trust_env=True):
"""This method takes proxy information from a request and configuration
input to resolve a mapping of target proxies. This will consider settings
such as NO_PROXY to strip proxy configurations.
:param request: Request or PreparedRequest
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
:param trust_env: Boolean declaring whether to trust environment configs
:rtype: dict
"""
proxies = proxies if proxies is not None else {}
url = request.url
scheme = urlparse(url).scheme
no_proxy = proxies.get("no_proxy")
new_proxies = proxies.copy()
if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy):
environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
proxy = environ_proxies.get(scheme, environ_proxies.get("all"))
if proxy:
new_proxies.setdefault(scheme, proxy)
return new_proxies
def default_user_agent(name="python-requests"):
"""
Return a string representing the default user agent.
:rtype: str
"""
return '%s/%s' % (name, __version__)
return f"{name}/{__version__}"
def default_headers():
"""
:rtype: requests.structures.CaseInsensitiveDict
"""
return CaseInsensitiveDict({
'User-Agent': default_user_agent(),
'Accept-Encoding': ', '.join(('gzip', 'deflate')),
'Accept': '*/*',
'Connection': 'keep-alive',
})
return CaseInsensitiveDict(
{
"User-Agent": default_user_agent(),
"Accept-Encoding": DEFAULT_ACCEPT_ENCODING,
"Accept": "*/*",
"Connection": "keep-alive",
}
)
def parse_header_links(value):
@@ -836,23 +921,23 @@ def parse_header_links(value):
links = []
replace_chars = ' \'"'
replace_chars = " '\""
value = value.strip(replace_chars)
if not value:
return links
for val in re.split(', *<', value):
for val in re.split(", *<", value):
try:
url, params = val.split(';', 1)
url, params = val.split(";", 1)
except ValueError:
url, params = val, ''
url, params = val, ""
link = {'url': url.strip('<> \'"')}
link = {"url": url.strip("<> '\"")}
for param in params.split(';'):
for param in params.split(";"):
try:
key, value = param.split('=')
key, value = param.split("=")
except ValueError:
break
@@ -864,7 +949,7 @@ def parse_header_links(value):
# Null bytes; no need to recreate these on each call to guess_json_utf
_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3
_null = "\x00".encode("ascii") # encoding to ASCII for Python 3
_null2 = _null * 2
_null3 = _null * 3
@@ -878,25 +963,25 @@ def guess_json_utf(data):
# determine the encoding. Also detect a BOM, if present.
sample = data[:4]
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
return 'utf-32' # BOM included
return "utf-32" # BOM included
if sample[:3] == codecs.BOM_UTF8:
return 'utf-8-sig' # BOM included, MS style (discouraged)
return "utf-8-sig" # BOM included, MS style (discouraged)
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
return 'utf-16' # BOM included
return "utf-16" # BOM included
nullcount = sample.count(_null)
if nullcount == 0:
return 'utf-8'
return "utf-8"
if nullcount == 2:
if sample[::2] == _null2: # 1st and 3rd are null
return 'utf-16-be'
if sample[::2] == _null2: # 1st and 3rd are null
return "utf-16-be"
if sample[1::2] == _null2: # 2nd and 4th are null
return 'utf-16-le'
return "utf-16-le"
# Did not detect 2 valid UTF-16 ascii-range characters
if nullcount == 3:
if sample[:3] == _null3:
return 'utf-32-be'
return "utf-32-be"
if sample[1:] == _null3:
return 'utf-32-le'
return "utf-32-le"
# Did not detect a valid UTF-32 ascii-range character
return None
@@ -907,15 +992,27 @@ def prepend_scheme_if_needed(url, new_scheme):
:rtype: str
"""
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
parsed = parse_url(url)
scheme, auth, host, port, path, query, fragment = parsed
# urlparse is a finicky beast, and sometimes decides that there isn't a
# netloc present. Assume that it's being over-cautious, and switch netloc
# and path if urlparse decided there was no netloc.
# A defect in urlparse determines that there isn't a netloc present in some
# urls. We previously assumed parsing was overly cautious, and swapped the
# netloc and path. Due to a lack of tests on the original defect, this is
# maintained with parse_url for backwards compatibility.
netloc = parsed.netloc
if not netloc:
netloc, path = path, netloc
return urlunparse((scheme, netloc, path, params, query, fragment))
if auth:
# parse_url doesn't provide the netloc with auth
# so we'll add it ourselves.
netloc = "@".join([auth, netloc])
if scheme is None:
scheme = new_scheme
if path is None:
path = ""
return urlunparse((scheme, netloc, path, "", query, fragment))
def get_auth_from_url(url):
@@ -929,37 +1026,39 @@ def get_auth_from_url(url):
try:
auth = (unquote(parsed.username), unquote(parsed.password))
except (AttributeError, TypeError):
auth = ('', '')
auth = ("", "")
return auth
# Moved outside of function to avoid recompile every call
_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$')
_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$')
def check_header_validity(header):
"""Verifies that header value is a string which doesn't contain
leading whitespace or return characters. This prevents unintended
header injection.
"""Verifies that header parts don't contain leading whitespace
reserved characters, or return characters.
:param header: tuple, in the format (name, value).
"""
name, value = header
if value is None:
return
_validate_header_part(header, name, 0)
_validate_header_part(header, value, 1)
if isinstance(value, bytes):
pat = _CLEAN_HEADER_REGEX_BYTE
def _validate_header_part(header, header_part, header_validator_index):
if isinstance(header_part, str):
validator = _HEADER_VALIDATORS_STR[header_validator_index]
elif isinstance(header_part, bytes):
validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
else:
pat = _CLEAN_HEADER_REGEX_STR
try:
if not pat.match(value):
raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
except TypeError:
raise InvalidHeader("Value for header {%s: %s} must be of type str or "
"bytes, not %s" % (name, value, type(value)))
raise InvalidHeader(
f"Header part ({header_part!r}) from {header} "
f"must be of type str or bytes, not {type(header_part)}"
)
if not validator.match(header_part):
header_kind = "name" if header_validator_index == 0 else "value"
raise InvalidHeader(
f"Invalid leading whitespace, reserved character(s), or return "
f"character(s) in header {header_kind}: {header_part!r}"
)
def urldefragauth(url):
@@ -974,21 +1073,24 @@ def urldefragauth(url):
if not netloc:
netloc, path = path, netloc
netloc = netloc.rsplit('@', 1)[-1]
netloc = netloc.rsplit("@", 1)[-1]
return urlunparse((scheme, netloc, path, params, query, ''))
return urlunparse((scheme, netloc, path, params, query, ""))
def rewind_body(prepared_request):
"""Move file pointer back to its recorded starting position
so it can be read again on redirect.
"""
body_seek = getattr(prepared_request.body, 'seek', None)
if body_seek is not None and isinstance(prepared_request._body_position, integer_types):
body_seek = getattr(prepared_request.body, "seek", None)
if body_seek is not None and isinstance(
prepared_request._body_position, integer_types
):
try:
body_seek(prepared_request._body_position)
except (IOError, OSError):
raise UnrewindableBodyError("An error occurred when rewinding request "
"body for redirect.")
except OSError:
raise UnrewindableBodyError(
"An error occurred when rewinding request body for redirect."
)
else:
raise UnrewindableBodyError("Unable to rewind request body for redirect.")

View File

@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
"""Requests test package initialisation."""
import warnings
import urllib3
from urllib3.exceptions import SNIMissingWarning
try:
from urllib3.exceptions import SNIMissingWarning
# urllib3 sets SNIMissingWarning to only go off once,
# while this test suite requires it to always fire
# so that it occurs during test_requests.test_https_warnings
warnings.simplefilter('always', SNIMissingWarning)
# urllib3 1.x sets SNIMissingWarning to only go off once,
# while this test suite requires it to always fire
# so that it occurs during test_requests.test_https_warnings
warnings.simplefilter("always", SNIMissingWarning)
except ImportError:
# urllib3 2.0 removed that warning and errors out instead
SNIMissingWarning = None

10
tests/certs/README.md Normal file
View File

@@ -0,0 +1,10 @@
# Testing Certificates
This is a collection of certificates useful for testing aspects of Requests'
behaviour.
The certificates include:
* [expired](./expired) server certificate with a valid certificate authority
* [mtls](./mtls) provides a valid client certificate with a 2 year validity
* [valid](./valid) has a valid server certificate

View File

@@ -0,0 +1,13 @@
.PHONY: all clean ca server
ca:
make -C $@ all
server:
make -C $@ all
all: ca server
clean:
make -C ca clean
make -C server clean

View File

@@ -0,0 +1,11 @@
# Expired Certificates and Configuration for Testing
This has a valid certificate authority in [ca](./ca) and an invalid server
certificate in [server](./server).
This can all be regenerated with:
```
make clean
make all
```

View File

@@ -0,0 +1,13 @@
.PHONY: all clean
root_files = ca-private.key ca.crt
ca-private.key:
openssl genrsa -out ca-private.key 2048
all: ca-private.key
openssl req -x509 -sha256 -days 7300 -key ca-private.key -out ca.crt -config ca.cnf
ln -s ca.crt cacert.pem
clean:
rm -f cacert.pem ca.crt ca-private.key *.csr

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHlIhe7GLCeSk8
RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He
mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK
na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6
fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm
zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy
e6lzFyWnAgMBAAECggEAFwzHhzcD3PQDWCus85PwZoxTeQ817BmUBGpBBOKM0gLG
GCsT7XsmGP2NjICBy9OK+QTKawmb/wR5XK0OMUWDHXqtWn+NFIyojyo8+HEeCf8n
4ZleTFHLnJ+d2N1etbc2qc9mY3tjpaurq8/0Tol9YH06ock1TY2+lO+a5HvMURnY
hcWs70CamL+5B/6n67DhjzMtIW3dIXuEEceM1BW/jW8SKq0JHpQ3t+OJwID7zFaJ
bLyOwAVheMzVGvN3yphf8tll3tMA65bNjdOzgOfZSjAy7EGjW3DyAolDw9jKLRyu
E0gw/exNGe618oMIeUDv0KParlL4RjdiUP8l0xYOwQKBgQD3eYj9rWeqZquI9vKP
gaSv6urb2UJLngShZUpEZRNJgBO+Ewiof0w8tpQdsnuMvWudxMLbzgiUNA+NyC/K
CpzIXFkWnWx+A/pxs8ZO8moOfajVRayJgeOLsQZb7c4fXGsVGApbN4+cPNhTNG6d
ucErv6tae/SzAzcLc5Vkw/ELxwKBgQDOdJ5Wl5JeKAvU/3kF6+MYWCrXxZqMjoHS
y1BtyMX5RbdaWTCfDUu1aV3qJOJjjWQ9DJdJQcEsrTjOpD4bVdZx4w/XEG0JXAa3
jRypVHGdeG/TjhUGJA8U+KX3a1DkcdqM9pqFYRw5Ie95Wz9YRroI+YkixqpK8d7W
C+5BodxXIQKBgCk8Lv9V7XgPM3XW8APJbk+BrTCEuu8unUbnQcCztssAdEmvkjnB
PErBgVyRaNTCmzPmnTFS20sWgaD2QkBAFG+uM4n5ISK+NvTLJ7fv3IwdlAw1V9Jx
uiCElrKqpTXEiHMzVkZss5ks6j6y9duCIBXSEhM5pERPvNRDphjsLTXxAoGARSNC
nyb1Kjjo9XR0V+pNy6pC9q1C+00B5tCVZ55zxe114Hi70pfGQcM+YxnlAoeoCNW9
mBfAFDESNAlGjyrovIzYkiH7EcZSrYdBEOepgJ2DfWo4Wi0bK9+03K2AknAaS1iO
GJqTtAJMSuymwu40gKroJNA42Q40nKO0LyCARGECgYEAiFRHkblBtStv22SpZxNC
jim9yuM0ikh7Ij1lEHysc/GWb2RQNxQVk54BU2kQ0d9xwMZQTKvpF3VE9t7uGdwt
AasWPr/tWYt35Ud0D4bNlagJJ4Xdslf8n1nkq3qqqDQbd7kkQRgwGzVr0uVg7ZfS
26qSPQ0/aF9nagb5eHX3AuU=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,12 @@
[req]
default_bits = 2048
prompt = no
default_md = sha256
encrypt_key = no
distinguished_name = dn
[dn]
C = US # country code
O = Python Software Foundation # organization
OU = python-requests # organization unit/department
CN = Self-Signed Root CA # common name / your cert name

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox
CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv
bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l
ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG
A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw
FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv
b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8
RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He
mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK
na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6
fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm
zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy
e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV
GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws
Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn
9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ
rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ
6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg=
-----END CERTIFICATE-----

View File

@@ -0,0 +1 @@
4F36C3A7E075BA6452D10EEB81E7F189FF489B74

View File

@@ -0,0 +1,16 @@
.PHONY: all clean
server.key:
openssl genrsa -out $@ 2048
server.csr: server.key
openssl req -key $< -new -out $@ -config cert.cnf
server.pem: server.csr
openssl x509 -req -CA ../ca/ca.crt -CAkey ../ca/ca-private.key -in server.csr -outform PEM -out server.pem -days 0 -CAcreateserial
openssl x509 -in ../ca/ca.crt -outform PEM >> $@
all: server.pem
clean:
rm -f server.*

View File

@@ -0,0 +1,24 @@
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt=no
[req_distinguished_name]
C = US
ST = DE
O = Python Software Foundation
OU = python-requests
CN = localhost
[v3_req]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.localhost
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = ::1

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDHjCCAgYCAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQCKulIMpo633iCgbkKv1UoiLC4sQt5xWpgguujywu3hLYwmPFp9
kvPt//imqtl8FhuhKqJ8FCGrVl2YIGj1RJIB3GW7MSPNCuIBFL/gwNi35LxDPtoA
IPyXytIR7VH9+ch9DFInJaoA/BekMuKvbXk54VW9whpHbwkXSG2lBS2vKL0XemYh
9VjvtuRDji2iOZpznlVE2PEN80bojArp6oYKakv2kYzgzgxAJiI/NZGvC7mbSI4e
ja7ad3R9G0kB1FzNj36jrNO5WtxHO/mrRiXSpDeyUbitYvt0HKoM0vhTnOR+BspP
IltfwOQh8qq2Q2AaMHNcVjMH3gHCZADfhk/zAgMBAAGgbDBqBgkqhkiG9w0BCQ4x
XTBbMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
MCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAN
BgkqhkiG9w0BAQsFAAOCAQEAfAhEhrulsZae71YFqgvzwJHm/hzXh47hErtgDXVJ
mFqAxgF6XrnzYujlt3XQXUx/8vdrU7jH+Pe8WO1rDvFwRPMDGoBF3RX29SzyX/2F
e102egnoRR+Hlf0Ixqu0CuTjEVnD+g4mRgXhV7LPKP4W6qGwzcVbaJ3c/zRcfqNR
g9gN6Q6Qt4fXDc7wlx2T3nOszBLQ2XCsIyzVtOJ2sSuadqKH9Aj+mrkrLBdzVFHD
FHnTMJ0t0+anZwd+AWDNsCr5lIwBGL634zw7/yJepMHuPFd2X24S3u8EaWPkfVQn
lV6rLQMGjXYTe2xuYzlUCUYnKvkyPTMjSXDkxWa+WSNwyQ==
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCKulIMpo633iCg
bkKv1UoiLC4sQt5xWpgguujywu3hLYwmPFp9kvPt//imqtl8FhuhKqJ8FCGrVl2Y
IGj1RJIB3GW7MSPNCuIBFL/gwNi35LxDPtoAIPyXytIR7VH9+ch9DFInJaoA/Bek
MuKvbXk54VW9whpHbwkXSG2lBS2vKL0XemYh9VjvtuRDji2iOZpznlVE2PEN80bo
jArp6oYKakv2kYzgzgxAJiI/NZGvC7mbSI4eja7ad3R9G0kB1FzNj36jrNO5WtxH
O/mrRiXSpDeyUbitYvt0HKoM0vhTnOR+BspPIltfwOQh8qq2Q2AaMHNcVjMH3gHC
ZADfhk/zAgMBAAECggEAFSF9RvUFzyb0BEvXN44+/QaKv+4tkMmSW4Xs3rFnZ4G3
E8nkpLUCF9ICD2z9tKNvcPScDFdKq5z7o6ToJ9faf5MRIdrBz8UlGLIO6g6l1Bjw
vjNwJE3h+8MGjXl/IDbwXW/HgbQAeabsePPRSJRdvz2+ACn1M8VLdrLvFJA93ayW
+n3Bk0bXdsrzqBGdoDiNzmIHI3WqdONiR9TymuJe41NJtMKxQDF+c6Y1n/X1OtBk
s9L+u9Xr+R3H72xSYrf1KH1mFZJfTnIPoOmdEU2tVZnZj03rZhT7p8R1fVNX6OHu
NX1Dy9VA6J7dbcqdPvTI743ByQeb+hNnqI/3hmV5eQKBgQC++1Wn3v/dxtczjA+I
tN4a7zyjhazpB25lde55HVfCQPxmYxIYct+j6S0JkMaoLrjiEDb4pnu4Gt4MDqZa
r0Xm8t3wD1YKUUbhpBEGvsMhAEZEIsBOcwkTiEwsoF0mKFa2mTyqAImgIQa8uFt8
Y/oTj55XFe1x6pZKEJRg+K+QSwKBgQC59ONVkMSBirLGS+G+b2kqiBdwZB/3s3wr
feS1xTa+deL3AChnKT9+MsVqOkxdE2TRj/mAeF+5Woa5bPMvgr9Kl7u8bulTH80l
YA/N6FneO11/ncnkgK9wN54kd5TiOtGsGB5S5t/nEAIMUIwWrM/cRau72xNEWOhT
Tvw7TOSF+QKBgQCa/texeiYmE24sA4vH4yIuseKAw8hlBwbtiRyVZt8GZD9zyQuy
k+g02tUWYk0XyXN65LX4bwURkZyMJIeWKZGNsaW1YnzturDQB5tZ4g/zBIoCWkHA
aVQAaimIPk3a3foiD5NQVUdckfEp0GVPOsSGg5R6EO23+i8mxPXnDW1OqQKBgGvf
lelTO8tyLFdAOcqBUt6rZ/1499p3snaAZ6bSqvk95dYnr0h48y5AQaln/FiaIYg4
HyLZsZ4S18jFXSWYkWOyNeQP6yafciBWY5StT0TN52VaoX3+8McGXKUHAcVjHbLZ
ou2wpP6jmKyQJVQaF9LOT9uAMOMbOFrrnQLBjmfxAoGAQAnUhMFG5mwi9Otxt6Mz
g+Gr+3JTlzwC3L7UwGdlFc3G2vSdGx/yOrfzpxPImfIBS95mibDfdvEBMer26pvw
a/ycqybyX9d/5nPDIaJ1lc4M4cbHC/cB52JI6avr/1g8OMK7lR7b/FsPVHS1w8kl
n6uwEjVt2+gP2o9DFTGs158=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,41 @@
-----BEGIN CERTIFICATE-----
MIIDXjCCAkYCFE82w6fgdbpkUtEO64Hn8Yn/SJt0MA0GCSqGSIb3DQEBCwUAMGox
CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv
bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l
ZCBSb290IENBMB4XDTI0MDMxMzIxMTQ0NVoXDTI0MDMxMzIxMTQ0NVowbTELMAkG
A1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUg
Rm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRIwEAYDVQQDDAls
b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCKulIMpo63
3iCgbkKv1UoiLC4sQt5xWpgguujywu3hLYwmPFp9kvPt//imqtl8FhuhKqJ8FCGr
Vl2YIGj1RJIB3GW7MSPNCuIBFL/gwNi35LxDPtoAIPyXytIR7VH9+ch9DFInJaoA
/BekMuKvbXk54VW9whpHbwkXSG2lBS2vKL0XemYh9VjvtuRDji2iOZpznlVE2PEN
80bojArp6oYKakv2kYzgzgxAJiI/NZGvC7mbSI4eja7ad3R9G0kB1FzNj36jrNO5
WtxHO/mrRiXSpDeyUbitYvt0HKoM0vhTnOR+BspPIltfwOQh8qq2Q2AaMHNcVjMH
3gHCZADfhk/zAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGeQdB4+iDbJ78eKhCMV
49Cm8nyYi9215rRRJ24Bw6BtVw1ECwymxLVOEB0gHCu8kKdsFnniFBtChts/ilFg
blIyPKTsb3+kQW9YV9QwVdFdC4mTIljujCSQ4HNUC/Vjfnz85SDKf9/3PMKRr36+
GtSLIozudPvkNmCv68jy3RRXyCwWHc43BLMSZKPD/W+DEuXShI9OIpIlSLBx16Hz
4ce3/1pGuITWcsw6UcRqW31oPR31QmNs5fsq5ZCojDNFzEFCA1t9LiR6UOftFUKy
yOZWfZeAGGdK75U+XDqS9Xkr5/ic5jE0I5rT7e7r3lpvQdgIj8lSx493fczLOGHr
YA0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox
CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv
bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l
ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG
A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw
FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv
b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8
RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He
mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK
na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6
fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm
zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy
e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV
GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws
Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn
9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ
rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ
6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,7 @@
.PHONY: all clean
all:
make -C client all
clean:
make -C client clean

View File

@@ -0,0 +1,4 @@
# Certificate Examples for mTLS
This has some generated certificates for mTLS utilization. The idea is to be
able to have testing around how Requests handles client certificates.

View File

@@ -0,0 +1,16 @@
.PHONY: all clean
client.key:
openssl genrsa -out $@ 2048
client.csr: client.key
openssl req -key $< -new -out $@ -config cert.cnf
client.pem: client.csr
openssl x509 -req -CA ./ca/ca.crt -CAkey ./ca/ca-private.key -in client.csr -outform PEM -out client.pem -days 730 -CAcreateserial
openssl x509 -in ./ca/ca.crt -outform PEM >> $@
all: client.pem
clean:
rm -f client.*

1
tests/certs/mtls/client/ca Symbolic link
View File

@@ -0,0 +1 @@
../../expired/ca/

View File

@@ -0,0 +1,26 @@
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt=no
[req_distinguished_name]
C = US
ST = DE
O = Python Software Foundation
OU = python-requests
CN = requests
[v3_req]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.localhost
IP.1 = 127.0.0.1
IP.2 = ::1
URI.1 = spiffe://trust.python.org/v0/maintainer/sigmavirus24/project/requests/org/psf
URI.2 = spiffe://trust.python.org/v1/maintainer:sigmavirus24/project:requests/org:psf
URI.3 = spiffe://trust.python.org/v1/maintainer=sigmavirus24/project=requests/org=psf

View File

@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEGjCCAwICAQAwbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
cXVlc3RzMREwDwYDVQQDDAhyZXF1ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAMn3iQycTjUzpKJChRNkcm33UB282cUwpxeqKN4ahHxBpS09HRhk
cQYO7yErEUQwzQnBQEcIpzzeIMZIqHuCkgnySjeEJd95AIzNzGyoLLkS51TcJwgR
v83AvT8ljA88s9h38qGTy4/TCxJgf76pfHIuC1qoKVQh3AuHj9nOxIZLUsrdDbWF
WoLqKSVyTby+RXvSAppAR+cuBCaWStQ6xFORn48RHfc6t30ggD4rDAjyU6Vz6oR8
ot3XmGdK0h42UdqidUWkRJajEbpkCnQSXS21IvfXKxF5sFqAXJrj9iVbUfpNPpaa
W8IrHByngyV8amazGZrASstUVRFtWrnrcWECAwEAAaCCAWcwggFjBgkqhkiG9w0B
CQ4xggFUMIIBUDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggr
BgEFBQcDAjCCAR8GA1UdEQSCARYwggESggsqLmxvY2FsaG9zdIcEfwAAAYcQAAAA
AAAAAAAAAAAAAAAAAYZNc3BpZmZlOi8vdHJ1c3QucHl0aG9uLm9yZy92MC9tYWlu
dGFpbmVyL3NpZ21hdmlydXMyNC9wcm9qZWN0L3JlcXVlc3RzL29yZy9wc2aGTXNw
aWZmZTovL3RydXN0LnB5dGhvbi5vcmcvdjEvbWFpbnRhaW5lcjpzaWdtYXZpcnVz
MjQvcHJvamVjdDpyZXF1ZXN0cy9vcmc6cHNmhk1zcGlmZmU6Ly90cnVzdC5weXRo
b24ub3JnL3YxL21haW50YWluZXI9c2lnbWF2aXJ1czI0L3Byb2plY3Q9cmVxdWVz
dHMvb3JnPXBzZjANBgkqhkiG9w0BAQsFAAOCAQEAwP1KJ+Evddn2RV1FM6BFkoDK
MPDO9qwb8ea3j57SIJXZlpw168DljmuGzxJw9oys2O6FYcspbHIocAkfFwiYgVAr
NEog6xlCdPxNBJgC3YFIKwnmBjMPG6ZCWiJn940qTbaJ/j6ZviN17uW4K7Sl+THp
IkMv29uQTWkfg+GbZ9q1hm2m2GHhYLGLAUdJdtv7JI+yq5uxdsWaCANpH6kc8SnK
2rik6D3iItDhHCmToHBpdEnP8J+KDzf5pJrv/g3WH8XVrl4ZzBsOhmciWF4C3Hbf
9eu8eAsp1AsIrZOEGTfClBd7vFCES5DmI0/iRs4czQooqZPnHjOw3Azp/LujrA==
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDJ94kMnE41M6Si
QoUTZHJt91AdvNnFMKcXqijeGoR8QaUtPR0YZHEGDu8hKxFEMM0JwUBHCKc83iDG
SKh7gpIJ8ko3hCXfeQCMzcxsqCy5EudU3CcIEb/NwL0/JYwPPLPYd/Khk8uP0wsS
YH++qXxyLgtaqClUIdwLh4/ZzsSGS1LK3Q21hVqC6iklck28vkV70gKaQEfnLgQm
lkrUOsRTkZ+PER33Ord9IIA+KwwI8lOlc+qEfKLd15hnStIeNlHaonVFpESWoxG6
ZAp0El0ttSL31ysRebBagFya4/YlW1H6TT6WmlvCKxwcp4MlfGpmsxmawErLVFUR
bVq563FhAgMBAAECggEABhWX97JJxN6JFNOjhgGzqiPA3R8lrFlv3zhNbODS9u9U
q404xYBZIKaYhkucLzgNJUBrevhZbsL+V8WJQIH0JlU57nw5ATIjAHA+uqiXraen
zRhTcLHK28b1AeRUA4LU+YN7jWnnawN075kf9WgjtfOJ0gcDimOkE7uCFjyyvPJA
LG9bG+8enGjvUleKXNgmwP4Sq/GlEdGz9Qy+8ga3mtfAULUWe8haFNZXK8CN3xPp
wmVqy7QzgH2TGN1p6Dyxib9ksSN/lOg0dShL8zgu+QXDNx2VwmVrI8Vr02vmB//0
bYxCo5pfICPIFLjLl5yo30dvrUfYqF29PperStHGlQKBgQD/TdemlLjJNP0fvSs7
KEVJj/22YuHK+wurNr2ZFbSdcF3v9sfiwysllmEyGr5cNYA56uUbfG+8VSw7kDll
G+6BKK2UdlPH++6RahqWLqo4k6rsNrkq7elj8xG4gIjR5qzu2uLpjNwp2BGmIoUI
eb1NcLfTlMcNCooV8RHjm1Z5WwKBgQDKhHkUPDcJm2/9Ltq2NZQMrCS7o4LV2uAI
GhGpISfY+SfHkQQNZ9Fvbe6hrFeZs31nAvlTDpPEg/LGSVKA5I2EZT9gwzAQU1TD
Cyol4xqqWFWlwze7w+RLYqX5LtXf7NJg2m5p+ZOoOzzqvTVpodDxqTlCNp2/6ICP
vAIvWhbA8wKBgAYlr62ZIyHlHrsm6OWRwKlWyDseAmXKyasjtEj9Vs37qKdgf8ub
+2v6RPjZ3/+EYkQCveV9h4s3WctNW7Rtib6eZh+PAdFs5X+m2GEJWpvmIlVxs9+u
vtHjRmf04FZ9gWh26MPK2no/c51Wc3GSzNYSgrqbeHd963k/xrh+QwTFAoGAZZjb
3UjwG4O9RPjyhCKQ6WKa8v9urbamWaoqXfziLrmgOUAJFmiU6x/tbXI2aEdhjAIz
7nULsLS5YLx8BWmjjV3106dYP3hut4KsXGF4iSjTnts25J27tA4DUeUrKrF2QVyT
s9qfNvCw+Np/J0Uku3e33/3iWdpcVL9vIS5C5/0CgYBEuxb3dffNRqEiNkpOUrCD
mQTqbO3X+hin9zT3GrxQE+7KpfCfdDIqdK6c5UWHirR3HUjUPZmIFLSx8msfLl3k
hgQw37NMV+asg0Wy3P908qbtnEA2P6aDOMQeHJoC7qEHIDOcOQ1KP3FMvOrdscwS
f0IIDygTH6fYr329s0iXjg==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,41 @@
-----BEGIN CERTIFICATE-----
MIIDXTCCAkUCFE82w6fgdbpkUtEO64Hn8Yn/SJtzMA0GCSqGSIb3DQEBCwUAMGox
CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv
bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l
ZCBSb290IENBMB4XDTI0MDMxMzE4MzUwNFoXDTI2MDMxMzE4MzUwNFowbDELMAkG
A1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUg
Rm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMREwDwYDVQQDDAhy
ZXF1ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn3iQycTjUz
pKJChRNkcm33UB282cUwpxeqKN4ahHxBpS09HRhkcQYO7yErEUQwzQnBQEcIpzze
IMZIqHuCkgnySjeEJd95AIzNzGyoLLkS51TcJwgRv83AvT8ljA88s9h38qGTy4/T
CxJgf76pfHIuC1qoKVQh3AuHj9nOxIZLUsrdDbWFWoLqKSVyTby+RXvSAppAR+cu
BCaWStQ6xFORn48RHfc6t30ggD4rDAjyU6Vz6oR8ot3XmGdK0h42UdqidUWkRJaj
EbpkCnQSXS21IvfXKxF5sFqAXJrj9iVbUfpNPpaaW8IrHByngyV8amazGZrASstU
VRFtWrnrcWECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHHgMckLDRV72p1FEVmCh
AAPZjCswiPZFrwGPN57JqSWjoRB9ilKvo87aPosEO7vfa05OD/qkM/T9Qykuhati
I1T1T7qX4Ymb5kTJIBouuflAO3uKVaq+ga2Q/HLlU5w/VoMU4RuK7+RaiRUEE3xL
iPSMBvZpoMj695LnzcGrT5oLkFI0bTIlpQt1SFjDpHFtOj/ZdwgSbZYLoTCBXQK3
7Y29qAj/XwEiCH63n8tJKvZcD8/ssMIMIdWhNmu+0jOWica/3WSih9Geoy6Ydtxi
I5t9vRjC4LIipMUAF86AJIfvHJyI6aCNT420LaR6NRW0FQn5CPTHPAsKg3JkAywn
Ew==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox
CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv
bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l
ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG
A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw
FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv
b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8
RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He
mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK
na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6
fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm
zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy
e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV
GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws
Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn
9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ
rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ
6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg=
-----END CERTIFICATE-----

1
tests/certs/valid/ca Symbolic link
View File

@@ -0,0 +1 @@
../expired/ca

View File

@@ -0,0 +1,16 @@
.PHONY: all clean
server.key:
openssl genrsa -out $@ 2048
server.csr: server.key
openssl req -key $< -config cert.cnf -new -out $@
server.pem: server.csr
openssl x509 -req -CA ../ca/ca.crt -CAkey ../ca/ca-private.key -in server.csr -outform PEM -out server.pem -extfile cert.cnf -extensions v3_ca -days 7200 -CAcreateserial
openssl x509 -in ../ca/ca.crt -outform PEM >> $@
all: server.pem
clean:
rm -f server.*

View File

@@ -0,0 +1,31 @@
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt=no
[req_distinguished_name]
C = US
ST = DE
O = Python Software Foundation
OU = python-requests
CN = localhost
[v3_req]
# Extensions to add to a certificate request
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth
subjectAltName = critical, @alt_names
[v3_ca]
# Extensions to add to a certificate request
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = critical, serverAuth
subjectAltName = critical, @alt_names
[alt_names]
DNS.1 = *.localhost
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = ::1

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDKjCCAhICAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQChEKOx377ymuDg23By5Re1DHi2RiBKSHr85/ZTZuwP/69lHN7q
TQEO//EMEFZ9+ZwezeJJsejjP2HO5lQZbcsWok3hbM0wVT+vApkogPvJ8WNFFWFe
ZBnGLi/1WM9cSZpUsDJ0XCsG0RTtO27wfgZQlKQMZxTkfi971oPYxNVSjTm2JcLT
kvwYIwxjJXPDTOgRo9TEAY3cWkCrBJN4w74GWBTM5KDDA230T7WwLuv81XD2LvYj
YYdMBGcxPr5tYTIlp3LncbcrDRNk3pbYQk0bRJgkw2vUkteiRGjkt+dgVnLc6+MI
W+VLXEpj+zsOZ5/R4d1pofqj9sDyDPhtNr1JAgMBAAGgeDB2BgkqhkiG9w0BCQ4x
aTBnMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBYGA1UdJQEB/wQMMAoG
CCsGAQUFBwMBMC8GA1UdEQEB/wQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAA
AAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAFTlFTn5Mn8JXtqB5bGjuiChe
ClA6Y32Co4l7N0CtAlf+bExwLdpLOleTX3WnryIPALl9uBUI/67dy/STn/J1Yn86
jWPEFwpmYNSKgQljYWcwtBdYLWfIsJO11kKdaAkOUHBEN5DKrXJ46Vs4918bD1/Q
6ztqdrThiKc646u9xB58Hg7F0IyMWbHfs0x16ZpcN9otrIkbqOE2wzTmc65O1t1i
HDljcSk7OnNy3a9wtLEnyPiyMqHf2k/bTlmiDRVe3cSy9xieoqmzHTnOCSASe1y9
7lcEBQild18Jo4nACV4vCYOUwrMi/58LWW+lD6OmMnPiWUqOvMbgMffMNDpWPA==
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQChEKOx377ymuDg
23By5Re1DHi2RiBKSHr85/ZTZuwP/69lHN7qTQEO//EMEFZ9+ZwezeJJsejjP2HO
5lQZbcsWok3hbM0wVT+vApkogPvJ8WNFFWFeZBnGLi/1WM9cSZpUsDJ0XCsG0RTt
O27wfgZQlKQMZxTkfi971oPYxNVSjTm2JcLTkvwYIwxjJXPDTOgRo9TEAY3cWkCr
BJN4w74GWBTM5KDDA230T7WwLuv81XD2LvYjYYdMBGcxPr5tYTIlp3LncbcrDRNk
3pbYQk0bRJgkw2vUkteiRGjkt+dgVnLc6+MIW+VLXEpj+zsOZ5/R4d1pofqj9sDy
DPhtNr1JAgMBAAECggEAIuLzBfXgCvXzlBjL2kMXd7p4EgkN+PEKnKmUr/t40b1Q
zR6sBQWBX3GeET4fseElSQHQzCQaPNCve4xltm1S4jftFREHP7sTVHHEYWLQxuy/
Uwkewj5927CI6ERgg82YfVP91bjaA/u5I+pt7O7rKLyNbPdN7fEMEW+FNuhpiVvg
JMrcK1BCFL6pmIT21LyTwkacMKZSPko58pWE24MA9aSCHk6cXdwQWQK0AfQT3XGT
C4I0hRed7LgqMH+gMuhpakiO13t8yTwxt2iQC9+aa4oSHD3BOi/CwIWfe1mHwmlr
cj4Kof1JSnK4SVTD16T++PlnWZkF6oaLUNg+/c2C9QKBgQDOFSYIY7+HzinT2hbI
yTIJCHpp+Iee+WVvvxjdZIPMDINrlIiHcMfXb0itUdcUO6tz0KYDMDLRC9CSP0ar
6mBWUTHfAKF2S4JpI9JYI4PNtIpOP1NiYuyJlnh5+ytU1yIeIvl39hmLcRwI9mgz
njy/D7yEoDCrG1dhcltubKpNXQKBgQDIFAVg0A7MNcxBZDLlk1NAME2JKOSszX8E
VNucvZD+9l+L9V9BmwwPQdzYifv/dNp3nYn+lxRPPgze3ZWu4+PeDuGudxu0I6ll
beFdbIcp1wbeQguzHYLjBYJqsMb4Pao5HPInjPu/HWfZlg9oZpJbKVucQwbonJLX
lgca9KaE3QKBgA+OUx+g/+0tZ8ThGoUvgsJhzHPBWeNrKfgEcckMdFJrw2PUg3XN
0pf1g4PpwJV7Z5bHcjCda8iR3r2bXydM+tapLF2L+6QlUQPEu3UBwUo+zY3Yg9/S
Xc6I+DEk/4FY9+9UboZaolT/RcF7cCQtVqKJeo58VRAlcTQe4L32H+jVAoGALXX3
Ht9HbXkP1w/YTLej4+LVy0OCag0rPiW13LBqALSkUx3GrhZ3sAPMFVuM6ad4eFNQ
ZouXbsXvkLgSabGYNf11o/mmTtEHjWdhHKQrNgOIqPmixOkAs2quDmXqX79LLTz5
fKkZDny0+wiQqa0cth/4k9HbAQGKj/ej16kdKPUCgYAz08Y39NnJYxRNz3tu/7C6
jKyXKxhuZCZCt3cSWto5Tg0mVVB+2Jk2GhG1hCfZoRCP25R3FFBR1HOJgOc59T7C
LL67FdO0+7mj/WNzHj3+9gyOYQyQgPVDaTmsJLbuzT2S+GpR94ZNliwL2NEa5baG
B/Nb2ruRNj0GgZVw48N4XQ==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,47 @@
-----BEGIN CERTIFICATE-----
MIIEhTCCA22gAwIBAgIUTzbDp+B1umRS0Q7rgefxif9Im3wwDQYJKoZIhvcNAQEL
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
U2lnbmVkIFJvb3QgQ0EwHhcNMjQwMzE0MDAxMDAzWhcNNDMxMTMwMDAxMDAzWjBt
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCREUxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0
d2FyZSBGb3VuZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxEjAQBgNV
BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKEQ
o7HfvvKa4ODbcHLlF7UMeLZGIEpIevzn9lNm7A//r2Uc3upNAQ7/8QwQVn35nB7N
4kmx6OM/Yc7mVBltyxaiTeFszTBVP68CmSiA+8nxY0UVYV5kGcYuL/VYz1xJmlSw
MnRcKwbRFO07bvB+BlCUpAxnFOR+L3vWg9jE1VKNObYlwtOS/BgjDGMlc8NM6BGj
1MQBjdxaQKsEk3jDvgZYFMzkoMMDbfRPtbAu6/zVcPYu9iNhh0wEZzE+vm1hMiWn
cudxtysNE2TelthCTRtEmCTDa9SS16JEaOS352BWctzr4whb5UtcSmP7Ow5nn9Hh
3Wmh+qP2wPIM+G02vUkCAwEAAaOCAR4wggEaMAwGA1UdEwEB/wQCMAAwDgYDVR0P
AQH/BAQDAgWgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMBMC8GA1UdEQEB/wQlMCOC
CWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUJ90a
UnXKPP13yDprLhG39fUrnu8wgZEGA1UdIwSBiTCBhqFupGwwajELMAkGA1UEBhMC
VVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgwFgYDVQQL
DA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJvb3QgQ0GC
FA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAA4IBAQCVh4hiraRv
JzYbS/TombP//xfVEWHXDBEYsT5GgWf7GPJ/QtSvv6uJFsK7heqLzf9f+r4Z5xMh
YAkb0oe/Ge0T30Mo1YaBEqkKuQL9lOMcP69S9uFz2VT6I/76I8qqAu2AFhu74p8f
qudwmQyRYo1Ryg4R/SgRhSJKF/ST/2wOusNWSsBe1s8S2PmtOb4dr3cMBGihrUzS
DmCQpWjuiuE23HXnnYDc/EUAnEEPkLDgCsE9iLq37FPUHcHjqdYIAhmImPBpv2EL
ftXeRWfxN2hRHpS5Fn3QuAOwfJw5tUcVXojJCJfSpL+Ac97iSjxNaDIPlyomauKw
1rgbUkSw+9JQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDWzCCAkMCFA9wdtNh/V99DRwYp8vXjPxSjJnWMA0GCSqGSIb3DQEBCwUAMGox
CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlv
bjEYMBYGA1UECwwPcHl0aG9uLXJlcXVlc3RzMRwwGgYDVQQDDBNTZWxmLVNpZ25l
ZCBSb290IENBMB4XDTI0MDMxMjIxMDQwM1oXDTQ0MDMwNzIxMDQwM1owajELMAkG
A1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMRgw
FgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv
b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHlIhe7GLCeSk8
RZOKdtmyKns6KdZgGw/LcxPkYvQlu1g0zV8X0DqVr2LdMumWUTNCc9sPdSlAG+He
mQp2TMoWUMumMuwDtit9RT0Sb6Eh9svWgjY9ferovPJRfCWUTsA2Ug8uoh0wyEXK
na7X6fHt5E3B9vj0+b9a4vDibdBXV11FheLT02/uEmAEJDdP/zeBgvVbhcVyumO6
fAGMIWzR2ukhe8z/ma5H9zoi4gZA8nsK6reZUD8+6affnPe+jIt/AdzggtV9jkWm
zSpr+RHeZ0y+q4eik2ZNUGg4XcF6JsJ9yu/AqLBXxd38uLdFfgyhP2y6K628yzgy
e6lzFyWnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGymNVTsKSAq8Ju6zV+AWAyV
GcUNBmLpgzDA0e7pkVYhHTdWKlGH4GnrRcp0nvnSbr6iq1Ob/8yEUUoRzK55Flws
Kt1OLwnZyhfRoSUesoEqpP68vzWEgiYv0QuIWvzNt0YfAAvEgGoc3iri44MelKLn
9ZMT8m91nVamA35R8ZjfeAkNp2xcz0a67V0ww6o4wSXrG7o5ZRXyjqZ/9K7SfwUJ
rV9RciccsjH/MzKbfrx73QwsbPWiFmjzHopdasIO0lDlmgm/r9gKfkbzfKoGCgLZ
6an6FlmLftLSXijf/QwtqeSP9fODeE3dzBmnTM3jdoVS53ZegUDWNl14o25v2Kg=
-----END CERTIFICATE-----

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
from requests.compat import is_py3
import warnings
try:
import StringIO
@@ -13,9 +10,14 @@ try:
except ImportError:
cStringIO = None
if is_py3:
def u(s):
return s
else:
def u(s):
return s.decode('unicode-escape')
def u(s):
warnings.warn(
(
"This helper function is no longer relevant in Python 3. "
"Usage of this alias should be discontinued as it will be "
"removed in a future release of Requests."
),
DeprecationWarning,
)
return s

View File

@@ -1,15 +1,23 @@
# -*- coding: utf-8 -*-
try:
from http.server import HTTPServer, SimpleHTTPRequestHandler
except ImportError:
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
import ssl
import threading
import pytest
from requests.compat import urljoin
def prepare_url(value):
# Issue #1483: Make sure the URL always has a trailing slash
httpbin_url = value.url.rstrip('/') + '/'
httpbin_url = value.url.rstrip("/") + "/"
def inner(*suffix):
return urljoin(httpbin_url, '/'.join(suffix))
return urljoin(httpbin_url, "/".join(suffix))
return inner
@@ -22,3 +30,29 @@ def httpbin(httpbin):
@pytest.fixture
def httpbin_secure(httpbin_secure):
return prepare_url(httpbin_secure)
@pytest.fixture
def nosan_server(tmp_path_factory):
# delay importing until the fixture in order to make it possible
# to deselect the test via command-line when trustme is not available
import trustme
tmpdir = tmp_path_factory.mktemp("certs")
ca = trustme.CA()
# only commonName, no subjectAltName
server_cert = ca.issue_cert(common_name="localhost")
ca_bundle = str(tmpdir / "ca.pem")
ca.cert_pem.write_to_path(ca_bundle)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
server_cert.configure_cert(context)
server = HTTPServer(("localhost", 0), SimpleHTTPRequestHandler)
server.socket = context.wrap_socket(server.socket, server_side=True)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.start()
yield "localhost", server.server_address[1], ca_bundle
server.shutdown()
server_thread.join()

8
tests/test_adapters.py Normal file
View File

@@ -0,0 +1,8 @@
import requests.adapters
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, {})

View File

@@ -1,31 +1,27 @@
# -*- encoding: utf-8
import sys
import pytest
from unittest import mock
from requests.help import info
def test_system_ssl():
"""Verify we're actually setting system_ssl when it should be available."""
assert info()['system_ssl']['version'] != ''
assert info()["system_ssl"]["version"] != ""
class VersionedPackage(object):
class VersionedPackage:
def __init__(self, version):
self.__version__ = version
def test_idna_without_version_attribute(mocker):
def test_idna_without_version_attribute():
"""Older versions of IDNA don't provide a __version__ attribute, verify
that if we have such a package, we don't blow up.
"""
mocker.patch('requests.help.idna', new=None)
assert info()['idna'] == {'version': ''}
with mock.patch("requests.help.idna", new=None):
assert info()["idna"] == {"version": ""}
def test_idna_with_version_attribute(mocker):
def test_idna_with_version_attribute():
"""Verify we're actually setting idna version when it should be available."""
mocker.patch('requests.help.idna', new=VersionedPackage('2.6'))
assert info()['idna'] == {'version': '2.6'}
with mock.patch("requests.help.idna", new=VersionedPackage("2.6")):
assert info()["idna"] == {"version": "2.6"}

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest
from requests import hooks
@@ -10,14 +8,15 @@ def hook(value):
@pytest.mark.parametrize(
'hooks_list, result', (
(hook, 'ata'),
([hook, lambda x: None, hook], 'ta'),
)
"hooks_list, result",
(
(hook, "ata"),
([hook, lambda x: None, hook], "ta"),
),
)
def test_hooks(hooks_list, result):
assert hooks.dispatch_hook('response', {'response': hooks_list}, 'Data') == result
assert hooks.dispatch_hook("response", {"response": hooks_list}, "Data") == result
def test_default_hooks():
assert hooks.default_hooks() == {'response': []}
assert hooks.default_hooks() == {"response": []}

View File

@@ -1,27 +1,127 @@
# -*- coding: utf-8 -*-
import threading
import pytest
import threading
import requests
from tests.testserver.server import Server, consume_socket_content
import requests
from requests.compat import JSONDecodeError
from .utils import override_environ
def echo_response_handler(sock):
"""Simple handler that will take request and echo it back to requester."""
request_content = consume_socket_content(sock, timeout=0.5)
text_200 = (
b"HTTP/1.1 200 OK\r\n"
b"Content-Length: %d\r\n\r\n"
b"%s"
) % (len(request_content), request_content)
sock.send(text_200)
def test_chunked_upload():
"""can safely send generators"""
close_server = threading.Event()
server = Server.basic_response_server(wait_to_close_event=close_server)
data = iter([b'a', b'b', b'c'])
data = iter([b"a", b"b", b"c"])
with server as (host, port):
url = 'http://{}:{}/'.format(host, port)
url = f"http://{host}:{port}/"
r = requests.post(url, data=data, stream=True)
close_server.set() # release server block
assert r.status_code == 200
assert r.request.headers['Transfer-Encoding'] == 'chunked'
assert r.request.headers["Transfer-Encoding"] == "chunked"
def test_chunked_encoding_error():
"""get a ChunkedEncodingError if the server returns a bad response"""
def incomplete_chunked_response_handler(sock):
request_content = consume_socket_content(sock, timeout=0.5)
# The server never ends the request and doesn't provide any valid chunks
sock.send(
b"HTTP/1.1 200 OK\r\n"
b"Transfer-Encoding: chunked\r\n"
)
return request_content
close_server = threading.Event()
server = Server(incomplete_chunked_response_handler)
with server as (host, port):
url = f"http://{host}:{port}/"
with pytest.raises(requests.exceptions.ChunkedEncodingError):
requests.get(url)
close_server.set() # release server block
def test_chunked_upload_uses_only_specified_host_header():
"""Ensure we use only the specified Host header for chunked requests."""
close_server = threading.Event()
server = Server(echo_response_handler, wait_to_close_event=close_server)
data = iter([b"a", b"b", b"c"])
custom_host = "sample-host"
with server as (host, port):
url = f"http://{host}:{port}/"
r = requests.post(url, data=data, headers={"Host": custom_host}, stream=True)
close_server.set() # release server block
expected_header = b"Host: %s\r\n" % custom_host.encode("utf-8")
assert expected_header in r.content
assert r.content.count(b"Host: ") == 1
def test_chunked_upload_doesnt_skip_host_header():
"""Ensure we don't omit all Host headers with chunked requests."""
close_server = threading.Event()
server = Server(echo_response_handler, wait_to_close_event=close_server)
data = iter([b"a", b"b", b"c"])
with server as (host, port):
expected_host = f"{host}:{port}"
url = f"http://{host}:{port}/"
r = requests.post(url, data=data, stream=True)
close_server.set() # release server block
expected_header = b"Host: %s\r\n" % expected_host.encode("utf-8")
assert expected_header in r.content
assert r.content.count(b"Host: ") == 1
def test_conflicting_content_lengths():
"""Ensure we correctly throw an InvalidHeader error if multiple
conflicting Content-Length headers are returned.
"""
def multiple_content_length_response_handler(sock):
request_content = consume_socket_content(sock, timeout=0.5)
response = (
b"HTTP/1.1 200 OK\r\n"
b"Content-Type: text/plain\r\n"
b"Content-Length: 16\r\n"
b"Content-Length: 32\r\n\r\n"
b"-- Bad Actor -- Original Content\r\n"
)
sock.send(response)
return request_content
close_server = threading.Event()
server = Server(multiple_content_length_response_handler)
with server as (host, port):
url = f"http://{host}:{port}/"
with pytest.raises(requests.exceptions.InvalidHeader):
requests.get(url)
close_server.set()
def test_digestauth_401_count_reset_on_redirect():
@@ -77,7 +177,7 @@ def test_digestauth_401_count_reset_on_redirect():
server = Server(digest_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{}:{}/'.format(host, port)
url = f'http://{host}:{port}/'
r = requests.get(url, auth=auth)
# Verify server succeeded in authenticating.
assert r.status_code == 200
@@ -127,7 +227,7 @@ def test_digestauth_401_only_sent_once():
server = Server(digest_failed_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{}:{}/'.format(host, port)
url = f'http://{host}:{port}/'
r = requests.get(url, auth=auth)
# Verify server didn't authenticate us.
assert r.status_code == 401
@@ -164,7 +264,7 @@ def test_digestauth_only_on_4xx():
server = Server(digest_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{}:{}/'.format(host, port)
url = f'http://{host}:{port}/'
r = requests.get(url, auth=auth)
# Verify server didn't receive auth from us.
assert r.status_code == 200
@@ -181,17 +281,17 @@ _schemes_by_var_prefix = [
_proxy_combos = []
for prefix, schemes in _schemes_by_var_prefix:
for scheme in schemes:
_proxy_combos.append(("{}_proxy".format(prefix), scheme))
_proxy_combos.append((f"{prefix}_proxy", scheme))
_proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos]
@pytest.mark.parametrize("var,scheme", _proxy_combos)
def test_use_proxy_from_environment(httpbin, var, scheme):
url = "{}://httpbin.org".format(scheme)
url = f"{scheme}://httpbin.org"
fake_proxy = Server() # do nothing with the requests; just close the socket
with fake_proxy as (host, port):
proxy_url = "socks5://{}:{}".format(host, port)
proxy_url = f"socks5://{host}:{port}"
kwargs = {var: proxy_url}
with override_environ(**kwargs):
# fake proxy's lack of response will cause a ConnectionError
@@ -206,18 +306,20 @@ def test_use_proxy_from_environment(httpbin, var, scheme):
def test_redirect_rfc1808_to_non_ascii_location():
path = u'š'
path = 'š'
expected_path = b'%C5%A1'
redirect_request = [] # stores the second request to the server
def redirect_resp_handler(sock):
consume_socket_content(sock, timeout=0.5)
location = u'//{}:{}/{}'.format(host, port, path)
location = f'//{host}:{port}/{path}'
sock.send(
b'HTTP/1.1 301 Moved Permanently\r\n'
b'Content-Length: 0\r\n'
b'Location: ' + location.encode('utf8') + b'\r\n'
b'\r\n'
(
b'HTTP/1.1 301 Moved Permanently\r\n'
b'Content-Length: 0\r\n'
b'Location: %s\r\n'
b'\r\n'
) % location.encode('utf8')
)
redirect_request.append(consume_socket_content(sock, timeout=0.5))
sock.send(b'HTTP/1.1 200 OK\r\n\r\n')
@@ -226,31 +328,24 @@ def test_redirect_rfc1808_to_non_ascii_location():
server = Server(redirect_resp_handler, wait_to_close_event=close_server)
with server as (host, port):
url = u'http://{}:{}'.format(host, port)
url = f'http://{host}:{port}'
r = requests.get(url=url, allow_redirects=True)
assert r.status_code == 200
assert len(r.history) == 1
assert r.history[0].status_code == 301
assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1')
assert r.url == u'{}/{}'.format(url, expected_path.decode('ascii'))
assert r.url == '{}/{}'.format(url, expected_path.decode('ascii'))
close_server.set()
def test_fragment_not_sent_with_request():
"""Verify that the fragment portion of a URI isn't sent to the server."""
def response_handler(sock):
req = consume_socket_content(sock, timeout=0.5)
sock.send(
b'HTTP/1.1 200 OK\r\n'
b'Content-Length: '+bytes(len(req))+b'\r\n'
b'\r\n'+req
)
close_server = threading.Event()
server = Server(response_handler, wait_to_close_event=close_server)
server = Server(echo_response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
url = f'http://{host}:{port}/path/to/thing/#view=edit&token=hunter2'
r = requests.get(url)
raw_request = r.content
@@ -265,6 +360,7 @@ def test_fragment_not_sent_with_request():
close_server.set()
def test_fragment_update_on_redirect():
"""Verify we only append previous fragment if one doesn't exist on new
location. If a new fragment is encountered in a Location header, it should
@@ -293,17 +389,40 @@ def test_fragment_update_on_redirect():
server = Server(response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
url = f'http://{host}:{port}/path/to/thing/#view=edit&token=hunter2'
r = requests.get(url)
raw_request = r.content
assert r.status_code == 200
assert len(r.history) == 2
assert r.history[0].request.url == url
# Verify we haven't overwritten the location with our previous fragment.
assert r.history[1].request.url == 'http://{}:{}/get#relevant-section'.format(host, port)
assert r.history[1].request.url == f'http://{host}:{port}/get#relevant-section'
# Verify previous fragment is used and not the original.
assert r.url == 'http://{}:{}/final-url/#relevant-section'.format(host, port)
assert r.url == f'http://{host}:{port}/final-url/#relevant-section'
close_server.set()
def test_json_decode_compatibility_for_alt_utf_encodings():
def response_handler(sock):
consume_socket_content(sock, timeout=0.5)
sock.send(
b'HTTP/1.1 200 OK\r\n'
b'Content-Length: 18\r\n\r\n'
b'\xff\xfe{\x00"\x00K0"\x00=\x00"\x00\xab0"\x00\r\n'
)
close_server = threading.Event()
server = Server(response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = f'http://{host}:{port}/'
r = requests.get(url)
r.encoding = None
with pytest.raises(requests.exceptions.JSONDecodeError) as excinfo:
r.json()
assert isinstance(excinfo.value, requests.exceptions.RequestException)
assert isinstance(excinfo.value, JSONDecodeError)
assert r.text not in str(excinfo.value)

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More