usfbios 1.0.0__py3-none-any.whl

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.
usfbios/__init__.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ usfbios -- Official Python SDK for the USF BIOS fine-tuning platform.
3
+
4
+ Usage::
5
+
6
+ from usfbios import USFBios
7
+
8
+ client = USFBios(api_key="sk_live_...")
9
+
10
+ # Search for models
11
+ models = client.models.search(query="llama")
12
+
13
+ # Create a training job
14
+ job = client.training.create(
15
+ model="meta-llama/Llama-3.1-8B-Instruct",
16
+ dataset_id="ds_abc123",
17
+ method="sft",
18
+ adapter="lora",
19
+ )
20
+ """
21
+
22
+ from typing import Any, Dict, Optional
23
+
24
+ from ._client import ApiError, HttpClient, VERSION
25
+ from ._types import ApiKeyIntrospection
26
+ from .models import Models
27
+ from .datasets import Datasets
28
+ from .training import Training
29
+ from .wallet import Wallet
30
+ from .gpu import GPU
31
+
32
+
33
+ __version__ = VERSION
34
+ __all__ = [
35
+ "USFBios",
36
+ "ApiError",
37
+ "VERSION",
38
+ "Models",
39
+ "Datasets",
40
+ "Training",
41
+ "Wallet",
42
+ "GPU",
43
+ ]
44
+
45
+
46
+ class USFBios:
47
+ """Main client for the USF BIOS API.
48
+
49
+ Create an instance with either an API key or a JWT access token,
50
+ then use the resource properties to interact with the API.
51
+
52
+ Example with API key::
53
+
54
+ client = USFBios(api_key="sk_live_...")
55
+
56
+ Example with JWT::
57
+
58
+ client = USFBios(access_token="eyJhbG...", org_id="org_abc123")
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ api_key: Optional[str] = None,
64
+ access_token: Optional[str] = None,
65
+ org_id: Optional[str] = None,
66
+ workspace_id: Optional[str] = None,
67
+ base_url: Optional[str] = None,
68
+ timeout: Optional[float] = None,
69
+ ) -> None:
70
+ http = HttpClient(
71
+ api_key=api_key,
72
+ access_token=access_token,
73
+ org_id=org_id,
74
+ workspace_id=workspace_id,
75
+ base_url=base_url,
76
+ timeout=timeout,
77
+ )
78
+ self._http = http
79
+
80
+ self.models: Models = Models(http)
81
+ """Search models, fetch configs, check adapter compatibility."""
82
+
83
+ self.datasets: Datasets = Datasets(http)
84
+ """Upload, import, preview, and manage training datasets."""
85
+
86
+ self.training: Training = Training(http)
87
+ """Create, monitor, stop, and resume fine-tuning jobs."""
88
+
89
+ self.wallet: Wallet = Wallet(http)
90
+ """View wallet balance and transaction history."""
91
+
92
+ self.gpu: GPU = GPU(http)
93
+ """View GPU pricing and get hardware recommendations."""
94
+
95
+ def introspect(self) -> ApiKeyIntrospection:
96
+ """Introspect the current API key to discover its permissions."""
97
+ return self._http.get("/api/api-keys/introspect")
usfbios/_client.py ADDED
@@ -0,0 +1,165 @@
1
+ """HTTP client for the USF BIOS API."""
2
+
3
+ import os
4
+ from typing import Any, Dict, Optional, Tuple
5
+
6
+ import requests
7
+
8
+ VERSION = "1.0.0"
9
+
10
+
11
+ class ApiError(Exception):
12
+ """Typed error raised when the API returns a non-2xx status."""
13
+
14
+ def __init__(self, status: int, body: Dict[str, Any]) -> None:
15
+ msg = (
16
+ body.get("detail")
17
+ or body.get("error")
18
+ or body.get("message")
19
+ or "API error {}".format(status)
20
+ )
21
+ super().__init__(msg)
22
+ self.status: int = status
23
+ self.code: Optional[str] = body.get("code")
24
+ self.request_id: Optional[str] = body.get("request_id")
25
+ self.message: str = msg
26
+
27
+
28
+ class HttpClient:
29
+ """Shared HTTP transport used by resource modules via composition."""
30
+
31
+ def __init__(
32
+ self,
33
+ api_key: Optional[str] = None,
34
+ access_token: Optional[str] = None,
35
+ org_id: Optional[str] = None,
36
+ workspace_id: Optional[str] = None,
37
+ base_url: Optional[str] = None,
38
+ timeout: Optional[float] = None,
39
+ ) -> None:
40
+ self._base_url = (base_url or "https://api-bios.us.inc").rstrip("/")
41
+ self._api_key = api_key
42
+ self._access_token = access_token
43
+ self._org_id = org_id
44
+ self._workspace_id = workspace_id
45
+ self._timeout = timeout if timeout is not None else 30.0
46
+
47
+ if not self._api_key and not self._access_token:
48
+ raise ValueError("USFBios: either api_key or access_token is required")
49
+
50
+ self._session = requests.Session()
51
+ self._session.headers.update(self._build_base_headers())
52
+
53
+ def _build_base_headers(self) -> Dict[str, str]:
54
+ headers: Dict[str, str] = {
55
+ "User-Agent": "usfbios-python/{}".format(VERSION),
56
+ "Accept": "application/json",
57
+ }
58
+
59
+ if self._api_key:
60
+ headers["X-API-Key"] = self._api_key
61
+ elif self._access_token:
62
+ headers["Authorization"] = "Bearer {}".format(self._access_token)
63
+
64
+ if self._org_id:
65
+ headers["X-Org-ID"] = self._org_id
66
+ if self._workspace_id:
67
+ headers["X-Workspace-ID"] = self._workspace_id
68
+
69
+ return headers
70
+
71
+ def _handle_response(self, response: requests.Response) -> Any:
72
+ if not response.ok:
73
+ try:
74
+ body = response.json()
75
+ except (ValueError, RuntimeError):
76
+ body = {"error": response.text or "API error {}".format(response.status_code)}
77
+ raise ApiError(response.status_code, body)
78
+
79
+ if response.status_code == 204 or not response.content:
80
+ return None
81
+
82
+ try:
83
+ return response.json()
84
+ except (ValueError, RuntimeError):
85
+ return None
86
+
87
+ def request(
88
+ self,
89
+ method: str,
90
+ path: str,
91
+ body: Optional[Any] = None,
92
+ extra_headers: Optional[Dict[str, str]] = None,
93
+ ) -> Any:
94
+ """Send a JSON request and parse the response."""
95
+ url = "{}{}".format(self._base_url, path)
96
+ headers = {"Content-Type": "application/json"}
97
+ if extra_headers:
98
+ headers.update(extra_headers)
99
+
100
+ response = self._session.request(
101
+ method=method,
102
+ url=url,
103
+ json=body,
104
+ headers=headers,
105
+ timeout=self._timeout,
106
+ )
107
+ return self._handle_response(response)
108
+
109
+ def get(self, path: str, extra_headers: Optional[Dict[str, str]] = None) -> Any:
110
+ """Send a GET request."""
111
+ url = "{}{}".format(self._base_url, path)
112
+ response = self._session.get(url, headers=extra_headers, timeout=self._timeout)
113
+ return self._handle_response(response)
114
+
115
+ def post(
116
+ self,
117
+ path: str,
118
+ body: Optional[Any] = None,
119
+ extra_headers: Optional[Dict[str, str]] = None,
120
+ ) -> Any:
121
+ """Send a POST request with a JSON body."""
122
+ return self.request("POST", path, body, extra_headers)
123
+
124
+ def patch(
125
+ self,
126
+ path: str,
127
+ body: Optional[Any] = None,
128
+ extra_headers: Optional[Dict[str, str]] = None,
129
+ ) -> Any:
130
+ """Send a PATCH request with a JSON body."""
131
+ return self.request("PATCH", path, body, extra_headers)
132
+
133
+ def put(
134
+ self,
135
+ path: str,
136
+ body: Optional[Any] = None,
137
+ extra_headers: Optional[Dict[str, str]] = None,
138
+ ) -> Any:
139
+ """Send a PUT request with a JSON body."""
140
+ return self.request("PUT", path, body, extra_headers)
141
+
142
+ def delete(
143
+ self,
144
+ path: str,
145
+ body: Optional[Any] = None,
146
+ extra_headers: Optional[Dict[str, str]] = None,
147
+ ) -> Any:
148
+ """Send a DELETE request."""
149
+ return self.request("DELETE", path, body, extra_headers)
150
+
151
+ def upload(
152
+ self,
153
+ path: str,
154
+ files: Dict[str, Tuple[str, Any, str]],
155
+ data: Optional[Dict[str, str]] = None,
156
+ ) -> Any:
157
+ """Upload files via multipart/form-data."""
158
+ url = "{}{}".format(self._base_url, path)
159
+ response = self._session.post(
160
+ url,
161
+ files=files,
162
+ data=data,
163
+ timeout=self._timeout,
164
+ )
165
+ return self._handle_response(response)