Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
_models.py517 linesDownload Raw Back to httpcore
1from __future__ import annotations2 3import base644import ssl5import typing6import urllib.parse7 8# Functions for typechecking...9 10 11ByteOrStr = typing.Union[bytes, str]12HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]]13HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr]14HeaderTypes = typing.Union[HeadersAsSequence, HeadersAsMapping, None]15 16Extensions = typing.MutableMapping[str, typing.Any]17 18 19def enforce_bytes(value: bytes | str, *, name: str) -> bytes:20    """21    Any arguments that are ultimately represented as bytes can be specified22    either as bytes or as strings.23 24    However we enforce that any string arguments must only contain characters in25    the plain ASCII range. chr(0)...chr(127). If you need to use characters26    outside that range then be precise, and use a byte-wise argument.27    """28    if isinstance(value, str):29        try:30            return value.encode("ascii")31        except UnicodeEncodeError:32            raise TypeError(f"{name} strings may not include unicode characters.")33    elif isinstance(value, bytes):34        return value35 36    seen_type = type(value).__name__37    raise TypeError(f"{name} must be bytes or str, but got {seen_type}.")38 39 40def enforce_url(value: URL | bytes | str, *, name: str) -> URL:41    """42    Type check for URL parameters.43    """44    if isinstance(value, (bytes, str)):45        return URL(value)46    elif isinstance(value, URL):47        return value48 49    seen_type = type(value).__name__50    raise TypeError(f"{name} must be a URL, bytes, or str, but got {seen_type}.")51 52 53def enforce_headers(54    value: HeadersAsMapping | HeadersAsSequence | None = None, *, name: str55) -> list[tuple[bytes, bytes]]:56    """57    Convienence function that ensure all items in request or response headers58    are either bytes or strings in the plain ASCII range.59    """60    if value is None:61        return []62    elif isinstance(value, typing.Mapping):63        return [64            (65                enforce_bytes(k, name="header name"),66                enforce_bytes(v, name="header value"),67            )68            for k, v in value.items()69        ]70    elif isinstance(value, typing.Sequence):71        return [72            (73                enforce_bytes(k, name="header name"),74                enforce_bytes(v, name="header value"),75            )76            for k, v in value77        ]78 79    seen_type = type(value).__name__80    raise TypeError(81        f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}."82    )83 84 85def enforce_stream(86    value: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None,87    *,88    name: str,89) -> typing.Iterable[bytes] | typing.AsyncIterable[bytes]:90    if value is None:91        return ByteStream(b"")92    elif isinstance(value, bytes):93        return ByteStream(value)94    return value95 96 97# * https://tools.ietf.org/html/rfc3986#section-3.2.398# * https://url.spec.whatwg.org/#url-miscellaneous99# * https://url.spec.whatwg.org/#scheme-state100DEFAULT_PORTS = {101    b"ftp": 21,102    b"http": 80,103    b"https": 443,104    b"ws": 80,105    b"wss": 443,106}107 108 109def include_request_headers(110    headers: list[tuple[bytes, bytes]],111    *,112    url: "URL",113    content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes],114) -> list[tuple[bytes, bytes]]:115    headers_set = set(k.lower() for k, v in headers)116 117    if b"host" not in headers_set:118        default_port = DEFAULT_PORTS.get(url.scheme)119        if url.port is None or url.port == default_port:120            header_value = url.host121        else:122            header_value = b"%b:%d" % (url.host, url.port)123        headers = [(b"Host", header_value)] + headers124 125    if (126        content is not None127        and b"content-length" not in headers_set128        and b"transfer-encoding" not in headers_set129    ):130        if isinstance(content, bytes):131            content_length = str(len(content)).encode("ascii")132            headers += [(b"Content-Length", content_length)]133        else:134            headers += [(b"Transfer-Encoding", b"chunked")]  # pragma: nocover135 136    return headers137 138 139# Interfaces for byte streams...140 141 142class ByteStream:143    """144    A container for non-streaming content, and that supports both sync and async145    stream iteration.146    """147 148    def __init__(self, content: bytes) -> None:149        self._content = content150 151    def __iter__(self) -> typing.Iterator[bytes]:152        yield self._content153 154    async def __aiter__(self) -> typing.AsyncIterator[bytes]:155        yield self._content156 157    def __repr__(self) -> str:158        return f"<{self.__class__.__name__} [{len(self._content)} bytes]>"159 160 161class Origin:162    def __init__(self, scheme: bytes, host: bytes, port: int) -> None:163        self.scheme = scheme164        self.host = host165        self.port = port166 167    def __eq__(self, other: typing.Any) -> bool:168        return (169            isinstance(other, Origin)170            and self.scheme == other.scheme171            and self.host == other.host172            and self.port == other.port173        )174 175    def __str__(self) -> str:176        scheme = self.scheme.decode("ascii")177        host = self.host.decode("ascii")178        port = str(self.port)179        return f"{scheme}://{host}:{port}"180 181 182class URL:183    """184    Represents the URL against which an HTTP request may be made.185 186    The URL may either be specified as a plain string, for convienence:187 188    ```python189    url = httpcore.URL("https://www.example.com/")190    ```191 192    Or be constructed with explicitily pre-parsed components:193 194    ```python195    url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/')196    ```197 198    Using this second more explicit style allows integrations that are using199    `httpcore` to pass through URLs that have already been parsed in order to use200    libraries such as `rfc-3986` rather than relying on the stdlib. It also ensures201    that URL parsing is treated identically at both the networking level and at any202    higher layers of abstraction.203 204    The four components are important here, as they allow the URL to be precisely205    specified in a pre-parsed format. They also allow certain types of request to206    be created that could not otherwise be expressed.207 208    For example, an HTTP request to `http://www.example.com/` forwarded via a proxy209    at `http://localhost:8080`...210 211    ```python212    # Constructs an HTTP request with a complete URL as the target:213    # GET https://www.example.com/ HTTP/1.1214    url = httpcore.URL(215        scheme=b'http',216        host=b'localhost',217        port=8080,218        target=b'https://www.example.com/'219    )220    request = httpcore.Request(221        method="GET",222        url=url223    )224    ```225 226    Another example is constructing an `OPTIONS *` request...227 228    ```python229    # Constructs an 'OPTIONS *' HTTP request:230    # OPTIONS * HTTP/1.1231    url = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*')232    request = httpcore.Request(method="OPTIONS", url=url)233    ```234 235    This kind of request is not possible to formulate with a URL string,236    because the `/` delimiter is always used to demark the target from the237    host/port portion of the URL.238 239    For convenience, string-like arguments may be specified either as strings or240    as bytes. However, once a request is being issue over-the-wire, the URL241    components are always ultimately required to be a bytewise representation.242 243    In order to avoid any ambiguity over character encodings, when strings are used244    as arguments, they must be strictly limited to the ASCII range `chr(0)`-`chr(127)`.245    If you require a bytewise representation that is outside this range you must246    handle the character encoding directly, and pass a bytes instance.247    """248 249    def __init__(250        self,251        url: bytes | str = "",252        *,253        scheme: bytes | str = b"",254        host: bytes | str = b"",255        port: int | None = None,256        target: bytes | str = b"",257    ) -> None:258        """259        Parameters:260            url: The complete URL as a string or bytes.261            scheme: The URL scheme as a string or bytes.262                Typically either `"http"` or `"https"`.263            host: The URL host as a string or bytes. Such as `"www.example.com"`.264            port: The port to connect to. Either an integer or `None`.265            target: The target of the HTTP request. Such as `"/items?search=red"`.266        """267        if url:268            parsed = urllib.parse.urlparse(enforce_bytes(url, name="url"))269            self.scheme = parsed.scheme270            self.host = parsed.hostname or b""271            self.port = parsed.port272            self.target = (parsed.path or b"/") + (273                b"?" + parsed.query if parsed.query else b""274            )275        else:276            self.scheme = enforce_bytes(scheme, name="scheme")277            self.host = enforce_bytes(host, name="host")278            self.port = port279            self.target = enforce_bytes(target, name="target")280 281    @property282    def origin(self) -> Origin:283        default_port = {284            b"http": 80,285            b"https": 443,286            b"ws": 80,287            b"wss": 443,288            b"socks5": 1080,289            b"socks5h": 1080,290        }[self.scheme]291        return Origin(292            scheme=self.scheme, host=self.host, port=self.port or default_port293        )294 295    def __eq__(self, other: typing.Any) -> bool:296        return (297            isinstance(other, URL)298            and other.scheme == self.scheme299            and other.host == self.host300            and other.port == self.port301            and other.target == self.target302        )303 304    def __bytes__(self) -> bytes:305        if self.port is None:306            return b"%b://%b%b" % (self.scheme, self.host, self.target)307        return b"%b://%b:%d%b" % (self.scheme, self.host, self.port, self.target)308 309    def __repr__(self) -> str:310        return (311            f"{self.__class__.__name__}(scheme={self.scheme!r}, "312            f"host={self.host!r}, port={self.port!r}, target={self.target!r})"313        )314 315 316class Request:317    """318    An HTTP request.319    """320 321    def __init__(322        self,323        method: bytes | str,324        url: URL | bytes | str,325        *,326        headers: HeaderTypes = None,327        content: bytes328        | typing.Iterable[bytes]329        | typing.AsyncIterable[bytes]330        | None = None,331        extensions: Extensions | None = None,332    ) -> None:333        """334        Parameters:335            method: The HTTP request method, either as a string or bytes.336                For example: `GET`.337            url: The request URL, either as a `URL` instance, or as a string or bytes.338                For example: `"https://www.example.com".`339            headers: The HTTP request headers.340            content: The content of the request body.341            extensions: A dictionary of optional extra information included on342                the request. Possible keys include `"timeout"`, and `"trace"`.343        """344        self.method: bytes = enforce_bytes(method, name="method")345        self.url: URL = enforce_url(url, name="url")346        self.headers: list[tuple[bytes, bytes]] = enforce_headers(347            headers, name="headers"348        )349        self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (350            enforce_stream(content, name="content")351        )352        self.extensions = {} if extensions is None else extensions353 354        if "target" in self.extensions:355            self.url = URL(356                scheme=self.url.scheme,357                host=self.url.host,358                port=self.url.port,359                target=self.extensions["target"],360            )361 362    def __repr__(self) -> str:363        return f"<{self.__class__.__name__} [{self.method!r}]>"364 365 366class Response:367    """368    An HTTP response.369    """370 371    def __init__(372        self,373        status: int,374        *,375        headers: HeaderTypes = None,376        content: bytes377        | typing.Iterable[bytes]378        | typing.AsyncIterable[bytes]379        | None = None,380        extensions: Extensions | None = None,381    ) -> None:382        """383        Parameters:384            status: The HTTP status code of the response. For example `200`.385            headers: The HTTP response headers.386            content: The content of the response body.387            extensions: A dictionary of optional extra information included on388                the responseself.Possible keys include `"http_version"`,389                `"reason_phrase"`, and `"network_stream"`.390        """391        self.status: int = status392        self.headers: list[tuple[bytes, bytes]] = enforce_headers(393            headers, name="headers"394        )395        self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = (396            enforce_stream(content, name="content")397        )398        self.extensions = {} if extensions is None else extensions399 400        self._stream_consumed = False401 402    @property403    def content(self) -> bytes:404        if not hasattr(self, "_content"):405            if isinstance(self.stream, typing.Iterable):406                raise RuntimeError(407                    "Attempted to access 'response.content' on a streaming response. "408                    "Call 'response.read()' first."409                )410            else:411                raise RuntimeError(412                    "Attempted to access 'response.content' on a streaming response. "413                    "Call 'await response.aread()' first."414                )415        return self._content416 417    def __repr__(self) -> str:418        return f"<{self.__class__.__name__} [{self.status}]>"419 420    # Sync interface...421 422    def read(self) -> bytes:423        if not isinstance(self.stream, typing.Iterable):  # pragma: nocover424            raise RuntimeError(425                "Attempted to read an asynchronous response using 'response.read()'. "426                "You should use 'await response.aread()' instead."427            )428        if not hasattr(self, "_content"):429            self._content = b"".join([part for part in self.iter_stream()])430        return self._content431 432    def iter_stream(self) -> typing.Iterator[bytes]:433        if not isinstance(self.stream, typing.Iterable):  # pragma: nocover434            raise RuntimeError(435                "Attempted to stream an asynchronous response using 'for ... in "436                "response.iter_stream()'. "437                "You should use 'async for ... in response.aiter_stream()' instead."438            )439        if self._stream_consumed:440            raise RuntimeError(441                "Attempted to call 'for ... in response.iter_stream()' more than once."442            )443        self._stream_consumed = True444        for chunk in self.stream:445            yield chunk446 447    def close(self) -> None:448        if not isinstance(self.stream, typing.Iterable):  # pragma: nocover449            raise RuntimeError(450                "Attempted to close an asynchronous response using 'response.close()'. "451                "You should use 'await response.aclose()' instead."452            )453        if hasattr(self.stream, "close"):454            self.stream.close()455 456    # Async interface...457 458    async def aread(self) -> bytes:459        if not isinstance(self.stream, typing.AsyncIterable):  # pragma: nocover460            raise RuntimeError(461                "Attempted to read an synchronous response using "462                "'await response.aread()'. "463                "You should use 'response.read()' instead."464            )465        if not hasattr(self, "_content"):466            self._content = b"".join([part async for part in self.aiter_stream()])467        return self._content468 469    async def aiter_stream(self) -> typing.AsyncIterator[bytes]:470        if not isinstance(self.stream, typing.AsyncIterable):  # pragma: nocover471            raise RuntimeError(472                "Attempted to stream an synchronous response using 'async for ... in "473                "response.aiter_stream()'. "474                "You should use 'for ... in response.iter_stream()' instead."475            )476        if self._stream_consumed:477            raise RuntimeError(478                "Attempted to call 'async for ... in response.aiter_stream()' "479                "more than once."480            )481        self._stream_consumed = True482        async for chunk in self.stream:483            yield chunk484 485    async def aclose(self) -> None:486        if not isinstance(self.stream, typing.AsyncIterable):  # pragma: nocover487            raise RuntimeError(488                "Attempted to close a synchronous response using "489                "'await response.aclose()'. "490                "You should use 'response.close()' instead."491            )492        if hasattr(self.stream, "aclose"):493            await self.stream.aclose()494 495 496class Proxy:497    def __init__(498        self,499        url: URL | bytes | str,500        auth: tuple[bytes | str, bytes | str] | None = None,501        headers: HeadersAsMapping | HeadersAsSequence | None = None,502        ssl_context: ssl.SSLContext | None = None,503    ):504        self.url = enforce_url(url, name="url")505        self.headers = enforce_headers(headers, name="headers")506        self.ssl_context = ssl_context507 508        if auth is not None:509            username = enforce_bytes(auth[0], name="auth")510            password = enforce_bytes(auth[1], name="auth")511            userpass = username + b":" + password512            authorization = b"Basic " + base64.b64encode(userpass)513            self.auth: tuple[bytes, bytes] | None = (username, password)514            self.headers = [(b"Proxy-Authorization", authorization)] + self.headers515        else:516            self.auth = None517 
codekingpro/portable-devtools · Team Ai