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.
Files changed (152) hide show
  1. thrustlab-0.1.0/.gitignore +8 -0
  2. thrustlab-0.1.0/CHANGELOG.md +60 -0
  3. thrustlab-0.1.0/LICENSE +21 -0
  4. thrustlab-0.1.0/Makefile +24 -0
  5. thrustlab-0.1.0/PKG-INFO +190 -0
  6. thrustlab-0.1.0/README.md +159 -0
  7. thrustlab-0.1.0/examples/projects/create.py +5 -0
  8. thrustlab-0.1.0/examples/projects/list.py +7 -0
  9. thrustlab-0.1.0/examples/projects/retrieve.py +5 -0
  10. thrustlab-0.1.0/examples/quickstart.py +15 -0
  11. thrustlab-0.1.0/examples/simulations/run_sync.py +25 -0
  12. thrustlab-0.1.0/examples/simulations/wait.py +8 -0
  13. thrustlab-0.1.0/examples/sweeps/run_and_poll.py +30 -0
  14. thrustlab-0.1.0/examples/webhooks/verify_handler.py +24 -0
  15. thrustlab-0.1.0/pyproject.toml +45 -0
  16. thrustlab-0.1.0/scripts/regen_models.py +147 -0
  17. thrustlab-0.1.0/tests/__init__.py +0 -0
  18. thrustlab-0.1.0/tests/conftest.py +16 -0
  19. thrustlab-0.1.0/tests/test_async_polling.py +163 -0
  20. thrustlab-0.1.0/tests/test_client.py +33 -0
  21. thrustlab-0.1.0/tests/test_exceptions.py +43 -0
  22. thrustlab-0.1.0/tests/test_http.py +133 -0
  23. thrustlab-0.1.0/tests/test_pagination.py +111 -0
  24. thrustlab-0.1.0/tests/test_resources/__init__.py +0 -0
  25. thrustlab-0.1.0/tests/test_resources/test_api_keys.py +45 -0
  26. thrustlab-0.1.0/tests/test_resources/test_components.py +61 -0
  27. thrustlab-0.1.0/tests/test_resources/test_credits.py +27 -0
  28. thrustlab-0.1.0/tests/test_resources/test_projects.py +58 -0
  29. thrustlab-0.1.0/tests/test_resources/test_simulations.py +64 -0
  30. thrustlab-0.1.0/tests/test_resources/test_starred_components.py +37 -0
  31. thrustlab-0.1.0/tests/test_resources/test_submissions.py +38 -0
  32. thrustlab-0.1.0/tests/test_resources/test_subscriptions.py +45 -0
  33. thrustlab-0.1.0/tests/test_resources/test_sweeps.py +74 -0
  34. thrustlab-0.1.0/tests/test_resources/test_users.py +13 -0
  35. thrustlab-0.1.0/tests/test_resources/test_webhook_endpoints.py +103 -0
  36. thrustlab-0.1.0/tests/test_spec_coverage.py +74 -0
  37. thrustlab-0.1.0/tests/test_webhooks.py +179 -0
  38. thrustlab-0.1.0/thrustlab/__init__.py +23 -0
  39. thrustlab-0.1.0/thrustlab/_async_polling.py +47 -0
  40. thrustlab-0.1.0/thrustlab/_http.py +166 -0
  41. thrustlab-0.1.0/thrustlab/_models/__init__.py +211 -0
  42. thrustlab-0.1.0/thrustlab/_models/api_key.py +184 -0
  43. thrustlab-0.1.0/thrustlab/_models/api_key_create.py +74 -0
  44. thrustlab-0.1.0/thrustlab/_models/api_key_with_secret.py +192 -0
  45. thrustlab-0.1.0/thrustlab/_models/billing_portal_session_resource.py +74 -0
  46. thrustlab-0.1.0/thrustlab/_models/checkout_session_resource.py +94 -0
  47. thrustlab-0.1.0/thrustlab/_models/component_brand.py +80 -0
  48. thrustlab-0.1.0/thrustlab/_models/component_brands_response.py +108 -0
  49. thrustlab-0.1.0/thrustlab/_models/component_create_body.py +83 -0
  50. thrustlab-0.1.0/thrustlab/_models/component_create_body_spec_json.py +46 -0
  51. thrustlab-0.1.0/thrustlab/_models/component_list_response.py +108 -0
  52. thrustlab-0.1.0/thrustlab/_models/component_patch.py +111 -0
  53. thrustlab-0.1.0/thrustlab/_models/component_patch_spec_json_type_0.py +46 -0
  54. thrustlab-0.1.0/thrustlab/_models/component_resource.py +188 -0
  55. thrustlab-0.1.0/thrustlab/_models/component_resource_spec_json.py +46 -0
  56. thrustlab-0.1.0/thrustlab/_models/create_webhook_endpoint_v1_webhook_endpoints_post_response_create_webhook_endpoint_v1_webhook_endpoints_post.py +50 -0
  57. thrustlab-0.1.0/thrustlab/_models/credit_balance_resource.py +135 -0
  58. thrustlab-0.1.0/thrustlab/_models/credit_bucket_breakdown.py +92 -0
  59. thrustlab-0.1.0/thrustlab/_models/credit_bucket_breakdown_type.py +9 -0
  60. thrustlab-0.1.0/thrustlab/_models/credit_usage_event_resource.py +154 -0
  61. thrustlab-0.1.0/thrustlab/_models/credit_usage_event_resource_credit_type.py +9 -0
  62. thrustlab-0.1.0/thrustlab/_models/credit_usage_event_resource_type.py +16 -0
  63. thrustlab-0.1.0/thrustlab/_models/credit_usage_list_response.py +116 -0
  64. thrustlab-0.1.0/thrustlab/_models/credit_usage_related_resource.py +71 -0
  65. thrustlab-0.1.0/thrustlab/_models/credit_usage_related_resource_object.py +9 -0
  66. 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
  67. 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
  68. thrustlab-0.1.0/thrustlab/_models/get_event_v1_events_event_id_get_response_get_event_v1_events_event_id_get.py +46 -0
  69. 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
  70. thrustlab-0.1.0/thrustlab/_models/http_validation_error.py +79 -0
  71. 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
  72. thrustlab-0.1.0/thrustlab/_models/list_events_v1_events_get_response_list_events_v1_events_get.py +46 -0
  73. thrustlab-0.1.0/thrustlab/_models/list_response_api_key.py +116 -0
  74. thrustlab-0.1.0/thrustlab/_models/list_webhook_endpoints_v1_webhook_endpoints_get_response_list_webhook_endpoints_v1_webhook_endpoints_get.py +46 -0
  75. 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
  76. thrustlab-0.1.0/thrustlab/_models/portal_session_in.py +42 -0
  77. thrustlab-0.1.0/thrustlab/_models/project_create_body.py +103 -0
  78. thrustlab-0.1.0/thrustlab/_models/project_list_response.py +108 -0
  79. thrustlab-0.1.0/thrustlab/_models/project_patch.py +113 -0
  80. thrustlab-0.1.0/thrustlab/_models/project_resource.py +127 -0
  81. 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
  82. 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
  83. thrustlab-0.1.0/thrustlab/_models/rotor_group_in.py +107 -0
  84. thrustlab-0.1.0/thrustlab/_models/rotor_group_resource.py +97 -0
  85. thrustlab-0.1.0/thrustlab/_models/simulation_cancel_body.py +24 -0
  86. thrustlab-0.1.0/thrustlab/_models/simulation_create_body.py +128 -0
  87. thrustlab-0.1.0/thrustlab/_models/simulation_inputs.py +66 -0
  88. thrustlab-0.1.0/thrustlab/_models/simulation_list_response.py +88 -0
  89. thrustlab-0.1.0/thrustlab/_models/simulation_patch.py +121 -0
  90. thrustlab-0.1.0/thrustlab/_models/simulation_patch_plot_config_json_type_0_item.py +46 -0
  91. thrustlab-0.1.0/thrustlab/_models/simulation_resource.py +283 -0
  92. thrustlab-0.1.0/thrustlab/_models/simulation_resource_error_type_0.py +46 -0
  93. thrustlab-0.1.0/thrustlab/_models/simulation_resource_plot_config_json_type_0_item.py +46 -0
  94. thrustlab-0.1.0/thrustlab/_models/simulation_resource_result_type_0.py +46 -0
  95. thrustlab-0.1.0/thrustlab/_models/starred_component_create_body.py +80 -0
  96. thrustlab-0.1.0/thrustlab/_models/starred_component_list_response.py +108 -0
  97. thrustlab-0.1.0/thrustlab/_models/starred_component_resource.py +106 -0
  98. thrustlab-0.1.0/thrustlab/_models/submission_create_body.py +121 -0
  99. thrustlab-0.1.0/thrustlab/_models/submission_create_body_data_json.py +46 -0
  100. thrustlab-0.1.0/thrustlab/_models/submission_list_response.py +108 -0
  101. thrustlab-0.1.0/thrustlab/_models/submission_patch.py +174 -0
  102. thrustlab-0.1.0/thrustlab/_models/submission_patch_data_json_type_0.py +46 -0
  103. thrustlab-0.1.0/thrustlab/_models/submission_resource.py +197 -0
  104. thrustlab-0.1.0/thrustlab/_models/submission_resource_data_json.py +46 -0
  105. thrustlab-0.1.0/thrustlab/_models/subscription_create.py +58 -0
  106. thrustlab-0.1.0/thrustlab/_models/subscription_resource.py +168 -0
  107. thrustlab-0.1.0/thrustlab/_models/subscription_resource_status.py +12 -0
  108. thrustlab-0.1.0/thrustlab/_models/subscription_resource_tier.py +12 -0
  109. thrustlab-0.1.0/thrustlab/_models/sweep_cancel_body.py +42 -0
  110. thrustlab-0.1.0/thrustlab/_models/sweep_config_in.py +172 -0
  111. thrustlab-0.1.0/thrustlab/_models/sweep_create_body.py +138 -0
  112. thrustlab-0.1.0/thrustlab/_models/sweep_inputs.py +66 -0
  113. thrustlab-0.1.0/thrustlab/_models/sweep_list_response.py +88 -0
  114. thrustlab-0.1.0/thrustlab/_models/sweep_param_range_in.py +136 -0
  115. thrustlab-0.1.0/thrustlab/_models/sweep_param_range_in_mode.py +9 -0
  116. thrustlab-0.1.0/thrustlab/_models/sweep_patch.py +121 -0
  117. thrustlab-0.1.0/thrustlab/_models/sweep_patch_plot_config_json_type_0_item.py +46 -0
  118. thrustlab-0.1.0/thrustlab/_models/sweep_point_list_response.py +88 -0
  119. thrustlab-0.1.0/thrustlab/_models/sweep_point_resource.py +103 -0
  120. thrustlab-0.1.0/thrustlab/_models/sweep_point_resource_inputs.py +46 -0
  121. thrustlab-0.1.0/thrustlab/_models/sweep_point_resource_rotors_type_0.py +46 -0
  122. thrustlab-0.1.0/thrustlab/_models/sweep_resource.py +327 -0
  123. thrustlab-0.1.0/thrustlab/_models/sweep_resource_error_type_0.py +46 -0
  124. thrustlab-0.1.0/thrustlab/_models/sweep_resource_plot_config_json_type_0_item.py +46 -0
  125. thrustlab-0.1.0/thrustlab/_models/sweep_resource_summary_type_0.py +46 -0
  126. thrustlab-0.1.0/thrustlab/_models/sweep_resource_sweep_config_type_0.py +46 -0
  127. thrustlab-0.1.0/thrustlab/_models/sweep_rotor_group_in.py +95 -0
  128. thrustlab-0.1.0/thrustlab/_models/types.py +55 -0
  129. thrustlab-0.1.0/thrustlab/_models/user_patch.py +73 -0
  130. thrustlab-0.1.0/thrustlab/_models/user_resource.py +170 -0
  131. thrustlab-0.1.0/thrustlab/_models/v1_health_v1_health_get_response_v1_health_v1_health_get.py +46 -0
  132. thrustlab-0.1.0/thrustlab/_models/validation_error.py +90 -0
  133. thrustlab-0.1.0/thrustlab/_models/webhook_endpoint_create.py +91 -0
  134. thrustlab-0.1.0/thrustlab/_models/webhook_endpoint_patch.py +144 -0
  135. thrustlab-0.1.0/thrustlab/_pagination.py +109 -0
  136. thrustlab-0.1.0/thrustlab/_version.py +3 -0
  137. thrustlab-0.1.0/thrustlab/client.py +100 -0
  138. thrustlab-0.1.0/thrustlab/exceptions.py +121 -0
  139. thrustlab-0.1.0/thrustlab/resources/__init__.py +1 -0
  140. thrustlab-0.1.0/thrustlab/resources/_base.py +12 -0
  141. thrustlab-0.1.0/thrustlab/resources/api_keys.py +43 -0
  142. thrustlab-0.1.0/thrustlab/resources/components.py +60 -0
  143. thrustlab-0.1.0/thrustlab/resources/credits.py +40 -0
  144. thrustlab-0.1.0/thrustlab/resources/projects.py +54 -0
  145. thrustlab-0.1.0/thrustlab/resources/simulations.py +74 -0
  146. thrustlab-0.1.0/thrustlab/resources/starred_components.py +57 -0
  147. thrustlab-0.1.0/thrustlab/resources/submissions.py +44 -0
  148. thrustlab-0.1.0/thrustlab/resources/subscriptions.py +42 -0
  149. thrustlab-0.1.0/thrustlab/resources/sweeps.py +96 -0
  150. thrustlab-0.1.0/thrustlab/resources/users.py +13 -0
  151. thrustlab-0.1.0/thrustlab/resources/webhook_endpoints.py +138 -0
  152. thrustlab-0.1.0/thrustlab/webhooks.py +121 -0
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .venv/
8
+ .tox/
@@ -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.
@@ -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.
@@ -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
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/thrustlab.svg)](https://pypi.org/project/thrustlab/)
35
+ [![Python](https://img.shields.io/pypi/pyversions/thrustlab.svg)](https://pypi.org/project/thrustlab/)
36
+ [![License](https://img.shields.io/pypi/l/thrustlab.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/thrustlab.svg)](https://pypi.org/project/thrustlab/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/thrustlab.svg)](https://pypi.org/project/thrustlab/)
5
+ [![License](https://img.shields.io/pypi/l/thrustlab.svg)](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,5 @@
1
+ from thrustlab import Client
2
+
3
+ client = Client()
4
+ project = client.projects.create(name="my new project")
5
+ print(project)
@@ -0,0 +1,7 @@
1
+ from thrustlab import Client
2
+
3
+ client = Client()
4
+
5
+ # Auto-iterate through all projects
6
+ for project in client.projects.list():
7
+ print(f"{project['id']} — {project['name']}")
@@ -0,0 +1,5 @@
1
+ from thrustlab import Client
2
+
3
+ client = Client()
4
+ project = client.projects.retrieve("proj_xxx")
5
+ print(project)
@@ -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"