Compare commits
364 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b25c87d7cb | ||
|
|
131e506079 | ||
|
|
b336cb2bc6 | ||
|
|
46e939b552 | ||
|
|
4b9c546aa3 | ||
|
|
7618dbef01 | ||
|
|
2edca11103 | ||
|
|
fec96cd597 | ||
|
|
d58d8aa2f4 | ||
|
|
91a3eabd3d | ||
|
|
90fee0876a | ||
|
|
8ff173b186 | ||
|
|
3f07f990ac | ||
|
|
e57b5bf05c | ||
|
|
021dc729f0 | ||
|
|
821770e822 | ||
|
|
59f8aa2adf | ||
|
|
5b4b64c346 | ||
|
|
7bc45877a8 | ||
|
|
96ba401c12 | ||
|
|
7341690e84 | ||
|
|
6716d7c9f2 | ||
|
|
a7e1c745dc | ||
|
|
c799b8167a | ||
|
|
c65c780849 | ||
|
|
579cd9f233 | ||
|
|
c54d0347e2 | ||
|
|
991f05dcd9 | ||
|
|
4ce9520a1c | ||
|
|
42a1dd640c | ||
|
|
c4c8e20289 | ||
|
|
a5cb4284e1 | ||
|
|
2019450b43 | ||
|
|
1764cc938e | ||
|
|
e946665c59 | ||
|
|
8ba805fa93 | ||
|
|
9ebebdef98 | ||
|
|
5074096613 | ||
|
|
1b7c37ef4e | ||
|
|
5d31ddbf7b | ||
|
|
a2fd25ff34 | ||
|
|
d96f9982a2 | ||
|
|
b25f2b5ec2 | ||
|
|
f761e74a4d | ||
|
|
f2a6ae5e32 | ||
|
|
11f63a330b | ||
|
|
e361622fae | ||
|
|
fe0583ba49 | ||
|
|
8c36da656d | ||
|
|
b653d80ded | ||
|
|
23540c93ca | ||
|
|
ad959acfcf | ||
|
|
a6cf27a77f | ||
|
|
ed0b1b5802 | ||
|
|
1f16cffa43 | ||
|
|
9787d0c09c | ||
|
|
26664fa578 | ||
|
|
7335bbf480 | ||
|
|
0bff2d94e6 | ||
|
|
1ae6fc3137 | ||
|
|
5984296b6a | ||
|
|
f519bbae33 | ||
|
|
314e7c9c70 | ||
|
|
92f10cec5b | ||
|
|
83e67c4485 | ||
|
|
0f5ef1be8c | ||
|
|
f12ccbef6d | ||
|
|
173890a48e | ||
|
|
a3ce6f0075 | ||
|
|
877892e67e | ||
|
|
79b74ef704 | ||
|
|
15e1f17bb6 | ||
|
|
01353d3b4a | ||
|
|
4e383642a9 | ||
|
|
f8aa36b92d | ||
|
|
06d2a38441 | ||
|
|
0e322af877 | ||
|
|
e188799322 | ||
|
|
145b539948 | ||
|
|
b1d73ddb50 | ||
|
|
6badbac6e0 | ||
|
|
2e1452234d | ||
|
|
a62a2d35d9 | ||
|
|
88dce9d854 | ||
|
|
c98e4d133e | ||
|
|
92075b330a | ||
|
|
aa1461b68a | ||
|
|
970e8cec98 | ||
|
|
d6ebc4a2f1 | ||
|
|
9a40d12778 | ||
|
|
0c030f78d2 | ||
|
|
555b870eb1 | ||
|
|
d6dded3f00 | ||
|
|
bf24b7d8d1 | ||
|
|
2d5f54779a | ||
|
|
f1bb07d39b | ||
|
|
60047ade64 | ||
|
|
31ebb8102c | ||
|
|
cc23d1c644 | ||
|
|
0790ea4250 | ||
|
|
e45b428960 | ||
|
|
2a438c27b5 | ||
|
|
2daa7b52a7 | ||
|
|
8dd3b26bf5 | ||
|
|
13d892bdbe | ||
|
|
1604e20fc8 | ||
|
|
eeafb6ab3a | ||
|
|
a94e9b5308 | ||
|
|
a58d7f2ffb | ||
|
|
c0813a2d91 | ||
|
|
eea3bbf9ac | ||
|
|
f3f2611d83 | ||
|
|
a0e79bad05 | ||
|
|
0a7f662aa7 | ||
|
|
5f1c3c22ff | ||
|
|
541aa80ca3 | ||
|
|
d3b3399ece | ||
|
|
0ec2780c29 | ||
|
|
b8be93a721 | ||
|
|
3587a5f869 | ||
|
|
60389df6d6 | ||
|
|
382fc2c0c6 | ||
|
|
7a13c041db | ||
|
|
58cea7a728 | ||
|
|
9439fad038 | ||
|
|
8fa4302322 | ||
|
|
28855fd43a | ||
|
|
5fc10bf1f5 | ||
|
|
46c1a3d8c8 | ||
|
|
6106a63eb6 | ||
|
|
4f3f189d6b | ||
|
|
a5a0e4b587 | ||
|
|
3ff3ff21dd | ||
|
|
96b22fa18c | ||
|
|
b0e6c9bf85 | ||
|
|
72eccc8dd8 | ||
|
|
25939d8784 | ||
|
|
bfba9dc68c | ||
|
|
f23346a9b8 | ||
|
|
f3f9784419 | ||
|
|
b5bd0f14cc | ||
|
|
242d3113c0 | ||
|
|
1396eb6f7a | ||
|
|
e4c821a247 | ||
|
|
421b1f1757 | ||
|
|
1447bccc05 | ||
|
|
3b5978fc5d | ||
|
|
1ddf014f47 | ||
|
|
92f9e431c9 | ||
|
|
951dd15fa6 | ||
|
|
51d0d83eaf | ||
|
|
c7c2ebf1a7 | ||
|
|
a64f32ba45 | ||
|
|
a25fde6989 | ||
|
|
ba67dc8dcc | ||
|
|
769bc3ae5c | ||
|
|
d6ffd868ee | ||
|
|
3fd309a5c1 | ||
|
|
ec84f2c539 | ||
|
|
889910c77a | ||
|
|
b37878d340 | ||
|
|
f6707042d8 | ||
|
|
0b4d494192 | ||
|
|
15849947ec | ||
|
|
c6de5a14e5 | ||
|
|
e66a07b286 | ||
|
|
9e98a8749a | ||
|
|
c32b046243 | ||
|
|
89cde235be | ||
|
|
d7490c9d2d | ||
|
|
e9b1217cff | ||
|
|
ad761abcdd | ||
|
|
839a8edec3 | ||
|
|
a8e9c1b436 | ||
|
|
b55bb15c35 | ||
|
|
8187768622 | ||
|
|
f75b9504fc | ||
|
|
8199a2b389 | ||
|
|
42a3b5cce8 | ||
|
|
f5a7aefc2d | ||
|
|
ee93fac6b2 | ||
|
|
a775435c4b | ||
|
|
12862617df | ||
|
|
6c8d9b1d0f | ||
|
|
881281250f | ||
|
|
29fc8d1e93 | ||
|
|
8b560ecb24 | ||
|
|
4585a7fbda | ||
|
|
16dc70df34 | ||
|
|
bea231b033 | ||
|
|
2ee5b0b01c | ||
|
|
89f0eb91b3 | ||
|
|
005571d118 | ||
|
|
d63e94f552 | ||
|
|
e9fa2e2da3 | ||
|
|
678fca8423 | ||
|
|
d8152769ce | ||
|
|
9ff1a25ef5 | ||
|
|
4bd06cd325 | ||
|
|
09a241f59e | ||
|
|
6ad493650c | ||
|
|
9acca90bf6 | ||
|
|
c7933453cf | ||
|
|
ea49261a27 | ||
|
|
8112fcc7be | ||
|
|
2c193bda0c | ||
|
|
34a64bc65e | ||
|
|
9b6c62e235 | ||
|
|
cb7fcd7ebd | ||
|
|
aa4cc78627 | ||
|
|
dfe46c1b7f | ||
|
|
2ecdb68561 | ||
|
|
fecf3dc472 | ||
|
|
cdbc2e2715 | ||
|
|
22db55a889 | ||
|
|
6e5b15d542 | ||
|
|
147c8511dd | ||
|
|
74ea7cf7a6 | ||
|
|
3022253346 | ||
|
|
b639e66c81 | ||
|
|
d3d504436e | ||
|
|
2ad18e0e10 | ||
|
|
f2629e9e3c | ||
|
|
87d63de873 | ||
|
|
51716c4ef3 | ||
|
|
a7da1ab349 | ||
|
|
dfc3e24338 | ||
|
|
26bea1e498 | ||
|
|
7f694b79e1 | ||
|
|
e90852d20c | ||
|
|
ec78348c4b | ||
|
|
46188256ee | ||
|
|
15585909c3 | ||
|
|
16a17a3ca7 | ||
|
|
61c324da43 | ||
|
|
eb07385b02 | ||
|
|
2745db131a | ||
|
|
ec553c275f | ||
|
|
ac3be98b19 | ||
|
|
192083576d | ||
|
|
53a0562331 | ||
|
|
1e62a3ec18 | ||
|
|
7908ad3af9 | ||
|
|
c57f1f0ca1 | ||
|
|
7104ad4b13 | ||
|
|
da246958b4 | ||
|
|
177dd90f18 | ||
|
|
786255613b | ||
|
|
bda7f0171f | ||
|
|
4d394574f5 | ||
|
|
16b418b44a | ||
|
|
0e28c8f1dc | ||
|
|
73793ce43e | ||
|
|
da9996fe4d | ||
|
|
1dcf3b7122 | ||
|
|
e36f34597c | ||
|
|
60865f21ae | ||
|
|
3af2f456d8 | ||
|
|
0f358eabe6 | ||
|
|
210095fd08 | ||
|
|
a5e7169e75 | ||
|
|
79f2ec3acc | ||
|
|
cb233a101d | ||
|
|
40956723f2 | ||
|
|
2a6f290bc0 | ||
|
|
2d5517682b | ||
|
|
8bce583b95 | ||
|
|
79f60274f7 | ||
|
|
79c4a017fe | ||
|
|
5e749546a2 | ||
|
|
3eed0512a6 | ||
|
|
3ed60078e2 | ||
|
|
15fde024f2 | ||
|
|
95f4567336 | ||
|
|
7ae38876b1 | ||
|
|
2c20474bfe | ||
|
|
24dfbfde55 | ||
|
|
fa1b0a367a | ||
|
|
d15a3b6e2e | ||
|
|
48676caf57 | ||
|
|
d718e75383 | ||
|
|
620ed4fc52 | ||
|
|
31a89d9c84 | ||
|
|
8fa9724398 | ||
|
|
38f3f8ecb9 | ||
|
|
0192aac241 | ||
|
|
e50dc12b0f | ||
|
|
17e6e27a93 | ||
|
|
ab38e2c726 | ||
|
|
77d1e9aa3e | ||
|
|
b0829a8690 | ||
|
|
28d537dde3 | ||
|
|
86bbee71b2 | ||
|
|
0d5347e628 | ||
|
|
ef59aa0227 | ||
|
|
d09659997c | ||
|
|
2b06a952a9 | ||
|
|
e2064a2c05 | ||
|
|
401e17bbdc | ||
|
|
4246792b81 | ||
|
|
658c10e84a | ||
|
|
39d0fdd909 | ||
|
|
c193d9742e | ||
|
|
8e764f355d | ||
|
|
7556ea4213 | ||
|
|
2d2447e210 | ||
|
|
e77dd8de6e | ||
|
|
55da533a87 | ||
|
|
99b3b49241 | ||
|
|
e2fa8d3654 | ||
|
|
590350f8d0 | ||
|
|
9dd6e47b5b | ||
|
|
94f9f7357e | ||
|
|
fc106ab586 | ||
|
|
1e5fad7433 | ||
|
|
248122a697 | ||
|
|
9572d6a299 | ||
|
|
4ab2550d66 | ||
|
|
6fbfca90b6 | ||
|
|
aae78010e9 | ||
|
|
10968357a1 | ||
|
|
b351e906b6 | ||
|
|
70a13e2a26 | ||
|
|
8ea90a5ce7 | ||
|
|
7adb8686ac | ||
|
|
827b87b9be | ||
|
|
2d30aeb7c6 | ||
|
|
b0e025ade7 | ||
|
|
d8829f9f24 | ||
|
|
b227e3cb82 | ||
|
|
db575eeedc | ||
|
|
941a77f2b8 | ||
|
|
a3d54b83f7 | ||
|
|
a5b45edf2f | ||
|
|
a1a6a549a0 | ||
|
|
e253eba5ed | ||
|
|
f6c0619d89 | ||
|
|
33cf965f72 | ||
|
|
5351469472 | ||
|
|
24630743ce | ||
|
|
2ed84f55b2 | ||
|
|
33d448eb21 | ||
|
|
1466ad713c | ||
|
|
f6d43b03fb | ||
|
|
05a1a21593 | ||
|
|
c45a4dfe6b | ||
|
|
2c2138e811 | ||
|
|
c267415882 | ||
|
|
5433656878 | ||
|
|
f79d1ef42f | ||
|
|
4193b95db0 | ||
|
|
06462a6e79 | ||
|
|
913880c45a | ||
|
|
bdc00eb097 | ||
|
|
ef6ceffc1c | ||
|
|
8c211a96cd | ||
|
|
74b7280049 | ||
|
|
083aa67a0d | ||
|
|
f6072f1e14 | ||
|
|
c2b307dbef | ||
|
|
6de4f104cd | ||
|
|
b223291693 | ||
|
|
516f84f490 | ||
|
|
60ea7f0c2d |
@@ -1,2 +1,2 @@
|
||||
[run]
|
||||
omit = requests/packages/*
|
||||
omit = requests/packages/*
|
||||
|
||||
5
.git-blame-ignore-revs
Normal file
5
.git-blame-ignore-revs
Normal 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
|
||||
7
.github/CODE_OF_CONDUCT.md
vendored
7
.github/CODE_OF_CONDUCT.md
vendored
@@ -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/).
|
||||
|
||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -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).
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -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). -->
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/Custom.md
vendored
3
.github/ISSUE_TEMPLATE/Custom.md
vendored
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: Request for Help
|
||||
about: Guidance on using Requests.
|
||||
labels:
|
||||
- "Question/Not a bug"
|
||||
- "actions/autoclose-qa"
|
||||
|
||||
---
|
||||
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
labels:
|
||||
- "Feature Request"
|
||||
- "actions/autoclose-feat"
|
||||
|
||||
---
|
||||
|
||||
|
||||
17
.github/SECURITY.md
vendored
17
.github/SECURITY.md
vendored
@@ -1,18 +1,9 @@
|
||||
# Vulnerability Disclosure
|
||||
|
||||
If you think you have found a potential security vulnerability in
|
||||
requests, please email [Nate](mailto:nate.prewitt@gmail.com)
|
||||
and [Seth](mailto:sethmichaellarson@gmail.com) directly.
|
||||
**Do not file a public issue.**
|
||||
|
||||
Our PGP Key fingerprints are:
|
||||
|
||||
- 8722 7E29 AD9C FF5C FAC3 EA6A 44D3 FF97 B80D C864 ([@nateprewitt](https://keybase.io/nateprewitt))
|
||||
|
||||
- EDD5 6765 A9D8 4653 CBC8 A134 51B0 6736 1740 F5FC ([@sethmlarson](https://keybase.io/sethmlarson))
|
||||
|
||||
You can also contact us on [Keybase](https://keybase.io) with the
|
||||
profiles above if desired.
|
||||
requests, please open a [draft Security Advisory](https://github.com/psf/requests/security/advisories/new)
|
||||
via GitHub. We will coordinate verification and next steps through
|
||||
that secure medium.
|
||||
|
||||
If English is not your first language, please try to describe the
|
||||
problem and its impact to the best of your ability. For greater detail,
|
||||
@@ -72,7 +63,7 @@ intended patch ahead of 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
|
||||
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal 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
35
.github/workflows/close-issues.yml
vendored
Normal 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
|
||||
21
.github/workflows/codeql-analysis.yml
vendored
21
.github/workflows/codeql-analysis.yml
vendored
@@ -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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
|
||||
20
.github/workflows/lint.yml
vendored
Normal file
20
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Lint code
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.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
19
.github/workflows/lock-issues.yml
vendored
Normal 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
|
||||
93
.github/workflows/publish.yml
vendored
Normal file
93
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build dists"
|
||||
runs-on: "ubuntu-latest"
|
||||
environment:
|
||||
name: "publish"
|
||||
outputs:
|
||||
hashes: ${{ steps.hash.outputs.hashes }}
|
||||
artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }}
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Checkout repository"
|
||||
uses: "actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8"
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Setup Python"
|
||||
uses: "actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065"
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: python -m pip install build==0.8.0
|
||||
|
||||
- name: "Build dists"
|
||||
run: |
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) \
|
||||
python -m build
|
||||
|
||||
- name: "Generate hashes"
|
||||
id: hash
|
||||
run: |
|
||||
cd dist && echo "::set-output name=hashes::$(sha256sum * | base64 -w0)"
|
||||
|
||||
- name: "Upload dists"
|
||||
uses: "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02"
|
||||
id: upload-artifact
|
||||
with:
|
||||
name: "dist"
|
||||
path: "dist/"
|
||||
if-no-files-found: error
|
||||
retention-days: 5
|
||||
|
||||
provenance:
|
||||
needs: [build]
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
id-token: write # Needed to access the workflow's OIDC identity.
|
||||
uses: "slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0"
|
||||
with:
|
||||
base64-subjects: "${{ needs.build.outputs.hashes }}"
|
||||
upload-assets: true
|
||||
compile-generator: true # Workaround for https://github.com/slsa-framework/slsa-github-generator/issues/1163
|
||||
|
||||
publish:
|
||||
name: "Publish"
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
needs: ["build", "provenance"]
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: "Download dists"
|
||||
uses: "actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0" # v5.0.0
|
||||
with:
|
||||
artifact-ids: ${{ needs.build.outputs.artifact-id }}
|
||||
path: "dist/"
|
||||
|
||||
- name: "Publish dists to PyPI"
|
||||
uses: "pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc"
|
||||
64
.github/workflows/run-tests.yml
vendored
64
.github/workflows/run-tests.yml
vendored
@@ -2,30 +2,74 @@ 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]
|
||||
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.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev", "pypy-3.10", "pypy-3.11"]
|
||||
os: [ubuntu-22.04, macOS-latest, windows-latest]
|
||||
# Pypy-3.11 can't install openssl-sys with rust
|
||||
# which prevents us from testing in GHA.
|
||||
exclude:
|
||||
- { python-version: "pypy-3.11", os: "windows-latest" }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
allow-prereleases: true
|
||||
- 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
- name: 'Set up Python 3.9'
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
make
|
||||
python -m pip uninstall -y "charset_normalizer" "chardet"
|
||||
- name: Run tests
|
||||
run: |
|
||||
make ci
|
||||
|
||||
urllib3:
|
||||
name: 'urllib3 1.x'
|
||||
runs-on: 'ubuntu-latest'
|
||||
strategy:
|
||||
fail-fast: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
- name: 'Set up Python 3.9'
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
make
|
||||
python -m pip install "urllib3<2"
|
||||
- name: Run tests
|
||||
run: |
|
||||
make ci
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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
28
.pre-commit-config.yaml
Normal 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
29
.readthedocs.yaml
Normal 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
|
||||
@@ -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>`_)
|
||||
|
||||
329
HISTORY.md
329
HISTORY.md
@@ -4,11 +4,325 @@ Release History
|
||||
dev
|
||||
---
|
||||
|
||||
- \[Short description of non-trivial change.\]
|
||||
- \[Short description of non-trivial change.\]
|
||||
|
||||
2.32.5 (2025-08-18)
|
||||
-------------------
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
- The SSLContext caching feature originally introduced in 2.32.0 has created
|
||||
a new class of issues in Requests that have had negative impact across a number
|
||||
of use cases. The Requests team has decided to revert this feature as long term
|
||||
maintenance of it is proving to be unsustainable in its current iteration.
|
||||
|
||||
**Deprecations**
|
||||
- Added support for Python 3.14.
|
||||
- Dropped support for Python 3.8 following its end of support.
|
||||
|
||||
2.32.4 (2025-06-10)
|
||||
-------------------
|
||||
|
||||
**Security**
|
||||
- CVE-2024-47081 Fixed an issue where a maliciously crafted URL and trusted
|
||||
environment will retrieve credentials for the wrong hostname/machine from a
|
||||
netrc file.
|
||||
|
||||
**Improvements**
|
||||
- Numerous documentation improvements
|
||||
|
||||
**Deprecations**
|
||||
- Added support for pypy 3.11 for Linux and macOS.
|
||||
- Dropped support for pypy 3.9 following its end of support.
|
||||
|
||||
|
||||
2.32.3 (2024-05-29)
|
||||
-------------------
|
||||
|
||||
**Bugfixes**
|
||||
- Fixed bug breaking the ability to specify custom SSLContexts in sub-classes of
|
||||
HTTPAdapter. (#6716)
|
||||
- Fixed issue where Requests started failing to run on Python versions compiled
|
||||
without the `ssl` module. (#6724)
|
||||
|
||||
2.32.2 (2024-05-21)
|
||||
-------------------
|
||||
|
||||
**Deprecations**
|
||||
- To provide a more stable migration for custom HTTPAdapters impacted
|
||||
by the CVE changes in 2.32.0, we've renamed `_get_connection` to
|
||||
a new public API, `get_connection_with_tls_context`. Existing custom
|
||||
HTTPAdapters will need to migrate their code to use this new API.
|
||||
`get_connection` is considered deprecated in all versions of Requests>=2.32.0.
|
||||
|
||||
A minimal (2-line) example has been provided in the linked PR to ease
|
||||
migration, but we strongly urge users to evaluate if their custom adapter
|
||||
is subject to the same issue described in CVE-2024-35195. (#6710)
|
||||
|
||||
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 +369,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 +724,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 +1958,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 +1977,7 @@ This is not a backwards compatible change.
|
||||
0.2.3 (2011-02-15)
|
||||
------------------
|
||||
|
||||
-
|
||||
-
|
||||
|
||||
New HTTPHandling Methods
|
||||
|
||||
@@ -1696,4 +2010,3 @@ This is not a backwards compatible change.
|
||||
|
||||
- Frustration
|
||||
- Conception
|
||||
|
||||
|
||||
@@ -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 *
|
||||
|
||||
25
Makefile
25
Makefile
@@ -1,26 +1,25 @@
|
||||
.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
|
||||
python -m pytest tests
|
||||
|
||||
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
|
||||
|
||||
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 setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
||||
.publishenv:
|
||||
python -m venv .publishenv
|
||||
.publishenv/bin/pip install 'twine>=1.5.0' build
|
||||
|
||||
publish: .publishenv
|
||||
.publishenv/bin/python -m build
|
||||
.publishenv/bin/python -m twine upload --skip-existing dist/*
|
||||
rm -fr build dist .egg requests.egg-info
|
||||
|
||||
docs:
|
||||
|
||||
34
README.md
34
README.md
@@ -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. There’s 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.
|
||||
|
||||
[](https://pepy.tech/project/requests/month)
|
||||
[](https://pepy.tech/project/requests)
|
||||
[](https://pypi.org/project/requests)
|
||||
[](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.9+.
|
||||
|
||||
## Supported Features & Best–Practices
|
||||
|
||||
@@ -55,8 +55,24 @@ Requests is ready for the demands of building robust and reliable HTTP–speakin
|
||||
|
||||
## API Reference and User Guide available on [Read the Docs](https://requests.readthedocs.io)
|
||||
|
||||
[](https://requests.readthedocs.io)
|
||||
[](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 timestamp (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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[](https://kennethreitz.org) [](https://www.python.org/psf)
|
||||
[](https://kennethreitz.org) [](https://www.python.org/psf)
|
||||
|
||||
BIN
docs/_static/requests-sidebar.png
vendored
Normal file
BIN
docs/_static/requests-sidebar.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 299 KiB |
60
docs/_templates/sidebarintro.html
vendored
60
docs/_templates/sidebarintro.html
vendored
@@ -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>
|
||||
|
||||
53
docs/_templates/sidebarlogo.html
vendored
53
docs/_templates/sidebarlogo.html
vendored
@@ -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>
|
||||
|
||||
@@ -31,11 +31,11 @@ Exceptions
|
||||
.. autoexception:: requests.RequestException
|
||||
.. autoexception:: requests.ConnectionError
|
||||
.. autoexception:: requests.HTTPError
|
||||
.. autoexception:: requests.URLRequired
|
||||
.. autoexception:: requests.TooManyRedirects
|
||||
.. autoexception:: requests.ConnectTimeout
|
||||
.. autoexception:: requests.ReadTimeout
|
||||
.. autoexception:: requests.Timeout
|
||||
.. autoexception:: requests.JSONDecodeError
|
||||
|
||||
|
||||
Request Sessions
|
||||
@@ -127,7 +127,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 +156,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):
|
||||
...
|
||||
|
||||
|
||||
@@ -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,20 +55,18 @@ Chris Adams gave an excellent summary on
|
||||
Python 3 Support?
|
||||
-----------------
|
||||
|
||||
Yes! Requests officially supports Python 2.7 & 3.5+ and PyPy.
|
||||
Yes! Requests supports all `officially supported versions of Python <https://devguide.python.org/versions/>`_
|
||||
and recent releases of 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
|
||||
2.7 is no longer receiving bug fixes or security updates as of January 1, 2020.
|
||||
It is *highly* recommended users migrate to a supported Python 3.x version 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 +84,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
|
||||
|
||||
@@ -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>`_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,110 +1,5 @@
|
||||
Vulnerability Disclosure
|
||||
========================
|
||||
|
||||
If you think you have found a potential security vulnerability in requests,
|
||||
please email `Nate <mailto:nate.prewitt@gmail.com>`_ and `Seth <mailto:@sethmichaellarson@gmail.com>`_ directly. **Do not file a public issue.**
|
||||
|
||||
Our PGP Key fingerprints are:
|
||||
|
||||
- 8722 7E29 AD9C FF5C FAC3 EA6A 44D3 FF97 B80D C864 (`@nateprewitt <https://keybase.io/nateprewitt>`_)
|
||||
|
||||
- EDD5 6765 A9D8 4653 CBC8 A134 51B0 6736 1740 F5FC (`@sethmlarson <https://keybase.io/sethmlarson>`_)
|
||||
|
||||
You can also contact us on `Keybase <https://keybase.io/>`_ with the
|
||||
profiles above if desired.
|
||||
|
||||
If English is not your first language, please try to describe the problem and
|
||||
its impact to the best of your ability. For greater detail, please use your
|
||||
native language and we will try our best to translate it using online services.
|
||||
|
||||
Please also include the code you used to find the problem and the shortest
|
||||
amount of code necessary to reproduce it.
|
||||
|
||||
Please do not disclose this to anyone else. We will retrieve a CVE identifier
|
||||
if necessary and give you full credit under whatever name or alias you provide.
|
||||
We will only request an identifier when we have a fix and can publish it in a
|
||||
release.
|
||||
|
||||
We will respect your privacy and will only publicize your involvement if you
|
||||
grant us permission.
|
||||
|
||||
Process
|
||||
-------
|
||||
|
||||
This following information discusses the process the requests project follows
|
||||
in response to vulnerability disclosures. If you are disclosing a
|
||||
vulnerability, this section of the documentation lets you know how we will
|
||||
respond to your disclosure.
|
||||
|
||||
Timeline
|
||||
~~~~~~~~
|
||||
|
||||
When you report an issue, one of the project members will respond to you within
|
||||
two days *at the outside*. In most cases responses will be faster, usually
|
||||
within 12 hours. This initial response will at the very least confirm receipt
|
||||
of the report.
|
||||
|
||||
If we were able to rapidly reproduce the issue, the initial response will also
|
||||
contain confirmation of the issue. If we are not, we will often ask for more
|
||||
information about the reproduction scenario.
|
||||
|
||||
Our goal is to have a fix for any vulnerability released within two weeks of
|
||||
the initial disclosure. This may potentially involve shipping an interim
|
||||
release that simply disables function while a more mature fix can be prepared,
|
||||
but will in the vast majority of cases mean shipping a complete release as soon
|
||||
as possible.
|
||||
|
||||
Throughout the fix process we will keep you up to speed with how the fix is
|
||||
progressing. Once the fix is prepared, we will notify you that we believe we
|
||||
have a fix. Often we will ask you to confirm the fix resolves the problem in
|
||||
your environment, especially if we are not confident of our reproduction
|
||||
scenario.
|
||||
|
||||
At this point, we will prepare for the release. We will obtain a CVE number
|
||||
if one is required, providing you with full credit for the discovery. We will
|
||||
also decide on a planned release date, and let you know when it is. This
|
||||
release date will *always* be on a weekday.
|
||||
|
||||
At this point we will reach out to our major downstream packagers to notify
|
||||
them of an impending security-related patch so they can make arrangements. In
|
||||
addition, these packagers will be provided with the intended patch ahead of
|
||||
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)
|
||||
- Daniele Tricoli, Debian (@eriol)
|
||||
|
||||
We will notify these individuals at least a week ahead of our planned release
|
||||
date to ensure that they have sufficient time to prepare. If you believe you
|
||||
should be on this list, please let one of the maintainers know at one of the
|
||||
email addresses at the top of this article.
|
||||
|
||||
On release day, we will push the patch to our public repository, along with an
|
||||
updated changelog that describes the issue and credits you. We will then issue
|
||||
a PyPI release containing the patch.
|
||||
|
||||
At this point, we will publicise the release. This will involve mails to
|
||||
mailing lists, Tweets, and all other communication mechanisms available to the
|
||||
core team.
|
||||
|
||||
We will also explicitly mention which commits contain the fix to make it easier
|
||||
for other distributors and users to easily patch their own versions of requests
|
||||
if upgrading is not an option.
|
||||
|
||||
Previous CVEs
|
||||
-------------
|
||||
|
||||
- Fixed in 2.20.0
|
||||
- `CVE 2018-18074 <https://cve.mitre.org/cgi-bin/cvename.cgi?name=2018-18074>`_
|
||||
|
||||
- Fixed in 2.6.0
|
||||
|
||||
- `CVE 2015-2296 <https://cve.mitre.org/cgi-bin/cvename.cgi?name=2015-2296>`_,
|
||||
reported by Matthew Daley of `BugFuzz <https://bugfuzz.com/>`_.
|
||||
|
||||
- Fixed in 2.3.0
|
||||
|
||||
- `CVE 2014-1829 <https://cve.mitre.org/cgi-bin/cvename.cgi?name=2014-1829>`_
|
||||
|
||||
- `CVE 2014-1830 <https://cve.mitre.org/cgi-bin/cvename.cgi?name=2014-1830>`_
|
||||
The latest vulnerability disclosure information can be found on GitHub in our
|
||||
`Security Policy <https://github.com/psf/requests/blob/main/.github/SECURITY.md>`_.
|
||||
|
||||
@@ -58,7 +58,7 @@ master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u"Requests"
|
||||
copyright = u'MMXVIX. A <a href="https://kenreitz.org/projects">Kenneth Reitz</a> Project'
|
||||
copyright = u'MMXVIX. A Kenneth Reitz Project'
|
||||
author = u"Kenneth Reitz"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
|
||||
@@ -22,19 +22,17 @@ The guide is split into sections based on the type of contribution you're
|
||||
thinking of making, with a section that covers general guidelines for all
|
||||
contributors.
|
||||
|
||||
Be Cordial
|
||||
----------
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
**Be cordial or be on your way**. *—Kenneth Reitz*
|
||||
The Python community is made up of members from around the globe with a diverse
|
||||
set of skills, personalities, and experiences. It is through these differences
|
||||
that our community experiences great successes and continued growth. When you're
|
||||
working with members of the community, follow the
|
||||
`Python Software Foundation Code of Conduct`_ to help steer your interactions
|
||||
and keep Python a positive, successful, and growing community.
|
||||
|
||||
Requests has one very important rule governing all forms of contribution,
|
||||
including reporting bugs or requesting features. This golden rule is
|
||||
"`be cordial or be on your way`_".
|
||||
|
||||
**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
|
||||
.. _Python Software Foundation Code of Conduct: https://policies.python.org/python.org/code-of-conduct/
|
||||
|
||||
.. _early-feedback:
|
||||
|
||||
@@ -78,7 +76,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 +91,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 +116,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
|
||||
---------------------------
|
||||
|
||||
@@ -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.9+, and runs great on PyPy.
|
||||
|
||||
|
||||
The User Guide
|
||||
|
||||
3
docs/requirements.txt
Normal file
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# Pinning to avoid unexpected breakages.
|
||||
# Used by RTD to generate docs.
|
||||
Sphinx==7.2.6
|
||||
@@ -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
|
||||
^^^^^
|
||||
|
||||
@@ -669,7 +681,7 @@ You can get the dependencies for this feature from ``pip``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python -m pip install requests[socks]
|
||||
$ python -m pip install 'requests[socks]'
|
||||
|
||||
Once you've installed those dependencies, using a SOCKS proxy is just as easy
|
||||
as using a HTTP one::
|
||||
@@ -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'
|
||||
@@ -946,11 +969,9 @@ Requests will automatically parse these link headers and make them easily consum
|
||||
Transport Adapters
|
||||
------------------
|
||||
|
||||
As of v1.0.0, Requests has moved to a modular internal design. Part of the
|
||||
reason this was done was to implement Transport Adapters, originally
|
||||
`described here`_. Transport Adapters provide a mechanism to define interaction
|
||||
methods for an HTTP service. In particular, they allow you to apply per-service
|
||||
configuration.
|
||||
As of v1.0.0, Requests has moved to a modular internal design using Transport
|
||||
Adapters. These objects provide a mechanism to define interaction methods for an
|
||||
HTTP service. In particular, they allow you to apply per-service configuration.
|
||||
|
||||
Requests ships with a single Transport Adapter, the :class:`HTTPAdapter
|
||||
<requests.adapters.HTTPAdapter>`. This adapter provides the default Requests
|
||||
@@ -973,6 +994,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 +1030,29 @@ 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))
|
||||
|
||||
.. _`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 +1071,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 +1080,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 +1096,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 +1121,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
|
||||
|
||||
@@ -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
|
||||
@@ -43,6 +44,16 @@ set with `headers=`.
|
||||
If credentials for the hostname are found, the request is sent with HTTP Basic
|
||||
Auth.
|
||||
|
||||
Requests will search for the netrc file at `~/.netrc`, `~/_netrc`, or at the path
|
||||
specified by the `NETRC` environment variable. `~` denotes the user's home
|
||||
directory, which is `$HOME` on Unix based systems and `%USERPROFILE%` on Windows.
|
||||
|
||||
Usage of netrc file can be disabled by setting `trust_env` to `False` in the
|
||||
Requests session::
|
||||
|
||||
>>> s = requests.Session()
|
||||
>>> s.trust_env = False
|
||||
>>> s.get('https://httpbin.org/basic-auth/user/pass')
|
||||
|
||||
Digest Authentication
|
||||
---------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
--------------
|
||||
|
||||
@@ -215,6 +222,7 @@ Note: Custom headers are given less precedence than more specific sources of inf
|
||||
are specified in ``.netrc``, which in turn will be overridden by the ``auth=``
|
||||
parameter. Requests will search for the netrc file at `~/.netrc`, `~/_netrc`,
|
||||
or at the path specified by the `NETRC` environment variable.
|
||||
Check details in :ref:`netrc authentication <authentication>`.
|
||||
* Authorization headers will be removed if you get redirected off-host.
|
||||
* Proxy-Authorization headers will be overridden by proxy credentials provided in the URL.
|
||||
* Content-Length headers will be overridden when we can determine the length of the content.
|
||||
@@ -232,7 +240,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 +286,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 +300,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 +569,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
10
pyproject.toml
Normal 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"]
|
||||
@@ -1,3 +0,0 @@
|
||||
[pytest]
|
||||
addopts = -p no:warnings --doctest-modules
|
||||
doctest_optionflags= NORMALIZE_WHITESPACE ELLIPSIS
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
@@ -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,)
|
||||
119
requests/help.py
119
requests/help.py
@@ -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()
|
||||
@@ -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?
|
||||
@@ -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()
|
||||
@@ -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.1.0
|
||||
httpbin~=0.10.0
|
||||
trustme
|
||||
wheel
|
||||
|
||||
18
setup.cfg
18
setup.cfg
@@ -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
|
||||
|
||||
145
setup.py
145
setup.py
@@ -1,112 +1,107 @@
|
||||
#!/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, 9)
|
||||
|
||||
class PyTest(TestCommand):
|
||||
user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")]
|
||||
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.
|
||||
|
||||
def initialize_options(self):
|
||||
TestCommand.initialize_options(self)
|
||||
try:
|
||||
from multiprocessing import cpu_count
|
||||
self.pytest_args = ['-n', str(cpu_count()), '--boxed']
|
||||
except (ImportError, NotImplementedError):
|
||||
self.pytest_args = ['-n', '1', '--boxed']
|
||||
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)
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
self.test_args = []
|
||||
self.test_suite = True
|
||||
|
||||
def run_tests(self):
|
||||
import pytest
|
||||
|
||||
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.1.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.9",
|
||||
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.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"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},
|
||||
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
184
src/requests/__init__.py
Normal 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)
|
||||
14
src/requests/__version__.py
Normal file
14
src/requests/__version__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# .-. .-. .-. . . .-. .-. .-. .-.
|
||||
# |( |- |.| | | |- `-. | `-.
|
||||
# ' ' `-' `-`.`-' `-' `-' ' `-'
|
||||
|
||||
__title__ = "requests"
|
||||
__description__ = "Python HTTP for Humans."
|
||||
__url__ = "https://requests.readthedocs.io"
|
||||
__version__ = "2.32.5"
|
||||
__build__ = 0x023205
|
||||
__author__ = "Kenneth Reitz"
|
||||
__author_email__ = "me@kennethreitz.org"
|
||||
__license__ = "Apache-2.0"
|
||||
__copyright__ = "Copyright Kenneth Reitz"
|
||||
__cake__ = "\u2728 \U0001f370 \u2728"
|
||||
@@ -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
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.adapters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -9,57 +7,118 @@ and maintain connections.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import socket
|
||||
import socket # noqa: F401
|
||||
import typing
|
||||
import warnings
|
||||
|
||||
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 .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
|
||||
|
||||
|
||||
class BaseAdapter(object):
|
||||
def _urllib3_request_context(
|
||||
request: "PreparedRequest",
|
||||
verify: "bool | str | None",
|
||||
client_cert: "typing.Tuple[str, str] | str | None",
|
||||
poolmanager: "PoolManager",
|
||||
) -> "(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 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 +166,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 +189,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 +209,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 +232,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 +253,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 +262,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 +272,8 @@ class HTTPAdapter(BaseAdapter):
|
||||
num_pools=self._pool_connections,
|
||||
maxsize=self._pool_maxsize,
|
||||
block=self._pool_block,
|
||||
**proxy_kwargs)
|
||||
**proxy_kwargs,
|
||||
)
|
||||
|
||||
return manager
|
||||
|
||||
@@ -212,8 +289,7 @@ 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:
|
||||
cert_loc = None
|
||||
|
||||
# Allow self-specified cert location.
|
||||
@@ -224,17 +300,19 @@ class HTTPAdapter(BaseAdapter):
|
||||
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
|
||||
|
||||
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))
|
||||
raise OSError(
|
||||
f"Could not find a suitable TLS CA certificate bundle, "
|
||||
f"invalid path: {cert_loc}"
|
||||
)
|
||||
|
||||
conn.cert_reqs = 'CERT_REQUIRED'
|
||||
conn.cert_reqs = "CERT_REQUIRED"
|
||||
|
||||
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 +324,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 +346,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 +357,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,8 +370,110 @@ class HTTPAdapter(BaseAdapter):
|
||||
|
||||
return response
|
||||
|
||||
def build_connection_pool_key_attributes(self, request, verify, cert=None):
|
||||
"""Build the PoolKey attributes used by urllib3 to return a connection.
|
||||
|
||||
This looks at the PreparedRequest, the user-specified verify value,
|
||||
and the value of the cert parameter to determine what PoolKey values
|
||||
to use to select a connection from a given urllib3 Connection Pool.
|
||||
|
||||
The SSL related pool key arguments are not consistently set. As of
|
||||
this writing, use the following to determine what keys may be in that
|
||||
dictionary:
|
||||
|
||||
* If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the
|
||||
default Requests SSL Context
|
||||
* If ``verify`` is ``False``, ``"ssl_context"`` will not be set but
|
||||
``"cert_reqs"`` will be set
|
||||
* If ``verify`` is a string, (i.e., it is a user-specified trust bundle)
|
||||
``"ca_certs"`` will be set if the string is not a directory recognized
|
||||
by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be
|
||||
set.
|
||||
* If ``"cert"`` is specified, ``"cert_file"`` will always be set. If
|
||||
``"cert"`` is a tuple with a second item, ``"key_file"`` will also
|
||||
be present
|
||||
|
||||
To override these settings, one may subclass this class, call this
|
||||
method and use the above logic to change parameters as desired. For
|
||||
example, if one wishes to use a custom :py:class:`ssl.SSLContext` one
|
||||
must both set ``"ssl_context"`` and based on what else they require,
|
||||
alter the other keys to ensure the desired behaviour.
|
||||
|
||||
:param request:
|
||||
The PreparedReqest being sent over the connection.
|
||||
:type request:
|
||||
:class:`~requests.models.PreparedRequest`
|
||||
:param verify:
|
||||
Either a boolean, in which case it controls whether
|
||||
we verify the server's TLS certificate, or a string, in which case it
|
||||
must be a path to a CA bundle to use.
|
||||
:param cert:
|
||||
(optional) Any user-provided SSL certificate for client
|
||||
authentication (a.k.a., mTLS). This may be a string (i.e., just
|
||||
the path to a file which holds both certificate and key) or a
|
||||
tuple of length 2 with the certificate file path and key file
|
||||
path.
|
||||
:returns:
|
||||
A tuple of two dictionaries. The first is the "host parameters"
|
||||
portion of the Pool Key including scheme, hostname, and port. The
|
||||
second is a dictionary of SSLContext related parameters.
|
||||
"""
|
||||
return _urllib3_request_context(request, verify, cert, self.poolmanager)
|
||||
|
||||
def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
|
||||
"""Returns a urllib3 connection for the given request and TLS settings.
|
||||
This should not be called from user code, and is only exposed for use
|
||||
when subclassing the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
:param request:
|
||||
The :class:`PreparedRequest <PreparedRequest>` object to be sent
|
||||
over the connection.
|
||||
:param verify:
|
||||
Either a boolean, in which case it controls whether we verify the
|
||||
server's TLS certificate, or a string, in which case it must be a
|
||||
path to a CA bundle to use.
|
||||
:param proxies:
|
||||
(optional) The proxies dictionary to apply to the request.
|
||||
:param cert:
|
||||
(optional) Any user-provided SSL certificate to be used for client
|
||||
authentication (a.k.a., mTLS).
|
||||
:rtype:
|
||||
urllib3.ConnectionPool
|
||||
"""
|
||||
proxy = select_proxy(request.url, proxies)
|
||||
try:
|
||||
host_params, pool_kwargs = self.build_connection_pool_key_attributes(
|
||||
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
|
||||
"""DEPRECATED: Users should move to `get_connection_with_tls_context`
|
||||
for all subclasses of HTTPAdapter using Requests>=2.32.2.
|
||||
|
||||
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
|
||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||
|
||||
@@ -298,14 +481,25 @@ class HTTPAdapter(BaseAdapter):
|
||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||
:rtype: urllib3.ConnectionPool
|
||||
"""
|
||||
warnings.warn(
|
||||
(
|
||||
"`get_connection` has been deprecated in favor of "
|
||||
"`get_connection_with_tls_context`. Custom HTTPAdapter subclasses "
|
||||
"will need to migrate for Requests>=2.32.2. Please see "
|
||||
"https://github.com/psf/requests/pull/6710 for more details."
|
||||
),
|
||||
DeprecationWarning,
|
||||
)
|
||||
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 +537,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 +583,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 +607,55 @@ class HTTPAdapter(BaseAdapter):
|
||||
"""
|
||||
|
||||
try:
|
||||
conn = self.get_connection(request.url, proxies)
|
||||
conn = self.get_connection_with_tls_context(
|
||||
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 +688,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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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())
|
||||
106
src/requests/compat.py
Normal file
106
src/requests/compat.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
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
|
||||
|
||||
# -------
|
||||
# urllib3
|
||||
# -------
|
||||
from urllib3 import __version__ as urllib3_version
|
||||
|
||||
# Detect which major version of urllib3 is being used.
|
||||
try:
|
||||
is_urllib3_1 = int(urllib3_version.split(".")[0]) == 1
|
||||
except (TypeError, AttributeError):
|
||||
# If we can't discern a version, prefer old functionality.
|
||||
is_urllib3_1 = True
|
||||
|
||||
# -------------------
|
||||
# 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,)
|
||||
@@ -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)
|
||||
@@ -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
134
src/requests/help.py
Normal 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()
|
||||
@@ -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)
|
||||
@@ -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,75 +940,87 @@ 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
|
||||
|
||||
def json(self, **kwargs):
|
||||
r"""Returns the json-encoded content of a response, if any.
|
||||
r"""Decodes the JSON response body (if any) as a Python object.
|
||||
|
||||
This may return a dictionary, list, etc. depending on what is in the response.
|
||||
|
||||
: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 +1034,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
23
src/requests/packages.py
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -498,7 +535,7 @@ class Session(SessionRedirectMixin):
|
||||
for multipart encoding upload.
|
||||
:param auth: (optional) Auth tuple or callable to enable
|
||||
Basic/Digest/Custom HTTP Auth.
|
||||
:param timeout: (optional) How long to wait for the server to send
|
||||
:param timeout: (optional) How many seconds to wait for the server to send
|
||||
data before giving up, as a float, or a :ref:`(connect timeout,
|
||||
read timeout) <timeouts>` tuple.
|
||||
:type timeout: float or tuple
|
||||
@@ -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"""
|
||||
128
src/requests/status_codes.py
Normal file
128
src/requests/status_codes.py
Normal 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()
|
||||
@@ -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
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.utils
|
||||
~~~~~~~~~~~~~~
|
||||
@@ -21,49 +19,77 @@ 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,
|
||||
is_urllib3_1,
|
||||
)
|
||||
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 +97,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 +127,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 +137,47 @@ def super_len(o):
|
||||
total_length = None
|
||||
current_position = 0
|
||||
|
||||
if hasattr(o, '__len__'):
|
||||
if not is_urllib3_1 and isinstance(o, str):
|
||||
# urllib3 2.x+ treats all strings as utf-8 instead
|
||||
# of latin-1 (iso-8859-1) like http.client.
|
||||
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 +185,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 +195,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,26 +207,19 @@ 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
|
||||
|
||||
for f in netrc_locations:
|
||||
try:
|
||||
loc = os.path.expanduser(f)
|
||||
except KeyError:
|
||||
# os.path.expanduser can fail when $HOME is undefined and
|
||||
# getpwuid fails. See https://bugs.python.org/issue20164 &
|
||||
# https://github.com/psf/requests/issues/1846
|
||||
return
|
||||
|
||||
loc = os.path.expanduser(f)
|
||||
if os.path.exists(loc):
|
||||
netrc_path = loc
|
||||
break
|
||||
@@ -198,21 +229,15 @@ def get_netrc_auth(url, raise_errors=False):
|
||||
return
|
||||
|
||||
ri = urlparse(url)
|
||||
|
||||
# Strip port numbers from netloc. This weird `if...encode`` dance is
|
||||
# used for Python 3.2, which doesn't support unicode literals.
|
||||
splitstr = b':'
|
||||
if isinstance(url, str):
|
||||
splitstr = splitstr.decode('ascii')
|
||||
host = ri.netloc.split(splitstr)[0]
|
||||
host = ri.hostname
|
||||
|
||||
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 +250,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 +269,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 +284,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 +327,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 +353,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 +418,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 +449,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 +461,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 +481,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 +509,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 +521,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 +533,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 +574,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 +590,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 +612,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 +629,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 +677,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 +691,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 +701,7 @@ def is_ipv4_address(string_ip):
|
||||
"""
|
||||
try:
|
||||
socket.inet_aton(string_ip)
|
||||
except socket.error:
|
||||
except OSError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -666,9 +712,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 +722,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 +758,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 +778,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 +792,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 +800,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 +834,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 +851,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 +911,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 +939,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 +953,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 +982,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 +1016,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 +1063,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.")
|
||||
@@ -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
10
tests/certs/README.md
Normal 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
|
||||
13
tests/certs/expired/Makefile
Normal file
13
tests/certs/expired/Makefile
Normal 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
|
||||
11
tests/certs/expired/README.md
Normal file
11
tests/certs/expired/README.md
Normal 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
|
||||
```
|
||||
13
tests/certs/expired/ca/Makefile
Normal file
13
tests/certs/expired/ca/Makefile
Normal 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
|
||||
28
tests/certs/expired/ca/ca-private.key
Normal file
28
tests/certs/expired/ca/ca-private.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCfZUh82dF/r9GW
|
||||
89IN2vqOiMMuikIAO3SEI3+uSGCdWT13C+NnrFJ7XF/D6UGy1mvm8KfhSnapWoAk
|
||||
toyPXSc/GNzJzCwZool7xE+rm/0vbu1XbUcQcqB8nQRLzTChDIGuuD8DHs7bmen1
|
||||
9sT5kZy0CIqac383cQxR8W1Fs48xEBJfuBBmyl+bz4ugPci96H4DIuAD2QvP2KKg
|
||||
Gqs4yyDPSmf86k9+okOsLMQVwGnHety+TPJawCn2aCXl+rmMTSCH2sUEc81cXaVQ
|
||||
Yxyf6HaqGncCs1O2MzeRbPugEzb5K4ZVM4NYtDMkxrQnZFCALf4XOma1uv5Kh6Qo
|
||||
FMFHOA6tAgMBAAECgf9YadXLawbJzLx0/smE5fIVHccmCYqSlmgK46XvBjaREO8H
|
||||
GZEJ8IvP4W09PiUzDbzMXLDCRouLZKevtZJB82nRlfjh9l5/2aho/nsytVO6+8yq
|
||||
sfK5LNvYQ0Aey7ItosJMJ+bL1ErphHZB+J9Jz3scYaCAC5CFMC+lREVYZEEI9QD4
|
||||
P2D5QbmaSeu8jmL/H3fWHjNXWDprue3W/MIf96NZa3qJew45go4TAYYMe5i757KW
|
||||
Ja40VNfmsgbz4uI9oDXaYL/NkWUaQP1lnh+Mfrm1YnBe2wsLcP/WuM5h0bYzJW/1
|
||||
ZeSrZM3fqCMW6SJyrVE1qzqvtw1xQBlrq0B6q0ECgYEA0fi4+ySFGR+mL6k5UjP1
|
||||
roREqQgKaLgdhOvD88EnO93Nl6tJ3Qk8LyzPUNbxe1/xTUEKMtglBKOoxCHJJZlg
|
||||
xXnKBAQUtlmrLFKIGe+UCD+r+wfSpS6Sl7BUDmeCSczG9dPN5vnyZA4ixUke2SCC
|
||||
k4Eb9Q0AHyNnbXv928r0sfkCgYEAwlZRYmGTVva6cY2YEmMrqbWy4Wxm2Zmdo+Uq
|
||||
Xu1RZF9a3tGzNbGsyYdeLNY7vVZoVOm1paMJCA8ScNLFtCux2jEPqwqd1OZ8OLhA
|
||||
1VF3/kYtUSdqwLzWoS1RdD6mZCAHeOE+N0pone4lt3A2o8wtpHsaDA+XSTw2rHLR
|
||||
LVS+b1UCgYEAtezJ4Ze31pfMdrkpmCa69JVXpBj6Y9c6hGN+aWFuq/k22/WmTuRk
|
||||
h/9MNR+3JQ1w1l3HB1ytXkKqxBz92hz1csReG3Kpu4EfxYxQriAdY7Q/P4Z8pXAf
|
||||
xVwayEw439aUgIQef8UKllSFHeiH2NrJKCKSZZT5CQG06HCo+Fn1/4kCgYAYuwtY
|
||||
TbqGUpefY7l6fYxM6IZ/EWB1SIs7FCq0MdctwsS5nk4EAzxN2SAu7IRlr91PEP7A
|
||||
uWKo1+Is4WWva/ASKDQqPAuh0EL2pNv7SYbPoPabYTzAkkdt82puNJrQGxNYWrGk
|
||||
L5/omSnLkkghyBX23IOQDVvfQf5jK6la73HckQKBgAI+iLECAkle9HvnJ3flicau
|
||||
9FAU1/9pOdM+WogSanhYQ/P2rAwRiyCIkqu62/OoZR5g4kLxWqOOmVvsK3j+gs5F
|
||||
FtwN7gauq06MAHnWr6qC8ZltzMsGZTVDvqSH2vgV4T1V6ovVpTBPKQ1gWtABEmpm
|
||||
dyfeA6HHeRAHx8VRGpL6
|
||||
-----END PRIVATE KEY-----
|
||||
17
tests/certs/expired/ca/ca.cnf
Normal file
17
tests/certs/expired/ca/ca.cnf
Normal file
@@ -0,0 +1,17 @@
|
||||
[req]
|
||||
default_bits = 2048
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
encrypt_key = no
|
||||
distinguished_name = dn
|
||||
x509_extensions = v3_ca
|
||||
|
||||
[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
|
||||
|
||||
[v3_ca]
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = critical, cRLSign, digitalSignature, keyCertSign
|
||||
22
tests/certs/expired/ca/ca.crt
Normal file
22
tests/certs/expired/ca/ca.crt
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDpDCCAoygAwIBAgIUQt0yyZmppkHKNx4aXRrmD5tvjbswDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
|
||||
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
|
||||
U2lnbmVkIFJvb3QgQ0EwHhcNMjUwMzI5MTM1MTQ1WhcNNDUwMzI0MTM1MTQ1WjBq
|
||||
MQswCQYDVQQGEwJVUzEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRp
|
||||
b24xGDAWBgNVBAsMD3B5dGhvbi1yZXF1ZXN0czEcMBoGA1UEAwwTU2VsZi1TaWdu
|
||||
ZWQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ9lSHzZ
|
||||
0X+v0Zbz0g3a+o6Iwy6KQgA7dIQjf65IYJ1ZPXcL42esUntcX8PpQbLWa+bwp+FK
|
||||
dqlagCS2jI9dJz8Y3MnMLBmiiXvET6ub/S9u7VdtRxByoHydBEvNMKEMga64PwMe
|
||||
ztuZ6fX2xPmRnLQIippzfzdxDFHxbUWzjzEQEl+4EGbKX5vPi6A9yL3ofgMi4APZ
|
||||
C8/YoqAaqzjLIM9KZ/zqT36iQ6wsxBXAacd63L5M8lrAKfZoJeX6uYxNIIfaxQRz
|
||||
zVxdpVBjHJ/odqoadwKzU7YzN5Fs+6ATNvkrhlUzg1i0MyTGtCdkUIAt/hc6ZrW6
|
||||
/kqHpCgUwUc4Dq0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
|
||||
BAMCAYYwHQYDVR0OBBYEFAhGiD3+10LBrdMW3+j/ceXMXSqcMA0GCSqGSIb3DQEB
|
||||
CwUAA4IBAQBRT21cyZ0Jx0JLA2ilYTLvpMsSryGyWrCOXlmRlBt1MAhmxdTRgCmu
|
||||
UB3UU2pfnrC16UeMVVS411lhzjowFXkXrjAqSUBRcetUIYHlpnGgDdUl4dV/X5kx
|
||||
HxD9VUBx/QwGeyzFhjzjeN89M2v9kPnhU/kkVfcsafwYiHdC6pwN6zeZNz7JP+GS
|
||||
rmI+KVpm5C+Nz6ekm3TR8rFgPIsiDTbY3qj/DNYX2+NhpU1DZfm687vhOr3Ekljx
|
||||
NHNu9++STEjGpirrI8EqQnK+FP2fRJ5D82YZM0d++8tmHKpY0+FRCr8//459sgun
|
||||
CojmhIobDa2NuF81Jx6Cc7lagCPG3/Ts
|
||||
-----END CERTIFICATE-----
|
||||
1
tests/certs/expired/ca/ca.srl
Normal file
1
tests/certs/expired/ca/ca.srl
Normal file
@@ -0,0 +1 @@
|
||||
4F36C3A7E075BA6452D10EEB81E7F189FF489B83
|
||||
16
tests/certs/expired/server/Makefile
Normal file
16
tests/certs/expired/server/Makefile
Normal 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.*
|
||||
24
tests/certs/expired/server/cert.cnf
Normal file
24
tests/certs/expired/server/cert.cnf
Normal 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
|
||||
19
tests/certs/expired/server/server.csr
Normal file
19
tests/certs/expired/server/server.csr
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIDHjCCAgYCAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
|
||||
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
|
||||
cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDcRRwk8IU1YoNu8CHzB5Vh8HP/yLfBtU69LLZq+7rDG31JlR5s
|
||||
lmcwLLoZ8opUQ5rg8JMRZ7toh5zB4Uc0B4Sg8RhQMSOZYBIJkXdHuqQkciR0vWnN
|
||||
vD/5CkWEhnj4dxE7xTbDufBlxmwAthC/u72UIsZHavAyLCBqsONa7xuTiogz/d+3
|
||||
G+525JrfVr05hhJpT4Ypx5YY+ABkIOuOk/XuudbGm5SquuX6BgjmgaGtDjAuE/Rn
|
||||
BnliIavCDrG0v3KGbG3Xxt7lFTu+98foForwGCbbQBraty27oZrzAtLltfIHlJRn
|
||||
jPz0JA9akNDhAihxEsTUhk2d7jFszsd0Ev7DAgMBAAGgbDBqBgkqhkiG9w0BCQ4x
|
||||
XTBbMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
|
||||
MCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEAVVIxJlDrgeG0bOSufgVVRqDQx7XSd15mGlT+CynM
|
||||
lEFJ3Q9k98T2vRNNGVYYYZAnbdSOW9ACwWGcYm2bzIjbgZV0H2Kz0dLD/GrNuEY+
|
||||
O9j6K2toFKc57G7UUkve+N74ldq+hkR4zbb6FQmTlnL2YaPp2dv5TxdMKfHEfPNf
|
||||
Bg8xpbXdoRc7CYW1ZACme+d2U063GVqQsrIfwGJ+BtE6aNo62T/oEm+G4Wy5iBay
|
||||
jNv/imwf+JKQ75bTvha9YLUg2scqdYwJj8JlBw7cvkBIHW8GydA3fX4dtV9YBbFi
|
||||
8RTlWhhLgCXpYbLoDGOqF6f/MuPSIGkV1wVhCUfYA+p+Gw==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
28
tests/certs/expired/server/server.key
Normal file
28
tests/certs/expired/server/server.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcRRwk8IU1YoNu
|
||||
8CHzB5Vh8HP/yLfBtU69LLZq+7rDG31JlR5slmcwLLoZ8opUQ5rg8JMRZ7toh5zB
|
||||
4Uc0B4Sg8RhQMSOZYBIJkXdHuqQkciR0vWnNvD/5CkWEhnj4dxE7xTbDufBlxmwA
|
||||
thC/u72UIsZHavAyLCBqsONa7xuTiogz/d+3G+525JrfVr05hhJpT4Ypx5YY+ABk
|
||||
IOuOk/XuudbGm5SquuX6BgjmgaGtDjAuE/RnBnliIavCDrG0v3KGbG3Xxt7lFTu+
|
||||
98foForwGCbbQBraty27oZrzAtLltfIHlJRnjPz0JA9akNDhAihxEsTUhk2d7jFs
|
||||
zsd0Ev7DAgMBAAECggEAUZjCX8a/ufJ3+OE42lQdWO4fsonS1I3LENYe70u4OC2X
|
||||
QGpenmAq8pQnDpSj/KocliZYfLKeII9YGRRQcaw1S/9z/8TsSJVnqSa7dpVj1+J2
|
||||
sc43AxEw65sL/Jdp+bT169vXOTNIpBMYkDzhwH0WMemd5Pfu6c8h5RQI7Pc1knYn
|
||||
nPNY848qSYCWOUjZS3QmBik/gp9X//yxVCyvxB3xVnb1cpvc952D90Va6nFIWfgN
|
||||
ix4NgFgAvwIxCFpWI2z7JF8uBdAHPeFAx8pFukQpAzwhaEILlgt3WbvEob9CsdP5
|
||||
E39SUkzxiIfVM1du+hRquk9SJ/X56OSLnEjxLarSIQKBgQDyVU00yrv7lG7TJLZ5
|
||||
YyfE20A7eUtYdcfatTKfsxKO4KVV+p0mGsNnFhhKziuT5/xtVunbUNFEOaUfmTUP
|
||||
dCjy4XkDbE/ufxzIm1WTSqknUluVoJRWKmLI5gbDFxu/XGRQNLxQbMK54l3N69PT
|
||||
EO4kz/jqBYbd4aEmtaSx2R8JowKBgQDosUTgBGDxDE2ODPVoXc+YjReOmVHvGMKK
|
||||
tA+KDBEs4spxaqhH0OFh6mewx3jA4VHbcIgM50STXrq1VL/QqYclDlY9/VWzkmqp
|
||||
2Ekc4NoAl3H022pgcbmXx3qapC4Z3hokNFOlbtD9xQf9NMx6c5djTKMYTBrBBWXH
|
||||
oFhSz6PIYQKBgQCMGqkydmvMffq89CLTd3JMq/4s5GmdUSsk1VHZZuy50kOEvAoT
|
||||
N7H1bZ7J0Pz83Eji5jb6Z3U1nqZK6Ib20k/CbH1Mb1ifKLp5eOU27Rly9Hiiv15D
|
||||
munWALe0Hy4Zqs8MWBDv5pGGasuU/F1RUB5/BgaBNoTMz2AeQzJe6Iq7RQKBgCca
|
||||
Ku3OLpAzNhEp4k9wfEMxaoT/BMK+EWsHiRj0oCo/zi8y8iZnVoiCwHv3eTZIZt4O
|
||||
Uf6BGof9QjjYjgc9hcVXXGy8Vpt/fkceXmLo8hlpWbAA8yZT1hFIZzT3Y/va09/D
|
||||
n07MiXgrlQUay0XEiOsZ5Mpfd5t6EblzG4SG+gnhAoGAZ+shbacxhji7cUUYJLrO
|
||||
9uZJCDCZiyq8yZ+lMzX1kILQwaP6VmSvF4TzKrkrCuD2HzUYcqebko/TvckeTp/a
|
||||
oYC2put3zt0CHBf/keeeJwjhff19qVyE9mpZwoo7PuS5zmM7pQOLxzCyAM9MdsCz
|
||||
kmnbborcfh74fkfRcwXm6G8=
|
||||
-----END PRIVATE KEY-----
|
||||
44
tests/certs/expired/server/server.pem
Normal file
44
tests/certs/expired/server/server.pem
Normal file
@@ -0,0 +1,44 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDpzCCAo+gAwIBAgIUTzbDp+B1umRS0Q7rgefxif9Im4EwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
|
||||
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
|
||||
U2lnbmVkIFJvb3QgQ0EwHhcNMjUwMjE3MDAzODIyWhcNMjUwMjE3MDAzODIyWjBt
|
||||
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCREUxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0
|
||||
d2FyZSBGb3VuZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxEjAQBgNV
|
||||
BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxF
|
||||
HCTwhTVig27wIfMHlWHwc//It8G1Tr0stmr7usMbfUmVHmyWZzAsuhnyilRDmuDw
|
||||
kxFnu2iHnMHhRzQHhKDxGFAxI5lgEgmRd0e6pCRyJHS9ac28P/kKRYSGePh3ETvF
|
||||
NsO58GXGbAC2EL+7vZQixkdq8DIsIGqw41rvG5OKiDP937cb7nbkmt9WvTmGEmlP
|
||||
hinHlhj4AGQg646T9e651sablKq65foGCOaBoa0OMC4T9GcGeWIhq8IOsbS/coZs
|
||||
bdfG3uUVO773x+gWivAYJttAGtq3LbuhmvMC0uW18geUlGeM/PQkD1qQ0OECKHES
|
||||
xNSGTZ3uMWzOx3QS/sMCAwEAAaNCMEAwHQYDVR0OBBYEFFK1WmMqRSzUD2isk8TL
|
||||
M3JfsVczMB8GA1UdIwQYMBaAFAhGiD3+10LBrdMW3+j/ceXMXSqcMA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQCekjxplL/AI32WtODgw7FpTXNXdyNy8PWEhn3ufL0MqiyseYOZ
|
||||
bIa0PAecsArlKs5hXJzB7p/hu5CZdvaCButw1jyWQBySCpeJXn3FmGdTkBvhwBHv
|
||||
y6npmBoy/nbLkIRNRcoLbALlfn/0iGWfmDTRblT7vRNWJmZCZCTA/+ILXJ36ItbF
|
||||
3JCs3ARF6XWORuZs5Y8cNloOy2brAC/4EHnTfOZBQf8cL8CfHlcNa+nbG1j+wVNO
|
||||
60e/0v9zTxa2wNzdnLBCW4rqJFJ44aGClxat5tWuypv0snA/0xrqIYWTQGXZPVT6
|
||||
rET47dfVbj1QxmW3sAyuy5PskZA9T7pOhcjR
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDlDCCAnygAwIBAgIUG/CTOPIQbH2BI36TyThUChQyR8wwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
|
||||
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
|
||||
U2lnbmVkIFJvb3QgQ0EwHhcNMjUwMjE3MDAzODIyWhcNNDUwMjEyMDAzODIyWjBq
|
||||
MQswCQYDVQQGEwJVUzEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRp
|
||||
b24xGDAWBgNVBAsMD3B5dGhvbi1yZXF1ZXN0czEcMBoGA1UEAwwTU2VsZi1TaWdu
|
||||
ZWQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ9lSHzZ
|
||||
0X+v0Zbz0g3a+o6Iwy6KQgA7dIQjf65IYJ1ZPXcL42esUntcX8PpQbLWa+bwp+FK
|
||||
dqlagCS2jI9dJz8Y3MnMLBmiiXvET6ub/S9u7VdtRxByoHydBEvNMKEMga64PwMe
|
||||
ztuZ6fX2xPmRnLQIippzfzdxDFHxbUWzjzEQEl+4EGbKX5vPi6A9yL3ofgMi4APZ
|
||||
C8/YoqAaqzjLIM9KZ/zqT36iQ6wsxBXAacd63L5M8lrAKfZoJeX6uYxNIIfaxQRz
|
||||
zVxdpVBjHJ/odqoadwKzU7YzN5Fs+6ATNvkrhlUzg1i0MyTGtCdkUIAt/hc6ZrW6
|
||||
/kqHpCgUwUc4Dq0CAwEAAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
|
||||
CEaIPf7XQsGt0xbf6P9x5cxdKpwwDQYJKoZIhvcNAQELBQADggEBAHMgyQNA3DQG
|
||||
0l9eX8RMl4YNwhAXChU/wOTcvD+F4OsJcPqzy6QHFh1AbBBGOI9/HN7du+EiKwGZ
|
||||
wE+69FEsSbhSv22XidPm+kHPTguWl1eKveeWrrT5MPl9F48s14lKb/8yMEa1/ryG
|
||||
Iu8NQ6ZL91JbTXdkLoBDya9HZqyXjwcGkXLE8fSqTibJ7EhWS5Q3Ic7WPgUoGAum
|
||||
b5ygoxqhm+SEyXC2/LAktwmFawkv1SsMeYpT790VIFqJ/TVVnUl+gQ2RjSEl2WLb
|
||||
UO4Hwq4FZbWF9NrY6JVThLmbcr8eW6+UxWfiXHLw/qTRre4/3367QAUQRt7EuEsb
|
||||
KOWpOS3fbsI=
|
||||
-----END CERTIFICATE-----
|
||||
7
tests/certs/mtls/Makefile
Normal file
7
tests/certs/mtls/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
.PHONY: all clean
|
||||
|
||||
all:
|
||||
make -C client all
|
||||
|
||||
clean:
|
||||
make -C client clean
|
||||
4
tests/certs/mtls/README.md
Normal file
4
tests/certs/mtls/README.md
Normal 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.
|
||||
16
tests/certs/mtls/client/Makefile
Normal file
16
tests/certs/mtls/client/Makefile
Normal 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
1
tests/certs/mtls/client/ca
Symbolic link
@@ -0,0 +1 @@
|
||||
../../expired/ca/
|
||||
26
tests/certs/mtls/client/cert.cnf
Normal file
26
tests/certs/mtls/client/cert.cnf
Normal 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
|
||||
24
tests/certs/mtls/client/client.csr
Normal file
24
tests/certs/mtls/client/client.csr
Normal file
@@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIEGjCCAwICAQAwbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
|
||||
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
|
||||
cXVlc3RzMREwDwYDVQQDDAhyZXF1ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBAJrdvu5kvCy5g6w67gczETk/u6K0UBmf6Mv0lqXCp3Voyt5fv6SI
|
||||
/OPWrFV1+m4imNe5bLM7JUoUfkqkmULsjTTh7sxVLjW226vLSYOWWy7PA+OSwUTN
|
||||
LjydF1dazlPedHPYdmhVShCmyoOd1pUCDQn0/4cEA/WW4mtzZImmoCf/pyAM3XAC
|
||||
9RBcSSJRywOTe6n9LY6Ko0YUW94ay2M4ClVblDxswDemaAsLFuciKmi53gKx4H/l
|
||||
areo8p60dgubooiMbcc4E9bzp0oJpfh7xhwKeJtCNEpOik1AiiQIZtwqmkkIHvtI
|
||||
Go3SZ7WAQU9Eh2r+u3E6aSl+N0PMXK4Y4JsCAwEAAaCCAWcwggFjBgkqhkiG9w0B
|
||||
CQ4xggFUMIIBUDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggr
|
||||
BgEFBQcDAjCCAR8GA1UdEQSCARYwggESggsqLmxvY2FsaG9zdIcEfwAAAYcQAAAA
|
||||
AAAAAAAAAAAAAAAAAYZNc3BpZmZlOi8vdHJ1c3QucHl0aG9uLm9yZy92MC9tYWlu
|
||||
dGFpbmVyL3NpZ21hdmlydXMyNC9wcm9qZWN0L3JlcXVlc3RzL29yZy9wc2aGTXNw
|
||||
aWZmZTovL3RydXN0LnB5dGhvbi5vcmcvdjEvbWFpbnRhaW5lcjpzaWdtYXZpcnVz
|
||||
MjQvcHJvamVjdDpyZXF1ZXN0cy9vcmc6cHNmhk1zcGlmZmU6Ly90cnVzdC5weXRo
|
||||
b24ub3JnL3YxL21haW50YWluZXI9c2lnbWF2aXJ1czI0L3Byb2plY3Q9cmVxdWVz
|
||||
dHMvb3JnPXBzZjANBgkqhkiG9w0BAQsFAAOCAQEAMOwYPyq+OpMRQUAgoRjfxTQO
|
||||
DiqfBzCsjUsPAacLTtebbWBx8y6TkLSb+/Qn3amq3ESo4iBqKpmVwdlAS4P486GD
|
||||
9f78W3zkZ29jGcnQ+XHb7WvPvzBRoXImE276F9JGqJ+9q39Cbxzh0U2+ofBx2iGY
|
||||
sSutzU0B/l/FKZRc8thuFoeKqHwVePLGD9p2+2nYI9I08QoGqEokTcFAq0tZ858F
|
||||
9PdxBZYOKOMpnLZhiJ8qZo23v3ycBXPOjg5ILtQ9EzHoNEA5Mxx/mfNLKJ0NktZD
|
||||
KXANLWKbXm+w9gTcBLtCPWNeqj5DIGPsHSfq/Bmjbp/o+uOJs6oq3s5rA7WrAA==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
28
tests/certs/mtls/client/client.key
Normal file
28
tests/certs/mtls/client/client.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCa3b7uZLwsuYOs
|
||||
Ou4HMxE5P7uitFAZn+jL9Jalwqd1aMreX7+kiPzj1qxVdfpuIpjXuWyzOyVKFH5K
|
||||
pJlC7I004e7MVS41ttury0mDllsuzwPjksFEzS48nRdXWs5T3nRz2HZoVUoQpsqD
|
||||
ndaVAg0J9P+HBAP1luJrc2SJpqAn/6cgDN1wAvUQXEkiUcsDk3up/S2OiqNGFFve
|
||||
GstjOApVW5Q8bMA3pmgLCxbnIipoud4CseB/5Wq3qPKetHYLm6KIjG3HOBPW86dK
|
||||
CaX4e8YcCnibQjRKTopNQIokCGbcKppJCB77SBqN0me1gEFPRIdq/rtxOmkpfjdD
|
||||
zFyuGOCbAgMBAAECggEABHa5xyNOLTfXrMIyFDELoQvOO71YxbRPQHm3UeXPb9nq
|
||||
Zwh5fKOaLnMEmp4A7uW+ZBFrKatdwsnebgZaiIxK8ahFesxFvbScllIQt2NBE5NR
|
||||
+GBFg9cqKwMYJiNu6Qnzb1dg6lbzAJHeKncFNVxOxeey6dBa0NxdgF1eG32bBiwT
|
||||
yIr6JO7cK5JfcExls5yxdZMZiW08quaeFMQR6Wocod2caDgJUBVbZwivEoNI3ak4
|
||||
/kzQaxvRoDMM8uN3LrVH2YUJgURUxSbpOLu8ycZtfg8JRArprUYI0P0OD6iDlly1
|
||||
3FhJXqBEY0dWQPmZKP/Elt3ywC1oncW+AqPh9cchiQKBgQDMriOKoBrTmplw0kbo
|
||||
EEuxTU1A23BqcZfn2+PoUww6UxOvStL/s7iupjl3g4CXw0RgIfyOuJQldLdq7mh6
|
||||
W7BpK855fH2CLy1VMdIFY1umHSgJ5Bayd5NmyKdfORUw88PTFFSARYyr5DDqufWg
|
||||
jI77BMjqHtbyujQKspV+9U66ZwKBgQDBsi5YptfaqMdzh343G5zAtCiGCd3m9NZO
|
||||
atEEvgq9LKqEVwvJ/FD+3QPAS1MN+ms9KcfJ3G05VUhhEsPGRKibgcE0FNA/VBDO
|
||||
jS2HK6kZ1M0clC5kHmQfLZxp1q3tA5nW6zqjVFdqYzJsis7G5YxFKhnzui0lgP6V
|
||||
I1Io+3QvrQKBgQCK6D+sq92o8AnkfICsq6qDCKA+PO68/pyGOUAiAoKQ7qK0W0Z5
|
||||
TMIwnRTxHCjgViAIUehx/6hjByQXiPcU2zcNGTLGVgtjl5rfb7FGANlJEg6DL+2L
|
||||
bwV1QwX75OSR1U136hsy9oByg6oDEvM040+B4gxsf0OHdYEuJWa5w8eLTwKBgCJt
|
||||
CM+416SFWu2tp0EkJzgYzRsFpermmTBWy8+L91yoE6Zx0iaUMdEadxA2Uwyo9WZp
|
||||
hpjaFI+cGMEoFKOokE8TQMOA74JR7qrHbNAZcnSk3c+2hohE3oasFKC7By6Y9T69
|
||||
kC53TxIZj1y7TwUKx2ODmBk5fcysoJLhNDkUeBIBAoGAQ/YMeMN/pSvTDCPhubbk
|
||||
9bF0SwoN6DWrzw7gzMMhHlVSPM4fjdBA+wXbhnYF7hiNan7uShtRHVhwyfj+glBz
|
||||
KIkDxETjxTWx5mbo3tNf8xEMsbHZBe8KPlKxizdAOvShLvkpthTCPiyqn6XTvbs4
|
||||
vC0zZrsQWCYT4Wbm7gcOOAU=
|
||||
-----END PRIVATE KEY-----
|
||||
44
tests/certs/mtls/client/client.pem
Normal file
44
tests/certs/mtls/client/client.pem
Normal file
@@ -0,0 +1,44 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDpjCCAo6gAwIBAgIUTzbDp+B1umRS0Q7rgefxif9Im4IwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
|
||||
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
|
||||
U2lnbmVkIFJvb3QgQ0EwHhcNMjUwMjE3MDAzODIzWhcNMjcwMjE3MDAzODIzWjBs
|
||||
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCREUxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0
|
||||
d2FyZSBGb3VuZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxETAPBgNV
|
||||
BAMMCHJlcXVlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmt2+
|
||||
7mS8LLmDrDruBzMROT+7orRQGZ/oy/SWpcKndWjK3l+/pIj849asVXX6biKY17ls
|
||||
szslShR+SqSZQuyNNOHuzFUuNbbbq8tJg5ZbLs8D45LBRM0uPJ0XV1rOU950c9h2
|
||||
aFVKEKbKg53WlQINCfT/hwQD9Zbia3NkiaagJ/+nIAzdcAL1EFxJIlHLA5N7qf0t
|
||||
joqjRhRb3hrLYzgKVVuUPGzAN6ZoCwsW5yIqaLneArHgf+Vqt6jynrR2C5uiiIxt
|
||||
xzgT1vOnSgml+HvGHAp4m0I0Sk6KTUCKJAhm3CqaSQge+0gajdJntYBBT0SHav67
|
||||
cTppKX43Q8xcrhjgmwIDAQABo0IwQDAdBgNVHQ4EFgQU3Ujw+VSuTzPgHqU+KFwO
|
||||
T4t2MHswHwYDVR0jBBgwFoAUCEaIPf7XQsGt0xbf6P9x5cxdKpwwDQYJKoZIhvcN
|
||||
AQELBQADggEBAEvrKXWHRRDIf26j2fH9hanx0nh+lxvI4jSWYmK0rJXZA3htEvWn
|
||||
gcoUspmhmLlgmRMP88lGINMjTsogUubu2j6WF/WuKxAWWvl/hUgK8NzOwOHvByPB
|
||||
lhO/rSNGdOWGlnaW1TVO4kI8w6c6LwzOCpY8WvOZLW+v7duLhKUdhaJMR9X77Tbt
|
||||
ohHkyYm0gV79izaFRpA6mdYoyHOR4gyWAKaj942doU794fT4gqQacRNifl/kUbWI
|
||||
lilktTLyLnmBJgrxHVBxcGe8kwnPafA1k3Gb1w7mY5BSoGKglKjutTfqR3uTjAQb
|
||||
vskS4SGXmsEIjLrXYWFNHyoC3pCBXWhX6Kc=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDlDCCAnygAwIBAgIUG/CTOPIQbH2BI36TyThUChQyR8wwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
|
||||
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
|
||||
U2lnbmVkIFJvb3QgQ0EwHhcNMjUwMjE3MDAzODIyWhcNNDUwMjEyMDAzODIyWjBq
|
||||
MQswCQYDVQQGEwJVUzEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRp
|
||||
b24xGDAWBgNVBAsMD3B5dGhvbi1yZXF1ZXN0czEcMBoGA1UEAwwTU2VsZi1TaWdu
|
||||
ZWQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ9lSHzZ
|
||||
0X+v0Zbz0g3a+o6Iwy6KQgA7dIQjf65IYJ1ZPXcL42esUntcX8PpQbLWa+bwp+FK
|
||||
dqlagCS2jI9dJz8Y3MnMLBmiiXvET6ub/S9u7VdtRxByoHydBEvNMKEMga64PwMe
|
||||
ztuZ6fX2xPmRnLQIippzfzdxDFHxbUWzjzEQEl+4EGbKX5vPi6A9yL3ofgMi4APZ
|
||||
C8/YoqAaqzjLIM9KZ/zqT36iQ6wsxBXAacd63L5M8lrAKfZoJeX6uYxNIIfaxQRz
|
||||
zVxdpVBjHJ/odqoadwKzU7YzN5Fs+6ATNvkrhlUzg1i0MyTGtCdkUIAt/hc6ZrW6
|
||||
/kqHpCgUwUc4Dq0CAwEAAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
|
||||
CEaIPf7XQsGt0xbf6P9x5cxdKpwwDQYJKoZIhvcNAQELBQADggEBAHMgyQNA3DQG
|
||||
0l9eX8RMl4YNwhAXChU/wOTcvD+F4OsJcPqzy6QHFh1AbBBGOI9/HN7du+EiKwGZ
|
||||
wE+69FEsSbhSv22XidPm+kHPTguWl1eKveeWrrT5MPl9F48s14lKb/8yMEa1/ryG
|
||||
Iu8NQ6ZL91JbTXdkLoBDya9HZqyXjwcGkXLE8fSqTibJ7EhWS5Q3Ic7WPgUoGAum
|
||||
b5ygoxqhm+SEyXC2/LAktwmFawkv1SsMeYpT790VIFqJ/TVVnUl+gQ2RjSEl2WLb
|
||||
UO4Hwq4FZbWF9NrY6JVThLmbcr8eW6+UxWfiXHLw/qTRre4/3367QAUQRt7EuEsb
|
||||
KOWpOS3fbsI=
|
||||
-----END CERTIFICATE-----
|
||||
1
tests/certs/valid/ca
Symbolic link
1
tests/certs/valid/ca
Symbolic link
@@ -0,0 +1 @@
|
||||
../expired/ca
|
||||
16
tests/certs/valid/server/Makefile
Normal file
16
tests/certs/valid/server/Makefile
Normal 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.*
|
||||
31
tests/certs/valid/server/cert.cnf
Normal file
31
tests/certs/valid/server/cert.cnf
Normal 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
|
||||
19
tests/certs/valid/server/server.csr
Normal file
19
tests/certs/valid/server/server.csr
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIDKjCCAhICAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
|
||||
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
|
||||
cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDD3OzTz9TgOiJp/STy/au8G1EDVUuP3HWKAX5ZpkWSTYZfc7FF
|
||||
pgPQvBq83oh/K1+9Rw0/529N1+KeTp1i9vUBUM2wJ3EzykJP4rMFh+J/Nt6VFfJh
|
||||
05pTQiHnc85l4U8Qz7fHS6Lc9EG/Yp6yDxt5OeK8QAkNYjvxVhVdpif3GlnICx1e
|
||||
y1EcPxb9rslERyz0eiL6+BtVbhlSvup/rz2skvaYxoh/pP1RVwbu8VtS0it046Fm
|
||||
U0TnIdsjrdFjHXNJ2JSs5g6FDB9QEFhYdDOonL4KMcAkXxBLYXpjJ5fj8/X4LFkU
|
||||
FINry+Q2zdFKYsghCxlW98hmJVJTscVWznqpAgMBAAGgeDB2BgkqhkiG9w0BCQ4x
|
||||
aTBnMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBYGA1UdJQEB/wQMMAoG
|
||||
CCsGAQUFBwMBMC8GA1UdEQEB/wQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAA
|
||||
AAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAn8TdRWYAGL7L8JKpkX2FRMMY
|
||||
EuFOEeJ2UaQLRbVtb1RhkIkMT6UYulQ16K45dbFe4k8DT59/mHrTl0bJ6pqVPawa
|
||||
vo7vmKCrSYEz8GrImh1+i6lHaFiqbkAs1mJ6z86dZUofMYwfTPVh64ztamUqwBk7
|
||||
Dey+MzWUQvpTVqoaP7cLgzMy+XfcyGuWSoL6pX5XKkt4+A2wiCTsHul1sXOn4vQz
|
||||
4xY96AUVnkKosXMWXvhbMkncLNH+gs+ZeQI0MekakuMa42N+0BAad3Zx60lifG3N
|
||||
ugyVUmvVI0Fq6QUlYp3YV6QQj3FDjbgOVsSJNh+2s4SIARJsb/k//Tddzxei7g==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
28
tests/certs/valid/server/server.key
Normal file
28
tests/certs/valid/server/server.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDD3OzTz9TgOiJp
|
||||
/STy/au8G1EDVUuP3HWKAX5ZpkWSTYZfc7FFpgPQvBq83oh/K1+9Rw0/529N1+Ke
|
||||
Tp1i9vUBUM2wJ3EzykJP4rMFh+J/Nt6VFfJh05pTQiHnc85l4U8Qz7fHS6Lc9EG/
|
||||
Yp6yDxt5OeK8QAkNYjvxVhVdpif3GlnICx1ey1EcPxb9rslERyz0eiL6+BtVbhlS
|
||||
vup/rz2skvaYxoh/pP1RVwbu8VtS0it046FmU0TnIdsjrdFjHXNJ2JSs5g6FDB9Q
|
||||
EFhYdDOonL4KMcAkXxBLYXpjJ5fj8/X4LFkUFINry+Q2zdFKYsghCxlW98hmJVJT
|
||||
scVWznqpAgMBAAECggEACY98I+6uJm/QBDpuFkpZmqn+r1n3gUMynZTrFPcvyC9u
|
||||
krQ0AAFViFfWOkfmg8abOsMAG5FxdmxGTJHrzsvdM748/A9A0FVcHUgkku2KGcmU
|
||||
3dQfa7UHgG7m9sRJW+G+mUR6ZQkFHyHxH6Vxt6FTJvyzW5sIlhWodWRNUK/unXoe
|
||||
5QNIh/PM9Mkg91wKVeg8AGdY27zfuUV0KLwhV052hCSwckayi7pjYrH2xDL8fahi
|
||||
Z/F43N1SI6Q7PTW6izDs2KrlGyexn97jOV+cooSXXaDvivTaB2K4RoidrPt+UAW4
|
||||
/e1zw/LbeBvQXX4vsyC9vLO+Av1gYo2PxAZ/ezc5gQKBgQDvY/kgUud6stdMs73N
|
||||
Qavu9o/Ul4oeQa4fsmfUwj43OjquxpXbfV9sNTLFxFbsl5ADGkpqsR3WhP2090TK
|
||||
62vkyCBiaYZjyMY6WLDATEvrpfbc74ATEzTU+r9T1rEByq/atbWPZq/zoMo8aAdg
|
||||
Qk2X7gQO3pTpPeEpetYG42Di+QKBgQDRc9IqPeELQugXEuI0RZ8rsfGRSFKVRelQ
|
||||
Rz/KpD/S8SsE7ERL0w/w5KgE0IPPbh/SYX2KYxafCqTrPWDNaoguROvUoUJQvf4a
|
||||
uOMTCRkqdqj8j70zaPj2ohuIIZ3GZHyaXgl/isWtuZ5OeaoY5h3r+4gk5mO41Llz
|
||||
YikB71SRMQKBgGBNG18BetVFNI9Kj0QO8xeCYIHpJErfqShfIJ3aNiUJa6n7gTV2
|
||||
zfg9vlsIjN9IaUqWPPGGprYxcc5m2mm3IwQ57a0pPkLN9dBq9U+mYbQ+Y3ylbCRA
|
||||
SbST2nvjlflejDezeYJikM21FSYPw0fZ5FUGDuPcbpMVrYp+O7MxrTwhAoGAcjQq
|
||||
xemTiWZj0iDzwfisP1D5HHRIwyepfaI7wCwquMPS5w5EduuQZ5LloipnlHTBWR7b
|
||||
Kte4f+N35OREofySYFgoFnoPBKNzp/Jjrf9p/2NP5NYjHaMBDMl7JZDezEwCPNFF
|
||||
cIukGYN6M+PWwVjHu+Ica7JLcX5b1/QP1ARBIiECgYAsdDa0CoYTVWMeqMq9GjF3
|
||||
BElBKCLp6iYqpElWyKTj39LCnLhzRICyYQpblM8zZgtQIvRGT8DYh2HA1Q29yPzt
|
||||
Rgrz9yfFACdT5gUM5D6sx7L37xbtMQQ7YYOxEAfCUZ0qnMJgpNTJ8/ECkjGJZaif
|
||||
CXrx6XCdvrM/evZW2JZbyg==
|
||||
-----END PRIVATE KEY-----
|
||||
46
tests/certs/valid/server/server.pem
Normal file
46
tests/certs/valid/server/server.pem
Normal file
@@ -0,0 +1,46 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEEDCCAvigAwIBAgIUTzbDp+B1umRS0Q7rgefxif9Im4MwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
|
||||
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
|
||||
U2lnbmVkIFJvb3QgQ0EwHhcNMjUwMjE3MDAzODIzWhcNNDQxMTA0MDAzODIzWjBt
|
||||
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCREUxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0
|
||||
d2FyZSBGb3VuZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxEjAQBgNV
|
||||
BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPc
|
||||
7NPP1OA6Imn9JPL9q7wbUQNVS4/cdYoBflmmRZJNhl9zsUWmA9C8GrzeiH8rX71H
|
||||
DT/nb03X4p5OnWL29QFQzbAncTPKQk/iswWH4n823pUV8mHTmlNCIedzzmXhTxDP
|
||||
t8dLotz0Qb9inrIPG3k54rxACQ1iO/FWFV2mJ/caWcgLHV7LURw/Fv2uyURHLPR6
|
||||
Ivr4G1VuGVK+6n+vPayS9pjGiH+k/VFXBu7xW1LSK3TjoWZTROch2yOt0WMdc0nY
|
||||
lKzmDoUMH1AQWFh0M6icvgoxwCRfEEthemMnl+Pz9fgsWRQUg2vL5DbN0UpiyCEL
|
||||
GVb3yGYlUlOxxVbOeqkCAwEAAaOBqjCBpzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB
|
||||
/wQEAwIFoDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDATAvBgNVHREBAf8EJTAjggls
|
||||
b2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwHQYDVR0OBBYEFGg5Q9Ll
|
||||
ADkONmo02kmPg9+aiOxQMB8GA1UdIwQYMBaAFAhGiD3+10LBrdMW3+j/ceXMXSqc
|
||||
MA0GCSqGSIb3DQEBCwUAA4IBAQA1RlUI34tXrIdsRiALD8iLDFhh816B7qUi+F1j
|
||||
3dOkTNgYw0CnQ+Vm4wrQjCEVSDQ/9sry5DOfXeGziDpFlZmQ0UuAeM1EJJD5/42l
|
||||
C/eKquA09+IMEq2U03GPhijrC68sFCfr5wgoB/4HmcZ1c3kLYvRaJhEK7AHDgenY
|
||||
knKbvuKAiRbCd584eG8HFOW7xv+YqGZ257Ic9flbarkPrazmAJg2P709w7/fP83x
|
||||
7lnkPZY09jS3lcIpEdtWvzfm+anGF190hKA1yfPdO5bYPvUcEMxXdMtQ6/pbZd5i
|
||||
F6t95o9CPCqI/lLIn5jAf9Z+Iil3GmKefEZYIGmKJ85HGUqE
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDlDCCAnygAwIBAgIUG/CTOPIQbH2BI36TyThUChQyR8wwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVVMxIzAhBgNVBAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3Vu
|
||||
ZGF0aW9uMRgwFgYDVQQLDA9weXRob24tcmVxdWVzdHMxHDAaBgNVBAMME1NlbGYt
|
||||
U2lnbmVkIFJvb3QgQ0EwHhcNMjUwMjE3MDAzODIyWhcNNDUwMjEyMDAzODIyWjBq
|
||||
MQswCQYDVQQGEwJVUzEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRp
|
||||
b24xGDAWBgNVBAsMD3B5dGhvbi1yZXF1ZXN0czEcMBoGA1UEAwwTU2VsZi1TaWdu
|
||||
ZWQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ9lSHzZ
|
||||
0X+v0Zbz0g3a+o6Iwy6KQgA7dIQjf65IYJ1ZPXcL42esUntcX8PpQbLWa+bwp+FK
|
||||
dqlagCS2jI9dJz8Y3MnMLBmiiXvET6ub/S9u7VdtRxByoHydBEvNMKEMga64PwMe
|
||||
ztuZ6fX2xPmRnLQIippzfzdxDFHxbUWzjzEQEl+4EGbKX5vPi6A9yL3ofgMi4APZ
|
||||
C8/YoqAaqzjLIM9KZ/zqT36iQ6wsxBXAacd63L5M8lrAKfZoJeX6uYxNIIfaxQRz
|
||||
zVxdpVBjHJ/odqoadwKzU7YzN5Fs+6ATNvkrhlUzg1i0MyTGtCdkUIAt/hc6ZrW6
|
||||
/kqHpCgUwUc4Dq0CAwEAAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
|
||||
CEaIPf7XQsGt0xbf6P9x5cxdKpwwDQYJKoZIhvcNAQELBQADggEBAHMgyQNA3DQG
|
||||
0l9eX8RMl4YNwhAXChU/wOTcvD+F4OsJcPqzy6QHFh1AbBBGOI9/HN7du+EiKwGZ
|
||||
wE+69FEsSbhSv22XidPm+kHPTguWl1eKveeWrrT5MPl9F48s14lKb/8yMEa1/ryG
|
||||
Iu8NQ6ZL91JbTXdkLoBDya9HZqyXjwcGkXLE8fSqTibJ7EhWS5Q3Ic7WPgUoGAum
|
||||
b5ygoxqhm+SEyXC2/LAktwmFawkv1SsMeYpT790VIFqJ/TVVnUl+gQ2RjSEl2WLb
|
||||
UO4Hwq4FZbWF9NrY6JVThLmbcr8eW6+UxWfiXHLw/qTRre4/3367QAUQRt7EuEsb
|
||||
KOWpOS3fbsI=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -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
|
||||
|
||||
@@ -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
8
tests/test_adapters.py
Normal 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, {})
|
||||
@@ -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"}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user