Compare commits

...

211 Commits

Author SHA1 Message Date
dependabot[bot]
dae7ef63b4 Bump https://github.com/astral-sh/ruff-pre-commit (#7616)
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.14t) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14t) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.14t) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
Type Check / typecheck (3.10) (push) Has been cancelled
Type Check / typecheck (3.14) (push) Has been cancelled
GitHub Actions Security Analysis with zizmor 🌈 / zizmor (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.16.3 to 0.16.4
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.16.3...v0.16.4)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.16.4
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 12:15:52 -06:00
Nate Prewitt
5460f467b0 Update lock-issues.yml to latest dependencies (#7609) 2026-08-24 18:45:57 -06:00
dependabot[bot]
6af0b94158 Bump https://github.com/astral-sh/ruff-pre-commit (#7608)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.16.2 to 0.16.3
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.16.2...v0.16.3)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.16.3
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 11:33:15 -06:00
dependabot[bot]
8f8b212de8 Bump https://github.com/astral-sh/ruff-pre-commit (#7606)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.16.1 to 0.16.2
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.16.1...v0.16.2)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.16.2
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 13:03:13 -06:00
dependabot[bot]
8068356288 Bump https://github.com/astral-sh/ruff-pre-commit (#7603)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.16.0 to 0.16.1
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.16.0...v0.16.1)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.16.1
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 11:14:26 -05:00
dependabot[bot]
1f6589ec3a Bump https://github.com/astral-sh/ruff-pre-commit (#7598)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.22 to 0.16.0
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.22...v0.16.0)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.16.0
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 11:52:44 -06:00
dependabot[bot]
414f0513c3 Bump the actions group with 2 updates (#7596)
Bumps the actions group with 2 updates: [actions/setup-python](https://github.com/actions/setup-python) and [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action).


Updates `actions/setup-python` from 6.3.0 to 7.0.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](ece7cb06ca...5fda3b95a4)

Updates `zizmorcore/zizmor-action` from 0.5.2 to 0.6.0
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](71321a20a9...6599ee8b7a)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 11:25:09 -06:00
dependabot[bot]
ded32878c0 Bump https://github.com/astral-sh/ruff-pre-commit (#7597)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.21 to 0.15.22
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.21...v0.15.22)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.22
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 11:18:18 -06:00
dependabot[bot]
69f8484704 Bump the actions group with 3 updates (#7591)
Bumps the actions group with 3 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.36.0 to 4.37.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](7211b7c807...99df26d4f1)

Updates `github/codeql-action/autobuild` from 4.36.0 to 4.37.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](7211b7c807...99df26d4f1)

