1# -*- coding: utf-8 -*-
2
3"""
4requests.session
5~~~~~~~~~~~~~~~~
6
7This module provides a Session object to manage and persist settings across
8requests (cookies, auth, proxies).
9"""
10import os
11import sys
12import time
13from datetime import timedelta
14
15from .auth import _basic_auth_str
16from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping
17from .cookies import (
18    cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
19from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
20from .hooks import default_hooks, dispatch_hook
21from ._internal_utils import to_native_string
22from .utils import to_key_val_list, default_headers, DEFAULT_PORTS
23from .exceptions import (
24    TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
25
26from .structures import CaseInsensitiveDict
27from .adapters import HTTPAdapter
28
29from .utils import (
30    requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
31    get_auth_from_url, rewind_body
32)
33
34from .status_codes import codes
35
36# formerly defined here, reexposed here for backward compatibility
37from .models import REDIRECT_STATI
38
39# Preferred clock, based on which one is more accurate on a given system.
40if sys.platform == 'win32':
41    try:  # Python 3.4+
42        preferred_clock = time.perf_counter
43    except AttributeError:  # Earlier than Python 3.
44        preferred_clock = time.clock
45else:
46    preferred_clock = time.time
47
48
49def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
50    """Determines appropriate setting for a given request, taking into account
51    the explicit setting on that request, and the setting in the session. If a
52    setting is a dictionary, they will be merged together using `dict_class`
53    """
54
55    if session_setting is None:
56        return request_setting
57
58    if request_setting is None:
59        return session_setting
60
61    # Bypass if not a dictionary (e.g. verify)
62    if not (
63            isinstance(session_setting, Mapping) and
64            isinstance(request_setting, Mapping)
65    ):
66        return request_setting
67
68    merged_setting = dict_class(to_key_val_list(session_setting))
69    merged_setting.update(to_key_val_list(request_setting))
70
71    # Remove keys that are set to None. Extract keys first to avoid altering
72    # the dictionary during iteration.
73    none_keys = [k for (k, v) in merged_setting.items() if v is None]
74    for key in none_keys:
75        del merged_setting[key]
76
77    return merged_setting
78
79
80def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
81    """Properly merges both requests and session hooks.
82
83    This is necessary because when request_hooks == {'response': []}, the
84    merge breaks Session hooks entirely.
85    """
86    if session_hooks is None or session_hooks.get('response') == []:
87        return request_hooks
88
89    if request_hooks is None or request_hooks.get('response') == []:
90        return session_hooks
91
92    return merge_setting(request_hooks, session_hooks, dict_class)
93
94
95class SessionRedirectMixin(object):
96
97    def get_redirect_target(self, resp):
98        """Receives a Response. Returns a redirect URI or ``None``"""
99        # Due to the nature of how requests processes redirects this method will
100        # be called at least once upon the original response and at least twice
101        # on each subsequent redirect response (if any).
102        # If a custom mixin is used to handle this logic, it may be advantageous
103        # to cache the redirect location onto the response object as a private
104        # attribute.
105        if resp.is_redirect:
106            location = resp.headers['location']
107            # Currently the underlying http module on py3 decode headers
108            # in latin1, but empirical evidence suggests that latin1 is very
109            # rarely used with non-ASCII characters in HTTP headers.
110            # It is more likely to get UTF8 header rather than latin1.
111            # This causes incorrect handling of UTF8 encoded location headers.
112            # To solve this, we re-encode the location in latin1.
113            if is_py3:
114                location = location.encode('latin1')
115            return to_native_string(location, 'utf8')
116        return None
117
118    def should_strip_auth(self, old_url, new_url):
119        """Decide whether Authorization header should be removed when redirecting"""
120        old_parsed = urlparse(old_url)
121        new_parsed = urlparse(new_url)
122        if old_parsed.hostname != new_parsed.hostname:
123            return True
124        # Special case: allow http -> https redirect when using the standard
125        # ports. This isn't specified by RFC 7235, but is kept to avoid
126        # breaking backwards compatibility with older versions of requests
127        # that allowed any redirects on the same host.
128        if (old_parsed.scheme == 'http' and old_parsed.port in (80, None)
129                and new_parsed.scheme == 'https' and new_parsed.port in (443, None)):
130            return False
131
132        # Handle default port usage corresponding to scheme.
133        changed_port = old_parsed.port != new_parsed.port
134        changed_scheme = old_parsed.scheme != new_parsed.scheme
135        default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
136        if (not changed_scheme and old_parsed.port in default_port
137                and new_parsed.port in default_port):
138            return False
139
140        # Standard case: root URI must match
141        return changed_port or changed_scheme
142
143    def resolve_redirects(self, resp, req, stream=False, timeout=None,
144                          verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs):
145        """Receives a Response. Returns a generator of Responses or Requests."""
146
147        hist = []  # keep track of history
148
149        url = self.get_redirect_target(resp)
150        previous_fragment = urlparse(req.url).fragment
151        while url:
152            prepared_request = req.copy()
153
154            # Update history and keep track of redirects.
155            # resp.history must ignore the original request in this loop
156            hist.append(resp)
157            resp.history = hist[1:]
158
159            try:
160                resp.content  # Consume socket so it can be released
161            except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
162                resp.raw.read(decode_content=False)
163
164            if len(resp.history) >= self.max_redirects:
165                raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
166
167            # Release the connection back into the pool.
168            resp.close()
169
170            # Handle redirection without scheme (see: RFC 1808 Section 4)
171            if url.startswith('//'):
172                parsed_rurl = urlparse(resp.url)
173                url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url)
174
175            # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
176            parsed = urlparse(url)
177            if parsed.fragment == '' and previous_fragment:
178                parsed = parsed._replace(fragment=previous_fragment)
179            elif parsed.fragment:
180                previous_fragment = parsed.fragment
181            url = parsed.geturl()
182
183            # Facilitate relative 'location' headers, as allowed by RFC 7231.
184            # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
185            # Compliant with RFC3986, we percent encode the url.
186            if not parsed.netloc:
187                url = urljoin(resp.url, requote_uri(url))
188            else:
189                url = requote_uri(url)
190
191            prepared_request.url = to_native_string(url)
192
193            self.rebuild_method(prepared_request, resp)
194
195            # https://github.com/requests/requests/issues/1084
196            if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
197                # https://github.com/requests/requests/issues/3490
198                purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding')
199                for header in purged_headers:
200                    prepared_request.headers.pop(header, None)
201                prepared_request.body = None
202
203            headers = prepared_request.headers
204            try:
205                del headers['Cookie']
206            except KeyError:
207                pass
208
209            # Extract any cookies sent on the response to the cookiejar
210            # in the new request. Because we've mutated our copied prepared
211            # request, use the old one that we haven't yet touched.
212            extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
213            merge_cookies(prepared_request._cookies, self.cookies)
214            prepared_request.prepare_cookies(prepared_request._cookies)
215
216            # Rebuild auth and proxy information.
217            proxies = self.rebuild_proxies(prepared_request, proxies)
218            self.rebuild_auth(prepared_request, resp)
219
220            # A failed tell() sets `_body_position` to `object()`. This non-None
221            # value ensures `rewindable` will be True, allowing us to raise an
222            # UnrewindableBodyError, instead of hanging the connection.
223            rewindable = (
224                prepared_request._body_position is not None and
225                ('Content-Length' in headers or 'Transfer-Encoding' in headers)
226            )
227
228            # Attempt to rewind consumed file-like object.
229            if rewindable:
230                rewind_body(prepared_request)
231
232            # Override the original request.
233            req = prepared_request
234
235            if yield_requests:
236                yield req
237            else:
238
239                resp = self.send(
240                    req,
241                    stream=stream,
242                    timeout=timeout,
243                    verify=verify,
244                    cert=cert,
245                    proxies=proxies,
246                    allow_redirects=False,
247                    **adapter_kwargs
248                )
249
250                extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
251
252                # extract redirect url, if any, for the next loop
253                url = self.get_redirect_target(resp)
254                yield resp
255
256    def rebuild_auth(self, prepared_request, response):
257        """When being redirected we may want to strip authentication from the
258        request to avoid leaking credentials. This method intelligently removes
259        and reapplies authentication where possible to avoid credential loss.
260        """
261        headers = prepared_request.headers
262        url = prepared_request.url
263
264        if 'Authorization' in headers and self.should_strip_auth(response.request.url, url):
265            # If we get redirected to a new host, we should strip out any
266            # authentication headers.
267            del headers['Authorization']
268
269        # .netrc might have more auth for us on our new host.
270        new_auth = get_netrc_auth(url) if self.trust_env else None
271        if new_auth is not None:
272            prepared_request.prepare_auth(new_auth)
273
274        return
275
276    def rebuild_proxies(self, prepared_request, proxies):
277        """This method re-evaluates the proxy configuration by considering the
278        environment variables. If we are redirected to a URL covered by
279        NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
280        proxy keys for this URL (in case they were stripped by a previous
281        redirect).
282
283        This method also replaces the Proxy-Authorization header where
284        necessary.
285
286        :rtype: dict
287        """
288        proxies = proxies if proxies is not None else {}
289        headers = prepared_request.headers
290        url = prepared_request.url
291        scheme = urlparse(url).scheme
292        new_proxies = proxies.copy()
293        no_proxy = proxies.get('no_proxy')
294
295        bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
296        if self.trust_env and not bypass_proxy:
297            environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
298
299            proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
300
301            if proxy:
302                new_proxies.setdefault(scheme, proxy)
303
304        if 'Proxy-Authorization' in headers:
305            del headers['Proxy-Authorization']
306
307        try:
308            username, password = get_auth_from_url(new_proxies[scheme])
309        except KeyError:
310            username, password = None, None
311
312        if username and password:
313            headers['Proxy-Authorization'] = _basic_auth_str(username, password)
314
315        return new_proxies
316
317    def rebuild_method(self, prepared_request, response):
318        """When being redirected we may want to change the method of the request
319        based on certain specs or browser behavior.
320        """
321        method = prepared_request.method
322
323        # https://tools.ietf.org/html/rfc7231#section-6.4.4
324        if response.status_code == codes.see_other and method != 'HEAD':
325            method = 'GET'
326
327        # Do what the browsers do, despite standards...
328        # First, turn 302s into GETs.
329        if response.status_code == codes.found and method != 'HEAD':
330            method = 'GET'
331
332        # Second, if a POST is responded to with a 301, turn it into a GET.
333        # This bizarre behaviour is explained in Issue 1704.
334        if response.status_code == codes.moved and method == 'POST':
335            method = 'GET'
336
337        prepared_request.method = method
338
339
340class Session(SessionRedirectMixin):
341    """A Requests session.
342
343    Provides cookie persistence, connection-pooling, and configuration.
344
345    Basic Usage::
346
347      >>> import requests
348      >>> s = requests.Session()
349      >>> s.get('https://httpbin.org/get')
350      <Response [200]>
351
352    Or as a context manager::
353
354      >>> with requests.Session() as s:
355      >>>     s.get('https://httpbin.org/get')
356      <Response [200]>
357    """
358
359    __attrs__ = [
360        'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
361        'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
362        'max_redirects',
363    ]
364
365    def __init__(self):
366
367        #: A case-insensitive dictionary of headers to be sent on each
368        #: :class:`Request <Request>` sent from this
369        #: :class:`Session <Session>`.
370        self.headers = default_headers()
371
372        #: Default Authentication tuple or object to attach to
373        #: :class:`Request <Request>`.
374        self.auth = None
375
376        #: Dictionary mapping protocol or protocol and host to the URL of the proxy
377        #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
378        #: be used on each :class:`Request <Request>`.
379        self.proxies = {}
380
381        #: Event-handling hooks.
382        self.hooks = default_hooks()
383
384        #: Dictionary of querystring data to attach to each
385        #: :class:`Request <Request>`. The dictionary values may be lists for
386        #: representing multivalued query parameters.
387        self.params = {}
388
389        #: Stream response content default.
390        self.stream = False
391
392        #: SSL Verification default.
393        self.verify = True
394
395        #: SSL client certificate default, if String, path to ssl client
396        #: cert file (.pem). If Tuple, ('cert', 'key') pair.
397        self.cert = None
398
399        #: Maximum number of redirects allowed. If the request exceeds this
400        #: limit, a :class:`TooManyRedirects` exception is raised.
401        #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
402        #: 30.
403        self.max_redirects = DEFAULT_REDIRECT_LIMIT
404
405        #: Trust environment settings for proxy configuration, default
406        #: authentication and similar.
407        self.trust_env = True
408
409        #: A CookieJar containing all currently outstanding cookies set on this
410        #: session. By default it is a
411        #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
412        #: may be any other ``cookielib.CookieJar`` compatible object.
413        self.cookies = cookiejar_from_dict({})
414
415        # Default connection adapters.
416        self.adapters = OrderedDict()
417        self.mount('https://', HTTPAdapter())
418        self.mount('http://', HTTPAdapter())
419
420    def __enter__(self):
421        return self
422
423    def __exit__(self, *args):
424        self.close()
425
426    def prepare_request(self, request):
427        """Constructs a :class:`PreparedRequest <PreparedRequest>` for
428        transmission and returns it. The :class:`PreparedRequest` has settings
429        merged from the :class:`Request <Request>` instance and those of the
430        :class:`Session`.
431
432        :param request: :class:`Request` instance to prepare with this
433            session's settings.
434        :rtype: requests.PreparedRequest
435        """
436        cookies = request.cookies or {}
437
438        # Bootstrap CookieJar.
439        if not isinstance(cookies, cookielib.CookieJar):
440            cookies = cookiejar_from_dict(cookies)
441
442        # Merge with session cookies
443        merged_cookies = merge_cookies(
444            merge_cookies(RequestsCookieJar(), self.cookies), cookies)
445
446        # Set environment's basic authentication if not explicitly set.
447        auth = request.auth
448        if self.trust_env and not auth and not self.auth:
449            auth = get_netrc_auth(request.url)
450
451        p = PreparedRequest()
452        p.prepare(
453            method=request.method.upper(),
454            url=request.url,
455            files=request.files,
456            data=request.data,
457            json=request.json,
458            headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),
459            params=merge_setting(request.params, self.params),
460            auth=merge_setting(auth, self.auth),
461            cookies=merged_cookies,
462            hooks=merge_hooks(request.hooks, self.hooks),
463        )
464        return p
465
466    def request(self, method, url,
467            params=None, data=None, headers=None, cookies=None, files=None,
468            auth=None, timeout=None, allow_redirects=True, proxies=None,
469            hooks=None, stream=None, verify=None, cert=None, json=None):
470        """Constructs a :class:`Request <Request>`, prepares it and sends it.
471        Returns :class:`Response <Response>` object.
472
473        :param method: method for the new :class:`Request` object.
474        :param url: URL for the new :class:`Request` object.
475        :param params: (optional) Dictionary or bytes to be sent in the query
476            string for the :class:`Request`.
477        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
478            object to send in the body of the :class:`Request`.
479        :param json: (optional) json to send in the body of the
480            :class:`Request`.
481        :param headers: (optional) Dictionary of HTTP Headers to send with the
482            :class:`Request`.
483        :param cookies: (optional) Dict or CookieJar object to send with the
484            :class:`Request`.
485        :param files: (optional) Dictionary of ``'filename': file-like-objects``
486            for multipart encoding upload.
487        :param auth: (optional) Auth tuple or callable to enable
488            Basic/Digest/Custom HTTP Auth.
489        :param timeout: (optional) How long to wait for the server to send
490            data before giving up, as a float, or a :ref:`(connect timeout,
491            read timeout) <timeouts>` tuple.
492        :type timeout: float or tuple
493        :param allow_redirects: (optional) Set to True by default.
494        :type allow_redirects: bool
495        :param proxies: (optional) Dictionary mapping protocol or protocol and
496            hostname to the URL of the proxy.
497        :param stream: (optional) whether to immediately download the response
498            content. Defaults to ``False``.
499        :param verify: (optional) Either a boolean, in which case it controls whether we verify
500            the server's TLS certificate, or a string, in which case it must be a path
501            to a CA bundle to use. Defaults to ``True``.
502        :param cert: (optional) if String, path to ssl client cert file (.pem).
503            If Tuple, ('cert', 'key') pair.
504        :rtype: requests.Response
505        """
506        # Create the Request.
507        req = Request(
508            method=method.upper(),
509            url=url,
510            headers=headers,
511            files=files,
512            data=data or {},
513            json=json,
514            params=params or {},
515            auth=auth,
516            cookies=cookies,
517            hooks=hooks,
518        )
519        prep = self.prepare_request(req)
520
521        proxies = proxies or {}
522
523        settings = self.merge_environment_settings(
524            prep.url, proxies, stream, verify, cert
525        )
526
527        # Send the request.
528        send_kwargs = {
529            'timeout': timeout,
530            'allow_redirects': allow_redirects,
531        }
532        send_kwargs.update(settings)
533        resp = self.send(prep, **send_kwargs)
534
535        return resp
536
537    def get(self, url, **kwargs):
538        r"""Sends a GET request. Returns :class:`Response` object.
539
540        :param url: URL for the new :class:`Request` object.
541        :param \*\*kwargs: Optional arguments that ``request`` takes.
542        :rtype: requests.Response
543        """
544
545        kwargs.setdefault('allow_redirects', True)
546        return self.request('GET', url, **kwargs)
547
548    def options(self, url, **kwargs):
549        r"""Sends a OPTIONS request. Returns :class:`Response` object.
550
551        :param url: URL for the new :class:`Request` object.
552        :param \*\*kwargs: Optional arguments that ``request`` takes.
553        :rtype: requests.Response
554        """
555
556        kwargs.setdefault('allow_redirects', True)
557        return self.request('OPTIONS', url, **kwargs)
558
559    def head(self, url, **kwargs):
560        r"""Sends a HEAD request. Returns :class:`Response` object.
561
562        :param url: URL for the new :class:`Request` object.
563        :param \*\*kwargs: Optional arguments that ``request`` takes.
564        :rtype: requests.Response
565        """
566
567        kwargs.setdefault('allow_redirects', False)
568        return self.request('HEAD', url, **kwargs)
569
570    def post(self, url, data=None, json=None, **kwargs):
571        r"""Sends a POST request. Returns :class:`Response` object.
572
573        :param url: URL for the new :class:`Request` object.
574        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
575            object to send in the body of the :class:`Request`.
576        :param json: (optional) json to send in the body of the :class:`Request`.
577        :param \*\*kwargs: Optional arguments that ``request`` takes.
578        :rtype: requests.Response
579        """
580
581        return self.request('POST', url, data=data, json=json, **kwargs)
582
583    def put(self, url, data=None, **kwargs):
584        r"""Sends a PUT request. Returns :class:`Response` object.
585
586        :param url: URL for the new :class:`Request` object.
587        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
588            object to send in the body of the :class:`Request`.
589        :param \*\*kwargs: Optional arguments that ``request`` takes.
590        :rtype: requests.Response
591        """
592
593        return self.request('PUT', url, data=data, **kwargs)
594
595    def patch(self, url, data=None, **kwargs):
596        r"""Sends a PATCH request. Returns :class:`Response` object.
597
598        :param url: URL for the new :class:`Request` object.
599        :param data: (optional) Dictionary, list of tuples, bytes, or file-like
600            object to send in the body of the :class:`Request`.
601        :param \*\*kwargs: Optional arguments that ``request`` takes.
602        :rtype: requests.Response
603        """
604
605        return self.request('PATCH', url, data=data, **kwargs)
606
607    def delete(self, url, **kwargs):
608        r"""Sends a DELETE request. Returns :class:`Response` object.
609
610        :param url: URL for the new :class:`Request` object.
611        :param \*\*kwargs: Optional arguments that ``request`` takes.
612        :rtype: requests.Response
613        """
614
615        return self.request('DELETE', url, **kwargs)
616
617    def send(self, request, **kwargs):
618        """Send a given PreparedRequest.
619
620        :rtype: requests.Response
621        """
622        # Set defaults that the hooks can utilize to ensure they always have
623        # the correct parameters to reproduce the previous request.
624        kwargs.setdefault('stream', self.stream)
625        kwargs.setdefault('verify', self.verify)
626        kwargs.setdefault('cert', self.cert)
627        kwargs.setdefault('proxies', self.proxies)
628
629        # It's possible that users might accidentally send a Request object.
630        # Guard against that specific failure case.
631        if isinstance(request, Request):
632            raise ValueError('You can only send PreparedRequests.')
633
634        # Set up variables needed for resolve_redirects and dispatching of hooks
635        allow_redirects = kwargs.pop('allow_redirects', True)
636        stream = kwargs.get('stream')
637        hooks = request.hooks
638
639        # Get the appropriate adapter to use
640        adapter = self.get_adapter(url=request.url)
641
642        # Start time (approximately) of the request
643        start = preferred_clock()
644
645        # Send the request
646        r = adapter.send(request, **kwargs)
647
648        # Total elapsed time of the request (approximately)
649        elapsed = preferred_clock() - start
650        r.elapsed = timedelta(seconds=elapsed)
651
652        # Response manipulation hooks
653        r = dispatch_hook('response', hooks, r, **kwargs)
654
655        # Persist cookies
656        if r.history:
657
658            # If the hooks create history then we want those cookies too
659            for resp in r.history:
660                extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
661
662        extract_cookies_to_jar(self.cookies, request, r.raw)
663
664        # Redirect resolving generator.
665        gen = self.resolve_redirects(r, request, **kwargs)
666
667        # Resolve redirects if allowed.
668        history = [resp for resp in gen] if allow_redirects else []
669
670        # Shuffle things around if there's history.
671        if history:
672            # Insert the first (original) request at the start
673            history.insert(0, r)
674            # Get the last request made
675            r = history.pop()
676            r.history = history
677
678        # If redirects aren't being followed, store the response on the Request for Response.next().
679        if not allow_redirects:
680            try:
681                r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))
682            except StopIteration:
683                pass
684
685        if not stream:
686            r.content
687
688        return r
689
690    def merge_environment_settings(self, url, proxies, stream, verify, cert):
691        """
692        Check the environment and merge it with some settings.
693
694        :rtype: dict
695        """
696        # Gather clues from the surrounding environment.
697        if self.trust_env:
698            # Set environment's proxies.
699            no_proxy = proxies.get('no_proxy') if proxies is not None else None
700            env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
701            for (k, v) in env_proxies.items():
702                proxies.setdefault(k, v)
703
704            # Look for requests environment configuration and be compatible
705            # with cURL.
706            if verify is True or verify is None:
707                verify = (os.environ.get('REQUESTS_CA_BUNDLE') or
708                          os.environ.get('CURL_CA_BUNDLE'))
709
710        # Merge all the kwargs.
711        proxies = merge_setting(proxies, self.proxies)
712        stream = merge_setting(stream, self.stream)
713        verify = merge_setting(verify, self.verify)
714        cert = merge_setting(cert, self.cert)
715
716        return {'verify': verify, 'proxies': proxies, 'stream': stream,
717                'cert': cert}
718
719    def get_adapter(self, url):
720        """
721        Returns the appropriate connection adapter for the given URL.
722
723        :rtype: requests.adapters.BaseAdapter
724        """
725        for (prefix, adapter) in self.adapters.items():
726
727            if url.lower().startswith(prefix.lower()):
728                return adapter
729
730        # Nothing matches :-/
731        raise InvalidSchema("No connection adapters were found for '%s'" % url)
732
733    def close(self):
734        """Closes all adapters and as such the session"""
735        for v in self.adapters.values():
736            v.close()
737
738    def mount(self, prefix, adapter):
739        """Registers a connection adapter to a prefix.
740
741        Adapters are sorted in descending order by prefix length.
742        """
743        self.adapters[prefix] = adapter
744        keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
745
746        for key in keys_to_move:
747            self.adapters[key] = self.adapters.pop(key)
748
749    def __getstate__(self):
750        state = {attr: getattr(self, attr, None) for attr in self.__attrs__}
751        return state
752
753    def __setstate__(self, state):
754        for attr, value in state.items():
755            setattr(self, attr, value)
756
757
758def session():
759    """
760    Returns a :class:`Session` for context-management.
761
762    .. deprecated:: 1.0.0
763
764        This method has been deprecated since version 1.0.0 and is only kept for
765        backwards compatibility. New code should use :class:`~requests.sessions.Session`
766        to create a session. This may be removed at a future date.
767
768    :rtype: Session
769    """
770    return Session()
771