runapi-core 0.9.0__tar.gz → 0.9.1__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.
Files changed (36) hide show
  1. {runapi_core-0.9.0 → runapi_core-0.9.1}/PKG-INFO +2 -2
  2. {runapi_core-0.9.0 → runapi_core-0.9.1}/pyproject.toml +1 -1
  3. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/contract_gen.py +6 -0
  4. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/resource.py +15 -5
  5. runapi_core-0.9.1/src/runapi/core/version.py +1 -0
  6. runapi_core-0.9.1/tests/test_contract_rule_messages.py +28 -0
  7. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_resource.py +1 -1
  8. runapi_core-0.9.0/src/runapi/core/version.py +0 -1
  9. {runapi_core-0.9.0 → runapi_core-0.9.1}/README.md +0 -0
  10. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/__init__.py +0 -0
  11. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/account.py +0 -0
  12. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/auth.py +0 -0
  13. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/config.py +0 -0
  14. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/constants.py +0 -0
  15. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/errors.py +0 -0
  16. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/files.py +0 -0
  17. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/http_client.py +0 -0
  18. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/models.py +0 -0
  19. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/multipart.py +0 -0
  20. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/options.py +0 -0
  21. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/polling.py +0 -0
  22. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/pricing.py +0 -0
  23. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/provider_client.py +0 -0
  24. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/py.typed +0 -0
  25. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/response.py +0 -0
  26. {runapi_core-0.9.0 → runapi_core-0.9.1}/src/runapi/core/uploads.py +0 -0
  27. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_account.py +0 -0
  28. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_auth.py +0 -0
  29. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_config.py +0 -0
  30. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_errors.py +0 -0
  31. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_files.py +0 -0
  32. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_http_client.py +0 -0
  33. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_models.py +0 -0
  34. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_polling.py +0 -0
  35. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_pricing.py +0 -0
  36. {runapi_core-0.9.0 → runapi_core-0.9.1}/tests/test_uploads.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: runapi-core
3
- Version: 0.9.0
3
+ Version: 0.9.1
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.9.0
10
+ Project-URL: Release Notes, https://github.com/runapi-ai/core-sdk/releases/tag/python%2Fv0.9.1
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.9.0"
22
+ "Release Notes" = "https://github.com/runapi-ai/core-sdk/releases/tag/python%2Fv0.9.1"
23
23
 
24
24
  [tool.hatch.version]
25
25
  path = "src/runapi/core/version.py"
@@ -2459,6 +2459,9 @@ CONTRACT = {
2459
2459
  }
2460
2460
  },
2461
2461
  "wan-2.5-image-to-video": {
2462
+ "duration_seconds": {
2463
+ "enum": [5, 10]
2464
+ },
2462
2465
  "output_resolution": {
2463
2466
  "enum": ["720p", "1080p"]
2464
2467
  }
@@ -2523,6 +2526,9 @@ CONTRACT = {
2523
2526
  }
2524
2527
  },
2525
2528
  "wan-2.5-text-to-video": {
2529
+ "duration_seconds": {
2530
+ "enum": [5, 10]
2531
+ },
2526
2532
  "output_resolution": {
2527
2533
  "enum": ["720p", "1080p"]
2528
2534
  }
@@ -227,7 +227,7 @@ class Resource:
227
227
 
228
228
  enum = rules.get("enum")
229
229
  if enum is not None and not self._enum_allowed(enum, value):
230
- raise ValidationError(f"{field} must be one of: {', '.join(str(option) for option in enum)}")
230
+ raise ValidationError(f"{field} must be one of: {', '.join(self._message_value(option) for option in enum)}")
231
231
 
232
232
  if rules.get("type") == "integer":
233
233
  self._validate_integer(field, value, rules)
@@ -318,8 +318,9 @@ class Resource:
318
318
  ):
319
319
  return
320
320
 
