thrustlab 0.1.1__tar.gz → 0.3.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 (265) hide show
  1. thrustlab-0.3.0/.impeccable/hook.cache.json +1 -0
  2. thrustlab-0.3.0/CHANGELOG.md +231 -0
  3. {thrustlab-0.1.1 → thrustlab-0.3.0}/PKG-INFO +56 -20
  4. {thrustlab-0.1.1 → thrustlab-0.3.0}/README.md +55 -19
  5. thrustlab-0.3.0/ci/public-ci.yml +35 -0
  6. thrustlab-0.3.0/examples/components/create_component.py +106 -0
  7. thrustlab-0.3.0/examples/dynamic/run_and_poll.py +74 -0
  8. thrustlab-0.3.0/examples/outputs/read_dynamic.py +99 -0
  9. thrustlab-0.3.0/examples/outputs/read_steady_state.py +114 -0
  10. thrustlab-0.3.0/examples/quickstart.py +50 -0
  11. thrustlab-0.3.0/examples/simulations/run_sync.py +54 -0
  12. thrustlab-0.3.0/examples/sweeps/run_and_poll.py +55 -0
  13. thrustlab-0.3.0/scripts/publish_public_repo.sh +184 -0
  14. {thrustlab-0.1.1 → thrustlab-0.3.0}/scripts/regen_models.py +5 -0
  15. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_async_polling.py +18 -13
  16. thrustlab-0.3.0/tests/test_client.py +66 -0
  17. thrustlab-0.3.0/tests/test_components_create.py +70 -0
  18. thrustlab-0.3.0/tests/test_components_find.py +143 -0
  19. thrustlab-0.3.0/tests/test_dynamic_resource.py +51 -0
  20. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_pagination.py +34 -3
  21. thrustlab-0.3.0/tests/test_resources/test_compute_units.py +52 -0
  22. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_simulations.py +2 -2
  23. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_sweeps.py +2 -2
  24. thrustlab-0.3.0/tests/test_spec_coverage.py +118 -0
  25. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/__init__.py +5 -3
  26. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_async_polling.py +4 -2
  27. thrustlab-0.3.0/thrustlab/_models/__init__.py +377 -0
  28. thrustlab-0.3.0/thrustlab/_models/airfoil_breakpoint.py +57 -0
  29. thrustlab-0.1.1/thrustlab/_models/list_response_api_key.py → thrustlab-0.3.0/thrustlab/_models/airfoil_list_response.py +19 -20
  30. thrustlab-0.3.0/thrustlab/_models/airfoil_resource.py +148 -0
  31. thrustlab-0.3.0/thrustlab/_models/analyze_geometry_request.py +340 -0
  32. thrustlab-0.3.0/thrustlab/_models/analyze_geometry_request_mode.py +10 -0
  33. thrustlab-0.3.0/thrustlab/_models/analyze_geometry_request_target_type_type_0.py +9 -0
  34. thrustlab-0.3.0/thrustlab/_models/analyze_geometry_response.py +345 -0
  35. thrustlab-0.3.0/thrustlab/_models/analyze_station_row.py +150 -0
  36. thrustlab-0.3.0/thrustlab/_models/body_import_airfoil_v1_airfoils_import_post.py +85 -0
  37. thrustlab-0.3.0/thrustlab/_models/calibrate_accepted_response.py +82 -0
  38. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_create_body_spec_json.py +1 -0
  39. thrustlab-0.3.0/thrustlab/_models/component_ids_response.py +98 -0
  40. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_list_response.py +20 -0
  41. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_patch_spec_json_type_0.py +1 -0
  42. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_resource_spec_json.py +1 -0
  43. thrustlab-0.3.0/thrustlab/_models/component_sweep_axis_in.py +100 -0
  44. thrustlab-0.3.0/thrustlab/_models/component_sweep_axis_in_axis.py +10 -0
  45. thrustlab-0.3.0/thrustlab/_models/component_write_response.py +232 -0
  46. thrustlab-0.1.1/thrustlab/_models/sweep_point_resource_rotors_type_0.py → thrustlab-0.3.0/thrustlab/_models/component_write_response_spec_json.py +6 -5
  47. thrustlab-0.3.0/thrustlab/_models/component_write_warning.py +94 -0
  48. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/create_webhook_endpoint_v1_webhook_endpoints_post_response_create_webhook_endpoint_v1_webhook_endpoints_post.py +1 -0
  49. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_event_resource.py +9 -9
  50. thrustlab-0.1.1/thrustlab/_models/credit_usage_event_resource_credit_type.py → thrustlab-0.3.0/thrustlab/_models/credit_usage_event_resource_unit_type.py +1 -1
  51. thrustlab-0.3.0/thrustlab/_models/credit_usage_summary_resource.py +147 -0
  52. thrustlab-0.3.0/thrustlab/_models/credit_usage_summary_resource_window.py +9 -0
  53. thrustlab-0.3.0/thrustlab/_models/design_request.py +134 -0
  54. thrustlab-0.3.0/thrustlab/_models/design_request_target_mode.py +9 -0
  55. thrustlab-0.3.0/thrustlab/_models/dynamic_create_body.py +187 -0
  56. thrustlab-0.3.0/thrustlab/_models/dynamic_estimate_body.py +270 -0
  57. thrustlab-0.3.0/thrustlab/_models/dynamic_estimate_resource.py +63 -0
  58. thrustlab-0.3.0/thrustlab/_models/dynamic_list_response.py +100 -0
  59. thrustlab-0.1.1/thrustlab/_models/api_key_create.py → thrustlab-0.3.0/thrustlab/_models/dynamic_patch.py +26 -26
  60. thrustlab-0.3.0/thrustlab/_models/dynamic_resource.py +289 -0
  61. thrustlab-0.3.0/thrustlab/_models/dynamic_resource_display_labels_type_0.py +47 -0
  62. thrustlab-0.3.0/thrustlab/_models/dynamic_resource_error_type_0.py +47 -0
  63. thrustlab-0.3.0/thrustlab/_models/dynamic_resource_input_snapshot_type_0.py +47 -0
  64. thrustlab-0.3.0/thrustlab/_models/dynamic_resource_result_type_0.py +47 -0
  65. thrustlab-0.3.0/thrustlab/_models/dynamic_resource_status.py +12 -0
  66. thrustlab-0.3.0/thrustlab/_models/dynamic_rotor_group_in.py +150 -0
  67. thrustlab-0.3.0/thrustlab/_models/dynamic_rotor_group_in_motor_cooling_source.py +10 -0
  68. thrustlab-0.3.0/thrustlab/_models/entitlements_resource.py +127 -0
  69. thrustlab-0.3.0/thrustlab/_models/export_geometry_request.py +362 -0
  70. thrustlab-0.3.0/thrustlab/_models/export_geometry_request_format.py +10 -0
  71. thrustlab-0.3.0/thrustlab/_models/export_geometry_request_rotation_type_0.py +9 -0
  72. {thrustlab-0.1.1 → thrustlab-0.3.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 +1 -0
  73. thrustlab-0.3.0/thrustlab/_models/generate_geometry_request.py +205 -0
  74. thrustlab-0.3.0/thrustlab/_models/geometry_preview_response.py +112 -0
  75. thrustlab-0.3.0/thrustlab/_models/geometry_response.py +174 -0
  76. thrustlab-0.3.0/thrustlab/_models/geometry_style_resource.py +131 -0
  77. thrustlab-0.3.0/thrustlab/_models/geometry_styles_list_response.py +115 -0
  78. {thrustlab-0.1.1 → thrustlab-0.3.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 +1 -0
  79. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/get_event_v1_events_event_id_get_response_get_event_v1_events_event_id_get.py +1 -0
  80. thrustlab-0.3.0/thrustlab/_models/get_public_result_v1_public_results_slug_get_response_get_public_result_v1_public_results_slug_get.py +47 -0
  81. thrustlab-0.3.0/thrustlab/_models/get_shared_result_v1_public_share_share_token_get_response_get_shared_result_v1_public_share_share_token_get.py +51 -0
  82. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/get_webhook_endpoint_v1_webhook_endpoints_public_id_get_response_get_webhook_endpoint_v1_webhook_endpoints_public_id_get.py +1 -0
  83. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/list_deliveries_v1_webhook_endpoints_public_id_deliveries_get_response_list_deliveries_v1_webhook_endpoints_public_id_deliveries_get.py +1 -0
  84. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/list_events_v1_events_get_response_list_events_v1_events_get.py +1 -0
  85. thrustlab-0.3.0/thrustlab/_models/list_public_propellers_v1_public_propellers_get_response_list_public_propellers_v1_public_propellers_get.py +47 -0
  86. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/list_webhook_endpoints_v1_webhook_endpoints_get_response_list_webhook_endpoints_v1_webhook_endpoints_get.py +1 -0
  87. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/patch_webhook_endpoint_v1_webhook_endpoints_public_id_patch_response_patch_webhook_endpoint_v1_webhook_endpoints_public_id_patch.py +1 -0
  88. thrustlab-0.3.0/thrustlab/_models/preview_request.py +88 -0
  89. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_resource.py +66 -0
  90. {thrustlab-0.1.1 → thrustlab-0.3.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 +1 -0
  91. {thrustlab-0.1.1 → thrustlab-0.3.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 +1 -0
  92. thrustlab-0.3.0/thrustlab/_models/rotor_group_in.py +381 -0
  93. thrustlab-0.3.0/thrustlab/_models/rotor_group_in_esc_timing.py +11 -0
  94. thrustlab-0.3.0/thrustlab/_models/rotor_group_in_esc_type.py +9 -0
  95. thrustlab-0.3.0/thrustlab/_models/rotor_group_in_motor_cooling_source.py +10 -0
  96. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/rotor_group_resource.py +37 -0
  97. thrustlab-0.3.0/thrustlab/_models/run_queued_v1v1_simulations_run_queued_post_response_run_queued_v1v1_simulations_run_queued_post.py +47 -0
  98. thrustlab-0.3.0/thrustlab/_models/run_selected_body.py +42 -0
  99. thrustlab-0.3.0/thrustlab/_models/run_selected_v1v1_simulations_run_selected_post_response_run_selected_v1v1_simulations_run_selected_post.py +47 -0
  100. thrustlab-0.3.0/thrustlab/_models/schedule_in.py +147 -0
  101. thrustlab-0.3.0/thrustlab/_models/schedule_in_interpolation_type_0.py +9 -0
  102. thrustlab-0.3.0/thrustlab/_models/schedule_in_mode.py +9 -0
  103. thrustlab-0.3.0/thrustlab/_models/segment_group_command_in.py +88 -0
  104. thrustlab-0.3.0/thrustlab/_models/segment_group_command_in_throttle_ramp.py +9 -0
  105. thrustlab-0.3.0/thrustlab/_models/segment_group_command_in_tilt_ramp.py +9 -0
  106. thrustlab-0.3.0/thrustlab/_models/segment_in.py +151 -0
  107. thrustlab-0.3.0/thrustlab/_models/segment_in_airspeed_ramp.py +9 -0
  108. thrustlab-0.3.0/thrustlab/_models/segment_in_per_group.py +60 -0
  109. thrustlab-0.3.0/thrustlab/_models/segment_in_throttle_ramp.py +9 -0
  110. thrustlab-0.3.0/thrustlab/_models/segment_in_vertical_speed_ramp.py +9 -0
  111. thrustlab-0.1.1/thrustlab/_models/billing_portal_session_resource.py → thrustlab-0.3.0/thrustlab/_models/share_resource.py +23 -17
  112. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_cancel_body.py +1 -0
  113. thrustlab-0.3.0/thrustlab/_models/simulation_create_body.py +323 -0
  114. thrustlab-0.3.0/thrustlab/_models/simulation_create_body_cooling_source_type_0.py +11 -0
  115. thrustlab-0.3.0/thrustlab/_models/simulation_create_body_flight_regime.py +11 -0
  116. thrustlab-0.3.0/thrustlab/_models/simulation_create_body_inflow_mode.py +9 -0
  117. thrustlab-0.3.0/thrustlab/_models/simulation_create_body_launch_intent.py +10 -0
  118. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_patch_plot_config_json_type_0_item.py +1 -0
  119. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_resource.py +122 -4
  120. thrustlab-0.3.0/thrustlab/_models/simulation_resource_display_labels_type_0.py +47 -0
  121. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_resource_error_type_0.py +1 -0
  122. thrustlab-0.3.0/thrustlab/_models/simulation_resource_input_snapshot_type_0.py +47 -0
  123. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_resource_plot_config_json_type_0_item.py +1 -0
  124. thrustlab-0.3.0/thrustlab/_models/simulation_resource_result_type_0.py +66 -0
  125. thrustlab-0.3.0/thrustlab/_models/simulation_resource_result_type_0_additional_property.py +47 -0
  126. thrustlab-0.3.0/thrustlab/_models/simulation_resource_status.py +13 -0
  127. thrustlab-0.1.1/thrustlab/_models/user_patch.py → thrustlab-0.3.0/thrustlab/_models/spline_data.py +30 -23
  128. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_create_body_data_json.py +1 -0
  129. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_patch_data_json_type_0.py +1 -0
  130. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_resource_data_json.py +1 -0
  131. thrustlab-0.3.0/thrustlab/_models/sweep_config_in.py +338 -0
  132. thrustlab-0.3.0/thrustlab/_models/sweep_config_in_esc_timing_values_type_0_item.py +11 -0
  133. thrustlab-0.3.0/thrustlab/_models/sweep_create_body.py +299 -0
  134. thrustlab-0.3.0/thrustlab/_models/sweep_create_body_cooling_source.py +11 -0
  135. thrustlab-0.3.0/thrustlab/_models/sweep_create_body_inflow_mode.py +9 -0
  136. thrustlab-0.3.0/thrustlab/_models/sweep_create_body_launch_intent.py +10 -0
  137. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_patch_plot_config_json_type_0_item.py +1 -0
  138. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_point_list_response.py +36 -0
  139. thrustlab-0.3.0/thrustlab/_models/sweep_point_list_response_display_labels_type_0.py +47 -0
  140. thrustlab-0.1.1/thrustlab/_models/portal_session_in.py → thrustlab-0.3.0/thrustlab/_models/sweep_point_patch.py +10 -10
  141. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_point_resource.py +34 -7
  142. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_point_resource_inputs.py +1 -0
  143. thrustlab-0.3.0/thrustlab/_models/sweep_point_resource_rotors_type_0.py +66 -0
  144. thrustlab-0.3.0/thrustlab/_models/sweep_point_resource_rotors_type_0_additional_property.py +47 -0
  145. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource.py +69 -4
  146. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_error_type_0.py +1 -0
  147. thrustlab-0.3.0/thrustlab/_models/sweep_resource_input_snapshot_type_0.py +47 -0
  148. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_plot_config_json_type_0_item.py +1 -0
  149. thrustlab-0.3.0/thrustlab/_models/sweep_resource_status.py +13 -0
  150. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_summary_type_0.py +1 -0
  151. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_sweep_config_type_0.py +1 -0
  152. thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in.py +388 -0
  153. thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in_esc_timing.py +11 -0
  154. thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in_esc_type.py +9 -0
  155. thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in_motor_cooling_source.py +10 -0
  156. thrustlab-0.3.0/thrustlab/_models/termination_in.py +102 -0
  157. thrustlab-0.3.0/thrustlab/_models/termination_in_mode.py +9 -0
  158. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/user_resource.py +29 -1
  159. thrustlab-0.3.0/thrustlab/_models/user_resource_unit_system.py +9 -0
  160. thrustlab-0.3.0/thrustlab/_models/v1_custom_component_override.py +84 -0
  161. thrustlab-0.3.0/thrustlab/_models/v1_custom_component_override_spec_json.py +47 -0
  162. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/v1_health_v1_health_get_response_v1_health_v1_health_get.py +1 -0
  163. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_pagination.py +52 -11
  164. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_version.py +1 -1
  165. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/client.py +7 -9
  166. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/exceptions.py +35 -0
  167. thrustlab-0.3.0/thrustlab/resources/components.py +116 -0
  168. thrustlab-0.3.0/thrustlab/resources/compute_units.py +45 -0
  169. thrustlab-0.3.0/thrustlab/resources/dynamic_simulations.py +133 -0
  170. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/simulations.py +8 -3
  171. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/sweeps.py +16 -6
  172. thrustlab-0.1.1/CHANGELOG.md +0 -78
  173. thrustlab-0.1.1/examples/quickstart.py +0 -15
  174. thrustlab-0.1.1/examples/simulations/run_sync.py +0 -25
  175. thrustlab-0.1.1/examples/sweeps/run_and_poll.py +0 -30
  176. thrustlab-0.1.1/tests/test_client.py +0 -33
  177. thrustlab-0.1.1/tests/test_resources/test_api_keys.py +0 -45
  178. thrustlab-0.1.1/tests/test_resources/test_credits.py +0 -27
  179. thrustlab-0.1.1/tests/test_resources/test_subscriptions.py +0 -45
  180. thrustlab-0.1.1/tests/test_spec_coverage.py +0 -74
  181. thrustlab-0.1.1/thrustlab/_models/__init__.py +0 -211
  182. thrustlab-0.1.1/thrustlab/_models/api_key.py +0 -184
  183. thrustlab-0.1.1/thrustlab/_models/api_key_with_secret.py +0 -192
  184. thrustlab-0.1.1/thrustlab/_models/checkout_session_resource.py +0 -94
  185. thrustlab-0.1.1/thrustlab/_models/rotor_group_in.py +0 -107
  186. thrustlab-0.1.1/thrustlab/_models/simulation_create_body.py +0 -128
  187. thrustlab-0.1.1/thrustlab/_models/simulation_resource_result_type_0.py +0 -46
  188. thrustlab-0.1.1/thrustlab/_models/subscription_create.py +0 -58
  189. thrustlab-0.1.1/thrustlab/_models/subscription_resource.py +0 -168
  190. thrustlab-0.1.1/thrustlab/_models/subscription_resource_status.py +0 -12
  191. thrustlab-0.1.1/thrustlab/_models/subscription_resource_tier.py +0 -12
  192. thrustlab-0.1.1/thrustlab/_models/sweep_config_in.py +0 -172
  193. thrustlab-0.1.1/thrustlab/_models/sweep_create_body.py +0 -138
  194. thrustlab-0.1.1/thrustlab/_models/sweep_rotor_group_in.py +0 -95
  195. thrustlab-0.1.1/thrustlab/resources/api_keys.py +0 -43
  196. thrustlab-0.1.1/thrustlab/resources/components.py +0 -60
  197. thrustlab-0.1.1/thrustlab/resources/credits.py +0 -40
  198. thrustlab-0.1.1/thrustlab/resources/subscriptions.py +0 -42
  199. {thrustlab-0.1.1 → thrustlab-0.3.0}/.gitignore +0 -0
  200. {thrustlab-0.1.1 → thrustlab-0.3.0}/LICENSE +0 -0
  201. {thrustlab-0.1.1 → thrustlab-0.3.0}/Makefile +0 -0
  202. {thrustlab-0.1.1 → thrustlab-0.3.0}/NOTICE +0 -0
  203. {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/projects/create.py +0 -0
  204. {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/projects/list.py +0 -0
  205. {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/projects/retrieve.py +0 -0
  206. {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/simulations/wait.py +0 -0
  207. {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/webhooks/verify_handler.py +0 -0
  208. {thrustlab-0.1.1 → thrustlab-0.3.0}/pyproject.toml +0 -0
  209. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/__init__.py +0 -0
  210. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/conftest.py +0 -0
  211. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_exceptions.py +0 -0
  212. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_http.py +0 -0
  213. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/__init__.py +0 -0
  214. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_components.py +0 -0
  215. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_projects.py +0 -0
  216. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_starred_components.py +0 -0
  217. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_submissions.py +0 -0
  218. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_users.py +0 -0
  219. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_webhook_endpoints.py +0 -0
  220. {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_webhooks.py +0 -0
  221. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_http.py +0 -0
  222. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_brand.py +0 -0
  223. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_brands_response.py +0 -0
  224. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_create_body.py +0 -0
  225. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_patch.py +0 -0
  226. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_resource.py +0 -0
  227. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_balance_resource.py +0 -0
  228. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_bucket_breakdown.py +0 -0
  229. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_bucket_breakdown_type.py +0 -0
  230. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_event_resource_type.py +0 -0
  231. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_list_response.py +0 -0
  232. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_related_resource.py +0 -0
  233. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_related_resource_object.py +0 -0
  234. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/http_validation_error.py +0 -0
  235. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_create_body.py +0 -0
  236. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_list_response.py +0 -0
  237. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_patch.py +0 -0
  238. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_inputs.py +0 -0
  239. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_list_response.py +0 -0
  240. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_patch.py +0 -0
  241. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/starred_component_create_body.py +0 -0
  242. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/starred_component_list_response.py +0 -0
  243. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/starred_component_resource.py +0 -0
  244. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_create_body.py +0 -0
  245. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_list_response.py +0 -0
  246. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_patch.py +0 -0
  247. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_resource.py +0 -0
  248. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_cancel_body.py +0 -0
  249. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_inputs.py +0 -0
  250. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_list_response.py +0 -0
  251. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_param_range_in.py +0 -0
  252. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_param_range_in_mode.py +0 -0
  253. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_patch.py +0 -0
  254. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/types.py +0 -0
  255. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/validation_error.py +0 -0
  256. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/webhook_endpoint_create.py +0 -0
  257. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/webhook_endpoint_patch.py +0 -0
  258. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/__init__.py +0 -0
  259. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/_base.py +0 -0
  260. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/projects.py +0 -0
  261. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/starred_components.py +0 -0
  262. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/submissions.py +0 -0
  263. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/users.py +0 -0
  264. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/webhook_endpoints.py +0 -0
  265. {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/webhooks.py +0 -0
@@ -0,0 +1 @@
1
+ {"version":1,"sessions":{"4a041e4d-8a20-42d5-a6e0-f38606baf43c":{"updatedAt":1784225683709,"files":{"C:\\dev\\thrustlab\\frontend\\src\\lib\\help\\registry\\new-simulation.ts":{"editCount":1,"findings":[]},"C:\\dev\\thrustlab\\frontend\\src\\components\\submissions\\SubmissionForm.tsx":{"editCount":2,"findings":[]}}}}}
@@ -0,0 +1,231 @@
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.3.0] - 2026-07-15
12
+
13
+ The ThrustLab public API was refocused to a **product-only** surface: account,
14
+ billing, subscription, API-key and geometry-calibration management are no
15
+ longer part of the public `/v1/` contract. This SDK release realigns the
16
+ client to that surface.
17
+
18
+ > **0.2.0 was built but never published to PyPI.** 0.3.0 is the first release
19
+ > after 0.1.2, and it **consolidates the unpublished 0.2.0 changes** (canonical
20
+ > `snake_case` result keys, the `dynamic_simulations` resource, the sims/sweeps
21
+ > `cursor` fix) together with the API-refocus breaking changes below. If you are
22
+ > upgrading from `0.1.x`, read this whole entry — it is the full delta.
23
+
24
+ ### Removed
25
+
26
+ - **BREAKING — `client.subscriptions` and `client.api_keys` resources are gone.**
27
+ Subscription/billing-portal and API-key management are not part of the public
28
+ product SDK. `client.subscriptions` / `client.api_keys` now raise
29
+ `AttributeError`. (Create and manage API keys from the ThrustLab dashboard.)
30
+
31
+ ### Changed
32
+
33
+ - **BREAKING — `client.credits` renamed to `client.compute_units`.** The public
34
+ credits surface was renamed to *compute units*:
35
+ - accessor `client.credits` → `client.compute_units`;
36
+ - method `usage(...)` → `transactions(...)`;
37
+ - endpoints `/v1/credits/balance` → `/v1/compute-units/balance` and
38
+ `/v1/credits/usage` → `/v1/compute-units/transactions`;
39
+ - the pager keyword `starting_after=` → `cursor=` (the endpoint reads `cursor`;
40
+ this matches the 0.2.0 sims/sweeps fix — a `starting_after=` kwarg was
41
+ silently ignored). The `credit_type=` filter keyword is unchanged.
42
+ - **BREAKING (from the unpublished 0.2.0) — result payloads are canonical
43
+ `snake_case` across single-point, sweep, AND dynamic endpoints**, each with a
44
+ sibling `display_labels` map (`{snake_key: "Human Label"}`). Consumers that
45
+ parsed the old display-label result keys on sweep points (`"Thrust (N)"`,
46
+ `"RPM"`, …) must read the snake keys (`thrust_n`, `rpm`, …) and use
47
+ `display_labels` to recover a human string.
48
+ - **BREAKING — simulation / sweep / dynamic create bodies drop the first-party
49
+ fields `solver`, `input_snapshot`, and `design_cl`.** These were never part of
50
+ the public product contract; passing them is no longer accepted. (They remain
51
+ present in *response* payloads where the server legitimately echoes them.)
52
+ - **BREAKING — rotor-group count is capped at 16 total rotors (Σ ≤ 16)** across
53
+ all groups on a create request; larger configurations are rejected by the API.
54
+
55
+ ### Added
56
+
57
+ - **`client.components.create(...)`** — create a private custom component
58
+ (`POST /v1/components`) without dropping to the raw transport. Takes
59
+ `type`, `name`, `spec_json`, and an optional `idempotency_key`.
60
+ - Component create/update responses may carry a non-blocking `warnings` list
61
+ (`code` / `param` / `message`) when a spec value looks physically
62
+ implausible — e.g. a motor `R` far outside the expected range for its `kv`
63
+ and `weight` (milliohm/ohm mix-up, or a per-phase value where phase-to-phase
64
+ is expected). The write always succeeds.
65
+ - **`client.compute_units.summary()`** — usage-meter read for the current
66
+ metering window (`GET /v1/compute-units/summary`).
67
+ - **`client.dynamic_simulations` resource** (`create` / `retrieve` / `wait` /
68
+ `list` / `cancel` / `estimate`) mirroring `client.sweeps`, plus a runnable
69
+ dynamic example (`examples/dynamic/run_and_poll.py`).
70
+ - Runnable end-to-end examples for all three simulation types
71
+ (single-point / sweep / dynamic) reading canonical `snake_case` result keys,
72
+ and a corrected README quickstart using the real create body
73
+ (`rotor_groups=[…]` + `battery_component_id`).
74
+
75
+ ### Fixed
76
+
77
+ - **`cursor` pagination** — an explicit `starting_after=` kwarg on
78
+ `simulations.list`, `sweeps.list`, and `sweeps.list_points` was silently
79
+ ignored (the backend reads `cursor`). The keyword argument is now `cursor=`.
80
+ Automatic multi-page iteration was unaffected.
81
+
82
+ ### Migration from 0.1.x / 0.2.0
83
+
84
+ ```python
85
+ # --- credits -> compute_units ---------------------------------------------
86
+ # before (<= 0.2.0)
87
+ balance = client.credits.balance()
88
+ for ev in client.credits.usage(credit_type="simulation", starting_after=cur):
89
+ ...
90
+
91
+ # after (0.3.0)
92
+ balance = client.compute_units.balance()
93
+ for ev in client.compute_units.transactions(credit_type="simulation", cursor=cur):
94
+ ...
95
+
96
+ # --- removed resources ----------------------------------------------------
97
+ # client.subscriptions.* and client.api_keys.* no longer exist — manage
98
+ # subscriptions and API keys from the ThrustLab dashboard.
99
+
100
+ # --- sweep result keys are snake_case (from 0.2.0) ------------------------
101
+ # before: point["rotors"]["1"]["Thrust (N)"]
102
+ # after: point["rotors"]["1"]["thrust_n"] # display_labels["thrust_n"] -> "Thrust (N)"
103
+ ```
104
+
105
+ ## [0.2.0] - 2026-07-09
106
+
107
+ ### Changed
108
+
109
+ - **BREAKING — result payloads are now canonical `snake_case` across
110
+ single-point, sweep, AND dynamic endpoints, each with a sibling
111
+ `display_labels` map (`{snake_key: "Human Label"}`).** Single-point results
112
+ were already snake_case; this release aligns **sweep points** and **dynamic
113
+ samples** to the same contract. Consumers that parsed the old display-label
114
+ result keys on sweep points (`"Thrust (N)"`, `"RPM"`, `"Current (A)"`, …)
115
+ must now read the snake keys (`thrust_n`, `rpm`, `current_a`, …). Use the
116
+ sibling `display_labels` map to recover a human string for any key.
117
+
118
+ Migration:
119
+
120
+ ```python
121
+ # before (0.1.x) — sweep point rotor keys were display labels
122
+ thrust = point["rotors"]["1"]["Thrust (N)"]
123
+
124
+ # after (0.2.0) — canonical snake_case, with display_labels for UI
125
+ thrust = point["rotors"]["1"]["thrust_n"]
126
+ label = sweep["display_labels"]["thrust_n"] # -> "Thrust (N)"
127
+ ```
128
+
129
+ ### Added
130
+
131
+ - **`client.dynamic_simulations` resource** (`create` / `retrieve` / `wait` /
132
+ `list` / `cancel` / `estimate`) mirroring `client.sweeps`, plus a runnable
133
+ dynamic example (`examples/dynamic/run_and_poll.py`).
134
+ - Runnable end-to-end examples for all three simulation types
135
+ (single-point / sweep / dynamic) reading canonical snake_case result keys,
136
+ and a corrected README quickstart that uses the real create body
137
+ (`rotor_groups=[…]` + `battery_component_id`, not the phantom
138
+ `motor=/propeller=/battery=/throttle=` kwargs).
139
+
140
+ ### Fixed
141
+
142
+ - **`cursor` pagination** — an explicit `starting_after=` kwarg on
143
+ `simulations.list`, `sweeps.list`, and `sweeps.list_points` was silently
144
+ ignored (the backend reads `cursor`). The keyword argument is now `cursor=`.
145
+ Automatic multi-page iteration was unaffected.
146
+
147
+ ### Corrected (documentation record)
148
+
149
+ - The 0.1.0 changelog listed a `geometry` resource module and terminal states
150
+ `succeeded`/`cancelled` that never matched the shipped library. The SDK has
151
+ **no** `geometry` resource, and the canonical terminal states are
152
+ `completed` / `failed` / `canceled` (one-L wire spelling), with a
153
+ `timed_out` client-side sentinel from `wait()`.
154
+
155
+ ## [0.1.2] - 2026-07-08
156
+
157
+ ### Fixed
158
+
159
+ - **`wait()` now recognizes the canonical terminal states
160
+ `completed`/`failed`/`canceled`** (one-L `canceled` wire spelling). The
161
+ previous set omitted `canceled`, so `wait()` polled forever on a canceled
162
+ run until it hit the timeout sentinel.
163
+
164
+ ## [0.1.1] - 2026-04-28
165
+
166
+ ### Changed
167
+
168
+ - **License relicensed from MIT to Apache-2.0.** The Apache-2.0 license
169
+ includes an explicit patent grant scoped to the SDK plus a defensive
170
+ termination clause (anyone who sues ThrustLab for patent infringement
171
+ loses the patent grant). MIT had no explicit patent terms. ThrustLab
172
+ reserves all rights to the underlying simulation methodology that this
173
+ SDK calls; the license here applies only to this client library.
174
+ - Added `NOTICE` file alongside `LICENSE` per Apache convention.
175
+
176
+ ### Migration from 0.1.0
177
+
178
+ If you pinned `thrustlab==0.1.0`, upgrade to `thrustlab==0.1.1`. No code
179
+ changes — only the license text differs. Version 0.1.0 has been yanked
180
+ from PyPI.
181
+
182
+ ## [0.1.0] - 2026-04-27
183
+
184
+ Initial public beta release of the official ThrustLab Python SDK.
185
+
186
+ ### Added
187
+
188
+ - Full coverage of the `/v1/` API surface across **12 resource modules**:
189
+ `api_keys`, `users`, `projects`, `simulations`, `sweeps`, `components`,
190
+ `submissions`, `starred_components`, `geometry`, `credits`,
191
+ `subscriptions`, `webhook_endpoints`.
192
+ - Sync transport (`httpx.Client`) with bearer-token authentication.
193
+ - Auto-generated `Idempotency-Key` header on every mutating request (override
194
+ via `idempotency_key=` keyword argument).
195
+ - Exponential backoff + jitter retry on 429, 5xx, and transient network
196
+ errors (`max_retries=3` default, configurable per client instance).
197
+ - `CursorPager[T]` lazy iterator for cursor-paginated list endpoints;
198
+ transparent multi-page iteration.
199
+ - `wait()` helpers on `simulations` and `sweeps` for polling async resources
200
+ to terminal state (`succeeded` / `failed` / `cancelled` / `timed_out`).
201
+ - `Webhook.verify()` for HMAC SHA256 webhook signature verification with
202
+ configurable timestamp tolerance (default 300 s).
203
+ - Stripe-style typed exception hierarchy:
204
+ `ThrustlabError` → `APIError` → `AuthenticationError`, `PermissionError`,
205
+ `NotFoundError`, `ValidationError`, `ConflictError`, `RateLimitError`,
206
+ `APIServerError`; plus `NetworkError`, `SignatureVerificationError`,
207
+ `ConfigurationError`.
208
+ - Pydantic v2 models generated from `openapi.json` via
209
+ `openapi-python-client`; used internally for request/response shaping.
210
+ - Configuration via constructor kwargs (`api_key`, `base_url`, `timeout`,
211
+ `max_retries`) or env vars (`THRUSTLAB_API_KEY`, `THRUSTLAB_BASE_URL`).
212
+ - 103 unit + integration tests (pytest).
213
+
214
+ ### Out of scope (deferred)
215
+
216
+ - Async transport (`httpx.AsyncClient`). Planned for v0.2.
217
+ - Streaming response support.
218
+ - Python 3.9 compatibility.
219
+ - CLI tool (`thrustlab` command).
220
+ - SDKs in other languages.
221
+
222
+ ## Pre-release rehearsal procedure
223
+
224
+ Before tagging v0.1.0, rehearse with:
225
+
226
+ 1. Create a short-lived release-rehearsal branch from master.
227
+ 2. Tag `sdk-py-v0.0.1-rc1` and push it.
228
+ 3. The CI workflow runs build + TestPyPI publish only (gate step enforces
229
+ ancestry before either publish step fires).
230
+ 4. Delete the rehearsal tag + branch after validation.
231
+ 5. Tag `sdk-py-v0.1.0` from the actual master-merge commit for production publish.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thrustlab
3
- Version: 0.1.1
3
+ Version: 0.3.0
4
4
  Summary: Official Python SDK for the ThrustLab API
5
5
  Project-URL: Homepage, https://thrustlab.com
6
6
  Project-URL: Documentation, https://thrustlab.com/docs
@@ -34,7 +34,7 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  [![PyPI](https://img.shields.io/pypi/v/thrustlab.svg)](https://pypi.org/project/thrustlab/)
36
36
  [![Python](https://img.shields.io/pypi/pyversions/thrustlab.svg)](https://pypi.org/project/thrustlab/)
37
- [![License](https://img.shields.io/pypi/l/thrustlab.svg)](https://github.com/kylebedrich/thrustlab/blob/master/backend/sdks/python/LICENSE)
37
+ [![License](https://img.shields.io/pypi/l/thrustlab.svg)](https://github.com/kylebedrich/thrustlab-sdk/blob/main/LICENSE)
38
38
 
39
39
  ## Install
40
40
 
@@ -49,23 +49,58 @@ Requires Python 3.10+.
49
49
  ```python
50
50
  from thrustlab import Client
51
51
 
52
- client = Client(api_key="key_...") # or set $THRUSTLAB_API_KEY
52
+ client = Client() # reads $THRUSTLAB_API_KEY (never hard-code the key)
53
53
 
54
54
  # Create a project
55
55
  project = client.projects.create(name="my project")
56
56
 
57
- # Run a simulation and wait for the result
57
+ # Run a single-point simulation and wait for the result.
58
+ # A rotor group carries the motor + propeller + throttle (0–100); the battery
59
+ # and flight condition are top-level. Resolve component IDs from the catalog
60
+ # (client.components.find(name=...)) or paste them directly.
58
61
  sim = client.simulations.create(
59
62
  project_id=project["id"],
60
- motor="comp_motor_xxx",
61
- propeller="comp_prop_xxx",
62
- battery="comp_batt_xxx",
63
- throttle=0.7,
63
+ battery_component_id="comp_batt_xxx",
64
+ airspeed_m_s=0.0,
65
+ density_kg_m3=1.225,
66
+ battery_charge_pct=100,
67
+ rotor_groups=[
68
+ {
69
+ "label": "main",
70
+ "count": 4,
71
+ "motor_component_id": "comp_motor_xxx",
72
+ "propeller_component_id": "comp_prop_xxx",
73
+ "throttle_pct": 70,
74
+ }
75
+ ],
64
76
  )
65
77
  result = client.simulations.wait(sim["id"], timeout=300)
66
- print(result)
78
+
79
+ # Canonical snake_case result: per-rotor group under "1"/"2"/…, roll-ups
80
+ # under "All" (a sibling `display_labels` map carries the human labels).
81
+ print(result["result"]["1"]["thrust_n"], result["result"]["All"]["total_thrust_n"])
67
82
  ```
68
83
 
84
+ Runnable end-to-end examples for every simulation type live in
85
+ [`examples/`](./examples): [`simulations/run_sync.py`](./examples/simulations/run_sync.py),
86
+ [`sweeps/run_and_poll.py`](./examples/sweeps/run_and_poll.py), and
87
+ [`dynamic/run_and_poll.py`](./examples/dynamic/run_and_poll.py).
88
+
89
+ ## Why the SDK (and not `urllib` / `wget`)?
90
+
91
+ Use this SDK — or, if you hand-roll a client, an HTTP library that is **not**
92
+ stdlib `urllib`. Requests from Python's `urllib.request` (and `wget`) are
93
+ **blocked at the Cloudflare edge** by a managed WAF rule matching the literal
94
+ `Python-urllib/*` User-Agent. The block is terminated at the edge: the request
95
+ **never reaches the ThrustLab API**, so you get an opaque HTML *error 1010*
96
+ challenge page — **not** a JSON error envelope. The API cannot return a
97
+ structured error for a request it never sees.
98
+
99
+ This SDK is built on [`httpx`](https://www.python-httpx.org/) and sends its own
100
+ `thrustlab-python/<version>` User-Agent, so it is **unaffected**. If you must
101
+ hand-roll a client, set an explicit non-`urllib` `User-Agent` header (e.g.
102
+ `requests`, `httpx`, or `curl`) and you will reach the API normally.
103
+
69
104
  ## Configuration
70
105
 
71
106
  | Setting | Constructor arg | Env var | Default |
@@ -80,17 +115,15 @@ print(result)
80
115
  Every `/v1/` route family is exposed as an attribute on the client:
81
116
 
82
117
  ```python
83
- client.api_keys
84
118
  client.users
85
119
  client.projects
86
120
  client.simulations
87
121
  client.sweeps
122
+ client.dynamic_simulations
88
123
  client.components
89
124
  client.submissions
90
125
  client.starred_components
91
- client.geometry
92
- client.credits
93
- client.subscriptions
126
+ client.compute_units
94
127
  client.webhook_endpoints
95
128
  ```
96
129
 
@@ -107,7 +140,7 @@ from thrustlab.exceptions import (
107
140
 
108
141
  client = Client()
109
142
  try:
110
- client.simulations.create(project_id="proj_xxx", motor="comp_xxx", ...)
143
+ client.simulations.create(project_id="proj_xxx", rotor_groups=[...], ...)
111
144
  except ValidationError as exc:
112
145
  print(f"bad request: {exc.code} ({exc.param}): {exc.message}")
113
146
  except RateLimitError as exc:
@@ -139,14 +172,17 @@ first_page = list(client.projects.list(limit=20))
139
172
 
140
173
  ## Async polling
141
174
 
142
- `simulations.wait()` and `sweeps.wait()` block until the resource reaches a
143
- terminal state (`succeeded`, `failed`, or `cancelled`) or the timeout fires:
175
+ `simulations.wait()`, `sweeps.wait()`, and `dynamic_simulations.wait()` block
176
+ until the resource reaches a terminal state (`completed`, `failed`, or
177
+ `canceled` — one 'l' on the wire) or the timeout fires. On timeout the returned
178
+ dict carries the SDK-side sentinel `status="timed_out"` (never a server state):
144
179
 
145
180
  ```python
146
181
  sweep = client.sweeps.create(...)
147
182
  result = client.sweeps.wait(sweep["id"], timeout=600, poll_interval=2.0)
148
- if result["status"] == "succeeded":
149
- print(result["outputs"])
183
+ if result["status"] == "completed":
184
+ for point in client.sweeps.list_points(sweep["id"]):
185
+ print(point["rotors"]["1"]["thrust_n"])
150
186
  ```
151
187
 
152
188
  ## Webhooks
@@ -184,8 +220,8 @@ client = Client(max_retries=0)
184
220
  - API reference: https://thrustlab.com/docs/reference
185
221
  - SDK guide: https://thrustlab.com/docs/sdk/python
186
222
  - Changelog: https://thrustlab.com/docs/changelog
187
- - Issue tracker: https://github.com/kylebedrich/thrustlab/issues
223
+ - Issue tracker: https://github.com/kylebedrich/thrustlab-sdk/issues
188
224
 
189
225
  ## License
190
226
 
191
- MIT
227
+ Apache-2.0. See [`LICENSE`](./LICENSE) and [`NOTICE`](./NOTICE).
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![PyPI](https://img.shields.io/pypi/v/thrustlab.svg)](https://pypi.org/project/thrustlab/)
4
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)
5
+ [![License](https://img.shields.io/pypi/l/thrustlab.svg)](https://github.com/kylebedrich/thrustlab-sdk/blob/main/LICENSE)
6
6
 
7
7
  ## Install
8
8
 
@@ -17,23 +17,58 @@ Requires Python 3.10+.
17
17
  ```python
18
18
  from thrustlab import Client
19
19
 
20
- client = Client(api_key="key_...") # or set $THRUSTLAB_API_KEY
20
+ client = Client() # reads $THRUSTLAB_API_KEY (never hard-code the key)
21
21
 
22
22
  # Create a project
23
23
  project = client.projects.create(name="my project")
24
24
 
25
- # Run a simulation and wait for the result
25
+ # Run a single-point simulation and wait for the result.
26
+ # A rotor group carries the motor + propeller + throttle (0–100); the battery
27
+ # and flight condition are top-level. Resolve component IDs from the catalog
28
+ # (client.components.find(name=...)) or paste them directly.
26
29
  sim = client.simulations.create(
27
30
  project_id=project["id"],
28
- motor="comp_motor_xxx",
29
- propeller="comp_prop_xxx",
30
- battery="comp_batt_xxx",
31
- throttle=0.7,
31
+ battery_component_id="comp_batt_xxx",
32
+ airspeed_m_s=0.0,
33
+ density_kg_m3=1.225,
34
+ battery_charge_pct=100,
35
+ rotor_groups=[
36
+ {
37
+ "label": "main",
38
+ "count": 4,
39
+ "motor_component_id": "comp_motor_xxx",
40
+ "propeller_component_id": "comp_prop_xxx",
41
+ "throttle_pct": 70,
42
+ }
43
+ ],
32
44
  )
33
45
  result = client.simulations.wait(sim["id"], timeout=300)
34
- print(result)
46
+
47
+ # Canonical snake_case result: per-rotor group under "1"/"2"/…, roll-ups
48
+ # under "All" (a sibling `display_labels` map carries the human labels).
49
+ print(result["result"]["1"]["thrust_n"], result["result"]["All"]["total_thrust_n"])
35
50
  ```
36
51
 
52
+ Runnable end-to-end examples for every simulation type live in
53
+ [`examples/`](./examples): [`simulations/run_sync.py`](./examples/simulations/run_sync.py),
54
+ [`sweeps/run_and_poll.py`](./examples/sweeps/run_and_poll.py), and
55
+ [`dynamic/run_and_poll.py`](./examples/dynamic/run_and_poll.py).
56
+
57
+ ## Why the SDK (and not `urllib` / `wget`)?
58
+
59
+ Use this SDK — or, if you hand-roll a client, an HTTP library that is **not**
60
+ stdlib `urllib`. Requests from Python's `urllib.request` (and `wget`) are
61
+ **blocked at the Cloudflare edge** by a managed WAF rule matching the literal
62
+ `Python-urllib/*` User-Agent. The block is terminated at the edge: the request
63
+ **never reaches the ThrustLab API**, so you get an opaque HTML *error 1010*
64
+ challenge page — **not** a JSON error envelope. The API cannot return a
65
+ structured error for a request it never sees.
66
+
67
+ This SDK is built on [`httpx`](https://www.python-httpx.org/) and sends its own
68
+ `thrustlab-python/<version>` User-Agent, so it is **unaffected**. If you must
69
+ hand-roll a client, set an explicit non-`urllib` `User-Agent` header (e.g.
70
+ `requests`, `httpx`, or `curl`) and you will reach the API normally.
71
+
37
72
  ## Configuration
38
73
 
39
74
  | Setting | Constructor arg | Env var | Default |
@@ -48,17 +83,15 @@ print(result)
48
83
  Every `/v1/` route family is exposed as an attribute on the client:
49
84
 
50
85
  ```python
51
- client.api_keys
52
86
  client.users
53
87
  client.projects
54
88
  client.simulations
55
89
  client.sweeps
90
+ client.dynamic_simulations
56
91
  client.components
57
92
  client.submissions
58
93
  client.starred_components
59
- client.geometry
60
- client.credits
61
- client.subscriptions
94
+ client.compute_units
62
95
  client.webhook_endpoints
63
96
  ```
64
97
 
@@ -75,7 +108,7 @@ from thrustlab.exceptions import (
75
108
 
76
109
  client = Client()
77
110
  try:
78
- client.simulations.create(project_id="proj_xxx", motor="comp_xxx", ...)
111
+ client.simulations.create(project_id="proj_xxx", rotor_groups=[...], ...)
79
112
  except ValidationError as exc:
80
113
  print(f"bad request: {exc.code} ({exc.param}): {exc.message}")
81
114
  except RateLimitError as exc:
@@ -107,14 +140,17 @@ first_page = list(client.projects.list(limit=20))
107
140
 
108
141
  ## Async polling
109
142
 
110
- `simulations.wait()` and `sweeps.wait()` block until the resource reaches a
111
- terminal state (`succeeded`, `failed`, or `cancelled`) or the timeout fires:
143
+ `simulations.wait()`, `sweeps.wait()`, and `dynamic_simulations.wait()` block
144
+ until the resource reaches a terminal state (`completed`, `failed`, or
145
+ `canceled` — one 'l' on the wire) or the timeout fires. On timeout the returned
146
+ dict carries the SDK-side sentinel `status="timed_out"` (never a server state):
112
147
 
113
148
  ```python
114
149
  sweep = client.sweeps.create(...)
115
150
  result = client.sweeps.wait(sweep["id"], timeout=600, poll_interval=2.0)
116
- if result["status"] == "succeeded":
117
- print(result["outputs"])
151
+ if result["status"] == "completed":
152
+ for point in client.sweeps.list_points(sweep["id"]):
153
+ print(point["rotors"]["1"]["thrust_n"])
118
154
  ```
119
155
 
120
156
  ## Webhooks
@@ -152,8 +188,8 @@ client = Client(max_retries=0)
152
188
  - API reference: https://thrustlab.com/docs/reference
153
189
  - SDK guide: https://thrustlab.com/docs/sdk/python
154
190
  - Changelog: https://thrustlab.com/docs/changelog
155
- - Issue tracker: https://github.com/kylebedrich/thrustlab/issues
191
+ - Issue tracker: https://github.com/kylebedrich/thrustlab-sdk/issues
156
192
 
157
193
  ## License
158
194
 
159
- MIT
195
+ Apache-2.0. See [`LICENSE`](./LICENSE) and [`NOTICE`](./NOTICE).
@@ -0,0 +1,35 @@
1
+ # Public CI for the standalone thrustlab-sdk repository.
2
+ #
3
+ # This file is the SOURCE template kept in the monorepo. The extraction script
4
+ # (scripts/publish_public_repo.sh) copies it into the public mirror as
5
+ # .github/workflows/ci.yml. It installs the package and runs the unit test
6
+ # suite (integration tests, which require a live API key, are excluded).
7
+ name: CI
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+
14
+ jobs:
15
+ test:
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+
29
+ - name: Install package + test dependencies
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ pip install -e ".[dev]"
33
+
34
+ - name: Run unit tests
35
+ run: pytest -m "not integration" -ra --tb=short
@@ -0,0 +1,106 @@
1
+ """Create your own custom motor, battery, and propeller from spec_json.
2
+
3
+ Run it:
4
+ export THRUSTLAB_API_KEY=key_... # never hard-code the key
5
+ python examples/components/create_component.py
6
+
7
+ A custom component is `POST /v1/components` with three fields: `type`
8
+ (motor | battery | propeller), `name`, and a type-specific `spec_json` map. The
9
+ create is scoped to your account (source="user", visibility="private"); the
10
+ returned resource echoes back an `id` (comp_...) you pass to a simulation.
11
+
12
+ Each create below is `client.components.create(...)`. Field names, units, and
13
+ accepted ranges come from the server-side validators — see the reference page
14
+ for the full tables.
15
+
16
+ Notes on validation:
17
+ * Battery: a physical pack needs chemistry, cell layout, capacity, weight_g,
18
+ one resistance figure (c_rating or pack_resistance_mOhm), and bounding-box
19
+ dims; form_factor is derived from chemistry when omitted.
20
+ * Propeller: per-station radius/chord/twist and rotation are required, and
21
+ persisting a propeller needs the hobbyist tier or higher — the create below
22
+ reports the tier gate cleanly on a free account.
23
+ """
24
+
25
+ from thrustlab import Client
26
+ from thrustlab.exceptions import APIError
27
+
28
+ client = Client() # reads $THRUSTLAB_API_KEY from the environment
29
+
30
+ # ---- motor ---------------------------------------------------------------
31
+ # Required: kv, n (pole count), R (phase-to-phase winding resistance, ohms),
32
+ # weight, diameter, length, u_nominal, iq_nominal, and one of iq_max /
33
+ # power_max. Inductance L and rotor inertia are auto-estimated from
34
+ # kv/n/diameter/length/weight — inertia is required for dynamic (time-domain)
35
+ # runs, so give real dimensions and mass.
36
+ motor = client.components.create(
37
+ type="motor",
38
+ name="My Custom 4260 800Kv",
39
+ spec_json={
40
+ "brand": "Custom",
41
+ "kv": 800.0, # rpm per volt
42
+ "n": 14, # magnet pole count
43
+ "R": 0.042, # phase-to-phase winding resistance (ohm)
44
+ "weight": 120.0, # g
45
+ "diameter": 42.0, # mm (drives L / inertia estimation)
46
+ "length": 25.0, # mm
47
+ "u_nominal": 14.8, # nominal voltage (V)
48
+ "iq_nominal": 1.1, # no-load / idle current (A)
49
+ "iq_max": 65.0, # max quadrature current (A)
50
+ "power_max": 960.0, # max electrical power (W)
51
+ "topology": "outrunner",
52
+ },
53
+ )
54
+ print(f"created motor {motor['id']}")
55
+
56
+ # ---- battery -------------------------------------------------------------
57
+ # chemistry in {lipo, lihv, nmc, nca, lfp, nimh, source}. A physical pack needs
58
+ # its cell layout, capacity, weight_g, one resistance figure (c_rating or
59
+ # pack_resistance_mOhm), and length/width/height_mm (1..2000 mm). form_factor
60
+ # is derived (lipo -> pouch) when omitted. Pack cap: series <= 24, S*P <= 256.
61
+ battery = client.components.create(
62
+ type="battery",
63
+ name="My Custom 4S 5000mAh",
64
+ spec_json={
65
+ "brand": "Custom",
66
+ "chemistry": "lipo",
67
+ "series_cells": 4,
68
+ "parallel_cells": 1,
69
+ "total_capacity_mAh": 5000.0,
70
+ "c_rating": 75.0,
71
+ "weight_g": 480.0,
72
+ "length_mm": 145.0, # required for a physical chemistry
73
+ "width_mm": 49.0,
74
+ "height_mm": 33.0,
75
+ },
76
+ )
77
+ print(f"created battery {battery['id']}")
78
+
79
+ # ---- propeller (hobbyist tier or higher) ---------------------------------
80
+ # Required: diameter, pitch, rotation, weight, and the per-station blade
81
+ # geometry — parallel radius/chord/twist arrays, hub -> tip (<= 50 stations;
82
+ # radius and chord in METERS, twist in degrees). inertia is required for
83
+ # dynamic (time-domain) runs and is estimated from weight + diameter when
84
+ # omitted.
85
+ try:
86
+ prop = client.components.create(
87
+ type="propeller",
88
+ name="My Custom 10.5x4.5",
89
+ spec_json={
90
+ "brand": "Custom",
91
+ "diameter": 10.5, # inch
92
+ "pitch": 4.5, # inch
93
+ "num_blades": 2,
94
+ "rotation": "ccw", # top view; "cw" | "ccw" | "both"
95
+ "weight": 14.0, # g
96
+ "inertia": 8.3e-5, # kg*m^2 — required for dynamic runs
97
+ # per-station geometry, hub -> tip (r/R = 0.20 .. 1.00)
98
+ "radius": [0.0267, 0.0400, 0.0600, 0.0800, 0.1000, 0.1200, 0.1334], # m
99
+ "chord": [0.016, 0.019, 0.022, 0.021, 0.018, 0.014, 0.008], # m
100
+ "twist": [30.0, 24.0, 17.0, 12.8, 10.3, 8.6, 7.8], # deg
101
+ },
102
+ )
103
+ print(f"created propeller {prop['id']}")
104
+ except APIError as exc:
105
+ # A free account 402s here (propeller persistence is a creator_edit gate).
106
+ print(f"propeller create skipped: {exc}")