subspacecomputing 0.1.1__tar.gz → 0.1.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 (21) hide show
  1. {subspacecomputing-0.1.1/subspacecomputing.egg-info → subspacecomputing-0.1.4}/PKG-INFO +33 -21
  2. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/README.md +28 -17
  3. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/pyproject.toml +6 -4
  4. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/setup.py +4 -4
  5. subspacecomputing-0.1.4/subspacecomputing/__init__.py +29 -0
  6. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing/client.py +49 -11
  7. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing/errors.py +24 -8
  8. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4/subspacecomputing.egg-info}/PKG-INFO +33 -21
  9. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/requires.txt +1 -0
  10. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/tests/test_client_integration.py +2 -2
  11. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/tests/test_client_unit.py +63 -63
  12. subspacecomputing-0.1.1/subspacecomputing/__init__.py +0 -24
  13. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/LICENSE +0 -0
  14. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/MANIFEST.in +0 -0
  15. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/setup.cfg +0 -0
  16. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing/utils/__init__.py +0 -0
  17. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing/utils/pandas_integration.py +0 -0
  18. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/SOURCES.txt +0 -0
  19. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/dependency_links.txt +0 -0
  20. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/top_level.txt +0 -0
  21. {subspacecomputing-0.1.1 → subspacecomputing-0.1.4}/tests/test_pandas_integration.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: subspacecomputing
3
- Version: 0.1.1
4
- Summary: Python SDK for Subspace Computing Engine API (by Beausoft)
3
+ Version: 0.1.4
4
+ Summary: Python SDK for ASTRIA — Subspace Computing Engine
5
5
  Home-page: https://www.subspacecomputing.com/developer
6
- Author: Beausoft
7
- Author-email: Beausoft <contact@beausoft.ca>
6
+ Author: Subspace Computing
7
+ Author-email: Subspace Computing <contact@beausoft.ca>
8
8
  Project-URL: Documentation, https://www.subspacecomputing.com/developer
9
9
  Project-URL: Homepage, https://www.subspacecomputing.com/developer
10
10
  Project-URL: Support, https://www.subspacecomputing.com/developer
@@ -20,6 +20,7 @@ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
20
20
  Requires-Dist: black>=23.0.0; extra == "dev"
21
21
  Requires-Dist: ruff>=0.1.0; extra == "dev"
22
22
  Requires-Dist: fastapi>=0.100.0; extra == "dev"
23
+ Requires-Dist: httpx>=0.25.0; extra == "dev"
23
24
  Dynamic: author
24
25
  Dynamic: home-page
25
26
  Dynamic: license-file
@@ -40,13 +41,24 @@ pip install subspacecomputing
40
41
  ### Initialization
41
42
 
42
43
  ```python
43
- from subspacecomputing import BSCE
44
+ from subspacecomputing import ASTRIA
44
45
 
45
46
  # Initialize the client (defaults to production URL)
46
- bsce = BSCE(api_key='your-api-key-here')
47
+ client = ASTRIA(api_key='your-api-key-here')
47
48
 
48
49
  # For local testing or custom environments (optional)
49
- # bsce = BSCE(api_key='your-api-key-here', base_url='http://localhost:8000')
50
+ # client = ASTRIA(api_key='your-api-key-here', base_url='http://localhost:8000')
51
+ ```
52
+
53
+ ### Teams (`X-Team-Id`)
54
+
55
+ Keys can carry a `default_team_id` from the portal. If the user is in **several** teams, some endpoints require an explicit team: pass `team_id=...` or call `set_team_id` so the SDK sends `X-Team-Id`.
56
+
57
+ If you omit it when required, the API returns **400** with `error.reason` **`team_context_required`**. Use `ValidationError` and read `exc.reason`.
58
+
59
+ ```python
60
+ client = ASTRIA(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
61
+ client.set_team_id(None) # clear override for following requests
50
62
  ```
51
63
 
52
64
  ### Simple Projection (1 scenario)
@@ -66,7 +78,7 @@ spec = {
66
78
  }
67
79
 
68
80
  # Run the projection
69
- result = bsce.project(spec)
81
+ result = client.project(spec)
70
82
 
71
83
  # Display results
72
84
  print(f"Final capital: {result['final_values']['capital']}")
@@ -96,7 +108,7 @@ spec = {
96
108
  }
97
109
 
98
110
  # Run the simulation
99
- result = bsce.simulate(spec)
111
+ result = client.simulate(spec)
100
112
 
101
113
  # Analyze results
102
114
  print(f"Mean final capital: {result['last_mean']['capital']}")
@@ -150,7 +162,7 @@ aggregations = [
150
162
  ]
151
163
 
152
164
  # Run batch
