Spaces:
Running
Running
ChatBot-UI-With-API
/
venv
/lib
/python3.11
/site-packages
/urllib3
/contrib
/emscripten
/response.py
from __future__ import annotations | |
import json as _json | |
import logging | |
import typing | |
from contextlib import contextmanager | |
from dataclasses import dataclass | |
from http.client import HTTPException as HTTPException | |
from io import BytesIO, IOBase | |
from ...exceptions import InvalidHeader, TimeoutError | |
from ...response import BaseHTTPResponse | |
from ...util.retry import Retry | |
from .request import EmscriptenRequest | |
if typing.TYPE_CHECKING: | |
from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection | |
log = logging.getLogger(__name__) | |
class EmscriptenResponse: | |
status_code: int | |
headers: dict[str, str] | |
body: IOBase | bytes | |
request: EmscriptenRequest | |
class EmscriptenHttpResponseWrapper(BaseHTTPResponse): | |
def __init__( | |
self, | |
internal_response: EmscriptenResponse, | |
url: str | None = None, | |
connection: BaseHTTPConnection | BaseHTTPSConnection | None = None, | |
): | |
self._pool = None # set by pool class | |
self._body = None | |
self._response = internal_response | |
self._url = url | |
self._connection = connection | |
self._closed = False | |
super().__init__( | |
headers=internal_response.headers, | |
status=internal_response.status_code, | |
request_url=url, | |
version=0, | |
version_string="HTTP/?", | |
reason="", | |
decode_content=True, | |
) | |
self.length_remaining = self._init_length(self._response.request.method) | |
self.length_is_certain = False | |
def url(self) -> str | None: | |
return self._url | |
def url(self, url: str | None) -> None: | |
self._url = url | |
def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None: | |
return self._connection | |
def retries(self) -> Retry | None: | |
return self._retries | |
def retries(self, retries: Retry | None) -> None: | |
# Override the request_url if retries has a redirect location. | |
self._retries = retries | |
def stream( | |
self, amt: int | None = 2**16, decode_content: bool | None = None | |
) -> typing.Generator[bytes]: | |
""" | |
A generator wrapper for the read() method. A call will block until | |
``amt`` bytes have been read from the connection or until the | |
connection is closed. | |
:param amt: | |
How much of the content to read. The generator will return up to | |
much data per iteration, but may return less. This is particularly | |
likely when using compressed data. However, the empty string will | |
never be returned. | |
:param decode_content: | |
If True, will attempt to decode the body based on the | |
'content-encoding' header. | |
""" | |
while True: | |
data = self.read(amt=amt, decode_content=decode_content) | |
if data: | |
yield data | |
else: | |
break | |
def _init_length(self, request_method: str | None) -> int | None: | |
length: int | None | |
content_length: str | None = self.headers.get("content-length") | |
if content_length is not None: | |
try: | |
# RFC 7230 section 3.3.2 specifies multiple content lengths can | |
# be sent in a single Content-Length header | |
# (e.g. Content-Length: 42, 42). This line ensures the values | |
# are all valid ints and that as long as the `set` length is 1, | |
# all values are the same. Otherwise, the header is invalid. | |
lengths = {int(val) for val in content_length.split(",")} | |
if len(lengths) > 1: | |
raise InvalidHeader( | |
"Content-Length contained multiple " | |
"unmatching values (%s)" % content_length | |
) | |
length = lengths.pop() | |
except ValueError: | |
length = None | |
else: | |
if length < 0: | |
length = None | |
else: # if content_length is None | |
length = None | |
# Check for responses that shouldn't include a body | |
if ( | |
self.status in (204, 304) | |
or 100 <= self.status < 200 | |
or request_method == "HEAD" | |
): | |
length = 0 | |
return length | |
def read( | |
self, | |
amt: int | None = None, | |
decode_content: bool | None = None, # ignored because browser decodes always | |
cache_content: bool = False, | |
) -> bytes: | |
if ( | |
self._closed | |
or self._response is None | |
or (isinstance(self._response.body, IOBase) and self._response.body.closed) | |
): | |
return b"" | |
with self._error_catcher(): | |
# body has been preloaded as a string by XmlHttpRequest | |
if not isinstance(self._response.body, IOBase): | |
self.length_remaining = len(self._response.body) | |
self.length_is_certain = True | |
# wrap body in IOStream | |
self._response.body = BytesIO(self._response.body) | |
if amt is not None and amt >= 0: | |
# don't cache partial content | |
cache_content = False | |
data = self._response.body.read(amt) | |
if self.length_remaining is not None: | |
self.length_remaining = max(self.length_remaining - len(data), 0) | |
if (self.length_is_certain and self.length_remaining == 0) or len( | |
data | |
) < amt: | |
# definitely finished reading, close response stream | |
self._response.body.close() | |
return typing.cast(bytes, data) | |
else: # read all we can (and cache it) | |
data = self._response.body.read() | |
if cache_content: | |
self._body = data | |
if self.length_remaining is not None: | |
self.length_remaining = max(self.length_remaining - len(data), 0) | |
if len(data) == 0 or ( | |
self.length_is_certain and self.length_remaining == 0 | |
): | |
# definitely finished reading, close response stream | |
self._response.body.close() | |
return typing.cast(bytes, data) | |
def read_chunked( | |
self, | |
amt: int | None = None, | |
decode_content: bool | None = None, | |
) -> typing.Generator[bytes]: | |
# chunked is handled by browser | |
while True: | |
bytes = self.read(amt, decode_content) | |
if not bytes: | |
break | |
yield bytes | |
def release_conn(self) -> None: | |
if not self._pool or not self._connection: | |
return None | |
self._pool._put_conn(self._connection) | |
self._connection = None | |
def drain_conn(self) -> None: | |
self.close() | |
def data(self) -> bytes: | |
if self._body: | |
return self._body | |
else: | |
return self.read(cache_content=True) | |
def json(self) -> typing.Any: | |
""" | |
Deserializes the body of the HTTP response as a Python object. | |
The body of the HTTP response must be encoded using UTF-8, as per | |
`RFC 8529 Section 8.1 <https://www.rfc-editor.org/rfc/rfc8259#section-8.1>`_. | |
To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to | |
your custom decoder instead. | |
If the body of the HTTP response is not decodable to UTF-8, a | |
`UnicodeDecodeError` will be raised. If the body of the HTTP response is not a | |
valid JSON document, a `json.JSONDecodeError` will be raised. | |
Read more :ref:`here <json_content>`. | |
:returns: The body of the HTTP response as a Python object. | |
""" | |
data = self.data.decode("utf-8") | |
return _json.loads(data) | |
def close(self) -> None: | |
if not self._closed: | |
if isinstance(self._response.body, IOBase): | |
self._response.body.close() | |
if self._connection: | |
self._connection.close() | |
self._connection = None | |
self._closed = True | |
def _error_catcher(self) -> typing.Generator[None]: | |
""" | |
Catch Emscripten specific exceptions thrown by fetch.py, | |
instead re-raising urllib3 variants, so that low-level exceptions | |
are not leaked in the high-level api. | |
On exit, release the connection back to the pool. | |
""" | |
from .fetch import _RequestError, _TimeoutError # avoid circular import | |
clean_exit = False | |
try: | |
yield | |
# If no exception is thrown, we should avoid cleaning up | |
# unnecessarily. | |
clean_exit = True | |
except _TimeoutError as e: | |
raise TimeoutError(str(e)) | |
except _RequestError as e: | |
raise HTTPException(str(e)) | |
finally: | |
# If we didn't terminate cleanly, we need to throw away our | |
# connection. | |
if not clean_exit: | |
# The response may not be closed but we're not going to use it | |
# anymore so close it now | |
if ( | |
isinstance(self._response.body, IOBase) | |
and not self._response.body.closed | |
): | |
self._response.body.close() | |
# release the connection back to the pool | |
self.release_conn() | |
else: | |
# If we have read everything from the response stream, | |
# return the connection back to the pool. | |
if ( | |
isinstance(self._response.body, IOBase) | |
and self._response.body.closed | |
): | |
self.release_conn() | |