mirror of
https://github.com/kennethreitz/requests.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'master' of github.com:kennethreitz/requests
This commit is contained in:
@@ -178,4 +178,5 @@ Patches and Suggestions
|
||||
- Moinuddin Quadri <moin18@gmail.com> (`@moin18 <https://github.com/moin18>`_)
|
||||
- Matt Kohl (`@mattkohl <https://github.com/mattkohl>`_)
|
||||
- Jonathan Vanasco (`@jvanasco <https://github.com/jvanasco>`_)
|
||||
- David Fontenot (`@davidfontenot <https://github.com/davidfontenot>`_)
|
||||
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ Requests: HTTP for Humans
|
||||
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,
|
||||
**Warning:** Recreational use of the Python standard library for HTTP may result in dangerous side-effects,
|
||||
including: security vulnerabilities, verbose code, reinventing the wheel,
|
||||
constantly reading documentation, depression, headaches, or even death.
|
||||
|
||||
@@ -50,7 +50,7 @@ Behold, the power of Requests:
|
||||
|
||||
See `the similar code, sans Requests <https://gist.github.com/973705>`_.
|
||||
|
||||
.. image:: http://docs.python-requests.org/en/master/_static/requests-sidebar.png
|
||||
.. image:: https://raw.githubusercontent.com/kennethreitz/requests/master/docs/_static/requests-logo-small.png
|
||||
:target: http://docs.python-requests.org/
|
||||
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
Vendored
+1
@@ -40,6 +40,7 @@
|
||||
|
||||
<p>More <a href="http://kennethreitz.org/">Kenneth Reitz</a> projects:</p>
|
||||
<ul>
|
||||
<li><a href="http://edmsynths.com/">edmsynths.com</a></li>
|
||||
<li><a href="http://pipenv.org/">pipenv</a></li>
|
||||
<li><a href="http://pep8.org/">pep8.org</a></li>
|
||||
<li><a href="http://httpbin.org/">httpbin.org</a></li>
|
||||
|
||||
Vendored
+1
@@ -42,6 +42,7 @@
|
||||
|
||||
<p>More <a href="http://kennethreitz.org/">Kenneth Reitz</a> projects:</p>
|
||||
<ul>
|
||||
<li><a href="http://edmsynths.com/">edmsynths.com</a></li>
|
||||
<li><a href="http://pipenv.org/">pipenv</a></li>
|
||||
<li><a href="http://pep8.org/">pep8.org</a></li>
|
||||
<li><a href="http://httpbin.org/">httpbin.org</a></li>
|
||||
|
||||
+12
-12
@@ -68,21 +68,21 @@ PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington
|
||||
Post, Twitter, SoundCloud, Kippt, 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
|
||||
right level of abstraction.
|
||||
**Armin Ronacher**—
|
||||
*Requests is the perfect example how beautiful an API can be with the
|
||||
right level of abstraction.*
|
||||
|
||||
**Matt DeBoard**
|
||||
I'm going to get `@kennethreitz <https://twitter.com/kennethreitz>`_'s Python requests module tattooed
|
||||
on my body, somehow. The whole thing.
|
||||
**Matt DeBoard**—
|
||||
*I'm going to get `@kennethreitz <https://twitter.com/kennethreitz>`_'s Python requests module tattooed
|
||||
on my body, somehow. The whole thing.*
|
||||
|
||||
**Daniel Greenfeld**
|
||||
Nuked a 1200 LOC spaghetti code library with 10 lines of code thanks to
|
||||
`@kennethreitz <https://twitter.com/kennethreitz>`_'s request library. Today has been AWESOME.
|
||||
**Daniel Greenfeld**—
|
||||
*Nuked a 1200 LOC spaghetti code library with 10 lines of code thanks to
|
||||
`@kennethreitz <https://twitter.com/kennethreitz>`_'s request library. Today has been AWESOME.*
|
||||
|
||||
**Kenny Meyers**
|
||||
Python HTTP: When in doubt, or when not in doubt, use Requests. Beautiful,
|
||||
simple, Pythonic.
|
||||
**Kenny Meyers**—
|
||||
*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 11,000,000 downloads every month. All the cool kids are doing it!
|
||||
|
||||
@@ -227,7 +227,7 @@ By default, ``verify`` is set to True. Option ``verify`` only applies to host ce
|
||||
|
||||
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::
|
||||
files' paths::
|
||||
|
||||
>>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key'))
|
||||
<Response [200]>
|
||||
|
||||
+4
-4
@@ -19,7 +19,7 @@ def request(method, url, **kwargs):
|
||||
:param method: method for the new :class:`Request` object.
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||
@@ -100,7 +100,7 @@ def post(url, data=None, json=None, **kwargs):
|
||||
"""Sends a POST request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
@@ -114,7 +114,7 @@ def put(url, data=None, **kwargs):
|
||||
"""Sends a PUT request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
@@ -128,7 +128,7 @@ def patch(url, data=None, **kwargs):
|
||||
"""Sends a PATCH request.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
|
||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||
:return: :class:`Response <Response>` object
|
||||
|
||||
+21
-2
@@ -659,11 +659,23 @@ class Response(object):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
return self.ok
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns true if :attr:`status_code` is 'OK'."""
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
return self.ok
|
||||
|
||||
def __iter__(self):
|
||||
@@ -672,6 +684,13 @@ class Response(object):
|
||||
|
||||
@property
|
||||
def ok(self):
|
||||
"""Returns True if :attr:`status_code` is less than 400.
|
||||
|
||||
This attribute checks if the status code of the response is between
|
||||
400 and 600 to see if there was a client error or a server error. If
|
||||
the status code, is between 200 and 400, this will return True. This
|
||||
is **not** a check to see if the response code is ``200 OK``.
|
||||
"""
|
||||
try:
|
||||
self.raise_for_status()
|
||||
except HTTPError:
|
||||
|
||||
@@ -8,9 +8,12 @@ Data structures that power Requests.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import time
|
||||
|
||||
from .compat import OrderedDict
|
||||
|
||||
current_time = getattr(time, 'monotonic', time.time)
|
||||
|
||||
|
||||
class CaseInsensitiveDict(collections.MutableMapping):
|
||||
"""A case-insensitive ``dict``-like object.
|
||||
@@ -103,3 +106,89 @@ class LookupDict(dict):
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.__dict__.get(key, default)
|
||||
|
||||
|
||||
class TimedCacheManaged(object):
|
||||
"""
|
||||
Wrap a function call in a timed cache
|
||||
"""
|
||||
def __init__(self, fnc):
|
||||
self.fnc = fnc
|
||||
self.cache = TimedCache()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
key = args[0]
|
||||
found = None
|
||||
try:
|
||||
found = self.cache[key]
|
||||
except KeyError:
|
||||
found = self.fnc(key, **kwargs)
|
||||
self.cache[key] = found
|
||||
|
||||
return found
|
||||
|
||||
|
||||
class TimedCache(collections.MutableMapping):
|
||||
"""
|
||||
Evicts entries after expiration_secs. If none are expired and maxlen is hit,
|
||||
will evict the oldest cached entry
|
||||
"""
|
||||
def __init__(self, maxlen=32, expiration_secs=60):
|
||||
"""
|
||||
:param maxlen: most number of entries to hold on to
|
||||
:param expiration_secs: the number of seconds to hold on
|
||||
to entries
|
||||
"""
|
||||
self.maxlen = maxlen
|
||||
self.expiration_secs = expiration_secs
|
||||
self._dict = OrderedDict()
|
||||
|
||||
def __repr__(self):
|
||||
return '<TimedCache maxlen:%d len:%d expiration_secs:%d>' % \
|
||||
(self.maxlen, len(self._dict), self.expiration_secs)
|
||||
|
||||
def __iter__(self):
|
||||
return ((key, value[1]) for key, value in self._dict.items())
|
||||
|
||||
def __delitem__(self, item):
|
||||
del self._dict[item]
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""
|
||||
Look up an item in the cache. If the item
|
||||
has already expired, it will be invalidated and not returned
|
||||
|
||||
:param key: which entry to look up
|
||||
:return: the value in the cache, or None
|
||||
"""
|
||||
occurred, value = self._dict[key]
|
||||
now = int(current_time())
|
||||
|
||||
if now - occurred > self.expiration_secs:
|
||||
del self._dict[key]
|
||||
raise KeyError(key)
|
||||
else:
|
||||
return value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""
|
||||
Locates the value at lookup key, if cache is full, will evict the
|
||||
oldest entry
|
||||
|
||||
:param key: the key to search the cache for
|
||||
:param value: the value to be added to the cache
|
||||
"""
|
||||
now = int(current_time())
|
||||
|
||||
while len(self._dict) >= self.maxlen:
|
||||
self._dict.popitem(last=False)
|
||||
|
||||
self._dict[key] = (now, value)
|
||||
|
||||
def __len__(self):
|
||||
""":return: the length of the cache"""
|
||||
return len(self._dict)
|
||||
|
||||
def clear(self):
|
||||
"""Clears the cache"""
|
||||
return self._dict.clear()
|
||||
|
||||
+22
-10
@@ -28,7 +28,7 @@ from .compat import (
|
||||
quote, urlparse, bytes, str, OrderedDict, unquote, getproxies,
|
||||
proxy_bypass, urlunparse, basestring, integer_types)
|
||||
from .cookies import RequestsCookieJar, cookiejar_from_dict
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .structures import CaseInsensitiveDict, TimedCache, TimedCacheManaged
|
||||
from .exceptions import (
|
||||
InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError)
|
||||
|
||||
@@ -92,14 +92,16 @@ def super_len(o):
|
||||
else:
|
||||
if hasattr(o, 'seek') and total_length is None:
|
||||
# StringIO and BytesIO have seek but no useable fileno
|
||||
try:
|
||||
# seek to end of file
|
||||
o.seek(0, 2)
|
||||
total_length = o.tell()
|
||||
|
||||
# seek to end of file
|
||||
o.seek(0, 2)
|
||||
total_length = o.tell()
|
||||
|
||||
# seek back to current position to support
|
||||
# partially read file-like objects
|
||||
o.seek(current_position or 0)
|
||||
# seek back to current position to support
|
||||
# partially read file-like objects
|
||||
o.seek(current_position or 0)
|
||||
except (OSError, IOError):
|
||||
total_length = 0
|
||||
|
||||
if total_length is None:
|
||||
total_length = 0
|
||||
@@ -577,6 +579,16 @@ def set_environ(env_name, value):
|
||||
os.environ[env_name] = old_value
|
||||
|
||||
|
||||
@TimedCacheManaged
|
||||
def _proxy_bypass_cached(netloc):
|
||||
"""
|
||||
Looks for netloc in the cache, if not found, will call proxy_bypass
|
||||
for the netloc and store its result in the cache
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
return proxy_bypass(netloc)
|
||||
|
||||
def should_bypass_proxies(url, no_proxy):
|
||||
"""
|
||||
Returns whether we should bypass proxies or not.
|
||||
@@ -624,7 +636,7 @@ def should_bypass_proxies(url, no_proxy):
|
||||
# legitimate problems.
|
||||
with set_environ('no_proxy', no_proxy_arg):
|
||||
try:
|
||||
bypass = proxy_bypass(netloc)
|
||||
bypass = _proxy_bypass_cached(netloc)
|
||||
except (TypeError, socket.gaierror):
|
||||
bypass = False
|
||||
|
||||
@@ -847,7 +859,7 @@ def rewind_body(prepared_request):
|
||||
try:
|
||||
body_seek(prepared_request._body_position)
|
||||
except (IOError, OSError):
|
||||
raise UnrewindableBodyError("An error occured when rewinding request "
|
||||
raise UnrewindableBodyError("An error occurred when rewinding request "
|
||||
"body for redirect.")
|
||||
else:
|
||||
raise UnrewindableBodyError("Unable to rewind request body for redirect.")
|
||||
|
||||
@@ -1546,7 +1546,7 @@ class TestRequests:
|
||||
def tell(self):
|
||||
return 0
|
||||
|
||||
def seek(self, pos):
|
||||
def seek(self, pos, whence=0):
|
||||
raise OSError()
|
||||
|
||||
def __iter__(self):
|
||||
@@ -1560,7 +1560,7 @@ class TestRequests:
|
||||
with pytest.raises(UnrewindableBodyError) as e:
|
||||
requests.utils.rewind_body(prep)
|
||||
|
||||
assert 'error occured when rewinding request body' in str(e)
|
||||
assert 'error occurred when rewinding request body' in str(e)
|
||||
|
||||
def test_rewind_body_failed_tell(self):
|
||||
class BadFileObj:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from requests.structures import CaseInsensitiveDict, LookupDict
|
||||
from requests.structures import CaseInsensitiveDict, LookupDict, TimedCache, TimedCacheManaged
|
||||
|
||||
|
||||
class TestCaseInsensitiveDict:
|
||||
@@ -74,3 +74,75 @@ class TestLookupDict:
|
||||
@get_item_parameters
|
||||
def test_get(self, key, value):
|
||||
assert self.lookup_dict.get(key) == value
|
||||
|
||||
|
||||
class TestTimedCache(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.any_value = 'some value'
|
||||
self.expiration_secs = 60
|
||||
self.cache = TimedCache(expiration_secs=self.expiration_secs)
|
||||
yield
|
||||
self.cache.clear()
|
||||
|
||||
def test_get(self):
|
||||
self.cache['a'] = self.any_value
|
||||
assert self.cache['a'] is self.any_value
|
||||
|
||||
def test_repr(self):
|
||||
repr = str(self.cache)
|
||||
assert repr == '<TimedCache maxlen:32 len:0 expiration_secs:60>'
|
||||
|
||||
def test_get_expired_item(self, mocker):
|
||||
self.cache = TimedCache(maxlen=1, expiration_secs=self.expiration_secs)
|
||||
|
||||
mocker.patch('requests.structures.current_time', lambda: 0)
|
||||
self.cache['a'] = self.any_value
|
||||
mocker.patch('requests.structures.current_time', lambda: self.expiration_secs + 1)
|
||||
assert self.cache.get('a') is None
|
||||
|
||||
def test_evict_first_entry_when_full(self, mocker):
|
||||
self.cache = TimedCache(maxlen=2, expiration_secs=2)
|
||||
mocker.patch('requests.structures.current_time', lambda: 0)
|
||||
self.cache['a'] = self.any_value
|
||||
mocker.patch('requests.structures.current_time', lambda: 1)
|
||||
self.cache['b'] = self.any_value
|
||||
mocker.patch('requests.structures.current_time', lambda: 3)
|
||||
self.cache['c'] = self.any_value
|
||||
assert len(self.cache) is 2
|
||||
with pytest.raises(KeyError, message='Expected key not found'):
|
||||
self.cache['a']
|
||||
assert self.cache['b'] is self.any_value
|
||||
assert self.cache['c'] is self.any_value
|
||||
|
||||
def test_delete_item_removes_item(self):
|
||||
self.cache['a'] = self.any_value
|
||||
del self.cache['a']
|
||||
with pytest.raises(KeyError, message='Expected key not found'):
|
||||
self.cache['a']
|
||||
|
||||
def test_iterating_hides_timestamps(self):
|
||||
self.cache['a'] = 1
|
||||
self.cache['b'] = 2
|
||||
expected = [('a', 1), ('b', 2)]
|
||||
actual = [(key, val) for key, val in self.cache]
|
||||
assert expected == actual
|
||||
|
||||
|
||||
class TestTimedCacheManagedDecorator(object):
|
||||
def test_caches_repeated_calls(self, mocker):
|
||||
mocker.patch('requests.structures.current_time', lambda: 0)
|
||||
|
||||
nonlocals = {'value': 0}
|
||||
|
||||
@TimedCacheManaged
|
||||
def some_method(x):
|
||||
nonlocals['value'] = nonlocals['value'] + x
|
||||
return nonlocals['value']
|
||||
|
||||
first_result = some_method(1)
|
||||
assert first_result is 1
|
||||
second_result = some_method(1)
|
||||
assert second_result is 1
|
||||
third_result = some_method(2)
|
||||
assert third_result is 3
|
||||
|
||||
Reference in New Issue
Block a user