153
- result = bsce.project_batch(
165
+ result = client.project_batch(
154
166
  template=template,
155
167
  batch_params=batch_params,
156
168
  aggregations=aggregations
@@ -168,7 +180,7 @@ print(f"Average: {result['aggregations']['moyenne_capital']}")
168
180
 
169
181
  ```python
170
182
  # Validate an SP Model before execution
171
- validation = bsce.validate(spec)
183
+ validation = client.validate(spec)
172
184
 
173
185
  if validation['is_valid']:
174
186
  print("✅ SP Model is valid")
@@ -182,15 +194,15 @@ else:
182
194
 
183
195
  ```python
184
196
  # Get examples
185
- examples = bsce.get_examples()
197
+ examples = client.get_examples()
186
198
  print(f"Available examples: {len(examples['examples'])}")
187
199
 
188
200
  # Check usage
189
- usage = bsce.get_usage()
201
+ usage = client.get_usage()
190
202
  print(f"Simulations used: {usage['usage']['simulations_used']}/{usage['usage']['simulations_limit']}")
191
203
 
192
204
  # Get plans
193
- plans = bsce.get_plans()
205
+ plans = client.get_plans()
194
206
  for plan in plans['plans']:
195
207
  print(f"{plan['name']}: ${plan['price_monthly']}/month")
196
208
  ```
@@ -199,16 +211,16 @@ for plan in plans['plans']:
199
211
 
200
212
  ```python
201
213
  from subspacecomputing import (
202
- BSCE,
214
+ Subspace,
215
+ SubspaceError,
203
216
  QuotaExceededError,
204
217
  RateLimitError,
205
218
  AuthenticationError,
206
219
  ValidationError,
207
- BSCEError
208
220
  )
209
221
 
210
222
  try:
211
- result = bsce.simulate(spec)
223
+ result = client.simulate(spec)
212
224
  except QuotaExceededError as e:
213
225
  print(f"Monthly quota exceeded: {e}")
214
226
  except RateLimitError as e:
@@ -218,7 +230,7 @@ except AuthenticationError as e:
218
230
  print(f"Invalid API key: {e}")
219
231
  except ValidationError as e:
220
232
  print(f"Validation error: {e.detail}")
221
- except BSCEError as e:
233
+ except SubspaceError as e:
222
234
  print(f"API error: {e}")
223
235
  ```
224
236
 
@@ -228,15 +240,15 @@ After making a request, you can check your rate limit and quota status:
228
240
 
229
241
  ```python
230
242
  # Make a request
231
- result = bsce.project(spec)
243
+ result = client.project(spec)
232
244
 
233
245
  # Check rate limit info
234
- rate_limit = bsce.get_rate_limit_info()
246
+ rate_limit = client.get_rate_limit_info()
235
247
  if rate_limit:
236
248
  print(f"Rate limit: {rate_limit['remaining']}/{rate_limit['limit']} remaining")
237
249
 
238
250
  # Check quota info
239
- quota = bsce.get_quota_info()
251
+ quota = client.get_quota_info()
240
252
  if quota:
241
253
  print(f"Quota: {quota['used']}/{quota['limit']} used, {quota['remaining']} remaining")
242
254
  ```
@@ -13,13 +13,24 @@ pip install subspacecomputing
13
13
  ### Initialization
14
14
 
15
15
  ```python
16
- from subspacecomputing import BSCE
16
+ from subspacecomputing import ASTRIA
17
17
 
18
18
  # Initialize the client (defaults to production URL)
19
- bsce = BSCE(api_key='your-api-key-here')
19
+ client = ASTRIA(api_key='your-api-key-here')
20
20
 
21
21
  # For local testing or custom environments (optional)
22
- # bsce = BSCE(api_key='your-api-key-here', base_url='http://localhost:8000')
22
+ # client = ASTRIA(api_key='your-api-key-here', base_url='http://localhost:8000')
23
+ ```
24
+
25
+ ### Teams (`X-Team-Id`)
26
+
27
+ Keys can carry a `default_team_id` from the portal. If the user is in **several** teams, some endpoints require an explicit team: pass `team_id=...` or call `set_team_id` so the SDK sends `X-Team-Id`.
28
+
29
+ If you omit it when required, the API returns **400** with `error.reason` **`team_context_required`**. Use `ValidationError` and read `exc.reason`.
30
+
31
+ ```python
32
+ client = ASTRIA(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
33
+ client.set_team_id(None) # clear override for following requests
23
34
  ```
24
35
 
25
36
  ### Simple Projection (1 scenario)
@@ -39,7 +50,7 @@ spec = {
39
50
  }
40
51
 
41
52
  # Run the projection
42
- result = bsce.project(spec)
53
+ result = client.project(spec)
43
54
 
44
55
  # Display results
45
56
  print(f"Final capital: {result['final_values']['capital']}")
@@ -69,7 +80,7 @@ spec = {
69
80
  }
70
81
 
71
82
  # Run the simulation
72
- result = bsce.simulate(spec)
83
+ result = client.simulate(spec)
73
84
 
74
85
  # Analyze results
75
86
  print(f"Mean final capital: {result['last_mean']['capital']}")
@@ -123,7 +134,7 @@ aggregations = [
123
134
  ]
124
135
 
125
136
  # Run batch
126
- result = bsce.project_batch(
137
+ result = client.project_batch(
127
138
  template=template,
128
139
  batch_params=batch_params,
129
140
  aggregations=aggregations
@@ -141,7 +152,7 @@ print(f"Average: {result['aggregations']['moyenne_capital']}")
141
152
 
142
153
  ```python
143
154
  # Validate an SP Model before execution
144
- validation = bsce.validate(spec)
155
+ validation = client.validate(spec)
145
156
 
146
157
  if validation['is_valid']:
147
158
  print("✅ SP Model is valid")
@@ -155,15 +166,15 @@ else:
155
166
 
156
167
  ```python
157
168
  # Get examples
158
- examples = bsce.get_examples()
169
+ examples = client.get_examples()
159
170
  print(f"Available examples: {len(examples['examples'])}")
160
171
 
161
172
  # Check usage
162
- usage = bsce.get_usage()
173
+ usage = client.get_usage()
163
174
  print(f"Simulations used: {usage['usage']['simulations_used']}/{usage['usage']['simulations_limit']}")
164
175
 
165
176
  # Get plans
166
- plans = bsce.get_plans()
177
+ plans = client.get_plans()
167
178
  for plan in plans['plans']:
168
179
  print(f"{plan['name']}: ${plan['price_monthly']}/month")
169
180
  ```
@@ -172,16 +183,16 @@ for plan in plans['plans']:
172
183
 
173
184
  ```python
174
185
  from subspacecomputing import (
175
- BSCE,
186
+ Subspace,
187
+ SubspaceError,
176
188
  QuotaExceededError,
177
189
  RateLimitError,
178
190
  AuthenticationError,
179
191
  ValidationError,
180
- BSCEError
181
192
  )
182
193
 
183
194
  try:
184
- result = bsce.simulate(spec)
195
+ result = client.simulate(spec)
185
196
  except QuotaExceededError as e:
186
197
  print(f"Monthly quota exceeded: {e}")
187
198
  except RateLimitError as e:
@@ -191,7 +202,7 @@ except AuthenticationError as e:
191
202
  print(f"Invalid API key: {e}")
192
203
  except ValidationError as e:
193
204
  print(f"Validation error: {e.detail}")
194
- except BSCEError as e:
205
+ except SubspaceError as e:
195
206
  print(f"API error: {e}")
196
207
  ```
197
208
 
@@ -201,15 +212,15 @@ After making a request, you can check your rate limit and quota status:
201
212
 
202
213
  ```python
203
214
  # Make a request
204
- result = bsce.project(spec)
215
+ result = client.project(spec)
205
216
 
206
217
  # Check rate limit info
207
- rate_limit = bsce.get_rate_limit_info()
218
+ rate_limit = client.get_rate_limit_info()
208
219
  if rate_limit:
209
220
  print(f"Rate limit: {rate_limit['remaining']}/{rate_limit['limit']} remaining")
210
221
 
211
222
  # Check quota info
212
- quota = bsce.get_quota_info()
223
+ quota = client.get_quota_info()
213
224
  if quota:
214
225
  print(f"Quota: {quota['used']}/{quota['limit']} used, {quota['remaining']} remaining")
215
226
  ```
@@ -6,9 +6,9 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "subspacecomputing"
9
- version = "0.1.1"
10
- description = "Python SDK for Subspace Computing Engine API (by Beausoft)"
11
- authors = [{name = "Beausoft", email = "contact@beausoft.ca"}]
9
+ version = "0.1.4"
10
+ description = "Python SDK for ASTRIA — Subspace Computing Engine"
11
+ authors = [{name = "Subspace Computing", email = "contact@beausoft.ca"}]
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
14
14
  dependencies = [
@@ -22,9 +22,11 @@ dev = [
22
22
  "pytest-cov>=4.0.0",
23
23
  "black>=23.0.0",
24
24
  "ruff>=0.1.0",
25
- "fastapi>=0.100.0", # Pour tests d'intégration
25
+ "fastapi>=0.100.0",
26
+ "httpx>=0.25.0", # TestClient (tests d'intégration)
26
27
  ]
27
28
 
29
+ # No Git repository URL: built from a private monorepo; only public product URLs are published on PyPI.
28
30
  [project.urls]
29
31
  Documentation = "https://www.subspacecomputing.com/developer"
30
32
  Homepage = "https://www.subspacecomputing.com/developer"
@@ -1,5 +1,5 @@
1
1
  """
2
- Setup.py pour le SDK Python Beausoft Subspace Computing Engine.
2
+ Setup.py pour le SDK Python ASTRIA Subspace Computing Engine.
3
3
  """
4
4
 
5
5
  from setuptools import setup, find_packages
@@ -13,11 +13,11 @@ except FileNotFoundError:
13
13
 
14
14
  setup(
15
15
  name="subspacecomputing",
16
- version="0.1.1",
17
- description="Python SDK for Subspace Computing Engine API (by Beausoft)",
16
+ version="0.1.4",
17
+ description="Python SDK for ASTRIA — Subspace Computing Engine",
18
18
  long_description=long_description,
19
19
  long_description_content_type="text/markdown",
20
- author="Beausoft",
20
+ author="Subspace Computing",
21
21
  author_email="contact@beausoft.ca",
22
22
  url="https://www.subspacecomputing.com/developer",
23
23
  packages=find_packages(),
@@ -0,0 +1,29 @@
1
+ """
2
+ ASTRIA — Python SDK
3
+
4
+ Python SDK for ASTRIA, the Subspace Computing Engine.
5
+ """
6
+
7
+ from .client import ASTRIA, Subspace, BSCE # Subspace & BSCE = backward compat aliases
8
+ from .errors import (
9
+ SubspaceError,
10
+ BSCEError, # backward compat alias
11
+ QuotaExceededError,
12
+ RateLimitError,
13
+ AuthenticationError,
14
+ ValidationError,
15
+ )
16
+
17
+ __version__ = "0.1.4"
18
+ __all__ = [
19
+ "ASTRIA",
20
+ "SubspaceError",
21
+ "QuotaExceededError",
22
+ "RateLimitError",
23
+ "AuthenticationError",
24
+ "ValidationError",
25
+ # Backward compatibility
26
+ "Subspace",
27
+ "BSCE",
28
+ "BSCEError",
29
+ ]
@@ -7,7 +7,7 @@ This module provides a simple Python interface to interact with the Subspace Com
7
7
  import requests
8
8
  from typing import Dict, Any, Optional, List
9
9
  from .errors import (
10
- BSCEError,
10
+ SubspaceError,
11
11
  QuotaExceededError,
12
12
  RateLimitError,
13
13
  AuthenticationError,
@@ -18,17 +18,19 @@ from .errors import (
18
18
  DEFAULT_TIMEOUT = 60
19
19
 
20
20
 
21
- class BSCE:
22
- """Python client for Subspace Computing Engine API."""
21
+ class ASTRIA:
22
+ """ASTRIA Moteur de calcul Subspace Computing. Client Python pour l'API."""
23
23
 
24
24
  def __init__(
25
25
  self,
26
26
  api_key: str,
27
27
  base_url: Optional[str] = None,
28
28
  timeout: Optional[int] = None,
29
+ *,
30
+ team_id: Optional[str] = None,
29
31
  ):
30
32
  """
31
- Initialize the BSCE client.
33
+ Initialize the ASTRIA client.
32
34
 
33
35
  Args:
34
36
  api_key: Your API key (format: be_live_... or sk_pro_...).
@@ -36,6 +38,7 @@ class BSCE:
36
38
  base_url: API base URL (optional, defaults to https://api.subspacecomputing.com).
37
39
  Only useful for local testing or custom environments.
38
40
  timeout: Request timeout in seconds (optional, defaults to 60).
41
+ team_id: Optional team UUID for header ``X-Team-Id`` (override default_team_id on the key).
39
42
 
40
43
  Raises:
41
44
  ValueError: If api_key is empty or None.
@@ -47,13 +50,25 @@ class BSCE:
47
50
  self.api_key = api_key.strip()
48
51
  self.base_url = (base_url or "https://api.subspacecomputing.com").rstrip("/")
49
52
  self.timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
53
+ self._team_id = (team_id or "").strip() or None
50
54
  self.session = requests.Session()
51
- self.session.headers.update({
55
+ headers = {
52
56
  "X-API-Key": self.api_key,
53
57
  "Content-Type": "application/json",
54
- })
58
+ }
59
+ if self._team_id:
60
+ headers["X-Team-Id"] = self._team_id
61
+ self.session.headers.update(headers)
55
62
  self.last_response = None # Store last response for header access
56
63
 
64
+ def set_team_id(self, team_id: Optional[str] = None) -> None:
65
+ """Set or clear ``X-Team-Id`` for subsequent requests."""
66
+ self._team_id = (team_id or "").strip() or None
67
+ if self._team_id:
68
+ self.session.headers["X-Team-Id"] = self._team_id
69
+ else:
70
+ self.session.headers.pop("X-Team-Id", None)
71
+
57
72
  def _handle_response(self, response: requests.Response):
58
73
  """
59
74
  Handle HTTP errors and raise appropriate exceptions.
@@ -66,7 +81,7 @@ class BSCE:
66
81
  QuotaExceededError: If monthly quota exceeded (429)
67
82
  AuthenticationError: If API key invalid (401)
68
83
  ValidationError: If validation error (400)
69
- BSCEError: For other errors
84
+ SubspaceError: For other errors
70
85
  """
71
86
  if response.status_code == 429:
72
87
  # Check if it's rate limit or quota
@@ -81,11 +96,11 @@ class BSCE:
81
96
  response,
82
97
  )
83
98
  elif response.status_code == 403:
84
- raise BSCEError("Access denied", response)
99
+ raise SubspaceError("Access denied", response)
85
100
  elif response.status_code == 400:
86
101
  raise ValidationError("Validation error", response)
87
102
  elif not response.ok:
88
- raise BSCEError(f"API error: {response.status_code}", response)
103
+ raise SubspaceError(f"API error: {response.status_code}", response)
89
104
 
90
105
  # Main endpoints (calculations)
91
106
 
@@ -331,7 +346,7 @@ class BSCE:
331
346
  projection_id: Projection ID
332
347
 
333
348
  Raises:
334
- BSCEError: If error during deletion
349
+ SubspaceError: If error during deletion
335
350
  """
336
351
  response = self.session.delete(
337
352
  f"{self.base_url}/projections/{projection_id}",
@@ -435,7 +450,7 @@ class BSCE:
435
450
  run_id: Run ID
436
451
 
437
452
  Raises:
438
- BSCEError: If error during deletion
453
+ SubspaceError: If error during deletion
439
454
  """
440
455
  response = self.session.delete(
441
456
  f"{self.base_url}/projection-runs/{run_id}",
@@ -445,6 +460,24 @@ class BSCE:
445
460
  self._handle_response(response)
446
461
  # 204 No Content, no body
447
462
 
463
+ def get_run_artifact(self, artifact_id: str) -> Dict[str, Any]:
464
+ """
465
+ Fetch a run artifact by id (authZ: view on parent projection).
466
+
467
+ Args:
468
+ artifact_id: run_artifacts.id
469
+
470
+ Returns:
471
+ Artifact payload (may include final_values or url).
472
+ """
473
+ response = self.session.get(
474
+ f"{self.base_url}/run-artifacts/{artifact_id}",
475
+ timeout=self.timeout,
476
+ )
477
+ self.last_response = response
478
+ self._handle_response(response)
479
+ return response.json()
480
+
448
481
  # Helper methods for accessing rate limit and quota info
449
482
 
450
483
  @staticmethod
@@ -497,3 +530,8 @@ class BSCE:
497
530
  "remaining": self._safe_int(remaining),
498
531
  "used": self._safe_int(used),
499
532
  }
533
+
534
+
535
+ # Backward compatibility aliases
536
+ Subspace = ASTRIA
537
+ BSCE = ASTRIA
@@ -3,54 +3,70 @@ Custom exceptions for the Subspace Computing SDK.
3
3
  """
4
4
 
5
5
  import requests
6
- from typing import Optional
6
+ from typing import Any, Optional
7
7
 
8
8
 
9
- class BSCEError(Exception):
9
+ class SubspaceError(Exception):
10
10
  """Base error for the Subspace Computing SDK."""
11
11
 
12
12
  def __init__(self, message: str, response: Optional[requests.Response] = None):
13
13
  self.message = message
14
14
  self.response = response
15
15
  self.status_code = response.status_code if response else None
16
- self.detail = None
16
+ self.detail: Any = None
17
+ self._body: Optional[dict] = None
17
18
 
18
- # Try to extract error details from the response
19
19
  if response is not None:
20
20
  try:
21
21
  error_data = response.json()
22
22
  if isinstance(error_data, dict):
23
+ self._body = error_data
23
24
  self.detail = error_data.get("detail", error_data.get("message"))
24
25
  except ValueError:
25
26
  self.detail = response.text
26
27
 
27
28
  super().__init__(self.message)
28
29
 
30
+ @property
31
+ def reason(self) -> Optional[str]:
32
+ """Structured error reason string, if the response body contains ``{"error": {"reason": ...}}``."""
33
+ if not self._body:
34
+ return None
35
+ err = self._body.get("error")
36
+ if isinstance(err, dict):
37
+ r = err.get("reason")
38
+ return str(r) if r is not None else None
39
+ return None
40
+
29
41
  def __str__(self):
30
42
  if self.detail:
31
43
  return f"{self.message}: {self.detail}"
32
44
  return self.message
33
45
 
34
46
 
35
- class QuotaExceededError(BSCEError):
47
+ class QuotaExceededError(SubspaceError):
36
48
  """Monthly quota exceeded (429)."""
37
49
 
38
50
  pass
39
51
 
40
52
 
41
- class RateLimitError(BSCEError):
53
+ class RateLimitError(SubspaceError):
42
54
  """Rate limit exceeded (429)."""
43
55
 
44
56
  pass
45
57
 
46
58
 
47
- class AuthenticationError(BSCEError):
59
+ class AuthenticationError(SubspaceError):
48
60
  """Invalid or missing API key (401)."""
49
61
 
50
62
  pass
51
63
 
52
64
 
53
- class ValidationError(BSCEError):
65
+ class ValidationError(SubspaceError):
54
66
  """SP Model validation error (400)."""
55
67
 
56
68
  pass
69
+
70
+
71
+ # Backward compatibility alias
72
+ BSCEError = SubspaceError
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: subspacecomputing
3
- Version: 0.1.1
4
- Summary: Python SDK for Subspace Computing Engine API (by Beausoft)
3
+ Version: 0.1.4
4
+ Summary: Python SDK for ASTRIA — Subspace Computing Engine
5
5
  Home-page: https://www.subspacecomputing.com/developer
6
- Author: Beausoft
7
- Author-email: Beausoft <contact@beausoft.ca>
6
+ Author: Subspace Computing
7
+ Author-email: Subspace Computing <contact@beausoft.ca>
8
8
  Project-URL: Documentation, https://www.subspacecomputing.com/developer
9
9
  Project-URL: Homepage, https://www.subspacecomputing.com/developer
10
10
  Project-URL: Support, https://www.subspacecomputing.com/developer
@@ -20,6 +20,7 @@ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
20
20
  Requires-Dist: black>=23.0.0; extra == "dev"
21
21
  Requires-Dist: ruff>=0.1.0; extra == "dev"
22
22
  Requires-Dist: fastapi>=0.100.0; extra == "dev"
23
+ Requires-Dist: httpx>=0.25.0; extra == "dev"
23
24
  Dynamic: author
24
25
  Dynamic: home-page
25
26
  Dynamic: license-file
@@ -40,13 +41,24 @@ pip install subspacecomputing
40
41
  ### Initialization
41
42
 
42
43
  ```python
43
- from subspacecomputing import BSCE
44
+ from subspacecomputing import ASTRIA
44
45
 
45
46
  # Initialize the client (defaults to production URL)
46
- bsce = BSCE(api_key='your-api-key-here')
47
+ client = ASTRIA(api_key='your-api-key-here')
47
48
 
48
49
  # For local testing or custom environments (optional)
49
- # bsce = BSCE(api_key='your-api-key-here', base_url='http://localhost:8000')
50
+ # client = ASTRIA(api_key='your-api-key-here', base_url='http://localhost:8000')
51
+ ```
52
+
53
+ ### Teams (`X-Team-Id`)
54
+
55
+ Keys can carry a `default_team_id` from the portal. If the user is in **several** teams, some endpoints require an explicit team: pass `team_id=...` or call `set_team_id` so the SDK sends `X-Team-Id`.
56
+
57
+ If you omit it when required, the API returns **400** with `error.reason` **`team_context_required`**. Use `ValidationError` and read `exc.reason`.
58
+
59
+ ```python
60
+ client = ASTRIA(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
61
+ client.set_team_id(None) # clear override for following requests
50
62
  ```
51
63
 
52
64
  ### Simple Projection (1 scenario)
@@ -66,7 +78,7 @@ spec = {
66
78
  }
67
79
 
68
80
  # Run the projection
69
- result = bsce.project(spec)
81
+ result = client.project(spec)
70
82
 
71
83
  # Display results
72
84
  print(f"Final capital: {result['final_values']['capital']}")
@@ -96,7 +108,7 @@ spec = {
96
108
  }
97
109
 
98
110
  # Run the simulation
99
- result = bsce.simulate(spec)
111
+ result = client.simulate(spec)
100
112
 
101
113
  # Analyze results
102
114
  print(f"Mean final capital: {result['last_mean']['capital']}")
@@ -150,7 +162,7 @@ aggregations = [
150
162
  ]
151
163
 
152
164
  # Run batch
153
- result = bsce.project_batch(
165
+ result = client.project_batch(
154
166
  template=template,
155
167
  batch_params=batch_params,
156
168
  aggregations=aggregations
@@ -168,7 +180,7 @@ print(f"Average: {result['aggregations']['moyenne_capital']}")
168
180
 
169
181
  ```python
170
182
  # Validate an SP Model before execution
171
- validation = bsce.validate(spec)
183
+ validation = client.validate(spec)
172
184
 
173
185
  if validation['is_valid']:
174
186
  print("✅ SP Model is valid")
@@ -182,15 +194,15 @@ else:
182
194
 
183
195
  ```python
184
196
  # Get examples
185
- examples = bsce.get_examples()
197
+ examples = client.get_examples()
186
198
  print(f"Available examples: {len(examples['examples'])}")
187
199
 
188
200
  # Check usage
189
- usage = bsce.get_usage()
201
+ usage = client.get_usage()
190
202
  print(f"Simulations used: {usage['usage']['simulations_used']}/{usage['usage']['simulations_limit']}")
191
203
 
192
204
  # Get plans
193
- plans = bsce.get_plans()
205
+ plans = client.get_plans()
194
206
  for plan in plans['plans']:
195
207
  print(f"{plan['name']}: ${plan['price_monthly']}/month")
196
208
  ```
@@ -199,16 +211,16 @@ for plan in plans['plans']:
199
211
 
200
212
  ```python
201
213
  from subspacecomputing import (
202
- BSCE,
214
+ Subspace,
215
+ SubspaceError,
203
216
  QuotaExceededError,
204
217
  RateLimitError,
205
218
  AuthenticationError,
206
219
  ValidationError,
207
- BSCEError
208
220
  )
209
221
 
210
222
  try:
211
- result = bsce.simulate(spec)
223
+ result = client.simulate(spec)
212
224
  except QuotaExceededError as e:
213
225
  print(f"Monthly quota exceeded: {e}")
214
226
  except RateLimitError as e:
@@ -218,7 +230,7 @@ except AuthenticationError as e:
218
230
  print(f"Invalid API key: {e}")
219
231
  except ValidationError as e:
220
232
  print(f"Validation error: {e.detail}")
221
- except BSCEError as e:
233
+ except SubspaceError as e:
222
234
  print(f"API error: {e}")
223
235
  ```
224
236
 
@@ -228,15 +240,15 @@ After making a request, you can check your rate limit and quota status:
228
240
 
229
241
  ```python
230
242
  # Make a request
231
- result = bsce.project(spec)
243
+ result = client.project(spec)
232
244
 
233
245
  # Check rate limit info
234
- rate_limit = bsce.get_rate_limit_info()
246
+ rate_limit = client.get_rate_limit_info()
235
247
  if rate_limit:
236
248
  print(f"Rate limit: {rate_limit['remaining']}/{rate_limit['limit']} remaining")
237
249
 
238
250
  # Check quota info
239
- quota = bsce.get_quota_info()
251
+ quota = client.get_quota_info()
240
252
  if quota:
241
253
  print(f"Quota: {quota['used']}/{quota['limit']} used, {quota['remaining']} remaining")
242
254
  ```
@@ -6,6 +6,7 @@ pytest-cov>=4.0.0
6
6
  black>=23.0.0
7
7
  ruff>=0.1.0
8
8
  fastapi>=0.100.0
9
+ httpx>=0.25.0
9
10
 
10
11
  [pandas]
11
12
  pandas>=2.0.0
@@ -15,7 +15,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
15
15
 
16
16
  from fastapi.testclient import TestClient
17
17
  from api.main import app
18
- from subspacecomputing import BSCE
18
+ from subspacecomputing import ASTRIA
19
19
 
20
20
 
21
21
  @pytest.fixture
@@ -33,7 +33,7 @@ def sdk_client(api_key, api_client):
33
33
  fonctionne avec le SDK qui utilise requests.Session.
34
34
  """
35
35
  # Créer une instance du SDK
36
- client = BSCE(api_key=api_key, base_url="http://testserver")
36
+ client = ASTRIA(api_key=api_key, base_url="http://testserver")
37
37
 
38
38
  # Créer un mock pour requests.Session qui utilise TestClient
39
39
  def make_request(method, url, **kwargs):
@@ -7,8 +7,8 @@ Ces tests vérifient le comportement du SDK sans dépendre de l'API réelle.
7
7
  import pytest
8
8
  from unittest.mock import Mock, patch
9
9
  from subspacecomputing import (
10
- BSCE,
11
- BSCEError,
10
+ ASTRIA,
11
+ SubspaceError,
12
12
  QuotaExceededError,
13
13
  RateLimitError,
14
14
  AuthenticationError,
@@ -21,30 +21,30 @@ class TestClientInitialization:
21
21
 
22
22
  def test_init_with_api_key(self, api_key, base_url):
23
23
  """Test initialisation avec API key."""
24
- bsce = BSCE(api_key=api_key, base_url=base_url)
25
- assert bsce.api_key == api_key
26
- assert bsce.base_url == base_url
27
- assert 'X-API-Key' in bsce.session.headers
28
- assert bsce.session.headers['X-API-Key'] == api_key
24
+ client = ASTRIA(api_key=api_key, base_url=base_url)
25
+ assert client.api_key == api_key
26
+ assert client.base_url == base_url
27
+ assert 'X-API-Key' in client.session.headers
28
+ assert client.session.headers['X-API-Key'] == api_key
29
29
 
30
30
  def test_init_default_base_url(self, api_key):
31
31
  """Test initialisation avec URL par défaut."""
32
- bsce = BSCE(api_key=api_key)
33
- assert bsce.base_url == 'https://api.subspacecomputing.com'
32
+ client = ASTRIA(api_key=api_key)
33
+ assert client.base_url == 'https://api.subspacecomputing.com'
34
34
 
35
35
  def test_init_strips_trailing_slash(self, api_key):
36
36
  """Test que l'URL est nettoyée (pas de slash final)."""
37
- bsce = BSCE(api_key=api_key, base_url='http://localhost:8000/')
38
- assert bsce.base_url == 'http://localhost:8000'
37
+ client = ASTRIA(api_key=api_key, base_url='http://localhost:8000/')
38
+ assert client.base_url == 'http://localhost:8000'
39
39
 
40
40
  def test_init_rejects_empty_api_key(self):
41
41
  """Test que api_key vide lève ValueError."""
42
42
  with pytest.raises(ValueError, match="API key is required"):
43
- BSCE(api_key="")
43
+ ASTRIA(api_key="")
44
44
  with pytest.raises(ValueError, match="API key is required"):
45
- BSCE(api_key=" ")
45
+ ASTRIA(api_key=" ")
46
46
  with pytest.raises(ValueError, match="API key is required"):
47
- BSCE(api_key=None)
47
+ ASTRIA(api_key=None)
48
48
 
49
49
 
50
50
  class TestErrorHandling:
@@ -52,7 +52,7 @@ class TestErrorHandling:
52
52
 
53
53
  def test_rate_limit_error(self, api_key, base_url):
54
54
  """Test gestion erreur 429 (rate limit)."""
55
- bsce = BSCE(api_key=api_key, base_url=base_url)
55
+ client = ASTRIA(api_key=api_key, base_url=base_url)
56
56
 
57
57
  mock_response = Mock()
58
58
  mock_response.status_code = 429
@@ -60,14 +60,14 @@ class TestErrorHandling:
60
60
  mock_response.headers = {"Retry-After": "60"}
61
61
  mock_response.ok = False
62
62
 
63
- with patch.object(bsce.session, 'post', return_value=mock_response):
63
+ with patch.object(client.session, 'post', return_value=mock_response):
64
64
  with pytest.raises(RateLimitError) as exc_info:
65
- bsce.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
65
+ client.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
66
66
  assert "Rate limit" in str(exc_info.value)
67
67
 
68
68
  def test_quota_exceeded_error(self, api_key, base_url):
69
69
  """Test gestion erreur 429 (quota mensuel)."""
70
- bsce = BSCE(api_key=api_key, base_url=base_url)
70
+ client = ASTRIA(api_key=api_key, base_url=base_url)
71
71
 
72
72
  mock_response = Mock()
73
73
  mock_response.status_code = 429
@@ -75,28 +75,28 @@ class TestErrorHandling:
75
75
  mock_response.headers = {}
76
76
  mock_response.ok = False
77
77
 
78
- with patch.object(bsce.session, 'post', return_value=mock_response):
78
+ with patch.object(client.session, 'post', return_value=mock_response):
79
79
  with pytest.raises(QuotaExceededError) as exc_info:
80
- bsce.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
80
+ client.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
81
81
  assert "quota" in str(exc_info.value).lower()
82
82
 
83
83
  def test_authentication_error(self, api_key, base_url):
84
84
  """Test gestion erreur 401 (API key invalide)."""
85
- bsce = BSCE(api_key=api_key, base_url=base_url)
85
+ client = ASTRIA(api_key=api_key, base_url=base_url)
86
86
 
87
87
  mock_response = Mock()
88
88
  mock_response.status_code = 401
89
89
  mock_response.text = "Invalid API key"
90
90
  mock_response.ok = False
91
91
 
92
- with patch.object(bsce.session, 'post', return_value=mock_response):
92
+ with patch.object(client.session, 'post', return_value=mock_response):
93
93
  with pytest.raises(AuthenticationError) as exc_info:
94
- bsce.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
94
+ client.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
95
95
  assert "API key" in str(exc_info.value)
96
96
 
97
97
  def test_validation_error(self, api_key, base_url):
98
98
  """Test gestion erreur 400 (validation)."""
99
- bsce = BSCE(api_key=api_key, base_url=base_url)
99
+ client = ASTRIA(api_key=api_key, base_url=base_url)
100
100
 
101
101
  mock_response = Mock()
102
102
  mock_response.status_code = 400
@@ -104,23 +104,23 @@ class TestErrorHandling:
104
104
  mock_response.json.return_value = {'detail': 'Invalid formula'}
105
105
  mock_response.ok = False
106
106
 
107
- with patch.object(bsce.session, 'post', return_value=mock_response):
107
+ with patch.object(client.session, 'post', return_value=mock_response):
108
108
  with pytest.raises(ValidationError) as exc_info:
109
- bsce.validate({'scenarios': 1, 'steps': 12, 'variables': []})
109
+ client.validate({'scenarios': 1, 'steps': 12, 'variables': []})
110
110
  assert "validation" in str(exc_info.value).lower()
111
111
 
112
112
  def test_generic_error(self, api_key, base_url):
113
113
  """Test gestion erreur générique (500)."""
114
- bsce = BSCE(api_key=api_key, base_url=base_url)
114
+ client = ASTRIA(api_key=api_key, base_url=base_url)
115
115
 
116
116
  mock_response = Mock()
117
117
  mock_response.status_code = 500
118
118
  mock_response.text = "Internal server error"
119
119
  mock_response.ok = False
120
120
 
121
- with patch.object(bsce.session, 'post', return_value=mock_response):
122
- with pytest.raises(BSCEError) as exc_info:
123
- bsce.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
121
+ with patch.object(client.session, 'post', return_value=mock_response):
122
+ with pytest.raises(SubspaceError) as exc_info:
123
+ client.simulate({'scenarios': 100, 'steps': 12, 'variables': []})
124
124
  assert "500" in str(exc_info.value)
125
125
 
126
126
 
@@ -129,7 +129,7 @@ class TestEndpoints:
129
129
 
130
130
  def test_project_success(self, api_key, base_url, simple_spec):
131
131
  """Test endpoint project() avec succès."""
132
- bsce = BSCE(api_key=api_key, base_url=base_url)
132
+ client = ASTRIA(api_key=api_key, base_url=base_url)
133
133
 
134
134
  mock_response = Mock()
135
135
  mock_response.status_code = 200
@@ -142,15 +142,15 @@ class TestEndpoints:
142
142
  'performance': {'execution_time_ms': 0.5}
143
143
  }
144
144
 
145
- with patch.object(bsce.session, 'post', return_value=mock_response):
146
- result = bsce.project(simple_spec)
145
+ with patch.object(client.session, 'post', return_value=mock_response):
146
+ result = client.project(simple_spec)
147
147
  assert result['scenarios'] == 1
148
148
  assert 'final_values' in result
149
149
  assert 'trajectory' in result
150
150
 
151
151
  def test_simulate_success(self, api_key, base_url, simulation_spec):
152
152
  """Test endpoint simulate() avec succès."""
153
- bsce = BSCE(api_key=api_key, base_url=base_url)
153
+ client = ASTRIA(api_key=api_key, base_url=base_url)
154
154
 
155
155
  mock_response = Mock()
156
156
  mock_response.status_code = 200
@@ -169,15 +169,15 @@ class TestEndpoints:
169
169
  'performance': {'execution_time_ms': 12.5}
170
170
  }
171
171
 
172
- with patch.object(bsce.session, 'post', return_value=mock_response):
173
- result = bsce.simulate(simulation_spec)
172
+ with patch.object(client.session, 'post', return_value=mock_response):
173
+ result = client.simulate(simulation_spec)
174
174
  assert result['scenarios'] == 100
175
175
  assert 'last_mean' in result
176
176
  assert 'statistics' in result
177
177
 
178
178
  def test_project_batch_success(self, api_key, base_url, batch_spec_simple):
179
179
  """Test endpoint project_batch() avec succès."""
180
- bsce = BSCE(api_key=api_key, base_url=base_url)
180
+ client = ASTRIA(api_key=api_key, base_url=base_url)
181
181
 
182
182
  mock_response = Mock()
183
183
  mock_response.status_code = 200
@@ -197,8 +197,8 @@ class TestEndpoints:
197
197
  'performance': {'execution_time_ms': 25.0}
198
198
  }
199
199
 
200
- with patch.object(bsce.session, 'post', return_value=mock_response):
201
- result = bsce.project_batch(
200
+ with patch.object(client.session, 'post', return_value=mock_response):
201
+ result = client.project_batch(
202
202
  template=batch_spec_simple['template'],
203
203
  batch_params=batch_spec_simple['batch_params']
204
204
  )
@@ -207,7 +207,7 @@ class TestEndpoints:
207
207
 
208
208
  def test_validate_success(self, api_key, base_url, simple_spec):
209
209
  """Test endpoint validate() avec succès."""
210
- bsce = BSCE(api_key=api_key, base_url=base_url)
210
+ client = ASTRIA(api_key=api_key, base_url=base_url)
211
211
 
212
212
  mock_response = Mock()
213
213
  mock_response.status_code = 200
@@ -218,14 +218,14 @@ class TestEndpoints:
218
218
  'warnings': []
219
219
  }
220
220
 
221
- with patch.object(bsce.session, 'post', return_value=mock_response):
222
- result = bsce.validate(simple_spec)
221
+ with patch.object(client.session, 'post', return_value=mock_response):
222
+ result = client.validate(simple_spec)
223
223
  assert result['valid'] is True
224
224
  assert len(result['errors']) == 0
225
225
 
226
226
  def test_get_examples_success(self, api_key, base_url):
227
227
  """Test endpoint get_examples() avec succès."""
228
- bsce = BSCE(api_key=api_key, base_url=base_url)
228
+ client = ASTRIA(api_key=api_key, base_url=base_url)
229
229
 
230
230
  mock_response = Mock()
231
231
  mock_response.status_code = 200
@@ -237,13 +237,13 @@ class TestEndpoints:
237
237
  ]
238
238
  }
239
239
 
240
- bsce.session.get = Mock(return_value=mock_response)
241
- result = bsce.get_examples()
240
+ client.session.get = Mock(return_value=mock_response)
241
+ result = client.get_examples()
242
242
  assert len(result['examples']) == 2
243
243
 
244
244
  def test_get_usage_success(self, api_key, base_url):
245
245
  """Test endpoint get_usage() avec succès."""
246
- bsce = BSCE(api_key=api_key, base_url=base_url)
246
+ client = ASTRIA(api_key=api_key, base_url=base_url)
247
247
 
248
248
  mock_response = Mock()
249
249
  mock_response.status_code = 200
@@ -259,14 +259,14 @@ class TestEndpoints:
259
259
  }
260
260
  }
261
261
 
262
- with patch.object(bsce.session, 'get', return_value=mock_response):
263
- result = bsce.get_usage()
262
+ with patch.object(client.session, 'get', return_value=mock_response):
263
+ result = client.get_usage()
264
264
  assert 'usage' in result
265
265
  assert result['usage']['simulations_used'] == 45
266
266
 
267
267
  def test_replay_run_success(self, api_key, base_url):
268
268
  """Test endpoint replay_run() avec succès."""
269
- bsce = BSCE(api_key=api_key, base_url=base_url)
269
+ client = ASTRIA(api_key=api_key, base_url=base_url)
270
270
 
271
271
  mock_response = Mock()
272
272
  mock_response.status_code = 200
@@ -281,8 +281,8 @@ class TestEndpoints:
281
281
  'run_id': 'run-123'
282
282
  }
283
283
 
284
- with patch.object(bsce.session, 'post', return_value=mock_response):
285
- result = bsce.replay_run(run_id='run-123', scenario_id=2)
284
+ with patch.object(client.session, 'post', return_value=mock_response):
285
+ result = client.replay_run(run_id='run-123', scenario_id=2)
286
286
  assert result['scenarios'] == 1
287
287
  assert result['steps'] == 6
288
288
  assert 'final_values' in result
@@ -294,7 +294,7 @@ class TestBatchParameters:
294
294
 
295
295
  def test_project_batch_with_aggregations(self, api_key, base_url):
296
296
  """Test project_batch() avec agrégations."""
297
- bsce = BSCE(api_key=api_key, base_url=base_url)
297
+ client = ASTRIA(api_key=api_key, base_url=base_url)
298
298
 
299
299
  template = {
300
300
  'scenarios': 1,
@@ -319,8 +319,8 @@ class TestBatchParameters:
319
319
  'aggregations': {'capital_total': 3000.0}
320
320
  }
321
321
 
322
- with patch.object(bsce.session, 'post', return_value=mock_response) as mock_post:
323
- result = bsce.project_batch(
322
+ with patch.object(client.session, 'post', return_value=mock_response) as mock_post:
323
+ result = client.project_batch(
324
324
  template=template,
325
325
  batch_params=batch_params,
326
326
  aggregations=aggregations
@@ -337,43 +337,43 @@ class TestRateLimitQuotaInfo:
337
337
 
338
338
  def test_get_rate_limit_info_unlimited(self, api_key, base_url):
339
339
  """get_rate_limit_info handles 'unlimited' without crashing."""
340
- bsce = BSCE(api_key=api_key, base_url=base_url)
340
+ client = ASTRIA(api_key=api_key, base_url=base_url)
341
341
  mock_response = Mock()
342
342
  mock_response.headers = {"X-RateLimit-Limit": "unlimited", "X-RateLimit-Remaining": "999"}
343
- bsce.last_response = mock_response
343
+ client.last_response = mock_response
344
344
 
345
- result = bsce.get_rate_limit_info()
345
+ result = client.get_rate_limit_info()
346
346
  assert result["limit"] is None
347
347
  assert result["remaining"] == 999
348
348
 
349
349
  def test_get_quota_info_unlimited(self, api_key, base_url):
350
350
  """get_quota_info handles 'unlimited' in X-Quota-Limit."""
351
- bsce = BSCE(api_key=api_key, base_url=base_url)
351
+ client = ASTRIA(api_key=api_key, base_url=base_url)
352
352
  mock_response = Mock()
353
353
  mock_response.headers = {
354
354
  "X-Quota-Limit": "unlimited",
355
355
  "X-Quota-Remaining": "100",
356
356
  "X-Quota-Used": "50",
357
357
  }
358
- bsce.last_response = mock_response
358
+ client.last_response = mock_response
359
359
 
360
- result = bsce.get_quota_info()
360
+ result = client.get_quota_info()
361
361
  assert result["limit"] is None
362
362
  assert result["remaining"] == 100
363
363
  assert result["used"] == 50
364
364
 
365
365
  def test_get_quota_info_invalid_values(self, api_key, base_url):
366
366
  """get_quota_info handles non-numeric values gracefully."""
367
- bsce = BSCE(api_key=api_key, base_url=base_url)
367
+ client = ASTRIA(api_key=api_key, base_url=base_url)
368
368
  mock_response = Mock()
369
369
  mock_response.headers = {
370
370
  "X-Quota-Limit": "N/A",
371
371
  "X-Quota-Remaining": "remaining",
372
372
  "X-Quota-Used": "50",
373
373
  }
374
- bsce.last_response = mock_response
374
+ client.last_response = mock_response
375
375
 
376
- result = bsce.get_quota_info()
376
+ result = client.get_quota_info()
377
377
  assert result["limit"] is None
378
378
  assert result["remaining"] is None
379
379
  assert result["used"] == 50
@@ -1,24 +0,0 @@
1
- """
2
- Subspace Computing Engine - Python SDK
3
-
4
- Python SDK for the Subspace Computing Engine API (by Beausoft).
5
- """
6
-
7
- from .client import BSCE
8
- from .errors import (
9
- BSCEError,
10
- QuotaExceededError,
11
- RateLimitError,
12
- AuthenticationError,
13
- ValidationError,
14
- )
15
-
16
- __version__ = "0.1.1"
17
- __all__ = [
18
- "BSCE",
19
- "BSCEError",
20
- "QuotaExceededError",
21
- "RateLimitError",
22
- "AuthenticationError",
23
- "ValidationError",
24
- ]