From b8263ec4ff8bd52262e9aadd51c1018d6247d8bc Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Fri, 13 Mar 2026 14:04:51 -0400 Subject: [PATCH 1/5] Fixed optional field setup --- runware/base.py | 75 ++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/runware/base.py b/runware/base.py index ef819d6..29babe3 100644 --- a/runware/base.py +++ b/runware/base.py @@ -4,7 +4,7 @@ import os import re from asyncio import gather -from dataclasses import asdict +from dataclasses import asdict, is_dataclass, fields from random import uniform from typing import List, Optional, Union, Callable, Any, Dict, Tuple @@ -2063,6 +2063,8 @@ async def _requestVideo(self, requestVideo: "IVideoInference") -> "Union[List[IV await self._processVideoImages(requestVideo) requestVideo.taskUUID = requestVideo.taskUUID or getUUID() request_object = self._buildVideoRequest(requestVideo) + import json + print(f"Request Object: {json.dumps(request_object, indent=4)}") if requestVideo.webhookURL: request_object["webhookURL"] = requestVideo.webhookURL @@ -2132,8 +2134,9 @@ def _buildVideoRequest(self, requestVideo: IVideoInference) -> Dict[str, Any]: if requestVideo.positivePrompt is not None: request_object["positivePrompt"] = requestVideo.positivePrompt.strip() + self._addOptionalBuiltInDataTypesFields(request_object, requestVideo) + self._addOptionalField(request_object, requestVideo.speech) - self._addOptionalVideoFields(request_object, requestVideo) self._addVideoImages(request_object, requestVideo) self._addOptionalField(request_object, requestVideo.inputs) self._addProviderSettings(request_object, requestVideo) @@ -2144,18 +2147,6 @@ def _buildVideoRequest(self, requestVideo: IVideoInference) -> Dict[str, Any]: return request_object - def _addOptionalVideoFields(self, request_object: Dict[str, Any], requestVideo: IVideoInference) -> None: - optional_fields = [ - "outputType", "outputFormat", "outputQuality", "uploadEndpoint", - "includeCost", "negativePrompt", "inputAudios", "referenceVideos", "fps", "steps", "scheduler", "seed", - "CFGScale", "seedImage", "duration", "width", "height", "nsfw_check", "resolution", - ] - - for field in optional_fields: - value = getattr(requestVideo, field, None) - if value is not None: - request_object[field] = value - def _addVideoImages(self, request_object: Dict[str, Any], requestVideo: IVideoInference) -> None: if requestVideo.frameImages: frame_images_data = [] @@ -2367,7 +2358,7 @@ def _buildImageRequest(self, requestImage: IImageInference, prompt: Optional[str if prompt: request_object["positivePrompt"] = prompt - self._addOptionalImageFields(request_object, requestImage) + self._addOptionalBuiltInDataTypesFields(request_object, requestImage) self._addImageSpecialFields(request_object, requestImage, control_net_data_dicts, instant_id_data, ip_adapters_data, ace_plus_plus_data, pulid_data) self._addOptionalField(request_object, requestImage.inputs) self._addImageProviderSettings(request_object, requestImage) @@ -2378,24 +2369,6 @@ def _buildImageRequest(self, requestImage: IImageInference, prompt: Optional[str return request_object - def _addOptionalImageFields(self, request_object: Dict[str, Any], requestImage: IImageInference) -> None: - optional_fields = [ - "outputType", "outputFormat", "outputQuality", "uploadEndpoint", - "includeCost", "checkNsfw", "negativePrompt", "seedImage", "maskImage", - "strength", "height", "width", "steps", "scheduler", "seed", "CFGScale", - "clipSkip", "promptWeighting", "maskMargin", "vae", "webhookURL", "acceleration", - "useCache", "ttl", "resolution" - ] - - for field in optional_fields: - value = getattr(requestImage, field, None) - if value is not None: - # Special handling for checkNsfw -> checkNSFW - if field == "checkNsfw": - request_object["checkNSFW"] = value - else: - request_object[field] = value - def _addImageSpecialFields(self, request_object: Dict[str, Any], requestImage: IImageInference, control_net_data_dicts: List[Dict], instant_id_data: Optional[Dict], ip_adapters_data: Optional[List[Dict]], ace_plus_plus_data: Optional[Dict], pulid_data: Optional[Dict]) -> None: # Add controlNet if present if control_net_data_dicts: @@ -2476,6 +2449,29 @@ def _addImageSpecialFields(self, request_object: Dict[str, Any], requestImage: I if hasattr(requestImage, "extraArgs") and isinstance(requestImage.extraArgs, dict): request_object.update(requestImage.extraArgs) + def _addOptionalBuiltInDataTypesFields(self, request_object: Dict[str, Any], obj: Any) -> None: + if not is_dataclass(obj): + return + + cls = obj.__class__ + for field in fields(cls): + name = field.name + value = getattr(obj, name, None) + + if ( + name in request_object + or value is None + or is_dataclass(value) + or ( + isinstance(value, (list, tuple)) + and value + and any(is_dataclass(v) for v in value) + ) + ): + continue + + request_object[name] = value + def _addSafetySettings(self, request_object: Dict[str, Any], safety: ISafety) -> None: safety_dict = asdict(safety) safety_dict = {k: v for k, v in safety_dict.items() if v is not None} @@ -2861,7 +2857,7 @@ def _buildAudioRequest(self, requestAudio: IAudioInference) -> Dict[str, Any]: if requestAudio.duration is not None: request_object["duration"] = requestAudio.duration - self._addOptionalAudioFields(request_object, requestAudio) + self._addOptionalBuiltInDataTypesFields(request_object, requestAudio) self._addOptionalField(request_object, requestAudio.speech) self._addOptionalField(request_object, requestAudio.audioSettings) self._addOptionalField(request_object, requestAudio.settings) @@ -2871,17 +2867,6 @@ def _buildAudioRequest(self, requestAudio: IAudioInference) -> Dict[str, Any]: return request_object - def _addOptionalAudioFields(self, request_object: Dict[str, Any], requestAudio: IAudioInference) -> None: - optional_fields = [ - "outputType", "outputFormat", "includeCost", "uploadEndpoint", "webhookURL", - "negativePrompt", "steps", "seed", "CFGScale", "strength" - ] - - for field in optional_fields: - value = getattr(requestAudio, field, None) - if value is not None: - request_object[field] = value - def _addAudioProviderSettings(self, request_object: Dict[str, Any], requestAudio: IAudioInference) -> None: if not requestAudio.providerSettings: From 493a103bd7adb6c11114991bf42956c9e569b811 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Fri, 13 Mar 2026 15:52:54 -0400 Subject: [PATCH 2/5] Removed skipResponse usage, updated _addOptionalBuiltInDataTypesFields to avoid callable too --- README.md | 40 +--------------------------------------- runware/base.py | 11 ++--------- runware/types.py | 5 ++--- 3 files changed, 5 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index fa5faf3..76f9033 100644 --- a/README.md +++ b/README.md @@ -201,44 +201,6 @@ Your webhook endpoint will receive a POST request with the same format as synchr } ``` -### Video Inference with Skip Response - -For long-running video generation tasks, you can use `skipResponse` to submit the task and retrieve results later. This is useful for handling system interruptions, batch processing, or building queue-based systems. -```python -from runware import Runware, IVideoInference - -async def main() -> None: - runware = Runware(api_key=RUNWARE_API_KEY) - await runware.connect() - - # Submit video task without waiting - request = IVideoInference( - model="openai:3@2", - positivePrompt="A beautiful sunset over the ocean", - duration=4, - width=1280, - height=720, - skipResponse=True, - ) - - response = await runware.videoInference(requestVideo=request) - task_uuid = response.taskUUID - print(f"Task submitted: {task_uuid}") - - # Later, retrieve results - videos = await runware.getResponse( - taskUUID=task_uuid, - numberResults=1 - ) - - for video in videos: - print(f"Video URL: {video.videoURL}") -``` - -**Parameters:** -- `skipResponse`: Set to `True` to return immediately with `taskUUID` instead of waiting for completion -- Use `getResponse(taskUUID)` to retrieve results at any time - ### Video Inference with Async Delivery Method For long-running video generation tasks, you can use `deliveryMethod="async"` to submit the task and retrieve results later. This is useful for handling system interruptions, batch processing, or building queue-based systems. @@ -1129,4 +1091,4 @@ async def main(): # Your code here ``` -**Note:** For long-running video operations, consider using webhooks or `skipResponse=True` to avoid timeout issues with extended generation times. \ No newline at end of file +**Note:** For long-running video operations, consider using webhooks or `deliveryMethod=\"async\"` to avoid timeout issues with extended generation times. \ No newline at end of file diff --git a/runware/base.py b/runware/base.py index 29babe3..7494cbf 100644 --- a/runware/base.py +++ b/runware/base.py @@ -2063,19 +2063,10 @@ async def _requestVideo(self, requestVideo: "IVideoInference") -> "Union[List[IV await self._processVideoImages(requestVideo) requestVideo.taskUUID = requestVideo.taskUUID or getUUID() request_object = self._buildVideoRequest(requestVideo) - import json - print(f"Request Object: {json.dumps(request_object, indent=4)}") if requestVideo.webhookURL: request_object["webhookURL"] = requestVideo.webhookURL - if requestVideo.skipResponse: - await self.send([request_object]) - return IAsyncTaskResponse( - taskType=ETaskType.VIDEO_INFERENCE.value, - taskUUID=requestVideo.taskUUID - ) - return await self._handleInitialVideoResponse( request_object=request_object, task_uuid=requestVideo.taskUUID, @@ -2454,6 +2445,7 @@ def _addOptionalBuiltInDataTypesFields(self, request_object: Dict[str, Any], obj return cls = obj.__class__ + for field in fields(cls): name = field.name value = getattr(obj, name, None) @@ -2461,6 +2453,7 @@ def _addOptionalBuiltInDataTypesFields(self, request_object: Dict[str, Any], obj if ( name in request_object or value is None + or callable(value) or is_dataclass(value) or ( isinstance(value, (list, tuple)) diff --git a/runware/types.py b/runware/types.py index 7d32aa8..a192fe4 100644 --- a/runware/types.py +++ b/runware/types.py @@ -1485,19 +1485,18 @@ class IVideoInference: advancedFeatures: Optional[IVideoAdvancedFeatures] = None acceleratorOptions: Optional[IAcceleratorOptions] = None inputs: Optional[Union[IVideoInputs, Dict[str, Any]]] = None - skipResponse: Optional[bool] = False resolution: Optional[str] = None settings: Optional[Union[ISettings, Dict[str, Any]]] = None def __post_init__(self): if self.settings is not None and isinstance(self.settings, dict): self.settings = ISettings(**self.settings) - - def __post_init__(self): if self.safety is not None and isinstance(self.safety, dict): self.safety = ISafety(**self.safety) if self.inputs is not None and isinstance(self.inputs, dict): self.inputs = IVideoInputs(**self.inputs) + if hasattr(self, "skipResponse"): + raise ValueError("skipResponse has been removed; use deliveryMethod='async' instead") I3dOutputFormat = Literal["GLB", "PLY"] From 59133e6ad3eab9be98316f2b10621a3bd73d0edc Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Fri, 13 Mar 2026 16:25:17 -0400 Subject: [PATCH 3/5] Removed debugging lines --- runware/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runware/base.py b/runware/base.py index 7494cbf..2dfd8b1 100644 --- a/runware/base.py +++ b/runware/base.py @@ -772,6 +772,7 @@ async def _imageInference( task_uuid = requestImage.taskUUID number_results = requestImage.numberResults or 1 + if delivery_method_enum is EDeliveryMethod.ASYNC: if requestImage.webhookURL: request_object["webhookURL"] = requestImage.webhookURL @@ -808,6 +809,7 @@ async def _imageInference( finally: await self._unregister_pending_operation(task_uuid) + future, should_send = await self._register_pending_operation( task_uuid, expected_results=number_results, @@ -2453,6 +2455,7 @@ def _addOptionalBuiltInDataTypesFields(self, request_object: Dict[str, Any], obj if ( name in request_object or value is None + or (isinstance(value, (list, tuple, dict)) and not value) or callable(value) or is_dataclass(value) or ( @@ -2824,6 +2827,7 @@ async def _requestAudio(self, requestAudio: "IAudioInference") -> Union[List["IA requestAudio.taskUUID = requestAudio.taskUUID or getUUID() request_object = self._buildAudioRequest(requestAudio) + return await self._handleInitialAudioResponse( request_object=request_object, task_uuid=requestAudio.taskUUID, From ee88254d9bb707c88a76bb82dcfcdf643fe781c5 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Fri, 13 Mar 2026 20:04:39 -0400 Subject: [PATCH 4/5] Copilot suggestions fixed --- README.md | 2 +- runware/base.py | 4 ++-- runware/types.py | 15 +++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 76f9033..5008ca0 100644 --- a/README.md +++ b/README.md @@ -1091,4 +1091,4 @@ async def main(): # Your code here ``` -**Note:** For long-running video operations, consider using webhooks or `deliveryMethod=\"async\"` to avoid timeout issues with extended generation times. \ No newline at end of file +**Note:** For long-running video operations, consider using webhooks or `deliveryMethod="async"` to avoid timeout issues with extended generation times. \ No newline at end of file diff --git a/runware/base.py b/runware/base.py index 2dfd8b1..65bd21e 100644 --- a/runware/base.py +++ b/runware/base.py @@ -2454,6 +2454,7 @@ def _addOptionalBuiltInDataTypesFields(self, request_object: Dict[str, Any], obj if ( name in request_object + or name == "extraArgs" or value is None or (isinstance(value, (list, tuple, dict)) and not value) or callable(value) @@ -2860,8 +2861,7 @@ def _buildAudioRequest(self, requestAudio: IAudioInference) -> Dict[str, Any]: self._addOptionalField(request_object, requestAudio.settings) self._addAudioProviderSettings(request_object, requestAudio) self._addOptionalField(request_object, requestAudio.inputs) - self._addOptionalField(request_object, requestAudio.settings) - + return request_object diff --git a/runware/types.py b/runware/types.py index a192fe4..d81e54c 100644 --- a/runware/types.py +++ b/runware/types.py @@ -1,6 +1,6 @@ from abc import abstractmethod, ABC from enum import Enum -from dataclasses import dataclass, field, asdict +from dataclasses import dataclass, field, asdict, InitVar from typing import List, Union, Optional, Callable, Any, Dict, TypeVar, Literal import warnings @@ -916,7 +916,7 @@ class IImageInference: outputType: Optional[IOutputType] = None outputFormat: Optional[IOutputFormat] = None uploadEndpoint: Optional[str] = None - checkNsfw: Optional[bool] = None + checkNsfw: InitVar[Optional[bool]] = None negativePrompt: Optional[str] = None seedImage: Optional[Union[File, str]] = None maskImage: Optional[Union[File, str]] = None @@ -960,7 +960,9 @@ class IImageInference: webhookURL: Optional[str] = None ttl: Optional[int] = None # time-to-live (TTL) in seconds, only applies when outputType is "URL" - def __post_init__(self): + def __post_init__(self, checkNsfw: Optional[bool] = None): + if checkNsfw is not None: + raise ValueError("checkNsfw has been deprecated; use safety.checkContent instead") if self.safety is not None and isinstance(self.safety, dict): self.safety = ISafety(**self.safety) if self.settings is not None and isinstance(self.settings, dict): @@ -1487,16 +1489,17 @@ class IVideoInference: inputs: Optional[Union[IVideoInputs, Dict[str, Any]]] = None resolution: Optional[str] = None settings: Optional[Union[ISettings, Dict[str, Any]]] = None + skipResponse: InitVar[Optional[bool]] = None - def __post_init__(self): + def __post_init__(self, skipResponse: Optional[bool] = None) -> None: + if skipResponse is not None: + raise ValueError("skipResponse has been deprecated; use deliveryMethod='async' instead") if self.settings is not None and isinstance(self.settings, dict): self.settings = ISettings(**self.settings) if self.safety is not None and isinstance(self.safety, dict): self.safety = ISafety(**self.safety) if self.inputs is not None and isinstance(self.inputs, dict): self.inputs = IVideoInputs(**self.inputs) - if hasattr(self, "skipResponse"): - raise ValueError("skipResponse has been removed; use deliveryMethod='async' instead") I3dOutputFormat = Literal["GLB", "PLY"] From 05d25a988d5311f4b6b3b00ed21a2e1335324986 Mon Sep 17 00:00:00 2001 From: Sirshendu Ganguly Date: Fri, 13 Mar 2026 20:21:52 -0400 Subject: [PATCH 5/5] Forwarding deprecated params --- runware/base.py | 20 ++++++++++++++++++-- runware/types.py | 20 ++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/runware/base.py b/runware/base.py index 65bd21e..db4ee47 100644 --- a/runware/base.py +++ b/runware/base.py @@ -5,6 +5,7 @@ import re from asyncio import gather from dataclasses import asdict, is_dataclass, fields +from enum import Enum from random import uniform from typing import List, Optional, Union, Callable, Any, Dict, Tuple @@ -809,7 +810,6 @@ async def _imageInference( finally: await self._unregister_pending_operation(task_uuid) - future, should_send = await self._register_pending_operation( task_uuid, expected_results=number_results, @@ -2442,6 +2442,22 @@ def _addImageSpecialFields(self, request_object: Dict[str, Any], requestImage: I if hasattr(requestImage, "extraArgs") and isinstance(requestImage.extraArgs, dict): request_object.update(requestImage.extraArgs) + def _convert_enums(self, val: Any) -> Any: + if is_dataclass(val): + return val + if isinstance(val, Enum): + return val.value + if isinstance(val, list): + return [self._convert_enums(v) for v in val] + if isinstance(val, tuple): + return tuple(self._convert_enums(v) for v in val) + if isinstance(val, dict): + return { + self._convert_enums(k) if isinstance(k, Enum) else k: self._convert_enums(v) + for k, v in val.items() + } + return val + def _addOptionalBuiltInDataTypesFields(self, request_object: Dict[str, Any], obj: Any) -> None: if not is_dataclass(obj): return @@ -2467,7 +2483,7 @@ def _addOptionalBuiltInDataTypesFields(self, request_object: Dict[str, Any], obj ): continue - request_object[name] = value + request_object[name] = self._convert_enums(value) def _addSafetySettings(self, request_object: Dict[str, Any], safety: ISafety) -> None: safety_dict = asdict(safety) diff --git a/runware/types.py b/runware/types.py index d81e54c..34c565b 100644 --- a/runware/types.py +++ b/runware/types.py @@ -962,7 +962,17 @@ class IImageInference: def __post_init__(self, checkNsfw: Optional[bool] = None): if checkNsfw is not None: - raise ValueError("checkNsfw has been deprecated; use safety.checkContent instead") + warnings.warn( + "checkNsfw has been deprecated and will be removed in a future version; please use safety.checkContent instead.", + DeprecationWarning, + stacklevel=2, + ) + if checkNsfw: + if isinstance(self.safety, dict): + self.safety.setdefault("checkContent", True) + elif self.safety is not None and hasattr(self.safety, "checkContent"): + if getattr(self.safety, "checkContent") is None: + self.safety.checkContent = True if self.safety is not None and isinstance(self.safety, dict): self.safety = ISafety(**self.safety) if self.settings is not None and isinstance(self.settings, dict): @@ -1493,7 +1503,13 @@ class IVideoInference: def __post_init__(self, skipResponse: Optional[bool] = None) -> None: if skipResponse is not None: - raise ValueError("skipResponse has been deprecated; use deliveryMethod='async' instead") + warnings.warn( + "skipResponse has been deprecated; use deliveryMethod='async' instead", + DeprecationWarning, + stacklevel=2, + ) + if skipResponse and getattr(self, "deliveryMethod", None) is None: + self.deliveryMethod = "async" if self.settings is not None and isinstance(self.settings, dict): self.settings = ISettings(**self.settings) if self.safety is not None and isinstance(self.safety, dict):