Skip to content

Commit 3aede27

Browse files
committed
Merge main into refactor-optionalSettings
2 parents 4118c71 + f0d9a98 commit 3aede27

3 files changed

Lines changed: 127 additions & 40 deletions

File tree

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,3 +1031,52 @@ asyncio.run(main())
10311031
```
10321032

10331033
For more detailed usage and additional examples, please refer to the examples directory.
1034+
1035+
## Configuring Timeouts
1036+
1037+
The Runware SDK provides configurable timeout settings for different operations through environment variables. All timeout values are in milliseconds.
1038+
1039+
### Timeout Configuration
1040+
1041+
Set environment variables to customize timeout behavior:
1042+
1043+
```bash
1044+
# Image Operations (milliseconds)
1045+
RUNWARE_IMAGE_INFERENCE_TIMEOUT=300000 # Image generation (default: 5 min)
1046+
RUNWARE_IMAGE_OPERATION_TIMEOUT=120000 # Caption, upscale, background removal (default: 2 min)
1047+
RUNWARE_IMAGE_UPLOAD_TIMEOUT=60000 # Image upload (default: 1 min)
1048+
1049+
# Video Operations (milliseconds)
1050+
RUNWARE_VIDEO_INITIAL_TIMEOUT=30000 # Initial response wait (default: 30 sec)
1051+
RUNWARE_VIDEO_POLLING_DELAY=3000 # Delay between status checks (default: 3 sec)
1052+
RUNWARE_MAX_POLLS_VIDEO_GENERATION=480 # Max polling attempts (default: 480, ~24 min total)
1053+
1054+
# Audio Operations (milliseconds)
1055+
RUNWARE_AUDIO_INFERENCE_TIMEOUT=300000 # Audio generation (default: 5 min)
1056+
RUNWARE_AUDIO_POLLING_DELAY=1000 # Delay between status checks (default: 1 sec)
1057+
RUNWARE_MAX_POLLS_AUDIO_GENERATION=240 # Max polling attempts (default: 240, ~4 min total)
1058+
1059+
# Other Operations (milliseconds)
1060+
RUNWARE_PROMPT_ENHANCE_TIMEOUT=60000 # Prompt enhancement (default: 1 min)
1061+
RUNWARE_WEBHOOK_TIMEOUT=30000 # Webhook acknowledgment (default: 30 sec)
1062+
RUNWARE_TIMEOUT_DURATION=480000 # General operations (default: 8 min)
1063+
```
1064+
1065+
### Usage Example
1066+
1067+
```python
1068+
import os
1069+
1070+
# Configure before importing Runware
1071+
os.environ["RUNWARE_VIDEO_POLLING_DELAY"] = "5000" # 5 seconds between checks
1072+
os.environ["RUNWARE_MAX_POLLS_VIDEO_GENERATION"] = "600" # Allow up to 50 minutes
1073+
1074+
from runware import Runware
1075+
1076+
async def main():
1077+
runware = Runware(api_key=os.getenv("RUNWARE_API_KEY"))
1078+
await runware.connect()
1079+
# Your code here
1080+
```
1081+
1082+
**Note:** For long-running video operations, consider using webhooks or `skipResponse=True` to avoid timeout issues with extended generation times.

runware/base.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,8 @@
4040
IVideoCaption,
4141
IVideoToText,
4242
IVideoBackgroundRemoval,
43-
IVideoBackgroundRemovalInputs,
44-
IVideoBackgroundRemovalSettings,
4543
IVideoUpscale,
46-
IVideoUpscaleInputs,
4744
IVideoInference,
48-
IVideoInputs,
4945
IVideoAdvancedFeatures,
5046
IAcceleratorOptions,
5147
IAudio,
@@ -73,7 +69,7 @@
7369
removeListener,
7470
LISTEN_TO_IMAGES_KEY,
7571
isLocalFile,
76-
process_image, delay,
72+
process_image,
7773
createAsyncTaskResponse,
7874
VIDEO_INITIAL_TIMEOUT,
7975
VIDEO_POLLING_DELAY,
@@ -83,13 +79,15 @@
8379
PROMPT_ENHANCE_TIMEOUT,
8480
IMAGE_UPLOAD_TIMEOUT,
8581
AUDIO_INFERENCE_TIMEOUT,
82+
AUDIO_POLLING_DELAY,
83+
MAX_POLLS_AUDIO_GENERATION,
84+
MAX_POLLS_VIDEO_GENERATION,
8685
)
8786

8887
# Configure logging
8988
configure_logging(log_level=logging.CRITICAL)
9089

9190
logger = logging.getLogger(__name__)
92-
MAX_POLLS_VIDEO_GENERATION = int(os.environ.get("RUNWARE_MAX_POLLS_VIDEO_GENERATION", 480))
9391

9492

9593
class RunwareBase:
@@ -1474,13 +1472,19 @@ async def ensureConnection(self) -> None:
14741472

14751473
try:
14761474
if self._invalidAPIkey:
1475+
if not self._reconnection_manager._had_successful_auth:
1476+
raise ConnectionError(self._invalidAPIkey)
1477+
14771478
circuit_state = self._reconnection_manager.get_state()
14781479
if circuit_state == ConnectionState.CIRCUIT_OPEN:
14791480
raise ConnectionError(self._invalidAPIkey)
14801481

14811482
if not isConnected:
14821483
await self.connect()
14831484

1485+
if self._invalidAPIkey and not self._reconnection_manager._had_successful_auth:
1486+
raise ConnectionError(self._invalidAPIkey)
1487+
14841488
except Exception as e:
14851489
raise ConnectionError(
14861490
self._invalidAPIkey
@@ -2131,7 +2135,7 @@ async def _pollVideoResults(self, task_uuid: str, number_results: int, response_
21312135
if poll_count >= MAX_POLLS_VIDEO_GENERATION - 1:
21322136
raise e
21332137

2134-
await delay(VIDEO_POLLING_DELAY)
2138+
await asyncio.sleep(VIDEO_POLLING_DELAY / 1000)
21352139

21362140
# Different timeout messages based on response type
21372141
timeout_msg = "Timed out"
@@ -2236,7 +2240,7 @@ async def _handleInitialAudioResponse(self, task_uuid: str, number_results: int)
22362240
return [response] if response else []
22372241
else:
22382242
# Multiple results - use polling
2239-
return await self._pollForAudioResults(task_uuid, number_results)
2243+
return await self._pollAudioResults(task_uuid, number_results)
22402244

22412245
async def _waitForAudioCompletion(self, task_uuid: str) -> Optional[IAudio]:
22422246
lis = self.globalListener(taskUUID=task_uuid)
@@ -2274,34 +2278,36 @@ async def check(resolve: Callable, reject: Callable, *args: Any) -> bool:
22742278
lis["destroy"]()
22752279
raise e
22762280

2277-
async def _pollForAudioResults(self, task_uuid: str, number_results: int) -> List[IAudio]:
2281+
async def _pollAudioResults(self, task_uuid: str, number_results: int) -> List[IAudio]:
22782282
completed_results = []
22792283
lis = self.globalListener(taskUUID=task_uuid)
22802284

22812285
try:
2282-
while len(completed_results) < number_results:
2283-
responses = self._globalMessages.get(task_uuid, [])
2284-
if not isinstance(responses, list):
2285-
responses = [responses] if responses else []
2286+
for poll_count in range(MAX_POLLS_AUDIO_GENERATION):
2287+
async with self._messages_lock:
2288+
responses = self._globalMessages.get(task_uuid, [])
2289+
if not isinstance(responses, list):
2290+
responses = [responses] if responses else []
22862291

22872292
processed_responses = self._processAudioPollingResponse(responses)
22882293
completed_results.extend(processed_responses)
22892294

22902295
if len(completed_results) >= number_results:
22912296
break
22922297

2293-
await asyncio.sleep(1) # Poll every second
2294-
2295-
# Clean up
2296-
if task_uuid in self._globalMessages:
2297-
del self._globalMessages[task_uuid]
2298-
lis["destroy"]()
2298+
if poll_count >= MAX_POLLS_AUDIO_GENERATION - 1:
2299+
raise RunwareAPIError(
2300+
{"message": f"Audio generation timeout after {MAX_POLLS_AUDIO_GENERATION} polls"})
22992301

2300-
return [self._createAudioFromResponse(response) for response in completed_results[:number_results]]
2302+
await asyncio.sleep(AUDIO_POLLING_DELAY / 1000)
23012303

2302-
except Exception as e:
2304+
finally:
23032305
lis["destroy"]()
2304-
raise e
2306+
async with self._messages_lock:
2307+
if task_uuid in self._globalMessages:
2308+
del self._globalMessages[task_uuid]
2309+
2310+
return [self._createAudioFromResponse(response) for response in completed_results[:number_results]]
23052311

23062312
def _processAudioPollingResponse(self, responses: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
23072313
completed_results = []

runware/utils.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,72 +48,104 @@
4848
"REQUEST_IMAGES": 2,
4949
}
5050

51-
51+
# WebSocket connection health check timeout (milliseconds)
52+
# Maximum time to wait for pong response after sending ping
53+
# Used in: server.heartBeat() to detect connection loss
5254
PING_TIMEOUT_DURATION = 10000
55+
56+
# WebSocket ping interval (milliseconds)
57+
# How often to send ping messages to keep connection alive
58+
# Used in: server.heartBeat() for periodic health checks
5359
PING_INTERVAL = 5000
5460

61+
# Image generation timeout (milliseconds)
62+
# Maximum time to wait for image generation completion (imageInference, photoMaker)
63+
# Used in: photoMaker(), getSimililarImage() for waiting image generation results
5564
IMAGE_INFERENCE_TIMEOUT = int(os.environ.get(
5665
"RUNWARE_IMAGE_INFERENCE_TIMEOUT",
5766
300000
5867
))
5968

69+
# Image operation timeout (milliseconds)
70+
# Maximum time to wait for image operations (caption, background removal, upscale)
71+
# Used in: imageCaption(), imageBackgroundRemoval(), imageUpscale()
6072
IMAGE_OPERATION_TIMEOUT = int(os.environ.get(
6173
"RUNWARE_IMAGE_OPERATION_TIMEOUT",
6274
120000
6375
))
6476

77+
# Image upload timeout (milliseconds)
78+
# Maximum time to wait for image upload to complete
79+
# Used in: uploadImage() for uploading local images or base64 data
6580
IMAGE_UPLOAD_TIMEOUT = int(os.environ.get(
6681
"RUNWARE_IMAGE_UPLOAD_TIMEOUT",
6782
60000
6883
))
6984

70-
VIDEO_INFERENCE_TIMEOUT = int(os.environ.get(
71-
"RUNWARE_VIDEO_INFERENCE_TIMEOUT",
72-
600000
73-
))
74-
85+
# Video initial response timeout (milliseconds)
86+
# Maximum time to wait for initial video generation response or polling response
87+
# Used in: _handleInitialVideoResponse(), _sendPollRequest()
7588
VIDEO_INITIAL_TIMEOUT = int(os.environ.get(
7689
"RUNWARE_VIDEO_INITIAL_TIMEOUT",
7790
30000
7891
))
7992

93+
# Video polling delay (milliseconds)
94+
# Delay between consecutive polling requests for video generation status
95+
# Used in: _pollVideoResults() for checking video generation progress
8096
VIDEO_POLLING_DELAY = int(os.environ.get(
8197
"RUNWARE_VIDEO_POLLING_DELAY",
82-
3
83-
))
84-
85-
VIDEO_OPERATION_TIMEOUT = int(os.environ.get(
86-
"RUNWARE_VIDEO_OPERATION_TIMEOUT",
87-
120000
98+
3000
8899
))
89100

101+
# Audio generation timeout (milliseconds)
102+
# Maximum time to wait for audio generation completion
103+
# Used in: _waitForAudioCompletion() for single audio generation
90104
AUDIO_INFERENCE_TIMEOUT = int(os.environ.get(
91105
"RUNWARE_AUDIO_INFERENCE_TIMEOUT",
92106
300000
93107
))
94108

95-
AUDIO_OPERATION_TIMEOUT = int(os.environ.get(
96-
"RUNWARE_AUDIO_OPERATION_TIMEOUT",
97-
120000
109+
# Audio polling delay (milliseconds)
110+
# Delay between consecutive polling requests for audio generation status
111+
# Used in: _pollForAudioResults() for checking audio generation progress
112+
AUDIO_POLLING_DELAY = int(os.environ.get(
113+
"RUNWARE_AUDIO_POLLING_DELAY",
114+
1000
98115
))
99116

117+
# Prompt enhancement timeout (milliseconds)
118+
# Maximum time to wait for prompt enhancement completion
119+
# Used in: promptEnhance() for enhancing text prompts
100120
PROMPT_ENHANCE_TIMEOUT = int(os.environ.get(
101121
"RUNWARE_PROMPT_ENHANCE_TIMEOUT",
102122
60000
103123
))
104124

125+
# Webhook acknowledgment timeout (milliseconds)
126+
# Maximum time to wait for webhook task acknowledgment
127+
# Used in: videoCaption(), videoBackgroundRemoval(), videoUpscale() when webhook is provided
105128
WEBHOOK_TIMEOUT = int(os.environ.get(
106129
"RUNWARE_WEBHOOK_TIMEOUT",
107130
30000
108131
))
109132

110-
POLLING_INTERVAL = int(os.environ.get("RUNWARE_POLLING_INTERVAL", 1000))
111-
133+
# Default timeout duration (milliseconds)
134+
# Maximum time to wait for general operations (model upload, model search, media upload)
135+
# Used in: RunwareBase.__init__(), uploadMedia(), modelUpload(), modelSearch()
112136
TIMEOUT_DURATION = int(os.environ.get(
113137
"RUNWARE_TIMEOUT_DURATION",
114138
480000
115139
))
140+
# Maximum polling attempts for video generation
141+
# Number of polling iterations before timing out video generation
142+
# Used in: _pollVideoResults() for video generation status checks
143+
MAX_POLLS_VIDEO_GENERATION = int(os.environ.get("RUNWARE_MAX_POLLS_VIDEO_GENERATION", 480))
116144

145+
# Maximum polling attempts for audio generation
146+
# Number of polling iterations before timing out audio generation
147+
# Used in: _pollAudioResults() for audio generation status checks
148+
MAX_POLLS_AUDIO_GENERATION = int(os.environ.get("RUNWARE_MAX_POLLS_AUDIO_GENERATION", 240))
117149

118150
class LISTEN_TO_IMAGES_KEY:
119151
REQUEST_IMAGES = "REQUEST_IMAGES"
@@ -874,4 +906,4 @@ async def process_image(
874906
return image.imageUUID
875907
if isLocalFile(image) and not image.startswith("http"):
876908
return await fileToBase64(image)
877-
return image
909+
return image

0 commit comments

Comments
 (0)