runapi-core 0.3.6__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runapi_core-0.3.6 → runapi_core-0.4.0}/PKG-INFO +2 -2
- {runapi_core-0.3.6 → runapi_core-0.4.0}/pyproject.toml +1 -1
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/contract_gen.py +149 -5
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/http_client.py +7 -5
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/resource.py +43 -10
- runapi_core-0.4.0/src/runapi/core/version.py +1 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_http_client.py +2 -1
- runapi_core-0.3.6/src/runapi/core/version.py +0 -1
- {runapi_core-0.3.6 → runapi_core-0.4.0}/README.md +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/__init__.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/account.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/auth.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/config.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/constants.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/errors.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/files.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/models.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/multipart.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/options.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/polling.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/pricing.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/provider_client.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/py.typed +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/src/runapi/core/response.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_account.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_auth.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_config.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_errors.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_files.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_models.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_polling.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_pricing.py +0 -0
- {runapi_core-0.3.6 → runapi_core-0.4.0}/tests/test_resource.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: runapi-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: RunAPI core SDK for JavaScript, Python, Ruby, Go, Java, and PHP
|
|
5
5
|
Project-URL: Homepage, https://runapi.ai/models
|
|
6
6
|
Project-URL: Documentation, https://runapi.ai/docs/resources/sdks
|
|
7
7
|
Project-URL: Source, https://github.com/runapi-ai/core-sdk
|
|
8
8
|
Project-URL: Issues, https://github.com/runapi-ai/core-sdk/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/runapi-ai/core-sdk/blob/main/CHANGELOG.md
|
|
10
|
-
Project-URL: Release Notes, https://github.com/runapi-ai/core-sdk/releases/tag/python%2Fv0.
|
|
10
|
+
Project-URL: Release Notes, https://github.com/runapi-ai/core-sdk/releases/tag/python%2Fv0.4.0
|
|
11
11
|
Author-email: RunAPI <contact@runapi.ai>
|
|
12
12
|
License-Expression: Apache-2.0
|
|
13
13
|
Keywords: api,core,golang,gradle,java,maven,pypi,python,ruby,runapi,runapi-ai,sdk,typescript
|
|
@@ -19,7 +19,7 @@ Documentation = "https://runapi.ai/docs/resources/sdks"
|
|
|
19
19
|
Source = "https://github.com/runapi-ai/core-sdk"
|
|
20
20
|
Issues = "https://github.com/runapi-ai/core-sdk/issues"
|
|
21
21
|
Changelog = "https://github.com/runapi-ai/core-sdk/blob/main/CHANGELOG.md"
|
|
22
|
-
"Release Notes" = "https://github.com/runapi-ai/core-sdk/releases/tag/python%2Fv0.
|
|
22
|
+
"Release Notes" = "https://github.com/runapi-ai/core-sdk/releases/tag/python%2Fv0.4.0"
|
|
23
23
|
|
|
24
24
|
[tool.hatch.version]
|
|
25
25
|
path = "src/runapi/core/version.py"
|
|
@@ -39,10 +39,41 @@ CONTRACT = {
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"fish-audio/text-to-speech": {
|
|
42
|
-
"models": ["s1", "s2-pro"],
|
|
42
|
+
"models": ["s1", "s2-pro", "s2.1-pro"],
|
|
43
43
|
"fields_by_model": {
|
|
44
|
-
"s1": {
|
|
45
|
-
|
|
44
|
+
"s1": {
|
|
45
|
+
"bitrate_kbps": {
|
|
46
|
+
"enum": [64, 128, 192]
|
|
47
|
+
},
|
|
48
|
+
"output_format": {
|
|
49
|
+
"enum": ["mp3", "wav"]
|
|
50
|
+
},
|
|
51
|
+
"sample_rate_hz": {
|
|
52
|
+
"enum": [8000, 16000, 24000, 32000, 44100]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"s2-pro": {
|
|
56
|
+
"bitrate_kbps": {
|
|
57
|
+
"enum": [64, 128, 192]
|
|
58
|
+
},
|
|
59
|
+
"output_format": {
|
|
60
|
+
"enum": ["mp3", "wav"]
|
|
61
|
+
},
|
|
62
|
+
"sample_rate_hz": {
|
|
63
|
+
"enum": [8000, 16000, 24000, 32000, 44100]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"s2.1-pro": {
|
|
67
|
+
"bitrate_kbps": {
|
|
68
|
+
"enum": [64, 128, 192]
|
|
69
|
+
},
|
|
70
|
+
"output_format": {
|
|
71
|
+
"enum": ["mp3", "wav"]
|
|
72
|
+
},
|
|
73
|
+
"sample_rate_hz": {
|
|
74
|
+
"enum": [8000, 16000, 24000, 32000, 44100]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
46
77
|
}
|
|
47
78
|
},
|
|
48
79
|
"flux-2/remix-image": {
|
|
@@ -331,7 +362,7 @@ CONTRACT = {
|
|
|
331
362
|
"enum": ["1:1", "16:9", "9:16", "3:2", "2:3", "auto"]
|
|
332
363
|
},
|
|
333
364
|
"output_resolution": {
|
|
334
|
-
"enum": ["480p", "720p"]
|
|
365
|
+
"enum": ["480p", "720p", "1080p"]
|
|
335
366
|
}
|
|
336
367
|
}
|
|
337
368
|
}
|
|
@@ -373,7 +404,7 @@ CONTRACT = {
|
|
|
373
404
|
"enum": ["1:1", "16:9", "9:16", "3:2", "2:3", "auto"]
|
|
374
405
|
},
|
|
375
406
|
"output_resolution": {
|
|
376
|
-
"enum": ["480p", "720p"]
|
|
407
|
+
"enum": ["480p", "720p", "1080p"]
|
|
377
408
|
}
|
|
378
409
|
}
|
|
379
410
|
}
|
|
@@ -893,6 +924,35 @@ CONTRACT = {
|
|
|
893
924
|
"midjourney-v8.1": {}
|
|
894
925
|
}
|
|
895
926
|
},
|
|
927
|
+
"minimax-h3/image-to-video": {
|
|
928
|
+
"models": ["minimax-h3"],
|
|
929
|
+
"fields_by_model": {
|
|
930
|
+
"minimax-h3": {
|
|
931
|
+
"duration_seconds": {
|
|
932
|
+
"enum": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
|
933
|
+
},
|
|
934
|
+
"output_resolution": {
|
|
935
|
+
"enum": ["768p", "2k"]
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
"minimax-h3/text-to-video": {
|
|
941
|
+
"models": ["minimax-h3"],
|
|
942
|
+
"fields_by_model": {
|
|
943
|
+
"minimax-h3": {
|
|
944
|
+
"aspect_ratio": {
|
|
945
|
+
"enum": ["adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"]
|
|
946
|
+
},
|
|
947
|
+
"duration_seconds": {
|
|
948
|
+
"enum": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
|
949
|
+
},
|
|
950
|
+
"output_resolution": {
|
|
951
|
+
"enum": ["768p", "2k"]
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
},
|
|
896
956
|
"nano-banana/edit-image": {
|
|
897
957
|
"models": ["nano-banana-2-lite", "nano-banana-edit"],
|
|
898
958
|
"fields_by_model": {
|
|
@@ -973,6 +1033,27 @@ CONTRACT = {
|
|
|
973
1033
|
"omnihuman-1.5-subject-detection": {}
|
|
974
1034
|
}
|
|
975
1035
|
},
|
|
1036
|
+
"openai-transcription/speech-to-text": {
|
|
1037
|
+
"models": ["gpt-transcribe", "whisper-1"],
|
|
1038
|
+
"fields_by_model": {
|
|
1039
|
+
"gpt-transcribe": {
|
|
1040
|
+
"model": {
|
|
1041
|
+
"enum": ["gpt-transcribe"]
|
|
1042
|
+
},
|
|
1043
|
+
"response_format": {
|
|
1044
|
+
"enum": ["json", "text"]
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
"whisper-1": {
|
|
1048
|
+
"model": {
|
|
1049
|
+
"enum": ["whisper-1"]
|
|
1050
|
+
},
|
|
1051
|
+
"response_format": {
|
|
1052
|
+
"enum": ["json", "text", "srt", "verbose_json", "vtt"]
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
976
1057
|
"openai-tts/text-to-speech": {
|
|
977
1058
|
"models": ["tts-1", "tts-1-hd"],
|
|
978
1059
|
"fields_by_model": {
|
|
@@ -987,6 +1068,9 @@ CONTRACT = {
|
|
|
987
1068
|
"aspect_ratio": {
|
|
988
1069
|
"enum": ["16:9", "4:3", "1:1", "3:4", "9:16", "2:3", "3:2", "21:9"]
|
|
989
1070
|
},
|
|
1071
|
+
"enable_audio": {
|
|
1072
|
+
"enum": [True, False]
|
|
1073
|
+
},
|
|
990
1074
|
"output_resolution": {
|
|
991
1075
|
"enum": ["360p", "540p", "720p", "1080p"]
|
|
992
1076
|
}
|
|
@@ -997,6 +1081,9 @@ CONTRACT = {
|
|
|
997
1081
|
"models": ["pixverse-v6"],
|
|
998
1082
|
"fields_by_model": {
|
|
999
1083
|
"pixverse-v6": {
|
|
1084
|
+
"enable_audio": {
|
|
1085
|
+
"enum": [True, False]
|
|
1086
|
+
},
|
|
1000
1087
|
"output_resolution": {
|
|
1001
1088
|
"enum": ["360p", "540p", "720p", "1080p"]
|
|
1002
1089
|
}
|
|
@@ -1036,6 +1123,9 @@ CONTRACT = {
|
|
|
1036
1123
|
"models": ["pixverse-v6"],
|
|
1037
1124
|
"fields_by_model": {
|
|
1038
1125
|
"pixverse-v6": {
|
|
1126
|
+
"enable_audio": {
|
|
1127
|
+
"enum": [True, False]
|
|
1128
|
+
},
|
|
1039
1129
|
"output_resolution": {
|
|
1040
1130
|
"enum": ["360p", "540p", "720p", "1080p"]
|
|
1041
1131
|
}
|
|
@@ -1113,6 +1203,60 @@ CONTRACT = {
|
|
|
1113
1203
|
}
|
|
1114
1204
|
}
|
|
1115
1205
|
},
|
|
1206
|
+
"qwen-3/edit-image": {
|
|
1207
|
+
"models": ["qwen-3-edit-image", "qwen-3-pro-edit-image"],
|
|
1208
|
+
"fields_by_model": {
|
|
1209
|
+
"qwen-3-edit-image": {
|
|
1210
|
+
"aspect_ratio": {
|
|
1211
|
+
"enum": ["1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16", "21:9"]
|
|
1212
|
+
},
|
|
1213
|
+
"output_format": {
|
|
1214
|
+
"enum": ["png", "jpeg"]
|
|
1215
|
+
},
|
|
1216
|
+
"output_resolution": {
|
|
1217
|
+
"enum": ["1k", "2k"]
|
|
1218
|
+
}
|
|
1219
|
+
},
|
|
1220
|
+
"qwen-3-pro-edit-image": {
|
|
1221
|
+
"aspect_ratio": {
|
|
1222
|
+
"enum": ["1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16", "21:9"]
|
|
1223
|
+
},
|
|
1224
|
+
"output_format": {
|
|
1225
|
+
"enum": ["png", "jpeg"]
|
|
1226
|
+
},
|
|
1227
|
+
"output_resolution": {
|
|
1228
|
+
"enum": ["1k", "2k"]
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
"qwen-3/text-to-image": {
|
|
1234
|
+
"models": ["qwen-3-pro-text-to-image", "qwen-3-text-to-image"],
|
|
1235
|
+
"fields_by_model": {
|
|
1236
|
+
"qwen-3-pro-text-to-image": {
|
|
1237
|
+
"aspect_ratio": {
|
|
1238
|
+
"enum": ["1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16", "21:9"]
|
|
1239
|
+
},
|
|
1240
|
+
"output_format": {
|
|
1241
|
+
"enum": ["png", "jpeg"]
|
|
1242
|
+
},
|
|
1243
|
+
"output_resolution": {
|
|
1244
|
+
"enum": ["1k", "2k"]
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
"qwen-3-text-to-image": {
|
|
1248
|
+
"aspect_ratio": {
|
|
1249
|
+
"enum": ["1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16", "21:9"]
|
|
1250
|
+
},
|
|
1251
|
+
"output_format": {
|
|
1252
|
+
"enum": ["png", "jpeg"]
|
|
1253
|
+
},
|
|
1254
|
+
"output_resolution": {
|
|
1255
|
+
"enum": ["1k", "2k"]
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1116
1260
|
"qwen-image/edit-image": {
|
|
1117
1261
|
"models": ["qwen-image-edit-image"],
|
|
1118
1262
|
"fields_by_model": {
|
|
@@ -123,18 +123,20 @@ class HttpClient:
|
|
|
123
123
|
self, body: Any
|
|
124
124
|
) -> Tuple[Any, Optional[Dict[str, Any]], Optional[Dict[str, Any]], List[Any]]:
|
|
125
125
|
if isinstance(body, MultipartBody):
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
files = []
|
|
127
|
+
for key, value in body.fields.items():
|
|
128
|
+
values = value if isinstance(value, (list, tuple)) else [value]
|
|
129
|
+
files.extend((key, (None, str(item))) for item in values)
|
|
128
130
|
opened: List[Any] = []
|
|
129
131
|
for key, part in body.files.items():
|
|
130
132
|
handle = open(part.path, "rb")
|
|
131
133
|
opened.append(handle)
|
|
132
134
|
filename = part.filename
|
|
133
135
|
if part.content_type:
|
|
134
|
-
files
|
|
136
|
+
files.append((key, (filename, handle, part.content_type)))
|
|
135
137
|
else:
|
|
136
|
-
files
|
|
137
|
-
return None,
|
|
138
|
+
files.append((key, (filename, handle)))
|
|
139
|
+
return None, None, files, opened
|
|
138
140
|
if body is not None:
|
|
139
141
|
return body, None, None, []
|
|
140
142
|
return None, None, None, []
|
|
@@ -177,32 +177,65 @@ class Resource:
|
|
|
177
177
|
|
|
178
178
|
def _enforce_rule(self, params: Dict[str, Any], rule: Dict[str, Any]) -> None:
|
|
179
179
|
conditions = rule.get("when", {})
|
|
180
|
-
if not all(
|
|
180
|
+
if not all(
|
|
181
|
+
self._rule_condition_met(params, key, val) for key, val in conditions.items()
|
|
182
|
+
):
|
|
181
183
|
return
|
|
182
184
|
|
|
183
|
-
context = " and ".join(
|
|
185
|
+
context = " and ".join(
|
|
186
|
+
self._rule_condition_label(key, val) for key, val in conditions.items()
|
|
187
|
+
)
|
|
188
|
+
qualifier = f" when {context}" if context else ""
|
|
189
|
+
|
|
184
190
|
for field in rule.get("required", []):
|
|
185
191
|
if not self._field_present(params, field):
|
|
186
|
-
raise ValidationError(f"{field} is required
|
|
192
|
+
raise ValidationError(f"{field} is required{qualifier}")
|
|
193
|
+
|
|
194
|
+
required_any = rule.get("required_any", [])
|
|
195
|
+
if required_any and not any(self._field_present(params, f) for f in required_any):
|
|
196
|
+
raise ValidationError(f"one of {', '.join(required_any)} is required{qualifier}")
|
|
197
|
+
|
|
187
198
|
for field in rule.get("forbidden", []):
|
|
188
199
|
if self._field_present(params, field):
|
|
189
|
-
raise ValidationError(f"{field} is not allowed
|
|
200
|
+
raise ValidationError(f"{field} is not allowed{qualifier}")
|
|
201
|
+
|
|
202
|
+
for field, allowed in (rule.get("enum") or {}).items():
|
|
203
|
+
if not self._field_present(params, field):
|
|
204
|
+
continue
|
|
205
|
+
if any(str(candidate) == str(params[field]) for candidate in allowed):
|
|
206
|
+
continue
|
|
207
|
+
joined = ", ".join(str(candidate) for candidate in allowed)
|
|
208
|
+
raise ValidationError(f"{field} must be one of: {joined}{qualifier}")
|
|
209
|
+
|
|
210
|
+
def _rule_condition_met(self, params: Dict[str, Any], field: str, condition: Any) -> bool:
|
|
211
|
+
"""A ``when`` entry is either ``{"present": bool}`` or a scalar the supplied
|
|
212
|
+
value must equal. Rules never resolve declared defaults."""
|
|
213
|
+
if self._is_presence_condition(condition):
|
|
214
|
+
return self._field_present(params, field) is (condition["present"] is True)
|
|
190
215
|
|
|
191
|
-
@staticmethod
|
|
192
|
-
def _rule_condition_met(params: Dict[str, Any], field: str, value: Any) -> bool:
|
|
193
216
|
if field not in params:
|
|
194
217
|
return False
|
|
195
|
-
return str(params[field]) == str(
|
|
218
|
+
return str(params[field]) == str(condition)
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def _is_presence_condition(condition: Any) -> bool:
|
|
222
|
+
return isinstance(condition, dict) and "present" in condition
|
|
223
|
+
|
|
224
|
+
def _rule_condition_label(self, field: str, condition: Any) -> str:
|
|
225
|
+
if self._is_presence_condition(condition):
|
|
226
|
+
return f"{field} is present" if condition["present"] is True else f"{field} is absent"
|
|
227
|
+
return f"{field} is {condition}"
|
|
196
228
|
|
|
197
|
-
|
|
229
|
+
@classmethod
|
|
230
|
+
def _field_present(cls, params: Dict[str, Any], field: str) -> bool:
|
|
198
231
|
if field not in params:
|
|
199
232
|
return False
|
|
200
233
|
value = params[field]
|
|
201
234
|
if value is False:
|
|
202
235
|
return True
|
|
203
236
|
if isinstance(value, (list, tuple)):
|
|
204
|
-
return any(
|
|
205
|
-
return
|
|
237
|
+
return any(cls._present(item) for item in value)
|
|
238
|
+
return cls._present(value)
|
|
206
239
|
|
|
207
240
|
@staticmethod
|
|
208
241
|
def _present(value: Any) -> bool:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -133,7 +133,7 @@ def test_sends_multipart_without_json_content_type():
|
|
|
133
133
|
path = handle.name
|
|
134
134
|
try:
|
|
135
135
|
body = MultipartBody(
|
|
136
|
-
fields={"file_name": "image.png"},
|
|
136
|
+
fields={"file_name": "image.png", "languages[]": ["en", "zh"]},
|
|
137
137
|
files={"file": MultipartFile(path=path, filename="image.png", content_type="image/png")},
|
|
138
138
|
)
|
|
139
139
|
make_client(handler).request("post", "/api/v1/files", body=body)
|
|
@@ -142,6 +142,7 @@ def test_sends_multipart_without_json_content_type():
|
|
|
142
142
|
|
|
143
143
|
assert captured["content_type"].startswith("multipart/form-data")
|
|
144
144
|
assert b"image.png" in captured["body"]
|
|
145
|
+
assert captured["body"].count(b'name="languages[]"') == 2
|
|
145
146
|
assert b"png" in captured["body"]
|
|
146
147
|
|
|
147
148
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|