thrustlab 0.1.0__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.
- thrustlab-0.1.0/.gitignore +8 -0
- thrustlab-0.1.0/CHANGELOG.md +60 -0
- thrustlab-0.1.0/LICENSE +21 -0
- thrustlab-0.1.0/Makefile +24 -0
- thrustlab-0.1.0/PKG-INFO +190 -0
- thrustlab-0.1.0/README.md +159 -0
- thrustlab-0.1.0/examples/projects/create.py +5 -0
- thrustlab-0.1.0/examples/projects/list.py +7 -0
- thrustlab-0.1.0/examples/projects/retrieve.py +5 -0
- thrustlab-0.1.0/examples/quickstart.py +15 -0
- thrustlab-0.1.0/examples/simulations/run_sync.py +25 -0
- thrustlab-0.1.0/examples/simulations/wait.py +8 -0
- thrustlab-0.1.0/examples/sweeps/run_and_poll.py +30 -0
- thrustlab-0.1.0/examples/webhooks/verify_handler.py +24 -0
- thrustlab-0.1.0/pyproject.toml +45 -0
- thrustlab-0.1.0/scripts/regen_models.py +147 -0
- thrustlab-0.1.0/tests/__init__.py +0 -0
- thrustlab-0.1.0/tests/conftest.py +16 -0
- thrustlab-0.1.0/tests/test_async_polling.py +163 -0
- thrustlab-0.1.0/tests/test_client.py +33 -0
- thrustlab-0.1.0/tests/test_exceptions.py +43 -0
- thrustlab-0.1.0/tests/test_http.py +133 -0
- thrustlab-0.1.0/tests/test_pagination.py +111 -0
- thrustlab-0.1.0/tests/test_resources/__init__.py +0 -0
- thrustlab-0.1.0/tests/test_resources/test_api_keys.py +45 -0
- thrustlab-0.1.0/tests/test_resources/test_components.py +61 -0
- thrustlab-0.1.0/tests/test_resources/test_credits.py +27 -0
- thrustlab-0.1.0/tests/test_resources/test_projects.py +58 -0
- thrustlab-0.1.0/tests/test_resources/test_simulations.py +64 -0
- thrustlab-0.1.0/tests/test_resources/test_starred_components.py +37 -0
- thrustlab-0.1.0/tests/test_resources/test_submissions.py +38 -0
- thrustlab-0.1.0/tests/test_resources/test_subscriptions.py +45 -0
- thrustlab-0.1.0/tests/test_resources/test_sweeps.py +74 -0
- thrustlab-0.1.0/tests/test_resources/test_users.py +13 -0
- thrustlab-0.1.0/tests/test_resources/test_webhook_endpoints.py +103 -0
- thrustlab-0.1.0/tests/test_spec_coverage.py +74 -0
- thrustlab-0.1.0/tests/test_webhooks.py +179 -0
- thrustlab-0.1.0/thrustlab/__init__.py +23 -0
- thrustlab-0.1.0/thrustlab/_async_polling.py +47 -0
- thrustlab-0.1.0/thrustlab/_http.py +166 -0
- thrustlab-0.1.0/thrustlab/_models/__init__.py +211 -0
- thrustlab-0.1.0/thrustlab/_models/api_key.py +184 -0
- thrustlab-0.1.0/thrustlab/_models/api_key_create.py +74 -0
- thrustlab-0.1.0/thrustlab/_models/api_key_with_secret.py +192 -0
- thrustlab-0.1.0/thrustlab/_models/billing_portal_session_resource.py +74 -0
- thrustlab-0.1.0/thrustlab/_models/checkout_session_resource.py +94 -0
- thrustlab-0.1.0/thrustlab/_models/component_brand.py +80 -0
- thrustlab-0.1.0/thrustlab/_models/component_brands_response.py +108 -0
- thrustlab-0.1.0/thrustlab/_models/component_create_body.py +83 -0
- thrustlab-0.1.0/thrustlab/_models/component_create_body_spec_json.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/component_list_response.py +108 -0
- thrustlab-0.1.0/thrustlab/_models/component_patch.py +111 -0
- thrustlab-0.1.0/thrustlab/_models/component_patch_spec_json_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/component_resource.py +188 -0
- thrustlab-0.1.0/thrustlab/_models/component_resource_spec_json.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/create_webhook_endpoint_v1_webhook_endpoints_post_response_create_webhook_endpoint_v1_webhook_endpoints_post.py +50 -0
- thrustlab-0.1.0/thrustlab/_models/credit_balance_resource.py +135 -0
- thrustlab-0.1.0/thrustlab/_models/credit_bucket_breakdown.py +92 -0
- thrustlab-0.1.0/thrustlab/_models/credit_bucket_breakdown_type.py +9 -0
- thrustlab-0.1.0/thrustlab/_models/credit_usage_event_resource.py +154 -0
- thrustlab-0.1.0/thrustlab/_models/credit_usage_event_resource_credit_type.py +9 -0
- thrustlab-0.1.0/thrustlab/_models/credit_usage_event_resource_type.py +16 -0
- thrustlab-0.1.0/thrustlab/_models/credit_usage_list_response.py +116 -0
- thrustlab-0.1.0/thrustlab/_models/credit_usage_related_resource.py +71 -0
- thrustlab-0.1.0/thrustlab/_models/credit_usage_related_resource_object.py +9 -0
- thrustlab-0.1.0/thrustlab/_models/fire_test_event_v1_webhook_endpoints_public_id_test_post_response_fire_test_event_v1_webhook_endpoints_public_id_test_post.py +48 -0
- thrustlab-0.1.0/thrustlab/_models/get_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_get_response_get_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_get.py +49 -0
- thrustlab-0.1.0/thrustlab/_models/get_event_v1_events_event_id_get_response_get_event_v1_events_event_id_get.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/get_webhook_endpoint_v1_webhook_endpoints_public_id_get_response_get_webhook_endpoint_v1_webhook_endpoints_public_id_get.py +48 -0
- thrustlab-0.1.0/thrustlab/_models/http_validation_error.py +79 -0
- thrustlab-0.1.0/thrustlab/_models/list_deliveries_v1_webhook_endpoints_public_id_deliveries_get_response_list_deliveries_v1_webhook_endpoints_public_id_deliveries_get.py +49 -0
- thrustlab-0.1.0/thrustlab/_models/list_events_v1_events_get_response_list_events_v1_events_get.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/list_response_api_key.py +116 -0
- thrustlab-0.1.0/thrustlab/_models/list_webhook_endpoints_v1_webhook_endpoints_get_response_list_webhook_endpoints_v1_webhook_endpoints_get.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/patch_webhook_endpoint_v1_webhook_endpoints_public_id_patch_response_patch_webhook_endpoint_v1_webhook_endpoints_public_id_patch.py +49 -0
- thrustlab-0.1.0/thrustlab/_models/portal_session_in.py +42 -0
- thrustlab-0.1.0/thrustlab/_models/project_create_body.py +103 -0
- thrustlab-0.1.0/thrustlab/_models/project_list_response.py +108 -0
- thrustlab-0.1.0/thrustlab/_models/project_patch.py +113 -0
- thrustlab-0.1.0/thrustlab/_models/project_resource.py +127 -0
- thrustlab-0.1.0/thrustlab/_models/retry_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_retry_post_response_retry_delivery_v1_webhook_endpoints_public_id_deliveries_delivery_public_id_retry_post.py +49 -0
- thrustlab-0.1.0/thrustlab/_models/rotate_webhook_secret_v1_webhook_endpoints_public_id_rotate_secret_post_response_rotate_webhook_secret_v1_webhook_endpoints_public_id_rotate_secret_post.py +49 -0
- thrustlab-0.1.0/thrustlab/_models/rotor_group_in.py +107 -0
- thrustlab-0.1.0/thrustlab/_models/rotor_group_resource.py +97 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_cancel_body.py +24 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_create_body.py +128 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_inputs.py +66 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_list_response.py +88 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_patch.py +121 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_patch_plot_config_json_type_0_item.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_resource.py +283 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_resource_error_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_resource_plot_config_json_type_0_item.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/simulation_resource_result_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/starred_component_create_body.py +80 -0
- thrustlab-0.1.0/thrustlab/_models/starred_component_list_response.py +108 -0
- thrustlab-0.1.0/thrustlab/_models/starred_component_resource.py +106 -0
- thrustlab-0.1.0/thrustlab/_models/submission_create_body.py +121 -0
- thrustlab-0.1.0/thrustlab/_models/submission_create_body_data_json.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/submission_list_response.py +108 -0
- thrustlab-0.1.0/thrustlab/_models/submission_patch.py +174 -0
- thrustlab-0.1.0/thrustlab/_models/submission_patch_data_json_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/submission_resource.py +197 -0
- thrustlab-0.1.0/thrustlab/_models/submission_resource_data_json.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/subscription_create.py +58 -0
- thrustlab-0.1.0/thrustlab/_models/subscription_resource.py +168 -0
- thrustlab-0.1.0/thrustlab/_models/subscription_resource_status.py +12 -0
- thrustlab-0.1.0/thrustlab/_models/subscription_resource_tier.py +12 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_cancel_body.py +42 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_config_in.py +172 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_create_body.py +138 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_inputs.py +66 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_list_response.py +88 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_param_range_in.py +136 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_param_range_in_mode.py +9 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_patch.py +121 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_patch_plot_config_json_type_0_item.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_point_list_response.py +88 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_point_resource.py +103 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_point_resource_inputs.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_point_resource_rotors_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_resource.py +327 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_resource_error_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_resource_plot_config_json_type_0_item.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_resource_summary_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_resource_sweep_config_type_0.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/sweep_rotor_group_in.py +95 -0
- thrustlab-0.1.0/thrustlab/_models/types.py +55 -0
- thrustlab-0.1.0/thrustlab/_models/user_patch.py +73 -0
- thrustlab-0.1.0/thrustlab/_models/user_resource.py +170 -0
- thrustlab-0.1.0/thrustlab/_models/v1_health_v1_health_get_response_v1_health_v1_health_get.py +46 -0
- thrustlab-0.1.0/thrustlab/_models/validation_error.py +90 -0
- thrustlab-0.1.0/thrustlab/_models/webhook_endpoint_create.py +91 -0
- thrustlab-0.1.0/thrustlab/_models/webhook_endpoint_patch.py +144 -0
- thrustlab-0.1.0/thrustlab/_pagination.py +109 -0
- thrustlab-0.1.0/thrustlab/_version.py +3 -0
- thrustlab-0.1.0/thrustlab/client.py +100 -0
- thrustlab-0.1.0/thrustlab/exceptions.py +121 -0
- thrustlab-0.1.0/thrustlab/resources/__init__.py +1 -0
- thrustlab-0.1.0/thrustlab/resources/_base.py +12 -0
- thrustlab-0.1.0/thrustlab/resources/api_keys.py +43 -0
- thrustlab-0.1.0/thrustlab/resources/components.py +60 -0
- thrustlab-0.1.0/thrustlab/resources/credits.py +40 -0
- thrustlab-0.1.0/thrustlab/resources/projects.py +54 -0
- thrustlab-0.1.0/thrustlab/resources/simulations.py +74 -0
- thrustlab-0.1.0/thrustlab/resources/starred_components.py +57 -0
- thrustlab-0.1.0/thrustlab/resources/submissions.py +44 -0
- thrustlab-0.1.0/thrustlab/resources/subscriptions.py +42 -0
- thrustlab-0.1.0/thrustlab/resources/sweeps.py +96 -0
- thrustlab-0.1.0/thrustlab/resources/users.py +13 -0
- thrustlab-0.1.0/thrustlab/resources/webhook_endpoints.py +138 -0
- thrustlab-0.1.0/thrustlab/webhooks.py +121 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `thrustlab` Python SDK are documented here. The
|
|
4
|
+
project follows [semantic versioning](https://semver.org/) independent of the
|
|
5
|
+
ThrustLab API version (which is permanently `/v1/`).
|
|
6
|
+
|
|
7
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.1.0] - 2026-04-27
|
|
12
|
+
|
|
13
|
+
Initial public beta release of the official ThrustLab Python SDK.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Full coverage of the `/v1/` API surface across **12 resource modules**:
|
|
18
|
+
`api_keys`, `users`, `projects`, `simulations`, `sweeps`, `components`,
|
|
19
|
+
`submissions`, `starred_components`, `geometry`, `credits`,
|
|
20
|
+
`subscriptions`, `webhook_endpoints`.
|
|
21
|
+
- Sync transport (`httpx.Client`) with bearer-token authentication.
|
|
22
|
+
- Auto-generated `Idempotency-Key` header on every mutating request (override
|
|
23
|
+
via `idempotency_key=` keyword argument).
|
|
24
|
+
- Exponential backoff + jitter retry on 429, 5xx, and transient network
|
|
25
|
+
errors (`max_retries=3` default, configurable per client instance).
|
|
26
|
+
- `CursorPager[T]` lazy iterator for cursor-paginated list endpoints;
|
|
27
|
+
transparent multi-page iteration.
|
|
28
|
+
- `wait()` helpers on `simulations` and `sweeps` for polling async resources
|
|
29
|
+
to terminal state (`succeeded` / `failed` / `cancelled` / `timed_out`).
|
|
30
|
+
- `Webhook.verify()` for HMAC SHA256 webhook signature verification with
|
|
31
|
+
configurable timestamp tolerance (default 300 s).
|
|
32
|
+
- Stripe-style typed exception hierarchy:
|
|
33
|
+
`ThrustlabError` → `APIError` → `AuthenticationError`, `PermissionError`,
|
|
34
|
+
`NotFoundError`, `ValidationError`, `ConflictError`, `RateLimitError`,
|
|
35
|
+
`APIServerError`; plus `NetworkError`, `SignatureVerificationError`,
|
|
36
|
+
`ConfigurationError`.
|
|
37
|
+
- Pydantic v2 models generated from `openapi.json` via
|
|
38
|
+
`openapi-python-client`; used internally for request/response shaping.
|
|
39
|
+
- Configuration via constructor kwargs (`api_key`, `base_url`, `timeout`,
|
|
40
|
+
`max_retries`) or env vars (`THRUSTLAB_API_KEY`, `THRUSTLAB_BASE_URL`).
|
|
41
|
+
- 103 unit + integration tests (pytest).
|
|
42
|
+
|
|
43
|
+
### Out of scope (deferred)
|
|
44
|
+
|
|
45
|
+
- Async transport (`httpx.AsyncClient`). Planned for v0.2.
|
|
46
|
+
- Streaming response support.
|
|
47
|
+
- Python 3.9 compatibility.
|
|
48
|
+
- CLI tool (`thrustlab` command).
|
|
49
|
+
- SDKs in other languages.
|
|
50
|
+
|
|
51
|
+
## Pre-release rehearsal procedure
|
|
52
|
+
|
|
53
|
+
Before tagging v0.1.0, rehearse with:
|
|
54
|
+
|
|
55
|
+
1. Create a short-lived release-rehearsal branch from master.
|
|
56
|
+
2. Tag `sdk-py-v0.0.1-rc1` and push it.
|
|
57
|
+
3. The CI workflow runs build + TestPyPI publish only (gate step enforces
|
|
58
|
+
ancestry before either publish step fires).
|
|
59
|
+
4. Delete the rehearsal tag + branch after validation.
|
|
60
|
+
5. Tag `sdk-py-v0.1.0` from the actual master-merge commit for production publish.
|
thrustlab-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ThrustLab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
thrustlab-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.PHONY: codegen test test-unit test-integration build publish clean check
|
|
2
|
+
|
|
3
|
+
codegen:
|
|
4
|
+
python scripts/regen_models.py
|
|
5
|
+
|
|
6
|
+
test: test-unit test-integration
|
|
7
|
+
|
|
8
|
+
test-unit:
|
|
9
|
+
pytest tests/ -m "not integration" -q
|
|
10
|
+
|
|
11
|
+
test-integration:
|
|
12
|
+
pytest tests/integration/ -q
|
|
13
|
+
|
|
14
|
+
build:
|
|
15
|
+
python -m build
|
|
16
|
+
|
|
17
|
+
check:
|
|
18
|
+
twine check dist/*
|
|
19
|
+
|
|
20
|
+
publish:
|
|
21
|
+
twine upload dist/*
|
|
22
|
+
|
|
23
|
+
clean:
|
|
24
|
+
rm -rf dist build *.egg-info
|
thrustlab-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: thrustlab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the ThrustLab API
|
|
5
|
+
Project-URL: Homepage, https://thrustlab.com
|
|
6
|
+
Project-URL: Documentation, https://thrustlab.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/<owner>/thrustlab
|
|
8
|
+
Project-URL: Changelog, https://thrustlab.com/docs/changelog
|
|
9
|
+
Author-email: ThrustLab <support@thrustlab.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: attrs>=23.0
|
|
22
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
23
|
+
Requires-Dist: pydantic<3.0,>=2.7
|
|
24
|
+
Requires-Dist: python-dateutil>=2.8
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: openapi-python-client>=0.21; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
29
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# thrustlab — official Python SDK for the ThrustLab API
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/thrustlab/)
|
|
35
|
+
[](https://pypi.org/project/thrustlab/)
|
|
36
|
+
[](https://github.com/kylebedrich/thrustlab/blob/master/backend/sdks/python/LICENSE)
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install thrustlab
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Requires Python 3.10+.
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from thrustlab import Client
|
|
50
|
+
|
|
51
|
+
client = Client(api_key="key_...") # or set $THRUSTLAB_API_KEY
|
|
52
|
+
|
|
53
|
+
# Create a project
|
|
54
|
+
project = client.projects.create(name="my project")
|
|
55
|
+
|
|
56
|
+
# Run a simulation and wait for the result
|
|
57
|
+
sim = client.simulations.create(
|
|
58
|
+
project_id=project["id"],
|
|
59
|
+
motor="comp_motor_xxx",
|
|
60
|
+
propeller="comp_prop_xxx",
|
|
61
|
+
battery="comp_batt_xxx",
|
|
62
|
+
throttle=0.7,
|
|
63
|
+
)
|
|
64
|
+
result = client.simulations.wait(sim["id"], timeout=300)
|
|
65
|
+
print(result)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
| Setting | Constructor arg | Env var | Default |
|
|
71
|
+
|--------------|------------------|-----------------------|--------------------------|
|
|
72
|
+
| API key | `api_key=` | `THRUSTLAB_API_KEY` | (required) |
|
|
73
|
+
| Base URL | `base_url=` | `THRUSTLAB_BASE_URL` | `https://thrustlab.com` |
|
|
74
|
+
| Timeout | `timeout=` | — | `30` (seconds) |
|
|
75
|
+
| Max retries | `max_retries=` | — | `3` |
|
|
76
|
+
|
|
77
|
+
## Resources
|
|
78
|
+
|
|
79
|
+
Every `/v1/` route family is exposed as an attribute on the client:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
client.api_keys
|
|
83
|
+
client.users
|
|
84
|
+
client.projects
|
|
85
|
+
client.simulations
|
|
86
|
+
client.sweeps
|
|
87
|
+
client.components
|
|
88
|
+
client.submissions
|
|
89
|
+
client.starred_components
|
|
90
|
+
client.geometry
|
|
91
|
+
client.credits
|
|
92
|
+
client.subscriptions
|
|
93
|
+
client.webhook_endpoints
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Error handling
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from thrustlab import Client
|
|
100
|
+
from thrustlab.exceptions import (
|
|
101
|
+
AuthenticationError,
|
|
102
|
+
ValidationError,
|
|
103
|
+
RateLimitError,
|
|
104
|
+
NotFoundError,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
client = Client()
|
|
108
|
+
try:
|
|
109
|
+
client.simulations.create(project_id="proj_xxx", motor="comp_xxx", ...)
|
|
110
|
+
except ValidationError as exc:
|
|
111
|
+
print(f"bad request: {exc.code} ({exc.param}): {exc.message}")
|
|
112
|
+
except RateLimitError as exc:
|
|
113
|
+
print(f"rate limited; retry after {exc.retry_after}s")
|
|
114
|
+
except NotFoundError as exc:
|
|
115
|
+
print(f"not found: {exc.message}")
|
|
116
|
+
except AuthenticationError as exc:
|
|
117
|
+
print(f"auth failed: {exc.message}")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Every error carries `.code`, `.type`, `.request_id`, `.http_status`, and
|
|
121
|
+
`.message`. `ValidationError` additionally exposes `.param`. See
|
|
122
|
+
[thrustlab.com/docs/guides/errors](https://thrustlab.com/docs/guides/errors)
|
|
123
|
+
for the full code reference.
|
|
124
|
+
|
|
125
|
+
## Pagination
|
|
126
|
+
|
|
127
|
+
List endpoints return a `CursorPager` — a lazy iterator that fetches the
|
|
128
|
+
next page only when needed:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# Iterate all pages automatically
|
|
132
|
+
for project in client.projects.list():
|
|
133
|
+
print(project["id"])
|
|
134
|
+
|
|
135
|
+
# Materialise the first page only
|
|
136
|
+
first_page = list(client.projects.list(limit=20))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Async polling
|
|
140
|
+
|
|
141
|
+
`simulations.wait()` and `sweeps.wait()` block until the resource reaches a
|
|
142
|
+
terminal state (`succeeded`, `failed`, or `cancelled`) or the timeout fires:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
sweep = client.sweeps.create(...)
|
|
146
|
+
result = client.sweeps.wait(sweep["id"], timeout=600, poll_interval=2.0)
|
|
147
|
+
if result["status"] == "succeeded":
|
|
148
|
+
print(result["outputs"])
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Webhooks
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from thrustlab import Webhook
|
|
155
|
+
from thrustlab.exceptions import SignatureVerificationError
|
|
156
|
+
|
|
157
|
+
WEBHOOK_SECRET = "whsec_..."
|
|
158
|
+
|
|
159
|
+
@app.post("/webhooks/thrustlab")
|
|
160
|
+
async def handle(request):
|
|
161
|
+
payload = await request.body()
|
|
162
|
+
sig = request.headers["Thrustlab-Signature"]
|
|
163
|
+
try:
|
|
164
|
+
event = Webhook.verify(payload, sig, WEBHOOK_SECRET)
|
|
165
|
+
except SignatureVerificationError:
|
|
166
|
+
return Response(status_code=400)
|
|
167
|
+
if event.type == "simulation.succeeded":
|
|
168
|
+
print(event.data)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Retries
|
|
172
|
+
|
|
173
|
+
The client automatically retries on 429 (rate limit) and 5xx responses using
|
|
174
|
+
exponential backoff with jitter. Set `max_retries=0` to disable:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
client = Client(max_retries=0)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Links
|
|
181
|
+
|
|
182
|
+
- Documentation: https://thrustlab.com/docs
|
|
183
|
+
- API reference: https://thrustlab.com/docs/reference
|
|
184
|
+
- SDK guide: https://thrustlab.com/docs/sdk/python
|
|
185
|
+
- Changelog: https://thrustlab.com/docs/changelog
|
|
186
|
+
- Issue tracker: https://github.com/kylebedrich/thrustlab/issues
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# thrustlab — official Python SDK for the ThrustLab API
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/thrustlab/)
|
|
4
|
+
[](https://pypi.org/project/thrustlab/)
|
|
5
|
+
[](https://github.com/kylebedrich/thrustlab/blob/master/backend/sdks/python/LICENSE)
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install thrustlab
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Python 3.10+.
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from thrustlab import Client
|
|
19
|
+
|
|
20
|
+
client = Client(api_key="key_...") # or set $THRUSTLAB_API_KEY
|
|
21
|
+
|
|
22
|
+
# Create a project
|
|
23
|
+
project = client.projects.create(name="my project")
|
|
24
|
+
|
|
25
|
+
# Run a simulation and wait for the result
|
|
26
|
+
sim = client.simulations.create(
|
|
27
|
+
project_id=project["id"],
|
|
28
|
+
motor="comp_motor_xxx",
|
|
29
|
+
propeller="comp_prop_xxx",
|
|
30
|
+
battery="comp_batt_xxx",
|
|
31
|
+
throttle=0.7,
|
|
32
|
+
)
|
|
33
|
+
result = client.simulations.wait(sim["id"], timeout=300)
|
|
34
|
+
print(result)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
| Setting | Constructor arg | Env var | Default |
|
|
40
|
+
|--------------|------------------|-----------------------|--------------------------|
|
|
41
|
+
| API key | `api_key=` | `THRUSTLAB_API_KEY` | (required) |
|
|
42
|
+
| Base URL | `base_url=` | `THRUSTLAB_BASE_URL` | `https://thrustlab.com` |
|
|
43
|
+
| Timeout | `timeout=` | — | `30` (seconds) |
|
|
44
|
+
| Max retries | `max_retries=` | — | `3` |
|
|
45
|
+
|
|
46
|
+
## Resources
|
|
47
|
+
|
|
48
|
+
Every `/v1/` route family is exposed as an attribute on the client:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
client.api_keys
|
|
52
|
+
client.users
|
|
53
|
+
client.projects
|
|
54
|
+
client.simulations
|
|
55
|
+
client.sweeps
|
|
56
|
+
client.components
|
|
57
|
+
client.submissions
|
|
58
|
+
client.starred_components
|
|
59
|
+
client.geometry
|
|
60
|
+
client.credits
|
|
61
|
+
client.subscriptions
|
|
62
|
+
client.webhook_endpoints
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Error handling
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from thrustlab import Client
|
|
69
|
+
from thrustlab.exceptions import (
|
|
70
|
+
AuthenticationError,
|
|
71
|
+
ValidationError,
|
|
72
|
+
RateLimitError,
|
|
73
|
+
NotFoundError,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
client = Client()
|
|
77
|
+
try:
|
|
78
|
+
client.simulations.create(project_id="proj_xxx", motor="comp_xxx", ...)
|
|
79
|
+
except ValidationError as exc:
|
|
80
|
+
print(f"bad request: {exc.code} ({exc.param}): {exc.message}")
|
|
81
|
+
except RateLimitError as exc:
|
|
82
|
+
print(f"rate limited; retry after {exc.retry_after}s")
|
|
83
|
+
except NotFoundError as exc:
|
|
84
|
+
print(f"not found: {exc.message}")
|
|
85
|
+
except AuthenticationError as exc:
|
|
86
|
+
print(f"auth failed: {exc.message}")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Every error carries `.code`, `.type`, `.request_id`, `.http_status`, and
|
|
90
|
+
`.message`. `ValidationError` additionally exposes `.param`. See
|
|
91
|
+
[thrustlab.com/docs/guides/errors](https://thrustlab.com/docs/guides/errors)
|
|
92
|
+
for the full code reference.
|
|
93
|
+
|
|
94
|
+
## Pagination
|
|
95
|
+
|
|
96
|
+
List endpoints return a `CursorPager` — a lazy iterator that fetches the
|
|
97
|
+
next page only when needed:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# Iterate all pages automatically
|
|
101
|
+
for project in client.projects.list():
|
|
102
|
+
print(project["id"])
|
|
103
|
+
|
|
104
|
+
# Materialise the first page only
|
|
105
|
+
first_page = list(client.projects.list(limit=20))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Async polling
|
|
109
|
+
|
|
110
|
+
`simulations.wait()` and `sweeps.wait()` block until the resource reaches a
|
|
111
|
+
terminal state (`succeeded`, `failed`, or `cancelled`) or the timeout fires:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
sweep = client.sweeps.create(...)
|
|
115
|
+
result = client.sweeps.wait(sweep["id"], timeout=600, poll_interval=2.0)
|
|
116
|
+
if result["status"] == "succeeded":
|
|
117
|
+
print(result["outputs"])
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Webhooks
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from thrustlab import Webhook
|
|
124
|
+
from thrustlab.exceptions import SignatureVerificationError
|
|
125
|
+
|
|
126
|
+
WEBHOOK_SECRET = "whsec_..."
|
|
127
|
+
|
|
128
|
+
@app.post("/webhooks/thrustlab")
|
|
129
|
+
async def handle(request):
|
|
130
|
+
payload = await request.body()
|
|
131
|
+
sig = request.headers["Thrustlab-Signature"]
|
|
132
|
+
try:
|
|
133
|
+
event = Webhook.verify(payload, sig, WEBHOOK_SECRET)
|
|
134
|
+
except SignatureVerificationError:
|
|
135
|
+
return Response(status_code=400)
|
|
136
|
+
if event.type == "simulation.succeeded":
|
|
137
|
+
print(event.data)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Retries
|
|
141
|
+
|
|
142
|
+
The client automatically retries on 429 (rate limit) and 5xx responses using
|
|
143
|
+
exponential backoff with jitter. Set `max_retries=0` to disable:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
client = Client(max_retries=0)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Links
|
|
150
|
+
|
|
151
|
+
- Documentation: https://thrustlab.com/docs
|
|
152
|
+
- API reference: https://thrustlab.com/docs/reference
|
|
153
|
+
- SDK guide: https://thrustlab.com/docs/sdk/python
|
|
154
|
+
- Changelog: https://thrustlab.com/docs/changelog
|
|
155
|
+
- Issue tracker: https://github.com/kylebedrich/thrustlab/issues
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Quickstart: install thrustlab, set $THRUSTLAB_API_KEY, run this file."""
|
|
2
|
+
|
|
3
|
+
from itertools import islice
|
|
4
|
+
|
|
5
|
+
from thrustlab import Client
|
|
6
|
+
|
|
7
|
+
client = Client() # reads $THRUSTLAB_API_KEY
|
|
8
|
+
|
|
9
|
+
project = client.projects.create(name="my first project")
|
|
10
|
+
print(f"created project {project['id']}")
|
|
11
|
+
|
|
12
|
+
print("first 5 motors:")
|
|
13
|
+
for component in islice(client.components.list(type="motor"), 5):
|
|
14
|
+
brand = component["spec_json"].get("brand", "?")
|
|
15
|
+
print(f" {component['id']} — {brand} {component['name']}")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from thrustlab import Client
|
|
2
|
+
|
|
3
|
+
client = Client()
|
|
4
|
+
|
|
5
|
+
sim = client.simulations.create(
|
|
6
|
+
project_id="proj_xxx",
|
|
7
|
+
battery_component_id="comp_batt_xxx",
|
|
8
|
+
airspeed_m_s=0.0,
|
|
9
|
+
density_kg_m3=1.225,
|
|
10
|
+
battery_charge_pct=100,
|
|
11
|
+
rotor_groups=[
|
|
12
|
+
{
|
|
13
|
+
"label": "main",
|
|
14
|
+
"count": 4,
|
|
15
|
+
"motor_component_id": "comp_motor_xxx",
|
|
16
|
+
"propeller_component_id": "comp_prop_xxx",
|
|
17
|
+
"throttle_pct": 70,
|
|
18
|
+
"esc_resistance_mohm": 0,
|
|
19
|
+
"esc_motor_wire_resistance_mohm": 0,
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
)
|
|
23
|
+
print(f"created {sim['id']}, status={sim['status']}")
|
|
24
|
+
result = client.simulations.wait(sim["id"], timeout=300)
|
|
25
|
+
print(f"final status: {result['status']}")
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from thrustlab import Client
|
|
2
|
+
|
|
3
|
+
client = Client()
|
|
4
|
+
|
|
5
|
+
# Assuming simulation is already running
|
|
6
|
+
sim_id = "sim_xxx"
|
|
7
|
+
result = client.simulations.wait(sim_id, timeout=300, poll_interval=2.0)
|
|
8
|
+
print(f"simulation {sim_id} reached terminal state: {result['status']}")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from thrustlab import Client
|
|
2
|
+
|
|
3
|
+
client = Client()
|
|
4
|
+
|
|
5
|
+
sweep = client.sweeps.create(
|
|
6
|
+
project_id="proj_xxx",
|
|
7
|
+
battery_component_id="comp_batt_xxx",
|
|
8
|
+
airspeed_m_s=0.0,
|
|
9
|
+
density_kg_m3=1.225,
|
|
10
|
+
battery_charge_pct=100,
|
|
11
|
+
rotor_groups=[
|
|
12
|
+
{
|
|
13
|
+
"label": "main",
|
|
14
|
+
"count": 4,
|
|
15
|
+
"motor_component_id": "comp_motor_xxx",
|
|
16
|
+
"propeller_component_id": "comp_prop_xxx",
|
|
17
|
+
"throttle_pct": 70,
|
|
18
|
+
"esc_resistance_mohm": 0,
|
|
19
|
+
"esc_motor_wire_resistance_mohm": 0,
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
sweep={
|
|
23
|
+
"rotor_sweep_mask": [True],
|
|
24
|
+
"throttle": {"mode": "range", "start": 10, "stop": 100, "steps": 10},
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
print(f"created {sweep['id']}")
|
|
28
|
+
result = client.sweeps.wait(sweep["id"], timeout=600)
|
|
29
|
+
points = list(client.sweeps.list_points(sweep["id"]))
|
|
30
|
+
print(f"sweep done with {len(points)} points; status={result['status']}")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Example FastAPI webhook handler verifying incoming events."""
|
|
2
|
+
import os
|
|
3
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
4
|
+
from thrustlab import Webhook
|
|
5
|
+
from thrustlab.exceptions import SignatureVerificationError
|
|
6
|
+
|
|
7
|
+
app = FastAPI()
|
|
8
|
+
WEBHOOK_SECRET = os.environ["THRUSTLAB_WEBHOOK_SECRET"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.post("/webhooks/thrustlab")
|
|
12
|
+
async def thrustlab_webhook(request: Request):
|
|
13
|
+
payload = await request.body()
|
|
14
|
+
sig = request.headers.get("Thrustlab-Signature", "")
|
|
15
|
+
try:
|
|
16
|
+
event = Webhook.verify(payload, sig, WEBHOOK_SECRET)
|
|
17
|
+
except SignatureVerificationError as exc:
|
|
18
|
+
raise HTTPException(status_code=400, detail=str(exc))
|
|
19
|
+
|
|
20
|
+
if event.type == "simulation.succeeded":
|
|
21
|
+
sim_id = event.data["id"]
|
|
22
|
+
# ... do work ...
|
|
23
|
+
|
|
24
|
+
return {"received": True}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "thrustlab"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Official Python SDK for the ThrustLab API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "ThrustLab", email = "support@thrustlab.com" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.27,<1.0",
|
|
25
|
+
"pydantic>=2.7,<3.0",
|
|
26
|
+
"attrs>=23.0",
|
|
27
|
+
"python-dateutil>=2.8",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=8",
|
|
33
|
+
"pytest-asyncio>=0.23",
|
|
34
|
+
"respx>=0.21",
|
|
35
|
+
"openapi-python-client>=0.21",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://thrustlab.com"
|
|
40
|
+
Documentation = "https://thrustlab.com/docs"
|
|
41
|
+
Repository = "https://github.com/<owner>/thrustlab"
|
|
42
|
+
Changelog = "https://thrustlab.com/docs/changelog"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.version]
|
|
45
|
+
path = "thrustlab/_version.py"
|