OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 | 2 |
3 """ | 3 """ |
4 requests.session | 4 requests.session |
5 ~~~~~~~~~~~~~~~~ | 5 ~~~~~~~~~~~~~~~~ |
6 | 6 |
7 This module provides a Session object to manage and persist settings across | 7 This module provides a Session object to manage and persist settings across |
8 requests (cookies, auth, proxies). | 8 requests (cookies, auth, proxies). |
9 | 9 |
10 """ | 10 """ |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
64 | 64 |
65 return merged_setting | 65 return merged_setting |
66 | 66 |
67 | 67 |
68 class SessionRedirectMixin(object): | 68 class SessionRedirectMixin(object): |
69 def resolve_redirects(self, resp, req, stream=False, timeout=None, | 69 def resolve_redirects(self, resp, req, stream=False, timeout=None, |
70 verify=True, cert=None, proxies=None): | 70 verify=True, cert=None, proxies=None): |
71 """Receives a Response. Returns a generator of Responses.""" | 71 """Receives a Response. Returns a generator of Responses.""" |
72 | 72 |
73 i = 0 | 73 i = 0 |
74 prepared_request = PreparedRequest() | |
75 prepared_request.body = req.body | |
76 prepared_request.headers = req.headers.copy() | |
77 prepared_request.hooks = req.hooks | |
78 prepared_request.method = req.method | |
79 prepared_request.url = req.url | |
80 | 74 |
81 # ((resp.status_code is codes.see_other)) | 75 # ((resp.status_code is codes.see_other)) |
82 while (('location' in resp.headers and resp.status_code in REDIRECT_STAT
I)): | 76 while (('location' in resp.headers and resp.status_code in REDIRECT_STAT
I)): |
| 77 prepared_request = req.copy() |
83 | 78 |
84 resp.content # Consume socket so it can be released | 79 resp.content # Consume socket so it can be released |
85 | 80 |
86 if i >= self.max_redirects: | 81 if i >= self.max_redirects: |
87 raise TooManyRedirects('Exceeded %s redirects.' % self.max_redir
ects) | 82 raise TooManyRedirects('Exceeded %s redirects.' % self.max_redir
ects) |
88 | 83 |
89 # Release the connection back into the pool. | 84 # Release the connection back into the pool. |
90 resp.close() | 85 resp.close() |
91 | 86 |
92 url = resp.headers['location'] | 87 url = resp.headers['location'] |
93 method = prepared_request.method | 88 method = req.method |
94 | 89 |
95 # Handle redirection without scheme (see: RFC 1808 Section 4) | 90 # Handle redirection without scheme (see: RFC 1808 Section 4) |
96 if url.startswith('//'): | 91 if url.startswith('//'): |
97 parsed_rurl = urlparse(resp.url) | 92 parsed_rurl = urlparse(resp.url) |
98 url = '%s:%s' % (parsed_rurl.scheme, url) | 93 url = '%s:%s' % (parsed_rurl.scheme, url) |
99 | 94 |
| 95 # The scheme should be lower case... |
| 96 if '://' in url: |
| 97 scheme, uri = url.split('://', 1) |
| 98 url = '%s://%s' % (scheme.lower(), uri) |
| 99 |
100 # Facilitate non-RFC2616-compliant 'location' headers | 100 # Facilitate non-RFC2616-compliant 'location' headers |
101 # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/re
source') | 101 # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/re
source') |
102 # Compliant with RFC3986, we percent encode the url. | 102 # Compliant with RFC3986, we percent encode the url. |
103 if not urlparse(url).netloc: | 103 if not urlparse(url).netloc: |
104 url = urljoin(resp.url, requote_uri(url)) | 104 url = urljoin(resp.url, requote_uri(url)) |
105 else: | 105 else: |
106 url = requote_uri(url) | 106 url = requote_uri(url) |
107 | 107 |
108 prepared_request.url = url | 108 prepared_request.url = url |
109 | 109 |
110 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 | 110 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 |
111 if (resp.status_code == codes.see_other and | 111 if (resp.status_code == codes.see_other and |
112 prepared_request.method != 'HEAD'): | 112 method != 'HEAD'): |
113 method = 'GET' | 113 method = 'GET' |
114 | 114 |
115 # Do what the browsers do, despite standards... | 115 # Do what the browsers do, despite standards... |
116 if (resp.status_code in (codes.moved, codes.found) and | 116 if (resp.status_code in (codes.moved, codes.found) and |
117 prepared_request.method not in ('GET', 'HEAD')): | 117 method not in ('GET', 'HEAD')): |
118 method = 'GET' | 118 method = 'GET' |
119 | 119 |
120 prepared_request.method = method | 120 prepared_request.method = method |
121 | 121 |
122 # https://github.com/kennethreitz/requests/issues/1084 | 122 # https://github.com/kennethreitz/requests/issues/1084 |
123 if resp.status_code not in (codes.temporary, codes.resume): | 123 if resp.status_code not in (codes.temporary, codes.resume): |
124 if 'Content-Length' in prepared_request.headers: | 124 if 'Content-Length' in prepared_request.headers: |
125 del prepared_request.headers['Content-Length'] | 125 del prepared_request.headers['Content-Length'] |
126 | 126 |
127 prepared_request.body = None | 127 prepared_request.body = None |
(...skipping 18 matching lines...) Expand all Loading... |
146 | 146 |
147 extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) | 147 extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) |
148 | 148 |
149 i += 1 | 149 i += 1 |
150 yield resp | 150 yield resp |
151 | 151 |
152 | 152 |
153 class Session(SessionRedirectMixin): | 153 class Session(SessionRedirectMixin): |
154 """A Requests session. | 154 """A Requests session. |
155 | 155 |
156 Provides cookie persistience, connection-pooling, and configuration. | 156 Provides cookie persistence, connection-pooling, and configuration. |
157 | 157 |
158 Basic Usage:: | 158 Basic Usage:: |
159 | 159 |
160 >>> import requests | 160 >>> import requests |
161 >>> s = requests.Session() | 161 >>> s = requests.Session() |
162 >>> s.get('http://httpbin.org/get') | 162 >>> s.get('http://httpbin.org/get') |
163 200 | 163 200 |
164 """ | 164 """ |
165 | 165 |
166 __attrs__ = [ | 166 __attrs__ = [ |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
201 #: SSL certificate default. | 201 #: SSL certificate default. |
202 self.cert = None | 202 self.cert = None |
203 | 203 |
204 #: Maximum number of redirects allowed. If the request exceeds this | 204 #: Maximum number of redirects allowed. If the request exceeds this |
205 #: limit, a :class:`TooManyRedirects` exception is raised. | 205 #: limit, a :class:`TooManyRedirects` exception is raised. |
206 self.max_redirects = DEFAULT_REDIRECT_LIMIT | 206 self.max_redirects = DEFAULT_REDIRECT_LIMIT |
207 | 207 |
208 #: Should we trust the environment? | 208 #: Should we trust the environment? |
209 self.trust_env = True | 209 self.trust_env = True |
210 | 210 |
211 # Set up a CookieJar to be used by default | 211 #: A CookieJar containing all currently outstanding cookies set on this |
| 212 #: session. By default it is a |
| 213 #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but |
| 214 #: may be any other ``cookielib.CookieJar`` compatible object. |
212 self.cookies = cookiejar_from_dict({}) | 215 self.cookies = cookiejar_from_dict({}) |
213 | 216 |
214 # Default connection adapters. | 217 # Default connection adapters. |
215 self.adapters = OrderedDict() | 218 self.adapters = OrderedDict() |
216 self.mount('https://', HTTPAdapter()) | 219 self.mount('https://', HTTPAdapter()) |
217 self.mount('http://', HTTPAdapter()) | 220 self.mount('http://', HTTPAdapter()) |
218 | 221 |
219 def __enter__(self): | 222 def __enter__(self): |
220 return self | 223 return self |
221 | 224 |
222 def __exit__(self, *args): | 225 def __exit__(self, *args): |
223 self.close() | 226 self.close() |
224 | 227 |
| 228 def prepare_request(self, request): |
| 229 """Constructs a :class:`PreparedRequest <PreparedRequest>` for |
| 230 transmission and returns it. The :class:`PreparedRequest` has settings |
| 231 merged from the :class:`Request <Request>` instance and those of the |
| 232 :class:`Session`. |
| 233 |
| 234 :param request: :class:`Request` instance to prepare with this |
| 235 session's settings. |
| 236 """ |
| 237 cookies = request.cookies or {} |
| 238 |
| 239 # Bootstrap CookieJar. |
| 240 if not isinstance(cookies, cookielib.CookieJar): |
| 241 cookies = cookiejar_from_dict(cookies) |
| 242 |
| 243 # Merge with session cookies |
| 244 merged_cookies = RequestsCookieJar() |
| 245 merged_cookies.update(self.cookies) |
| 246 merged_cookies.update(cookies) |
| 247 |
| 248 |
| 249 # Set environment's basic authentication if not explicitly set. |
| 250 auth = request.auth |
| 251 if self.trust_env and not auth and not self.auth: |
| 252 auth = get_netrc_auth(request.url) |
| 253 |
| 254 p = PreparedRequest() |
| 255 p.prepare( |
| 256 method=request.method.upper(), |
| 257 url=request.url, |
| 258 files=request.files, |
| 259 data=request.data, |
| 260 headers=merge_setting(request.headers, self.headers, dict_class=Case
InsensitiveDict), |
| 261 params=merge_setting(request.params, self.params), |
| 262 auth=merge_setting(auth, self.auth), |
| 263 cookies=merged_cookies, |
| 264 hooks=merge_setting(request.hooks, self.hooks), |
| 265 ) |
| 266 return p |
| 267 |
225 def request(self, method, url, | 268 def request(self, method, url, |
226 params=None, | 269 params=None, |
227 data=None, | 270 data=None, |
228 headers=None, | 271 headers=None, |
229 cookies=None, | 272 cookies=None, |
230 files=None, | 273 files=None, |
231 auth=None, | 274 auth=None, |
232 timeout=None, | 275 timeout=None, |
233 allow_redirects=True, | 276 allow_redirects=True, |
234 proxies=None, | 277 proxies=None, |
(...skipping 23 matching lines...) Expand all Loading... |
258 :param allow_redirects: (optional) Boolean. Set to True by default. | 301 :param allow_redirects: (optional) Boolean. Set to True by default. |
259 :param proxies: (optional) Dictionary mapping protocol to the URL of | 302 :param proxies: (optional) Dictionary mapping protocol to the URL of |
260 the proxy. | 303 the proxy. |
261 :param stream: (optional) whether to immediately download the response | 304 :param stream: (optional) whether to immediately download the response |
262 content. Defaults to ``False``. | 305 content. Defaults to ``False``. |
263 :param verify: (optional) if ``True``, the SSL cert will be verified. | 306 :param verify: (optional) if ``True``, the SSL cert will be verified. |
264 A CA_BUNDLE path can also be provided. | 307 A CA_BUNDLE path can also be provided. |
265 :param cert: (optional) if String, path to ssl client cert file (.pem). | 308 :param cert: (optional) if String, path to ssl client cert file (.pem). |
266 If Tuple, ('cert', 'key') pair. | 309 If Tuple, ('cert', 'key') pair. |
267 """ | 310 """ |
| 311 # Create the Request. |
| 312 req = Request( |
| 313 method = method.upper(), |
| 314 url = url, |
| 315 headers = headers, |
| 316 files = files, |
| 317 data = data or {}, |
| 318 params = params or {}, |
| 319 auth = auth, |
| 320 cookies = cookies, |
| 321 hooks = hooks, |
| 322 ) |
| 323 prep = self.prepare_request(req) |
268 | 324 |
269 cookies = cookies or {} | |
270 proxies = proxies or {} | 325 proxies = proxies or {} |
271 | 326 |
272 # Bootstrap CookieJar. | |
273 if not isinstance(cookies, cookielib.CookieJar): | |
274 cookies = cookiejar_from_dict(cookies) | |
275 | |
276 # Merge with session cookies | |
277 merged_cookies = RequestsCookieJar() | |
278 merged_cookies.update(self.cookies) | |
279 merged_cookies.update(cookies) | |
280 cookies = merged_cookies | |
281 | |
282 # Gather clues from the surrounding environment. | 327 # Gather clues from the surrounding environment. |
283 if self.trust_env: | 328 if self.trust_env: |
284 # Set environment's proxies. | 329 # Set environment's proxies. |
285 env_proxies = get_environ_proxies(url) or {} | 330 env_proxies = get_environ_proxies(url) or {} |
286 for (k, v) in env_proxies.items(): | 331 for (k, v) in env_proxies.items(): |
287 proxies.setdefault(k, v) | 332 proxies.setdefault(k, v) |
288 | 333 |
289 # Set environment's basic authentication. | |
290 if not auth: | |
291 auth = get_netrc_auth(url) | |
292 | |
293 # Look for configuration. | 334 # Look for configuration. |
294 if not verify and verify is not False: | 335 if not verify and verify is not False: |
295 verify = os.environ.get('REQUESTS_CA_BUNDLE') | 336 verify = os.environ.get('REQUESTS_CA_BUNDLE') |
296 | 337 |
297 # Curl compatibility. | 338 # Curl compatibility. |
298 if not verify and verify is not False: | 339 if not verify and verify is not False: |
299 verify = os.environ.get('CURL_CA_BUNDLE') | 340 verify = os.environ.get('CURL_CA_BUNDLE') |
300 | 341 |
301 # Merge all the kwargs. | 342 # Merge all the kwargs. |
302 params = merge_setting(params, self.params) | |
303 headers = merge_setting(headers, self.headers, dict_class=CaseInsensitiv
eDict) | |
304 auth = merge_setting(auth, self.auth) | |
305 proxies = merge_setting(proxies, self.proxies) | 343 proxies = merge_setting(proxies, self.proxies) |
306 hooks = merge_setting(hooks, self.hooks) | |
307 stream = merge_setting(stream, self.stream) | 344 stream = merge_setting(stream, self.stream) |
308 verify = merge_setting(verify, self.verify) | 345 verify = merge_setting(verify, self.verify) |
309 cert = merge_setting(cert, self.cert) | 346 cert = merge_setting(cert, self.cert) |
310 | 347 |
311 # Create the Request. | |
312 req = Request() | |
313 req.method = method.upper() | |
314 req.url = url | |
315 req.headers = headers | |
316 req.files = files | |
317 req.data = data | |
318 req.params = params | |
319 req.auth = auth | |
320 req.cookies = cookies | |
321 req.hooks = hooks | |
322 | |
323 # Prepare the Request. | |
324 prep = req.prepare() | |
325 | |
326 # Send the request. | 348 # Send the request. |
327 send_kwargs = { | 349 send_kwargs = { |
328 'stream': stream, | 350 'stream': stream, |
329 'timeout': timeout, | 351 'timeout': timeout, |
330 'verify': verify, | 352 'verify': verify, |
331 'cert': cert, | 353 'cert': cert, |
332 'proxies': proxies, | 354 'proxies': proxies, |
333 'allow_redirects': allow_redirects, | 355 'allow_redirects': allow_redirects, |
334 } | 356 } |
335 resp = self.send(prep, **send_kwargs) | 357 resp = self.send(prep, **send_kwargs) |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
409 """Send a given PreparedRequest.""" | 431 """Send a given PreparedRequest.""" |
410 # Set defaults that the hooks can utilize to ensure they always have | 432 # Set defaults that the hooks can utilize to ensure they always have |
411 # the correct parameters to reproduce the previous request. | 433 # the correct parameters to reproduce the previous request. |
412 kwargs.setdefault('stream', self.stream) | 434 kwargs.setdefault('stream', self.stream) |
413 kwargs.setdefault('verify', self.verify) | 435 kwargs.setdefault('verify', self.verify) |
414 kwargs.setdefault('cert', self.cert) | 436 kwargs.setdefault('cert', self.cert) |
415 kwargs.setdefault('proxies', self.proxies) | 437 kwargs.setdefault('proxies', self.proxies) |
416 | 438 |
417 # It's possible that users might accidentally send a Request object. | 439 # It's possible that users might accidentally send a Request object. |
418 # Guard against that specific failure case. | 440 # Guard against that specific failure case. |
419 if getattr(request, 'prepare', None): | 441 if not isinstance(request, PreparedRequest): |
420 raise ValueError('You can only send PreparedRequests.') | 442 raise ValueError('You can only send PreparedRequests.') |
421 | 443 |
422 # Set up variables needed for resolve_redirects and dispatching of | 444 # Set up variables needed for resolve_redirects and dispatching of |
423 # hooks | 445 # hooks |
424 allow_redirects = kwargs.pop('allow_redirects', True) | 446 allow_redirects = kwargs.pop('allow_redirects', True) |
425 stream = kwargs.get('stream') | 447 stream = kwargs.get('stream') |
426 timeout = kwargs.get('timeout') | 448 timeout = kwargs.get('timeout') |
427 verify = kwargs.get('verify') | 449 verify = kwargs.get('verify') |
428 cert = kwargs.get('cert') | 450 cert = kwargs.get('cert') |
429 proxies = kwargs.get('proxies') | 451 proxies = kwargs.get('proxies') |
430 hooks = request.hooks | 452 hooks = request.hooks |
431 | 453 |
432 # Get the appropriate adapter to use | 454 # Get the appropriate adapter to use |
433 adapter = self.get_adapter(url=request.url) | 455 adapter = self.get_adapter(url=request.url) |
434 | 456 |
435 # Start time (approximately) of the request | 457 # Start time (approximately) of the request |
436 start = datetime.utcnow() | 458 start = datetime.utcnow() |
437 # Send the request | 459 # Send the request |
438 r = adapter.send(request, **kwargs) | 460 r = adapter.send(request, **kwargs) |
439 # Total elapsed time of the request (approximately) | 461 # Total elapsed time of the request (approximately) |
440 r.elapsed = datetime.utcnow() - start | 462 r.elapsed = datetime.utcnow() - start |
441 | 463 |
442 # Response manipulation hooks | 464 # Response manipulation hooks |
443 r = dispatch_hook('response', hooks, r, **kwargs) | 465 r = dispatch_hook('response', hooks, r, **kwargs) |
444 | 466 |
445 # Persist cookies | 467 # Persist cookies |
| 468 if r.history: |
| 469 # If the hooks create history then we want those cookies too |
| 470 for resp in r.history: |
| 471 extract_cookies_to_jar(self.cookies, resp.request, resp.raw) |
446 extract_cookies_to_jar(self.cookies, request, r.raw) | 472 extract_cookies_to_jar(self.cookies, request, r.raw) |
447 | 473 |
448 # Redirect resolving generator. | 474 # Redirect resolving generator. |
449 gen = self.resolve_redirects(r, request, stream=stream, | 475 gen = self.resolve_redirects(r, request, stream=stream, |
450 timeout=timeout, verify=verify, cert=cert, | 476 timeout=timeout, verify=verify, cert=cert, |
451 proxies=proxies) | 477 proxies=proxies) |
452 | 478 |
453 # Resolve redirects if allowed. | 479 # Resolve redirects if allowed. |
454 history = [resp for resp in gen] if allow_redirects else [] | 480 history = [resp for resp in gen] if allow_redirects else [] |
455 | 481 |
456 # Shuffle things around if there's history. | 482 # Shuffle things around if there's history. |
457 if history: | 483 if history: |
458 # Insert the first (original) request at the start | 484 # Insert the first (original) request at the start |
459 history.insert(0, r) | 485 history.insert(0, r) |
460 # Get the last request made | 486 # Get the last request made |
461 r = history.pop() | 487 r = history.pop() |
462 r.history = tuple(history) | 488 r.history = tuple(history) |
463 | 489 |
464 return r | 490 return r |
465 | 491 |
466 def get_adapter(self, url): | 492 def get_adapter(self, url): |
467 """Returns the appropriate connnection adapter for the given URL.""" | 493 """Returns the appropriate connnection adapter for the given URL.""" |
468 for (prefix, adapter) in self.adapters.items(): | 494 for (prefix, adapter) in self.adapters.items(): |
469 | 495 |
470 if url.startswith(prefix): | 496 if url.lower().startswith(prefix): |
471 return adapter | 497 return adapter |
472 | 498 |
473 # Nothing matches :-/ | 499 # Nothing matches :-/ |
474 raise InvalidSchema("No connection adapters were found for '%s'" % url) | 500 raise InvalidSchema("No connection adapters were found for '%s'" % url) |
475 | 501 |
476 def close(self): | 502 def close(self): |
477 """Closes all adapters and as such the session""" | 503 """Closes all adapters and as such the session""" |
478 for _, v in self.adapters.items(): | 504 for v in self.adapters.values(): |
479 v.close() | 505 v.close() |
480 | 506 |
481 def mount(self, prefix, adapter): | 507 def mount(self, prefix, adapter): |
482 """Registers a connection adapter to a prefix. | 508 """Registers a connection adapter to a prefix. |
483 | 509 |
484 Adapters are sorted in descending order by key length.""" | 510 Adapters are sorted in descending order by key length.""" |
485 self.adapters[prefix] = adapter | 511 self.adapters[prefix] = adapter |
486 keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] | 512 keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] |
487 for key in keys_to_move: | 513 for key in keys_to_move: |
488 self.adapters[key] = self.adapters.pop(key) | 514 self.adapters[key] = self.adapters.pop(key) |
489 | 515 |
490 def __getstate__(self): | 516 def __getstate__(self): |
491 return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__
) | 517 return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__
) |
492 | 518 |
493 def __setstate__(self, state): | 519 def __setstate__(self, state): |
494 for attr, value in state.items(): | 520 for attr, value in state.items(): |
495 setattr(self, attr, value) | 521 setattr(self, attr, value) |
496 | 522 |
497 | 523 |
498 def session(): | 524 def session(): |
499 """Returns a :class:`Session` for context-management.""" | 525 """Returns a :class:`Session` for context-management.""" |
500 | 526 |
501 return Session() | 527 return Session() |
OLD | NEW |