sessions.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. # -*- coding: utf-8 -*-
  2. """
  3. requests.session
  4. ~~~~~~~~~~~~~~~~
  5. This module provides a Session object to manage and persist settings across
  6. requests (cookies, auth, proxies).
  7. """
  8. import os
  9. from collections import Mapping
  10. from datetime import datetime
  11. from .auth import _basic_auth_str
  12. from .compat import cookielib, OrderedDict, urljoin, urlparse
  13. from .cookies import (
  14. cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
  15. from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
  16. from .hooks import default_hooks, dispatch_hook
  17. from .utils import to_key_val_list, default_headers, to_native_string
  18. from .exceptions import (
  19. TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
  20. from .packages.urllib3._collections import RecentlyUsedContainer
  21. from .structures import CaseInsensitiveDict
  22. from .adapters import HTTPAdapter
  23. from .utils import (
  24. requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
  25. get_auth_from_url
  26. )
  27. from .status_codes import codes
  28. # formerly defined here, reexposed here for backward compatibility
  29. from .models import REDIRECT_STATI
  30. REDIRECT_CACHE_SIZE = 1000
  31. def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
  32. """
  33. Determines appropriate setting for a given request, taking into account the
  34. explicit setting on that request, and the setting in the session. If a
  35. setting is a dictionary, they will be merged together using `dict_class`
  36. """
  37. if session_setting is None:
  38. return request_setting
  39. if request_setting is None:
  40. return session_setting
  41. # Bypass if not a dictionary (e.g. verify)
  42. if not (
  43. isinstance(session_setting, Mapping) and
  44. isinstance(request_setting, Mapping)
  45. ):
  46. return request_setting
  47. merged_setting = dict_class(to_key_val_list(session_setting))
  48. merged_setting.update(to_key_val_list(request_setting))
  49. # Remove keys that are set to None. Extract keys first to avoid altering
  50. # the dictionary during iteration.
  51. none_keys = [k for (k, v) in merged_setting.items() if v is None]
  52. for key in none_keys:
  53. del merged_setting[key]
  54. return merged_setting
  55. def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
  56. """
  57. Properly merges both requests and session hooks.
  58. This is necessary because when request_hooks == {'response': []}, the
  59. merge breaks Session hooks entirely.
  60. """
  61. if session_hooks is None or session_hooks.get('response') == []:
  62. return request_hooks
  63. if request_hooks is None or request_hooks.get('response') == []:
  64. return session_hooks
  65. return merge_setting(request_hooks, session_hooks, dict_class)
  66. class SessionRedirectMixin(object):
  67. def resolve_redirects(self, resp, req, stream=False, timeout=None,
  68. verify=True, cert=None, proxies=None, **adapter_kwargs):
  69. """Receives a Response. Returns a generator of Responses."""
  70. i = 0
  71. hist = [] # keep track of history
  72. while resp.is_redirect:
  73. prepared_request = req.copy()
  74. if i > 0:
  75. # Update history and keep track of redirects.
  76. hist.append(resp)
  77. new_hist = list(hist)
  78. resp.history = new_hist
  79. try:
  80. resp.content # Consume socket so it can be released
  81. except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
  82. resp.raw.read(decode_content=False)
  83. if i >= self.max_redirects:
  84. raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
  85. # Release the connection back into the pool.
  86. resp.close()
  87. url = resp.headers['location']
  88. # Handle redirection without scheme (see: RFC 1808 Section 4)
  89. if url.startswith('//'):
  90. parsed_rurl = urlparse(resp.url)
  91. url = '%s:%s' % (parsed_rurl.scheme, url)
  92. # The scheme should be lower case...
  93. parsed = urlparse(url)
  94. url = parsed.geturl()
  95. # Facilitate relative 'location' headers, as allowed by RFC 7231.
  96. # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
  97. # Compliant with RFC3986, we percent encode the url.
  98. if not parsed.netloc:
  99. url = urljoin(resp.url, requote_uri(url))
  100. else:
  101. url = requote_uri(url)
  102. prepared_request.url = to_native_string(url)
  103. # Cache the url, unless it redirects to itself.
  104. if resp.is_permanent_redirect and req.url != prepared_request.url:
  105. self.redirect_cache[req.url] = prepared_request.url
  106. self.rebuild_method(prepared_request, resp)
  107. # https://github.com/kennethreitz/requests/issues/1084
  108. if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
  109. if 'Content-Length' in prepared_request.headers:
  110. del prepared_request.headers['Content-Length']
  111. prepared_request.body = None
  112. headers = prepared_request.headers
  113. try:
  114. del headers['Cookie']
  115. except KeyError:
  116. pass
  117. # Extract any cookies sent on the response to the cookiejar
  118. # in the new request. Because we've mutated our copied prepared
  119. # request, use the old one that we haven't yet touched.
  120. extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
  121. prepared_request._cookies.update(self.cookies)
  122. prepared_request.prepare_cookies(prepared_request._cookies)
  123. # Rebuild auth and proxy information.
  124. proxies = self.rebuild_proxies(prepared_request, proxies)
  125. self.rebuild_auth(prepared_request, resp)
  126. # Override the original request.
  127. req = prepared_request
  128. resp = self.send(
  129. req,
  130. stream=stream,
  131. timeout=timeout,
  132. verify=verify,
  133. cert=cert,
  134. proxies=proxies,
  135. allow_redirects=False,
  136. **adapter_kwargs
  137. )
  138. extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
  139. i += 1
  140. yield resp
  141. def rebuild_auth(self, prepared_request, response):
  142. """
  143. When being redirected we may want to strip authentication from the
  144. request to avoid leaking credentials. This method intelligently removes
  145. and reapplies authentication where possible to avoid credential loss.
  146. """
  147. headers = prepared_request.headers
  148. url = prepared_request.url
  149. if 'Authorization' in headers:
  150. # If we get redirected to a new host, we should strip out any
  151. # authentication headers.
  152. original_parsed = urlparse(response.request.url)
  153. redirect_parsed = urlparse(url)
  154. if (original_parsed.hostname != redirect_parsed.hostname):
  155. del headers['Authorization']
  156. # .netrc might have more auth for us on our new host.
  157. new_auth = get_netrc_auth(url) if self.trust_env else None
  158. if new_auth is not None:
  159. prepared_request.prepare_auth(new_auth)
  160. return
  161. def rebuild_proxies(self, prepared_request, proxies):
  162. """
  163. This method re-evaluates the proxy configuration by considering the
  164. environment variables. If we are redirected to a URL covered by
  165. NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
  166. proxy keys for this URL (in case they were stripped by a previous
  167. redirect).
  168. This method also replaces the Proxy-Authorization header where
  169. necessary.
  170. """
  171. headers = prepared_request.headers
  172. url = prepared_request.url
  173. scheme = urlparse(url).scheme
  174. new_proxies = proxies.copy() if proxies is not None else {}
  175. if self.trust_env and not should_bypass_proxies(url):
  176. environ_proxies = get_environ_proxies(url)
  177. proxy = environ_proxies.get(scheme)
  178. if proxy:
  179. new_proxies.setdefault(scheme, environ_proxies[scheme])
  180. if 'Proxy-Authorization' in headers:
  181. del headers['Proxy-Authorization']
  182. try:
  183. username, password = get_auth_from_url(new_proxies[scheme])
  184. except KeyError:
  185. username, password = None, None
  186. if username and password:
  187. headers['Proxy-Authorization'] = _basic_auth_str(username, password)
  188. return new_proxies
  189. def rebuild_method(self, prepared_request, response):
  190. """When being redirected we may want to change the method of the request
  191. based on certain specs or browser behavior.
  192. """
  193. method = prepared_request.method
  194. # http://tools.ietf.org/html/rfc7231#section-6.4.4
  195. if response.status_code == codes.see_other and method != 'HEAD':
  196. method = 'GET'
  197. # Do what the browsers do, despite standards...
  198. # First, turn 302s into GETs.
  199. if response.status_code == codes.found and method != 'HEAD':
  200. method = 'GET'
  201. # Second, if a POST is responded to with a 301, turn it into a GET.
  202. # This bizarre behaviour is explained in Issue 1704.
  203. if response.status_code == codes.moved and method == 'POST':
  204. method = 'GET'
  205. prepared_request.method = method
  206. class Session(SessionRedirectMixin):
  207. """A Requests session.
  208. Provides cookie persistence, connection-pooling, and configuration.
  209. Basic Usage::
  210. >>> import requests
  211. >>> s = requests.Session()
  212. >>> s.get('http://httpbin.org/get')
  213. <Response [200]>
  214. Or as a context manager::
  215. >>> with requests.Session() as s:
  216. >>> s.get('http://httpbin.org/get')
  217. <Response [200]>
  218. """
  219. __attrs__ = [
  220. 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
  221. 'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
  222. 'max_redirects',
  223. ]
  224. def __init__(self):
  225. #: A case-insensitive dictionary of headers to be sent on each
  226. #: :class:`Request <Request>` sent from this
  227. #: :class:`Session <Session>`.
  228. self.headers = default_headers()
  229. #: Default Authentication tuple or object to attach to
  230. #: :class:`Request <Request>`.
  231. self.auth = None
  232. #: Dictionary mapping protocol or protocol and host to the URL of the proxy
  233. #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
  234. #: be used on each :class:`Request <Request>`.
  235. self.proxies = {}
  236. #: Event-handling hooks.
  237. self.hooks = default_hooks()
  238. #: Dictionary of querystring data to attach to each
  239. #: :class:`Request <Request>`. The dictionary values may be lists for
  240. #: representing multivalued query parameters.
  241. self.params = {}
  242. #: Stream response content default.
  243. self.stream = False
  244. #: SSL Verification default.
  245. self.verify = True
  246. #: SSL certificate default.
  247. self.cert = None
  248. #: Maximum number of redirects allowed. If the request exceeds this
  249. #: limit, a :class:`TooManyRedirects` exception is raised.
  250. self.max_redirects = DEFAULT_REDIRECT_LIMIT
  251. #: Trust environment settings for proxy configuration, default
  252. #: authentication and similar.
  253. self.trust_env = True
  254. #: A CookieJar containing all currently outstanding cookies set on this
  255. #: session. By default it is a
  256. #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
  257. #: may be any other ``cookielib.CookieJar`` compatible object.
  258. self.cookies = cookiejar_from_dict({})
  259. # Default connection adapters.
  260. self.adapters = OrderedDict()
  261. self.mount('https://', HTTPAdapter())
  262. self.mount('http://', HTTPAdapter())
  263. # Only store 1000 redirects to prevent using infinite memory
  264. self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
  265. def __enter__(self):
  266. return self
  267. def __exit__(self, *args):
  268. self.close()
  269. def prepare_request(self, request):
  270. """Constructs a :class:`PreparedRequest <PreparedRequest>` for
  271. transmission and returns it. The :class:`PreparedRequest` has settings
  272. merged from the :class:`Request <Request>` instance and those of the
  273. :class:`Session`.
  274. :param request: :class:`Request` instance to prepare with this
  275. session's settings.
  276. """
  277. cookies = request.cookies or {}
  278. # Bootstrap CookieJar.
  279. if not isinstance(cookies, cookielib.CookieJar):
  280. cookies = cookiejar_from_dict(cookies)
  281. # Merge with session cookies
  282. merged_cookies = merge_cookies(
  283. merge_cookies(RequestsCookieJar(), self.cookies), cookies)
  284. # Set environment's basic authentication if not explicitly set.
  285. auth = request.auth
  286. if self.trust_env and not auth and not self.auth:
  287. auth = get_netrc_auth(request.url)
  288. p = PreparedRequest()
  289. p.prepare(
  290. method=request.method.upper(),
  291. url=request.url,
  292. files=request.files,
  293. data=request.data,
  294. json=request.json,
  295. headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
  296. params=merge_setting(request.params, self.params),
  297. auth=merge_setting(auth, self.auth),
  298. cookies=merged_cookies,
  299. hooks=merge_hooks(request.hooks, self.hooks),
  300. )
  301. return p
  302. def request(self, method, url,
  303. params=None,
  304. data=None,
  305. headers=None,
  306. cookies=None,
  307. files=None,
  308. auth=None,
  309. timeout=None,
  310. allow_redirects=True,
  311. proxies=None,
  312. hooks=None,
  313. stream=None,
  314. verify=None,
  315. cert=None,
  316. json=None):
  317. """Constructs a :class:`Request <Request>`, prepares it and sends it.
  318. Returns :class:`Response <Response>` object.
  319. :param method: method for the new :class:`Request` object.
  320. :param url: URL for the new :class:`Request` object.
  321. :param params: (optional) Dictionary or bytes to be sent in the query
  322. string for the :class:`Request`.
  323. :param data: (optional) Dictionary, bytes, or file-like object to send
  324. in the body of the :class:`Request`.
  325. :param json: (optional) json to send in the body of the
  326. :class:`Request`.
  327. :param headers: (optional) Dictionary of HTTP Headers to send with the
  328. :class:`Request`.
  329. :param cookies: (optional) Dict or CookieJar object to send with the
  330. :class:`Request`.
  331. :param files: (optional) Dictionary of ``'filename': file-like-objects``
  332. for multipart encoding upload.
  333. :param auth: (optional) Auth tuple or callable to enable
  334. Basic/Digest/Custom HTTP Auth.
  335. :param timeout: (optional) How long to wait for the server to send
  336. data before giving up, as a float, or a :ref:`(connect timeout,
  337. read timeout) <timeouts>` tuple.
  338. :type timeout: float or tuple
  339. :param allow_redirects: (optional) Set to True by default.
  340. :type allow_redirects: bool
  341. :param proxies: (optional) Dictionary mapping protocol or protocol and
  342. hostname to the URL of the proxy.
  343. :param stream: (optional) whether to immediately download the response
  344. content. Defaults to ``False``.
  345. :param verify: (optional) whether the SSL cert will be verified.
  346. A CA_BUNDLE path can also be provided. Defaults to ``True``.
  347. :param cert: (optional) if String, path to ssl client cert file (.pem).
  348. If Tuple, ('cert', 'key') pair.
  349. :rtype: requests.Response
  350. """
  351. # Create the Request.
  352. req = Request(
  353. method = method.upper(),
  354. url = url,
  355. headers = headers,
  356. files = files,
  357. data = data or {},
  358. json = json,
  359. params = params or {},
  360. auth = auth,
  361. cookies = cookies,
  362. hooks = hooks,
  363. )
  364. prep = self.prepare_request(req)
  365. proxies = proxies or {}
  366. settings = self.merge_environment_settings(
  367. prep.url, proxies, stream, verify, cert
  368. )
  369. # Send the request.
  370. send_kwargs = {
  371. 'timeout': timeout,
  372. 'allow_redirects': allow_redirects,
  373. }
  374. send_kwargs.update(settings)
  375. resp = self.send(prep, **send_kwargs)
  376. return resp
  377. def get(self, url, **kwargs):
  378. """Sends a GET request. Returns :class:`Response` object.
  379. :param url: URL for the new :class:`Request` object.
  380. :param \*\*kwargs: Optional arguments that ``request`` takes.
  381. """
  382. kwargs.setdefault('allow_redirects', True)
  383. return self.request('GET', url, **kwargs)
  384. def options(self, url, **kwargs):
  385. """Sends a OPTIONS request. Returns :class:`Response` object.
  386. :param url: URL for the new :class:`Request` object.
  387. :param \*\*kwargs: Optional arguments that ``request`` takes.
  388. """
  389. kwargs.setdefault('allow_redirects', True)
  390. return self.request('OPTIONS', url, **kwargs)
  391. def head(self, url, **kwargs):
  392. """Sends a HEAD request. Returns :class:`Response` object.
  393. :param url: URL for the new :class:`Request` object.
  394. :param \*\*kwargs: Optional arguments that ``request`` takes.
  395. """
  396. kwargs.setdefault('allow_redirects', False)
  397. return self.request('HEAD', url, **kwargs)
  398. def post(self, url, data=None, json=None, **kwargs):
  399. """Sends a POST request. Returns :class:`Response` object.
  400. :param url: URL for the new :class:`Request` object.
  401. :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
  402. :param json: (optional) json to send in the body of the :class:`Request`.
  403. :param \*\*kwargs: Optional arguments that ``request`` takes.
  404. """
  405. return self.request('POST', url, data=data, json=json, **kwargs)
  406. def put(self, url, data=None, **kwargs):
  407. """Sends a PUT request. Returns :class:`Response` object.
  408. :param url: URL for the new :class:`Request` object.
  409. :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
  410. :param \*\*kwargs: Optional arguments that ``request`` takes.
  411. """
  412. return self.request('PUT', url, data=data, **kwargs)
  413. def patch(self, url, data=None, **kwargs):
  414. """Sends a PATCH request. Returns :class:`Response` object.
  415. :param url: URL for the new :class:`Request` object.
  416. :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
  417. :param \*\*kwargs: Optional arguments that ``request`` takes.
  418. """
  419. return self.request('PATCH', url, data=data, **kwargs)
  420. def delete(self, url, **kwargs):
  421. """Sends a DELETE request. Returns :class:`Response` object.
  422. :param url: URL for the new :class:`Request` object.
  423. :param \*\*kwargs: Optional arguments that ``request`` takes.
  424. """
  425. return self.request('DELETE', url, **kwargs)
  426. def send(self, request, **kwargs):
  427. """Send a given PreparedRequest."""
  428. # Set defaults that the hooks can utilize to ensure they always have
  429. # the correct parameters to reproduce the previous request.
  430. kwargs.setdefault('stream', self.stream)
  431. kwargs.setdefault('verify', self.verify)
  432. kwargs.setdefault('cert', self.cert)
  433. kwargs.setdefault('proxies', self.proxies)
  434. # It's possible that users might accidentally send a Request object.
  435. # Guard against that specific failure case.
  436. if isinstance(request, Request):
  437. raise ValueError('You can only send PreparedRequests.')
  438. # Set up variables needed for resolve_redirects and dispatching of hooks
  439. allow_redirects = kwargs.pop('allow_redirects', True)
  440. stream = kwargs.get('stream')
  441. hooks = request.hooks
  442. # Resolve URL in redirect cache, if available.
  443. if allow_redirects:
  444. checked_urls = set()
  445. while request.url in self.redirect_cache:
  446. checked_urls.add(request.url)
  447. new_url = self.redirect_cache.get(request.url)
  448. if new_url in checked_urls:
  449. break
  450. request.url = new_url
  451. # Get the appropriate adapter to use
  452. adapter = self.get_adapter(url=request.url)
  453. # Start time (approximately) of the request
  454. start = datetime.utcnow()
  455. # Send the request
  456. r = adapter.send(request, **kwargs)
  457. # Total elapsed time of the request (approximately)
  458. r.elapsed = datetime.utcnow() - start
  459. # Response manipulation hooks
  460. r = dispatch_hook('response', hooks, r, **kwargs)
  461. # Persist cookies
  462. if r.history:
  463. # If the hooks create history then we want those cookies too
  464. for resp in r.history:
  465. extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
  466. extract_cookies_to_jar(self.cookies, request, r.raw)
  467. # Redirect resolving generator.
  468. gen = self.resolve_redirects(r, request, **kwargs)
  469. # Resolve redirects if allowed.
  470. history = [resp for resp in gen] if allow_redirects else []
  471. # Shuffle things around if there's history.
  472. if history:
  473. # Insert the first (original) request at the start
  474. history.insert(0, r)
  475. # Get the last request made
  476. r = history.pop()
  477. r.history = history
  478. if not stream:
  479. r.content
  480. return r
  481. def merge_environment_settings(self, url, proxies, stream, verify, cert):
  482. """Check the environment and merge it with some settings."""
  483. # Gather clues from the surrounding environment.
  484. if self.trust_env:
  485. # Set environment's proxies.
  486. env_proxies = get_environ_proxies(url) or {}
  487. for (k, v) in env_proxies.items():
  488. proxies.setdefault(k, v)
  489. # Look for requests environment configuration and be compatible
  490. # with cURL.
  491. if verify is True or verify is None:
  492. verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
  493. os.environ.get('CURL_CA_BUNDLE'))
  494. # Merge all the kwargs.
  495. proxies = merge_setting(proxies, self.proxies)
  496. stream = merge_setting(stream, self.stream)
  497. verify = merge_setting(verify, self.verify)
  498. cert = merge_setting(cert, self.cert)
  499. return {'verify': verify, 'proxies': proxies, 'stream': stream,
  500. 'cert': cert}
  501. def get_adapter(self, url):
  502. """Returns the appropriate connection adapter for the given URL."""
  503. for (prefix, adapter) in self.adapters.items():
  504. if url.lower().startswith(prefix):
  505. return adapter
  506. # Nothing matches :-/
  507. raise InvalidSchema("No connection adapters were found for '%s'" % url)
  508. def close(self):
  509. """Closes all adapters and as such the session"""
  510. for v in self.adapters.values():
  511. v.close()
  512. def mount(self, prefix, adapter):
  513. """Registers a connection adapter to a prefix.
  514. Adapters are sorted in descending order by key length."""
  515. self.adapters[prefix] = adapter
  516. keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
  517. for key in keys_to_move:
  518. self.adapters[key] = self.adapters.pop(key)
  519. def __getstate__(self):
  520. state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
  521. state['redirect_cache'] = dict(self.redirect_cache)
  522. return state
  523. def __setstate__(self, state):
  524. redirect_cache = state.pop('redirect_cache', {})
  525. for attr, value in state.items():
  526. setattr(self, attr, value)
  527. self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)
  528. for redirect, to in redirect_cache.items():
  529. self.redirect_cache[redirect] = to
  530. def session():
  531. """Returns a :class:`Session` for context-management."""
  532. return Session()