runwayml 3.0.1__tar.gz → 3.0.3__tar.gz
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.
- runwayml-3.0.3/.release-please-manifest.json +3 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/CHANGELOG.md +30 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/PKG-INFO +1 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/pyproject.toml +2 -2
- {runwayml-3.0.1 → runwayml-3.0.3}/requirements-dev.lock +1 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_base_client.py +205 -240
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_client.py +1 -4
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_models.py +2 -3
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_response.py +1 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_typing.py +1 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_utils.py +9 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_version.py +1 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/resources/image_to_video.py +1 -4
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/types/image_to_video_create_response.py +0 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/conftest.py +1 -1
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_models.py +4 -1
- runwayml-3.0.1/.release-please-manifest.json +0 -3
- {runwayml-3.0.1 → runwayml-3.0.3}/.gitignore +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/CONTRIBUTING.md +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/LICENSE +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/README.md +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/SECURITY.md +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/api.md +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/bin/check-release-environment +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/bin/publish-pypi +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/examples/.keep +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/mypy.ini +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/noxfile.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/release-please-config.json +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/requirements.lock +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/__init__.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_compat.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_constants.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_exceptions.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_files.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_qs.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_resource.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_streaming.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_types.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/__init__.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_logs.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_proxy.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_reflection.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_streams.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_sync.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/_utils/_transform.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/lib/.keep +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/py.typed +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/resources/__init__.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/resources/organization.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/resources/tasks.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/types/__init__.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/types/image_to_video_create_params.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/types/organization_retrieve_response.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/src/runwayml/types/task_retrieve_response.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/__init__.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/api_resources/__init__.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/api_resources/test_image_to_video.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/api_resources/test_organization.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/api_resources/test_tasks.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/sample_file.txt +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_client.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_deepcopy.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_extract_files.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_files.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_qs.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_required_args.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_response.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_streaming.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_transform.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_utils/test_proxy.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/test_utils/test_typing.py +0 -0
- {runwayml-3.0.1 → runwayml-3.0.3}/tests/utils.py +0 -0
@@ -1,5 +1,35 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 3.0.3 (2025-04-24)
|
4
|
+
|
5
|
+
Full Changelog: [v3.0.2...v3.0.3](https://github.com/runwayml/sdk-python/compare/v3.0.2...v3.0.3)
|
6
|
+
|
7
|
+
### Bug Fixes
|
8
|
+
|
9
|
+
* **pydantic v1:** more robust ModelField.annotation check ([1f45aa4](https://github.com/runwayml/sdk-python/commit/1f45aa4ab023db52bc79a33492c4441f00d30d17))
|
10
|
+
|
11
|
+
|
12
|
+
### Chores
|
13
|
+
|
14
|
+
* broadly detect json family of content-type headers ([7adcad9](https://github.com/runwayml/sdk-python/commit/7adcad916636065b709d0bdff9c00047f48cf167))
|
15
|
+
* **ci:** add timeout thresholds for CI jobs ([a59f6e6](https://github.com/runwayml/sdk-python/commit/a59f6e62adc33fe07b81088f9ed066fd06118f63))
|
16
|
+
* **ci:** only use depot for staging repos ([bdd8635](https://github.com/runwayml/sdk-python/commit/bdd8635b236ca1686047d382f271e8a4954bc302))
|
17
|
+
* **internal:** codegen related update ([6ec7118](https://github.com/runwayml/sdk-python/commit/6ec7118bb5e1d1737d42cb017e2c8b133aa471d0))
|
18
|
+
* **internal:** fix list file params ([c745a86](https://github.com/runwayml/sdk-python/commit/c745a864dd1edcdc3ac38645963075e672723874))
|
19
|
+
* **internal:** import reformatting ([a991a2d](https://github.com/runwayml/sdk-python/commit/a991a2d2ac36ee544470999eeef0fe536b040b62))
|
20
|
+
* **internal:** minor formatting changes ([0f34a44](https://github.com/runwayml/sdk-python/commit/0f34a44ec0ee863d00e7656e7675ab43aa9ef973))
|
21
|
+
* **internal:** refactor retries to not use recursion ([b5637fb](https://github.com/runwayml/sdk-python/commit/b5637fbfc50816122689694ed2e0acef3020da03))
|
22
|
+
* **internal:** update models test ([79488d6](https://github.com/runwayml/sdk-python/commit/79488d6e50b1075d8f7cad6e3fb3ed022332e42e))
|
23
|
+
|
24
|
+
## 3.0.2 (2025-04-17)
|
25
|
+
|
26
|
+
Full Changelog: [v3.0.1...v3.0.2](https://github.com/runwayml/sdk-python/compare/v3.0.1...v3.0.2)
|
27
|
+
|
28
|
+
### Chores
|
29
|
+
|
30
|
+
* **internal:** base client updates ([6d4d8bb](https://github.com/runwayml/sdk-python/commit/6d4d8bb73df9c0985d12d349d783188de0cd7d7d))
|
31
|
+
* **internal:** bump pyright version ([5693e26](https://github.com/runwayml/sdk-python/commit/5693e261dcbc21f6a900c514ba8e6659e0a2cd4f))
|
32
|
+
|
3
33
|
## 3.0.1 (2025-04-15)
|
4
34
|
|
5
35
|
Full Changelog: [v3.0.0...v3.0.1](https://github.com/runwayml/sdk-python/compare/v3.0.0...v3.0.1)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "runwayml"
|
3
|
-
version = "3.0.
|
3
|
+
version = "3.0.3"
|
4
4
|
description = "The official Python library for the runwayml API"
|
5
5
|
dynamic = ["readme"]
|
6
6
|
license = "Apache-2.0"
|
@@ -42,7 +42,7 @@ Repository = "https://github.com/runwayml/sdk-python"
|
|
42
42
|
managed = true
|
43
43
|
# version pins are in requirements-dev.lock
|
44
44
|
dev-dependencies = [
|
45
|
-
"pyright
|
45
|
+
"pyright==1.1.399",
|
46
46
|
"mypy",
|
47
47
|
"respx",
|
48
48
|
"pytest",
|
@@ -98,7 +98,11 @@ _StreamT = TypeVar("_StreamT", bound=Stream[Any])
|
|
98
98
|
_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])
|
99
99
|
|
100
100
|
if TYPE_CHECKING:
|
101
|
-
from httpx._config import
|
101
|
+
from httpx._config import (
|
102
|
+
DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage]
|
103
|
+
)
|
104
|
+
|
105
|
+
HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
|
102
106
|
else:
|
103
107
|
try:
|
104
108
|
from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
|
@@ -115,6 +119,7 @@ class PageInfo:
|
|
115
119
|
|
116
120
|
url: URL | NotGiven
|
117
121
|
params: Query | NotGiven
|
122
|
+
json: Body | NotGiven
|
118
123
|
|
119
124
|
@overload
|
120
125
|
def __init__(
|
@@ -130,19 +135,30 @@ class PageInfo:
|
|
130
135
|
params: Query,
|
131
136
|
) -> None: ...
|
132
137
|
|
138
|
+
@overload
|
139
|
+
def __init__(
|
140
|
+
self,
|
141
|
+
*,
|
142
|
+
json: Body,
|
143
|
+
) -> None: ...
|
144
|
+
|
133
145
|
def __init__(
|
134
146
|
self,
|
135
147
|
*,
|
136
148
|
url: URL | NotGiven = NOT_GIVEN,
|
149
|
+
json: Body | NotGiven = NOT_GIVEN,
|
137
150
|
params: Query | NotGiven = NOT_GIVEN,
|
138
151
|
) -> None:
|
139
152
|
self.url = url
|
153
|
+
self.json = json
|
140
154
|
self.params = params
|
141
155
|
|
142
156
|
@override
|
143
157
|
def __repr__(self) -> str:
|
144
158
|
if self.url:
|
145
159
|
return f"{self.__class__.__name__}(url={self.url})"
|
160
|
+
if self.json:
|
161
|
+
return f"{self.__class__.__name__}(json={self.json})"
|
146
162
|
return f"{self.__class__.__name__}(params={self.params})"
|
147
163
|
|
148
164
|
|
@@ -191,6 +207,19 @@ class BasePage(GenericModel, Generic[_T]):
|
|
191
207
|
options.url = str(url)
|
192
208
|
return options
|
193
209
|
|
210
|
+
if not isinstance(info.json, NotGiven):
|
211
|
+
if not is_mapping(info.json):
|
212
|
+
raise TypeError("Pagination is only supported with mappings")
|
213
|
+
|
214
|
+
if not options.json_data:
|
215
|
+
options.json_data = {**info.json}
|
216
|
+
else:
|
217
|
+
if not is_mapping(options.json_data):
|
218
|
+
raise TypeError("Pagination is only supported with mappings")
|
219
|
+
|
220
|
+
options.json_data = {**options.json_data, **info.json}
|
221
|
+
return options
|
222
|
+
|
194
223
|
raise ValueError("Unexpected PageInfo state")
|
195
224
|
|
196
225
|
|
@@ -408,8 +437,7 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
408
437
|
headers = httpx.Headers(headers_dict)
|
409
438
|
|
410
439
|
idempotency_header = self._idempotency_header
|
411
|
-
if idempotency_header and options.
|
412
|
-
options.idempotency_key = options.idempotency_key or self._idempotency_key()
|
440
|
+
if idempotency_header and options.idempotency_key and idempotency_header not in headers:
|
413
441
|
headers[idempotency_header] = options.idempotency_key
|
414
442
|
|
415
443
|
# Don't set these headers if they were already set or removed by the caller. We check
|
@@ -874,7 +902,6 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
874
902
|
self,
|
875
903
|
cast_to: Type[ResponseT],
|
876
904
|
options: FinalRequestOptions,
|
877
|
-
remaining_retries: Optional[int] = None,
|
878
905
|
*,
|
879
906
|
stream: Literal[True],
|
880
907
|
stream_cls: Type[_StreamT],
|
@@ -885,7 +912,6 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
885
912
|
self,
|
886
913
|
cast_to: Type[ResponseT],
|
887
914
|
options: FinalRequestOptions,
|
888
|
-
remaining_retries: Optional[int] = None,
|
889
915
|
*,
|
890
916
|
stream: Literal[False] = False,
|
891
917
|
) -> ResponseT: ...
|
@@ -895,7 +921,6 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
895
921
|
self,
|
896
922
|
cast_to: Type[ResponseT],
|
897
923
|
options: FinalRequestOptions,
|
898
|
-
remaining_retries: Optional[int] = None,
|
899
924
|
*,
|
900
925
|
stream: bool = False,
|
901
926
|
stream_cls: Type[_StreamT] | None = None,
|
@@ -905,125 +930,109 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
905
930
|
self,
|
906
931
|
cast_to: Type[ResponseT],
|
907
932
|
options: FinalRequestOptions,
|
908
|
-
remaining_retries: Optional[int] = None,
|
909
933
|
*,
|
910
934
|
stream: bool = False,
|
911
935
|
stream_cls: type[_StreamT] | None = None,
|
912
936
|
) -> ResponseT | _StreamT:
|
913
|
-
|
914
|
-
retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
|
915
|
-
else:
|
916
|
-
retries_taken = 0
|
917
|
-
|
918
|
-
return self._request(
|
919
|
-
cast_to=cast_to,
|
920
|
-
options=options,
|
921
|
-
stream=stream,
|
922
|
-
stream_cls=stream_cls,
|
923
|
-
retries_taken=retries_taken,
|
924
|
-
)
|
937
|
+
cast_to = self._maybe_override_cast_to(cast_to, options)
|
925
938
|
|
926
|
-
def _request(
|
927
|
-
self,
|
928
|
-
*,
|
929
|
-
cast_to: Type[ResponseT],
|
930
|
-
options: FinalRequestOptions,
|
931
|
-
retries_taken: int,
|
932
|
-
stream: bool,
|
933
|
-
stream_cls: type[_StreamT] | None,
|
934
|
-
) -> ResponseT | _StreamT:
|
935
939
|
# create a copy of the options we were given so that if the
|
936
940
|
# options are mutated later & we then retry, the retries are
|
937
941
|
# given the original options
|
938
942
|
input_options = model_copy(options)
|
939
|
-
|
940
|
-
cast_to = self._maybe_override_cast_to(cast_to, options)
|
941
|
-
options = self._prepare_options(options)
|
942
|
-
|
943
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
944
|
-
request = self._build_request(options, retries_taken=retries_taken)
|
945
|
-
self._prepare_request(request)
|
946
|
-
|
947
|
-
if options.idempotency_key:
|
943
|
+
if input_options.idempotency_key is None and input_options.method.lower() != "get":
|
948
944
|
# ensure the idempotency key is reused between requests
|
949
|
-
input_options.idempotency_key =
|
945
|
+
input_options.idempotency_key = self._idempotency_key()
|
950
946
|
|
951
|
-
|
952
|
-
|
953
|
-
kwargs["auth"] = self.custom_auth
|
947
|
+
response: httpx.Response | None = None
|
948
|
+
max_retries = input_options.get_max_retries(self.max_retries)
|
954
949
|
|
955
|
-
|
950
|
+
retries_taken = 0
|
951
|
+
for retries_taken in range(max_retries + 1):
|
952
|
+
options = model_copy(input_options)
|
953
|
+
options = self._prepare_options(options)
|
956
954
|
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
stream=stream or self._should_stream_response_body(request=request),
|
961
|
-
**kwargs,
|
962
|
-
)
|
963
|
-
except httpx.TimeoutException as err:
|
964
|
-
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
955
|
+
remaining_retries = max_retries - retries_taken
|
956
|
+
request = self._build_request(options, retries_taken=retries_taken)
|
957
|
+
self._prepare_request(request)
|
965
958
|
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
cast_to,
|
970
|
-
retries_taken=retries_taken,
|
971
|
-
stream=stream,
|
972
|
-
stream_cls=stream_cls,
|
973
|
-
response_headers=None,
|
974
|
-
)
|
959
|
+
kwargs: HttpxSendArgs = {}
|
960
|
+
if self.custom_auth is not None:
|
961
|
+
kwargs["auth"] = self.custom_auth
|
975
962
|
|
976
|
-
log.debug("
|
977
|
-
raise APITimeoutError(request=request) from err
|
978
|
-
except Exception as err:
|
979
|
-
log.debug("Encountered Exception", exc_info=True)
|
963
|
+
log.debug("Sending HTTP Request: %s %s", request.method, request.url)
|
980
964
|
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
stream_cls=stream_cls,
|
988
|
-
response_headers=None,
|
965
|
+
response = None
|
966
|
+
try:
|
967
|
+
response = self._client.send(
|
968
|
+
request,
|
969
|
+
stream=stream or self._should_stream_response_body(request=request),
|
970
|
+
**kwargs,
|
989
971
|
)
|
972
|
+
except httpx.TimeoutException as err:
|
973
|
+
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
974
|
+
|
975
|
+
if remaining_retries > 0:
|
976
|
+
self._sleep_for_retry(
|
977
|
+
retries_taken=retries_taken,
|
978
|
+
max_retries=max_retries,
|
979
|
+
options=input_options,
|
980
|
+
response=None,
|
981
|
+
)
|
982
|
+
continue
|
983
|
+
|
984
|
+
log.debug("Raising timeout error")
|
985
|
+
raise APITimeoutError(request=request) from err
|
986
|
+
except Exception as err:
|
987
|
+
log.debug("Encountered Exception", exc_info=True)
|
988
|
+
|
989
|
+
if remaining_retries > 0:
|
990
|
+
self._sleep_for_retry(
|
991
|
+
retries_taken=retries_taken,
|
992
|
+
max_retries=max_retries,
|
993
|
+
options=input_options,
|
994
|
+
response=None,
|
995
|
+
)
|
996
|
+
continue
|
997
|
+
|
998
|
+
log.debug("Raising connection error")
|
999
|
+
raise APIConnectionError(request=request) from err
|
1000
|
+
|
1001
|
+
log.debug(
|
1002
|
+
'HTTP Response: %s %s "%i %s" %s',
|
1003
|
+
request.method,
|
1004
|
+
request.url,
|
1005
|
+
response.status_code,
|
1006
|
+
response.reason_phrase,
|
1007
|
+
response.headers,
|
1008
|
+
)
|
990
1009
|
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1010
|
+
try:
|
1011
|
+
response.raise_for_status()
|
1012
|
+
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
|
1013
|
+
log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
|
1014
|
+
|
1015
|
+
if remaining_retries > 0 and self._should_retry(err.response):
|
1016
|
+
err.response.close()
|
1017
|
+
self._sleep_for_retry(
|
1018
|
+
retries_taken=retries_taken,
|
1019
|
+
max_retries=max_retries,
|
1020
|
+
options=input_options,
|
1021
|
+
response=response,
|
1022
|
+
)
|
1023
|
+
continue
|
1002
1024
|
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
if remaining_retries > 0 and self._should_retry(err.response):
|
1009
|
-
err.response.close()
|
1010
|
-
return self._retry_request(
|
1011
|
-
input_options,
|
1012
|
-
cast_to,
|
1013
|
-
retries_taken=retries_taken,
|
1014
|
-
response_headers=err.response.headers,
|
1015
|
-
stream=stream,
|
1016
|
-
stream_cls=stream_cls,
|
1017
|
-
)
|
1025
|
+
# If the response is streamed then we need to explicitly read the response
|
1026
|
+
# to completion before attempting to access the response text.
|
1027
|
+
if not err.response.is_closed:
|
1028
|
+
err.response.read()
|
1018
1029
|
|
1019
|
-
|
1020
|
-
|
1021
|
-
if not err.response.is_closed:
|
1022
|
-
err.response.read()
|
1030
|
+
log.debug("Re-raising status error")
|
1031
|
+
raise self._make_status_error_from_response(err.response) from None
|
1023
1032
|
|
1024
|
-
|
1025
|
-
raise self._make_status_error_from_response(err.response) from None
|
1033
|
+
break
|
1026
1034
|
|
1035
|
+
assert response is not None, "could not resolve response (should never happen)"
|
1027
1036
|
return self._process_response(
|
1028
1037
|
cast_to=cast_to,
|
1029
1038
|
options=options,
|
@@ -1033,37 +1042,20 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
|
|
1033
1042
|
retries_taken=retries_taken,
|
1034
1043
|
)
|
1035
1044
|
|
1036
|
-
def
|
1037
|
-
self,
|
1038
|
-
|
1039
|
-
|
1040
|
-
*,
|
1041
|
-
retries_taken: int,
|
1042
|
-
response_headers: httpx.Headers | None,
|
1043
|
-
stream: bool,
|
1044
|
-
stream_cls: type[_StreamT] | None,
|
1045
|
-
) -> ResponseT | _StreamT:
|
1046
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
1045
|
+
def _sleep_for_retry(
|
1046
|
+
self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
|
1047
|
+
) -> None:
|
1048
|
+
remaining_retries = max_retries - retries_taken
|
1047
1049
|
if remaining_retries == 1:
|
1048
1050
|
log.debug("1 retry left")
|
1049
1051
|
else:
|
1050
1052
|
log.debug("%i retries left", remaining_retries)
|
1051
1053
|
|
1052
|
-
timeout = self._calculate_retry_timeout(remaining_retries, options,
|
1054
|
+
timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
|
1053
1055
|
log.info("Retrying request to %s in %f seconds", options.url, timeout)
|
1054
1056
|
|
1055
|
-
# In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
|
1056
|
-
# different thread if necessary.
|
1057
1057
|
time.sleep(timeout)
|
1058
1058
|
|
1059
|
-
return self._request(
|
1060
|
-
options=options,
|
1061
|
-
cast_to=cast_to,
|
1062
|
-
retries_taken=retries_taken + 1,
|
1063
|
-
stream=stream,
|
1064
|
-
stream_cls=stream_cls,
|
1065
|
-
)
|
1066
|
-
|
1067
1059
|
def _process_response(
|
1068
1060
|
self,
|
1069
1061
|
*,
|
@@ -1407,7 +1399,6 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1407
1399
|
options: FinalRequestOptions,
|
1408
1400
|
*,
|
1409
1401
|
stream: Literal[False] = False,
|
1410
|
-
remaining_retries: Optional[int] = None,
|
1411
1402
|
) -> ResponseT: ...
|
1412
1403
|
|
1413
1404
|
@overload
|
@@ -1418,7 +1409,6 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1418
1409
|
*,
|
1419
1410
|
stream: Literal[True],
|
1420
1411
|
stream_cls: type[_AsyncStreamT],
|
1421
|
-
remaining_retries: Optional[int] = None,
|
1422
1412
|
) -> _AsyncStreamT: ...
|
1423
1413
|
|
1424
1414
|
@overload
|
@@ -1429,7 +1419,6 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1429
1419
|
*,
|
1430
1420
|
stream: bool,
|
1431
1421
|
stream_cls: type[_AsyncStreamT] | None = None,
|
1432
|
-
remaining_retries: Optional[int] = None,
|
1433
1422
|
) -> ResponseT | _AsyncStreamT: ...
|
1434
1423
|
|
1435
1424
|
async def request(
|
@@ -1439,120 +1428,111 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1439
1428
|
*,
|
1440
1429
|
stream: bool = False,
|
1441
1430
|
stream_cls: type[_AsyncStreamT] | None = None,
|
1442
|
-
remaining_retries: Optional[int] = None,
|
1443
|
-
) -> ResponseT | _AsyncStreamT:
|
1444
|
-
if remaining_retries is not None:
|
1445
|
-
retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
|
1446
|
-
else:
|
1447
|
-
retries_taken = 0
|
1448
|
-
|
1449
|
-
return await self._request(
|
1450
|
-
cast_to=cast_to,
|
1451
|
-
options=options,
|
1452
|
-
stream=stream,
|
1453
|
-
stream_cls=stream_cls,
|
1454
|
-
retries_taken=retries_taken,
|
1455
|
-
)
|
1456
|
-
|
1457
|
-
async def _request(
|
1458
|
-
self,
|
1459
|
-
cast_to: Type[ResponseT],
|
1460
|
-
options: FinalRequestOptions,
|
1461
|
-
*,
|
1462
|
-
stream: bool,
|
1463
|
-
stream_cls: type[_AsyncStreamT] | None,
|
1464
|
-
retries_taken: int,
|
1465
1431
|
) -> ResponseT | _AsyncStreamT:
|
1466
1432
|
if self._platform is None:
|
1467
1433
|
# `get_platform` can make blocking IO calls so we
|
1468
1434
|
# execute it earlier while we are in an async context
|
1469
1435
|
self._platform = await asyncify(get_platform)()
|
1470
1436
|
|
1437
|
+
cast_to = self._maybe_override_cast_to(cast_to, options)
|
1438
|
+
|
1471
1439
|
# create a copy of the options we were given so that if the
|
1472
1440
|
# options are mutated later & we then retry, the retries are
|
1473
1441
|
# given the original options
|
1474
1442
|
input_options = model_copy(options)
|
1475
|
-
|
1476
|
-
cast_to = self._maybe_override_cast_to(cast_to, options)
|
1477
|
-
options = await self._prepare_options(options)
|
1478
|
-
|
1479
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
1480
|
-
request = self._build_request(options, retries_taken=retries_taken)
|
1481
|
-
await self._prepare_request(request)
|
1482
|
-
|
1483
|
-
if options.idempotency_key:
|
1443
|
+
if input_options.idempotency_key is None and input_options.method.lower() != "get":
|
1484
1444
|
# ensure the idempotency key is reused between requests
|
1485
|
-
input_options.idempotency_key =
|
1445
|
+
input_options.idempotency_key = self._idempotency_key()
|
1486
1446
|
|
1487
|
-
|
1488
|
-
|
1489
|
-
kwargs["auth"] = self.custom_auth
|
1447
|
+
response: httpx.Response | None = None
|
1448
|
+
max_retries = input_options.get_max_retries(self.max_retries)
|
1490
1449
|
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
**kwargs,
|
1496
|
-
)
|
1497
|
-
except httpx.TimeoutException as err:
|
1498
|
-
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
1450
|
+
retries_taken = 0
|
1451
|
+
for retries_taken in range(max_retries + 1):
|
1452
|
+
options = model_copy(input_options)
|
1453
|
+
options = await self._prepare_options(options)
|
1499
1454
|
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
cast_to,
|
1504
|
-
retries_taken=retries_taken,
|
1505
|
-
stream=stream,
|
1506
|
-
stream_cls=stream_cls,
|
1507
|
-
response_headers=None,
|
1508
|
-
)
|
1455
|
+
remaining_retries = max_retries - retries_taken
|
1456
|
+
request = self._build_request(options, retries_taken=retries_taken)
|
1457
|
+
await self._prepare_request(request)
|
1509
1458
|
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
log.debug("Encountered Exception", exc_info=True)
|
1459
|
+
kwargs: HttpxSendArgs = {}
|
1460
|
+
if self.custom_auth is not None:
|
1461
|
+
kwargs["auth"] = self.custom_auth
|
1514
1462
|
|
1515
|
-
|
1516
|
-
return await self._retry_request(
|
1517
|
-
input_options,
|
1518
|
-
cast_to,
|
1519
|
-
retries_taken=retries_taken,
|
1520
|
-
stream=stream,
|
1521
|
-
stream_cls=stream_cls,
|
1522
|
-
response_headers=None,
|
1523
|
-
)
|
1463
|
+
log.debug("Sending HTTP Request: %s %s", request.method, request.url)
|
1524
1464
|
|
1525
|
-
|
1526
|
-
|
1465
|
+
response = None
|
1466
|
+
try:
|
1467
|
+
response = await self._client.send(
|
1468
|
+
request,
|
1469
|
+
stream=stream or self._should_stream_response_body(request=request),
|
1470
|
+
**kwargs,
|
1471
|
+
)
|
1472
|
+
except httpx.TimeoutException as err:
|
1473
|
+
log.debug("Encountered httpx.TimeoutException", exc_info=True)
|
1474
|
+
|
1475
|
+
if remaining_retries > 0:
|
1476
|
+
await self._sleep_for_retry(
|
1477
|
+
retries_taken=retries_taken,
|
1478
|
+
max_retries=max_retries,
|
1479
|
+
options=input_options,
|
1480
|
+
response=None,
|
1481
|
+
)
|
1482
|
+
continue
|
1483
|
+
|
1484
|
+
log.debug("Raising timeout error")
|
1485
|
+
raise APITimeoutError(request=request) from err
|
1486
|
+
except Exception as err:
|
1487
|
+
log.debug("Encountered Exception", exc_info=True)
|
1488
|
+
|
1489
|
+
if remaining_retries > 0:
|
1490
|
+
await self._sleep_for_retry(
|
1491
|
+
retries_taken=retries_taken,
|
1492
|
+
max_retries=max_retries,
|
1493
|
+
options=input_options,
|
1494
|
+
response=None,
|
1495
|
+
)
|
1496
|
+
continue
|
1497
|
+
|
1498
|
+
log.debug("Raising connection error")
|
1499
|
+
raise APIConnectionError(request=request) from err
|
1500
|
+
|
1501
|
+
log.debug(
|
1502
|
+
'HTTP Response: %s %s "%i %s" %s',
|
1503
|
+
request.method,
|
1504
|
+
request.url,
|
1505
|
+
response.status_code,
|
1506
|
+
response.reason_phrase,
|
1507
|
+
response.headers,
|
1508
|
+
)
|
1527
1509
|
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1510
|
+
try:
|
1511
|
+
response.raise_for_status()
|
1512
|
+
except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
|
1513
|
+
log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
|
1514
|
+
|
1515
|
+
if remaining_retries > 0 and self._should_retry(err.response):
|
1516
|
+
await err.response.aclose()
|
1517
|
+
await self._sleep_for_retry(
|
1518
|
+
retries_taken=retries_taken,
|
1519
|
+
max_retries=max_retries,
|
1520
|
+
options=input_options,
|
1521
|
+
response=response,
|
1522
|
+
)
|
1523
|
+
continue
|
1531
1524
|
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
if remaining_retries > 0 and self._should_retry(err.response):
|
1538
|
-
await err.response.aclose()
|
1539
|
-
return await self._retry_request(
|
1540
|
-
input_options,
|
1541
|
-
cast_to,
|
1542
|
-
retries_taken=retries_taken,
|
1543
|
-
response_headers=err.response.headers,
|
1544
|
-
stream=stream,
|
1545
|
-
stream_cls=stream_cls,
|
1546
|
-
)
|
1525
|
+
# If the response is streamed then we need to explicitly read the response
|
1526
|
+
# to completion before attempting to access the response text.
|
1527
|
+
if not err.response.is_closed:
|
1528
|
+
await err.response.aread()
|
1547
1529
|
|
1548
|
-
|
1549
|
-
|
1550
|
-
if not err.response.is_closed:
|
1551
|
-
await err.response.aread()
|
1530
|
+
log.debug("Re-raising status error")
|
1531
|
+
raise self._make_status_error_from_response(err.response) from None
|
1552
1532
|
|
1553
|
-
|
1554
|
-
raise self._make_status_error_from_response(err.response) from None
|
1533
|
+
break
|
1555
1534
|
|
1535
|
+
assert response is not None, "could not resolve response (should never happen)"
|
1556
1536
|
return await self._process_response(
|
1557
1537
|
cast_to=cast_to,
|
1558
1538
|
options=options,
|
@@ -1562,35 +1542,20 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
|
|
1562
1542
|
retries_taken=retries_taken,
|
1563
1543
|
)
|
1564
1544
|
|
1565
|
-
async def
|
1566
|
-
self,
|
1567
|
-
|
1568
|
-
|
1569
|
-
*,
|
1570
|
-
retries_taken: int,
|
1571
|
-
response_headers: httpx.Headers | None,
|
1572
|
-
stream: bool,
|
1573
|
-
stream_cls: type[_AsyncStreamT] | None,
|
1574
|
-
) -> ResponseT | _AsyncStreamT:
|
1575
|
-
remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
|
1545
|
+
async def _sleep_for_retry(
|
1546
|
+
self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
|
1547
|
+
) -> None:
|
1548
|
+
remaining_retries = max_retries - retries_taken
|
1576
1549
|
if remaining_retries == 1:
|
1577
1550
|
log.debug("1 retry left")
|
1578
1551
|
else:
|
1579
1552
|
log.debug("%i retries left", remaining_retries)
|
1580
1553
|
|
1581
|
-
timeout = self._calculate_retry_timeout(remaining_retries, options,
|
1554
|
+
timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
|
1582
1555
|
log.info("Retrying request to %s in %f seconds", options.url, timeout)
|
1583
1556
|
|
1584
1557
|
await anyio.sleep(timeout)
|
1585
1558
|
|
1586
|
-
return await self._request(
|
1587
|
-
options=options,
|
1588
|
-
cast_to=cast_to,
|
1589
|
-
retries_taken=retries_taken + 1,
|
1590
|
-
stream=stream,
|
1591
|
-
stream_cls=stream_cls,
|
1592
|
-
)
|
1593
|
-
|
1594
1559
|
async def _process_response(
|
1595
1560
|
self,
|
1596
1561
|
*,
|
@@ -19,10 +19,7 @@ from ._types import (
|
|
19
19
|
ProxiesTypes,
|
20
20
|
RequestOptions,
|
21
21
|
)
|
22
|
-
from ._utils import
|
23
|
-
is_given,
|
24
|
-
get_async_library,
|
25
|
-
)
|
22
|
+
from ._utils import is_given, get_async_library
|
26
23
|
from ._version import __version__
|
27
24
|
from .resources import tasks, organization, image_to_video
|
28
25
|
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
|
@@ -19,7 +19,6 @@ from typing_extensions import (
|
|
19
19
|
)
|
20
20
|
|
21
21
|
import pydantic
|
22
|
-
import pydantic.generics
|
23
22
|
from pydantic.fields import FieldInfo
|
24
23
|
|
25
24
|
from ._types import (
|
@@ -627,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
|
|
627
626
|
# Note: if one variant defines an alias then they all should
|
628
627
|
discriminator_alias = field_info.alias
|
629
628
|
|
630
|
-
if field_info
|
631
|
-
for entry in get_args(
|
629
|
+
if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
|
630
|
+
for entry in get_args(annotation):
|
632
631
|
if isinstance(entry, str):
|
633
632
|
mapping[entry] = variant
|
634
633
|
|
@@ -233,7 +233,7 @@ class BaseAPIResponse(Generic[R]):
|
|
233
233
|
# split is required to handle cases where additional information is included
|
234
234
|
# in the response, e.g. application/json; charset=utf-8
|
235
235
|
content_type, *_ = response.headers.get("content-type", "*").split(";")
|
236
|
-
if content_type
|
236
|
+
if not content_type.endswith("json"):
|
237
237
|
if is_basemodel(cast_to):
|
238
238
|
try:
|
239
239
|
data = response.json()
|
@@ -110,7 +110,7 @@ def extract_type_var_from_base(
|
|
110
110
|
```
|
111
111
|
"""
|
112
112
|
cls = cast(object, get_origin(typ) or typ)
|
113
|
-
if cls in generic_bases:
|
113
|
+
if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains]
|
114
114
|
# we're given the class directly
|
115
115
|
return extract_type_arg(typ, index)
|
116
116
|
|
@@ -72,8 +72,16 @@ def _extract_items(
|
|
72
72
|
from .._files import assert_is_file_content
|
73
73
|
|
74
74
|
# We have exhausted the path, return the entry we found.
|
75
|
-
assert_is_file_content(obj, key=flattened_key)
|
76
75
|
assert flattened_key is not None
|
76
|
+
|
77
|
+
if is_list(obj):
|
78
|
+
files: list[tuple[str, FileTypes]] = []
|
79
|
+
for entry in obj:
|
80
|
+
assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
|
81
|
+
files.append((flattened_key + "[]", cast(FileTypes, entry)))
|
82
|
+
return files
|
83
|
+
|
84
|
+
assert_is_file_content(obj, key=flattened_key)
|
77
85
|
return [(flattened_key, cast(FileTypes, obj))]
|
78
86
|
|
79
87
|
index += 1
|
@@ -9,10 +9,7 @@ import httpx
|
|
9
9
|
|
10
10
|
from ..types import image_to_video_create_params
|
11
11
|
from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
|
12
|
-
from .._utils import
|
13
|
-
maybe_transform,
|
14
|
-
async_maybe_transform,
|
15
|
-
)
|
12
|
+
from .._utils import maybe_transform, async_maybe_transform
|
16
13
|
from .._compat import cached_property
|
17
14
|
from .._resource import SyncAPIResource, AsyncAPIResource
|
18
15
|
from .._response import (
|
@@ -10,7 +10,7 @@ from pytest_asyncio import is_async_test
|
|
10
10
|
from runwayml import RunwayML, AsyncRunwayML
|
11
11
|
|
12
12
|
if TYPE_CHECKING:
|
13
|
-
from _pytest.fixtures import FixtureRequest
|
13
|
+
from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage]
|
14
14
|
|
15
15
|
pytest.register_assert_rewrite("tests.utils")
|
16
16
|
|
@@ -492,12 +492,15 @@ def test_omitted_fields() -> None:
|
|
492
492
|
resource_id: Optional[str] = None
|
493
493
|
|
494
494
|
m = Model.construct()
|
495
|
+
assert m.resource_id is None
|
495
496
|
assert "resource_id" not in m.model_fields_set
|
496
497
|
|
497
498
|
m = Model.construct(resource_id=None)
|
499
|
+
assert m.resource_id is None
|
498
500
|
assert "resource_id" in m.model_fields_set
|
499
501
|
|
500
502
|
m = Model.construct(resource_id="foo")
|
503
|
+
assert m.resource_id == "foo"
|
501
504
|
assert "resource_id" in m.model_fields_set
|
502
505
|
|
503
506
|
|
@@ -832,7 +835,7 @@ def test_discriminated_unions_invalid_data_uses_cache() -> None:
|
|
832
835
|
|
833
836
|
@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
|
834
837
|
def test_type_alias_type() -> None:
|
835
|
-
Alias = TypeAliasType("Alias", str)
|
838
|
+
Alias = TypeAliasType("Alias", str) # pyright: ignore
|
836
839
|
|
837
840
|
class Model(BaseModel):
|
838
841
|
alias: Alias
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|