mirror of
https://github.com/kennethreitz/requests.git
synced 2026-06-05 22:50:18 +00:00
Merge branch 'develop'
This commit is contained in:
+14
-5
@@ -1,10 +1,19 @@
|
||||
History
|
||||
-------
|
||||
|
||||
0.7.0 (2011-10-22)
|
||||
++++++++++++++++++
|
||||
|
||||
* Sessions are now the primary interface.
|
||||
* Deprecated InvalidMethodException.
|
||||
* PATCH fix.
|
||||
* New config system (no more global settings).
|
||||
|
||||
|
||||
0.6.6 (2011-10-19)
|
||||
++++++++++++++++++
|
||||
|
||||
* Session parameter bugfix (params merging)
|
||||
* Session parameter bugfix (params merging).
|
||||
|
||||
|
||||
0.6.5 (2011-10-18)
|
||||
@@ -18,10 +27,10 @@ History
|
||||
++++++++++++++++++
|
||||
|
||||
* Automatic decoding of unicode, based on HTTP Headers.
|
||||
* New ``decode_unicode`` setting
|
||||
* Removal of ``r.read/close`` methods
|
||||
* New ``decode_unicode`` setting.
|
||||
* Removal of ``r.read/close`` methods.
|
||||
* New ``r.faw`` interface for advanced response usage.*
|
||||
* Automatic expansion of parameterized headers
|
||||
* Automatic expansion of parameterized headers.
|
||||
|
||||
|
||||
0.6.3 (2011-10-13)
|
||||
@@ -33,7 +42,7 @@ History
|
||||
0.6.2 (2011-10-09)
|
||||
++++++++++++++++++
|
||||
|
||||
* GET/HEAD obeys allow_redirects=False
|
||||
* GET/HEAD obeys allow_redirects=False.
|
||||
|
||||
|
||||
0.6.1 (2011-08-20)
|
||||
|
||||
@@ -2,7 +2,7 @@ init:
|
||||
pip install -r reqs.txt
|
||||
|
||||
test:
|
||||
nosetests test_requests.py --with-color
|
||||
nosetests test_requests.py
|
||||
|
||||
ci: init
|
||||
nosetests test_requests.py --with-xunit --xunit-file=junit-report.xml
|
||||
|
||||
@@ -6,7 +6,7 @@ Modules
|
||||
- `robotframework-requests <https://github.com/bulkan/robotframework-requests>`_, a Robot Framework API wrapper.
|
||||
- `fullerene <https://github.com/bitprophet/fullerene>`_, a Graphite Dashboard.
|
||||
- `urbanairship-python <https://github.com/benjaminws/urbanairship-python>`_, a fork of the Urban Airship API wrapper.
|
||||
|
||||
- `WhitespaceBot <https://github.com/Gunio/WhitespaceBot/>`_, a project that automatically forks repos, strips trailing whitespace, and sends a pull request.
|
||||
|
||||
Articles & Talks
|
||||
================
|
||||
|
||||
@@ -17,13 +17,13 @@ A session object has all the methods of the main Requests API.
|
||||
|
||||
Let's persist some cookies across requests::
|
||||
|
||||
with requests.session() as s:
|
||||
s = requests.session()
|
||||
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
|
||||
r = s.get("http://httpbin.org/cookies")
|
||||
|
||||
print r.content
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
print r.content
|
||||
# '{"cookies": {"sessioncookie": "123456789"}}'
|
||||
|
||||
|
||||
Sessions can also be used to provide default data to the request methods::
|
||||
@@ -39,8 +39,8 @@ Sessions can also be used to provide default data to the request methods::
|
||||
|
||||
.. admonition:: Global Settings
|
||||
|
||||
Certain parameters are best set at the ``request.config`` level
|
||||
(e.g.. a global proxy, user agent header).
|
||||
Certain parameters are best set in the ``config`` dictionary
|
||||
(e.g. user agent header).
|
||||
|
||||
|
||||
Asynchronous Requests
|
||||
|
||||
@@ -18,7 +18,7 @@ or, with `easy_install <http://pypi.python.org/pypi/setuptools>`_::
|
||||
|
||||
$ easy_install requests
|
||||
|
||||
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/index.html#pip-compared-to-easy-install>`_.
|
||||
But, you really `shouldn't do that <http://www.pip-installer.org/en/latest/other-tools.html#pip-compared-to-easy-install>`_.
|
||||
|
||||
|
||||
|
||||
|
||||
+31
-2
@@ -1,4 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from core import *
|
||||
from core import __version__
|
||||
# __
|
||||
# /__) _ _ _ _ _/ _
|
||||
# / ( (- (/ (/ (- _) / _)
|
||||
# /
|
||||
|
||||
"""
|
||||
requests
|
||||
~~~~~~~~
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.7.0'
|
||||
__build__ = 0x000700
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
|
||||
from . import utils
|
||||
from .models import HTTPError, Request, Response
|
||||
from .api import request, get, head, post, patch, put, delete
|
||||
from .sessions import session
|
||||
from .status_codes import codes
|
||||
from .exceptions import (
|
||||
RequestException, AuthenticationError, Timeout, URLRequired,
|
||||
TooManyRedirects
|
||||
)
|
||||
|
||||
+20
-75
@@ -11,96 +11,41 @@ This module implements the Requests API.
|
||||
|
||||
"""
|
||||
|
||||
import config
|
||||
from .models import Request, Response, AuthObject
|
||||
from .status_codes import codes
|
||||
from .hooks import dispatch_hook
|
||||
from .utils import cookiejar_from_dict, header_expand
|
||||
|
||||
from .sessions import session
|
||||
|
||||
__all__ = ('request', 'get', 'head', 'post', 'patch', 'put', 'delete')
|
||||
|
||||
|
||||
def request(method, url,
|
||||
params=None, data=None, headers=None, cookies=None, files=None, auth=None,
|
||||
timeout=None, allow_redirects=False, proxies=None, hooks=None, return_response=True):
|
||||
params=None,
|
||||
data=None,
|
||||
headers=None,
|
||||
cookies=None,
|
||||
files=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
allow_redirects=False,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
return_response=True,
|
||||
config=None):
|
||||
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
: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 or bytes 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`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
"""
|
||||
|
||||
method = str(method).upper()
|
||||
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
# Expand header values
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
args = dict(
|
||||
method = method,
|
||||
url = url,
|
||||
data = data,
|
||||
params = params,
|
||||
headers = headers,
|
||||
cookiejar = cookies,
|
||||
files = files,
|
||||
auth = auth,
|
||||
hooks = hooks,
|
||||
timeout = timeout or config.settings.timeout,
|
||||
allow_redirects = allow_redirects,
|
||||
proxies = proxies or config.settings.proxies,
|
||||
s = session()
|
||||
return s.request(
|
||||
method, url, params, data, headers, cookies, files, auth,
|
||||
timeout, allow_redirects, proxies, hooks, return_response,
|
||||
config
|
||||
)
|
||||
|
||||
# Arguments manipulation hook.
|
||||
args = dispatch_hook('args', hooks, args)
|
||||
|
||||
r = Request(**args)
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', hooks, r)
|
||||
|
||||
# Don't send if asked nicely.
|
||||
if not return_response:
|
||||
return r
|
||||
|
||||
# Send the HTTP Request.
|
||||
r.send()
|
||||
|
||||
# Post-request hook.
|
||||
r = dispatch_hook('post_request', hooks, r)
|
||||
|
||||
# Response manipulation hook.
|
||||
r.response = dispatch_hook('response', hooks, r.response)
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
def get(url, **kwargs):
|
||||
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return request('GET', url, **kwargs)
|
||||
|
||||
@@ -146,7 +91,7 @@ def patch(url, data='', **kwargs):
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return request('patch', url, **kwargs)
|
||||
return request('patch', url, data='', **kwargs)
|
||||
|
||||
|
||||
def delete(url, **kwargs):
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.config
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides the Requests settings feature set.
|
||||
|
||||
"""
|
||||
|
||||
class Settings(object):
|
||||
_singleton = {}
|
||||
|
||||
# attributes with defaults
|
||||
__attrs__ = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Settings, self).__init__()
|
||||
|
||||
self.__dict__ = self._singleton
|
||||
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# new instance of class to call
|
||||
r = self.__class__()
|
||||
|
||||
# cache previous settings for __exit__
|
||||
r.__cache = self.__dict__.copy()
|
||||
map(self.__cache.setdefault, self.__attrs__)
|
||||
|
||||
# set new settings
|
||||
self.__dict__.update(*args, **kwargs)
|
||||
|
||||
return r
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
|
||||
def __exit__(self, *args):
|
||||
|
||||
# restore cached copy
|
||||
self.__dict__.update(self.__cache.copy())
|
||||
del self.__cache
|
||||
|
||||
|
||||
def __getattribute__(self, key):
|
||||
if key in object.__getattribute__(self, '__attrs__'):
|
||||
try:
|
||||
return object.__getattribute__(self, key)
|
||||
except AttributeError:
|
||||
return None
|
||||
return object.__getattribute__(self, key)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
settings.base_headers = {'User-Agent': 'python-requests.org'}
|
||||
settings.accept_gzip = True
|
||||
settings.proxies = None
|
||||
settings.verbose = None
|
||||
settings.timeout = None
|
||||
settings.max_redirects = 30
|
||||
settings.decode_unicode = True
|
||||
|
||||
#: Use socket.setdefaulttimeout() as fallback?
|
||||
settings.timeout_fallback = True
|
||||
@@ -1,29 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.core
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This module implements the main Requests system.
|
||||
|
||||
:copyright: (c) 2011 by Kenneth Reitz.
|
||||
:license: ISC, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'requests'
|
||||
__version__ = '0.6.6'
|
||||
__build__ = 0x000606
|
||||
__author__ = 'Kenneth Reitz'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = 'Copyright 2011 Kenneth Reitz'
|
||||
|
||||
|
||||
from models import HTTPError, Request, Response
|
||||
from api import *
|
||||
from exceptions import *
|
||||
from sessions import session
|
||||
from status_codes import codes
|
||||
from config import settings
|
||||
|
||||
import utils
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
requests.defaults
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides the Requests configuration defaults.
|
||||
|
||||
settings parameters:
|
||||
|
||||
- :base_headers: - Sets default User-Agent to `python-requests.org`
|
||||
- :accept_gzip: - Whether or not to accept gzip-compressed data
|
||||
- :proxies: - http proxies?
|
||||
- :verbose: - display verbose information?
|
||||
- :timeout: - timeout time until request terminates
|
||||
- :max_redirects: - maximum number of allowed redirects?
|
||||
- :decode_unicode: - whether or not to accept unicode?
|
||||
|
||||
"""
|
||||
|
||||
from . import __version__
|
||||
|
||||
defaults = dict()
|
||||
|
||||
|
||||
defaults['base_headers'] = {
|
||||
'User-Agent': 'python-requests/%s' % __version__,
|
||||
'Accept-Encoding': ', '.join([ 'identity', 'deflate', 'compress', 'gzip' ]),
|
||||
}
|
||||
|
||||
defaults['proxies'] = {}
|
||||
defaults['verbose'] = None
|
||||
defaults['timeout'] = None
|
||||
defaults['max_redirects'] = 30
|
||||
defaults['decode_unicode'] = True
|
||||
defaults['timeout_fallback'] = True
|
||||
# defaults['keep_alive'] = True
|
||||
# defaults['max_connections'] = 10
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
"""
|
||||
requests.exceptions
|
||||
~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains the set of Requests' exceptions.
|
||||
|
||||
"""
|
||||
|
||||
@@ -12,15 +14,12 @@ class RequestException(Exception):
|
||||
|
||||
class AuthenticationError(RequestException):
|
||||
"""The authentication credentials provided were invalid."""
|
||||
|
||||
|
||||
class Timeout(RequestException):
|
||||
"""The request timed out."""
|
||||
|
||||
class URLRequired(RequestException):
|
||||
"""A valid URL is required to make a request."""
|
||||
|
||||
class InvalidMethod(RequestException):
|
||||
"""An inappropriate method was attempted."""
|
||||
|
||||
class TooManyRedirects(RequestException):
|
||||
"""Too many redirects."""
|
||||
|
||||
+73
-54
@@ -1,3 +1,4 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@@ -9,22 +10,22 @@ requests.models
|
||||
import urllib
|
||||
import urllib2
|
||||
import socket
|
||||
import codecs
|
||||
import zlib
|
||||
|
||||
|
||||
from urllib2 import HTTPError
|
||||
from urlparse import urlparse, urlunparse, urljoin
|
||||
from datetime import datetime
|
||||
|
||||
from .config import settings
|
||||
from .monkeys import Request as _Request, HTTPBasicAuthHandler, HTTPForcedBasicAuthHandler, HTTPDigestAuthHandler, HTTPRedirectHandler
|
||||
from .structures import CaseInsensitiveDict
|
||||
from .packages.poster.encode import multipart_encode
|
||||
from .packages.poster.streaminghttp import register_openers, get_handlers
|
||||
from .utils import dict_from_cookiejar, get_unicode_from_response, stream_decode_response_unicode, decode_gzip, stream_decode_gzip
|
||||
from .utils import (dict_from_cookiejar, get_unicode_from_response, stream_decode_response_unicode, decode_gzip, stream_decode_gzip)
|
||||
from .status_codes import codes
|
||||
from .exceptions import RequestException, AuthenticationError, Timeout, URLRequired, InvalidMethod, TooManyRedirects
|
||||
from .exceptions import Timeout, URLRequired, TooManyRedirects
|
||||
from .monkeys import Request as _Request
|
||||
from .monkeys import (
|
||||
HTTPBasicAuthHandler, HTTPForcedBasicAuthHandler,
|
||||
HTTPDigestAuthHandler, HTTPRedirectHandler)
|
||||
|
||||
|
||||
REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
|
||||
@@ -37,9 +38,20 @@ class Request(object):
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
url=None, headers=dict(), files=None, method=None, data=dict(),
|
||||
params=dict(), auth=None, cookiejar=None, timeout=None, redirect=False,
|
||||
allow_redirects=False, proxies=None, hooks=None):
|
||||
url=None,
|
||||
headers=dict(),
|
||||
files=None,
|
||||
method=None,
|
||||
data=dict(),
|
||||
params=dict(),
|
||||
auth=None,
|
||||
cookies=None,
|
||||
timeout=None,
|
||||
redirect=False,
|
||||
allow_redirects=False,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
config=None):
|
||||
|
||||
#: Float describes the timeout of the request.
|
||||
# (Use socket.setdefaulttimeout() as fallback)
|
||||
@@ -91,7 +103,10 @@ class Request(object):
|
||||
self.auth = auth
|
||||
|
||||
#: CookieJar to attach to :class:`Request <Request>`.
|
||||
self.cookiejar = cookiejar
|
||||
self.cookies = cookies
|
||||
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = config
|
||||
|
||||
#: True if Request has been sent.
|
||||
self.sent = False
|
||||
@@ -99,17 +114,12 @@ class Request(object):
|
||||
#: Event-handling hooks.
|
||||
self.hooks = hooks
|
||||
|
||||
# Header manipulation and defaults.
|
||||
|
||||
if settings.accept_gzip:
|
||||
settings.base_headers.update({'Accept-Encoding': 'gzip'})
|
||||
|
||||
if headers:
|
||||
headers = CaseInsensitiveDict(self.headers)
|
||||
else:
|
||||
headers = CaseInsensitiveDict()
|
||||
|
||||
for (k, v) in settings.base_headers.items():
|
||||
for (k, v) in self.config.get('base_headers', {}).items():
|
||||
if k not in headers:
|
||||
headers[k] = v
|
||||
|
||||
@@ -120,20 +130,13 @@ class Request(object):
|
||||
return '<Request [%s]>' % (self.method)
|
||||
|
||||
|
||||
def _checks(self):
|
||||
"""Deterministic checks for consistency."""
|
||||
|
||||
if not self.url:
|
||||
raise URLRequired
|
||||
|
||||
|
||||
def _get_opener(self):
|
||||
"""Creates appropriate opener object for urllib2."""
|
||||
|
||||
_handlers = []
|
||||
|
||||
if self.cookiejar is not None:
|
||||
_handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
|
||||
if self.cookies is not None:
|
||||
_handlers.append(urllib2.HTTPCookieProcessor(self.cookies))
|
||||
|
||||
if self.auth:
|
||||
if not isinstance(self.auth.handler,
|
||||
@@ -185,14 +188,15 @@ class Request(object):
|
||||
def build(resp):
|
||||
|
||||
response = Response()
|
||||
response.config = self.config
|
||||
response.status_code = getattr(resp, 'code', None)
|
||||
|
||||
try:
|
||||
response.headers = CaseInsensitiveDict(getattr(resp.info(), 'dict', None))
|
||||
response.raw = resp
|
||||
|
||||
if self.cookiejar:
|
||||
response.cookies = dict_from_cookiejar(self.cookiejar)
|
||||
if self.cookies:
|
||||
response.cookies = dict_from_cookiejar(self.cookies)
|
||||
|
||||
|
||||
except AttributeError:
|
||||
@@ -219,7 +223,7 @@ class Request(object):
|
||||
|
||||
r.raw.close()
|
||||
|
||||
if not len(history) < settings.max_redirects:
|
||||
if not len(history) < self.config.get('max_redirects'):
|
||||
raise TooManyRedirects()
|
||||
|
||||
history.append(r)
|
||||
@@ -243,9 +247,16 @@ class Request(object):
|
||||
method = self.method
|
||||
|
||||
request = Request(
|
||||
url, self.headers, self.files, method,
|
||||
self.data, self.params, self.auth, self.cookiejar,
|
||||
redirect=True
|
||||
url=url,
|
||||
headers=self.headers,
|
||||
files=self.files,
|
||||
method=method,
|
||||
# data=self.data,
|
||||
# params=self.params,
|
||||
auth=self.auth,
|
||||
cookies=self.cookies,
|
||||
redirect=True,
|
||||
config=self.config
|
||||
)
|
||||
request.send()
|
||||
r = request.response
|
||||
@@ -310,32 +321,37 @@ class Request(object):
|
||||
already been sent.
|
||||
"""
|
||||
|
||||
self._checks()
|
||||
# Some people...
|
||||
if not self.url:
|
||||
raise URLRequired
|
||||
|
||||
# Logging
|
||||
if settings.verbose:
|
||||
settings.verbose.write('%s %s %s\n' % (
|
||||
if self.config.get('verbose'):
|
||||
self.config.get('verbose').write('%s %s %s\n' % (
|
||||
datetime.now().isoformat(), self.method, self.url
|
||||
))
|
||||
|
||||
|
||||
# Build the URL
|
||||
url = self._build_url()
|
||||
if self.method in ('GET', 'HEAD', 'DELETE'):
|
||||
req = _Request(url, method=self.method)
|
||||
|
||||
# Attach uploaded files.
|
||||
if self.files:
|
||||
register_openers()
|
||||
|
||||
# Add form-data to the multipart.
|
||||
if self.data:
|
||||
self.files.update(self.data)
|
||||
|
||||
data, headers = multipart_encode(self.files)
|
||||
|
||||
else:
|
||||
data = self._enc_data
|
||||
headers = {}
|
||||
|
||||
if self.files:
|
||||
register_openers()
|
||||
|
||||
if self.data:
|
||||
self.files.update(self.data)
|
||||
|
||||
datagen, headers = multipart_encode(self.files)
|
||||
req = _Request(url, data=datagen, headers=headers, method=self.method)
|
||||
|
||||
else:
|
||||
req = _Request(url, data=self._enc_data, method=self.method)
|
||||
# Build the Urllib2 Request.
|
||||
req = _Request(url, data=data, headers=headers, method=self.method)
|
||||
|
||||
# Add the headers to the request.
|
||||
if self.headers:
|
||||
for k,v in self.headers.iteritems():
|
||||
req.add_header(k, v)
|
||||
@@ -353,19 +369,19 @@ class Request(object):
|
||||
if not 'timeout' in str(err):
|
||||
raise
|
||||
|
||||
if settings.timeout_fallback:
|
||||
if self.config.get('timeout_fallback'):
|
||||
# fall-back and use global socket timeout (This is not thread-safe!)
|
||||
old_timeout = socket.getdefaulttimeout()
|
||||
socket.setdefaulttimeout(self.timeout)
|
||||
|
||||
resp = opener(req)
|
||||
|
||||
if settings.timeout_fallback:
|
||||
if self.config.get('timeout_fallback'):
|
||||
# restore global timeout
|
||||
socket.setdefaulttimeout(old_timeout)
|
||||
|
||||
if self.cookiejar is not None:
|
||||
self.cookiejar.extract_cookies(resp, req)
|
||||
if self.cookies is not None:
|
||||
self.cookies.extract_cookies(resp, req)
|
||||
|
||||
except (urllib2.HTTPError, urllib2.URLError), why:
|
||||
if hasattr(why, 'reason'):
|
||||
@@ -429,6 +445,9 @@ class Response(object):
|
||||
#: A dictionary of Cookies the server sent back.
|
||||
self.cookies = None
|
||||
|
||||
#: Dictionary of configurations for this request.
|
||||
self.config = None
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<Response [%s]>' % (self.status_code)
|
||||
@@ -460,7 +479,7 @@ class Response(object):
|
||||
if 'gzip' in self.headers.get('content-encoding', ''):
|
||||
gen = stream_decode_gzip(gen)
|
||||
if decode_unicode is None:
|
||||
decode_unicode = settings.decode_unicode
|
||||
decode_unicode = self.config.get('decode_unicode')
|
||||
if decode_unicode:
|
||||
gen = stream_decode_response_unicode(gen, self)
|
||||
return gen
|
||||
@@ -489,7 +508,7 @@ class Response(object):
|
||||
pass
|
||||
|
||||
# Decode unicode content.
|
||||
if settings.decode_unicode:
|
||||
if self.config.get('decode_unicode'):
|
||||
self._content = get_unicode_from_response(self)
|
||||
|
||||
self._content_consumed = True
|
||||
|
||||
+160
-42
@@ -11,9 +11,10 @@ requests (cookies, auth, proxies).
|
||||
|
||||
import cookielib
|
||||
|
||||
from . import api
|
||||
from .utils import add_dict_to_cookiejar
|
||||
|
||||
from .defaults import defaults
|
||||
from .models import Request
|
||||
from .hooks import dispatch_hook
|
||||
from .utils import add_dict_to_cookiejar, cookiejar_from_dict, header_expand
|
||||
|
||||
|
||||
def merge_kwargs(local_kwarg, default_kwarg):
|
||||
@@ -25,6 +26,9 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
if default_kwarg is None:
|
||||
return local_kwarg
|
||||
|
||||
if isinstance(local_kwarg, basestring):
|
||||
return local_kwarg
|
||||
|
||||
if local_kwarg is None:
|
||||
return default_kwarg
|
||||
|
||||
@@ -32,8 +36,6 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
if not hasattr(default_kwarg, 'items'):
|
||||
return local_kwarg
|
||||
|
||||
|
||||
|
||||
# Update new values.
|
||||
kwargs = default_kwarg.copy()
|
||||
kwargs.update(local_kwarg)
|
||||
@@ -49,7 +51,7 @@ def merge_kwargs(local_kwarg, default_kwarg):
|
||||
class Session(object):
|
||||
"""A Requests session."""
|
||||
|
||||
__attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params']
|
||||
__attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params', 'config']
|
||||
|
||||
|
||||
def __init__(self,
|
||||
@@ -59,7 +61,8 @@ class Session(object):
|
||||
timeout=None,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
params=None):
|
||||
params=None,
|
||||
config=None):
|
||||
|
||||
self.headers = headers or {}
|
||||
self.cookies = cookies or {}
|
||||
@@ -68,14 +71,14 @@ class Session(object):
|
||||
self.proxies = proxies or {}
|
||||
self.hooks = hooks or {}
|
||||
self.params = params or {}
|
||||
self.config = config or {}
|
||||
|
||||
for (k, v) in defaults.items():
|
||||
self.config.setdefault(k, v)
|
||||
|
||||
# Set up a CookieJar to be used by default
|
||||
self.cookies = cookielib.FileCookieJar()
|
||||
|
||||
# Map and wrap requests.api methods
|
||||
self._map_api_methods()
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<requests-client at 0x%x>' % (id(self))
|
||||
|
||||
@@ -85,48 +88,163 @@ class Session(object):
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
def _map_api_methods(self):
|
||||
"""Reads each available method from requests.api and decorates
|
||||
them with a wrapper, which inserts any instance-local attributes
|
||||
(from __attrs__) that have been set, combining them with **kwargs.
|
||||
def request(self, method, url,
|
||||
params=None,
|
||||
data=None,
|
||||
headers=None,
|
||||
cookies=None,
|
||||
files=None,
|
||||
auth=None,
|
||||
timeout=None,
|
||||
allow_redirects=False,
|
||||
proxies=None,
|
||||
hooks=None,
|
||||
return_response=True,
|
||||
config=None):
|
||||
|
||||
"""Constructs and sends a :class:`Request <Request>`.
|
||||
Returns :class:`Response <Response>` object.
|
||||
|
||||
: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 or bytes 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`.
|
||||
:param files: (optional) Dictionary of 'filename': file-like-objects for multipart encoding upload.
|
||||
:param auth: (optional) AuthObject to enable Basic HTTP Auth.
|
||||
:param timeout: (optional) Float describing the timeout of the request.
|
||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||
:param return_response: (optional) If False, an un-sent Request object will returned.
|
||||
:param config: (optional) A configuration dictionary.
|
||||
"""
|
||||
|
||||
def pass_args(func):
|
||||
def wrapper_func(*args, **kwargs):
|
||||
method = str(method).upper()
|
||||
|
||||
# Argument collector.
|
||||
_kwargs = {}
|
||||
if cookies is None:
|
||||
cookies = {}
|
||||
|
||||
# If a session request has a cookie_dict, inject the
|
||||
# values into the existing CookieJar instead.
|
||||
if isinstance(kwargs.get('cookies', None), dict):
|
||||
kwargs['cookies'] = add_dict_to_cookiejar(
|
||||
self.cookies, kwargs['cookies']
|
||||
)
|
||||
if isinstance(cookies, dict):
|
||||
cookies = add_dict_to_cookiejar(self.cookies, cookies)
|
||||
|
||||
for attr in self.__attrs__:
|
||||
# for attr in ['headers',]:
|
||||
s_val = self.__dict__.get(attr)
|
||||
r_val = kwargs.get(attr)
|
||||
cookies = cookiejar_from_dict(cookies)
|
||||
|
||||
new_attr = merge_kwargs(r_val, s_val)
|
||||
# Expand header values
|
||||
if headers:
|
||||
for k, v in headers.items() or {}:
|
||||
headers[k] = header_expand(v)
|
||||
|
||||
# Skip attributes that were set to None.
|
||||
if new_attr is not None:
|
||||
_kwargs[attr] = new_attr
|
||||
args = dict(
|
||||
method=method,
|
||||
url=url,
|
||||
data=data,
|
||||
params=params,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
files=files,
|
||||
auth=auth,
|
||||
hooks=hooks,
|
||||
timeout=timeout,
|
||||
allow_redirects=allow_redirects,
|
||||
proxies=proxies,
|
||||
config=config
|
||||
)
|
||||
|
||||
# Make sure we didn't miss anything.
|
||||
for (k, v) in kwargs.items():
|
||||
if k not in _kwargs:
|
||||
_kwargs[k] = v
|
||||
for attr in self.__attrs__:
|
||||
session_val = getattr(self, attr, None)
|
||||
local_val = args.get(attr)
|
||||
|
||||
return func(*args, **_kwargs)
|
||||
args[attr] = merge_kwargs(local_val, session_val)
|
||||
|
||||
return wrapper_func
|
||||
# Arguments manipulation hook.
|
||||
args = dispatch_hook('args', hooks, args)
|
||||
|
||||
r = Request(**args)
|
||||
|
||||
# Pre-request hook.
|
||||
r = dispatch_hook('pre_request', hooks, r)
|
||||
|
||||
# Don't send if asked nicely.
|
||||
if not return_response:
|
||||
return r
|
||||
|
||||
# Send the HTTP Request.
|
||||
r.send()
|
||||
|
||||
# Post-request hook.
|
||||
r = dispatch_hook('post_request', hooks, r)
|
||||
|
||||
# Response manipulation hook.
|
||||
r.response = dispatch_hook('response', hooks, r.response)
|
||||
|
||||
return r.response
|
||||
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
"""Sends a GET request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('GET', url, **kwargs)
|
||||
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
kwargs.setdefault('allow_redirects', True)
|
||||
return self.request('HEAD', url, **kwargs)
|
||||
|
||||
|
||||
def post(self, url, data='', **kwargs):
|
||||
"""Sends a POST request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('post', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def put(self, url, data='', **kwargs):
|
||||
"""Sends a PUT request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('put', url, data=data, **kwargs)
|
||||
|
||||
|
||||
def patch(self, url, data='', **kwargs):
|
||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('patch', url, data='', **kwargs)
|
||||
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
||||
|
||||
:param url: URL for the new :class:`Request` object.
|
||||
:param **kwargs: Optional arguments that ``request`` takes.
|
||||
"""
|
||||
|
||||
return self.request('delete', url, **kwargs)
|
||||
|
||||
# Map and decorate each function available in requests.api
|
||||
map(lambda fn: setattr(self, fn, pass_args(getattr(api, fn))),
|
||||
api.__all__)
|
||||
|
||||
|
||||
def session(**kwargs):
|
||||
|
||||
@@ -56,6 +56,9 @@ _codes = {
|
||||
424: ('failed_dependency', 'dependency'),
|
||||
425: ('unordered_collection', 'unordered'),
|
||||
426: ('upgrade_required', 'upgrade'),
|
||||
428: ('precondition_required', 'precondition'),
|
||||
429: ('too_many_requests', 'too_many'),
|
||||
431: ('header_fields_too_large', 'fields_too_large'),
|
||||
444: ('no_response', 'none'),
|
||||
449: ('retry_with', 'retry'),
|
||||
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
|
||||
|
||||
+25
-13
@@ -8,6 +8,7 @@ import cookielib
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import requests
|
||||
import envoy
|
||||
|
||||
try:
|
||||
@@ -15,11 +16,9 @@ try:
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
from requests.sessions import Session
|
||||
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7045')
|
||||
# TODO: Detect an open port.
|
||||
PORT = os.environ.get('HTTPBIN_PORT', '7077')
|
||||
|
||||
HTTPBIN_URL = 'http://0.0.0.0:%s/' % (PORT)
|
||||
# HTTPBIN_URL = 'http://127.0.0.1:8000/'
|
||||
@@ -47,8 +46,9 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
if not _httpbin:
|
||||
|
||||
self.httpbin = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
|
||||
c = envoy.connect('gunicorn httpbin:app --bind=0.0.0.0:%s' % (PORT))
|
||||
|
||||
self.httpbin = c
|
||||
_httpbin = True
|
||||
time.sleep(1)
|
||||
|
||||
@@ -58,22 +58,34 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
"""Teardown."""
|
||||
# self.httpbin.kill()
|
||||
|
||||
def test_entry_points(self):
|
||||
import requests
|
||||
|
||||
requests.session
|
||||
requests.session().get
|
||||
requests.session().head
|
||||
requests.get
|
||||
requests.head
|
||||
requests.put
|
||||
requests.patch
|
||||
requests.post
|
||||
|
||||
|
||||
|
||||
def test_invalid_url(self):
|
||||
self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET(self):
|
||||
r = requests.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_ALLOW_REDIRECT_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
r = requests.get(httpbin('redirect', '1'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_HTTP_302_GET(self):
|
||||
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
r = requests.get(httpbin('redirect', '1'), allow_redirects=False)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
||||
def test_HTTP_200_OK_GET_WITH_PARAMS(self):
|
||||
@@ -442,7 +454,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
def test_session_HTTP_200_OK_GET(self):
|
||||
|
||||
s = Session()
|
||||
s = requests.session()
|
||||
r = s.get(httpbin('/get'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
@@ -451,7 +463,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
heads = {'User-agent': 'Mozilla/5.0'}
|
||||
|
||||
s = Session()
|
||||
s = requests.session()
|
||||
s.headers = heads
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
@@ -472,7 +484,7 @@ class RequestsTestSuite(unittest.TestCase):
|
||||
|
||||
params = {'a': 'a_test'}
|
||||
|
||||
s = Session()
|
||||
s = requests.session()
|
||||
s.params = params
|
||||
|
||||
# Make 2 requests from Session object, should send header both times
|
||||
|
||||
Reference in New Issue
Block a user