subspacecomputing 0.1.2__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.2/subspacecomputing.egg-info → subspacecomputing-0.1.4}/PKG-INFO +24 -23
  2. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/README.md +19 -19
  3. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/pyproject.toml +5 -4
  4. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/setup.py +4 -4
  5. subspacecomputing-0.1.4/subspacecomputing/__init__.py +29 -0
  6. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/client.py +14 -9
  7. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/errors.py +10 -6
  8. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4/subspacecomputing.egg-info}/PKG-INFO +24 -23
  9. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/requires.txt +1 -0
  10. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/tests/test_client_integration.py +2 -2
  11. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/tests/test_client_unit.py +63 -63
  12. subspacecomputing-0.1.2/subspacecomputing/__init__.py +0 -24
  13. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/LICENSE +0 -0
  14. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/MANIFEST.in +0 -0
  15. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/setup.cfg +0 -0
  16. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/utils/__init__.py +0 -0
  17. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/utils/pandas_integration.py +0 -0
  18. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/SOURCES.txt +0 -0
  19. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/dependency_links.txt +0 -0
  20. {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/top_level.txt +0 -0
  21. {subspacecomputing-0.1.2 → 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.2
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,13 @@ 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')
50
51
  ```
51
52
 
52
53
  ### Teams (`X-Team-Id`)
@@ -56,8 +57,8 @@ Keys can carry a `default_team_id` from the portal. If the user is in **several*
56
57
  If you omit it when required, the API returns **400** with `error.reason` **`team_context_required`**. Use `ValidationError` and read `exc.reason`.
57
58
 
58
59
  ```python
59
- bsce = BSCE(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
60
- bsce.set_team_id(None) # clear override for following requests
60
+ client = ASTRIA(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
61
+ client.set_team_id(None) # clear override for following requests
61
62
  ```
62
63
 
63
64
  ### Simple Projection (1 scenario)
@@ -77,7 +78,7 @@ spec = {
77
78
  }
78
79
 
79
80
  # Run the projection
80
- result = bsce.project(spec)
81
+ result = client.project(spec)
81
82
 
82
83
  # Display results
83
84
  print(f"Final capital: {result['final_values']['capital']}")
@@ -107,7 +108,7 @@ spec = {
107
108
  }
108
109
 
109
110
  # Run the simulation
110
- result = bsce.simulate(spec)
111
+ result = client.simulate(spec)
111
112
 
112
113
  # Analyze results
113
114
  print(f"Mean final capital: {result['last_mean']['capital']}")
@@ -161,7 +162,7 @@ aggregations = [
161
162
  ]
162
163
 
163
164
  # Run batch
164
- result = bsce.project_batch(
165
+ result = client.project_batch(
165
166
  template=template,
166
167
  batch_params=batch_params,
167
168
  aggregations=aggregations
@@ -179,7 +180,7 @@ print(f"Average: {result['aggregations']['moyenne_capital']}")
179
180
 
180
181
  ```python
181
182
  # Validate an SP Model before execution
182
- validation = bsce.validate(spec)
183
+ validation = client.validate(spec)
183
184
 
184
185
  if validation['is_valid']:
185
186
  print("✅ SP Model is valid")
@@ -193,15 +194,15 @@ else:
193
194
 
194
195
  ```python
195
196
  # Get examples
196
- examples = bsce.get_examples()
197
+ examples = client.get_examples()
197
198
  print(f"Available examples: {len(examples['examples'])}")
198
199
 
199
200
  # Check usage
200
- usage = bsce.get_usage()
201
+ usage = client.get_usage()
201
202
  print(f"Simulations used: {usage['usage']['simulations_used']}/{usage['usage']['simulations_limit']}")
202
203
 
203
204
  # Get plans
204
- plans = bsce.get_plans()
205
+ plans = client.get_plans()
205
206
  for plan in plans['plans']:
206
207
  print(f"{plan['name']}: ${plan['price_monthly']}/month")
207
208
  ```
@@ -210,16 +211,16 @@ for plan in plans['plans']:
210
211
 
211
212
  ```python
212
213
  from subspacecomputing import (
213
- BSCE,
214
+ Subspace,
215
+ SubspaceError,
214
216
  QuotaExceededError,
215
217
  RateLimitError,
216
218
  AuthenticationError,
217
219
  ValidationError,
218
- BSCEError
219
220
  )
220
221
 
221
222
  try:
222
- result = bsce.simulate(spec)
223
+ result = client.simulate(spec)
223
224
  except QuotaExceededError as e:
224
225
  print(f"Monthly quota exceeded: {e}")
225
226
  except RateLimitError as e:
@@ -229,7 +230,7 @@ except AuthenticationError as e:
229
230
  print(f"Invalid API key: {e}")
230
231
  except ValidationError as e:
231
232
  print(f"Validation error: {e.detail}")
232
- except BSCEError as e:
233
+ except SubspaceError as e:
233
234
  print(f"API error: {e}")
234
235
  ```
235
236
 
@@ -239,15 +240,15 @@ After making a request, you can check your rate limit and quota status:
239
240
 
240
241
  ```python
241
242
  # Make a request
242
- result = bsce.project(spec)
243
+ result = client.project(spec)
243
244
 
244
245
  # Check rate limit info
245
- rate_limit = bsce.get_rate_limit_info()
246
+ rate_limit = client.get_rate_limit_info()
246
247
  if rate_limit:
247
248
  print(f"Rate limit: {rate_limit['remaining']}/{rate_limit['limit']} remaining")
248
249
 
249
250
  # Check quota info
250
- quota = bsce.get_quota_info()
251
+ quota = client.get_quota_info()
251
252
  if quota:
252
253
  print(f"Quota: {quota['used']}/{quota['limit']} used, {quota['remaining']} remaining")
253
254
  ```
@@ -13,13 +13,13 @@ 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
23
  ```
24
24
 
25
25
  ### Teams (`X-Team-Id`)
@@ -29,8 +29,8 @@ Keys can carry a `default_team_id` from the portal. If the user is in **several*
29
29
  If you omit it when required, the API returns **400** with `error.reason` **`team_context_required`**. Use `ValidationError` and read `exc.reason`.
30
30
 
31
31
  ```python
32
- bsce = BSCE(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
33
- bsce.set_team_id(None) # clear override for following requests
32
+ client = ASTRIA(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
33
+ client.set_team_id(None) # clear override for following requests
34
34
  ```
35
35
 
36
36
  ### Simple Projection (1 scenario)
@@ -50,7 +50,7 @@ spec = {
50
50
  }
51
51
 
52
52
  # Run the projection
53
- result = bsce.project(spec)
53
+ result = client.project(spec)
54
54
 
55
55
  # Display results
56
56
  print(f"Final capital: {result['final_values']['capital']}")
@@ -80,7 +80,7 @@ spec = {
80
80
  }
81
81
 
82
82
  # Run the simulation
83
- result = bsce.simulate(spec)
83
+ result = client.simulate(spec)
84
84
 
85
85
  # Analyze results
86
86
  print(f"Mean final capital: {result['last_mean']['capital']}")
@@ -134,7 +134,7 @@ aggregations = [
134
134
  ]
135
135
 
136
136
  # Run batch
137
- result = bsce.project_batch(
137
+ result = client.project_batch(
138
138
  template=template,
139
139
  batch_params=batch_params,
140
140
  aggregations=aggregations
@@ -152,7 +152,7 @@ print(f"Average: {result['aggregations']['moyenne_capital']}")
152
152
 
153
153
  ```python
154
154
  # Validate an SP Model before execution
155
- validation = bsce.validate(spec)
155
+ validation = client.validate(spec)
156
156
 
157
157
  if validation['is_valid']:
158
158
  print("✅ SP Model is valid")
@@ -166,15 +166,15 @@ else:
166
166
 
167
167
  ```python
168
168
  # Get examples
169
- examples = bsce.get_examples()
169
+ examples = client.get_examples()
170
170
  print(f"Available examples: {len(examples['examples'])}")
171
171
 
172
172
  # Check usage
173
- usage = bsce.get_usage()
173
+ usage = client.get_usage()
174
174
  print(f"Simulations used: {usage['usage']['simulations_used']}/{usage['usage']['simulations_limit']}")
175
175
 
176
176
  # Get plans
177
- plans = bsce.get_plans()
177
+ plans = client.get_plans()
178
178
  for plan in plans['plans']:
179
179
  print(f"{plan['name']}: ${plan['price_monthly']}/month")
180
180
  ```
@@ -183,16 +183,16 @@ for plan in plans['plans']:
183
183
 
184
184
  ```python
185
185
  from subspacecomputing import (
186
- BSCE,
186
+ Subspace,
187
+ SubspaceError,
187
188
  QuotaExceededError,
188
189
  RateLimitError,
189
190
  AuthenticationError,
190
191
  ValidationError,
191
- BSCEError
192
192
  )
193
193
 
194
194
  try:
195
- result = bsce.simulate(spec)
195
+ result = client.simulate(spec)
196
196
  except QuotaExceededError as e:
197
197
  print(f"Monthly quota exceeded: {e}")
198
198
  except RateLimitError as e:
@@ -202,7 +202,7 @@ except AuthenticationError as e:
202
202
  print(f"Invalid API key: {e}")
203
203
  except ValidationError as e:
204
204
  print(f"Validation error: {e.detail}")
205
- except BSCEError as e:
205
+ except SubspaceError as e:
206
206
  print(f"API error: {e}")
207
207
  ```
208
208
 
@@ -212,15 +212,15 @@ After making a request, you can check your rate limit and quota status:
212
212
 
213
213
  ```python
214
214
  # Make a request
215
- result = bsce.project(spec)
215
+ result = client.project(spec)
216
216
 
217
217
  # Check rate limit info
218
- rate_limit = bsce.get_rate_limit_info()
218
+ rate_limit = client.get_rate_limit_info()
219
219
  if rate_limit:
220
220
  print(f"Rate limit: {rate_limit['remaining']}/{rate_limit['limit']} remaining")
221
221
 
222
222
  # Check quota info
223
- quota = bsce.get_quota_info()
223
+ quota = client.get_quota_info()
224
224
  if quota:
225
225
  print(f"Quota: {quota['used']}/{quota['limit']} used, {quota['remaining']} remaining")
226
226
  ```
@@ -6,9 +6,9 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "subspacecomputing"
9
- version = "0.1.2"
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,7 +22,8 @@ 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
 
28
29
  # No Git repository URL: built from a private monorepo; only public product URLs are published on PyPI.
@@ -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.2",
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,8 +18,8 @@ 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,
@@ -30,7 +30,7 @@ class BSCE:
30
30
  team_id: Optional[str] = None,
31
31
  ):
32
32
  """
33
- Initialize the BSCE client.
33
+ Initialize the ASTRIA client.
34
34
 
35
35
  Args:
36
36
  api_key: Your API key (format: be_live_... or sk_pro_...).
@@ -81,7 +81,7 @@ class BSCE:
81
81
  QuotaExceededError: If monthly quota exceeded (429)
82
82
  AuthenticationError: If API key invalid (401)
83
83
  ValidationError: If validation error (400)
84
- BSCEError: For other errors
84
+ SubspaceError: For other errors
85
85
  """
86
86
  if response.status_code == 429:
87
87
  # Check if it's rate limit or quota
@@ -96,11 +96,11 @@ class BSCE:
96
96
  response,
97
97
  )
98
98
  elif response.status_code == 403:
99
- raise BSCEError("Access denied", response)
99
+ raise SubspaceError("Access denied", response)
100
100
  elif response.status_code == 400:
101
101
  raise ValidationError("Validation error", response)
102
102
  elif not response.ok:
103
- raise BSCEError(f"API error: {response.status_code}", response)
103
+ raise SubspaceError(f"API error: {response.status_code}", response)
104
104
 
105
105
  # Main endpoints (calculations)
106
106
 
@@ -346,7 +346,7 @@ class BSCE:
346
346
  projection_id: Projection ID
347
347
 
348
348
  Raises:
349
- BSCEError: If error during deletion
349
+ SubspaceError: If error during deletion
350
350
  """
351
351
  response = self.session.delete(
352
352
  f"{self.base_url}/projections/{projection_id}",
@@ -450,7 +450,7 @@ class BSCE:
450
450
  run_id: Run ID
451
451
 
452
452
  Raises:
453
- BSCEError: If error during deletion
453
+ SubspaceError: If error during deletion
454
454
  """
455
455
  response = self.session.delete(
456
456
  f"{self.base_url}/projection-runs/{run_id}",
@@ -530,3 +530,8 @@ class BSCE:
530
530
  "remaining": self._safe_int(remaining),
531
531
  "used": self._safe_int(used),
532
532
  }
533
+
534
+
535
+ # Backward compatibility aliases
536
+ Subspace = ASTRIA
537
+ BSCE = ASTRIA
@@ -6,7 +6,7 @@ import requests
6
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):
@@ -29,7 +29,7 @@ class BSCEError(Exception):
29
29
 
30
30
  @property
31
31
  def reason(self) -> Optional[str]:
32
- """Clé stable contrat §6.8 si le corps est ``{\"error\": {\"reason\": ...}}``."""
32
+ """Structured error reason string, if the response body contains ``{"error": {"reason": ...}}``."""
33
33
  if not self._body:
34
34
  return None
35
35
  err = self._body.get("error")
@@ -44,25 +44,29 @@ class BSCEError(Exception):
44
44
  return self.message
45
45
 
46
46
 
47
- class QuotaExceededError(BSCEError):
47
+ class QuotaExceededError(SubspaceError):
48
48
  """Monthly quota exceeded (429)."""
49
49
 
50
50
  pass
51
51
 
52
52
 
53
- class RateLimitError(BSCEError):
53
+ class RateLimitError(SubspaceError):
54
54
  """Rate limit exceeded (429)."""
55
55
 
56
56
  pass
57
57
 
58
58
 
59
- class AuthenticationError(BSCEError):
59
+ class AuthenticationError(SubspaceError):
60
60
  """Invalid or missing API key (401)."""
61
61
 
62
62
  pass
63
63
 
64
64
 
65
- class ValidationError(BSCEError):
65
+ class ValidationError(SubspaceError):
66
66
  """SP Model validation error (400)."""
67
67
 
68
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.2
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,13 @@ 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')
50
51
  ```
51
52
 
52
53
  ### Teams (`X-Team-Id`)
@@ -56,8 +57,8 @@ Keys can carry a `default_team_id` from the portal. If the user is in **several*
56
57
  If you omit it when required, the API returns **400** with `error.reason` **`team_context_required`**. Use `ValidationError` and read `exc.reason`.
57
58
 
58
59
  ```python
59
- bsce = BSCE(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
60
- bsce.set_team_id(None) # clear override for following requests
60
+ client = ASTRIA(api_key="...", team_id="00000000-0000-0000-0000-000000000000")
61
+ client.set_team_id(None) # clear override for following requests
61
62
  ```
62
63
 
63
64
  ### Simple Projection (1 scenario)
@@ -77,7 +78,7 @@ spec = {
77
78
  }
78
79
 
79
80
  # Run the projection
80
- result = bsce.project(spec)
81
+ result = client.project(spec)
81
82
 
82
83
  # Display results
83
84
  print(f"Final capital: {result['final_values']['capital']}")
@@ -107,7 +108,7 @@ spec = {
107
108
  }
108
109
 
109
110
  # Run the simulation
110
- result = bsce.simulate(spec)
111
+ result = client.simulate(spec)
111
112
 
112
113
  # Analyze results
113
114
  print(f"Mean final capital: {result['last_mean']['capital']}")
@@ -161,7 +162,7 @@ aggregations = [
161
162
  ]
162
163
 
163
164
  # Run batch
164
- result = bsce.project_batch(
165
+ result = client.project_batch(
165
166
  template=template,
166
167
  batch_params=batch_params,
167
168
  aggregations=aggregations
@@ -179,7 +180,7 @@ print(f"Average: {result['aggregations']['moyenne_capital']}")
179
180
 
180
181
  ```python
181
182
  # Validate an SP Model before execution
182
- validation = bsce.validate(spec)
183
+ validation = client.validate(spec)
183
184
 
184
185
  if validation['is_valid']:
185
186
  print("✅ SP Model is valid")
@@ -193,15 +194,15 @@ else:
193
194
 
194
195
  ```python
195
196
  # Get examples
196
- examples = bsce.get_examples()
197
+ examples = client.get_examples()
197
198
  print(f"Available examples: {len(examples['examples'])}")
198
199
 
199
200
  # Check usage
200
- usage = bsce.get_usage()
201
+ usage = client.get_usage()
201
202
  print(f"Simulations used: {usage['usage']['simulations_used']}/{usage['usage']['simulations_limit']}")
202
203
 
203
204
  # Get plans
204
- plans = bsce.get_plans()
205
+ plans = client.get_plans()
205
206
  for plan in plans['plans']:
206
207
  print(f"{plan['name']}: ${plan['price_monthly']}/month")
207
208
  ```
@@ -210,16 +211,16 @@ for plan in plans['plans']:
210
211
 
211
212
  ```python
212
213
  from subspacecomputing import (
213
- BSCE,
214
+ Subspace,
215
+ SubspaceError,
214
216
  QuotaExceededError,
215
217
  RateLimitError,
216
218
  AuthenticationError,
217
219
  ValidationError,
218
- BSCEError
219
220
  )
220
221
 
221
222
  try:
222
- result = bsce.simulate(spec)
223
+ result = client.simulate(spec)
223
224
  except QuotaExceededError as e:
224
225
  print(f"Monthly quota exceeded: {e}")
225
226
  except RateLimitError as e:
@@ -229,7 +230,7 @@ except AuthenticationError as e:
229
230
  print(f"Invalid API key: {e}")
230
231
  except ValidationError as e:
231
232
  print(f"Validation error: {e.detail}")
232
- except BSCEError as e:
233
+ except SubspaceError as e:
233
234
  print(f"API error: {e}")
234
235
  ```
235
236
 
@@ -239,15 +240,15 @@ After making a request, you can check your rate limit and quota status:
239
240
 
240
241
  ```python
241
242
  # Make a request
242
- result = bsce.project(spec)
243
+ result = client.project(spec)
243
244
 
244
245
  # Check rate limit info
245
- rate_limit = bsce.get_rate_limit_info()
246
+ rate_limit = client.get_rate_limit_info()
246
247
  if rate_limit:
247
248
  print(f"Rate limit: {rate_limit['remaining']}/{rate_limit['limit']} remaining")
248
249
 
249
250
  # Check quota info
250
- quota = bsce.get_quota_info()
251
+ quota = client.get_quota_info()
251
252
  if quota:
252
253
  print(f"Quota: {quota['used']}/{quota['limit']} used, {quota['remaining']} remaining")
253
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.2"
17
- __all__ = [
18
- "BSCE",
19
- "BSCEError",
20
- "QuotaExceededError",
21
- "RateLimitError",
22
- "AuthenticationError",
23
- "ValidationError",
24
- ]