diff --git a/AUTHORS.rst b/AUTHORS.rst
index 6a7f889b..fc760599 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -134,7 +134,7 @@ Patches and Suggestions
- Kevin Burke
- Flavio Curella
- David Pursehouse (`@dpursehouse `_)
-- Jon Parise
+- Jon Parise (`@jparise `_)
- Alexander Karpinsky (`@homm86 `_)
- Marc Schlaich (`@schlamar `_)
- Park Ilsu (`@daftshady `_)
@@ -162,3 +162,5 @@ Patches and Suggestions
- Smiley Barry (`@smiley `_)
- Shagun Sodhani (`@shagunsodhani `_)
- Robin Linderborg (`@vienno `_)
+- Brian Samek (`@bsamek `_)
+- Dmitry Dygalo (`@Stranger6667 `_)
diff --git a/HISTORY.rst b/HISTORY.rst
index 34226f49..1fa26b7f 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,13 +3,60 @@
Release History
---------------
-dev (XXXX)
-++++++++++
+2.9.2 (???)
++++++++++++
+
+**Bugfixes**
+
+- Don't use redirect_cache if allow_redirects=False
+
+2.9.1 (2015-12-21)
+++++++++++++++++++
+
+**Bugfixes**
+
+- Resolve regression introduced in 2.9.0 that made it impossible to send binary
+ strings as bodies in Python 3.
+- Fixed errors when calculating cookie expiration dates in certain locales.
+
+**Miscellaneous**
+
+- Updated bundled urllib3 to 1.13.1.
+
+2.9.0 (2015-12-15)
+++++++++++++++++++
**Minor Improvements** (Backwards compatible)
- The ``verify`` keyword argument now supports being passed a path to a
directory of CA certificates, not just a single-file bundle.
+- Warnings are now emitted when sending files opened in text mode.
+- Added the 511 Network Authentication Required status code to the status code
+ registry.
+
+**Bugfixes**
+
+- For file-like objects that are not seeked to the very beginning, we now
+ send the content length for the number of bytes we will actually read, rather
+ than the total size of the file, allowing partial file uploads.
+- When uploading file-like objects, if they are empty or have no obvious
+ content length we set ``Transfer-Encoding: chunked`` rather than
+ ``Content-Length: 0``.
+- We correctly receive the response in buffered mode when uploading chunked
+ bodies.
+- We now handle being passed a query string as a bytestring on Python 3, by
+ decoding it as UTF-8.
+- Sessions are now closed in all cases (exceptional and not) when using the
+ functional API rather than leaking and waiting for the garbage collector to
+ clean them up.
+- Correctly handle digest auth headers with a malformed ``qop`` directive that
+ contains no token, by treating it the same as if no ``qop`` directive was
+ provided at all.
+- Minor performance improvements when removing specific cookies by name.
+
+**Miscellaneous**
+
+- Updated urllib3 to 1.13.
2.8.1 (2015-10-13)
++++++++++++++++++
@@ -50,7 +97,7 @@ dev (XXXX)
- The ``json`` parameter to ``post()`` and friends will now only be used if
neither ``data`` nor ``files`` are present, consistent with the
documentation.
-- We now ignore empty fields in the ``NO_PROXY`` enviroment variable.
+- We now ignore empty fields in the ``NO_PROXY`` environment variable.
- Fixed problem where ``httplib.BadStatusLine`` would get raised if combining
``stream=True`` with ``contextlib.closing``.
- Prevented bugs where we would attempt to return the same connection back to
@@ -533,7 +580,7 @@ This is not a backwards compatible change.
- Improved mime-compatible JSON handling
- Proxy fixes
- Path hack fixes
-- Case-Insensistive Content-Encoding headers
+- Case-Insensitive Content-Encoding headers
- Support for CJK parameters in form posts
@@ -620,7 +667,7 @@ This is not a backwards compatible change.
+++++++++++++++++++
- Removal of Requests.async in favor of `grequests `_
-- Allow disabling of cookie persistiance.
+- Allow disabling of cookie persistence.
- New implementation of safe_mode
- cookies.get now supports default argument
- Session cookies not saved when Session.request is called with return_response=False
diff --git a/LICENSE b/LICENSE
index a103fc91..8c1dd448 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2015 Kenneth Reitz
+Copyright 2016 Kenneth Reitz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/Makefile b/Makefile
index dea33a3f..a8289712 100644
--- a/Makefile
+++ b/Makefile
@@ -6,10 +6,10 @@ init:
test:
# This runs all of the tests. To run an individual test, run py.test with
# the -k flag, like "py.test -k test_path_is_not_double_encoded"
- py.test test_requests.py
+ py.test tests
coverage:
- py.test --verbose --cov-report term --cov=requests test_requests.py
+ py.test --verbose --cov-report term --cov=requests tests
ci: init
py.test --junitxml=junit.xml
@@ -28,7 +28,8 @@ chardet:
publish:
python setup.py register
python setup.py sdist upload
- python setup.py bdist_wheel upload
+ python setup.py bdist_wheel --universal upload
+ rm -fr build dist .egg requests.egg-info
docs-init:
diff --git a/README.rst b/README.rst
index 99d30e72..9fe548d2 100644
--- a/README.rst
+++ b/README.rst
@@ -7,41 +7,45 @@ Requests: HTTP for Humans
.. image:: https://img.shields.io/pypi/dm/requests.svg
:target: https://pypi.python.org/pypi/requests
+Requests is the only *Non-GMO* HTTP library for Python, safe for human
+consumption.
+**Warning:** Recreational use of other HTTP libraries may result in dangerous side-effects,
+including: security vulnerabilities, verbose code, reinventing the wheel,
+constantly reading documentation, depression, headaches, or even death.
-
-Requests is an Apache2 Licensed HTTP library, written in Python, for human
-beings.
-
-Most existing Python modules for sending HTTP requests are extremely
-verbose and cumbersome. Python's builtin urllib2 module provides most of
-the HTTP capabilities you should need, but the api is thoroughly broken.
-It requires an enormous amount of work (even method overrides) to
-perform the simplest of tasks.
-
-Things shouldn't be this way. Not in Python.
+Behold, the power of Requests:
.. code-block:: python
- >>> r = requests.get('https://api.github.com', auth=('user', 'pass'))
+ >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
- 204
+ 200
>>> r.headers['content-type']
- 'application/json'
+ 'application/json; charset=utf8'
+ >>> r.encoding
+ 'utf-8'
>>> r.text
- ...
+ u'{"type":"User"...'
+ >>> r.json()
+ {u'disk_usage': 368627, u'private_gists': 484, ...}
-See `the same code, without Requests `_.
+See `the similar code, sans Requests `_.
-Requests allow you to send HTTP/1.1 requests. You can add headers, form data,
-multipart files, and parameters with simple Python dictionaries, and access the
-response data in the same way. It's powered by httplib and `urllib3
-`_, but it does all the hard work and crazy
-hacks for you.
+Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, without the
+need for manual labor. There's no need to manually add query strings to your
+URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling
+are 100% automatic, powered by `urllib3 `_,
+which is embedded within Requests.
+Besides, all the cool kids are doing it. Requests is one of the most
+downloaded Python packages of all time, pulling in over 7,000,000 downloads
+every month. You don't want to be left out!
-Features
---------
+Feature Support
+---------------
+
+Requests is ready for today's web.
- International Domains and URLs
- Keep-Alive & Connection Pooling
@@ -50,12 +54,17 @@ Features
- Basic/Digest Authentication
- Elegant Key/Value Cookies
- Automatic Decompression
+- Automatic Content Decoding
- Unicode Response Bodies
- Multipart File Uploads
+- HTTP(S) Proxy Support
- Connection Timeouts
+- Streaming Downloads
+- ``.netrc`` Support
+- Chunked Requests
- Thread-safety
-- HTTP(S) proxy support
+Requests supports Python 2.6 — 3.5, and runs great on PyPy.
Installation
------------
@@ -65,16 +74,18 @@ To install Requests, simply:
.. code-block:: bash
$ pip install requests
+ ✨🍰✨
+Satisfaction, guaranteed.
Documentation
-------------
-Documentation is available at http://docs.python-requests.org/.
+Fantastic documentation is available at http://docs.python-requests.org/, for a limited time only.
-Contribute
-----------
+How to Contribute
+-----------------
#. 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.
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).
diff --git a/docs/MANIFEST.in b/docs/MANIFEST.in
deleted file mode 100644
index fb1021bf..00000000
--- a/docs/MANIFEST.in
+++ /dev/null
@@ -1 +0,0 @@
-include HISTORY.rst README.rst LICENSE
\ No newline at end of file
diff --git a/docs/Makefile b/docs/Makefile
index 946ba445..08a2acf6 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -7,6 +7,11 @@ SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
@@ -14,8 +19,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
+.PHONY: help
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@@ -25,53 +29,66 @@ help:
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
+ @echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
+ @echo " coverage to run coverage check of the documentation (if enabled)"
+.PHONY: clean
clean:
- -rm -rf $(BUILDDIR)/*
+ rm -rf $(BUILDDIR)/*
+.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
+.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
+.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
+.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@@ -81,6 +98,16 @@ qthelp:
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Requests.qhc"
+.PHONY: applehelp
+applehelp:
+ $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+ @echo
+ @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+ @echo "N.B. You won't be able to view it unless you put it in" \
+ "~/Library/Documentation/Help or install it in your application" \
+ "bundle."
+
+.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@@ -90,11 +117,13 @@ devhelp:
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Requests"
@echo "# devhelp"
+.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@@ -102,22 +131,33 @@ latex:
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
+.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
- make -C $(BUILDDIR)/latex all-pdf
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+.PHONY: latexpdfja
+latexpdfja:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
+.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@@ -125,29 +165,52 @@ texinfo:
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
+.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
+.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
+.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
+
+.PHONY: coverage
+coverage:
+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+ @echo "Testing of coverage in the sources finished, look at the " \
+ "results in $(BUILDDIR)/coverage/python.txt."
+
+.PHONY: xml
+xml:
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+.PHONY: pseudoxml
+pseudoxml:
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/docs/_static/konami.js b/docs/_static/konami.js
new file mode 100644
index 00000000..d72cf9df
--- /dev/null
+++ b/docs/_static/konami.js
@@ -0,0 +1,116 @@
+/*
+ * Konami-JS ~
+ * :: Now with support for touch events and multiple instances for
+ * :: those situations that call for multiple easter eggs!
+ * Code: http://konami-js.googlecode.com/
+ * Examples: http://www.snaptortoise.com/konami-js
+ * Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
+ * Version: 1.4.2 (9/2/2013)
+ * Licensed under the MIT License (http://opensource.org/licenses/MIT)
+ * Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser
+ */
+
+var Konami = function (callback) {
+ var konami = {
+ addEvent: function (obj, type, fn, ref_obj) {
+ if (obj.addEventListener)
+ obj.addEventListener(type, fn, false);
+ else if (obj.attachEvent) {
+ // IE
+ obj["e" + type + fn] = fn;
+ obj[type + fn] = function () {
+ obj["e" + type + fn](window.event, ref_obj);
+ };
+ obj.attachEvent("on" + type, obj[type + fn]);
+ }
+ },
+ input: "",
+ pattern: "38384040373937396665",
+ load: function (link) {
+ this.addEvent(document, "keydown", function (e, ref_obj) {
+ if (ref_obj) konami = ref_obj; // IE
+ konami.input += e ? e.keyCode : event.keyCode;
+ if (konami.input.length > konami.pattern.length)
+ konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
+ if (konami.input == konami.pattern) {
+ konami.code(link);
+ konami.input = "";
+ e.preventDefault();
+ return false;
+ }
+ }, this);
+ this.iphone.load(link);
+ },
+ code: function (link) {
+ window.location = link
+ },
+ iphone: {
+ start_x: 0,
+ start_y: 0,
+ stop_x: 0,
+ stop_y: 0,
+ tapTolerance: 8,
+ capture: false,
+ orig_keys: "",
+ keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
+ code: function (link) {
+ konami.code(link);
+ },
+ touchCapture: function(evt) {
+ konami.iphone.start_x = evt.changedTouches[0].pageX;
+ konami.iphone.start_y = evt.changedTouches[0].pageY;
+ konami.iphone.capture = true;
+ },
+ load: function (link) {
+ this.orig_keys = this.keys;
+ konami.addEvent(document, "touchmove", function (e) {
+ if (e.touches.length == 1 && konami.iphone.capture == true) {
+ var touch = e.touches[0];
+ konami.iphone.stop_x = touch.pageX;
+ konami.iphone.stop_y = touch.pageY;
+ konami.iphone.check_direction();
+ }
+ });
+ konami.addEvent(document, "touchend", function (evt) {
+ konami.touchCapture(evt);
+ konami.iphone.check_direction(link);
+ }, false);
+ konami.addEvent(document, "touchstart", function (evt) {
+ konami.touchCapture(evt);
+ });
+ },
+ check_direction: function (link) {
+ var x_magnitude = Math.abs(this.start_x - this.stop_x);
+ var y_magnitude = Math.abs(this.start_y - this.stop_y);
+ var hasMoved = (x_magnitude > this.tapTolerance || y_magnitude > this.tapTolerance);
+ var result;
+ if (this.capture === true && hasMoved) {
+ this.capture = false;
+ var x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
+ var y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
+ var result = (x_magnitude > y_magnitude) ? x : y;
+ }
+ else if (this.capture === false && !hasMoved) {
+ result = (this.tap == true) ? "TAP" : result;
+ result = "TAP";
+ }
+ if (result) {
+ if (result == this.keys[0]) this.keys = this.keys.slice(1, this.keys.length);
+ else this.keys = this.orig_keys;
+ }
+ if (this.keys.length == 0) {
+ this.keys = this.orig_keys;
+ this.code(link);
+ }
+ }
+ }
+ }
+
+ typeof callback === "string" && konami.load(callback);
+ if (typeof callback === "function") {
+ konami.code = callback;
+ konami.load();
+ }
+
+ return konami;
+};
diff --git a/docs/_templates/hacks.html b/docs/_templates/hacks.html
new file mode 100644
index 00000000..f9fc96cb
--- /dev/null
+++ b/docs/_templates/hacks.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html
index d2cbfe9d..7dbd74c8 100644
--- a/docs/_templates/sidebarintro.html
+++ b/docs/_templates/sidebarintro.html
@@ -14,30 +14,30 @@
human beings.
-
-
- Buy Requests Pro
-
-
-
-Get Updates
+Stay Informed
Receive updates on new releases and upcoming projects.
-Subscribe to Newsletter
+
+
-Translations
+Join Mailing List .
+
+Other Projects
+
+More Kenneth Reitz projects:
+
Useful Links
+
+
+Translations
+
+
+
diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html
index 928cd2fd..1e295f81 100644
--- a/docs/_templates/sidebarlogo.html
+++ b/docs/_templates/sidebarlogo.html
@@ -14,13 +14,40 @@
development release.
-
- Buy Requests Pro
-
-
-Get Updates
+Stay Informed
Receive updates on new releases and upcoming projects.
-Subscribe to Newsletter
+Join Mailing List .
+
+
+
+
+
+Other Projects
+
+More Kenneth Reitz projects:
+
+Translations
+
+
+
diff --git a/docs/_themes/README.rst b/docs/_themes/README.rst
deleted file mode 100644
index dd8d7c07..00000000
--- a/docs/_themes/README.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-krTheme Sphinx Style
-====================
-
-This repository contains sphinx styles Kenneth Reitz uses in most of
-his projects. It is a derivative of Mitsuhiko's themes for Flask and Flask related
-projects. To use this style in your Sphinx documentation, follow
-this guide:
-
-1. put this folder as _themes into your docs folder. Alternatively
- you can also use git submodules to check out the contents there.
-
-2. add this to your conf.py: ::
-
- sys.path.append(os.path.abspath('_themes'))
- html_theme_path = ['_themes']
- html_theme = 'flask'
-
-The following themes exist:
-
-**kr**
- the standard flask documentation theme for large projects
-
-**kr_small**
- small one-page theme. Intended to be used by very small addon libraries.
-
diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html
deleted file mode 100644
index a9a3cb6e..00000000
--- a/docs/_themes/kr/layout.html
+++ /dev/null
@@ -1,85 +0,0 @@
-{%- extends "basic/layout.html" %}
-{%- block extrahead %}
- {{ super() }}
- {% if theme_touch_icon %}
-
- {% endif %}
-
-
-{% endblock %}
-{%- block relbar2 %}{% endblock %}
-{%- block footer %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{%- endblock %}
diff --git a/docs/_themes/kr/relations.html b/docs/_themes/kr/relations.html
deleted file mode 100644
index 3bbcde85..00000000
--- a/docs/_themes/kr/relations.html
+++ /dev/null
@@ -1,19 +0,0 @@
-Related Topics
-
diff --git a/docs/_themes/kr/static/flasky.css_t b/docs/_themes/kr/static/flasky.css_t
deleted file mode 100644
index 3a13c358..00000000
--- a/docs/_themes/kr/static/flasky.css_t
+++ /dev/null
@@ -1,536 +0,0 @@
-/*
- * flasky.css_t
- * ~~~~~~~~~~~~
- *
- * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz.
- * :license: Flask Design License, see LICENSE for details.
- */
-
-{% set page_width = '940px' %}
-{% set sidebar_width = '220px' %}
-
-@import url("basic.css");
-
-/* -- page layout ----------------------------------------------------------- */
-
-body {
- font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro';
- font-size: 17px;
- background-color: white;
- color: #000;
- margin: 0;
- padding: 0;
-}
-
-div.document {
- width: {{ page_width }};
- margin: 30px auto 0 auto;
-}
-
-div.documentwrapper {
- float: left;
- width: 100%;
-}
-
-div.bodywrapper {
- margin: 0 0 0 {{ sidebar_width }};
-}
-
-div.sphinxsidebar {
- width: {{ sidebar_width }};
-}
-
-hr {
- border: 1px solid #B1B4B6;
-}
-
-div.body {
- background-color: #ffffff;
- color: #3E4349;
- padding: 0 30px 0 30px;
-}
-
-img.floatingflask {
- padding: 0 0 10px 10px;
- float: right;
-}
-
-div.footer {
- width: {{ page_width }};
- margin: 20px auto 30px auto;
- font-size: 14px;
- color: #888;
- text-align: right;
-}
-
-div.footer a {
- color: #888;
-}
-
-div.related {
- display: none;
-}
-
-div.sphinxsidebar a {
- color: #444;
- text-decoration: none;
- border-bottom: 1px dotted #999;
-}
-
-div.sphinxsidebar a:hover {
- border-bottom: 1px solid #999;
-}
-
-div.sphinxsidebar {
- font-size: 14px;
- line-height: 1.5;
-}
-
-div.sphinxsidebarwrapper {
- padding: 18px 10px;
-}
-
-div.sphinxsidebarwrapper p.logo {
- padding: 0;
- margin: -10px 0 0 -20px;
- text-align: center;
-}
-
-div.sphinxsidebar h3,
-div.sphinxsidebar h4 {
- font-family: 'Garamond', 'Georgia', serif;
- color: #444;
- font-size: 24px;
- font-weight: normal;
- margin: 0 0 5px 0;
- padding: 0;
-}
-
-div.sphinxsidebar h4 {
- font-size: 20px;
-}
-
-div.sphinxsidebar h3 a {
- color: #444;
-}
-
-div.sphinxsidebar p.logo a,
-div.sphinxsidebar h3 a,
-div.sphinxsidebar p.logo a:hover,
-div.sphinxsidebar h3 a:hover {
- border: none;
-}
-
-div.sphinxsidebar p {
- color: #555;
- margin: 10px 0;
-}
-
-div.sphinxsidebar ul {
- margin: 10px 0;
- padding: 0;
- color: #000;
-}
-
-div.sphinxsidebar input {
- border: 1px solid #ccc;
- font-family: 'Georgia', serif;
- font-size: 1em;
-}
-
-/* -- body styles ----------------------------------------------------------- */
-
-a {
- color: #004B6B;
- text-decoration: underline;
-}
-
-a:hover {
- color: #6D4100;
- text-decoration: underline;
-}
-
-div.body h1,
-div.body h2,
-div.body h3,
-div.body h4,
-div.body h5,
-div.body h6 {
- font-family: 'Garamond', 'Georgia', serif;
- font-weight: normal;
- margin: 30px 0px 10px 0px;
- padding: 0;
-}
-
-div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
-div.body h2 { font-size: 180%; }
-div.body h3 { font-size: 150%; }
-div.body h4 { font-size: 130%; }
-div.body h5 { font-size: 100%; }
-div.body h6 { font-size: 100%; }
-
-a.headerlink {
- color: #ddd;
- padding: 0 4px;
- text-decoration: none;
-}
-
-a.headerlink:hover {
- color: #444;
- background: #eaeaea;
-}
-
-div.body p, div.body dd, div.body li {
- line-height: 1.4em;
-}
-
-div.admonition {
- background: #fafafa;
- margin: 20px -30px;
- padding: 10px 30px;
- border-top: 1px solid #ccc;
- border-bottom: 1px solid #ccc;
-}
-
-div.admonition tt.xref, div.admonition a tt {
- border-bottom: 1px solid #fafafa;
-}
-
-dd div.admonition {
- margin-left: -60px;
- padding-left: 60px;
-}
-
-div.admonition p.admonition-title {
- font-family: 'Garamond', 'Georgia', serif;
- font-weight: normal;
- font-size: 24px;
- margin: 0 0 10px 0;
- padding: 0;
- line-height: 1;
-}
-
-div.admonition p.last {
- margin-bottom: 0;
-}
-
-div.highlight {
- background-color: white;
-}
-
-dt:target, .highlight {
- background: #FAF3E8;
-}
-
-div.note {
- background-color: #eee;
- border: 1px solid #ccc;
-}
-
-div.seealso {
- background-color: #ffc;
- border: 1px solid #ff6;
-}
-
-div.topic {
- background-color: #eee;
-}
-
-p.admonition-title {
- display: inline;
-}
-
-p.admonition-title:after {
- content: ":";
-}
-
-pre, tt {
- font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
- font-size: 0.9em;
-}
-
-img.screenshot {
-}
-
-tt.descname, tt.descclassname {
- font-size: 0.95em;
-}
-
-tt.descname {
- padding-right: 0.08em;
-}
-
-img.screenshot {
- -moz-box-shadow: 2px 2px 4px #eee;
- -webkit-box-shadow: 2px 2px 4px #eee;
- box-shadow: 2px 2px 4px #eee;
-}
-
-table.docutils {
- border: 1px solid #888;
- -moz-box-shadow: 2px 2px 4px #eee;
- -webkit-box-shadow: 2px 2px 4px #eee;
- box-shadow: 2px 2px 4px #eee;
-}
-
-table.docutils td, table.docutils th {
- border: 1px solid #888;
- padding: 0.25em 0.7em;
-}
-
-table.field-list, table.footnote {
- border: none;
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
- box-shadow: none;
-}
-
-table.footnote {
- margin: 15px 0;
- width: 100%;
- border: 1px solid #eee;
- background: #fdfdfd;
- font-size: 0.9em;
-}
-
-table.footnote + table.footnote {
- margin-top: -15px;
- border-top: none;
-}
-
-table.field-list th {
- padding: 0 0.8em 0 0;
-}
-
-table.field-list td {
- padding: 0;
-}
-
-table.footnote td.label {
- width: 0px;
- padding: 0.3em 0 0.3em 0.5em;
-}
-
-table.footnote td {
- padding: 0.3em 0.5em;
-}
-
-dl {
- margin: 0;
- padding: 0;
-}
-
-dl dd {
- margin-left: 30px;
-}
-
-blockquote {
- margin: 0 0 0 30px;
- padding: 0;
-}
-
-ul, ol {
- margin: 10px 0 10px 30px;
- padding: 0;
-}
-
-pre {
- background: #eee;
- padding: 7px 30px;
- margin: 15px -30px;
- line-height: 1.3em;
-}
-
-dl pre, blockquote pre, li pre {
- margin-left: -60px;
- padding-left: 60px;
-}
-
-dl dl pre {
- margin-left: -90px;
- padding-left: 90px;
-}
-
-tt {
- background-color: #ecf0f3;
- color: #222;
- /* padding: 1px 2px; */
-}
-
-tt.xref, a tt {
- background-color: #FBFBFB;
- border-bottom: 1px solid white;
-}
-
-a.reference {
- text-decoration: none;
- border-bottom: 1px dotted #004B6B;
-}
-
-a.reference:hover {
- border-bottom: 1px solid #6D4100;
-}
-
-a.footnote-reference {
- text-decoration: none;
- font-size: 0.7em;
- vertical-align: top;
- border-bottom: 1px dotted #004B6B;
-}
-
-a.footnote-reference:hover {
- border-bottom: 1px solid #6D4100;
-}
-
-a:hover tt {
- background: #EEE;
-}
-
-
-@media screen and (max-width: 870px) {
-
- div.sphinxsidebar {
- display: none;
- }
-
- div.document {
- width: 100%;
-
- }
-
- div.documentwrapper {
- margin-left: 0;
- margin-top: 0;
- margin-right: 0;
- margin-bottom: 0;
- }
-
- div.bodywrapper {
- margin-top: 0;
- margin-right: 0;
- margin-bottom: 0;
- margin-left: 0;
- }
-
- ul {
- margin-left: 0;
- }
-
- .document {
- width: auto;
- }
-
- .footer {
- width: auto;
- }
-
- .bodywrapper {
- margin: 0;
- }
-
- .footer {
- width: auto;
- }
-
- .github {
- display: none;
- }
-
-
-
-}
-
-
-
-@media screen and (max-width: 875px) {
-
- body {
- margin: 0;
- padding: 20px 30px;
- }
-
- div.documentwrapper {
- float: none;
- background: white;
- }
-
- div.sphinxsidebar {
- display: block;
- float: none;
- width: 102.5%;
- margin: 50px -30px -20px -30px;
- padding: 10px 20px;
- background: #333;
- color: white;
- }
-
- div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
- div.sphinxsidebar h3 a {
- color: white;
- }
-
- div.sphinxsidebar a {
- color: #aaa;
- }
-
- div.sphinxsidebar p.logo {
- display: none;
- }
-
- div.document {
- width: 100%;
- margin: 0;
- }
-
- div.related {
- display: block;
- margin: 0;
- padding: 10px 0 20px 0;
- }
-
- div.related ul,
- div.related ul li {
- margin: 0;
- padding: 0;
- }
-
- div.footer {
- display: none;
- }
-
- div.bodywrapper {
- margin: 0;
- }
-
- div.body {
- min-height: 0;
- padding: 0;
- }
-
- .rtd_doc_footer {
- display: none;
- }
-
- .document {
- width: auto;
- }
-
- .footer {
- width: auto;
- }
-
- .footer {
- width: auto;
- }
-
- .github {
- display: none;
- }
-}
-
-
-/* misc. */
-
-.revsys-inline {
- display: none!important;
-}
\ No newline at end of file
diff --git a/docs/_themes/kr/theme.conf b/docs/_themes/kr/theme.conf
deleted file mode 100644
index 07698f6f..00000000
--- a/docs/_themes/kr/theme.conf
+++ /dev/null
@@ -1,7 +0,0 @@
-[theme]
-inherit = basic
-stylesheet = flasky.css
-pygments_style = flask_theme_support.FlaskyStyle
-
-[options]
-touch_icon =
diff --git a/docs/_themes/kr_small/layout.html b/docs/_themes/kr_small/layout.html
deleted file mode 100644
index aa1716aa..00000000
--- a/docs/_themes/kr_small/layout.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "basic/layout.html" %}
-{% block header %}
- {{ super() }}
- {% if pagename == 'index' %}
-
- {% endif %}
-{% endblock %}
-{% block footer %}
- {% if pagename == 'index' %}
-
- {% endif %}
-{% endblock %}
-{# do not display relbars #}
-{% block relbar1 %}{% endblock %}
-{% block relbar2 %}
- {% if theme_github_fork %}
-
- {% endif %}
-{% endblock %}
-{% block sidebar1 %}{% endblock %}
-{% block sidebar2 %}{% endblock %}
diff --git a/docs/_themes/kr_small/static/flasky.css_t b/docs/_themes/kr_small/static/flasky.css_t
deleted file mode 100644
index 71961a27..00000000
--- a/docs/_themes/kr_small/static/flasky.css_t
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * flasky.css_t
- * ~~~~~~~~~~~~
- *
- * Sphinx stylesheet -- flasky theme based on nature theme.
- *
- * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
- * :license: BSD, see LICENSE for details.
- *
- */
-
-@import url("basic.css");
-
-/* -- page layout ----------------------------------------------------------- */
-
-body {
- font-family: 'Georgia', serif;
- font-size: 17px;
- color: #000;
- background: white;
- margin: 0;
- padding: 0;
-}
-
-div.documentwrapper {
- float: left;
- width: 100%;
-}
-
-div.bodywrapper {
- margin: 40px auto 0 auto;
- width: 700px;
-}
-
-hr {
- border: 1px solid #B1B4B6;
-}
-
-div.body {
- background-color: #ffffff;
- color: #3E4349;
- padding: 0 30px 30px 30px;
-}
-
-img.floatingflask {
- padding: 0 0 10px 10px;
- float: right;
-}
-
-div.footer {
- text-align: right;
- color: #888;
- padding: 10px;
- font-size: 14px;
- width: 650px;
- margin: 0 auto 40px auto;
-}
-
-div.footer a {
- color: #888;
- text-decoration: underline;
-}
-
-div.related {
- line-height: 32px;
- color: #888;
-}
-
-div.related ul {
- padding: 0 0 0 10px;
-}
-
-div.related a {
- color: #444;
-}
-
-/* -- body styles ----------------------------------------------------------- */
-
-a {
- color: #004B6B;
- text-decoration: underline;
-}
-
-a:hover {
- color: #6D4100;
- text-decoration: underline;
-}
-
-div.body {
- padding-bottom: 40px; /* saved for footer */
-}
-
-div.body h1,
-div.body h2,
-div.body h3,
-div.body h4,
-div.body h5,
-div.body h6 {
- font-family: 'Garamond', 'Georgia', serif;
- font-weight: normal;
- margin: 30px 0px 10px 0px;
- padding: 0;
-}
-
-{% if theme_index_logo %}
-div.indexwrapper h1 {
- text-indent: -999999px;
- background: url({{ theme_index_logo }}) no-repeat center center;
- height: {{ theme_index_logo_height }};
-}
-{% endif %}
-
-div.body h2 { font-size: 180%; }
-div.body h3 { font-size: 150%; }
-div.body h4 { font-size: 130%; }
-div.body h5 { font-size: 100%; }
-div.body h6 { font-size: 100%; }
-
-a.headerlink {
- color: white;
- padding: 0 4px;
- text-decoration: none;
-}
-
-a.headerlink:hover {
- color: #444;
- background: #eaeaea;
-}
-
-div.body p, div.body dd, div.body li {
- line-height: 1.4em;
-}
-
-div.admonition {
- background: #fafafa;
- margin: 20px -30px;
- padding: 10px 30px;
- border-top: 1px solid #ccc;
- border-bottom: 1px solid #ccc;
-}
-
-div.admonition p.admonition-title {
- font-family: 'Garamond', 'Georgia', serif;
- font-weight: normal;
- font-size: 24px;
- margin: 0 0 10px 0;
- padding: 0;
- line-height: 1;
-}
-
-div.admonition p.last {
- margin-bottom: 0;
-}
-
-div.highlight{
- background-color: white;
-}
-
-dt:target, .highlight {
- background: #FAF3E8;
-}
-
-div.note {
- background-color: #eee;
- border: 1px solid #ccc;
-}
-
-div.seealso {
- background-color: #ffc;
- border: 1px solid #ff6;
-}
-
-div.topic {
- background-color: #eee;
-}
-
-div.warning {
- background-color: #ffe4e4;
- border: 1px solid #f66;
-}
-
-p.admonition-title {
- display: inline;
-}
-
-p.admonition-title:after {
- content: ":";
-}
-
-pre, tt {
- font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
- font-size: 0.85em;
-}
-
-img.screenshot {
-}
-
-tt.descname, tt.descclassname {
- font-size: 0.95em;
-}
-
-tt.descname {
- padding-right: 0.08em;
-}
-
-img.screenshot {
- -moz-box-shadow: 2px 2px 4px #eee;
- -webkit-box-shadow: 2px 2px 4px #eee;
- box-shadow: 2px 2px 4px #eee;
-}
-
-table.docutils {
- border: 1px solid #888;
- -moz-box-shadow: 2px 2px 4px #eee;
- -webkit-box-shadow: 2px 2px 4px #eee;
- box-shadow: 2px 2px 4px #eee;
-}
-
-table.docutils td, table.docutils th {
- border: 1px solid #888;
- padding: 0.25em 0.7em;
-}
-
-table.field-list, table.footnote {
- border: none;
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
- box-shadow: none;
-}
-
-table.footnote {
- margin: 15px 0;
- width: 100%;
- border: 1px solid #eee;
-}
-
-table.field-list th {
- padding: 0 0.8em 0 0;
-}
-
-table.field-list td {
- padding: 0;
-}
-
-table.footnote td {
- padding: 0.5em;
-}
-
-dl {
- margin: 0;
- padding: 0;
-}
-
-dl dd {
- margin-left: 30px;
-}
-
-pre {
- padding: 0;
- margin: 15px -30px;
- padding: 8px;
- line-height: 1.3em;
- padding: 7px 30px;
- background: #eee;
- border-radius: 2px;
- -moz-border-radius: 2px;
- -webkit-border-radius: 2px;
-}
-
-dl pre {
- margin-left: -60px;
- padding-left: 60px;
-}
-
-tt {
- background-color: #ecf0f3;
- color: #222;
- /* padding: 1px 2px; */
-}
-
-tt.xref, a tt {
- background-color: #FBFBFB;
-}
-
-a:hover tt {
- background: #EEE;
-}
diff --git a/docs/_themes/kr_small/theme.conf b/docs/_themes/kr_small/theme.conf
deleted file mode 100644
index 542b4625..00000000
--- a/docs/_themes/kr_small/theme.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-[theme]
-inherit = basic
-stylesheet = flasky.css
-nosidebar = true
-pygments_style = flask_theme_support.FlaskyStyle
-
-[options]
-index_logo = ''
-index_logo_height = 120px
-github_fork = ''
diff --git a/docs/api.rst b/docs/api.rst
index 8d277c81..f3bc2107 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -25,9 +25,30 @@ They all return an instance of the :class:`Response ` object.
.. autofunction:: patch
.. autofunction:: delete
+Exceptions
+----------
+
+.. autoexception:: requests.RequestException
+.. autoexception:: requests.ConnectionError
+.. autoexception:: requests.HTTPError
+.. autoexception:: requests.URLRequired
+.. autoexception:: requests.TooManyRedirects
+.. autoexception:: requests.ConnectTimeout
+.. autoexception:: requests.ReadTimeout
+.. autoexception:: requests.Timeout
+
+
+Request Sessions
+----------------
+
+.. _sessionapi:
+
+.. autoclass:: Session
+ :inherited-members:
+
Lower-Level Classes
-~~~~~~~~~~~~~~~~~~~
+-------------------
.. autoclass:: requests.Request
:inherited-members:
@@ -35,10 +56,11 @@ Lower-Level Classes
.. autoclass:: Response
:inherited-members:
-Request Sessions
-----------------
-.. autoclass:: Session
+Lower-Lower-Level Classes
+-------------------------
+
+.. autoclass:: requests.PreparedRequest
:inherited-members:
.. autoclass:: requests.adapters.HTTPAdapter
@@ -52,21 +74,35 @@ Authentication
.. autoclass:: requests.auth.HTTPProxyAuth
.. autoclass:: requests.auth.HTTPDigestAuth
-Exceptions
-~~~~~~~~~~
-.. autoexception:: requests.exceptions.RequestException
-.. autoexception:: requests.exceptions.ConnectionError
-.. autoexception:: requests.exceptions.HTTPError
-.. autoexception:: requests.exceptions.URLRequired
-.. autoexception:: requests.exceptions.TooManyRedirects
-.. autoexception:: requests.exceptions.ConnectTimeout
-.. autoexception:: requests.exceptions.ReadTimeout
-.. autoexception:: requests.exceptions.Timeout
+
+Encodings
+---------
+
+.. autofunction:: requests.utils.get_encodings_from_content
+.. autofunction:: requests.utils.get_encoding_from_headers
+.. autofunction:: requests.utils.get_unicode_from_response
+
+
+.. _api-cookies:
+
+Cookies
+-------
+
+.. autofunction:: requests.utils.dict_from_cookiejar
+.. autofunction:: requests.utils.cookiejar_from_dict
+.. autofunction:: requests.utils.add_dict_to_cookiejar
+
+.. autoclass:: requests.cookies.RequestsCookieJar
+ :inherited-members:
+
+.. autoclass:: requests.cookies.CookieConflictError
+ :inherited-members:
+
Status Code Lookup
-~~~~~~~~~~~~~~~~~~
+------------------
.. autofunction:: requests.codes
@@ -81,49 +117,6 @@ Status Code Lookup
>>> requests.codes['\o/']
200
-.. _api-cookies:
-
-Cookies
-~~~~~~~
-
-.. autofunction:: requests.utils.dict_from_cookiejar
-.. autofunction:: requests.utils.cookiejar_from_dict
-.. autofunction:: requests.utils.add_dict_to_cookiejar
-
-.. autoclass:: requests.cookies.RequestsCookieJar
- :inherited-members:
-
-.. autoclass:: requests.cookies.CookieConflictError
- :inherited-members:
-
-
-Encodings
-~~~~~~~~~
-
-.. autofunction:: requests.utils.get_encodings_from_content
-.. autofunction:: requests.utils.get_encoding_from_headers
-.. autofunction:: requests.utils.get_unicode_from_response
-
-
-Classes
-~~~~~~~
-
-.. autoclass:: requests.Response
- :inherited-members:
-
-.. autoclass:: requests.Request
- :inherited-members:
-
-.. autoclass:: requests.PreparedRequest
- :inherited-members:
-
-.. _sessionapi:
-
-.. autoclass:: requests.Session
- :inherited-members:
-
-.. autoclass:: requests.adapters.HTTPAdapter
- :inherited-members:
Migrating to 1.x
@@ -184,11 +177,14 @@ API Changes
import requests
import logging
- # these two lines enable debugging at httplib level (requests->urllib3->httplib)
+ # Enabling debugging at http.client level (requests->urllib3->http.client)
# you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA.
# the only thing missing will be the response.body which is not logged.
- import httplib
- httplib.HTTPConnection.debuglevel = 1
+ try: # for Python 3
+ from http.client import HTTPConnection
+ except ImportError:
+ from httplib import HTTPConnection
+ HTTPConnection.debuglevel = 1
logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests
logging.getLogger().setLevel(logging.DEBUG)
diff --git a/docs/conf.py b/docs/conf.py
index 4969857b..00e4261c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
#
# Requests documentation build configuration file, created by
-# sphinx-quickstart on Sun Feb 13 23:54:25 2011.
+# sphinx-quickstart on Fri Feb 19 00:05:47 2016.
#
-# This file is execfile()d with the current directory set to its containing dir.
+# This file is execfile()d with the current directory set to its
+# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
@@ -11,31 +12,43 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys, os
+import sys
+import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# Insert Requests' path into the system.
sys.path.insert(0, os.path.abspath('..'))
+sys.path.insert(0, os.path.abspath('_themes'))
+
import requests
from requests import __version__
-# -- General configuration -----------------------------------------------------
+
+# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.viewcode',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
-# The suffix of source filenames.
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
@@ -46,7 +59,8 @@ master_doc = 'index'
# General information about the project.
project = u'Requests'
-copyright = u'2015. A Kenneth Reitz Project'
+copyright = u'2016. A Kenneth Reitz Project'
+author = u'Kenneth Reitz'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -55,11 +69,14 @@ copyright = u'2015. A tag referring to it. The value of this option must be the
@@ -173,23 +208,45 @@ html_show_sphinx = False
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
+# Language to be used for generating the HTML full-text search index.
+# Sphinx supports the following languages:
+# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
+# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
+#html_search_language = 'en'
+
+# A dictionary with options for the search language support, empty by default.
+# Now only 'ja' uses this config value
+#html_search_options = {'type': 'default'}
+
+# The name of a javascript file (relative to the configuration directory) that
+# implements a search results scorer. If empty, the default will be used.
+#html_search_scorer = 'scorer.js'
+
# Output file base name for HTML help builder.
htmlhelp_basename = 'Requestsdoc'
+# -- Options for LaTeX output ---------------------------------------------
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+
+# Latex figure (float) alignment
+#'figure_align': 'htbp',
+}
# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
latex_documents = [
- ('index', 'Requests.tex', u'Requests Documentation',
- u'Kenneth Reitz', 'manual'),
+ (master_doc, 'Requests.tex', u'Requests Documentation',
+ u'Kenneth Reitz', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -206,9 +263,6 @@ latex_documents = [
# If true, show URL addresses after external links.
#latex_show_urls = False
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
# Documents to append as an appendix to all manuals.
#latex_appendices = []
@@ -216,33 +270,110 @@ latex_documents = [
#latex_domain_indices = True
-# -- Options for manual page output --------------------------------------------
+# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'requests', u'Requests Documentation',
- [u'Kenneth Reitz'], 1)
+ (master_doc, 'requests', u'Requests Documentation',
+ [author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
-# -- Options for Texinfo output ------------------------------------------------
+
+# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- ('index', 'Requests', u'Requests Documentation', u'Kenneth Reitz',
- 'Requests', 'One line description of project.', 'Miscellaneous'),
+ (master_doc, 'Requests', u'Requests Documentation',
+ author, 'Requests', 'One line description of project.',
+ 'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
-texinfo_appendices = []
+#texinfo_appendices = []
-sys.path.append(os.path.abspath('_themes'))
-html_theme_path = ['_themes']
-html_theme = 'kr'
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+
+# -- Options for Epub output ----------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+epub_author = author
+epub_publisher = author
+epub_copyright = copyright
+
+# The basename for the epub file. It defaults to the project name.
+#epub_basename = project
+
+# The HTML theme for the epub output. Since the default themes are not
+# optimized for small screen space, using the same theme for HTML and epub
+# output is usually not wise. This defaults to 'epub', a theme designed to save
+# visual space.
+#epub_theme = 'epub'
+
+# The language of the text. It defaults to the language option
+# or 'en' if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# A sequence of (type, uri, title) tuples for the guide element of content.opf.
+#epub_guide = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files that should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
+
+# Choose between 'default' and 'includehidden'.
+#epub_tocscope = 'default'
+
+# Fix unsupported image types using the Pillow.
+#epub_fix_images = False
+
+# Scale large images.
+#epub_max_image_width = 0
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#epub_show_urls = 'inline'
+
+# If false, no index is generated.
+#epub_use_index = True
intersphinx_mapping = {'urllib3': ('http://urllib3.readthedocs.org/en/latest', None)}
diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst
index 075d042a..93181dad 100644
--- a/docs/dev/contributing.rst
+++ b/docs/dev/contributing.rst
@@ -3,37 +3,40 @@
Contributor's Guide
===================
-If you're reading this you're probably interested in contributing to
-Requests. First, We'd like to say: thank you! Open source projects
-live-and-die based on the support they receive from others, and the fact that
-you're even considering supporting Requests is very generous of
-you.
+If you're reading this, you're probably interested in contributing to Requests.
+Thank you very much! Open source projects live-and-die based on the support
+they receive from others, and the fact that you're even considering
+contributing to the Requests project is *very* generous of you.
-This document lays out guidelines and advice for contributing to Requests.
-If you're thinking of contributing, start by reading this thoroughly and
-getting a feel for how contributing to the project works. If you have any
+This document lays out guidelines and advice for contributing to this project.
+If you're thinking of contributing, please start by reading this document and
+getting a feel for how contributing to this project works. If you have any
questions, feel free to reach out to either `Ian Cordasco`_ or `Cory Benfield`_,
the primary maintainers.
+.. _Ian Cordasco: http://www.coglib.com/~icordasc/
+.. _Cory Benfield: https://lukasa.co.uk/about
+
+If you have non-technical feedback, philosophical ponderings, crazy ideas, or
+other general thoughts about Requests or its position within the Python
+ecosystem, the BDFL, `Kenneth Reitz`_, would love to hear from you.
+
The guide is split into sections based on the type of contribution you're
thinking of making, with a section that covers general guidelines for all
contributors.
-.. _Ian Cordasco: http://www.coglib.com/~icordasc/
-.. _Cory Benfield: https://lukasa.co.uk/about
-
-
-All Contributions
------------------
+.. _Kenneth Reitz: mailto:me@kennethreitz.org
Be Cordial
-~~~~~~~~~~
+----------
-**Be cordial or be on your way.**
+ **Be cordial or be on your way**. *—Kenneth Reitz*
Requests has one very important rule governing all forms of contribution,
including reporting bugs or requesting features. This golden rule is
-`be cordial or be on your way`_. **All contributions are welcome**, as long as
+"`be cordial or be on your way`_".
+
+**All contributions are welcome**, as long as
everyone involved is treated with respect.
.. _be cordial or be on your way: http://kennethreitz.org/be-cordial-or-be-on-your-way/
@@ -41,7 +44,7 @@ everyone involved is treated with respect.
.. _early-feedback:
Get Early Feedback
-~~~~~~~~~~~~~~~~~~
+------------------
If you are contributing, do not feel the need to sit on your contribution until
it is perfectly polished and complete. It helps everyone involved for you to
@@ -51,22 +54,23 @@ getting that contribution accepted, and can save you from putting a lot of work
into a contribution that is not suitable for the project.
Contribution Suitability
-~~~~~~~~~~~~~~~~~~~~~~~~
+------------------------
-The project maintainer has the last word on whether or not a contribution is
-suitable for Requests. All contributions will be considered, but from time
-to time contributions will be rejected because they do not suit the project.
+Our project maintainers have the last word on whether or not a contribution is
+suitable for Requests. All contributions will be considered carefully, but from
+time to time, contributions will be rejected because they do not suit the
+current goals or needs of the project.
-If your contribution is rejected, don't despair! So long as you followed these
-guidelines, you'll have a much better chance of getting your next contribution
-accepted.
+If your contribution is rejected, don't despair! As long as you followed these
+guidelines, you will have a much better chance of getting your next
+contribution accepted.
Code Contributions
------------------
-Steps
-~~~~~
+Steps for Submitting Code
+~~~~~~~~~~~~~~~~~~~~~~~~~
When contributing code, you'll want to follow this checklist:
@@ -104,6 +108,56 @@ asking for help.
Please also check the :ref:`early-feedback` section.
+Kenneth Reitz's Code Style™
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Requests codebase uses the `PEP 8`_ code style.
+
+In addition to the standards outlined in PEP 8, we have a few guidelines:
+
+- Line-length can exceed 79 characters, to 100, when convenient.
+- Line-length can exceed 100 characters, when doing otherwise would be *terribly* inconvenient.
+- Always use single-quoted strings (e.g. ``'#flatearth'``), unless a single-quote occurs within the string.
+
+Additionally, one of the styles that PEP8 recommends for `line continuations`_
+completely lacks all sense of taste, and is not to be permitted within
+the Requests codebase::
+
+ # Aligned with opening delimiter.
+ foo = long_function_name(var_one, var_two,
+ var_three, var_four)
+
+No. Just don't. Please.
+
+Docstrings are to follow the following syntaxes::
+
+ def the_earth_is_flat():
+ """NASA divided up the seas into thirty-three degrees."""
+ pass
+
+::
+
+ def fibonacci_spiral_tool():
+ """With my feet upon the ground I lose myself / between the sounds
+ and open wide to suck it in. / I feel it move across my skin. / I'm
+ reaching up and reaching out. / I'm reaching for the random or
+ whatever will bewilder me. / Whatever will bewilder me. / And
+ following our will and wind we may just go where no one's been. /
+ We'll ride the spiral to the end and may just go where no one's
+ been.
+
+ Spiral out. Keep going...
+ """
+ pass
+
+All functions, methods, and classes are to contain docstrings. Object data
+model methods (e.g. ``__repr__``) are typically the exception to this rule.
+
+Thanks for helping to make the world a better place!
+
+.. _PEP 8: http://pep8.org
+.. _line continuations: https://www.python.org/dev/peps/pep-0008/#indentation
+
Documentation Contributions
---------------------------
@@ -112,9 +166,12 @@ the ``docs/`` directory of the codebase. They're written in
`reStructuredText`_, and use `Sphinx`_ to generate the full suite of
documentation.
-When contributing documentation, please attempt to follow the style of the
+When contributing documentation, please do your best to follow the style of the
documentation files. This means a soft-limit of 79 characters wide in your text
-files and a semi-formal prose style.
+files and a semi-formal, yet friendly and approachable, prose style.
+
+When presenting Python code, use single-quoted strings (``'hello'`` instead of
+``"hello"``).
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _Sphinx: http://sphinx-doc.org/index.html
@@ -136,10 +193,14 @@ of other contributors, and should be avoided as much as possible.
Feature Requests
----------------
-Requests is in a perpetual feature freeze. The maintainers believe that
-requests contains every major feature currently required by the vast majority
-of users.
+Requests is in a perpetual feature freeze, only the BDFL can add or approve of
+new features. The maintainers believe that Requests is a feature-complete
+piece of software at this time.
+
+One of the most important skills to have while maintaining a largely-used
+open source project is learning the ability to say "no" to suggested changes,
+while keeping an open ear and mind.
If you believe there is a feature missing, feel free to raise a feature
request, but please do be aware that the overwhelming likelihood is that your
-feature request will not be accepted.
+feature request will not be accepted.
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index d1ebbd58..5eb643e1 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -8,18 +8,14 @@ Requests: HTTP for Humans
Release v\ |version|. (:ref:`Installation `)
-Requests is an :ref:`Apache2 Licensed ` HTTP library, written in
-Python, for human beings.
+Requests is the only *Non-GMO* HTTP library for Python, safe for human
+consumption.
-Python's standard **urllib2** module provides most of
-the HTTP capabilities you need, but the API is thoroughly **broken**.
-It was built for a different time — and a different web. It requires an
-*enormous* amount of work (even method overrides) to perform the simplest of
-tasks.
+**Warning:** Recreational use of other HTTP libraries may result in dangerous side-effects,
+including: security vulnerabilities, verbose code, reinventing the wheel,
+constantly reading documentation, depression, headaches, or even death.
-Things shouldn’t be this way. Not in Python.
-
-::
+Behold, the power of Requests::
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
@@ -33,22 +29,22 @@ Things shouldn’t be this way. Not in Python.
>>> r.json()
{u'private_gists': 419, u'total_private_repos': 77, ...}
-See `similar code, without Requests `_.
+See `similar code, sans Requests `_.
-Requests takes all of the work out of Python HTTP/1.1 — making your integration
-with web services seamless. There's no need to manually add query strings to
-your URLs, or to form-encode your POST data. Keep-alive and HTTP connection
-pooling are 100% automatic, powered by `urllib3 `_,
+
+Requests allows you to send *organic, grass-fed* HTTP/1.1 requests, without the
+need for manual labor. There's no need to manually add query strings to your
+URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling
+are 100% automatic, powered by `urllib3 `_,
which is embedded within Requests.
-
-Testimonials
-------------
+User Testimonials
+-----------------
Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku,
PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington
-Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed
-use Requests internally. It has been downloaded over 60,000,000 times from PyPI.
+Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal U.S.
+Institutions that prefer to be unnamed claim to use Requests internally.
**Armin Ronacher**
Requests is the perfect example how beautiful an API can be with the
@@ -66,9 +62,11 @@ use Requests internally. It has been downloaded over 60,000,000 times from PyPI.
Python HTTP: When in doubt, or when not in doubt, use Requests. Beautiful,
simple, Pythonic.
+Requests is one of the most downloaded Python packages of all time, pulling in
+over 7,000,000 downloads every month. All the cool kids are doing it!
-Feature Support
----------------
+Supported Features
+------------------
Requests is ready for today's web.
@@ -79,16 +77,21 @@ Requests is ready for today's web.
- Basic/Digest Authentication
- Elegant Key/Value Cookies
- Automatic Decompression
+- Automatic Content Decoding
- Unicode Response Bodies
- Multipart File Uploads
+- HTTP(S) Proxy Support
- Connection Timeouts
-- ``.netrc`` support
-- Python 2.6—3.4
-- Thread-safe.
+- Streaming Downloads
+- ``.netrc`` Support
+- Chunked Requests
+- Thread-safety
+
+Requests supports Python 2.6 — 3.5, and runs great on PyPy.
-User Guide
-----------
+The User Guide
+--------------
This part of the documentation, which is mostly prose, begins with some
background information about Requests, then focuses on step-by-step
@@ -104,8 +107,8 @@ instructions for getting the most out of Requests.
user/authentication
-Community Guide
------------------
+The Community Guide
+-------------------
This part of the documentation, which is mostly prose, details the
Requests ecosystem and community.
@@ -121,10 +124,10 @@ Requests ecosystem and community.
community/updates
community/release-process
-API Documentation
------------------
+The API Documentation / Guide
+-----------------------------
-If you are looking for information on a specific function, class or method,
+If you are looking for information on a specific function, class, or method,
this part of the documentation is for you.
.. toctree::
@@ -133,16 +136,19 @@ this part of the documentation is for you.
api
-Contributor Guide
------------------
+The Contributor Guide
+---------------------
If you want to contribute to the project, this part of the documentation is for
you.
.. toctree::
- :maxdepth: 1
+ :maxdepth: 3
dev/contributing
dev/philosophy
dev/todo
dev/authors
+
+There are no more guides. You are now guideless.
+Good luck.
diff --git a/docs/make.bat b/docs/make.bat
index 4441160c..9eaf9b88 100644
--- a/docs/make.bat
+++ b/docs/make.bat
@@ -1,190 +1,263 @@
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=_build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
-set I18NSPHINXOPTS=%SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
- set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^` where ^ is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. singlehtml to make a single large HTML file
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. devhelp to make HTML files and a Devhelp project
- echo. epub to make an epub
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. text to make text files
- echo. man to make manual pages
- echo. texinfo to make Texinfo files
- echo. gettext to make PO message catalogs
- echo. changes to make an overview over all changed/added/deprecated items
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "singlehtml" (
- %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Requests.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Requests.ghc
- goto end
-)
-
-if "%1" == "devhelp" (
- %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished.
- goto end
-)
-
-if "%1" == "epub" (
- %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The epub file is in %BUILDDIR%/epub.
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "text" (
- %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The text files are in %BUILDDIR%/text.
- goto end
-)
-
-if "%1" == "man" (
- %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The manual pages are in %BUILDDIR%/man.
- goto end
-)
-
-if "%1" == "texinfo" (
- %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
- goto end
-)
-
-if "%1" == "gettext" (
- %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- if errorlevel 1 exit /b 1
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- if errorlevel 1 exit /b 1
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-:end
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^` where ^ is one of
+ echo. html to make standalone HTML files
+ echo. dirhtml to make HTML files named index.html in directories
+ echo. singlehtml to make a single large HTML file
+ echo. pickle to make pickle files
+ echo. json to make JSON files
+ echo. htmlhelp to make HTML files and a HTML help project
+ echo. qthelp to make HTML files and a qthelp project
+ echo. devhelp to make HTML files and a Devhelp project
+ echo. epub to make an epub
+ echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+ echo. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over all changed/added/deprecated items
+ echo. xml to make Docutils-native XML files
+ echo. pseudoxml to make pseudoxml-XML files for display purposes
+ echo. linkcheck to check all external links for integrity
+ echo. doctest to run all doctests embedded in the documentation if enabled
+ echo. coverage to run coverage check of the documentation if enabled
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+
+REM Check if sphinx-build is available and fallback to Python version if any
+%SPHINXBUILD% 1>NUL 2>NUL
+if errorlevel 9009 goto sphinx_python
+goto sphinx_ok
+
+:sphinx_python
+
+set SPHINXBUILD=python -m sphinx.__init__
+%SPHINXBUILD% 2> nul
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+:sphinx_ok
+
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Requests.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Requests.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "latexpdf" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ cd %BUILDDIR%/latex
+ make all-pdf
+ cd %~dp0
+ echo.
+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "latexpdfja" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ cd %BUILDDIR%/latex
+ make all-pdf-ja
+ cd %~dp0
+ echo.
+ echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+if "%1" == "coverage" (
+ %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of coverage in the sources finished, look at the ^
+results in %BUILDDIR%/coverage/python.txt.
+ goto end
+)
+
+if "%1" == "xml" (
+ %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The XML files are in %BUILDDIR%/xml.
+ goto end
+)
+
+if "%1" == "pseudoxml" (
+ %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
+ goto end
+)
+
+:end
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 30491772..00000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-Sphinx==1.1.3
diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst
index f0d2ffd9..ddd6edf6 100644
--- a/docs/user/advanced.rst
+++ b/docs/user/advanced.rst
@@ -24,7 +24,7 @@ Let's persist some cookies across requests::
s = requests.Session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
- r = s.get("http://httpbin.org/cookies")
+ r = s.get('http://httpbin.org/cookies')
print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'
@@ -50,6 +50,7 @@ requests, even if using a session. This example will only send the cookies
with the first request, but not the second::
s = requests.Session()
+
r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# '{"cookies": {"from-my": "browser"}}'
@@ -129,14 +130,15 @@ request. The simple recipe for this is the following::
from requests import Request, Session
s = Session()
- req = Request('GET', url,
- data=data,
- headers=header
- )
+
+ req = Request('POST', url, data=data, headers=headers)
prepped = req.prepare()
# do something with prepped.body
+ prepped.body = 'No, I want exactly this as the body.'
+
# do something with prepped.headers
+ del prepped.headers['Content-Type']
resp = s.send(prepped,
stream=stream,
@@ -165,15 +167,15 @@ applied, replace the call to :meth:`Request.prepare()
from requests import Request, Session
s = Session()
- req = Request('GET', url,
- data=data
- headers=headers
- )
+ req = Request('GET', url, data=data, headers=headers)
prepped = s.prepare_request(req)
# do something with prepped.body
+ prepped.body = 'Seriously, send exactly these bytes.'
+
# do something with prepped.headers
+ prepped.headers['Keep-Dead'] = 'parrot'
resp = s.send(prepped,
stream=stream,
@@ -220,14 +222,17 @@ You can also specify a local cert to use as client side certificate, as a single
file (containing the private key and the certificate) or as a tuple of both
file's path::
- >>> requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key'))
+ >>> requests.get('https://kennethreitz.com', cert=('/path/client.cert', '/path/client.key'))
If you specify a wrong path or an invalid cert::
- >>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem')
+ >>> requests.get('https://kennethreitz.com', cert='/wrong_path/client.pem')
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
+.. warning:: The private key to your local certificate *must* be unencrypted.
+ Currently, requests does not support using encrypted keys.
+
.. _ca-certificates:
CA Certificates
@@ -353,15 +358,16 @@ POST Multiple Multipart-Encoded Files
-------------------------------------
You can send multiple files in one request. For example, suppose you want to
-upload image files to an HTML form with a multiple file field 'images':
+upload image files to an HTML form with a multiple file field 'images'::
-To do that, just set files to a list of tuples of (form_field_name, file_info):
+To do that, just set files to a list of tuples of ``(form_field_name, file_info)``::
>>> url = 'http://httpbin.org/post'
- >>> multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
- ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
+ >>> multiple_files = [
+ ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
+ ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
>>> r = requests.post(url, files=multiple_files)
>>> r.text
{
@@ -488,7 +494,9 @@ set ``stream`` to ``True`` and iterate over the response with
lines = r.iter_lines()
# Save the first line for later or just skip it
+
first_line = next(lines)
+
for line in lines:
print(line)
@@ -503,11 +511,11 @@ If you need to use a proxy, you can configure individual requests with the
import requests
proxies = {
- "http": "http://10.10.1.10:3128",
- "https": "http://10.10.1.10:1080",
+ 'http': 'http://10.10.1.10:3128',
+ 'https': 'http://10.10.1.10:1080',
}
- requests.get("http://example.org", proxies=proxies)
+ requests.get('http://example.org', proxies=proxies)
You can also configure proxies by setting the environment variables
``HTTP_PROXY`` and ``HTTPS_PROXY``.
@@ -516,15 +524,14 @@ You can also configure proxies by setting the environment variables
$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
+
$ python
>>> import requests
- >>> requests.get("http://example.org")
+ >>> requests.get('http://example.org')
To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syntax::
- proxies = {
- "http": "http://user:pass@10.10.1.10:3128/",
- }
+ proxies = {'http': 'http://user:pass@10.10.1.10:3128/'}
To give a proxy for a specific scheme and host, use the
`scheme://hostname` form for the key. This will match for
@@ -532,9 +539,7 @@ any request to the given scheme and exact hostname.
::
- proxies = {
- "http://10.20.1.128": "http://10.10.1.10:5323",
- }
+ proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}
Note that proxy URLs must include the scheme.
@@ -599,10 +604,13 @@ So, GitHub returns JSON. That's great, we can use the :meth:`r.json
::
>>> commit_data = r.json()
+
>>> print(commit_data.keys())
[u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
+
>>> print(commit_data[u'committer'])
{u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'}
+
>>> print(commit_data[u'message'])
makin' history
@@ -642,9 +650,12 @@ already exists, we will use it as an example. Let's start by getting it.
>>> r = requests.get('https://api.github.com/repos/kennethreitz/requests/issues/482')
>>> r.status_code
200
+
>>> issue = json.loads(r.text)
+
>>> print(issue[u'title'])
Feature any http verb in docs
+
>>> print(issue[u'comments'])
3
@@ -655,9 +666,12 @@ Cool, we have three comments. Let's take a look at the last of them.
>>> r = requests.get(r.url + u'/comments')
>>> r.status_code
200
+
>>> comments = r.json()
+
>>> print(comments[0].keys())
[u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
+
>>> print(comments[2][u'body'])
Probably in the "advanced" section
@@ -677,6 +691,7 @@ is to POST to the thread. Let's do it.
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
+
>>> r = requests.post(url=url, data=body)
>>> r.status_code
404
@@ -689,9 +704,11 @@ the very common Basic Auth.
>>> from requests.auth import HTTPBasicAuth
>>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')
+
>>> r = requests.post(url=url, data=body, auth=auth)
>>> r.status_code
201
+
>>> content = r.json()
>>> print(content[u'body'])
Sounds great! I'll get right on it.
@@ -705,8 +722,10 @@ that.
>>> print(content[u"id"])
5804413
+
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413"
+
>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
200
@@ -827,10 +846,9 @@ SSLv3:
""""Transport adapter" that allows us to use SSLv3."""
def init_poolmanager(self, connections, maxsize, block=False):
- self.poolmanager = PoolManager(num_pools=connections,
- maxsize=maxsize,
- block=block,
- ssl_version=ssl.PROTOCOL_SSLv3)
+ self.poolmanager = PoolManager(
+ num_pools=connections, maxsize=maxsize,
+ block=block, ssl_version=ssl.PROTOCOL_SSLv3)
.. _`described here`: http://www.kennethreitz.org/essays/the-future-of-python-http
.. _`urllib3`: https://github.com/shazow/urllib3
diff --git a/docs/user/authentication.rst b/docs/user/authentication.rst
index dd0bf2b8..2779e9c8 100644
--- a/docs/user/authentication.rst
+++ b/docs/user/authentication.rst
@@ -37,7 +37,8 @@ netrc Authentication
If no authentication method is given with the ``auth`` argument, Requests will
attempt to get the authentication credentials for the URL's hostname from the
-user's netrc file.
+user's netrc file. The netrc file overrides raw HTTP authentication headers
+set with `headers=`.
If credentials for the hostname are found, the request is sent with HTTP Basic
Auth.
@@ -125,4 +126,3 @@ Further examples can be found under the `Requests organization`_ and in the
.. _Kerberos: https://github.com/requests/requests-kerberos
.. _NTLM: https://github.com/requests/requests-ntlm
.. _Requests organization: https://github.com/requests
-
diff --git a/docs/user/install.rst b/docs/user/install.rst
index 5f0ef9c4..c3f0084e 100644
--- a/docs/user/install.rst
+++ b/docs/user/install.rst
@@ -7,23 +7,19 @@ This part of the documentation covers the installation of Requests.
The first step to using any software package is getting it properly installed.
-Distribute & Pip
-----------------
+Pip Install Requests
+--------------------
-Installing Requests is simple with `pip `_, just run
-this in your terminal::
+To install Requests, simply run this simple command in your terminal of choice::
$ pip install requests
-or, with `easy_install `_::
+If you don't have `pip `_ installed (tisk tisk!),
+`this Python installation guide `_
+can guide you through the process.
- $ easy_install requests
-
-But, you really `shouldn't do that `_.
-
-
-Get the Code
-------------
+Get the Source Code
+-------------------
Requests is actively developed on GitHub, where the code is
`always available `_.
@@ -32,16 +28,12 @@ You can either clone the public repository::
$ git clone git://github.com/kennethreitz/requests.git
-Download the `tarball `_::
+Or, download the `tarball `_::
$ curl -OL https://github.com/kennethreitz/requests/tarball/master
+ # optionally, zipball is also available (for Windows users).
-Or, download the `zipball `_::
-
- $ curl -OL https://github.com/kennethreitz/requests/zipball/master
-
-
-Once you have a copy of the source, you can embed it in your Python package,
-or install it into your site-packages easily::
+Once you have a copy of the source, you can embed it in your own Python
+package, or install it into your site-packages easily::
$ python setup.py install
diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst
index 1ff66593..d31f224e 100644
--- a/docs/user/quickstart.rst
+++ b/docs/user/quickstart.rst
@@ -37,15 +37,15 @@ get all the information we need from this object.
Requests' simple API means that all forms of HTTP request are as obvious. For
example, this is how you make an HTTP POST request::
- >>> r = requests.post("http://httpbin.org/post", data = {"key":"value"})
+ >>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})
Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
OPTIONS? These are all just as simple::
- >>> r = requests.put("http://httpbin.org/put", data = {"key":"value"})
- >>> r = requests.delete("http://httpbin.org/delete")
- >>> r = requests.head("http://httpbin.org/get")
- >>> r = requests.options("http://httpbin.org/get")
+ >>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})
+ >>> r = requests.delete('http://httpbin.org/delete')
+ >>> r = requests.head('http://httpbin.org/get')
+ >>> r = requests.options('http://httpbin.org/get')
That's all well and good, but it's also only the start of what Requests can
do.
@@ -63,7 +63,7 @@ Requests allows you to provide these arguments as a dictionary, using the
following code::
>>> payload = {'key1': 'value1', 'key2': 'value2'}
- >>> r = requests.get("http://httpbin.org/get", params=payload)
+ >>> r = requests.get('http://httpbin.org/get', params=payload)
You can see that the URL has been correctly encoded by printing the URL::
@@ -76,7 +76,8 @@ URL's query string.
You can also pass a list of items as a value::
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
- >>> r = requests.get("http://httpbin.org/get", params=payload)
+
+ >>> r = requests.get('http://httpbin.org/get', params=payload)
>>> print(r.url)
http://httpbin.org/get?key1=value1&key2=value2&key2=value3
@@ -87,6 +88,7 @@ We can read the content of the server's response. Consider the GitHub timeline
again::
>>> import requests
+
>>> r = requests.get('https://api.github.com/events')
>>> r.text
u'[{"repository":{"open_issues":0,"url":"https://github.com/...
@@ -131,6 +133,7 @@ use the following code::
>>> from PIL import Image
>>> from StringIO import StringIO
+
>>> i = Image.open(StringIO(r.content))
@@ -140,6 +143,7 @@ JSON Response Content
There's also a builtin JSON decoder, in case you're dealing with JSON data::
>>> import requests
+
>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
@@ -163,8 +167,10 @@ server, you can access ``r.raw``. If you want to do this, make sure you set
``stream=True`` in your initial request. Once you do, you can do this::
>>> r = requests.get('https://api.github.com/events', stream=True)
+
>>> r.raw
+
>>> r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
@@ -189,7 +195,6 @@ If you'd like to add HTTP headers to a request, simply pass in a ``dict`` to the
For example, we didn't specify our user-agent in the previous example::
- >>> import json
>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}
@@ -197,7 +202,9 @@ For example, we didn't specify our user-agent in the previous example::
Note: Custom headers are given less precedence than more specific sources of information. For instance:
-* Authorization headers will be overridden if credentials are passed via the ``auth`` parameter or are specified in a ``.netrc`` accessible in the environment.
+* Authorization headers set with `headers=` will be overridden if credentials
+ are specified in ``.netrc``, which in turn will be overridden by the ``auth=``
+ parameter.
* Authorization headers will be removed if you get redirected off-host.
* Proxy-Authorization headers will be overridden by proxy credentials provided in the URL.
* Content-Length headers will be overridden when we can determine the length of the content.
@@ -213,6 +220,7 @@ To do this, simply pass a dictionary to the ``data`` argument. Your
dictionary of data will automatically be form-encoded when the request is made::
>>> payload = {'key1': 'value1', 'key2': 'value2'}
+
>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print(r.text)
{
@@ -230,6 +238,7 @@ you pass in a ``string`` instead of a ``dict``, that data will be posted directl
For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data::
>>> import json
+
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
@@ -424,10 +433,13 @@ response.
For example, GitHub redirects all HTTP requests to HTTPS::
>>> r = requests.get('http://github.com')
+
>>> r.url
'https://github.com/'
+
>>> r.status_code
200
+
>>> r.history
[]
@@ -436,16 +448,20 @@ If you're using GET, OPTIONS, POST, PUT, PATCH or DELETE, you can disable
redirection handling with the ``allow_redirects`` parameter::
>>> r = requests.get('http://github.com', allow_redirects=False)
+
>>> r.status_code
301
+
>>> r.history
[]
If you're using HEAD, you can enable redirection as well::
>>> r = requests.head('http://github.com', allow_redirects=True)
+
>>> r.url
'https://github.com/'
+
>>> r.history
[]
diff --git a/requests/__init__.py b/requests/__init__.py
index f7924dc8..1218d432 100644
--- a/requests/__init__.py
+++ b/requests/__init__.py
@@ -42,11 +42,11 @@ is at .
"""
__title__ = 'requests'
-__version__ = '2.8.1'
-__build__ = 0x020801
+__version__ = '2.9.1'
+__build__ = 0x020901
__author__ = 'Kenneth Reitz'
__license__ = 'Apache 2.0'
-__copyright__ = 'Copyright 2015 Kenneth Reitz'
+__copyright__ = 'Copyright 2016 Kenneth Reitz'
# Attempt to enable urllib3's SNI support, if possible
try:
diff --git a/requests/adapters.py b/requests/adapters.py
index cecf6c2a..4f2b23cf 100644
--- a/requests/adapters.py
+++ b/requests/adapters.py
@@ -65,7 +65,7 @@ class HTTPAdapter(BaseAdapter):
:param pool_connections: The number of urllib3 connection pools to cache.
:param pool_maxsize: The maximum number of connections to save in the pool.
- :param int max_retries: The maximum number of retries each connection
+ :param max_retries: The maximum number of retries each connection
should attempt. Note, this applies only to failed DNS lookups, socket
connections and connection timeouts, never to requests where data has
made it to the server. By default, Requests does not retry failed
@@ -108,7 +108,7 @@ class HTTPAdapter(BaseAdapter):
def __setstate__(self, state):
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
- # because self.poolmanager uses a lambda function, which isn't pickleable.
+ # self.poolmanager uses a lambda function, which isn't pickleable.
self.proxy_manager = {}
self.config = {}
diff --git a/requests/api.py b/requests/api.py
index 09ec731b..b21a1a4f 100644
--- a/requests/api.py
+++ b/requests/api.py
@@ -33,7 +33,7 @@ def request(method, url, **kwargs):
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
- :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
+ :param verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``.
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
:return: :class:`Response ` object
diff --git a/requests/auth.py b/requests/auth.py
index 8c4e847f..edf4c8dc 100644
--- a/requests/auth.py
+++ b/requests/auth.py
@@ -47,6 +47,15 @@ class HTTPBasicAuth(AuthBase):
self.username = username
self.password = password
+ def __eq__(self, other):
+ return all([
+ self.username == getattr(other, 'username', None),
+ self.password == getattr(other, 'password', None)
+ ])
+
+ def __ne__(self, other):
+ return not self == other
+
def __call__(self, r):
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
return r
@@ -136,7 +145,7 @@ class HTTPDigestAuth(AuthBase):
if _algorithm == 'MD5-SESS':
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
- if qop is None:
+ if not qop:
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
elif qop == 'auth' or 'auth' in qop.split(','):
noncebit = "%s:%s:%s:%s:%s" % (
@@ -221,3 +230,12 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.num_401_calls = 1
return r
+
+ def __eq__(self, other):
+ return all([
+ self.username == getattr(other, 'username', None),
+ self.password == getattr(other, 'password', None)
+ ])
+
+ def __ne__(self, other):
+ return not self == other
diff --git a/requests/cookies.py b/requests/cookies.py
index d097d7db..b85fd2b6 100644
--- a/requests/cookies.py
+++ b/requests/cookies.py
@@ -8,6 +8,7 @@ requests.utils imports from here, so be careful with imports.
import copy
import time
+import calendar
import collections
from .compat import cookielib, urlparse, urlunparse, Morsel
@@ -368,7 +369,7 @@ def _copy_cookie_jar(jar):
return None
if hasattr(jar, 'copy'):
- # We're dealing with an instane of RequestsCookieJar
+ # We're dealing with an instance of RequestsCookieJar
return jar.copy()
# We're dealing with a generic CookieJar instance
new_jar = copy.copy(jar)
@@ -424,8 +425,9 @@ def morsel_to_cookie(morsel):
raise TypeError('max-age: %s must be integer' % morsel['max-age'])
elif morsel['expires']:
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
- expires = int(time.mktime(
- time.strptime(morsel['expires'], time_template)) - time.timezone)
+ expires = calendar.timegm(
+ time.strptime(morsel['expires'], time_template)
+ )
return create_cookie(
comment=morsel['comment'],
comment_url=bool(morsel['comment']),
diff --git a/requests/models.py b/requests/models.py
index 639565cf..4bcbc548 100644
--- a/requests/models.py
+++ b/requests/models.py
@@ -81,7 +81,7 @@ class RequestEncodingMixin(object):
"""
if isinstance(data, (str, bytes)):
- return to_native_string(data)
+ return data
elif hasattr(data, 'read'):
return data
elif hasattr(data, '__iter__'):
@@ -324,7 +324,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
def prepare_url(self, url, params):
"""Prepares the given HTTP URL."""
#: Accept objects that have string representations.
- #: We're unable to blindy call unicode/str functions
+ #: We're unable to blindly call unicode/str functions
#: as this will include the bytestring indicator (b'')
#: on python 3.x.
#: https://github.com/kennethreitz/requests/pull/2238
@@ -385,6 +385,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if isinstance(fragment, str):
fragment = fragment.encode('utf-8')
+ if isinstance(params, (str, bytes)):
+ params = to_native_string(params)
+
enc_params = self._encode_params(params)
if enc_params:
if query:
@@ -434,7 +437,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if files:
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
- if length is not None:
+ if length:
self.headers['Content-Length'] = builtin_str(length)
else:
self.headers['Transfer-Encoding'] = 'chunked'
@@ -631,7 +634,7 @@ class Response(object):
@property
def is_permanent_redirect(self):
- """True if this Response one of the permanant versions of redirect"""
+ """True if this Response one of the permanent versions of redirect"""
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
@property
diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py
index 86bb71d2..e43991a9 100644
--- a/requests/packages/urllib3/__init__.py
+++ b/requests/packages/urllib3/__init__.py
@@ -2,10 +2,8 @@
urllib3 - Thread-safe connection pooling and re-using.
"""
-__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
-__license__ = 'MIT'
-__version__ = '1.12'
-
+from __future__ import absolute_import
+import warnings
from .connectionpool import (
HTTPConnectionPool,
@@ -32,8 +30,30 @@ except ImportError:
def emit(self, record):
pass
+__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
+__license__ = 'MIT'
+__version__ = '1.13.1'
+
+__all__ = (
+ 'HTTPConnectionPool',
+ 'HTTPSConnectionPool',
+ 'PoolManager',
+ 'ProxyManager',
+ 'HTTPResponse',
+ 'Retry',
+ 'Timeout',
+ 'add_stderr_logger',
+ 'connection_from_url',
+ 'disable_warnings',
+ 'encode_multipart_formdata',
+ 'get_host',
+ 'make_headers',
+ 'proxy_from_url',
+)
+
logging.getLogger(__name__).addHandler(NullHandler())
+
def add_stderr_logger(level=logging.DEBUG):
"""
Helper for quickly adding a StreamHandler to the logger. Useful for
@@ -55,7 +75,6 @@ def add_stderr_logger(level=logging.DEBUG):
del NullHandler
-import warnings
# SecurityWarning's always go off by default.
warnings.simplefilter('always', exceptions.SecurityWarning, append=True)
# SubjectAltNameWarning's should go off once per host
@@ -63,6 +82,9 @@ warnings.simplefilter('default', exceptions.SubjectAltNameWarning)
# InsecurePlatformWarning's don't vary between requests, so we keep it default.
warnings.simplefilter('default', exceptions.InsecurePlatformWarning,
append=True)
+# SNIMissingWarnings should go off only once.
+warnings.simplefilter('default', exceptions.SNIMissingWarning)
+
def disable_warnings(category=exceptions.HTTPWarning):
"""
diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py
index b68b9a59..67f3ce99 100644
--- a/requests/packages/urllib3/_collections.py
+++ b/requests/packages/urllib3/_collections.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
from collections import Mapping, MutableMapping
try:
from threading import RLock
@@ -167,7 +168,7 @@ class HTTPHeaderDict(MutableMapping):
def __ne__(self, other):
return not self.__eq__(other)
- if not PY3: # Python 2
+ if not PY3: # Python 2
iterkeys = MutableMapping.iterkeys
itervalues = MutableMapping.itervalues
@@ -234,7 +235,7 @@ class HTTPHeaderDict(MutableMapping):
"""
if len(args) > 1:
raise TypeError("extend() takes at most 1 positional "
- "arguments ({} given)".format(len(args)))
+ "arguments ({0} given)".format(len(args)))
other = args[0] if len(args) >= 1 else ()
if isinstance(other, HTTPHeaderDict):
@@ -304,7 +305,7 @@ class HTTPHeaderDict(MutableMapping):
return list(self.iteritems())
@classmethod
- def from_httplib(cls, message): # Python 2
+ def from_httplib(cls, message): # Python 2
"""Read headers from a Python 2 httplib message object."""
# python2.7 does not expose a proper API for exporting multiheaders
# efficiently. This function re-reads raw lines from the message
diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py
index 3eab1e28..1e4cd417 100644
--- a/requests/packages/urllib3/connection.py
+++ b/requests/packages/urllib3/connection.py
@@ -1,4 +1,6 @@
+from __future__ import absolute_import
import datetime
+import os
import sys
import socket
from socket import error as SocketError, timeout as SocketTimeout
@@ -6,18 +8,13 @@ import warnings
from .packages import six
try: # Python 3
- from http.client import HTTPConnection as _HTTPConnection, HTTPException
+ from http.client import HTTPConnection as _HTTPConnection
+ from http.client import HTTPException # noqa: unused in this module
except ImportError:
- from httplib import HTTPConnection as _HTTPConnection, HTTPException
-
-
-class DummyConnection(object):
- "Used to detect a failed ConnectionCls import."
- pass
-
+ from httplib import HTTPConnection as _HTTPConnection
+ from httplib import HTTPException # noqa: unused in this module
try: # Compiled with SSL?
- HTTPSConnection = DummyConnection
import ssl
BaseSSLError = ssl.SSLError
except (ImportError, AttributeError): # Platform-specific: No SSL.
@@ -61,6 +58,11 @@ port_by_scheme = {
RECENT_DATE = datetime.date(2014, 1, 1)
+class DummyConnection(object):
+ """Used to detect a failed ConnectionCls import."""
+ pass
+
+
class HTTPConnection(_HTTPConnection, object):
"""
Based on httplib.HTTPConnection but provides an extra constructor
@@ -205,10 +207,10 @@ class VerifiedHTTPSConnection(HTTPSConnection):
self.key_file = key_file
self.cert_file = cert_file
self.cert_reqs = cert_reqs
- self.ca_certs = ca_certs
- self.ca_cert_dir = ca_cert_dir
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
+ self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
+ self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
def connect(self):
# Add certificate verification
@@ -263,10 +265,19 @@ class VerifiedHTTPSConnection(HTTPSConnection):
'for details.)'.format(hostname)),
SubjectAltNameWarning
)
- match_hostname(cert, self.assert_hostname or hostname)
- self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED
- or self.assert_fingerprint is not None)
+ # In case the hostname is an IPv6 address, strip the square
+ # brackets from it before using it to validate. This is because
+ # a certificate with an IPv6 address in it won't have square
+ # brackets around that address. Sadly, match_hostname won't do this
+ # for us: it expects the plain host part without any extra work
+ # that might have been done to make it palatable to httplib.
+ asserted_hostname = self.assert_hostname or hostname
+ asserted_hostname = asserted_hostname.strip('[]')
+ match_hostname(cert, asserted_hostname)
+
+ self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or
+ self.assert_fingerprint is not None)
if ssl:
diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py
index b38ac68d..995b4167 100644
--- a/requests/packages/urllib3/connectionpool.py
+++ b/requests/packages/urllib3/connectionpool.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import errno
import logging
import sys
@@ -10,7 +11,8 @@ try: # Python 3
from queue import LifoQueue, Empty, Full
except ImportError:
from Queue import LifoQueue, Empty, Full
- import Queue as _ # Platform-specific: Windows
+ # Queue is imported for side effects on MS Windows
+ import Queue as _unused_module_Queue # noqa: unused
from .exceptions import (
@@ -22,7 +24,6 @@ from .exceptions import (
LocationValueError,
MaxRetryError,
ProxyError,
- ConnectTimeoutError,
ReadTimeoutError,
SSLError,
TimeoutError,
@@ -35,7 +36,7 @@ from .connection import (
port_by_scheme,
DummyConnection,
HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection,
- HTTPException, BaseSSLError, ConnectionError
+ HTTPException, BaseSSLError,
)
from .request import RequestMethods
from .response import HTTPResponse
@@ -54,7 +55,7 @@ log = logging.getLogger(__name__)
_Default = object()
-## Pool objects
+# Pool objects
class ConnectionPool(object):
"""
Base class for all connection pools, such as
@@ -68,8 +69,7 @@ class ConnectionPool(object):
if not host:
raise LocationValueError("No host specified.")
- # httplib doesn't like it when we include brackets in ipv6 addresses
- self.host = host.strip('[]')
+ self.host = host
self.port = port
def __str__(self):
@@ -645,22 +645,24 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
return response
log.info("Redirecting %s -> %s" % (url, redirect_location))
- return self.urlopen(method, redirect_location, body, headers,
- retries=retries, redirect=redirect,
- assert_same_host=assert_same_host,
- timeout=timeout, pool_timeout=pool_timeout,
- release_conn=release_conn, **response_kw)
+ return self.urlopen(
+ method, redirect_location, body, headers,
+ retries=retries, redirect=redirect,
+ assert_same_host=assert_same_host,
+ timeout=timeout, pool_timeout=pool_timeout,
+ release_conn=release_conn, **response_kw)
# Check if we should retry the HTTP response.
if retries.is_forced_retry(method, status_code=response.status):
retries = retries.increment(method, url, response=response, _pool=self)
retries.sleep()
log.info("Forced retry: %s" % url)
- return self.urlopen(method, url, body, headers,
- retries=retries, redirect=redirect,
- assert_same_host=assert_same_host,
- timeout=timeout, pool_timeout=pool_timeout,
- release_conn=release_conn, **response_kw)
+ return self.urlopen(
+ method, url, body, headers,
+ retries=retries, redirect=redirect,
+ assert_same_host=assert_same_host,
+ timeout=timeout, pool_timeout=pool_timeout,
+ release_conn=release_conn, **response_kw)
return response
diff --git a/requests/packages/urllib3/contrib/appengine.py b/requests/packages/urllib3/contrib/appengine.py
index ed9d8b81..884cdb22 100644
--- a/requests/packages/urllib3/contrib/appengine.py
+++ b/requests/packages/urllib3/contrib/appengine.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import logging
import os
import warnings
@@ -60,7 +61,7 @@ class AppEngineManager(RequestMethods):
raise AppEnginePlatformError(
"URLFetch is not available in this environment.")
- if is_prod_appengine_v2():
+ if is_prod_appengine_mvms():
raise AppEnginePlatformError(
"Use normal urllib3.PoolManager instead of AppEngineManager"
"on Managed VMs, as using URLFetch is not necessary in "
@@ -108,14 +109,14 @@ class AppEngineManager(RequestMethods):
raise TimeoutError(self, e)
except urlfetch.InvalidURLError as e:
- if 'too large' in e.message:
+ if 'too large' in str(e):
raise AppEnginePlatformError(
"URLFetch request too large, URLFetch only "
"supports requests up to 10mb in size.", e)
raise ProtocolError(e)
except urlfetch.DownloadError as e:
- if 'Too many redirects' in e.message:
+ if 'Too many redirects' in str(e):
raise MaxRetryError(self, url, reason=e)
raise ProtocolError(e)
@@ -155,7 +156,7 @@ class AppEngineManager(RequestMethods):
def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw):
- if is_prod_appengine_v1():
+ if is_prod_appengine():
# Production GAE handles deflate encoding automatically, but does
# not remove the encoding header.
content_encoding = urlfetch_resp.headers.get('content-encoding')
@@ -176,7 +177,7 @@ class AppEngineManager(RequestMethods):
if timeout is Timeout.DEFAULT_TIMEOUT:
return 5 # 5s is the default timeout for URLFetch.
if isinstance(timeout, Timeout):
- if not timeout.read is timeout.connect:
+ if timeout.read is not timeout.connect:
warnings.warn(
"URLFetch does not support granular timeout settings, "
"reverting to total timeout.", AppEnginePlatformWarning)
@@ -199,12 +200,12 @@ class AppEngineManager(RequestMethods):
def is_appengine():
return (is_local_appengine() or
- is_prod_appengine_v1() or
- is_prod_appengine_v2())
+ is_prod_appengine() or
+ is_prod_appengine_mvms())
def is_appengine_sandbox():
- return is_appengine() and not is_prod_appengine_v2()
+ return is_appengine() and not is_prod_appengine_mvms()
def is_local_appengine():
@@ -212,11 +213,11 @@ def is_local_appengine():
'Development/' in os.environ['SERVER_SOFTWARE'])
-def is_prod_appengine_v1():
+def is_prod_appengine():
return ('APPENGINE_RUNTIME' in os.environ and
'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and
- not is_prod_appengine_v2())
+ not is_prod_appengine_mvms())
-def is_prod_appengine_v2():
+def is_prod_appengine_mvms():
return os.environ.get('GAE_VM', False) == 'true'
diff --git a/requests/packages/urllib3/contrib/ntlmpool.py b/requests/packages/urllib3/contrib/ntlmpool.py
index c6b266f5..c136a238 100644
--- a/requests/packages/urllib3/contrib/ntlmpool.py
+++ b/requests/packages/urllib3/contrib/ntlmpool.py
@@ -3,6 +3,7 @@ NTLM authenticating pool, contributed by erikcederstran
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
"""
+from __future__ import absolute_import
try:
from http.client import HTTPSConnection
diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py
index c20ae46d..5996153a 100644
--- a/requests/packages/urllib3/contrib/pyopenssl.py
+++ b/requests/packages/urllib3/contrib/pyopenssl.py
@@ -43,6 +43,7 @@ Module Variables
.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
'''
+from __future__ import absolute_import
try:
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
@@ -53,7 +54,7 @@ except SyntaxError as e:
import OpenSSL.SSL
from pyasn1.codec.der import decoder as der_decoder
from pyasn1.type import univ, constraint
-from socket import _fileobject, timeout
+from socket import _fileobject, timeout, error as SocketError
import ssl
import select
@@ -71,6 +72,12 @@ _openssl_versions = {
ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
}
+if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'):
+ _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
+
+if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'):
+ _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
+
try:
_openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
except AttributeError:
@@ -79,8 +86,8 @@ except AttributeError:
_openssl_verify = {
ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
- ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
- + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
+ ssl.CERT_REQUIRED:
+ OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
}
DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS
@@ -88,12 +95,6 @@ DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS
# OpenSSL will only write 16K at a time
SSL_WRITE_BLOCKSIZE = 16384
-try:
- _ = memoryview
- has_memoryview = True
-except NameError:
- has_memoryview = False
-
orig_util_HAS_SNI = util.HAS_SNI
orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket
@@ -112,7 +113,7 @@ def extract_from_urllib3():
util.HAS_SNI = orig_util_HAS_SNI
-### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
+# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
class SubjectAltName(BaseSubjectAltName):
'''ASN.1 implementation for subjectAltNames support'''
@@ -123,7 +124,7 @@ class SubjectAltName(BaseSubjectAltName):
constraint.ValueSizeConstraint(1, 1024)
-### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
+# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
def get_subj_alt_name(peer_cert):
# Search through extensions
dns_name = []
@@ -181,7 +182,7 @@ class WrappedSocket(object):
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
return b''
else:
- raise
+ raise SocketError(e)
except OpenSSL.SSL.ZeroReturnError as e:
if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
return b''
@@ -212,12 +213,9 @@ class WrappedSocket(object):
continue
def sendall(self, data):
- if has_memoryview and not isinstance(data, memoryview):
- data = memoryview(data)
-
total_sent = 0
while total_sent < len(data):
- sent = self._send_until_done(data[total_sent:total_sent+SSL_WRITE_BLOCKSIZE])
+ sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE])
total_sent += sent
def shutdown(self):
@@ -226,7 +224,10 @@ class WrappedSocket(object):
def close(self):
if self._makefile_refs < 1:
- return self.connection.close()
+ try:
+ return self.connection.close()
+ except OpenSSL.SSL.Error:
+ return
else:
self._makefile_refs -= 1
diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py
index 9607d65f..8e07eb61 100644
--- a/requests/packages/urllib3/exceptions.py
+++ b/requests/packages/urllib3/exceptions.py
@@ -1,16 +1,17 @@
+from __future__ import absolute_import
+# Base Exceptions
-## Base Exceptions
class HTTPError(Exception):
"Base exception used by this module."
pass
+
class HTTPWarning(Warning):
"Base warning used by this module."
pass
-
class PoolError(HTTPError):
"Base exception for errors caused within a pool."
def __init__(self, pool, message):
@@ -57,7 +58,7 @@ class ProtocolError(HTTPError):
ConnectionError = ProtocolError
-## Leaf Exceptions
+# Leaf Exceptions
class MaxRetryError(RequestError):
"""Raised when the maximum number of retries is exceeded.
@@ -112,10 +113,12 @@ class ConnectTimeoutError(TimeoutError):
"Raised when a socket timeout occurs while connecting to a server"
pass
+
class NewConnectionError(ConnectTimeoutError, PoolError):
"Raised when we fail to establish a new connection. Usually ECONNREFUSED."
pass
+
class EmptyPoolError(PoolError):
"Raised when a pool runs out of connections and no more are allowed."
pass
@@ -172,6 +175,11 @@ class InsecurePlatformWarning(SecurityWarning):
pass
+class SNIMissingWarning(HTTPWarning):
+ "Warned when making a HTTPS request without SNI available."
+ pass
+
+
class ResponseNotChunked(ProtocolError, ValueError):
"Response needs to be chunked in order to read it as chunks."
pass
diff --git a/requests/packages/urllib3/fields.py b/requests/packages/urllib3/fields.py
index c853f8d5..c7d48113 100644
--- a/requests/packages/urllib3/fields.py
+++ b/requests/packages/urllib3/fields.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import email.utils
import mimetypes
diff --git a/requests/packages/urllib3/filepost.py b/requests/packages/urllib3/filepost.py
index 0fbf488d..97a2843c 100644
--- a/requests/packages/urllib3/filepost.py
+++ b/requests/packages/urllib3/filepost.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import codecs
from uuid import uuid4
diff --git a/requests/packages/urllib3/packages/__init__.py b/requests/packages/urllib3/packages/__init__.py
index 37e83515..170e974c 100644
--- a/requests/packages/urllib3/packages/__init__.py
+++ b/requests/packages/urllib3/packages/__init__.py
@@ -2,3 +2,4 @@ from __future__ import absolute_import
from . import ssl_match_hostname
+__all__ = ('ssl_match_hostname', )
diff --git a/requests/packages/urllib3/packages/ssl_match_hostname/.gitignore b/requests/packages/urllib3/packages/ssl_match_hostname/.gitignore
new file mode 100644
index 00000000..0a764a4d
--- /dev/null
+++ b/requests/packages/urllib3/packages/ssl_match_hostname/.gitignore
@@ -0,0 +1 @@
+env
diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py
index 76b6a129..f13e673d 100644
--- a/requests/packages/urllib3/poolmanager.py
+++ b/requests/packages/urllib3/poolmanager.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import logging
try: # Python 3
@@ -25,7 +26,7 @@ pool_classes_by_scheme = {
log = logging.getLogger(__name__)
SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs',
- 'ssl_version')
+ 'ssl_version', 'ca_cert_dir')
class PoolManager(RequestMethods):
diff --git a/requests/packages/urllib3/request.py b/requests/packages/urllib3/request.py
index a1a12bc5..d5aa62d8 100644
--- a/requests/packages/urllib3/request.py
+++ b/requests/packages/urllib3/request.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
try:
from urllib.parse import urlencode
except ImportError:
@@ -133,7 +134,8 @@ class RequestMethods(object):
if fields:
if 'body' in urlopen_kw:
- raise TypeError('request got values for both \'fields\' and \'body\', can only specify one.')
+ raise TypeError(
+ "request got values for both 'fields' and 'body', can only specify one.")
if encode_multipart:
body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary)
diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py
index 788eb6ca..8f2a1b5c 100644
--- a/requests/packages/urllib3/response.py
+++ b/requests/packages/urllib3/response.py
@@ -1,7 +1,9 @@
+from __future__ import absolute_import
from contextlib import contextmanager
import zlib
import io
from socket import timeout as SocketTimeout
+from socket import error as SocketError
from ._collections import HTTPHeaderDict
from .exceptions import (
@@ -130,8 +132,8 @@ class HTTPResponse(io.IOBase):
if "chunked" in encodings:
self.chunked = True
- # We certainly don't want to preload content when the response is chunked.
- if not self.chunked and preload_content and not self._body:
+ # If requested, preload the body.
+ if preload_content and not self._body:
self._body = self.read(decode_content=decode_content)
def get_redirect_location(self):
@@ -194,12 +196,22 @@ class HTTPResponse(io.IOBase):
"Received response with content-encoding: %s, but "
"failed to decode it." % content_encoding, e)
- if flush_decoder and decode_content and self._decoder:
- buf = self._decoder.decompress(binary_type())
- data += buf + self._decoder.flush()
+ if flush_decoder and decode_content:
+ data += self._flush_decoder()
return data
+ def _flush_decoder(self):
+ """
+ Flushes the decoder. Should only be called if the decoder is actually
+ being used.
+ """
+ if self._decoder:
+ buf = self._decoder.decompress(b'')
+ return buf + self._decoder.flush()
+
+ return b''
+
@contextmanager
def _error_catcher(self):
"""
@@ -227,15 +239,22 @@ class HTTPResponse(io.IOBase):
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
- except HTTPException as e:
+ except (HTTPException, SocketError) as e:
# This includes IncompleteRead.
raise ProtocolError('Connection broken: %r' % e, e)
+
except Exception:
# The response may not be closed but we're not going to use it anymore
# so close it now to ensure that the connection is released back to the pool.
if self._original_response and not self._original_response.isclosed():
self._original_response.close()
+ # Closing the response may not actually be sufficient to close
+ # everything, so if we have a hold of the connection close that
+ # too.
+ if self._connection is not None:
+ self._connection.close()
+
raise
finally:
if self._original_response and self._original_response.isclosed():
@@ -301,7 +320,6 @@ class HTTPResponse(io.IOBase):
return data
-
def stream(self, amt=2**16, decode_content=None):
"""
A generator wrapper for the read() method. A call will block until
@@ -340,9 +358,9 @@ class HTTPResponse(io.IOBase):
headers = r.msg
if not isinstance(headers, HTTPHeaderDict):
- if PY3: # Python 3
+ if PY3: # Python 3
headers = HTTPHeaderDict(headers.items())
- else: # Python 2
+ else: # Python 2
headers = HTTPHeaderDict.from_httplib(headers)
# HTTPResponse objects in Python 3 don't have a .strict attribute
@@ -454,7 +472,8 @@ class HTTPResponse(io.IOBase):
self._init_decoder()
# FIXME: Rewrite this method and make it a class with a better structured logic.
if not self.chunked:
- raise ResponseNotChunked("Response is not chunked. "
+ raise ResponseNotChunked(
+ "Response is not chunked. "
"Header 'transfer-encoding: chunked' is missing.")
# Don't bother reading the body of a HEAD request.
@@ -468,8 +487,18 @@ class HTTPResponse(io.IOBase):
if self.chunk_left == 0:
break
chunk = self._handle_chunk(amt)
- yield self._decode(chunk, decode_content=decode_content,
- flush_decoder=True)
+ decoded = self._decode(chunk, decode_content=decode_content,
+ flush_decoder=False)
+ if decoded:
+ yield decoded
+
+ if decode_content:
+ # On CPython and PyPy, we should never need to flush the
+ # decoder. However, on Jython we *might* need to, so
+ # lets defensively do it anyway.
+ decoded = self._flush_decoder()
+ if decoded: # Platform-specific: Jython.
+ yield decoded
# Chunk content ends with \r\n: discard it.
while True:
diff --git a/requests/packages/urllib3/util/__init__.py b/requests/packages/urllib3/util/__init__.py
index 8becc814..c6c6243c 100644
--- a/requests/packages/urllib3/util/__init__.py
+++ b/requests/packages/urllib3/util/__init__.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
# For backwards compatibility, provide imports that used to be here.
from .connection import is_connection_dropped
from .request import make_headers
@@ -22,3 +23,22 @@ from .url import (
split_first,
Url,
)
+
+__all__ = (
+ 'HAS_SNI',
+ 'SSLContext',
+ 'Retry',
+ 'Timeout',
+ 'Url',
+ 'assert_fingerprint',
+ 'current_time',
+ 'is_connection_dropped',
+ 'is_fp_closed',
+ 'get_host',
+ 'parse_url',
+ 'make_headers',
+ 'resolve_cert_reqs',
+ 'resolve_ssl_version',
+ 'split_first',
+ 'ssl_wrap_socket',
+)
diff --git a/requests/packages/urllib3/util/connection.py b/requests/packages/urllib3/util/connection.py
index 4f2f0f18..01a4812f 100644
--- a/requests/packages/urllib3/util/connection.py
+++ b/requests/packages/urllib3/util/connection.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import socket
try:
from select import poll, POLLIN
diff --git a/requests/packages/urllib3/util/request.py b/requests/packages/urllib3/util/request.py
index bc64f6b1..73779315 100644
--- a/requests/packages/urllib3/util/request.py
+++ b/requests/packages/urllib3/util/request.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
from base64 import b64encode
from ..packages.six import b
diff --git a/requests/packages/urllib3/util/response.py b/requests/packages/urllib3/util/response.py
index 2c1de154..bc723272 100644
--- a/requests/packages/urllib3/util/response.py
+++ b/requests/packages/urllib3/util/response.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
from ..packages.six.moves import http_client as httplib
from ..exceptions import HeaderParsingError
@@ -44,7 +45,7 @@ def assert_header_parsing(headers):
# This will fail silently if we pass in the wrong kind of parameter.
# To make debugging easier add an explicit check.
if not isinstance(headers, httplib.HTTPMessage):
- raise TypeError('expected httplib.Message, got {}.'.format(
+ raise TypeError('expected httplib.Message, got {0}.'.format(
type(headers)))
defects = getattr(headers, 'defects', None)
diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py
index 1fb1f23b..03a01249 100644
--- a/requests/packages/urllib3/util/retry.py
+++ b/requests/packages/urllib3/util/retry.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import time
import logging
@@ -126,7 +127,7 @@ class Retry(object):
self.method_whitelist = method_whitelist
self.backoff_factor = backoff_factor
self.raise_on_redirect = raise_on_redirect
- self._observed_errors = _observed_errors # TODO: use .history instead?
+ self._observed_errors = _observed_errors # TODO: use .history instead?
def new(self, **kw):
params = dict(
@@ -206,7 +207,8 @@ class Retry(object):
return min(retry_counts) < 0
- def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None):
+ def increment(self, method=None, url=None, response=None, error=None,
+ _pool=None, _stacktrace=None):
""" Return a new Retry object with incremented retry counters.
:param response: A response object, or None, if the server did not
@@ -274,7 +276,6 @@ class Retry(object):
return new_retry
-
def __repr__(self):
return ('{cls.__name__}(total={self.total}, connect={self.connect}, '
'read={self.read}, redirect={self.redirect})').format(
diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py
index 47b817e3..67f83441 100644
--- a/requests/packages/urllib3/util/ssl_.py
+++ b/requests/packages/urllib3/util/ssl_.py
@@ -1,7 +1,12 @@
+from __future__ import absolute_import
+import errno
+import warnings
+import hmac
+
from binascii import hexlify, unhexlify
from hashlib import md5, sha1, sha256
-from ..exceptions import SSLError, InsecurePlatformWarning
+from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning
SSLContext = None
@@ -15,8 +20,23 @@ HASHFUNC_MAP = {
64: sha256,
}
-import errno
-import warnings
+
+def _const_compare_digest_backport(a, b):
+ """
+ Compare two digests of equal length in constant time.
+
+ The digests must be of type str/bytes.
+ Returns True if the digests match, and False otherwise.
+ """
+ result = abs(len(a) - len(b))
+ for l, r in zip(bytearray(a), bytearray(b)):
+ result |= l ^ r
+ return result == 0
+
+
+_const_compare_digest = getattr(hmac, 'compare_digest',
+ _const_compare_digest_backport)
+
try: # Test for SSL features
import ssl
@@ -134,7 +154,7 @@ def assert_fingerprint(cert, fingerprint):
cert_digest = hashfunc(cert).digest()
- if cert_digest != fingerprint_bytes:
+ if not _const_compare_digest(cert_digest, fingerprint_bytes):
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".'
.format(fingerprint, hexlify(cert_digest)))
@@ -283,4 +303,15 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
context.load_cert_chain(certfile, keyfile)
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
return context.wrap_socket(sock, server_hostname=server_hostname)
+
+ warnings.warn(
+ 'An HTTPS request has been made, but the SNI (Subject Name '
+ 'Indication) extension to TLS is not available on this platform. '
+ 'This may cause the server to present an incorrect TLS '
+ 'certificate, which can cause validation failures. For more '
+ 'information, see '
+ 'https://urllib3.readthedocs.org/en/latest/security.html'
+ '#snimissingwarning.',
+ SNIMissingWarning
+ )
return context.wrap_socket(sock)
diff --git a/requests/packages/urllib3/util/timeout.py b/requests/packages/urllib3/util/timeout.py
index ea7027f3..ff62f476 100644
--- a/requests/packages/urllib3/util/timeout.py
+++ b/requests/packages/urllib3/util/timeout.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
# The default socket timeout, used by httplib to indicate that no timeout was
# specified by the user
from socket import _GLOBAL_DEFAULT_TIMEOUT
@@ -9,6 +10,7 @@ from ..exceptions import TimeoutStateError
# urllib3
_Default = object()
+
def current_time():
"""
Retrieve the current time. This function is mocked out in unit testing.
@@ -226,9 +228,9 @@ class Timeout(object):
has not yet been called on this object.
"""
if (self.total is not None and
- self.total is not self.DEFAULT_TIMEOUT and
- self._read is not None and
- self._read is not self.DEFAULT_TIMEOUT):
+ self.total is not self.DEFAULT_TIMEOUT and
+ self._read is not None and
+ self._read is not self.DEFAULT_TIMEOUT):
# In case the connect timeout has not yet been established.
if self._start_connect is None:
return self._read
diff --git a/requests/packages/urllib3/util/url.py b/requests/packages/urllib3/util/url.py
index e58050cd..e996204a 100644
--- a/requests/packages/urllib3/util/url.py
+++ b/requests/packages/urllib3/util/url.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
from collections import namedtuple
from ..exceptions import LocationParseError
@@ -85,6 +86,7 @@ class Url(namedtuple('Url', url_attrs)):
def __str__(self):
return self.url
+
def split_first(s, delims):
"""
Given a string and an iterable of delimiters, split on the first found
@@ -115,7 +117,7 @@ def split_first(s, delims):
if min_idx is None or min_idx < 0:
return s, '', None
- return s[:min_idx], s[min_idx+1:], min_delim
+ return s[:min_idx], s[min_idx + 1:], min_delim
def parse_url(url):
@@ -206,6 +208,7 @@ def parse_url(url):
return Url(scheme, auth, host, port, path, query, fragment)
+
def get_host(url):
"""
Deprecated. Use :func:`.parse_url` instead.
diff --git a/requests/sessions.py b/requests/sessions.py
index 12879a5b..639668f2 100644
--- a/requests/sessions.py
+++ b/requests/sessions.py
@@ -110,7 +110,7 @@ class SessionRedirectMixin(object):
resp.raw.read(decode_content=False)
if i >= self.max_redirects:
- raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects)
+ raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
# Release the connection back into the pool.
resp.close()
@@ -325,7 +325,7 @@ class Session(SessionRedirectMixin):
#: limit, a :class:`TooManyRedirects` exception is raised.
self.max_redirects = DEFAULT_REDIRECT_LIMIT
- #: Trust environement settings for proxy configuration, default
+ #: Trust environment settings for proxy configuration, default
#: authentication and similar.
self.trust_env = True
@@ -433,8 +433,8 @@ class Session(SessionRedirectMixin):
hostname to the URL of the proxy.
:param stream: (optional) whether to immediately download the response
content. Defaults to ``False``.
- :param verify: (optional) if ``True``, the SSL cert will be verified.
- A CA_BUNDLE path can also be provided.
+ :param verify: (optional) whether the SSL cert will be verified.
+ A CA_BUNDLE path can also be provided. Defaults to ``True``.
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
"""
@@ -553,19 +553,21 @@ class Session(SessionRedirectMixin):
if not isinstance(request, PreparedRequest):
raise ValueError('You can only send PreparedRequests.')
- checked_urls = set()
- while request.url in self.redirect_cache:
- checked_urls.add(request.url)
- new_url = self.redirect_cache.get(request.url)
- if new_url in checked_urls:
- break
- request.url = new_url
-
# Set up variables needed for resolve_redirects and dispatching of hooks
allow_redirects = kwargs.pop('allow_redirects', True)
stream = kwargs.get('stream')
hooks = request.hooks
+ # Resolve URL in redirect cache, if available.
+ if allow_redirects:
+ checked_urls = set()
+ while request.url in self.redirect_cache:
+ checked_urls.add(request.url)
+ new_url = self.redirect_cache.get(request.url)
+ if new_url in checked_urls:
+ break
+ request.url = new_url
+
# Get the appropriate adapter to use
adapter = self.get_adapter(url=request.url)
@@ -634,7 +636,7 @@ class Session(SessionRedirectMixin):
'cert': cert}
def get_adapter(self, url):
- """Returns the appropriate connnection adapter for the given URL."""
+ """Returns the appropriate connection adapter for the given URL."""
for (prefix, adapter) in self.adapters.items():
if url.lower().startswith(prefix):
diff --git a/requests/utils.py b/requests/utils.py
index 132cd2b5..c5c3fd01 100644
--- a/requests/utils.py
+++ b/requests/utils.py
@@ -115,8 +115,12 @@ def get_netrc_auth(url, raise_errors=False):
ri = urlparse(url)
- # Strip port numbers from netloc
- host = ri.netloc.split(':')[0]
+ # Strip port numbers from netloc. This weird `if...encode`` dance is
+ # used for Python 3.2, which doesn't support unicode literals.
+ splitstr = b':'
+ if isinstance(url, str):
+ splitstr = splitstr.decode('ascii')
+ host = ri.netloc.split(splitstr)[0]
try:
_netrc = netrc(netrc_path).authenticators(host)
diff --git a/requirements-to-freeze.txt b/requirements-to-freeze.txt
new file mode 100644
index 00000000..e8b9e354
--- /dev/null
+++ b/requirements-to-freeze.txt
@@ -0,0 +1,4 @@
+pytest
+pytest-cov
+pytest-httpbin
+sphinx
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index ad5da761..3d29de0c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,22 @@
-py==1.4.30
-pytest==2.8.1
-pytest-cov==2.1.0
-pytest-httpbin==0.0.7
-httpbin==0.4.0
-wheel
+alabaster==0.7.7
+Babel==2.2.0
+coverage==4.0.3
+decorator==4.0.9
+docutils==0.12
+Flask==0.10.1
+httpbin==0.4.1
+itsdangerous==0.24
+Jinja2==2.8
+MarkupSafe==0.23
+py==1.4.31
+Pygments==2.1.1
+pytest==2.8.7
+pytest-cov==2.2.1
+pytest-httpbin==0.2.0
+pytz==2015.7
+six==1.10.0
+snowballstemmer==1.2.1
+Sphinx==1.3.5
+sphinx-rtd-theme==0.1.9
+Werkzeug==0.11.4
+wheel==0.29.0
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 5e409001..00000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[wheel]
-universal = 1
diff --git a/setup.py b/setup.py
index b7ed12ba..5991f93e 100755
--- a/setup.py
+++ b/setup.py
@@ -6,10 +6,28 @@ import sys
from codecs import open
-try:
- from setuptools import setup
-except ImportError:
- from distutils.core import setup
+from setuptools import setup
+from setuptools.command.test import test as TestCommand
+
+
+class PyTest(TestCommand):
+ user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")]
+
+ def initialize_options(self):
+ TestCommand.initialize_options(self)
+ self.pytest_args = []
+
+ def finalize_options(self):
+ TestCommand.finalize_options(self)
+ self.test_args = []
+ self.test_suite = True
+
+ def run_tests(self):
+ import pytest
+
+ errno = pytest.main(self.pytest_args)
+ sys.exit(errno)
+
if sys.argv[-1] == 'publish':
os.system('python setup.py sdist upload')
@@ -27,8 +45,8 @@ packages = [
]
requires = []
+test_requirements = ['pytest>=2.8.0', 'pytest-httpbin==0.0.7', 'pytest-cov']
-version = ''
with open('requests/__init__.py', 'r') as fd:
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
fd.read(), re.MULTILINE).group(1)
@@ -62,12 +80,17 @@ setup(
'Natural Language :: English',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy'
),
+ cmdclass={'test': PyTest},
+ tests_require=test_requirements,
extras_require={
'security': ['pyOpenSSL>=0.13', 'ndg-httpsclient', 'pyasn1'],
},
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 00000000..57d631c3
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+# coding: utf-8
diff --git a/tests/compat.py b/tests/compat.py
new file mode 100644
index 00000000..a26bd9f4
--- /dev/null
+++ b/tests/compat.py
@@ -0,0 +1,20 @@
+# coding: utf-8
+from requests.compat import is_py3
+
+
+try:
+ import StringIO
+except ImportError:
+ import io as StringIO
+
+try:
+ from cStringIO import StringIO as cStringIO
+except ImportError:
+ cStringIO = None
+
+if is_py3:
+ def u(s):
+ return s
+else:
+ def u(s):
+ return s.decode('unicode-escape')
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..af20e54d
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,23 @@
+# coding: utf-8
+import pytest
+from requests.compat import urljoin
+
+
+def prepare_url(value):
+ # Issue #1483: Make sure the URL always has a trailing slash
+ httpbin_url = value.url.rstrip('/') + '/'
+
+ def inner(*suffix):
+ return urljoin(httpbin_url, '/'.join(suffix))
+
+ return inner
+
+
+@pytest.fixture
+def httpbin(httpbin):
+ return prepare_url(httpbin)
+
+
+@pytest.fixture
+def httpbin_secure(httpbin_secure):
+ return prepare_url(httpbin_secure)
diff --git a/test_requests.py b/tests/test_requests.py
similarity index 63%
rename from test_requests.py
rename to tests/test_requests.py
index 6c7b295b..efb93d3a 100755
--- a/test_requests.py
+++ b/tests/test_requests.py
@@ -7,7 +7,6 @@ from __future__ import division
import json
import os
import pickle
-import unittest
import collections
import contextlib
@@ -17,78 +16,25 @@ import pytest
from requests.adapters import HTTPAdapter
from requests.auth import HTTPDigestAuth, _basic_auth_str
from requests.compat import (
- Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3,
- builtin_str, OrderedDict
- )
+ Morsel, cookielib, getproxies, str, urlparse,
+ builtin_str, OrderedDict)
from requests.cookies import cookiejar_from_dict, morsel_to_cookie
-from requests.exceptions import (ConnectionError, ConnectTimeout,
- InvalidSchema, InvalidURL, MissingSchema,
- ReadTimeout, Timeout, RetryError)
+from requests.exceptions import (
+ ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL,
+ MissingSchema, ReadTimeout, Timeout, RetryError, TooManyRedirects)
from requests.models import PreparedRequest
from requests.structures import CaseInsensitiveDict
from requests.sessions import SessionRedirectMixin
from requests.models import urlencode
from requests.hooks import default_hooks
-from testserver.server import Server
-import socket
-import threading
-import time
-
-try:
- import StringIO
-except ImportError:
- import io as StringIO
-
-try:
- from multiprocessing.pool import ThreadPool
-except ImportError:
- ThreadPool = None
-
-if is_py3:
- def u(s):
- return s
-else:
- def u(s):
- return s.decode('unicode-escape')
-
-
-@pytest.fixture
-def httpbin(httpbin):
- # Issue #1483: Make sure the URL always has a trailing slash
- httpbin_url = httpbin.url.rstrip('/') + '/'
-
- def inner(*suffix):
- return urljoin(httpbin_url, '/'.join(suffix))
-
- return inner
-
-
-@pytest.fixture
-def httpsbin_url(httpbin_secure):
- # Issue #1483: Make sure the URL always has a trailing slash
- httpbin_url = httpbin_secure.url.rstrip('/') + '/'
-
- def inner(*suffix):
- return urljoin(httpbin_url, '/'.join(suffix))
-
- return inner
-
+from .compat import StringIO, u
# Requests to this URL should always fail with a connection timeout (nothing
# listening on that port)
-TARPIT = "http://10.255.255.1"
+TARPIT = 'http://10.255.255.1'
-class TestRequests(object):
- _multiprocess_can_split_ = True
-
- def setUp(self):
- """Create simple data set with headers."""
- pass
-
- def tearDown(self):
- """Teardown."""
- pass
+class TestRequests:
def test_entry_points(self):
@@ -101,17 +47,17 @@ class TestRequests(object):
requests.patch
requests.post
- def test_invalid_url(self):
- with pytest.raises(MissingSchema):
- requests.get('hiwpefhipowhefopw')
- with pytest.raises(InvalidSchema):
- requests.get('localhost:3128')
- with pytest.raises(InvalidSchema):
- requests.get('localhost.localdomain:3128/')
- with pytest.raises(InvalidSchema):
- requests.get('10.122.1.1:3128/')
- with pytest.raises(InvalidURL):
- requests.get('http://')
+ @pytest.mark.parametrize(
+ 'exception, url', (
+ (MissingSchema, 'hiwpefhipowhefopw'),
+ (InvalidSchema, 'localhost:3128'),
+ (InvalidSchema, 'localhost.localdomain:3128/'),
+ (InvalidSchema, '10.122.1.1:3128/'),
+ (InvalidURL, 'http://'),
+ ))
+ def test_invalid_url(self, exception, url):
+ with pytest.raises(exception):
+ requests.get(url)
def test_basic_building(self):
req = requests.Request()
@@ -122,11 +68,10 @@ class TestRequests(object):
assert pr.url == req.url
assert pr.body == 'life=42'
- def test_no_content_length(self, httpbin):
- get_req = requests.Request('GET', httpbin('get')).prepare()
- assert 'Content-Length' not in get_req.headers
- head_req = requests.Request('HEAD', httpbin('head')).prepare()
- assert 'Content-Length' not in head_req.headers
+ @pytest.mark.parametrize('method', ('GET', 'HEAD'))
+ def test_no_content_length(self, httpbin, method):
+ req = requests.Request(method, httpbin(method.lower())).prepare()
+ assert 'Content-Length' not in req.headers
def test_override_content_length(self, httpbin):
headers = {
@@ -141,13 +86,14 @@ class TestRequests(object):
assert request.path_url == '/get/test%20case'
- def test_params_are_added_before_fragment(self):
- request = requests.Request('GET',
- "http://example.com/path#fragment", params={"a": "b"}).prepare()
- assert request.url == "http://example.com/path?a=b#fragment"
- request = requests.Request('GET',
- "http://example.com/path?key=value#fragment", params={"a": "b"}).prepare()
- assert request.url == "http://example.com/path?key=value&a=b#fragment"
+ @pytest.mark.parametrize(
+ 'url, expected', (
+ ('http://example.com/path#fragment', 'http://example.com/path?a=b#fragment'),
+ ('http://example.com/path?key=value#fragment', 'http://example.com/path?key=value&a=b#fragment')
+ ))
+ def test_params_are_added_before_fragment(self, url, expected):
+ request = requests.Request('GET', url, params={"a": "b"}).prepare()
+ assert request.url == expected
def test_params_original_order_is_preserved_by_default(self):
param_ordered_dict = OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1)))
@@ -161,16 +107,20 @@ class TestRequests(object):
params=b'test=foo').prepare()
assert request.url == 'http://example.com/?test=foo'
- def test_mixed_case_scheme_acceptable(self, httpbin):
+ def test_binary_put(self):
+ request = requests.Request('PUT', 'http://example.com',
+ data=u"ööö".encode("utf-8")).prepare()
+ assert isinstance(request.body, bytes)
+
+ @pytest.mark.parametrize('scheme', ('http://', 'HTTP://', 'hTTp://', 'HttP://'))
+ def test_mixed_case_scheme_acceptable(self, httpbin, scheme):
s = requests.Session()
s.proxies = getproxies()
parts = urlparse(httpbin('get'))
- schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://']
- for scheme in schemes:
- url = scheme + parts.netloc + parts.path
- r = requests.Request('GET', url)
- r = s.send(r.prepare())
- assert r.status_code == 200, 'failed for scheme {0}'.format(scheme)
+ url = scheme + parts.netloc + parts.path
+ r = requests.Request('GET', url)
+ r = s.send(r.prepare())
+ assert r.status_code == 200, 'failed for scheme {0}'.format(scheme)
def test_HTTP_200_OK_GET_ALTERNATIVE(self, httpbin):
r = requests.Request('GET', httpbin('get'))
@@ -187,6 +137,30 @@ class TestRequests(object):
assert r.history[0].status_code == 302
assert r.history[0].is_redirect
+ def test_HTTP_302_TOO_MANY_REDIRECTS(self, httpbin):
+ try:
+ requests.get(httpbin('relative-redirect', '50'))
+ except TooManyRedirects as e:
+ url = httpbin('relative-redirect', '20')
+ assert e.request.url == url
+ assert e.response.url == url
+ assert len(e.response.history) == 30
+ else:
+ pytest.fail('Expected redirect to raise TooManyRedirects but it did not')
+
+ def test_HTTP_302_TOO_MANY_REDIRECTS_WITH_PARAMS(self, httpbin):
+ s = requests.session()
+ s.max_redirects = 5
+ try:
+ s.get(httpbin('relative-redirect', '50'))
+ except TooManyRedirects as e:
+ url = httpbin('relative-redirect', '45')
+ assert e.request.url == url
+ assert e.response.url == url
+ assert len(e.response.history) == 5
+ else:
+ pytest.fail('Expected custom max number of redirects to be respected but was not')
+
# def test_HTTP_302_ALLOW_REDIRECT_POST(self):
# r = requests.post(httpbin('status', '302'), data={'some': 'data'})
# self.assertEqual(r.status_code, 200)
@@ -281,9 +255,7 @@ class TestRequests(object):
assert urls == req_urls
def test_history_is_always_a_list(self, httpbin):
- """
- Show that even with redirects, Response.history is always a list.
- """
+ """Show that even with redirects, Response.history is always a list."""
resp = requests.get(httpbin('get'))
assert isinstance(resp.history, list)
resp = requests.get(httpbin('redirect/1'))
@@ -298,21 +270,13 @@ class TestRequests(object):
prep = ses.prepare_request(req)
assert 'Accept-Encoding' not in prep.headers
- def test_user_agent_transfers(self, httpbin):
+ @pytest.mark.parametrize('key', ('User-agent', 'user-agent'))
+ def test_user_agent_transfers(self, httpbin, key):
- heads = {
- 'User-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)'
- }
+ heads = {key: 'Mozilla/5.0 (github.com/kennethreitz/requests)'}
r = requests.get(httpbin('user-agent'), headers=heads)
- assert heads['User-agent'] in r.text
-
- heads = {
- 'user-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)'
- }
-
- r = requests.get(httpbin('user-agent'), headers=heads)
- assert heads['user-agent'] in r.text
+ assert heads[key] in r.text
def test_HTTP_200_OK_HEAD(self, httpbin):
r = requests.head(httpbin('get'))
@@ -337,20 +301,18 @@ class TestRequests(object):
r = s.get(url)
assert r.status_code == 200
- def test_connection_error_invalid_domain(self):
- """Connecting to an unknown domain should raise a ConnectionError"""
- with pytest.raises(ConnectionError):
- requests.get("http://doesnotexist.google.com")
-
- def test_connection_error_invalid_port(self):
- """Connecting to an invalid port should raise a ConnectionError"""
- with pytest.raises(ConnectionError):
- requests.get("http://localhost:1", timeout=1)
-
- def test_LocationParseError(self):
- """Inputing a URL that cannot be parsed should raise an InvalidURL error"""
- with pytest.raises(InvalidURL):
- requests.get("http://fe80::5054:ff:fe5a:fc0")
+ @pytest.mark.parametrize(
+ 'url, exception', (
+ # Connecting to an unknown domain should raise a ConnectionError
+ ('http://doesnotexist.google.com', ConnectionError),
+ # Connecting to an invalid port should raise a ConnectionError
+ ('http://localhost:1', ConnectionError),
+ # Inputing a URL that cannot be parsed should raise an InvalidURL error
+ ('http://fe80::5054:ff:fe5a:fc0', InvalidURL)
+ ))
+ def test_errors(self, url, exception):
+ with pytest.raises(exception):
+ requests.get(url, timeout=1)
def test_basicauth_with_netrc(self, httpbin):
auth = ('user', 'pass')
@@ -455,7 +417,7 @@ class TestRequests(object):
def test_POSTBIN_GET_POST_FILES(self, httpbin):
url = httpbin('post')
- post1 = requests.post(url).raise_for_status()
+ requests.post(url).raise_for_status()
post1 = requests.post(url, data={'some': 'data'})
assert post1.status_code == 200
@@ -473,7 +435,7 @@ class TestRequests(object):
def test_POSTBIN_GET_POST_FILES_WITH_DATA(self, httpbin):
url = httpbin('post')
- post1 = requests.post(url).raise_for_status()
+ requests.post(url).raise_for_status()
post1 = requests.post(url, data={'some': 'data'})
assert post1.status_code == 200
@@ -511,13 +473,16 @@ class TestRequests(object):
r = requests.get(httpbin('gzip'))
r.content.decode('ascii')
- def test_unicode_get(self, httpbin):
- url = httpbin('/get')
- requests.get(url, params={'foo': 'føø'})
- requests.get(url, params={'føø': 'føø'})
- requests.get(url, params={'føø': 'føø'})
- requests.get(url, params={'foo': 'foo'})
- requests.get(httpbin('ø'), params={'foo': 'foo'})
+ @pytest.mark.parametrize(
+ 'url, params', (
+ ('/get', {'foo': 'føø'}),
+ ('/get', {'føø': 'føø'}),
+ ('/get', {'føø': 'føø'}),
+ ('/get', {'foo': 'foo'}),
+ ('ø', {'foo': 'foo'}),
+ ))
+ def test_unicode_get(self, httpbin, url, params):
+ requests.get(httpbin(url), params=params)
def test_unicode_header_name(self, httpbin):
requests.put(
@@ -525,8 +490,8 @@ class TestRequests(object):
headers={str('Content-Type'): 'application/octet-stream'},
data='\xff') # compat.str is unicode.
- def test_pyopenssl_redirect(self, httpsbin_url, httpbin_ca_bundle):
- requests.get(httpsbin_url('status', '301'), verify=httpbin_ca_bundle)
+ def test_pyopenssl_redirect(self, httpbin_secure, httpbin_ca_bundle):
+ requests.get(httpbin_secure('status', '301'), verify=httpbin_ca_bundle)
def test_urlencoded_get_query_multivalued_param(self, httpbin):
@@ -541,51 +506,42 @@ class TestRequests(object):
files={'file': ('test_requests.py', open(__file__, 'rb'))})
assert r.status_code == 200
- def test_unicode_multipart_post(self, httpbin):
+ @pytest.mark.parametrize(
+ 'data', (
+ {'stuff': u('ëlïxr')},
+ {'stuff': u('ëlïxr').encode('utf-8')},
+ {'stuff': 'elixr'},
+ {'stuff': 'elixr'.encode('utf-8')},
+ ))
+ def test_unicode_multipart_post(self, httpbin, data):
r = requests.post(httpbin('post'),
- data={'stuff': u('ëlïxr')},
- files={'file': ('test_requests.py', open(__file__, 'rb'))})
- assert r.status_code == 200
-
- r = requests.post(httpbin('post'),
- data={'stuff': u('ëlïxr').encode('utf-8')},
- files={'file': ('test_requests.py', open(__file__, 'rb'))})
- assert r.status_code == 200
-
- r = requests.post(httpbin('post'),
- data={'stuff': 'elixr'},
- files={'file': ('test_requests.py', open(__file__, 'rb'))})
- assert r.status_code == 200
-
- r = requests.post(httpbin('post'),
- data={'stuff': 'elixr'.encode('utf-8')},
+ data=data,
files={'file': ('test_requests.py', open(__file__, 'rb'))})
assert r.status_code == 200
def test_unicode_multipart_post_fieldnames(self, httpbin):
filename = os.path.splitext(__file__)[0] + '.py'
- r = requests.Request(method='POST',
- url=httpbin('post'),
- data={'stuff'.encode('utf-8'): 'elixr'},
- files={'file': ('test_requests.py',
- open(filename, 'rb'))})
+ r = requests.Request(
+ method='POST', url=httpbin('post'),
+ data={'stuff'.encode('utf-8'): 'elixr'},
+ files={'file': ('test_requests.py', open(filename, 'rb'))})
prep = r.prepare()
assert b'name="stuff"' in prep.body
assert b'name="b\'stuff\'"' not in prep.body
def test_unicode_method_name(self, httpbin):
- files = {'file': open('test_requests.py', 'rb')}
+ files = {'file': open(__file__, 'rb')}
r = requests.request(
method=u('POST'), url=httpbin('post'), files=files)
assert r.status_code == 200
def test_unicode_method_name_with_request_object(self, httpbin):
- files = {'file': open('test_requests.py', 'rb')}
+ files = {'file': open(__file__, 'rb')}
s = requests.Session()
- req = requests.Request(u("POST"), httpbin('post'), files=files)
+ req = requests.Request(u('POST'), httpbin('post'), files=files)
prep = s.prepare_request(req)
assert isinstance(prep.method, builtin_str)
- assert prep.method == "POST"
+ assert prep.method == 'POST'
resp = s.send(prep)
assert resp.status_code == 200
@@ -594,9 +550,10 @@ class TestRequests(object):
r = requests.post(
httpbin('post'),
data={'stuff': json.dumps({'a': 123})},
- files={'file1': ('test_requests.py', open(__file__, 'rb')),
- 'file2': ('test_requests', open(__file__, 'rb'),
- 'text/py-content-type')})
+ files={
+ 'file1': ('test_requests.py', open(__file__, 'rb')),
+ 'file2': ('test_requests', open(__file__, 'rb'),
+ 'text/py-content-type')})
assert r.status_code == 200
assert b"text/py-content-type" in r.request.body
@@ -605,7 +562,10 @@ class TestRequests(object):
assert resp is not None
assert kwargs != {}
- requests.Request('GET', httpbin(), hooks={'response': hook})
+ s = requests.Session()
+ r = requests.Request('GET', httpbin(), hooks={'response': hook})
+ prep = s.prepare_request(r)
+ s.send(prep)
def test_session_hooks_are_used_with_no_request_hooks(self, httpbin):
hook = lambda x, *args, **kwargs: x
@@ -616,7 +576,7 @@ class TestRequests(object):
assert prep.hooks['response'] != []
assert prep.hooks['response'] == [hook]
- def test_session_hooks_are_overriden_by_request_hooks(self, httpbin):
+ def test_session_hooks_are_overridden_by_request_hooks(self, httpbin):
hook1 = lambda x, *args, **kwargs: x
hook2 = lambda x, *args, **kwargs: x
assert hook1 is not hook2
@@ -844,26 +804,6 @@ class TestRequests(object):
assert r.request.url == pr.request.url
assert r.request.headers == pr.request.headers
- def test_get_auth_from_url(self):
- url = 'http://user:pass@complex.url.com/path?query=yes'
- assert ('user', 'pass') == requests.utils.get_auth_from_url(url)
-
- def test_get_auth_from_url_encoded_spaces(self):
- url = 'http://user:pass%20pass@complex.url.com/path?query=yes'
- assert ('user', 'pass pass') == requests.utils.get_auth_from_url(url)
-
- def test_get_auth_from_url_not_encoded_spaces(self):
- url = 'http://user:pass pass@complex.url.com/path?query=yes'
- assert ('user', 'pass pass') == requests.utils.get_auth_from_url(url)
-
- def test_get_auth_from_url_percent_chars(self):
- url = 'http://user%25user:pass@complex.url.com/path?query=yes'
- assert ('user%user', 'pass') == requests.utils.get_auth_from_url(url)
-
- def test_get_auth_from_url_encoded_hashes(self):
- url = 'http://user:pass%23pass@complex.url.com/path?query=yes'
- assert ('user', 'pass#pass') == requests.utils.get_auth_from_url(url)
-
def test_cannot_send_unprepared_requests(self, httpbin):
r = requests.Request(url=httpbin())
with pytest.raises(ValueError):
@@ -979,23 +919,10 @@ class TestRequests(object):
assert 'unicode' in p.headers.keys()
assert 'byte' in p.headers.keys()
- def test_can_send_nonstring_objects_with_files(self, httpbin):
- data = {'a': 0.0}
- files = {'b': 'foo'}
- r = requests.Request('POST', httpbin('post'), data=data, files=files)
- p = r.prepare()
-
- assert 'multipart/form-data' in p.headers['Content-Type']
-
- def test_can_send_bytes_bytearray_objects_with_files(self, httpbin):
- # Test bytes:
+ @pytest.mark.parametrize('files', ('foo', b'foo', bytearray(b'foo')))
+ def test_can_send_objects_with_files(self, httpbin, files):
data = {'a': 'this is a string'}
- files = {'b': b'foo'}
- r = requests.Request('POST', httpbin('post'), data=data, files=files)
- p = r.prepare()
- assert 'multipart/form-data' in p.headers['Content-Type']
- # Test bytearrays:
- files = {'b': bytearray(b'foo')}
+ files = {'b': files}
r = requests.Request('POST', httpbin('post'), data=data, files=files)
p = r.prepare()
assert 'multipart/form-data' in p.headers['Content-Type']
@@ -1093,7 +1020,7 @@ class TestRequests(object):
i = 0
for item in r.history:
assert item.history == total[0:i]
- i = i + 1
+ i += 1
def test_json_param_post_content_type_works(self, httpbin):
r = requests.post(
@@ -1138,79 +1065,15 @@ class TestRequests(object):
assert len(list(r.iter_lines())) == 3
-class TestContentEncodingDetection(unittest.TestCase):
+class TestCaseInsensitiveDict:
- def test_none(self):
- encodings = requests.utils.get_encodings_from_content('')
- assert not len(encodings)
-
- def test_html_charset(self):
- """HTML5 meta charset attribute"""
- content = ' '
- encodings = requests.utils.get_encodings_from_content(content)
- assert len(encodings) == 1
- assert encodings[0] == 'UTF-8'
-
- def test_html4_pragma(self):
- """HTML4 pragma directive"""
- content = ' '
- encodings = requests.utils.get_encodings_from_content(content)
- assert len(encodings) == 1
- assert encodings[0] == 'UTF-8'
-
- def test_xhtml_pragma(self):
- """XHTML 1.x served with text/html MIME type"""
- content = ' '
- encodings = requests.utils.get_encodings_from_content(content)
- assert len(encodings) == 1
- assert encodings[0] == 'UTF-8'
-
- def test_xml(self):
- """XHTML 1.x served as XML"""
- content = ''
- encodings = requests.utils.get_encodings_from_content(content)
- assert len(encodings) == 1
- assert encodings[0] == 'UTF-8'
-
- def test_precedence(self):
- content = '''
-
-
-
- '''.strip()
- encodings = requests.utils.get_encodings_from_content(content)
- assert encodings == ['HTML5', 'HTML4', 'XML']
-
- def test_chunked_upload(self):
- """can safely send generators"""
- block_server = threading.Event()
- server = Server.basic_response_server(wait_to_close_event=block_server)
- data = (i for i in [b'a', b'b', b'c'])
-
- with server as (host, port):
- url = 'http://{}:{}/'.format(host, port)
- r = requests.post(url, data=data, stream=True)
- block_server.set() # release server block
-
- assert r.status_code == 200
- assert r.request.headers['Transfer-Encoding'] == 'chunked'
-
-class TestCaseInsensitiveDict(unittest.TestCase):
-
- def test_mapping_init(self):
- cid = CaseInsensitiveDict({'Foo': 'foo', 'BAr': 'bar'})
- assert len(cid) == 2
- assert 'foo' in cid
- assert 'bar' in cid
-
- def test_iterable_init(self):
- cid = CaseInsensitiveDict([('Foo', 'foo'), ('BAr', 'bar')])
- assert len(cid) == 2
- assert 'foo' in cid
- assert 'bar' in cid
-
- def test_kwargs_init(self):
- cid = CaseInsensitiveDict(FOO='foo', BAr='bar')
+ @pytest.mark.parametrize(
+ 'cid', (
+ CaseInsensitiveDict({'Foo': 'foo', 'BAr': 'bar'}),
+ CaseInsensitiveDict([('Foo', 'foo'), ('BAr', 'bar')]),
+ CaseInsensitiveDict(FOO='foo', BAr='bar'),
+ ))
+ def test_init(self, cid):
assert len(cid) == 2
assert 'foo' in cid
assert 'bar' in cid
@@ -1344,148 +1207,7 @@ class TestCaseInsensitiveDict(unittest.TestCase):
assert cid != cid_copy
-class UtilsTestCase(unittest.TestCase):
-
- def test_super_len_io_streams(self):
- """ Ensures that we properly deal with different kinds of IO streams. """
- # uses StringIO or io.StringIO (see import above)
- from io import BytesIO
- from requests.utils import super_len
-
- assert super_len(StringIO.StringIO()) == 0
- assert super_len(
- StringIO.StringIO('with so much drama in the LBC')) == 29
-
- assert super_len(BytesIO()) == 0
- assert super_len(
- BytesIO(b"it's kinda hard bein' snoop d-o-double-g")) == 40
-
- try:
- import cStringIO
- except ImportError:
- pass
- else:
- assert super_len(
- cStringIO.StringIO('but some how, some way...')) == 25
-
- def test_super_len_correctly_calculates_len_of_partially_read_file(self):
- """Ensure that we handle partially consumed file like objects."""
- from requests.utils import super_len
- s = StringIO.StringIO()
- s.write('foobarbogus')
- assert super_len(s) == 0
-
- def test_get_environ_proxies_ip_ranges(self):
- """Ensures that IP addresses are correctly matches with ranges
- in no_proxy variable."""
- from requests.utils import get_environ_proxies
- os.environ['no_proxy'] = "192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1"
- assert get_environ_proxies('http://192.168.0.1:5000/') == {}
- assert get_environ_proxies('http://192.168.0.1/') == {}
- assert get_environ_proxies('http://172.16.1.1/') == {}
- assert get_environ_proxies('http://172.16.1.1:5000/') == {}
- assert get_environ_proxies('http://192.168.1.1:5000/') != {}
- assert get_environ_proxies('http://192.168.1.1/') != {}
-
- def test_get_environ_proxies(self):
- """Ensures that IP addresses are correctly matches with ranges
- in no_proxy variable."""
- from requests.utils import get_environ_proxies
- os.environ['no_proxy'] = "127.0.0.1,localhost.localdomain,192.168.0.0/24,172.16.1.1"
- assert get_environ_proxies(
- 'http://localhost.localdomain:5000/v1.0/') == {}
- assert get_environ_proxies('http://www.requests.com/') != {}
-
- def test_select_proxies(self):
- """Make sure we can select per-host proxies correctly."""
- from requests.utils import select_proxy
- proxies = {'http': 'http://http.proxy',
- 'http://some.host': 'http://some.host.proxy'}
- assert select_proxy('hTTp://u:p@Some.Host/path', proxies) == 'http://some.host.proxy'
- assert select_proxy('hTTp://u:p@Other.Host/path', proxies) == 'http://http.proxy'
- assert select_proxy('hTTps://Other.Host', proxies) is None
-
- def test_guess_filename_when_int(self):
- from requests.utils import guess_filename
- assert None is guess_filename(1)
-
- def test_guess_filename_when_filename_is_an_int(self):
- from requests.utils import guess_filename
- fake = type('Fake', (object,), {'name': 1})()
- assert None is guess_filename(fake)
-
- def test_guess_filename_with_file_like_obj(self):
- from requests.utils import guess_filename
- from requests import compat
- fake = type('Fake', (object,), {'name': b'value'})()
- guessed_name = guess_filename(fake)
- assert b'value' == guessed_name
- assert isinstance(guessed_name, compat.bytes)
-
- def test_guess_filename_with_unicode_name(self):
- from requests.utils import guess_filename
- from requests import compat
- filename = b'value'.decode('utf-8')
- fake = type('Fake', (object,), {'name': filename})()
- guessed_name = guess_filename(fake)
- assert filename == guessed_name
- assert isinstance(guessed_name, compat.str)
-
- def test_is_ipv4_address(self):
- from requests.utils import is_ipv4_address
- assert is_ipv4_address('8.8.8.8')
- assert not is_ipv4_address('8.8.8.8.8')
- assert not is_ipv4_address('localhost.localdomain')
-
- def test_is_valid_cidr(self):
- from requests.utils import is_valid_cidr
- assert not is_valid_cidr('8.8.8.8')
- assert is_valid_cidr('192.168.1.0/24')
-
- def test_dotted_netmask(self):
- from requests.utils import dotted_netmask
- assert dotted_netmask(8) == '255.0.0.0'
- assert dotted_netmask(24) == '255.255.255.0'
- assert dotted_netmask(25) == '255.255.255.128'
-
- def test_address_in_network(self):
- from requests.utils import address_in_network
- assert address_in_network('192.168.1.1', '192.168.1.0/24')
- assert not address_in_network('172.16.0.1', '192.168.1.0/24')
-
- def test_get_auth_from_url(self):
- """Ensures that username and password in well-encoded URI as per
- RFC 3986 are correclty extracted."""
- from requests.utils import get_auth_from_url
- from requests.compat import quote
- percent_encoding_test_chars = "%!*'();:@&=+$,/?#[] "
- url_address = "request.com/url.html#test"
- url = "http://" + quote(
- percent_encoding_test_chars, '') + ':' + quote(
- percent_encoding_test_chars, '') + '@' + url_address
- (username, password) = get_auth_from_url(url)
- assert username == percent_encoding_test_chars
- assert password == percent_encoding_test_chars
-
- def test_requote_uri_with_unquoted_percents(self):
- """Ensure we handle unquoted percent signs in redirects.
-
- See: https://github.com/kennethreitz/requests/issues/2356
- """
- from requests.utils import requote_uri
- bad_uri = 'http://example.com/fiz?buz=%ppicture'
- quoted = 'http://example.com/fiz?buz=%25ppicture'
- assert quoted == requote_uri(bad_uri)
-
- def test_requote_uri_properly_requotes(self):
- """Ensure requoting doesn't break expectations."""
- from requests.utils import requote_uri
- quoted = 'http://example.com/fiz?buz=%25ppicture'
- assert quoted == requote_uri(quoted)
-
-
-class TestMorselToCookieExpires(unittest.TestCase):
-
+class TestMorselToCookieExpires:
"""Tests for morsel_to_cookie when morsel contains expires."""
def test_expires_valid_str(self):
@@ -1496,20 +1218,16 @@ class TestMorselToCookieExpires(unittest.TestCase):
cookie = morsel_to_cookie(morsel)
assert cookie.expires == 1
- def test_expires_invalid_int(self):
+ @pytest.mark.parametrize(
+ 'value, exception', (
+ (100, TypeError),
+ ('woops', ValueError),
+ ))
+ def test_expires_invalid_int(self, value, exception):
"""Test case where an invalid type is passed for expires."""
-
morsel = Morsel()
- morsel['expires'] = 100
- with pytest.raises(TypeError):
- morsel_to_cookie(morsel)
-
- def test_expires_invalid_str(self):
- """Test case where an invalid string is input."""
-
- morsel = Morsel()
- morsel['expires'] = 'woops'
- with pytest.raises(ValueError):
+ morsel['expires'] = value
+ with pytest.raises(exception):
morsel_to_cookie(morsel)
def test_expires_none(self):
@@ -1521,7 +1239,7 @@ class TestMorselToCookieExpires(unittest.TestCase):
assert cookie.expires is None
-class TestMorselToCookieMaxAge(unittest.TestCase):
+class TestMorselToCookieMaxAge:
"""Tests for morsel_to_cookie when morsel contains max-age."""
@@ -1543,20 +1261,22 @@ class TestMorselToCookieMaxAge(unittest.TestCase):
class TestTimeout:
+
def test_stream_timeout(self, httpbin):
try:
requests.get(httpbin('delay/10'), timeout=2.0)
except requests.exceptions.Timeout as e:
assert 'Read timed out' in e.args[0].args[0]
- def test_invalid_timeout(self, httpbin):
+ @pytest.mark.parametrize(
+ 'timeout, error_text', (
+ ((3, 4, 5), '(connect, read)'),
+ ('foo', 'must be an int or float'),
+ ))
+ def test_invalid_timeout(self, httpbin, timeout, error_text):
with pytest.raises(ValueError) as e:
- requests.get(httpbin('get'), timeout=(3, 4, 5))
- assert '(connect, read)' in str(e)
-
- with pytest.raises(ValueError) as e:
- requests.get(httpbin('get'), timeout="foo")
- assert 'must be an int or float' in str(e)
+ requests.get(httpbin('get'), timeout=timeout)
+ assert error_text in str(e)
def test_none_timeout(self, httpbin):
""" Check that you can set None as a valid timeout value.
@@ -1573,14 +1293,14 @@ class TestTimeout:
def test_read_timeout(self, httpbin):
try:
requests.get(httpbin('delay/10'), timeout=(None, 0.1))
- assert False, "The recv() request should time out."
+ pytest.fail('The recv() request should time out.')
except ReadTimeout:
pass
def test_connect_timeout(self):
try:
requests.get(TARPIT, timeout=(0.1, None))
- assert False, "The connect() request should time out."
+ pytest.fail('The connect() request should time out.')
except ConnectTimeout as e:
assert isinstance(e, ConnectionError)
assert isinstance(e, Timeout)
@@ -1588,7 +1308,7 @@ class TestTimeout:
def test_total_timeout_connect(self):
try:
requests.get(TARPIT, timeout=(0.1, 0.1))
- assert False, "The connect() request should time out."
+ pytest.fail('The connect() request should time out.')
except ConnectTimeout:
pass
@@ -1633,7 +1353,13 @@ class RedirectSession(SessionRedirectMixin):
return string
-class TestRedirects:
+def test_requests_are_updated_each_time(httpbin):
+ session = RedirectSession([303, 307])
+ prep = requests.Request('POST', httpbin('post')).prepare()
+ r0 = session.send(prep)
+ assert r0.request.method == 'POST'
+ assert session.calls[-1] == SendCall((r0.request,), {})
+ redirect_generator = session.resolve_redirects(r0, prep)
default_keyword_args = {
'stream': False,
'verify': True,
@@ -1642,87 +1368,60 @@ class TestRedirects:
'allow_redirects': False,
'proxies': {},
}
-
- def test_requests_are_updated_each_time(self, httpbin):
- session = RedirectSession([303, 307])
- prep = requests.Request('POST', httpbin('post')).prepare()
- r0 = session.send(prep)
- assert r0.request.method == 'POST'
- assert session.calls[-1] == SendCall((r0.request,), {})
- redirect_generator = session.resolve_redirects(r0, prep)
- for response in redirect_generator:
- assert response.request.method == 'GET'
- send_call = SendCall((response.request,),
- TestRedirects.default_keyword_args)
- assert session.calls[-1] == send_call
+ for response in redirect_generator:
+ assert response.request.method == 'GET'
+ send_call = SendCall((response.request,), default_keyword_args)
+ assert session.calls[-1] == send_call
-
-@pytest.fixture
-def list_of_tuples():
- return [
+@pytest.mark.parametrize(
+ 'data', (
(('a', 'b'), ('c', 'd')),
(('c', 'd'), ('a', 'b')),
(('a', 'b'), ('c', 'd'), ('e', 'f')),
- ]
-
-
-def test_data_argument_accepts_tuples(list_of_tuples):
- """
- Ensure that the data argument will accept tuples of strings
+ ))
+def test_data_argument_accepts_tuples(data):
+ """Ensure that the data argument will accept tuples of strings
and properly encode them.
"""
- for data in list_of_tuples:
- p = PreparedRequest()
- p.prepare(
- method='GET',
- url='http://www.example.com',
- data=data,
- hooks=default_hooks()
- )
- assert p.body == urlencode(data)
-
-
-def assert_copy(p, p_copy):
- for attr in ('method', 'url', 'headers', '_cookies', 'body', 'hooks'):
- assert getattr(p, attr) == getattr(p_copy, attr)
-
-
-def test_prepared_request_empty_copy():
- p = PreparedRequest()
- assert_copy(p, p.copy())
-
-
-def test_prepared_request_no_cookies_copy():
p = PreparedRequest()
p.prepare(
method='GET',
url='http://www.example.com',
- data='foo=bar',
+ data=data,
hooks=default_hooks()
)
- assert_copy(p, p.copy())
+ assert p.body == urlencode(data)
-def test_prepared_request_complete_copy():
+@pytest.mark.parametrize(
+ 'kwargs', (
+ None,
+ {
+ 'method': 'GET',
+ 'url': 'http://www.example.com',
+ 'data': 'foo=bar',
+ 'hooks': default_hooks()
+ },
+ {
+ 'method': 'GET',
+ 'url': 'http://www.example.com',
+ 'data': 'foo=bar',
+ 'hooks': default_hooks(),
+ 'cookies': {'foo': 'bar'}
+ },
+ {
+ 'method': 'GET',
+ 'url': u('http://www.example.com/üniçø∂é')
+ },
+ ))
+def test_prepared_copy(kwargs):
p = PreparedRequest()
- p.prepare(
- method='GET',
- url='http://www.example.com',
- data='foo=bar',
- hooks=default_hooks(),
- cookies={'foo': 'bar'}
- )
- assert_copy(p, p.copy())
-
-
-def test_prepare_unicode_url():
- p = PreparedRequest()
- p.prepare(
- method='GET',
- url=u('http://www.example.com/üniçø∂é'),
- )
- assert_copy(p, p.copy())
+ if kwargs:
+ p.prepare(**kwargs)
+ copy = p.copy()
+ for attr in ('method', 'url', 'headers', '_cookies', 'body', 'hooks'):
+ assert getattr(p, attr) == getattr(copy, attr)
def test_urllib3_retries(httpbin):
@@ -1753,126 +1452,7 @@ def test_vendor_aliases():
with pytest.raises(ImportError):
from requests.packages import webbrowser
-class TestTestServer(unittest.TestCase):
- def test_basic(self):
- question = b"sucess?"
- answer = b"yeah, success"
- def handler(sock):
- text = sock.recv(1000)
- assert text == question
- sock.send(answer)
-
- with Server(handler) as (host, port):
- sock = socket.socket()
- sock.connect((host, port))
- sock.send(question)
- text = sock.recv(1000)
- assert text == answer
- sock.close()
- def test_server_closes(self):
- with Server.basic_response_server() as (host, port):
- sock = socket.socket()
- sock.connect((host, port))
-
- sock.close()
-
- with pytest.raises(socket.error):
- new_sock = socket.socket()
- new_sock.connect((host, port))
-
- def test_text_response(self):
- server = Server.text_response_server(
- "HTTP/1.1 200 OK\r\n" +
- "Content-Length: 6\r\n" +
- "\r\nroflol"
- )
-
- with server as (host, port):
- r = requests.get('http://{}:{}'.format(host, port))
-
- assert r.status_code == 200
- assert r.text == 'roflol'
- assert r.headers['Content-Length'] == '6'
-
- def test_basic_response(self):
- with Server.basic_response_server() as (host, port):
- r = requests.get('http://{}:{}'.format(host, port))
- assert r.status_code == 200
- assert r.text == ''
- assert r.headers['Content-Length'] == '0'
-
- def test_basic_waiting_server(self):
- block_server = threading.Event()
-
- with Server.basic_response_server(wait_to_close_event=block_server) as (host, port):
- sock = socket.socket()
- sock.connect((host, port))
- sock.send(b'send something')
- time.sleep(2.5)
- sock.send(b'still alive')
- block_server.set() # release server block
-
- def test_multiple_requests(self):
- requests_to_handle = 5
-
- server = Server.basic_response_server(requests_to_handle=requests_to_handle)
-
- with server as (host, port):
- server_url = 'http://{}:{}'.format(host, port)
- for _ in range(requests_to_handle):
- r = requests.get(server_url)
- assert r.status_code == 200
-
- # the (n+1)th request fails
- with pytest.raises(requests.exceptions.ConnectionError):
- r = requests.get(server_url)
-
- def test_request_recovery(self):
- server = Server.basic_response_server(requests_to_handle=2)
- first_request = "put your hands up in the air"
- second_request = "put your hand down in the floor"
-
- with server as address:
- sock1 = socket.socket()
- sock2 = socket.socket()
-
- sock1.connect(address)
- sock1.send(first_request.encode())
- sock1.close()
-
- sock2.connect(address)
- sock2.send(second_request.encode())
- sock2.close()
-
- assert server.handler_results[0] == first_request
- assert server.handler_results[1] == second_request
-
- def test_requests_after_timeout_are_not_received(self):
- server = Server.basic_response_server(request_timeout=1)
-
- with server as address:
- sock = socket.socket()
- sock.connect(address)
- time.sleep(1.5)
- sock.send(b"hehehe, not received")
- sock.close()
-
- assert server.handler_results[0] == ""
-
-
- def test_request_recovery_with_bigger_timeout(self):
- server = Server.basic_response_server(request_timeout=3)
- data = "bananadine"
-
- with server as address:
- sock = socket.socket()
- sock.connect(address)
- time.sleep(1.5)
- sock.send(data.encode())
- sock.close()
-
- assert server.handler_results[0] == data
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 00000000..29dd4b85
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,374 @@
+# coding: utf-8
+import os
+import threading
+import socket
+import time
+from io import BytesIO
+
+import pytest
+import requests
+from requests import compat
+from requests.utils import (
+ address_in_network, dotted_netmask,
+ get_auth_from_url, get_encodings_from_content,
+ get_environ_proxies, guess_filename,
+ is_ipv4_address, is_valid_cidr, requote_uri,
+ select_proxy, super_len)
+
+from .compat import StringIO, cStringIO
+
+from testserver.server import Server
+
+
+class TestSuperLen:
+
+ @pytest.mark.parametrize(
+ 'stream, value', (
+ (StringIO.StringIO, 'Test'),
+ (BytesIO, b'Test'),
+ pytest.mark.skipif('cStringIO is None')((cStringIO, 'Test')),
+ ))
+ def test_io_streams(self, stream, value):
+ """Ensures that we properly deal with different kinds of IO streams."""
+ assert super_len(stream()) == 0
+ assert super_len(stream(value)) == 4
+
+ def test_super_len_correctly_calculates_len_of_partially_read_file(self):
+ """Ensure that we handle partially consumed file like objects."""
+ s = StringIO.StringIO()
+ s.write('foobarbogus')
+ assert super_len(s) == 0
+
+
+class TestGetEnvironProxies:
+ """Ensures that IP addresses are correctly matches with ranges
+ in no_proxy variable."""
+
+ @pytest.yield_fixture(scope='class', autouse=True, params=['no_proxy', 'NO_PROXY'])
+ def no_proxy(self, request):
+ os.environ[request.param] = '192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1'
+ yield
+ del os.environ[request.param]
+
+ @pytest.mark.parametrize(
+ 'url', (
+ 'http://192.168.0.1:5000/',
+ 'http://192.168.0.1/',
+ 'http://172.16.1.1/',
+ 'http://172.16.1.1:5000/',
+ 'http://localhost.localdomain:5000/v1.0/',
+ ))
+ def test_bypass(self, url):
+ assert get_environ_proxies(url) == {}
+
+ @pytest.mark.parametrize(
+ 'url', (
+ 'http://192.168.1.1:5000/',
+ 'http://192.168.1.1/',
+ 'http://www.requests.com/',
+ ))
+ def test_not_bypass(self, url):
+ assert get_environ_proxies(url) != {}
+
+
+class TestIsIPv4Address:
+
+ def test_valid(self):
+ assert is_ipv4_address('8.8.8.8')
+
+ @pytest.mark.parametrize('value', ('8.8.8.8.8', 'localhost.localdomain'))
+ def test_invalid(self, value):
+ assert not is_ipv4_address(value)
+
+
+class TestIsValidCIDR:
+
+ def test_valid(self):
+ assert is_valid_cidr('192.168.1.0/24')
+
+ @pytest.mark.parametrize(
+ 'value', (
+ '8.8.8.8',
+ '192.168.1.0/a',
+ '192.168.1.0/128',
+ '192.168.1.0/-1',
+ '192.168.1.999/24',
+ ))
+ def test_invalid(self, value):
+ assert not is_valid_cidr(value)
+
+
+class TestAddressInNetwork:
+
+ def test_valid(self):
+ assert address_in_network('192.168.1.1', '192.168.1.0/24')
+
+ def test_invalid(self):
+ assert not address_in_network('172.16.0.1', '192.168.1.0/24')
+
+
+class TestGuessFilename:
+
+ @pytest.mark.parametrize(
+ 'value', (1, type('Fake', (object,), {'name': 1})()),
+ )
+ def test_guess_filename_invalid(self, value):
+ assert guess_filename(value) is None
+
+ @pytest.mark.parametrize(
+ 'value, expected_type', (
+ (b'value', compat.bytes),
+ (b'value'.decode('utf-8'), compat.str)
+ ))
+ def test_guess_filename_valid(self, value, expected_type):
+ obj = type('Fake', (object,), {'name': value})()
+ result = guess_filename(obj)
+ assert result == value
+ assert isinstance(result, expected_type)
+
+
+class TestContentEncodingDetection:
+
+ def test_none(self):
+ encodings = get_encodings_from_content('')
+ assert not len(encodings)
+
+ @pytest.mark.parametrize(
+ 'content', (
+ # HTML5 meta charset attribute
+ ' ',
+ # HTML4 pragma directive
+ ' ',
+ # XHTML 1.x served with text/html MIME type
+ ' ',
+ # XHTML 1.x served as XML
+ '',
+ ))
+ def test_pragmas(self, content):
+ encodings = get_encodings_from_content(content)
+ assert len(encodings) == 1
+ assert encodings[0] == 'UTF-8'
+
+ def test_precedence(self):
+ content = '''
+
+
+
+ '''.strip()
+ assert get_encodings_from_content(content) == ['HTML5', 'HTML4', 'XML']
+
+ def test_chunked_upload(self):
+ """can safely send generators"""
+ block_server = threading.Event()
+ server = Server.basic_response_server(wait_to_close_event=block_server)
+ data = (i for i in [b'a', b'b', b'c'])
+
+ with server as (host, port):
+ url = 'http://{}:{}/'.format(host, port)
+ r = requests.post(url, data=data, stream=True)
+ block_server.set() # release server block
+
+ assert r.status_code == 200
+ assert r.request.headers['Transfer-Encoding'] == 'chunked'
+
+
+
+
+USER = PASSWORD = "%!*'();:@&=+$,/?#[] "
+ENCODED_USER = compat.quote(USER, '')
+ENCODED_PASSWORD = compat.quote(PASSWORD, '')
+
+
+@pytest.mark.parametrize(
+ 'url, auth', (
+ (
+ 'http://' + ENCODED_USER + ':' + ENCODED_PASSWORD + '@' +
+ 'request.com/url.html#test',
+ (USER, PASSWORD)
+ ),
+ (
+ 'http://user:pass@complex.url.com/path?query=yes',
+ ('user', 'pass')
+ ),
+ (
+ 'http://user:pass%20pass@complex.url.com/path?query=yes',
+ ('user', 'pass pass')
+ ),
+ (
+ 'http://user:pass pass@complex.url.com/path?query=yes',
+ ('user', 'pass pass')
+ ),
+ (
+ 'http://user%25user:pass@complex.url.com/path?query=yes',
+ ('user%user', 'pass')
+ ),
+ (
+ 'http://user:pass%23pass@complex.url.com/path?query=yes',
+ ('user', 'pass#pass')
+ ),
+ ))
+def test_get_auth_from_url(url, auth):
+ assert get_auth_from_url(url) == auth
+
+
+@pytest.mark.parametrize(
+ 'uri, expected', (
+ (
+ # Ensure requoting doesn't break expectations
+ 'http://example.com/fiz?buz=%25ppicture',
+ 'http://example.com/fiz?buz=%25ppicture',
+ ),
+ (
+ # Ensure we handle unquoted percent signs in redirects
+ 'http://example.com/fiz?buz=%ppicture',
+ 'http://example.com/fiz?buz=%25ppicture',
+ ),
+ ))
+def test_requote_uri_with_unquoted_percents(uri, expected):
+ """See: https://github.com/kennethreitz/requests/issues/2356
+ """
+ assert requote_uri(uri) == expected
+
+
+@pytest.mark.parametrize(
+ 'mask, expected', (
+ (8, '255.0.0.0'),
+ (24, '255.255.255.0'),
+ (25, '255.255.255.128'),
+ ))
+def test_dotted_netmask(mask, expected):
+ assert dotted_netmask(mask) == expected
+
+
+@pytest.mark.parametrize(
+ 'url, expected', (
+ ('hTTp://u:p@Some.Host/path', 'http://some.host.proxy'),
+ ('hTTp://u:p@Other.Host/path', 'http://http.proxy'),
+ ('hTTps://Other.Host', None),
+ ))
+def test_select_proxies(url, expected):
+ """Make sure we can select per-host proxies correctly."""
+ proxies = {'http': 'http://http.proxy',
+ 'http://some.host': 'http://some.host.proxy'}
+ assert select_proxy(url, proxies) == expected
+
+class TestTestServer:
+ def test_basic(self):
+ question = b"sucess?"
+ answer = b"yeah, success"
+ def handler(sock):
+ text = sock.recv(1000)
+ assert text == question
+ sock.send(answer)
+
+ with Server(handler) as (host, port):
+ sock = socket.socket()
+ sock.connect((host, port))
+ sock.send(question)
+ text = sock.recv(1000)
+ assert text == answer
+ sock.close()
+
+ def test_server_closes(self):
+ with Server.basic_response_server() as (host, port):
+ sock = socket.socket()
+ sock.connect((host, port))
+
+ sock.close()
+
+ with pytest.raises(socket.error):
+ new_sock = socket.socket()
+ new_sock.connect((host, port))
+
+ def test_text_response(self):
+ server = Server.text_response_server(
+ "HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 6\r\n" +
+ "\r\nroflol"
+ )
+
+ with server as (host, port):
+ r = requests.get('http://{}:{}'.format(host, port))
+
+ assert r.status_code == 200
+ assert r.text == 'roflol'
+ assert r.headers['Content-Length'] == '6'
+
+ def test_basic_response(self):
+ with Server.basic_response_server() as (host, port):
+ r = requests.get('http://{}:{}'.format(host, port))
+ assert r.status_code == 200
+ assert r.text == ''
+ assert r.headers['Content-Length'] == '0'
+
+ def test_basic_waiting_server(self):
+ block_server = threading.Event()
+
+ with Server.basic_response_server(wait_to_close_event=block_server) as (host, port):
+ sock = socket.socket()
+ sock.connect((host, port))
+ sock.send(b'send something')
+ time.sleep(2.5)
+ sock.send(b'still alive')
+ block_server.set() # release server block
+
+ def test_multiple_requests(self):
+ requests_to_handle = 5
+
+ server = Server.basic_response_server(requests_to_handle=requests_to_handle)
+
+ with server as (host, port):
+ server_url = 'http://{}:{}'.format(host, port)
+ for _ in range(requests_to_handle):
+ r = requests.get(server_url)
+ assert r.status_code == 200
+
+ # the (n+1)th request fails
+ with pytest.raises(requests.exceptions.ConnectionError):
+ r = requests.get(server_url)
+
+ def test_request_recovery(self):
+ server = Server.basic_response_server(requests_to_handle=2)
+ first_request = "put your hands up in the air"
+ second_request = "put your hand down in the floor"
+
+ with server as address:
+ sock1 = socket.socket()
+ sock2 = socket.socket()
+
+ sock1.connect(address)
+ sock1.send(first_request.encode())
+ sock1.close()
+
+ sock2.connect(address)
+ sock2.send(second_request.encode())
+ sock2.close()
+
+ assert server.handler_results[0] == first_request
+ assert server.handler_results[1] == second_request
+
+ def test_requests_after_timeout_are_not_received(self):
+ server = Server.basic_response_server(request_timeout=1)
+
+ with server as address:
+ sock = socket.socket()
+ sock.connect(address)
+ time.sleep(1.5)
+ sock.send(b"hehehe, not received")
+ sock.close()
+
+ assert server.handler_results[0] == ""
+
+
+ def test_request_recovery_with_bigger_timeout(self):
+ server = Server.basic_response_server(request_timeout=3)
+ data = "bananadine"
+
+ with server as address:
+ sock = socket.socket()
+ sock.connect(address)
+ time.sleep(1.5)
+ sock.send(data.encode())
+ sock.close()
+
+ assert server.handler_results[0] == data