From 9d4f77c847837f20ef333a10e5a0d1d9e6a25cf5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:16:24 +0000 Subject: [PATCH 01/11] fix(types): allow pyright to infer TypedDict types within SequenceNotStr --- src/runwayml/_types.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/runwayml/_types.py b/src/runwayml/_types.py index e845cd6..8facef4 100644 --- a/src/runwayml/_types.py +++ b/src/runwayml/_types.py @@ -243,6 +243,9 @@ class HttpxSendArgs(TypedDict, total=False): if TYPE_CHECKING: # This works because str.__contains__ does not accept object (either in typeshed or at runtime) # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. class SequenceNotStr(Protocol[_T_co]): @overload def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... @@ -251,8 +254,6 @@ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... def __contains__(self, value: object, /) -> bool: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[_T_co]: ... - def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... - def count(self, value: Any, /) -> int: ... def __reversed__(self) -> Iterator[_T_co]: ... else: # just point this to a normal `Sequence` at runtime to avoid having to special case From 4c142226410821c794e9e3bf43c7bf9a7d5bf6c1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 03:18:36 +0000 Subject: [PATCH 02/11] chore: add missing docstrings --- .../types/character_performance_create_params.py | 16 ++++++++++++++++ .../types/image_to_video_create_params.py | 4 ++++ .../types/organization_retrieve_response.py | 8 ++++++++ .../types/speech_to_speech_create_params.py | 6 ++++++ src/runwayml/types/task_retrieve_response.py | 12 ++++++++++++ .../types/text_to_image_create_params.py | 4 ++++ .../types/text_to_speech_create_params.py | 2 ++ .../types/video_to_video_create_params.py | 6 ++++++ 8 files changed, 58 insertions(+) diff --git a/src/runwayml/types/character_performance_create_params.py b/src/runwayml/types/character_performance_create_params.py index 8cc93cf..aa66adc 100644 --- a/src/runwayml/types/character_performance_create_params.py +++ b/src/runwayml/types/character_performance_create_params.py @@ -62,6 +62,11 @@ class CharacterPerformanceCreateParams(TypedDict, total=False): class CharacterImage(TypedDict, total=False): + """An image of your character. + + In the output, the character will use the reference video performance in its original static environment. + """ + type: Required[Literal["image"]] uri: Required[str] @@ -69,6 +74,11 @@ class CharacterImage(TypedDict, total=False): class CharacterVideo(TypedDict, total=False): + """A video of your character. + + In the output, the character will use the reference video performance in its original animated environment and some of the character's own movements. + """ + type: Required[Literal["video"]] uri: Required[str] @@ -79,6 +89,10 @@ class CharacterVideo(TypedDict, total=False): class Reference(TypedDict, total=False): + """ + A video of a person performing in the manner that you would like your character to perform. The video must be between 3 and 30 seconds in duration. + """ + type: Required[Literal["video"]] uri: Required[str] @@ -86,6 +100,8 @@ class Reference(TypedDict, total=False): class ContentModeration(TypedDict, total=False): + """Settings that affect the behavior of the content moderation system.""" + public_figure_threshold: Annotated[Literal["auto", "low"], PropertyInfo(alias="publicFigureThreshold")] """ When set to `low`, the content moderation system will be less strict about diff --git a/src/runwayml/types/image_to_video_create_params.py b/src/runwayml/types/image_to_video_create_params.py index ecae04a..16ef9a0 100644 --- a/src/runwayml/types/image_to_video_create_params.py +++ b/src/runwayml/types/image_to_video_create_params.py @@ -68,6 +68,8 @@ class Gen4TurboPromptImagePromptImage(TypedDict, total=False): class Gen4TurboContentModeration(TypedDict, total=False): + """Settings that affect the behavior of the content moderation system.""" + public_figure_threshold: Annotated[Literal["auto", "low"], PropertyInfo(alias="publicFigureThreshold")] """ When set to `low`, the content moderation system will be less strict about @@ -160,6 +162,8 @@ class Gen3aTurboPromptImagePromptImage(TypedDict, total=False): class Gen3aTurboContentModeration(TypedDict, total=False): + """Settings that affect the behavior of the content moderation system.""" + public_figure_threshold: Annotated[Literal["auto", "low"], PropertyInfo(alias="publicFigureThreshold")] """ When set to `low`, the content moderation system will be less strict about diff --git a/src/runwayml/types/organization_retrieve_response.py b/src/runwayml/types/organization_retrieve_response.py index f3a56ca..236bb0b 100644 --- a/src/runwayml/types/organization_retrieve_response.py +++ b/src/runwayml/types/organization_retrieve_response.py @@ -10,6 +10,8 @@ class TierModels(BaseModel): + """Limits associated with the model.""" + max_concurrent_generations: int = FieldInfo(alias="maxConcurrentGenerations") """The maximum number of generations that can be run concurrently for this model.""" @@ -18,6 +20,8 @@ class TierModels(BaseModel): class Tier(BaseModel): + """Limits associated with the organization's tier.""" + max_monthly_credit_spend: int = FieldInfo(alias="maxMonthlyCreditSpend") """The maximum number of credits that can be purchased in a month.""" @@ -26,11 +30,15 @@ class Tier(BaseModel): class UsageModels(BaseModel): + """Usage data for the model.""" + daily_generations: int = FieldInfo(alias="dailyGenerations") """The number of generations that have been run for this model in the past day.""" class Usage(BaseModel): + """Usage data for the organization.""" + models: Dict[str, UsageModels] diff --git a/src/runwayml/types/speech_to_speech_create_params.py b/src/runwayml/types/speech_to_speech_create_params.py index 50bda5f..18fc4db 100644 --- a/src/runwayml/types/speech_to_speech_create_params.py +++ b/src/runwayml/types/speech_to_speech_create_params.py @@ -24,6 +24,8 @@ class SpeechToSpeechCreateParams(TypedDict, total=False): class MediaAudio(TypedDict, total=False): + """An audio file containing dialogue to be processed.""" + type: Required[Literal["audio"]] uri: Required[str] @@ -31,6 +33,8 @@ class MediaAudio(TypedDict, total=False): class MediaVideo(TypedDict, total=False): + """A video file containing dialogue to be processed.""" + type: Required[Literal["video"]] uri: Required[str] @@ -41,6 +45,8 @@ class MediaVideo(TypedDict, total=False): class Voice(TypedDict, total=False): + """A voice preset from the RunwayML API.""" + preset_id: Required[ Annotated[ Literal[ diff --git a/src/runwayml/types/task_retrieve_response.py b/src/runwayml/types/task_retrieve_response.py index 9284246..c6a2d91 100644 --- a/src/runwayml/types/task_retrieve_response.py +++ b/src/runwayml/types/task_retrieve_response.py @@ -13,6 +13,8 @@ class Pending(BaseModel): + """A pending task""" + id: str """The ID of the task being returned.""" @@ -23,6 +25,8 @@ class Pending(BaseModel): class Throttled(BaseModel): + """A throttled task""" + id: str """The ID of the task being returned.""" @@ -33,6 +37,8 @@ class Throttled(BaseModel): class Cancelled(BaseModel): + """A cancelled or deleted task""" + id: str """The ID of the task being returned.""" @@ -43,6 +49,8 @@ class Cancelled(BaseModel): class Running(BaseModel): + """A running task""" + id: str """The ID of the task being returned.""" @@ -55,6 +63,8 @@ class Running(BaseModel): class Failed(BaseModel): + """A failed task""" + id: str """The ID of the task being returned.""" @@ -77,6 +87,8 @@ class Failed(BaseModel): class Succeeded(BaseModel): + """A succeeded task""" + id: str """The ID of the task being returned.""" diff --git a/src/runwayml/types/text_to_image_create_params.py b/src/runwayml/types/text_to_image_create_params.py index 8a1e6c6..daaf399 100644 --- a/src/runwayml/types/text_to_image_create_params.py +++ b/src/runwayml/types/text_to_image_create_params.py @@ -81,6 +81,8 @@ class Gen4ImageTurboReferenceImage(TypedDict, total=False): class Gen4ImageTurboContentModeration(TypedDict, total=False): + """Settings that affect the behavior of the content moderation system.""" + public_figure_threshold: Annotated[Literal["auto", "low"], PropertyInfo(alias="publicFigureThreshold")] """ When set to `low`, the content moderation system will be less strict about @@ -138,6 +140,8 @@ class Gen4Image(TypedDict, total=False): class Gen4ImageContentModeration(TypedDict, total=False): + """Settings that affect the behavior of the content moderation system.""" + public_figure_threshold: Annotated[Literal["auto", "low"], PropertyInfo(alias="publicFigureThreshold")] """ When set to `low`, the content moderation system will be less strict about diff --git a/src/runwayml/types/text_to_speech_create_params.py b/src/runwayml/types/text_to_speech_create_params.py index 7518af7..3940a85 100644 --- a/src/runwayml/types/text_to_speech_create_params.py +++ b/src/runwayml/types/text_to_speech_create_params.py @@ -23,6 +23,8 @@ class TextToSpeechCreateParams(TypedDict, total=False): class Voice(TypedDict, total=False): + """A voice preset from the RunwayML API.""" + preset_id: Required[ Annotated[ Literal[ diff --git a/src/runwayml/types/video_to_video_create_params.py b/src/runwayml/types/video_to_video_create_params.py index b2cb513..56c5350 100644 --- a/src/runwayml/types/video_to_video_create_params.py +++ b/src/runwayml/types/video_to_video_create_params.py @@ -47,6 +47,8 @@ class VideoToVideoCreateParams(TypedDict, total=False): class ContentModeration(TypedDict, total=False): + """Settings that affect the behavior of the content moderation system.""" + public_figure_threshold: Annotated[Literal["auto", "low"], PropertyInfo(alias="publicFigureThreshold")] """ When set to `low`, the content moderation system will be less strict about @@ -55,6 +57,10 @@ class ContentModeration(TypedDict, total=False): class Reference(TypedDict, total=False): + """ + Passing an image reference allows the model to emulate the style or content of the reference in the output. + """ + type: Required[Literal["image"]] uri: Required[str] From 1c5f3af332fdd873865234b11c0b1b24aa9df688 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 03:12:43 +0000 Subject: [PATCH 03/11] chore(internal): add missing files argument to base client --- src/runwayml/_base_client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/runwayml/_base_client.py b/src/runwayml/_base_client.py index 8e9efd7..cc0bf65 100644 --- a/src/runwayml/_base_client.py +++ b/src/runwayml/_base_client.py @@ -1247,9 +1247,12 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1767,9 +1770,12 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return await self.request(cast_to, opts) async def put( From cfcc2e96de68498e8b1cbca4b02567283d8af207 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:14:39 +0000 Subject: [PATCH 04/11] chore: speedup initial import --- examples/upload_file.py | 6 +- src/runwayml/_client.py | 713 ++++++++++++++---- src/runwayml/lib/polling.py | 64 +- src/runwayml/resources/tasks.py | 8 +- src/runwayml/resources/uploads.py | 20 +- .../types/upload_create_ephemeral_response.py | 1 - tests/api_resources/test_uploads.py | 1 - 7 files changed, 636 insertions(+), 177 deletions(-) diff --git a/examples/upload_file.py b/examples/upload_file.py index c02573b..ea75574 100644 --- a/examples/upload_file.py +++ b/examples/upload_file.py @@ -20,7 +20,7 @@ async def async_upload() -> None: client = AsyncRunwayML() upload_response = await client.uploads.create_ephemeral(file=file_path) print(f"Upload successful! URI: {upload_response.uri}") - + if args.i: image_task = await client.text_to_image.create( model="gen4_image", @@ -35,7 +35,7 @@ def sync_upload() -> None: client = RunwayML() upload_response = client.uploads.create_ephemeral(file=file_path) print(f"Upload successful! URI: {upload_response.uri}") - + if args.i: image_task = client.text_to_image.create( model="gen4_image", @@ -49,4 +49,4 @@ def sync_upload() -> None: if args.use_async: asyncio.run(async_upload()) else: - sync_upload() \ No newline at end of file + sync_upload() diff --git a/src/runwayml/_client.py b/src/runwayml/_client.py index 1feac10..0172f3c 100644 --- a/src/runwayml/_client.py +++ b/src/runwayml/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Mapping +from typing import TYPE_CHECKING, Any, Mapping from typing_extensions import Self, override import httpx @@ -20,23 +20,8 @@ not_given, ) from ._utils import is_given, get_async_library +from ._compat import cached_property from ._version import __version__ -from .resources import ( - tasks, - uploads, - organization, - sound_effect, - text_to_image, - text_to_video, - video_upscale, - voice_dubbing, - image_to_video, - text_to_speech, - video_to_video, - voice_isolation, - speech_to_speech, - character_performance, -) from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import RunwayMLError, APIStatusError from ._base_client import ( @@ -45,6 +30,38 @@ AsyncAPIClient, ) +if TYPE_CHECKING: + from .resources import ( + tasks, + uploads, + organization, + sound_effect, + text_to_image, + text_to_video, + video_upscale, + voice_dubbing, + image_to_video, + text_to_speech, + video_to_video, + voice_isolation, + speech_to_speech, + character_performance, + ) + from .resources.tasks import TasksResource, AsyncTasksResource + from .resources.uploads import UploadsResource, AsyncUploadsResource + from .resources.organization import OrganizationResource, AsyncOrganizationResource + from .resources.sound_effect import SoundEffectResource, AsyncSoundEffectResource + from .resources.text_to_image import TextToImageResource, AsyncTextToImageResource + from .resources.text_to_video import TextToVideoResource, AsyncTextToVideoResource + from .resources.video_upscale import VideoUpscaleResource, AsyncVideoUpscaleResource + from .resources.voice_dubbing import VoiceDubbingResource, AsyncVoiceDubbingResource + from .resources.image_to_video import ImageToVideoResource, AsyncImageToVideoResource + from .resources.text_to_speech import TextToSpeechResource, AsyncTextToSpeechResource + from .resources.video_to_video import VideoToVideoResource, AsyncVideoToVideoResource + from .resources.voice_isolation import VoiceIsolationResource, AsyncVoiceIsolationResource + from .resources.speech_to_speech import SpeechToSpeechResource, AsyncSpeechToSpeechResource + from .resources.character_performance import CharacterPerformanceResource, AsyncCharacterPerformanceResource + __all__ = [ "Timeout", "Transport", @@ -58,23 +75,6 @@ class RunwayML(SyncAPIClient): - tasks: tasks.TasksResource - image_to_video: image_to_video.ImageToVideoResource - video_to_video: video_to_video.VideoToVideoResource - text_to_video: text_to_video.TextToVideoResource - text_to_image: text_to_image.TextToImageResource - video_upscale: video_upscale.VideoUpscaleResource - character_performance: character_performance.CharacterPerformanceResource - text_to_speech: text_to_speech.TextToSpeechResource - sound_effect: sound_effect.SoundEffectResource - voice_isolation: voice_isolation.VoiceIsolationResource - voice_dubbing: voice_dubbing.VoiceDubbingResource - speech_to_speech: speech_to_speech.SpeechToSpeechResource - organization: organization.OrganizationResource - uploads: uploads.UploadsResource - with_raw_response: RunwayMLWithRawResponse - with_streaming_response: RunwayMLWithStreamedResponse - # client options api_key: str runway_version: str @@ -135,22 +135,97 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.tasks = tasks.TasksResource(self) - self.image_to_video = image_to_video.ImageToVideoResource(self) - self.video_to_video = video_to_video.VideoToVideoResource(self) - self.text_to_video = text_to_video.TextToVideoResource(self) - self.text_to_image = text_to_image.TextToImageResource(self) - self.video_upscale = video_upscale.VideoUpscaleResource(self) - self.character_performance = character_performance.CharacterPerformanceResource(self) - self.text_to_speech = text_to_speech.TextToSpeechResource(self) - self.sound_effect = sound_effect.SoundEffectResource(self) - self.voice_isolation = voice_isolation.VoiceIsolationResource(self) - self.voice_dubbing = voice_dubbing.VoiceDubbingResource(self) - self.speech_to_speech = speech_to_speech.SpeechToSpeechResource(self) - self.organization = organization.OrganizationResource(self) - self.uploads = uploads.UploadsResource(self) - self.with_raw_response = RunwayMLWithRawResponse(self) - self.with_streaming_response = RunwayMLWithStreamedResponse(self) + @cached_property + def tasks(self) -> TasksResource: + from .resources.tasks import TasksResource + + return TasksResource(self) + + @cached_property + def uploads(self) -> UploadsResource: + from .resources.uploads import UploadsResource + + return UploadsResource(self) + + @cached_property + def image_to_video(self) -> ImageToVideoResource: + from .resources.image_to_video import ImageToVideoResource + + return ImageToVideoResource(self) + + @cached_property + def video_to_video(self) -> VideoToVideoResource: + from .resources.video_to_video import VideoToVideoResource + + return VideoToVideoResource(self) + + @cached_property + def text_to_video(self) -> TextToVideoResource: + from .resources.text_to_video import TextToVideoResource + + return TextToVideoResource(self) + + @cached_property + def text_to_image(self) -> TextToImageResource: + from .resources.text_to_image import TextToImageResource + + return TextToImageResource(self) + + @cached_property + def video_upscale(self) -> VideoUpscaleResource: + from .resources.video_upscale import VideoUpscaleResource + + return VideoUpscaleResource(self) + + @cached_property + def character_performance(self) -> CharacterPerformanceResource: + from .resources.character_performance import CharacterPerformanceResource + + return CharacterPerformanceResource(self) + + @cached_property + def text_to_speech(self) -> TextToSpeechResource: + from .resources.text_to_speech import TextToSpeechResource + + return TextToSpeechResource(self) + + @cached_property + def sound_effect(self) -> SoundEffectResource: + from .resources.sound_effect import SoundEffectResource + + return SoundEffectResource(self) + + @cached_property + def voice_isolation(self) -> VoiceIsolationResource: + from .resources.voice_isolation import VoiceIsolationResource + + return VoiceIsolationResource(self) + + @cached_property + def voice_dubbing(self) -> VoiceDubbingResource: + from .resources.voice_dubbing import VoiceDubbingResource + + return VoiceDubbingResource(self) + + @cached_property + def speech_to_speech(self) -> SpeechToSpeechResource: + from .resources.speech_to_speech import SpeechToSpeechResource + + return SpeechToSpeechResource(self) + + @cached_property + def organization(self) -> OrganizationResource: + from .resources.organization import OrganizationResource + + return OrganizationResource(self) + + @cached_property + def with_raw_response(self) -> RunwayMLWithRawResponse: + return RunwayMLWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RunwayMLWithStreamedResponse: + return RunwayMLWithStreamedResponse(self) @property @override @@ -261,23 +336,6 @@ def _make_status_error( class AsyncRunwayML(AsyncAPIClient): - tasks: tasks.AsyncTasksResource - image_to_video: image_to_video.AsyncImageToVideoResource - video_to_video: video_to_video.AsyncVideoToVideoResource - text_to_video: text_to_video.AsyncTextToVideoResource - text_to_image: text_to_image.AsyncTextToImageResource - video_upscale: video_upscale.AsyncVideoUpscaleResource - character_performance: character_performance.AsyncCharacterPerformanceResource - text_to_speech: text_to_speech.AsyncTextToSpeechResource - sound_effect: sound_effect.AsyncSoundEffectResource - voice_isolation: voice_isolation.AsyncVoiceIsolationResource - voice_dubbing: voice_dubbing.AsyncVoiceDubbingResource - speech_to_speech: speech_to_speech.AsyncSpeechToSpeechResource - organization: organization.AsyncOrganizationResource - uploads: uploads.AsyncUploadsResource - with_raw_response: AsyncRunwayMLWithRawResponse - with_streaming_response: AsyncRunwayMLWithStreamedResponse - # client options api_key: str runway_version: str @@ -338,22 +396,97 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.tasks = tasks.AsyncTasksResource(self) - self.image_to_video = image_to_video.AsyncImageToVideoResource(self) - self.video_to_video = video_to_video.AsyncVideoToVideoResource(self) - self.text_to_video = text_to_video.AsyncTextToVideoResource(self) - self.text_to_image = text_to_image.AsyncTextToImageResource(self) - self.video_upscale = video_upscale.AsyncVideoUpscaleResource(self) - self.character_performance = character_performance.AsyncCharacterPerformanceResource(self) - self.text_to_speech = text_to_speech.AsyncTextToSpeechResource(self) - self.sound_effect = sound_effect.AsyncSoundEffectResource(self) - self.voice_isolation = voice_isolation.AsyncVoiceIsolationResource(self) - self.voice_dubbing = voice_dubbing.AsyncVoiceDubbingResource(self) - self.speech_to_speech = speech_to_speech.AsyncSpeechToSpeechResource(self) - self.organization = organization.AsyncOrganizationResource(self) - self.uploads = uploads.AsyncUploadsResource(self) - self.with_raw_response = AsyncRunwayMLWithRawResponse(self) - self.with_streaming_response = AsyncRunwayMLWithStreamedResponse(self) + @cached_property + def tasks(self) -> AsyncTasksResource: + from .resources.tasks import AsyncTasksResource + + return AsyncTasksResource(self) + + @cached_property + def uploads(self) -> AsyncUploadsResource: + from .resources.uploads import AsyncUploadsResource + + return AsyncUploadsResource(self) + + @cached_property + def image_to_video(self) -> AsyncImageToVideoResource: + from .resources.image_to_video import AsyncImageToVideoResource + + return AsyncImageToVideoResource(self) + + @cached_property + def video_to_video(self) -> AsyncVideoToVideoResource: + from .resources.video_to_video import AsyncVideoToVideoResource + + return AsyncVideoToVideoResource(self) + + @cached_property + def text_to_video(self) -> AsyncTextToVideoResource: + from .resources.text_to_video import AsyncTextToVideoResource + + return AsyncTextToVideoResource(self) + + @cached_property + def text_to_image(self) -> AsyncTextToImageResource: + from .resources.text_to_image import AsyncTextToImageResource + + return AsyncTextToImageResource(self) + + @cached_property + def video_upscale(self) -> AsyncVideoUpscaleResource: + from .resources.video_upscale import AsyncVideoUpscaleResource + + return AsyncVideoUpscaleResource(self) + + @cached_property + def character_performance(self) -> AsyncCharacterPerformanceResource: + from .resources.character_performance import AsyncCharacterPerformanceResource + + return AsyncCharacterPerformanceResource(self) + + @cached_property + def text_to_speech(self) -> AsyncTextToSpeechResource: + from .resources.text_to_speech import AsyncTextToSpeechResource + + return AsyncTextToSpeechResource(self) + + @cached_property + def sound_effect(self) -> AsyncSoundEffectResource: + from .resources.sound_effect import AsyncSoundEffectResource + + return AsyncSoundEffectResource(self) + + @cached_property + def voice_isolation(self) -> AsyncVoiceIsolationResource: + from .resources.voice_isolation import AsyncVoiceIsolationResource + + return AsyncVoiceIsolationResource(self) + + @cached_property + def voice_dubbing(self) -> AsyncVoiceDubbingResource: + from .resources.voice_dubbing import AsyncVoiceDubbingResource + + return AsyncVoiceDubbingResource(self) + + @cached_property + def speech_to_speech(self) -> AsyncSpeechToSpeechResource: + from .resources.speech_to_speech import AsyncSpeechToSpeechResource + + return AsyncSpeechToSpeechResource(self) + + @cached_property + def organization(self) -> AsyncOrganizationResource: + from .resources.organization import AsyncOrganizationResource + + return AsyncOrganizationResource(self) + + @cached_property + def with_raw_response(self) -> AsyncRunwayMLWithRawResponse: + return AsyncRunwayMLWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRunwayMLWithStreamedResponse: + return AsyncRunwayMLWithStreamedResponse(self) @property @override @@ -464,85 +597,367 @@ def _make_status_error( class RunwayMLWithRawResponse: + _client: RunwayML + def __init__(self, client: RunwayML) -> None: - self.tasks = tasks.TasksResourceWithRawResponse(client.tasks) - self.image_to_video = image_to_video.ImageToVideoResourceWithRawResponse(client.image_to_video) - self.video_to_video = video_to_video.VideoToVideoResourceWithRawResponse(client.video_to_video) - self.text_to_video = text_to_video.TextToVideoResourceWithRawResponse(client.text_to_video) - self.text_to_image = text_to_image.TextToImageResourceWithRawResponse(client.text_to_image) - self.video_upscale = video_upscale.VideoUpscaleResourceWithRawResponse(client.video_upscale) - self.character_performance = character_performance.CharacterPerformanceResourceWithRawResponse( - client.character_performance - ) - self.text_to_speech = text_to_speech.TextToSpeechResourceWithRawResponse(client.text_to_speech) - self.sound_effect = sound_effect.SoundEffectResourceWithRawResponse(client.sound_effect) - self.voice_isolation = voice_isolation.VoiceIsolationResourceWithRawResponse(client.voice_isolation) - self.voice_dubbing = voice_dubbing.VoiceDubbingResourceWithRawResponse(client.voice_dubbing) - self.speech_to_speech = speech_to_speech.SpeechToSpeechResourceWithRawResponse(client.speech_to_speech) - self.organization = organization.OrganizationResourceWithRawResponse(client.organization) - self.uploads = uploads.UploadsResourceWithRawResponse(client.uploads) + self._client = client + + @cached_property + def tasks(self) -> tasks.TasksResourceWithRawResponse: + from .resources.tasks import TasksResourceWithRawResponse + + return TasksResourceWithRawResponse(self._client.tasks) + + @cached_property + def uploads(self) -> uploads.UploadsResourceWithRawResponse: + from .resources.uploads import UploadsResourceWithRawResponse + + return UploadsResourceWithRawResponse(self._client.uploads) + + @cached_property + def image_to_video(self) -> image_to_video.ImageToVideoResourceWithRawResponse: + from .resources.image_to_video import ImageToVideoResourceWithRawResponse + + return ImageToVideoResourceWithRawResponse(self._client.image_to_video) + + @cached_property + def video_to_video(self) -> video_to_video.VideoToVideoResourceWithRawResponse: + from .resources.video_to_video import VideoToVideoResourceWithRawResponse + + return VideoToVideoResourceWithRawResponse(self._client.video_to_video) + + @cached_property + def text_to_video(self) -> text_to_video.TextToVideoResourceWithRawResponse: + from .resources.text_to_video import TextToVideoResourceWithRawResponse + + return TextToVideoResourceWithRawResponse(self._client.text_to_video) + + @cached_property + def text_to_image(self) -> text_to_image.TextToImageResourceWithRawResponse: + from .resources.text_to_image import TextToImageResourceWithRawResponse + + return TextToImageResourceWithRawResponse(self._client.text_to_image) + + @cached_property + def video_upscale(self) -> video_upscale.VideoUpscaleResourceWithRawResponse: + from .resources.video_upscale import VideoUpscaleResourceWithRawResponse + + return VideoUpscaleResourceWithRawResponse(self._client.video_upscale) + + @cached_property + def character_performance(self) -> character_performance.CharacterPerformanceResourceWithRawResponse: + from .resources.character_performance import CharacterPerformanceResourceWithRawResponse + + return CharacterPerformanceResourceWithRawResponse(self._client.character_performance) + + @cached_property + def text_to_speech(self) -> text_to_speech.TextToSpeechResourceWithRawResponse: + from .resources.text_to_speech import TextToSpeechResourceWithRawResponse + + return TextToSpeechResourceWithRawResponse(self._client.text_to_speech) + + @cached_property + def sound_effect(self) -> sound_effect.SoundEffectResourceWithRawResponse: + from .resources.sound_effect import SoundEffectResourceWithRawResponse + + return SoundEffectResourceWithRawResponse(self._client.sound_effect) + + @cached_property + def voice_isolation(self) -> voice_isolation.VoiceIsolationResourceWithRawResponse: + from .resources.voice_isolation import VoiceIsolationResourceWithRawResponse + + return VoiceIsolationResourceWithRawResponse(self._client.voice_isolation) + + @cached_property + def voice_dubbing(self) -> voice_dubbing.VoiceDubbingResourceWithRawResponse: + from .resources.voice_dubbing import VoiceDubbingResourceWithRawResponse + + return VoiceDubbingResourceWithRawResponse(self._client.voice_dubbing) + + @cached_property + def speech_to_speech(self) -> speech_to_speech.SpeechToSpeechResourceWithRawResponse: + from .resources.speech_to_speech import SpeechToSpeechResourceWithRawResponse + + return SpeechToSpeechResourceWithRawResponse(self._client.speech_to_speech) + + @cached_property + def organization(self) -> organization.OrganizationResourceWithRawResponse: + from .resources.organization import OrganizationResourceWithRawResponse + + return OrganizationResourceWithRawResponse(self._client.organization) class AsyncRunwayMLWithRawResponse: + _client: AsyncRunwayML + def __init__(self, client: AsyncRunwayML) -> None: - self.tasks = tasks.AsyncTasksResourceWithRawResponse(client.tasks) - self.image_to_video = image_to_video.AsyncImageToVideoResourceWithRawResponse(client.image_to_video) - self.video_to_video = video_to_video.AsyncVideoToVideoResourceWithRawResponse(client.video_to_video) - self.text_to_video = text_to_video.AsyncTextToVideoResourceWithRawResponse(client.text_to_video) - self.text_to_image = text_to_image.AsyncTextToImageResourceWithRawResponse(client.text_to_image) - self.video_upscale = video_upscale.AsyncVideoUpscaleResourceWithRawResponse(client.video_upscale) - self.character_performance = character_performance.AsyncCharacterPerformanceResourceWithRawResponse( - client.character_performance - ) - self.text_to_speech = text_to_speech.AsyncTextToSpeechResourceWithRawResponse(client.text_to_speech) - self.sound_effect = sound_effect.AsyncSoundEffectResourceWithRawResponse(client.sound_effect) - self.voice_isolation = voice_isolation.AsyncVoiceIsolationResourceWithRawResponse(client.voice_isolation) - self.voice_dubbing = voice_dubbing.AsyncVoiceDubbingResourceWithRawResponse(client.voice_dubbing) - self.speech_to_speech = speech_to_speech.AsyncSpeechToSpeechResourceWithRawResponse(client.speech_to_speech) - self.organization = organization.AsyncOrganizationResourceWithRawResponse(client.organization) - self.uploads = uploads.AsyncUploadsResourceWithRawResponse(client.uploads) + self._client = client + + @cached_property + def tasks(self) -> tasks.AsyncTasksResourceWithRawResponse: + from .resources.tasks import AsyncTasksResourceWithRawResponse + + return AsyncTasksResourceWithRawResponse(self._client.tasks) + + @cached_property + def uploads(self) -> uploads.AsyncUploadsResourceWithRawResponse: + from .resources.uploads import AsyncUploadsResourceWithRawResponse + + return AsyncUploadsResourceWithRawResponse(self._client.uploads) + + @cached_property + def image_to_video(self) -> image_to_video.AsyncImageToVideoResourceWithRawResponse: + from .resources.image_to_video import AsyncImageToVideoResourceWithRawResponse + + return AsyncImageToVideoResourceWithRawResponse(self._client.image_to_video) + + @cached_property + def video_to_video(self) -> video_to_video.AsyncVideoToVideoResourceWithRawResponse: + from .resources.video_to_video import AsyncVideoToVideoResourceWithRawResponse + + return AsyncVideoToVideoResourceWithRawResponse(self._client.video_to_video) + + @cached_property + def text_to_video(self) -> text_to_video.AsyncTextToVideoResourceWithRawResponse: + from .resources.text_to_video import AsyncTextToVideoResourceWithRawResponse + + return AsyncTextToVideoResourceWithRawResponse(self._client.text_to_video) + + @cached_property + def text_to_image(self) -> text_to_image.AsyncTextToImageResourceWithRawResponse: + from .resources.text_to_image import AsyncTextToImageResourceWithRawResponse + + return AsyncTextToImageResourceWithRawResponse(self._client.text_to_image) + + @cached_property + def video_upscale(self) -> video_upscale.AsyncVideoUpscaleResourceWithRawResponse: + from .resources.video_upscale import AsyncVideoUpscaleResourceWithRawResponse + + return AsyncVideoUpscaleResourceWithRawResponse(self._client.video_upscale) + + @cached_property + def character_performance(self) -> character_performance.AsyncCharacterPerformanceResourceWithRawResponse: + from .resources.character_performance import AsyncCharacterPerformanceResourceWithRawResponse + + return AsyncCharacterPerformanceResourceWithRawResponse(self._client.character_performance) + + @cached_property + def text_to_speech(self) -> text_to_speech.AsyncTextToSpeechResourceWithRawResponse: + from .resources.text_to_speech import AsyncTextToSpeechResourceWithRawResponse + + return AsyncTextToSpeechResourceWithRawResponse(self._client.text_to_speech) + + @cached_property + def sound_effect(self) -> sound_effect.AsyncSoundEffectResourceWithRawResponse: + from .resources.sound_effect import AsyncSoundEffectResourceWithRawResponse + + return AsyncSoundEffectResourceWithRawResponse(self._client.sound_effect) + + @cached_property + def voice_isolation(self) -> voice_isolation.AsyncVoiceIsolationResourceWithRawResponse: + from .resources.voice_isolation import AsyncVoiceIsolationResourceWithRawResponse + + return AsyncVoiceIsolationResourceWithRawResponse(self._client.voice_isolation) + + @cached_property + def voice_dubbing(self) -> voice_dubbing.AsyncVoiceDubbingResourceWithRawResponse: + from .resources.voice_dubbing import AsyncVoiceDubbingResourceWithRawResponse + + return AsyncVoiceDubbingResourceWithRawResponse(self._client.voice_dubbing) + + @cached_property + def speech_to_speech(self) -> speech_to_speech.AsyncSpeechToSpeechResourceWithRawResponse: + from .resources.speech_to_speech import AsyncSpeechToSpeechResourceWithRawResponse + + return AsyncSpeechToSpeechResourceWithRawResponse(self._client.speech_to_speech) + + @cached_property + def organization(self) -> organization.AsyncOrganizationResourceWithRawResponse: + from .resources.organization import AsyncOrganizationResourceWithRawResponse + + return AsyncOrganizationResourceWithRawResponse(self._client.organization) class RunwayMLWithStreamedResponse: + _client: RunwayML + def __init__(self, client: RunwayML) -> None: - self.tasks = tasks.TasksResourceWithStreamingResponse(client.tasks) - self.image_to_video = image_to_video.ImageToVideoResourceWithStreamingResponse(client.image_to_video) - self.video_to_video = video_to_video.VideoToVideoResourceWithStreamingResponse(client.video_to_video) - self.text_to_video = text_to_video.TextToVideoResourceWithStreamingResponse(client.text_to_video) - self.text_to_image = text_to_image.TextToImageResourceWithStreamingResponse(client.text_to_image) - self.video_upscale = video_upscale.VideoUpscaleResourceWithStreamingResponse(client.video_upscale) - self.character_performance = character_performance.CharacterPerformanceResourceWithStreamingResponse( - client.character_performance - ) - self.text_to_speech = text_to_speech.TextToSpeechResourceWithStreamingResponse(client.text_to_speech) - self.sound_effect = sound_effect.SoundEffectResourceWithStreamingResponse(client.sound_effect) - self.voice_isolation = voice_isolation.VoiceIsolationResourceWithStreamingResponse(client.voice_isolation) - self.voice_dubbing = voice_dubbing.VoiceDubbingResourceWithStreamingResponse(client.voice_dubbing) - self.speech_to_speech = speech_to_speech.SpeechToSpeechResourceWithStreamingResponse(client.speech_to_speech) - self.organization = organization.OrganizationResourceWithStreamingResponse(client.organization) - self.uploads = uploads.UploadsResourceWithStreamingResponse(client.uploads) + self._client = client + + @cached_property + def tasks(self) -> tasks.TasksResourceWithStreamingResponse: + from .resources.tasks import TasksResourceWithStreamingResponse + + return TasksResourceWithStreamingResponse(self._client.tasks) + + @cached_property + def uploads(self) -> uploads.UploadsResourceWithStreamingResponse: + from .resources.uploads import UploadsResourceWithStreamingResponse + + return UploadsResourceWithStreamingResponse(self._client.uploads) + + @cached_property + def image_to_video(self) -> image_to_video.ImageToVideoResourceWithStreamingResponse: + from .resources.image_to_video import ImageToVideoResourceWithStreamingResponse + + return ImageToVideoResourceWithStreamingResponse(self._client.image_to_video) + + @cached_property + def video_to_video(self) -> video_to_video.VideoToVideoResourceWithStreamingResponse: + from .resources.video_to_video import VideoToVideoResourceWithStreamingResponse + + return VideoToVideoResourceWithStreamingResponse(self._client.video_to_video) + + @cached_property + def text_to_video(self) -> text_to_video.TextToVideoResourceWithStreamingResponse: + from .resources.text_to_video import TextToVideoResourceWithStreamingResponse + + return TextToVideoResourceWithStreamingResponse(self._client.text_to_video) + + @cached_property + def text_to_image(self) -> text_to_image.TextToImageResourceWithStreamingResponse: + from .resources.text_to_image import TextToImageResourceWithStreamingResponse + + return TextToImageResourceWithStreamingResponse(self._client.text_to_image) + + @cached_property + def video_upscale(self) -> video_upscale.VideoUpscaleResourceWithStreamingResponse: + from .resources.video_upscale import VideoUpscaleResourceWithStreamingResponse + + return VideoUpscaleResourceWithStreamingResponse(self._client.video_upscale) + + @cached_property + def character_performance(self) -> character_performance.CharacterPerformanceResourceWithStreamingResponse: + from .resources.character_performance import CharacterPerformanceResourceWithStreamingResponse + + return CharacterPerformanceResourceWithStreamingResponse(self._client.character_performance) + + @cached_property + def text_to_speech(self) -> text_to_speech.TextToSpeechResourceWithStreamingResponse: + from .resources.text_to_speech import TextToSpeechResourceWithStreamingResponse + + return TextToSpeechResourceWithStreamingResponse(self._client.text_to_speech) + + @cached_property + def sound_effect(self) -> sound_effect.SoundEffectResourceWithStreamingResponse: + from .resources.sound_effect import SoundEffectResourceWithStreamingResponse + + return SoundEffectResourceWithStreamingResponse(self._client.sound_effect) + + @cached_property + def voice_isolation(self) -> voice_isolation.VoiceIsolationResourceWithStreamingResponse: + from .resources.voice_isolation import VoiceIsolationResourceWithStreamingResponse + + return VoiceIsolationResourceWithStreamingResponse(self._client.voice_isolation) + + @cached_property + def voice_dubbing(self) -> voice_dubbing.VoiceDubbingResourceWithStreamingResponse: + from .resources.voice_dubbing import VoiceDubbingResourceWithStreamingResponse + + return VoiceDubbingResourceWithStreamingResponse(self._client.voice_dubbing) + + @cached_property + def speech_to_speech(self) -> speech_to_speech.SpeechToSpeechResourceWithStreamingResponse: + from .resources.speech_to_speech import SpeechToSpeechResourceWithStreamingResponse + + return SpeechToSpeechResourceWithStreamingResponse(self._client.speech_to_speech) + + @cached_property + def organization(self) -> organization.OrganizationResourceWithStreamingResponse: + from .resources.organization import OrganizationResourceWithStreamingResponse + + return OrganizationResourceWithStreamingResponse(self._client.organization) class AsyncRunwayMLWithStreamedResponse: + _client: AsyncRunwayML + def __init__(self, client: AsyncRunwayML) -> None: - self.tasks = tasks.AsyncTasksResourceWithStreamingResponse(client.tasks) - self.image_to_video = image_to_video.AsyncImageToVideoResourceWithStreamingResponse(client.image_to_video) - self.video_to_video = video_to_video.AsyncVideoToVideoResourceWithStreamingResponse(client.video_to_video) - self.text_to_video = text_to_video.AsyncTextToVideoResourceWithStreamingResponse(client.text_to_video) - self.text_to_image = text_to_image.AsyncTextToImageResourceWithStreamingResponse(client.text_to_image) - self.video_upscale = video_upscale.AsyncVideoUpscaleResourceWithStreamingResponse(client.video_upscale) - self.character_performance = character_performance.AsyncCharacterPerformanceResourceWithStreamingResponse( - client.character_performance - ) - self.text_to_speech = text_to_speech.AsyncTextToSpeechResourceWithStreamingResponse(client.text_to_speech) - self.sound_effect = sound_effect.AsyncSoundEffectResourceWithStreamingResponse(client.sound_effect) - self.voice_isolation = voice_isolation.AsyncVoiceIsolationResourceWithStreamingResponse(client.voice_isolation) - self.voice_dubbing = voice_dubbing.AsyncVoiceDubbingResourceWithStreamingResponse(client.voice_dubbing) - self.speech_to_speech = speech_to_speech.AsyncSpeechToSpeechResourceWithStreamingResponse( - client.speech_to_speech - ) - self.organization = organization.AsyncOrganizationResourceWithStreamingResponse(client.organization) - self.uploads = uploads.AsyncUploadsResourceWithStreamingResponse(client.uploads) + self._client = client + + @cached_property + def tasks(self) -> tasks.AsyncTasksResourceWithStreamingResponse: + from .resources.tasks import AsyncTasksResourceWithStreamingResponse + + return AsyncTasksResourceWithStreamingResponse(self._client.tasks) + + @cached_property + def uploads(self) -> uploads.AsyncUploadsResourceWithStreamingResponse: + from .resources.uploads import AsyncUploadsResourceWithStreamingResponse + + return AsyncUploadsResourceWithStreamingResponse(self._client.uploads) + + @cached_property + def image_to_video(self) -> image_to_video.AsyncImageToVideoResourceWithStreamingResponse: + from .resources.image_to_video import AsyncImageToVideoResourceWithStreamingResponse + + return AsyncImageToVideoResourceWithStreamingResponse(self._client.image_to_video) + + @cached_property + def video_to_video(self) -> video_to_video.AsyncVideoToVideoResourceWithStreamingResponse: + from .resources.video_to_video import AsyncVideoToVideoResourceWithStreamingResponse + + return AsyncVideoToVideoResourceWithStreamingResponse(self._client.video_to_video) + + @cached_property + def text_to_video(self) -> text_to_video.AsyncTextToVideoResourceWithStreamingResponse: + from .resources.text_to_video import AsyncTextToVideoResourceWithStreamingResponse + + return AsyncTextToVideoResourceWithStreamingResponse(self._client.text_to_video) + + @cached_property + def text_to_image(self) -> text_to_image.AsyncTextToImageResourceWithStreamingResponse: + from .resources.text_to_image import AsyncTextToImageResourceWithStreamingResponse + + return AsyncTextToImageResourceWithStreamingResponse(self._client.text_to_image) + + @cached_property + def video_upscale(self) -> video_upscale.AsyncVideoUpscaleResourceWithStreamingResponse: + from .resources.video_upscale import AsyncVideoUpscaleResourceWithStreamingResponse + + return AsyncVideoUpscaleResourceWithStreamingResponse(self._client.video_upscale) + + @cached_property + def character_performance(self) -> character_performance.AsyncCharacterPerformanceResourceWithStreamingResponse: + from .resources.character_performance import AsyncCharacterPerformanceResourceWithStreamingResponse + + return AsyncCharacterPerformanceResourceWithStreamingResponse(self._client.character_performance) + + @cached_property + def text_to_speech(self) -> text_to_speech.AsyncTextToSpeechResourceWithStreamingResponse: + from .resources.text_to_speech import AsyncTextToSpeechResourceWithStreamingResponse + + return AsyncTextToSpeechResourceWithStreamingResponse(self._client.text_to_speech) + + @cached_property + def sound_effect(self) -> sound_effect.AsyncSoundEffectResourceWithStreamingResponse: + from .resources.sound_effect import AsyncSoundEffectResourceWithStreamingResponse + + return AsyncSoundEffectResourceWithStreamingResponse(self._client.sound_effect) + + @cached_property + def voice_isolation(self) -> voice_isolation.AsyncVoiceIsolationResourceWithStreamingResponse: + from .resources.voice_isolation import AsyncVoiceIsolationResourceWithStreamingResponse + + return AsyncVoiceIsolationResourceWithStreamingResponse(self._client.voice_isolation) + + @cached_property + def voice_dubbing(self) -> voice_dubbing.AsyncVoiceDubbingResourceWithStreamingResponse: + from .resources.voice_dubbing import AsyncVoiceDubbingResourceWithStreamingResponse + + return AsyncVoiceDubbingResourceWithStreamingResponse(self._client.voice_dubbing) + + @cached_property + def speech_to_speech(self) -> speech_to_speech.AsyncSpeechToSpeechResourceWithStreamingResponse: + from .resources.speech_to_speech import AsyncSpeechToSpeechResourceWithStreamingResponse + + return AsyncSpeechToSpeechResourceWithStreamingResponse(self._client.speech_to_speech) + + @cached_property + def organization(self) -> organization.AsyncOrganizationResourceWithStreamingResponse: + from .resources.organization import AsyncOrganizationResourceWithStreamingResponse + + return AsyncOrganizationResourceWithStreamingResponse(self._client.organization) Client = RunwayML diff --git a/src/runwayml/lib/polling.py b/src/runwayml/lib/polling.py index d91c537..b3b8750 100644 --- a/src/runwayml/lib/polling.py +++ b/src/runwayml/lib/polling.py @@ -129,34 +129,71 @@ def __init__(self, task_details: TaskRetrieveResponse): super().__init__(f"Task timed out") - class AwaitablePending(AwaitableTaskResponseMixin, Pending): ... + + class AwaitableThrottled(AwaitableTaskResponseMixin, Throttled): ... + + class AwaitableCancelled(AwaitableTaskResponseMixin, Cancelled): ... + + class AwaitableRunning(AwaitableTaskResponseMixin, Running): ... + + class AwaitableFailed(AwaitableTaskResponseMixin, Failed): ... + + class AwaitableSucceeded(AwaitableTaskResponseMixin, Succeeded): ... + AwaitableTaskRetrieveResponse: TypeAlias = Annotated[ - Union[AwaitablePending, AwaitableThrottled, AwaitableCancelled, AwaitableRunning, AwaitableFailed, AwaitableSucceeded], - PropertyInfo(discriminator="status") + Union[ + AwaitablePending, AwaitableThrottled, AwaitableCancelled, AwaitableRunning, AwaitableFailed, AwaitableSucceeded + ], + PropertyInfo(discriminator="status"), ] + class AsyncAwaitablePending(AsyncAwaitableTaskResponseMixin, Pending): ... + + class AsyncAwaitableThrottled(AsyncAwaitableTaskResponseMixin, Throttled): ... + + class AsyncAwaitableCancelled(AsyncAwaitableTaskResponseMixin, Cancelled): ... + + class AsyncAwaitableRunning(AsyncAwaitableTaskResponseMixin, Running): ... + + class AsyncAwaitableFailed(AsyncAwaitableTaskResponseMixin, Failed): ... + + class AsyncAwaitableSucceeded(AsyncAwaitableTaskResponseMixin, Succeeded): ... + AsyncAwaitableTaskRetrieveResponse: TypeAlias = Annotated[ - Union[AsyncAwaitablePending, AsyncAwaitableThrottled, AsyncAwaitableCancelled, AsyncAwaitableRunning, AsyncAwaitableFailed, AsyncAwaitableSucceeded], - PropertyInfo(discriminator="status") + Union[ + AsyncAwaitablePending, + AsyncAwaitableThrottled, + AsyncAwaitableCancelled, + AsyncAwaitableRunning, + AsyncAwaitableFailed, + AsyncAwaitableSucceeded, + ], + PropertyInfo(discriminator="status"), ] -def _make_sync_wait_for_task_output(client: "RunwayML") -> Callable[["AwaitableTaskResponseMixin", Union[float, None]], TaskRetrieveResponse]: + +def _make_sync_wait_for_task_output( + client: "RunwayML", +) -> Callable[["AwaitableTaskResponseMixin", Union[float, None]], TaskRetrieveResponse]: """Create a wait_for_task_output method bound to the given client.""" - def wait_for_task_output(self: "AwaitableTaskResponseMixin", timeout: Union[float, None] = 60 * 10) -> TaskRetrieveResponse: + + def wait_for_task_output( + self: "AwaitableTaskResponseMixin", timeout: Union[float, None] = 60 * 10 + ) -> TaskRetrieveResponse: start_time = time.time() while True: time.sleep(POLL_TIME + random.random() * POLL_JITTER - POLL_JITTER / 2) @@ -167,19 +204,26 @@ def wait_for_task_output(self: "AwaitableTaskResponseMixin", timeout: Union[floa raise TaskFailedError(task_details) if timeout is not None and time.time() - start_time > timeout: raise TaskTimeoutError(task_details) + return wait_for_task_output def inject_sync_wait_method(client: "RunwayML", response: T) -> T: """Inject the wait_for_task_output method onto the response instance.""" import types + response.wait_for_task_output = types.MethodType(_make_sync_wait_for_task_output(client), response) # type: ignore[attr-defined] return response -def _make_async_wait_for_task_output(client: "AsyncRunwayML") -> Callable[["AsyncAwaitableTaskResponseMixin", Union[float, None]], Coroutine[None, None, TaskRetrieveResponse]]: +def _make_async_wait_for_task_output( + client: "AsyncRunwayML", +) -> Callable[["AsyncAwaitableTaskResponseMixin", Union[float, None]], Coroutine[None, None, TaskRetrieveResponse]]: """Create an async wait_for_task_output method bound to the given client.""" - async def wait_for_task_output(self: "AsyncAwaitableTaskResponseMixin", timeout: Union[float, None] = 60 * 10) -> TaskRetrieveResponse: + + async def wait_for_task_output( + self: "AsyncAwaitableTaskResponseMixin", timeout: Union[float, None] = 60 * 10 + ) -> TaskRetrieveResponse: start_time = anyio.current_time() while True: await anyio.sleep(POLL_TIME + random.random() * POLL_JITTER - POLL_JITTER / 2) @@ -190,11 +234,13 @@ async def wait_for_task_output(self: "AsyncAwaitableTaskResponseMixin", timeout: raise TaskFailedError(task_details) if timeout is not None and anyio.current_time() - start_time > timeout: raise TaskTimeoutError(task_details) + return wait_for_task_output def inject_async_wait_method(client: "AsyncRunwayML", response: T) -> T: """Inject the async wait_for_task_output method onto the response instance.""" import types + response.wait_for_task_output = types.MethodType(_make_async_wait_for_task_output(client), response) # type: ignore[attr-defined] return response diff --git a/src/runwayml/resources/tasks.py b/src/runwayml/resources/tasks.py index 3803e98..f610695 100644 --- a/src/runwayml/resources/tasks.py +++ b/src/runwayml/resources/tasks.py @@ -78,7 +78,9 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=cast(Any, AwaitableTaskRetrieveResponse), # Union types cannot be passed in as arguments in the type system + cast_to=cast( + Any, AwaitableTaskRetrieveResponse + ), # Union types cannot be passed in as arguments in the type system ) return cast(AwaitableTaskRetrieveResponse, inject_sync_wait_method(self._client, response)) @@ -174,7 +176,9 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=cast(Any, AsyncAwaitableTaskRetrieveResponse), # Union types cannot be passed in as arguments in the type system + cast_to=cast( + Any, AsyncAwaitableTaskRetrieveResponse + ), # Union types cannot be passed in as arguments in the type system ) return cast(AsyncAwaitableTaskRetrieveResponse, inject_async_wait_method(self._client, response)) diff --git a/src/runwayml/resources/uploads.py b/src/runwayml/resources/uploads.py index 92b80c4..27c2898 100644 --- a/src/runwayml/resources/uploads.py +++ b/src/runwayml/resources/uploads.py @@ -202,13 +202,11 @@ def create_ephemeral( ), cast_to=_RunwayUploadStartedResponse, ) - - file_tuple = _build_file_tuple_with_filename( - filename, _transform_file(file) - ) + + file_tuple = _build_file_tuple_with_filename(filename, _transform_file(file)) form_data = _prepare_upload_data(upload_placeholder.fields, file_metadata) upload_timeout = _get_upload_timeout(timeout, self._client.timeout) - + with httpx.Client(timeout=upload_timeout) as client: response = client.post( upload_placeholder.uploadUrl, @@ -216,7 +214,7 @@ def create_ephemeral( files={"file": file_tuple}, ) _handle_upload_errors(response, upload_placeholder.uploadUrl) - + return UploadCreateEphemeralResponse(uri=upload_placeholder.runwayUri) @@ -286,13 +284,11 @@ async def create_ephemeral( ), cast_to=_RunwayUploadStartedResponse, ) - - file_tuple = _build_file_tuple_with_filename( - filename, await _async_transform_file(file) - ) + + file_tuple = _build_file_tuple_with_filename(filename, await _async_transform_file(file)) form_data = _prepare_upload_data(upload_placeholder.fields, file_metadata) upload_timeout = _get_upload_timeout(timeout, self._client.timeout) - + async with httpx.AsyncClient(timeout=upload_timeout) as client: response = await client.post( upload_placeholder.uploadUrl, @@ -300,7 +296,7 @@ async def create_ephemeral( files={"file": file_tuple}, ) _handle_upload_errors(response, upload_placeholder.uploadUrl) - + return UploadCreateEphemeralResponse(uri=upload_placeholder.runwayUri) diff --git a/src/runwayml/types/upload_create_ephemeral_response.py b/src/runwayml/types/upload_create_ephemeral_response.py index 2ef6067..edccb50 100644 --- a/src/runwayml/types/upload_create_ephemeral_response.py +++ b/src/runwayml/types/upload_create_ephemeral_response.py @@ -8,4 +8,3 @@ class UploadCreateEphemeralResponse(BaseModel): uri: str """The Runway upload URI to use in other API generation requests.""" - diff --git a/tests/api_resources/test_uploads.py b/tests/api_resources/test_uploads.py index 8916101..7086558 100644 --- a/tests/api_resources/test_uploads.py +++ b/tests/api_resources/test_uploads.py @@ -123,4 +123,3 @@ async def test_create_ephemeral_accepts_valid_files( finally: if file_type == "file_object": file.close() - From c6b880232812c60dce1b0b23678a70c7e158ae84 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 03:25:49 +0000 Subject: [PATCH 05/11] fix: use async_to_httpx_files in patch method --- src/runwayml/_base_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runwayml/_base_client.py b/src/runwayml/_base_client.py index cc0bf65..3902c8c 100644 --- a/src/runwayml/_base_client.py +++ b/src/runwayml/_base_client.py @@ -1774,7 +1774,7 @@ async def patch( options: RequestOptions = {}, ) -> ResponseT: opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) From 4e5931e064f4596d31f9c5c71abb24ad5f8a1727 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 03:24:55 +0000 Subject: [PATCH 06/11] chore(internal): add `--fix` argument to lint script --- scripts/lint | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/lint b/scripts/lint index 87c45f7..369e389 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,8 +4,13 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running lints with --fix" + rye run fix:ruff +else + echo "==> Running lints" + rye run lint +fi echo "==> Making sure it imports" rye run python -c 'import runwayml' From feac56919d1a635a1b3b577591d5a528c587c14e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 03:15:03 +0000 Subject: [PATCH 07/11] chore(internal): codegen related update --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7651810..b6e0cb5 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 RunwayML + Copyright 2026 RunwayML Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 384cd8bc9a23b0d9b7b37c737e8cb19d581f5198 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 04:11:36 +0000 Subject: [PATCH 08/11] feat(client): add support for binary request streaming --- src/runwayml/_base_client.py | 145 ++++++++++++++++++++++++--- src/runwayml/_models.py | 17 +++- src/runwayml/_types.py | 9 ++ tests/test_client.py | 187 ++++++++++++++++++++++++++++++++++- 4 files changed, 344 insertions(+), 14 deletions(-) diff --git a/src/runwayml/_base_client.py b/src/runwayml/_base_client.py index 3902c8c..e3a7174 100644 --- a/src/runwayml/_base_client.py +++ b/src/runwayml/_base_client.py @@ -9,6 +9,7 @@ import inspect import logging import platform +import warnings import email.utils from types import TracebackType from random import random @@ -51,9 +52,11 @@ ResponseT, AnyMapping, PostParser, + BinaryTypes, RequestFiles, HttpxSendArgs, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, not_given, @@ -477,8 +480,19 @@ def _build_request( retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): - log.debug("Request options: %s", model_dump(options, exclude_unset=True)) - + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) kwargs: dict[str, Any] = {} json_data = options.json_data @@ -532,7 +546,13 @@ def _build_request( is_body_allowed = options.method.lower() != "get" if is_body_allowed: - if isinstance(json_data, bytes): + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): kwargs["content"] = json_data else: kwargs["json"] = json_data if is_given(json_data) else None @@ -1194,6 +1214,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, @@ -1206,6 +1227,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1219,6 +1241,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1231,13 +1254,25 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) @@ -1247,11 +1282,23 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1261,11 +1308,23 @@ def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1275,9 +1334,19 @@ def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return self.request(cast_to, opts) def get_api_list( @@ -1717,6 +1786,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, @@ -1729,6 +1799,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], @@ -1742,6 +1813,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1754,13 +1826,25 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) @@ -1770,11 +1854,28 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, ) return await self.request(cast_to, opts) @@ -1784,11 +1885,23 @@ async def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) @@ -1798,9 +1911,19 @@ async def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return await self.request(cast_to, opts) def get_api_list( diff --git a/src/runwayml/_models.py b/src/runwayml/_models.py index ca9500b..29070e0 100644 --- a/src/runwayml/_models.py +++ b/src/runwayml/_models.py @@ -3,7 +3,20 @@ import os import inspect import weakref -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) from datetime import date, datetime from typing_extensions import ( List, @@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): timeout: float | Timeout | None files: HttpxRequestFiles | None idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] json_data: Body extra_json: AnyMapping follow_redirects: bool @@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel): post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() follow_redirects: Union[bool, None] = None + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. json_data: Union[Body, None] = None diff --git a/src/runwayml/_types.py b/src/runwayml/_types.py index 8facef4..bae9789 100644 --- a/src/runwayml/_types.py +++ b/src/runwayml/_types.py @@ -13,9 +13,11 @@ Mapping, TypeVar, Callable, + Iterable, Iterator, Optional, Sequence, + AsyncIterable, ) from typing_extensions import ( Set, @@ -56,6 +58,13 @@ else: Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + FileTypes = Union[ # file (or bytes) FileContent, diff --git a/tests/test_client.py b/tests/test_client.py index 68d28a2..3e4deaa 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -8,10 +8,11 @@ import json import asyncio import inspect +import dataclasses import tracemalloc -from typing import Any, Union, cast +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock -from typing_extensions import Literal +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -36,6 +37,7 @@ from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" @@ -50,6 +52,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 +def mirror_request_content(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, content=request.content) + + +# note: we can't use the httpx.MockTransport class as it consumes the request +# body itself, which means we can't test that the body is read lazily +class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): + def __init__( + self, + handler: Callable[[httpx.Request], httpx.Response] + | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], + ) -> None: + self.handler = handler + + @override + def handle_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" + assert inspect.isfunction(self.handler), "handler must be a function" + return self.handler(request) + + @override + async def handle_async_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" + return await self.handler(request) + + +@dataclasses.dataclass +class Counter: + value: int = 0 + + +def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + +async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + def _get_open_connections(client: RunwayML | AsyncRunwayML) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -502,6 +555,70 @@ def test_multipart_repeating_array(self, client: RunwayML) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload(self, respx_mock: MockRouter, client: RunwayML) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + def test_binary_content_upload_with_iterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_sync_iterator([file_content], counter=counter) + + def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=request.read()) + + with RunwayML( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: RunwayML) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) def test_basic_union_response(self, respx_mock: MockRouter, client: RunwayML) -> None: class Model1(BaseModel): @@ -1337,6 +1454,72 @@ def test_multipart_repeating_array(self, async_client: AsyncRunwayML) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncRunwayML) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = await async_client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + async def test_binary_content_upload_with_asynciterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_async_iterator([file_content], counter=counter) + + async def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=await request.aread()) + + async with AsyncRunwayML( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncRunwayML + ) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = await async_client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncRunwayML) -> None: class Model1(BaseModel): From a0b9bbe40fcde1c6286a19b77c8ac1c8425b2327 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:17:02 +0000 Subject: [PATCH 09/11] feat(api): Sunset upscale endpoint --- .stats.yml | 6 +- api.md | 12 -- src/runwayml/_client.py | 38 ---- src/runwayml/resources/__init__.py | 14 -- src/runwayml/resources/video_upscale.py | 193 ------------------ src/runwayml/types/__init__.py | 2 - .../organization_retrieve_usage_response.py | 2 - .../types/video_upscale_create_params.py | 16 -- .../types/video_upscale_create_response.py | 10 - tests/api_resources/test_video_upscale.py | 92 --------- 10 files changed, 3 insertions(+), 382 deletions(-) delete mode 100644 src/runwayml/resources/video_upscale.py delete mode 100644 src/runwayml/types/video_upscale_create_params.py delete mode 100644 src/runwayml/types/video_upscale_create_response.py delete mode 100644 tests/api_resources/test_video_upscale.py diff --git a/.stats.yml b/.stats.yml index cfa3db4..9ff44e1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml%2Frunwayml-f1f066255f00c8db87d86efaff73852278f21c2619f83dcf9f4e5cf54ef146b0.yml -openapi_spec_hash: af56570f2801ec4bfc94a05ca5393f9c +configured_endpoints: 14 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml%2Frunwayml-9cf1febc614000f72d4332f8bee51b6873b6bce49c9bdc02daae300e6882f9dd.yml +openapi_spec_hash: 03bc8d7e2d182e5a9aa9fdb7ca4f4e07 config_hash: b98eed33f1bba05e3c2a3e20d85d582a diff --git a/api.md b/api.md index 0f29848..72145fe 100644 --- a/api.md +++ b/api.md @@ -59,18 +59,6 @@ Methods: - client.text_to_image.create(\*\*params) -> TextToImageCreateResponse -# VideoUpscale - -Types: - -```python -from runwayml.types import VideoUpscaleCreateResponse -``` - -Methods: - -- client.video_upscale.create(\*\*params) -> VideoUpscaleCreateResponse - # CharacterPerformance Types: diff --git a/src/runwayml/_client.py b/src/runwayml/_client.py index 0172f3c..827ba30 100644 --- a/src/runwayml/_client.py +++ b/src/runwayml/_client.py @@ -38,7 +38,6 @@ sound_effect, text_to_image, text_to_video, - video_upscale, voice_dubbing, image_to_video, text_to_speech, @@ -53,7 +52,6 @@ from .resources.sound_effect import SoundEffectResource, AsyncSoundEffectResource from .resources.text_to_image import TextToImageResource, AsyncTextToImageResource from .resources.text_to_video import TextToVideoResource, AsyncTextToVideoResource - from .resources.video_upscale import VideoUpscaleResource, AsyncVideoUpscaleResource from .resources.voice_dubbing import VoiceDubbingResource, AsyncVoiceDubbingResource from .resources.image_to_video import ImageToVideoResource, AsyncImageToVideoResource from .resources.text_to_speech import TextToSpeechResource, AsyncTextToSpeechResource @@ -171,12 +169,6 @@ def text_to_image(self) -> TextToImageResource: return TextToImageResource(self) - @cached_property - def video_upscale(self) -> VideoUpscaleResource: - from .resources.video_upscale import VideoUpscaleResource - - return VideoUpscaleResource(self) - @cached_property def character_performance(self) -> CharacterPerformanceResource: from .resources.character_performance import CharacterPerformanceResource @@ -432,12 +424,6 @@ def text_to_image(self) -> AsyncTextToImageResource: return AsyncTextToImageResource(self) - @cached_property - def video_upscale(self) -> AsyncVideoUpscaleResource: - from .resources.video_upscale import AsyncVideoUpscaleResource - - return AsyncVideoUpscaleResource(self) - @cached_property def character_performance(self) -> AsyncCharacterPerformanceResource: from .resources.character_performance import AsyncCharacterPerformanceResource @@ -638,12 +624,6 @@ def text_to_image(self) -> text_to_image.TextToImageResourceWithRawResponse: return TextToImageResourceWithRawResponse(self._client.text_to_image) - @cached_property - def video_upscale(self) -> video_upscale.VideoUpscaleResourceWithRawResponse: - from .resources.video_upscale import VideoUpscaleResourceWithRawResponse - - return VideoUpscaleResourceWithRawResponse(self._client.video_upscale) - @cached_property def character_performance(self) -> character_performance.CharacterPerformanceResourceWithRawResponse: from .resources.character_performance import CharacterPerformanceResourceWithRawResponse @@ -729,12 +709,6 @@ def text_to_image(self) -> text_to_image.AsyncTextToImageResourceWithRawResponse return AsyncTextToImageResourceWithRawResponse(self._client.text_to_image) - @cached_property - def video_upscale(self) -> video_upscale.AsyncVideoUpscaleResourceWithRawResponse: - from .resources.video_upscale import AsyncVideoUpscaleResourceWithRawResponse - - return AsyncVideoUpscaleResourceWithRawResponse(self._client.video_upscale) - @cached_property def character_performance(self) -> character_performance.AsyncCharacterPerformanceResourceWithRawResponse: from .resources.character_performance import AsyncCharacterPerformanceResourceWithRawResponse @@ -820,12 +794,6 @@ def text_to_image(self) -> text_to_image.TextToImageResourceWithStreamingRespons return TextToImageResourceWithStreamingResponse(self._client.text_to_image) - @cached_property - def video_upscale(self) -> video_upscale.VideoUpscaleResourceWithStreamingResponse: - from .resources.video_upscale import VideoUpscaleResourceWithStreamingResponse - - return VideoUpscaleResourceWithStreamingResponse(self._client.video_upscale) - @cached_property def character_performance(self) -> character_performance.CharacterPerformanceResourceWithStreamingResponse: from .resources.character_performance import CharacterPerformanceResourceWithStreamingResponse @@ -911,12 +879,6 @@ def text_to_image(self) -> text_to_image.AsyncTextToImageResourceWithStreamingRe return AsyncTextToImageResourceWithStreamingResponse(self._client.text_to_image) - @cached_property - def video_upscale(self) -> video_upscale.AsyncVideoUpscaleResourceWithStreamingResponse: - from .resources.video_upscale import AsyncVideoUpscaleResourceWithStreamingResponse - - return AsyncVideoUpscaleResourceWithStreamingResponse(self._client.video_upscale) - @cached_property def character_performance(self) -> character_performance.AsyncCharacterPerformanceResourceWithStreamingResponse: from .resources.character_performance import AsyncCharacterPerformanceResourceWithStreamingResponse diff --git a/src/runwayml/resources/__init__.py b/src/runwayml/resources/__init__.py index b5aec52..1b6f367 100644 --- a/src/runwayml/resources/__init__.py +++ b/src/runwayml/resources/__init__.py @@ -48,14 +48,6 @@ TextToVideoResourceWithStreamingResponse, AsyncTextToVideoResourceWithStreamingResponse, ) -from .video_upscale import ( - VideoUpscaleResource, - AsyncVideoUpscaleResource, - VideoUpscaleResourceWithRawResponse, - AsyncVideoUpscaleResourceWithRawResponse, - VideoUpscaleResourceWithStreamingResponse, - AsyncVideoUpscaleResourceWithStreamingResponse, -) from .voice_dubbing import ( VoiceDubbingResource, AsyncVoiceDubbingResource, @@ -144,12 +136,6 @@ "AsyncTextToImageResourceWithRawResponse", "TextToImageResourceWithStreamingResponse", "AsyncTextToImageResourceWithStreamingResponse", - "VideoUpscaleResource", - "AsyncVideoUpscaleResource", - "VideoUpscaleResourceWithRawResponse", - "AsyncVideoUpscaleResourceWithRawResponse", - "VideoUpscaleResourceWithStreamingResponse", - "AsyncVideoUpscaleResourceWithStreamingResponse", "CharacterPerformanceResource", "AsyncCharacterPerformanceResource", "CharacterPerformanceResourceWithRawResponse", diff --git a/src/runwayml/resources/video_upscale.py b/src/runwayml/resources/video_upscale.py deleted file mode 100644 index 3c5b1ea..0000000 --- a/src/runwayml/resources/video_upscale.py +++ /dev/null @@ -1,193 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal - -import httpx - -from ..types import video_upscale_create_params -from .._types import Body, Query, Headers, NotGiven, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..lib.polling import ( - NewTaskCreatedResponse, - AsyncNewTaskCreatedResponse, - create_waitable_resource, - create_async_waitable_resource, -) -from .._base_client import make_request_options -from ..types.video_upscale_create_response import VideoUpscaleCreateResponse - -__all__ = ["VideoUpscaleResource", "AsyncVideoUpscaleResource"] - - -class VideoUpscaleResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> VideoUpscaleResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers - """ - return VideoUpscaleResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> VideoUpscaleResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response - """ - return VideoUpscaleResourceWithStreamingResponse(self) - - def create( - self, - *, - model: Literal["upscale_v1"], - video_uri: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> NewTaskCreatedResponse: - """This endpoint will start a new task to upscale a video. - - Videos will be upscaled - by a factor of 4X, capped at a maximum of 4096px along each side. - - Args: - video_uri: A HTTPS URL. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/v1/video_upscale", - body=maybe_transform( - { - "model": model, - "video_uri": video_uri, - }, - video_upscale_create_params.VideoUpscaleCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=create_waitable_resource(VideoUpscaleCreateResponse, self._client), - ) - - -class AsyncVideoUpscaleResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncVideoUpscaleResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/runwayml/sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncVideoUpscaleResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncVideoUpscaleResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/runwayml/sdk-python#with_streaming_response - """ - return AsyncVideoUpscaleResourceWithStreamingResponse(self) - - async def create( - self, - *, - model: Literal["upscale_v1"], - video_uri: str, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AsyncNewTaskCreatedResponse: - """This endpoint will start a new task to upscale a video. - - Videos will be upscaled - by a factor of 4X, capped at a maximum of 4096px along each side. - - Args: - video_uri: A HTTPS URL. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/v1/video_upscale", - body=await async_maybe_transform( - { - "model": model, - "video_uri": video_uri, - }, - video_upscale_create_params.VideoUpscaleCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=create_async_waitable_resource(VideoUpscaleCreateResponse, self._client), - ) - - -class VideoUpscaleResourceWithRawResponse: - def __init__(self, video_upscale: VideoUpscaleResource) -> None: - self._video_upscale = video_upscale - - self.create = to_raw_response_wrapper( - video_upscale.create, - ) - - -class AsyncVideoUpscaleResourceWithRawResponse: - def __init__(self, video_upscale: AsyncVideoUpscaleResource) -> None: - self._video_upscale = video_upscale - - self.create = async_to_raw_response_wrapper( - video_upscale.create, - ) - - -class VideoUpscaleResourceWithStreamingResponse: - def __init__(self, video_upscale: VideoUpscaleResource) -> None: - self._video_upscale = video_upscale - - self.create = to_streamed_response_wrapper( - video_upscale.create, - ) - - -class AsyncVideoUpscaleResourceWithStreamingResponse: - def __init__(self, video_upscale: AsyncVideoUpscaleResource) -> None: - self._video_upscale = video_upscale - - self.create = async_to_streamed_response_wrapper( - video_upscale.create, - ) diff --git a/src/runwayml/types/__init__.py b/src/runwayml/types/__init__.py index 4accfa0..7dd42d4 100644 --- a/src/runwayml/types/__init__.py +++ b/src/runwayml/types/__init__.py @@ -6,7 +6,6 @@ from .sound_effect_create_params import SoundEffectCreateParams as SoundEffectCreateParams from .text_to_image_create_params import TextToImageCreateParams as TextToImageCreateParams from .text_to_video_create_params import TextToVideoCreateParams as TextToVideoCreateParams -from .video_upscale_create_params import VideoUpscaleCreateParams as VideoUpscaleCreateParams from .voice_dubbing_create_params import VoiceDubbingCreateParams as VoiceDubbingCreateParams from .image_to_video_create_params import ImageToVideoCreateParams as ImageToVideoCreateParams from .sound_effect_create_response import SoundEffectCreateResponse as SoundEffectCreateResponse @@ -14,7 +13,6 @@ from .video_to_video_create_params import VideoToVideoCreateParams as VideoToVideoCreateParams from .text_to_image_create_response import TextToImageCreateResponse as TextToImageCreateResponse from .text_to_video_create_response import TextToVideoCreateResponse as TextToVideoCreateResponse -from .video_upscale_create_response import VideoUpscaleCreateResponse as VideoUpscaleCreateResponse from .voice_dubbing_create_response import VoiceDubbingCreateResponse as VoiceDubbingCreateResponse from .voice_isolation_create_params import VoiceIsolationCreateParams as VoiceIsolationCreateParams from .image_to_video_create_response import ImageToVideoCreateResponse as ImageToVideoCreateResponse diff --git a/src/runwayml/types/organization_retrieve_usage_response.py b/src/runwayml/types/organization_retrieve_usage_response.py index bce820c..0e83260 100644 --- a/src/runwayml/types/organization_retrieve_usage_response.py +++ b/src/runwayml/types/organization_retrieve_usage_response.py @@ -20,7 +20,6 @@ class ResultUsedCredit(BaseModel): "gen4_turbo", "gen4_image", "gen4_image_turbo", - "upscale_v1", "act_two", "gen4_aleph", "veo3", @@ -54,7 +53,6 @@ class OrganizationRetrieveUsageResponse(BaseModel): "gen4_turbo", "gen4_image", "gen4_image_turbo", - "upscale_v1", "act_two", "gen4_aleph", "veo3", diff --git a/src/runwayml/types/video_upscale_create_params.py b/src/runwayml/types/video_upscale_create_params.py deleted file mode 100644 index eee9e15..0000000 --- a/src/runwayml/types/video_upscale_create_params.py +++ /dev/null @@ -1,16 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["VideoUpscaleCreateParams"] - - -class VideoUpscaleCreateParams(TypedDict, total=False): - model: Required[Literal["upscale_v1"]] - - video_uri: Required[Annotated[str, PropertyInfo(alias="videoUri")]] - """A HTTPS URL.""" diff --git a/src/runwayml/types/video_upscale_create_response.py b/src/runwayml/types/video_upscale_create_response.py deleted file mode 100644 index 0cf0a9e..0000000 --- a/src/runwayml/types/video_upscale_create_response.py +++ /dev/null @@ -1,10 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["VideoUpscaleCreateResponse"] - - -class VideoUpscaleCreateResponse(BaseModel): - id: str - """The ID of the task that was created. Use this to retrieve the task later.""" diff --git a/tests/api_resources/test_video_upscale.py b/tests/api_resources/test_video_upscale.py deleted file mode 100644 index 9b2f1a8..0000000 --- a/tests/api_resources/test_video_upscale.py +++ /dev/null @@ -1,92 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from runwayml import RunwayML, AsyncRunwayML -from tests.utils import assert_matches_type -from runwayml.types import VideoUpscaleCreateResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestVideoUpscale: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_create(self, client: RunwayML) -> None: - video_upscale = client.video_upscale.create( - model="upscale_v1", - video_uri="https://example.com/video.mp4", - ) - assert_matches_type(VideoUpscaleCreateResponse, video_upscale, path=["response"]) - - @parametrize - def test_raw_response_create(self, client: RunwayML) -> None: - response = client.video_upscale.with_raw_response.create( - model="upscale_v1", - video_uri="https://example.com/video.mp4", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - video_upscale = response.parse() - assert_matches_type(VideoUpscaleCreateResponse, video_upscale, path=["response"]) - - @parametrize - def test_streaming_response_create(self, client: RunwayML) -> None: - with client.video_upscale.with_streaming_response.create( - model="upscale_v1", - video_uri="https://example.com/video.mp4", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - video_upscale = response.parse() - assert_matches_type(VideoUpscaleCreateResponse, video_upscale, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncVideoUpscale: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @parametrize - async def test_method_create(self, async_client: AsyncRunwayML) -> None: - video_upscale = await async_client.video_upscale.create( - model="upscale_v1", - video_uri="https://example.com/video.mp4", - ) - assert_matches_type(VideoUpscaleCreateResponse, video_upscale, path=["response"]) - - @parametrize - async def test_raw_response_create(self, async_client: AsyncRunwayML) -> None: - response = await async_client.video_upscale.with_raw_response.create( - model="upscale_v1", - video_uri="https://example.com/video.mp4", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - video_upscale = await response.parse() - assert_matches_type(VideoUpscaleCreateResponse, video_upscale, path=["response"]) - - @parametrize - async def test_streaming_response_create(self, async_client: AsyncRunwayML) -> None: - async with async_client.video_upscale.with_streaming_response.create( - model="upscale_v1", - video_uri="https://example.com/video.mp4", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - video_upscale = await response.parse() - assert_matches_type(VideoUpscaleCreateResponse, video_upscale, path=["response"]) - - assert cast(Any, response.is_closed) is True From 501bd0aee752add4e2379b0bb444ca73f4f56e81 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:15:35 +0000 Subject: [PATCH 10/11] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9ff44e1..31fdcde 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml%2Frunwayml-9cf1febc614000f72d4332f8bee51b6873b6bce49c9bdc02daae300e6882f9dd.yml openapi_spec_hash: 03bc8d7e2d182e5a9aa9fdb7ca4f4e07 -config_hash: b98eed33f1bba05e3c2a3e20d85d582a +config_hash: 33cae5198e16130c1534533c5200987c From 3a9d400e7284d73ef5564b35908f1688c55dfc62 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:17:43 +0000 Subject: [PATCH 11/11] release: 4.3.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 24 ++++++++++++++++++++++++ pyproject.toml | 2 +- src/runwayml/_version.py | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bd7f384..29102ae 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.2.0" + ".": "4.3.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 02392b6..75e6626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 4.3.0 (2026-01-17) + +Full Changelog: [v4.2.0...v4.3.0](https://github.com/runwayml/sdk-python/compare/v4.2.0...v4.3.0) + +### Features + +* **api:** Sunset upscale endpoint ([a0b9bbe](https://github.com/runwayml/sdk-python/commit/a0b9bbe40fcde1c6286a19b77c8ac1c8425b2327)) +* **client:** add support for binary request streaming ([384cd8b](https://github.com/runwayml/sdk-python/commit/384cd8bc9a23b0d9b7b37c737e8cb19d581f5198)) + + +### Bug Fixes + +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([9d4f77c](https://github.com/runwayml/sdk-python/commit/9d4f77c847837f20ef333a10e5a0d1d9e6a25cf5)) +* use async_to_httpx_files in patch method ([c6b8802](https://github.com/runwayml/sdk-python/commit/c6b880232812c60dce1b0b23678a70c7e158ae84)) + + +### Chores + +* add missing docstrings ([4c14222](https://github.com/runwayml/sdk-python/commit/4c142226410821c794e9e3bf43c7bf9a7d5bf6c1)) +* **internal:** add `--fix` argument to lint script ([4e5931e](https://github.com/runwayml/sdk-python/commit/4e5931e064f4596d31f9c5c71abb24ad5f8a1727)) +* **internal:** add missing files argument to base client ([1c5f3af](https://github.com/runwayml/sdk-python/commit/1c5f3af332fdd873865234b11c0b1b24aa9df688)) +* **internal:** codegen related update ([feac569](https://github.com/runwayml/sdk-python/commit/feac56919d1a635a1b3b577591d5a528c587c14e)) +* speedup initial import ([cfcc2e9](https://github.com/runwayml/sdk-python/commit/cfcc2e96de68498e8b1cbca4b02567283d8af207)) + ## 4.2.0 (2025-12-04) Full Changelog: [v4.1.1...v4.2.0](https://github.com/runwayml/sdk-python/compare/v4.1.1...v4.2.0) diff --git a/pyproject.toml b/pyproject.toml index 668ea2e..1321ba8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runwayml" -version = "4.2.0" +version = "4.3.0" description = "The official Python library for the runwayml API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/runwayml/_version.py b/src/runwayml/_version.py index 9e0ccac..15fc2a6 100644 --- a/src/runwayml/_version.py +++ b/src/runwayml/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "runwayml" -__version__ = "4.2.0" # x-release-please-version +__version__ = "4.3.0" # x-release-please-version