runwayml 1.0.0__py3-none-any.whl → 2.1.0__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.
runwayml/_base_client.py CHANGED
@@ -143,6 +143,12 @@ class PageInfo:
143
143
  self.url = url
144
144
  self.params = params
145
145
 
146
+ @override
147
+ def __repr__(self) -> str:
148
+ if self.url:
149
+ return f"{self.__class__.__name__}(url={self.url})"
150
+ return f"{self.__class__.__name__}(params={self.params})"
151
+
146
152
 
147
153
  class BasePage(GenericModel, Generic[_T]):
148
154
  """
@@ -400,14 +406,7 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
400
406
  ) -> _exceptions.APIStatusError:
401
407
  raise NotImplementedError()
402
408
 
403
- def _remaining_retries(
404
- self,
405
- remaining_retries: Optional[int],
406
- options: FinalRequestOptions,
407
- ) -> int:
408
- return remaining_retries if remaining_retries is not None else options.get_max_retries(self.max_retries)
409
-
410
- def _build_headers(self, options: FinalRequestOptions) -> httpx.Headers:
409
+ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
411
410
  custom_headers = options.headers or {}
412
411
  headers_dict = _merge_mappings(self.default_headers, custom_headers)
413
412
  self._validate_headers(headers_dict, custom_headers)
@@ -419,6 +418,11 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
419
418
  if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
420
419
  headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
421
420
 
421
+ # Don't set the retry count header if it was already set or removed by the caller. We check
422
+ # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
423
+ if "x-stainless-retry-count" not in (header.lower() for header in custom_headers):
424
+ headers["x-stainless-retry-count"] = str(retries_taken)
425
+
422
426
  return headers
423
427
 
424
428
  def _prepare_url(self, url: str) -> URL:
@@ -440,6 +444,8 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
440
444
  def _build_request(
441
445
  self,
442
446
  options: FinalRequestOptions,
447
+ *,
448
+ retries_taken: int = 0,
443
449
  ) -> httpx.Request:
444
450
  if log.isEnabledFor(logging.DEBUG):
445
451
  log.debug("Request options: %s", model_dump(options, exclude_unset=True))
@@ -455,7 +461,7 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
455
461
  else:
456
462
  raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")
457
463
 
458
- headers = self._build_headers(options)
464
+ headers = self._build_headers(options, retries_taken=retries_taken)
459
465
  params = _merge_mappings(self.default_query, options.params)
460
466
  content_type = headers.get("Content-Type")
461
467
  files = options.files
@@ -489,12 +495,17 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
489
495
  if not files:
490
496
  files = cast(HttpxRequestFiles, ForceMultipartDict())
491
497
 
498
+ prepared_url = self._prepare_url(options.url)
499
+ if "_" in prepared_url.host:
500
+ # work around https://github.com/encode/httpx/discussions/2880
501
+ kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
502
+
492
503
  # TODO: report this error to httpx
493
504
  return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
494
505
  headers=headers,
495
506
  timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
496
507
  method=options.method,
497
- url=self._prepare_url(options.url),
508
+ url=prepared_url,
498
509
  # the `Query` type that we use is incompatible with qs'
499
510
  # `Params` type as it needs to be typed as `Mapping[str, object]`
500
511
  # so that passing a `TypedDict` doesn't cause an error.
@@ -684,7 +695,8 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
684
695
  if retry_after is not None and 0 < retry_after <= 60:
685
696
  return retry_after
686
697
 
687
- nb_retries = max_retries - remaining_retries
698
+ # Also cap retry count to 1000 to avoid any potential overflows with `pow`
699
+ nb_retries = min(max_retries - remaining_retries, 1000)
688
700
 
689
701
  # Apply exponential backoff, but not more than the max.
690
702
  sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)
@@ -933,12 +945,17 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
933
945
  stream: bool = False,
934
946
  stream_cls: type[_StreamT] | None = None,
935
947
  ) -> ResponseT | _StreamT:
948
+ if remaining_retries is not None:
949
+ retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
950
+ else:
951
+ retries_taken = 0
952
+
936
953
  return self._request(
937
954
  cast_to=cast_to,
938
955
  options=options,
939
956
  stream=stream,
940
957
  stream_cls=stream_cls,
941
- remaining_retries=remaining_retries,
958
+ retries_taken=retries_taken,
942
959
  )
943
960
 
944
961
  def _request(
@@ -946,7 +963,7 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
946
963
  *,
947
964
  cast_to: Type[ResponseT],
948
965
  options: FinalRequestOptions,
949
- remaining_retries: int | None,
966
+ retries_taken: int,
950
967
  stream: bool,
951
968
  stream_cls: type[_StreamT] | None,
952
969
  ) -> ResponseT | _StreamT:
@@ -958,8 +975,8 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
958
975
  cast_to = self._maybe_override_cast_to(cast_to, options)
959
976
  options = self._prepare_options(options)
960
977
 
961
- retries = self._remaining_retries(remaining_retries, options)
962
- request = self._build_request(options)
978
+ remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
979
+ request = self._build_request(options, retries_taken=retries_taken)
963
980
  self._prepare_request(request)
964
981
 
965
982
  kwargs: HttpxSendArgs = {}
@@ -977,11 +994,11 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
977
994
  except httpx.TimeoutException as err:
978
995
  log.debug("Encountered httpx.TimeoutException", exc_info=True)
979
996
 
980
- if retries > 0:
997
+ if remaining_retries > 0:
981
998
  return self._retry_request(
982
999
  input_options,
983
1000
  cast_to,
984
- retries,
1001
+ retries_taken=retries_taken,
985
1002
  stream=stream,
986
1003
  stream_cls=stream_cls,
987
1004
  response_headers=None,
@@ -992,11 +1009,11 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
992
1009
  except Exception as err:
993
1010
  log.debug("Encountered Exception", exc_info=True)
994
1011
 
995
- if retries > 0:
1012
+ if remaining_retries > 0:
996
1013
  return self._retry_request(
997
1014
  input_options,
998
1015
  cast_to,
999
- retries,
1016
+ retries_taken=retries_taken,
1000
1017
  stream=stream,
1001
1018
  stream_cls=stream_cls,
1002
1019
  response_headers=None,
@@ -1019,13 +1036,13 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1019
1036
  except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
1020
1037
  log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
1021
1038
 
1022
- if retries > 0 and self._should_retry(err.response):
1039
+ if remaining_retries > 0 and self._should_retry(err.response):
1023
1040
  err.response.close()
1024
1041
  return self._retry_request(
1025
1042
  input_options,
1026
1043
  cast_to,
1027
- retries,
1028
- err.response.headers,
1044
+ retries_taken=retries_taken,
1045
+ response_headers=err.response.headers,
1029
1046
  stream=stream,
1030
1047
  stream_cls=stream_cls,
1031
1048
  )
@@ -1044,26 +1061,26 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1044
1061
  response=response,
1045
1062
  stream=stream,
1046
1063
  stream_cls=stream_cls,
1047
- retries_taken=options.get_max_retries(self.max_retries) - retries,
1064
+ retries_taken=retries_taken,
1048
1065
  )
1049
1066
 
1050
1067
  def _retry_request(
1051
1068
  self,
1052
1069
  options: FinalRequestOptions,
1053
1070
  cast_to: Type[ResponseT],
1054
- remaining_retries: int,
1055
- response_headers: httpx.Headers | None,
1056
1071
  *,
1072
+ retries_taken: int,
1073
+ response_headers: httpx.Headers | None,
1057
1074
  stream: bool,
1058
1075
  stream_cls: type[_StreamT] | None,
1059
1076
  ) -> ResponseT | _StreamT:
1060
- remaining = remaining_retries - 1
1061
- if remaining == 1:
1077
+ remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
1078
+ if remaining_retries == 1:
1062
1079
  log.debug("1 retry left")
1063
1080
  else:
1064
- log.debug("%i retries left", remaining)
1081
+ log.debug("%i retries left", remaining_retries)
1065
1082
 
1066
- timeout = self._calculate_retry_timeout(remaining, options, response_headers)
1083
+ timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
1067
1084
  log.info("Retrying request to %s in %f seconds", options.url, timeout)
1068
1085
 
1069
1086
  # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
@@ -1073,7 +1090,7 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1073
1090
  return self._request(
1074
1091
  options=options,
1075
1092
  cast_to=cast_to,
1076
- remaining_retries=remaining,
1093
+ retries_taken=retries_taken + 1,
1077
1094
  stream=stream,
1078
1095
  stream_cls=stream_cls,
1079
1096
  )
@@ -1491,12 +1508,17 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1491
1508
  stream_cls: type[_AsyncStreamT] | None = None,
1492
1509
  remaining_retries: Optional[int] = None,
1493
1510
  ) -> ResponseT | _AsyncStreamT:
1511
+ if remaining_retries is not None:
1512
+ retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
1513
+ else:
1514
+ retries_taken = 0
1515
+
1494
1516
  return await self._request(
1495
1517
  cast_to=cast_to,
1496
1518
  options=options,
1497
1519
  stream=stream,
1498
1520
  stream_cls=stream_cls,
1499
- remaining_retries=remaining_retries,
1521
+ retries_taken=retries_taken,
1500
1522
  )
1501
1523
 
1502
1524
  async def _request(
@@ -1506,7 +1528,7 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1506
1528
  *,
1507
1529
  stream: bool,
1508
1530
  stream_cls: type[_AsyncStreamT] | None,
1509
- remaining_retries: int | None,
1531
+ retries_taken: int,
1510
1532
  ) -> ResponseT | _AsyncStreamT:
1511
1533
  if self._platform is None:
1512
1534
  # `get_platform` can make blocking IO calls so we
@@ -1521,8 +1543,8 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1521
1543
  cast_to = self._maybe_override_cast_to(cast_to, options)
1522
1544
  options = await self._prepare_options(options)
1523
1545
 
1524
- retries = self._remaining_retries(remaining_retries, options)
1525
- request = self._build_request(options)
1546
+ remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
1547
+ request = self._build_request(options, retries_taken=retries_taken)
1526
1548
  await self._prepare_request(request)
1527
1549
 
1528
1550
  kwargs: HttpxSendArgs = {}
@@ -1538,11 +1560,11 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1538
1560
  except httpx.TimeoutException as err:
1539
1561
  log.debug("Encountered httpx.TimeoutException", exc_info=True)
1540
1562
 
1541
- if retries > 0:
1563
+ if remaining_retries > 0:
1542
1564
  return await self._retry_request(
1543
1565
  input_options,
1544
1566
  cast_to,
1545
- retries,
1567
+ retries_taken=retries_taken,
1546
1568
  stream=stream,
1547
1569
  stream_cls=stream_cls,
1548
1570
  response_headers=None,
@@ -1553,11 +1575,11 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1553
1575
  except Exception as err:
1554
1576
  log.debug("Encountered Exception", exc_info=True)
1555
1577
 
1556
- if retries > 0:
1578
+ if remaining_retries > 0:
1557
1579
  return await self._retry_request(
1558
1580
  input_options,
1559
1581
  cast_to,
1560
- retries,
1582
+ retries_taken=retries_taken,
1561
1583
  stream=stream,
1562
1584
  stream_cls=stream_cls,
1563
1585
  response_headers=None,
@@ -1575,13 +1597,13 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1575
1597
  except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
1576
1598
  log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
1577
1599
 
1578
- if retries > 0 and self._should_retry(err.response):
1600
+ if remaining_retries > 0 and self._should_retry(err.response):
1579
1601
  await err.response.aclose()
1580
1602
  return await self._retry_request(
1581
1603
  input_options,
1582
1604
  cast_to,
1583
- retries,
1584
- err.response.headers,
1605
+ retries_taken=retries_taken,
1606
+ response_headers=err.response.headers,
1585
1607
  stream=stream,
1586
1608
  stream_cls=stream_cls,
1587
1609
  )
@@ -1600,26 +1622,26 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1600
1622
  response=response,
1601
1623
  stream=stream,
1602
1624
  stream_cls=stream_cls,
1603
- retries_taken=options.get_max_retries(self.max_retries) - retries,
1625
+ retries_taken=retries_taken,
1604
1626
  )
1605
1627
 
1606
1628
  async def _retry_request(
1607
1629
  self,
1608
1630
  options: FinalRequestOptions,
1609
1631
  cast_to: Type[ResponseT],
1610
- remaining_retries: int,
1611
- response_headers: httpx.Headers | None,
1612
1632
  *,
1633
+ retries_taken: int,
1634
+ response_headers: httpx.Headers | None,
1613
1635
  stream: bool,
1614
1636
  stream_cls: type[_AsyncStreamT] | None,
1615
1637
  ) -> ResponseT | _AsyncStreamT:
1616
- remaining = remaining_retries - 1
1617
- if remaining == 1:
1638
+ remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
1639
+ if remaining_retries == 1:
1618
1640
  log.debug("1 retry left")
1619
1641
  else:
1620
- log.debug("%i retries left", remaining)
1642
+ log.debug("%i retries left", remaining_retries)
1621
1643
 
1622
- timeout = self._calculate_retry_timeout(remaining, options, response_headers)
1644
+ timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
1623
1645
  log.info("Retrying request to %s in %f seconds", options.url, timeout)
1624
1646
 
1625
1647
  await anyio.sleep(timeout)
@@ -1627,7 +1649,7 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1627
1649
  return await self._request(
1628
1650
  options=options,
1629
1651
  cast_to=cast_to,
1630
- remaining_retries=remaining,
1652
+ retries_taken=retries_taken + 1,
1631
1653
  stream=stream,
1632
1654
  stream_cls=stream_cls,
1633
1655
  )
runwayml/_client.py CHANGED
@@ -59,7 +59,7 @@ class RunwayML(SyncAPIClient):
59
59
  self,
60
60
  *,
61
61
  api_key: str | None = None,
62
- runway_version: str | None = "2024-09-13",
62
+ runway_version: str | None = "2024-11-06",
63
63
  base_url: str | httpx.URL | None = None,
64
64
  timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
65
65
  max_retries: int = DEFAULT_MAX_RETRIES,
@@ -92,7 +92,7 @@ class RunwayML(SyncAPIClient):
92
92
  self.api_key = api_key
93
93
 
94
94
  if runway_version is None:
95
- runway_version = "2024-09-13"
95
+ runway_version = "2024-11-06"
96
96
  self.runway_version = runway_version
97
97
 
98
98
  if base_url is None:
@@ -238,7 +238,7 @@ class AsyncRunwayML(AsyncAPIClient):
238
238
  self,
239
239
  *,
240
240
  api_key: str | None = None,
241
- runway_version: str | None = "2024-09-13",
241
+ runway_version: str | None = "2024-11-06",
242
242
  base_url: str | httpx.URL | None = None,
243
243
  timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
244
244
  max_retries: int = DEFAULT_MAX_RETRIES,
@@ -271,7 +271,7 @@ class AsyncRunwayML(AsyncAPIClient):
271
271
  self.api_key = api_key
272
272
 
273
273
  if runway_version is None:
274
- runway_version = "2024-09-13"
274
+ runway_version = "2024-11-06"
275
275
  self.runway_version = runway_version
276
276
 
277
277
  if base_url is None:
runwayml/_compat.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
4
4
  from datetime import date, datetime
5
- from typing_extensions import Self
5
+ from typing_extensions import Self, Literal
6
6
 
7
7
  import pydantic
8
8
  from pydantic.fields import FieldInfo
@@ -133,15 +133,19 @@ def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
133
133
  def model_dump(
134
134
  model: pydantic.BaseModel,
135
135
  *,
136
- exclude: IncEx = None,
136
+ exclude: IncEx | None = None,
137
137
  exclude_unset: bool = False,
138
138
  exclude_defaults: bool = False,
139
+ warnings: bool = True,
140
+ mode: Literal["json", "python"] = "python",
139
141
  ) -> dict[str, Any]:
140
- if PYDANTIC_V2:
142
+ if PYDANTIC_V2 or hasattr(model, "model_dump"):
141
143
  return model.model_dump(
144
+ mode=mode,
142
145
  exclude=exclude,
143
146
  exclude_unset=exclude_unset,
144
147
  exclude_defaults=exclude_defaults,
148
+ warnings=warnings,
145
149
  )
146
150
  return cast(
147
151
  "dict[str, Any]",
runwayml/_models.py CHANGED
@@ -37,6 +37,7 @@ from ._utils import (
37
37
  PropertyInfo,
38
38
  is_list,
39
39
  is_given,
40
+ json_safe,
40
41
  lru_cache,
41
42
  is_mapping,
42
43
  parse_date,
@@ -176,7 +177,7 @@ class BaseModel(pydantic.BaseModel):
176
177
  # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
177
178
  @classmethod
178
179
  @override
179
- def construct(
180
+ def construct( # pyright: ignore[reportIncompatibleMethodOverride]
180
181
  cls: Type[ModelT],
181
182
  _fields_set: set[str] | None = None,
182
183
  **values: object,
@@ -248,8 +249,8 @@ class BaseModel(pydantic.BaseModel):
248
249
  self,
249
250
  *,
250
251
  mode: Literal["json", "python"] | str = "python",
251
- include: IncEx = None,
252
- exclude: IncEx = None,
252
+ include: IncEx | None = None,
253
+ exclude: IncEx | None = None,
253
254
  by_alias: bool = False,
254
255
  exclude_unset: bool = False,
255
256
  exclude_defaults: bool = False,
@@ -279,8 +280,8 @@ class BaseModel(pydantic.BaseModel):
279
280
  Returns:
280
281
  A dictionary representation of the model.
281
282
  """
