OLD | NEW |
(Empty) | |
| 1 # -*- coding: utf-8 -*- |
| 2 |
| 3 """ |
| 4 requests.models |
| 5 ~~~~~~~~~~~~~~~ |
| 6 |
| 7 This module contains the primary objects that power Requests. |
| 8 """ |
| 9 |
| 10 import collections |
| 11 import logging |
| 12 import datetime |
| 13 |
| 14 from io import BytesIO |
| 15 from .hooks import default_hooks |
| 16 from .structures import CaseInsensitiveDict |
| 17 |
| 18 from .auth import HTTPBasicAuth |
| 19 from .cookies import cookiejar_from_dict, get_cookie_header |
| 20 from .packages.urllib3.filepost import encode_multipart_formdata |
| 21 from .packages.urllib3.util import parse_url |
| 22 from .exceptions import HTTPError, RequestException, MissingSchema, InvalidURL |
| 23 from .utils import ( |
| 24 guess_filename, get_auth_from_url, requote_uri, |
| 25 stream_decode_response_unicode, to_key_val_list, parse_header_links, |
| 26 iter_slices, guess_json_utf, super_len) |
| 27 from .compat import ( |
| 28 cookielib, urlparse, urlunparse, urlsplit, urlencode, str, bytes, StringIO, |
| 29 is_py2, chardet, json, builtin_str, basestring) |
| 30 |
| 31 CONTENT_CHUNK_SIZE = 10 * 1024 |
| 32 ITER_CHUNK_SIZE = 512 |
| 33 |
| 34 log = logging.getLogger(__name__) |
| 35 |
| 36 |
| 37 class RequestEncodingMixin(object): |
| 38 @property |
| 39 def path_url(self): |
| 40 """Build the path URL to use.""" |
| 41 |
| 42 url = [] |
| 43 |
| 44 p = urlsplit(self.url) |
| 45 |
| 46 path = p.path |
| 47 if not path: |
| 48 path = '/' |
| 49 |
| 50 url.append(path) |
| 51 |
| 52 query = p.query |
| 53 if query: |
| 54 url.append('?') |
| 55 url.append(query) |
| 56 |
| 57 return ''.join(url) |
| 58 |
| 59 @staticmethod |
| 60 def _encode_params(data): |
| 61 """Encode parameters in a piece of data. |
| 62 |
| 63 Will successfully encode parameters when passed as a dict or a list of |
| 64 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary |
| 65 if parameters are supplied as a dict. |
| 66 """ |
| 67 |
| 68 if isinstance(data, (str, bytes)): |
| 69 return data |
| 70 elif hasattr(data, 'read'): |
| 71 return data |
| 72 elif hasattr(data, '__iter__'): |
| 73 result = [] |
| 74 for k, vs in to_key_val_list(data): |
| 75 if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): |
| 76 vs = [vs] |
| 77 for v in vs: |
| 78 if v is not None: |
| 79 result.append( |
| 80 (k.encode('utf-8') if isinstance(k, str) else k, |
| 81 v.encode('utf-8') if isinstance(v, str) else v)) |
| 82 return urlencode(result, doseq=True) |
| 83 else: |
| 84 return data |
| 85 |
| 86 @staticmethod |
| 87 def _encode_files(files, data): |
| 88 """Build the body for a multipart/form-data request. |
| 89 |
| 90 Will successfully encode files when passed as a dict or a list of |
| 91 2-tuples. Order is retained if data is a list of 2-tuples but abritrary |
| 92 if parameters are supplied as a dict. |
| 93 |
| 94 """ |
| 95 if (not files) or isinstance(data, str): |
| 96 return None |
| 97 |
| 98 new_fields = [] |
| 99 fields = to_key_val_list(data or {}) |
| 100 files = to_key_val_list(files or {}) |
| 101 |
| 102 for field, val in fields: |
| 103 if isinstance(val, basestring) or not hasattr(val, '__iter__'): |
| 104 val = [val] |
| 105 for v in val: |
| 106 if v is not None: |
| 107 new_fields.append( |
| 108 (field.decode('utf-8') if isinstance(field, bytes) else
field, |
| 109 v.encode('utf-8') if isinstance(v, str) else v)) |
| 110 |
| 111 for (k, v) in files: |
| 112 # support for explicit filename |
| 113 ft = None |
| 114 if isinstance(v, (tuple, list)): |
| 115 if len(v) == 2: |
| 116 fn, fp = v |
| 117 else: |
| 118 fn, fp, ft = v |
| 119 else: |
| 120 fn = guess_filename(v) or k |
| 121 fp = v |
| 122 if isinstance(fp, str): |
| 123 fp = StringIO(fp) |
| 124 if isinstance(fp, bytes): |
| 125 fp = BytesIO(fp) |
| 126 |
| 127 if ft: |
| 128 new_v = (fn, fp.read(), ft) |
| 129 else: |
| 130 new_v = (fn, fp.read()) |
| 131 new_fields.append((k, new_v)) |
| 132 |
| 133 body, content_type = encode_multipart_formdata(new_fields) |
| 134 |
| 135 return body, content_type |
| 136 |
| 137 |
| 138 class RequestHooksMixin(object): |
| 139 def register_hook(self, event, hook): |
| 140 """Properly register a hook.""" |
| 141 |
| 142 if isinstance(hook, collections.Callable): |
| 143 self.hooks[event].append(hook) |
| 144 elif hasattr(hook, '__iter__'): |
| 145 self.hooks[event].extend(h for h in hook if isinstance(h, collection
s.Callable)) |
| 146 |
| 147 def deregister_hook(self, event, hook): |
| 148 """Deregister a previously registered hook. |
| 149 Returns True if the hook existed, False if not. |
| 150 """ |
| 151 |
| 152 try: |
| 153 self.hooks[event].remove(hook) |
| 154 return True |
| 155 except ValueError: |
| 156 return False |
| 157 |
| 158 |
| 159 class Request(RequestHooksMixin): |
| 160 """A user-created :class:`Request <Request>` object. |
| 161 |
| 162 Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent
to the server. |
| 163 |
| 164 :param method: HTTP method to use. |
| 165 :param url: URL to send. |
| 166 :param headers: dictionary of headers to send. |
| 167 :param files: dictionary of {filename: fileobject} files to multipart upload
. |
| 168 :param data: the body to attach the request. If a dictionary is provided, fo
rm-encoding will take place. |
| 169 :param params: dictionary of URL parameters to append to the URL. |
| 170 :param auth: Auth handler or (user, pass) tuple. |
| 171 :param cookies: dictionary or CookieJar of cookies to attach to this request
. |
| 172 :param hooks: dictionary of callback hooks, for internal usage. |
| 173 |
| 174 Usage:: |
| 175 |
| 176 >>> import requests |
| 177 >>> req = requests.Request('GET', 'http://httpbin.org/get') |
| 178 >>> req.prepare() |
| 179 <PreparedRequest [GET]> |
| 180 |
| 181 """ |
| 182 def __init__(self, |
| 183 method=None, |
| 184 url=None, |
| 185 headers=None, |
| 186 files=None, |
| 187 data=dict(), |
| 188 params=dict(), |
| 189 auth=None, |
| 190 cookies=None, |
| 191 hooks=None): |
| 192 |
| 193 # Default empty dicts for dict params. |
| 194 data = [] if data is None else data |
| 195 files = [] if files is None else files |
| 196 headers = {} if headers is None else headers |
| 197 params = {} if params is None else params |
| 198 hooks = {} if hooks is None else hooks |
| 199 |
| 200 self.hooks = default_hooks() |
| 201 for (k, v) in list(hooks.items()): |
| 202 self.register_hook(event=k, hook=v) |
| 203 |
| 204 self.method = method |
| 205 self.url = url |
| 206 self.headers = headers |
| 207 self.files = files |
| 208 self.data = data |
| 209 self.params = params |
| 210 self.auth = auth |
| 211 self.cookies = cookies |
| 212 self.hooks = hooks |
| 213 |
| 214 def __repr__(self): |
| 215 return '<Request [%s]>' % (self.method) |
| 216 |
| 217 def prepare(self): |
| 218 """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmiss
ion and returns it.""" |
| 219 p = PreparedRequest() |
| 220 |
| 221 p.prepare_method(self.method) |
| 222 p.prepare_url(self.url, self.params) |
| 223 p.prepare_headers(self.headers) |
| 224 p.prepare_cookies(self.cookies) |
| 225 p.prepare_body(self.data, self.files) |
| 226 p.prepare_auth(self.auth, self.url) |
| 227 # Note that prepare_auth must be last to enable authentication schemes |
| 228 # such as OAuth to work on a fully prepared request. |
| 229 |
| 230 # This MUST go after prepare_auth. Authenticators could add a hook |
| 231 p.prepare_hooks(self.hooks) |
| 232 |
| 233 return p |
| 234 |
| 235 |
| 236 class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): |
| 237 """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, |
| 238 containing the exact bytes that will be sent to the server. |
| 239 |
| 240 Generated from either a :class:`Request <Request>` object or manually. |
| 241 |
| 242 Usage:: |
| 243 |
| 244 >>> import requests |
| 245 >>> req = requests.Request('GET', 'http://httpbin.org/get') |
| 246 >>> r = req.prepare() |
| 247 <PreparedRequest [GET]> |
| 248 |
| 249 >>> s = requests.Session() |
| 250 >>> s.send(r) |
| 251 <Response [200]> |
| 252 |
| 253 """ |
| 254 |
| 255 def __init__(self): |
| 256 #: HTTP verb to send to the server. |
| 257 self.method = None |
| 258 #: HTTP URL to send the request to. |
| 259 self.url = None |
| 260 #: dictionary of HTTP headers. |
| 261 self.headers = None |
| 262 #: request body to send to the server. |
| 263 self.body = None |
| 264 #: dictionary of callback hooks, for internal usage. |
| 265 self.hooks = default_hooks() |
| 266 |
| 267 def __repr__(self): |
| 268 return '<PreparedRequest [%s]>' % (self.method) |
| 269 |
| 270 def prepare_method(self, method): |
| 271 """Prepares the given HTTP method.""" |
| 272 self.method = method |
| 273 if self.method is not None: |
| 274 self.method = self.method.upper() |
| 275 |
| 276 def prepare_url(self, url, params): |
| 277 """Prepares the given HTTP URL.""" |
| 278 #: Accept objects that have string representations. |
| 279 try: |
| 280 url = unicode(url) |
| 281 except NameError: |
| 282 # We're on Python 3. |
| 283 url = str(url) |
| 284 except UnicodeDecodeError: |
| 285 pass |
| 286 |
| 287 # Support for unicode domain names and paths. |
| 288 scheme, auth, host, port, path, query, fragment = parse_url(url) |
| 289 |
| 290 if not scheme: |
| 291 raise MissingSchema("Invalid URL %r: No schema supplied" % url) |
| 292 |
| 293 if not host: |
| 294 raise InvalidURL("Invalid URL %r: No host supplied" % url) |
| 295 |
| 296 # Only want to apply IDNA to the hostname |
| 297 try: |
| 298 host = host.encode('idna').decode('utf-8') |
| 299 except UnicodeError: |
| 300 raise InvalidURL('URL has an invalid label.') |
| 301 |
| 302 # Carefully reconstruct the network location |
| 303 netloc = auth or '' |
| 304 if netloc: |
| 305 netloc += '@' |
| 306 netloc += host |
| 307 if port: |
| 308 netloc += ':' + str(port) |
| 309 |
| 310 # Bare domains aren't valid URLs. |
| 311 if not path: |
| 312 path = '/' |
| 313 |
| 314 if is_py2: |
| 315 if isinstance(scheme, str): |
| 316 scheme = scheme.encode('utf-8') |
| 317 if isinstance(netloc, str): |
| 318 netloc = netloc.encode('utf-8') |
| 319 if isinstance(path, str): |
| 320 path = path.encode('utf-8') |
| 321 if isinstance(query, str): |
| 322 query = query.encode('utf-8') |
| 323 if isinstance(fragment, str): |
| 324 fragment = fragment.encode('utf-8') |
| 325 |
| 326 enc_params = self._encode_params(params) |
| 327 if enc_params: |
| 328 if query: |
| 329 query = '%s&%s' % (query, enc_params) |
| 330 else: |
| 331 query = enc_params |
| 332 |
| 333 url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragmen
t])) |
| 334 self.url = url |
| 335 |
| 336 def prepare_headers(self, headers): |
| 337 """Prepares the given HTTP headers.""" |
| 338 |
| 339 if headers: |
| 340 headers = dict((name.encode('ascii'), value) for name, value in head
ers.items()) |
| 341 self.headers = CaseInsensitiveDict(headers) |
| 342 else: |
| 343 self.headers = CaseInsensitiveDict() |
| 344 |
| 345 def prepare_body(self, data, files): |
| 346 """Prepares the given HTTP body data.""" |
| 347 |
| 348 # Check if file, fo, generator, iterator. |
| 349 # If not, run through normal process. |
| 350 |
| 351 # Nottin' on you. |
| 352 body = None |
| 353 content_type = None |
| 354 length = None |
| 355 is_stream = False |
| 356 |
| 357 is_stream = all([ |
| 358 hasattr(data, '__iter__'), |
| 359 not isinstance(data, basestring), |
| 360 not isinstance(data, list), |
| 361 not isinstance(data, dict) |
| 362 ]) |
| 363 |
| 364 try: |
| 365 length = super_len(data) |
| 366 except (TypeError, AttributeError): |
| 367 length = False |
| 368 |
| 369 if is_stream: |
| 370 body = data |
| 371 |
| 372 if files: |
| 373 raise NotImplementedError('Streamed bodies and files are mutuall
y exclusive.') |
| 374 |
| 375 if length: |
| 376 self.headers['Content-Length'] = str(length) |
| 377 else: |
| 378 self.headers['Transfer-Encoding'] = 'chunked' |
| 379 # Check if file, fo, generator, iterator. |
| 380 # If not, run through normal process. |
| 381 |
| 382 else: |
| 383 # Multi-part file uploads. |
| 384 if files: |
| 385 (body, content_type) = self._encode_files(files, data) |
| 386 else: |
| 387 if data: |
| 388 body = self._encode_params(data) |
| 389 if isinstance(data, str) or isinstance(data, builtin_str) or
hasattr(data, 'read'): |
| 390 content_type = None |
| 391 else: |
| 392 content_type = 'application/x-www-form-urlencoded' |
| 393 |
| 394 self.prepare_content_length(body) |
| 395 |
| 396 # Add content-type if it wasn't explicitly provided. |
| 397 if (content_type) and (not 'content-type' in self.headers): |
| 398 self.headers['Content-Type'] = content_type |
| 399 |
| 400 self.body = body |
| 401 |
| 402 def prepare_content_length(self, body): |
| 403 if hasattr(body, 'seek') and hasattr(body, 'tell'): |
| 404 body.seek(0, 2) |
| 405 self.headers['Content-Length'] = str(body.tell()) |
| 406 body.seek(0, 0) |
| 407 elif body is not None: |
| 408 l = super_len(body) |
| 409 if l: |
| 410 self.headers['Content-Length'] = str(l) |
| 411 elif self.method not in ('GET', 'HEAD'): |
| 412 self.headers['Content-Length'] = '0' |
| 413 |
| 414 def prepare_auth(self, auth, url=''): |
| 415 """Prepares the given HTTP auth data.""" |
| 416 |
| 417 # If no Auth is explicitly provided, extract it from the URL first. |
| 418 if auth is None: |
| 419 url_auth = get_auth_from_url(self.url) |
| 420 auth = url_auth if any(url_auth) else None |
| 421 |
| 422 if auth: |
| 423 if isinstance(auth, tuple) and len(auth) == 2: |
| 424 # special-case basic HTTP auth |
| 425 auth = HTTPBasicAuth(*auth) |
| 426 |
| 427 # Allow auth to make its changes. |
| 428 r = auth(self) |
| 429 |
| 430 # Update self to reflect the auth changes. |
| 431 self.__dict__.update(r.__dict__) |
| 432 |
| 433 # Recompute Content-Length |
| 434 self.prepare_content_length(self.body) |
| 435 |
| 436 def prepare_cookies(self, cookies): |
| 437 """Prepares the given HTTP cookie data.""" |
| 438 |
| 439 if isinstance(cookies, cookielib.CookieJar): |
| 440 cookies = cookies |
| 441 else: |
| 442 cookies = cookiejar_from_dict(cookies) |
| 443 |
| 444 if 'cookie' not in self.headers: |
| 445 cookie_header = get_cookie_header(cookies, self) |
| 446 if cookie_header is not None: |
| 447 self.headers['Cookie'] = cookie_header |
| 448 |
| 449 def prepare_hooks(self, hooks): |
| 450 """Prepares the given hooks.""" |
| 451 for event in hooks: |
| 452 self.register_hook(event, hooks[event]) |
| 453 |
| 454 |
| 455 class Response(object): |
| 456 """The :class:`Response <Response>` object, which contains a |
| 457 server's response to an HTTP request. |
| 458 """ |
| 459 |
| 460 def __init__(self): |
| 461 super(Response, self).__init__() |
| 462 |
| 463 self._content = False |
| 464 self._content_consumed = False |
| 465 |
| 466 #: Integer Code of responded HTTP Status. |
| 467 self.status_code = None |
| 468 |
| 469 #: Case-insensitive Dictionary of Response Headers. |
| 470 #: For example, ``headers['content-encoding']`` will return the |
| 471 #: value of a ``'Content-Encoding'`` response header. |
| 472 self.headers = CaseInsensitiveDict() |
| 473 |
| 474 #: File-like object representation of response (for advanced usage). |
| 475 #: Requires that ``stream=True` on the request. |
| 476 # This requirement does not apply for use internally to Requests. |
| 477 self.raw = None |
| 478 |
| 479 #: Final URL location of Response. |
| 480 self.url = None |
| 481 |
| 482 #: Encoding to decode with when accessing r.text. |
| 483 self.encoding = None |
| 484 |
| 485 #: A list of :class:`Response <Response>` objects from |
| 486 #: the history of the Request. Any redirect responses will end |
| 487 #: up here. The list is sorted from the oldest to the most recent reques
t. |
| 488 self.history = [] |
| 489 |
| 490 self.reason = None |
| 491 |
| 492 #: A CookieJar of Cookies the server sent back. |
| 493 self.cookies = cookiejar_from_dict({}) |
| 494 |
| 495 #: The amount of time elapsed between sending the request |
| 496 #: and the arrival of the response (as a timedelta) |
| 497 self.elapsed = datetime.timedelta(0) |
| 498 |
| 499 def __repr__(self): |
| 500 return '<Response [%s]>' % (self.status_code) |
| 501 |
| 502 def __bool__(self): |
| 503 """Returns true if :attr:`status_code` is 'OK'.""" |
| 504 return self.ok |
| 505 |
| 506 def __nonzero__(self): |
| 507 """Returns true if :attr:`status_code` is 'OK'.""" |
| 508 return self.ok |
| 509 |
| 510 def __iter__(self): |
| 511 """Allows you to use a response as an iterator.""" |
| 512 return self.iter_content(128) |
| 513 |
| 514 @property |
| 515 def ok(self): |
| 516 try: |
| 517 self.raise_for_status() |
| 518 except RequestException: |
| 519 return False |
| 520 return True |
| 521 |
| 522 @property |
| 523 def apparent_encoding(self): |
| 524 """The apparent encoding, provided by the lovely Charade library |
| 525 (Thanks, Ian!).""" |
| 526 return chardet.detect(self.content)['encoding'] |
| 527 |
| 528 def iter_content(self, chunk_size=1, decode_unicode=False): |
| 529 """Iterates over the response data. When stream=True is set on the |
| 530 request, this avoids reading the content at once into memory for |
| 531 large responses. The chunk size is the number of bytes it should |
| 532 read into memory. This is not necessarily the length of each item |
| 533 returned as decoding can take place. |
| 534 """ |
| 535 if self._content_consumed: |
| 536 # simulate reading small chunks of the content |
| 537 return iter_slices(self._content, chunk_size) |
| 538 |
| 539 def generate(): |
| 540 while 1: |
| 541 chunk = self.raw.read(chunk_size, decode_content=True) |
| 542 if not chunk: |
| 543 break |
| 544 yield chunk |
| 545 self._content_consumed = True |
| 546 |
| 547 gen = generate() |
| 548 |
| 549 if decode_unicode: |
| 550 gen = stream_decode_response_unicode(gen, self) |
| 551 |
| 552 return gen |
| 553 |
| 554 def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None): |
| 555 """Iterates over the response data, one line at a time. When |
| 556 stream=True is set on the request, this avoids reading the |
| 557 content at once into memory for large responses. |
| 558 """ |
| 559 |
| 560 pending = None |
| 561 |
| 562 for chunk in self.iter_content(chunk_size=chunk_size, |
| 563 decode_unicode=decode_unicode): |
| 564 |
| 565 if pending is not None: |
| 566 chunk = pending + chunk |
| 567 lines = chunk.splitlines() |
| 568 |
| 569 if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: |
| 570 pending = lines.pop() |
| 571 else: |
| 572 pending = None |
| 573 |
| 574 for line in lines: |
| 575 yield line |
| 576 |
| 577 if pending is not None: |
| 578 yield pending |
| 579 |
| 580 @property |
| 581 def content(self): |
| 582 """Content of the response, in bytes.""" |
| 583 |
| 584 if self._content is False: |
| 585 # Read the contents. |
| 586 try: |
| 587 if self._content_consumed: |
| 588 raise RuntimeError( |
| 589 'The content for this response was already consumed') |
| 590 |
| 591 if self.status_code == 0: |
| 592 self._content = None |
| 593 else: |
| 594 self._content = bytes().join(self.iter_content(CONTENT_CHUNK
_SIZE)) or bytes() |
| 595 |
| 596 except AttributeError: |
| 597 self._content = None |
| 598 |
| 599 self._content_consumed = True |
| 600 # don't need to release the connection; that's been handled by urllib3 |
| 601 # since we exhausted the data. |
| 602 return self._content |
| 603 |
| 604 @property |
| 605 def text(self): |
| 606 """Content of the response, in unicode. |
| 607 |
| 608 if Response.encoding is None and chardet module is available, encoding |
| 609 will be guessed. |
| 610 """ |
| 611 |
| 612 # Try charset from content-type |
| 613 content = None |
| 614 encoding = self.encoding |
| 615 |
| 616 if not self.content: |
| 617 return str('') |
| 618 |
| 619 # Fallback to auto-detected encoding. |
| 620 if self.encoding is None: |
| 621 encoding = self.apparent_encoding |
| 622 |
| 623 # Decode unicode from given encoding. |
| 624 try: |
| 625 content = str(self.content, encoding, errors='replace') |
| 626 except (LookupError, TypeError): |
| 627 # A LookupError is raised if the encoding was not found which could |
| 628 # indicate a misspelling or similar mistake. |
| 629 # |
| 630 # A TypeError can be raised if encoding is None |
| 631 # |
| 632 # So we try blindly encoding. |
| 633 content = str(self.content, errors='replace') |
| 634 |
| 635 return content |
| 636 |
| 637 def json(self, **kwargs): |
| 638 """Returns the json-encoded content of a response, if any. |
| 639 |
| 640 :param \*\*kwargs: Optional arguments that ``json.loads`` takes. |
| 641 """ |
| 642 |
| 643 if not self.encoding and len(self.content) > 3: |
| 644 # No encoding set. JSON RFC 4627 section 3 states we should expect |
| 645 # UTF-8, -16 or -32. Detect which one to use; If the detection or |
| 646 # decoding fails, fall back to `self.text` (using chardet to make |
| 647 # a best guess). |
| 648 encoding = guess_json_utf(self.content) |
| 649 if encoding is not None: |
| 650 return json.loads(self.content.decode(encoding), **kwargs) |
| 651 return json.loads(self.text or self.content, **kwargs) |
| 652 |
| 653 @property |
| 654 def links(self): |
| 655 """Returns the parsed header links of the response, if any.""" |
| 656 |
| 657 header = self.headers.get('link') |
| 658 |
| 659 # l = MultiDict() |
| 660 l = {} |
| 661 |
| 662 if header: |
| 663 links = parse_header_links(header) |
| 664 |
| 665 for link in links: |
| 666 key = link.get('rel') or link.get('url') |
| 667 l[key] = link |
| 668 |
| 669 return l |
| 670 |
| 671 def raise_for_status(self): |
| 672 """Raises stored :class:`HTTPError`, if one occurred.""" |
| 673 |
| 674 http_error_msg = '' |
| 675 |
| 676 if 400 <= self.status_code < 500: |
| 677 http_error_msg = '%s Client Error: %s' % (self.status_code, self.rea
son) |
| 678 |
| 679 elif 500 <= self.status_code < 600: |
| 680 http_error_msg = '%s Server Error: %s' % (self.status_code, self.rea
son) |
| 681 |
| 682 if http_error_msg: |
| 683 raise HTTPError(http_error_msg, response=self) |
| 684 |
| 685 def close(self): |
| 686 return self.raw.release_conn() |
OLD | NEW |