321
+ # Sorted so the message matches every other SDK; Go maps have no declaration order.
321
322
  context = " and ".join(
322
- self._rule_condition_label(key, val) for key, val in conditions.items()
323
+ self._rule_condition_label(key, conditions[key]) for key in sorted(conditions)
323
324
  )
324
325
  qualifier = f" when {context}" if context else ""
325
326
 
@@ -335,12 +336,14 @@ class Resource:
335
336
  if self._field_present(params, field):
336
337
  raise ValidationError(f"{field} is not allowed{qualifier}")
337
338
 
338
- for field, allowed in (rule.get("enum") or {}).items():
339
+ narrowed = rule.get("enum") or {}
340
+ for field in sorted(narrowed):
341
+ allowed = narrowed[field]
339
342
  if not self._field_present(params, field):
340
343
  continue
341
344
  if any(str(candidate) == str(params[field]) for candidate in allowed):
342
345
  continue
343
- joined = ", ".join(str(candidate) for candidate in allowed)
346
+ joined = ", ".join(self._message_value(candidate) for candidate in allowed)
344
347
  raise ValidationError(f"{field} must be one of: {joined}{qualifier}")
345
348
 
346
349
  def _rule_condition_met(self, params: Dict[str, Any], field: str, condition: Any) -> bool:
@@ -360,7 +363,14 @@ class Resource:
360
363
  def _rule_condition_label(self, field: str, condition: Any) -> str:
361
364
  if self._is_presence_condition(condition):
362
365
  return f"{field} is present" if condition["present"] is True else f"{field} is absent"
363
- return f"{field} is {condition}"
366
+ return f"{field} is {self._message_value(condition)}"
367
+
368
+ @staticmethod
369
+ def _message_value(value: Any) -> str:
370
+ """Render booleans as JSON does (``false``) to match the other SDKs."""
371
+ if isinstance(value, bool):
372
+ return "true" if value else "false"
373
+ return str(value)
364
374
 
365
375
  @classmethod
366
376
  def _field_present(cls, params: Dict[str, Any], field: str) -> bool:
@@ -0,0 +1 @@
1
+ __version__ = "0.9.1"
@@ -0,0 +1,28 @@
1
+ """Rule messages must match every other SDK (sdk/contract_rule_messages.json)."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from runapi.core import Resource
9
+ from runapi.core.errors import ValidationError
10
+
11
+ SDK_ROOT = Path(__file__).resolve().parents[4]
12
+ FIXTURE = SDK_ROOT / "contract_rule_messages.json"
13
+ CONTRACT = SDK_ROOT / "contract.json"
14
+
15
+ # The shared fixture lives in the SDK monorepo; the public package repo does not ship it.
16
+ pytestmark = pytest.mark.skipif(not FIXTURE.exists(), reason="shared SDK fixture not available")
17
+
18
+ CASES = json.loads(FIXTURE.read_text())["cases"] if FIXTURE.exists() else []
19
+
20
+
21
+ @pytest.mark.parametrize("case", CASES, ids=[case["name"] for case in CASES])
22
+ def test_rule_message_matches_shared_fixture(case):
23
+ schema = json.loads(CONTRACT.read_text())["actions"][case["action"]]
24
+
25
+ with pytest.raises(ValidationError) as error:
26
+ Resource(object())._validate_contract(schema, dict(case["params"]))
27
+
28
+ assert str(error.value) == case["message"]
@@ -151,7 +151,7 @@ def test_validate_boolean_enum_requires_boolean_value():
151
151
 
152
152
  resource._validate_contract(schema, {"model": "m", "flag": True})
153
153
  resource._validate_contract(schema, {"model": "m", "flag": False})
154
- with pytest.raises(ValidationError, match="^flag must be one of: True, False$"):
154
+ with pytest.raises(ValidationError, match="^flag must be one of: true, false$"):
155
155
  resource._validate_contract(schema, {"model": "m", "flag": "true"})
156
156
 
157
157
 
@@ -1 +0,0 @@
1
- __version__ = "0.9.0"
File without changes