282
- if mode != "python":
283
- raise ValueError("mode is only supported in Pydantic v2")
283
+ if mode not in {"json", "python"}:
284
+ raise ValueError("mode must be either 'json' or 'python'")
284
285
  if round_trip != False:
285
286
  raise ValueError("round_trip is only supported in Pydantic v2")
286
287
  if warnings != True:
@@ -289,7 +290,7 @@ class BaseModel(pydantic.BaseModel):
289
290
  raise ValueError("context is only supported in Pydantic v2")
290
291
  if serialize_as_any != False:
291
292
  raise ValueError("serialize_as_any is only supported in Pydantic v2")
292
- return super().dict( # pyright: ignore[reportDeprecated]
293
+ dumped = super().dict( # pyright: ignore[reportDeprecated]
293
294
  include=include,
294
295
  exclude=exclude,
295
296
  by_alias=by_alias,
@@ -298,13 +299,15 @@ class BaseModel(pydantic.BaseModel):
298
299
  exclude_none=exclude_none,
299
300
  )
300
301
 
302
+ return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped
303
+
301
304
  @override
302
305
  def model_dump_json(
303
306
  self,
304
307
  *,
305
308
  indent: int | None = None,
306
- include: IncEx = None,
307
- exclude: IncEx = None,
309
+ include: IncEx | None = None,
310
+ exclude: IncEx | None = None,
308
311
  by_alias: bool = False,
309
312
  exclude_unset: bool = False,
310
313
  exclude_defaults: bool = False,
runwayml/_response.py CHANGED
@@ -192,6 +192,9 @@ class BaseAPIResponse(Generic[R]):
192
192
  if cast_to == float:
193
193
  return cast(R, float(response.text))
194
194
 
195
+ if cast_to == bool:
196
+ return cast(R, response.text.lower() == "true")
197
+
195
198
  origin = get_origin(cast_to) or cast_to
196
199
 
197
200
  if origin == APIResponse:
runwayml/_types.py CHANGED
@@ -16,7 +16,7 @@ from typing import (
16
16
  Optional,
17
17
  Sequence,
18
18
  )
19
- from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable
19
+ from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable
20
20
 
21
21
  import httpx
22
22
  import pydantic
@@ -193,7 +193,9 @@ StrBytesIntFloat = Union[str, bytes, int, float]
193
193
 
194
194
  # Note: copied from Pydantic
195
195
  # https://github.com/pydantic/pydantic/blob/32ea570bf96e84234d2992e1ddf40ab8a565925a/pydantic/main.py#L49
196
- IncEx: TypeAlias = "set[int] | set[str] | dict[int, Any] | dict[str, Any] | None"
196
+ IncEx: TypeAlias = Union[
197
+ Set[int], Set[str], Mapping[int, Union["IncEx", Literal[True]]], Mapping[str, Union["IncEx", Literal[True]]]
198
+ ]
197
199
 
198
200
  PostParser = Callable[[Any], Any]
199
201
 
@@ -6,6 +6,7 @@ from ._utils import (
6
6
  is_list as is_list,
7
7
  is_given as is_given,
8
8
  is_tuple as is_tuple,
9
+ json_safe as json_safe,
9
10
  lru_cache as lru_cache,
10
11
  is_mapping as is_mapping,
11
12
  is_tuple_t as is_tuple_t,
@@ -173,6 +173,11 @@ def _transform_recursive(
173
173
  # Iterable[T]
174
174
  or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
175
175
  ):
176
+ # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
177
+ # intended as an iterable, so we don't transform it.
178
+ if isinstance(data, dict):
179
+ return cast(object, data)
180
+
176
181
  inner_type = extract_type_arg(stripped_type, 0)
177
182
  return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
178
183
 
@@ -186,7 +191,7 @@ def _transform_recursive(
186
191
  return data
187
192
 
188
193
  if isinstance(data, pydantic.BaseModel):
189
- return model_dump(data, exclude_unset=True)
194
+ return model_dump(data, exclude_unset=True, mode="json")
190
195
 
191
196
  annotated_type = _get_annotated_type(annotation)
192
197
  if annotated_type is None:
@@ -324,7 +329,7 @@ async def _async_transform_recursive(
324
329
  return data
325
330
 
326
331
  if isinstance(data, pydantic.BaseModel):
327
- return model_dump(data, exclude_unset=True)
332
+ return model_dump(data, exclude_unset=True, mode="json")
328
333
 
329
334
  annotated_type = _get_annotated_type(annotation)
330
335
  if annotated_type is None:
runwayml/_utils/_utils.py CHANGED
@@ -16,6 +16,7 @@ from typing import (
16
16
  overload,
17
17
  )
18
18
  from pathlib import Path
19
+ from datetime import date, datetime
19
20
  from typing_extensions import TypeGuard
20
21
 
21
22
  import sniffio
@@ -395,3 +396,19 @@ def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
395
396
  maxsize=maxsize,
396
397
  )
397
398
  return cast(Any, wrapper) # type: ignore[no-any-return]
399
+
400
+
401
+ def json_safe(data: object) -> object:
402
+ """Translates a mapping / sequence recursively in the same fashion
403
+ as `pydantic` v2's `model_dump(mode="json")`.
404
+ """
405
+ if is_mapping(data):
406
+ return {json_safe(key): json_safe(value) for key, value in data.items()}
407
+
408
+ if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
409
+ return [json_safe(item) for item in data]
410
+
411
+ if isinstance(data, (datetime, date)):
412
+ return data.isoformat()
413
+
414
+ return data
runwayml/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "runwayml"
4
- __version__ = "1.0.0" # x-release-please-version
4
+ __version__ = "2.1.0" # x-release-please-version
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from typing import Union, Iterable
5
6
  from typing_extensions import Literal
6
7
 
7
8
  import httpx
@@ -50,8 +51,10 @@ class ImageToVideoResource(SyncAPIResource):
50
51
  self,
51
52
  *,
52
53
  model: Literal["gen3a_turbo"],
53
- prompt_image: str,
54
+ prompt_image: Union[str, Iterable[image_to_video_create_params.PromptImagePromptImage]],
55
+ duration: Literal[5, 10] | NotGiven = NOT_GIVEN,
54
56
  prompt_text: str | NotGiven = NOT_GIVEN,
57
+ ratio: Literal["1280:768", "768:1280"] | NotGiven = NOT_GIVEN,
55
58
  seed: int | NotGiven = NOT_GIVEN,
56
59
  watermark: bool | NotGiven = NOT_GIVEN,
57
60
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -67,11 +70,16 @@ class ImageToVideoResource(SyncAPIResource):
67
70
  Args:
68
71
  model: The model variant to use.
69
72
 
70
- prompt_image: A HTTPS URL pointing to an image. Images must be JPEG, PNG, or WebP and are
71
- limited to 16MB. Responses must include a valid `Content-Length` header.
73
+ prompt_image: A HTTPS URL or data URI containing an encoded image to be used as the first
74
+ frame of the generated video. See [our docs](/assets/inputs#images) on image
75
+ inputs for more information.
76
+
77
+ duration: The number of seconds of duration for the output video.
72
78
 
73
79
  prompt_text
74
80
 
81
+ ratio
82
+
75
83
  seed: If unspecified, a random number is chosen. Varying the seed integer is a way to
76
84
  get different results for the same other request parameters. Using the same seed
77
85
  integer for an identical request will produce similar results.
@@ -93,7 +101,9 @@ class ImageToVideoResource(SyncAPIResource):
93
101
  {
94
102
  "model": model,
95
103
  "prompt_image": prompt_image,
104
+ "duration": duration,
96
105
  "prompt_text": prompt_text,
106
+ "ratio": ratio,
97
107
  "seed": seed,
98
108
  "watermark": watermark,
99
109
  },
@@ -130,8 +140,10 @@ class AsyncImageToVideoResource(AsyncAPIResource):
130
140
  self,
131
141
  *,
132
142
  model: Literal["gen3a_turbo"],
133
- prompt_image: str,
143
+ prompt_image: Union[str, Iterable[image_to_video_create_params.PromptImagePromptImage]],
144
+ duration: Literal[5, 10] | NotGiven = NOT_GIVEN,
134
145
  prompt_text: str | NotGiven = NOT_GIVEN,
146
+ ratio: Literal["1280:768", "768:1280"] | NotGiven = NOT_GIVEN,
135
147
  seed: int | NotGiven = NOT_GIVEN,
136
148
  watermark: bool | NotGiven = NOT_GIVEN,
137
149
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -147,11 +159,16 @@ class AsyncImageToVideoResource(AsyncAPIResource):
147
159
  Args:
148
160
  model: The model variant to use.
149
161
 
150
- prompt_image: A HTTPS URL pointing to an image. Images must be JPEG, PNG, or WebP and are
151
- limited to 16MB. Responses must include a valid `Content-Length` header.
162
+ prompt_image: A HTTPS URL or data URI containing an encoded image to be used as the first
163
+ frame of the generated video. See [our docs](/assets/inputs#images) on image
164
+ inputs for more information.
165
+
166
+ duration: The number of seconds of duration for the output video.
152
167
 
153
168
  prompt_text
154
169
 
170
+ ratio
171
+
155
172
  seed: If unspecified, a random number is chosen. Varying the seed integer is a way to
156
173
  get different results for the same other request parameters. Using the same seed
157
174
  integer for an identical request will produce similar results.
@@ -173,7 +190,9 @@ class AsyncImageToVideoResource(AsyncAPIResource):
173
190
  {
174
191
  "model": model,
175
192
  "prompt_image": prompt_image,
193
+ "duration": duration,
176
194
  "prompt_text": prompt_text,
195
+ "ratio": ratio,
177
196
  "seed": seed,
178
197
  "watermark": watermark,
179
198
  },
@@ -2,26 +2,32 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from typing import Union, Iterable
5
6
  from typing_extensions import Literal, Required, Annotated, TypedDict
6
7
 
7
8
  from .._utils import PropertyInfo
8
9
 
9
- __all__ = ["ImageToVideoCreateParams"]
10
+ __all__ = ["ImageToVideoCreateParams", "PromptImagePromptImage"]
10
11
 
11
12
 
12
13
  class ImageToVideoCreateParams(TypedDict, total=False):
13
14
  model: Required[Literal["gen3a_turbo"]]
14
15
  """The model variant to use."""
15
16
 
16
- prompt_image: Required[Annotated[str, PropertyInfo(alias="promptImage")]]
17
- """A HTTPS URL pointing to an image.
18
-
19
- Images must be JPEG, PNG, or WebP and are limited to 16MB. Responses must
20
- include a valid `Content-Length` header.
17
+ prompt_image: Required[Annotated[Union[str, Iterable[PromptImagePromptImage]], PropertyInfo(alias="promptImage")]]
18
+ """
19
+ A HTTPS URL or data URI containing an encoded image to be used as the first
20
+ frame of the generated video. See [our docs](/assets/inputs#images) on image
21
+ inputs for more information.
21
22
  """
22
23
 
24
+ duration: Literal[5, 10]
25
+ """The number of seconds of duration for the output video."""
26
+
23
27
  prompt_text: Annotated[str, PropertyInfo(alias="promptText")]
24
28
 
29
+ ratio: Literal["1280:768", "768:1280"]
30
+
25
31
  seed: int
26
32
  """If unspecified, a random number is chosen.
27
33
 
@@ -35,3 +41,18 @@ class ImageToVideoCreateParams(TypedDict, total=False):
35
41
  A boolean indicating whether or not the output video will contain a Runway
36
42
  watermark.
37
43
  """
44
+
45
+
46
+ class PromptImagePromptImage(TypedDict, total=False):
47
+ position: Required[Literal["first", "last"]]
48
+ """The position of the image in the output video.
49
+
50
+ "first" will use the image as the first frame of the video, "last" will use the
51
+ image as the last frame of the video.
52
+ """
53
+
54
+ uri: Required[str]
55
+ """A HTTPS URL or data URI containing an encoded image.
56
+
57
+ See [our docs](/assets/inputs#images) on image inputs for more information.
58
+ """
@@ -1,7 +1,6 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
 
4
-
5
4
  from .._models import BaseModel
6
5
 
7
6
  __all__ = ["ImageToVideoCreateResponse"]
@@ -9,4 +8,7 @@ __all__ = ["ImageToVideoCreateResponse"]
9
8
 
10
9
  class ImageToVideoCreateResponse(BaseModel):
11
10
  id: str
12
- """The ID of the newly created task."""
11
+ """The ID of the newly created task.
12
+
13
+ Use this ID to query the task status and retrieve the generated video.
14
+ """
@@ -13,6 +13,7 @@ __all__ = ["TaskRetrieveResponse"]
13
13
 
14
14
  class TaskRetrieveResponse(BaseModel):
15
15
  id: str
16
+ """The ID of the task being returned."""
16
17
 
17
18
  created_at: datetime = FieldInfo(alias="createdAt")
18
19
  """The timestamp that the task was submitted at."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: runwayml
3
- Version: 1.0.0
3
+ Version: 2.1.0
4
4
  Summary: The official Python library for the runwayml API
5
5
  Project-URL: Homepage, https://github.com/runwayml/sdk-python
6
6
  Project-URL: Repository, https://github.com/runwayml/sdk-python
@@ -14,7 +14,6 @@ Classifier: Operating System :: Microsoft :: Windows
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Operating System :: POSIX
16
16
  Classifier: Operating System :: POSIX :: Linux
17
- Classifier: Programming Language :: Python :: 3.7
18
17
  Classifier: Programming Language :: Python :: 3.8
19
18
  Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
@@ -22,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
23
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
23
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.7
24
+ Requires-Python: >=3.8
26
25
  Requires-Dist: anyio<5,>=3.5.0
27
26
  Requires-Dist: cached-property; python_version < '3.8'
28
27
  Requires-Dist: distro<2,>=1.7.0
@@ -36,7 +35,7 @@ Description-Content-Type: text/markdown
36
35
 
37
36
  [![PyPI version](https://img.shields.io/pypi/v/runwayml.svg)](https://pypi.org/project/runwayml/)
38
37
 
39
- The RunwayML Python library provides convenient access to the RunwayML REST API from any Python 3.7+
38
+ The RunwayML Python library provides convenient access to the RunwayML REST API from any Python 3.8+
40
39
  application. The library includes type definitions for all request params and response fields,
41
40
  and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
42
41
 
@@ -376,4 +375,8 @@ print(runwayml.__version__)
376
375
 
377
376
  ## Requirements
378
377
 
379
- Python 3.7 or higher.
378
+ Python 3.8 or higher.
379
+
380
+ ## Contributing
381
+
382
+ See [the contributing documentation](https://github.com/runwayml/sdk-python/tree/main/./CONTRIBUTING.md).
@@ -1,36 +1,36 @@
1
1
  runwayml/__init__.py,sha256=LsywCoz3efgd9DJAXxam_dZp4-0CuDCfyY658Isqy8k,2458
2
- runwayml/_base_client.py,sha256=fVyB-clTTcUMNtz4jy74XitcUx2wOSNhdLKy-PON-bg,66418
3
- runwayml/_client.py,sha256=bXqlcEWa3xtVB70cQ3UN9eSBKV45WfEdSflQbeJpK0U,16428
4
- runwayml/_compat.py,sha256=FgGcnNlyW7uHKyGh_Wvo7qZi-zVPmHx7mhb3F1GEZSw,6430
2
+ runwayml/_base_client.py,sha256=JZj36mhT5VFQZj-_KhawxsYsoiIzBKDaNmqosZwOQC4,67846
3
+ runwayml/_client.py,sha256=mJfU5Cq2rBWL1fwMlbjFd9w5oWZ8kb5Ps5B37y53PQw,16428
4
+ runwayml/_compat.py,sha256=fQkXUY7reJc8m_yguMWSjHBfO8lNzw4wOAxtkhP9d1Q,6607
5
5
  runwayml/_constants.py,sha256=JE8kyZa2Q4NK_i4fO--8siEYTzeHnT0fYbOFDgDP4uk,464
6
6
  runwayml/_exceptions.py,sha256=p2Q8kywHCVQzArLQL4Ht-HetTBhAvevU6yDvEq7PpIE,3224
7
7
  runwayml/_files.py,sha256=mf4dOgL4b0ryyZlbqLhggD3GVgDf6XxdGFAgce01ugE,3549
8
- runwayml/_models.py,sha256=pXirq94yiihFXBbiR50vA-0NIlDurxbJ_rLXK062vWQ,28267
8
+ runwayml/_models.py,sha256=uhxvXZC0JO7HuGR_GWXH-zYKuptF2rwiGVJfMMfg3fw,28470
9
9
  runwayml/_qs.py,sha256=AOkSz4rHtK4YI3ZU_kzea-zpwBUgEY8WniGmTPyEimc,4846
10
10
  runwayml/_resource.py,sha256=BF-j3xY5eRTKmuTxg8eDhLtLP4MLB1phDh_B6BKipKA,1112
11
- runwayml/_response.py,sha256=sKuVjGpyu3n8kUojbOPqPUtxKMKYWQOBEaPM9Y-I9J4,28532
11
+ runwayml/_response.py,sha256=7EGNYCVRHiVys7y6gAx5HWIwUxWvmjhd05ur53OeFkQ,28621
12
12
  runwayml/_streaming.py,sha256=NSVuAgknVQWU1cgZEjQn01IdZKKynb5rOeYp5Lo-OEQ,10108
13
- runwayml/_types.py,sha256=8a6x8uZ5xoBLrmu6rK3X9NKZhcMYcaE2tUeh3wgKHNk,6105
14
- runwayml/_version.py,sha256=05i5rxCtOf16mfIjrCTjLig-CuA0xwWg_Rv0cFh5POw,160
13
+ runwayml/_types.py,sha256=Mr22kL-koHslGlVH9m-m0B9AMh9Ke2iktDDu_rTGdy4,6169
14
+ runwayml/_version.py,sha256=Q13Mdu-wI8eYR-VMNkBsal9FhtGE8eXvUuWPzokQAfs,160
15
15
  runwayml/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- runwayml/_utils/__init__.py,sha256=Uzq1-FIih_VUjzdNVWXks0sdC39KBKLMrZoz-_JOjJ4,1988
16
+ runwayml/_utils/__init__.py,sha256=k266EatJr88V8Zseb7xUimTlCeno9SynRfLwadHP1b4,2016
17
17
  runwayml/_utils/_logs.py,sha256=ZfS5W59hdqEBVV86lNrk28PhvUxtHOzs9JqiLhSu0pI,780
18
18
  runwayml/_utils/_proxy.py,sha256=z3zsateHtb0EARTWKk8QZNHfPkqJbqwd1lM993LBwGE,1902
19
19
  runwayml/_utils/_reflection.py,sha256=ZmGkIgT_PuwedyNBrrKGbxoWtkpytJNU1uU4QHnmEMU,1364
20
20
  runwayml/_utils/_streams.py,sha256=SMC90diFFecpEg_zgDRVbdR3hSEIgVVij4taD-noMLM,289
21
21
  runwayml/_utils/_sync.py,sha256=9ex9pfOyd8xAF1LxpFx4IkqL8k0vk8srE2Ee-OTMQ0A,2840
22
- runwayml/_utils/_transform.py,sha256=NCz3q9_O-vuj60xVe-qzhEQ8uJWlZWJTsM-GwHDccf8,12958
22
+ runwayml/_utils/_transform.py,sha256=GcsgDf2GwWGI6a7DTKRKa7JPdzcv2EFK8IHFG_8Cw5E,13226
23
23
  runwayml/_utils/_typing.py,sha256=tFbktdpdHCQliwzGsWysgn0P5H0JRdagkZdb_LegGkY,3838
24
- runwayml/_utils/_utils.py,sha256=tYrr7IX-5NMwsVKbNggbzOM84uNw7XnAe06e2Ln8Or0,11472
24
+ runwayml/_utils/_utils.py,sha256=8UmbPOy_AAr2uUjjFui-VZSrVBHRj6bfNEKRp5YZP2A,12004
25
25
  runwayml/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
26
26
  runwayml/resources/__init__.py,sha256=O-ZVFaODsGXK0pKVlV4HKoeJyq3p9sK_9COJTv7P1WM,1069
27
- runwayml/resources/image_to_video.py,sha256=Gc2jhcIjPWhxfZjRcS1JovyBrHdAr4mlHT8NZUEFgoM,8522
27
+ runwayml/resources/image_to_video.py,sha256=bts-S7MA6Rk9p1BMx-2J8WOdrP5iw4c4Kor6jiYaseE,9391
28
28
  runwayml/resources/tasks.py,sha256=j80KvE6QkVAJvfIUx37tJEizcBB1u5vhd9zkSiv6Tuk,9681
29
29
  runwayml/types/__init__.py,sha256=R3cLEXzpcEpEOuxaFBo3R72ewH1LtjpkZ0aYOIt1CAo,400
30
- runwayml/types/image_to_video_create_params.py,sha256=tNsLFhV1ZPk5FOHXJRftPIoEobPE0tLR2qEE5CSIGko,1123
31
- runwayml/types/image_to_video_create_response.py,sha256=Qc5C86xJ_i1JkRLCCkEh6lMnPoO3-iB92gcR7mXooUw,266
32
- runwayml/types/task_retrieve_response.py,sha256=2BsxvP95VgKkn3rdv1vXotMV1zw8RwDSsO27AHjlpyc,2094
33
- runwayml-1.0.0.dist-info/METADATA,sha256=OU255hflxY4KXZnEdFUgwrjYQ0wobKbEGniwmRvTOAg,13284
34
- runwayml-1.0.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
35
- runwayml-1.0.0.dist-info/licenses/LICENSE,sha256=p4nykVRdA5nXTd8-slKCIfrlJZMB3wn8QBVasNQ_G_Q,11338
36
- runwayml-1.0.0.dist-info/RECORD,,
30
+ runwayml/types/image_to_video_create_params.py,sha256=98DsjOHnmHEi8mVjZOQEDL3P1hJUT8uktp0mDA5WJ5Y,1869
31
+ runwayml/types/image_to_video_create_response.py,sha256=l5GszzUSItV-ZYHCB8hH_GSVibUZEkzfRLrAhXkd8O4,346
32
+ runwayml/types/task_retrieve_response.py,sha256=v8y2bLxsW6srzScW-B3Akv72q_PI_NQmduGrGRQMHds,2139
33
+ runwayml-2.1.0.dist-info/METADATA,sha256=bH3HLY2e_TgITkCpbnSqYILCuc_2ugd8bwvyfqYjgUo,13358
34
+ runwayml-2.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
35
+ runwayml-2.1.0.dist-info/licenses/LICENSE,sha256=p4nykVRdA5nXTd8-slKCIfrlJZMB3wn8QBVasNQ_G_Q,11338
36
+ runwayml-2.1.0.dist-info/RECORD,,