cartesia 1.4.0__py3-none-any.whl → 2.0.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cartesia/__init__.py +288 -3
- cartesia/api_status/__init__.py +6 -0
- cartesia/api_status/client.py +104 -0
- cartesia/api_status/requests/__init__.py +5 -0
- cartesia/api_status/requests/api_info.py +8 -0
- cartesia/api_status/types/__init__.py +5 -0
- cartesia/api_status/types/api_info.py +20 -0
- cartesia/base_client.py +160 -0
- cartesia/client.py +163 -40
- cartesia/core/__init__.py +47 -0
- cartesia/core/api_error.py +15 -0
- cartesia/core/client_wrapper.py +55 -0
- cartesia/core/datetime_utils.py +28 -0
- cartesia/core/file.py +67 -0
- cartesia/core/http_client.py +499 -0
- cartesia/core/jsonable_encoder.py +101 -0
- cartesia/core/pydantic_utilities.py +296 -0
- cartesia/core/query_encoder.py +58 -0
- cartesia/core/remove_none_from_dict.py +11 -0
- cartesia/core/request_options.py +35 -0
- cartesia/core/serialization.py +272 -0
- cartesia/datasets/__init__.py +24 -0
- cartesia/datasets/client.py +422 -0
- cartesia/datasets/requests/__init__.py +15 -0
- cartesia/datasets/requests/create_dataset_request.py +7 -0
- cartesia/datasets/requests/dataset.py +9 -0
- cartesia/datasets/requests/dataset_file.py +9 -0
- cartesia/datasets/requests/paginated_dataset_files.py +10 -0
- cartesia/datasets/requests/paginated_datasets.py +10 -0
- cartesia/datasets/types/__init__.py +17 -0
- cartesia/datasets/types/create_dataset_request.py +19 -0
- cartesia/datasets/types/dataset.py +21 -0
- cartesia/datasets/types/dataset_file.py +21 -0
- cartesia/datasets/types/file_purpose.py +5 -0
- cartesia/datasets/types/paginated_dataset_files.py +21 -0
- cartesia/datasets/types/paginated_datasets.py +21 -0
- cartesia/embedding/__init__.py +5 -0
- cartesia/embedding/types/__init__.py +5 -0
- cartesia/embedding/types/embedding.py +201 -0
- cartesia/environment.py +7 -0
- cartesia/infill/__init__.py +2 -0
- cartesia/infill/client.py +294 -0
- cartesia/tts/__init__.py +167 -0
- cartesia/{_async_websocket.py → tts/_async_websocket.py} +159 -84
- cartesia/tts/_websocket.py +430 -0
- cartesia/tts/client.py +407 -0
- cartesia/tts/requests/__init__.py +76 -0
- cartesia/tts/requests/cancel_context_request.py +17 -0
- cartesia/tts/requests/controls.py +11 -0
- cartesia/tts/requests/generation_request.py +53 -0
- cartesia/tts/requests/mp_3_output_format.py +11 -0
- cartesia/tts/requests/output_format.py +30 -0
- cartesia/tts/requests/phoneme_timestamps.py +10 -0
- cartesia/tts/requests/raw_output_format.py +11 -0
- cartesia/tts/requests/speed.py +7 -0
- cartesia/tts/requests/tts_request.py +24 -0
- cartesia/tts/requests/tts_request_embedding_specifier.py +16 -0
- cartesia/tts/requests/tts_request_id_specifier.py +16 -0
- cartesia/tts/requests/tts_request_voice_specifier.py +7 -0
- cartesia/tts/requests/wav_output_format.py +7 -0
- cartesia/tts/requests/web_socket_base_response.py +11 -0
- cartesia/tts/requests/web_socket_chunk_response.py +8 -0
- cartesia/tts/requests/web_socket_done_response.py +7 -0
- cartesia/tts/requests/web_socket_error_response.py +7 -0
- cartesia/tts/requests/web_socket_flush_done_response.py +9 -0
- cartesia/tts/requests/web_socket_phoneme_timestamps_response.py +9 -0
- cartesia/tts/requests/web_socket_raw_output_format.py +11 -0
- cartesia/tts/requests/web_socket_request.py +7 -0
- cartesia/tts/requests/web_socket_response.py +69 -0
- cartesia/tts/requests/web_socket_stream_options.py +8 -0
- cartesia/tts/requests/web_socket_timestamps_response.py +9 -0
- cartesia/tts/requests/web_socket_tts_output.py +18 -0
- cartesia/tts/requests/web_socket_tts_request.py +24 -0
- cartesia/tts/requests/word_timestamps.py +10 -0
- cartesia/tts/socket_client.py +302 -0
- cartesia/tts/types/__init__.py +90 -0
- cartesia/tts/types/cancel_context_request.py +28 -0
- cartesia/tts/types/context_id.py +3 -0
- cartesia/tts/types/controls.py +22 -0
- cartesia/tts/types/emotion.py +29 -0
- cartesia/tts/types/flush_id.py +3 -0
- cartesia/tts/types/generation_request.py +66 -0
- cartesia/tts/types/mp_3_output_format.py +23 -0
- cartesia/tts/types/natural_specifier.py +5 -0
- cartesia/tts/types/numerical_specifier.py +3 -0
- cartesia/tts/types/output_format.py +58 -0
- cartesia/tts/types/phoneme_timestamps.py +21 -0
- cartesia/tts/types/raw_encoding.py +5 -0
- cartesia/tts/types/raw_output_format.py +22 -0
- cartesia/tts/types/speed.py +7 -0
- cartesia/tts/types/supported_language.py +7 -0
- cartesia/tts/types/tts_request.py +35 -0
- cartesia/tts/types/tts_request_embedding_specifier.py +27 -0
- cartesia/tts/types/tts_request_id_specifier.py +27 -0
- cartesia/tts/types/tts_request_voice_specifier.py +7 -0
- cartesia/tts/types/wav_output_format.py +17 -0
- cartesia/tts/types/web_socket_base_response.py +22 -0
- cartesia/tts/types/web_socket_chunk_response.py +20 -0
- cartesia/tts/types/web_socket_done_response.py +17 -0
- cartesia/tts/types/web_socket_error_response.py +19 -0
- cartesia/tts/types/web_socket_flush_done_response.py +21 -0
- cartesia/tts/types/web_socket_phoneme_timestamps_response.py +20 -0
- cartesia/tts/types/web_socket_raw_output_format.py +22 -0
- cartesia/tts/types/web_socket_request.py +7 -0
- cartesia/tts/types/web_socket_response.py +124 -0
- cartesia/tts/types/web_socket_stream_options.py +19 -0
- cartesia/tts/types/web_socket_timestamps_response.py +20 -0
- cartesia/tts/types/web_socket_tts_output.py +27 -0
- cartesia/tts/types/web_socket_tts_request.py +36 -0
- cartesia/tts/types/word_timestamps.py +21 -0
- cartesia/tts/utils/tts.py +64 -0
- cartesia/tts/utils/types.py +70 -0
- cartesia/version.py +3 -1
- cartesia/voice_changer/__init__.py +27 -0
- cartesia/voice_changer/client.py +395 -0
- cartesia/voice_changer/requests/__init__.py +15 -0
- cartesia/voice_changer/requests/streaming_response.py +36 -0
- cartesia/voice_changer/types/__init__.py +17 -0
- cartesia/voice_changer/types/output_format_container.py +5 -0
- cartesia/voice_changer/types/streaming_response.py +62 -0
- cartesia/voices/__init__.py +67 -0
- cartesia/voices/client.py +1812 -0
- cartesia/voices/requests/__init__.py +27 -0
- cartesia/voices/requests/create_voice_request.py +21 -0
- cartesia/voices/requests/embedding_response.py +8 -0
- cartesia/voices/requests/embedding_specifier.py +10 -0
- cartesia/voices/requests/id_specifier.py +10 -0
- cartesia/voices/requests/localize_dialect.py +6 -0
- cartesia/voices/requests/localize_voice_request.py +15 -0
- cartesia/voices/requests/mix_voice_specifier.py +7 -0
- cartesia/voices/requests/mix_voices_request.py +9 -0
- cartesia/voices/requests/update_voice_request.py +15 -0
- cartesia/voices/requests/voice.py +39 -0
- cartesia/voices/requests/voice_metadata.py +36 -0
- cartesia/voices/types/__init__.py +41 -0
- cartesia/voices/types/base_voice_id.py +5 -0
- cartesia/voices/types/clone_mode.py +5 -0
- cartesia/voices/types/create_voice_request.py +32 -0
- cartesia/voices/types/embedding_response.py +20 -0
- cartesia/voices/types/embedding_specifier.py +22 -0
- cartesia/voices/types/gender.py +5 -0
- cartesia/voices/types/id_specifier.py +22 -0
- cartesia/voices/types/localize_dialect.py +6 -0
- cartesia/voices/types/localize_english_dialect.py +5 -0
- cartesia/voices/types/localize_target_language.py +7 -0
- cartesia/voices/types/localize_voice_request.py +26 -0
- cartesia/voices/types/mix_voice_specifier.py +7 -0
- cartesia/voices/types/mix_voices_request.py +20 -0
- cartesia/voices/types/update_voice_request.py +27 -0
- cartesia/voices/types/voice.py +50 -0
- cartesia/voices/types/voice_id.py +3 -0
- cartesia/voices/types/voice_metadata.py +48 -0
- cartesia/voices/types/weight.py +3 -0
- cartesia-2.0.0a0.dist-info/METADATA +306 -0
- cartesia-2.0.0a0.dist-info/RECORD +158 -0
- {cartesia-1.4.0.dist-info → cartesia-2.0.0a0.dist-info}/WHEEL +1 -1
- cartesia/_async_sse.py +0 -95
- cartesia/_logger.py +0 -3
- cartesia/_sse.py +0 -143
- cartesia/_types.py +0 -70
- cartesia/_websocket.py +0 -358
- cartesia/async_client.py +0 -82
- cartesia/async_tts.py +0 -176
- cartesia/resource.py +0 -44
- cartesia/tts.py +0 -292
- cartesia/utils/deprecated.py +0 -55
- cartesia/utils/retry.py +0 -87
- cartesia/utils/tts.py +0 -78
- cartesia/voices.py +0 -204
- cartesia-1.4.0.dist-info/METADATA +0 -663
- cartesia-1.4.0.dist-info/RECORD +0 -23
- cartesia-1.4.0.dist-info/licenses/LICENSE.md +0 -21
- /cartesia/{utils/__init__.py → py.typed} +0 -0
- /cartesia/{_constants.py → tts/utils/constants.py} +0 -0
@@ -0,0 +1,499 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import email.utils
|
5
|
+
import json
|
6
|
+
import re
|
7
|
+
import time
|
8
|
+
import typing
|
9
|
+
import urllib.parse
|
10
|
+
from contextlib import asynccontextmanager, contextmanager
|
11
|
+
from random import random
|
12
|
+
|
13
|
+
import httpx
|
14
|
+
|
15
|
+
from .file import File, convert_file_dict_to_httpx_tuples
|
16
|
+
from .jsonable_encoder import jsonable_encoder
|
17
|
+
from .query_encoder import encode_query
|
18
|
+
from .remove_none_from_dict import remove_none_from_dict
|
19
|
+
from .request_options import RequestOptions
|
20
|
+
|
21
|
+
INITIAL_RETRY_DELAY_SECONDS = 0.5
|
22
|
+
MAX_RETRY_DELAY_SECONDS = 10
|
23
|
+
MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30
|
24
|
+
|
25
|
+
|
26
|
+
def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]:
|
27
|
+
"""
|
28
|
+
This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait.
|
29
|
+
|
30
|
+
Inspired by the urllib3 retry implementation.
|
31
|
+
"""
|
32
|
+
retry_after_ms = response_headers.get("retry-after-ms")
|
33
|
+
if retry_after_ms is not None:
|
34
|
+
try:
|
35
|
+
return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0
|
36
|
+
except Exception:
|
37
|
+
pass
|
38
|
+
|
39
|
+
retry_after = response_headers.get("retry-after")
|
40
|
+
if retry_after is None:
|
41
|
+
return None
|
42
|
+
|
43
|
+
# Attempt to parse the header as an int.
|
44
|
+
if re.match(r"^\s*[0-9]+\s*$", retry_after):
|
45
|
+
seconds = float(retry_after)
|
46
|
+
# Fallback to parsing it as a date.
|
47
|
+
else:
|
48
|
+
retry_date_tuple = email.utils.parsedate_tz(retry_after)
|
49
|
+
if retry_date_tuple is None:
|
50
|
+
return None
|
51
|
+
if retry_date_tuple[9] is None: # Python 2
|
52
|
+
# Assume UTC if no timezone was specified
|
53
|
+
# On Python2.7, parsedate_tz returns None for a timezone offset
|
54
|
+
# instead of 0 if no timezone is given, where mktime_tz treats
|
55
|
+
# a None timezone offset as local time.
|
56
|
+
retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:]
|
57
|
+
|
58
|
+
retry_date = email.utils.mktime_tz(retry_date_tuple)
|
59
|
+
seconds = retry_date - time.time()
|
60
|
+
|
61
|
+
if seconds < 0:
|
62
|
+
seconds = 0
|
63
|
+
|
64
|
+
return seconds
|
65
|
+
|
66
|
+
|
67
|
+
def _retry_timeout(response: httpx.Response, retries: int) -> float:
|
68
|
+
"""
|
69
|
+
Determine the amount of time to wait before retrying a request.
|
70
|
+
This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff
|
71
|
+
with a jitter to determine the number of seconds to wait.
|
72
|
+
"""
|
73
|
+
|
74
|
+
# If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
|
75
|
+
retry_after = _parse_retry_after(response.headers)
|
76
|
+
if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER:
|
77
|
+
return retry_after
|
78
|
+
|
79
|
+
# Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS.
|
80
|
+
retry_delay = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS)
|
81
|
+
|
82
|
+
# Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries.
|
83
|
+
timeout = retry_delay * (1 - 0.25 * random())
|
84
|
+
return timeout if timeout >= 0 else 0
|
85
|
+
|
86
|
+
|
87
|
+
def _should_retry(response: httpx.Response) -> bool:
|
88
|
+
retriable_400s = [429, 408, 409]
|
89
|
+
return response.status_code >= 500 or response.status_code in retriable_400s
|
90
|
+
|
91
|
+
|
92
|
+
def remove_omit_from_dict(
|
93
|
+
original: typing.Dict[str, typing.Optional[typing.Any]],
|
94
|
+
omit: typing.Optional[typing.Any],
|
95
|
+
) -> typing.Dict[str, typing.Any]:
|
96
|
+
if omit is None:
|
97
|
+
return original
|
98
|
+
new: typing.Dict[str, typing.Any] = {}
|
99
|
+
for key, value in original.items():
|
100
|
+
if value is not omit:
|
101
|
+
new[key] = value
|
102
|
+
return new
|
103
|
+
|
104
|
+
|
105
|
+
def maybe_filter_request_body(
|
106
|
+
data: typing.Optional[typing.Any],
|
107
|
+
request_options: typing.Optional[RequestOptions],
|
108
|
+
omit: typing.Optional[typing.Any],
|
109
|
+
) -> typing.Optional[typing.Any]:
|
110
|
+
if data is None:
|
111
|
+
return (
|
112
|
+
jsonable_encoder(request_options.get("additional_body_parameters", {})) or {}
|
113
|
+
if request_options is not None
|
114
|
+
else None
|
115
|
+
)
|
116
|
+
elif not isinstance(data, typing.Mapping):
|
117
|
+
data_content = jsonable_encoder(data)
|
118
|
+
else:
|
119
|
+
data_content = {
|
120
|
+
**(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore
|
121
|
+
**(
|
122
|
+
jsonable_encoder(request_options.get("additional_body_parameters", {})) or {}
|
123
|
+
if request_options is not None
|
124
|
+
else {}
|
125
|
+
),
|
126
|
+
}
|
127
|
+
return data_content
|
128
|
+
|
129
|
+
|
130
|
+
# Abstracted out for testing purposes
|
131
|
+
def get_request_body(
|
132
|
+
*,
|
133
|
+
json: typing.Optional[typing.Any],
|
134
|
+
data: typing.Optional[typing.Any],
|
135
|
+
request_options: typing.Optional[RequestOptions],
|
136
|
+
omit: typing.Optional[typing.Any],
|
137
|
+
) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]:
|
138
|
+
json_body = None
|
139
|
+
data_body = None
|
140
|
+
if data is not None:
|
141
|
+
data_body = maybe_filter_request_body(data, request_options, omit)
|
142
|
+
else:
|
143
|
+
# If both data and json are None, we send json data in the event extra properties are specified
|
144
|
+
json_body = maybe_filter_request_body(json, request_options, omit)
|
145
|
+
|
146
|
+
# If you have an empty JSON body, you should just send None
|
147
|
+
return (json_body if json_body != {} else None), data_body if data_body != {} else None
|
148
|
+
|
149
|
+
|
150
|
+
class HttpClient:
|
151
|
+
def __init__(
|
152
|
+
self,
|
153
|
+
*,
|
154
|
+
httpx_client: httpx.Client,
|
155
|
+
base_timeout: typing.Callable[[], typing.Optional[float]],
|
156
|
+
base_headers: typing.Callable[[], typing.Dict[str, str]],
|
157
|
+
base_url: typing.Optional[typing.Callable[[], str]] = None,
|
158
|
+
):
|
159
|
+
self.base_url = base_url
|
160
|
+
self.base_timeout = base_timeout
|
161
|
+
self.base_headers = base_headers
|
162
|
+
self.httpx_client = httpx_client
|
163
|
+
|
164
|
+
def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
|
165
|
+
base_url = maybe_base_url
|
166
|
+
if self.base_url is not None and base_url is None:
|
167
|
+
base_url = self.base_url()
|
168
|
+
|
169
|
+
if base_url is None:
|
170
|
+
raise ValueError("A base_url is required to make this request, please provide one and try again.")
|
171
|
+
return base_url
|
172
|
+
|
173
|
+
def request(
|
174
|
+
self,
|
175
|
+
path: typing.Optional[str] = None,
|
176
|
+
*,
|
177
|
+
method: str,
|
178
|
+
base_url: typing.Optional[str] = None,
|
179
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
180
|
+
json: typing.Optional[typing.Any] = None,
|
181
|
+
data: typing.Optional[typing.Any] = None,
|
182
|
+
content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
|
183
|
+
files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
|
184
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
185
|
+
request_options: typing.Optional[RequestOptions] = None,
|
186
|
+
retries: int = 0,
|
187
|
+
omit: typing.Optional[typing.Any] = None,
|
188
|
+
) -> httpx.Response:
|
189
|
+
base_url = self.get_base_url(base_url)
|
190
|
+
timeout = (
|
191
|
+
request_options.get("timeout_in_seconds")
|
192
|
+
if request_options is not None and request_options.get("timeout_in_seconds") is not None
|
193
|
+
else self.base_timeout()
|
194
|
+
)
|
195
|
+
|
196
|
+
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
|
197
|
+
|
198
|
+
response = self.httpx_client.request(
|
199
|
+
method=method,
|
200
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
201
|
+
headers=jsonable_encoder(
|
202
|
+
remove_none_from_dict(
|
203
|
+
{
|
204
|
+
**self.base_headers(),
|
205
|
+
**(headers if headers is not None else {}),
|
206
|
+
**(request_options.get("additional_headers", {}) or {} if request_options is not None else {}),
|
207
|
+
}
|
208
|
+
)
|
209
|
+
),
|
210
|
+
params=encode_query(
|
211
|
+
jsonable_encoder(
|
212
|
+
remove_none_from_dict(
|
213
|
+
remove_omit_from_dict(
|
214
|
+
{
|
215
|
+
**(params if params is not None else {}),
|
216
|
+
**(
|
217
|
+
request_options.get("additional_query_parameters", {}) or {}
|
218
|
+
if request_options is not None
|
219
|
+
else {}
|
220
|
+
),
|
221
|
+
},
|
222
|
+
omit,
|
223
|
+
)
|
224
|
+
)
|
225
|
+
)
|
226
|
+
),
|
227
|
+
json=json_body,
|
228
|
+
data=data_body,
|
229
|
+
content=content,
|
230
|
+
files=(
|
231
|
+
convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
|
232
|
+
if (files is not None and files is not omit)
|
233
|
+
else None
|
234
|
+
),
|
235
|
+
timeout=timeout,
|
236
|
+
)
|
237
|
+
|
238
|
+
max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0
|
239
|
+
if _should_retry(response=response):
|
240
|
+
if max_retries > retries:
|
241
|
+
time.sleep(_retry_timeout(response=response, retries=retries))
|
242
|
+
return self.request(
|
243
|
+
path=path,
|
244
|
+
method=method,
|
245
|
+
base_url=base_url,
|
246
|
+
params=params,
|
247
|
+
json=json,
|
248
|
+
content=content,
|
249
|
+
files=files,
|
250
|
+
headers=headers,
|
251
|
+
request_options=request_options,
|
252
|
+
retries=retries + 1,
|
253
|
+
omit=omit,
|
254
|
+
)
|
255
|
+
|
256
|
+
return response
|
257
|
+
|
258
|
+
@contextmanager
|
259
|
+
def stream(
|
260
|
+
self,
|
261
|
+
path: typing.Optional[str] = None,
|
262
|
+
*,
|
263
|
+
method: str,
|
264
|
+
base_url: typing.Optional[str] = None,
|
265
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
266
|
+
json: typing.Optional[typing.Any] = None,
|
267
|
+
data: typing.Optional[typing.Any] = None,
|
268
|
+
content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
|
269
|
+
files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
|
270
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
271
|
+
request_options: typing.Optional[RequestOptions] = None,
|
272
|
+
retries: int = 0,
|
273
|
+
omit: typing.Optional[typing.Any] = None,
|
274
|
+
) -> typing.Iterator[httpx.Response]:
|
275
|
+
base_url = self.get_base_url(base_url)
|
276
|
+
timeout = (
|
277
|
+
request_options.get("timeout_in_seconds")
|
278
|
+
if request_options is not None and request_options.get("timeout_in_seconds") is not None
|
279
|
+
else self.base_timeout()
|
280
|
+
)
|
281
|
+
|
282
|
+
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
|
283
|
+
|
284
|
+
with self.httpx_client.stream(
|
285
|
+
method=method,
|
286
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
287
|
+
headers=jsonable_encoder(
|
288
|
+
remove_none_from_dict(
|
289
|
+
{
|
290
|
+
**self.base_headers(),
|
291
|
+
**(headers if headers is not None else {}),
|
292
|
+
**(request_options.get("additional_headers", {}) if request_options is not None else {}),
|
293
|
+
}
|
294
|
+
)
|
295
|
+
),
|
296
|
+
params=encode_query(
|
297
|
+
jsonable_encoder(
|
298
|
+
remove_none_from_dict(
|
299
|
+
remove_omit_from_dict(
|
300
|
+
{
|
301
|
+
**(params if params is not None else {}),
|
302
|
+
**(
|
303
|
+
request_options.get("additional_query_parameters", {})
|
304
|
+
if request_options is not None
|
305
|
+
else {}
|
306
|
+
),
|
307
|
+
},
|
308
|
+
omit,
|
309
|
+
)
|
310
|
+
)
|
311
|
+
)
|
312
|
+
),
|
313
|
+
json=json_body,
|
314
|
+
data=data_body,
|
315
|
+
content=content,
|
316
|
+
files=(
|
317
|
+
convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
|
318
|
+
if (files is not None and files is not omit)
|
319
|
+
else None
|
320
|
+
),
|
321
|
+
timeout=timeout,
|
322
|
+
) as stream:
|
323
|
+
yield stream
|
324
|
+
|
325
|
+
|
326
|
+
class AsyncHttpClient:
|
327
|
+
def __init__(
|
328
|
+
self,
|
329
|
+
*,
|
330
|
+
httpx_client: httpx.AsyncClient,
|
331
|
+
base_timeout: typing.Callable[[], typing.Optional[float]],
|
332
|
+
base_headers: typing.Callable[[], typing.Dict[str, str]],
|
333
|
+
base_url: typing.Optional[typing.Callable[[], str]] = None,
|
334
|
+
):
|
335
|
+
self.base_url = base_url
|
336
|
+
self.base_timeout = base_timeout
|
337
|
+
self.base_headers = base_headers
|
338
|
+
self.httpx_client = httpx_client
|
339
|
+
|
340
|
+
def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
|
341
|
+
base_url = maybe_base_url
|
342
|
+
if self.base_url is not None and base_url is None:
|
343
|
+
base_url = self.base_url()
|
344
|
+
|
345
|
+
if base_url is None:
|
346
|
+
raise ValueError("A base_url is required to make this request, please provide one and try again.")
|
347
|
+
return base_url
|
348
|
+
|
349
|
+
async def request(
|
350
|
+
self,
|
351
|
+
path: typing.Optional[str] = None,
|
352
|
+
*,
|
353
|
+
method: str,
|
354
|
+
base_url: typing.Optional[str] = None,
|
355
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
356
|
+
json: typing.Optional[typing.Any] = None,
|
357
|
+
data: typing.Optional[typing.Any] = None,
|
358
|
+
content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
|
359
|
+
files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
|
360
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
361
|
+
request_options: typing.Optional[RequestOptions] = None,
|
362
|
+
retries: int = 0,
|
363
|
+
omit: typing.Optional[typing.Any] = None,
|
364
|
+
) -> httpx.Response:
|
365
|
+
base_url = self.get_base_url(base_url)
|
366
|
+
timeout = (
|
367
|
+
request_options.get("timeout_in_seconds")
|
368
|
+
if request_options is not None and request_options.get("timeout_in_seconds") is not None
|
369
|
+
else self.base_timeout()
|
370
|
+
)
|
371
|
+
|
372
|
+
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
|
373
|
+
|
374
|
+
# Add the input to each of these and do None-safety checks
|
375
|
+
response = await self.httpx_client.request(
|
376
|
+
method=method,
|
377
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
378
|
+
headers=jsonable_encoder(
|
379
|
+
remove_none_from_dict(
|
380
|
+
{
|
381
|
+
**self.base_headers(),
|
382
|
+
**(headers if headers is not None else {}),
|
383
|
+
**(request_options.get("additional_headers", {}) or {} if request_options is not None else {}),
|
384
|
+
}
|
385
|
+
)
|
386
|
+
),
|
387
|
+
params=encode_query(
|
388
|
+
jsonable_encoder(
|
389
|
+
remove_none_from_dict(
|
390
|
+
remove_omit_from_dict(
|
391
|
+
{
|
392
|
+
**(params if params is not None else {}),
|
393
|
+
**(
|
394
|
+
request_options.get("additional_query_parameters", {}) or {}
|
395
|
+
if request_options is not None
|
396
|
+
else {}
|
397
|
+
),
|
398
|
+
},
|
399
|
+
omit,
|
400
|
+
)
|
401
|
+
)
|
402
|
+
)
|
403
|
+
),
|
404
|
+
json=json_body,
|
405
|
+
data=data_body,
|
406
|
+
content=content,
|
407
|
+
files=(
|
408
|
+
convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
|
409
|
+
if files is not None
|
410
|
+
else None
|
411
|
+
),
|
412
|
+
timeout=timeout,
|
413
|
+
)
|
414
|
+
|
415
|
+
max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0
|
416
|
+
if _should_retry(response=response):
|
417
|
+
if max_retries > retries:
|
418
|
+
await asyncio.sleep(_retry_timeout(response=response, retries=retries))
|
419
|
+
return await self.request(
|
420
|
+
path=path,
|
421
|
+
method=method,
|
422
|
+
base_url=base_url,
|
423
|
+
params=params,
|
424
|
+
json=json,
|
425
|
+
content=content,
|
426
|
+
files=files,
|
427
|
+
headers=headers,
|
428
|
+
request_options=request_options,
|
429
|
+
retries=retries + 1,
|
430
|
+
omit=omit,
|
431
|
+
)
|
432
|
+
return response
|
433
|
+
|
434
|
+
@asynccontextmanager
|
435
|
+
async def stream(
|
436
|
+
self,
|
437
|
+
path: typing.Optional[str] = None,
|
438
|
+
*,
|
439
|
+
method: str,
|
440
|
+
base_url: typing.Optional[str] = None,
|
441
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
442
|
+
json: typing.Optional[typing.Any] = None,
|
443
|
+
data: typing.Optional[typing.Any] = None,
|
444
|
+
content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
|
445
|
+
files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
|
446
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
447
|
+
request_options: typing.Optional[RequestOptions] = None,
|
448
|
+
retries: int = 0,
|
449
|
+
omit: typing.Optional[typing.Any] = None,
|
450
|
+
) -> typing.AsyncIterator[httpx.Response]:
|
451
|
+
base_url = self.get_base_url(base_url)
|
452
|
+
timeout = (
|
453
|
+
request_options.get("timeout_in_seconds")
|
454
|
+
if request_options is not None and request_options.get("timeout_in_seconds") is not None
|
455
|
+
else self.base_timeout()
|
456
|
+
)
|
457
|
+
|
458
|
+
json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
|
459
|
+
|
460
|
+
async with self.httpx_client.stream(
|
461
|
+
method=method,
|
462
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
463
|
+
headers=jsonable_encoder(
|
464
|
+
remove_none_from_dict(
|
465
|
+
{
|
466
|
+
**self.base_headers(),
|
467
|
+
**(headers if headers is not None else {}),
|
468
|
+
**(request_options.get("additional_headers", {}) if request_options is not None else {}),
|
469
|
+
}
|
470
|
+
)
|
471
|
+
),
|
472
|
+
params=encode_query(
|
473
|
+
jsonable_encoder(
|
474
|
+
remove_none_from_dict(
|
475
|
+
remove_omit_from_dict(
|
476
|
+
{
|
477
|
+
**(params if params is not None else {}),
|
478
|
+
**(
|
479
|
+
request_options.get("additional_query_parameters", {})
|
480
|
+
if request_options is not None
|
481
|
+
else {}
|
482
|
+
),
|
483
|
+
},
|
484
|
+
omit=omit,
|
485
|
+
)
|
486
|
+
)
|
487
|
+
)
|
488
|
+
),
|
489
|
+
json=json_body,
|
490
|
+
data=data_body,
|
491
|
+
content=content,
|
492
|
+
files=(
|
493
|
+
convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
|
494
|
+
if files is not None
|
495
|
+
else None
|
496
|
+
),
|
497
|
+
timeout=timeout,
|
498
|
+
) as stream:
|
499
|
+
yield stream
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
"""
|
4
|
+
jsonable_encoder converts a Python object to a JSON-friendly dict
|
5
|
+
(e.g. datetimes to strings, Pydantic models to dicts).
|
6
|
+
|
7
|
+
Taken from FastAPI, and made a bit simpler
|
8
|
+
https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py
|
9
|
+
"""
|
10
|
+
|
11
|
+
import base64
|
12
|
+
import dataclasses
|
13
|
+
import datetime as dt
|
14
|
+
from enum import Enum
|
15
|
+
from pathlib import PurePath
|
16
|
+
from types import GeneratorType
|
17
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
18
|
+
|
19
|
+
import pydantic
|
20
|
+
|
21
|
+
from .datetime_utils import serialize_datetime
|
22
|
+
from .pydantic_utilities import (
|
23
|
+
IS_PYDANTIC_V2,
|
24
|
+
encode_by_type,
|
25
|
+
to_jsonable_with_fallback,
|
26
|
+
)
|
27
|
+
|
28
|
+
SetIntStr = Set[Union[int, str]]
|
29
|
+
DictIntStrAny = Dict[Union[int, str], Any]
|
30
|
+
|
31
|
+
|
32
|
+
def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any:
|
33
|
+
custom_encoder = custom_encoder or {}
|
34
|
+
if custom_encoder:
|
35
|
+
if type(obj) in custom_encoder:
|
36
|
+
return custom_encoder[type(obj)](obj)
|
37
|
+
else:
|
38
|
+
for encoder_type, encoder_instance in custom_encoder.items():
|
39
|
+
if isinstance(obj, encoder_type):
|
40
|
+
return encoder_instance(obj)
|
41
|
+
if isinstance(obj, pydantic.BaseModel):
|
42
|
+
if IS_PYDANTIC_V2:
|
43
|
+
encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2
|
44
|
+
else:
|
45
|
+
encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1
|
46
|
+
if custom_encoder:
|
47
|
+
encoder.update(custom_encoder)
|
48
|
+
obj_dict = obj.dict(by_alias=True)
|
49
|
+
if "__root__" in obj_dict:
|
50
|
+
obj_dict = obj_dict["__root__"]
|
51
|
+
if "root" in obj_dict:
|
52
|
+
obj_dict = obj_dict["root"]
|
53
|
+
return jsonable_encoder(obj_dict, custom_encoder=encoder)
|
54
|
+
if dataclasses.is_dataclass(obj):
|
55
|
+
obj_dict = dataclasses.asdict(obj) # type: ignore
|
56
|
+
return jsonable_encoder(obj_dict, custom_encoder=custom_encoder)
|
57
|
+
if isinstance(obj, bytes):
|
58
|
+
return base64.b64encode(obj).decode("utf-8")
|
59
|
+
if isinstance(obj, Enum):
|
60
|
+
return obj.value
|
61
|
+
if isinstance(obj, PurePath):
|
62
|
+
return str(obj)
|
63
|
+
if isinstance(obj, (str, int, float, type(None))):
|
64
|
+
return obj
|
65
|
+
if isinstance(obj, dt.datetime):
|
66
|
+
return serialize_datetime(obj)
|
67
|
+
if isinstance(obj, dt.date):
|
68
|
+
return str(obj)
|
69
|
+
if isinstance(obj, dict):
|
70
|
+
encoded_dict = {}
|
71
|
+
allowed_keys = set(obj.keys())
|
72
|
+
for key, value in obj.items():
|
73
|
+
if key in allowed_keys:
|
74
|
+
encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder)
|
75
|
+
encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder)
|
76
|
+
encoded_dict[encoded_key] = encoded_value
|
77
|
+
return encoded_dict
|
78
|
+
if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
|
79
|
+
encoded_list = []
|
80
|
+
for item in obj:
|
81
|
+
encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder))
|
82
|
+
return encoded_list
|
83
|
+
|
84
|
+
def fallback_serializer(o: Any) -> Any:
|
85
|
+
attempt_encode = encode_by_type(o)
|
86
|
+
if attempt_encode is not None:
|
87
|
+
return attempt_encode
|
88
|
+
|
89
|
+
try:
|
90
|
+
data = dict(o)
|
91
|
+
except Exception as e:
|
92
|
+
errors: List[Exception] = []
|
93
|
+
errors.append(e)
|
94
|
+
try:
|
95
|
+
data = vars(o)
|
96
|
+
except Exception as e:
|
97
|
+
errors.append(e)
|
98
|
+
raise ValueError(errors) from e
|
99
|
+
return jsonable_encoder(data, custom_encoder=custom_encoder)
|
100
|
+
|
101
|
+
return to_jsonable_with_fallback(obj, fallback_serializer)
|