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.
- {subspacecomputing-0.1.2/subspacecomputing.egg-info → subspacecomputing-0.1.4}/PKG-INFO +24 -23
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/README.md +19 -19
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/pyproject.toml +5 -4
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/setup.py +4 -4
- subspacecomputing-0.1.4/subspacecomputing/__init__.py +29 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/client.py +14 -9
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/errors.py +10 -6
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4/subspacecomputing.egg-info}/PKG-INFO +24 -23
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/requires.txt +1 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/tests/test_client_integration.py +2 -2
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/tests/test_client_unit.py +63 -63
- subspacecomputing-0.1.2/subspacecomputing/__init__.py +0 -24
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/LICENSE +0 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/MANIFEST.in +0 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/setup.cfg +0 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/utils/__init__.py +0 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/utils/pandas_integration.py +0 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/SOURCES.txt +0 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/dependency_links.txt +0 -0
- {subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/top_level.txt +0 -0
- {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.
|
|
4
|
-
Summary: Python SDK for Subspace Computing Engine
|
|
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:
|
|
7
|
-
Author-email:
|
|
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
|
|
44
|
+
from subspacecomputing import ASTRIA
|
|
44
45
|
|
|
45
46
|
# Initialize the client (defaults to production URL)
|
|
46
|
-
|
|
47
|
+
client = ASTRIA(api_key='your-api-key-here')
|
|
47
48
|
|
|
48
49
|
# For local testing or custom environments (optional)
|
|
49
|
-
#
|
|
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
|
-
|
|
60
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
197
|
+
examples = client.get_examples()
|
|
197
198
|
print(f"Available examples: {len(examples['examples'])}")
|
|
198
199
|
|
|
199
200
|
# Check usage
|
|
200
|
-
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
243
|
+
result = client.project(spec)
|
|
243
244
|
|
|
244
245
|
# Check rate limit info
|
|
245
|
-
rate_limit =
|
|
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 =
|
|
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
|
|
16
|
+
from subspacecomputing import ASTRIA
|
|
17
17
|
|
|
18
18
|
# Initialize the client (defaults to production URL)
|
|
19
|
-
|
|
19
|
+
client = ASTRIA(api_key='your-api-key-here')
|
|
20
20
|
|
|
21
21
|
# For local testing or custom environments (optional)
|
|
22
|
-
#
|
|
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
|
-
|
|
33
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
169
|
+
examples = client.get_examples()
|
|
170
170
|
print(f"Available examples: {len(examples['examples'])}")
|
|
171
171
|
|
|
172
172
|
# Check usage
|
|
173
|
-
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
215
|
+
result = client.project(spec)
|
|
216
216
|
|
|
217
217
|
# Check rate limit info
|
|
218
|
-
rate_limit =
|
|
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 =
|
|
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.
|
|
10
|
-
description = "Python SDK for Subspace Computing Engine
|
|
11
|
-
authors = [{name = "
|
|
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",
|
|
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
|
|
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.
|
|
17
|
-
description="Python SDK for Subspace Computing Engine
|
|
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="
|
|
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
|
-
|
|
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
|
|
22
|
-
"""
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
"""
|
|
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(
|
|
47
|
+
class QuotaExceededError(SubspaceError):
|
|
48
48
|
"""Monthly quota exceeded (429)."""
|
|
49
49
|
|
|
50
50
|
pass
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
class RateLimitError(
|
|
53
|
+
class RateLimitError(SubspaceError):
|
|
54
54
|
"""Rate limit exceeded (429)."""
|
|
55
55
|
|
|
56
56
|
pass
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
class AuthenticationError(
|
|
59
|
+
class AuthenticationError(SubspaceError):
|
|
60
60
|
"""Invalid or missing API key (401)."""
|
|
61
61
|
|
|
62
62
|
pass
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
class ValidationError(
|
|
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.
|
|
4
|
-
Summary: Python SDK for Subspace Computing Engine
|
|
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:
|
|
7
|
-
Author-email:
|
|
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
|
|
44
|
+
from subspacecomputing import ASTRIA
|
|
44
45
|
|
|
45
46
|
# Initialize the client (defaults to production URL)
|
|
46
|
-
|
|
47
|
+
client = ASTRIA(api_key='your-api-key-here')
|
|
47
48
|
|
|
48
49
|
# For local testing or custom environments (optional)
|
|
49
|
-
#
|
|
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
|
-
|
|
60
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
197
|
+
examples = client.get_examples()
|
|
197
198
|
print(f"Available examples: {len(examples['examples'])}")
|
|
198
199
|
|
|
199
200
|
# Check usage
|
|
200
|
-
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
243
|
+
result = client.project(spec)
|
|
243
244
|
|
|
244
245
|
# Check rate limit info
|
|
245
|
-
rate_limit =
|
|
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 =
|
|
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
|
```
|
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
25
|
-
assert
|
|
26
|
-
assert
|
|
27
|
-
assert 'X-API-Key' in
|
|
28
|
-
assert
|
|
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
|
-
|
|
33
|
-
assert
|
|
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
|
-
|
|
38
|
-
assert
|
|
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
|
-
|
|
43
|
+
ASTRIA(api_key="")
|
|
44
44
|
with pytest.raises(ValueError, match="API key is required"):
|
|
45
|
-
|
|
45
|
+
ASTRIA(api_key=" ")
|
|
46
46
|
with pytest.raises(ValueError, match="API key is required"):
|
|
47
|
-
|
|
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
|
-
|
|
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(
|
|
63
|
+
with patch.object(client.session, 'post', return_value=mock_response):
|
|
64
64
|
with pytest.raises(RateLimitError) as exc_info:
|
|
65
|
-
|
|
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
|
-
|
|
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(
|
|
78
|
+
with patch.object(client.session, 'post', return_value=mock_response):
|
|
79
79
|
with pytest.raises(QuotaExceededError) as exc_info:
|
|
80
|
-
|
|
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
|
-
|
|
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(
|
|
92
|
+
with patch.object(client.session, 'post', return_value=mock_response):
|
|
93
93
|
with pytest.raises(AuthenticationError) as exc_info:
|
|
94
|
-
|
|
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
|
-
|
|
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(
|
|
107
|
+
with patch.object(client.session, 'post', return_value=mock_response):
|
|
108
108
|
with pytest.raises(ValidationError) as exc_info:
|
|
109
|
-
|
|
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
|
-
|
|
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(
|
|
122
|
-
with pytest.raises(
|
|
123
|
-
|
|
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
|
-
|
|
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(
|
|
146
|
-
result =
|
|
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
|
-
|
|
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(
|
|
173
|
-
result =
|
|
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
|
-
|
|
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(
|
|
201
|
-
result =
|
|
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
|
-
|
|
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(
|
|
222
|
-
result =
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
result =
|
|
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
|
-
|
|
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(
|
|
263
|
-
result =
|
|
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
|
-
|
|
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(
|
|
285
|
-
result =
|
|
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
|
-
|
|
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(
|
|
323
|
-
result =
|
|
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
|
-
|
|
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
|
-
|
|
343
|
+
client.last_response = mock_response
|
|
344
344
|
|
|
345
|
-
result =
|
|
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
|
-
|
|
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
|
-
|
|
358
|
+
client.last_response = mock_response
|
|
359
359
|
|
|
360
|
-
result =
|
|
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
|
-
|
|
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
|
-
|
|
374
|
+
client.last_response = mock_response
|
|
375
375
|
|
|
376
|
-
result =
|
|
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
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing/utils/pandas_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{subspacecomputing-0.1.2 → subspacecomputing-0.1.4}/subspacecomputing.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|