You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
11 KiB
328 lines
11 KiB
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
requests.session
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
This module provides a Session object to manage and persist settings across
|
|
requests (cookies, auth, proxies).
|
|
|
|
"""
|
|
|
|
from copy import deepcopy
|
|
from .compat import cookielib
|
|
from .cookies import cookiejar_from_dict, remove_cookie_by_name
|
|
from .defaults import defaults
|
|
from .models import Request
|
|
from .hooks import dispatch_hook
|
|
from .utils import header_expand, from_key_val_list
|
|
from .packages.urllib3.poolmanager import PoolManager
|
|
|
|
|
|
def merge_kwargs(local_kwarg, default_kwarg):
|
|
"""Merges kwarg dictionaries.
|
|
|
|
If a local key in the dictionary is set to None, it will be removed.
|
|
"""
|
|
|
|
if default_kwarg is None:
|
|
return local_kwarg
|
|
|
|
if isinstance(local_kwarg, str):
|
|
return local_kwarg
|
|
|
|
if local_kwarg is None:
|
|
return default_kwarg
|
|
|
|
# Bypass if not a dictionary (e.g. timeout)
|
|
if not hasattr(default_kwarg, 'items'):
|
|
return local_kwarg
|
|
|
|
default_kwarg = from_key_val_list(default_kwarg)
|
|
local_kwarg = from_key_val_list(local_kwarg)
|
|
|
|
# Update new values.
|
|
kwargs = default_kwarg.copy()
|
|
kwargs.update(local_kwarg)
|
|
|
|
# Remove keys that are set to None.
|
|
for (k, v) in local_kwarg.items():
|
|
if v is None:
|
|
del kwargs[k]
|
|
|
|
return kwargs
|
|
|
|
|
|
class Session(object):
|
|
"""A Requests session."""
|
|
|
|
__attrs__ = [
|
|
'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks',
|
|
'params', 'config', 'verify', 'cert', 'prefetch']
|
|
|
|
def __init__(self,
|
|
headers=None,
|
|
cookies=None,
|
|
auth=None,
|
|
timeout=None,
|
|
proxies=None,
|
|
hooks=None,
|
|
params=None,
|
|
config=None,
|
|
prefetch=True,
|
|
verify=True,
|
|
cert=None):
|
|
|
|
self.headers = from_key_val_list(headers or [])
|
|
self.auth = auth
|
|
self.timeout = timeout
|
|
self.proxies = from_key_val_list(proxies or [])
|
|
self.hooks = from_key_val_list(hooks or {})
|
|
self.params = from_key_val_list(params or [])
|
|
self.config = from_key_val_list(config or {})
|
|
self.prefetch = prefetch
|
|
self.verify = verify
|
|
self.cert = cert
|
|
|
|
for (k, v) in list(defaults.items()):
|
|
self.config.setdefault(k, deepcopy(v))
|
|
|
|
self.init_poolmanager()
|
|
|
|
# Set up a CookieJar to be used by default
|
|
if isinstance(cookies, cookielib.CookieJar):
|
|
self.cookies = cookies
|
|
else:
|
|
self.cookies = cookiejar_from_dict(cookies)
|
|
|
|
def init_poolmanager(self):
|
|
self.poolmanager = PoolManager(
|
|
num_pools=self.config.get('pool_connections'),
|
|
maxsize=self.config.get('pool_maxsize')
|
|
)
|
|
|
|
def __repr__(self):
|
|
return '<requests-client at 0x%x>' % (id(self))
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
self.close()
|
|
|
|
def close(self):
|
|
"""Dispose of any internal state.
|
|
|
|
Currently, this just closes the PoolManager, which closes pooled
|
|
connections.
|
|
"""
|
|
self.poolmanager.clear()
|
|
|
|
def request(self, method, url,
|
|
params=None,
|
|
data=None,
|
|
headers=None,
|
|
cookies=None,
|
|
files=None,
|
|
auth=None,
|
|
timeout=None,
|
|
allow_redirects=True,
|
|
proxies=None,
|
|
hooks=None,
|
|
return_response=True,
|
|
config=None,
|
|
prefetch=None,
|
|
verify=None,
|
|
cert=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) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth.
|
|
:param timeout: (optional) Float describing the timeout of the request.
|
|
:param allow_redirects: (optional) Boolean. Set to True by default.
|
|
: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. See ``request.defaults`` for allowed keys and their default values.
|
|
:param prefetch: (optional) whether to immediately download the response content. Defaults to ``True``.
|
|
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
|
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
|
"""
|
|
|
|
method = str(method).upper()
|
|
|
|
# Default empty dicts for dict params.
|
|
data = [] if data is None else data
|
|
files = [] if files is None else files
|
|
headers = {} if headers is None else headers
|
|
params = {} if params is None else params
|
|
hooks = {} if hooks is None else hooks
|
|
prefetch = prefetch if prefetch is not None else self.prefetch
|
|
|
|
# use session's hooks as defaults
|
|
for key, cb in list(self.hooks.items()):
|
|
hooks.setdefault(key, cb)
|
|
|
|
# Expand header values.
|
|
if headers:
|
|
for k, v in list(headers.items() or {}):
|
|
headers[k] = header_expand(v)
|
|
|
|
args = dict(
|
|
method=method,
|
|
url=url,
|
|
data=data,
|
|
params=from_key_val_list(params),
|
|
headers=from_key_val_list(headers),
|
|
cookies=cookies,
|
|
files=files,
|
|
auth=auth,
|
|
hooks=from_key_val_list(hooks),
|
|
timeout=timeout,
|
|
allow_redirects=allow_redirects,
|
|
proxies=from_key_val_list(proxies),
|
|
config=from_key_val_list(config),
|
|
prefetch=prefetch,
|
|
verify=verify,
|
|
cert=cert,
|
|
_poolmanager=self.poolmanager
|
|
)
|
|
|
|
# merge session cookies into passed-in ones
|
|
dead_cookies = None
|
|
# passed-in cookies must become a CookieJar:
|
|
if not isinstance(cookies, cookielib.CookieJar):
|
|
args['cookies'] = cookiejar_from_dict(cookies)
|
|
# support unsetting cookies that have been passed in with None values
|
|
# this is only meaningful when `cookies` is a dict ---
|
|
# for a real CookieJar, the client should use session.cookies.clear()
|
|
if cookies is not None:
|
|
dead_cookies = [name for name in cookies if cookies[name] is None]
|
|
# merge the session's cookies into the passed-in cookies:
|
|
for cookie in self.cookies:
|
|
args['cookies'].set_cookie(cookie)
|
|
# remove the unset cookies from the jar we'll be using with the current request
|
|
# (but not from the session's own store of cookies):
|
|
if dead_cookies is not None:
|
|
for name in dead_cookies:
|
|
remove_cookie_by_name(args['cookies'], name)
|
|
|
|
# Merge local kwargs with session kwargs.
|
|
for attr in self.__attrs__:
|
|
# we already merged cookies:
|
|
if attr == 'cookies':
|
|
continue
|
|
|
|
session_val = getattr(self, attr, None)
|
|
local_val = args.get(attr)
|
|
args[attr] = merge_kwargs(local_val, session_val)
|
|
|
|
# Arguments manipulation hook.
|
|
args = dispatch_hook('args', args['hooks'], args)
|
|
|
|
# Create the (empty) response.
|
|
r = Request(**args)
|
|
|
|
# Give the response some context.
|
|
r.session = self
|
|
|
|
# Don't send if asked nicely.
|
|
if not return_response:
|
|
return r
|
|
|
|
# Send the HTTP Request.
|
|
r.send(prefetch=prefetch)
|
|
|
|
# Return the 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 options(self, url, **kwargs):
|
|
"""Sends a OPTIONS 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('options', 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', False)
|
|
return self.request('head', url, **kwargs)
|
|
|
|
def post(self, url, data=None, **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=None, **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=None, **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=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)
|
|
|
|
def __getstate__(self):
|
|
return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
|
|
|
|
def __setstate__(self, state):
|
|
for attr, value in state.items():
|
|
setattr(self, attr, value)
|
|
|
|
self.init_poolmanager()
|
|
|
|
|
|
def session(**kwargs):
|
|
"""Returns a :class:`Session` for context-management."""
|
|
|
|
return Session(**kwargs)
|
|
|