Updates `github/codeql-action/analyze` from 4.36.0 to 4.37.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](7211b7c807...99df26d4f1)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 12:41:16 -06:00
dependabot[bot]
b17c61b618 Bump https://github.com/astral-sh/ruff-pre-commit (#7590)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.20 to 0.15.21
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.20...v0.15.21)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.21
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 11:55:26 -06:00
dependabot[bot]
f361ead047 Bump https://github.com/astral-sh/ruff-pre-commit (#7562)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.18 to 0.15.20
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.18...v0.15.20)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.20
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 08:12:40 -07:00
Yamac
d38495c906 Fix link to AI Policy in CONTRIBUTING.md (#7576) 2026-07-09 08:10:39 -07:00
dependabot[bot]
4c800e9aea Bump actions/setup-python from 6.2.0 to 6.3.0 in the actions group (#7561)
Bumps the actions group with 1 update: [actions/setup-python](https://github.com/actions/setup-python).


Updates `actions/setup-python` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](a309ff8b42...ece7cb06ca)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 11:32:29 -06:00
dependabot[bot]
23953c0c87 Bump https://github.com/astral-sh/ruff-pre-commit (#7551)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.17 to 0.15.18
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.17...v0.15.18)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.18
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 10:37:19 -07:00
dependabot[bot]
4ed3d1b320 Bump actions/checkout from 6.0.2 to 7.0.0 in the actions group (#7540)
Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6.0.2 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](de0fac2e45...9c091bb21b)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 15:47:15 -07:00
dependabot[bot]
d44b67eb55 Bump https://github.com/astral-sh/ruff-pre-commit (#7536)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.16 to 0.15.17
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.16...v0.15.17)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.17
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 15:40:29 -07:00
Nate Prewitt
74c56d5ffb Inline pre-commit linting (#7539) 2026-06-24 15:40:12 -07:00
dependabot[bot]
d64b9ad4bf Bump https://github.com/astral-sh/ruff-pre-commit (#7515)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.15 to 0.15.16
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.15...v0.15.16)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.16
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 11:12:28 -07:00
Nate Prewitt
6f66281a1d Add hasattr checks for remaining protocol isinstance checks (#7505) 2026-06-09 08:45:59 -07:00
Loïc Pernod
6f205ff422 Fix _encode_files detection for __getattr__-based file wrappers (#7502) 2026-06-08 13:52:51 -07:00
Joren Hammudoglu
661970d171 Disable commonly ignored Pyright linting rules (#7497) 2026-06-08 10:33:26 -07:00
dependabot[bot]
fbd0eb3732 Bump https://github.com/astral-sh/ruff-pre-commit (#7503)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.14 to 0.15.15
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.14...v0.15.15)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.15
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-08 10:04:24 -07:00
Joren Hammudoglu
1190afd14f Add type annotation for Request.hooks (#7498) 2026-06-04 14:43:21 -07:00
Joren Hammudoglu
e50e594529 Improve static typing of 3rd party imports (#7496)
* Add missing `typecheck` dependencies

* Fix static typing of `compat.chardet`

* Remove unused `# type: ignore` comments
2026-06-04 12:20:33 -05:00
dependabot[bot]
3be097d450 Bump https://github.com/astral-sh/ruff-pre-commit (#7493)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.12 to 0.15.14
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.12...v0.15.14)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.14
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-02 17:45:34 -07:00
dependabot[bot]
a634611dd1 Bump github/codeql-action from 4.35.1 to 4.36.0 in the actions group (#7492)
Bumps the actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 4.35.1 to 4.36.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](c10b8064de...7211b7c807)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-02 17:44:54 -07:00
Ian Stapleton Cordasco
c4367f231b Add AI Policy
The immense influx of low-quality pull requests, many of which are
duplicated tens of times, are creating unnecessary toil. As such, we now
need to spell out what used to be basic, decent, and reasonable open
source behaviour because many are just using LLMs to spam and reputation
farm instead of trying to make meaningful contributions.
2026-05-31 08:35:04 -05:00
aldbr
cd90742ed9 docs: indent continuation line in Session.get :param params: (#7460) 2026-05-18 08:02:35 -07:00
Nate Prewitt
6e83187b8f v2.34.2
Some checks failed
Publish to PyPI / Build dists (push) Has been cancelled
Publish to PyPI / Publish (push) Has been cancelled
Publish to PyPI / Publish to Test PyPI (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.14t) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14t) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.14t) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
Type Check / typecheck (3.10) (push) Has been cancelled
Type Check / typecheck (3.14) (push) Has been cancelled
2026-05-14 12:21:52 -07:00
Nate Prewitt
84d10f0be8 Move Request.headers back to Mapping (#7441) 2026-05-14 09:16:25 -07:00
Nate Prewitt
b7b549b545 v2.34.1
Some checks failed
Publish to PyPI / Build dists (push) Has been cancelled
Publish to PyPI / Publish (push) Has been cancelled
Publish to PyPI / Publish to Test PyPI (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.14t) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14t) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.14t) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
Type Check / typecheck (3.10) (push) Has been cancelled
Type Check / typecheck (3.14) (push) Has been cancelled
2026-05-13 12:17:24 -07:00
Nate Prewitt
e511bc7277 Fix mutability issues with headers input types (#7431) 2026-05-13 12:16:50 -07:00
Nate Prewitt
5691f59613 Update JsonType containers to read-based collections (#7436) 2026-05-13 08:43:16 -06:00
Nate Prewitt
2144213c30 Constrain Response.reason to str (#7437) 2026-05-13 08:42:42 -06:00
Kaeun Kim
6404f345e5 Fix prepare_body stream detection for __getattr__-based file wrappers (#7433)
---------

Co-authored-by: Nate Prewitt <nate.prewitt@gmail.com>
2026-05-12 10:51:01 -07:00
Nate Prewitt
0b401c76b6 v2.34.0
Some checks failed
Publish to PyPI / Build dists (push) Has been cancelled
Publish to PyPI / Publish (push) Has been cancelled
Publish to PyPI / Publish to Test PyPI (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.14t) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14t) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.14t) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
Type Check / typecheck (3.10) (push) Has been cancelled
Type Check / typecheck (3.14) (push) Has been cancelled
2026-05-11 12:24:57 -07:00
Nate Prewitt
86b378d3f6 Align Session.get parameters with requests.get (#7429) 2026-05-11 12:24:34 -07:00
Nate Prewitt
a4f9a5999b Port bpo-39057 to Requests (#7427) 2026-05-11 12:24:21 -07:00
Nate Prewitt
3816cfa1ab Parameterize SupportsItems to handle Mapping key invariance (#7426) 2026-05-11 10:33:17 -07:00
Sebastian Meyer
b684dcb9bb sessions: fix hooks type (#7425) 2026-05-11 09:14:13 -07:00
Nate Prewitt
dc9dbdfb34 Formalize 3.15 support (#7422) 2026-05-10 19:05:34 -07:00
Nate Prewitt
25340ebad0 Clear proxy env vars before every test run (#7423) 2026-05-10 19:05:15 -07:00
typhon8
fd628095d7 Preserve leading slashes in request path_url (#7315)
---------

Co-authored-by: Nate Prewitt <nate.prewitt@gmail.com>
2026-05-10 14:41:25 -07:00
eggsort
e8d2c015ee docs: Fix missing hook output in docs example (#7421) 2026-05-09 12:51:50 -06:00
Nate Prewitt
eb173bc819 Add 3.14t support to CI (#7419) 2026-05-08 13:18:00 -07:00
Nate Prewitt
d60f4773b1 Cleanup remaining http:// reference URIs 2026-05-07 16:27:46 -06:00
Joshua Kothapalle
f64a0d782b docs: update stale Sphinx link in contributing guide 2026-05-07 16:27:46 -06:00
dependabot[bot]
04d750509b Bump https://github.com/astral-sh/ruff-pre-commit (#7411)
Bumps the pre-commit group with 1 update: [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit).


Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.15.11 to 0.15.12
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.11...v0.15.12)

---
updated-dependencies:
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.15.12
  dependency-type: direct:production
  dependency-group: pre-commit
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 14:42:06 -07:00
Nate Prewitt
f7822f7c41 v2.34.0.dev1
Some checks failed
Lint code / lint (push) Has been cancelled
Publish to PyPI / Build dists (push) Has been cancelled
Publish to PyPI / Publish (push) Has been cancelled
Publish to PyPI / Publish to Test PyPI (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
Type Check / typecheck (3.10) (push) Has been cancelled
Type Check / typecheck (3.14) (push) Has been cancelled
2026-05-03 12:51:27 -07:00
Nate Prewitt
561e4b6889 Add inline types to Requests (#7272) 2026-05-03 12:38:48 -07:00
Nate Prewitt
8f6cda9969 Add tests for hasattr/getattr usage on LookupDict (#7401) 2026-04-30 10:05:37 -07:00
Dimitri Papadopoulos Orfanos
27e0981962 Fix remaining typos (#7395) 2026-04-30 07:35:13 -07:00
Dimitri Papadopoulos Orfanos
6ec76b4a36 Ruff rule UP038 has been removed (#7393) 2026-04-27 08:08:18 -07:00
dependabot[bot]
f43f750ee1 Bump https://github.com/astral-sh/ruff-pre-commit (#7390) 2026-04-24 08:19:25 -06:00
Nate Prewitt
9450dd51fb Create groups for GHA and pre-commit dependabot PRs (#7387) 2026-04-24 05:52:01 -05:00
Nate Prewitt
93bf5331a7 Update index.rst to match README info (#7386) 2026-04-23 13:53:48 -07:00
dependabot[bot]
79f4df84cf Bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0 (#7378)
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](ed0c53931b...cef221092e)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-version: 1.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-20 12:32:32 -07:00
Dejan SANADER
b294b08fe3 Consider urllib3 version has 3 numbers at most (#7375) (#7376) 2026-04-20 09:24:32 -07:00
Christian Clauss
5f3ff9b9e4 Fix typos discovered by codespell (#7371) 2026-04-19 11:34:20 -07:00
Nate Prewitt
514c1623fe Update README and remove extraneous images (#7366) 2026-04-15 11:30:57 -07:00
Chris Riches
d2f6bdecc8 Clarify decode_unicode behaviour in iter_{content,lines} docstrings (#7365) 2026-04-15 08:06:46 -07:00
Nate Prewitt
a044b020de Move DigestAuth hash algorithms to use usedforsecurity=False (#7310) 2026-04-15 07:56:38 -07:00
Nate Prewitt
16df2a0917 Move pytest pin to support 9.x series (#7364) 2026-04-14 20:04:46 -07:00
Nate Prewitt
fe2063be0c Don't hide navigation on mobile webpage (#7360) 2026-04-14 10:58:28 -07:00
Nate Prewitt
4b0b1a3e9f Update pre-commit versions (#7348)
* Update pre-commit versions

* Add dependabot entry for pre-commit

* Update pre-commit and ruff hooks
2026-04-11 19:05:37 -05:00
Nate Prewitt
185f587a78 Cleanup docs and add i18n wrappers (#7354) 2026-04-11 13:39:24 -06:00
Nate Prewitt
ef439eb779 Prevent Response self-reference in redirect history (#7328) 2026-04-05 08:31:02 -07:00
Nate Prewitt
cbce031327 Add Zizmor workflow and make initial updates 2026-03-31 21:28:16 -05:00
dependabot[bot]
6360477c52 Bump github/codeql-action from 4.34.1 to 4.35.1
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.34.1 to 4.35.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3869755554...c10b8064de)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 12:33:02 -06:00
Nate Prewitt
111d2b7779 v2.33.1
Some checks failed
Lint code / lint (push) Has been cancelled
Publish to PyPI / Build dists (push) Has been cancelled
Publish to PyPI / Publish (push) Has been cancelled
Publish to PyPI / Publish to Test PyPI (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
2026-03-30 09:07:00 -07:00
Nate Prewitt
f0198e6dfc Fix malformed value parsing for Content-Type (#7309) 2026-03-30 08:27:35 -07:00
Nate Prewitt
bc7dd0fc4d Fix cosmetic header validity parsing regex (#7308) 2026-03-30 08:24:49 -07:00
Nate Prewitt
4443b1a847 Fix unintended test extra (#7306) 2026-03-30 08:23:00 -07:00
Nate Prewitt
389eea58df Cleanup extracted file after extract_zipped_path test (#7305) 2026-03-30 08:21:38 -07:00
Frank Dana
7407309c8a Packaging: DRY out extras definition (#7277) 2026-03-29 21:07:31 -07:00
Nate Prewitt
bc04dfd6da v2.33.0
Some checks failed
Lint code / lint (push) Has been cancelled
Publish to PyPI / Build dists (push) Has been cancelled
Publish to PyPI / Publish (push) Has been cancelled
Publish to PyPI / Publish to Test PyPI (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14) (push) Has been cancelled
Tests / build (macOS-latest, 3.15-dev) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.15-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14) (push) Has been cancelled
Tests / build (windows-latest, 3.15-dev) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
2026-03-25 08:06:17 -07:00
Nate Prewitt
66d21cb07b Merge commit from fork 2026-03-25 07:57:56 -07:00
Nate Prewitt
8b9bc8fc0f Move badges to top of README (#7293) 2026-03-25 05:18:38 +00:00
Nate Prewitt
e331a288f3 Remove unused extraction call (#7292) 2026-03-24 22:15:21 -07:00
rohan436
753fd08c5e docs: fix FAQ grammar in httplib2 example
Signed-off-by: rohan436 <rohan.santhoshkumar@googlemail.com>
2026-03-24 19:36:48 -06:00
Magsen (CD)
774a0b837a docs(socks): same block as other sections
strange formatting:
https://docs.python-requests.org/en/latest/user/advanced/#socks
2026-03-24 19:26:47 -06:00
dependabot[bot]
9c72a41bec Bump github/codeql-action from 4.33.0 to 4.34.1
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.33.0 to 4.34.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b1bff81932...3869755554)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 11:43:09 -06:00
dependabot[bot]
ebf7190679 Bump github/codeql-action from 4.32.0 to 4.33.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.0 to 4.33.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b20883b0cd...b1bff81932)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 12:44:23 -06:00
Dr Alex Mitre
0e4ae38f0c docs: exclude Response.is_permanent_redirect from API docs (#7244)
Hide the internal Response.is_permanent_redirect attribute from public API documentation.
2026-03-05 13:14:29 -07:00
Ethan Price
d568f47278 docs: clarify Quickstart POST example (#6960)
(fixes #6949)

---------

Co-authored-by: Nate Prewitt <nate.prewitt@gmail.com>
2026-03-03 12:59:47 -07:00
Dan Blanchard
b2a1d33f57 Increase chardet upper limit to 8
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:46:41 -07:00
dependabot[bot]
392b01f26e Bump actions/download-artifact from 7.0.0 to 8.0.0
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](37930b1c2a...70fc10c6e5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 13:45:19 -07:00
dependabot[bot]
27f8b40d5c Bump actions/upload-artifact from 6.0.0 to 7.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](b7c566a772...bbbca2ddaa)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 13:33:25 -07:00
Amin Vakil
4bd79e3973 Increase chardet upper limit to 7 2026-02-22 16:02:37 -06:00
Nate Prewitt
da9113c046 Add CODEOWNERS file to repo (#7208) 2026-02-13 18:06:57 -06:00
Nate Prewitt
a2ce3d8ace Remove harden runner from publish workflow (#7207) 2026-02-13 16:48:20 -06:00
Nate Prewitt
5e7cb71cd7 Bump version to dev for test publish 2026-02-13 13:23:44 -07:00
Nate Prewitt
00600b38f4 Update publish workflow and add test pypi option 2026-02-13 13:23:44 -07:00
m0d3v
5f338446f9 Update docstring for Session.verify to include string support (#6859) (#6865)
---------

Co-authored-by: Nate Prewitt <nate.prewitt@gmail.com>
2026-02-12 19:52:04 -07:00
Nate Prewitt
47914226c2 Fix empty netrc entry usage (#7205) 2026-02-12 19:07:05 -07:00
Nate Prewitt
1b40fdd004 Merge pull request #7200 from psf/dependabot/github_actions/step-security/harden-runner-2.14.2
Bump step-security/harden-runner from 2.13.0 to 2.14.2
2026-02-10 17:29:35 -07:00
dependabot[bot]
514b3f2345 Bump step-security/harden-runner from 2.13.0 to 2.14.2
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.0 to 2.14.2.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](ec9f2d5744...5ef0c079ce)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.14.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 00:23:00 +00:00
Nate Prewitt
dedee0f456 Merge pull request #7199 from psf/dependabot/github_actions/dessant/lock-threads-6.0.0
Bump dessant/lock-threads from 5.0.0 to 6.0.0
2026-02-10 17:22:16 -07:00
dependabot[bot]
7112775514 Bump dessant/lock-threads from 5.0.0 to 6.0.0
Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md)
- [Commits](d42e5f4980...7266a7ce5c)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-10 23:59:55 +00:00
Nate Prewitt
733b2018a3 Update floors for certifi and urllib3 (#7189) 2026-02-10 16:36:31 -07:00
Nate Prewitt
0c2bbe7398 Drop support for Python 3.9 (#7196) 2026-02-06 08:02:46 -07:00
Nate Prewitt
32e26af3af Start testing Python 3.15 Alpha (#7195)
* Add optional 3.15-dev runners to test matrix

* Set PYO3_USE_ABI3_FORWARD_COMPATIBILITY for 3.15 builds
2026-02-04 19:01:16 -06:00
Nate Prewitt
421b8733cf Merge pull request #7101 from psf/dependabot/github_actions/github/codeql-action-4.31.6
Bump github/codeql-action from 4.30.8 to 4.31.6
2026-01-30 20:01:53 -07:00
dependabot[bot]
968863678b Bump github/codeql-action from 4.30.8 to 4.31.6
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.30.8 to 4.31.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f443b600d9...fe4161a26a)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-30 20:00:02 -07:00
Nate Prewitt
0b950d8e97 Merge pull request #7100 from psf/dependabot/github_actions/actions/setup-python-6.1.0
Bump actions/setup-python from 6.0.0 to 6.1.0
2026-01-30 19:59:20 -07:00
dependabot[bot]
42eaeb4da8 Bump actions/setup-python from 6.0.0 to 6.1.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](e797f83bcb...83679a892e)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-30 19:53:54 -07:00
Nate Prewitt
49f8968867 Merge pull request #7092 from psf/dependabot/github_actions/actions/checkout-6.0.0
Bump actions/checkout from 5.0.0 to 6.0.0
2026-01-30 19:47:47 -07:00
dependabot[bot]
8e398336f8 Bump actions/checkout from 5.0.0 to 6.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](08c6903cd8...1af3b93b68)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-30 19:46:03 -07:00
Nate Prewitt
5f33bddabd Merge pull request #7073 from psf/dependabot/github_actions/actions/download-artifact-6.0.0
Bump actions/download-artifact from 5.0.0 to 6.0.0
2026-01-30 19:45:13 -07:00
dependabot[bot]
ca78ca9611 Bump actions/download-artifact from 5.0.0 to 6.0.0
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](634f93cb29...018cc2cf5b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-30 19:38:25 -07:00
Nate Prewitt
8e5ac8e0eb Merge pull request #7071 from psf/dependabot/github_actions/actions/upload-artifact-5.0.0
Bump actions/upload-artifact from 4.6.2 to 5.0.0
2026-01-30 19:36:42 -07:00
dependabot[bot]
6c88b6b09f Bump actions/upload-artifact from 4.6.2 to 5.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 5.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](ea165f8d65...330a01c490)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-31 02:31:13 +00:00
Nate Prewitt
cb2c800518 Migrate build system to PEP 517 (#7012) 2026-01-30 19:25:55 -07:00
Nate Prewitt
a389aaad45 Update maintainers in the AUTHORS.rst (#7191)
---------

Co-authored-by: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>
2026-01-30 19:12:40 -07:00
Nate Prewitt
f8bec2f7ca Fix CI and build failures (#7190)
* Migrate linting from various tooling to Ruff

* Run ruff over codebase

* Drop support for pypy-3.10
2026-01-30 15:10:27 -06:00
Nate Prewitt
7029833289 Merge pull request #7042 from psf/dependabot/github_actions/github/codeql-action-4.30.8 2025-10-15 20:45:42 +09:00
dependabot[bot]
6e4134b204 Bump github/codeql-action from 3.30.0 to 4.30.8
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.0 to 4.30.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](2d92b76c45...f443b600d9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 16:16:05 +00:00
Nate Prewitt
420d16bc7e Merge pull request #7026 from psf/dependabot/github_actions/actions/setup-python-6.0.0 2025-09-09 18:00:19 +09:00
dependabot[bot]
3c8decb92f Bump actions/setup-python from 5.6.0 to 6.0.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](a26af69be9...e797f83bcb)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 00:09:47 +00:00
Nate Prewitt
5a270e2a95 Merge pull request #7025 from psf/dependabot/github_actions/pypa/gh-action-pypi-publish-1.13.0 2025-09-09 09:08:49 +09:00
dependabot[bot]
7d8df1df57 Bump pypa/gh-action-pypi-publish from 1.12.4 to 1.13.0
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.4 to 1.13.0.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](76f52bc884...ed0c53931b)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-version: 1.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 16:18:37 +00:00
dependabot[bot]
1c49660b84 Bump github/codeql-action from 3.29.0 to 3.30.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.0 to 3.30.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ce28f5bb42...2d92b76c45)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-02 03:15:01 -05:00
Nate Prewitt
b25c87d7cb v2.32.5
Some checks failed
Lint code / lint (push) Has been cancelled
Publish to PyPI / Build dists (push) Has been cancelled
Publish to PyPI / provenance (push) Has been cancelled
Publish to PyPI / Publish (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.14-dev) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.10) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.14-dev) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.14-dev) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.10) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
2025-08-18 13:23:20 -07:00
Nate Prewitt
131e506079 Merge pull request #7010 from psf/dependabot/github_actions/actions/checkout-5.0.0
Bump actions/checkout from 4.2.0 to 5.0.0
2025-08-11 15:32:16 -07:00
dependabot[bot]
b336cb2bc6 Bump actions/checkout from 4.2.0 to 5.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.0 to 5.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.2.0...08c6903cd8c0fde910a37f88322edcfb5dd907a8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 22:15:22 +00:00
Grant Birkinbine
46e939b552 Update publish workflow to use artifact-id instead of name
Added artifact-id output to publish workflow and updated download-artifact action version to `v5.0.0`. Also hardens the workflow a bit by adding `persist-credentials: false` to the checkout step
2025-08-06 07:36:19 -05:00
Nate Prewitt
4b9c546aa3 Merge pull request #6999 from psf/dependabot/github_actions/step-security/harden-runner-2.13.0
Bump step-security/harden-runner from 2.12.0 to 2.13.0
2025-07-28 12:09:42 -07:00
dependabot[bot]
7618dbef01 Bump step-security/harden-runner from 2.12.0 to 2.13.0
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.12.0 to 2.13.0.
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](0634a2670c...ec9f2d5744)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 19:01:25 +00:00
Nate Prewitt
2edca11103 Add support for Python 3.14 and drop support for Python 3.8 (#6993)
* Add testing for Python 3.14 preview build

* Add trove classifier for Python 3.14

* Remove support for Python 3.8
2025-07-25 14:42:15 -06:00
Abhishek Jha
fec96cd597 Update Makefile rules (#6996) 2025-07-18 10:24:31 -06:00
Quaylyn Rimer
d58d8aa2f4 docs: clarify timeout parameter uses seconds in Session.request (#6994) 2025-07-18 02:09:19 +00:00
dependabot[bot]
91a3eabd3d Bump github/codeql-action from 3.28.5 to 3.29.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.5 to 3.29.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f6091c0113...ce28f5bb42)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 14:10:36 -05:00
Nate Prewitt
90fee0876a Revert caching a default SSLContext (#6767) 2025-06-13 10:42:08 -06:00
AHMAD FAIZ
8ff173b186 Clarify error description in cloning instructions
This clarifies the error description in the 'Cloning the repository' section of the README. The current wording mentions 'an error about a bad commit', which is somewhat vague. I've updated it to 'an error about a bad commit timestamp', which more accurately describes the nature of the error referenced in issue #2690.
This small change improves the clarity of the documentation, helping users better understand the potential issue they might encounter when cloning the repository and why they need to use the specified Git flag.
2025-06-10 07:40:33 -05:00
Jonas Fonseca
3f07f990ac Fix typo in documentation for verify 2025-06-09 21:54:25 -05:00
Ian Stapleton Cordasco
e57b5bf05c Add Trusted Publishing Release Workflow
Rather than rely on manual releases from a developer laptop, let's use
tag pushes to trigger a workflow to publish artifacts to PyPI. This will
leverage trusted publishing and upload attestations as well.
2025-06-09 21:32:45 -05:00
Ian Stapleton Cordasco
021dc729f0 Polish up release tooling for last manual release
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.13) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.10) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.13) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.13) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.10) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
Tests / urllib3 1.x (push) Has been cancelled
2025-06-09 11:39:48 -05:00
Ian Stapleton Cordasco
821770e822 Bump version and add release notes for v2.32.4 2025-06-09 11:39:48 -05:00
Piotr Szlazak
59f8aa2adf Add netrc file search information to authentication documentation (#6876) 2025-06-08 12:47:55 -05:00
Arthur Woimbée
5b4b64c346 Add more tests to prevent regression of CVE 2024 47081
Remove workaround not needed since py38 for os.path.expanduser.
2025-06-05 09:55:33 -05:00
danigm
7bc45877a8 Add new test to check netrc auth leak (#6962)
This patch adds a new test that reproduces the security issue reported
here:
https://seclists.org/oss-sec/2025/q2/204

Doing a request to a malicious url with a prefix like "domain.com:@"
will use the "domain.com" netrc credentials in the request to other
domain.
2025-06-05 06:21:46 -05:00
Nate Prewitt
96ba401c12 Only use hostname to do netrc lookup instead of netloc 2025-06-04 10:39:25 -05:00
Nate Prewitt
7341690e84 Merge pull request #6951 from tswast/patch-1 2025-06-01 11:07:17 -07:00
Tim Sweña
6716d7c9f2 remove links 2025-06-01 09:03:51 -05:00
Ian Stapleton Cordasco
a7e1c745dc Update docs/conf.py
Co-authored-by: Nate Prewitt <nate.prewitt@gmail.com>
2025-05-31 11:59:51 -05:00
Tim Sweña (Swast)
c799b8167a docs: fix dead links to kenreitz.org 2025-05-21 11:08:05 -05:00
Robin
c65c780849 Add two more tests exercising the adapter (#6936)
Closes #6935
2025-05-03 11:39:14 -05:00
Nate Prewitt
579cd9f233 Drop pypy 3.9 and add pypy 3.11 support 2025-05-01 21:54:57 -05:00
Nate Prewitt
c54d0347e2 Merge pull request #6939 from psf/dependabot/github_actions/actions/setup-python-5.6.0 2025-04-28 10:36:20 -07:00
dependabot[bot]
991f05dcd9 Bump actions/setup-python from 5.5.0 to 5.6.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](8d9ed9ac5c...a26af69be9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 17:28:05 +00:00
Robin
4ce9520a1c Update lint workflow to ubuntu-24.04
Ubuntu 20.04 was the old default and is no longer supported by GitHub Actions
2025-04-23 07:44:05 -05:00
Ian Stapleton Cordasco
42a1dd640c Merge pull request #6924 from RichieB2B/rfc5280
Add key usage extension to test ca.crt
2025-04-15 21:46:50 -05:00
Nate Prewitt
c4c8e20289 Merge pull request #6925 from psf/dependabot/github_actions/actions/setup-python-5.5.0
Bump actions/setup-python from 5.4.0 to 5.5.0
2025-03-31 09:43:07 -07:00
dependabot[bot]
a5cb4284e1 Bump actions/setup-python from 5.4.0 to 5.5.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](42375524e2...8d9ed9ac5c)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 16:31:10 +00:00
Richard van den Berg
2019450b43 Add key usage extension to test ca.crt 2025-03-29 14:53:45 +01:00
Nate Prewitt
1764cc938e Merge pull request #6899 from duzhuoshanwai/main
Docs: Add quotes to prevent Zsh wildcard interpretation.
2025-02-20 10:43:13 -08:00
duzhuoshanwai
e946665c59 Update advanced.rst
Add quotes to prevent Zsh wildcard interpretation.
2025-02-19 10:05:28 +08:00
Ian Stapleton Cordasco
8ba805fa93 Merge pull request #6897 from cjwatson/fix-test-certificates
Add CA constraint to test CA
2025-02-17 06:40:19 -06:00
Colin Watson
9ebebdef98 Regenerate test certificates
Created using the following command with OpenSSL 3.4.0:

    for cert in expired mtls valid/server; do
        make -C tests/certs/$cert clean all
    done
2025-02-17 00:41:30 +00:00
Colin Watson
5074096613 Add CA constraint to test CA
Otherwise recent versions of OpenSSL reject it as an invalid CA
certificate (at least once the test certificates are regenerated).

Fixes: #6896
2025-02-17 00:39:07 +00:00
Nate Prewitt
1b7c37ef4e Merge pull request #6529 from anupam-arista/patch-1 2025-02-13 08:49:49 -08:00
anupam-arista
5d31ddbf7b Update src/requests/models.py
Co-authored-by: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>
2025-02-12 17:58:07 +05:30
anupam-arista
a2fd25ff34 Update models.py 2025-02-11 21:28:59 +05:30
anupam-arista
d96f9982a2 Update src/requests/models.py
Co-authored-by: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>
2025-02-11 21:25:29 +05:30
anupam-arista
b25f2b5ec2 Merge branch 'psf:main' into patch-1 2025-02-11 07:49:26 +05:30
Nate Prewitt
f761e74a4d Merge pull request #6880 from jakobheine/td/remove-urlrequired-from-docs
docs(exceptions): Remove unused exception URLRequired from documentation
2025-02-10 13:13:26 -08:00
Nate Prewitt
f2a6ae5e32 Merge pull request #6883 from psf/dependabot/github_actions/actions/setup-python-5.4.0
Bump actions/setup-python from 5.3.0 to 5.4.0
2025-02-03 08:58:57 -08:00
dependabot[bot]
11f63a330b Bump actions/setup-python from 5.3.0 to 5.4.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](0b93645e9f...42375524e2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 16:27:40 +00:00
jakobheine
e361622fae docs(exceptions): Remove unused exception URLRequired from documentation
The documentation previously listed `requests.URLRequired` as a valid
exception, suggesting it would be raised for invalid URLs. However,
this exception has been dead code since commit ab27027 (2012) and is
never actually raised.

Instead, invalid URLs raise `MissingSchema`, `InvalidSchema`, or
`InvalidURL`, none of which were documented. This commit removes
`URLRequired` from the documentation to reflect the actual behavior
and prevent confusion.

Signed-off-by: jakobheine <me@jakobheine.de>
2025-02-01 19:42:06 +00:00
Nate Prewitt
fe0583ba49 Merge pull request #6875 from psf/dependabot/github_actions/github/codeql-action-3.28.5
Bump github/codeql-action from 3.27.0 to 3.28.5
2025-01-27 09:39:35 -08:00
dependabot[bot]
8c36da656d Bump github/codeql-action from 3.27.0 to 3.28.5
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.0 to 3.28.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](662472033e...f6091c0113)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 16:56:12 +00:00
anupam-arista
b653d80ded Merge branch 'psf:main' into patch-1 2024-12-13 05:08:45 +05:30
Ian Stapleton Cordasco
23540c93ca Merge pull request #6824 from psf/nateprewitt-patch-1
Remove old macOS runners
2024-11-10 10:18:36 -06:00
Nate Prewitt
ad959acfcf Remove old macOS runners 2024-11-09 22:50:30 -07:00
Nate Prewitt
a6cf27a77f Update vulnerability disclosure process (#6820)
* Update contact point for Vulnerability disclosures

* Fix RedHat Contact

* Point vulnerabilities.rst to our .SECURITY file
2024-11-02 13:13:02 -07:00
Nate Prewitt
ed0b1b5802 Merge pull request #6817 from psf/dependabot/github_actions/actions/setup-python-5.3.0
Bump actions/setup-python from 5.2.0 to 5.3.0
2024-10-28 10:24:23 -06:00
Nate Prewitt
1f16cffa43 Merge pull request #6818 from psf/dependabot/github_actions/github/codeql-action-3.27.0
Bump github/codeql-action from 3.26.0 to 3.27.0
2024-10-28 10:24:12 -06:00
dependabot[bot]
9787d0c09c Bump github/codeql-action from 3.26.0 to 3.27.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.0 to 3.27.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](eb055d739a...662472033e)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 16:05:20 +00:00
dependabot[bot]
26664fa578 Bump actions/setup-python from 5.2.0 to 5.3.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](f677139bbe...0b93645e9f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 16:05:11 +00:00
Nate Prewitt
7335bbf480 Merge pull request #6803 from psf/dependabot/github_actions/actions/checkout-4.2.0
Bump actions/checkout from 4.1.0 to 4.2.0
2024-09-30 10:32:21 -07:00
dependabot[bot]
0bff2d94e6 Bump actions/checkout from 4.1.0 to 4.2.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8ade135a41...d632683dd7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 16:34:20 +00:00
Ian Stapleton Cordasco
1ae6fc3137 Merge pull request #6802 from graingert/3.13
start testing on 3.13rc
2024-09-19 06:19:03 -05:00
Thomas Grainger
5984296b6a Update tox.ini 2024-09-18 19:40:02 +01:00
Thomas Grainger
f519bbae33 add trove classifier for 3.13 2024-09-18 19:14:19 +01:00
Thomas Grainger
314e7c9c70 upgrade to pytest-httpbin 2.1.0 final 2024-09-18 16:36:43 +01:00
Thomas Grainger
92f10cec5b upgrade to pytest-httpbin 2.1.0rc1 2024-09-18 15:04:53 +01:00
Thomas Grainger
83e67c4485 use allow-prereleases: true instead of 3.13-dev 2024-09-18 15:04:08 +01:00
Thomas Grainger
0f5ef1be8c Merge branch 'main' of github.com:psf/requests into 3.13 2024-09-18 15:03:08 +01:00
Nate Prewitt
f12ccbef6d Merge pull request #6797 from psf/dependabot/github_actions/actions/setup-python-5.2.0 2024-09-02 10:37:44 -07:00
dependabot[bot]
173890a48e Bump actions/setup-python from 5.1.0 to 5.2.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.0 to 5.2.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](82c7e631bb...f677139bbe)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 16:56:26 +00:00
Nate Prewitt
a3ce6f0075 Merge pull request #6787 from psf/dependabot/github_actions/github/codeql-action-3.26.0
Bump github/codeql-action from 3.25.0 to 3.26.0
2024-08-12 09:54:11 -07:00
dependabot[bot]
877892e67e Bump github/codeql-action from 3.25.0 to 3.26.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.0 to 3.26.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](df5a14dc28...eb055d739a)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 16:23:16 +00:00
Nate Prewitt
79b74ef704 Merge pull request #6757 from nateprewitt/urllib-1.x-tests 2024-07-29 18:44:13 -07:00
Branch Vincent
15e1f17bb6 remove setuptools test command 2024-07-29 08:49:17 -05:00
Nate Prewitt
01353d3b4a Invert major version check 2024-07-23 04:28:03 -07:00
Nate Prewitt
4e383642a9 Add conditional string encoding based on urllib3 major version 2024-07-18 10:20:38 -07:00
Nate Prewitt
f8aa36b92d Test on urllib3 1.26.x 2024-07-01 18:08:04 -07:00
anupam-arista
06d2a38441 Merge branch 'psf:main' into patch-1 2024-07-01 10:46:41 +05:30
Nate Prewitt
0e322af877 v2.32.3
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.10) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.9) (push) Has been cancelled
Tests / build (macos-13, 3.8) (push) Has been cancelled
Tests / build (macos-13, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.10) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.9) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
2024-05-29 08:36:10 -07:00
Nate Prewitt
e188799322 Don't create default SSLContext if ssl module isn't present (#6724) 2024-05-29 08:23:39 -07:00
Nate Prewitt
145b539948 Merge pull request #6716 from sigmavirus24/bug/6715 2024-05-24 14:37:06 -07:00
Nate Prewitt
b1d73ddb50 Don't use default SSLContext with custom poolmanager kwargs 2024-05-24 11:38:10 -07:00
Nate Prewitt
6badbac6e0 Update HISTORY.md 2024-05-24 11:38:06 -07:00
Nate Prewitt
2e1452234d Start testing on 3.13 beta 2024-05-23 11:51:07 -07:00
Ian Stapleton Cordasco
a62a2d35d9 Allow for overriding of specific pool key params
This re-enables the use case of providing a custom SSLContext via a
Transport Adapter as broken in #6655 and reported in #6715

Closes #6715
2024-05-22 06:53:24 -05:00
Nate Prewitt
88dce9d854 v2.32.2
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.10) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.9) (push) Has been cancelled
Tests / build (macos-13, 3.8) (push) Has been cancelled
Tests / build (macos-13, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.10) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.9) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
2024-05-21 11:49:22 -07:00
Nate Prewitt
c98e4d133e Merge pull request #6710 from nateprewitt/api_rename
Move _get_connection to get_connection_with_tls_context
2024-05-21 09:41:18 -07:00
Nate Prewitt
92075b330a Add deprecation warning 2024-05-21 09:28:30 -07:00
Nate Prewitt
aa1461b68a Move _get_connection to get_connection_with_tls_context 2024-05-21 05:42:49 -07:00
Nate Prewitt
970e8cec98 v2.32.1
Some checks failed
Lint code / lint (push) Has been cancelled
Tests / build (macOS-latest, 3.10) (push) Has been cancelled
Tests / build (macOS-latest, 3.11) (push) Has been cancelled
Tests / build (macOS-latest, 3.12) (push) Has been cancelled
Tests / build (macOS-latest, 3.8) (push) Has been cancelled
Tests / build (macOS-latest, 3.9) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.10) (push) Has been cancelled
Tests / build (macOS-latest, pypy-3.9) (push) Has been cancelled
Tests / build (macos-13, 3.8) (push) Has been cancelled
Tests / build (macos-13, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.11) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.12) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.8) (push) Has been cancelled
Tests / build (ubuntu-22.04, 3.9) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.10) (push) Has been cancelled
Tests / build (ubuntu-22.04, pypy-3.9) (push) Has been cancelled
Tests / build (windows-latest, 3.10) (push) Has been cancelled
Tests / build (windows-latest, 3.11) (push) Has been cancelled
Tests / build (windows-latest, 3.12) (push) Has been cancelled
Tests / build (windows-latest, 3.8) (push) Has been cancelled
Tests / build (windows-latest, 3.9) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.10) (push) Has been cancelled
Tests / build (windows-latest, pypy-3.9) (push) Has been cancelled
Tests / No Character Detection (push) Has been cancelled
2024-05-20 15:02:29 -07:00
anupam-arista
12862617df Update models.Response.json docstring clearer 2023-09-14 14:29:37 +05:30
85 changed files with 2748 additions and 1648 deletions

81
.github/AI_POLICY.md vendored Normal file
View File

@@ -0,0 +1,81 @@
# Generative AI / LLM Policy
We appreciate that we can't realistically police how you author your pull requests, which includes whether you employ large-language model (LLM)-based development tools.
So, we don't.
However, due to both legal and human reasons, we have to establish boundaries.
> [!CAUTION]
> **TL;DR:**
> - We take the responsibility for this project very seriously and we expect you to take your responsibility for your contributions seriously, too.
> This used to be a given, but it changed now that a pull request is just one prompt away.
>
> - Every contribution has to be backed by a human who unequivocally owns the copyright for all changes.
> No LLM bots in `Co-authored-by:`s.
>
> - DoS-by-slop leads to a permanent ban.
>
> - Absolutely **no** unsupervised agentic tools like OpenClaw.
>
> ---
>
> By submitting a pull request, you certify that:
>
> - You are the author of the contribution or have the legal right to submit it.
> - You either hold the copyright to the changes or have explicit legal authorization to contribute them under this project's license.
> - You understand the code.
> - You accept full responsibility for it.
## Legal
There is ongoing legal uncertainty regarding the copyright status of LLM-generated works and their provenance.
Since we do not have a formal [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_license_agreement) (CLA), you retain your copyright to your changes to this project.
Therefore, allowing contributions by LLMs has unpredictable consequences for the copyright status of this project even when leaving aside possible copyright violations due to plagiarism.
## Human
As the makers of software that is used by millions of people worldwide and with a reputation for high-quality maintenance, we take our responsibility to our users very seriously.
No matter what LLM vendors or boosters on LinkedIn tell you, we have to manually review every change before merging, because it's **our responsibility** to keep the project stable.
Please understand that by opening low-quality pull requests you're not helping anyone.
Worse, you're [poisoning the open source ecosystem](https://lwn.net/Articles/1058266/) that was precarious even before the arrival of LLM tools.
Having to wade through plausible-looking-but-low-quality pull requests and trying to determine which ones are legit is extremely demoralizing and has already burned out many good maintainers.
Put bluntly, we have no time or interest to become part of your vibe coding loop where you drop LLM slop at our door, we spend time and energy to review it, and you just feed it back into the LLM for another iteration.
This dynamic is especially pernicious because it poisons the well for mentoring new contributors which we are committed to.
## Summary
In practice, this means:
- Pull requests that have an LLM product listed as co-author can't be merged and will be closed without further discussion.
We cannot risk the copyright status of this project.
If you used LLM tools during development, you may still submit but you must remove any LLM co-author tags and take full ownership of every line.
- By submitting a pull request, **you** take full **technical and legal** responsibility for the contents of the pull request and promise that **you** hold the copyright for the changes submitted.
"An LLM wrote it" is **not** an acceptable response to questions or critique.
**If you cannot explain and defend the changes you submit, do not submit them** and open a high-quality bug report/feature request instead.
- Accounts that exercise bot-like behavior like automated mass pull requests will be permanently banned, whether they belong to a human or not.
- Do **not** post LLM-generated review comments we can prompt LLMs ourselves should we desire their wisdom.
Do **not** post summaries unless you've fact-checked them and take responsibility for 100% of their content.
Remember that *all* LLM output *looks* **plausible**.
When using these tools, it's **your** responsibility to ensure that it's also **correct** and has a reasonable signal-to-noise ratio.
---
# Credits, Attribution
The original version of this can be found in [pyca/service-identity][] and is
used with permission from Hynek Schlawack.
[pyca/service-identity]:
https://github.com/pyca/service-identity/blob/fa91bf55cfda64145aa3d202cc84059befb98af4/.github/AI_POLICY.md

8
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,8 @@
# Restrict all files related to deploying to
# require lead maintainer approval.
.github/workflows/ @nateprewitt @sigmavirus24
.github/CODEOWNERS @nateprewitt @sigmavirus24
src/requests/__version__.py @nateprewitt @sigmavirus24
HISTORY.md @nateprewitt @sigmavirus24
pyproject.toml @nateprewitt @sigmavirus24

View File

@@ -1,7 +1,8 @@
# Contribution Guidelines
Before opening any issues or proposing any pull requests, please read
our [Contributor's Guide](https://requests.readthedocs.io/en/latest/dev/contributing/).
our [Contributor's Guide](https://requests.readthedocs.io/en/latest/dev/contributing/)
as well as our [AI Policy](/.github/AI_POLICY.md).
To get the greatest chance of helpful responses, please also observe the
following additional notes.
@@ -52,3 +53,14 @@ Please be aware of the following things when filing bug reports:
If you do not provide all of these things, it will take us much longer to
fix your problem. If we ask you to clarify these and you never respond, we
will close your issue without fixing it.
## Good Pull Requests
1. Avoid raising duplicate pull requests. *Please* use the GitHub pull request
search feature to check whether someone else has already filed a pull
request.
2. When filing a pull request, always include tests that fail without your
changes thus preventing the fix from regressing.
3. Always run the test suite locally
4. Write commit messages that explain _why_ a change was made beyond "Fixes
#NNNN". Why is this the right fix? What other options were ruled out? etc.

17
.github/SECURITY.md vendored
View File

@@ -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

View File

@@ -1,11 +1,30 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
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"]
cooldown:
default-days: 7
groups:
actions:
patterns:
- "*"
- package-ecosystem: "pre-commit"
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-patch"]
cooldown:
default-days: 7
groups:
pre-commit:
patterns:
- "*"

View File

@@ -5,11 +5,12 @@ on:
types:
- labeled
permissions:
issues: write
permissions: {}
jobs:
close_qa:
permissions:
issues: write
if: github.event.label.name == 'actions/autoclose-qa'
runs-on: ubuntu-latest
steps:
@@ -22,6 +23,8 @@ jobs:
--reason completed
gh issue lock $ISSUE_URL --reason off_topic
close_feature_request:
permissions:
issues: write
if: github.event.label.name == 'actions/autoclose-feat'
runs-on: ubuntu-latest
steps:

View File

@@ -32,11 +32,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
persist-credentials: false
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
@@ -45,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
languages: "python"
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +57,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0
uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -70,4 +71,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0

View File

@@ -7,14 +7,18 @@ permissions:
jobs:
lint:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.x"
- name: Run pre-commit
uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507 # v3.0.0
run: |
python -m pip install pre-commit==4.6.0
pre-commit run --show-diff-on-failure --color=always --all-files

View File

@@ -13,7 +13,7 @@ jobs:
if: github.repository_owner == 'psf'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@d42e5f49803f3c4e14ffee0378e31481265dda22 # v5.0.0
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
with:
issue-lock-inactive-days: 90
pr-lock-inactive-days: 90

95
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Publish to PyPI
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
test-pypi-only:
description: "Publish to Test PyPI only"
type: boolean
default: true
permissions:
contents: read
jobs:
build:
name: "Build dists"
runs-on: "ubuntu-latest"
outputs:
artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }}
steps:
- name: "Checkout repository"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
with:
persist-credentials: false
- name: "Setup Python"
uses: "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97" # v7.0.0
with:
python-version: "3.x"
- name: "Install dependencies"
run: python -m pip install build==1.4.0
- name: "Build dists"
run: |
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) \
python -m build
- name: "Upload dists"
uses: "actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f"
id: upload-artifact
with:
name: "dist"
path: "dist/"
if-no-files-found: error
retention-days: 5
publish:
name: "Publish"
if: startsWith(github.ref, 'refs/tags/')
needs: ["build"]
permissions:
id-token: write
runs-on: "ubuntu-latest"
environment:
name: "publish"
steps:
- name: "Download dists"
uses: "actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3" # v8.0.0
with:
artifact-ids: ${{ needs.build.outputs.artifact-id }}
path: "dist/"
- name: "Publish dists to PyPI"
uses: "pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b" # v1.14.0
with:
attestations: true
publish-test-pypi:
name: "Publish to Test PyPI"
if: github.event_name == 'workflow_dispatch'
needs: ["build"]
permissions:
id-token: write
runs-on: "ubuntu-latest"
environment:
name: "testpypi"
steps:
- name: "Download dists"
uses: "actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3" # v8.0.0
with:
artifact-ids: ${{ needs.build.outputs.artifact-id }}
path: "dist/"
- name: "Publish dists to Test PyPI"
uses: "pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b" # v1.14.0
with:
repository-url: https://test.pypi.org/legacy/
attestations: true

View File

@@ -12,26 +12,26 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.9", "pypy-3.10"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15-dev", "pypy-3.11"]
os: [ubuntu-22.04, macOS-latest, windows-latest]
# Python 3.8 and 3.9 do not run on macOS-latest which
# is now using arm64 hardware.
# https://github.com/actions/setup-python/issues/696#issuecomment-1637587760
# Pypy-3.11 can't install openssl-sys with rust
# which prevents us from testing in GHA.
exclude:
- { python-version: "3.8", os: "macos-latest" }
- { python-version: "3.9", os: "macos-latest" }
include:
- { python-version: "3.8", os: "macos-13" }
- { python-version: "3.9", os: "macos-13" }
- { python-version: "pypy-3.11", os: "windows-latest" }
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
allow-prereleases: true
- name: Install dependencies
env:
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: ${{ matrix.python-version == '3.15-dev' && '1' || '' }}
run: |
make
- name: Run tests
@@ -45,11 +45,13 @@ jobs:
fail-fast: true
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- name: 'Set up Python 3.8'
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
python-version: '3.8'
persist-credentials: false
- name: 'Set up Python 3.10'
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: '3.10'
- name: Install dependencies
run: |
make
@@ -57,3 +59,25 @@ jobs:
- name: Run tests
run: |
make ci
urllib3:
name: 'urllib3 1.x'
runs-on: 'ubuntu-latest'
strategy:
fail-fast: true
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
persist-credentials: false
- name: 'Set up Python 3.10'
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: '3.10'
- name: Install dependencies
run: |
make
python -m pip install "urllib3<2"
- name: Run tests
run: |
make ci

32
.github/workflows/typecheck.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Type Check
on: [push, pull_request]
permissions:
contents: read
jobs:
typecheck:
runs-on: ubuntu-24.04
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.10", "3.14"]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install pip==26.0.1
python -m pip install -e . --group typecheck
- name: Run pyright
run: python -m pyright src/requests/

24
.github/workflows/zizmor.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
# Sourced from https://github.com/zizmorcore/zizmor-action
name: GitHub Actions Security Analysis with zizmor 🌈
on:
push:
branches: ["main"]
pull_request:
branches: ["**"]
permissions: {}
jobs:
zizmor:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0

View File

@@ -2,27 +2,20 @@ exclude: 'docs/|ext/'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v6.0.0
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
args: [--fix=auto]
- id: trailing-whitespace
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.4
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
- id: ruff-check
args: [--fix]
- id: ruff-format
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

View File

@@ -1,17 +1,17 @@
Requests was lovingly created by Kenneth Reitz.
Keepers of the Crystals
```````````````````````
Requests Maintainers
````````````````````
- Ian Stapleton Cordasco <graffatcolmingov@gmail.com> `@sigmavirus24 <https://github.com/sigmavirus24>`_.
- Nate Prewitt `@nateprewitt <https://github.com/nateprewitt>`_.
- Seth M. Larson `@sethmlarson <https://github.com/sethmlarson>`_.
Previous Keepers of Crystals
````````````````````````````
Previous Maintainers
````````````````````
- 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>`_.
- Seth M. Larson `@sethmlarson <https://github.com/sethmlarson>`_.
Patches and Suggestions
```````````````````````

View File

@@ -6,6 +6,156 @@ dev
- \[Short description of non-trivial change.\]
2.34.2 (2026-05-14)
-------------------
- Moved `headers` input type back to `Mapping` to avoid invariance issues
with `MutableMapping` and inferred dict types. Users calling
`Request.headers.update()` may need to narrow typing in their code. (#7441)
2.34.1 (2026-05-13)
-------------------
**Bugfixes**
- Widened `json` input type from `dict` and `list` to `Mapping`
and `Sequence`. (#7436)
- Changed `headers` input type to MutableMapping and removed `None` from
`Request.headers` typing to improve handling for users. (#7431)
- `Response.reason` moved from `str | None` to `str` to improve handling
for users. (#7437)
- Fixed a bug where some bodies with custom `__getattr__` implementations
weren't being properly detected as Iterables. (#7433)
2.34.0 (2026-05-11)
-------------------
**Announcements**
- Requests 2.34.0 introduces inline types, replacing those provided by
typeshed. Public API types should be fully compatible with mypy, pyright,
and ty. We believe types are comprehensive but if you find issues, please
report them to the pinned tracking issue.
Special thanks to @bastimeyer, @cthoyt, @edgarrmondragon, and @srittau for
helping review and test the types ahead of the release. (#7272)
**Improvements**
- Digest Auth hashing algorithms have added `usedforsecurity=False` to clarify
security considerations. (#7310)
- Requests added support for Python 3.15 based on beta1. Downstream projects
should be able to start testing prior to its release in October. (#7422)
- Requests added support for Python 3.14t. (#7419)
**Bugfixes**
- ``Response.history`` no longer contains a reference to itself, preventing
accidental looping when traversing the history list. (#7328)
- Requests no longer performs greedy matching on no_proxy domains. The
proxy_bypass implementation has been updated with CPython's fix from
bpo-39057. (#7427)
- Requests no longer incorrectly strips duplicate leading slashes in
URI paths. This should address user issues with specific presigned
URLs. Note the full fix requires urllib3 2.7.0+. (#7315)
2.33.1 (2026-03-30)
-------------------
**Bugfixes**
- Fixed test cleanup for CVE-2026-25645 to avoid leaving unnecessary
files in the tmp directory. (#7305)
- Fixed Content-Type header parsing for malformed values. (#7309)
- Improved error consistency for malformed header values. (#7308)
2.33.0 (2026-03-25)
-------------------
**Announcements**
- 📣 Requests is adding inline types. If you have a typed code base that
uses Requests, please take a look at #7271. Give it a try, and report
any gaps or feedback you may have in the issue. 📣
**Security**
- CVE-2026-25645 ``requests.utils.extract_zipped_paths`` now extracts
contents to a non-deterministic location to prevent malicious file
replacement. This does not affect default usage of Requests, only
applications calling the utility function directly.
**Improvements**
- Migrated to a PEP 517 build system using setuptools. (#7012)
**Bugfixes**
- Fixed an issue where an empty netrc entry could cause
malformed authentication to be applied to Requests on
Python 3.11+. (#7205)
**Deprecations**
- Dropped support for Python 3.9 following its end of support. (#7196)
**Documentation**
- Various typo fixes and doc improvements.
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)
-------------------

View File

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

View File

@@ -2,24 +2,24 @@
init:
python -m pip install -r requirements-dev.txt
test:
# This runs all of the tests on all supported Python versions.
tox -p
python -m pytest tests
ci:
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:
python -m flake8 src/requests
coverage:
python -m pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=src/requests tests
publish:
python -m 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:

View File

@@ -1,5 +1,11 @@
# Requests
[![Version](https://img.shields.io/pypi/v/requests.svg?maxAge=86400)](https://pypi.org/project/requests/)
[![Supported Versions](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests)
[![Downloads](https://static.pepy.tech/badge/requests/month)](https://pepy.tech/project/requests)
[![Contributors](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors)
[![Documentation](https://readthedocs.org/projects/requests/badge/?version=latest)](https://requests.readthedocs.io)
**Requests** is a simple, yet elegant, HTTP library.
```python
@@ -19,11 +25,7 @@
Requests allows you to send HTTP/1.1 requests extremely easily. Theres no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method!
Requests is one of the most downloaded Python packages today, pulling in around `30M downloads / week`— according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `1,000,000+` repositories. You may certainly put your trust in this code.
[![Downloads](https://static.pepy.tech/badge/requests/month)](https://pepy.tech/project/requests)
[![Supported Versions](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests)
[![Contributors](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors)
Requests is one of the most downloaded Python packages today, pulling in around `300M downloads / week` — according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `4,000,000+` repositories.
## Installing Requests and Supported Versions
@@ -33,7 +35,7 @@ Requests is available on PyPI:
$ python -m pip install requests
```
Requests officially supports Python 3.8+.
Requests officially supports Python 3.10+.
## Supported Features & BestPractices
@@ -53,14 +55,10 @@ Requests is ready for the demands of building robust and reliable HTTPspeakin
- Automatic honoring of `.netrc`
- Chunked HTTP Requests
## API Reference and User Guide available on [Read the Docs](https://requests.readthedocs.io)
[![Read the Docs](https://raw.githubusercontent.com/psf/requests/main/ext/ss.png)](https://requests.readthedocs.io)
## Cloning the repository
When cloning the Requests repository, you may need to add the `-c
fetch.fsck.badTimezone=ignore` flag to avoid an error about a bad commit (see
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

View File

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

View File

@@ -1,177 +1,12 @@
body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-child(2) > td > select {
width: 100%!important;
}
#python27 > a {
color: white;
}
/* Carbon by BuySellAds */
#carbonads {
display: block;
overflow: hidden;
margin: 1.5em 0 2em;
padding: 1em;
border: solid 1px #cccccc;
border-radius: 2px;
background-color: #eeeeee;
text-align: center;
line-height: 1.5;
}
#carbonads a {
border-bottom: 0;
}
#carbonads span {
display: block;
overflow: hidden;
}
.carbon-img {
display: block;
margin: 0 auto 1em;
text-align: center;
}
.carbon-text {
display: block;
margin-bottom: 1em;
}
.carbon-poweredby {
display: block;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 10px;
line-height: 1;
}
/* Native CPC by BuySellAds */
#native-ribbon #_custom_ {
position: fixed;
right: 0;
bottom: 0;
left: 0;
box-shadow: 0 -1px 4px 1px hsla(0, 0%, 0%, .15);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu,
Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
transition: all .25s ease-in-out;
transform: translateY(calc(100% - 35px));
flex-flow: column nowrap;
}
#native-ribbon #_custom_:hover {
transform: translateY(0);
}
.native-img {
margin-right: 20px;
max-height: 50px;
border-radius: 3px;
}
.native-sponsor {
margin: 10px 20px;
text-align: center;
text-transform: uppercase;
letter-spacing: .5px;
font-size: 12px;
transition: all .3s ease-in-out;
transform-origin: left;
}
#native-ribbon #_custom_:hover .native-sponsor {
margin: 0 20px;
opacity: 0;
transform: scaleY(0);
}
.native-flex {
display: flex;
padding: 10px 20px 25px;
text-decoration: none;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
}
.native-main {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
.native-details {
display: flex;
margin-right: 30px;
flex-flow: column nowrap;
}
.native-company {
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 2px;
font-size: 10px;
}
.native-desc {
letter-spacing: 1px;
font-weight: 300;
font-size: 14px;
line-height: 1.4;
}
.native-cta {
padding: 10px 14px;
border-radius: 3px;
box-shadow: 0 6px 13px 0 hsla(0, 0%, 0%, .15);
text-transform: uppercase;
white-space: nowrap;
letter-spacing: 1px;
font-weight: 400;
font-size: 12px;
transition: all .3s ease-in-out;
transform: translateY(-1px);
}
.native-cta:hover {
box-shadow: none;
transform: translateY(1px);
}
@media only screen and (min-width: 320px) and (max-width: 759px) {
.native-flex {
padding: 5px 5px 15px;
flex-direction: column;
flex-wrap: wrap;
}
.native-img {
margin: 0;
display: none;
}
.native-details {
margin: 0;
}
.native-main {
flex-direction: column;
text-align: left;
flex-wrap: wrap;
align-content: center;
}
.native-cta {
display: none;
}
img.logo {margin-left: -20px!important;}
div#searchbox h3 {text-transform: capitalize;}
div.document {width: 1008px;}
div.highlight pre {padding: 11px 14px;}
@media screen and (max-width: 1008px) {
div.document {width: 100%!important;}
div.highlight pre {margin-right: -30px;}
}

View File

@@ -1,80 +0,0 @@
<!-- Alabaster (krTheme++) Hacks -->
<!-- CSS Adjustments (I'm very picky.) -->
<style type="text/css">
/* Rezzy requires precise alignment. */
img.logo {margin-left: -20px!important;}
/* "Quick Search" should be capitalized. */
div#searchbox h3 {text-transform: capitalize;}
/* Make the document a little wider, less code is cut-off. */
div.document {width: 1008px;}
/* Much-improved spacing around code blocks. */
div.highlight pre {padding: 11px 14px;}
/* Remain Responsive! */
@media screen and (max-width: 1008px) {
div.sphinxsidebar {display: none;}
div.document {width: 100%!important;}
/* Have code blocks escape the document right-margin. */
div.highlight pre {margin-right: -30px;}
}
</style>
<!-- Native CPC by BuySellAds -->
<script type="text/javascript" src="//m.servedby-buysellads.com/monetization.js"></script>
<div id="native-ribbon">
</div>
<script>
_bsa.init('custom', 'CK7D62JU', 'placement:pythonrequestsorg',
{
target: '#native-ribbon',
template: `
<div class="native-sponsor">Sponsored by ##company## — Learn More</div>
<a href="##link##" class="native-flex">
<style>
#native-ribbon #_custom_ {
background: linear-gradient(-30deg, ##backgroundColor##E5, ##backgroundColor##E5 45%, ##backgroundColor## 45%) #fff;
}
.native-details,
.native-sponsor,
.native-bsa {
color: ##textColor## !important;
}
.native-details:hover {
color: ##textColorHover## !important;
}
.native-cta {
color: ##ctaTextColor##;
background-color: ##ctaBackgroundColor##;
}
.native-cta:hover {
color: ##ctaTextColorHover##;
background-color: ##ctaBackgroundColorHover##;
}
</style>
<div class="native-main">
<img class="native-img" src="##logo##">
<div class="native-details">
<span class="native-company">##title##</span>
<span class="native-desc">##description##</span>
</div>
</div>
<span class="native-cta">##callToAction##</span>
</a>
</div>
`
}
);
</script>

33
docs/_templates/sidebar.html vendored Normal file
View File

@@ -0,0 +1,33 @@
<p class="logo">
<a href="{{ pathto(root_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>
<p>
{{ _('Requests is an elegant and simple HTTP library for Python, built for human beings.') }}
</p>
<h3>{{ _('Useful Links') }}</h3>
<ul>
<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="{{ pathto('community/recommended') }}">{{ _('Recommended Packages and Extensions') }}</a></li>
<p></p>
<li><a href="https://github.com/psf/requests">{{ _('Requests @ GitHub') }}</a></li>
<li><a href="https://pypi.org/project/requests/">{{ _('Requests @ PyPI') }}</a></li>
<li><a href="https://github.com/psf/requests/issues">{{ _('Issue Tracker') }}</a></li>
</ul>

View File

@@ -1,37 +0,0 @@
<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>
<p>
Requests is an elegant and simple HTTP library for Python, built for
human beings.
</p>
<h3>Useful Links</h3>
<ul>
<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="{{ pathto('community/recommended') }}">Recommended Packages and Extensions</a></li>
<p></p>
<li><a href="https://github.com/psf/requests">Requests @ GitHub</a></li>
<li><a href="https://pypi.org/project/requests/">Requests @ PyPI</a></li>
<li><a href="https://github.com/psf/requests/issues">Issue Tracker</a></li>
</ul>
<div id="native-ribbon">
</div>

View File

@@ -1,30 +0,0 @@
<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>
<p>
Requests is an elegant and simple HTTP library for Python, built for
human beings. You are currently looking at the documentation of the
development release.
</p>
<h3>Useful Links</h3>
<ul>
<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="{{ pathto('community/recommended') }}">Recommended Packages and Extensions</a></li>
<p></p>
<li><a href="https://github.com/psf/requests">Requests @ GitHub</a></li>
<li><a href="https://pypi.org/project/requests/">Requests @ PyPI</a></li>
<li><a href="https://github.com/psf/requests/issues">Issue Tracker</a></li>
</ul>

View File

@@ -31,7 +31,6 @@ Exceptions
.. autoexception:: requests.RequestException
.. autoexception:: requests.ConnectionError
.. autoexception:: requests.HTTPError
.. autoexception:: requests.URLRequired
.. autoexception:: requests.TooManyRedirects
.. autoexception:: requests.ConnectTimeout
.. autoexception:: requests.ReadTimeout
@@ -56,6 +55,7 @@ Lower-Level Classes
.. autoclass:: Response
:inherited-members:
:exclude-members: is_permanent_redirect
Lower-Lower-Level Classes

View File

@@ -30,7 +30,7 @@ Why not Httplib2?
-----------------
Chris Adams gave an excellent summary on
`Hacker News <http://news.ycombinator.com/item?id=2884406>`_:
`Hacker News <https://news.ycombinator.com/item?id=2884406>`_:
httplib2 is part of why you should use requests: it's far more respectable
as a client but not as well documented and it still takes way too much code
@@ -44,8 +44,8 @@ Chris Adams gave an excellent summary on
Disclosure: I'm listed in the requests AUTHORS file but can claim credit
for, oh, about 0.0001% of the awesomeness.
1. http://code.google.com/p/httplib2/issues/detail?id=96 is a good example:
an annoying bug which affect many people, there was a fix available for
1. https://code.google.com/p/httplib2/issues/detail?id=96 is a good example:
an annoying bug that affected many people, there was a fix available for
months, which worked great when I applied it in a fork and pounded a couple
TB of data through it, but it took over a year to make it into trunk and
even longer to make it onto PyPI where any other project which required "
@@ -55,7 +55,8 @@ Chris Adams gave an excellent summary on
Python 3 Support?
-----------------
Yes! Requests officially supports Python 3.8+ and PyPy.
Yes! Requests supports all `officially supported versions of Python <https://devguide.python.org/versions/>`_
and recent releases of PyPy.
Python 2 Support?
-----------------
@@ -64,8 +65,8 @@ 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>`_.
It is *highly* recommended users migrate to Python 3.8+ now since Python
2.7 is no longer receiving bug fixes or security updates as of January 1, 2020.
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?
-----------------------------------------

View File

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

View File

@@ -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:
- Python Maintenance Team, Red Hat (python-maint@redhat.com)
- Daniele Tricoli, Debian (@eriol)
We will notify these individuals at least a week ahead of our planned release
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>`_.

View File

@@ -54,11 +54,11 @@ source_suffix = ".rst"
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
root_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"Kenneth Reitz and contributors"
author = u"Kenneth Reitz"
# The version info for the project you're documenting, acts as replacement for
@@ -126,7 +126,7 @@ html_theme = "alabaster"
# documentation.
html_theme_options = {
"show_powered_by": False,
"github_user": "requests",
"github_user": "psf",
"github_repo": "requests",
"github_banner": True,
"show_related": False,
@@ -172,14 +172,13 @@ html_use_smartypants = False
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
"index": ["sidebarintro.html", "sourcelink.html", "searchbox.html", "hacks.html"],
"index": ["sidebar.html", "sourcelink.html", "searchbox.html"],
"**": [
"sidebarlogo.html",
"sidebar.html",
"localtoc.html",
"relations.html",
"sourcelink.html",
"searchbox.html",
"hacks.html",
],
}
@@ -247,7 +246,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, "Requests.tex", u"Requests Documentation", u"Kenneth Reitz", "manual")
(root_doc, "Requests.tex", u"Requests Documentation", u"Kenneth Reitz", "manual")
]
# The name of an image file (relative to this directory) to place at the top of
@@ -275,7 +274,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "requests", u"Requests Documentation", [author], 1)]
man_pages = [(root_doc, "requests", u"Requests Documentation", [author], 1)]
# If true, show URL addresses after external links.
# man_show_urls = False
@@ -288,7 +287,7 @@ man_pages = [(master_doc, "requests", u"Requests Documentation", [author], 1)]
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
root_doc,
"Requests",
u"Requests Documentation",
author,

View File

@@ -14,7 +14,7 @@ getting a feel for how contributing to this project works. If you have any
questions, feel free to reach out to either `Nate Prewitt`_, `Ian Cordasco`_,
or `Seth Michael Larson`_, the primary maintainers.
.. _Ian Cordasco: http://www.coglib.com/~icordasc/
.. _Ian Cordasco: https://www.coglib.com/~icordasc/
.. _Nate Prewitt: https://www.nateprewitt.com/
.. _Seth Michael Larson: https://sethmlarson.dev/
@@ -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://kenreitz.org/essays/2013/01/27/be-cordial-or-be-on-your-way
.. _Python Software Foundation Code of Conduct: https://policies.python.org/python.org/code-of-conduct/
.. _early-feedback:
@@ -134,8 +132,8 @@ files and a semi-formal, yet friendly and approachable, prose style.
When presenting Python code, use single-quoted strings (``'hello'`` instead of
``"hello"``).
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _Sphinx: http://sphinx-doc.org/index.html
.. _reStructuredText: https://docutils.sourceforge.net/rst.html
.. _Sphinx: https://www.sphinx-doc.org/en/master/
.. _bug-reports:

View File

@@ -9,21 +9,25 @@ Requests: HTTP for Humans™
Release v\ |version|. (:ref:`Installation <install>`)
.. 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
.. image:: https://img.shields.io/pypi/v/requests.svg?maxAge=86400
: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
:alt: PyPI Version Badge
.. image:: https://img.shields.io/pypi/pyversions/requests.svg
:target: https://pypi.org/project/requests/
:alt: Python Version Support Badge
:alt: Supported Versions Badge
.. image:: https://static.pepy.tech/badge/requests/month
:target: https://pepy.tech/project/requests
:alt: Downloads Per Month Badge
.. image:: https://img.shields.io/github/contributors/psf/requests.svg
:target: https://github.com/psf/requests/graphs/contributors
:alt: Contributors Badge
.. image:: https://readthedocs.org/projects/requests/badge/?version=latest
:target: https://requests.readthedocs.io
:alt: Documentation Badge
**Requests** is an elegant and simple HTTP library for Python, built for human beings.
@@ -72,7 +76,7 @@ Requests is ready for today's web.
- Chunked Requests
- ``.netrc`` Support
Requests officially supports Python 3.8+, and runs great on PyPy.
Requests officially supports Python 3.10+, and runs great on PyPy.
The User Guide

View File

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

View File

@@ -467,6 +467,7 @@ Let's print some request method arguments at runtime::
You can add multiple hooks to a single request. Let's call two hooks at once::
>>> r = requests.get('https://httpbin.org/', hooks={'response': [print_url, record_hook]})
https://httpbin.org/
>>> r.hook_called
True
@@ -677,11 +678,9 @@ In addition to basic HTTP proxies, Requests also supports proxies using the
SOCKS protocol. This is an optional feature that requires that additional
third-party libraries be installed before use.
You can get the dependencies for this feature from ``pip``:
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::
@@ -969,11 +968,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
@@ -1053,7 +1050,6 @@ backoff, within a Requests :class:`Session <requests.Session>` using the
)
s.mount('https://', HTTPAdapter(max_retries=retries))
.. _`described here`: https://kenreitz.org/essays/2012/06/14/the-future-of-python-http
.. _`urllib3`: https://github.com/urllib3/urllib3
.. _`urllib3.util.Retry`: https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Retry

View File

@@ -44,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
---------------------

View File

@@ -222,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.
@@ -270,7 +271,9 @@ use the same key::
},
...
}
>>> r1.text == r2.text
>>> # httpbin may embed non-deterministic metadata,
>>> # so we only compare our submitted data here.
>>> r1.json()['form'] == r2.json()['form']
True
There are times that you may want to send data that is not form-encoded. If

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

View File

@@ -1,10 +1,125 @@
[tool.isort]
profile = "black"
src_paths = ["src/requests", "test"]
honor_noqa = true
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "requests"
description = "Python HTTP for Humans."
readme = "README.md"
license = {text = "Apache-2.0"}
authors = [
{ name = "Kenneth Reitz", email = "me@kennethreitz.org" },
]
maintainers = [
{name = "Ian Stapleton Cordasco", email="graffatcolmingov@gmail.com"},
{name = "Nate Prewitt", email="nate.prewitt@gmail.com"}
]
requires-python = ">=3.10"
dependencies = [
"charset_normalizer>=2,<4",
"idna>=2.5,<4",
"urllib3>=1.26,<3",
"certifi>=2023.5.7"
]
dynamic = ["version"]
classifiers = [
"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.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.15",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: Free Threading :: 2 - Beta",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries"
]
[project.urls]
Documentation = "https://requests.readthedocs.io"
Source = "https://github.com/psf/requests"
[project.optional-dependencies]
security = []
socks = ["PySocks>=1.5.6, !=1.5.7"]
use_chardet_on_py3 = ["chardet>=3.0.2,<8"]
[dependency-groups]
test = [
"requests[socks]",
"pytest-httpbin==2.1.0",
"httpbin~=0.10.0",
"pytest-cov",
"pytest-mock",
"pytest-xdist",
"pytest>=3",
"trustme",
]
typecheck = [
"pyright",
"typing_extensions",
"chardet",
"cryptography",
"PyOpenSSL",
]
[tool.setuptools]
license-files = ["LICENSE", "NOTICE"]
[tool.setuptools.dynamic]
version = {attr = "requests.__version__.__version__"}
[tool.setuptools.packages.find]
where = ["src"]
[tool.ruff]
target-version = "py310"
src = ["src/requests", "tests"]
exclude = ["docs/", "ext/"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"T10", # flake8-debugger (replaces debug-statements hook)
]
ignore = ["E203", "E501", "UP031"]
per-file-ignores = {"src/requests/__init__.py" = ["E402", "F401"], "src/requests/compat.py" = ["E402", "F401"], "tests/compat.py" = ["F401"]}
[tool.ruff.lint.isort]
known-first-party = ["requests"]
[tool.ruff.format]
# Use black-compatible formatting
quote-style = "double"
indent-style = "space"
[tool.pytest.ini_options]
addopts = "--doctest-modules"
doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS"
minversion = "6.2"
testpaths = ["tests"]
[tool.pyright]
include = ["src/requests"]
typeCheckingMode = "strict"
# linting rules unrelated to type checking
reportPrivateUsage = false
reportPrivateImportUsage = false
reportUnnecessaryIsInstance = false
reportUnusedImport = false # duplicate of Ruff's F401 rule

View File

@@ -1,7 +1,7 @@
-e .[socks]
pytest>=2.8.0,<9
pytest>=2.8.0,<10
pytest-cov
pytest-httpbin==2.0.0
pytest-httpbin==2.1.0
httpbin~=0.10.0
trustme
wheel

View File

@@ -1,17 +0,0 @@
[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

131
setup.py
View File

@@ -1,132 +1,9 @@
#!/usr/bin/env python
import os
import sys
from codecs import open
from setuptools import setup
from setuptools.command.test import test as TestCommand
CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 8)
if CURRENT_PYTHON < REQUIRED_PYTHON:
sys.stderr.write(
"""
==========================
Unsupported Python version
==========================
This version of Requests requires at least Python {}.{}, but
you're trying to install it on Python {}.{}. To resolve this,
consider upgrading to a supported Python version.
If you can't upgrade your Python version, you'll need to
pin to an older version of Requests (<2.32.0).
""".format(
*(REQUIRED_PYTHON + CURRENT_PYTHON)
)
)
if sys.version_info < (3, 10): # noqa: UP036
sys.stderr.write("Requests requires Python 3.10 or later.\n")
sys.exit(1)
from setuptools import setup
class PyTest(TestCommand):
user_options = [("pytest-args=", "a", "Arguments to pass into py.test")]
def initialize_options(self):
TestCommand.initialize_options(self)
try:
from multiprocessing import cpu_count
self.pytest_args = ["-n", str(cpu_count()), "--boxed"]
except (ImportError, NotImplementedError):
self.pytest_args = ["-n", "1", "--boxed"]
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/*")
sys.exit()
requires = [
"charset_normalizer>=2,<4",
"idna>=2.5,<4",
"urllib3>=1.21.1,<3",
"certifi>=2017.4.17",
]
test_requirements = [
"pytest-httpbin==2.0.0",
"pytest-cov",
"pytest-mock",
"pytest-xdist",
"PySocks>=1.5.6, !=1.5.7",
"pytest>=3",
]
about = {}
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:
readme = f.read()
setup(
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=["requests"],
package_data={"": ["LICENSE", "NOTICE"]},
package_dir={"": "src"},
include_package_data=True,
python_requires=">=3.8",
install_requires=requires,
license=about["__license__"],
zip_safe=False,
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries",
],
cmdclass={"test": PyTest},
tests_require=test_requirements,
extras_require={
"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",
},
)
setup()

View File

@@ -38,6 +38,8 @@ is at <https://requests.readthedocs.io>.
:license: Apache 2.0, see LICENSE for more details.
"""
from __future__ import annotations
import warnings
import urllib3
@@ -55,16 +57,20 @@ 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.
def check_compatibility(
urllib3_version: str,
chardet_version: str | None,
charset_normalizer_version: str | None,
) -> None:
urllib3_version_list = urllib3_version.split(".")[:3]
assert urllib3_version_list != ["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")
if len(urllib3_version_list) == 2:
urllib3_version_list.append("0")
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = urllib3_version_list # noqa: F811
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1
assert major >= 1
@@ -75,8 +81,8 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver
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)
# chardet_version >= 3.0.2, < 8.0.0
assert (3, 0, 2) <= (major, minor, patch) < (8, 0, 0)
elif charset_normalizer_version:
major, minor, patch = charset_normalizer_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch)
@@ -90,31 +96,30 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver
)
def _check_cryptography(cryptography_version):
def _check_cryptography(cryptography_version: str) -> None:
# cryptography < 1.3.4
try:
cryptography_version = list(map(int, cryptography_version.split(".")))
cryptography_version_list = 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
)
if cryptography_version_list < [1, 3, 4]:
warning = f"Old version of cryptography ({cryptography_version_list}) may cause slowdown."
warnings.warn(warning, RequestsDependencyWarning)
# Check imported dependencies for compatibility.
try:
check_compatibility(
urllib3.__version__, chardet_version, charset_normalizer_version
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
),
f"urllib3 ({urllib3.__version__}) or chardet "
f"({chardet_version})/charset_normalizer ({charset_normalizer_version}) "
"doesn't match a supported version!",
RequestsDependencyWarning,
)
@@ -133,7 +138,9 @@ try:
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
from cryptography import (
__version__ as cryptography_version,
)
_check_cryptography(cryptography_version)
except ImportError:
@@ -178,6 +185,34 @@ from .models import PreparedRequest, Request, Response
from .sessions import Session, session
from .status_codes import codes
__all__ = (
"ConnectionError",
"ConnectTimeout",
"HTTPError",
"JSONDecodeError",
"PreparedRequest",
"ReadTimeout",
"Request",
"RequestException",
"Response",
"Session",
"Timeout",
"TooManyRedirects",
"URLRequired",
"codes",
"delete",
"get",
"head",
"options",
"packages",
"patch",
"post",
"put",
"request",
"session",
"utils",
)
logging.getLogger(__name__).addHandler(NullHandler())
# FileModeWarnings go off per the default.

View File

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

View File

@@ -5,14 +5,15 @@ 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 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]*$|^$")
_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*\Z")
_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*\Z")
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*\Z|^\Z")
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*\Z|^\Z")
_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)
@@ -22,7 +23,7 @@ HEADER_VALIDATORS = {
}
def to_native_string(string, encoding="ascii"):
def to_native_string(string: str | bytes, encoding: str = "ascii") -> str:
"""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.
@@ -35,7 +36,7 @@ def to_native_string(string, encoding="ascii"):
return out
def unicode_is_ascii(u_string):
def unicode_is_ascii(u_string: str) -> bool:
"""Determine if unicode string only contains ASCII characters.
:param str u_string: unicode string to check. Must be unicode

188
src/requests/_types.py Normal file
View File

@@ -0,0 +1,188 @@
"""
requests._types
~~~~~~~~~~~~~~~
This module contains type aliases used internally by the Requests library.
These types are not part of the public API and must not be relied upon
by external code.
"""
from __future__ import annotations
from collections.abc import Callable, Iterable, Mapping, MutableMapping, Sequence
from typing import (
TYPE_CHECKING,
Any,
Protocol,
TypeAlias,
TypeVar,
runtime_checkable,
)
_T_co = TypeVar("_T_co", covariant=True)
_KT_co = TypeVar("_KT_co", covariant=True)
_VT_co = TypeVar("_VT_co", covariant=True)
@runtime_checkable
class SupportsRead(Protocol[_T_co]):
def read(self, length: int = ..., /) -> _T_co: ...
def has_read(obj: Any) -> TypeIs[SupportsRead[str | bytes]]:
"""Check if obj supports read, including __getattr__ based proxies."""
return isinstance(obj, SupportsRead) or hasattr(obj, "read")
@runtime_checkable
class SupportsItems(Protocol[_KT_co, _VT_co]):
def items(self) -> Iterable[tuple[_KT_co, _VT_co]]: ...
# These are needed at runtime for default_hooks() return type
HookType: TypeAlias = Callable[["Response"], Any]
HooksInputType: TypeAlias = Mapping[str, Iterable[HookType] | HookType]
def is_prepared(request: PreparedRequest) -> TypeIs[_ValidatedRequest]:
"""Verify a PreparedRequest has been fully prepared."""
if TYPE_CHECKING:
return request.url is not None and request.method is not None
# noop at runtime to avoid AssertionError
return True
if TYPE_CHECKING:
from http.cookiejar import CookieJar
from typing import TypeAlias, TypedDict
from typing_extensions import (
Buffer, # TODO: move to collections.abc when Python >= 3.12
TypeIs, # TODO: move to typing when Python >= 3.13
)
from .auth import AuthBase
from .cookies import RequestsCookieJar
from .models import PreparedRequest, Response
from .structures import CaseInsensitiveDict
class _ValidatedRequest(PreparedRequest):
"""Subtype asserting a PreparedRequest has been fully prepared before calling.
The override suppression is required because mutable attribute types are
invariant (Liskov), but we only narrow after preparation is complete. This
is the explicit contract for Requests but Python's typing doesn't have a
better way to represent the requirement.
"""
url: str # type: ignore[reportIncompatibleVariableOverride]
method: str # type: ignore[reportIncompatibleVariableOverride]
# Type aliases for core API concepts (ordered by request() signature)
UriType: TypeAlias = str | bytes
_ParamsMappingKeyType: TypeAlias = str | bytes | int | float
_ParamsMappingValueType: TypeAlias = (
str | bytes | int | float | Iterable[str | bytes | int | float] | None
)
ParamsType: TypeAlias = (
SupportsItems[_ParamsMappingKeyType, _ParamsMappingValueType]
| tuple[tuple[_ParamsMappingKeyType, _ParamsMappingValueType], ...]
| Iterable[tuple[_ParamsMappingKeyType, _ParamsMappingValueType]]
| str
| bytes
| None
)
KVDataType: TypeAlias = Iterable[tuple[Any, Any]] | SupportsItems[Any, Any]
RawDataType: TypeAlias = KVDataType | str | bytes
StreamDataType: TypeAlias = SupportsRead[str | bytes]
EncodableDataType: TypeAlias = RawDataType | StreamDataType
DataType: TypeAlias = (
KVDataType
| Iterable[bytes | str]
| str
| bytes
| Buffer
| SupportsRead[str | bytes]
| None
)
BodyType: TypeAlias = (
bytes | str | Iterable[bytes | str] | SupportsRead[bytes | str] | None
)
HeadersType: TypeAlias = Mapping[str, str | bytes] | None
CookiesType: TypeAlias = RequestsCookieJar | Mapping[str, str]
# Building blocks for FilesType
_FileName: TypeAlias = str | None
_FileContent: TypeAlias = SupportsRead[str | bytes] | str | bytes
_FileSpecBasic: TypeAlias = tuple[_FileName, _FileContent]
_FileSpecWithContentType: TypeAlias = tuple[_FileName, _FileContent, str]
_FileSpecWithHeaders: TypeAlias = tuple[
_FileName, _FileContent, str, CaseInsensitiveDict[str] | Mapping[str, str]
]
_FileSpec: TypeAlias = (
_FileContent | _FileSpecBasic | _FileSpecWithContentType | _FileSpecWithHeaders
)
FilesType: TypeAlias = (
Mapping[str, _FileSpec] | Iterable[tuple[str, _FileSpec]] | None
)
AuthType: TypeAlias = (
tuple[str, str] | AuthBase | Callable[[PreparedRequest], PreparedRequest] | None
)
TimeoutType: TypeAlias = float | tuple[float | None, float | None] | None
ProxiesType: TypeAlias = MutableMapping[str, str]
HooksType: TypeAlias = dict[str, list[HookType]] | None
VerifyType: TypeAlias = bool | str
CertType: TypeAlias = str | tuple[str, str] | None
JsonType: TypeAlias = (
None
| bool
| int
| float
| str
| Sequence["JsonType"]
| Mapping[str, "JsonType"]
)
# TypedDicts for Unpack kwargs (PEP 692)
class BaseRequestKwargs(TypedDict, total=False):
headers: HeadersType
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None
files: FilesType
auth: AuthType
timeout: TimeoutType
allow_redirects: bool
proxies: dict[str, str] | None
hooks: HooksInputType | None
stream: bool | None
verify: VerifyType | None
cert: CertType
class RequestKwargs(BaseRequestKwargs, total=False):
"""kwargs for request(), options(), head(), delete()."""
params: ParamsType
data: DataType
json: JsonType
class GetKwargs(BaseRequestKwargs, total=False):
data: DataType
json: JsonType
class PostKwargs(BaseRequestKwargs, total=False):
params: ParamsType
class DataKwargs(BaseRequestKwargs, total=False):
"""kwargs for put(), patch()."""
params: ParamsType
json: JsonType

View File

@@ -6,27 +6,32 @@ This module contains the transport adapters that Requests uses to define
and maintain connections.
"""
from __future__ import annotations
import os.path
import socket # noqa: F401
import typing
import warnings
from typing import Any
from urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
from urllib3.exceptions import HTTPError as _HTTPError
from urllib3.exceptions import InvalidHeader as _InvalidHeader
from urllib3.exceptions import (
ClosedPoolError,
ConnectTimeoutError,
LocationValueError,
MaxRetryError,
NewConnectionError,
ProtocolError,
ReadTimeoutError,
ResponseError,
)
from urllib3.exceptions import HTTPError as _HTTPError
from urllib3.exceptions import InvalidHeader as _InvalidHeader
from urllib3.exceptions import ProxyError as _ProxyError
from urllib3.exceptions import ReadTimeoutError, ResponseError
from urllib3.exceptions import SSLError as _SSLError
from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util import parse_url
from urllib3.util.retry import Retry
from urllib3.util.ssl_ import create_urllib3_context
from .auth import _basic_auth_str
from .compat import basestring, urlparse
@@ -47,7 +52,6 @@ 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,
@@ -56,43 +60,43 @@ from .utils import (
)
try:
from urllib3.contrib.socks import SOCKSProxyManager
from urllib3.contrib.socks import SOCKSProxyManager # type: ignore[assignment]
except ImportError:
def SOCKSProxyManager(*args, **kwargs):
def SOCKSProxyManager(*args: Any, **kwargs: Any) -> None:
raise InvalidSchema("Missing dependencies for SOCKS support.")
if typing.TYPE_CHECKING:
from urllib3.connectionpool import HTTPConnectionPool
from urllib3.poolmanager import PoolManager as _PoolManager
from . import _types as _t
from .models import PreparedRequest
from ._types import is_prepared as _is_prepared
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None
_preloaded_ssl_context = create_urllib3_context()
_preloaded_ssl_context.load_verify_locations(
extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
)
def _urllib3_request_context(
request: "PreparedRequest",
verify: "bool | str | None",
client_cert: "typing.Tuple[str, str] | str | None",
) -> "(typing.Dict[str, typing.Any], typing.Dict[str, typing.Any])":
host_params = {}
pool_kwargs = {}
request: PreparedRequest,
verify: bool | str | None,
client_cert: tuple[str, str] | str | None,
poolmanager: PoolManager,
) -> tuple[dict[str, Any], dict[str, Any]]:
host_params: dict[str, Any] = {}
pool_kwargs: dict[str, Any] = {}
parsed_request_url = urlparse(request.url)
scheme = parsed_request_url.scheme.lower()
port = parsed_request_url.port
cert_reqs = "CERT_REQUIRED"
if verify is False:
cert_reqs = "CERT_NONE"
elif verify is True:
pool_kwargs["ssl_context"] = _preloaded_ssl_context
elif isinstance(verify, str):
if not os.path.isdir(verify):
pool_kwargs["ca_certs"] = verify
@@ -118,12 +122,18 @@ def _urllib3_request_context(
class BaseAdapter:
"""The Base Transport Adapter"""
def __init__(self):
def __init__(self) -> None:
super().__init__()
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
self,
request: PreparedRequest,
stream: bool = False,
timeout: _t.TimeoutType = None,
verify: _t.VerifyType = True,
cert: _t.CertType = None,
proxies: dict[str, str] | None = None,
) -> Response:
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -140,7 +150,7 @@ class BaseAdapter:
"""
raise NotImplementedError
def close(self):
def close(self) -> None:
"""Cleans up adapter specific items."""
raise NotImplementedError
@@ -172,7 +182,7 @@ class HTTPAdapter(BaseAdapter):
>>> s.mount('http://', a)
"""
__attrs__ = [
__attrs__: list[str] = [
"max_retries",
"config",
"_pool_connections",
@@ -180,13 +190,21 @@ class HTTPAdapter(BaseAdapter):
"_pool_block",
]
max_retries: Retry
config: dict[str, Any]
proxy_manager: dict[str, Any]
_pool_connections: int
_pool_maxsize: int
_pool_block: bool
poolmanager: _PoolManager
def __init__(
self,
pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE,
max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK,
):
pool_connections: int = DEFAULT_POOLSIZE,
pool_maxsize: int = DEFAULT_POOLSIZE,
max_retries: int | Retry = DEFAULT_RETRIES,
pool_block: bool = DEFAULT_POOLBLOCK,
) -> None:
if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False)
else:
@@ -202,10 +220,10 @@ class HTTPAdapter(BaseAdapter):
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
def __getstate__(self):
def __getstate__(self) -> dict[str, Any]:
return {attr: getattr(self, attr, None) for attr in self.__attrs__}
def __setstate__(self, state):
def __setstate__(self, state: dict[str, Any]) -> None:
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
# self.poolmanager uses a lambda function, which isn't pickleable.
self.proxy_manager = {}
@@ -219,8 +237,12 @@ class HTTPAdapter(BaseAdapter):
)
def init_poolmanager(
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
):
self,
connections: int,
maxsize: int,
block: bool = DEFAULT_POOLBLOCK,
**pool_kwargs: Any,
) -> None:
"""Initializes a urllib3 PoolManager.
This method should not be called from user code, and is only
@@ -244,7 +266,7 @@ class HTTPAdapter(BaseAdapter):
**pool_kwargs,
)
def proxy_manager_for(self, proxy, **proxy_kwargs):
def proxy_manager_for(self, proxy: str, **proxy_kwargs: Any) -> Any:
"""Return urllib3 ProxyManager for the given proxy.
This method should not be called from user code, and is only
@@ -282,7 +304,9 @@ class HTTPAdapter(BaseAdapter):
return manager
def cert_verify(self, conn, url, verify, cert):
def cert_verify(
self, conn: Any, url: str, verify: _t.VerifyType, cert: _t.CertType
) -> None:
"""Verify a SSL certificate. This method should not be called from user
code, and is only exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
@@ -295,26 +319,27 @@ class HTTPAdapter(BaseAdapter):
:param cert: The SSL certificate to verify.
"""
if url.lower().startswith("https") and verify:
conn.cert_reqs = "CERT_REQUIRED"
cert_loc = None
# Only load the CA certificates if 'verify' is a string indicating the CA bundle to use.
# Otherwise, if verify is a boolean, we don't load anything since
# the connection will be using a context with the default certificates already loaded,
# and this avoids a call to the slow load_verify_locations()
# Allow self-specified cert location.
if verify is not True:
# `verify` must be a str with a path then
cert_loc = verify
if not os.path.exists(cert_loc):
raise OSError(
f"Could not find a suitable TLS CA certificate bundle, "
f"invalid path: {cert_loc}"
)
if not cert_loc:
cert_loc = DEFAULT_CA_BUNDLE_PATH
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
if not cert_loc or not os.path.exists(cert_loc):
raise OSError(
f"Could not find a suitable TLS CA certificate bundle, "
f"invalid path: {cert_loc}"
)
conn.cert_reqs = "CERT_REQUIRED"
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
else:
conn.cert_reqs = "CERT_NONE"
conn.ca_certs = None
@@ -337,7 +362,7 @@ class HTTPAdapter(BaseAdapter):
f"Could not find the TLS key file, invalid path: {conn.key_file}"
)
def build_response(self, req, resp):
def build_response(self, req: PreparedRequest, resp: Any) -> Response:
"""Builds a :class:`Response <requests.Response>` object from a urllib3
response. This should not be called from user code, and is only exposed
for use when subclassing the
@@ -347,10 +372,11 @@ class HTTPAdapter(BaseAdapter):
:param resp: The urllib3 response object.
:rtype: requests.Response
"""
assert _is_prepared(req)
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) # type: ignore[assignment]
# Make headers case-insensitive.
response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))
@@ -374,13 +400,93 @@ class HTTPAdapter(BaseAdapter):
return response
def _get_connection(self, request, verify, proxies=None, cert=None):
# Replace the existing get_connection without breaking things and
# ensure that TLS settings are considered when we interact with
# urllib3 HTTP Pools
def build_connection_pool_key_attributes(
self, request: PreparedRequest, verify: _t.VerifyType, cert: _t.CertType = None
) -> tuple[dict[str, Any], dict[str, Any]]:
"""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 PreparedRequest 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: PreparedRequest,
verify: _t.VerifyType,
proxies: dict[str, str] | None = None,
cert: _t.CertType = None,
) -> HTTPConnectionPool:
"""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.HTTPConnectionPool
"""
assert _is_prepared(request)
proxy = select_proxy(request.url, proxies)
try:
host_params, pool_kwargs = _urllib3_request_context(request, verify, cert)
host_params, pool_kwargs = self.build_connection_pool_key_attributes(
request,
verify,
cert,
)
except ValueError as e:
raise InvalidURL(e, request=request)
if proxy:
@@ -403,15 +509,29 @@ class HTTPAdapter(BaseAdapter):
return conn
def get_connection(self, url, proxies=None):
"""Returns a urllib3 connection for the given URL. This should not be
def get_connection(
self, url: str, proxies: dict[str, str] | None = None
) -> HTTPConnectionPool:
"""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>`.
:param url: The URL to connect to.
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
:rtype: urllib3.ConnectionPool
:rtype: urllib3.HTTPConnectionPool
"""
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:
@@ -432,7 +552,7 @@ class HTTPAdapter(BaseAdapter):
return conn
def close(self):
def close(self) -> None:
"""Disposes of any internal state.
Currently, this closes the PoolManager and any active ProxyManager,
@@ -442,7 +562,9 @@ class HTTPAdapter(BaseAdapter):
for proxy in self.proxy_manager.values():
proxy.clear()
def request_url(self, request, proxies):
def request_url(
self, request: PreparedRequest, proxies: dict[str, str] | None
) -> str:
"""Obtain the url to use when making the final request.
If the message is being sent through a HTTP proxy, the full URL has to
@@ -456,6 +578,8 @@ class HTTPAdapter(BaseAdapter):
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
:rtype: str
"""
assert _is_prepared(request)
proxy = select_proxy(request.url, proxies)
scheme = urlparse(request.url).scheme
@@ -466,15 +590,13 @@ class HTTPAdapter(BaseAdapter):
using_socks_proxy = proxy_scheme.startswith("socks")
url = request.path_url
if url.startswith("//"): # Don't confuse urllib3
url = f"/{url.lstrip('/')}"
if is_proxied_http_request and not using_socks_proxy:
url = urldefragauth(request.url)
return url
def add_headers(self, request, **kwargs):
def add_headers(self, request: PreparedRequest, **kwargs: Any) -> None:
"""Add any headers needed by the connection. As of v2.0 this does
nothing by default, but is left for overriding by users that subclass
the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
@@ -488,7 +610,7 @@ class HTTPAdapter(BaseAdapter):
"""
pass
def proxy_headers(self, proxy):
def proxy_headers(self, proxy: str) -> dict[str, str]:
"""Returns a dictionary of the headers to add to any request sent
through a proxy. This works with urllib3 magic to ensure that they are
correctly sent to the proxy, rather than in a tunnelled request if
@@ -501,7 +623,7 @@ class HTTPAdapter(BaseAdapter):
:param proxy: The url of the proxy being used for this request.
:rtype: dict
"""
headers = {}
headers: dict[str, str] = {}
username, password = get_auth_from_url(proxy)
if username:
@@ -510,8 +632,14 @@ class HTTPAdapter(BaseAdapter):
return headers
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
self,
request: PreparedRequest,
stream: bool = False,
timeout: _t.TimeoutType = None,
verify: _t.VerifyType = True,
cert: _t.CertType = None,
proxies: dict[str, str] | None = None,
) -> Response:
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -528,8 +656,12 @@ class HTTPAdapter(BaseAdapter):
:rtype: requests.Response
"""
assert _is_prepared(request)
try:
conn = self._get_connection(request, verify, proxies=proxies, cert=cert)
conn = self.get_connection_with_tls_context(
request, verify, proxies=proxies, cert=cert
)
except LocationValueError as e:
raise InvalidURL(e, request=request)
@@ -549,29 +681,29 @@ class HTTPAdapter(BaseAdapter):
if isinstance(timeout, tuple):
try:
connect, read = timeout
timeout = TimeoutSauce(connect=connect, read=read)
resolved_timeout = TimeoutSauce(connect=connect, read=read)
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
resolved_timeout = timeout
else:
timeout = TimeoutSauce(connect=timeout, read=timeout)
resolved_timeout = TimeoutSauce(connect=timeout, read=timeout)
try:
resp = conn.urlopen(
method=request.method,
url=url,
body=request.body,
headers=request.headers,
body=request.body, # type: ignore[arg-type] # urllib3 stubs don't accept Iterable[bytes | str]
headers=request.headers, # type: ignore[arg-type] # urllib3#3072
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout,
timeout=resolved_timeout,
chunked=chunked,
)

View File

@@ -8,10 +8,22 @@ This module implements the Requests API.
:license: Apache2, see LICENSE for more details.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from . import sessions
from .models import Response
if TYPE_CHECKING:
from typing_extensions import Unpack
from . import _types as _t
def request(method, url, **kwargs):
def request(
method: str, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]
) -> Response:
"""Constructs and sends a :class:`Request <Request>`.
:param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
@@ -59,7 +71,9 @@ def request(method, url, **kwargs):
return session.request(method=method, url=url, **kwargs)
def get(url, params=None, **kwargs):
def get(
url: _t.UriType, params: _t.ParamsType = None, **kwargs: Unpack[_t.GetKwargs]
) -> Response:
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
@@ -73,7 +87,7 @@ def get(url, params=None, **kwargs):
return request("get", url, params=params, **kwargs)
def options(url, **kwargs):
def options(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
r"""Sends an OPTIONS request.
:param url: URL for the new :class:`Request` object.
@@ -85,7 +99,7 @@ def options(url, **kwargs):
return request("options", url, **kwargs)
def head(url, **kwargs):
def head(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
r"""Sends a HEAD request.
:param url: URL for the new :class:`Request` object.
@@ -100,7 +114,12 @@ def head(url, **kwargs):
return request("head", url, **kwargs)
def post(url, data=None, json=None, **kwargs):
def post(
url: _t.UriType,
data: _t.DataType = None,
json: _t.JsonType = None,
**kwargs: Unpack[_t.PostKwargs],
) -> Response:
r"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
@@ -115,7 +134,9 @@ def post(url, data=None, json=None, **kwargs):
return request("post", url, data=data, json=json, **kwargs)
def put(url, data=None, **kwargs):
def put(
url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
) -> Response:
r"""Sends a PUT request.
:param url: URL for the new :class:`Request` object.
@@ -130,7 +151,9 @@ def put(url, data=None, **kwargs):
return request("put", url, data=data, **kwargs)
def patch(url, data=None, **kwargs):
def patch(
url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
) -> Response:
r"""Sends a PATCH request.
:param url: URL for the new :class:`Request` object.
@@ -145,7 +168,7 @@ def patch(url, data=None, **kwargs):
return request("patch", url, data=data, **kwargs)
def delete(url, **kwargs):
def delete(url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
r"""Sends a DELETE request.
:param url: URL for the new :class:`Request` object.

View File

@@ -5,6 +5,8 @@ requests.auth
This module contains the authentication handlers for Requests.
"""
from __future__ import annotations
import hashlib
import os
import re
@@ -12,17 +14,24 @@ import threading
import time
import warnings
from base64 import b64encode
from typing import TYPE_CHECKING, Any, Final, cast, overload
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"
if TYPE_CHECKING:
from http.cookiejar import CookieJar
from typing import Any
from .models import PreparedRequest, Response
CONTENT_TYPE_FORM_URLENCODED: Final = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART: Final = "multipart/form-data"
def _basic_auth_str(username, password):
def _basic_auth_str(username: bytes | str, password: bytes | str) -> str:
"""Returns a Basic Auth string."""
# "I want us to put a big-ol' comment on top of it that
@@ -32,22 +41,22 @@ def _basic_auth_str(username, password):
#
# These are here solely to maintain backwards compatibility
# for things like ints. This will be removed in 3.0.0.
if not isinstance(username, basestring):
if not isinstance(username, basestring): # runtime guard for non-str/bytes
warnings.warn(
"Non-string usernames will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({!r}) to "
f"3.0.0. Please convert the object you've passed in ({username!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(username),
"problems.",
category=DeprecationWarning,
)
username = str(username)
if not isinstance(password, basestring):
if not isinstance(password, basestring): # runtime guard for non-str/bytes
warnings.warn(
"Non-string passwords will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({!r}) to "
f"3.0.0. Please convert the object you've passed in ({type(password)!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(type(password)),
"problems.",
category=DeprecationWarning,
)
password = str(password)
@@ -69,18 +78,26 @@ def _basic_auth_str(username, password):
class AuthBase:
"""Base class that all auth implementations derive from"""
def __call__(self, r):
def __call__(self, r: PreparedRequest) -> PreparedRequest:
raise NotImplementedError("Auth hooks must be callable.")
class HTTPBasicAuth(AuthBase):
"""Attaches HTTP Basic Authentication to the given Request object."""
def __init__(self, username, password):
username: bytes | str
password: bytes | str
@overload
def __init__(self, username: str, password: str) -> None: ...
@overload
def __init__(self, username: bytes, password: bytes) -> None: ...
def __init__(self, username: bytes | str, password: bytes | str) -> None:
self.username = username
self.password = password
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
return all(
[
self.username == getattr(other, "username", None),
@@ -88,10 +105,10 @@ class HTTPBasicAuth(AuthBase):
]
)
def __ne__(self, other):
def __ne__(self, other: Any) -> bool:
return not self == other
def __call__(self, r):
def __call__(self, r: PreparedRequest) -> PreparedRequest:
r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
return r
@@ -99,7 +116,7 @@ class HTTPBasicAuth(AuthBase):
class HTTPProxyAuth(HTTPBasicAuth):
"""Attaches HTTP Proxy Authentication to a given Request object."""
def __call__(self, r):
def __call__(self, r: PreparedRequest) -> PreparedRequest:
r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
return r
@@ -107,13 +124,27 @@ class HTTPProxyAuth(HTTPBasicAuth):
class HTTPDigestAuth(AuthBase):
"""Attaches HTTP Digest Authentication to the given Request object."""
def __init__(self, username, password):
username: bytes | str
password: bytes | str
_thread_local: threading.local
last_nonce: str
nonce_count: int
chal: dict[str, str]
pos: int | None
num_401_calls: int | None
@overload
def __init__(self, username: str, password: str) -> None: ...
@overload
def __init__(self, username: bytes, password: bytes) -> None: ...
def __init__(self, username: bytes | str, password: bytes | str) -> None:
self.username = username
self.password = password
# Keep state in per-thread local storage
self._thread_local = threading.local()
def init_per_thread_state(self):
def init_per_thread_state(self) -> None:
# Ensure state is initialized just once per-thread
if not hasattr(self._thread_local, "init"):
self._thread_local.init = True
@@ -123,7 +154,7 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.pos = None
self._thread_local.num_401_calls = None
def build_digest_header(self, method, url):
def build_digest_header(self, method: str, url: str) -> str | None:
"""
:rtype: str
"""
@@ -142,42 +173,43 @@ class HTTPDigestAuth(AuthBase):
# lambdas assume digest modules are imported at the top level
if _algorithm == "MD5" or _algorithm == "MD5-SESS":
def md5_utf8(x):
def md5_utf8(x: str | bytes) -> str:
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.md5(x).hexdigest()
return hashlib.md5(x, usedforsecurity=False).hexdigest()
hash_utf8 = md5_utf8
elif _algorithm == "SHA":
def sha_utf8(x):
def sha_utf8(x: str | bytes) -> str:
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.sha1(x).hexdigest()
return hashlib.sha1(x, usedforsecurity=False).hexdigest()
hash_utf8 = sha_utf8
elif _algorithm == "SHA-256":
def sha256_utf8(x):
def sha256_utf8(x: str | bytes) -> str:
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.sha256(x).hexdigest()
return hashlib.sha256(x, usedforsecurity=False).hexdigest()
hash_utf8 = sha256_utf8
elif _algorithm == "SHA-512":
def sha512_utf8(x):
def sha512_utf8(x: str | bytes) -> str:
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.sha512(x).hexdigest()
return hashlib.sha512(x, usedforsecurity=False).hexdigest()
hash_utf8 = sha512_utf8
KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731
if hash_utf8 is None:
return None
def KD(s: str, d: str) -> str:
return hash_utf8(f"{s}:{d}")
# XXX not implemented yet
entdig = None
p_parsed = urlparse(url)
@@ -202,9 +234,9 @@ class HTTPDigestAuth(AuthBase):
s += time.ctime().encode("utf-8")
s += os.urandom(8)
cnonce = hashlib.sha1(s).hexdigest()[:16]
cnonce = hashlib.sha1(s, usedforsecurity=False).hexdigest()[:16]
if _algorithm == "MD5-SESS":
HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")
HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}") # type: ignore[reportConstantRedefinition] # RFC 2617 terminology
if not qop:
respdig = KD(HA1, f"{nonce}:{HA2}")
@@ -233,12 +265,12 @@ class HTTPDigestAuth(AuthBase):
return f"Digest {base}"
def handle_redirect(self, r, **kwargs):
def handle_redirect(self, r: Response, **kwargs: Any) -> None:
"""Reset num_401_calls counter on redirects."""
if r.is_redirect:
self._thread_local.num_401_calls = 1
def handle_401(self, r, **kwargs):
def handle_401(self, r: Response, **kwargs: Any) -> Response:
"""
Takes the given response and tries digest-auth, if needed.
@@ -254,7 +286,8 @@ class HTTPDigestAuth(AuthBase):
if self._thread_local.pos is not None:
# Rewind the file position indicator of the body to where
# it was to resend the request.
r.request.body.seek(self._thread_local.pos)
if (seek := getattr(r.request.body, "seek", None)) is not None:
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:
@@ -267,12 +300,15 @@ class HTTPDigestAuth(AuthBase):
r.content
r.close()
prep = r.request.copy()
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
prep.prepare_cookies(prep._cookies)
cookie_jar = cast("CookieJar", prep._cookies)
extract_cookies_to_jar(cookie_jar, r.request, r.raw)
prep.prepare_cookies(cookie_jar)
prep.headers["Authorization"] = self.build_digest_header(
prep.method, prep.url
_digest_auth = self.build_digest_header(
cast(str, prep.method), cast(str, prep.url)
)
if _digest_auth:
prep.headers["Authorization"] = _digest_auth
_r = r.connection.send(prep, **kwargs)
_r.history.append(r)
_r.request = prep
@@ -282,15 +318,19 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.num_401_calls = 1
return r
def __call__(self, r):
def __call__(self, r: PreparedRequest) -> PreparedRequest:
# Initialize per-thread state, if needed
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)
try:
self._thread_local.pos = r.body.tell()
except AttributeError:
_digest_auth = self.build_digest_header(
cast(str, r.method), cast(str, r.url)
)
if _digest_auth:
r.headers["Authorization"] = _digest_auth
if (tell := getattr(r.body, "tell", None)) is not None:
self._thread_local.pos = tell()
else:
# In the case of HTTPDigestAuth being reused and the body of
# the previous request was a file-like object, pos has the
# file position of the previous body. Ensure it's set to
@@ -302,7 +342,7 @@ class HTTPDigestAuth(AuthBase):
return r
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
return all(
[
self.username == getattr(other, "username", None),
@@ -310,5 +350,5 @@ class HTTPDigestAuth(AuthBase):
]
)
def __ne__(self, other):
def __ne__(self, other: Any) -> bool:
return not self == other

View File

@@ -11,6 +11,7 @@ If you are packaging Requests, e.g., for a Linux distribution or a managed
environment, you can change the definition of where() to return a separately
packaged CA bundle.
"""
from certifi import where
if __name__ == "__main__":

View File

@@ -7,15 +7,33 @@ between Python 2 and Python 3. It remains for backwards
compatibility until the next major version.
"""
# pyright: reportUnusedImport=false
from __future__ import annotations
import importlib
import sys
from types import ModuleType
from typing import TYPE_CHECKING
# -------
# urllib3
# -------
from urllib3 import __version__ as urllib3_version
# 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():
def _resolve_char_detection() -> ModuleType | None:
"""Find supported character detection libraries."""
chardet = None
for lib in ("chardet", "charset_normalizer"):
@@ -27,7 +45,10 @@ def _resolve_char_detection():
return chardet
chardet = _resolve_char_detection()
if TYPE_CHECKING:
import chardet
else:
chardet = _resolve_char_detection()
# -------
# Pythons
@@ -45,14 +66,14 @@ is_py3 = _ver[0] == 3
# json/simplejson module import resolution
has_simplejson = False
try:
import simplejson as json
import simplejson as json # type: ignore[import-not-found]
has_simplejson = True
except ImportError:
import json
if has_simplejson:
from simplejson import JSONDecodeError
from simplejson import JSONDecodeError # type: ignore[import-not-found]
else:
from json import JSONDecodeError
@@ -83,7 +104,7 @@ from urllib.request import (
getproxies_environment,
parse_http_list,
proxy_bypass,
proxy_bypass_environment,
proxy_bypass_environment, # type: ignore[attr-defined] # https://github.com/python/cpython/issues/145331
)
builtin_str = str

View File

@@ -7,21 +7,29 @@ Compatibility code to be able to use `http.cookiejar.CookieJar` with requests.
requests.utils imports from here, so be careful with imports.
"""
from __future__ import annotations
import calendar
import copy
import time
from collections.abc import Iterator, MutableMapping
from http.cookiejar import Cookie, CookieJar, CookiePolicy
from typing import TYPE_CHECKING, Any, TypeVar, overload
from ._internal_utils import to_native_string
from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse
from ._types import is_prepared as _is_prepared
from .compat import Morsel, cookielib, urlparse, urlunparse
try:
import threading
except ImportError:
import dummy_threading as threading
if TYPE_CHECKING:
from _typeshed import SupportsKeysAndGetItem
from .models import PreparedRequest
import threading
class MockRequest:
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
"""Wraps a `requests.PreparedRequest` to mimic a `urllib2.Request`.
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
@@ -32,21 +40,24 @@ class MockRequest:
probably want `get_cookie_header`, defined below.
"""
def __init__(self, request):
type: str
def __init__(self, request: PreparedRequest) -> None:
assert _is_prepared(request)
self._r = request
self._new_headers = {}
self._new_headers: dict[str, str] = {}
self.type = urlparse(self._r.url).scheme
def get_type(self):
def get_type(self) -> str:
return self.type
def get_host(self):
def get_host(self) -> str:
return urlparse(self._r.url).netloc
def get_origin_req_host(self):
def get_origin_req_host(self) -> str:
return self.get_host()
def get_full_url(self):
def get_full_url(self) -> str:
# Only return the response's URL if the user hadn't set the Host
# header
if not self._r.headers.get("Host"):
@@ -66,37 +77,37 @@ class MockRequest:
]
)
def is_unverifiable(self):
def is_unverifiable(self) -> bool:
return True
def has_header(self, name):
def has_header(self, name: str) -> bool:
return name in self._r.headers or name in self._new_headers
def get_header(self, name, default=None):
return self._r.headers.get(name, self._new_headers.get(name, default))
def get_header(self, name: str, default: str | None = None) -> str | None:
return self._r.headers.get(name, self._new_headers.get(name, default)) # type: ignore[return-value]
def add_header(self, key, val):
def add_header(self, key: str, val: str) -> None:
"""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):
def add_unredirected_header(self, name: str, value: str) -> None:
self._new_headers[name] = value
def get_new_headers(self):
def get_new_headers(self) -> dict[str, str]:
return self._new_headers
@property
def unverifiable(self):
def unverifiable(self) -> bool:
return self.is_unverifiable()
@property
def origin_req_host(self):
def origin_req_host(self) -> str:
return self.get_origin_req_host()
@property
def host(self):
def host(self) -> str:
return self.get_host()
@@ -107,21 +118,23 @@ class MockResponse:
the way `http.cookiejar` expects to see them.
"""
def __init__(self, headers):
def __init__(self, headers: Any) -> None:
"""Make a MockResponse for `cookiejar` to read.
:param headers: a httplib.HTTPMessage or analogous carrying the headers
"""
self._headers = headers
def info(self):
def info(self) -> Any:
return self._headers
def getheaders(self, name):
def getheaders(self, name: str) -> Any:
self._headers.getheaders(name)
def extract_cookies_to_jar(jar, request, response):
def extract_cookies_to_jar(
jar: CookieJar, request: PreparedRequest, response: Any
) -> None:
"""Extract the cookies from the response into a CookieJar.
:param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar)
@@ -134,26 +147,28 @@ def extract_cookies_to_jar(jar, request, response):
req = MockRequest(request)
# pull out the HTTPMessage with the headers and put it in the mock:
res = MockResponse(response._original_response.msg)
jar.extract_cookies(res, req)
jar.extract_cookies(res, req) # type: ignore[arg-type]
def get_cookie_header(jar, request):
def get_cookie_header(jar: CookieJar, request: PreparedRequest) -> str | None:
"""
Produce an appropriate Cookie header string to be sent with `request`, or None.
:rtype: str
"""
r = MockRequest(request)
jar.add_cookie_header(r)
jar.add_cookie_header(r) # type: ignore[arg-type]
return r.get_new_headers().get("Cookie")
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
def remove_cookie_by_name(
cookiejar: CookieJar, name: str, domain: str | None = None, path: str | None = None
) -> None:
"""Unsets a cookie by name, by default over all domains and paths.
Wraps CookieJar.clear(), is O(n).
"""
clearables = []
clearables: list[tuple[str, str, str]] = []
for cookie in cookiejar:
if cookie.name != name:
continue
@@ -173,7 +188,7 @@ class CookieConflictError(RuntimeError):
"""
class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
class RequestsCookieJar(CookieJar, MutableMapping[str, str | None]): # type: ignore[misc]
"""Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict
interface.
@@ -191,7 +206,15 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
.. warning:: dictionary operations that are normally O(1) may be O(n).
"""
def get(self, name, default=None, domain=None, path=None):
_policy: CookiePolicy
def get( # type: ignore[override]
self,
name: str,
default: str | None = None,
domain: str | None = None,
path: str | None = None,
) -> str | None:
"""Dict-like get() that also supports optional domain and path args in
order to resolve naming collisions from using one cookie jar over
multiple domains.
@@ -203,7 +226,9 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
except KeyError:
return default
def set(self, name, value, **kwargs):
def set(
self, name: str, value: str | Morsel[dict[str, str]] | None, **kwargs: Any
) -> Cookie | None:
"""Dict-like set() that also supports optional domain and path args in
order to resolve naming collisions from using one cookie jar over
multiple domains.
@@ -222,7 +247,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
self.set_cookie(c)
return c
def iterkeys(self):
def iterkeys(self) -> Iterator[str]:
"""Dict-like iterkeys() that returns an iterator of names of cookies
from the jar.
@@ -231,7 +256,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
for cookie in iter(self):
yield cookie.name
def keys(self):
def keys(self) -> list[str]: # type: ignore[override]
"""Dict-like keys() that returns a list of names of cookies from the
jar.
@@ -239,7 +264,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
return list(self.iterkeys())
def itervalues(self):
def itervalues(self) -> Iterator[str | None]:
"""Dict-like itervalues() that returns an iterator of values of cookies
from the jar.
@@ -248,7 +273,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
for cookie in iter(self):
yield cookie.value
def values(self):
def values(self) -> list[str | None]: # type: ignore[override]
"""Dict-like values() that returns a list of values of cookies from the
jar.
@@ -256,7 +281,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
return list(self.itervalues())
def iteritems(self):
def iteritems(self) -> Iterator[tuple[str, str | None]]:
"""Dict-like iteritems() that returns an iterator of name-value tuples
from the jar.
@@ -265,7 +290,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
for cookie in iter(self):
yield cookie.name, cookie.value
def items(self):
def items(self) -> list[tuple[str, str | None]]: # type: ignore[override]
"""Dict-like items() that returns a list of name-value tuples from the
jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
vanilla python dict of key value pairs.
@@ -274,43 +299,45 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
return list(self.iteritems())
def list_domains(self):
def list_domains(self) -> list[str]:
"""Utility method to list all the domains in the jar."""
domains = []
domains: list[str] = []
for cookie in iter(self):
if cookie.domain not in domains:
domains.append(cookie.domain)
return domains
def list_paths(self):
def list_paths(self) -> list[str]:
"""Utility method to list all the paths in the jar."""
paths = []
paths: list[str] = []
for cookie in iter(self):
if cookie.path not in paths:
paths.append(cookie.path)
return paths
def multiple_domains(self):
def multiple_domains(self) -> bool:
"""Returns True if there are multiple domains in the jar.
Returns False otherwise.
:rtype: bool
"""
domains = []
domains: list[str] = []
for cookie in iter(self):
if cookie.domain is not None and cookie.domain in domains:
if cookie.domain is not None and cookie.domain in domains: # type: ignore[reportUnnecessaryComparison] # defensive check
return True
domains.append(cookie.domain)
return False # there is only one domain in jar
def get_dict(self, domain=None, path=None):
def get_dict(
self, domain: str | None = None, path: str | None = None
) -> dict[str, str | None]:
"""Takes as an argument an optional domain and path and returns a plain
old Python dict of name-value pairs of cookies that meet the
requirements.
:rtype: dict
"""
dictionary = {}
dictionary: dict[str, str | None] = {}
for cookie in iter(self):
if (domain is None or cookie.domain == domain) and (
path is None or cookie.path == path
@@ -318,13 +345,17 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
dictionary[cookie.name] = cookie.value
return dictionary
def __contains__(self, name):
def __iter__(self) -> Iterator[Cookie]: # type: ignore[override]
"""RequestCookieJar's __iter__ comes from CookieJar not MutableMapping."""
return super().__iter__()
def __contains__(self, name: object) -> bool:
try:
return super().__contains__(name)
except CookieConflictError:
return True
def __getitem__(self, name):
def __getitem__(self, name: str) -> str | None:
"""Dict-like __getitem__() for compatibility with client code. Throws
exception if there are more than one cookie with name. In that case,
use the more explicit get() method instead.
@@ -333,29 +364,33 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""
return self._find_no_duplicates(name)
def __setitem__(self, name, value):
def __setitem__(
self, name: str, value: str | Morsel[dict[str, str]] | None
) -> None:
"""Dict-like __setitem__ for compatibility with client code. Throws
exception if there is already a cookie of that name in the jar. In that
case, use the more explicit set() method instead.
"""
self.set(name, value)
def __delitem__(self, name):
def __delitem__(self, name: str) -> None:
"""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):
def set_cookie(self, cookie: Cookie, *args: Any, **kwargs: Any) -> None:
if (
hasattr(cookie.value, "startswith")
and cookie.value.startswith('"')
and cookie.value.endswith('"')
(value := cookie.value) is not None
and value.startswith('"')
and value.endswith('"')
):
cookie.value = cookie.value.replace('\\"', "")
cookie.value = value.replace('\\"', "")
return super().set_cookie(cookie, *args, **kwargs)
def update(self, other):
def update( # type: ignore[override]
self, other: CookieJar | SupportsKeysAndGetItem[str, str]
) -> None:
"""Updates this jar with cookies from another CookieJar or dict-like"""
if isinstance(other, cookielib.CookieJar):
for cookie in other:
@@ -363,7 +398,9 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
else:
super().update(other)
def _find(self, name, domain=None, path=None):
def _find(
self, name: str, domain: str | None = None, path: str | None = None
) -> str | None:
"""Requests uses this method internally to get cookie values.
If there are conflicting cookies, _find arbitrarily chooses one.
@@ -383,7 +420,9 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def _find_no_duplicates(self, name, domain=None, path=None):
def _find_no_duplicates(
self, name: str, domain: str | None = None, path: str | None = None
) -> str:
"""Both ``__get_item__`` and ``get`` call this function: it's never
used elsewhere in Requests.
@@ -408,42 +447,42 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
# we will eventually return this as long as no cookie conflict
toReturn = cookie.value
if toReturn:
if toReturn is not None:
return toReturn
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def __getstate__(self):
def __getstate__(self) -> dict[str, Any]:
"""Unlike a normal CookieJar, this class is pickleable."""
state = self.__dict__.copy()
# remove the unpickleable RLock object
state.pop("_cookies_lock")
return state
def __setstate__(self, state):
def __setstate__(self, state: dict[str, Any]) -> None:
"""Unlike a normal CookieJar, this class is pickleable."""
self.__dict__.update(state)
if "_cookies_lock" not in self.__dict__:
self._cookies_lock = threading.RLock()
def copy(self):
def copy(self) -> RequestsCookieJar:
"""Return a copy of this RequestsCookieJar."""
new_cj = RequestsCookieJar()
new_cj.set_policy(self.get_policy())
new_cj.update(self)
return new_cj
def get_policy(self):
def get_policy(self) -> CookiePolicy:
"""Return the CookiePolicy instance used."""
return self._policy
def _copy_cookie_jar(jar):
def _copy_cookie_jar(jar: CookieJar | None) -> CookieJar | None: # type: ignore[reportUnusedFunction] # cross-module usage in models.py
if jar is None:
return None
if hasattr(jar, "copy"):
if copy_method := getattr(jar, "copy", None):
# We're dealing with an instance of RequestsCookieJar
return jar.copy()
return copy_method()
# We're dealing with a generic CookieJar instance
new_jar = copy.copy(jar)
new_jar.clear()
@@ -452,13 +491,13 @@ def _copy_cookie_jar(jar):
return new_jar
def create_cookie(name, value, **kwargs):
def create_cookie(name: str, value: str, **kwargs: Any) -> Cookie:
"""Make a cookie from underspecified parameters.
By default, the pair of `name` and `value` will be set for the domain ''
and sent on every request (this is sometimes called a "supercookie").
"""
result = {
result: dict[str, Any] = {
"version": 0,
"name": name,
"value": value,
@@ -489,10 +528,10 @@ def create_cookie(name, value, **kwargs):
return cookielib.Cookie(**result)
def morsel_to_cookie(morsel):
def morsel_to_cookie(morsel: Morsel[Any]) -> Cookie:
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
expires = None
expires: int | None = None
if morsel["max-age"]:
try:
expires = int(time.time() + int(morsel["max-age"]))
@@ -518,7 +557,30 @@ def morsel_to_cookie(morsel):
)
def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
_CookieJarT = TypeVar("_CookieJarT", bound=CookieJar)
@overload
def cookiejar_from_dict(
cookie_dict: dict[str, str] | None,
cookiejar: None = None,
overwrite: bool = True,
) -> RequestsCookieJar: ...
@overload
def cookiejar_from_dict(
cookie_dict: dict[str, str] | None,
cookiejar: _CookieJarT,
overwrite: bool = True,
) -> _CookieJarT: ...
def cookiejar_from_dict(
cookie_dict: dict[str, str] | None,
cookiejar: CookieJar | None = None,
overwrite: bool = True,
) -> CookieJar:
"""Returns a CookieJar from a key/value dictionary.
:param cookie_dict: Dict of key/values to insert into CookieJar.
@@ -539,22 +601,24 @@ def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
return cookiejar
def merge_cookies(cookiejar, cookies):
def merge_cookies(
cookiejar: CookieJar, cookies: dict[str, str] | CookieJar | None
) -> CookieJar:
"""Add cookies to cookiejar and returns a merged CookieJar.
:param cookiejar: CookieJar object to add the cookies to.
:param cookies: Dictionary or CookieJar object to be added.
:rtype: CookieJar
"""
if not isinstance(cookiejar, cookielib.CookieJar):
if not isinstance(cookiejar, cookielib.CookieJar): # runtime guard
raise ValueError("You can only merge into CookieJar")
if isinstance(cookies, dict):
cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
elif isinstance(cookies, cookielib.CookieJar):
try:
cookiejar.update(cookies)
except AttributeError:
if update_method := getattr(cookiejar, "update", None):
update_method(cookies)
else:
for cookie_in_jar in cookies:
cookiejar.set_cookie(cookie_in_jar)

View File

@@ -4,23 +4,34 @@ requests.exceptions
This module contains the set of Requests' exceptions.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from urllib3.exceptions import HTTPError as BaseHTTPError
from .compat import JSONDecodeError as CompatJSONDecodeError
if TYPE_CHECKING:
from .models import PreparedRequest, Request, Response
class RequestException(IOError):
"""There was an ambiguous exception that occurred while handling your
request.
"""
def __init__(self, *args, **kwargs):
response: Response | None
request: Request | PreparedRequest | None
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize RequestException with `request` and `response` objects."""
response = kwargs.pop("response", None)
response: Response | None = 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 = self.response.request
self.request = response.request
super().__init__(*args, **kwargs)
@@ -31,7 +42,7 @@ class InvalidJSONError(RequestException):
class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
"""Couldn't decode the text into json"""
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""
Construct the JSONDecodeError instance first with all
args. Then use it's args to construct the IOError so that
@@ -41,7 +52,7 @@ class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
CompatJSONDecodeError.__init__(self, *args)
InvalidJSONError.__init__(self, *self.args, **kwargs)
def __reduce__(self):
def __reduce__(self) -> tuple[Any, ...] | str:
"""
The __reduce__ method called when pickling the object must
be the one from the JSONDecodeError (be it json/simplejson)

View File

@@ -4,6 +4,7 @@ import json
import platform
import ssl
import sys
from typing import Any
import idna
import urllib3
@@ -47,14 +48,11 @@ def _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":
pypy = sys.pypy_version_info # type: ignore[attr-defined]
implementation_version = f"{pypy.major}.{pypy.minor}.{pypy.micro}" # pyright: ignore[reportUnknownMemberType]
if sys.pypy_version_info.releaselevel != "final": # type: ignore[attr-defined]
implementation_version = "".join(
[implementation_version, sys.pypy_version_info.releaselevel]
[implementation_version, sys.pypy_version_info.releaselevel] # type: ignore[attr-defined]
)
elif implementation == "Jython":
implementation_version = platform.python_version() # Complete Guess
@@ -66,7 +64,7 @@ def _implementation():
return {"name": implementation, "version": implementation_version}
def info():
def info() -> dict[str, Any]:
"""Generate information for a bug report."""
try:
platform_info = {
@@ -82,13 +80,13 @@ def info():
implementation_info = _implementation()
urllib3_info = {"version": urllib3.__version__}
charset_normalizer_info = {"version": None}
chardet_info = {"version": None}
chardet_info: dict[str, str | None] = {"version": None}
if charset_normalizer:
charset_normalizer_info = {"version": charset_normalizer.__version__}
if chardet:
chardet_info = {"version": chardet.__version__}
pyopenssl_info = {
pyopenssl_info: dict[str, str | None] = {
"version": None,
"openssl_version": "",
}
@@ -105,7 +103,7 @@ def info():
}
system_ssl = ssl.OPENSSL_VERSION_NUMBER
system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}
system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""} # type: ignore[reportUnnecessaryComparison]
return {
"platform": platform_info,

View File

@@ -9,24 +9,39 @@ Available hooks:
``response``:
The response generated from a Request.
"""
HOOKS = ["response"]
from __future__ import annotations
from collections.abc import Callable, Iterable
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from . import _types as _t
from .models import Response
HOOKS: list[str] = ["response"]
def default_hooks():
def default_hooks() -> dict[str, list[_t.HookType]]:
return {event: [] for event in HOOKS}
# TODO: response is the only one
def dispatch_hook(key, hooks, hook_data, **kwargs):
def dispatch_hook(
key: str,
hooks: _t.HooksInputType | None,
hook_data: Response,
**kwargs: Any,
) -> Response:
"""Dispatches a hook dictionary on a given piece of data."""
hooks = hooks or {}
hooks = hooks.get(key)
if hooks:
if hasattr(hooks, "__call__"):
hooks = [hooks]
for hook in hooks:
hooks_dict = hooks or {}
hook_list: Iterable[_t.HookType] | _t.HookType | None = hooks_dict.get(key)
if hook_list:
if isinstance(hook_list, Callable):
hook_list = [hook_list]
for hook in hook_list:
_hook_data = hook(hook_data, **kwargs)
if _hook_data is not None:
hook_data = _hook_data

View File

@@ -5,13 +5,24 @@ requests.models
This module contains the primary objects that power Requests.
"""
from __future__ import annotations
import datetime
# Import encoding now, to avoid implicit import later.
# Implicit import within threads may cause LookupError when standard library is in a ZIP,
# such as in Embedded Python. See https://github.com/psf/requests/issues/3578.
import encodings.idna # noqa: F401
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping
from io import UnsupportedOperation
from typing import (
TYPE_CHECKING,
Any,
Final,
Literal,
cast,
overload,
)
from urllib3.exceptions import (
DecodeError,
@@ -24,20 +35,25 @@ from urllib3.fields import RequestField
from urllib3.filepost import encode_multipart_formdata
from urllib3.util import parse_url
from . import _types as _t
from ._internal_utils import to_native_string, unicode_is_ascii
from .auth import HTTPBasicAuth
from .compat import (
Callable,
JSONDecodeError,
Mapping,
basestring,
builtin_str,
chardet,
cookielib,
urlencode,
urlsplit,
urlunparse,
)
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 .cookies import (
_copy_cookie_jar,
cookiejar_from_dict,
get_cookie_header,
)
from .exceptions import (
ChunkedEncodingError,
ConnectionError,
@@ -45,11 +61,11 @@ from .exceptions import (
HTTPError,
InvalidJSONError,
InvalidURL,
MissingSchema,
StreamConsumedError,
)
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
@@ -66,9 +82,17 @@ from .utils import (
to_key_val_list,
)
if TYPE_CHECKING:
from http.cookiejar import CookieJar
from typing_extensions import Self
from .adapters import HTTPAdapter
from .cookies import RequestsCookieJar
#: The set of HTTP status codes that indicate an automatically
#: processable redirect.
REDIRECT_STATI = (
REDIRECT_STATI: Final[tuple[int, ...]] = ( # type: ignore[assignment]
codes.moved, # 301
codes.found, # 302
codes.other, # 303
@@ -76,19 +100,21 @@ REDIRECT_STATI = (
codes.permanent_redirect, # 308
)
DEFAULT_REDIRECT_LIMIT = 30
CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512
DEFAULT_REDIRECT_LIMIT: int = 30
CONTENT_CHUNK_SIZE: int = 10 * 1024
ITER_CHUNK_SIZE: int = 512
class RequestEncodingMixin:
url: str | None
@property
def path_url(self):
def path_url(self) -> str:
"""Build the path URL to use."""
url = []
url: list[str] = []
p = urlsplit(self.url)
p = urlsplit(cast(str, self.url))
path = p.path
if not path:
@@ -103,8 +129,28 @@ class RequestEncodingMixin:
return "".join(url)
@overload
@staticmethod
def _encode_params(data):
def _encode_params(data: str) -> str: ...
@overload
@staticmethod
def _encode_params(data: bytes) -> bytes: ...
@overload
@staticmethod
def _encode_params(
data: _t.SupportsRead[str | bytes],
) -> _t.SupportsRead[str | bytes]: ...
@overload
@staticmethod
def _encode_params(data: _t.KVDataType) -> str: ...
@staticmethod
def _encode_params(
data: _t.EncodableDataType,
) -> str | bytes | _t.SupportsRead[str | bytes]:
"""Encode parameters in a piece of data.
Will successfully encode parameters when passed as a dict or a list of
@@ -114,10 +160,10 @@ class RequestEncodingMixin:
if isinstance(data, (str, bytes)):
return data
elif hasattr(data, "read"):
elif _t.has_read(data):
return data
elif hasattr(data, "__iter__"):
result = []
result: list[tuple[bytes, bytes]] = []
for k, vs in to_key_val_list(data):
if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
vs = [vs]
@@ -131,10 +177,12 @@ class RequestEncodingMixin:
)
return urlencode(result, doseq=True)
else:
return data
return data # type: ignore[return-value] # unreachable for valid _t.DataType
@staticmethod
def _encode_files(files, data):
def _encode_files(
files: _t.FilesType, data: _t.RawDataType | None
) -> tuple[bytes, str]:
"""Build the body for a multipart/form-data request.
Will successfully encode files when passed as a dict or a list of
@@ -148,7 +196,7 @@ class RequestEncodingMixin:
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
new_fields = []
new_fields: list[RequestField | tuple[str, bytes]] = []
fields = to_key_val_list(data or {})
files = to_key_val_list(files or {})
@@ -187,9 +235,9 @@ class RequestEncodingMixin:
if isinstance(fp, (str, bytes, bytearray)):
fdata = fp
elif hasattr(fp, "read"):
elif _t.has_read(fp):
fdata = fp.read()
elif fp is None:
elif fp is None: # defensive check for untyped callers
continue
else:
fdata = fp
@@ -204,7 +252,11 @@ class RequestEncodingMixin:
class RequestHooksMixin:
def register_hook(self, event, hook):
hooks: dict[str, list[_t.HookType]]
def register_hook(
self, event: str, hook: Iterable[_t.HookType] | _t.HookType
) -> None:
"""Properly register a hook."""
if event not in self.hooks:
@@ -213,9 +265,11 @@ class RequestHooksMixin:
if isinstance(hook, Callable):
self.hooks[event].append(hook)
elif hasattr(hook, "__iter__"):
self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
self.hooks[event].extend(
h for h in hook if isinstance(h, Callable)
) # defensive runtime filter
def deregister_hook(self, event, hook):
def deregister_hook(self, event: str, hook: _t.HookType) -> bool:
"""Deregister a previously registered hook.
Returns True if the hook existed, False if not.
"""
@@ -255,19 +309,30 @@ class Request(RequestHooksMixin):
<PreparedRequest [GET]>
"""
hooks: dict[str, list[_t.HookType]]
method: str | None
url: _t.UriType | None
headers: Mapping[str, str | bytes]
files: _t.FilesType
data: _t.DataType
json: _t.JsonType
params: _t.ParamsType
auth: _t.AuthType
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None
def __init__(
self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
method: str | None = None,
url: _t.UriType | None = None,
headers: _t.HeadersType = None,
files: _t.FilesType = None,
data: _t.DataType = None,
params: _t.ParamsType = None,
auth: _t.AuthType = None,
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
hooks: _t.HooksInputType | None = None,
json: _t.JsonType = None,
) -> None:
# Default empty dicts for dict params.
data = [] if data is None else data
files = [] if files is None else files
@@ -289,10 +354,10 @@ class Request(RequestHooksMixin):
self.auth = auth
self.cookies = cookies
def __repr__(self):
def __repr__(self) -> str:
return f"<Request [{self.method}]>"
def prepare(self):
def prepare(self) -> PreparedRequest:
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
p = PreparedRequest()
p.prepare(
@@ -331,13 +396,21 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
<Response [200]>
"""
def __init__(self):
method: str | None
url: str | None
headers: CaseInsensitiveDict[str | bytes]
_cookies: RequestsCookieJar | CookieJar | None
body: _t.BodyType
hooks: dict[str, list[_t.HookType]]
_body_position: int | object | None
def __init__(self) -> None:
#: HTTP verb to send to the server.
self.method = None
#: HTTP URL to send the request to.
self.url = None
#: dictionary of HTTP headers.
self.headers = None
self.headers = None # type: ignore[assignment]
# The `CookieJar` used to create the Cookie header will be stored here
# after prepare_cookies is called
self._cookies = None
@@ -350,19 +423,20 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
def prepare(
self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
method: str | None = None,
url: _t.UriType | None = None,
headers: Mapping[str, str | bytes] | None = None,
files: _t.FilesType = None,
data: _t.DataType = None,
params: _t.ParamsType = None,
auth: _t.AuthType = None,
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
hooks: _t.HooksInputType | None = None,
json: _t.JsonType = None,
) -> None:
"""Prepares the entire request with the given parameters."""
url = cast("_t.UriType", url)
self.prepare_method(method)
self.prepare_url(url, params)
self.prepare_headers(headers)
@@ -376,28 +450,28 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# This MUST go after prepare_auth. Authenticators could add a hook
self.prepare_hooks(hooks)
def __repr__(self):
def __repr__(self) -> str:
return f"<PreparedRequest [{self.method}]>"
def copy(self):
def copy(self) -> PreparedRequest:
p = PreparedRequest()
p.method = self.method
p.url = self.url
p.headers = self.headers.copy() if self.headers is not None else None
p.headers = self.headers.copy() if self.headers is not None else None # type: ignore[assignment]
p._cookies = _copy_cookie_jar(self._cookies)
p.body = self.body
p.hooks = self.hooks
p._body_position = self._body_position
return p
def prepare_method(self, method):
def prepare_method(self, method: str | None) -> None:
"""Prepares the given HTTP method."""
self.method = method
if self.method is not None:
self.method = to_native_string(self.method.upper())
@staticmethod
def _get_idna_encoded_host(host):
def _get_idna_encoded_host(host: str) -> str:
import idna
try:
@@ -406,7 +480,11 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
raise UnicodeError
return host
def prepare_url(self, url, params):
def prepare_url(
self,
url: _t.UriType,
params: _t.ParamsType,
) -> None:
"""Prepares the given HTTP URL."""
#: Accept objects that have string representations.
#: We're unable to blindly call unicode/str functions
@@ -470,17 +548,21 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if isinstance(params, (str, bytes)):
params = to_native_string(params)
enc_params = self._encode_params(params)
if params is not None:
enc_params = self._encode_params(params)
else:
enc_params = ""
if enc_params:
if query:
query = f"{query}&{enc_params}"
else:
query = enc_params
url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment]))
url = requote_uri(urlunparse((scheme, netloc, path, "", query, fragment)))
self.url = url
def prepare_headers(self, headers):
def prepare_headers(self, headers: Mapping[str, str | bytes] | None) -> None:
"""Prepares the given HTTP headers."""
self.headers = CaseInsensitiveDict()
@@ -491,7 +573,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
name, value = header
self.headers[to_native_string(name)] = value
def prepare_body(self, data, files, json=None):
def prepare_body(
self, data: _t.DataType, files: _t.FilesType, json: _t.JsonType = None
) -> None:
"""Prepares the given HTTP body data."""
# Check if file, fo, generator, iterator.
@@ -514,14 +598,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
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:
# data that proxies attributes to underlying objects needs hasattr
is_iterable = isinstance(data, Iterable) or hasattr(data, "__iter__")
if is_iterable and not isinstance(data, (str, bytes, list, tuple, Mapping)):
try:
length = super_len(data)
except (TypeError, AttributeError, UnsupportedOperation):
@@ -534,7 +613,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# This will allow us to rewind a file in the event
# of a redirect.
try:
self._body_position = body.tell()
self._body_position = body.tell() # type: ignore[union-attr] # guarded by getattr check
except OSError:
# This differentiates from None, allowing us to catch
# a failed `tell()` later when trying to rewind the body
@@ -550,13 +629,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
else:
self.headers["Transfer-Encoding"] = "chunked"
else:
# After is_stream filtering, remaining data is raw (not streamed)
raw_data = cast("_t.RawDataType | None", data)
# Multi-part file uploads.
if files:
(body, content_type) = self._encode_files(files, data)
(body, content_type) = self._encode_files(files, raw_data)
else:
if data:
body = self._encode_params(data)
if isinstance(data, basestring) or hasattr(data, "read"):
if raw_data:
body = self._encode_params(raw_data)
if isinstance(data, basestring) or _t.has_read(data):
content_type = None
else:
content_type = "application/x-www-form-urlencoded"
@@ -567,9 +649,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if content_type and ("content-type" not in self.headers):
self.headers["Content-Type"] = content_type
self.body = body
self.body = body # type: ignore[assignment] # body transforms from DataType to BodyType
def prepare_content_length(self, body):
def prepare_content_length(self, body: _t.BodyType) -> None:
"""Prepare Content-Length header based on request method and body"""
if body is not None:
length = super_len(body)
@@ -585,21 +667,28 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# but don't provide one. (i.e. not GET or HEAD)
self.headers["Content-Length"] = "0"
def prepare_auth(self, auth, url=""):
def prepare_auth(
self,
auth: _t.AuthType,
url: _t.UriType = "",
) -> None:
"""Prepares the given HTTP auth data."""
# If no Auth is explicitly provided, extract it from the URL first.
if auth is None:
url_auth = get_auth_from_url(self.url)
url_auth = get_auth_from_url(cast(str, self.url))
auth = url_auth if any(url_auth) else None
if auth:
if isinstance(auth, tuple) and len(auth) == 2:
if isinstance(auth, tuple) and len(auth) == 2: # type: ignore[arg-type] # pyright widens tuple from Callable in AuthType
# special-case basic HTTP auth
auth = HTTPBasicAuth(*auth)
auth_handler = HTTPBasicAuth(*auth) # type: ignore[arg-type] # pyright widens tuple from Callable in AuthType
else:
# TODO: can be fixed by flipping the conditionals
auth_handler = cast("Callable[..., PreparedRequest]", auth)
# Allow auth to make its changes.
r = auth(self)
r = auth_handler(self)
# Update self to reflect the auth changes.
self.__dict__.update(r.__dict__)
@@ -607,7 +696,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# Recompute Content-Length
self.prepare_content_length(self.body)
def prepare_cookies(self, cookies):
def prepare_cookies(
self, cookies: RequestsCookieJar | CookieJar | dict[str, str] | None
) -> None:
"""Prepares the given HTTP cookie data.
This function eventually generates a ``Cookie`` header from the
@@ -623,16 +714,17 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
else:
self._cookies = cookiejar_from_dict(cookies)
cookie_header = get_cookie_header(self._cookies, self)
cookies_jar = cast("CookieJar", self._cookies)
cookie_header = get_cookie_header(cookies_jar, self)
if cookie_header is not None:
self.headers["Cookie"] = cookie_header
def prepare_hooks(self, hooks):
def prepare_hooks(self, hooks: _t.HooksInputType | None) -> None:
"""Prepares the given hooks."""
# hooks can be passed as None to the prepare method and to this
# method. To prevent iterating over None, simply use an empty list
# if hooks is False-y
hooks = hooks or []
hooks = hooks or {}
for event in hooks:
self.register_hook(event, hooks[event])
@@ -642,7 +734,22 @@ class Response:
server's response to an HTTP request.
"""
__attrs__ = [
_content: bytes | Literal[False] | None
_content_consumed: bool
_next: PreparedRequest | None
status_code: int
headers: CaseInsensitiveDict[str]
raw: Any
url: str
encoding: str | None
history: list[Response]
reason: str
cookies: RequestsCookieJar
elapsed: datetime.timedelta
request: PreparedRequest
connection: HTTPAdapter
__attrs__: list[str] = [
"_content",
"status_code",
"headers",
@@ -655,13 +762,13 @@ class Response:
"request",
]
def __init__(self):
def __init__(self) -> None:
self._content = False
self._content_consumed = False
self._next = None
#: Integer Code of responded HTTP Status, e.g. 404 or 200.
self.status_code = None
self.status_code = None # type: ignore[assignment]
#: Case-insensitive Dictionary of Response Headers.
#: For example, ``headers['content-encoding']`` will return the
@@ -674,7 +781,7 @@ class Response:
self.raw = None
#: Final URL location of Response.
self.url = None
self.url = None # type: ignore[assignment]
#: Encoding to decode with when accessing r.text.
self.encoding = None
@@ -685,7 +792,7 @@ class Response:
self.history = []
#: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK".
self.reason = None
self.reason = None # type: ignore[assignment]
#: A CookieJar of Cookies the server sent back.
self.cookies = cookiejar_from_dict({})
@@ -700,15 +807,15 @@ class Response:
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
#: is a response.
self.request = None
self.request = None # type: ignore[assignment]
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *args):
def __exit__(self, *args: Any) -> None:
self.close()
def __getstate__(self):
def __getstate__(self) -> dict[str, Any]:
# Consume everything; accessing the content attribute makes
# sure the content has been fully read.
if not self._content_consumed:
@@ -716,7 +823,7 @@ class Response:
return {attr: getattr(self, attr, None) for attr in self.__attrs__}
def __setstate__(self, state):
def __setstate__(self, state: dict[str, Any]) -> None:
for name, value in state.items():
setattr(self, name, value)
@@ -724,10 +831,10 @@ class Response:
setattr(self, "_content_consumed", True)
setattr(self, "raw", None)
def __repr__(self):
def __repr__(self) -> str:
return f"<Response [{self.status_code}]>"
def __bool__(self):
def __bool__(self) -> bool:
"""Returns True if :attr:`status_code` is less than 400.
This attribute checks if the status code of the response is between
@@ -737,7 +844,7 @@ class Response:
"""
return self.ok
def __nonzero__(self):
def __nonzero__(self) -> bool:
"""Returns True if :attr:`status_code` is less than 400.
This attribute checks if the status code of the response is between
@@ -747,12 +854,12 @@ class Response:
"""
return self.ok
def __iter__(self):
def __iter__(self) -> Iterator[bytes]:
"""Allows you to use a response as an iterator."""
return self.iter_content(128)
@property
def ok(self):
def ok(self) -> bool:
"""Returns True if :attr:`status_code` is less than 400, False if not.
This attribute checks if the status code of the response is between
@@ -767,14 +874,14 @@ class Response:
return True
@property
def is_redirect(self):
def is_redirect(self) -> bool:
"""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
@property
def is_permanent_redirect(self):
def is_permanent_redirect(self) -> bool:
"""True if this Response one of the permanent versions of redirect."""
return "location" in self.headers and self.status_code in (
codes.moved_permanently,
@@ -782,12 +889,12 @@ class Response:
)
@property
def next(self):
def next(self) -> PreparedRequest | None:
"""Returns a PreparedRequest for the next request in a redirect chain, if there is one."""
return self._next
@property
def apparent_encoding(self):
def apparent_encoding(self) -> str | None:
"""The apparent encoding, provided by the charset_normalizer or chardet libraries."""
if chardet is not None:
return chardet.detect(self.content)["encoding"]
@@ -796,7 +903,17 @@ class Response:
# to a standard Python utf-8 str.
return "utf-8"
def iter_content(self, chunk_size=1, decode_unicode=False):
@overload
def iter_content(
self, chunk_size: int | None = 1, decode_unicode: Literal[False] = False
) -> Iterator[bytes]: ...
@overload
def iter_content(
self, chunk_size: int | None = 1, *, decode_unicode: Literal[True]
) -> Iterator[str | bytes]: ...
def iter_content(
self, chunk_size: int | None = 1, decode_unicode: bool = False
) -> Iterator[str | bytes]:
"""Iterates over the response data. When stream=True is set on the
request, this avoids reading the content at once into memory for
large responses. The chunk size is the number of bytes it should
@@ -809,11 +926,13 @@ class Response:
chunks are received. If stream=False, data is returned as
a single chunk.
If decode_unicode is True, content will be decoded using the best
available encoding based on the response.
If decode_unicode is True, content will be decoded using encoding
information from the response. If no encoding information is available,
bytes will be returned. This can be bypassed by manually setting
`encoding` on the response.
"""
def generate():
def generate() -> Generator[bytes, None, None]:
# Special case for urllib3.
if hasattr(self.raw, "stream"):
try:
@@ -838,42 +957,67 @@ class Response:
if self._content_consumed and isinstance(self._content, bool):
raise StreamConsumedError()
elif chunk_size is not None and not isinstance(chunk_size, int):
elif chunk_size is not None and not isinstance(
chunk_size, int
): # runtime guard for untyped callers
raise TypeError(
f"chunk_size must be an int, it is instead a {type(chunk_size)}."
)
# simulate reading small chunks of the content
reused_chunks = iter_slices(self._content, chunk_size)
stream_chunks = generate()
chunks = reused_chunks if self._content_consumed else stream_chunks
if self._content_consumed:
# simulate reading small chunks of the content
content = cast(bytes, self._content)
chunks = iter_slices(content, chunk_size)
else:
chunks = generate()
if decode_unicode:
chunks = stream_decode_response_unicode(chunks, self)
return chunks
@overload
def iter_lines(
self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None
):
self,
chunk_size: int = ITER_CHUNK_SIZE,
decode_unicode: Literal[False] = False,
delimiter: bytes | None = None,
) -> Iterator[bytes]: ...
@overload
def iter_lines(
self,
chunk_size: int = ITER_CHUNK_SIZE,
*,
decode_unicode: Literal[True],
delimiter: str | bytes | None = None,
) -> Iterator[str | bytes]: ...
def iter_lines(
self,
chunk_size: int = ITER_CHUNK_SIZE,
decode_unicode: bool = False,
delimiter: str | bytes | None = None,
) -> Iterator[str | bytes]:
"""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.
The decode_unicode param works the same as in `iter_content`, with the
same caveats.
.. note:: This method is not reentrant safe.
"""
pending = None
pending: str | bytes | None = None
for chunk in self.iter_content(
chunk_size=chunk_size, decode_unicode=decode_unicode
):
if pending is not None:
chunk = pending + chunk
# TODO: remove cast after iter_lines rewrite
chunk = cast("str | bytes", pending + chunk)
if delimiter:
lines = chunk.split(delimiter)
lines = chunk.split(delimiter) # type: ignore[arg-type]
else:
lines = chunk.splitlines()
@@ -888,7 +1032,7 @@ class Response:
yield pending
@property
def content(self):
def content(self) -> bytes:
"""Content of the response, in bytes."""
if self._content is False:
@@ -904,10 +1048,10 @@ class Response:
self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3
# since we exhausted the data.
return self._content
return self._content # type: ignore[return-value]
@property
def text(self):
def text(self) -> str:
"""Content of the response, in unicode.
If Response.encoding is None, encoding will be guessed using
@@ -932,7 +1076,7 @@ class Response:
# Decode unicode from given encoding.
try:
content = str(self.content, encoding, errors="replace")
content = str(self.content, encoding or "utf-8", errors="replace")
except (LookupError, TypeError):
# A LookupError is raised if the encoding was not found which could
# indicate a misspelling or similar mistake.
@@ -944,8 +1088,10 @@ class Response:
return content
def json(self, **kwargs):
r"""Returns the json-encoded content of a response, if any.
def json(self, **kwargs: Any) -> 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 requests.exceptions.JSONDecodeError: If the response body does not
@@ -978,23 +1124,24 @@ class Response:
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
@property
def links(self):
def links(self) -> dict[str, dict[str, str]]:
"""Returns the parsed header links of the response, if any."""
header = self.headers.get("link")
resolved_links = {}
resolved_links: dict[str, dict[str, str]] = {}
if header:
links = parse_header_links(header)
for link in links:
key = link.get("rel") or link.get("url")
resolved_links[key] = link
if key is not None:
resolved_links[key] = link
return resolved_links
def raise_for_status(self):
def raise_for_status(self) -> None:
"""Raises :class:`HTTPError`, if one occurred."""
http_error_msg = ""
@@ -1023,7 +1170,7 @@ class Response:
if http_error_msg:
raise HTTPError(http_error_msg, response=self)
def close(self):
def close(self) -> None:
"""Releases the connection back to the pool. Once this method has been
called the underlying ``raw`` object must not be accessed again.

0
src/requests/py.typed Normal file
View File

View File

@@ -5,16 +5,22 @@ requests.sessions
This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
"""
from __future__ import annotations
import os
import sys
import time
from collections import OrderedDict
from collections.abc import Generator, Mapping, MutableMapping
from datetime import timedelta
from typing import TYPE_CHECKING, Any, cast
from ._internal_utils import to_native_string
from ._types import is_prepared as _is_prepared
from .adapters import HTTPAdapter
from .auth import _basic_auth_str
from .compat import Mapping, cookielib, urljoin, urlparse
from .compat import cookielib, urljoin, urlparse
from .cookies import (
RequestsCookieJar,
cookiejar_from_dict,
@@ -35,6 +41,7 @@ from .models import ( # noqa: F401
REDIRECT_STATI,
PreparedRequest,
Request,
Response,
)
from .status_codes import codes
from .structures import CaseInsensitiveDict
@@ -47,10 +54,18 @@ from .utils import ( # noqa: F401
requote_uri,
resolve_proxies,
rewind_body,
should_bypass_proxies,
should_bypass_proxies, # re-export for external consumers
to_key_val_list,
)
if TYPE_CHECKING:
from http.cookiejar import CookieJar
from typing_extensions import Self, Unpack
from . import _types as _t
from .adapters import BaseAdapter
# Preferred clock, based on which one is more accurate on a given system.
if sys.platform == "win32":
preferred_clock = time.perf_counter
@@ -58,7 +73,9 @@ else:
preferred_clock = time.time
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
def merge_setting(
request_setting: Any, session_setting: Any, dict_class: type = OrderedDict
) -> Any:
"""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`
@@ -76,8 +93,8 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
):
return request_setting
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
merged_setting = dict_class(to_key_val_list(session_setting)) # type: ignore[arg-type] # isinstance narrows Any to Mapping[Unknown]
merged_setting.update(to_key_val_list(request_setting)) # type: ignore[arg-type]
# Remove keys that are set to None. Extract keys first to avoid altering
# the dictionary during iteration.
@@ -88,7 +105,11 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
return merged_setting
def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
def merge_hooks(
request_hooks: _t.HooksType,
session_hooks: _t.HooksType,
dict_class: type = OrderedDict,
) -> _t.HooksType:
"""Properly merges both requests and session hooks.
This is necessary because when request_hooks == {'response': []}, the
@@ -104,7 +125,13 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
class SessionRedirectMixin:
def get_redirect_target(self, resp):
max_redirects: int
trust_env: bool
cookies: RequestsCookieJar
def send(self, request: PreparedRequest, **kwargs: Any) -> Response: ...
def get_redirect_target(self, resp: Response) -> str | None:
"""Receives a Response. Returns a redirect URI or ``None``"""
# Due to the nature of how requests processes redirects this method will
# be called at least once upon the original response and at least twice
@@ -124,7 +151,7 @@ class SessionRedirectMixin:
return to_native_string(location, "utf8")
return None
def should_strip_auth(self, old_url, new_url):
def should_strip_auth(self, old_url: str, new_url: str) -> bool:
"""Decide whether Authorization header should be removed when redirecting"""
old_parsed = urlparse(old_url)
new_parsed = urlparse(new_url)
@@ -158,19 +185,19 @@ class SessionRedirectMixin:
def resolve_redirects(
self,
resp,
req,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
yield_requests=False,
**adapter_kwargs,
):
resp: Response,
req: PreparedRequest,
stream: bool = False,
timeout: _t.TimeoutType = None,
verify: _t.VerifyType = True,
cert: _t.CertType = None,
proxies: dict[str, str] | None = None,
yield_requests: bool = False,
**adapter_kwargs: Any,
) -> Generator[Response, None, None]:
"""Receives a Response. Returns a generator of Responses or Requests."""
hist = [] # keep track of history
hist: list[Response] = [] # keep track of history
url = self.get_redirect_target(resp)
previous_fragment = urlparse(req.url).fragment
@@ -178,9 +205,8 @@ class SessionRedirectMixin:
prepared_request = req.copy()
# Update history and keep track of redirects.
# resp.history must ignore the original request in this loop
resp.history = hist[:]
hist.append(resp)
resp.history = hist[1:]
try:
resp.content # Consume socket so it can be released
@@ -237,9 +263,10 @@ class SessionRedirectMixin:
# Extract any cookies sent on the response to the cookiejar
# in the new request. Because we've mutated our copied prepared
# request, use the old one that we haven't yet touched.
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
merge_cookies(prepared_request._cookies, self.cookies)
prepared_request.prepare_cookies(prepared_request._cookies)
cookie_jar = cast("CookieJar", prepared_request._cookies)
extract_cookies_to_jar(cookie_jar, req, resp.raw)
merge_cookies(cookie_jar, self.cookies)
prepared_request.prepare_cookies(cookie_jar)
# Rebuild auth and proxy information.
proxies = self.rebuild_proxies(prepared_request, proxies)
@@ -260,7 +287,7 @@ class SessionRedirectMixin:
req = prepared_request
if yield_requests:
yield req
yield req # type: ignore[misc] # Internal use only, returns PreparedRequest
else:
resp = self.send(
req,
@@ -279,17 +306,22 @@ class SessionRedirectMixin:
url = self.get_redirect_target(resp)
yield resp
def rebuild_auth(self, prepared_request, response):
def rebuild_auth(
self, prepared_request: PreparedRequest, response: Response
) -> None:
"""When being redirected we may want to strip authentication from the
request to avoid leaking credentials. This method intelligently removes
and reapplies authentication where possible to avoid credential loss.
"""
original_request = response.request
assert _is_prepared(original_request)
assert _is_prepared(prepared_request)
headers = prepared_request.headers
original_url = original_request.url
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(original_url, url):
# If we get redirected to a new host, we should strip out any
# authentication headers.
del headers["Authorization"]
@@ -299,7 +331,11 @@ class SessionRedirectMixin:
if new_auth is not None:
prepared_request.prepare_auth(new_auth)
def rebuild_proxies(self, prepared_request, proxies):
def rebuild_proxies(
self,
prepared_request: PreparedRequest,
proxies: dict[str, str] | None,
) -> dict[str, str]:
"""This method re-evaluates the proxy configuration by considering the
environment variables. If we are redirected to a URL covered by
NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
@@ -311,6 +347,7 @@ class SessionRedirectMixin:
:rtype: dict
"""
assert _is_prepared(prepared_request)
headers = prepared_request.headers
scheme = urlparse(prepared_request.url).scheme
new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
@@ -330,7 +367,9 @@ class SessionRedirectMixin:
return new_proxies
def rebuild_method(self, prepared_request, response):
def rebuild_method(
self, prepared_request: PreparedRequest, response: Response
) -> None:
"""When being redirected we may want to change the method of the request
based on certain specs or browser behavior.
"""
@@ -372,7 +411,20 @@ class Session(SessionRedirectMixin):
<Response [200]>
"""
__attrs__ = [
headers: CaseInsensitiveDict[str]
auth: _t.AuthType
proxies: dict[str, str]
hooks: dict[str, list[_t.HookType]]
params: MutableMapping[str, Any]
stream: bool
verify: _t.VerifyType
cert: _t.CertType
max_redirects: int
trust_env: bool
cookies: RequestsCookieJar
adapters: MutableMapping[str, BaseAdapter]
__attrs__: list[str] = [
"headers",
"cookies",
"auth",
@@ -387,7 +439,7 @@ class Session(SessionRedirectMixin):
"max_redirects",
]
def __init__(self):
def __init__(self) -> None:
#: A case-insensitive dictionary of headers to be sent on each
#: :class:`Request <Request>` sent from this
#: :class:`Session <Session>`.
@@ -421,6 +473,8 @@ class Session(SessionRedirectMixin):
#: expired certificates, which will make your application vulnerable to
#: man-in-the-middle (MitM) attacks.
#: Only set this to `False` for testing.
#: If verify is set to a string, it must be the path to a CA bundle file
#: that will be used to verify the TLS certificate.
self.verify = True
#: SSL client certificate default, if String, path to ssl client
@@ -448,13 +502,13 @@ class Session(SessionRedirectMixin):
self.mount("https://", HTTPAdapter())
self.mount("http://", HTTPAdapter())
def __enter__(self):
def __enter__(self) -> Self:
return self
def __exit__(self, *args):
def __exit__(self, *args: Any) -> None:
self.close()
def prepare_request(self, request):
def prepare_request(self, request: Request) -> PreparedRequest:
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for
transmission and returns it. The :class:`PreparedRequest` has settings
merged from the :class:`Request <Request>` instance and those of the
@@ -464,6 +518,9 @@ class Session(SessionRedirectMixin):
session's settings.
:rtype: requests.PreparedRequest
"""
url = cast("_t.UriType", request.url)
method = cast(str, request.method)
cookies = request.cookies or {}
# Bootstrap CookieJar.
@@ -478,12 +535,12 @@ class Session(SessionRedirectMixin):
# Set environment's basic authentication if not explicitly set.
auth = request.auth
if self.trust_env and not auth and not self.auth:
auth = get_netrc_auth(request.url)
auth = get_netrc_auth(url)
p = PreparedRequest()
p.prepare(
method=request.method.upper(),
url=request.url,
method=method.upper(),
url=url,
files=request.files,
data=request.data,
json=request.json,
@@ -499,23 +556,23 @@ class Session(SessionRedirectMixin):
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,
):
method: str,
url: _t.UriType,
params: _t.ParamsType = None,
data: _t.DataType = None,
headers: _t.HeadersType = None,
cookies: RequestsCookieJar | CookieJar | dict[str, str] | None = None,
files: _t.FilesType = None,
auth: _t.AuthType = None,
timeout: _t.TimeoutType = None,
allow_redirects: bool = True,
proxies: dict[str, str] | None = None,
hooks: _t.HooksInputType | None = None,
stream: bool | None = None,
verify: _t.VerifyType | None = None,
cert: _t.CertType = None,
json: _t.JsonType = None,
) -> Response:
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
Returns :class:`Response <Response>` object.
@@ -535,7 +592,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
@@ -559,6 +616,9 @@ class Session(SessionRedirectMixin):
If Tuple, ('cert', 'key') pair.
:rtype: requests.Response
"""
if isinstance(url, bytes):
url = url.decode("utf-8")
# Create the Request.
req = Request(
method=method.upper(),
@@ -574,6 +634,8 @@ class Session(SessionRedirectMixin):
)
prep = self.prepare_request(req)
assert _is_prepared(prep)
proxies = proxies or {}
settings = self.merge_environment_settings(
@@ -590,18 +652,25 @@ class Session(SessionRedirectMixin):
return resp
def get(self, url, **kwargs):
def get(
self,
url: _t.UriType,
params: _t.ParamsType = None,
**kwargs: Unpack[_t.GetKwargs],
) -> Response:
r"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault("allow_redirects", True)
return self.request("GET", url, **kwargs)
return self.request("GET", url, params=params, **kwargs)
def options(self, url, **kwargs):
def options(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
r"""Sends a OPTIONS request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
@@ -612,7 +681,7 @@ class Session(SessionRedirectMixin):
kwargs.setdefault("allow_redirects", True)
return self.request("OPTIONS", url, **kwargs)
def head(self, url, **kwargs):
def head(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
r"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
@@ -623,7 +692,13 @@ class Session(SessionRedirectMixin):
kwargs.setdefault("allow_redirects", False)
return self.request("HEAD", url, **kwargs)
def post(self, url, data=None, json=None, **kwargs):
def post(
self,
url: _t.UriType,
data: _t.DataType = None,
json: _t.JsonType = None,
**kwargs: Unpack[_t.PostKwargs],
) -> Response:
r"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
@@ -636,7 +711,9 @@ class Session(SessionRedirectMixin):
return self.request("POST", url, data=data, json=json, **kwargs)
def put(self, url, data=None, **kwargs):
def put(
self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
) -> Response:
r"""Sends a PUT request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
@@ -648,7 +725,9 @@ class Session(SessionRedirectMixin):
return self.request("PUT", url, data=data, **kwargs)
def patch(self, url, data=None, **kwargs):
def patch(
self, url: _t.UriType, data: _t.DataType = None, **kwargs: Unpack[_t.DataKwargs]
) -> Response:
r"""Sends a PATCH request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
@@ -660,7 +739,7 @@ class Session(SessionRedirectMixin):
return self.request("PATCH", url, data=data, **kwargs)
def delete(self, url, **kwargs):
def delete(self, url: _t.UriType, **kwargs: Unpack[_t.RequestKwargs]) -> Response:
r"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
@@ -670,7 +749,7 @@ class Session(SessionRedirectMixin):
return self.request("DELETE", url, **kwargs)
def send(self, request, **kwargs):
def send(self, request: PreparedRequest, **kwargs: Any) -> Response:
"""Send a given PreparedRequest.
:rtype: requests.Response
@@ -688,6 +767,8 @@ class Session(SessionRedirectMixin):
if isinstance(request, Request):
raise ValueError("You can only send PreparedRequests.")
assert _is_prepared(request)
# Set up variables needed for resolve_redirects and dispatching of hooks
allow_redirects = kwargs.pop("allow_redirects", True)
stream = kwargs.get("stream")
@@ -736,7 +817,7 @@ 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(
r._next = next( # type: ignore[assignment] # yield_requests=True returns PreparedRequest
self.resolve_redirects(r, request, yield_requests=True, **kwargs)
)
except StopIteration:
@@ -747,7 +828,14 @@ class Session(SessionRedirectMixin):
return r
def merge_environment_settings(self, url, proxies, stream, verify, cert):
def merge_environment_settings(
self,
url: str,
proxies: dict[str, str] | None,
stream: bool | None,
verify: _t.VerifyType | None,
cert: _t.CertType,
) -> dict[str, Any]:
"""
Check the environment and merge it with some settings.
@@ -758,8 +846,9 @@ class Session(SessionRedirectMixin):
# Set environment's proxies.
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():
proxies.setdefault(k, v)
if proxies is not None:
for k, v in env_proxies.items():
proxies.setdefault(k, v)
# Look for requests environment configuration
# and be compatible with cURL.
@@ -778,7 +867,7 @@ class Session(SessionRedirectMixin):
return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}
def get_adapter(self, url):
def get_adapter(self, url: str) -> BaseAdapter:
"""
Returns the appropriate connection adapter for the given URL.
@@ -791,12 +880,12 @@ class Session(SessionRedirectMixin):
# Nothing matches :-/
raise InvalidSchema(f"No connection adapters were found for {url!r}")
def close(self):
def close(self) -> None:
"""Closes all adapters and as such the session"""
for v in self.adapters.values():
v.close()
def mount(self, prefix, adapter):
def mount(self, prefix: str, adapter: BaseAdapter) -> None:
"""Registers a connection adapter to a prefix.
Adapters are sorted in descending order by prefix length.
@@ -807,16 +896,16 @@ class Session(SessionRedirectMixin):
for key in keys_to_move:
self.adapters[key] = self.adapters.pop(key)
def __getstate__(self):
def __getstate__(self) -> dict[str, Any]:
state = {attr: getattr(self, attr, None) for attr in self.__attrs__}
return state
def __setstate__(self, state):
def __setstate__(self, state: dict[str, Any]) -> None:
for attr, value in state.items():
setattr(self, attr, value)
def session():
def session() -> Session:
"""
Returns a :class:`Session` for context-management.

View File

@@ -103,7 +103,7 @@ _codes = {
511: ("network_authentication_required", "network_auth", "network_authentication"),
}
codes = LookupDict(name="status_codes")
codes: LookupDict[int] = LookupDict(name="status_codes")
def _init():
@@ -113,7 +113,7 @@ def _init():
if not title.startswith(("\\", "/")):
setattr(codes, title.upper(), code)
def doc(code):
def doc(code: int) -> str:
names = ", ".join(f"``{n}``" for n in _codes[code])
return "* %d: %s" % (code, names)

View File

@@ -5,12 +5,19 @@ requests.structures
Data structures that power Requests.
"""
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Iterable, Iterator, Mapping
from typing import Any, Generic, TypeVar, overload
from .compat import Mapping, MutableMapping
from .compat import MutableMapping
_VT = TypeVar("_VT")
_D = TypeVar("_D")
class CaseInsensitiveDict(MutableMapping):
class CaseInsensitiveDict(MutableMapping[str, _VT], Generic[_VT]):
"""A case-insensitive ``dict``-like object.
Implements all methods and operations of
@@ -37,63 +44,87 @@ class CaseInsensitiveDict(MutableMapping):
behavior is undefined.
"""
def __init__(self, data=None, **kwargs):
_store: OrderedDict[str, tuple[str, _VT]]
def __init__(
self,
data: Mapping[str, _VT] | Iterable[tuple[str, _VT]] | None = None,
**kwargs: _VT,
) -> None:
self._store = OrderedDict()
if data is None:
data = {}
self.update(data, **kwargs)
def __setitem__(self, key, value):
def __setitem__(self, key: str, value: _VT) -> None:
# Use the lowercased key for lookups, but store the actual
# key alongside the value.
self._store[key.lower()] = (key, value)
def __getitem__(self, key):
def __getitem__(self, key: str) -> _VT:
return self._store[key.lower()][1]
def __delitem__(self, key):
def __delitem__(self, key: str) -> None:
del self._store[key.lower()]
def __iter__(self):
return (casedkey for casedkey, mappedvalue in self._store.values())
def __iter__(self) -> Iterator[str]:
return (casedkey for casedkey, _ in self._store.values())
def __len__(self):
def __len__(self) -> int:
return len(self._store)
def lower_items(self):
def lower_items(self) -> Iterator[tuple[str, _VT]]:
"""Like iteritems(), but with all lowercase keys."""
return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if isinstance(other, Mapping):
other = CaseInsensitiveDict(other)
other_dict: CaseInsensitiveDict[Any] = CaseInsensitiveDict(other) # type: ignore[reportUnknownArgumentType]
else:
return NotImplemented
# Compare insensitively
return dict(self.lower_items()) == dict(other.lower_items())
return dict(self.lower_items()) == dict(other_dict.lower_items())
# Copy is required
def copy(self):
def copy(self) -> CaseInsensitiveDict[_VT]:
return CaseInsensitiveDict(self._store.values())
def __repr__(self):
def __repr__(self) -> str:
return str(dict(self.items()))
class LookupDict(dict):
class LookupDict(dict[str, _VT]):
"""Dictionary lookup object."""
def __init__(self, name=None):
name: Any
def __init__(self, name: Any = None) -> None:
self.name = name
super().__init__()
def __repr__(self):
def __repr__(self) -> str:
return f"<lookup '{self.name}'>"
def __getitem__(self, key):
def __getattr__(self, key: str) -> _VT | None:
# We need this for type checkers to infer typing
# on attribute access with status_codes.py
if key in self.__dict__:
return self.__dict__[key]
else:
raise AttributeError(
f"'{type(self).__name__}' object has no attribute '{key}'"
)
def __getitem__(self, key: str) -> _VT | None: # type: ignore[override]
# We allow fall-through here, so values default to None
return self.__dict__.get(key, None)
def get(self, key, default=None):
@overload
def get(self, key: str, default: None = None) -> _VT | None: ...
@overload
def get(self, key: str, default: _D | _VT) -> _D | _VT: ...
def get(self, key: str, default: _D | None = None) -> _VT | _D | None:
return self.__dict__.get(key, default)

View File

@@ -6,6 +6,8 @@ This module provides utility functions that are used within Requests
that are also useful for external consumption.
"""
from __future__ import annotations
import codecs
import contextlib
import io
@@ -18,6 +20,15 @@ import tempfile
import warnings
import zipfile
from collections import OrderedDict
from collections.abc import Generator, Iterable
from typing import (
TYPE_CHECKING,
Any,
Final,
TypeVar,
cast,
overload,
)
from urllib3.util import make_headers, parse_url
@@ -31,24 +42,23 @@ from ._internal_utils import ( # noqa: F401
HEADER_VALIDATORS,
to_native_string,
)
from ._types import SupportsItems as _SupportsItems
from .compat import (
Mapping,
basestring,
bytes,
getproxies,
getproxies_environment,
integer_types,
)
from .compat import parse_http_list as _parse_list_header
from .compat import (
is_urllib3_1,
proxy_bypass,
proxy_bypass_environment,
proxy_bypass_environment, # type: ignore[attr-defined] # https://github.com/python/cpython/issues/145331
quote,
str,
unquote,
urlparse,
urlunparse,
)
from .compat import parse_http_list as _parse_list_header
from .cookies import cookiejar_from_dict
from .exceptions import (
FileModeWarning,
@@ -58,14 +68,27 @@ from .exceptions import (
)
from .structures import CaseInsensitiveDict
NETRC_FILES = (".netrc", "_netrc")
if TYPE_CHECKING:
from http.cookiejar import CookieJar
from io import BufferedWriter
DEFAULT_CA_BUNDLE_PATH = certs.where()
from . import _types as _t
from .models import PreparedRequest, Request, Response
DEFAULT_PORTS = {"http": 80, "https": 443}
NETRC_FILES: Final = (".netrc", "_netrc")
# Certificate is extracted by certifi when needed.
DEFAULT_CA_BUNDLE_PATH: str = certs.where()
DEFAULT_PORTS: Final = {"http": 80, "https": 443}
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
# Ensure that ', ' is used to preserve previous delimiter behavior.
DEFAULT_ACCEPT_ENCODING = ", ".join(
DEFAULT_ACCEPT_ENCODING: Final = ", ".join(
re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"])
)
@@ -73,7 +96,7 @@ DEFAULT_ACCEPT_ENCODING = ", ".join(
if sys.platform == "win32":
# provide a proxy_bypass version on Windows without DNS lookups
def proxy_bypass_registry(host):
def proxy_bypass_registry(host: str) -> bool:
try:
import winreg
except ImportError:
@@ -111,7 +134,7 @@ if sys.platform == "win32":
return True
return False
def proxy_bypass(host): # noqa
def proxy_bypass(host: str) -> bool: # noqa
"""Return True, if the host should be bypassed.
Checks proxy settings gathered from the environment, if specified,
@@ -123,20 +146,24 @@ if sys.platform == "win32":
return proxy_bypass_registry(host)
def dict_to_sequence(d):
def dict_to_sequence(
d: _t.SupportsItems[Any, Any] | Iterable[tuple[Any, Any]],
) -> Iterable[tuple[Any, Any]]:
"""Returns an internal sequence dictionary update."""
if hasattr(d, "items"):
d = d.items()
if isinstance(d, _SupportsItems):
return d.items()
return d
def super_len(o):
def super_len(o: Any) -> int:
total_length = None
current_position = 0
if isinstance(o, str):
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__"):
@@ -201,9 +228,14 @@ def super_len(o):
return max(0, total_length - current_position)
def get_netrc_auth(url, raise_errors=False):
def get_netrc_auth(
url: _t.UriType, raise_errors: bool = False
) -> tuple[str, str] | None:
"""Returns the Requests tuple auth for a given url from netrc."""
if isinstance(url, bytes):
url = url.decode("utf-8")
netrc_file = os.environ.get("NETRC")
if netrc_file is not None:
netrc_locations = (netrc_file,)
@@ -216,14 +248,7 @@ def get_netrc_auth(url, raise_errors=False):
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
@@ -233,20 +258,17 @@ def get_netrc_auth(url, raise_errors=False):
return
ri = urlparse(url)
host = ri.hostname
# 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]
if host is None:
return
try:
_netrc = netrc(netrc_path).authenticators(host)
if _netrc:
if _netrc and any(_netrc):
# Return with login / password
login_i = 0 if _netrc[0] else 1
return (_netrc[login_i], _netrc[2])
return (_netrc[login_i] or "", _netrc[2] or "")
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.
@@ -258,14 +280,14 @@ def get_netrc_auth(url, raise_errors=False):
pass
def guess_filename(obj):
def guess_filename(obj: Any) -> str | None:
"""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] != ">":
return os.path.basename(name)
if name and isinstance(name, (str, bytes)) and name[0] != "<" and name[-1] != ">":
return os.path.basename(name) # type: ignore[return-value] # urllib3 accepts bytes but types str only
def extract_zipped_paths(path):
def extract_zipped_paths(path: str) -> str:
"""Replace nonexistent paths that look like they refer to a member of a zip
archive with the location of an extracted copy of the target, or else
just return the provided path unchanged.
@@ -293,17 +315,18 @@ def extract_zipped_paths(path):
return 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("/")[-1])
if not os.path.exists(extracted_path):
# 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))
suffix = os.path.splitext(member.split("/")[-1])[-1]
fd, extracted_path = tempfile.mkstemp(suffix=suffix)
try:
os.write(fd, zip_file.read(member))
finally:
os.close(fd)
return extracted_path
@contextlib.contextmanager
def atomic_open(filename):
def atomic_open(filename: str) -> Generator[BufferedWriter, None, None]:
"""Write a file to the disk in an atomic fashion"""
tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
try:
@@ -315,7 +338,9 @@ def atomic_open(filename):
raise
def from_key_val_list(value):
def from_key_val_list(
value: Mapping[Any, Any] | Iterable[tuple[Any, Any]] | None,
) -> dict[Any, Any] | None:
"""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
OrderedDict, e.g.,
@@ -342,7 +367,15 @@ def from_key_val_list(value):
return OrderedDict(value)
def to_key_val_list(value):
@overload
def to_key_val_list(value: None) -> None: ...
@overload
def to_key_val_list(
value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]],
) -> list[tuple[_KT, _VT]]: ...
def to_key_val_list(
value: _t.SupportsItems[_KT, _VT] | Iterable[tuple[_KT, _VT]] | None,
) -> list[tuple[_KT, _VT]] | None:
"""Take an object and test to see if it can be represented as a
dictionary. If it can be, return a list of tuples, e.g.,
@@ -365,14 +398,14 @@ def to_key_val_list(value):
if isinstance(value, (str, bytes, bool, int)):
raise ValueError("cannot encode objects that are not 2-tuples")
if isinstance(value, Mapping):
value = value.items()
if isinstance(value, _SupportsItems):
return list(value.items())
return list(value)
# From mitsuhiko/werkzeug (used with permission).
def parse_list_header(value):
def parse_list_header(value: str) -> list[str]:
"""Parse lists as described by RFC 2068 Section 2.
In particular, parse comma-separated lists where the elements of
@@ -395,7 +428,7 @@ def parse_list_header(value):
:return: :class:`list`
:rtype: list
"""
result = []
result: list[str] = []
for item in _parse_list_header(value):
if item[:1] == item[-1:] == '"':
item = unquote_header_value(item[1:-1])
@@ -404,7 +437,7 @@ def parse_list_header(value):
# From mitsuhiko/werkzeug (used with permission).
def parse_dict_header(value):
def parse_dict_header(value: str) -> dict[str, str | None]:
"""Parse lists of key, value pairs as described by RFC 2068 Section 2 and
convert them into a python dict:
@@ -426,7 +459,7 @@ def parse_dict_header(value):
:return: :class:`dict`
:rtype: dict
"""
result = {}
result: dict[str, str | None] = {}
for item in _parse_list_header(value):
if "=" not in item:
result[item] = None
@@ -439,7 +472,7 @@ def parse_dict_header(value):
# From mitsuhiko/werkzeug (used with permission).
def unquote_header_value(value, is_filename=False):
def unquote_header_value(value: str, is_filename: bool = False) -> str:
r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
This does not use the real unquoting but what browsers are actually
using for quoting.
@@ -464,7 +497,7 @@ def unquote_header_value(value, is_filename=False):
return value
def dict_from_cookiejar(cj):
def dict_from_cookiejar(cj: CookieJar) -> dict[str, str | None]:
"""Returns a key/value dictionary from a CookieJar.
:param cj: CookieJar object to extract cookies from.
@@ -475,7 +508,7 @@ def dict_from_cookiejar(cj):
return cookie_dict
def add_dict_to_cookiejar(cj, cookie_dict):
def add_dict_to_cookiejar(cj: CookieJar, cookie_dict: dict[str, str]) -> CookieJar:
"""Returns a CookieJar from a key/value dictionary.
:param cj: CookieJar to insert cookies into.
@@ -486,7 +519,7 @@ def add_dict_to_cookiejar(cj, cookie_dict):
return cookiejar_from_dict(cookie_dict, cj)
def get_encodings_from_content(content):
def get_encodings_from_content(content: str) -> list[str]:
"""Returns encodings from given content string.
:param content: bytestring to extract encodings from.
@@ -511,32 +544,29 @@ def get_encodings_from_content(content):
)
def _parse_content_type_header(header):
"""Returns content type and parameters from given header
def _parse_content_type_header(header: str) -> tuple[str, dict[str, Any]]:
"""Returns content type and parameters from given header.
:param header: string
:return: tuple containing content type and dictionary of
parameters
parameters.
"""
tokens = header.split(";")
content_type, params = tokens[0].strip(), tokens[1:]
params_dict = {}
items_to_strip = "\"' "
params_dict: dict[str, str | bool] = {}
strip_chars = "\"' "
for param in params:
param = param.strip()
if param:
key, value = param, True
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)
if param and (idx := param.find("=")) != -1:
key = param[:idx].strip(strip_chars)
value = param[idx + 1 :].strip(strip_chars)
params_dict[key.lower()] = value
return content_type, params_dict
def get_encoding_from_headers(headers):
def get_encoding_from_headers(headers: CaseInsensitiveDict[str]) -> str | None:
"""Returns encodings from given HTTP Header Dict.
:param headers: dictionary to extract encoding from.
@@ -561,7 +591,9 @@ def get_encoding_from_headers(headers):
return "utf-8"
def stream_decode_response_unicode(iterator, r):
def stream_decode_response_unicode(
iterator: Iterable[bytes], r: Response
) -> Generator[str | bytes, None, None]:
"""Stream decodes an iterator."""
if r.encoding is None:
@@ -578,7 +610,17 @@ def stream_decode_response_unicode(iterator, r):
yield rv
def iter_slices(string, slice_length):
@overload
def iter_slices(
string: bytes, slice_length: int | None
) -> Generator[bytes, None, None]: ...
@overload
def iter_slices(
string: str, slice_length: int | None
) -> Generator[str, None, None]: ...
def iter_slices(
string: bytes | str, slice_length: int | None
) -> Generator[bytes | str, None, None]:
"""Iterate over slices of a string."""
pos = 0
if slice_length is None or slice_length <= 0:
@@ -588,7 +630,7 @@ def iter_slices(string, slice_length):
pos += slice_length
def get_unicode_from_response(r):
def get_unicode_from_response(r: Response) -> str | bytes | None:
"""Returns the requested content back in unicode.
:param r: Response object to get unicode content from.
@@ -608,8 +650,10 @@ def get_unicode_from_response(r):
),
DeprecationWarning,
)
if r.content is None: # type: ignore[reportUnnecessaryComparison]
return None
tried_encodings = []
tried_encodings: list[str] = []
# Try charset from content-type
encoding = get_encoding_from_headers(r.headers)
@@ -622,18 +666,18 @@ def get_unicode_from_response(r):
# Fall back:
try:
return str(r.content, encoding, errors="replace")
return str(r.content, encoding or "utf-8", errors="replace")
except TypeError:
return r.content
# The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset(
UNRESERVED_SET: Final = frozenset(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
)
def unquote_unreserved(uri):
def unquote_unreserved(uri: str) -> str:
"""Un-escape any percent-escape sequences in a URI that are unreserved
characters. This leaves all reserved, illegal and non-ASCII bytes encoded.
@@ -657,7 +701,7 @@ def unquote_unreserved(uri):
return "".join(parts)
def requote_uri(uri):
def requote_uri(uri: str) -> str:
"""Re-quote the given URI.
This function passes the given URI through an unquote/quote cycle to
@@ -679,7 +723,7 @@ def requote_uri(uri):
return quote(uri, safe=safe_without_percent)
def address_in_network(ip, net):
def address_in_network(ip: str, net: str) -> bool:
"""This function allows you to check if an IP belongs to a network subnet
Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24
@@ -694,7 +738,7 @@ def address_in_network(ip, net):
return (ipaddr & netmask) == (network & netmask)
def dotted_netmask(mask):
def dotted_netmask(mask: int) -> str:
"""Converts mask from /xx format to xxx.xxx.xxx.xxx
Example: if mask is 24 function returns 255.255.255.0
@@ -705,7 +749,7 @@ def dotted_netmask(mask):
return socket.inet_ntoa(struct.pack(">I", bits))
def is_ipv4_address(string_ip):
def is_ipv4_address(string_ip: str) -> bool:
"""
:rtype: bool
"""
@@ -716,7 +760,7 @@ def is_ipv4_address(string_ip):
return True
def is_valid_cidr(string_network):
def is_valid_cidr(string_network: str) -> bool:
"""
Very simple check of the cidr format in no_proxy variable.
@@ -741,7 +785,7 @@ def is_valid_cidr(string_network):
@contextlib.contextmanager
def set_environ(env_name, value):
def set_environ(env_name: str, value: str | None) -> Generator[None, None, None]:
"""Set the environment variable 'env_name' to 'value'
Save previous value, yield, and then restore the previous value stored in
@@ -749,6 +793,7 @@ def set_environ(env_name, value):
If 'value' is None, do nothing"""
value_changed = value is not None
old_value: str | None = None
if value_changed:
old_value = os.environ.get(env_name)
os.environ[env_name] = value
@@ -762,7 +807,7 @@ def set_environ(env_name, value):
os.environ[env_name] = old_value
def should_bypass_proxies(url, no_proxy):
def should_bypass_proxies(url: str, no_proxy: str | None) -> bool:
"""
Returns whether we should bypass proxies or not.
@@ -771,7 +816,7 @@ def should_bypass_proxies(url, no_proxy):
# Prioritize lowercase environment variables over uppercase
# to keep a consistent behaviour with other http projects (curl, wget).
def get_proxy(key):
def get_proxy(key: str) -> str | None:
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
@@ -780,40 +825,42 @@ def should_bypass_proxies(url, no_proxy):
if no_proxy is None:
no_proxy = get_proxy("no_proxy")
parsed = urlparse(url)
hostname = parsed.hostname
if parsed.hostname is None:
if hostname is None:
# URLs don't always have hostnames, e.g. file:/// urls.
return True
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_hosts = (host for host in no_proxy.replace(" ", "").split(",") if host)
if is_ipv4_address(parsed.hostname):
for proxy_ip in no_proxy:
if is_ipv4_address(hostname):
for proxy_ip in no_proxy_hosts:
if is_valid_cidr(proxy_ip):
if address_in_network(parsed.hostname, proxy_ip):
if address_in_network(hostname, proxy_ip):
return True
elif parsed.hostname == proxy_ip:
elif hostname == proxy_ip:
# If no_proxy ip was defined in plain IP notation instead of cidr notation &
# matches the IP of the index
return True
else:
host_with_port = parsed.hostname
host_with_port = hostname
if parsed.port:
host_with_port += f":{parsed.port}"
for host in no_proxy:
if parsed.hostname.endswith(host) or host_with_port.endswith(host):
# The URL does match something in no_proxy, so we don't want
# to apply the proxies on this URL.
for host in no_proxy_hosts:
host = host.lstrip(".")
if hostname == host or host_with_port == host:
return True
host = "." + host
if hostname.endswith(host) or host_with_port.endswith(host):
return True
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)
bypass = proxy_bypass(hostname)
except (TypeError, socket.gaierror):
bypass = False
@@ -823,7 +870,7 @@ def should_bypass_proxies(url, no_proxy):
return False
def get_environ_proxies(url, no_proxy=None):
def get_environ_proxies(url: str, no_proxy: str | None = None) -> dict[str, str]:
"""
Return a dict of environment proxies.
@@ -835,7 +882,7 @@ def get_environ_proxies(url, no_proxy=None):
return getproxies()
def select_proxy(url, proxies):
def select_proxy(url: str, proxies: dict[str, str] | None) -> str | None:
"""Select a proxy for the url, if applicable.
:param url: The url being for the request
@@ -861,7 +908,11 @@ def select_proxy(url, proxies):
return proxy
def resolve_proxies(request, proxies, trust_env=True):
def resolve_proxies(
request: Request | PreparedRequest,
proxies: dict[str, str] | None,
trust_env: bool = True,
) -> dict[str, str]:
"""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.
@@ -873,7 +924,7 @@ def resolve_proxies(request, proxies, trust_env=True):
:rtype: dict
"""
proxies = proxies if proxies is not None else {}
url = request.url
url = cast(str, request.url)
scheme = urlparse(url).scheme
no_proxy = proxies.get("no_proxy")
new_proxies = proxies.copy()
@@ -888,7 +939,7 @@ def resolve_proxies(request, proxies, trust_env=True):
return new_proxies
def default_user_agent(name="python-requests"):
def default_user_agent(name: str = "python-requests") -> str:
"""
Return a string representing the default user agent.
@@ -897,7 +948,7 @@ def default_user_agent(name="python-requests"):
return f"{name}/{__version__}"
def default_headers():
def default_headers() -> CaseInsensitiveDict[str]:
"""
:rtype: requests.structures.CaseInsensitiveDict
"""
@@ -911,7 +962,7 @@ def default_headers():
)
def parse_header_links(value):
def parse_header_links(value: str) -> list[dict[str, str]]:
"""Return a list of parsed link headers proxies.
i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg"
@@ -919,7 +970,7 @@ def parse_header_links(value):
:rtype: list
"""
links = []
links: list[dict[str, str]] = []
replace_chars = " '\""
@@ -933,7 +984,7 @@ def parse_header_links(value):
except ValueError:
url, params = val, ""
link = {"url": url.strip("<> '\"")}
link: dict[str, str] = {"url": url.strip("<> '\"")}
for param in params.split(";"):
try:
@@ -954,7 +1005,7 @@ _null2 = _null * 2
_null3 = _null * 3
def guess_json_utf(data):
def guess_json_utf(data: bytes) -> str | None:
"""
:rtype: str
"""
@@ -986,14 +1037,14 @@ def guess_json_utf(data):
return None
def prepend_scheme_if_needed(url, new_scheme):
def prepend_scheme_if_needed(url: str, new_scheme: str) -> str:
"""Given a URL that may or may not have a scheme, prepend the given scheme.
Does not replace a present scheme with the one provided as an argument.
:rtype: str
"""
parsed = parse_url(url)
scheme, auth, host, port, path, query, fragment = parsed
scheme, auth, _host, _port, path, query, fragment = parsed
# 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
@@ -1006,6 +1057,7 @@ def prepend_scheme_if_needed(url, new_scheme):
if auth:
# parse_url doesn't provide the netloc with auth
# so we'll add it ourselves.
netloc = cast(str, netloc)
netloc = "@".join([auth, netloc])
if scheme is None:
scheme = new_scheme
@@ -1015,7 +1067,7 @@ def prepend_scheme_if_needed(url, new_scheme):
return urlunparse((scheme, netloc, path, "", query, fragment))
def get_auth_from_url(url):
def get_auth_from_url(url: str) -> tuple[str, str]:
"""Given a url with authentication components, extract them into a tuple of
username,password.
@@ -1024,14 +1076,15 @@ def get_auth_from_url(url):
parsed = urlparse(url)
try:
auth = (unquote(parsed.username), unquote(parsed.password))
# except handles parsed.username/password being None
auth = (unquote(parsed.username), unquote(parsed.password)) # type: ignore[arg-type]
except (AttributeError, TypeError):
auth = ("", "")
return auth
def check_header_validity(header):
def check_header_validity(header: tuple[str | bytes, str | bytes]) -> None:
"""Verifies that header parts don't contain leading whitespace
reserved characters, or return characters.
@@ -1042,10 +1095,15 @@ def check_header_validity(header):
_validate_header_part(header, value, 1)
def _validate_header_part(header, header_part, header_validator_index):
def _validate_header_part(
header: tuple[str | bytes, str | bytes],
header_part: str | bytes,
header_validator_index: int,
) -> None:
if isinstance(header_part, str):
validator = _HEADER_VALIDATORS_STR[header_validator_index]
elif isinstance(header_part, bytes):
# runtime guard for non-str/bytes input
validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
else:
raise InvalidHeader(
@@ -1053,7 +1111,7 @@ def _validate_header_part(header, header_part, header_validator_index):
f"must be of type str or bytes, not {type(header_part)}"
)
if not validator.match(header_part):
if not validator.match(header_part): # type: ignore[arg-type]
header_kind = "name" if header_validator_index == 0 else "value"
raise InvalidHeader(
f"Invalid leading whitespace, reserved character(s), or return "
@@ -1061,13 +1119,13 @@ def _validate_header_part(header, header_part, header_validator_index):
)
def urldefragauth(url):
def urldefragauth(url: str) -> str:
"""
Given a url remove the fragment and the authentication part.
:rtype: str
"""
scheme, netloc, path, params, query, fragment = urlparse(url)
scheme, netloc, path, params, query, _fragment = urlparse(url)
# see func:`prepend_scheme_if_needed`
if not netloc:
@@ -1078,13 +1136,14 @@ def urldefragauth(url):
return urlunparse((scheme, netloc, path, params, query, ""))
def rewind_body(prepared_request):
def rewind_body(prepared_request: PreparedRequest) -> None:
"""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
prepared_request._body_position,
integer_types,
):
try:
body_seek(prepared_request._body_position)

View File

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

View File

@@ -4,9 +4,14 @@ 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

View File

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

View File

@@ -1 +1 @@
4F36C3A7E075BA6452D10EEB81E7F189FF489B74
4F36C3A7E075BA6452D10EEB81E7F189FF489B83

View File

@@ -2,18 +2,18 @@
MIIDHjCCAgYCAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQCKulIMpo633iCgbkKv1UoiLC4sQt5xWpgguujywu3hLYwmPFp9
kvPt//imqtl8FhuhKqJ8FCGrVl2YIGj1RJIB3GW7MSPNCuIBFL/gwNi35LxDPtoA
IPyXytIR7VH9+ch9DFInJaoA/BekMuKvbXk54VW9whpHbwkXSG2lBS2vKL0XemYh
9VjvtuRDji2iOZpznlVE2PEN80bojArp6oYKakv2kYzgzgxAJiI/NZGvC7mbSI4e
ja7ad3R9G0kB1FzNj36jrNO5WtxHO/mrRiXSpDeyUbitYvt0HKoM0vhTnOR+BspP
IltfwOQh8qq2Q2AaMHNcVjMH3gHCZADfhk/zAgMBAAGgbDBqBgkqhkiG9w0BCQ4x
DwAwggEKAoIBAQDcRRwk8IU1YoNu8CHzB5Vh8HP/yLfBtU69LLZq+7rDG31JlR5s
lmcwLLoZ8opUQ5rg8JMRZ7toh5zB4Uc0B4Sg8RhQMSOZYBIJkXdHuqQkciR0vWnN
vD/5CkWEhnj4dxE7xTbDufBlxmwAthC/u72UIsZHavAyLCBqsONa7xuTiogz/d+3
G+525JrfVr05hhJpT4Ypx5YY+ABkIOuOk/XuudbGm5SquuX6BgjmgaGtDjAuE/Rn
BnliIavCDrG0v3KGbG3Xxt7lFTu+98foForwGCbbQBraty27oZrzAtLltfIHlJRn
jPz0JA9akNDhAihxEsTUhk2d7jFszsd0Ev7DAgMBAAGgbDBqBgkqhkiG9w0BCQ4x
XTBbMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
MCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAN
BgkqhkiG9w0BAQsFAAOCAQEAfAhEhrulsZae71YFqgvzwJHm/hzXh47hErtgDXVJ
mFqAxgF6XrnzYujlt3XQXUx/8vdrU7jH+Pe8WO1rDvFwRPMDGoBF3RX29SzyX/2F
e102egnoRR+Hlf0Ixqu0CuTjEVnD+g4mRgXhV7LPKP4W6qGwzcVbaJ3c/zRcfqNR
g9gN6Q6Qt4fXDc7wlx2T3nOszBLQ2XCsIyzVtOJ2sSuadqKH9Aj+mrkrLBdzVFHD
FHnTMJ0t0+anZwd+AWDNsCr5lIwBGL634zw7/yJepMHuPFd2X24S3u8EaWPkfVQn
lV6rLQMGjXYTe2xuYzlUCUYnKvkyPTMjSXDkxWa+WSNwyQ==
BgkqhkiG9w0BAQsFAAOCAQEAVVIxJlDrgeG0bOSufgVVRqDQx7XSd15mGlT+CynM
lEFJ3Q9k98T2vRNNGVYYYZAnbdSOW9ACwWGcYm2bzIjbgZV0H2Kz0dLD/GrNuEY+
O9j6K2toFKc57G7UUkve+N74ldq+hkR4zbb6FQmTlnL2YaPp2dv5TxdMKfHEfPNf
Bg8xpbXdoRc7CYW1ZACme+d2U063GVqQsrIfwGJ+BtE6aNo62T/oEm+G4Wy5iBay
jNv/imwf+JKQ75bTvha9YLUg2scqdYwJj8JlBw7cvkBIHW8GydA3fX4dtV9YBbFi
8RTlWhhLgCXpYbLoDGOqF6f/MuPSIGkV1wVhCUfYA+p+Gw==
-----END CERTIFICATE REQUEST-----

View File

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

View File

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

View File

@@ -2,12 +2,12 @@
MIIEGjCCAwICAQAwbDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
cXVlc3RzMREwDwYDVQQDDAhyZXF1ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAMn3iQycTjUzpKJChRNkcm33UB282cUwpxeqKN4ahHxBpS09HRhk
cQYO7yErEUQwzQnBQEcIpzzeIMZIqHuCkgnySjeEJd95AIzNzGyoLLkS51TcJwgR
v83AvT8ljA88s9h38qGTy4/TCxJgf76pfHIuC1qoKVQh3AuHj9nOxIZLUsrdDbWF
WoLqKSVyTby+RXvSAppAR+cuBCaWStQ6xFORn48RHfc6t30ggD4rDAjyU6Vz6oR8
ot3XmGdK0h42UdqidUWkRJajEbpkCnQSXS21IvfXKxF5sFqAXJrj9iVbUfpNPpaa
W8IrHByngyV8amazGZrASstUVRFtWrnrcWECAwEAAaCCAWcwggFjBgkqhkiG9w0B
ADCCAQoCggEBAJrdvu5kvCy5g6w67gczETk/u6K0UBmf6Mv0lqXCp3Voyt5fv6SI
/OPWrFV1+m4imNe5bLM7JUoUfkqkmULsjTTh7sxVLjW226vLSYOWWy7PA+OSwUTN
LjydF1dazlPedHPYdmhVShCmyoOd1pUCDQn0/4cEA/WW4mtzZImmoCf/pyAM3XAC
9RBcSSJRywOTe6n9LY6Ko0YUW94ay2M4ClVblDxswDemaAsLFuciKmi53gKx4H/l
areo8p60dgubooiMbcc4E9bzp0oJpfh7xhwKeJtCNEpOik1AiiQIZtwqmkkIHvtI
Go3SZ7WAQU9Eh2r+u3E6aSl+N0PMXK4Y4JsCAwEAAaCCAWcwggFjBgkqhkiG9w0B
CQ4xggFUMIIBUDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggr
BgEFBQcDAjCCAR8GA1UdEQSCARYwggESggsqLmxvY2FsaG9zdIcEfwAAAYcQAAAA
AAAAAAAAAAAAAAAAAYZNc3BpZmZlOi8vdHJ1c3QucHl0aG9uLm9yZy92MC9tYWlu
@@ -15,10 +15,10 @@ dGFpbmVyL3NpZ21hdmlydXMyNC9wcm9qZWN0L3JlcXVlc3RzL29yZy9wc2aGTXNw
aWZmZTovL3RydXN0LnB5dGhvbi5vcmcvdjEvbWFpbnRhaW5lcjpzaWdtYXZpcnVz
MjQvcHJvamVjdDpyZXF1ZXN0cy9vcmc6cHNmhk1zcGlmZmU6Ly90cnVzdC5weXRo
b24ub3JnL3YxL21haW50YWluZXI9c2lnbWF2aXJ1czI0L3Byb2plY3Q9cmVxdWVz
dHMvb3JnPXBzZjANBgkqhkiG9w0BAQsFAAOCAQEAwP1KJ+Evddn2RV1FM6BFkoDK
MPDO9qwb8ea3j57SIJXZlpw168DljmuGzxJw9oys2O6FYcspbHIocAkfFwiYgVAr
NEog6xlCdPxNBJgC3YFIKwnmBjMPG6ZCWiJn940qTbaJ/j6ZviN17uW4K7Sl+THp
IkMv29uQTWkfg+GbZ9q1hm2m2GHhYLGLAUdJdtv7JI+yq5uxdsWaCANpH6kc8SnK
2rik6D3iItDhHCmToHBpdEnP8J+KDzf5pJrv/g3WH8XVrl4ZzBsOhmciWF4C3Hbf
9eu8eAsp1AsIrZOEGTfClBd7vFCES5DmI0/iRs4czQooqZPnHjOw3Azp/LujrA==
dHMvb3JnPXBzZjANBgkqhkiG9w0BAQsFAAOCAQEAMOwYPyq+OpMRQUAgoRjfxTQO
DiqfBzCsjUsPAacLTtebbWBx8y6TkLSb+/Qn3amq3ESo4iBqKpmVwdlAS4P486GD
9f78W3zkZ29jGcnQ+XHb7WvPvzBRoXImE276F9JGqJ+9q39Cbxzh0U2+ofBx2iGY
sSutzU0B/l/FKZRc8thuFoeKqHwVePLGD9p2+2nYI9I08QoGqEokTcFAq0tZ858F
9PdxBZYOKOMpnLZhiJ8qZo23v3ycBXPOjg5ILtQ9EzHoNEA5Mxx/mfNLKJ0NktZD
KXANLWKbXm+w9gTcBLtCPWNeqj5DIGPsHSfq/Bmjbp/o+uOJs6oq3s5rA7WrAA==
-----END CERTIFICATE REQUEST-----

View File

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

View File

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

View File

@@ -2,18 +2,18 @@
MIIDKjCCAhICAQAwbTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMSMwIQYDVQQK
DBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEYMBYGA1UECwwPcHl0aG9uLXJl
cXVlc3RzMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQChEKOx377ymuDg23By5Re1DHi2RiBKSHr85/ZTZuwP/69lHN7q
TQEO//EMEFZ9+ZwezeJJsejjP2HO5lQZbcsWok3hbM0wVT+vApkogPvJ8WNFFWFe
ZBnGLi/1WM9cSZpUsDJ0XCsG0RTtO27wfgZQlKQMZxTkfi971oPYxNVSjTm2JcLT
kvwYIwxjJXPDTOgRo9TEAY3cWkCrBJN4w74GWBTM5KDDA230T7WwLuv81XD2LvYj
YYdMBGcxPr5tYTIlp3LncbcrDRNk3pbYQk0bRJgkw2vUkteiRGjkt+dgVnLc6+MI
W+VLXEpj+zsOZ5/R4d1pofqj9sDyDPhtNr1JAgMBAAGgeDB2BgkqhkiG9w0BCQ4x
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
AAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAFTlFTn5Mn8JXtqB5bGjuiChe
ClA6Y32Co4l7N0CtAlf+bExwLdpLOleTX3WnryIPALl9uBUI/67dy/STn/J1Yn86
jWPEFwpmYNSKgQljYWcwtBdYLWfIsJO11kKdaAkOUHBEN5DKrXJ46Vs4918bD1/Q
6ztqdrThiKc646u9xB58Hg7F0IyMWbHfs0x16ZpcN9otrIkbqOE2wzTmc65O1t1i
HDljcSk7OnNy3a9wtLEnyPiyMqHf2k/bTlmiDRVe3cSy9xieoqmzHTnOCSASe1y9
7lcEBQild18Jo4nACV4vCYOUwrMi/58LWW+lD6OmMnPiWUqOvMbgMffMNDpWPA==
AAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAn8TdRWYAGL7L8JKpkX2FRMMY
EuFOEeJ2UaQLRbVtb1RhkIkMT6UYulQ16K45dbFe4k8DT59/mHrTl0bJ6pqVPawa
vo7vmKCrSYEz8GrImh1+i6lHaFiqbkAs1mJ6z86dZUofMYwfTPVh64ztamUqwBk7
Dey+MzWUQvpTVqoaP7cLgzMy+XfcyGuWSoL6pX5XKkt4+A2wiCTsHul1sXOn4vQz
4xY96AUVnkKosXMWXvhbMkncLNH+gs+ZeQI0MekakuMa42N+0BAad3Zx60lifG3N
ugyVUmvVI0Fq6QUlYp3YV6QQj3FDjbgOVsSJNh+2s4SIARJsb/k//Tddzxei7g==
-----END CERTIFICATE REQUEST-----

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
import threading
import pytest
from tests.testserver.server import Server, consume_socket_content
import requests
from requests.compat import JSONDecodeError
from tests.testserver.server import Server, consume_socket_content
from .utils import override_environ

View File

@@ -7,6 +7,7 @@ import json
import os
import pickle
import re
import tempfile
import threading
import warnings
from unittest import mock
@@ -25,6 +26,7 @@ from requests.compat import (
builtin_str,
cookielib,
getproxies,
is_urllib3_1,
urlparse,
)
from requests.cookies import cookiejar_from_dict, morsel_to_cookie
@@ -42,9 +44,11 @@ from requests.exceptions import (
ReadTimeout,
RequestException,
RetryError,
Timeout,
TooManyRedirects,
UnrewindableBodyError,
)
from requests.exceptions import SSLError as RequestsSSLError
from requests.exceptions import Timeout, TooManyRedirects, UnrewindableBodyError
from requests.hooks import default_hooks
from requests.models import PreparedRequest, urlencode
from requests.sessions import SessionRedirectMixin
@@ -213,6 +217,14 @@ class TestRequests:
assert r.history[0].status_code == 302
assert r.history[0].is_redirect
def test_redirect_history_no_self_reference(self, httpbin):
r = requests.get(httpbin("redirect", "3"))
assert r.status_code == 200
assert len(r.history) == 3
for i, resp in enumerate(r.history):
assert resp not in resp.history
assert resp.history == r.history[:i]
def test_HTTP_307_ALLOW_REDIRECT_POST(self, httpbin):
r = requests.post(
httpbin("redirect-to"),
@@ -574,7 +586,7 @@ class TestRequests:
("http://doesnotexist.google.com", ConnectionError),
# Connecting to an invalid port should raise a ConnectionError
("http://localhost:1", ConnectionError),
# Inputing a URL that cannot be parsed should raise an InvalidURL error
# Inputting a URL that cannot be parsed should raise an InvalidURL error
("http://fe80::5054:ff:fe5a:fc0", InvalidURL),
),
)
@@ -703,6 +715,36 @@ class TestRequests:
finally:
requests.sessions.get_netrc_auth = old_auth
def test_basicauth_with_netrc_leak(self, httpbin):
url1 = httpbin("basic-auth", "user", "pass")
url = url1[len("http://") :]
domain = url.split(":")[0]
url = f"http://example.com:@{url}"
netrc_file = ""
with tempfile.NamedTemporaryFile(mode="w", delete=False) as fp:
fp.write("machine example.com\n")
fp.write("login wronguser\n")
fp.write("password wrongpass\n")
fp.write(f"machine {domain}\n")
fp.write("login user\n")
fp.write("password pass\n")
fp.close()
netrc_file = fp.name
old_netrc = os.environ.get("NETRC", "")
os.environ["NETRC"] = netrc_file
try:
# Should use netrc
# Make sure that we don't use the example.com credentials
# for the request
r = requests.get(url)
assert r.status_code == 200
finally:
os.environ["NETRC"] = old_netrc
os.unlink(netrc_file)
def test_DIGEST_HTTP_200_OK_GET(self, httpbin):
for authtype in self.digest_auth_algo:
auth = HTTPDigestAuth("user", "pass")
@@ -928,20 +970,18 @@ class TestRequests:
INVALID_PATH = "/garbage"
with pytest.raises(IOError) as e:
requests.get(httpbin_secure(), verify=INVALID_PATH)
assert str(
e.value
) == "Could not find a suitable TLS CA certificate bundle, invalid path: {}".format(
INVALID_PATH
assert (
str(e.value)
== f"Could not find a suitable TLS CA certificate bundle, invalid path: {INVALID_PATH}"
)
def test_invalid_ssl_certificate_files(self, httpbin_secure):
INVALID_PATH = "/garbage"
with pytest.raises(IOError) as e:
requests.get(httpbin_secure(), cert=INVALID_PATH)
assert str(
e.value
) == "Could not find the TLS certificate file, invalid path: {}".format(
INVALID_PATH
assert (
str(e.value)
== f"Could not find the TLS certificate file, invalid path: {INVALID_PATH}"
)
with pytest.raises(IOError) as e:
@@ -1047,6 +1087,32 @@ class TestRequests:
)
assert r.status_code == 200
def test_post_named_tempfile(self, httpbin):
with tempfile.NamedTemporaryFile(mode="w+") as f:
f.write("named temp file contents\n")
f.seek(0)
r = requests.post(
httpbin("post"),
files={"file": f},
)
assert r.status_code == 200
assert r.json()["files"]["file"] == "named temp file contents\n"
def test_post_getattr_proxy_read_only(self, httpbin):
class ReadProxy:
def __init__(self):
self._file = io.BytesIO(b"streamed body")
def __getattr__(self, name):
if name == "__iter__":
raise AttributeError(name)
return getattr(self._file, name)
r = requests.post(httpbin("post"), data=ReadProxy())
assert r.status_code == 200
assert r.json()["data"] == "streamed body"
@pytest.mark.parametrize(
"data",
(
@@ -1662,6 +1728,32 @@ class TestRequests:
assert s.get_adapter(url_matching_prefix_with_different_case) is my_adapter
def test_session_get_adapter_prefix_with_trailing_slash(self):
# from issue #6935
prefix = "https://example.com/" # trailing slash
url_matching_prefix = "https://example.com/some/path"
url_not_matching_prefix = "https://example.com.other.com/some/path"
s = requests.Session()
adapter = HTTPAdapter()
s.mount(prefix, adapter)
assert s.get_adapter(url_matching_prefix) is adapter
assert s.get_adapter(url_not_matching_prefix) is not adapter
def test_session_get_adapter_prefix_without_trailing_slash(self):
# from issue #6935
prefix = "https://example.com" # no trailing slash
url_matching_prefix = "https://example.com/some/path"
url_extended_hostname = "https://example.com.other.com/some/path"
s = requests.Session()
adapter = HTTPAdapter()
s.mount(prefix, adapter)
assert s.get_adapter(url_matching_prefix) is adapter
assert s.get_adapter(url_extended_hostname) is adapter
def test_header_remove_is_case_insensitive(self, httpbin):
# From issue #1321
s = requests.Session()
@@ -1733,6 +1825,11 @@ class TestRequests:
{"fo\r\no": "bar"},
{"fo\n\ro": "bar"},
{"fo\no": "bar"},
{"foo": "bar\n"},
{"foo\n": "bar"},
{"foo": "bar\r\n"},
{"foo": "\n"},
{"foo": "\r\n"},
),
)
def test_header_no_return_chars(self, httpbin, invalid_header):
@@ -1810,23 +1907,6 @@ class TestRequests:
assert p.headers["Content-Length"] == length
def test_content_length_for_bytes_data(self, httpbin):
data = "This is a string containing multi-byte UTF-8 ☃️"
encoded_data = data.encode("utf-8")
length = str(len(encoded_data))
req = requests.Request("POST", httpbin("post"), data=encoded_data)
p = req.prepare()
assert p.headers["Content-Length"] == length
def test_content_length_for_string_data_counts_bytes(self, httpbin):
data = "This is a string containing multi-byte UTF-8 ☃️"
length = str(len(data.encode("utf-8")))
req = requests.Request("POST", httpbin("post"), data=data)
p = req.prepare()
assert p.headers["Content-Length"] == length
def test_nonhttp_schemes_dont_check_URLs(self):
test_urls = (
"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",
@@ -2019,6 +2099,21 @@ class TestRequests:
assert "Unable to rewind request body" in str(e)
def test_getattr_proxy_stream_follows_redirect(self, httpbin):
"""Ensure stream wrappers that don't implement __iter__ directly are still detected."""
class AttrProxy:
def __init__(self):
self._file = io.BytesIO(b"data")
def __getattr__(self, name):
return getattr(self._file, name)
r = requests.post(
httpbin("redirect-to?url=/post&status_code=307"), data=AttrProxy()
)
assert r.json()["data"] == "data"
def _patch_adapter_gzipped_redirect(self, session, url):
adapter = session.get_adapter(url=url)
org_build_response = adapter.build_response
@@ -2419,7 +2514,6 @@ class TestMorselToCookieExpires:
class TestMorselToCookieMaxAge:
"""Tests for morsel_to_cookie when morsel contains max-age."""
def test_max_age_valid_int(self):
@@ -2526,6 +2620,7 @@ class RedirectSession(SessionRedirectMixin):
def build_response(self):
request = self.calls[-1].args[0]
r = requests.Response()
r.url = request.url
try:
r.status_code = int(self.redirects.pop(0))
@@ -2966,6 +3061,29 @@ class TestPreparingURLs:
assert client_cert is not None
def test_content_length_for_bytes_data(httpbin):
data = "This is a string containing multi-byte UTF-8 ☃️"
encoded_data = data.encode("utf-8")
length = str(len(encoded_data))
req = requests.Request("POST", httpbin("post"), data=encoded_data)
p = req.prepare()
assert p.headers["Content-Length"] == length
@pytest.mark.skipif(
is_urllib3_1,
reason="urllib3 2.x encodes all strings to utf-8, urllib3 1.x uses latin-1",
)
def test_content_length_for_string_data_counts_bytes(httpbin):
data = "This is a string containing multi-byte UTF-8 ☃️"
length = str(len(data.encode("utf-8")))
req = requests.Request("POST", httpbin("post"), data=data)
p = req.prepare()
assert p.headers["Content-Length"] == length
def test_json_decode_errors_are_serializable_deserializable():
json_decode_error = requests.exceptions.JSONDecodeError(
"Extra data",

View File

@@ -76,3 +76,16 @@ class TestLookupDict:
@get_item_parameters
def test_get(self, key, value):
assert self.lookup_dict.get(key) == value
def test_hasattr(self):
assert hasattr(self.lookup_dict, "bad_gateway") is True
assert hasattr(self.lookup_dict, "not_a_key") is False
def test_getattr(self):
assert getattr(self.lookup_dict, "bad_gateway") == 502
with pytest.raises(AttributeError):
getattr(self.lookup_dict, "not_a_key")
@get_item_parameters
def test_getattr_default(self, key, value):
assert getattr(self.lookup_dict, key, None) == value

View File

@@ -3,9 +3,9 @@ import threading
import time
import pytest
from tests.testserver.server import Server
import requests
from tests.testserver.server import Server
class TestTestServer:
@@ -42,7 +42,7 @@ class TestTestServer:
def test_text_response(self):
"""the text_response_server sends the given text"""
server = Server.text_response_server(
"HTTP/1.1 200 OK\r\n" "Content-Length: 6\r\n" "\r\nroflol"
"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nroflol"
)
with server as (host, port):

View File

@@ -23,6 +23,7 @@ from requests.utils import (
get_encoding_from_headers,
get_encodings_from_content,
get_environ_proxies,
get_netrc_auth,
guess_filename,
guess_json_utf,
is_ipv4_address,
@@ -152,6 +153,34 @@ class TestSuperLen:
assert super_len(object()) == 0
class TestGetNetrcAuth:
def test_works(self, tmp_path, monkeypatch):
netrc_path = tmp_path / ".netrc"
monkeypatch.setenv("NETRC", str(netrc_path))
with open(netrc_path, "w") as f:
f.write("machine example.com login aaaa password bbbb\n")
auth = get_netrc_auth("http://example.com/thing")
assert auth == ("aaaa", "bbbb")
def test_not_vulnerable_to_bad_url_parsing(self, tmp_path, monkeypatch):
netrc_path = tmp_path / ".netrc"
monkeypatch.setenv("NETRC", str(netrc_path))
with open(netrc_path, "w") as f:
f.write("machine example.com login aaaa password bbbb\n")
auth = get_netrc_auth("http://example.com:@evil.com/&apos;")
assert auth is None
def test_empty_default_credentials_ignored(self, tmp_path, monkeypatch):
"""Empty default credentials should not be returned."""
netrc_path = tmp_path / ".netrc"
monkeypatch.setenv("NETRC", str(netrc_path))
with open(netrc_path, "w") as f:
f.write("machine example.com login user password pass\ndefault\n")
auth = get_netrc_auth("http://httpbin.org/")
assert auth is None
class TestToKeyValList:
@pytest.mark.parametrize(
"value, expected",
@@ -332,9 +361,12 @@ class TestExtractZippedPaths:
zipped_path = os.path.join(zipped_py.strpath, name.lstrip(r"\/"))
extracted_path = extract_zipped_paths(zipped_path)
assert extracted_path != zipped_path
assert os.path.exists(extracted_path)
assert filecmp.cmp(extracted_path, __file__)
try:
assert extracted_path != zipped_path
assert os.path.exists(extracted_path)
assert filecmp.cmp(extracted_path, __file__)
finally:
os.remove(extracted_path)
def test_invalid_unc_path(self):
path = r"\\localhost\invalid\location"
@@ -564,7 +596,6 @@ def test_parse_dict_header(value, expected):
{
"boundary": "something",
"boundary2": "something_else",
"no_equals": True,
},
),
),
@@ -575,7 +606,6 @@ def test_parse_dict_header(value, expected):
{
"boundary": "something",
"boundary2": "something_else",
"no_equals": True,
},
),
),
@@ -586,7 +616,6 @@ def test_parse_dict_header(value, expected):
{
"boundary": "something",
"boundary2": "something_else",
"no_equals": True,
},
),
),
@@ -597,7 +626,6 @@ def test_parse_dict_header(value, expected):
{
"boundary": "something",
"boundary2": "something_else",
"no_equals": True,
},
),
),
@@ -617,6 +645,10 @@ def test__parse_content_type_header(value, expected):
"utf-8",
),
(CaseInsensitiveDict({"content-type": "text/plain"}), "ISO-8859-1"),
(
CaseInsensitiveDict({"content-type": "text/html; charset"}),
"ISO-8859-1",
),
),
)
def test_get_encoding_from_headers(value, expected):
@@ -812,6 +844,29 @@ def test_should_bypass_proxies_no_proxy(url, expected, monkeypatch):
assert should_bypass_proxies(url, no_proxy=no_proxy) == expected
@pytest.mark.parametrize(
"url, expected",
(
("http://localhost/", True),
("http://anotherdomain.com:8888/", True),
("http://newdomain.com:1234/", True),
("http://www.newdomain.com:1234/", True),
("http://foo.d.o.t/", True),
("http://d.o.t/", True),
("http://prelocalhost/", False),
("http://newdomain.com/", False),
("http://newdomain.com:1235/", False),
),
)
def test_should_bypass_proxies_no_proxy_domain_boundary(url, expected):
"""Ensure no_proxy matching respects domain boundaries and does not
greedily match domains that merely endswith the no_proxy entry.
See CPython bpo-39057.
"""
no_proxy = "localhost, anotherdomain.com, newdomain.com:1234, .d.o.t"
assert should_bypass_proxies(url, no_proxy=no_proxy) == expected
@pytest.mark.skipif(os.name != "nt", reason="Test only on Windows")
@pytest.mark.parametrize(
"url, expected, override",

View File

@@ -1,5 +1,5 @@
[tox]
envlist = py{38,39,310,311,312}-{default, use_chardet_on_py3}
envlist = py{310,311,312,313,314}-{default, use_chardet_on_py3}
[testenv]
deps = -rrequirements-dev.txt