runbios-sdk 0.2.2__tar.gz → 0.2.4__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 (29) hide show
  1. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/PKG-INFO +1 -1
  2. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/pyproject.toml +1 -1
  3. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/_client.py +23 -1
  4. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/datasets.py +2 -4
  5. runbios_sdk-0.2.4/tests/test_workspace_resolution.py +59 -0
  6. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/.gitignore +0 -0
  7. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/LICENSE +0 -0
  8. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/README.md +0 -0
  9. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/__init__.py +0 -0
  10. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/_gpu_priorities.py +0 -0
  11. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/_types.py +0 -0
  12. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/errors.py +0 -0
  13. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/gpu.py +0 -0
  14. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/inference.py +0 -0
  15. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/integrations.py +0 -0
  16. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/loop.py +0 -0
  17. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/models.py +0 -0
  18. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/py.typed +0 -0
  19. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/training.py +0 -0
  20. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/bios/wallet.py +0 -0
  21. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/src/runbios/__init__.py +0 -0
  22. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_capacity_contract.py +0 -0
  23. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_coming_soon.py +0 -0
  24. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_contracts.py +0 -0
  25. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_inference.py +0 -0
  26. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_loop.py +0 -0
  27. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_loop_training.py +0 -0
  28. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_release_metadata.py +0 -0
  29. {runbios_sdk-0.2.2 → runbios_sdk-0.2.4}/tests/test_sdk_contract_drift.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: runbios-sdk
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Official Python SDK for the Run BiOS training and deployment platform API
5
5
  Project-URL: Homepage, https://runbios.ai
6
6
  Project-URL: Documentation, https://runbios.ai/docs/sdk-python
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "runbios-sdk"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Official Python SDK for the Run BiOS training and deployment platform API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -5,7 +5,7 @@ from typing import Any, Dict, Optional, Tuple
5
5
 
6
6
  import requests
7
7
 
8
- VERSION = "0.2.2"
8
+ VERSION = "0.2.4"
9
9
 
10
10
 
11
11
  class ApiError(Exception):
@@ -120,6 +120,28 @@ class HttpClient:
120
120
  self._session = requests.Session()
121
121
  self._session.headers.update(self._build_base_headers())
122
122
 
123
+ def resolve_workspace_id(self) -> str:
124
+ """Return the configured workspace or the API key's bound workspace.
125
+
126
+ Multipart dataset endpoints require workspace_id in the form body while
127
+ ordinary JSON routes can rely on gateway identity headers. A bound API
128
+ key must therefore remain sufficient for upload: introspect once and
129
+ cache the authoritative binding instead of requiring a duplicate ID.
130
+ """
131
+ if self._workspace_id:
132
+ return self._workspace_id
133
+ data = self.get("/api/api-keys/introspect")
134
+ workspace = data.get("workspace", {}) if isinstance(data, dict) else {}
135
+ workspace_id = workspace.get("id") if isinstance(workspace, dict) else None
136
+ if not isinstance(workspace_id, str) or not workspace_id.strip():
137
+ raise ValueError(
138
+ "Run BiOS: the API key is not bound to a workspace; "
139
+ "dataset operations require a workspace-bound key"
140
+ )
141
+ self._workspace_id = workspace_id.strip()
142
+ self._session.headers["X-Workspace-ID"] = self._workspace_id
143
+ return self._workspace_id
144
+
123
145
  def _build_base_headers(self) -> Dict[str, str]:
124
146
  headers: Dict[str, str] = {
125
147
  "User-Agent": "bios-python/{}".format(VERSION),
@@ -145,7 +145,7 @@ class Datasets:
145
145
 
146
146
  ws = workspace_id or self._http._workspace_id
147
147
  if not ws:
148
- raise ValueError("Run BiOS: workspace_id is required for dataset uploads")
148
+ ws = self._http.resolve_workspace_id()
149
149
  with open(file_path, "rb") as f:
150
150
  files = {"file": (file_name, f, content_type)}
151
151
  data: Dict[str, str] = {"name": name}
@@ -323,9 +323,7 @@ class Datasets:
323
323
  """Register a Hub source for background validation and optional materialization."""
324
324
  ws = workspace_id or self._http._workspace_id
325
325
  if not ws:
326
- raise ValueError(
327
- "Run BiOS: workspace_id is required to register a HuggingFace dataset"
328
- )
326
+ ws = self._http.resolve_workspace_id()
329
327
  body: Dict[str, Any] = {
330
328
  "hf_dataset_id": repo_id,
331
329
  "name": name or repo_id.split("/")[-1],
@@ -0,0 +1,59 @@
1
+ import os
2
+ import tempfile
3
+ import unittest
4
+ from unittest.mock import Mock
5
+
6
+ from bios._client import HttpClient
7
+ from bios.datasets import Datasets
8
+
9
+
10
+ class WorkspaceResolutionTests(unittest.TestCase):
11
+ def test_http_client_introspects_once_and_caches_bound_workspace(self) -> None:
12
+ client = HttpClient(api_key="bios-test", base_url="https://api.test")
13
+ client.get = Mock(return_value={"workspace": {"id": "ws-from-key"}}) # type: ignore[method-assign]
14
+
15
+ self.assertEqual(client.resolve_workspace_id(), "ws-from-key")
16
+ self.assertEqual(client.resolve_workspace_id(), "ws-from-key")
17
+ client.get.assert_called_once_with("/api/api-keys/introspect")
18
+ self.assertEqual(client._session.headers["X-Workspace-ID"], "ws-from-key")
19
+
20
+ def test_dataset_upload_uses_resolved_workspace_when_not_explicit(self) -> None:
21
+ http = Mock()
22
+ http._workspace_id = None
23
+ http.resolve_workspace_id.return_value = "ws-from-key"
24
+ http.upload.return_value = {"id": "ds-1", "name": "sample", "status": "processing"}
25
+ datasets = Datasets(http)
26
+
27
+ with tempfile.NamedTemporaryFile(suffix=".jsonl", delete=False) as handle:
28
+ handle.write(b'{"messages":[{"role":"user","content":"hi"},{"role":"assistant","content":"hello"}]}\n')
29
+ path = handle.name
30
+ try:
31
+ result = datasets.upload(path, "sample")
32
+ finally:
33
+ os.unlink(path)
34
+
35
+ self.assertEqual(result["id"], "ds-1")
36
+ http.resolve_workspace_id.assert_called_once_with()
37
+ data = http.upload.call_args.kwargs["data"]
38
+ self.assertEqual(data["workspace_id"], "ws-from-key")
39
+
40
+ def test_explicit_workspace_does_not_introspect(self) -> None:
41
+ http = Mock()
42
+ http._workspace_id = None
43
+ http.upload.return_value = {"id": "ds-1", "name": "sample", "status": "processing"}
44
+ datasets = Datasets(http)
45
+
46
+ with tempfile.NamedTemporaryFile(suffix=".jsonl", delete=False) as handle:
47
+ handle.write(b'{"messages":[{"role":"user","content":"hi"},{"role":"assistant","content":"hello"}]}\n')
48
+ path = handle.name
49
+ try:
50
+ datasets.upload(path, "sample", workspace_id="ws-explicit")
51
+ finally:
52
+ os.unlink(path)
53
+
54
+ http.resolve_workspace_id.assert_not_called()
55
+ self.assertEqual(http.upload.call_args.kwargs["data"]["workspace_id"], "ws-explicit")
56
+
57
+
58
+ if __name__ == "__main__":
59
+ unittest.main()
File without changes
File without changes
File without changes
File without changes