├── .coveragerc ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── _appveyor └── install.ps1 ├── appveyor.yml ├── docs ├── MANIFEST.in ├── Makefile ├── _static │ ├── custom.css │ ├── konami.js │ ├── requests-logo-small.png │ └── requests-sidebar.png ├── _templates │ ├── hacks.html │ ├── sidebarintro.html │ └── sidebarlogo.html ├── _themes │ ├── .gitignore │ ├── LICENSE │ └── flask_theme_support.py ├── api.rst ├── community │ ├── faq.rst │ ├── out-there.rst │ ├── recommended.rst │ ├── release-process.rst │ ├── support.rst │ ├── updates.rst │ └── vulnerabilities.rst ├── conf.py ├── dev │ ├── authors.rst │ ├── contributing.rst │ ├── philosophy.rst │ └── todo.rst ├── index.rst ├── make.bat └── user │ ├── advanced.rst │ ├── authentication.rst │ ├── install.rst │ ├── intro.rst │ └── quickstart.rst ├── ext ├── requests-logo.ai └── requests-logo.svg ├── pytest.ini ├── requests ├── __init__.py ├── __version__.py ├── _internal_utils.py ├── adapters.py ├── api.py ├── auth.py ├── certs.py ├── compat.py ├── cookies.py ├── exceptions.py ├── help.py ├── hooks.py ├── models.py ├── packages.py ├── sessions.py ├── status_codes.py ├── structures.py └── utils.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── compat.py ├── conftest.py ├── test_help.py ├── test_hooks.py ├── test_lowlevel.py ├── test_packages.py ├── test_requests.py ├── test_structures.py ├── test_testserver.py ├── test_utils.py ├── testserver │ ├── __init__.py │ └── server.py └── utils.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = requests/packages/* -------------------------------------------------------------------------------- /.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). -------------------------------------------------------------------------------- /.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 | 18 | .workon 19 | 20 | t.py 21 | 22 | t2.py 23 | dist 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - "2.6" 5 | - "2.7" 6 | - "3.3" 7 | - "3.4" 8 | - "3.5" 9 | - "3.6" 10 | # - "3.7-dev" 11 | # - "pypy" -- appears to hang 12 | # - "pypy3" 13 | # command to install dependencies 14 | install: "make" 15 | # command to run tests 16 | script: 17 | - | 18 | if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi 19 | - make ci 20 | cache: pip 21 | jobs: 22 | include: 23 | - stage: test 24 | script: 25 | - | 26 | if [[ "$TRAVIS_PYTHON_VERSION" != "2.6" ]] ; then make test-readme; fi 27 | - make ci 28 | - stage: coverage 29 | python: 3.6 30 | script: codecov 31 | 32 | 33 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Requests is written and maintained by Kenneth Reitz and 2 | various contributors: 3 | 4 | Keepers of the Four Crystals 5 | ```````````````````````````` 6 | 7 | - Kenneth Reitz `@kennethreitz `_, Keeper of the Master Crystal. 8 | - Cory Benfield `@lukasa `_ 9 | - Ian Cordasco `@sigmavirus24 `_ 10 | - Nate Prewitt `@nateprewitt `_ 11 | 12 | 13 | 14 | Urllib3 15 | ``````` 16 | 17 | - Andrey Petrov 18 | 19 | 20 | Patches and Suggestions 21 | ``````````````````````` 22 | 23 | - Various Pocoo Members 24 | - Chris Adams 25 | - Flavio Percoco Premoli 26 | - Dj Gilcrease 27 | - Justin Murphy 28 | - Rob Madole 29 | - Aram Dulyan 30 | - Johannes Gorset 31 | - 村山めがね (Megane Murayama) 32 | - James Rowe 33 | - Daniel Schauenberg 34 | - Zbigniew Siciarz 35 | - Daniele Tricoli 'Eriol' 36 | - Richard Boulton 37 | - Miguel Olivares 38 | - Alberto Paro 39 | - Jérémy Bethmont 40 | - 潘旭 (Xu Pan) 41 | - Tamás Gulácsi 42 | - Rubén Abad 43 | - Peter Manser 44 | - Jeremy Selier 45 | - Jens Diemer 46 | - Alex (`@alopatin `_) 47 | - Tom Hogans 48 | - Armin Ronacher 49 | - Shrikant Sharat Kandula 50 | - Mikko Ohtamaa 51 | - Den Shabalin 52 | - Daniel Miller 53 | - Alejandro Giacometti 54 | - Rick Mak 55 | - Johan Bergström 56 | - Josselin Jacquard 57 | - Travis N. Vaught 58 | - Fredrik Möllerstrand 59 | - Daniel Hengeveld 60 | - Dan Head 61 | - Bruno Renié 62 | - David Fischer 63 | - Joseph McCullough 64 | - Juergen Brendel 65 | - Juan Riaza 66 | - Ryan Kelly 67 | - Rolando Espinoza La fuente 68 | - Robert Gieseke 69 | - Idan Gazit 70 | - Ed Summers 71 | - Chris Van Horne 72 | - Christopher Davis 73 | - Ori Livneh 74 | - Jason Emerick 75 | - Bryan Helmig 76 | - Jonas Obrist 77 | - Lucian Ursu 78 | - Tom Moertel 79 | - Frank Kumro Jr 80 | - Chase Sterling 81 | - Marty Alchin 82 | - takluyver 83 | - Ben Toews (`@mastahyeti `_) 84 | - David Kemp 85 | - Brendon Crawford 86 | - Denis (`@Telofy `_) 87 | - Matt Giuca 88 | - Adam Tauber 89 | - Honza Javorek 90 | - Brendan Maguire 91 | - Chris Dary 92 | - Danver Braganza 93 | - Max Countryman 94 | - Nick Chadwick 95 | - Jonathan Drosdeck 96 | - Jiri Machalek 97 | - Steve Pulec 98 | - Michael Kelly 99 | - Michael Newman 100 | - Jonty Wareing 101 | - Shivaram Lingamneni 102 | - Miguel Turner 103 | - Rohan Jain (`@crodjer `_) 104 | - Justin Barber 105 | - Roman Haritonov (`@reclosedev `_) 106 | - Josh Imhoff 107 | - Arup Malakar 108 | - Danilo Bargen (`@dbrgn `_) 109 | - Torsten Landschoff 110 | - Michael Holler (`@apotheos `_) 111 | - Timnit Gebru 112 | - Sarah Gonzalez 113 | - Victoria Mo 114 | - Leila Muhtasib 115 | - Matthias Rahlf 116 | - Jakub Roztocil 117 | - Rhys Elsmore 118 | - André Graf (`@dergraf `_) 119 | - Stephen Zhuang (`@everbird `_) 120 | - Martijn Pieters 121 | - Jonatan Heyman 122 | - David Bonner (`@rascalking `_) 123 | - Vinod Chandru 124 | - Johnny Goodnow 125 | - Denis Ryzhkov 126 | - Wilfred Hughes 127 | - Dmitry Medvinsky 128 | - Bryce Boe (`@bboe `_) 129 | - Colin Dunklau (`@cdunklau `_) 130 | - Bob Carroll (`@rcarz `_) 131 | - Hugo Osvaldo Barrera (`@hobarrera `_) 132 | - Łukasz Langa 133 | - Dave Shawley 134 | - James Clarke (`@jam `_) 135 | - Kevin Burke 136 | - Flavio Curella 137 | - David Pursehouse (`@dpursehouse `_) 138 | - Jon Parise (`@jparise `_) 139 | - Alexander Karpinsky (`@homm86 `_) 140 | - Marc Schlaich (`@schlamar `_) 141 | - Park Ilsu (`@daftshady `_) 142 | - Matt Spitz (`@mattspitz `_) 143 | - Vikram Oberoi (`@voberoi `_) 144 | - Can Ibanoglu (`@canibanoglu `_) 145 | - Thomas Weißschuh (`@t-8ch `_) 146 | - Jayson Vantuyl 147 | - Pengfei.X 148 | - Kamil Madac 149 | - Michael Becker (`@beckerfuffle `_) 150 | - Erik Wickstrom (`@erikwickstrom `_) 151 | - Константин Подшумок (`@podshumok `_) 152 | - Ben Bass (`@codedstructure `_) 153 | - Jonathan Wong (`@ContinuousFunction `_) 154 | - Martin Jul (`@mjul `_) 155 | - Joe Alcorn (`@buttscicles `_) 156 | - Syed Suhail Ahmed (`@syedsuhail `_) 157 | - Scott Sadler (`@ssadler `_) 158 | - Arthur Darcet (`@arthurdarcet `_) 159 | - Ulrich Petri (`@ulope `_) 160 | - Muhammad Yasoob Ullah Khalid (`@yasoob `_) 161 | - Paul van der Linden (`@pvanderlinden `_) 162 | - Colin Dickson (`@colindickson `_) 163 | - Smiley Barry (`@smiley `_) 164 | - Shagun Sodhani (`@shagunsodhani `_) 165 | - Robin Linderborg (`@vienno `_) 166 | - Brian Samek (`@bsamek `_) 167 | - Dmitry Dygalo (`@Stranger6667 `_) 168 | - piotrjurkiewicz 169 | - Jesse Shapiro (`@haikuginger `_) 170 | - Nate Prewitt (`@nateprewitt `_) 171 | - Maik Himstedt 172 | - Michael Hunsinger 173 | - Brian Bamsch (`@bbamsch `_) 174 | - Om Prakash Kumar (`@iamprakashom `_) 175 | - Philipp Konrad (`@gardiac2002 `_) 176 | - Hussain Tamboli (`@hussaintamboli `_) 177 | - Casey Davidson (`@davidsoncasey `_) 178 | - Andrii Soldatenko (`@a_soldatenko `_) 179 | - Moinuddin Quadri (`@moin18 `_) 180 | - Matt Kohl (`@mattkohl `_) 181 | - Jonathan Vanasco (`@jvanasco `_) 182 | - David Fontenot (`@davidfontenot `_) 183 | - Shmuel Amar (`@shmuelamar `_) 184 | - Gary Wu (`@garywu `_) 185 | - Ryan Pineo (`@ryanpineo `_) 186 | - Ed Morley (`@edmorley `_) 187 | - Matt Liu (`@mlcrazy `_) 188 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Be cordial or be on your way. 2 | 3 | https://www.kennethreitz.org/essays/be-cordial-or-be-on-your-way 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Before opening any issues or proposing any pull requests, please do the 4 | following: 5 | 6 | 1. Read our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/). 7 | 2. Understand our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/). 8 | 9 | To get the greatest chance of helpful responses, please also observe the 10 | following additional notes. 11 | 12 | ## Questions 13 | 14 | The GitHub issue tracker is for *bug reports* and *feature requests*. Please do 15 | not use it to ask questions about how to use Requests. These questions should 16 | instead be directed to [Stack Overflow](https://stackoverflow.com/). Make sure 17 | that your question is tagged with the `python-requests` tag when asking it on 18 | Stack Overflow, to ensure that it is answered promptly and accurately. 19 | 20 | ## Good Bug Reports 21 | 22 | Please be aware of the following things when filing bug reports: 23 | 24 | 1. Avoid raising duplicate issues. *Please* use the GitHub issue search feature 25 | to check whether your bug report or feature request has been mentioned in 26 | the past. Duplicate bug reports and feature requests are a huge maintenance 27 | burden on the limited resources of the project. If it is clear from your 28 | report that you would have struggled to find the original, that's ok, but 29 | if searching for a selection of words in your issue title would have found 30 | the duplicate then the issue will likely be closed extremely abruptly. 31 | 2. When filing bug reports about exceptions or tracebacks, please include the 32 | *complete* traceback. Partial tracebacks, or just the exception text, are 33 | not helpful. Issues that do not contain complete tracebacks may be closed 34 | without warning. 35 | 3. Make sure you provide a suitable amount of information to work with. This 36 | means you should provide: 37 | 38 | - Guidance on **how to reproduce the issue**. Ideally, this should be a 39 | *small* code sample that can be run immediately by the maintainers. 40 | Failing that, let us know what you're doing, how often it happens, what 41 | environment you're using, etc. Be thorough: it prevents us needing to ask 42 | further questions. 43 | - Tell us **what you expected to happen**. When we run your example code, 44 | what are we expecting to happen? What does "success" look like for your 45 | code? 46 | - Tell us **what actually happens**. It's not helpful for you to say "it 47 | doesn't work" or "it fails". Tell us *how* it fails: do you get an 48 | exception? A hang? A non-200 status code? How was the actual result 49 | different from your expected result? 50 | - Tell us **what version of Requests you're using**, and 51 | **how you installed it**. Different versions of Requests behave 52 | differently and have different bugs, and some distributors of Requests 53 | ship patches on top of the code we supply. 54 | 55 | If you do not provide all of these things, it will take us much longer to 56 | fix your problem. If we ask you to clarify these and you never respond, we 57 | will close your issue without fixing it. 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Kenneth Reitz 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE NOTICE HISTORY.rst pytest.ini requirements.txt 2 | recursive-include tests *.py 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs 2 | init: 3 | pip install -r requirements.txt 4 | test: 5 | # This runs all of the tests, on both Python 2 and Python 3. 6 | detox 7 | ci: 8 | py.test -n 8 --boxed --junitxml=report.xml 9 | 10 | test-readme: 11 | @python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!" 12 | 13 | flake8: 14 | flake8 --ignore=E501,F401,E128,E402,E731,F821 requests 15 | 16 | coverage: 17 | py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests 18 | 19 | publish: 20 | pip install 'twine>=1.5.0' 21 | python setup.py sdist bdist_wheel 22 | twine upload dist/* 23 | rm -fr build dist .egg requests.egg-info 24 | 25 | docs: 26 | cd docs && make html 27 | @echo "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m" -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Requests: HTTP for Humans 2 | ========================= 3 | 4 | .. image:: https://img.shields.io/pypi/v/requests.svg 5 | :target: https://pypi.python.org/pypi/requests 6 | 7 | Requests is the only *Non-GMO* HTTP library for Python, safe for human 8 | consumption. 9 | 10 | **Warning:** Recreational use of other HTTP libraries may result in dangerous side-effects, 11 | including: security vulnerabilities, verbose code, reinventing the wheel, 12 | constantly reading documentation, depression, headaches, or even death. 13 | 14 | Behold, the power of Requests: 15 | 16 | .. code-block:: python 17 | 18 | >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) 19 | >>> r.status_code 20 | 200 21 | >>> r.headers['content-type'] 22 | 'application/json; charset=utf8' 23 | >>> r.encoding 24 | 'utf-8' 25 | >>> r.text 26 | u'{"type":"User"...' 27 | >>> r.json() 28 | {u'disk_usage': 368627, u'private_gists': 484, ...} 29 | 30 | See `the similar code, sans Requests `_. 31 | 32 | Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, without the 33 | need for manual labor. There's no need to manually add query strings to your 34 | URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling 35 | are 100% automatic, powered by `urllib3 `_, 36 | which is embedded within Requests. 37 | 38 | Besides, all the cool kids are doing it. Requests is one of the most 39 | downloaded Python packages of all time, pulling in over 7,000,000 downloads 40 | every month. You don't want to be left out! 41 | 42 | Feature Support 43 | --------------- 44 | 45 | Requests is ready for today's web. 46 | 47 | - International Domains and URLs 48 | - Keep-Alive & Connection Pooling 49 | - Sessions with Cookie Persistence 50 | - Browser-style SSL Verification 51 | - Basic/Digest Authentication 52 | - Elegant Key/Value Cookies 53 | - Automatic Decompression 54 | - Automatic Content Decoding 55 | - Unicode Response Bodies 56 | - Multipart File Uploads 57 | - HTTP(S) Proxy Support 58 | - Connection Timeouts 59 | - Streaming Downloads 60 | - ``.netrc`` Support 61 | - Chunked Requests 62 | - Thread-safety 63 | 64 | Requests supports Python 2.6 — 3.5, and runs great on PyPy. 65 | 66 | Installation 67 | ------------ 68 | 69 | To install Requests, simply: 70 | 71 | .. code-block:: bash 72 | 73 | $ pip install requests 74 | ✨🍰✨ 75 | 76 | Satisfaction, guaranteed. 77 | 78 | Documentation 79 | ------------- 80 | 81 | Fantastic documentation is available at http://docs.python-requests.org/, for a limited time only. 82 | 83 | 84 | How to Contribute 85 | ----------------- 86 | 87 | #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a `Contributor Friendly`_ tag for issues that should be ideal for people who are not very familiar with the codebase yet. 88 | #. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it). 89 | #. Write a test which shows that the bug was fixed or that the feature works as expected. 90 | #. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_. 91 | 92 | .. _`the repository`: http://github.com/kennethreitz/requests 93 | .. _AUTHORS: https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst 94 | .. _Contributor Friendly: https://github.com/kennethreitz/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open 95 | -------------------------------------------------------------------------------- /_appveyor/install.ps1: -------------------------------------------------------------------------------- 1 | # Sample script to install Python and pip under Windows 2 | # Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer 3 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | $MINICONDA_URL = "http://repo.continuum.io/miniconda/" 6 | $BASE_URL = "https://www.python.org/ftp/python/" 7 | $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" 8 | $GET_PIP_PATH = "C:\get-pip.py" 9 | 10 | $PYTHON_PRERELEASE_REGEX = @" 11 | (?x) 12 | (?\d+) 13 | \. 14 | (?\d+) 15 | \. 16 | (?\d+) 17 | (?[a-z]{1,2}\d+) 18 | "@ 19 | 20 | 21 | function Download ($filename, $url) { 22 | $webclient = New-Object System.Net.WebClient 23 | 24 | $basedir = $pwd.Path + "\" 25 | $filepath = $basedir + $filename 26 | if (Test-Path $filename) { 27 | Write-Host "Reusing" $filepath 28 | return $filepath 29 | } 30 | 31 | # Download and retry up to 3 times in case of network transient errors. 32 | Write-Host "Downloading" $filename "from" $url 33 | $retry_attempts = 2 34 | for ($i = 0; $i -lt $retry_attempts; $i++) { 35 | try { 36 | $webclient.DownloadFile($url, $filepath) 37 | break 38 | } 39 | Catch [Exception]{ 40 | Start-Sleep 1 41 | } 42 | } 43 | if (Test-Path $filepath) { 44 | Write-Host "File saved at" $filepath 45 | } else { 46 | # Retry once to get the error message if any at the last try 47 | $webclient.DownloadFile($url, $filepath) 48 | } 49 | return $filepath 50 | } 51 | 52 | 53 | function ParsePythonVersion ($python_version) { 54 | if ($python_version -match $PYTHON_PRERELEASE_REGEX) { 55 | return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, 56 | $matches.prerelease) 57 | } 58 | $version_obj = [version]$python_version 59 | return ($version_obj.major, $version_obj.minor, $version_obj.build, "") 60 | } 61 | 62 | 63 | function DownloadPython ($python_version, $platform_suffix) { 64 | $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version 65 | 66 | if (($major -le 2 -and $micro -eq 0) ` 67 | -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` 68 | ) { 69 | $dir = "$major.$minor" 70 | $python_version = "$major.$minor$prerelease" 71 | } else { 72 | $dir = "$major.$minor.$micro" 73 | } 74 | 75 | if ($prerelease) { 76 | if (($major -le 2) ` 77 | -or ($major -eq 3 -and $minor -eq 1) ` 78 | -or ($major -eq 3 -and $minor -eq 2) ` 79 | -or ($major -eq 3 -and $minor -eq 3) ` 80 | ) { 81 | $dir = "$dir/prev" 82 | } 83 | } 84 | 85 | if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { 86 | $ext = "msi" 87 | if ($platform_suffix) { 88 | $platform_suffix = ".$platform_suffix" 89 | } 90 | } else { 91 | $ext = "exe" 92 | if ($platform_suffix) { 93 | $platform_suffix = "-$platform_suffix" 94 | } 95 | } 96 | 97 | $filename = "python-$python_version$platform_suffix.$ext" 98 | $url = "$BASE_URL$dir/$filename" 99 | $filepath = Download $filename $url 100 | return $filepath 101 | } 102 | 103 | 104 | function InstallPython ($python_version, $architecture, $python_home) { 105 | Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home 106 | if (Test-Path $python_home) { 107 | Write-Host $python_home "already exists, skipping." 108 | return $false 109 | } 110 | if ($architecture -eq "32") { 111 | $platform_suffix = "" 112 | } else { 113 | $platform_suffix = "amd64" 114 | } 115 | $installer_path = DownloadPython $python_version $platform_suffix 116 | $installer_ext = [System.IO.Path]::GetExtension($installer_path) 117 | Write-Host "Installing $installer_path to $python_home" 118 | $install_log = $python_home + ".log" 119 | if ($installer_ext -eq '.msi') { 120 | InstallPythonMSI $installer_path $python_home $install_log 121 | } else { 122 | InstallPythonEXE $installer_path $python_home $install_log 123 | } 124 | if (Test-Path $python_home) { 125 | Write-Host "Python $python_version ($architecture) installation complete" 126 | } else { 127 | Write-Host "Failed to install Python in $python_home" 128 | Get-Content -Path $install_log 129 | Exit 1 130 | } 131 | } 132 | 133 | 134 | function InstallPythonEXE ($exepath, $python_home, $install_log) { 135 | $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" 136 | RunCommand $exepath $install_args 137 | } 138 | 139 | 140 | function InstallPythonMSI ($msipath, $python_home, $install_log) { 141 | $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" 142 | $uninstall_args = "/qn /x $msipath" 143 | RunCommand "msiexec.exe" $install_args 144 | if (-not(Test-Path $python_home)) { 145 | Write-Host "Python seems to be installed else-where, reinstalling." 146 | RunCommand "msiexec.exe" $uninstall_args 147 | RunCommand "msiexec.exe" $install_args 148 | } 149 | } 150 | 151 | function RunCommand ($command, $command_args) { 152 | Write-Host $command $command_args 153 | Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru 154 | } 155 | 156 | 157 | function InstallPip ($python_home) { 158 | $pip_path = $python_home + "\Scripts\pip.exe" 159 | $python_path = $python_home + "\python.exe" 160 | if (-not(Test-Path $pip_path)) { 161 | Write-Host "Installing pip..." 162 | $webclient = New-Object System.Net.WebClient 163 | $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) 164 | Write-Host "Executing:" $python_path $GET_PIP_PATH 165 | & $python_path $GET_PIP_PATH 166 | } else { 167 | Write-Host "pip already installed." 168 | } 169 | } 170 | 171 | 172 | function DownloadMiniconda ($python_version, $platform_suffix) { 173 | if ($python_version -eq "3.4") { 174 | $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" 175 | } else { 176 | $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" 177 | } 178 | $url = $MINICONDA_URL + $filename 179 | $filepath = Download $filename $url 180 | return $filepath 181 | } 182 | 183 | 184 | function InstallMiniconda ($python_version, $architecture, $python_home) { 185 | Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home 186 | if (Test-Path $python_home) { 187 | Write-Host $python_home "already exists, skipping." 188 | return $false 189 | } 190 | if ($architecture -eq "32") { 191 | $platform_suffix = "x86" 192 | } else { 193 | $platform_suffix = "x86_64" 194 | } 195 | $filepath = DownloadMiniconda $python_version $platform_suffix 196 | Write-Host "Installing" $filepath "to" $python_home 197 | $install_log = $python_home + ".log" 198 | $args = "/S /D=$python_home" 199 | Write-Host $filepath $args 200 | Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru 201 | if (Test-Path $python_home) { 202 | Write-Host "Python $python_version ($architecture) installation complete" 203 | } else { 204 | Write-Host "Failed to install Python in $python_home" 205 | Get-Content -Path $install_log 206 | Exit 1 207 | } 208 | } 209 | 210 | 211 | function InstallMinicondaPip ($python_home) { 212 | $pip_path = $python_home + "\Scripts\pip.exe" 213 | $conda_path = $python_home + "\Scripts\conda.exe" 214 | if (-not(Test-Path $pip_path)) { 215 | Write-Host "Installing pip..." 216 | $args = "install --yes pip" 217 | Write-Host $conda_path $args 218 | Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru 219 | } else { 220 | Write-Host "pip already installed." 221 | } 222 | } 223 | 224 | function main () { 225 | InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON 226 | InstallPip $env:PYTHON 227 | } 228 | 229 | main -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor.yml from https://github.com/ogrisel/python-appveyor-demo 2 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 3 | 4 | build: off 5 | 6 | environment: 7 | matrix: 8 | - PYTHON: "C:\\Python27-x64" 9 | PYTHON_VERSION: "2.7.x" 10 | PYTHON_ARCH: "64" 11 | TOXENV: "py27" 12 | 13 | - PYTHON: "C:\\Python33-x64" 14 | PYTHON_VERSION: "3.3.x" 15 | PYTHON_ARCH: "64" 16 | TOXENV: "py33" 17 | 18 | - PYTHON: "C:\\Python34-x64" 19 | PYTHON_VERSION: "3.4.x" 20 | PYTHON_ARCH: "64" 21 | TOXENV: "py34" 22 | 23 | - PYTHON: "C:\\Python35-x64" 24 | PYTHON_VERSION: "3.5.x" 25 | PYTHON_ARCH: "64" 26 | TOXENV: "py35" 27 | 28 | - PYTHON: "C:\\Python36-x64" 29 | PYTHON_VERSION: "3.6.x" 30 | PYTHON_ARCH: "64" 31 | TOXENV: "py36" 32 | 33 | install: 34 | # Install Python (from the official .msi of http://python.org) and pip when 35 | # not already installed. 36 | - ps: if (-not(Test-Path($env:PYTHON))) { & _appveyor\install.ps1 } 37 | 38 | # Prepend newly installed Python to the PATH of this build (this cannot be 39 | # done from inside the powershell script as it would require to restart 40 | # the parent CMD process). 41 | - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 42 | 43 | # Check that we have the expected version and architecture for Python 44 | - "python --version" 45 | - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" 46 | 47 | # Upgrade to the latest version of pip to avoid it displaying warnings 48 | # about it being out of date. 49 | - "pip install --disable-pip-version-check --user --upgrade pip" 50 | - "C:\\MinGW\\bin\\mingw32-make" 51 | 52 | test_script: 53 | - "C:\\MinGW\\bin\\mingw32-make coverage" 54 | 55 | on_success: 56 | - "codecov -f coverage.xml" 57 | -------------------------------------------------------------------------------- /docs/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include HISTORY.rst README.rst LICENSE -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | #carbonads { 2 | display: block; 3 | overflow: hidden; 4 | padding: 1em; 5 | background-color: #eeeeee; 6 | text-align: center; 7 | border: solid 1px #cccccc; 8 | margin: 1.5em 0 2em; 9 | border-radius: 2px; 10 | line-height: 1.5; 11 | } 12 | 13 | #carbonads a { 14 | border-bottom: 0; 15 | } 16 | 17 | #carbonads span { 18 | display: block; 19 | overflow: hidden; 20 | } 21 | 22 | .carbon-img { 23 | display: block; 24 | margin: 0 auto 1em; 25 | text-align: center; 26 | } 27 | 28 | .carbon-text { 29 | display: block; 30 | margin-bottom: 1em; 31 | } 32 | 33 | .carbon-poweredby { 34 | display: block; 35 | text-transform: uppercase; 36 | letter-spacing: 1px; 37 | line-height: 1; 38 | font-size: 10px; 39 | } 40 | -------------------------------------------------------------------------------- /docs/_static/konami.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Konami-JS ~ 3 | * :: Now with support for touch events and multiple instances for 4 | * :: those situations that call for multiple easter eggs! 5 | * Code: http://konami-js.googlecode.com/ 6 | * Examples: http://www.snaptortoise.com/konami-js 7 | * Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com) 8 | * Version: 1.4.2 (9/2/2013) 9 | * Licensed under the MIT License (http://opensource.org/licenses/MIT) 10 | * Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser 11 | */ 12 | 13 | var Konami = function (callback) { 14 | var konami = { 15 | addEvent: function (obj, type, fn, ref_obj) { 16 | if (obj.addEventListener) 17 | obj.addEventListener(type, fn, false); 18 | else if (obj.attachEvent) { 19 | // IE 20 | obj["e" + type + fn] = fn; 21 | obj[type + fn] = function () { 22 | obj["e" + type + fn](window.event, ref_obj); 23 | }; 24 | obj.attachEvent("on" + type, obj[type + fn]); 25 | } 26 | }, 27 | input: "", 28 | pattern: "38384040373937396665", 29 | load: function (link) { 30 | this.addEvent(document, "keydown", function (e, ref_obj) { 31 | if (ref_obj) konami = ref_obj; // IE 32 | konami.input += e ? e.keyCode : event.keyCode; 33 | if (konami.input.length > konami.pattern.length) 34 | konami.input = konami.input.substr((konami.input.length - konami.pattern.length)); 35 | if (konami.input == konami.pattern) { 36 | konami.code(link); 37 | konami.input = ""; 38 | e.preventDefault(); 39 | return false; 40 | } 41 | }, this); 42 | this.iphone.load(link); 43 | }, 44 | code: function (link) { 45 | window.location = link 46 | }, 47 | iphone: { 48 | start_x: 0, 49 | start_y: 0, 50 | stop_x: 0, 51 | stop_y: 0, 52 | tapTolerance: 8, 53 | capture: false, 54 | orig_keys: "", 55 | keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"], 56 | code: function (link) { 57 | konami.code(link); 58 | }, 59 | touchCapture: function(evt) { 60 | konami.iphone.start_x = evt.changedTouches[0].pageX; 61 | konami.iphone.start_y = evt.changedTouches[0].pageY; 62 | konami.iphone.capture = true; 63 | }, 64 | load: function (link) { 65 | this.orig_keys = this.keys; 66 | konami.addEvent(document, "touchmove", function (e) { 67 | if (e.touches.length == 1 && konami.iphone.capture == true) { 68 | var touch = e.touches[0]; 69 | konami.iphone.stop_x = touch.pageX; 70 | konami.iphone.stop_y = touch.pageY; 71 | konami.iphone.check_direction(); 72 | } 73 | }); 74 | konami.addEvent(document, "touchend", function (evt) { 75 | konami.touchCapture(evt); 76 | konami.iphone.check_direction(link); 77 | }, false); 78 | konami.addEvent(document, "touchstart", function (evt) { 79 | konami.touchCapture(evt); 80 | }); 81 | }, 82 | check_direction: function (link) { 83 | var x_magnitude = Math.abs(this.start_x - this.stop_x); 84 | var y_magnitude = Math.abs(this.start_y - this.stop_y); 85 | var hasMoved = (x_magnitude > this.tapTolerance || y_magnitude > this.tapTolerance); 86 | var result; 87 | if (this.capture === true && hasMoved) { 88 | this.capture = false; 89 | var x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT"; 90 | var y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP"; 91 | var result = (x_magnitude > y_magnitude) ? x : y; 92 | } 93 | else if (this.capture === false && !hasMoved) { 94 | result = (this.tap == true) ? "TAP" : result; 95 | result = "TAP"; 96 | } 97 | if (result) { 98 | if (result == this.keys[0]) this.keys = this.keys.slice(1, this.keys.length); 99 | else this.keys = this.orig_keys; 100 | } 101 | if (this.keys.length == 0) { 102 | this.keys = this.orig_keys; 103 | this.code(link); 104 | } 105 | } 106 | } 107 | } 108 | 109 | typeof callback === "string" && konami.load(callback); 110 | if (typeof callback === "function") { 111 | konami.code = callback; 112 | konami.load(); 113 | } 114 | 115 | return konami; 116 | }; 117 | -------------------------------------------------------------------------------- /docs/_static/requests-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requests/requests-docs-cn/a7bffa84978a5018011f497b6b90fde247a4380a/docs/_static/requests-logo-small.png -------------------------------------------------------------------------------- /docs/_static/requests-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requests/requests-docs-cn/a7bffa84978a5018011f497b6b90fde247a4380a/docs/_static/requests-sidebar.png -------------------------------------------------------------------------------- /docs/_templates/hacks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 28 | 29 | 30 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

8 | 10 |

11 | 12 |

13 | Requests is an elegant and simple HTTP library for Python, built for 14 | human beings. 15 |

16 | 17 | 18 | 19 |

Stay Informed

20 |

Receive updates on new releases and upcoming projects.

21 | 22 |

24 | 25 |

26 |

Say Thanks!

27 |

Join Mailing List.

28 | 29 | 30 |

Other Projects

31 | 32 |

More Kenneth Reitz projects:

33 | 44 | 45 | 46 |

Useful Links

47 | 57 | 58 | 59 |

Translations

60 | 61 | 71 | 72 | -------------------------------------------------------------------------------- /docs/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 6 |

7 | 9 |

10 | 11 |

12 | Requests is an elegant and simple HTTP library for Python, built for 13 | human beings. You are currently looking at the documentation of the 14 | development release. 15 |

16 | 17 |

Stay Informed

18 |

Receive updates on new releases and upcoming projects.

19 | 20 |

Join Mailing List.

21 | 22 |
23 | 24 | 25 | 26 |

If you enjoy using this project, Say Thanks!

27 | 28 |

30 | 31 |

32 | 33 | 34 |

Other Projects

35 | 36 |

More Kenneth Reitz projects:

37 | 48 | 49 | 50 |

Translations

51 | 52 | 62 | 63 | -------------------------------------------------------------------------------- /docs/_themes/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /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/_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/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | 开发接口 4 | =================== 5 | 6 | .. module:: requests 7 | 8 | 这部分文档包含了 Requests 所有的接口。对于 Requests 依赖的外部库部分,我们在这里介绍最重要\ 9 | 的部分,并提供了规范文档的链接。 10 | 11 | 12 | 主要接口 13 | -------------- 14 | 15 | Requests 所有的功能都可以通过以下 7 个方法访问。它们全部都会返回一个 16 | :class:`Response ` 对象的实例。 17 | 18 | .. autofunction:: request 19 | 20 | .. autofunction:: head 21 | .. autofunction:: get 22 | .. autofunction:: post 23 | .. autofunction:: put 24 | .. autofunction:: patch 25 | .. autofunction:: delete 26 | 27 | 28 | 异常 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 | 请求会话 42 | ---------------- 43 | 44 | .. _sessionapi: 45 | 46 | .. autoclass:: Session 47 | :inherited-members: 48 | 49 | 50 | 低级类 51 | ------------------- 52 | 53 | .. autoclass:: requests.Request 54 | :inherited-members: 55 | 56 | .. autoclass:: Response 57 | :inherited-members: 58 | 59 | 60 | 更低级的类 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 | 身份验证 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 | 编码 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 | Cookie 93 | ------- 94 | 95 | .. autofunction:: requests.utils.add_dict_to_cookiejar 96 | .. autofunction:: requests.utils.cookiejar_from_dict 97 | 98 | .. autoclass:: requests.cookies.RequestsCookieJar 99 | :inherited-members: 100 | 101 | .. autoclass:: requests.cookies.CookieConflictError 102 | :inherited-members: 103 | 104 | 105 | 106 | 状态码查询 107 | ------------------ 108 | 109 | .. autoclass:: requests.codes 110 | 111 | :: 112 | 113 | >>> requests.codes['temporary_redirect'] 114 | 307 115 | 116 | >>> requests.codes.teapot 117 | 418 118 | 119 | >>> requests.codes['\o/'] 120 | 200 121 | 122 | 123 | 124 | ~~~~~~~~~~~~~~~~~~~ 125 | 126 | .. autoclass:: requests.Request 127 | :inherited-members: 128 | 129 | .. autoclass:: Response 130 | :inherited-members: 131 | 132 | 133 | 134 | 迁移到 1.x 135 | ---------------- 136 | 137 | 本节详细介绍 0.x 和 1.x 的主要区别,减少升级带来的一些不便。 138 | 139 | 140 | API 变化 141 | ~~~~~~~~~~~ 142 | 143 | * ``Response.json`` 现在是可调用的并且不再是响应体的属性。 144 | 145 | :: 146 | 147 | import requests 148 | r = requests.get('https://github.com/timeline.json') 149 | r.json() # 如果 JSON 解码失败,该调用会引发异常。 150 | 151 | * ``Session`` API 也发生了变化. Sessions 对象不再需要参数了。 152 | ``Session`` 现在是大写的了,但为了向后兼容,它仍然能被小写的 ``session`` 实例化。 153 | 154 | :: 155 | 156 | s = requests.Session() # 过去会话需要接收参数 157 | s.auth = auth 158 | s.headers.update(headers) 159 | r = s.get('http://httpbin.org/headers') 160 | 161 | * 除了'response',所有的请求挂钩已被移除。 162 | 163 | * 认证助手已经被分解成单独的模块了. 参见 164 | requests-oauthlib_ and requests-kerberos_. 165 | 166 | .. _requests-oauthlib: https://github.com/requests/requests-oauthlib 167 | .. _requests-kerberos: https://github.com/requests/requests-kerberos 168 | 169 | * 流请求的参数已从 ``prefetch`` 改变到 ``stream`` ,并且逻辑也被颠倒。除此之外, 170 | ``stream`` 现在对于原始响应读取也是必需的。 171 | 172 | :: 173 | 174 | # 在 0.x 中,传入 prefetch=False 会达到同样的结果 175 | r = requests.get('https://github.com/timeline.json', stream=True) 176 | for chunk in r.iter_content(8192): 177 | ... 178 | 179 | * requests 方法的 ``config`` 参数已全部删除。 现在配置这些选项都在 ``Session`` ,比如 180 | keep-alive 和最大数目的重定向。 详细程度选项应当由配置日志来处理。 181 | 182 | :: 183 | 184 | import requests 185 | import logging 186 | 187 | # 启用调试于 http.client 级别 (requests->urllib3->http.client) 188 | # 你将能看到 REQUEST,包括 HEADERS 和 DATA,以及包含 HEADERS 但不包含 DATA 的 RESPONSE。 189 | # 唯一缺少的是 response.body,它不会被 log 记录。 190 | try: # for Python 3 191 | from http.client import HTTPConnection 192 | except ImportError: 193 | from httplib import HTTPConnection 194 | HTTPConnection.debuglevel = 1 195 | 196 | logging.basicConfig() # 初始化 logging,否则不会看到任何 requests 的输出。 197 | logging.getLogger().setLevel(logging.DEBUG) 198 | requests_log = logging.getLogger("requests.packages.urllib3") 199 | requests_log.setLevel(logging.DEBUG) 200 | requests_log.propagate = True 201 | 202 | requests.get('http://httpbin.org/headers') 203 | 204 | 205 | 许可 206 | ~~~~~~~~~ 207 | 208 | 有一个关键的与 API 无关的区别是开放许可从 ISC_ 许可 变更到 `Apache 2.0`_ 许可. Apache 2.0 209 | license 确保了对于 Requests 的贡献也被涵盖在 Apache 2.0 许可内. 210 | 211 | .. _ISC: http://opensource.org/licenses/ISC 212 | .. _Apache 2.0: http://opensource.org/licenses/Apache-2.0 213 | 214 | 迁移到 2.x 215 | ---------------- 216 | 217 | 218 | 和 1.0 发布版相比,破坏向后兼容的更改比较少。不过在这个主发布版本中,依然还有一些应该注意的问题。 219 | 220 | 更多关于变更的细节,包括 API,以及相关的 GitHub Issue 和部分 bug 修复,请参阅 Cory blog_ 221 | 中的相关主题。 222 | 223 | .. _blog: http://lukasa.co.uk/2013/09/Requests_20/ 224 | 225 | 226 | API 变化 227 | ~~~~~~~~~~~ 228 | 229 | * Requests 处理异常的行为有部分更改。 ``RequestException`` 现在是 ``IOError`` 230 | 的子类,而非 ``RuntimeError`` 的子类,新的归类更为合理。此外,无效的 URL 231 | 转义序列现在会引发 ``RequestException`` 的一个子类,而非一个 ``ValueError``\。 232 | 233 | :: 234 | 235 | requests.get('http://%zz/') # raises requests.exceptions.InvalidURL 236 | 237 | 最后, 错误分块导致的 ``httplib.IncompleteRead`` 异常现在变成了 ``ChunkedEncodingError``\。 238 | 239 | * 代理 API 略有改动,现在需要提供代理 URL 的 scheme。 240 | 241 | :: 242 | 243 | proxies = { 244 | "http": "10.10.1.10:3128", # 需使用 http://10.10.1.10:3128 245 | } 246 | 247 | # requests 1.x 中有效, requests 2.x 中会引发 requests.exceptions.MissingSchema 248 | 249 | requests.get("http://example.org", proxies=proxies) 250 | 251 | 252 | 行为变化 253 | ~~~~~~~~~~~~~~~~~~~~~~~ 254 | 255 | * ``headers`` 字典中的 key 现在都是原生字符串,在所有版本的 Python 256 | 中都是如此。也就是说,Python 2 中是 bytestring,Python 3 中是 unicode。 257 | 如果 key 不是原声字符串(Python 2 中 unicode,或 Python 3 中 bytestring) 258 | 它们会被以 UTF-8 编码转成原生字符串。 259 | 260 | * ``headers`` 字典中的 value 应该都是字符串。在 1.0 版之前该项目就是要求这样做的,只不过\ 261 | 最近(v2.11.0之后)这条变成了强制条款。建议在可能的情况下,避免让 header 值使用 unicode 编码。 262 | -------------------------------------------------------------------------------- /docs/community/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | 常见问题 4 | ========================== 5 | 6 | 这部分的文档回答了有关 Requests 的常见问题。 7 | 8 | 已编码的数据? 9 | ------------- 10 | 11 | Requests 自动解压缩的 gzip 编码的响应体,并在可能的情况下尽可能的将响应内容解码为 unicode. 12 | 13 | 如果需要的话,你可以直接访问原始响应内容(甚至是套接字)。 14 | 15 | 16 | 自定义 User-Agent? 17 | ------------------- 18 | 19 | Requests 允许你使用其它的 HTTP Header 来轻松的覆盖自带的 User-Agent 字符串。 20 | 21 | 22 | 怎么不使用 Httplib2? 23 | --------------------- 24 | 25 | Chris Adams 给出了一个很好的总结 26 | `Hacker News `_: 27 | 28 | httplib2 是你应该使用 Request 的一个原因,尽管 httplib2 名声在外,但它文档欠佳,\ 29 | 而且基本操作要写的代码依旧太多。对于 httplib2 我是很感激的,要写一个现代 HTTP 客户端\ 30 | 要跟一吨低级麻烦事打交道,实在是件苦差事。但无论如何,还是直接使用 Requests 吧。\ 31 | Kenneth Reitz 是一个很负责的作者,他能把简单的东西做简单。httplib2 感觉像是一个\ 32 | 学术产物,而 Requests 才真的是一个人们可以在生产系统中使用的东西。[1] 33 | 34 | 免责声明:尽管我名列在 Requests 的 AUTHORS 文件中,但对于 Requests 的优秀状态,\ 35 | 我的贡献大约只有 0.0001% 吧。 36 | 37 | 1. http://code.google.com/p/httplib2/issues/detail?id=96 是一个好例子,\ 38 | 这个讨厌的 bug 影响了很多人,有人几个月前就写了一个修正,这个修正很有效,我把它放在\ 39 | 一个代码分支中,用它处理了几 TB 的数据都没问题,但它花了一年多才进到主分支中,进到 40 | PyPI 则花了更长时间,所以用到 httplib2 的项目花了很长时间才等到问题的解决。 41 | 42 | 43 | 支持 Python 3 吗? 44 | ----------------- 45 | 46 | 当然!下面是官方支持的python平台列表: 47 | 48 | * Python 2.6 49 | * Python 2.7 50 | * Python 3.3 51 | * Python 3.4 52 | * Python 3.5 53 | * Python 3.6 54 | * PyPy 55 | 56 | "hostname doesn't match" 错误是怎么回事? 57 | -------------------------------------------- 58 | 59 | 当 :ref:`SSL certificate verification ` 发现服务器响应的认证\ 60 | 和它认为自己连接的主机名不匹配时,就会发生这样的错误。如果你确定服务器的 SSL 设置\ 61 | 是正确的(例如你可以用浏览器访问页面),而且你使用的是 Python 2.6 或者 2.7,那么一个\ 62 | 可能的解释就是你需要 Server-Name-Indication。 63 | 64 | 65 | `Server-Name-Indication`_ 简称 SNI,是一个 SSL 的官方扩展,其中客户端会告诉服务器\ 66 | 它连接了哪个主机名。当服务器使用虚拟主机( `Virtual Hosting`_)时这点很重要。这样的\ 67 | 服务器会服务多个 SSL 网站,所以它们需要能够针对客户端连接的主机名返回正确的证书。 68 | 69 | Python 3 和 Python 2.7.9+ 的 SSL 模块包含了原生的 SNI 支持。更多关于在 Request、\ 70 | SNI 以及 Python < 2.7.9 的信息请参见这个 `Stack Overflow 答案`_\。 71 | 72 | .. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication 73 | .. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting 74 | .. _`Stack Overflow 答案`: https://stackoverflow.com/questions/18578439/using-requests-with-tls-doesnt-give-sni-support/18579484#18579484 75 | -------------------------------------------------------------------------------- /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 `_ -------------------------------------------------------------------------------- /docs/community/recommended.rst: -------------------------------------------------------------------------------- 1 | .. _recommended: 2 | 3 | 推荐的库和扩展 4 | =================================== 5 | 6 | Requests 拥有很多强大有用的第三方扩展。这里概述了其中最好的几个。 7 | 8 | Certifi CA Bundle 9 | ----------------- 10 | 11 | `Certifi`_ 是一个精心准备的根证书集合,用来验证 SSL 证书的可信任度,同时还会验证 12 | TLS 主机的身份。这是一个从 Requests 项目中剥离出来的项目。 13 | 14 | .. _Certifi: http://certifi.io/en/latest/ 15 | 16 | CacheControl 17 | ------------ 18 | 19 | `CacheControl`_ 这个扩展能为 Requests 添加完整的 HTTP 缓存功能。这样你的 web 20 | 请求的效率会高很多,这个扩展适合在需要大量 web 请求的场合使用。 21 | 22 | .. _CacheControl: https://cachecontrol.readthedocs.io/en/latest/ 23 | 24 | Requests-Toolbelt 25 | ----------------- 26 | 27 | `Requests-Toolbelt`_ 使一些有用工具的集合,有的用户会觉得它们很有用,但 Requests 28 | 中不适合将它们包进去。Requests 的核心组积极维护着这个库,它反映了社区用户最想要的\ 29 | 一些功能。 30 | 31 | .. _Requests-Toolbelt: http://toolbelt.readthedocs.io/en/latest/index.html 32 | 33 | Requests-OAuthlib 34 | ----------------- 35 | 36 | `requests-oauthlib`_ 使得从 Requests 自动进行 OAuth 跳转变成可能。这对很多使用 37 | OAuth 进行验证的网站都很有用。它还提供了很多小配置,用来对非标准的 OAuth 提供者进行处理。 38 | 39 | .. _requests-oauthlib: https://requests-oauthlib.readthedocs.io/en/latest/ 40 | 41 | 42 | Betamax 43 | ------- 44 | 45 | `Betamax`_ 记录了你的 HTTP 交互,这样就不需要 NSA 费心了。这是一个专为 Python-Requests 46 | 设计的模拟录像机。 47 | 48 | .. _betamax: https://github.com/sigmavirus24/betamax 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /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 and upgrades to 21 | vendored packages. The core developers of Requests are committed to providing 22 | a good user experience. This means we're also committed to preserving 23 | backwards compatibility as much as possible. Major releases will be infrequent 24 | and will need strong justifications before they are considered. 25 | 26 | Minor Releases 27 | -------------- 28 | 29 | A minor release will not include breaking changes but may include 30 | miscellaneous bug fixes and upgrades to vendored packages. If the previous 31 | version of Requests released was ``v10.2.7`` a minor release would be 32 | versioned as ``v10.3.0``. 33 | 34 | Minor releases will be backwards compatible with releases that have the same 35 | major version number. In other words, all versions that would start with 36 | ``v10.`` should be compatible with each other. 37 | 38 | Hotfix Releases 39 | --------------- 40 | 41 | A hotfix release will only include bug fixes that were missed when the project 42 | released the previous version. If the previous version of Requests released 43 | ``v10.2.7`` the hotfix release would be versioned as ``v10.2.8``. 44 | 45 | Hotfixes will **not** include upgrades to vendored dependencies after 46 | ``v2.6.2`` 47 | 48 | Reasoning 49 | --------- 50 | 51 | In the 2.5 and 2.6 release series, the Requests core team upgraded vendored 52 | dependencies and caused a great deal of headaches for both users and the core 53 | team. To reduce this pain, we're forming a concrete set of procedures so 54 | expectations will be properly set. 55 | -------------------------------------------------------------------------------- /docs/community/support.rst: -------------------------------------------------------------------------------- 1 | .. _support: 2 | 3 | 支持 4 | ======= 5 | 6 | 对于Requests,如果你有问题或者建议,可以通过下面几种方法得到支持: 7 | 8 | StackOverflow 9 | ------------- 10 | 11 | 如果你的问题不包含敏感或私有信息,或者你能将这些信息匿名化,那你就可以在 12 | `StackOverflow `_ 13 | 上使用 ``python-requests`` 标签提问。 14 | 15 | 发送推文 16 | ------------ 17 | 18 | 如果你的问题在140个字符内描述,欢迎在 twitter 上发送推文至 19 | `@kennethreitz `_, 20 | `@sigmavirus24 `_, 或 21 | `@lukasaoz `_\。 22 | 23 | 24 | 提交 issue 25 | ------------- 26 | 27 | 如果你在 Requests 中注意到了一些意想不到的行为,或者希望看到一个新的功能支持,请 28 | `在 GitHub 上提交 issue `_. 29 | 30 | 31 | 邮件 32 | ------ 33 | 34 | 我很乐意回答关于 Requests 的个人或者更深入的问题。你可以随时写信至 35 | `requests@kennethreitz.com `_. 36 | 37 | 38 | IRC 39 | --- 40 | 41 | Requests 的官方 Freenode 频道是 42 | `#python-requests `_ 43 | 44 | Requests 的核心开发人员白天会在 IRC 中出没,你可以在 ``#python-requests`` 中找到他们: 45 | 46 | - kennethreitz 47 | - lukasa 48 | - sigmavirus24 49 | -------------------------------------------------------------------------------- /docs/community/updates.rst: -------------------------------------------------------------------------------- 1 | .. _updates: 2 | 3 | 更新 4 | ======= 5 | 6 | 如果你想和社区以及开发版的 Requests 保持最新的联系, 7 | 这有几种方式: 8 | 9 | GitHub 10 | ------ 11 | 12 | 最好的方式是追踪 Requests 开发版本的 13 | `GitHub 库 `_. 14 | 15 | Twitter 16 | ------- 17 | 18 | 我经常推送关于 Requests 的新功能和新版本. 19 | 20 | 关注 `@kennethreitz `_ 即可获得更新。 21 | 22 | 23 | 版本历史 24 | =========================== 25 | 26 | .. include:: ../../HISTORY.rst 27 | -------------------------------------------------------------------------------- /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 `sigmavirus24 `_ and 6 | `Lukasa `_ directly. **Do not file a public issue.** 7 | 8 | Our PGP Key fingerprints are: 9 | 10 | - 0161 BB7E B208 B5E0 4FDC 9F81 D9DA 0A04 9113 F853 (@sigmavirus24) 11 | 12 | - 90DC AE40 FEA7 4B14 9B70 662D F25F 2144 EEC1 373D (@lukasa) 13 | 14 | If English is not your first language, please try to describe the problem and 15 | its impact to the best of your ability. For greater detail, please use your 16 | native language and we will try our best to translate it using online services. 17 | 18 | Please also include the code you used to find the problem and the shortest 19 | amount of code necessary to reproduce it. 20 | 21 | Please do not disclose this to anyone else. We will retrieve a CVE identifier 22 | if necessary and give you full credit under whatever name or alias you provide. 23 | We will only request an identifier when we have a fix and can publish it in a 24 | release. 25 | 26 | We will respect your privacy and will only publicize your involvement if you 27 | grant us permission. 28 | 29 | Process 30 | ------- 31 | 32 | This following information discusses the process the requests project follows 33 | in response to vulnerability disclosures. If you are disclosing a 34 | vulnerability, this section of the documentation lets you know how we will 35 | respond to your disclosure. 36 | 37 | Timeline 38 | ~~~~~~~~ 39 | 40 | When you report an issue, one of the project members will respond to you within 41 | two days *at the outside*. In most cases responses will be faster, usually 42 | within 12 hours. This initial response will at the very least confirm receipt 43 | of the report. 44 | 45 | If we were able to rapidly reproduce the issue, the initial response will also 46 | contain confirmation of the issue. If we are not, we will often ask for more 47 | information about the reproduction scenario. 48 | 49 | Our goal is to have a fix for any vulnerability released within two weeks of 50 | the initial disclosure. This may potentially involve shipping an interim 51 | release that simply disables function while a more mature fix can be prepared, 52 | but will in the vast majority of cases mean shipping a complete release as soon 53 | as possible. 54 | 55 | Throughout the fix process we will keep you up to speed with how the fix is 56 | progressing. Once the fix is prepared, we will notify you that we believe we 57 | have a fix. Often we will ask you to confirm the fix resolves the problem in 58 | your environment, especially if we are not confident of our reproduction 59 | scenario. 60 | 61 | At this point, we will prepare for the release. We will obtain a CVE number 62 | if one is required, providing you with full credit for the discovery. We will 63 | also decide on a planned release date, and let you know when it is. This 64 | release date will *always* be on a weekday. 65 | 66 | At this point we will reach out to our major downstream packagers to notify 67 | them of an impending security-related patch so they can make arrangements. In 68 | addition, these packagers will be provided with the intended patch ahead of 69 | time, to ensure that they are able to promptly release their downstream 70 | packages. Currently the list of people we actively contact *ahead of a public 71 | release* is: 72 | 73 | - Jeremy Cline, Red Hat (@jeremycline) 74 | - Daniele Tricoli, Debian (@eriol) 75 | 76 | We will notify these individuals at least a week ahead of our planned release 77 | date to ensure that they have sufficient time to prepare. If you believe you 78 | should be on this list, please let one of the maintainers know at one of the 79 | email addresses at the top of this article. 80 | 81 | On release day, we will push the patch to our public repository, along with an 82 | updated changelog that describes the issue and credits you. We will then issue 83 | a PyPI release containing the patch. 84 | 85 | At this point, we will publicise the release. This will involve mails to 86 | mailing lists, Tweets, and all other communication mechanisms available to the 87 | core team. 88 | 89 | We will also explicitly mention which commits contain the fix to make it easier 90 | for other distributors and users to easily patch their own versions of requests 91 | if upgrading is not an option. 92 | 93 | Previous CVEs 94 | ------------- 95 | 96 | - Fixed in 2.6.0 97 | 98 | - `CVE 2015-2296 `_, 99 | reported by Matthew Daley of `BugFuzz `_. 100 | 101 | - Fixed in 2.3.0 102 | 103 | - `CVE 2014-1829 `_ 104 | 105 | - `CVE 2014-1830 `_ -------------------------------------------------------------------------------- /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'MMXVII. 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 | } 134 | 135 | # Add any paths that contain custom themes here, relative to this directory. 136 | #html_theme_path = [] 137 | 138 | # The name for this set of Sphinx documents. If None, it defaults to 139 | # " v documentation". 140 | #html_title = None 141 | 142 | # A shorter title for the navigation bar. Default is the same as html_title. 143 | #html_short_title = None 144 | 145 | # The name of an image file (relative to this directory) to place at the top 146 | # of the sidebar. 147 | #html_logo = None 148 | 149 | # The name of an image file (within the static path) to use as favicon of the 150 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 151 | # pixels large. 152 | #html_favicon = None 153 | 154 | # Add any paths that contain custom static files (such as style sheets) here, 155 | # relative to this directory. They are copied after the builtin static files, 156 | # so a file named "default.css" will overwrite the builtin "default.css". 157 | html_static_path = ['_static'] 158 | 159 | # Add any extra paths that contain custom files (such as robots.txt or 160 | # .htaccess) here, relative to this directory. These files are copied 161 | # directly to the root of the documentation. 162 | #html_extra_path = [] 163 | 164 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 165 | # using the given strftime format. 166 | #html_last_updated_fmt = '%b %d, %Y' 167 | 168 | # If true, SmartyPants will be used to convert quotes and dashes to 169 | # typographically correct entities. 170 | html_use_smartypants = False 171 | 172 | # Custom sidebar templates, maps document names to template names. 173 | html_sidebars = { 174 | 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html', 175 | 'hacks.html'], 176 | '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 177 | 'sourcelink.html', 'searchbox.html', 'hacks.html'] 178 | } 179 | 180 | # Additional templates that should be rendered to pages, maps page names to 181 | # template names. 182 | #html_additional_pages = {} 183 | 184 | # If false, no module index is generated. 185 | #html_domain_indices = True 186 | 187 | # If false, no index is generated. 188 | #html_use_index = True 189 | 190 | # If true, the index is split into individual pages for each letter. 191 | #html_split_index = False 192 | 193 | # If true, links to the reST sources are added to the pages. 194 | html_show_sourcelink = False 195 | 196 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 197 | html_show_sphinx = False 198 | 199 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 200 | html_show_copyright = True 201 | 202 | # If true, an OpenSearch description file will be output, and all pages will 203 | # contain a tag referring to it. The value of this option must be the 204 | # base URL from which the finished HTML is served. 205 | #html_use_opensearch = '' 206 | 207 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 208 | #html_file_suffix = None 209 | 210 | # Language to be used for generating the HTML full-text search index. 211 | # Sphinx supports the following languages: 212 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 213 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 214 | #html_search_language = 'en' 215 | 216 | # A dictionary with options for the search language support, empty by default. 217 | # Now only 'ja' uses this config value 218 | #html_search_options = {'type': 'default'} 219 | 220 | # The name of a javascript file (relative to the configuration directory) that 221 | # implements a search results scorer. If empty, the default will be used. 222 | #html_search_scorer = 'scorer.js' 223 | 224 | # Output file base name for HTML help builder. 225 | htmlhelp_basename = 'Requestsdoc' 226 | 227 | # -- Options for LaTeX output --------------------------------------------- 228 | 229 | latex_elements = { 230 | # The paper size ('letterpaper' or 'a4paper'). 231 | #'papersize': 'letterpaper', 232 | 233 | # The font size ('10pt', '11pt' or '12pt'). 234 | #'pointsize': '10pt', 235 | 236 | # Additional stuff for the LaTeX preamble. 237 | #'preamble': '', 238 | 239 | # Latex figure (float) alignment 240 | #'figure_align': 'htbp', 241 | } 242 | 243 | # Grouping the document tree into LaTeX files. List of tuples 244 | # (source start file, target name, title, 245 | # author, documentclass [howto, manual, or own class]). 246 | latex_documents = [ 247 | (master_doc, 'Requests.tex', u'Requests Documentation', 248 | u'Kenneth Reitz', 'manual'), 249 | ] 250 | 251 | # The name of an image file (relative to this directory) to place at the top of 252 | # the title page. 253 | #latex_logo = None 254 | 255 | # For "manual" documents, if this is true, then toplevel headings are parts, 256 | # not chapters. 257 | #latex_use_parts = False 258 | 259 | # If true, show page references after internal links. 260 | #latex_show_pagerefs = False 261 | 262 | # If true, show URL addresses after external links. 263 | #latex_show_urls = False 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #latex_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #latex_domain_indices = True 270 | 271 | 272 | # -- Options for manual page output --------------------------------------- 273 | 274 | # One entry per manual page. List of tuples 275 | # (source start file, name, description, authors, manual section). 276 | man_pages = [ 277 | (master_doc, 'requests', u'Requests Documentation', 278 | [author], 1) 279 | ] 280 | 281 | # If true, show URL addresses after external links. 282 | #man_show_urls = False 283 | 284 | 285 | # -- Options for Texinfo output ------------------------------------------- 286 | 287 | # Grouping the document tree into Texinfo files. List of tuples 288 | # (source start file, target name, title, author, 289 | # dir menu entry, description, category) 290 | texinfo_documents = [ 291 | (master_doc, 'Requests', u'Requests Documentation', 292 | author, 'Requests', 'One line description of project.', 293 | 'Miscellaneous'), 294 | ] 295 | 296 | # Documents to append as an appendix to all manuals. 297 | #texinfo_appendices = [] 298 | 299 | # If false, no module index is generated. 300 | #texinfo_domain_indices = True 301 | 302 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 303 | #texinfo_show_urls = 'footnote' 304 | 305 | # If true, do not generate a @detailmenu in the "Top" node's menu. 306 | #texinfo_no_detailmenu = False 307 | 308 | 309 | # -- Options for Epub output ---------------------------------------------- 310 | 311 | # Bibliographic Dublin Core info. 312 | epub_title = project 313 | epub_author = author 314 | epub_publisher = author 315 | epub_copyright = copyright 316 | 317 | # The basename for the epub file. It defaults to the project name. 318 | #epub_basename = project 319 | 320 | # The HTML theme for the epub output. Since the default themes are not 321 | # optimized for small screen space, using the same theme for HTML and epub 322 | # output is usually not wise. This defaults to 'epub', a theme designed to save 323 | # visual space. 324 | #epub_theme = 'epub' 325 | 326 | # The language of the text. It defaults to the language option 327 | # or 'en' if the language is not set. 328 | #epub_language = '' 329 | 330 | # The scheme of the identifier. Typical schemes are ISBN or URL. 331 | #epub_scheme = '' 332 | 333 | # The unique identifier of the text. This can be a ISBN number 334 | # or the project homepage. 335 | #epub_identifier = '' 336 | 337 | # A unique identification for the text. 338 | #epub_uid = '' 339 | 340 | # A tuple containing the cover image and cover page html template filenames. 341 | #epub_cover = () 342 | 343 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 344 | #epub_guide = () 345 | 346 | # HTML files that should be inserted before the pages created by sphinx. 347 | # The format is a list of tuples containing the path and title. 348 | #epub_pre_files = [] 349 | 350 | # HTML files that should be inserted after the pages created by sphinx. 351 | # The format is a list of tuples containing the path and title. 352 | #epub_post_files = [] 353 | 354 | # A list of files that should not be packed into the epub file. 355 | epub_exclude_files = ['search.html'] 356 | 357 | # The depth of the table of contents in toc.ncx. 358 | #epub_tocdepth = 3 359 | 360 | # Allow duplicate toc entries. 361 | #epub_tocdup = True 362 | 363 | # Choose between 'default' and 'includehidden'. 364 | #epub_tocscope = 'default' 365 | 366 | # Fix unsupported image types using the Pillow. 367 | #epub_fix_images = False 368 | 369 | # Scale large images. 370 | #epub_max_image_width = 0 371 | 372 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 373 | #epub_show_urls = 'inline' 374 | 375 | # If false, no index is generated. 376 | #epub_use_index = True 377 | 378 | intersphinx_mapping = {'urllib3': ('http://urllib3.readthedocs.io/en/latest', None)} 379 | -------------------------------------------------------------------------------- /docs/dev/authors.rst: -------------------------------------------------------------------------------- 1 | 贡献者 2 | ======= 3 | 4 | 5 | .. include:: ../../AUTHORS.rst 6 | -------------------------------------------------------------------------------- /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 `Ian Cordasco`_ or `Cory Benfield`_, 15 | the primary maintainers. 16 | 17 | .. _Ian Cordasco: http://www.coglib.com/~icordasc/ 18 | .. _Cory Benfield: https://lukasa.co.uk/about 19 | 20 | If you have non-technical feedback, philosophical ponderings, crazy ideas, or 21 | other general thoughts about Requests or its position within the Python 22 | ecosystem, the BDFL, `Kenneth Reitz`_, would love to hear from you. 23 | 24 | The guide is split into sections based on the type of contribution you're 25 | thinking of making, with a section that covers general guidelines for all 26 | contributors. 27 | 28 | .. _Kenneth Reitz: mailto:me@kennethreitz.org 29 | 30 | Be Cordial 31 | ---------- 32 | 33 | **Be cordial or be on your way**. *—Kenneth Reitz* 34 | 35 | Requests has one very important rule governing all forms of contribution, 36 | including reporting bugs or requesting features. This golden rule is 37 | "`be cordial or be on your way`_". 38 | 39 | **All contributions are welcome**, as long as 40 | everyone involved is treated with respect. 41 | 42 | .. _be cordial or be on your way: http://kennethreitz.org/be-cordial-or-be-on-your-way/ 43 | 44 | .. _early-feedback: 45 | 46 | Get Early Feedback 47 | ------------------ 48 | 49 | If you are contributing, do not feel the need to sit on your contribution until 50 | it is perfectly polished and complete. It helps everyone involved for you to 51 | seek feedback as early as you possibly can. Submitting an early, unfinished 52 | version of your contribution for feedback in no way prejudices your chances of 53 | getting that contribution accepted, and can save you from putting a lot of work 54 | into a contribution that is not suitable for the project. 55 | 56 | Contribution Suitability 57 | ------------------------ 58 | 59 | Our project maintainers have the last word on whether or not a contribution is 60 | suitable for Requests. All contributions will be considered carefully, but from 61 | time to time, contributions will be rejected because they do not suit the 62 | current goals or needs of the project. 63 | 64 | If your contribution is rejected, don't despair! As long as you followed these 65 | guidelines, you will have a much better chance of getting your next 66 | contribution accepted. 67 | 68 | 69 | Code Contributions 70 | ------------------ 71 | 72 | Steps for Submitting Code 73 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 74 | 75 | When contributing code, you'll want to follow this checklist: 76 | 77 | 1. Fork the repository on GitHub. 78 | 2. Run the tests to confirm they all pass on your system. If they don't, you'll 79 | need to investigate why they fail. If you're unable to diagnose this 80 | yourself, raise it as a bug report by following the guidelines in this 81 | document: :ref:`bug-reports`. 82 | 3. Write tests that demonstrate your bug or feature. Ensure that they fail. 83 | 4. Make your change. 84 | 5. Run the entire test suite again, confirming that all tests pass *including 85 | the ones you just added*. 86 | 6. Send a GitHub Pull Request to the main repository's ``master`` branch. 87 | GitHub Pull Requests are the expected method of code collaboration on this 88 | project. 89 | 90 | The following sub-sections go into more detail on some of the points above. 91 | 92 | Code Review 93 | ~~~~~~~~~~~ 94 | 95 | Contributions will not be merged until they've been code reviewed. You should 96 | implement any code review feedback unless you strongly object to it. In the 97 | event that you object to the code review feedback, you should make your case 98 | clearly and calmly. If, after doing so, the feedback is judged to still apply, 99 | you must either apply the feedback or withdraw your contribution. 100 | 101 | New Contributors 102 | ~~~~~~~~~~~~~~~~ 103 | 104 | If you are new or relatively new to Open Source, welcome! Requests aims to 105 | be a gentle introduction to the world of Open Source. If you're concerned about 106 | how best to contribute, please consider mailing a maintainer (listed above) and 107 | asking for help. 108 | 109 | Please also check the :ref:`early-feedback` section. 110 | 111 | Kenneth Reitz's Code Style™ 112 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 113 | 114 | The Requests codebase uses the `PEP 8`_ code style. 115 | 116 | In addition to the standards outlined in PEP 8, we have a few guidelines: 117 | 118 | - Line-length can exceed 79 characters, to 100, when convenient. 119 | - Line-length can exceed 100 characters, when doing otherwise would be *terribly* inconvenient. 120 | - Always use single-quoted strings (e.g. ``'#flatearth'``), unless a single-quote occurs within the string. 121 | 122 | Additionally, one of the styles that PEP8 recommends for `line continuations`_ 123 | completely lacks all sense of taste, and is not to be permitted within 124 | the Requests codebase:: 125 | 126 | # Aligned with opening delimiter. 127 | foo = long_function_name(var_one, var_two, 128 | var_three, var_four) 129 | 130 | No. Just don't. Please. 131 | 132 | Docstrings are to follow the following syntaxes:: 133 | 134 | def the_earth_is_flat(): 135 | """NASA divided up the seas into thirty-three degrees.""" 136 | pass 137 | 138 | :: 139 | 140 | def fibonacci_spiral_tool(): 141 | """With my feet upon the ground I lose myself / between the sounds 142 | and open wide to suck it in. / I feel it move across my skin. / I'm 143 | reaching up and reaching out. / I'm reaching for the random or 144 | whatever will bewilder me. / Whatever will bewilder me. / And 145 | following our will and wind we may just go where no one's been. / 146 | We'll ride the spiral to the end and may just go where no one's 147 | been. 148 | 149 | Spiral out. Keep going... 150 | """ 151 | pass 152 | 153 | All functions, methods, and classes are to contain docstrings. Object data 154 | model methods (e.g. ``__repr__``) are typically the exception to this rule. 155 | 156 | Thanks for helping to make the world a better place! 157 | 158 | .. _PEP 8: http://pep8.org 159 | .. _line continuations: https://www.python.org/dev/peps/pep-0008/#indentation 160 | 161 | Documentation Contributions 162 | --------------------------- 163 | 164 | Documentation improvements are always welcome! The documentation files live in 165 | the ``docs/`` directory of the codebase. They're written in 166 | `reStructuredText`_, and use `Sphinx`_ to generate the full suite of 167 | documentation. 168 | 169 | When contributing documentation, please do your best to follow the style of the 170 | documentation files. This means a soft-limit of 79 characters wide in your text 171 | files and a semi-formal, yet friendly and approachable, prose style. 172 | 173 | When presenting Python code, use single-quoted strings (``'hello'`` instead of 174 | ``"hello"``). 175 | 176 | .. _reStructuredText: http://docutils.sourceforge.net/rst.html 177 | .. _Sphinx: http://sphinx-doc.org/index.html 178 | 179 | 180 | .. _bug-reports: 181 | 182 | Bug Reports 183 | ----------- 184 | 185 | Bug reports are hugely important! Before you raise one, though, please check 186 | through the `GitHub issues`_, **both open and closed**, to confirm that the bug 187 | hasn't been reported before. Duplicate bug reports are a huge drain on the time 188 | of other contributors, and should be avoided as much as possible. 189 | 190 | .. _GitHub issues: https://github.com/kennethreitz/requests/issues 191 | 192 | 193 | Feature Requests 194 | ---------------- 195 | 196 | Requests is in a perpetual feature freeze, only the BDFL can add or approve of 197 | new features. The maintainers believe that Requests is a feature-complete 198 | piece of software at this time. 199 | 200 | One of the most important skills to have while maintaining a largely-used 201 | open source project is learning the ability to say "no" to suggested changes, 202 | while keeping an open ear and mind. 203 | 204 | If you believe there is a feature missing, feel free to raise a feature 205 | request, but please do be aware that the overwhelming likelihood is that your 206 | feature request will not be accepted. 207 | -------------------------------------------------------------------------------- /docs/dev/philosophy.rst: -------------------------------------------------------------------------------- 1 | Development Philosophy 2 | ====================== 3 | 4 | Requests is an open but opinionated library, created by an open but opinionated developer. 5 | 6 | 7 | Management Style 8 | ~~~~~~~~~~~~~~~~ 9 | 10 | `Kenneth Reitz `_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests. Only Kenneth may merge code into Requests. 11 | 12 | `Ian Cordasco `_ and `Cory Benfield `_ are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. The day-to-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is likely to be accepted by Kenneth. They do not have the authority to change code or merge code changes, though they may change documentation. Their word is not final. 13 | 14 | Values 15 | ~~~~~~ 16 | 17 | - Simplicity is always better than functionality. 18 | - Listen to everyone, then disregard it. 19 | - The API is all that matters. Everything else is secondary. 20 | - Fit the 90% use-case. Ignore the nay-sayers. 21 | 22 | Semantic Versioning 23 | ~~~~~~~~~~~~~~~~~~~ 24 | 25 | For many years, the open source community has been plagued with version number dystonia. Numbers vary so greatly from project to project, they are practically meaningless. 26 | 27 | Requests uses `Semantic Versioning `_. This specification seeks to put an end to this madness with a small set of practical guidelines for you and your colleagues to use in your next project. 28 | 29 | Standard Library? 30 | ~~~~~~~~~~~~~~~~~ 31 | 32 | Requests has no *active* plans to be included in the standard library. This decision has been discussed at length with Guido as well as numerous core developers. 33 | 34 | Essentially, the standard library is where a library goes to die. It is appropriate for a module to be included when active development is no longer necessary. 35 | 36 | Linux Distro Packages 37 | ~~~~~~~~~~~~~~~~~~~~~ 38 | 39 | Distributions have been made for many Linux repositories, including: Ubuntu, Debian, RHEL, and Arch. 40 | 41 | These distributions are sometimes divergent forks, or are otherwise not kept up-to-date with the latest code and bugfixes. PyPI (and its mirrors) and GitHub are the official distribution sources; alternatives are not supported by the Requests project. 42 | -------------------------------------------------------------------------------- /docs/dev/todo.rst: -------------------------------------------------------------------------------- 1 | How to Help 2 | =========== 3 | 4 | Requests is under active development, and contributions are more than welcome! 5 | 6 | #. Check for open issues or open a fresh issue to start a discussion around a bug. 7 | There is a Contributor Friendly tag for issues that should be ideal for people who are not very 8 | familiar with the codebase yet. 9 | #. Fork `the repository `_ on GitHub and start making your 10 | changes to a new branch. 11 | #. Write a test which shows that the bug was fixed. 12 | #. Send a pull request and bug the maintainer until it gets merged and published. :) 13 | Make sure to add yourself to `AUTHORS `_. 14 | 15 | Feature Freeze 16 | -------------- 17 | 18 | As of v1.0.0, Requests has now entered a feature freeze. Requests for new 19 | features and Pull Requests implementing those features will not be accepted. 20 | 21 | Development Dependencies 22 | ------------------------ 23 | 24 | You'll need to install py.test in order to run the Requests' test suite:: 25 | 26 | $ pip install -r requirements.txt 27 | $ py.test 28 | platform darwin -- Python 2.7.3 -- pytest-2.3.4 29 | collected 25 items 30 | 31 | test_requests.py ......................... 32 | 25 passed in 3.50 seconds 33 | 34 | Runtime Environments 35 | -------------------- 36 | 37 | Requests currently supports the following versions of Python: 38 | 39 | - Python 2.6 40 | - Python 2.7 41 | - Python 3.3 42 | - Python 3.4 43 | - Python 3.5 44 | - PyPy 1.9 45 | 46 | Google AppEngine is not officially supported although support is available 47 | with the `Requests-Toolbelt`_. 48 | 49 | .. _Requests-Toolbelt: http://toolbelt.readthedocs.io/ 50 | 51 | 52 | Are you crazy? 53 | -------------- 54 | 55 | - SPDY support would be awesome. No C extensions. 56 | 57 | Downstream Repackaging 58 | ---------------------- 59 | 60 | If you are repackaging Requests, please note that you must also redistribute the ``cacerts.pem`` file in order to get correct SSL functionality. 61 | -------------------------------------------------------------------------------- /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 服务人类 7 | ============================ 8 | 9 | 发行版本 v\ |version|. (:ref:`安装说明 `) 10 | 11 | .. image:: https://img.shields.io/pypi/l/requests.svg 12 | :target: https://pypi.python.org/pypi/requests 13 | 14 | .. image:: https://img.shields.io/pypi/wheel/requests.svg 15 | :target: https://pypi.python.org/pypi/requests 16 | 17 | .. image:: https://img.shields.io/pypi/pyversions/requests.svg 18 | :target: https://pypi.python.org/pypi/requests 19 | 20 | .. image:: https://codecov.io/github/requests/requests/coverage.svg?branch=master 21 | :target: https://codecov.io/github/requests/requests 22 | :alt: codecov.io 23 | 24 | .. image:: https://img.shields.io/badge/Say%20Thanks!-🦉-1EAEDB.svg 25 | :target: https://saythanks.io/to/kennethreitz 26 | 27 | Requests 唯一的一个\ **非转基因**\的 Python HTTP 库,人类可以安全享用。 28 | 29 | **警告**:非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症、冗余代码症、重新发明轮子\ 30 | 症、啃文档症、抑郁、头疼、甚至死亡。 31 | 32 | 看吧,这就是 Requests 的威力: 33 | :: 34 | 35 | >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) 36 | >>> r.status_code 37 | 200 38 | >>> r.headers['content-type'] 39 | 'application/json; charset=utf8' 40 | >>> r.encoding 41 | 'utf-8' 42 | >>> r.text 43 | u'{"type":"User"...' 44 | >>> r.json() 45 | {u'private_gists': 419, u'total_private_repos': 77, ...} 46 | 47 | 参见 `未使用 Requests 的相似代码 `_. 48 | 49 | Requests 允许你发送\ **纯天然,植物饲养**\的 HTTP/1.1 请求,无需手工劳动。你不需要手动为 50 | URL 添加查询字串,也不需要对 POST 数据进行表单编码。Keep-alive 和 HTTP 连接池的功能是 51 | 100% 自动化的,一切动力都来自于根植在 Requests 内部的 `urllib3 `_。 52 | 53 | 用户见证 54 | ------------ 55 | 56 | Twitter、Spotify、Microsoft、Amazon、Lyft、BuzzFeed、Reddit、NSA、女王殿下的政府、\ 57 | Amazon、Google、Twilio、Mozilla、Heroku、PayPal、NPR、Obama for America、\ 58 | Transifex、Native Instruments、Washington Post、Twitter、SoundCloud、Kippt、Readability、\ 59 | 以及若干不愿公开身份的联邦政府机构都在内部使用。 60 | 61 | **Armin Ronacher** 62 | Requests 是一个完美的例子,它证明了通过恰到好处的抽象,API 可以写得多么优美。 63 | 64 | **Matt DeBoard** 65 | 我要想个办法,把 @kennethreitz 写的 Python requests 模块做成纹身。一字不漏。 66 | 67 | **Daniel Greenfeld** 68 | 感谢 @kennethreitz 的 Requests 库,刚刚用 10 行代码炸掉了 1200 行意大利面代码。\ 69 | 今天真是爽呆了! 70 | 71 | **Kenny Meyers** 72 | Python HTTP: 疑惑与否,都去用 Requests 吧。简单优美,而且符合 Python 风格。 73 | 74 | 75 | 功能特性 76 | --------------- 77 | 78 | Requests 完全满足今日 web 的需求。 79 | 80 | - Keep-Alive & 连接池 81 | - 国际化域名和 URL 82 | - 带持久 Cookie 的会话 83 | - 浏览器式的 SSL 认证 84 | - 自动内容解码 85 | - 基本/摘要式的身份认证 86 | - 优雅的 key/value Cookie 87 | - 自动解压 88 | - Unicode 响应体 89 | - HTTP(S) 代理支持 90 | - 文件分块上传 91 | - 流下载 92 | - 连接超时 93 | - 分块请求 94 | - 支持 ``.netrc`` 95 | 96 | Requests 支持 Python 2.6—2.7以及3.3—3.7,而且能在 PyPy 下完美运行。 97 | 98 | 用户指南 99 | ---------- 100 | 101 | 这部分文档是以文字为主,从 Requests 的背景讲起,然后对 Requests 的重点功能做了逐一的介绍。 102 | 103 | .. toctree:: 104 | :maxdepth: 2 105 | 106 | user/intro 107 | user/install 108 | user/quickstart 109 | user/advanced 110 | user/authentication 111 | 112 | 社区指南 113 | ---------- 114 | 115 | 这部分文档也是文字为主,详细介绍了 Requests 的生态和社区。 116 | 117 | .. toctree:: 118 | :maxdepth: 1 119 | 120 | community/faq 121 | community/recommended 122 | community/out-there 123 | community/support 124 | community/vulnerabilities 125 | community/updates 126 | community/release-process 127 | 128 | API 文档/指南 129 | ---------------- 130 | 131 | 如果你要了解具体的函数、类、方法,这部分文档就是为你准备的。 132 | 133 | .. toctree:: 134 | :maxdepth: 2 135 | 136 | api 137 | 138 | 贡献指南 139 | --------- 140 | 141 | 如果你要为项目做出贡献,请参考这部分文档。 142 | 143 | .. toctree:: 144 | :maxdepth: 3 145 | 146 | dev/contributing 147 | dev/philosophy 148 | dev/todo 149 | dev/authors 150 | 151 | 没有别的指南了,你现在要靠自己了。 152 | 153 | 祝你好运。 154 | -------------------------------------------------------------------------------- /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/user/authentication.rst: -------------------------------------------------------------------------------- 1 | .. _authentication: 2 | 3 | 身份认证 4 | ============== 5 | 6 | 本篇文档讨论如何配合 Requests 使用多种身份认证方式。 7 | 8 | 许多 web 服务都需要身份认证,并且也有多种不同的认证类型。 9 | 以下,我们会从简单到复杂概述 Requests 中可用的几种身份认证形式。 10 | 11 | 12 | 基本身份认证 13 | ------------ 14 | 15 | 许多要求身份认证的web服务都接受 HTTP Basic Auth。这是最简单的一种身份认证,并且 Requests 16 | 对这种认证方式的支持是直接开箱即可用。 17 | 18 | 19 | 以 HTTP Basic Auth 发送请求非常简单: 20 | 21 | :: 22 | 23 | >>> from requests.auth import HTTPBasicAuth 24 | >>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass')) 25 | 26 | 27 | 28 | 事实上,HTTP Basic Auth 如此常见,Requests 就提供了一种简写的使用方式: 29 | 30 | :: 31 | 32 | >>> requests.get('https://api.github.com/user', auth=('user', 'pass')) 33 | 34 | 35 | 36 | 像这样在一个元组中提供认证信息与前一个 ``HTTPBasicAuth`` 例子是完全相同的。 37 | 38 | 39 | netrc 认证 40 | ~~~~~~~~~~~~~~~~~~~~ 41 | 42 | 如果认证方法没有收到 ``auth`` 参数,Requests 将试图从用户的 netrc 43 | 文件中获取 URL 的 hostname 需要的认证身份。The netrc file overrides raw HTTP authentication headers 44 | set with `headers=`. 45 | 46 | 如果找到了 hostname 对应的身份,就会以 HTTP Basic Auth 的形式发送请求。 47 | 48 | 49 | 摘要式身份认证 50 | --------------------- 51 | 52 | 另一种非常流行的 HTTP 身份认证形式是摘要式身份认证,Requests 对它的支持也是开箱即可用的: 53 | 54 | :: 55 | 56 | >>> from requests.auth import HTTPDigestAuth 57 | >>> url = 'http://httpbin.org/digest-auth/auth/user/pass' 58 | >>> requests.get(url, auth=HTTPDigestAuth('user', 'pass')) 59 | 60 | 61 | 62 | OAuth 1 认证 63 | ---------------------- 64 | 65 | Oauth 是一种常见的 Web API 认证方式。 ``requests-oauthlib`` 66 | 库可以让 Requests 用户简单地创建 OAuth 认证的请求: 67 | 68 | :: 69 | >>> import requests 70 | >>> from requests_oauthlib import OAuth1 71 | 72 | >>> url = 'https://api.twitter.com/1.1/account/verify_credentials.json' 73 | >>> auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET', 74 | ... 'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET') 75 | 76 | >>> requests.get(url, auth=auth) 77 | 78 | 79 | 关于 OAuth 工作流程的更多信息,请参见 `OAuth`_ 官方网站。 80 | 关于 requests-oauthlib 的文档和用例,请参见 GitHub 的 `requests_oauthlib`_ 代码库。 81 | 82 | OAuth 2 与 OpenID 连接认证 83 | ----------------------------------------- 84 | 85 | ``requests-oauthlib`` 库还可以处理 OAuth 2,OAuth 2 是 OpenID 连接的基础机制。 86 | 请查看 `requests-oauthlib OAuth2 documentation`_ 文档以了解 OAuth 2 的各种认证管理流程: 87 | 88 | * `Web Application Flow`_ 89 | * `Mobile Application Flow`_ 90 | * `Legacy Application Flow`_ 91 | * `Backend Application Flow`_ 92 | 93 | 其他身份认证形式 94 | -------------------- 95 | 96 | Requests 的设计允许其他形式的身份认证用简易的方式插入其中。开源社区的成员\ 97 | 时常为更复杂或不那么常用的身份认证形式编写认证处理插件。其中一些最优秀的已被\ 98 | 收集在 `Requests organization`_ 页面中,包括: 99 | 100 | - Kerberos_ 101 | - NTLM_ 102 | 103 | 如果你想使用其中任何一种身份认证形式,直接去它们的 GitHub 页面,依照说明进行。 104 | 105 | 新的身份认证形式 106 | ------------------- 107 | 108 | 如果你找不到所需要的身份认证形式的一个良好实现,你也可以自己实现它。Requests 非常易于添加你\ 109 | 自己的身份认证形式。 110 | 111 | 要想自己实现,就从 :class:`AuthBase ` 继承一个子类,并实现 ``__call__()`` 方法: 112 | 113 | :: 114 | 115 | >>> import requests 116 | >>> class MyAuth(requests.auth.AuthBase): 117 | ... def __call__(self, r): 118 | ... # Implement my authentication 119 | ... return r 120 | ... 121 | >>> url = 'http://httpbin.org/get' 122 | >>> requests.get(url, auth=MyAuth()) 123 | 124 | 125 | 当一个身份认证模块被附加到一个请求上,在设置 request 期间就会调用该模块。因此 ``__call__`` 126 | 方法必须完成使得身份认证生效的所有事情。一些身份认证形式会额外地添加钩子来提供进一步的功能。 127 | 128 | 你可以在 `Requests organization`_ 页面的 ``auth.py`` 文件中找到更多示例。 129 | 130 | .. _OAuth: http://oauth.net/ 131 | .. _requests_oauthlib: https://github.com/requests/requests-oauthlib 132 | .. _requests-oauthlib OAuth2 documentation: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html 133 | .. _Web Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#web-application-flow 134 | .. _Mobile Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#mobile-application-flow 135 | .. _Legacy Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#legacy-application-flow 136 | .. _Backend Application Flow: http://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#backend-application-flow 137 | .. _Kerberos: https://github.com/requests/requests-kerberos 138 | .. _NTLM: https://github.com/requests/requests-ntlm 139 | .. _Requests organization: https://github.com/requests 140 | -------------------------------------------------------------------------------- /docs/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | 安装 Requests 4 | ============== 5 | 6 | 这部分文档包含了 Requests 的安装过程,使用任何软件的第一步就是正确地安装它。 7 | 8 | 9 | pip install requests 10 | --------------------- 11 | 12 | 要安装 Requests,只要在你的终端中运行这个简单命令即可: 13 | 14 | :: 15 | $ pip install requests 16 | 17 | 如果你没有安装 pip (啧啧),这个 `Python installation guide `_ 18 | 可以带你完成这一流程。 19 | 20 | 21 | 获得源码 22 | ------------ 23 | 24 | Requests 一直在 Github 上积极地开发,你可以一直从\ 25 | `这里 `_\获取到代码。 26 | 27 | 你可以克隆公共版本库: 28 | 29 | :: 30 | 31 | git clone git://github.com/kennethreitz/requests.git 32 | 33 | 也可以下载 `tarball `_:: 34 | 35 | $ curl -OL https://github.com/requests/requests/tarball/master 36 | # Windows 用户也可选择 zip 包 37 | 38 | 获得代码之后,你就可以轻松的将它嵌入到你的 python 包里,或者安装到你的 site-packages:: 39 | 40 | $ cd requests 41 | $ pip install . 42 | -------------------------------------------------------------------------------- /docs/user/intro.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | 简介 4 | ============ 5 | 6 | 开发哲学 7 | ---------- 8 | 9 | Requests 是以 :pep:`20` 的箴言为中心开发的 10 | 11 | 12 | #. Beautiful is better than ugly.(美丽优于丑陋) 13 | #. Explicit is better than implicit.(直白优于含蓄) 14 | #. Simple is better than complex.(简单优于复杂) 15 | #. Complex is better than complicated.(复杂优于繁琐) 16 | #. Readability counts.(可读性很重要) 17 | 18 | 对于 Requests 所有的贡献都应牢记这些重要的准则。 19 | 20 | .. _`apache2`: 21 | 22 | Apache2 协议 23 | ------------- 24 | 25 | 现在你找到的许多开源项目都是以 `GPL 协议`_\发布的。虽然 GPL 有它自己的一席之地, 26 | 但在开始你的下一个开源项目时,GPL 应该不再是你的默认选择。 27 | 28 | 项目发行于 GPL 协议之后,就不能用于任何本身没开源的商业产品中。 29 | 30 | MIT、BSD、ISC、Apache2 许可都是优秀的替代品,它们允许你的开源软件自由应用在私有闭源软件中。 31 | 32 | Requests 的发布许可为 `Apache2 License`_. 33 | 34 | .. _`GPL 协议`: http://www.opensource.org/licenses/gpl-license.php 35 | .. _`Apache2 License`: http://opensource.org/licenses/Apache-2.0 36 | 37 | 38 | Requests 协议 39 | ---------------- 40 | 41 | .. include:: ../../LICENSE 42 | -------------------------------------------------------------------------------- /docs/user/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _quickstart: 2 | 3 | 快速上手 4 | ========== 5 | 6 | .. module:: requests.models 7 | 8 | 迫不及待了吗?本页内容为如何入门 Requests 提供了很好的指引。其假设你已经安装了 Requests。\ 9 | 如果还没有,去\ :ref:`安装 `\ 一节看看吧。 10 | 11 | 首先,确认一下: 12 | 13 | * Requests :ref:`已安装 ` 14 | * Requests :ref:`是最新的 ` 15 | 16 | 让我们从一些简单的示例开始吧。 17 | 18 | 发送请求 19 | ---------- 20 | 21 | 使用 Requests 发送网络请求非常简单。 22 | 23 | 一开始要导入 Requests 模块: 24 | 25 | :: 26 | 27 | >>> import requests 28 | 29 | 然后,尝试获取某个网页。本例子中,我们来获取 Github 的公共时间线: 30 | 31 | :: 32 | 33 | >>> r = requests.get('https://api.github.com/events') 34 | 35 | 现在,我们有一个名为 ``r`` 的 :class:`Response ` 36 | 对象。我们可以从这个对象中获取所有我们想要的信息。 37 | 38 | Requests 简便的 API 意味着所有 HTTP 请求类型都是显而易见的。例如,你可以这样发送一个 39 | HTTP POST 请求: 40 | 41 | :: 42 | 43 | >>> r = requests.post('http://httpbin.org/post', data = {'key':'value'}) 44 | 45 | 漂亮,对吧?那么其他 HTTP 请求类型:PUT,DELETE,HEAD 以及 OPTIONS 又是如何的呢?都是一样的简单: 46 | 47 | :: 48 | 49 | >>> r = requests.put('http://httpbin.org/put', data = {'key':'value'}) 50 | >>> r = requests.delete('http://httpbin.org/delete') 51 | >>> r = requests.head('http://httpbin.org/get') 52 | >>> r = requests.options('http://httpbin.org/get') 53 | 54 | 都很不错吧,但这也仅是 Requests 的冰山一角呢。 55 | 56 | 传递 URL 参数 57 | ------------------- 58 | 59 | 你也许经常想为 URL 的查询字符串(query string)传递某种数据。如果你是手工构建 URL,那么数据会以键/值\ 60 | 对的形式置于 URL 中,跟在一个问号的后面。例如, ``httpbin.org/get?key=val``\。 61 | Requests 允许你使用 ``params`` 关键字参数,以一个字符串字典来提供这些参数。举例来说,如果你想传递 62 | ``key1=value1`` 和 ``key2=value2`` 到 ``httpbin.org/get`` ,那么你可以使用如下代码: 63 | 64 | :: 65 | 66 | >>> payload = {'key1': 'value1', 'key2': 'value2'} 67 | >>> r = requests.get("http://httpbin.org/get", params=payload) 68 | 69 | 通过打印输出该 URL,你能看到 URL 已被正确编码: 70 | 71 | :: 72 | 73 | >>> print(r.url) 74 | http://httpbin.org/get?key2=value2&key1=value1 75 | 76 | 注意字典里值为 ``None`` 的键都不会被添加到 URL 的查询字符串里。 77 | 78 | 你还可以将一个列表作为值传入: 79 | 80 | :: 81 | 82 | >>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']} 83 | 84 | >>> r = requests.get('http://httpbin.org/get', params=payload) 85 | >>> print(r.url) 86 | http://httpbin.org/get?key1=value1&key2=value2&key2=value3 87 | 88 | 响应内容 89 | -------------- 90 | 91 | 我们能读取服务器响应的内容。再次以 GitHub 时间线为例: 92 | 93 | :: 94 | 95 | >>> import requests 96 | >>> r = requests.get('https://api.github.com/events') 97 | >>> r.text 98 | u'[{"repository":{"open_issues":0,"url":"https://github.com/... 99 | 100 | Requests 会自动解码来自服务器的内容。大多数 unicode 字符集都能被无缝地解码。 101 | 102 | 请求发出后,Requests 会基于 HTTP 头部对响应的编码作出有根据的推测。当你访问 ``r.text`` 103 | 之时,Requests 会使用其推测的文本编码。你可以找出 Requests 使用了什么编码,并且能够使用 104 | ``r.encoding`` 属性来改变它: 105 | 106 | :: 107 | 108 | >>> r.encoding 109 | 'utf-8' 110 | >>> r.encoding = 'ISO-8859-1' 111 | 112 | 如果你改变了编码,每当你访问 ``r.text`` ,Request 都将会使用 ``r.encoding`` 113 | 的新值。你可能希望在使用特殊逻辑计算出文本的编码的情况下来修改编码。比如 HTTP 和 XML 114 | 自身可以指定编码。这样的话,你应该使用 ``r.content`` 来找到编码,然后设置 ``r.encoding`` 115 | 为相应的编码。这样就能使用正确的编码解析 ``r.text`` 了。 116 | 117 | 在你需要的情况下,Requests 也可以使用定制的编码。如果你创建了自己的编码,并使用 118 | ``codecs`` 模块进行注册,你就可以轻松地使用这个解码器名称作为 ``r.encoding`` 的值, 119 | 然后由 Requests 来为你处理编码。 120 | 121 | 122 | 二进制响应内容 123 | ------------------- 124 | 125 | 你也能以字节的方式访问请求响应体,对于非文本请求: 126 | 127 | :: 128 | 129 | >>> r.content 130 | b'[{"repository":{"open_issues":0,"url":"https://github.com/... 131 | 132 | Requests 会自动为你解码 ``gzip`` 和 ``deflate`` 传输编码的响应数据。 133 | 134 | 例如,以请求返回的二进制数据创建一张图片,你可以使用如下代码: 135 | 136 | :: 137 | 138 | >>> from PIL import Image 139 | >>> from io import BytesIO 140 | 141 | >>> i = Image.open(BytesIO(r.content)) 142 | 143 | 144 | JSON 响应内容 145 | --------------- 146 | 147 | Requests 中也有一个内置的 JSON 解码器,助你处理 JSON 数据: 148 | 149 | :: 150 | 151 | >>> import requests 152 | 153 | >>> r = requests.get('https://api.github.com/events') 154 | >>> r.json() 155 | [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/... 156 | 157 | 如果 JSON 解码失败, ``r.json()`` 就会抛出一个异常。例如,响应内容是 401 (Unauthorized),\ 158 | 尝试访问 ``r.json()`` 将会抛出 ``ValueError: No JSON object could be decoded`` 异常。 159 | 160 | 需要注意的是,成功调用 ``r.json()`` 并\**不**\ 意味着响应的成功。有的服务器会在失败的响应中\ 161 | 包含一个 JSON 对象(比如 HTTP 500 的错误细节)。这种 JSON 会被解码返回。要检查请求是否\ 162 | 成功,请使用 ``r.raise_for_status()`` 或者检查 ``r.status_code`` 是否和你的期望相同。 163 | 164 | 165 | 原始响应内容 166 | ---------------- 167 | 168 | 在罕见的情况下,你可能想获取来自服务器的原始套接字响应,那么你可以访问 ``r.raw``\。 169 | 如果你确实想这么干,那请你确保在初始请求中设置了 ``stream=True``\。具体你可以这么做: 170 | 171 | :: 172 | 173 | >>> r = requests.get('https://api.github.com/events', stream=True) 174 | >>> r.raw 175 | 176 | >>> r.raw.read(10) 177 | '\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03' 178 | 179 | 但一般情况下,你应该以下面的模式将文本流保存到文件: 180 | 181 | :: 182 | 183 | with open(filename, 'wb') as fd: 184 | for chunk in r.iter_content(chunk_size): 185 | fd.write(chunk) 186 | 187 | 使用 ``Response.iter_content`` 将会处理大量你直接使用 ``Response.raw`` 不得不处理的。 188 | 当流下载时,上面是优先推荐的获取内容方式。 Note that ``chunk_size`` can be freely adjusted to a number that 189 | may better fit your use cases. 190 | 191 | 定制请求头 192 | ------------- 193 | 194 | 如果你想为请求添加 HTTP 头部,只要简单地传递一个 ``dict`` 给 ``headers`` 参数就可以了。 195 | 196 | 例如,在前一个示例中我们没有指定 content-type:: 197 | 198 | >>> url = 'https://api.github.com/some/endpoint' 199 | >>> headers = {'user-agent': 'my-app/0.0.1'} 200 | 201 | >>> r = requests.get(url, headers=headers) 202 | 203 | 注意: 定制 header 的优先级低于某些特定的信息源,例如: 204 | 205 | * 如果在 ``.netrc`` 中设置了用户认证信息,使用 `headers=` 设置的授权就不会生效。而如果设置了 206 | ``auth=`` 参数,\``.netrc`` 的设置就无效了。 207 | * 如果被重定向到别的主机,授权 header 就会被删除。 208 | * 代理授权 header 会被 URL 中提供的代理身份覆盖掉。 209 | * 在我们能判断内容长度的情况下,header 的 Content-Length 会被改写。 210 | 211 | 更进一步讲,Requests 不会基于定制 header 的具体情况改变自己的行为。只不过在最后的请求中,所有的 212 | header 信息都会被传递进去。 213 | 214 | 注意: 所有的 header 值必须是 ``string``、bytestring 或者 unicode。尽管传递 unicode 215 | header 也是允许的,但不建议这样做。 216 | 217 | 更加复杂的 POST 请求 218 | ---------------------- 219 | 220 | 通常,你想要发送一些编码为表单形式的数据——非常像一个 HTML 表单。要实现这个,只需简单地传递\ 221 | 一个字典给 `data` 参数。你的数据字典在发出请求时会自动编码为表单形式: 222 | 223 | :: 224 | 225 | >>> payload = {'key1': 'value1', 'key2': 'value2'} 226 | 227 | >>> r = requests.post("http://httpbin.org/post", data=payload) 228 | >>> print(r.text) 229 | { 230 | ... 231 | "form": { 232 | "key2": "value2", 233 | "key1": "value1" 234 | }, 235 | ... 236 | } 237 | 238 | 你还可以为 ``data`` 参数传入一个元组列表。在表单中多个元素使用同一 key 的时候,这种方式尤其有效: 239 | 240 | :: 241 | 242 | >>> payload = (('key1', 'value1'), ('key1', 'value2')) 243 | >>> r = requests.post('http://httpbin.org/post', data=payload) 244 | >>> print(r.text) 245 | { 246 | ... 247 | "form": { 248 | "key1": [ 249 | "value1", 250 | "value2" 251 | ] 252 | }, 253 | ... 254 | } 255 | 256 | 很多时候你想要发送的数据并非编码为表单形式的。如果你传递一个 ``string`` 而不是一个 ``dict``\,\ 257 | 那么数据会被直接发布出去。 258 | 259 | 例如,Github API v3 接受编码为 JSON 的 POST/PATCH 数据: 260 | 261 | :: 262 | 263 | >>> import json 264 | 265 | >>> url = 'https://api.github.com/some/endpoint' 266 | >>> payload = {'some': 'data'} 267 | 268 | >>> r = requests.post(url, data=json.dumps(payload)) 269 | 270 | 此处除了可以自行对 ``dict`` 进行编码,你还可以使用 ``json`` 参数直接传递,然后它就会被自动\ 271 | 编码。这是 2.4.2 版的新加功能: 272 | 273 | :: 274 | 275 | >>> url = 'https://api.github.com/some/endpoint' 276 | >>> payload = {'some': 'data'} 277 | 278 | >>> r = requests.post(url, json=payload) 279 | 280 | 281 | POST一个多部分编码(Multipart-Encoded)的文件 282 | --------------------------------------------- 283 | 284 | Requests 使得上传多部分编码文件变得很简单: 285 | 286 | :: 287 | 288 | >>> url = 'http://httpbin.org/post' 289 | >>> files = {'file': open('report.xls', 'rb')} 290 | 291 | >>> r = requests.post(url, files=files) 292 | >>> r.text 293 | { 294 | ... 295 | "files": { 296 | "file": "" 297 | }, 298 | ... 299 | } 300 | 301 | 你可以显式地设置文件名,文件类型和请求头: 302 | 303 | :: 304 | 305 | >>> url = 'http://httpbin.org/post' 306 | >>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})} 307 | 308 | >>> r = requests.post(url, files=files) 309 | >>> r.text 310 | { 311 | ... 312 | "files": { 313 | "file": "" 314 | }, 315 | ... 316 | } 317 | 318 | 如果你想,你也可以发送作为文件来接收的字符串: 319 | 320 | :: 321 | 322 | >>> url = 'http://httpbin.org/post' 323 | >>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')} 324 | 325 | >>> r = requests.post(url, files=files) 326 | >>> r.text 327 | { 328 | ... 329 | "files": { 330 | "file": "some,data,to,send\\nanother,row,to,send\\n" 331 | }, 332 | ... 333 | } 334 | 335 | 如果你发送一个非常大的文件作为 ``multipart/form-data`` 请求,你可能希望将请求做成数据流。\ 336 | 默认下 ``requests`` 不支持, 但有个第三方包 ``requests-toolbelt`` 是支持的。你可以阅读 337 | `toolbelt 文档 `_ 来了解使用方法。 338 | 339 | 在一个请求中发送多文件参考 :ref:`高级用法 ` 一节。 340 | 341 | .. admonition:: 警告 342 | 343 | 我们强烈建议你用二进制模式(`binary mode`_)打开文件。这是因为 Requests 可能会试图为你提供 344 | ``Content-Length`` header,在它这样做的时候,这个值会被设为文件的字节数(*bytes*)。\ 345 | 如果用文本模式(text mode)打开文件,就可能会发生错误。 346 | 347 | .. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files 348 | 349 | 350 | 响应状态码 351 | -------------- 352 | 353 | 我们可以检测响应状态码: 354 | 355 | :: 356 | 357 | >>> r = requests.get('http://httpbin.org/get') 358 | >>> r.status_code 359 | 200 360 | 361 | 为方便引用,Requests还附带了一个内置的状态码查询对象: 362 | 363 | :: 364 | 365 | >>> r.status_code == requests.codes.ok 366 | True 367 | 368 | 如果发送了一个错误请求(一个 4XX 客户端错误,或者 5XX 服务器错误响应),我们可以通过 369 | :meth:`Response.raise_for_status() ` 370 | 来抛出异常: 371 | 372 | :: 373 | 374 | >>> bad_r = requests.get('http://httpbin.org/status/404') 375 | >>> bad_r.status_code 376 | 404 377 | 378 | >>> bad_r.raise_for_status() 379 | Traceback (most recent call last): 380 | File "requests/models.py", line 832, in raise_for_status 381 | raise http_error 382 | requests.exceptions.HTTPError: 404 Client Error 383 | 384 | 但是,由于我们的例子中 ``r`` 的 ``status_code`` 是 ``200`` ,当我们调用 385 | ``raise_for_status()`` 时,得到的是: 386 | 387 | :: 388 | 389 | >>> r.raise_for_status() 390 | None 391 | 392 | 一切都挺和谐哈。 393 | 394 | 395 | 响应头 396 | ---------- 397 | 398 | 我们可以查看以一个 Python 字典形式展示的服务器响应头: 399 | 400 | :: 401 | 402 | >>> r.headers 403 | { 404 | 'content-encoding': 'gzip', 405 | 'transfer-encoding': 'chunked', 406 | 'connection': 'close', 407 | 'server': 'nginx/1.0.4', 408 | 'x-runtime': '148ms', 409 | 'etag': '"e1ca502697e5c9317743dc078f67693f"', 410 | 'content-type': 'application/json' 411 | } 412 | 413 | 但是这个字典比较特殊:它是仅为 HTTP 头部而生的。根据 414 | `RFC 2616 `_\, 415 | HTTP 头部是大小写不敏感的。 416 | 417 | 因此,我们可以使用任意大写形式来访问这些响应头字段: 418 | 419 | :: 420 | 421 | >>> r.headers['Content-Type'] 422 | 'application/json' 423 | 424 | >>> r.headers.get('content-type') 425 | 'application/json' 426 | 427 | 它还有一个特殊点,那就是服务器可以多次接受同一 header,每次都使用不同的值。但 Requests 428 | 会将它们合并,这样它们就可以用一个映射来表示出来,参见 429 | `RFC 7230 `_: 430 | 431 | A recipient MAY combine multiple header fields with the same field name 432 | into one "field-name: field-value" pair, without changing the semantics 433 | of the message, by appending each subsequent field value to the combined 434 | field value in order, separated by a comma. 435 | 436 | 接收者可以合并多个相同名称的 header 栏位,把它们合为一个 "field-name: field-value" 437 | 配对,将每个后续的栏位值依次追加到合并的栏位值中,用逗号隔开即可,这样做不会改变信息的语义。 438 | 439 | Cookie 440 | --------- 441 | 442 | 如果某个响应中包含一些 cookie,你可以快速访问它们: 443 | 444 | :: 445 | 446 | >>> url = 'http://example.com/some/cookie/setting/url' 447 | >>> r = requests.get(url) 448 | 449 | >>> r.cookies['example_cookie_name'] 450 | 'example_cookie_value' 451 | 452 | 要想发送你的cookies到服务器,可以使用 ``cookies`` 参数: 453 | 454 | :: 455 | 456 | >>> url = 'http://httpbin.org/cookies' 457 | >>> cookies = dict(cookies_are='working') 458 | 459 | >>> r = requests.get(url, cookies=cookies) 460 | >>> r.text 461 | '{"cookies": {"cookies_are": "working"}}' 462 | 463 | Cookie 的返回对象为 :class:`~requests.cookies.RequestsCookieJar`\,它的行为和字典\ 464 | 类似,但接口更为完整,适合跨域名跨路径使用。你还可以把 Cookie Jar 传到 Requests 中: 465 | 466 | :: 467 | 468 | >>> jar = requests.cookies.RequestsCookieJar() 469 | >>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies') 470 | >>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere') 471 | >>> url = 'http://httpbin.org/cookies' 472 | >>> r = requests.get(url, cookies=jar) 473 | >>> r.text 474 | '{"cookies": {"tasty_cookie": "yum"}}' 475 | 476 | 重定向与请求历史 477 | ------------------- 478 | 479 | 默认情况下,除了 HEAD, Requests 会自动处理所有重定向。 480 | 481 | 可以使用响应对象的 ``history`` 方法来追踪重定向。 482 | 483 | :attr:`Response.history ` 是一个 484 | :class:`Response ` 对象的列表,为了完成请求而创建了这些对象。\ 485 | 这个对象列表按照从最老到最近的请求进行排序。 486 | 487 | 例如,Github 将所有的 HTTP 请求重定向到 HTTPS: 488 | 489 | :: 490 | 491 | >>> r = requests.get('http://github.com') 492 | 493 | >>> r.url 494 | 'https://github.com/' 495 | 496 | >>> r.status_code 497 | 200 498 | 499 | >>> r.history 500 | [] 501 | 502 | 503 | 如果你使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么你可以通过 ``allow_redirects`` 504 | 参数禁用重定向处理: 505 | 506 | :: 507 | 508 | >>> r = requests.get('http://github.com', allow_redirects=False) 509 | >>> r.status_code 510 | 301 511 | >>> r.history 512 | [] 513 | 514 | 如果你使用了 HEAD,你也可以启用重定向: 515 | 516 | :: 517 | 518 | >>> r = requests.head('http://github.com', allow_redirects=True) 519 | >>> r.url 520 | 'https://github.com/' 521 | >>> r.history 522 | [] 523 | 524 | 525 | 超时 526 | -------- 527 | 528 | 你可以告诉 requests 在经过以 ``timeout`` 参数设定的秒数时间之后停止等待响应。\ 529 | 基本上所有的生产代码都应该使用这一参数。如果不使用,你的程序可能会永远失去响应: 530 | 531 | :: 532 | 533 | >>> requests.get('http://github.com', timeout=0.001) 534 | Traceback (most recent call last): 535 | File "", line 1, in 536 | requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001) 537 | 538 | 539 | .. admonition:: 注意 540 | 541 | ``timeout`` 仅对连接过程有效,与响应体的下载无关。 ``timeout`` 并不是整个下载响应的\ 542 | 时间限制,而是如果服务器在 ``timeout`` 秒内没有应答,将会引发一个异常(更精确地说,是在 543 | ``timeout`` 秒内没有从基础套接字上接收到任何字节的数据时)If no timeout is specified explicitly, requests do 544 | not time out. 545 | 546 | 错误与异常 547 | -------------- 548 | 549 | 遇到网络问题(如:DNS 查询失败、拒绝连接等)时,Requests 会抛出一个 550 | :exc:`~requests.exceptions.ConnectionError` 异常。 551 | 552 | 如果 HTTP 请求返回了不成功的状态码, :meth:`Response.raise_for_status() ` 553 | 会抛出一个 :exc:`~requests.exceptions.HTTPError` 异常。 554 | 555 | 若请求超时,则抛出一个 :exc:`~requests.exceptions.Timeout` 异常。 556 | 557 | 若请求超过了设定的最大重定向次数,则会抛出一个 :exc:`~requests.exceptions.TooManyRedirects` 异常。 558 | 559 | 所有Requests显式抛出的异常都继承自 :exc:`requests.exceptions.RequestException` 。 560 | 561 | ----------------------- 562 | 563 | 准备好学习更多内容了吗?去 :ref:`高级用法 ` 一节看看吧。 564 | -------------------------------------------------------------------------------- /ext/requests-logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requests/requests-docs-cn/a7bffa84978a5018011f497b6b90fde247a4380a/ext/requests-logo.ai -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -p no:warnings -------------------------------------------------------------------------------- /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. Basic GET 13 | usage: 14 | 15 | >>> import requests 16 | >>> r = requests.get('https://www.python.org') 17 | >>> r.status_code 18 | 200 19 | >>> '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('http://httpbin.org/post', data=payload) 26 | >>> print(r.text) 27 | { 28 | ... 29 | "form": { 30 | "key2": "value2", 31 | "key1": "value1" 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 chardet 45 | import warnings 46 | from .exceptions import RequestsDependencyWarning 47 | 48 | 49 | def check_compatibility(urllib3_version, chardet_version): 50 | urllib3_version = urllib3_version.split('.') 51 | assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. 52 | 53 | # Sometimes, urllib3 only reports its version as 16.1. 54 | if len(urllib3_version) == 2: 55 | urllib3_version.append('0') 56 | 57 | # Check urllib3 for compatibility. 58 | major, minor, patch = urllib3_version # noqa: F811 59 | major, minor, patch = int(major), int(minor), int(patch) 60 | # urllib3 >= 1.21.1, < 1.22 61 | assert major == 1 62 | assert minor >= 21 63 | assert minor <= 22 64 | 65 | # Check chardet for compatibility. 66 | major, minor, patch = chardet_version.split('.')[:3] 67 | major, minor, patch = int(major), int(minor), int(patch) 68 | # chardet >= 3.0.2, < 3.1.0 69 | assert major == 3 70 | assert minor < 1 71 | assert patch >= 2 72 | 73 | 74 | # Check imported dependencies for compatibility. 75 | try: 76 | check_compatibility(urllib3.__version__, chardet.__version__) 77 | except (AssertionError, ValueError): 78 | warnings.warn("urllib3 ({0}) or chardet ({1}) doesn't match a supported " 79 | "version!".format(urllib3.__version__, chardet.__version__), 80 | RequestsDependencyWarning) 81 | 82 | # Attempt to enable urllib3's SNI support, if possible 83 | try: 84 | from urllib3.contrib import pyopenssl 85 | pyopenssl.inject_into_urllib3() 86 | except ImportError: 87 | pass 88 | 89 | # urllib3's DependencyWarnings should be silenced. 90 | from urllib3.exceptions import DependencyWarning 91 | warnings.simplefilter('ignore', DependencyWarning) 92 | 93 | from .__version__ import __title__, __description__, __url__, __version__ 94 | from .__version__ import __build__, __author__, __author_email__, __license__ 95 | from .__version__ import __copyright__, __cake__ 96 | 97 | from . import utils 98 | from . import packages 99 | from .models import Request, Response, PreparedRequest 100 | from .api import request, get, head, post, patch, put, delete, options 101 | from .sessions import session, Session 102 | from .status_codes import codes 103 | from .exceptions import ( 104 | RequestException, Timeout, URLRequired, 105 | TooManyRedirects, HTTPError, ConnectionError, 106 | FileModeWarning, ConnectTimeout, ReadTimeout 107 | ) 108 | 109 | # Set default logging handler to avoid "No handler found" warnings. 110 | import logging 111 | try: # Python 2.7+ 112 | from logging import NullHandler 113 | except ImportError: 114 | class NullHandler(logging.Handler): 115 | def emit(self, record): 116 | pass 117 | 118 | logging.getLogger(__name__).addHandler(NullHandler()) 119 | 120 | # FileModeWarnings go off per the default. 121 | warnings.simplefilter('default', FileModeWarning, append=True) 122 | -------------------------------------------------------------------------------- /requests/__version__.py: -------------------------------------------------------------------------------- 1 | # .-. .-. .-. . . .-. .-. .-. .-. 2 | # |( |- |.| | | |- `-. | `-. 3 | # ' ' `-' `-`.`-' `-' `-' ' `-' 4 | 5 | __title__ = 'requests' 6 | __description__ = 'Python HTTP for Humans.' 7 | __url__ = 'http://python-requests.org' 8 | __version__ = '2.18.1' 9 | __build__ = 0x021801 10 | __author__ = 'Kenneth Reitz' 11 | __author_email__ = 'me@kennethreitz.org' 12 | __license__ = 'Apache 2.0' 13 | __copyright__ = 'Copyright 2017 Kenneth Reitz' 14 | __cake__ = u'\u2728 \U0001f370 \u2728' 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. 20 | :param url: URL for the new :class:`Request` object. 21 | :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. 22 | :param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`. 23 | :param json: (optional) json data to send in the body of the :class:`Request`. 24 | :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. 25 | :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. 26 | :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. 27 | ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` 28 | or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string 29 | defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers 30 | to add for the file. 31 | :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. 32 | :param timeout: (optional) How many seconds to wait for the server to send data 33 | before giving up, as a float, or a :ref:`(connect timeout, read 34 | timeout) ` tuple. 35 | :type timeout: float or tuple 36 | :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. 37 | :type allow_redirects: bool 38 | :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. 39 | :param verify: (optional) Either a boolean, in which case it controls whether we verify 40 | the server's TLS certificate, or a string, in which case it must be a path 41 | to a CA bundle to use. Defaults to ``True``. 42 | :param stream: (optional) if ``False``, the response content will be immediately downloaded. 43 | :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. 44 | :return: :class:`Response ` object 45 | :rtype: requests.Response 46 | 47 | Usage:: 48 | 49 | >>> import requests 50 | >>> req = requests.request('GET', 'http://httpbin.org/get') 51 | 52 | """ 53 | 54 | # By using the 'with' statement we are sure the session is closed, thus we 55 | # avoid leaving sockets open which can trigger a ResourceWarning in some 56 | # cases, and look like a memory leak in others. 57 | with sessions.Session() as session: 58 | return session.request(method=method, url=url, **kwargs) 59 | 60 | 61 | def get(url, params=None, **kwargs): 62 | r"""Sends a GET request. 63 | 64 | :param url: URL for the new :class:`Request` object. 65 | :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. 66 | :param \*\*kwargs: Optional arguments that ``request`` takes. 67 | :return: :class:`Response ` object 68 | :rtype: requests.Response 69 | """ 70 | 71 | kwargs.setdefault('allow_redirects', True) 72 | return request('get', url, params=params, **kwargs) 73 | 74 | 75 | def options(url, **kwargs): 76 | r"""Sends an OPTIONS request. 77 | 78 | :param url: URL for the new :class:`Request` object. 79 | :param \*\*kwargs: Optional arguments that ``request`` takes. 80 | :return: :class:`Response ` object 81 | :rtype: requests.Response 82 | """ 83 | 84 | kwargs.setdefault('allow_redirects', True) 85 | return request('options', url, **kwargs) 86 | 87 | 88 | def head(url, **kwargs): 89 | r"""Sends a HEAD request. 90 | 91 | :param url: URL for the new :class:`Request` object. 92 | :param \*\*kwargs: Optional arguments that ``request`` takes. 93 | :return: :class:`Response ` object 94 | :rtype: requests.Response 95 | """ 96 | 97 | kwargs.setdefault('allow_redirects', False) 98 | return request('head', url, **kwargs) 99 | 100 | 101 | def post(url, data=None, json=None, **kwargs): 102 | r"""Sends a POST request. 103 | 104 | :param url: URL for the new :class:`Request` object. 105 | :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`. 106 | :param json: (optional) json data to send in the body of the :class:`Request`. 107 | :param \*\*kwargs: Optional arguments that ``request`` takes. 108 | :return: :class:`Response ` object 109 | :rtype: requests.Response 110 | """ 111 | 112 | return request('post', url, data=data, json=json, **kwargs) 113 | 114 | 115 | def put(url, data=None, **kwargs): 116 | r"""Sends a PUT request. 117 | 118 | :param url: URL for the new :class:`Request` object. 119 | :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`. 120 | :param json: (optional) json data to send in the body of the :class:`Request`. 121 | :param \*\*kwargs: Optional arguments that ``request`` takes. 122 | :return: :class:`Response ` object 123 | :rtype: requests.Response 124 | """ 125 | 126 | return request('put', url, data=data, **kwargs) 127 | 128 | 129 | def patch(url, data=None, **kwargs): 130 | r"""Sends a PATCH request. 131 | 132 | :param url: URL for the new :class:`Request` object. 133 | :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`. 134 | :param json: (optional) json data to send in the body of the :class:`Request`. 135 | :param \*\*kwargs: Optional arguments that ``request`` takes. 136 | :return: :class:`Response ` object 137 | :rtype: requests.Response 138 | """ 139 | 140 | return request('patch', url, data=data, **kwargs) 141 | 142 | 143 | def delete(url, **kwargs): 144 | r"""Sends a DELETE request. 145 | 146 | :param url: URL for the new :class:`Request` object. 147 | :param \*\*kwargs: Optional arguments that ``request`` takes. 148 | :return: :class:`Response ` object 149 | :rtype: requests.Response 150 | """ 151 | 152 | return request('delete', url, **kwargs) 153 | -------------------------------------------------------------------------------- /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 ({0!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 ({0!r}) to " 52 | "a string or bytes object in the near future to avoid " 53 | "problems.".format(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 | 157 | KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) 158 | 159 | if hash_utf8 is None: 160 | return None 161 | 162 | # XXX not implemented yet 163 | entdig = None 164 | p_parsed = urlparse(url) 165 | #: path is request-uri defined in RFC 2616 which should not be empty 166 | path = p_parsed.path or "/" 167 | if p_parsed.query: 168 | path += '?' + p_parsed.query 169 | 170 | A1 = '%s:%s:%s' % (self.username, realm, self.password) 171 | A2 = '%s:%s' % (method, path) 172 | 173 | HA1 = hash_utf8(A1) 174 | HA2 = hash_utf8(A2) 175 | 176 | if nonce == self._thread_local.last_nonce: 177 | self._thread_local.nonce_count += 1 178 | else: 179 | self._thread_local.nonce_count = 1 180 | ncvalue = '%08x' % self._thread_local.nonce_count 181 | s = str(self._thread_local.nonce_count).encode('utf-8') 182 | s += nonce.encode('utf-8') 183 | s += time.ctime().encode('utf-8') 184 | s += os.urandom(8) 185 | 186 | cnonce = (hashlib.sha1(s).hexdigest()[:16]) 187 | if _algorithm == 'MD5-SESS': 188 | HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) 189 | 190 | if not qop: 191 | respdig = KD(HA1, "%s:%s" % (nonce, HA2)) 192 | elif qop == 'auth' or 'auth' in qop.split(','): 193 | noncebit = "%s:%s:%s:%s:%s" % ( 194 | nonce, ncvalue, cnonce, 'auth', HA2 195 | ) 196 | respdig = KD(HA1, noncebit) 197 | else: 198 | # XXX handle auth-int. 199 | return None 200 | 201 | self._thread_local.last_nonce = nonce 202 | 203 | # XXX should the partial digests be encoded too? 204 | base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ 205 | 'response="%s"' % (self.username, realm, nonce, path, respdig) 206 | if opaque: 207 | base += ', opaque="%s"' % opaque 208 | if algorithm: 209 | base += ', algorithm="%s"' % algorithm 210 | if entdig: 211 | base += ', digest="%s"' % entdig 212 | if qop: 213 | base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) 214 | 215 | return 'Digest %s' % (base) 216 | 217 | def handle_redirect(self, r, **kwargs): 218 | """Reset num_401_calls counter on redirects.""" 219 | if r.is_redirect: 220 | self._thread_local.num_401_calls = 1 221 | 222 | def handle_401(self, r, **kwargs): 223 | """ 224 | Takes the given response and tries digest-auth, if needed. 225 | 226 | :rtype: requests.Response 227 | """ 228 | 229 | # If response is not 4xx, do not auth 230 | # See https://github.com/requests/requests/issues/3772 231 | if not 400 <= r.status_code < 500: 232 | self._thread_local.num_401_calls = 1 233 | return r 234 | 235 | if self._thread_local.pos is not None: 236 | # Rewind the file position indicator of the body to where 237 | # it was to resend the request. 238 | r.request.body.seek(self._thread_local.pos) 239 | s_auth = r.headers.get('www-authenticate', '') 240 | 241 | if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: 242 | 243 | self._thread_local.num_401_calls += 1 244 | pat = re.compile(r'digest ', flags=re.IGNORECASE) 245 | self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) 246 | 247 | # Consume content and release the original connection 248 | # to allow our new request to reuse the same one. 249 | r.content 250 | r.close() 251 | prep = r.request.copy() 252 | extract_cookies_to_jar(prep._cookies, r.request, r.raw) 253 | prep.prepare_cookies(prep._cookies) 254 | 255 | prep.headers['Authorization'] = self.build_digest_header( 256 | prep.method, prep.url) 257 | _r = r.connection.send(prep, **kwargs) 258 | _r.history.append(r) 259 | _r.request = prep 260 | 261 | return _r 262 | 263 | self._thread_local.num_401_calls = 1 264 | return r 265 | 266 | def __call__(self, r): 267 | # Initialize per-thread state, if needed 268 | self.init_per_thread_state() 269 | # If we have a saved nonce, skip the 401 270 | if self._thread_local.last_nonce: 271 | r.headers['Authorization'] = self.build_digest_header(r.method, r.url) 272 | try: 273 | self._thread_local.pos = r.body.tell() 274 | except AttributeError: 275 | # In the case of HTTPDigestAuth being reused and the body of 276 | # the previous request was a file-like object, pos has the 277 | # file position of the previous body. Ensure it's set to 278 | # None. 279 | self._thread_local.pos = None 280 | r.register_hook('response', self.handle_401) 281 | r.register_hook('response', self.handle_redirect) 282 | self._thread_local.num_401_calls = 1 283 | 284 | return r 285 | 286 | def __eq__(self, other): 287 | return all([ 288 | self.username == getattr(other, 'username', None), 289 | self.password == getattr(other, 'password', None) 290 | ]) 291 | 292 | def __ne__(self, other): 293 | return not self == other 294 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | import chardet 12 | 13 | import sys 14 | 15 | # ------- 16 | # Pythons 17 | # ------- 18 | 19 | # Syntax sugar. 20 | _ver = sys.version_info 21 | 22 | #: Python 2.x? 23 | is_py2 = (_ver[0] == 2) 24 | 25 | #: Python 3.x? 26 | is_py3 = (_ver[0] == 3) 27 | 28 | try: 29 | import simplejson as json 30 | except (ImportError, SyntaxError): 31 | # simplejson does not support Python 3.2, it throws a SyntaxError 32 | # because of u'...' Unicode literals. 33 | import json 34 | 35 | # --------- 36 | # Specifics 37 | # --------- 38 | 39 | if is_py2: 40 | from urllib import ( 41 | quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, 42 | proxy_bypass, proxy_bypass_environment, getproxies_environment) 43 | from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag 44 | from urllib2 import parse_http_list 45 | import cookielib 46 | from Cookie import Morsel 47 | from StringIO import StringIO 48 | 49 | from urllib3.packages.ordered_dict import OrderedDict 50 | 51 | builtin_str = str 52 | bytes = str 53 | str = unicode 54 | basestring = basestring 55 | numeric_types = (int, long, float) 56 | integer_types = (int, long) 57 | 58 | elif is_py3: 59 | from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag 60 | from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment 61 | from http import cookiejar as cookielib 62 | from http.cookies import Morsel 63 | from io import StringIO 64 | from collections import OrderedDict 65 | 66 | builtin_str = str 67 | str = str 68 | bytes = bytes 69 | basestring = (str, bytes) 70 | numeric_types = (int, float) 71 | integer_types = (int,) 72 | -------------------------------------------------------------------------------- /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 | 12 | class RequestException(IOError): 13 | """There was an ambiguous exception that occurred while handling your 14 | request. 15 | """ 16 | 17 | def __init__(self, *args, **kwargs): 18 | """Initialize RequestException with `request` and `response` objects.""" 19 | response = kwargs.pop('response', None) 20 | self.response = response 21 | self.request = kwargs.pop('request', None) 22 | if (response is not None and not self.request and 23 | hasattr(response, 'request')): 24 | self.request = self.response.request 25 | super(RequestException, self).__init__(*args, **kwargs) 26 | 27 | 28 | class HTTPError(RequestException): 29 | """An HTTP error occurred.""" 30 | 31 | 32 | class ConnectionError(RequestException): 33 | """A Connection error occurred.""" 34 | 35 | 36 | class ProxyError(ConnectionError): 37 | """A proxy error occurred.""" 38 | 39 | 40 | class SSLError(ConnectionError): 41 | """An SSL error occurred.""" 42 | 43 | 44 | class Timeout(RequestException): 45 | """The request timed out. 46 | 47 | Catching this error will catch both 48 | :exc:`~requests.exceptions.ConnectTimeout` and 49 | :exc:`~requests.exceptions.ReadTimeout` errors. 50 | """ 51 | 52 | 53 | class ConnectTimeout(ConnectionError, Timeout): 54 | """The request timed out while trying to connect to the remote server. 55 | 56 | Requests that produced this error are safe to retry. 57 | """ 58 | 59 | 60 | class ReadTimeout(Timeout): 61 | """The server did not send any data in the allotted amount of time.""" 62 | 63 | 64 | class URLRequired(RequestException): 65 | """A valid URL is required to make a request.""" 66 | 67 | 68 | class TooManyRedirects(RequestException): 69 | """Too many redirects.""" 70 | 71 | 72 | class MissingSchema(RequestException, ValueError): 73 | """The URL schema (e.g. http or https) is missing.""" 74 | 75 | 76 | class InvalidSchema(RequestException, ValueError): 77 | """See defaults.py for valid schemas.""" 78 | 79 | 80 | class InvalidURL(RequestException, ValueError): 81 | """The URL provided was somehow invalid.""" 82 | 83 | 84 | class InvalidHeader(RequestException, ValueError): 85 | """The header value provided was somehow invalid.""" 86 | 87 | 88 | class ChunkedEncodingError(RequestException): 89 | """The server declared chunked encoding but sent an invalid chunk.""" 90 | 91 | 92 | class ContentDecodingError(RequestException, BaseHTTPError): 93 | """Failed to decode response content""" 94 | 95 | 96 | class StreamConsumedError(RequestException, TypeError): 97 | """The content for this response was already consumed""" 98 | 99 | 100 | class RetryError(RequestException): 101 | """Custom retries logic failed""" 102 | 103 | 104 | class UnrewindableBodyError(RequestException): 105 | """Requests encountered an error when trying to rewind a body""" 106 | 107 | # Warnings 108 | 109 | 110 | class RequestsWarning(Warning): 111 | """Base warning for Requests.""" 112 | pass 113 | 114 | 115 | class FileModeWarning(RequestsWarning, DeprecationWarning): 116 | """A file was opened in text mode, but Requests determined its binary length.""" 117 | pass 118 | 119 | 120 | class RequestsDependencyWarning(RequestsWarning): 121 | """An imported dependency doesn't match the expected version range.""" 122 | pass 123 | -------------------------------------------------------------------------------- /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 urllib3 10 | import chardet 11 | 12 | from . import __version__ as requests_version 13 | 14 | try: 15 | from .packages.urllib3.contrib import pyopenssl 16 | except ImportError: 17 | pyopenssl = None 18 | OpenSSL = None 19 | cryptography = None 20 | else: 21 | import OpenSSL 22 | import cryptography 23 | 24 | 25 | def _implementation(): 26 | """Return a dict with the Python implementation and version. 27 | 28 | Provide both the name and the version of the Python implementation 29 | currently running. For example, on CPython 2.7.5 it will return 30 | {'name': 'CPython', 'version': '2.7.5'}. 31 | 32 | This function works best on CPython and PyPy: in particular, it probably 33 | doesn't work for Jython or IronPython. Future investigation should be done 34 | to work out the correct shape of the code for those platforms. 35 | """ 36 | implementation = platform.python_implementation() 37 | 38 | if implementation == 'CPython': 39 | implementation_version = platform.python_version() 40 | elif implementation == 'PyPy': 41 | implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, 42 | sys.pypy_version_info.minor, 43 | sys.pypy_version_info.micro) 44 | if sys.pypy_version_info.releaselevel != 'final': 45 | implementation_version = ''.join([ 46 | implementation_version, sys.pypy_version_info.releaselevel 47 | ]) 48 | elif implementation == 'Jython': 49 | implementation_version = platform.python_version() # Complete Guess 50 | elif implementation == 'IronPython': 51 | implementation_version = platform.python_version() # Complete Guess 52 | else: 53 | implementation_version = 'Unknown' 54 | 55 | return {'name': implementation, 'version': implementation_version} 56 | 57 | 58 | def info(): 59 | """Generate information for a bug report.""" 60 | try: 61 | platform_info = { 62 | 'system': platform.system(), 63 | 'release': platform.release(), 64 | } 65 | except IOError: 66 | platform_info = { 67 | 'system': 'Unknown', 68 | 'release': 'Unknown', 69 | } 70 | 71 | implementation_info = _implementation() 72 | urllib3_info = {'version': urllib3.__version__} 73 | chardet_info = {'version': chardet.__version__} 74 | 75 | pyopenssl_info = { 76 | 'version': None, 77 | 'openssl_version': '', 78 | } 79 | if OpenSSL: 80 | pyopenssl_info = { 81 | 'version': OpenSSL.__version__, 82 | 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, 83 | } 84 | cryptography_info = { 85 | 'version': getattr(cryptography, '__version__', ''), 86 | } 87 | 88 | # OPENSSL_VERSION_NUMBER doesn't exist in the Python 2.6 ssl module. 89 | system_ssl = getattr(ssl, 'OPENSSL_VERSION_NUMBER', None) 90 | system_ssl_info = { 91 | 'version': '%x' % system_ssl if system_ssl is not None else '' 92 | } 93 | 94 | return { 95 | 'platform': platform_info, 96 | 'implementation': implementation_info, 97 | 'system_ssl': system_ssl_info, 98 | 'using_pyopenssl': pyopenssl is not None, 99 | 'pyOpenSSL': pyopenssl_info, 100 | 'urllib3': urllib3_info, 101 | 'chardet': chardet_info, 102 | 'cryptography': cryptography_info, 103 | 'requests': { 104 | 'version': requests_version, 105 | }, 106 | } 107 | 108 | 109 | def main(): 110 | """Pretty-print the bug information as JSON.""" 111 | print(json.dumps(info(), sort_keys=True, indent=2)) 112 | 113 | 114 | if __name__ == '__main__': 115 | main() 116 | -------------------------------------------------------------------------------- /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 dict((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 dict() 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 | -------------------------------------------------------------------------------- /requests/packages.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # This code exists for backwards compatibility reasons. 4 | # I don't like it either. Just look the other way. :) 5 | 6 | for package in ('urllib3', 'idna', 'chardet'): 7 | locals()[package] = __import__(package) 8 | # This traversal is apparently necessary such that the identities are 9 | # preserved (requests.packages.urllib3.* is urllib3.*) 10 | for mod in list(sys.modules): 11 | if mod == package or mod.startswith(package + '.'): 12 | sys.modules['requests.packages.' + mod] = sys.modules[mod] 13 | 14 | # Kinda cool, though, right? 15 | -------------------------------------------------------------------------------- /requests/status_codes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .structures import LookupDict 4 | 5 | _codes = { 6 | 7 | # Informational. 8 | 100: ('continue',), 9 | 101: ('switching_protocols',), 10 | 102: ('processing',), 11 | 103: ('checkpoint',), 12 | 122: ('uri_too_long', 'request_uri_too_long'), 13 | 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), 14 | 201: ('created',), 15 | 202: ('accepted',), 16 | 203: ('non_authoritative_info', 'non_authoritative_information'), 17 | 204: ('no_content',), 18 | 205: ('reset_content', 'reset'), 19 | 206: ('partial_content', 'partial'), 20 | 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), 21 | 208: ('already_reported',), 22 | 226: ('im_used',), 23 | 24 | # Redirection. 25 | 300: ('multiple_choices',), 26 | 301: ('moved_permanently', 'moved', '\\o-'), 27 | 302: ('found',), 28 | 303: ('see_other', 'other'), 29 | 304: ('not_modified',), 30 | 305: ('use_proxy',), 31 | 306: ('switch_proxy',), 32 | 307: ('temporary_redirect', 'temporary_moved', 'temporary'), 33 | 308: ('permanent_redirect', 34 | 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 35 | 36 | # Client Error. 37 | 400: ('bad_request', 'bad'), 38 | 401: ('unauthorized',), 39 | 402: ('payment_required', 'payment'), 40 | 403: ('forbidden',), 41 | 404: ('not_found', '-o-'), 42 | 405: ('method_not_allowed', 'not_allowed'), 43 | 406: ('not_acceptable',), 44 | 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), 45 | 408: ('request_timeout', 'timeout'), 46 | 409: ('conflict',), 47 | 410: ('gone',), 48 | 411: ('length_required',), 49 | 412: ('precondition_failed', 'precondition'), 50 | 413: ('request_entity_too_large',), 51 | 414: ('request_uri_too_large',), 52 | 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), 53 | 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), 54 | 417: ('expectation_failed',), 55 | 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), 56 | 421: ('misdirected_request',), 57 | 422: ('unprocessable_entity', 'unprocessable'), 58 | 423: ('locked',), 59 | 424: ('failed_dependency', 'dependency'), 60 | 425: ('unordered_collection', 'unordered'), 61 | 426: ('upgrade_required', 'upgrade'), 62 | 428: ('precondition_required', 'precondition'), 63 | 429: ('too_many_requests', 'too_many'), 64 | 431: ('header_fields_too_large', 'fields_too_large'), 65 | 444: ('no_response', 'none'), 66 | 449: ('retry_with', 'retry'), 67 | 450: ('blocked_by_windows_parental_controls', 'parental_controls'), 68 | 451: ('unavailable_for_legal_reasons', 'legal_reasons'), 69 | 499: ('client_closed_request',), 70 | 71 | # Server Error. 72 | 500: ('internal_server_error', 'server_error', '/o\\', '✗'), 73 | 501: ('not_implemented',), 74 | 502: ('bad_gateway',), 75 | 503: ('service_unavailable', 'unavailable'), 76 | 504: ('gateway_timeout',), 77 | 505: ('http_version_not_supported', 'http_version'), 78 | 506: ('variant_also_negotiates',), 79 | 507: ('insufficient_storage',), 80 | 509: ('bandwidth_limit_exceeded', 'bandwidth'), 81 | 510: ('not_extended',), 82 | 511: ('network_authentication_required', 'network_auth', 'network_authentication'), 83 | } 84 | 85 | codes = LookupDict(name='status_codes') 86 | 87 | for code, titles in _codes.items(): 88 | for title in titles: 89 | setattr(codes, title, code) 90 | if not title.startswith(('\\', '/')): 91 | setattr(codes, title.upper(), code) 92 | -------------------------------------------------------------------------------- /requests/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | requests.structures 5 | ~~~~~~~~~~~~~~~~~~~ 6 | 7 | Data structures that power Requests. 8 | """ 9 | 10 | import collections 11 | 12 | from .compat import OrderedDict 13 | 14 | 15 | class CaseInsensitiveDict(collections.MutableMapping): 16 | """A case-insensitive ``dict``-like object. 17 | 18 | Implements all methods and operations of 19 | ``collections.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, collections.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 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e .[socks] 2 | pytest>=2.8.0 3 | codecov 4 | pytest-httpbin==0.0.7 5 | pytest-mock 6 | pytest-cov 7 | pytest-xdist 8 | alabaster 9 | readme_renderer 10 | Sphinx<=1.5.5 11 | PySocks 12 | setuptools>=18.5 13 | docutils 14 | flake8 15 | tox 16 | detox 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | from codecs import open 8 | 9 | from setuptools import setup 10 | from setuptools.command.test import test as TestCommand 11 | 12 | here = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | class PyTest(TestCommand): 15 | user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] 16 | 17 | def initialize_options(self): 18 | TestCommand.initialize_options(self) 19 | try: 20 | from multiprocessing import cpu_count 21 | self.pytest_args = ['-n', str(cpu_count()), '--boxed'] 22 | except (ImportError, NotImplementedError): 23 | self.pytest_args = ['-n', '1', '--boxed'] 24 | 25 | def finalize_options(self): 26 | TestCommand.finalize_options(self) 27 | self.test_args = [] 28 | self.test_suite = True 29 | 30 | def run_tests(self): 31 | import pytest 32 | 33 | errno = pytest.main(self.pytest_args) 34 | sys.exit(errno) 35 | 36 | # 'setup.py publish' shortcut. 37 | if sys.argv[-1] == 'publish': 38 | os.system('python setup.py sdist bdist_wheel') 39 | os.system('twine upload dist/*') 40 | sys.exit() 41 | 42 | packages = ['requests'] 43 | 44 | requires = [ 45 | 'chardet>=3.0.2,<3.1.0', 46 | 'idna>=2.5,<2.6', 47 | 'urllib3>=1.21.1,<1.23', 48 | 'certifi>=2017.4.17' 49 | 50 | ] 51 | test_requirements = ['pytest-httpbin==0.0.7', 'pytest-cov', 'pytest-mock', 'pytest-xdist', 'PySocks>=1.5.6, !=1.5.7', 'pytest>=2.8.0'] 52 | 53 | about = {} 54 | with open(os.path.join(here, 'requests', '__version__.py'), 'r', 'utf-8') as f: 55 | exec(f.read(), about) 56 | 57 | with open('README.rst', 'r', 'utf-8') as f: 58 | readme = f.read() 59 | with open('HISTORY.rst', 'r', 'utf-8') as f: 60 | history = f.read() 61 | 62 | setup( 63 | name=about['__title__'], 64 | version=about['__version__'], 65 | description=about['__description__'], 66 | long_description=readme + '\n\n' + history, 67 | author=about['__author__'], 68 | author_email=about['__author_email__'], 69 | url=about['__url__'], 70 | packages=packages, 71 | package_data={'': ['LICENSE', 'NOTICE'], 'requests': ['*.pem']}, 72 | package_dir={'requests': 'requests'}, 73 | include_package_data=True, 74 | install_requires=requires, 75 | license=about['__license__'], 76 | zip_safe=False, 77 | classifiers=( 78 | 'Development Status :: 5 - Production/Stable', 79 | 'Intended Audience :: Developers', 80 | 'Natural Language :: English', 81 | 'License :: OSI Approved :: Apache Software License', 82 | 'Programming Language :: Python', 83 | 'Programming Language :: Python :: 2.6', 84 | 'Programming Language :: Python :: 2.7', 85 | 'Programming Language :: Python :: 3', 86 | 'Programming Language :: Python :: 3.3', 87 | 'Programming Language :: Python :: 3.4', 88 | 'Programming Language :: Python :: 3.5', 89 | 'Programming Language :: Python :: 3.6', 90 | 'Programming Language :: Python :: Implementation :: CPython', 91 | 'Programming Language :: Python :: Implementation :: PyPy' 92 | ), 93 | cmdclass={'test': PyTest}, 94 | tests_require=test_requirements, 95 | extras_require={ 96 | 'security': ['pyOpenSSL>=0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'], 97 | 'socks': ['PySocks>=1.5.6, !=1.5.7'], 98 | 'socks:sys_platform == "win32" and (python_version == "2.7" or python_version == "2.6")': ['win_inet_pton'], 99 | }, 100 | ) 101 | 102 | -------------------------------------------------------------------------------- /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/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | from requests.compat import urljoin 5 | 6 | 7 | def prepare_url(value): 8 | # Issue #1483: Make sure the URL always has a trailing slash 9 | httpbin_url = value.url.rstrip('/') + '/' 10 | 11 | def inner(*suffix): 12 | return urljoin(httpbin_url, '/'.join(suffix)) 13 | 14 | return inner 15 | 16 | 17 | @pytest.fixture 18 | def httpbin(httpbin): 19 | return prepare_url(httpbin) 20 | 21 | 22 | @pytest.fixture 23 | def httpbin_secure(httpbin_secure): 24 | return prepare_url(httpbin_secure) 25 | -------------------------------------------------------------------------------- /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 | @pytest.mark.skipif(sys.version_info[:2] != (2,6), reason="Only run on Python 2.6") 11 | def test_system_ssl_py26(): 12 | """OPENSSL_VERSION_NUMBER isn't provided in Python 2.6, verify we don't 13 | blow up in this case. 14 | """ 15 | assert info()['system_ssl'] == {'version': ''} 16 | 17 | 18 | @pytest.mark.skipif(sys.version_info < (2,7), reason="Only run on Python 2.7+") 19 | def test_system_ssl(): 20 | """Verify we're actually setting system_ssl when it should be available.""" 21 | assert info()['system_ssl']['version'] != '' 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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://{0}:{1}/'.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_digestauth_401_count_reset_on_redirect(): 28 | """Ensure we correctly reset num_401_calls after a successful digest auth, 29 | followed by a 302 redirect to another digest auth prompt. 30 | 31 | See https://github.com/requests/requests/issues/1979. 32 | """ 33 | text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n' 34 | b'Content-Length: 0\r\n' 35 | b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"' 36 | b', opaque="372825293d1c26955496c80ed6426e9e", ' 37 | b'realm="me@kennethreitz.com", qop=auth\r\n\r\n') 38 | 39 | text_302 = (b'HTTP/1.1 302 FOUND\r\n' 40 | b'Content-Length: 0\r\n' 41 | b'Location: /\r\n\r\n') 42 | 43 | text_200 = (b'HTTP/1.1 200 OK\r\n' 44 | b'Content-Length: 0\r\n\r\n') 45 | 46 | expected_digest = (b'Authorization: Digest username="user", ' 47 | b'realm="me@kennethreitz.com", ' 48 | b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"') 49 | 50 | auth = requests.auth.HTTPDigestAuth('user', 'pass') 51 | 52 | def digest_response_handler(sock): 53 | # Respond to initial GET with a challenge. 54 | request_content = consume_socket_content(sock, timeout=0.5) 55 | assert request_content.startswith(b"GET / HTTP/1.1") 56 | sock.send(text_401) 57 | 58 | # Verify we receive an Authorization header in response, then redirect. 59 | request_content = consume_socket_content(sock, timeout=0.5) 60 | assert expected_digest in request_content 61 | sock.send(text_302) 62 | 63 | # Verify Authorization isn't sent to the redirected host, 64 | # then send another challenge. 65 | request_content = consume_socket_content(sock, timeout=0.5) 66 | assert b'Authorization:' not in request_content 67 | sock.send(text_401) 68 | 69 | # Verify Authorization is sent correctly again, and return 200 OK. 70 | request_content = consume_socket_content(sock, timeout=0.5) 71 | assert expected_digest in request_content 72 | sock.send(text_200) 73 | 74 | return request_content 75 | 76 | close_server = threading.Event() 77 | server = Server(digest_response_handler, wait_to_close_event=close_server) 78 | 79 | with server as (host, port): 80 | url = 'http://{0}:{1}/'.format(host, port) 81 | r = requests.get(url, auth=auth) 82 | # Verify server succeeded in authenticating. 83 | assert r.status_code == 200 84 | # Verify Authorization was sent in final request. 85 | assert 'Authorization' in r.request.headers 86 | assert r.request.headers['Authorization'].startswith('Digest ') 87 | # Verify redirect happened as we expected. 88 | assert r.history[0].status_code == 302 89 | close_server.set() 90 | 91 | 92 | def test_digestauth_401_only_sent_once(): 93 | """Ensure we correctly respond to a 401 challenge once, and then 94 | stop responding if challenged again. 95 | """ 96 | text_401 = (b'HTTP/1.1 401 UNAUTHORIZED\r\n' 97 | b'Content-Length: 0\r\n' 98 | b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"' 99 | b', opaque="372825293d1c26955496c80ed6426e9e", ' 100 | b'realm="me@kennethreitz.com", qop=auth\r\n\r\n') 101 | 102 | expected_digest = (b'Authorization: Digest username="user", ' 103 | b'realm="me@kennethreitz.com", ' 104 | b'nonce="6bf5d6e4da1ce66918800195d6b9130d", uri="/"') 105 | 106 | auth = requests.auth.HTTPDigestAuth('user', 'pass') 107 | 108 | def digest_failed_response_handler(sock): 109 | # Respond to initial GET with a challenge. 110 | request_content = consume_socket_content(sock, timeout=0.5) 111 | assert request_content.startswith(b"GET / HTTP/1.1") 112 | sock.send(text_401) 113 | 114 | # Verify we receive an Authorization header in response, then 115 | # challenge again. 116 | request_content = consume_socket_content(sock, timeout=0.5) 117 | assert expected_digest in request_content 118 | sock.send(text_401) 119 | 120 | # Verify the client didn't respond to second challenge. 121 | request_content = consume_socket_content(sock, timeout=0.5) 122 | assert request_content == b'' 123 | 124 | return request_content 125 | 126 | close_server = threading.Event() 127 | server = Server(digest_failed_response_handler, wait_to_close_event=close_server) 128 | 129 | with server as (host, port): 130 | url = 'http://{0}:{1}/'.format(host, port) 131 | r = requests.get(url, auth=auth) 132 | # Verify server didn't authenticate us. 133 | assert r.status_code == 401 134 | assert r.history[0].status_code == 401 135 | close_server.set() 136 | 137 | 138 | def test_digestauth_only_on_4xx(): 139 | """Ensure we only send digestauth on 4xx challenges. 140 | 141 | See https://github.com/requests/requests/issues/3772. 142 | """ 143 | text_200_chal = (b'HTTP/1.1 200 OK\r\n' 144 | b'Content-Length: 0\r\n' 145 | b'WWW-Authenticate: Digest nonce="6bf5d6e4da1ce66918800195d6b9130d"' 146 | b', opaque="372825293d1c26955496c80ed6426e9e", ' 147 | b'realm="me@kennethreitz.com", qop=auth\r\n\r\n') 148 | 149 | auth = requests.auth.HTTPDigestAuth('user', 'pass') 150 | 151 | def digest_response_handler(sock): 152 | # Respond to GET with a 200 containing www-authenticate header. 153 | request_content = consume_socket_content(sock, timeout=0.5) 154 | assert request_content.startswith(b"GET / HTTP/1.1") 155 | sock.send(text_200_chal) 156 | 157 | # Verify the client didn't respond with auth. 158 | request_content = consume_socket_content(sock, timeout=0.5) 159 | assert request_content == b'' 160 | 161 | return request_content 162 | 163 | close_server = threading.Event() 164 | server = Server(digest_response_handler, wait_to_close_event=close_server) 165 | 166 | with server as (host, port): 167 | url = 'http://{0}:{1}/'.format(host, port) 168 | r = requests.get(url, auth=auth) 169 | # Verify server didn't receive auth from us. 170 | assert r.status_code == 200 171 | assert len(r.history) == 0 172 | close_server.set() 173 | 174 | 175 | _schemes_by_var_prefix = [ 176 | ('http', ['http']), 177 | ('https', ['https']), 178 | ('all', ['http', 'https']), 179 | ] 180 | 181 | _proxy_combos = [] 182 | for prefix, schemes in _schemes_by_var_prefix: 183 | for scheme in schemes: 184 | _proxy_combos.append(("{0}_proxy".format(prefix), scheme)) 185 | 186 | _proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos] 187 | 188 | 189 | @pytest.mark.parametrize("var,scheme", _proxy_combos) 190 | def test_use_proxy_from_environment(httpbin, var, scheme): 191 | url = "{0}://httpbin.org".format(scheme) 192 | fake_proxy = Server() # do nothing with the requests; just close the socket 193 | with fake_proxy as (host, port): 194 | proxy_url = "socks5://{0}:{1}".format(host, port) 195 | kwargs = {var: proxy_url} 196 | with override_environ(**kwargs): 197 | # fake proxy's lack of response will cause a ConnectionError 198 | with pytest.raises(requests.exceptions.ConnectionError): 199 | requests.get(url) 200 | 201 | # the fake proxy received a request 202 | assert len(fake_proxy.handler_results) == 1 203 | 204 | # it had actual content (not checking for SOCKS protocol for now) 205 | assert len(fake_proxy.handler_results[0]) > 0 206 | 207 | 208 | def test_redirect_rfc1808_to_non_ascii_location(): 209 | path = u'š' 210 | expected_path = b'%C5%A1' 211 | redirect_request = [] # stores the second request to the server 212 | 213 | def redirect_resp_handler(sock): 214 | consume_socket_content(sock, timeout=0.5) 215 | location = u'//{0}:{1}/{2}'.format(host, port, path) 216 | sock.send( 217 | b'HTTP/1.1 301 Moved Permanently\r\n' 218 | b'Content-Length: 0\r\n' 219 | b'Location: ' + location.encode('utf8') + b'\r\n' 220 | b'\r\n' 221 | ) 222 | redirect_request.append(consume_socket_content(sock, timeout=0.5)) 223 | sock.send(b'HTTP/1.1 200 OK\r\n\r\n') 224 | 225 | close_server = threading.Event() 226 | server = Server(redirect_resp_handler, wait_to_close_event=close_server) 227 | 228 | with server as (host, port): 229 | url = u'http://{0}:{1}'.format(host, port) 230 | r = requests.get(url=url, allow_redirects=True) 231 | assert r.status_code == 200 232 | assert len(r.history) == 1 233 | assert r.history[0].status_code == 301 234 | assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1') 235 | assert r.url == u'{0}/{1}'.format(url, expected_path.decode('ascii')) 236 | 237 | close_server.set() 238 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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://{0}:{1}'.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://{0}:{1}'.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://{0}:{1}'.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 | -------------------------------------------------------------------------------- /tests/testserver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/requests/requests-docs-cn/a7bffa84978a5018011f497b6b90fde247a4380a/tests/testserver/__init__.py -------------------------------------------------------------------------------- /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 | sock.listen(0) 82 | return sock 83 | 84 | def _close_server_sock_ignore_errors(self): 85 | try: 86 | self.server_sock.close() 87 | except IOError: 88 | pass 89 | 90 | def _handle_requests(self): 91 | for _ in range(self.requests_to_handle): 92 | sock = self._accept_connection() 93 | if not sock: 94 | break 95 | 96 | handler_result = self.handler(sock) 97 | 98 | self.handler_results.append(handler_result) 99 | 100 | def _accept_connection(self): 101 | try: 102 | ready, _, _ = select.select([self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT) 103 | if not ready: 104 | return None 105 | 106 | return self.server_sock.accept()[0] 107 | except (select.error, socket.error): 108 | return None 109 | 110 | def __enter__(self): 111 | self.start() 112 | self.ready_event.wait(self.WAIT_EVENT_TIMEOUT) 113 | return self.host, self.port 114 | 115 | def __exit__(self, exc_type, exc_value, traceback): 116 | if exc_type is None: 117 | self.stop_event.wait(self.WAIT_EVENT_TIMEOUT) 118 | else: 119 | if self.wait_to_close_event: 120 | # avoid server from waiting for event timeouts 121 | # if an exception is found in the main thread 122 | self.wait_to_close_event.set() 123 | 124 | # ensure server thread doesn't get stuck waiting for connections 125 | self._close_server_sock_ignore_errors() 126 | self.join() 127 | return False # allow exceptions to propagate 128 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26,py27,py33,py34,py35,py36 3 | 4 | [testenv] 5 | 6 | commands = 7 | pip install -e .[socks] 8 | python setup.py test --------------------------------------------------------------------------------