├── docs ├── .nojekyll ├── _themes │ ├── .gitignore │ ├── LICENSE │ └── flask_theme_support.py ├── dev │ ├── authors.rst │ └── contributing.rst ├── requirements.txt ├── _static │ ├── requests-sidebar.png │ └── custom.css ├── community │ ├── updates.rst │ ├── support.rst │ ├── out-there.rst │ ├── release-process.rst │ ├── recommended.rst │ ├── faq.rst │ └── vulnerabilities.rst ├── user │ ├── install.rst │ └── authentication.rst ├── _templates │ ├── hacks.html │ ├── sidebarlogo.html │ └── sidebarintro.html ├── index.rst ├── make.bat ├── api.rst ├── Makefile └── conf.py ├── tests ├── testserver │ ├── __init__.py │ └── server.py ├── test_packages.py ├── __init__.py ├── compat.py ├── utils.py ├── test_hooks.py ├── test_help.py ├── conftest.py ├── test_structures.py ├── test_testserver.py └── test_lowlevel.py ├── .coveragerc ├── NOTICE ├── ext ├── LICENSE ├── kr.png ├── ss.png ├── psf.png ├── requests-logo.ai ├── flower-of-life.jpg ├── kr-compressed.png ├── psf-compressed.png ├── requests-logo.png ├── ss-compressed.png └── requests-logo-compressed.png ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── Feature_request.md │ ├── Custom.md │ └── Bug_report.md ├── CODE_OF_CONDUCT.md ├── workflows │ ├── lock-issues.yml │ ├── run-tests.yml │ └── codeql-analysis.yml ├── ISSUE_TEMPLATE.md ├── CONTRIBUTING.md └── SECURITY.md ├── setup.cfg ├── pytest.ini ├── MANIFEST.in ├── requirements-dev.txt ├── tox.ini ├── .gitignore ├── requests ├── __version__.py ├── certs.py ├── hooks.py ├── packages.py ├── _internal_utils.py ├── compat.py ├── structures.py ├── exceptions.py ├── help.py ├── status_codes.py ├── __init__.py ├── api.py └── auth.py ├── Makefile ├── README.md ├── setup.py ├── AUTHORS.rst └── LICENSE /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/testserver/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = requests/packages/* -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Requests 2 | Copyright 2019 Kenneth Reitz 3 | -------------------------------------------------------------------------------- /docs/_themes/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /ext/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Kenneth Reitz. All rights reserved. 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.python.org/psf/sponsorship/'] 2 | -------------------------------------------------------------------------------- /ext/kr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/kr.png -------------------------------------------------------------------------------- /ext/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/ss.png -------------------------------------------------------------------------------- /ext/psf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/psf.png -------------------------------------------------------------------------------- /docs/dev/authors.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | .. include:: ../../AUTHORS.rst 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | license_file = LICENSE 6 | -------------------------------------------------------------------------------- /ext/requests-logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/requests-logo.ai -------------------------------------------------------------------------------- /ext/flower-of-life.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/flower-of-life.jpg -------------------------------------------------------------------------------- /ext/kr-compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/kr-compressed.png -------------------------------------------------------------------------------- /ext/psf-compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/psf-compressed.png -------------------------------------------------------------------------------- /ext/requests-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/requests-logo.png -------------------------------------------------------------------------------- /ext/ss-compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/ss-compressed.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # Pinning to avoid unexpected breakages. 2 | # Used by RTD to generate docs. 3 | Sphinx==4.2.0 4 | -------------------------------------------------------------------------------- /ext/requests-logo-compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/ext/requests-logo-compressed.png -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -p no:warnings --doctest-modules 3 | doctest_optionflags= NORMALIZE_WHITESPACE ELLIPSIS -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE NOTICE HISTORY.md pytest.ini requirements-dev.txt 2 | recursive-include tests *.py 3 | -------------------------------------------------------------------------------- /docs/_static/requests-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey-wenjun/requests/main/docs/_static/requests-sidebar.png -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest>=2.8.0,<=6.2.5 2 | pytest-cov 3 | pytest-httpbin==1.0.0 4 | pytest-mock==2.0.0 5 | httpbin==0.7.0 6 | Flask>=1.0,<2.0 7 | trustme 8 | wheel 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | Requests is not accepting feature requests at this time. 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request for Help 3 | about: Guidance on using Requests. 4 | 5 | --- 6 | 7 | Please refer to our [Stack Overflow tag](https://stackoverflow.com/questions/tagged/python-requests) for guidance. 8 | -------------------------------------------------------------------------------- /tests/test_packages.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def test_can_access_urllib3_attribute(): 5 | requests.packages.urllib3 6 | 7 | 8 | def test_can_access_idna_attribute(): 9 | requests.packages.idna 10 | 11 | 12 | def test_can_access_chardet_attribute(): 13 | requests.packages.chardet 14 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,36,37,38,39}-{default,use_chardet_on_py3} 3 | 4 | [testenv] 5 | deps = -rrequirements-dev.txt 6 | extras = 7 | security 8 | socks 9 | commands = 10 | pytest tests 11 | 12 | [testenv:default] 13 | 14 | [testenv:use_chardet_on_py3] 15 | extras = 16 | security 17 | socks 18 | use_chardet_on_py3 19 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Treat each other well 2 | 3 | Everyone participating in the _requests_ project, and in particular in the issue tracker, 4 | pull requests, and social media activity, is expected to treat other people with respect 5 | and more generally to follow the guidelines articulated in the 6 | [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/). 7 | -------------------------------------------------------------------------------- /.github/workflows/lock-issues.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock Threads' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 * * * *' 6 | 7 | permissions: 8 | issues: write 9 | pull-requests: write 10 | 11 | jobs: 12 | action: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: dessant/lock-threads@v2 16 | with: 17 | issue-lock-inactive-days: 90 18 | pr-lock-inactive-days: 90 19 | -------------------------------------------------------------------------------- /docs/community/updates.rst: -------------------------------------------------------------------------------- 1 | .. _updates: 2 | 3 | 4 | Community Updates 5 | ================= 6 | 7 | If you'd like to stay up to date on the community and development of Requests, 8 | there are several options: 9 | 10 | 11 | GitHub 12 | ------ 13 | 14 | The best way to track the development of Requests is through 15 | `the GitHub repo `_. 16 | 17 | 18 | .. include:: ../../HISTORY.md 19 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Requests test package initialisation.""" 4 | 5 | import warnings 6 | 7 | import urllib3 8 | from urllib3.exceptions import SNIMissingWarning 9 | 10 | # urllib3 sets SNIMissingWarning to only go off once, 11 | # while this test suite requires it to always fire 12 | # so that it occurs during test_requests.test_https_warnings 13 | warnings.simplefilter('always', SNIMissingWarning) 14 | -------------------------------------------------------------------------------- /tests/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from requests.compat import is_py3 4 | 5 | 6 | try: 7 | import StringIO 8 | except ImportError: 9 | import io as StringIO 10 | 11 | try: 12 | from cStringIO import StringIO as cStringIO 13 | except ImportError: 14 | cStringIO = None 15 | 16 | if is_py3: 17 | def u(s): 18 | return s 19 | else: 20 | def u(s): 21 | return s.decode('unicode-escape') 22 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import contextlib 4 | import os 5 | 6 | 7 | @contextlib.contextmanager 8 | def override_environ(**kwargs): 9 | save_env = dict(os.environ) 10 | for key, value in kwargs.items(): 11 | if value is None: 12 | del os.environ[key] 13 | else: 14 | os.environ[key] = value 15 | try: 16 | yield 17 | finally: 18 | os.environ.clear() 19 | os.environ.update(save_env) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | MANIFEST 3 | coverage.xml 4 | nosetests.xml 5 | junit-report.xml 6 | pylint.txt 7 | toy.py 8 | .cache/ 9 | cover/ 10 | build/ 11 | docs/_build 12 | requests.egg-info/ 13 | *.pyc 14 | *.swp 15 | *.egg 16 | env/ 17 | .venv/ 18 | .eggs/ 19 | .tox/ 20 | .pytest_cache/ 21 | .vscode/ 22 | .eggs/ 23 | 24 | .workon 25 | 26 | # in case you work with IntelliJ/PyCharm 27 | .idea 28 | *.iml 29 | .python-version 30 | 31 | 32 | t.py 33 | 34 | t2.py 35 | dist 36 | 37 | /.mypy_cache/ 38 | -------------------------------------------------------------------------------- /requests/__version__.py: -------------------------------------------------------------------------------- 1 | # .-. .-. .-. . . .-. .-. .-. .-. 2 | # |( |- |.| | | |- `-. | `-. 3 | # ' ' `-' `-`.`-' `-' `-' ' `-' 4 | 5 | __title__ = 'requests' 6 | __description__ = 'Python HTTP for Humans.' 7 | __url__ = 'https://requests.readthedocs.io' 8 | __version__ = '2.26.0' 9 | __build__ = 0x022600 10 | __author__ = 'Kenneth Reitz' 11 | __author_email__ = 'me@kennethreitz.org' 12 | __license__ = 'Apache 2.0' 13 | __copyright__ = 'Copyright 2020 Kenneth Reitz' 14 | __cake__ = u'\u2728 \U0001f370 \u2728' 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Summary. 2 | 3 | ## Expected Result 4 | 5 | What you expected. 6 | 7 | ## Actual Result 8 | 9 | What happened instead. 10 | 11 | ## Reproduction Steps 12 | 13 | ```python 14 | import requests 15 | 16 | ``` 17 | 18 | ## System Information 19 | 20 | $ python -m requests.help 21 | 22 | ``` 23 | 24 | ``` 25 | 26 | This command is only available on Requests v2.16.4 and greater. Otherwise, 27 | please provide some basic information about your system (Python version, 28 | operating system, &c). -------------------------------------------------------------------------------- /requests/certs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | requests.certs 6 | ~~~~~~~~~~~~~~ 7 | 8 | This module returns the preferred default CA certificate bundle. There is 9 | only one — the one from the certifi package. 10 | 11 | If you are packaging Requests, e.g., for a Linux distribution or a managed 12 | environment, you can change the definition of where() to return a separately 13 | packaged CA bundle. 14 | """ 15 | from certifi import where 16 | 17 | if __name__ == '__main__': 18 | print(where()) 19 | -------------------------------------------------------------------------------- /tests/test_hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | 5 | from requests import hooks 6 | 7 | 8 | def hook(value): 9 | return value[1:] 10 | 11 | 12 | @pytest.mark.parametrize( 13 | 'hooks_list, result', ( 14 | (hook, 'ata'), 15 | ([hook, lambda x: None, hook], 'ta'), 16 | ) 17 | ) 18 | def test_hooks(hooks_list, result): 19 | assert hooks.dispatch_hook('response', {'response': hooks_list}, 'Data') == result 20 | 21 | 22 | def test_default_hooks(): 23 | assert hooks.default_hooks() == {'response': []} 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | Summary. 8 | 9 | ## Expected Result 10 | 11 | What you expected. 12 | 13 | ## Actual Result 14 | 15 | What happened instead. 16 | 17 | ## Reproduction Steps 18 | 19 | ```python 20 | import requests 21 | 22 | ``` 23 | 24 | ## System Information 25 | 26 | $ python -m requests.help 27 | 28 | ``` 29 | 30 | ``` 31 | 32 | This command is only available on Requests v2.16.4 and greater. Otherwise, 33 | please provide some basic information about your system (Python version, 34 | operating system, &c). 35 | -------------------------------------------------------------------------------- /requests/hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.hooks 5 | ~~~~~~~~~~~~~~ 6 | 7 | This module provides the capabilities for the Requests hooks system. 8 | 9 | Available hooks: 10 | 11 | ``response``: 12 | The response generated from a Request. 13 | """ 14 | HOOKS = ['response'] 15 | 16 | 17 | def default_hooks(): 18 | return {event: [] for event in HOOKS} 19 | 20 | # TODO: response is the only one 21 | 22 | 23 | def dispatch_hook(key, hooks, hook_data, **kwargs): 24 | """Dispatches a hook dictionary on a given piece of data.""" 25 | hooks = hooks or {} 26 | hooks = hooks.get(key) 27 | if hooks: 28 | if hasattr(hooks, '__call__'): 29 | hooks = [hooks] 30 | for hook in hooks: 31 | _hook_data = hook(hook_data, **kwargs) 32 | if _hook_data is not None: 33 | hook_data = _hook_data 34 | return hook_data 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs 2 | init: 3 | pip install -e .[socks] 4 | pip install -r requirements-dev.txt 5 | test: 6 | # This runs all of the tests, on both Python 2 and Python 3. 7 | detox 8 | ci: 9 | pytest tests --junitxml=report.xml 10 | 11 | test-readme: 12 | python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!" 13 | 14 | flake8: 15 | flake8 --ignore=E501,F401,E128,E402,E731,F821 requests 16 | 17 | coverage: 18 | pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests 19 | 20 | publish: 21 | pip install 'twine>=1.5.0' 22 | python setup.py sdist bdist_wheel 23 | twine upload dist/* 24 | rm -fr build dist .egg requests.egg-info 25 | 26 | docs: 27 | cd docs && make html 28 | @echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m" 29 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | timeout-minutes: 10 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10-dev] 13 | os: [ubuntu-18.04, macOS-latest, windows-latest] 14 | include: 15 | # pypy3 on Mac OS currently fails trying to compile 16 | # brotlipy. Moving pypy3 to only test linux. 17 | - python-version: pypy3 18 | os: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | make 29 | - name: Run tests 30 | run: | 31 | make ci 32 | -------------------------------------------------------------------------------- /docs/community/support.rst: -------------------------------------------------------------------------------- 1 | .. _support: 2 | 3 | Support 4 | ======= 5 | 6 | If you have questions or issues about Requests, there are several options: 7 | 8 | Stack Overflow 9 | -------------- 10 | 11 | If your question does not contain sensitive (possibly proprietary) 12 | information or can be properly anonymized, please ask a question on 13 | `Stack Overflow `_ 14 | and use the tag ``python-requests``. 15 | 16 | 17 | File an Issue 18 | ------------- 19 | 20 | If you notice some unexpected behaviour in Requests, or want to see support 21 | for a new feature, 22 | `file an issue on GitHub `_. 23 | 24 | 25 | Send a Tweet 26 | ------------ 27 | 28 | If your question is less than 280 characters, feel free to send a tweet to 29 | `@nateprewitt `_, 30 | `@sethmlarson `_, or 31 | `@sigmavirus24 `_. 32 | -------------------------------------------------------------------------------- /tests/test_help.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 2 | 3 | import sys 4 | 5 | import pytest 6 | 7 | from requests.help import info 8 | 9 | 10 | def test_system_ssl(): 11 | """Verify we're actually setting system_ssl when it should be available.""" 12 | assert info()['system_ssl']['version'] != '' 13 | 14 | 15 | class VersionedPackage(object): 16 | def __init__(self, version): 17 | self.__version__ = version 18 | 19 | 20 | def test_idna_without_version_attribute(mocker): 21 | """Older versions of IDNA don't provide a __version__ attribute, verify 22 | that if we have such a package, we don't blow up. 23 | """ 24 | mocker.patch('requests.help.idna', new=None) 25 | assert info()['idna'] == {'version': ''} 26 | 27 | 28 | def test_idna_with_version_attribute(mocker): 29 | """Verify we're actually setting idna version when it should be available.""" 30 | mocker.patch('requests.help.idna', new=VersionedPackage('2.6')) 31 | assert info()['idna'] == {'version': '2.6'} 32 | -------------------------------------------------------------------------------- /requests/packages.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | try: 4 | import chardet 5 | except ImportError: 6 | import charset_normalizer as chardet 7 | import warnings 8 | 9 | warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer') 10 | 11 | # This code exists for backwards compatibility reasons. 12 | # I don't like it either. Just look the other way. :) 13 | 14 | for package in ('urllib3', 'idna'): 15 | locals()[package] = __import__(package) 16 | # This traversal is apparently necessary such that the identities are 17 | # preserved (requests.packages.urllib3.* is urllib3.*) 18 | for mod in list(sys.modules): 19 | if mod == package or mod.startswith(package + '.'): 20 | sys.modules['requests.packages.' + mod] = sys.modules[mod] 21 | 22 | target = chardet.__name__ 23 | for mod in list(sys.modules): 24 | if mod == target or mod.startswith(target + '.'): 25 | sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod] 26 | # Kinda cool, though, right? 27 | -------------------------------------------------------------------------------- /docs/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation of Requests 4 | ======================== 5 | 6 | This part of the documentation covers the installation of Requests. 7 | The first step to using any software package is getting it properly installed. 8 | 9 | 10 | $ python -m pip install requests 11 | -------------------------------- 12 | 13 | To install Requests, simply run this simple command in your terminal of choice:: 14 | 15 | $ python -m pip install requests 16 | 17 | Get the Source Code 18 | ------------------- 19 | 20 | Requests is actively developed on GitHub, where the code is 21 | `always available `_. 22 | 23 | You can either clone the public repository:: 24 | 25 | $ git clone git://github.com/psf/requests.git 26 | 27 | Or, download the `tarball `_:: 28 | 29 | $ curl -OL https://github.com/psf/requests/tarball/main 30 | # optionally, zipball is also available (for Windows users). 31 | 32 | Once you have a copy of the source, you can embed it in your own Python 33 | package, or install it into your site-packages easily:: 34 | 35 | $ cd requests 36 | $ python -m pip install . 37 | -------------------------------------------------------------------------------- /docs/community/out-there.rst: -------------------------------------------------------------------------------- 1 | Integrations 2 | ============ 3 | 4 | Python for iOS 5 | -------------- 6 | 7 | Requests is built into the wonderful `Python for iOS `_ runtime! 8 | 9 | To give it a try, simply:: 10 | 11 | import requests 12 | 13 | 14 | Articles & Talks 15 | ================ 16 | - `Python for the Web `_ teaches how to use Python to interact with the web, using Requests. 17 | - `Daniel Greenfeld's Review of Requests `_ 18 | - `My 'Python for Humans' talk `_ ( `audio `_ ) 19 | - `Issac Kelly's 'Consuming Web APIs' talk `_ 20 | - `Blog post about Requests via Yum `_ 21 | - `Russian blog post introducing Requests `_ 22 | - `Sending JSON in Requests `_ 23 | -------------------------------------------------------------------------------- /requests/_internal_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests._internal_utils 5 | ~~~~~~~~~~~~~~ 6 | 7 | Provides utility functions that are consumed internally by Requests 8 | which depend on extremely few external helpers (such as compat) 9 | """ 10 | 11 | from .compat import is_py2, builtin_str, str 12 | 13 | 14 | def to_native_string(string, encoding='ascii'): 15 | """Given a string object, regardless of type, returns a representation of 16 | that string in the native string type, encoding and decoding where 17 | necessary. This assumes ASCII unless told otherwise. 18 | """ 19 | if isinstance(string, builtin_str): 20 | out = string 21 | else: 22 | if is_py2: 23 | out = string.encode(encoding) 24 | else: 25 | out = string.decode(encoding) 26 | 27 | return out 28 | 29 | 30 | def unicode_is_ascii(u_string): 31 | """Determine if unicode string only contains ASCII characters. 32 | 33 | :param str u_string: unicode string to check. Must be unicode 34 | and not Python 2 `str`. 35 | :rtype: bool 36 | """ 37 | assert isinstance(u_string, str) 38 | try: 39 | u_string.encode('ascii') 40 | return True 41 | except UnicodeEncodeError: 42 | return False 43 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | from http.server import HTTPServer 5 | from http.server import SimpleHTTPRequestHandler 6 | except ImportError: 7 | from BaseHTTPServer import HTTPServer 8 | from SimpleHTTPServer import SimpleHTTPRequestHandler 9 | 10 | import ssl 11 | import tempfile 12 | import threading 13 | 14 | import pytest 15 | from requests.compat import urljoin 16 | import trustme 17 | 18 | 19 | def prepare_url(value): 20 | # Issue #1483: Make sure the URL always has a trailing slash 21 | httpbin_url = value.url.rstrip('/') + '/' 22 | 23 | def inner(*suffix): 24 | return urljoin(httpbin_url, '/'.join(suffix)) 25 | 26 | return inner 27 | 28 | 29 | @pytest.fixture 30 | def httpbin(httpbin): 31 | return prepare_url(httpbin) 32 | 33 | 34 | @pytest.fixture 35 | def httpbin_secure(httpbin_secure): 36 | return prepare_url(httpbin_secure) 37 | 38 | 39 | @pytest.fixture 40 | def nosan_server(tmp_path_factory): 41 | tmpdir = tmp_path_factory.mktemp("certs") 42 | ca = trustme.CA() 43 | # only commonName, no subjectAltName 44 | server_cert = ca.issue_cert(common_name=u"localhost") 45 | ca_bundle = str(tmpdir / "ca.pem") 46 | ca.cert_pem.write_to_path(ca_bundle) 47 | 48 | context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 49 | server_cert.configure_cert(context) 50 | server = HTTPServer(("localhost", 0), SimpleHTTPRequestHandler) 51 | server.socket = context.wrap_socket(server.socket, server_side=True) 52 | server_thread = threading.Thread(target=server.serve_forever) 53 | server_thread.start() 54 | 55 | yield "localhost", server.server_address[1], ca_bundle 56 | 57 | server.shutdown() 58 | server_thread.join() 59 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Modifications: 2 | 3 | Copyright (c) 2011 Kenneth Reitz. 4 | 5 | 6 | Original Project: 7 | 8 | Copyright (c) 2010 by Armin Ronacher. 9 | 10 | 11 | Some rights reserved. 12 | 13 | Redistribution and use in source and binary forms of the theme, with or 14 | without modification, are permitted provided that the following conditions 15 | are met: 16 | 17 | * Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 20 | * Redistributions in binary form must reproduce the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer in the documentation and/or other materials provided 23 | with the distribution. 24 | 25 | * The names of the contributors may not be used to endorse or 26 | promote products derived from this software without specific 27 | prior written permission. 28 | 29 | We kindly ask you to only use these themes in an unmodified manner just 30 | for Flask and Flask-related products, not for unrelated projects. If you 31 | like the visual style and want to use it for your own projects, please 32 | consider making some larger changes to the themes (such as changing 33 | font faces, sizes, colors or margins). 34 | 35 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 36 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 38 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 39 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 40 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 41 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 42 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 43 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 44 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 45 | POSSIBILITY OF SUCH DAMAGE. 46 | -------------------------------------------------------------------------------- /docs/community/release-process.rst: -------------------------------------------------------------------------------- 1 | Release Process and Rules 2 | ========================= 3 | 4 | .. versionadded:: v2.6.2 5 | 6 | Starting with the version to be released after ``v2.6.2``, the following rules 7 | will govern and describe how the Requests core team produces a new release. 8 | 9 | Major Releases 10 | -------------- 11 | 12 | A major release will include breaking changes. When it is versioned, it will 13 | be versioned as ``vX.0.0``. For example, if the previous release was 14 | ``v10.2.7`` the next version will be ``v11.0.0``. 15 | 16 | Breaking changes are changes that break backwards compatibility with prior 17 | versions. If the project were to change the ``text`` attribute on a 18 | ``Response`` object to a method, that would only happen in a Major release. 19 | 20 | Major releases may also include miscellaneous bug fixes. The core developers of 21 | Requests are committed to providing a good user experience. This means we're 22 | also committed to preserving backwards compatibility as much as possible. Major 23 | releases will be infrequent and will need strong justifications before they are 24 | considered. 25 | 26 | Minor Releases 27 | -------------- 28 | 29 | A minor release will not include breaking changes but may include miscellaneous 30 | bug fixes. If the previous version of Requests released was ``v10.2.7`` a minor 31 | release would be versioned as ``v10.3.0``. 32 | 33 | Minor releases will be backwards compatible with releases that have the same 34 | major version number. In other words, all versions that would start with 35 | ``v10.`` should be compatible with each other. 36 | 37 | Hotfix Releases 38 | --------------- 39 | 40 | A hotfix release will only include bug fixes that were missed when the project 41 | released the previous version. If the previous version of Requests released 42 | ``v10.2.7`` the hotfix release would be versioned as ``v10.2.8``. 43 | 44 | Hotfixes will **not** include upgrades to vendored dependencies after 45 | ``v2.6.2`` 46 | 47 | Reasoning 48 | --------- 49 | 50 | In the 2.5 and 2.6 release series, the Requests core team upgraded vendored 51 | dependencies and caused a great deal of headaches for both users and the core 52 | team. To reduce this pain, we're forming a concrete set of procedures so 53 | expectations will be properly set. 54 | -------------------------------------------------------------------------------- /requests/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.compat 5 | ~~~~~~~~~~~~~~~ 6 | 7 | This module handles import compatibility issues between Python 2 and 8 | Python 3. 9 | """ 10 | 11 | try: 12 | import chardet 13 | except ImportError: 14 | import charset_normalizer as chardet 15 | 16 | import sys 17 | 18 | # ------- 19 | # Pythons 20 | # ------- 21 | 22 | # Syntax sugar. 23 | _ver = sys.version_info 24 | 25 | #: Python 2.x? 26 | is_py2 = (_ver[0] == 2) 27 | 28 | #: Python 3.x? 29 | is_py3 = (_ver[0] == 3) 30 | 31 | has_simplejson = False 32 | try: 33 | import simplejson as json 34 | has_simplejson = True 35 | except ImportError: 36 | import json 37 | 38 | # --------- 39 | # Specifics 40 | # --------- 41 | 42 | if is_py2: 43 | from urllib import ( 44 | quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, 45 | proxy_bypass, proxy_bypass_environment, getproxies_environment) 46 | from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag 47 | from urllib2 import parse_http_list 48 | import cookielib 49 | from Cookie import Morsel 50 | from StringIO import StringIO 51 | # Keep OrderedDict for backwards compatibility. 52 | from collections import Callable, Mapping, MutableMapping, OrderedDict 53 | 54 | builtin_str = str 55 | bytes = str 56 | str = unicode 57 | basestring = basestring 58 | numeric_types = (int, long, float) 59 | integer_types = (int, long) 60 | JSONDecodeError = ValueError 61 | 62 | elif is_py3: 63 | from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag 64 | from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment 65 | from http import cookiejar as cookielib 66 | from http.cookies import Morsel 67 | from io import StringIO 68 | # Keep OrderedDict for backwards compatibility. 69 | from collections import OrderedDict 70 | from collections.abc import Callable, Mapping, MutableMapping 71 | if has_simplejson: 72 | from simplejson import JSONDecodeError 73 | else: 74 | from json import JSONDecodeError 75 | 76 | builtin_str = str 77 | str = str 78 | bytes = bytes 79 | basestring = (str, bytes) 80 | numeric_types = (int, float) 81 | integer_types = (int,) 82 | -------------------------------------------------------------------------------- /docs/_templates/hacks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 81 | -------------------------------------------------------------------------------- /docs/community/recommended.rst: -------------------------------------------------------------------------------- 1 | .. _recommended: 2 | 3 | Recommended Packages and Extensions 4 | =================================== 5 | 6 | Requests has a great variety of powerful and useful third-party extensions. 7 | This page provides an overview of some of the best of them. 8 | 9 | Certifi CA Bundle 10 | ----------------- 11 | 12 | `Certifi`_ is a carefully curated collection of Root Certificates for 13 | validating the trustworthiness of SSL certificates while verifying the 14 | identity of TLS hosts. It has been extracted from the Requests project. 15 | 16 | .. _Certifi: https://github.com/certifi/python-certifi 17 | 18 | CacheControl 19 | ------------ 20 | 21 | `CacheControl`_ is an extension that adds a full HTTP cache to Requests. This 22 | makes your web requests substantially more efficient, and should be used 23 | whenever you're making a lot of web requests. 24 | 25 | .. _CacheControl: https://cachecontrol.readthedocs.io/en/latest/ 26 | 27 | Requests-Toolbelt 28 | ----------------- 29 | 30 | `Requests-Toolbelt`_ is a collection of utilities that some users of Requests may desire, 31 | but do not belong in Requests proper. This library is actively maintained 32 | by members of the Requests core team, and reflects the functionality most 33 | requested by users within the community. 34 | 35 | .. _Requests-Toolbelt: https://toolbelt.readthedocs.io/en/latest/index.html 36 | 37 | 38 | Requests-Threads 39 | ---------------- 40 | 41 | `Requests-Threads`_ is a Requests session that returns the amazing Twisted's awaitable Deferreds instead of Response objects. This allows the use of ``async``/``await`` keyword usage on Python 3, or Twisted's style of programming, if desired. 42 | 43 | .. _Requests-Threads: https://github.com/requests/requests-threads 44 | 45 | Requests-OAuthlib 46 | ----------------- 47 | 48 | `requests-oauthlib`_ makes it possible to do the OAuth dance from Requests 49 | automatically. This is useful for the large number of websites that use OAuth 50 | to provide authentication. It also provides a lot of tweaks that handle ways 51 | that specific OAuth providers differ from the standard specifications. 52 | 53 | .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ 54 | 55 | 56 | Betamax 57 | ------- 58 | 59 | `Betamax`_ records your HTTP interactions so the NSA does not have to. 60 | A VCR imitation designed only for Python-Requests. 61 | 62 | .. _betamax: https://github.com/betamaxpy/betamax 63 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [main] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [main] 14 | schedule: 15 | - cron: '0 23 * * 0' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v2 29 | with: 30 | # We must fetch at least the immediate parents so that if this is 31 | # a pull request then we can checkout the head. 32 | fetch-depth: 2 33 | 34 | # If this run was triggered by a pull request event, then checkout 35 | # the head of the pull request instead of the merge commit. 36 | - run: git checkout HEAD^2 37 | if: ${{ github.event_name == 'pull_request' }} 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v1 42 | with: 43 | languages: "python" 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 50 | # If this step fails, then you should remove it and run the build manually (see below) 51 | - name: Autobuild 52 | uses: github/codeql-action/autobuild@v1 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v1 67 | -------------------------------------------------------------------------------- /tests/test_structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | 5 | from requests.structures import CaseInsensitiveDict, LookupDict 6 | 7 | 8 | class TestCaseInsensitiveDict: 9 | 10 | @pytest.fixture(autouse=True) 11 | def setup(self): 12 | """CaseInsensitiveDict instance with "Accept" header.""" 13 | self.case_insensitive_dict = CaseInsensitiveDict() 14 | self.case_insensitive_dict['Accept'] = 'application/json' 15 | 16 | def test_list(self): 17 | assert list(self.case_insensitive_dict) == ['Accept'] 18 | 19 | possible_keys = pytest.mark.parametrize('key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept')) 20 | 21 | @possible_keys 22 | def test_getitem(self, key): 23 | assert self.case_insensitive_dict[key] == 'application/json' 24 | 25 | @possible_keys 26 | def test_delitem(self, key): 27 | del self.case_insensitive_dict[key] 28 | assert key not in self.case_insensitive_dict 29 | 30 | def test_lower_items(self): 31 | assert list(self.case_insensitive_dict.lower_items()) == [('accept', 'application/json')] 32 | 33 | def test_repr(self): 34 | assert repr(self.case_insensitive_dict) == "{'Accept': 'application/json'}" 35 | 36 | def test_copy(self): 37 | copy = self.case_insensitive_dict.copy() 38 | assert copy is not self.case_insensitive_dict 39 | assert copy == self.case_insensitive_dict 40 | 41 | @pytest.mark.parametrize( 42 | 'other, result', ( 43 | ({'AccePT': 'application/json'}, True), 44 | ({}, False), 45 | (None, False) 46 | ) 47 | ) 48 | def test_instance_equality(self, other, result): 49 | assert (self.case_insensitive_dict == other) is result 50 | 51 | 52 | class TestLookupDict: 53 | 54 | @pytest.fixture(autouse=True) 55 | def setup(self): 56 | """LookupDict instance with "bad_gateway" attribute.""" 57 | self.lookup_dict = LookupDict('test') 58 | self.lookup_dict.bad_gateway = 502 59 | 60 | def test_repr(self): 61 | assert repr(self.lookup_dict) == "" 62 | 63 | get_item_parameters = pytest.mark.parametrize( 64 | 'key, value', ( 65 | ('bad_gateway', 502), 66 | ('not_a_key', None) 67 | ) 68 | ) 69 | 70 | @get_item_parameters 71 | def test_getitem(self, key, value): 72 | assert self.lookup_dict[key] == value 73 | 74 | @get_item_parameters 75 | def test_get(self, key, value): 76 | assert self.lookup_dict.get(key) == value 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Before opening any issues or proposing any pull requests, please read 4 | our [Contributor's Guide](https://requests.readthedocs.io/en/latest/dev/contributing/). 5 | 6 | To get the greatest chance of helpful responses, please also observe the 7 | following additional notes. 8 | 9 | ## Questions 10 | 11 | The GitHub issue tracker is for *bug reports* and *feature requests*. Please do 12 | not use it to ask questions about how to use Requests. These questions should 13 | instead be directed to [Stack Overflow](https://stackoverflow.com/). Make sure 14 | that your question is tagged with the `python-requests` tag when asking it on 15 | Stack Overflow, to ensure that it is answered promptly and accurately. 16 | 17 | ## Good Bug Reports 18 | 19 | Please be aware of the following things when filing bug reports: 20 | 21 | 1. Avoid raising duplicate issues. *Please* use the GitHub issue search feature 22 | to check whether your bug report or feature request has been mentioned in 23 | the past. Duplicate bug reports and feature requests are a huge maintenance 24 | burden on the limited resources of the project. If it is clear from your 25 | report that you would have struggled to find the original, that's ok, but 26 | if searching for a selection of words in your issue title would have found 27 | the duplicate then the issue will likely be closed extremely abruptly. 28 | 2. When filing bug reports about exceptions or tracebacks, please include the 29 | *complete* traceback. Partial tracebacks, or just the exception text, are 30 | not helpful. Issues that do not contain complete tracebacks may be closed 31 | without warning. 32 | 3. Make sure you provide a suitable amount of information to work with. This 33 | means you should provide: 34 | 35 | - Guidance on **how to reproduce the issue**. Ideally, this should be a 36 | *small* code sample that can be run immediately by the maintainers. 37 | Failing that, let us know what you're doing, how often it happens, what 38 | environment you're using, etc. Be thorough: it prevents us needing to ask 39 | further questions. 40 | - Tell us **what you expected to happen**. When we run your example code, 41 | what are we expecting to happen? What does "success" look like for your 42 | code? 43 | - Tell us **what actually happens**. It's not helpful for you to say "it 44 | doesn't work" or "it fails". Tell us *how* it fails: do you get an 45 | exception? A hang? A non-200 status code? How was the actual result 46 | different from your expected result? 47 | - Tell us **what version of Requests you're using**, and 48 | **how you installed it**. Different versions of Requests behave 49 | differently and have different bugs, and some distributors of Requests 50 | ship patches on top of the code we supply. 51 | 52 | If you do not provide all of these things, it will take us much longer to 53 | fix your problem. If we ask you to clarify these and you never respond, we 54 | will close your issue without fixing it. 55 | -------------------------------------------------------------------------------- /docs/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 |

2 | 4 |

5 | 6 | 21 | 22 | 28 | 29 |

30 | Requests is an elegant and simple HTTP library for Python, built for 31 | human beings. You are currently looking at the documentation of the 32 | development release. 33 |

34 | 35 |

Sponsored by CERT Gouvernemental - GOVCERT.LU.

36 | 37 | 40 | 41 |

Useful Links

42 | 59 | 60 |

Translations

61 | 62 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Requests 2 | 3 | **Requests** is a simple, yet elegant, HTTP library. 4 | 5 | ```python 6 | >>> import requests 7 | >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) 8 | >>> r.status_code 9 | 200 10 | >>> r.headers['content-type'] 11 | 'application/json; charset=utf8' 12 | >>> r.encoding 13 | 'utf-8' 14 | >>> r.text 15 | '{"type":"User"...' 16 | >>> r.json() 17 | {'disk_usage': 368627, 'private_gists': 484, ...} 18 | ``` 19 | 20 | Requests allows you to send HTTP/1.1 requests extremely easily. There’s no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method! 21 | 22 | Requests is one of the most downloaded Python package 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 `500,000+` repositories. You may certainly put your trust in this code. 23 | 24 | [![Downloads](https://pepy.tech/badge/requests/month)](https://pepy.tech/project/requests) 25 | [![Supported Versions](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests) 26 | [![Contributors](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors) 27 | 28 | ## Installing Requests and Supported Versions 29 | 30 | Requests is available on PyPI: 31 | 32 | ```console 33 | $ python -m pip install requests 34 | ``` 35 | 36 | Requests officially supports Python 2.7 & 3.6+. 37 | 38 | ## Supported Features & Best–Practices 39 | 40 | Requests is ready for the demands of building robust and reliable HTTP–speaking applications, for the needs of today. 41 | 42 | - Keep-Alive & Connection Pooling 43 | - International Domains and URLs 44 | - Sessions with Cookie Persistence 45 | - Browser-style TLS/SSL Verification 46 | - Basic & Digest Authentication 47 | - Familiar `dict`–like Cookies 48 | - Automatic Content Decompression and Decoding 49 | - Multi-part File Uploads 50 | - SOCKS Proxy Support 51 | - Connection Timeouts 52 | - Streaming Downloads 53 | - Automatic honoring of `.netrc` 54 | - Chunked HTTP Requests 55 | 56 | ## API Reference and User Guide available on [Read the Docs](https://requests.readthedocs.io) 57 | 58 | [![Read the Docs](https://raw.githubusercontent.com/psf/requests/main/ext/ss.png)](https://requests.readthedocs.io) 59 | 60 | ## Cloning the repository 61 | 62 | When cloning the Requests repository, you may need to add the `-c 63 | fetch.fsck.badTimezone=ignore` flag to avoid an error about a bad commit (see 64 | [this issue](https://github.com/psf/requests/issues/2690) for more background): 65 | 66 | ```shell 67 | git clone -c fetch.fsck.badTimezone=ignore https://github.com/psf/requests.git 68 | ``` 69 | 70 | You can also apply this setting to your global Git config: 71 | 72 | ```shell 73 | git config --global fetch.fsck.badTimezone ignore 74 | ``` 75 | 76 | --- 77 | 78 | [![Kenneth Reitz](https://raw.githubusercontent.com/psf/requests/main/ext/kr.png)](https://kennethreitz.org) [![Python Software Foundation](https://raw.githubusercontent.com/psf/requests/main/ext/psf.png)](https://www.python.org/psf) 79 | -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

8 | 10 |

11 | 12 | 27 | 28 | 34 | 35 |

36 | Requests is an elegant and simple HTTP library for Python, built for 37 | human beings. 38 |

39 |

Sponsored by CERT Gouvernemental - GOVCERT.LU.

40 | 41 | 44 | 45 |

Useful Links

46 | 63 | 64 | 65 |

Translations

66 | 67 | 77 | 78 |
79 |
80 | -------------------------------------------------------------------------------- /requests/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.structures 5 | ~~~~~~~~~~~~~~~~~~~ 6 | 7 | Data structures that power Requests. 8 | """ 9 | 10 | from collections import OrderedDict 11 | 12 | from .compat import Mapping, MutableMapping 13 | 14 | 15 | class CaseInsensitiveDict(MutableMapping): 16 | """A case-insensitive ``dict``-like object. 17 | 18 | Implements all methods and operations of 19 | ``MutableMapping`` as well as dict's ``copy``. Also 20 | provides ``lower_items``. 21 | 22 | All keys are expected to be strings. The structure remembers the 23 | case of the last key to be set, and ``iter(instance)``, 24 | ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` 25 | will contain case-sensitive keys. However, querying and contains 26 | testing is case insensitive:: 27 | 28 | cid = CaseInsensitiveDict() 29 | cid['Accept'] = 'application/json' 30 | cid['aCCEPT'] == 'application/json' # True 31 | list(cid) == ['Accept'] # True 32 | 33 | For example, ``headers['content-encoding']`` will return the 34 | value of a ``'Content-Encoding'`` response header, regardless 35 | of how the header name was originally stored. 36 | 37 | If the constructor, ``.update``, or equality comparison 38 | operations are given keys that have equal ``.lower()``s, the 39 | behavior is undefined. 40 | """ 41 | 42 | def __init__(self, data=None, **kwargs): 43 | self._store = OrderedDict() 44 | if data is None: 45 | data = {} 46 | self.update(data, **kwargs) 47 | 48 | def __setitem__(self, key, value): 49 | # Use the lowercased key for lookups, but store the actual 50 | # key alongside the value. 51 | self._store[key.lower()] = (key, value) 52 | 53 | def __getitem__(self, key): 54 | return self._store[key.lower()][1] 55 | 56 | def __delitem__(self, key): 57 | del self._store[key.lower()] 58 | 59 | def __iter__(self): 60 | return (casedkey for casedkey, mappedvalue in self._store.values()) 61 | 62 | def __len__(self): 63 | return len(self._store) 64 | 65 | def lower_items(self): 66 | """Like iteritems(), but with all lowercase keys.""" 67 | return ( 68 | (lowerkey, keyval[1]) 69 | for (lowerkey, keyval) 70 | in self._store.items() 71 | ) 72 | 73 | def __eq__(self, other): 74 | if isinstance(other, Mapping): 75 | other = CaseInsensitiveDict(other) 76 | else: 77 | return NotImplemented 78 | # Compare insensitively 79 | return dict(self.lower_items()) == dict(other.lower_items()) 80 | 81 | # Copy is required 82 | def copy(self): 83 | return CaseInsensitiveDict(self._store.values()) 84 | 85 | def __repr__(self): 86 | return str(dict(self.items())) 87 | 88 | 89 | class LookupDict(dict): 90 | """Dictionary lookup object.""" 91 | 92 | def __init__(self, name=None): 93 | self.name = name 94 | super(LookupDict, self).__init__() 95 | 96 | def __repr__(self): 97 | return '' % (self.name) 98 | 99 | def __getitem__(self, key): 100 | # We allow fall-through here, so values default to None 101 | 102 | return self.__dict__.get(key, None) 103 | 104 | def get(self, key, default=None): 105 | return self.__dict__.get(key, default) 106 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | body > div.document > div.sphinxsidebar > div > form > table > tbody > tr:nth-child(2) > td > select { 2 | width: 100%!important; 3 | } 4 | 5 | #python27 > a { 6 | color: white; 7 | } 8 | 9 | /* Carbon by BuySellAds */ 10 | #carbonads { 11 | display: block; 12 | overflow: hidden; 13 | margin: 1.5em 0 2em; 14 | padding: 1em; 15 | border: solid 1px #cccccc; 16 | border-radius: 2px; 17 | background-color: #eeeeee; 18 | text-align: center; 19 | line-height: 1.5; 20 | } 21 | 22 | #carbonads a { 23 | border-bottom: 0; 24 | } 25 | 26 | #carbonads span { 27 | display: block; 28 | overflow: hidden; 29 | } 30 | 31 | .carbon-img { 32 | display: block; 33 | margin: 0 auto 1em; 34 | text-align: center; 35 | } 36 | 37 | .carbon-text { 38 | display: block; 39 | margin-bottom: 1em; 40 | } 41 | 42 | .carbon-poweredby { 43 | display: block; 44 | text-transform: uppercase; 45 | letter-spacing: 1px; 46 | font-size: 10px; 47 | line-height: 1; 48 | } 49 | 50 | 51 | /* Native CPC by BuySellAds */ 52 | 53 | #native-ribbon #_custom_ { 54 | position: fixed; 55 | right: 0; 56 | bottom: 0; 57 | left: 0; 58 | box-shadow: 0 -1px 4px 1px hsla(0, 0%, 0%, .15); 59 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, 60 | Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; 61 | transition: all .25s ease-in-out; 62 | transform: translateY(calc(100% - 35px)); 63 | 64 | flex-flow: column nowrap; 65 | } 66 | 67 | #native-ribbon #_custom_:hover { 68 | transform: translateY(0); 69 | } 70 | 71 | .native-img { 72 | margin-right: 20px; 73 | max-height: 50px; 74 | border-radius: 3px; 75 | } 76 | 77 | .native-sponsor { 78 | margin: 10px 20px; 79 | text-align: center; 80 | text-transform: uppercase; 81 | letter-spacing: .5px; 82 | font-size: 12px; 83 | transition: all .3s ease-in-out; 84 | transform-origin: left; 85 | } 86 | 87 | #native-ribbon #_custom_:hover .native-sponsor { 88 | margin: 0 20px; 89 | opacity: 0; 90 | transform: scaleY(0); 91 | } 92 | 93 | .native-flex { 94 | display: flex; 95 | padding: 10px 20px 25px; 96 | text-decoration: none; 97 | 98 | flex-flow: row nowrap; 99 | justify-content: center; 100 | align-items: center; 101 | } 102 | 103 | .native-main { 104 | display: flex; 105 | 106 | flex-flow: row nowrap; 107 | align-items: center; 108 | } 109 | 110 | .native-details { 111 | display: flex; 112 | margin-right: 30px; 113 | 114 | flex-flow: column nowrap; 115 | } 116 | 117 | .native-company { 118 | margin-bottom: 4px; 119 | text-transform: uppercase; 120 | letter-spacing: 2px; 121 | font-size: 10px; 122 | } 123 | 124 | .native-desc { 125 | letter-spacing: 1px; 126 | font-weight: 300; 127 | font-size: 14px; 128 | line-height: 1.4; 129 | } 130 | 131 | .native-cta { 132 | padding: 10px 14px; 133 | border-radius: 3px; 134 | box-shadow: 0 6px 13px 0 hsla(0, 0%, 0%, .15); 135 | text-transform: uppercase; 136 | white-space: nowrap; 137 | letter-spacing: 1px; 138 | font-weight: 400; 139 | font-size: 12px; 140 | transition: all .3s ease-in-out; 141 | transform: translateY(-1px); 142 | } 143 | 144 | .native-cta:hover { 145 | box-shadow: none; 146 | transform: translateY(1px); 147 | } 148 | 149 | @media only screen and (min-width: 320px) and (max-width: 759px) { 150 | .native-flex { 151 | padding: 5px 5px 15px; 152 | flex-direction: column; 153 | 154 | flex-wrap: wrap; 155 | } 156 | 157 | .native-img { 158 | margin: 0; 159 | display: none; 160 | } 161 | 162 | .native-details { 163 | margin: 0; 164 | } 165 | 166 | .native-main { 167 | flex-direction: column; 168 | text-align: left; 169 | 170 | flex-wrap: wrap; 171 | align-content: center; 172 | } 173 | 174 | .native-cta { 175 | display: none; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /requests/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.exceptions 5 | ~~~~~~~~~~~~~~~~~~~ 6 | 7 | This module contains the set of Requests' exceptions. 8 | """ 9 | from urllib3.exceptions import HTTPError as BaseHTTPError 10 | 11 | from .compat import JSONDecodeError as CompatJSONDecodeError 12 | 13 | 14 | class RequestException(IOError): 15 | """There was an ambiguous exception that occurred while handling your 16 | request. 17 | """ 18 | 19 | def __init__(self, *args, **kwargs): 20 | """Initialize RequestException with `request` and `response` objects.""" 21 | response = kwargs.pop('response', None) 22 | self.response = response 23 | self.request = kwargs.pop('request', None) 24 | if (response is not None and not self.request and 25 | hasattr(response, 'request')): 26 | self.request = self.response.request 27 | super(RequestException, self).__init__(*args, **kwargs) 28 | 29 | 30 | class InvalidJSONError(RequestException): 31 | """A JSON error occurred.""" 32 | 33 | 34 | class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): 35 | """Couldn't decode the text into json""" 36 | 37 | 38 | class HTTPError(RequestException): 39 | """An HTTP error occurred.""" 40 | 41 | 42 | class ConnectionError(RequestException): 43 | """A Connection error occurred.""" 44 | 45 | 46 | class ProxyError(ConnectionError): 47 | """A proxy error occurred.""" 48 | 49 | 50 | class SSLError(ConnectionError): 51 | """An SSL error occurred.""" 52 | 53 | 54 | class Timeout(RequestException): 55 | """The request timed out. 56 | 57 | Catching this error will catch both 58 | :exc:`~requests.exceptions.ConnectTimeout` and 59 | :exc:`~requests.exceptions.ReadTimeout` errors. 60 | """ 61 | 62 | 63 | class ConnectTimeout(ConnectionError, Timeout): 64 | """The request timed out while trying to connect to the remote server. 65 | 66 | Requests that produced this error are safe to retry. 67 | """ 68 | 69 | 70 | class ReadTimeout(Timeout): 71 | """The server did not send any data in the allotted amount of time.""" 72 | 73 | 74 | class URLRequired(RequestException): 75 | """A valid URL is required to make a request.""" 76 | 77 | 78 | class TooManyRedirects(RequestException): 79 | """Too many redirects.""" 80 | 81 | 82 | class MissingSchema(RequestException, ValueError): 83 | """The URL schema (e.g. http or https) is missing.""" 84 | 85 | 86 | class InvalidSchema(RequestException, ValueError): 87 | """See defaults.py for valid schemas.""" 88 | 89 | 90 | class InvalidURL(RequestException, ValueError): 91 | """The URL provided was somehow invalid.""" 92 | 93 | 94 | class InvalidHeader(RequestException, ValueError): 95 | """The header value provided was somehow invalid.""" 96 | 97 | 98 | class InvalidProxyURL(InvalidURL): 99 | """The proxy URL provided is invalid.""" 100 | 101 | 102 | class ChunkedEncodingError(RequestException): 103 | """The server declared chunked encoding but sent an invalid chunk.""" 104 | 105 | 106 | class ContentDecodingError(RequestException, BaseHTTPError): 107 | """Failed to decode response content.""" 108 | 109 | 110 | class StreamConsumedError(RequestException, TypeError): 111 | """The content for this response was already consumed.""" 112 | 113 | 114 | class RetryError(RequestException): 115 | """Custom retries logic failed""" 116 | 117 | 118 | class UnrewindableBodyError(RequestException): 119 | """Requests encountered an error when trying to rewind a body.""" 120 | 121 | # Warnings 122 | 123 | 124 | class RequestsWarning(Warning): 125 | """Base warning for Requests.""" 126 | 127 | 128 | class FileModeWarning(RequestsWarning, DeprecationWarning): 129 | """A file was opened in text mode, but Requests determined its binary length.""" 130 | 131 | 132 | class RequestsDependencyWarning(RequestsWarning): 133 | """An imported dependency doesn't match the expected version range.""" 134 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Learn more: https://github.com/kennethreitz/setup.py 3 | import os 4 | import sys 5 | 6 | from codecs import open 7 | 8 | from setuptools import setup 9 | from setuptools.command.test import test as TestCommand 10 | 11 | here = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | class PyTest(TestCommand): 14 | user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] 15 | 16 | def initialize_options(self): 17 | TestCommand.initialize_options(self) 18 | try: 19 | from multiprocessing import cpu_count 20 | self.pytest_args = ['-n', str(cpu_count()), '--boxed'] 21 | except (ImportError, NotImplementedError): 22 | self.pytest_args = ['-n', '1', '--boxed'] 23 | 24 | def finalize_options(self): 25 | TestCommand.finalize_options(self) 26 | self.test_args = [] 27 | self.test_suite = True 28 | 29 | def run_tests(self): 30 | import pytest 31 | 32 | errno = pytest.main(self.pytest_args) 33 | sys.exit(errno) 34 | 35 | # 'setup.py publish' shortcut. 36 | if sys.argv[-1] == 'publish': 37 | os.system('python setup.py sdist bdist_wheel') 38 | os.system('twine upload dist/*') 39 | sys.exit() 40 | 41 | packages = ['requests'] 42 | 43 | requires = [ 44 | 'charset_normalizer~=2.0.0; python_version >= "3"', 45 | 'chardet>=3.0.2,<5; python_version < "3"', 46 | 'idna>=2.5,<3; python_version < "3"', 47 | 'idna>=2.5,<4; python_version >= "3"', 48 | 'urllib3>=1.21.1,<1.27', 49 | 'certifi>=2017.4.17' 50 | 51 | ] 52 | test_requirements = [ 53 | 'pytest-httpbin==0.0.7', 54 | 'pytest-cov', 55 | 'pytest-mock', 56 | 'pytest-xdist', 57 | 'PySocks>=1.5.6, !=1.5.7', 58 | 'pytest>=3' 59 | ] 60 | 61 | about = {} 62 | with open(os.path.join(here, 'requests', '__version__.py'), 'r', 'utf-8') as f: 63 | exec(f.read(), about) 64 | 65 | with open('README.md', 'r', 'utf-8') as f: 66 | readme = f.read() 67 | 68 | setup( 69 | name=about['__title__'], 70 | version=about['__version__'], 71 | description=about['__description__'], 72 | long_description=readme, 73 | long_description_content_type='text/markdown', 74 | author=about['__author__'], 75 | author_email=about['__author_email__'], 76 | url=about['__url__'], 77 | packages=packages, 78 | package_data={'': ['LICENSE', 'NOTICE']}, 79 | package_dir={'requests': 'requests'}, 80 | include_package_data=True, 81 | python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*", 82 | install_requires=requires, 83 | license=about['__license__'], 84 | zip_safe=False, 85 | classifiers=[ 86 | 'Development Status :: 5 - Production/Stable', 87 | 'Intended Audience :: Developers', 88 | 'Natural Language :: English', 89 | 'License :: OSI Approved :: Apache Software License', 90 | 'Programming Language :: Python', 91 | 'Programming Language :: Python :: 2', 92 | 'Programming Language :: Python :: 2.7', 93 | 'Programming Language :: Python :: 3', 94 | 'Programming Language :: Python :: 3.6', 95 | 'Programming Language :: Python :: 3.7', 96 | 'Programming Language :: Python :: 3.8', 97 | 'Programming Language :: Python :: 3.9', 98 | 'Programming Language :: Python :: 3.10', 99 | 'Programming Language :: Python :: Implementation :: CPython', 100 | 'Programming Language :: Python :: Implementation :: PyPy' 101 | ], 102 | cmdclass={'test': PyTest}, 103 | tests_require=test_requirements, 104 | extras_require={ 105 | 'security': [], 106 | 'socks': ['PySocks>=1.5.6, !=1.5.7'], 107 | 'socks:sys_platform == "win32" and python_version == "2.7"': ['win_inet_pton'], 108 | 'use_chardet_on_py3': ['chardet>=3.0.2,<5'] 109 | }, 110 | project_urls={ 111 | 'Documentation': 'https://requests.readthedocs.io', 112 | 'Source': 'https://github.com/psf/requests', 113 | }, 114 | ) 115 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Requests documentation master file, created by 2 | sphinx-quickstart on Sun Feb 13 23:54:25 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Requests: HTTP for Humans™ 7 | ========================== 8 | 9 | Release v\ |version|. (:ref:`Installation `) 10 | 11 | 12 | .. image:: https://pepy.tech/badge/requests/month 13 | :target: https://pepy.tech/project/requests 14 | :alt: Requests Downloads Per Month Badge 15 | 16 | .. image:: https://img.shields.io/pypi/l/requests.svg 17 | :target: https://pypi.org/project/requests/ 18 | :alt: License Badge 19 | 20 | .. image:: https://img.shields.io/pypi/wheel/requests.svg 21 | :target: https://pypi.org/project/requests/ 22 | :alt: Wheel Support Badge 23 | 24 | .. image:: https://img.shields.io/pypi/pyversions/requests.svg 25 | :target: https://pypi.org/project/requests/ 26 | :alt: Python Version Support Badge 27 | 28 | **Requests** is an elegant and simple HTTP library for Python, built for human beings. 29 | 30 | ------------------- 31 | 32 | **Behold, the power of Requests**:: 33 | 34 | >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) 35 | >>> r.status_code 36 | 200 37 | >>> r.headers['content-type'] 38 | 'application/json; charset=utf8' 39 | >>> r.encoding 40 | 'utf-8' 41 | >>> r.text 42 | '{"type":"User"...' 43 | >>> r.json() 44 | {'private_gists': 419, 'total_private_repos': 77, ...} 45 | 46 | See `similar code, sans Requests `_. 47 | 48 | 49 | **Requests** allows you to send HTTP/1.1 requests extremely easily. 50 | There's no need to manually add query strings to your 51 | URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling 52 | are 100% automatic, thanks to `urllib3 `_. 53 | 54 | Beloved Features 55 | ---------------- 56 | 57 | Requests is ready for today's web. 58 | 59 | - Keep-Alive & Connection Pooling 60 | - International Domains and URLs 61 | - Sessions with Cookie Persistence 62 | - Browser-style SSL Verification 63 | - Automatic Content Decoding 64 | - Basic/Digest Authentication 65 | - Elegant Key/Value Cookies 66 | - Automatic Decompression 67 | - Unicode Response Bodies 68 | - HTTP(S) Proxy Support 69 | - Multipart File Uploads 70 | - Streaming Downloads 71 | - Connection Timeouts 72 | - Chunked Requests 73 | - ``.netrc`` Support 74 | 75 | Requests officially supports Python 2.7 & 3.6+, and runs great on PyPy. 76 | 77 | 78 | The User Guide 79 | -------------- 80 | 81 | This part of the documentation, which is mostly prose, begins with some 82 | background information about Requests, then focuses on step-by-step 83 | instructions for getting the most out of Requests. 84 | 85 | .. toctree:: 86 | :maxdepth: 2 87 | 88 | user/install 89 | user/quickstart 90 | user/advanced 91 | user/authentication 92 | 93 | 94 | The Community Guide 95 | ------------------- 96 | 97 | This part of the documentation, which is mostly prose, details the 98 | Requests ecosystem and community. 99 | 100 | .. toctree:: 101 | :maxdepth: 2 102 | 103 | community/recommended 104 | community/faq 105 | community/out-there 106 | community/support 107 | community/vulnerabilities 108 | community/release-process 109 | 110 | .. toctree:: 111 | :maxdepth: 1 112 | 113 | community/updates 114 | 115 | The API Documentation / Guide 116 | ----------------------------- 117 | 118 | If you are looking for information on a specific function, class, or method, 119 | this part of the documentation is for you. 120 | 121 | .. toctree:: 122 | :maxdepth: 2 123 | 124 | api 125 | 126 | 127 | The Contributor Guide 128 | --------------------- 129 | 130 | If you want to contribute to the project, this part of the documentation is for 131 | you. 132 | 133 | .. toctree:: 134 | :maxdepth: 3 135 | 136 | dev/contributing 137 | dev/authors 138 | 139 | There are no more guides. You are now guideless. 140 | Good luck. 141 | -------------------------------------------------------------------------------- /docs/community/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | Frequently Asked Questions 4 | ========================== 5 | 6 | This part of the documentation answers common questions about Requests. 7 | 8 | Encoded Data? 9 | ------------- 10 | 11 | Requests automatically decompresses gzip-encoded responses, and does 12 | its best to decode response content to unicode when possible. 13 | 14 | When either the `brotli `_ or `brotlicffi `_ 15 | package is installed, requests also decodes Brotli-encoded responses. 16 | 17 | You can get direct access to the raw response (and even the socket), 18 | if needed as well. 19 | 20 | 21 | Custom User-Agents? 22 | ------------------- 23 | 24 | Requests allows you to easily override User-Agent strings, along with 25 | any other HTTP Header. See `documentation about headers `_. 26 | 27 | 28 | 29 | Why not Httplib2? 30 | ----------------- 31 | 32 | Chris Adams gave an excellent summary on 33 | `Hacker News `_: 34 | 35 | httplib2 is part of why you should use requests: it's far more respectable 36 | as a client but not as well documented and it still takes way too much code 37 | for basic operations. I appreciate what httplib2 is trying to do, that 38 | there's a ton of hard low-level annoyances in building a modern HTTP 39 | client, but really, just use requests instead. Kenneth Reitz is very 40 | motivated and he gets the degree to which simple things should be simple 41 | whereas httplib2 feels more like an academic exercise than something 42 | people should use to build production systems[1]. 43 | 44 | Disclosure: I'm listed in the requests AUTHORS file but can claim credit 45 | for, oh, about 0.0001% of the awesomeness. 46 | 47 | 1. http://code.google.com/p/httplib2/issues/detail?id=96 is a good example: 48 | an annoying bug which affect many people, there was a fix available for 49 | months, which worked great when I applied it in a fork and pounded a couple 50 | TB of data through it, but it took over a year to make it into trunk and 51 | even longer to make it onto PyPI where any other project which required " 52 | httplib2" would get the working version. 53 | 54 | 55 | Python 3 Support? 56 | ----------------- 57 | 58 | Yes! Requests officially supports Python 2.7 & 3.6+ and PyPy. 59 | 60 | Python 2 Support? 61 | ----------------- 62 | 63 | Yes! We do not have immediate plans to `sunset 64 | `_ our support for Python 65 | 2.7. We understand that we have a large user base with varying needs. 66 | 67 | That said, it is *highly* recommended users migrate to Python 3.6+ since Python 68 | 2.7 is no longer receiving bug fixes or security updates as of January 1, 2020. 69 | 70 | What are "hostname doesn't match" errors? 71 | ----------------------------------------- 72 | 73 | These errors occur when :ref:`SSL certificate verification ` 74 | fails to match the certificate the server responds with to the hostname 75 | Requests thinks it's contacting. If you're certain the server's SSL setup is 76 | correct (for example, because you can visit the site with your browser) and 77 | you're using Python 2.7, a possible explanation is that you need 78 | Server-Name-Indication. 79 | 80 | `Server-Name-Indication`_, or SNI, is an official extension to SSL where the 81 | client tells the server what hostname it is contacting. This is important 82 | when servers are using `Virtual Hosting`_. When such servers are hosting 83 | more than one SSL site they need to be able to return the appropriate 84 | certificate based on the hostname the client is connecting to. 85 | 86 | Python3 and Python 2.7.9+ include native support for SNI in their SSL modules. 87 | For information on using SNI with Requests on Python < 2.7.9 refer to this 88 | `Stack Overflow answer`_. 89 | 90 | .. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication 91 | .. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting 92 | .. _`Stack Overflow answer`: https://stackoverflow.com/questions/18578439/using-requests-with-tls-doesnt-give-sni-support/18579484#18579484 93 | -------------------------------------------------------------------------------- /tests/testserver/server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import threading 4 | import socket 5 | import select 6 | 7 | 8 | def consume_socket_content(sock, timeout=0.5): 9 | chunks = 65536 10 | content = b'' 11 | 12 | while True: 13 | more_to_read = select.select([sock], [], [], timeout)[0] 14 | if not more_to_read: 15 | break 16 | 17 | new_content = sock.recv(chunks) 18 | if not new_content: 19 | break 20 | 21 | content += new_content 22 | 23 | return content 24 | 25 | 26 | class Server(threading.Thread): 27 | """Dummy server using for unit testing""" 28 | WAIT_EVENT_TIMEOUT = 5 29 | 30 | def __init__(self, handler=None, host='localhost', port=0, requests_to_handle=1, wait_to_close_event=None): 31 | super(Server, self).__init__() 32 | 33 | self.handler = handler or consume_socket_content 34 | self.handler_results = [] 35 | 36 | self.host = host 37 | self.port = port 38 | self.requests_to_handle = requests_to_handle 39 | 40 | self.wait_to_close_event = wait_to_close_event 41 | self.ready_event = threading.Event() 42 | self.stop_event = threading.Event() 43 | 44 | @classmethod 45 | def text_response_server(cls, text, request_timeout=0.5, **kwargs): 46 | def text_response_handler(sock): 47 | request_content = consume_socket_content(sock, timeout=request_timeout) 48 | sock.send(text.encode('utf-8')) 49 | 50 | return request_content 51 | 52 | 53 | return Server(text_response_handler, **kwargs) 54 | 55 | @classmethod 56 | def basic_response_server(cls, **kwargs): 57 | return cls.text_response_server( 58 | "HTTP/1.1 200 OK\r\n" + 59 | "Content-Length: 0\r\n\r\n", 60 | **kwargs 61 | ) 62 | 63 | def run(self): 64 | try: 65 | self.server_sock = self._create_socket_and_bind() 66 | # in case self.port = 0 67 | self.port = self.server_sock.getsockname()[1] 68 | self.ready_event.set() 69 | self._handle_requests() 70 | 71 | if self.wait_to_close_event: 72 | self.wait_to_close_event.wait(self.WAIT_EVENT_TIMEOUT) 73 | finally: 74 | self.ready_event.set() # just in case of exception 75 | self._close_server_sock_ignore_errors() 76 | self.stop_event.set() 77 | 78 | def _create_socket_and_bind(self): 79 | sock = socket.socket() 80 | sock.bind((self.host, self.port)) 81 | # NB: when Python 2.7 is no longer supported, the argument 82 | # can be removed to use a default backlog size 83 | sock.listen(5) 84 | return sock 85 | 86 | def _close_server_sock_ignore_errors(self): 87 | try: 88 | self.server_sock.close() 89 | except IOError: 90 | pass 91 | 92 | def _handle_requests(self): 93 | for _ in range(self.requests_to_handle): 94 | sock = self._accept_connection() 95 | if not sock: 96 | break 97 | 98 | handler_result = self.handler(sock) 99 | 100 | self.handler_results.append(handler_result) 101 | sock.close() 102 | 103 | def _accept_connection(self): 104 | try: 105 | ready, _, _ = select.select([self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT) 106 | if not ready: 107 | return None 108 | 109 | return self.server_sock.accept()[0] 110 | except (select.error, socket.error): 111 | return None 112 | 113 | def __enter__(self): 114 | self.start() 115 | self.ready_event.wait(self.WAIT_EVENT_TIMEOUT) 116 | return self.host, self.port 117 | 118 | def __exit__(self, exc_type, exc_value, traceback): 119 | if exc_type is None: 120 | self.stop_event.wait(self.WAIT_EVENT_TIMEOUT) 121 | else: 122 | if self.wait_to_close_event: 123 | # avoid server from waiting for event timeouts 124 | # if an exception is found in the main thread 125 | self.wait_to_close_event.set() 126 | 127 | # ensure server thread doesn't get stuck waiting for connections 128 | self._close_server_sock_ignore_errors() 129 | self.join() 130 | return False # allow exceptions to propagate 131 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Vulnerability Disclosure 2 | 3 | If you think you have found a potential security vulnerability in 4 | requests, please email [Nate](mailto:nate.prewitt@gmail.com) 5 | and [Seth](mailto:sethmichaellarson@gmail.com) directly. 6 | **Do not file a public issue.** 7 | 8 | Our PGP Key fingerprints are: 9 | 10 | - 8722 7E29 AD9C FF5C FAC3 EA6A 44D3 FF97 B80D C864 ([@nateprewitt](https://keybase.io/nateprewitt)) 11 | 12 | - EDD5 6765 A9D8 4653 CBC8 A134 51B0 6736 1740 F5FC ([@sethmlarson](https://keybase.io/sethmlarson)) 13 | 14 | You can also contact us on [Keybase](https://keybase.io) with the 15 | profiles above if desired. 16 | 17 | If English is not your first language, please try to describe the 18 | problem and its impact to the best of your ability. For greater detail, 19 | please use your native language and we will try our best to translate it 20 | using online services. 21 | 22 | Please also include the code you used to find the problem and the 23 | shortest amount of code necessary to reproduce it. 24 | 25 | Please do not disclose this to anyone else. We will retrieve a CVE 26 | identifier if necessary and give you full credit under whatever name or 27 | alias you provide. We will only request an identifier when we have a fix 28 | and can publish it in a release. 29 | 30 | We will respect your privacy and will only publicize your involvement if 31 | you grant us permission. 32 | 33 | ## Process 34 | 35 | This following information discusses the process the requests project 36 | follows in response to vulnerability disclosures. If you are disclosing 37 | a vulnerability, this section of the documentation lets you know how we 38 | will respond to your disclosure. 39 | 40 | ### Timeline 41 | 42 | When you report an issue, one of the project members will respond to you 43 | within two days *at the outside*. In most cases responses will be 44 | faster, usually within 12 hours. This initial response will at the very 45 | least confirm receipt of the report. 46 | 47 | If we were able to rapidly reproduce the issue, the initial response 48 | will also contain confirmation of the issue. If we are not, we will 49 | often ask for more information about the reproduction scenario. 50 | 51 | Our goal is to have a fix for any vulnerability released within two 52 | weeks of the initial disclosure. This may potentially involve shipping 53 | an interim release that simply disables function while a more mature fix 54 | can be prepared, but will in the vast majority of cases mean shipping a 55 | complete release as soon as possible. 56 | 57 | Throughout the fix process we will keep you up to speed with how the fix 58 | is progressing. Once the fix is prepared, we will notify you that we 59 | believe we have a fix. Often we will ask you to confirm the fix resolves 60 | the problem in your environment, especially if we are not confident of 61 | our reproduction scenario. 62 | 63 | At this point, we will prepare for the release. We will obtain a CVE 64 | number if one is required, providing you with full credit for the 65 | discovery. We will also decide on a planned release date, and let you 66 | know when it is. This release date will *always* be on a weekday. 67 | 68 | At this point we will reach out to our major downstream packagers to 69 | notify them of an impending security-related patch so they can make 70 | arrangements. In addition, these packagers will be provided with the 71 | intended patch ahead of time, to ensure that they are able to promptly 72 | release their downstream packages. Currently the list of people we 73 | actively contact *ahead of a public release* is: 74 | 75 | - Jeremy Cline, Red Hat (@jeremycline) 76 | - Daniele Tricoli, Debian (@eriol) 77 | 78 | We will notify these individuals at least a week ahead of our planned 79 | release date to ensure that they have sufficient time to prepare. If you 80 | believe you should be on this list, please let one of the maintainers 81 | know at one of the email addresses at the top of this article. 82 | 83 | On release day, we will push the patch to our public repository, along 84 | with an updated changelog that describes the issue and credits you. We 85 | will then issue a PyPI release containing the patch. 86 | 87 | At this point, we will publicise the release. This will involve mails to 88 | mailing lists, Tweets, and all other communication mechanisms available 89 | to the core team. 90 | 91 | We will also explicitly mention which commits contain the fix to make it 92 | easier for other distributors and users to easily patch their own 93 | versions of requests if upgrading is not an option. 94 | -------------------------------------------------------------------------------- /requests/help.py: -------------------------------------------------------------------------------- 1 | """Module containing bug report helper(s).""" 2 | from __future__ import print_function 3 | 4 | import json 5 | import platform 6 | import sys 7 | import ssl 8 | 9 | import idna 10 | import urllib3 11 | 12 | from . import __version__ as requests_version 13 | 14 | try: 15 | import charset_normalizer 16 | except ImportError: 17 | charset_normalizer = None 18 | 19 | try: 20 | import chardet 21 | except ImportError: 22 | chardet = None 23 | 24 | try: 25 | from urllib3.contrib import pyopenssl 26 | except ImportError: 27 | pyopenssl = None 28 | OpenSSL = None 29 | cryptography = None 30 | else: 31 | import OpenSSL 32 | import cryptography 33 | 34 | 35 | def _implementation(): 36 | """Return a dict with the Python implementation and version. 37 | 38 | Provide both the name and the version of the Python implementation 39 | currently running. For example, on CPython 2.7.5 it will return 40 | {'name': 'CPython', 'version': '2.7.5'}. 41 | 42 | This function works best on CPython and PyPy: in particular, it probably 43 | doesn't work for Jython or IronPython. Future investigation should be done 44 | to work out the correct shape of the code for those platforms. 45 | """ 46 | implementation = platform.python_implementation() 47 | 48 | if implementation == 'CPython': 49 | implementation_version = platform.python_version() 50 | elif implementation == 'PyPy': 51 | implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, 52 | sys.pypy_version_info.minor, 53 | sys.pypy_version_info.micro) 54 | if sys.pypy_version_info.releaselevel != 'final': 55 | implementation_version = ''.join([ 56 | implementation_version, sys.pypy_version_info.releaselevel 57 | ]) 58 | elif implementation == 'Jython': 59 | implementation_version = platform.python_version() # Complete Guess 60 | elif implementation == 'IronPython': 61 | implementation_version = platform.python_version() # Complete Guess 62 | else: 63 | implementation_version = 'Unknown' 64 | 65 | return {'name': implementation, 'version': implementation_version} 66 | 67 | 68 | def info(): 69 | """Generate information for a bug report.""" 70 | try: 71 | platform_info = { 72 | 'system': platform.system(), 73 | 'release': platform.release(), 74 | } 75 | except IOError: 76 | platform_info = { 77 | 'system': 'Unknown', 78 | 'release': 'Unknown', 79 | } 80 | 81 | implementation_info = _implementation() 82 | urllib3_info = {'version': urllib3.__version__} 83 | charset_normalizer_info = {'version': None} 84 | chardet_info = {'version': None} 85 | if charset_normalizer: 86 | charset_normalizer_info = {'version': charset_normalizer.__version__} 87 | if chardet: 88 | chardet_info = {'version': chardet.__version__} 89 | 90 | pyopenssl_info = { 91 | 'version': None, 92 | 'openssl_version': '', 93 | } 94 | if OpenSSL: 95 | pyopenssl_info = { 96 | 'version': OpenSSL.__version__, 97 | 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, 98 | } 99 | cryptography_info = { 100 | 'version': getattr(cryptography, '__version__', ''), 101 | } 102 | idna_info = { 103 | 'version': getattr(idna, '__version__', ''), 104 | } 105 | 106 | system_ssl = ssl.OPENSSL_VERSION_NUMBER 107 | system_ssl_info = { 108 | 'version': '%x' % system_ssl if system_ssl is not None else '' 109 | } 110 | 111 | return { 112 | 'platform': platform_info, 113 | 'implementation': implementation_info, 114 | 'system_ssl': system_ssl_info, 115 | 'using_pyopenssl': pyopenssl is not None, 116 | 'using_charset_normalizer': chardet is None, 117 | 'pyOpenSSL': pyopenssl_info, 118 | 'urllib3': urllib3_info, 119 | 'chardet': chardet_info, 120 | 'charset_normalizer': charset_normalizer_info, 121 | 'cryptography': cryptography_info, 122 | 'idna': idna_info, 123 | 'requests': { 124 | 'version': requests_version, 125 | }, 126 | } 127 | 128 | 129 | def main(): 130 | """Pretty-print the bug information as JSON.""" 131 | print(json.dumps(info(), sort_keys=True, indent=2)) 132 | 133 | 134 | if __name__ == '__main__': 135 | main() 136 | -------------------------------------------------------------------------------- /requests/status_codes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | r""" 4 | The ``codes`` object defines a mapping from common names for HTTP statuses 5 | to their numerical codes, accessible either as attributes or as dictionary 6 | items. 7 | 8 | Example:: 9 | 10 | >>> import requests 11 | >>> requests.codes['temporary_redirect'] 12 | 307 13 | >>> requests.codes.teapot 14 | 418 15 | >>> requests.codes['\o/'] 16 | 200 17 | 18 | Some codes have multiple names, and both upper- and lower-case versions of 19 | the names are allowed. For example, ``codes.ok``, ``codes.OK``, and 20 | ``codes.okay`` all correspond to the HTTP status code 200. 21 | """ 22 | 23 | from .structures import LookupDict 24 | 25 | _codes = { 26 | 27 | # Informational. 28 | 100: ('continue',), 29 | 101: ('switching_protocols',), 30 | 102: ('processing',), 31 | 103: ('checkpoint',), 32 | 122: ('uri_too_long', 'request_uri_too_long'), 33 | 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), 34 | 201: ('created',), 35 | 202: ('accepted',), 36 | 203: ('non_authoritative_info', 'non_authoritative_information'), 37 | 204: ('no_content',), 38 | 205: ('reset_content', 'reset'), 39 | 206: ('partial_content', 'partial'), 40 | 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), 41 | 208: ('already_reported',), 42 | 226: ('im_used',), 43 | 44 | # Redirection. 45 | 300: ('multiple_choices',), 46 | 301: ('moved_permanently', 'moved', '\\o-'), 47 | 302: ('found',), 48 | 303: ('see_other', 'other'), 49 | 304: ('not_modified',), 50 | 305: ('use_proxy',), 51 | 306: ('switch_proxy',), 52 | 307: ('temporary_redirect', 'temporary_moved', 'temporary'), 53 | 308: ('permanent_redirect', 54 | 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 55 | 56 | # Client Error. 57 | 400: ('bad_request', 'bad'), 58 | 401: ('unauthorized',), 59 | 402: ('payment_required', 'payment'), 60 | 403: ('forbidden',), 61 | 404: ('not_found', '-o-'), 62 | 405: ('method_not_allowed', 'not_allowed'), 63 | 406: ('not_acceptable',), 64 | 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), 65 | 408: ('request_timeout', 'timeout'), 66 | 409: ('conflict',), 67 | 410: ('gone',), 68 | 411: ('length_required',), 69 | 412: ('precondition_failed', 'precondition'), 70 | 413: ('request_entity_too_large',), 71 | 414: ('request_uri_too_large',), 72 | 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), 73 | 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), 74 | 417: ('expectation_failed',), 75 | 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), 76 | 421: ('misdirected_request',), 77 | 422: ('unprocessable_entity', 'unprocessable'), 78 | 423: ('locked',), 79 | 424: ('failed_dependency', 'dependency'), 80 | 425: ('unordered_collection', 'unordered'), 81 | 426: ('upgrade_required', 'upgrade'), 82 | 428: ('precondition_required', 'precondition'), 83 | 429: ('too_many_requests', 'too_many'), 84 | 431: ('header_fields_too_large', 'fields_too_large'), 85 | 444: ('no_response', 'none'), 86 | 449: ('retry_with', 'retry'), 87 | 450: ('blocked_by_windows_parental_controls', 'parental_controls'), 88 | 451: ('unavailable_for_legal_reasons', 'legal_reasons'), 89 | 499: ('client_closed_request',), 90 | 91 | # Server Error. 92 | 500: ('internal_server_error', 'server_error', '/o\\', '✗'), 93 | 501: ('not_implemented',), 94 | 502: ('bad_gateway',), 95 | 503: ('service_unavailable', 'unavailable'), 96 | 504: ('gateway_timeout',), 97 | 505: ('http_version_not_supported', 'http_version'), 98 | 506: ('variant_also_negotiates',), 99 | 507: ('insufficient_storage',), 100 | 509: ('bandwidth_limit_exceeded', 'bandwidth'), 101 | 510: ('not_extended',), 102 | 511: ('network_authentication_required', 'network_auth', 'network_authentication'), 103 | } 104 | 105 | codes = LookupDict(name='status_codes') 106 | 107 | def _init(): 108 | for code, titles in _codes.items(): 109 | for title in titles: 110 | setattr(codes, title, code) 111 | if not title.startswith(('\\', '/')): 112 | setattr(codes, title.upper(), code) 113 | 114 | def doc(code): 115 | names = ', '.join('``%s``' % n for n in _codes[code]) 116 | return '* %d: %s' % (code, names) 117 | 118 | global __doc__ 119 | __doc__ = (__doc__ + '\n' + 120 | '\n'.join(doc(code) for code in sorted(_codes)) 121 | if __doc__ is not None else None) 122 | 123 | _init() 124 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/community/vulnerabilities.rst: -------------------------------------------------------------------------------- 1 | Vulnerability Disclosure 2 | ======================== 3 | 4 | If you think you have found a potential security vulnerability in requests, 5 | please email `Nate `_ and `Seth `_ directly. **Do not file a public issue.** 6 | 7 | Our PGP Key fingerprints are: 8 | 9 | - 8722 7E29 AD9C FF5C FAC3 EA6A 44D3 FF97 B80D C864 (`@nateprewitt `_) 10 | 11 | - EDD5 6765 A9D8 4653 CBC8 A134 51B0 6736 1740 F5FC (`@sethmlarson `_) 12 | 13 | You can also contact us on `Keybase `_ with the 14 | profiles above if desired. 15 | 16 | If English is not your first language, please try to describe the problem and 17 | its impact to the best of your ability. For greater detail, please use your 18 | native language and we will try our best to translate it using online services. 19 | 20 | Please also include the code you used to find the problem and the shortest 21 | amount of code necessary to reproduce it. 22 | 23 | Please do not disclose this to anyone else. We will retrieve a CVE identifier 24 | if necessary and give you full credit under whatever name or alias you provide. 25 | We will only request an identifier when we have a fix and can publish it in a 26 | release. 27 | 28 | We will respect your privacy and will only publicize your involvement if you 29 | grant us permission. 30 | 31 | Process 32 | ------- 33 | 34 | This following information discusses the process the requests project follows 35 | in response to vulnerability disclosures. If you are disclosing a 36 | vulnerability, this section of the documentation lets you know how we will 37 | respond to your disclosure. 38 | 39 | Timeline 40 | ~~~~~~~~ 41 | 42 | When you report an issue, one of the project members will respond to you within 43 | two days *at the outside*. In most cases responses will be faster, usually 44 | within 12 hours. This initial response will at the very least confirm receipt 45 | of the report. 46 | 47 | If we were able to rapidly reproduce the issue, the initial response will also 48 | contain confirmation of the issue. If we are not, we will often ask for more 49 | information about the reproduction scenario. 50 | 51 | Our goal is to have a fix for any vulnerability released within two weeks of 52 | the initial disclosure. This may potentially involve shipping an interim 53 | release that simply disables function while a more mature fix can be prepared, 54 | but will in the vast majority of cases mean shipping a complete release as soon 55 | as possible. 56 | 57 | Throughout the fix process we will keep you up to speed with how the fix is 58 | progressing. Once the fix is prepared, we will notify you that we believe we 59 | have a fix. Often we will ask you to confirm the fix resolves the problem in 60 | your environment, especially if we are not confident of our reproduction 61 | scenario. 62 | 63 | At this point, we will prepare for the release. We will obtain a CVE number 64 | if one is required, providing you with full credit for the discovery. We will 65 | also decide on a planned release date, and let you know when it is. This 66 | release date will *always* be on a weekday. 67 | 68 | At this point we will reach out to our major downstream packagers to notify 69 | them of an impending security-related patch so they can make arrangements. In 70 | addition, these packagers will be provided with the intended patch ahead of 71 | time, to ensure that they are able to promptly release their downstream 72 | packages. Currently the list of people we actively contact *ahead of a public 73 | release* is: 74 | 75 | - Python Maintenance Team, Red Hat (python-maint@redhat.com) 76 | - Daniele Tricoli, Debian (@eriol) 77 | 78 | We will notify these individuals at least a week ahead of our planned release 79 | date to ensure that they have sufficient time to prepare. If you believe you 80 | should be on this list, please let one of the maintainers know at one of the 81 | email addresses at the top of this article. 82 | 83 | On release day, we will push the patch to our public repository, along with an 84 | updated changelog that describes the issue and credits you. We will then issue 85 | a PyPI release containing the patch. 86 | 87 | At this point, we will publicise the release. This will involve mails to 88 | mailing lists, Tweets, and all other communication mechanisms available to the 89 | core team. 90 | 91 | We will also explicitly mention which commits contain the fix to make it easier 92 | for other distributors and users to easily patch their own versions of requests 93 | if upgrading is not an option. 94 | 95 | Previous CVEs 96 | ------------- 97 | 98 | - Fixed in 2.20.0 99 | - `CVE 2018-18074 `_ 100 | 101 | - Fixed in 2.6.0 102 | 103 | - `CVE 2015-2296 `_, 104 | reported by Matthew Daley of `BugFuzz `_. 105 | 106 | - Fixed in 2.3.0 107 | 108 | - `CVE 2014-1829 `_ 109 | 110 | - `CVE 2014-1830 `_ 111 | -------------------------------------------------------------------------------- /requests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # __ 4 | # /__) _ _ _ _ _/ _ 5 | # / ( (- (/ (/ (- _) / _) 6 | # / 7 | 8 | """ 9 | Requests HTTP Library 10 | ~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | Requests is an HTTP library, written in Python, for human beings. 13 | Basic GET usage: 14 | 15 | >>> import requests 16 | >>> r = requests.get('https://www.python.org') 17 | >>> r.status_code 18 | 200 19 | >>> b'Python is a programming language' in r.content 20 | True 21 | 22 | ... or POST: 23 | 24 | >>> payload = dict(key1='value1', key2='value2') 25 | >>> r = requests.post('https://httpbin.org/post', data=payload) 26 | >>> print(r.text) 27 | { 28 | ... 29 | "form": { 30 | "key1": "value1", 31 | "key2": "value2" 32 | }, 33 | ... 34 | } 35 | 36 | The other HTTP methods are supported - see `requests.api`. Full documentation 37 | is at . 38 | 39 | :copyright: (c) 2017 by Kenneth Reitz. 40 | :license: Apache 2.0, see LICENSE for more details. 41 | """ 42 | 43 | import urllib3 44 | import warnings 45 | from .exceptions import RequestsDependencyWarning 46 | 47 | try: 48 | from charset_normalizer import __version__ as charset_normalizer_version 49 | except ImportError: 50 | charset_normalizer_version = None 51 | 52 | try: 53 | from chardet import __version__ as chardet_version 54 | except ImportError: 55 | chardet_version = None 56 | 57 | def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): 58 | urllib3_version = urllib3_version.split('.') 59 | assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. 60 | 61 | # Sometimes, urllib3 only reports its version as 16.1. 62 | if len(urllib3_version) == 2: 63 | urllib3_version.append('0') 64 | 65 | # Check urllib3 for compatibility. 66 | major, minor, patch = urllib3_version # noqa: F811 67 | major, minor, patch = int(major), int(minor), int(patch) 68 | # urllib3 >= 1.21.1, <= 1.26 69 | assert major == 1 70 | assert minor >= 21 71 | assert minor <= 26 72 | 73 | # Check charset_normalizer for compatibility. 74 | if chardet_version: 75 | major, minor, patch = chardet_version.split('.')[:3] 76 | major, minor, patch = int(major), int(minor), int(patch) 77 | # chardet_version >= 3.0.2, < 5.0.0 78 | assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0) 79 | elif charset_normalizer_version: 80 | major, minor, patch = charset_normalizer_version.split('.')[:3] 81 | major, minor, patch = int(major), int(minor), int(patch) 82 | # charset_normalizer >= 2.0.0 < 3.0.0 83 | assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0) 84 | else: 85 | raise Exception("You need either charset_normalizer or chardet installed") 86 | 87 | def _check_cryptography(cryptography_version): 88 | # cryptography < 1.3.4 89 | try: 90 | cryptography_version = list(map(int, cryptography_version.split('.'))) 91 | except ValueError: 92 | return 93 | 94 | if cryptography_version < [1, 3, 4]: 95 | warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version) 96 | warnings.warn(warning, RequestsDependencyWarning) 97 | 98 | # Check imported dependencies for compatibility. 99 | try: 100 | check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version) 101 | except (AssertionError, ValueError): 102 | warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " 103 | "version!".format(urllib3.__version__, chardet_version, charset_normalizer_version), 104 | RequestsDependencyWarning) 105 | 106 | # Attempt to enable urllib3's fallback for SNI support 107 | # if the standard library doesn't support SNI or the 108 | # 'ssl' library isn't available. 109 | try: 110 | try: 111 | import ssl 112 | except ImportError: 113 | ssl = None 114 | 115 | if not getattr(ssl, "HAS_SNI", False): 116 | from urllib3.contrib import pyopenssl 117 | pyopenssl.inject_into_urllib3() 118 | 119 | # Check cryptography version 120 | from cryptography import __version__ as cryptography_version 121 | _check_cryptography(cryptography_version) 122 | except ImportError: 123 | pass 124 | 125 | # urllib3's DependencyWarnings should be silenced. 126 | from urllib3.exceptions import DependencyWarning 127 | warnings.simplefilter('ignore', DependencyWarning) 128 | 129 | from .__version__ import __title__, __description__, __url__, __version__ 130 | from .__version__ import __build__, __author__, __author_email__, __license__ 131 | from .__version__ import __copyright__, __cake__ 132 | 133 | from . import utils 134 | from . import packages 135 | from .models import Request, Response, PreparedRequest 136 | from .api import request, get, head, post, patch, put, delete, options 137 | from .sessions import session, Session 138 | from .status_codes import codes 139 | from .exceptions import ( 140 | RequestException, Timeout, URLRequired, 141 | TooManyRedirects, HTTPError, ConnectionError, 142 | FileModeWarning, ConnectTimeout, ReadTimeout, JSONDecodeError 143 | ) 144 | 145 | # Set default logging handler to avoid "No handler found" warnings. 146 | import logging 147 | from logging import NullHandler 148 | 149 | logging.getLogger(__name__).addHandler(NullHandler()) 150 | 151 | # FileModeWarnings go off per the default. 152 | warnings.simplefilter('default', FileModeWarning, append=True) 153 | -------------------------------------------------------------------------------- /docs/user/authentication.rst: -------------------------------------------------------------------------------- 1 | .. _authentication: 2 | 3 | Authentication 4 | ============== 5 | 6 | This document discusses using various kinds of authentication with Requests. 7 | 8 | Many web services require authentication, and there are many different types. 9 | Below, we outline various forms of authentication available in Requests, from 10 | the simple to the complex. 11 | 12 | 13 | Basic Authentication 14 | -------------------- 15 | 16 | Many web services that require authentication accept HTTP Basic Auth. This is 17 | the simplest kind, and Requests supports it straight out of the box. 18 | 19 | Making requests with HTTP Basic Auth is very simple:: 20 | 21 | >>> from requests.auth import HTTPBasicAuth 22 | >>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass')) 23 | 24 | 25 | In fact, HTTP Basic Auth is so common that Requests provides a handy shorthand 26 | for using it:: 27 | 28 | >>> requests.get('https://api.github.com/user', auth=('user', 'pass')) 29 | 30 | 31 | Providing the credentials in a tuple like this is exactly the same as the 32 | ``HTTPBasicAuth`` example above. 33 | 34 | 35 | netrc Authentication 36 | ~~~~~~~~~~~~~~~~~~~~ 37 | 38 | If no authentication method is given with the ``auth`` argument, Requests will 39 | attempt to get the authentication credentials for the URL's hostname from the 40 | user's netrc file. The netrc file overrides raw HTTP authentication headers 41 | set with `headers=`. 42 | 43 | If credentials for the hostname are found, the request is sent with HTTP Basic 44 | Auth. 45 | 46 | 47 | Digest Authentication 48 | --------------------- 49 | 50 | Another very popular form of HTTP Authentication is Digest Authentication, 51 | and Requests supports this out of the box as well:: 52 | 53 | >>> from requests.auth import HTTPDigestAuth 54 | >>> url = 'https://httpbin.org/digest-auth/auth/user/pass' 55 | >>> requests.get(url, auth=HTTPDigestAuth('user', 'pass')) 56 | 57 | 58 | 59 | OAuth 1 Authentication 60 | ---------------------- 61 | 62 | A common form of authentication for several web APIs is OAuth. The ``requests-oauthlib`` 63 | library allows Requests users to easily make OAuth 1 authenticated requests:: 64 | 65 | >>> import requests 66 | >>> from requests_oauthlib import OAuth1 67 | 68 | >>> url = 'https://api.twitter.com/1.1/account/verify_credentials.json' 69 | >>> auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET', 70 | ... 'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET') 71 | 72 | >>> requests.get(url, auth=auth) 73 | 74 | 75 | For more information on how to OAuth flow works, please see the official `OAuth`_ website. 76 | For examples and documentation on requests-oauthlib, please see the `requests_oauthlib`_ 77 | repository on GitHub 78 | 79 | OAuth 2 and OpenID Connect Authentication 80 | ----------------------------------------- 81 | 82 | The ``requests-oauthlib`` library also handles OAuth 2, the authentication mechanism 83 | underpinning OpenID Connect. See the `requests-oauthlib OAuth2 documentation`_ for 84 | details of the various OAuth 2 credential management flows: 85 | 86 | * `Web Application Flow`_ 87 | * `Mobile Application Flow`_ 88 | * `Legacy Application Flow`_ 89 | * `Backend Application Flow`_ 90 | 91 | Other Authentication 92 | -------------------- 93 | 94 | Requests is designed to allow other forms of authentication to be easily and 95 | quickly plugged in. Members of the open-source community frequently write 96 | authentication handlers for more complicated or less commonly-used forms of 97 | authentication. Some of the best have been brought together under the 98 | `Requests organization`_, including: 99 | 100 | - Kerberos_ 101 | - NTLM_ 102 | 103 | If you want to use any of these forms of authentication, go straight to their 104 | GitHub page and follow the instructions. 105 | 106 | 107 | New Forms of Authentication 108 | --------------------------- 109 | 110 | If you can't find a good implementation of the form of authentication you 111 | want, you can implement it yourself. Requests makes it easy to add your own 112 | forms of authentication. 113 | 114 | To do so, subclass :class:`AuthBase ` and implement the 115 | ``__call__()`` method:: 116 | 117 | >>> import requests 118 | >>> class MyAuth(requests.auth.AuthBase): 119 | ... def __call__(self, r): 120 | ... # Implement my authentication 121 | ... return r 122 | ... 123 | >>> url = 'https://httpbin.org/get' 124 | >>> requests.get(url, auth=MyAuth()) 125 | 126 | 127 | When an authentication handler is attached to a request, 128 | it is called during request setup. The ``__call__`` method must therefore do 129 | whatever is required to make the authentication work. Some forms of 130 | authentication will additionally add hooks to provide further functionality. 131 | 132 | Further examples can be found under the `Requests organization`_ and in the 133 | ``auth.py`` file. 134 | 135 | .. _OAuth: https://oauth.net/ 136 | .. _requests_oauthlib: https://github.com/requests/requests-oauthlib 137 | .. _requests-oauthlib OAuth2 documentation: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html 138 | .. _Web Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#web-application-flow 139 | .. _Mobile Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#mobile-application-flow 140 | .. _Legacy Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#legacy-application-flow 141 | .. _Backend Application Flow: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#backend-application-flow 142 | .. _Kerberos: https://github.com/requests/requests-kerberos 143 | .. _NTLM: https://github.com/requests/requests-ntlm 144 | .. _Requests organization: https://github.com/requests 145 | -------------------------------------------------------------------------------- /tests/test_testserver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import threading 4 | import socket 5 | import time 6 | 7 | import pytest 8 | import requests 9 | from tests.testserver.server import Server 10 | 11 | 12 | class TestTestServer: 13 | 14 | def test_basic(self): 15 | """messages are sent and received properly""" 16 | question = b"success?" 17 | answer = b"yeah, success" 18 | 19 | def handler(sock): 20 | text = sock.recv(1000) 21 | assert text == question 22 | sock.sendall(answer) 23 | 24 | with Server(handler) as (host, port): 25 | sock = socket.socket() 26 | sock.connect((host, port)) 27 | sock.sendall(question) 28 | text = sock.recv(1000) 29 | assert text == answer 30 | sock.close() 31 | 32 | def test_server_closes(self): 33 | """the server closes when leaving the context manager""" 34 | with Server.basic_response_server() as (host, port): 35 | sock = socket.socket() 36 | sock.connect((host, port)) 37 | 38 | sock.close() 39 | 40 | with pytest.raises(socket.error): 41 | new_sock = socket.socket() 42 | new_sock.connect((host, port)) 43 | 44 | def test_text_response(self): 45 | """the text_response_server sends the given text""" 46 | server = Server.text_response_server( 47 | "HTTP/1.1 200 OK\r\n" + 48 | "Content-Length: 6\r\n" + 49 | "\r\nroflol" 50 | ) 51 | 52 | with server as (host, port): 53 | r = requests.get('http://{}:{}'.format(host, port)) 54 | 55 | assert r.status_code == 200 56 | assert r.text == u'roflol' 57 | assert r.headers['Content-Length'] == '6' 58 | 59 | def test_basic_response(self): 60 | """the basic response server returns an empty http response""" 61 | with Server.basic_response_server() as (host, port): 62 | r = requests.get('http://{}:{}'.format(host, port)) 63 | assert r.status_code == 200 64 | assert r.text == u'' 65 | assert r.headers['Content-Length'] == '0' 66 | 67 | def test_basic_waiting_server(self): 68 | """the server waits for the block_server event to be set before closing""" 69 | block_server = threading.Event() 70 | 71 | with Server.basic_response_server(wait_to_close_event=block_server) as (host, port): 72 | sock = socket.socket() 73 | sock.connect((host, port)) 74 | sock.sendall(b'send something') 75 | time.sleep(2.5) 76 | sock.sendall(b'still alive') 77 | block_server.set() # release server block 78 | 79 | def test_multiple_requests(self): 80 | """multiple requests can be served""" 81 | requests_to_handle = 5 82 | 83 | server = Server.basic_response_server(requests_to_handle=requests_to_handle) 84 | 85 | with server as (host, port): 86 | server_url = 'http://{}:{}'.format(host, port) 87 | for _ in range(requests_to_handle): 88 | r = requests.get(server_url) 89 | assert r.status_code == 200 90 | 91 | # the (n+1)th request fails 92 | with pytest.raises(requests.exceptions.ConnectionError): 93 | r = requests.get(server_url) 94 | 95 | @pytest.mark.skip(reason="this fails non-deterministically under pytest-xdist") 96 | def test_request_recovery(self): 97 | """can check the requests content""" 98 | # TODO: figure out why this sometimes fails when using pytest-xdist. 99 | server = Server.basic_response_server(requests_to_handle=2) 100 | first_request = b'put your hands up in the air' 101 | second_request = b'put your hand down in the floor' 102 | 103 | with server as address: 104 | sock1 = socket.socket() 105 | sock2 = socket.socket() 106 | 107 | sock1.connect(address) 108 | sock1.sendall(first_request) 109 | sock1.close() 110 | 111 | sock2.connect(address) 112 | sock2.sendall(second_request) 113 | sock2.close() 114 | 115 | assert server.handler_results[0] == first_request 116 | assert server.handler_results[1] == second_request 117 | 118 | def test_requests_after_timeout_are_not_received(self): 119 | """the basic response handler times out when receiving requests""" 120 | server = Server.basic_response_server(request_timeout=1) 121 | 122 | with server as address: 123 | sock = socket.socket() 124 | sock.connect(address) 125 | time.sleep(1.5) 126 | sock.sendall(b'hehehe, not received') 127 | sock.close() 128 | 129 | assert server.handler_results[0] == b'' 130 | 131 | def test_request_recovery_with_bigger_timeout(self): 132 | """a biggest timeout can be specified""" 133 | server = Server.basic_response_server(request_timeout=3) 134 | data = b'bananadine' 135 | 136 | with server as address: 137 | sock = socket.socket() 138 | sock.connect(address) 139 | time.sleep(1.5) 140 | sock.sendall(data) 141 | sock.close() 142 | 143 | assert server.handler_results[0] == data 144 | 145 | def test_server_finishes_on_error(self): 146 | """the server thread exits even if an exception exits the context manager""" 147 | server = Server.basic_response_server() 148 | with pytest.raises(Exception): 149 | with server: 150 | raise Exception() 151 | 152 | assert len(server.handler_results) == 0 153 | 154 | # if the server thread fails to finish, the test suite will hang 155 | # and get killed by the jenkins timeout. 156 | 157 | def test_server_finishes_when_no_connections(self): 158 | """the server thread exits even if there are no connections""" 159 | server = Server.basic_response_server() 160 | with server: 161 | pass 162 | 163 | assert len(server.handler_results) == 0 164 | 165 | # if the server thread fails to finish, the test suite will hang 166 | # and get killed by the jenkins timeout. 167 | -------------------------------------------------------------------------------- /requests/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.api 5 | ~~~~~~~~~~~~ 6 | 7 | This module implements the Requests API. 8 | 9 | :copyright: (c) 2012 by Kenneth Reitz. 10 | :license: Apache2, see LICENSE for more details. 11 | """ 12 | 13 | from . import sessions 14 | 15 | 16 | def request(method, url, **kwargs): 17 | """Constructs and sends a :class:`Request `. 18 | 19 | :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. 20 | :param url: URL for the new :class:`Request` object. 21 | :param params: (optional) Dictionary, list of tuples or bytes to send 22 | in the query string for the :class:`Request`. 23 | :param data: (optional) Dictionary, list of tuples, bytes, or file-like 24 | object to send in the body of the :class:`Request`. 25 | :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. 26 | :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. 27 | :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. 28 | :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. 29 | ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` 30 | or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string 31 | defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers 32 | to add for the file. 33 | :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. 34 | :param timeout: (optional) How many seconds to wait for the server to send data 35 | before giving up, as a float, or a :ref:`(connect timeout, read 36 | timeout) ` tuple. 37 | :type timeout: float or tuple 38 | :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. 39 | :type allow_redirects: bool 40 | :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. 41 | :param verify: (optional) Either a boolean, in which case it controls whether we verify 42 | the server's TLS certificate, or a string, in which case it must be a path 43 | to a CA bundle to use. Defaults to ``True``. 44 | :param stream: (optional) if ``False``, the response content will be immediately downloaded. 45 | :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. 46 | :return: :class:`Response ` object 47 | :rtype: requests.Response 48 | 49 | Usage:: 50 | 51 | >>> import requests 52 | >>> req = requests.request('GET', 'https://httpbin.org/get') 53 | >>> req 54 | 55 | """ 56 | 57 | # By using the 'with' statement we are sure the session is closed, thus we 58 | # avoid leaving sockets open which can trigger a ResourceWarning in some 59 | # cases, and look like a memory leak in others. 60 | with sessions.Session() as session: 61 | return session.request(method=method, url=url, **kwargs) 62 | 63 | 64 | def get(url, params=None, **kwargs): 65 | r"""Sends a GET request. 66 | 67 | :param url: URL for the new :class:`Request` object. 68 | :param params: (optional) Dictionary, list of tuples or bytes to send 69 | in the query string for the :class:`Request`. 70 | :param \*\*kwargs: Optional arguments that ``request`` takes. 71 | :return: :class:`Response ` object 72 | :rtype: requests.Response 73 | """ 74 | 75 | return request('get', url, params=params, **kwargs) 76 | 77 | 78 | def options(url, **kwargs): 79 | r"""Sends an OPTIONS request. 80 | 81 | :param url: URL for the new :class:`Request` object. 82 | :param \*\*kwargs: Optional arguments that ``request`` takes. 83 | :return: :class:`Response ` object 84 | :rtype: requests.Response 85 | """ 86 | 87 | return request('options', url, **kwargs) 88 | 89 | 90 | def head(url, **kwargs): 91 | r"""Sends a HEAD request. 92 | 93 | :param url: URL for the new :class:`Request` object. 94 | :param \*\*kwargs: Optional arguments that ``request`` takes. If 95 | `allow_redirects` is not provided, it will be set to `False` (as 96 | opposed to the default :meth:`request` behavior). 97 | :return: :class:`Response ` object 98 | :rtype: requests.Response 99 | """ 100 | 101 | kwargs.setdefault('allow_redirects', False) 102 | return request('head', url, **kwargs) 103 | 104 | 105 | def post(url, data=None, json=None, **kwargs): 106 | r"""Sends a POST request. 107 | 108 | :param url: URL for the new :class:`Request` object. 109 | :param data: (optional) Dictionary, list of tuples, bytes, or file-like 110 | object to send in the body of the :class:`Request`. 111 | :param json: (optional) json data to send in the body of the :class:`Request`. 112 | :param \*\*kwargs: Optional arguments that ``request`` takes. 113 | :return: :class:`Response ` object 114 | :rtype: requests.Response 115 | """ 116 | 117 | return request('post', url, data=data, json=json, **kwargs) 118 | 119 | 120 | def put(url, data=None, **kwargs): 121 | r"""Sends a PUT request. 122 | 123 | :param url: URL for the new :class:`Request` object. 124 | :param data: (optional) Dictionary, list of tuples, bytes, or file-like 125 | object to send in the body of the :class:`Request`. 126 | :param json: (optional) json data to send in the body of the :class:`Request`. 127 | :param \*\*kwargs: Optional arguments that ``request`` takes. 128 | :return: :class:`Response ` object 129 | :rtype: requests.Response 130 | """ 131 | 132 | return request('put', url, data=data, **kwargs) 133 | 134 | 135 | def patch(url, data=None, **kwargs): 136 | r"""Sends a PATCH request. 137 | 138 | :param url: URL for the new :class:`Request` object. 139 | :param data: (optional) Dictionary, list of tuples, bytes, or file-like 140 | object to send in the body of the :class:`Request`. 141 | :param json: (optional) json data to send in the body of the :class:`Request`. 142 | :param \*\*kwargs: Optional arguments that ``request`` takes. 143 | :return: :class:`Response ` object 144 | :rtype: requests.Response 145 | """ 146 | 147 | return request('patch', url, data=data, **kwargs) 148 | 149 | 150 | def delete(url, **kwargs): 151 | r"""Sends a DELETE request. 152 | 153 | :param url: URL for the new :class:`Request` object. 154 | :param \*\*kwargs: Optional arguments that ``request`` takes. 155 | :return: :class:`Response ` object 156 | :rtype: requests.Response 157 | """ 158 | 159 | return request('delete', url, **kwargs) 160 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Requests.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Requests.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | Developer Interface 4 | =================== 5 | 6 | .. module:: requests 7 | 8 | This part of the documentation covers all the interfaces of Requests. For 9 | parts where Requests depends on external libraries, we document the most 10 | important right here and provide links to the canonical documentation. 11 | 12 | 13 | Main Interface 14 | -------------- 15 | 16 | All of Requests' functionality can be accessed by these 7 methods. 17 | They all return an instance of the :class:`Response ` object. 18 | 19 | .. autofunction:: request 20 | 21 | .. autofunction:: head 22 | .. autofunction:: get 23 | .. autofunction:: post 24 | .. autofunction:: put 25 | .. autofunction:: patch 26 | .. autofunction:: delete 27 | 28 | Exceptions 29 | ---------- 30 | 31 | .. autoexception:: requests.RequestException 32 | .. autoexception:: requests.ConnectionError 33 | .. autoexception:: requests.HTTPError 34 | .. autoexception:: requests.URLRequired 35 | .. autoexception:: requests.TooManyRedirects 36 | .. autoexception:: requests.ConnectTimeout 37 | .. autoexception:: requests.ReadTimeout 38 | .. autoexception:: requests.Timeout 39 | 40 | 41 | Request Sessions 42 | ---------------- 43 | 44 | .. _sessionapi: 45 | 46 | .. autoclass:: Session 47 | :inherited-members: 48 | 49 | 50 | Lower-Level Classes 51 | ------------------- 52 | 53 | .. autoclass:: requests.Request 54 | :inherited-members: 55 | 56 | .. autoclass:: Response 57 | :inherited-members: 58 | 59 | 60 | Lower-Lower-Level Classes 61 | ------------------------- 62 | 63 | .. autoclass:: requests.PreparedRequest 64 | :inherited-members: 65 | 66 | .. autoclass:: requests.adapters.BaseAdapter 67 | :inherited-members: 68 | 69 | .. autoclass:: requests.adapters.HTTPAdapter 70 | :inherited-members: 71 | 72 | Authentication 73 | -------------- 74 | 75 | .. autoclass:: requests.auth.AuthBase 76 | .. autoclass:: requests.auth.HTTPBasicAuth 77 | .. autoclass:: requests.auth.HTTPProxyAuth 78 | .. autoclass:: requests.auth.HTTPDigestAuth 79 | 80 | 81 | 82 | Encodings 83 | --------- 84 | 85 | .. autofunction:: requests.utils.get_encodings_from_content 86 | .. autofunction:: requests.utils.get_encoding_from_headers 87 | .. autofunction:: requests.utils.get_unicode_from_response 88 | 89 | 90 | .. _api-cookies: 91 | 92 | Cookies 93 | ------- 94 | 95 | .. autofunction:: requests.utils.dict_from_cookiejar 96 | .. autofunction:: requests.utils.add_dict_to_cookiejar 97 | .. autofunction:: requests.cookies.cookiejar_from_dict 98 | 99 | .. autoclass:: requests.cookies.RequestsCookieJar 100 | :inherited-members: 101 | 102 | .. autoclass:: requests.cookies.CookieConflictError 103 | :inherited-members: 104 | 105 | 106 | 107 | Status Code Lookup 108 | ------------------ 109 | 110 | .. autoclass:: requests.codes 111 | 112 | .. automodule:: requests.status_codes 113 | 114 | 115 | Migrating to 1.x 116 | ---------------- 117 | 118 | This section details the main differences between 0.x and 1.x and is meant 119 | to ease the pain of upgrading. 120 | 121 | 122 | API Changes 123 | ~~~~~~~~~~~ 124 | 125 | * ``Response.json`` is now a callable and not a property of a response. 126 | 127 | :: 128 | 129 | import requests 130 | r = requests.get('https://api.github.com/events') 131 | r.json() # This *call* raises an exception if JSON decoding fails 132 | 133 | * The ``Session`` API has changed. Sessions objects no longer take parameters. 134 | ``Session`` is also now capitalized, but it can still be 135 | instantiated with a lowercase ``session`` for backwards compatibility. 136 | 137 | :: 138 | 139 | s = requests.Session() # formerly, session took parameters 140 | s.auth = auth 141 | s.headers.update(headers) 142 | r = s.get('https://httpbin.org/headers') 143 | 144 | * All request hooks have been removed except 'response'. 145 | 146 | * Authentication helpers have been broken out into separate modules. See 147 | requests-oauthlib_ and requests-kerberos_. 148 | 149 | .. _requests-oauthlib: https://github.com/requests/requests-oauthlib 150 | .. _requests-kerberos: https://github.com/requests/requests-kerberos 151 | 152 | * The parameter for streaming requests was changed from ``prefetch`` to 153 | ``stream`` and the logic was inverted. In addition, ``stream`` is now 154 | required for raw response reading. 155 | 156 | :: 157 | 158 | # in 0.x, passing prefetch=False would accomplish the same thing 159 | r = requests.get('https://api.github.com/events', stream=True) 160 | for chunk in r.iter_content(8192): 161 | ... 162 | 163 | * The ``config`` parameter to the requests method has been removed. Some of 164 | these options are now configured on a ``Session`` such as keep-alive and 165 | maximum number of redirects. The verbosity option should be handled by 166 | configuring logging. 167 | 168 | :: 169 | 170 | import requests 171 | import logging 172 | 173 | # Enabling debugging at http.client level (requests->urllib3->http.client) 174 | # you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. 175 | # the only thing missing will be the response.body which is not logged. 176 | try: # for Python 3 177 | from http.client import HTTPConnection 178 | except ImportError: 179 | from httplib import HTTPConnection 180 | HTTPConnection.debuglevel = 1 181 | 182 | logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests 183 | logging.getLogger().setLevel(logging.DEBUG) 184 | requests_log = logging.getLogger("urllib3") 185 | requests_log.setLevel(logging.DEBUG) 186 | requests_log.propagate = True 187 | 188 | requests.get('https://httpbin.org/headers') 189 | 190 | 191 | 192 | Licensing 193 | ~~~~~~~~~ 194 | 195 | One key difference that has nothing to do with the API is a change in the 196 | license from the ISC_ license to the `Apache 2.0`_ license. The Apache 2.0 197 | license ensures that contributions to Requests are also covered by the Apache 198 | 2.0 license. 199 | 200 | .. _ISC: https://opensource.org/licenses/ISC 201 | .. _Apache 2.0: https://opensource.org/licenses/Apache-2.0 202 | 203 | 204 | Migrating to 2.x 205 | ---------------- 206 | 207 | 208 | Compared with the 1.0 release, there were relatively few backwards 209 | incompatible changes, but there are still a few issues to be aware of with 210 | this major release. 211 | 212 | For more details on the changes in this release including new APIs, links 213 | to the relevant GitHub issues and some of the bug fixes, read Cory's blog_ 214 | on the subject. 215 | 216 | .. _blog: https://lukasa.co.uk/2013/09/Requests_20/ 217 | 218 | 219 | API Changes 220 | ~~~~~~~~~~~ 221 | 222 | * There were a couple changes to how Requests handles exceptions. 223 | ``RequestException`` is now a subclass of ``IOError`` rather than 224 | ``RuntimeError`` as that more accurately categorizes the type of error. 225 | In addition, an invalid URL escape sequence now raises a subclass of 226 | ``RequestException`` rather than a ``ValueError``. 227 | 228 | :: 229 | 230 | requests.get('http://%zz/') # raises requests.exceptions.InvalidURL 231 | 232 | Lastly, ``httplib.IncompleteRead`` exceptions caused by incorrect chunked 233 | encoding will now raise a Requests ``ChunkedEncodingError`` instead. 234 | 235 | * The proxy API has changed slightly. The scheme for a proxy URL is now 236 | required. 237 | 238 | :: 239 | 240 | proxies = { 241 | "http": "10.10.1.10:3128", # use http://10.10.1.10:3128 instead 242 | } 243 | 244 | # In requests 1.x, this was legal, in requests 2.x, 245 | # this raises requests.exceptions.MissingSchema 246 | requests.get("http://example.org", proxies=proxies) 247 | 248 | 249 | Behavioural Changes 250 | ~~~~~~~~~~~~~~~~~~~~~~~ 251 | 252 | * Keys in the ``headers`` dictionary are now native strings on all Python 253 | versions, i.e. bytestrings on Python 2 and unicode on Python 3. If the 254 | keys are not native strings (unicode on Python 2 or bytestrings on Python 3) 255 | they will be converted to the native string type assuming UTF-8 encoding. 256 | 257 | * Values in the ``headers`` dictionary should always be strings. This has 258 | been the project's position since before 1.0 but a recent change 259 | (since version 2.11.0) enforces this more strictly. It's advised to avoid 260 | passing header values as unicode when possible. 261 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(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/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Requests.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Requests.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Requests" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Requests" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Requests was lovingly created by Kenneth Reitz. 2 | 3 | Keepers of the Crystals 4 | ``````````````````````` 5 | 6 | - Nate Prewitt `@nateprewitt `_. 7 | - Seth M. Larson `@sethmlarson `_. 8 | 9 | Previous Keepers of Crystals 10 | ```````````````````````````` 11 | - Kenneth Reitz `@ken-reitz `_, reluctant Keeper of the Master Crystal. 12 | - Cory Benfield `@lukasa `_ 13 | - Ian Cordasco `@sigmavirus24 `_. 14 | 15 | 16 | Patches and Suggestions 17 | ``````````````````````` 18 | 19 | - Various Pocoo Members 20 | - Chris Adams 21 | - Flavio Percoco Premoli 22 | - Dj Gilcrease 23 | - Justin Murphy 24 | - Rob Madole 25 | - Aram Dulyan 26 | - Johannes Gorset 27 | - 村山めがね (Megane Murayama) 28 | - James Rowe 29 | - Daniel Schauenberg 30 | - Zbigniew Siciarz 31 | - Daniele Tricoli 'Eriol' 32 | - Richard Boulton 33 | - Miguel Olivares 34 | - Alberto Paro 35 | - Jérémy Bethmont 36 | - 潘旭 (Xu Pan) 37 | - Tamás Gulácsi 38 | - Rubén Abad 39 | - Peter Manser 40 | - Jeremy Selier 41 | - Jens Diemer 42 | - Alex (`@alopatin `_) 43 | - Tom Hogans 44 | - Armin Ronacher 45 | - Shrikant Sharat Kandula 46 | - Mikko Ohtamaa 47 | - Den Shabalin 48 | - Daniel Miller 49 | - Alejandro Giacometti 50 | - Rick Mak 51 | - Johan Bergström 52 | - Josselin Jacquard 53 | - Travis N. Vaught 54 | - Fredrik Möllerstrand 55 | - Daniel Hengeveld 56 | - Dan Head 57 | - Bruno Renié 58 | - David Fischer 59 | - Joseph McCullough 60 | - Juergen Brendel 61 | - Juan Riaza 62 | - Ryan Kelly 63 | - Rolando Espinoza La fuente 64 | - Robert Gieseke 65 | - Idan Gazit 66 | - Ed Summers 67 | - Chris Van Horne 68 | - Christopher Davis 69 | - Ori Livneh 70 | - Jason Emerick 71 | - Bryan Helmig 72 | - Jonas Obrist 73 | - Lucian Ursu 74 | - Tom Moertel 75 | - Frank Kumro Jr 76 | - Chase Sterling 77 | - Marty Alchin 78 | - takluyver 79 | - Ben Toews (`@mastahyeti `_) 80 | - David Kemp 81 | - Brendon Crawford 82 | - Denis (`@Telofy `_) 83 | - Matt Giuca 84 | - Adam Tauber 85 | - Honza Javorek 86 | - Brendan Maguire 87 | - Chris Dary 88 | - Danver Braganza 89 | - Max Countryman 90 | - Nick Chadwick 91 | - Jonathan Drosdeck 92 | - Jiri Machalek 93 | - Steve Pulec 94 | - Michael Kelly 95 | - Michael Newman 96 | - Jonty Wareing 97 | - Shivaram Lingamneni 98 | - Miguel Turner 99 | - Rohan Jain (`@crodjer `_) 100 | - Justin Barber 101 | - Roman Haritonov (`@reclosedev `_) 102 | - Josh Imhoff 103 | - Arup Malakar 104 | - Danilo Bargen (`@dbrgn `_) 105 | - Torsten Landschoff 106 | - Michael Holler (`@apotheos `_) 107 | - Timnit Gebru 108 | - Sarah Gonzalez 109 | - Victoria Mo 110 | - Leila Muhtasib 111 | - Matthias Rahlf 112 | - Jakub Roztocil 113 | - Rhys Elsmore 114 | - André Graf (`@dergraf `_) 115 | - Stephen Zhuang (`@everbird `_) 116 | - Martijn Pieters 117 | - Jonatan Heyman 118 | - David Bonner (`@rascalking `_) 119 | - Vinod Chandru 120 | - Johnny Goodnow 121 | - Denis Ryzhkov 122 | - Wilfred Hughes 123 | - Dmitry Medvinsky 124 | - Bryce Boe (`@bboe `_) 125 | - Colin Dunklau (`@cdunklau `_) 126 | - Bob Carroll (`@rcarz `_) 127 | - Hugo Osvaldo Barrera (`@hobarrera `_) 128 | - Łukasz Langa 129 | - Dave Shawley 130 | - James Clarke (`@jam `_) 131 | - Kevin Burke 132 | - Flavio Curella 133 | - David Pursehouse (`@dpursehouse `_) 134 | - Jon Parise (`@jparise `_) 135 | - Alexander Karpinsky (`@homm86 `_) 136 | - Marc Schlaich (`@schlamar `_) 137 | - Park Ilsu (`@daftshady `_) 138 | - Matt Spitz (`@mattspitz `_) 139 | - Vikram Oberoi (`@voberoi `_) 140 | - Can Ibanoglu (`@canibanoglu `_) 141 | - Thomas Weißschuh (`@t-8ch `_) 142 | - Jayson Vantuyl 143 | - Pengfei.X 144 | - Kamil Madac 145 | - Michael Becker (`@beckerfuffle `_) 146 | - Erik Wickstrom (`@erikwickstrom `_) 147 | - Константин Подшумок (`@podshumok `_) 148 | - Ben Bass (`@codedstructure `_) 149 | - Jonathan Wong (`@ContinuousFunction `_) 150 | - Martin Jul (`@mjul `_) 151 | - Joe Alcorn (`@buttscicles `_) 152 | - Syed Suhail Ahmed (`@syedsuhail `_) 153 | - Scott Sadler (`@ssadler `_) 154 | - Arthur Darcet (`@arthurdarcet `_) 155 | - Ulrich Petri (`@ulope `_) 156 | - Muhammad Yasoob Ullah Khalid (`@yasoob `_) 157 | - Paul van der Linden (`@pvanderlinden `_) 158 | - Colin Dickson (`@colindickson `_) 159 | - Smiley Barry (`@smiley `_) 160 | - Shagun Sodhani (`@shagunsodhani `_) 161 | - Robin Linderborg (`@vienno `_) 162 | - Brian Samek (`@bsamek `_) 163 | - Dmitry Dygalo (`@Stranger6667 `_) 164 | - piotrjurkiewicz 165 | - Jesse Shapiro (`@haikuginger `_) 166 | - Nate Prewitt (`@nateprewitt `_) 167 | - Maik Himstedt 168 | - Michael Hunsinger 169 | - Brian Bamsch (`@bbamsch `_) 170 | - Om Prakash Kumar (`@iamprakashom `_) 171 | - Philipp Konrad (`@gardiac2002 `_) 172 | - Hussain Tamboli (`@hussaintamboli `_) 173 | - Casey Davidson (`@davidsoncasey `_) 174 | - Andrii Soldatenko (`@a_soldatenko `_) 175 | - Moinuddin Quadri (`@moin18 `_) 176 | - Matt Kohl (`@mattkohl `_) 177 | - Jonathan Vanasco (`@jvanasco `_) 178 | - David Fontenot (`@davidfontenot `_) 179 | - Shmuel Amar (`@shmuelamar `_) 180 | - Gary Wu (`@garywu `_) 181 | - Ryan Pineo (`@ryanpineo `_) 182 | - Ed Morley (`@edmorley `_) 183 | - Matt Liu (`@mlcrazy `_) 184 | - Taylor Hoff (`@PrimordialHelios `_) 185 | - Arthur Vigil (`@ahvigil `_) 186 | - Nehal J Wani (`@nehaljwani `_) 187 | - Demetrios Bairaktaris (`@DemetriosBairaktaris `_) 188 | - Darren Dormer (`@ddormer `_) 189 | - Rajiv Mayani (`@mayani `_) 190 | - Antti Kaihola (`@akaihola `_) 191 | - "Dull Bananas" (`@dullbananas `_) 192 | - Alessio Izzo (`@aless10 `_) 193 | - Sylvain Marié (`@smarie `_) 194 | -------------------------------------------------------------------------------- /docs/dev/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | 3 | Contributor's Guide 4 | =================== 5 | 6 | If you're reading this, you're probably interested in contributing to Requests. 7 | Thank you very much! Open source projects live-and-die based on the support 8 | they receive from others, and the fact that you're even considering 9 | contributing to the Requests project is *very* generous of you. 10 | 11 | This document lays out guidelines and advice for contributing to this project. 12 | If you're thinking of contributing, please start by reading this document and 13 | getting a feel for how contributing to this project works. If you have any 14 | questions, feel free to reach out to either `Nate Prewitt`_, `Ian Cordasco`_, 15 | or `Seth Michael Larson`_, the primary maintainers. 16 | 17 | .. _Ian Cordasco: http://www.coglib.com/~icordasc/ 18 | .. _Nate Prewitt: https://www.nateprewitt.com/ 19 | .. _Seth Michael Larson: https://sethmlarson.dev/ 20 | 21 | The guide is split into sections based on the type of contribution you're 22 | thinking of making, with a section that covers general guidelines for all 23 | contributors. 24 | 25 | Be Cordial 26 | ---------- 27 | 28 | **Be cordial or be on your way**. *—Kenneth Reitz* 29 | 30 | Requests has one very important rule governing all forms of contribution, 31 | including reporting bugs or requesting features. This golden rule is 32 | "`be cordial or be on your way`_". 33 | 34 | **All contributions are welcome**, as long as 35 | everyone involved is treated with respect. 36 | 37 | .. _be cordial or be on your way: https://kenreitz.org/essays/2013/01/27/be-cordial-or-be-on-your-way 38 | 39 | .. _early-feedback: 40 | 41 | Get Early Feedback 42 | ------------------ 43 | 44 | If you are contributing, do not feel the need to sit on your contribution until 45 | it is perfectly polished and complete. It helps everyone involved for you to 46 | seek feedback as early as you possibly can. Submitting an early, unfinished 47 | version of your contribution for feedback in no way prejudices your chances of 48 | getting that contribution accepted, and can save you from putting a lot of work 49 | into a contribution that is not suitable for the project. 50 | 51 | Contribution Suitability 52 | ------------------------ 53 | 54 | Our project maintainers have the last word on whether or not a contribution is 55 | suitable for Requests. All contributions will be considered carefully, but from 56 | time to time, contributions will be rejected because they do not suit the 57 | current goals or needs of the project. 58 | 59 | If your contribution is rejected, don't despair! As long as you followed these 60 | guidelines, you will have a much better chance of getting your next 61 | contribution accepted. 62 | 63 | 64 | Code Contributions 65 | ------------------ 66 | 67 | Steps for Submitting Code 68 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 69 | 70 | When contributing code, you'll want to follow this checklist: 71 | 72 | 1. Fork the repository on GitHub. 73 | 2. Run the tests to confirm they all pass on your system. If they don't, you'll 74 | need to investigate why they fail. If you're unable to diagnose this 75 | yourself, raise it as a bug report by following the guidelines in this 76 | document: :ref:`bug-reports`. 77 | 3. Write tests that demonstrate your bug or feature. Ensure that they fail. 78 | 4. Make your change. 79 | 5. Run the entire test suite again, confirming that all tests pass *including 80 | the ones you just added*. 81 | 6. Send a GitHub Pull Request to the main repository's ``main`` branch. 82 | GitHub Pull Requests are the expected method of code collaboration on this 83 | project. 84 | 85 | The following sub-sections go into more detail on some of the points above. 86 | 87 | Code Review 88 | ~~~~~~~~~~~ 89 | 90 | Contributions will not be merged until they've been code reviewed. You should 91 | implement any code review feedback unless you strongly object to it. In the 92 | event that you object to the code review feedback, you should make your case 93 | clearly and calmly. If, after doing so, the feedback is judged to still apply, 94 | you must either apply the feedback or withdraw your contribution. 95 | 96 | New Contributors 97 | ~~~~~~~~~~~~~~~~ 98 | 99 | If you are new or relatively new to Open Source, welcome! Requests aims to 100 | be a gentle introduction to the world of Open Source. If you're concerned about 101 | how best to contribute, please consider mailing a maintainer (listed above) and 102 | asking for help. 103 | 104 | Please also check the :ref:`early-feedback` section. 105 | 106 | Kenneth Reitz's Code Style™ 107 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 108 | 109 | The Requests codebase uses the `PEP 8`_ code style. 110 | 111 | In addition to the standards outlined in PEP 8, we have a few guidelines: 112 | 113 | - Line-length can exceed 79 characters, to 100, when convenient. 114 | - Line-length can exceed 100 characters, when doing otherwise would be *terribly* inconvenient. 115 | - Always use single-quoted strings (e.g. ``'#flatearth'``), unless a single-quote occurs within the string. 116 | 117 | Additionally, one of the styles that PEP8 recommends for `line continuations`_ 118 | completely lacks all sense of taste, and is not to be permitted within 119 | the Requests codebase:: 120 | 121 | # Aligned with opening delimiter. 122 | foo = long_function_name(var_one, var_two, 123 | var_three, var_four) 124 | 125 | No. Just don't. Please. This is much better:: 126 | 127 | foo = long_function_name( 128 | var_one, 129 | var_two, 130 | var_three, 131 | var_four, 132 | ) 133 | 134 | Docstrings are to follow the following syntaxes:: 135 | 136 | def the_earth_is_flat(): 137 | """NASA divided up the seas into thirty-three degrees.""" 138 | pass 139 | 140 | :: 141 | 142 | def fibonacci_spiral_tool(): 143 | """With my feet upon the ground I lose myself / between the sounds 144 | and open wide to suck it in. / I feel it move across my skin. / I'm 145 | reaching up and reaching out. / I'm reaching for the random or 146 | whatever will bewilder me. / Whatever will bewilder me. / And 147 | following our will and wind we may just go where no one's been. / 148 | We'll ride the spiral to the end and may just go where no one's 149 | been. 150 | 151 | Spiral out. Keep going... 152 | """ 153 | pass 154 | 155 | All functions, methods, and classes are to contain docstrings. Object data 156 | model methods (e.g. ``__repr__``) are typically the exception to this rule. 157 | 158 | Thanks for helping to make the world a better place! 159 | 160 | .. _PEP 8: https://pep8.org/ 161 | .. _line continuations: https://www.python.org/dev/peps/pep-0008/#indentation 162 | 163 | Documentation Contributions 164 | --------------------------- 165 | 166 | Documentation improvements are always welcome! The documentation files live in 167 | the ``docs/`` directory of the codebase. They're written in 168 | `reStructuredText`_, and use `Sphinx`_ to generate the full suite of 169 | documentation. 170 | 171 | When contributing documentation, please do your best to follow the style of the 172 | documentation files. This means a soft-limit of 79 characters wide in your text 173 | files and a semi-formal, yet friendly and approachable, prose style. 174 | 175 | When presenting Python code, use single-quoted strings (``'hello'`` instead of 176 | ``"hello"``). 177 | 178 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html 179 | .. _Sphinx: http://sphinx-doc.org/index.html 180 | 181 | 182 | .. _bug-reports: 183 | 184 | Bug Reports 185 | ----------- 186 | 187 | Bug reports are hugely important! Before you raise one, though, please check 188 | through the `GitHub issues`_, **both open and closed**, to confirm that the bug 189 | hasn't been reported before. Duplicate bug reports are a huge drain on the time 190 | of other contributors, and should be avoided as much as possible. 191 | 192 | .. _GitHub issues: https://github.com/psf/requests/issues 193 | 194 | 195 | Feature Requests 196 | ---------------- 197 | 198 | Requests is in a perpetual feature freeze, only the BDFL can add or approve of 199 | new features. The maintainers believe that Requests is a feature-complete 200 | piece of software at this time. 201 | 202 | One of the most important skills to have while maintaining a largely-used 203 | open source project is learning the ability to say "no" to suggested changes, 204 | while keeping an open ear and mind. 205 | 206 | If you believe there is a feature missing, feel free to raise a feature 207 | request, but please do be aware that the overwhelming likelihood is that your 208 | feature request will not be accepted. 209 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /requests/auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.auth 5 | ~~~~~~~~~~~~~ 6 | 7 | This module contains the authentication handlers for Requests. 8 | """ 9 | 10 | import os 11 | import re 12 | import time 13 | import hashlib 14 | import threading 15 | import warnings 16 | 17 | from base64 import b64encode 18 | 19 | from .compat import urlparse, str, basestring 20 | from .cookies import extract_cookies_to_jar 21 | from ._internal_utils import to_native_string 22 | from .utils import parse_dict_header 23 | 24 | CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' 25 | CONTENT_TYPE_MULTI_PART = 'multipart/form-data' 26 | 27 | 28 | def _basic_auth_str(username, password): 29 | """Returns a Basic Auth string.""" 30 | 31 | # "I want us to put a big-ol' comment on top of it that 32 | # says that this behaviour is dumb but we need to preserve 33 | # it because people are relying on it." 34 | # - Lukasa 35 | # 36 | # These are here solely to maintain backwards compatibility 37 | # for things like ints. This will be removed in 3.0.0. 38 | if not isinstance(username, basestring): 39 | warnings.warn( 40 | "Non-string usernames will no longer be supported in Requests " 41 | "3.0.0. Please convert the object you've passed in ({!r}) to " 42 | "a string or bytes object in the near future to avoid " 43 | "problems.".format(username), 44 | category=DeprecationWarning, 45 | ) 46 | username = str(username) 47 | 48 | if not isinstance(password, basestring): 49 | warnings.warn( 50 | "Non-string passwords will no longer be supported in Requests " 51 | "3.0.0. Please convert the object you've passed in ({!r}) to " 52 | "a string or bytes object in the near future to avoid " 53 | "problems.".format(type(password)), 54 | category=DeprecationWarning, 55 | ) 56 | password = str(password) 57 | # -- End Removal -- 58 | 59 | if isinstance(username, str): 60 | username = username.encode('latin1') 61 | 62 | if isinstance(password, str): 63 | password = password.encode('latin1') 64 | 65 | authstr = 'Basic ' + to_native_string( 66 | b64encode(b':'.join((username, password))).strip() 67 | ) 68 | 69 | return authstr 70 | 71 | 72 | class AuthBase(object): 73 | """Base class that all auth implementations derive from""" 74 | 75 | def __call__(self, r): 76 | raise NotImplementedError('Auth hooks must be callable.') 77 | 78 | 79 | class HTTPBasicAuth(AuthBase): 80 | """Attaches HTTP Basic Authentication to the given Request object.""" 81 | 82 | def __init__(self, username, password): 83 | self.username = username 84 | self.password = password 85 | 86 | def __eq__(self, other): 87 | return all([ 88 | self.username == getattr(other, 'username', None), 89 | self.password == getattr(other, 'password', None) 90 | ]) 91 | 92 | def __ne__(self, other): 93 | return not self == other 94 | 95 | def __call__(self, r): 96 | r.headers['Authorization'] = _basic_auth_str(self.username, self.password) 97 | return r 98 | 99 | 100 | class HTTPProxyAuth(HTTPBasicAuth): 101 | """Attaches HTTP Proxy Authentication to a given Request object.""" 102 | 103 | def __call__(self, r): 104 | r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) 105 | return r 106 | 107 | 108 | class HTTPDigestAuth(AuthBase): 109 | """Attaches HTTP Digest Authentication to the given Request object.""" 110 | 111 | def __init__(self, username, password): 112 | self.username = username 113 | self.password = password 114 | # Keep state in per-thread local storage 115 | self._thread_local = threading.local() 116 | 117 | def init_per_thread_state(self): 118 | # Ensure state is initialized just once per-thread 119 | if not hasattr(self._thread_local, 'init'): 120 | self._thread_local.init = True 121 | self._thread_local.last_nonce = '' 122 | self._thread_local.nonce_count = 0 123 | self._thread_local.chal = {} 124 | self._thread_local.pos = None 125 | self._thread_local.num_401_calls = None 126 | 127 | def build_digest_header(self, method, url): 128 | """ 129 | :rtype: str 130 | """ 131 | 132 | realm = self._thread_local.chal['realm'] 133 | nonce = self._thread_local.chal['nonce'] 134 | qop = self._thread_local.chal.get('qop') 135 | algorithm = self._thread_local.chal.get('algorithm') 136 | opaque = self._thread_local.chal.get('opaque') 137 | hash_utf8 = None 138 | 139 | if algorithm is None: 140 | _algorithm = 'MD5' 141 | else: 142 | _algorithm = algorithm.upper() 143 | # lambdas assume digest modules are imported at the top level 144 | if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': 145 | def md5_utf8(x): 146 | if isinstance(x, str): 147 | x = x.encode('utf-8') 148 | return hashlib.md5(x).hexdigest() 149 | hash_utf8 = md5_utf8 150 | elif _algorithm == 'SHA': 151 | def sha_utf8(x): 152 | if isinstance(x, str): 153 | x = x.encode('utf-8') 154 | return hashlib.sha1(x).hexdigest() 155 | hash_utf8 = sha_utf8 156 | elif _algorithm == 'SHA-256': 157 | def sha256_utf8(x): 158 | if isinstance(x, str): 159 | x = x.encode('utf-8') 160 | return hashlib.sha256(x).hexdigest() 161 | hash_utf8 = sha256_utf8 162 | elif _algorithm == 'SHA-512': 163 | def sha512_utf8(x): 164 | if isinstance(x, str): 165 | x = x.encode('utf-8') 166 | return hashlib.sha512(x).hexdigest() 167 | hash_utf8 = sha512_utf8 168 | 169 | KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) 170 | 171 | if hash_utf8 is None: 172 | return None 173 | 174 | # XXX not implemented yet 175 | entdig = None 176 | p_parsed = urlparse(url) 177 | #: path is request-uri defined in RFC 2616 which should not be empty 178 | path = p_parsed.path or "/" 179 | if p_parsed.query: 180 | path += '?' + p_parsed.query 181 | 182 | A1 = '%s:%s:%s' % (self.username, realm, self.password) 183 | A2 = '%s:%s' % (method, path) 184 | 185 | HA1 = hash_utf8(A1) 186 | HA2 = hash_utf8(A2) 187 | 188 | if nonce == self._thread_local.last_nonce: 189 | self._thread_local.nonce_count += 1 190 | else: 191 | self._thread_local.nonce_count = 1 192 | ncvalue = '%08x' % self._thread_local.nonce_count 193 | s = str(self._thread_local.nonce_count).encode('utf-8') 194 | s += nonce.encode('utf-8') 195 | s += time.ctime().encode('utf-8') 196 | s += os.urandom(8) 197 | 198 | cnonce = (hashlib.sha1(s).hexdigest()[:16]) 199 | if _algorithm == 'MD5-SESS': 200 | HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) 201 | 202 | if not qop: 203 | respdig = KD(HA1, "%s:%s" % (nonce, HA2)) 204 | elif qop == 'auth' or 'auth' in qop.split(','): 205 | noncebit = "%s:%s:%s:%s:%s" % ( 206 | nonce, ncvalue, cnonce, 'auth', HA2 207 | ) 208 | respdig = KD(HA1, noncebit) 209 | else: 210 | # XXX handle auth-int. 211 | return None 212 | 213 | self._thread_local.last_nonce = nonce 214 | 215 | # XXX should the partial digests be encoded too? 216 | base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ 217 | 'response="%s"' % (self.username, realm, nonce, path, respdig) 218 | if opaque: 219 | base += ', opaque="%s"' % opaque 220 | if algorithm: 221 | base += ', algorithm="%s"' % algorithm 222 | if entdig: 223 | base += ', digest="%s"' % entdig 224 | if qop: 225 | base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) 226 | 227 | return 'Digest %s' % (base) 228 | 229 | def handle_redirect(self, r, **kwargs): 230 | """Reset num_401_calls counter on redirects.""" 231 | if r.is_redirect: 232 | self._thread_local.num_401_calls = 1 233 | 234 | def handle_401(self, r, **kwargs): 235 | """ 236 | Takes the given response and tries digest-auth, if needed. 237 | 238 | :rtype: requests.Response 239 | """ 240 | 241 | # If response is not 4xx, do not auth 242 | # See https://github.com/psf/requests/issues/3772 243 | if not 400 <= r.status_code < 500: 244 | self._thread_local.num_401_calls = 1 245 | return r 246 | 247 | if self._thread_local.pos is not None: 248 | # Rewind the file position indicator of the body to where 249 | # it was to resend the request. 250 | r.request.body.seek(self._thread_local.pos) 251 | s_auth = r.headers.get('www-authenticate', '') 252 | 253 | if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: 254 | 255 | self._thread_local.num_401_calls += 1 256 | pat = re.compile(r'digest ', flags=re.IGNORECASE) 257 | self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) 258 | 259 | # Consume content and release the original connection 260 | # to allow our new request to reuse the same one. 261 | r.content 262 | r.close() 263 | prep = r.request.copy() 264 | extract_cookies_to_jar(prep._cookies, r.request, r.raw) 265 | prep.prepare_cookies(prep._cookies) 266 | 267 | prep.headers['Authorization'] = self.build_digest_header( 268 | prep.method, prep.url) 269 | _r = r.connection.send(prep, **kwargs) 270 | _r.history.append(r) 271 | _r.request = prep 272 | 273 | return _r 274 | 275 | self._thread_local.num_401_calls = 1 276 | return r 277 | 278 | def __call__(self, r): 279 | # Initialize per-thread state, if needed 280 | self.init_per_thread_state() 281 | # If we have a saved nonce, skip the 401 282 | if self._thread_local.last_nonce: 283 | r.headers['Authorization'] = self.build_digest_header(r.method, r.url) 284 | try: 285 | self._thread_local.pos = r.body.tell() 286 | except AttributeError: 287 | # In the case of HTTPDigestAuth being reused and the body of 288 | # the previous request was a file-like object, pos has the 289 | # file position of the previous body. Ensure it's set to 290 | # None. 291 | self._thread_local.pos = None 292 | r.register_hook('response', self.handle_401) 293 | r.register_hook('response', self.handle_redirect) 294 | self._thread_local.num_401_calls = 1 295 | 296 | return r 297 | 298 | def __eq__(self, other): 299 | return all([ 300 | self.username == getattr(other, 'username', None), 301 | self.password == getattr(other, 'password', None) 302 | ]) 303 | 304 | def __ne__(self, other): 305 | return not self == other 306 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Requests documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Feb 19 00:05:47 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # Insert Requests' path into the system. 24 | sys.path.insert(0, os.path.abspath("..")) 25 | sys.path.insert(0, os.path.abspath("_themes")) 26 | 27 | import requests 28 | 29 | 30 | # -- General configuration ------------------------------------------------ 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | # needs_sphinx = '1.0' 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [ 39 | "sphinx.ext.autodoc", 40 | "sphinx.ext.intersphinx", 41 | "sphinx.ext.todo", 42 | "sphinx.ext.viewcode", 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ["_templates"] 47 | 48 | # The suffix(es) of source filenames. 49 | # You can specify multiple suffix as a list of string: 50 | # source_suffix = ['.rst', '.md'] 51 | source_suffix = ".rst" 52 | 53 | # The encoding of source files. 54 | # source_encoding = 'utf-8-sig' 55 | 56 | # The master toctree document. 57 | master_doc = "index" 58 | 59 | # General information about the project. 60 | project = u"Requests" 61 | copyright = u'MMXVIX. A Kenneth Reitz Project' 62 | author = u"Kenneth Reitz" 63 | 64 | # The version info for the project you're documenting, acts as replacement for 65 | # |version| and |release|, also used in various other places throughout the 66 | # built documents. 67 | # 68 | # The short X.Y version. 69 | version = requests.__version__ 70 | # The full version, including alpha/beta/rc tags. 71 | release = requests.__version__ 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # 76 | # This is also used if you do content translation via gettext catalogs. 77 | # Usually you set "language" from the command line for these cases. 78 | language = None 79 | 80 | # There are two options for replacing |today|: either, you set today to some 81 | # non-false value, then it is used: 82 | # today = '' 83 | # Else, today_fmt is used as the format for a strftime call. 84 | # today_fmt = '%B %d, %Y' 85 | 86 | # List of patterns, relative to source directory, that match files and 87 | # directories to ignore when looking for source files. 88 | exclude_patterns = ["_build"] 89 | 90 | # The reST default role (used for this markup: `text`) to use for all 91 | # documents. 92 | # default_role = None 93 | 94 | # If true, '()' will be appended to :func: etc. cross-reference text. 95 | add_function_parentheses = False 96 | 97 | # If true, the current module name will be prepended to all description 98 | # unit titles (such as .. function::). 99 | add_module_names = True 100 | 101 | # If true, sectionauthor and moduleauthor directives will be shown in the 102 | # output. They are ignored by default. 103 | # show_authors = False 104 | 105 | # The name of the Pygments (syntax highlighting) style to use. 106 | pygments_style = "flask_theme_support.FlaskyStyle" 107 | 108 | # A list of ignored prefixes for module index sorting. 109 | # modindex_common_prefix = [] 110 | 111 | # If true, keep warnings as "system message" paragraphs in the built documents. 112 | # keep_warnings = False 113 | 114 | # If true, `todo` and `todoList` produce output, else they produce nothing. 115 | todo_include_todos = True 116 | 117 | 118 | # -- Options for HTML output ---------------------------------------------- 119 | 120 | # The theme to use for HTML and HTML Help pages. See the documentation for 121 | # a list of builtin themes. 122 | html_theme = "alabaster" 123 | 124 | # Theme options are theme-specific and customize the look and feel of a theme 125 | # further. For a list of options available for each theme, see the 126 | # documentation. 127 | html_theme_options = { 128 | "show_powered_by": False, 129 | "github_user": "requests", 130 | "github_repo": "requests", 131 | "github_banner": True, 132 | "show_related": False, 133 | "note_bg": "#FFF59C", 134 | } 135 | 136 | # Add any paths that contain custom themes here, relative to this directory. 137 | # html_theme_path = [] 138 | 139 | # The name for this set of Sphinx documents. If None, it defaults to 140 | # " v documentation". 141 | # html_title = None 142 | 143 | # A shorter title for the navigation bar. Default is the same as html_title. 144 | # html_short_title = None 145 | 146 | # The name of an image file (relative to this directory) to place at the top 147 | # of the sidebar. 148 | # html_logo = None 149 | 150 | # The name of an image file (within the static path) to use as favicon of the 151 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 152 | # pixels large. 153 | # html_favicon = None 154 | 155 | # Add any paths that contain custom static files (such as style sheets) here, 156 | # relative to this directory. They are copied after the builtin static files, 157 | # so a file named "default.css" will overwrite the builtin "default.css". 158 | html_static_path = ["_static"] 159 | 160 | # Add any extra paths that contain custom files (such as robots.txt or 161 | # .htaccess) here, relative to this directory. These files are copied 162 | # directly to the root of the documentation. 163 | # html_extra_path = [] 164 | 165 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 166 | # using the given strftime format. 167 | # html_last_updated_fmt = '%b %d, %Y' 168 | 169 | # If true, SmartyPants will be used to convert quotes and dashes to 170 | # typographically correct entities. 171 | html_use_smartypants = False 172 | 173 | # Custom sidebar templates, maps document names to template names. 174 | html_sidebars = { 175 | "index": ["sidebarintro.html", "sourcelink.html", "searchbox.html", "hacks.html"], 176 | "**": [ 177 | "sidebarlogo.html", 178 | "localtoc.html", 179 | "relations.html", 180 | "sourcelink.html", 181 | "searchbox.html", 182 | "hacks.html", 183 | ], 184 | } 185 | 186 | # Additional templates that should be rendered to pages, maps page names to 187 | # template names. 188 | # html_additional_pages = {} 189 | 190 | # If false, no module index is generated. 191 | # html_domain_indices = True 192 | 193 | # If false, no index is generated. 194 | # html_use_index = True 195 | 196 | # If true, the index is split into individual pages for each letter. 197 | # html_split_index = False 198 | 199 | # If true, links to the reST sources are added to the pages. 200 | html_show_sourcelink = False 201 | 202 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 203 | html_show_sphinx = False 204 | 205 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 206 | html_show_copyright = True 207 | 208 | # If true, an OpenSearch description file will be output, and all pages will 209 | # contain a tag referring to it. The value of this option must be the 210 | # base URL from which the finished HTML is served. 211 | # html_use_opensearch = '' 212 | 213 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 214 | # html_file_suffix = None 215 | 216 | # Language to be used for generating the HTML full-text search index. 217 | # Sphinx supports the following languages: 218 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 219 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 220 | # html_search_language = 'en' 221 | 222 | # A dictionary with options for the search language support, empty by default. 223 | # Now only 'ja' uses this config value 224 | # html_search_options = {'type': 'default'} 225 | 226 | # The name of a javascript file (relative to the configuration directory) that 227 | # implements a search results scorer. If empty, the default will be used. 228 | # html_search_scorer = 'scorer.js' 229 | 230 | # Output file base name for HTML help builder. 231 | htmlhelp_basename = "Requestsdoc" 232 | 233 | # -- Options for LaTeX output --------------------------------------------- 234 | 235 | latex_elements = { 236 | # The paper size ('letterpaper' or 'a4paper'). 237 | #'papersize': 'letterpaper', 238 | # The font size ('10pt', '11pt' or '12pt'). 239 | #'pointsize': '10pt', 240 | # Additional stuff for the LaTeX preamble. 241 | #'preamble': '', 242 | # Latex figure (float) alignment 243 | #'figure_align': 'htbp', 244 | } 245 | 246 | # Grouping the document tree into LaTeX files. List of tuples 247 | # (source start file, target name, title, 248 | # author, documentclass [howto, manual, or own class]). 249 | latex_documents = [ 250 | (master_doc, "Requests.tex", u"Requests Documentation", u"Kenneth Reitz", "manual") 251 | ] 252 | 253 | # The name of an image file (relative to this directory) to place at the top of 254 | # the title page. 255 | # latex_logo = None 256 | 257 | # For "manual" documents, if this is true, then toplevel headings are parts, 258 | # not chapters. 259 | # latex_use_parts = False 260 | 261 | # If true, show page references after internal links. 262 | # latex_show_pagerefs = False 263 | 264 | # If true, show URL addresses after external links. 265 | # latex_show_urls = False 266 | 267 | # Documents to append as an appendix to all manuals. 268 | # latex_appendices = [] 269 | 270 | # If false, no module index is generated. 271 | # latex_domain_indices = True 272 | 273 | 274 | # -- Options for manual page output --------------------------------------- 275 | 276 | # One entry per manual page. List of tuples 277 | # (source start file, name, description, authors, manual section). 278 | man_pages = [(master_doc, "requests", u"Requests Documentation", [author], 1)] 279 | 280 | # If true, show URL addresses after external links. 281 | # man_show_urls = False 282 | 283 | 284 | # -- Options for Texinfo output ------------------------------------------- 285 | 286 | # Grouping the document tree into Texinfo files. List of tuples 287 | # (source start file, target name, title, author, 288 | # dir menu entry, description, category) 289 | texinfo_documents = [ 290 | ( 291 | master_doc, 292 | "Requests", 293 | u"Requests Documentation", 294 | author, 295 | "Requests", 296 | "One line description of project.", 297 | "Miscellaneous", 298 | ) 299 | ] 300 | 301 | # Documents to append as an appendix to all manuals. 302 | # texinfo_appendices = [] 303 | 304 | # If false, no module index is generated. 305 | # texinfo_domain_indices = True 306 | 307 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 308 | # texinfo_show_urls = 'footnote' 309 | 310 | # If true, do not generate a @detailmenu in the "Top" node's menu. 311 | # texinfo_no_detailmenu = False 312 | 313 | 314 | # -- Options for Epub output ---------------------------------------------- 315 | 316 | # Bibliographic Dublin Core info. 317 | epub_title = project 318 | epub_author = author 319 | epub_publisher = author 320 | epub_copyright = copyright 321 | 322 | # The basename for the epub file. It defaults to the project name. 323 | # epub_basename = project 324 | 325 | # The HTML theme for the epub output. Since the default themes are not 326 | # optimized for small screen space, using the same theme for HTML and epub 327 | # output is usually not wise. This defaults to 'epub', a theme designed to save 328 | # visual space. 329 | # epub_theme = 'epub' 330 | 331 | # The language of the text. It defaults to the language option 332 | # or 'en' if the language is not set. 333 | # epub_language = '' 334 | 335 | # The scheme of the identifier. Typical schemes are ISBN or URL. 336 | # epub_scheme = '' 337 | 338 | # The unique identifier of the text. This can be a ISBN number 339 | # or the project homepage. 340 | # epub_identifier = '' 341 | 342 | # A unique identification for the text. 343 | # epub_uid = '' 344 | 345 | # A tuple containing the cover image and cover page html template filenames. 346 | # epub_cover = () 347 | 348 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 349 | # epub_guide = () 350 | 351 | # HTML files that should be inserted before the pages created by sphinx. 352 | # The format is a list of tuples containing the path and title. 353 | # epub_pre_files = [] 354 | 355 | # HTML files that should be inserted after the pages created by sphinx. 356 | # The format is a list of tuples containing the path and title. 357 | # epub_post_files = [] 358 | 359 | # A list of files that should not be packed into the epub file. 360 | epub_exclude_files = ["search.html"] 361 | 362 | # The depth of the table of contents in toc.ncx. 363 | # epub_tocdepth = 3 364 | 365 | # Allow duplicate toc entries. 366 | # epub_tocdup = True 367 | 368 | # Choose between 'default' and 'includehidden'. 369 | # epub_tocscope = 'default' 370 | 371 | # Fix unsupported image types using the Pillow. 372 | # epub_fix_images = False 373 | 374 | # Scale large images. 375 | # epub_max_image_width = 0 376 | 377 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 378 | # epub_show_urls = 'inline' 379 | 380 | # If false, no index is generated. 381 | # epub_use_index = True 382 | 383 | intersphinx_mapping = { 384 | "python": ("https://docs.python.org/3/", None), 385 | "urllib3": ("https://urllib3.readthedocs.io/en/latest", None), 386 | } 387 | -------------------------------------------------------------------------------- /tests/test_lowlevel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | import threading 5 | import requests 6 | 7 | from tests.testserver.server import Server, consume_socket_content 8 | 9 | from .utils import override_environ 10 | 11 | 12 | def test_chunked_upload(): 13 | """can safely send generators""" 14 | close_server = threading.Event() 15 | server = Server.basic_response_server(wait_to_close_event=close_server) 16 | data = iter([b'a', b'b', b'c']) 17 | 18 | with server as (host, port): 19 | url = 'http://{}:{}/'.format(host, port) 20 | r = requests.post(url, data=data, stream=True) 21 | close_server.set() # release server block 22 | 23 | assert r.status_code == 200 24 | assert r.request.headers['Transfer-Encoding'] == 'chunked' 25 | 26 | 27 | def test_chunked_encoding_error(): 28 | """get a ChunkedEncodingError if the server returns a bad response""" 29 | 30 | def incomplete_chunked_response_handler(sock): 31 | request_content = consume_socket_content(sock, timeout=0.5) 32 | 33 | # The server never ends the request and doesn't provide any valid chunks 34 | sock.send(b"HTTP/1.1 200 OK\r\n" + 35 | b"Transfer-Encoding: chunked\r\n") 36 | 37 | return request_content 38 | 39 | close_server = threading.Event() 40 | server = Server(incomplete_chunked_response_handler) 41 | 42 | with server as (host, port): 43 | url = 'http://{}:{}/'.format(host, port) 44 | with pytest.raises(requests.exceptions.ChunkedEncodingError): 45 | r = requests.get(url) 46 | close_server.set() # release server block 47 | 48 | 49 | def test_conflicting_content_lengths(): 50 | """Ensure we correctly throw an InvalidHeader error if multiple 51 | conflicting Content-Length headers are returned. 52 | """ 53 | 54 | def multiple_content_length_response_handler(sock): 55 | request_content = consume_socket_content(sock, timeout=0.5) 56 | 57 | sock.send(b"HTTP/1.1 200 OK\r\n" + 58 | b"Content-Type: text/plain\r\n" + 59 | b"Content-Length: 16\r\n" + 60 | b"Content-Length: 32\r\n\r\n" + 61 | b"-- Bad Actor -- Original Content\r\n") 62 | 63 | return request_content 64 | 65 | close_server = threading.Event() 66 | server = Server(multiple_content_length_response_handler) 67 | 68 | with server as (host, port): 69 | url = 'http://{}:{}/'.format(host, port) 70 | with pytest.raises(requests.exceptions.InvalidHeader): 71 | r = requests.get(url) 72 | close_server.set() 73 | 74 | 75 | def test_digestauth_401_count_reset_on_redirect(): 76 | """Ensure we correctly reset num_401_calls after a successful digest auth, 77 | followed by a 302 redirect to another digest auth prompt. 78 | 79 | See https://github.com/psf/requests/issues/1979. 80 | """ 81 | text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n' 82 | b'Content-Length: 0\r\n' 83 | b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"' 84 | b', opaque="372825293d1c26955496c80ed6426e9e", ' 85 | b'realm="me@kennethreitz.com", qop=auth\r\n\r\n') 86 | 87 | text_302 = (b'HTTP/1.1 302 FOUND\r\n' 88 | b'Content-Length: 0\r\n' 89 | b'Location: /\r\n\r\n') 90 | 91 | text_200 = (b'HTTP/1.1 200 OK\r\n' 92 | b'Content-Length: 0\r\n\r\n') 93 | 94 | expected_digest = (b'Authorization: Digest username="user", ' 95 | b'realm="me@kennethreitz.com", ' 96 | b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"') 97 | 98 | auth = requests.auth.HTTPDigestAuth('user', 'pass') 99 | 100 | def digest_response_handler(sock): 101 | # Respond to initial GET with a challenge. 102 | request_content = consume_socket_content(sock, timeout=0.5) 103 | assert request_content.startswith(b"GET / HTTP/1.1") 104 | sock.send(text_401) 105 | 106 | # Verify we receive an Authorization header in response, then redirect. 107 | request_content = consume_socket_content(sock, timeout=0.5) 108 | assert expected_digest in request_content 109 | sock.send(text_302) 110 | 111 | # Verify Authorization isn't sent to the redirected host, 112 | # then send another challenge. 113 | request_content = consume_socket_content(sock, timeout=0.5) 114 | assert b'Authorization:' not in request_content 115 | sock.send(text_401) 116 | 117 | # Verify Authorization is sent correctly again, and return 200 OK. 118 | request_content = consume_socket_content(sock, timeout=0.5) 119 | assert expected_digest in request_content 120 | sock.send(text_200) 121 | 122 | return request_content 123 | 124 | close_server = threading.Event() 125 | server = Server(digest_response_handler, wait_to_close_event=close_server) 126 | 127 | with server as (host, port): 128 | url = 'http://{}:{}/'.format(host, port) 129 | r = requests.get(url, auth=auth) 130 | # Verify server succeeded in authenticating. 131 | assert r.status_code == 200 132 | # Verify Authorization was sent in final request. 133 | assert 'Authorization' in r.request.headers 134 | assert r.request.headers['Authorization'].startswith('Digest ') 135 | # Verify redirect happened as we expected. 136 | assert r.history[0].status_code == 302 137 | close_server.set() 138 | 139 | 140 | def test_digestauth_401_only_sent_once(): 141 | """Ensure we correctly respond to a 401 challenge once, and then 142 | stop responding if challenged again. 143 | """ 144 | text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n' 145 | b'Content-Length: 0\r\n' 146 | b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"' 147 | b', opaque="372825293d1c26955496c80ed6426e9e", ' 148 | b'realm="me@kennethreitz.com", qop=auth\r\n\r\n') 149 | 150 | expected_digest = (b'Authorization: Digest username="user", ' 151 | b'realm="me@kennethreitz.com", ' 152 | b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"') 153 | 154 | auth = requests.auth.HTTPDigestAuth('user', 'pass') 155 | 156 | def digest_failed_response_handler(sock): 157 | # Respond to initial GET with a challenge. 158 | request_content = consume_socket_content(sock, timeout=0.5) 159 | assert request_content.startswith(b"GET / HTTP/1.1") 160 | sock.send(text_401) 161 | 162 | # Verify we receive an Authorization header in response, then 163 | # challenge again. 164 | request_content = consume_socket_content(sock, timeout=0.5) 165 | assert expected_digest in request_content 166 | sock.send(text_401) 167 | 168 | # Verify the client didn't respond to second challenge. 169 | request_content = consume_socket_content(sock, timeout=0.5) 170 | assert request_content == b'' 171 | 172 | return request_content 173 | 174 | close_server = threading.Event() 175 | server = Server(digest_failed_response_handler, wait_to_close_event=close_server) 176 | 177 | with server as (host, port): 178 | url = 'http://{}:{}/'.format(host, port) 179 | r = requests.get(url, auth=auth) 180 | # Verify server didn't authenticate us. 181 | assert r.status_code == 401 182 | assert r.history[0].status_code == 401 183 | close_server.set() 184 | 185 | 186 | def test_digestauth_only_on_4xx(): 187 | """Ensure we only send digestauth on 4xx challenges. 188 | 189 | See https://github.com/psf/requests/issues/3772. 190 | """ 191 | text_200_chal = (b'HTTP/1.1 200 OK\r\n' 192 | b'Content-Length: 0\r\n' 193 | b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"' 194 | b', opaque="372825293d1c26955496c80ed6426e9e", ' 195 | b'realm="me@kennethreitz.com", qop=auth\r\n\r\n') 196 | 197 | auth = requests.auth.HTTPDigestAuth('user', 'pass') 198 | 199 | def digest_response_handler(sock): 200 | # Respond to GET with a 200 containing www-authenticate header. 201 | request_content = consume_socket_content(sock, timeout=0.5) 202 | assert request_content.startswith(b"GET / HTTP/1.1") 203 | sock.send(text_200_chal) 204 | 205 | # Verify the client didn't respond with auth. 206 | request_content = consume_socket_content(sock, timeout=0.5) 207 | assert request_content == b'' 208 | 209 | return request_content 210 | 211 | close_server = threading.Event() 212 | server = Server(digest_response_handler, wait_to_close_event=close_server) 213 | 214 | with server as (host, port): 215 | url = 'http://{}:{}/'.format(host, port) 216 | r = requests.get(url, auth=auth) 217 | # Verify server didn't receive auth from us. 218 | assert r.status_code == 200 219 | assert len(r.history) == 0 220 | close_server.set() 221 | 222 | 223 | _schemes_by_var_prefix = [ 224 | ('http', ['http']), 225 | ('https', ['https']), 226 | ('all', ['http', 'https']), 227 | ] 228 | 229 | _proxy_combos = [] 230 | for prefix, schemes in _schemes_by_var_prefix: 231 | for scheme in schemes: 232 | _proxy_combos.append(("{}_proxy".format(prefix), scheme)) 233 | 234 | _proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos] 235 | 236 | 237 | @pytest.mark.parametrize("var,scheme", _proxy_combos) 238 | def test_use_proxy_from_environment(httpbin, var, scheme): 239 | url = "{}://httpbin.org".format(scheme) 240 | fake_proxy = Server() # do nothing with the requests; just close the socket 241 | with fake_proxy as (host, port): 242 | proxy_url = "socks5://{}:{}".format(host, port) 243 | kwargs = {var: proxy_url} 244 | with override_environ(**kwargs): 245 | # fake proxy's lack of response will cause a ConnectionError 246 | with pytest.raises(requests.exceptions.ConnectionError): 247 | requests.get(url) 248 | 249 | # the fake proxy received a request 250 | assert len(fake_proxy.handler_results) == 1 251 | 252 | # it had actual content (not checking for SOCKS protocol for now) 253 | assert len(fake_proxy.handler_results[0]) > 0 254 | 255 | 256 | def test_redirect_rfc1808_to_non_ascii_location(): 257 | path = u'š' 258 | expected_path = b'%C5%A1' 259 | redirect_request = [] # stores the second request to the server 260 | 261 | def redirect_resp_handler(sock): 262 | consume_socket_content(sock, timeout=0.5) 263 | location = u'//{}:{}/{}'.format(host, port, path) 264 | sock.send( 265 | b'HTTP/1.1 301 Moved Permanently\r\n' 266 | b'Content-Length: 0\r\n' 267 | b'Location: ' + location.encode('utf8') + b'\r\n' 268 | b'\r\n' 269 | ) 270 | redirect_request.append(consume_socket_content(sock, timeout=0.5)) 271 | sock.send(b'HTTP/1.1 200 OK\r\n\r\n') 272 | 273 | close_server = threading.Event() 274 | server = Server(redirect_resp_handler, wait_to_close_event=close_server) 275 | 276 | with server as (host, port): 277 | url = u'http://{}:{}'.format(host, port) 278 | r = requests.get(url=url, allow_redirects=True) 279 | assert r.status_code == 200 280 | assert len(r.history) == 1 281 | assert r.history[0].status_code == 301 282 | assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1') 283 | assert r.url == u'{}/{}'.format(url, expected_path.decode('ascii')) 284 | 285 | close_server.set() 286 | 287 | def test_fragment_not_sent_with_request(): 288 | """Verify that the fragment portion of a URI isn't sent to the server.""" 289 | def response_handler(sock): 290 | req = consume_socket_content(sock, timeout=0.5) 291 | sock.send( 292 | b'HTTP/1.1 200 OK\r\n' 293 | b'Content-Length: '+bytes(len(req))+b'\r\n' 294 | b'\r\n'+req 295 | ) 296 | 297 | close_server = threading.Event() 298 | server = Server(response_handler, wait_to_close_event=close_server) 299 | 300 | with server as (host, port): 301 | url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port) 302 | r = requests.get(url) 303 | raw_request = r.content 304 | 305 | assert r.status_code == 200 306 | headers, body = raw_request.split(b'\r\n\r\n', 1) 307 | status_line, headers = headers.split(b'\r\n', 1) 308 | 309 | assert status_line == b'GET /path/to/thing/ HTTP/1.1' 310 | for frag in (b'view', b'edit', b'token', b'hunter2'): 311 | assert frag not in headers 312 | assert frag not in body 313 | 314 | close_server.set() 315 | 316 | def test_fragment_update_on_redirect(): 317 | """Verify we only append previous fragment if one doesn't exist on new 318 | location. If a new fragment is encountered in a Location header, it should 319 | be added to all subsequent requests. 320 | """ 321 | 322 | def response_handler(sock): 323 | consume_socket_content(sock, timeout=0.5) 324 | sock.send( 325 | b'HTTP/1.1 302 FOUND\r\n' 326 | b'Content-Length: 0\r\n' 327 | b'Location: /get#relevant-section\r\n\r\n' 328 | ) 329 | consume_socket_content(sock, timeout=0.5) 330 | sock.send( 331 | b'HTTP/1.1 302 FOUND\r\n' 332 | b'Content-Length: 0\r\n' 333 | b'Location: /final-url/\r\n\r\n' 334 | ) 335 | consume_socket_content(sock, timeout=0.5) 336 | sock.send( 337 | b'HTTP/1.1 200 OK\r\n\r\n' 338 | ) 339 | 340 | close_server = threading.Event() 341 | server = Server(response_handler, wait_to_close_event=close_server) 342 | 343 | with server as (host, port): 344 | url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port) 345 | r = requests.get(url) 346 | raw_request = r.content 347 | 348 | assert r.status_code == 200 349 | assert len(r.history) == 2 350 | assert r.history[0].request.url == url 351 | 352 | # Verify we haven't overwritten the location with our previous fragment. 353 | assert r.history[1].request.url == 'http://{}:{}/get#relevant-section'.format(host, port) 354 | # Verify previous fragment is used and not the original. 355 | assert r.url == 'http://{}:{}/final-url/#relevant-section'.format(host, port) 356 | 357 | close_server.set() 358 | --------------------------------------------------------------------------------