|
| 1 | +# Copyright (c) Meta Platforms, Inc. and affiliates. |
| 2 | +# All rights reserved. |
| 3 | +# |
| 4 | +# This source code is licensed under the terms described in the LICENSE file in |
| 5 | +# the root directory of this source tree. |
| 6 | + |
| 7 | +from __future__ import annotations |
| 8 | + |
| 9 | +from os import PathLike |
| 10 | +from typing import ( |
| 11 | + IO, |
| 12 | + TYPE_CHECKING, |
| 13 | + Any, |
| 14 | + Dict, |
| 15 | + List, |
| 16 | + Type, |
| 17 | + Tuple, |
| 18 | + Union, |
| 19 | + Mapping, |
| 20 | + TypeVar, |
| 21 | + Callable, |
| 22 | + Iterator, |
| 23 | + Optional, |
| 24 | + Sequence, |
| 25 | +) |
| 26 | +from typing_extensions import ( |
| 27 | + Set, |
| 28 | + Literal, |
| 29 | + Protocol, |
| 30 | + TypeAlias, |
| 31 | + TypedDict, |
| 32 | + SupportsIndex, |
| 33 | + overload, |
| 34 | + override, |
| 35 | + runtime_checkable, |
| 36 | +) |
| 37 | + |
| 38 | +import httpx |
| 39 | +import pydantic |
| 40 | +from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport |
| 41 | + |
| 42 | +if TYPE_CHECKING: |
| 43 | + from ._models import BaseModel |
| 44 | + from ._response import APIResponse, AsyncAPIResponse |
| 45 | + |
| 46 | +Transport = BaseTransport |
| 47 | +AsyncTransport = AsyncBaseTransport |
| 48 | +Query = Mapping[str, object] |
| 49 | +Body = object |
| 50 | +AnyMapping = Mapping[str, object] |
| 51 | +ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) |
| 52 | +_T = TypeVar("_T") |
| 53 | + |
| 54 | + |
| 55 | +# Approximates httpx internal ProxiesTypes and RequestFiles types |
| 56 | +# while adding support for `PathLike` instances |
| 57 | +ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] |
| 58 | +ProxiesTypes = Union[str, Proxy, ProxiesDict] |
| 59 | +if TYPE_CHECKING: |
| 60 | + Base64FileInput = Union[IO[bytes], PathLike[str]] |
| 61 | + FileContent = Union[IO[bytes], bytes, PathLike[str]] |
| 62 | +else: |
| 63 | + Base64FileInput = Union[IO[bytes], PathLike] |
| 64 | + FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. |
| 65 | +FileTypes = Union[ |
| 66 | + # file (or bytes) |
| 67 | + FileContent, |
| 68 | + # (filename, file (or bytes)) |
| 69 | + Tuple[Optional[str], FileContent], |
| 70 | + # (filename, file (or bytes), content_type) |
| 71 | + Tuple[Optional[str], FileContent, Optional[str]], |
| 72 | + # (filename, file (or bytes), content_type, headers) |
| 73 | + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], |
| 74 | +] |
| 75 | +RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] |
| 76 | + |
| 77 | +# duplicate of the above but without our custom file support |
| 78 | +HttpxFileContent = Union[IO[bytes], bytes] |
| 79 | +HttpxFileTypes = Union[ |
| 80 | + # file (or bytes) |
| 81 | + HttpxFileContent, |
| 82 | + # (filename, file (or bytes)) |
| 83 | + Tuple[Optional[str], HttpxFileContent], |
| 84 | + # (filename, file (or bytes), content_type) |
| 85 | + Tuple[Optional[str], HttpxFileContent, Optional[str]], |
| 86 | + # (filename, file (or bytes), content_type, headers) |
| 87 | + Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]], |
| 88 | +] |
| 89 | +HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]] |
| 90 | + |
| 91 | +# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT |
| 92 | +# where ResponseT includes `None`. In order to support directly |
| 93 | +# passing `None`, overloads would have to be defined for every |
| 94 | +# method that uses `ResponseT` which would lead to an unacceptable |
| 95 | +# amount of code duplication and make it unreadable. See _base_client.py |
| 96 | +# for example usage. |
| 97 | +# |
| 98 | +# This unfortunately means that you will either have |
| 99 | +# to import this type and pass it explicitly: |
| 100 | +# |
| 101 | +# from llama_stack_client import NoneType |
| 102 | +# client.get('/foo', cast_to=NoneType) |
| 103 | +# |
| 104 | +# or build it yourself: |
| 105 | +# |
| 106 | +# client.get('/foo', cast_to=type(None)) |
| 107 | +if TYPE_CHECKING: |
| 108 | + NoneType: Type[None] |
| 109 | +else: |
| 110 | + NoneType = type(None) |
| 111 | + |
| 112 | + |
| 113 | +class RequestOptions(TypedDict, total=False): |
| 114 | + headers: Headers |
| 115 | + max_retries: int |
| 116 | + timeout: float | Timeout | None |
| 117 | + params: Query |
| 118 | + extra_json: AnyMapping |
| 119 | + idempotency_key: str |
| 120 | + follow_redirects: bool |
| 121 | + |
| 122 | + |
| 123 | +# Sentinel class used until PEP 0661 is accepted |
| 124 | +class NotGiven: |
| 125 | + """ |
| 126 | + For parameters with a meaningful None value, we need to distinguish between |
| 127 | + the user explicitly passing None, and the user not passing the parameter at |
| 128 | + all. |
| 129 | + |
| 130 | + User code shouldn't need to use not_given directly. |
| 131 | + |
| 132 | + For example: |
| 133 | + |
| 134 | + ```py |
| 135 | + def create(timeout: Timeout | None | NotGiven = not_given): ... |
| 136 | + |
| 137 | + |
| 138 | + create(timeout=1) # 1s timeout |
| 139 | + create(timeout=None) # No timeout |
| 140 | + create() # Default timeout behavior |
| 141 | + ``` |
| 142 | + """ |
| 143 | + |
| 144 | + def __bool__(self) -> Literal[False]: |
| 145 | + return False |
| 146 | + |
| 147 | + @override |
| 148 | + def __repr__(self) -> str: |
| 149 | + return "NOT_GIVEN" |
| 150 | + |
| 151 | + |
| 152 | +not_given = NotGiven() |
| 153 | +# for backwards compatibility: |
| 154 | +NOT_GIVEN = NotGiven() |
| 155 | + |
| 156 | + |
| 157 | +class Omit: |
| 158 | + """ |
| 159 | + To explicitly omit something from being sent in a request, use `omit`. |
| 160 | + |
| 161 | + ```py |
| 162 | + # as the default `Content-Type` header is `application/json` that will be sent |
| 163 | + client.post("/upload/files", files={"file": b"my raw file content"}) |
| 164 | + |
| 165 | + # you can't explicitly override the header as it has to be dynamically generated |
| 166 | + # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' |
| 167 | + client.post(..., headers={"Content-Type": "multipart/form-data"}) |
| 168 | + |
| 169 | + # instead you can remove the default `application/json` header by passing omit |
| 170 | + client.post(..., headers={"Content-Type": omit}) |
| 171 | + ``` |
| 172 | + """ |
| 173 | + |
| 174 | + def __bool__(self) -> Literal[False]: |
| 175 | + return False |
| 176 | + |
| 177 | + |
| 178 | +omit = Omit() |
| 179 | + |
| 180 | + |
| 181 | +@runtime_checkable |
| 182 | +class ModelBuilderProtocol(Protocol): |
| 183 | + @classmethod |
| 184 | + def build( |
| 185 | + cls: type[_T], |
| 186 | + *, |
| 187 | + response: Response, |
| 188 | + data: object, |
| 189 | + ) -> _T: ... |
| 190 | + |
| 191 | + |
| 192 | +Headers = Mapping[str, Union[str, Omit]] |
| 193 | + |
| 194 | + |
| 195 | +class HeadersLikeProtocol(Protocol): |
| 196 | + def get(self, __key: str) -> str | None: ... |
| 197 | + |
| 198 | + |
| 199 | +HeadersLike = Union[Headers, HeadersLikeProtocol] |
| 200 | + |
| 201 | +ResponseT = TypeVar( |
| 202 | + "ResponseT", |
| 203 | + bound=Union[ |
| 204 | + object, |
| 205 | + str, |
| 206 | + None, |
| 207 | + "BaseModel", |
| 208 | + List[Any], |
| 209 | + Dict[str, Any], |
| 210 | + Response, |
| 211 | + ModelBuilderProtocol, |
| 212 | + "APIResponse[Any]", |
| 213 | + "AsyncAPIResponse[Any]", |
| 214 | + ], |
| 215 | +) |
| 216 | + |
| 217 | +StrBytesIntFloat = Union[str, bytes, int, float] |
| 218 | + |
| 219 | +# Note: copied from Pydantic |
| 220 | +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 |
| 221 | +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] |
| 222 | + |
| 223 | +PostParser = Callable[[Any], Any] |
| 224 | + |
| 225 | + |
| 226 | +@runtime_checkable |
| 227 | +class InheritsGeneric(Protocol): |
| 228 | + """Represents a type that has inherited from `Generic` |
| 229 | + |
| 230 | + The `__orig_bases__` property can be used to determine the resolved |
| 231 | + type variable for a given base class. |
| 232 | + """ |
| 233 | + |
| 234 | + __orig_bases__: tuple[_GenericAlias] |
| 235 | + |
| 236 | + |
| 237 | +class _GenericAlias(Protocol): |
| 238 | + __origin__: type[object] |
| 239 | + |
| 240 | + |
| 241 | +class HttpxSendArgs(TypedDict, total=False): |
| 242 | + auth: httpx.Auth |
| 243 | + follow_redirects: bool |
| 244 | + |
| 245 | + |
| 246 | +_T_co = TypeVar("_T_co", covariant=True) |
| 247 | + |
| 248 | + |
| 249 | +if TYPE_CHECKING: |
| 250 | + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) |
| 251 | + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 |
| 252 | + class SequenceNotStr(Protocol[_T_co]): |
| 253 | + @overload |
| 254 | + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... |
| 255 | + @overload |
| 256 | + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... |
| 257 | + def __contains__(self, value: object, /) -> bool: ... |
| 258 | + def __len__(self) -> int: ... |
| 259 | + def __iter__(self) -> Iterator[_T_co]: ... |
| 260 | + def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... |
| 261 | + def count(self, value: Any, /) -> int: ... |
| 262 | + def __reversed__(self) -> Iterator[_T_co]: ... |
| 263 | +else: |
| 264 | + # just point this to a normal `Sequence` at runtime to avoid having to special case |
| 265 | + # deserializing our custom sequence type |
| 266 | + SequenceNotStr = Sequence |
0 commit comments