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.
- thrustlab-0.3.0/.impeccable/hook.cache.json +1 -0
- thrustlab-0.3.0/CHANGELOG.md +231 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/PKG-INFO +56 -20
- {thrustlab-0.1.1 → thrustlab-0.3.0}/README.md +55 -19
- thrustlab-0.3.0/ci/public-ci.yml +35 -0
- thrustlab-0.3.0/examples/components/create_component.py +106 -0
- thrustlab-0.3.0/examples/dynamic/run_and_poll.py +74 -0
- thrustlab-0.3.0/examples/outputs/read_dynamic.py +99 -0
- thrustlab-0.3.0/examples/outputs/read_steady_state.py +114 -0
- thrustlab-0.3.0/examples/quickstart.py +50 -0
- thrustlab-0.3.0/examples/simulations/run_sync.py +54 -0
- thrustlab-0.3.0/examples/sweeps/run_and_poll.py +55 -0
- thrustlab-0.3.0/scripts/publish_public_repo.sh +184 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/scripts/regen_models.py +5 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_async_polling.py +18 -13
- thrustlab-0.3.0/tests/test_client.py +66 -0
- thrustlab-0.3.0/tests/test_components_create.py +70 -0
- thrustlab-0.3.0/tests/test_components_find.py +143 -0
- thrustlab-0.3.0/tests/test_dynamic_resource.py +51 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_pagination.py +34 -3
- thrustlab-0.3.0/tests/test_resources/test_compute_units.py +52 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_simulations.py +2 -2
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_sweeps.py +2 -2
- thrustlab-0.3.0/tests/test_spec_coverage.py +118 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/__init__.py +5 -3
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_async_polling.py +4 -2
- thrustlab-0.3.0/thrustlab/_models/__init__.py +377 -0
- thrustlab-0.3.0/thrustlab/_models/airfoil_breakpoint.py +57 -0
- thrustlab-0.1.1/thrustlab/_models/list_response_api_key.py → thrustlab-0.3.0/thrustlab/_models/airfoil_list_response.py +19 -20
- thrustlab-0.3.0/thrustlab/_models/airfoil_resource.py +148 -0
- thrustlab-0.3.0/thrustlab/_models/analyze_geometry_request.py +340 -0
- thrustlab-0.3.0/thrustlab/_models/analyze_geometry_request_mode.py +10 -0
- thrustlab-0.3.0/thrustlab/_models/analyze_geometry_request_target_type_type_0.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/analyze_geometry_response.py +345 -0
- thrustlab-0.3.0/thrustlab/_models/analyze_station_row.py +150 -0
- thrustlab-0.3.0/thrustlab/_models/body_import_airfoil_v1_airfoils_import_post.py +85 -0
- thrustlab-0.3.0/thrustlab/_models/calibrate_accepted_response.py +82 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_create_body_spec_json.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/component_ids_response.py +98 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_list_response.py +20 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_patch_spec_json_type_0.py +1 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_resource_spec_json.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/component_sweep_axis_in.py +100 -0
- thrustlab-0.3.0/thrustlab/_models/component_sweep_axis_in_axis.py +10 -0
- thrustlab-0.3.0/thrustlab/_models/component_write_response.py +232 -0
- 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
- thrustlab-0.3.0/thrustlab/_models/component_write_warning.py +94 -0
- {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
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_event_resource.py +9 -9
- 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
- thrustlab-0.3.0/thrustlab/_models/credit_usage_summary_resource.py +147 -0
- thrustlab-0.3.0/thrustlab/_models/credit_usage_summary_resource_window.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/design_request.py +134 -0
- thrustlab-0.3.0/thrustlab/_models/design_request_target_mode.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_create_body.py +187 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_estimate_body.py +270 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_estimate_resource.py +63 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_list_response.py +100 -0
- thrustlab-0.1.1/thrustlab/_models/api_key_create.py → thrustlab-0.3.0/thrustlab/_models/dynamic_patch.py +26 -26
- thrustlab-0.3.0/thrustlab/_models/dynamic_resource.py +289 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_resource_display_labels_type_0.py +47 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_resource_error_type_0.py +47 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_resource_input_snapshot_type_0.py +47 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_resource_result_type_0.py +47 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_resource_status.py +12 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_rotor_group_in.py +150 -0
- thrustlab-0.3.0/thrustlab/_models/dynamic_rotor_group_in_motor_cooling_source.py +10 -0
- thrustlab-0.3.0/thrustlab/_models/entitlements_resource.py +127 -0
- thrustlab-0.3.0/thrustlab/_models/export_geometry_request.py +362 -0
- thrustlab-0.3.0/thrustlab/_models/export_geometry_request_format.py +10 -0
- thrustlab-0.3.0/thrustlab/_models/export_geometry_request_rotation_type_0.py +9 -0
- {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
- thrustlab-0.3.0/thrustlab/_models/generate_geometry_request.py +205 -0
- thrustlab-0.3.0/thrustlab/_models/geometry_preview_response.py +112 -0
- thrustlab-0.3.0/thrustlab/_models/geometry_response.py +174 -0
- thrustlab-0.3.0/thrustlab/_models/geometry_style_resource.py +131 -0
- thrustlab-0.3.0/thrustlab/_models/geometry_styles_list_response.py +115 -0
- {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
- {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
- 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
- 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
- {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
- {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
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/list_events_v1_events_get_response_list_events_v1_events_get.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/list_public_propellers_v1_public_propellers_get_response_list_public_propellers_v1_public_propellers_get.py +47 -0
- {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
- {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
- thrustlab-0.3.0/thrustlab/_models/preview_request.py +88 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_resource.py +66 -0
- {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
- {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
- thrustlab-0.3.0/thrustlab/_models/rotor_group_in.py +381 -0
- thrustlab-0.3.0/thrustlab/_models/rotor_group_in_esc_timing.py +11 -0
- thrustlab-0.3.0/thrustlab/_models/rotor_group_in_esc_type.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/rotor_group_in_motor_cooling_source.py +10 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/rotor_group_resource.py +37 -0
- thrustlab-0.3.0/thrustlab/_models/run_queued_v1v1_simulations_run_queued_post_response_run_queued_v1v1_simulations_run_queued_post.py +47 -0
- thrustlab-0.3.0/thrustlab/_models/run_selected_body.py +42 -0
- thrustlab-0.3.0/thrustlab/_models/run_selected_v1v1_simulations_run_selected_post_response_run_selected_v1v1_simulations_run_selected_post.py +47 -0
- thrustlab-0.3.0/thrustlab/_models/schedule_in.py +147 -0
- thrustlab-0.3.0/thrustlab/_models/schedule_in_interpolation_type_0.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/schedule_in_mode.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/segment_group_command_in.py +88 -0
- thrustlab-0.3.0/thrustlab/_models/segment_group_command_in_throttle_ramp.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/segment_group_command_in_tilt_ramp.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/segment_in.py +151 -0
- thrustlab-0.3.0/thrustlab/_models/segment_in_airspeed_ramp.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/segment_in_per_group.py +60 -0
- thrustlab-0.3.0/thrustlab/_models/segment_in_throttle_ramp.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/segment_in_vertical_speed_ramp.py +9 -0
- thrustlab-0.1.1/thrustlab/_models/billing_portal_session_resource.py → thrustlab-0.3.0/thrustlab/_models/share_resource.py +23 -17
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_cancel_body.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_create_body.py +323 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_create_body_cooling_source_type_0.py +11 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_create_body_flight_regime.py +11 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_create_body_inflow_mode.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_create_body_launch_intent.py +10 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_patch_plot_config_json_type_0_item.py +1 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_resource.py +122 -4
- thrustlab-0.3.0/thrustlab/_models/simulation_resource_display_labels_type_0.py +47 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_resource_error_type_0.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_resource_input_snapshot_type_0.py +47 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_resource_plot_config_json_type_0_item.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_resource_result_type_0.py +66 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_resource_result_type_0_additional_property.py +47 -0
- thrustlab-0.3.0/thrustlab/_models/simulation_resource_status.py +13 -0
- thrustlab-0.1.1/thrustlab/_models/user_patch.py → thrustlab-0.3.0/thrustlab/_models/spline_data.py +30 -23
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_create_body_data_json.py +1 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_patch_data_json_type_0.py +1 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_resource_data_json.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_config_in.py +338 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_config_in_esc_timing_values_type_0_item.py +11 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_create_body.py +299 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_create_body_cooling_source.py +11 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_create_body_inflow_mode.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_create_body_launch_intent.py +10 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_patch_plot_config_json_type_0_item.py +1 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_point_list_response.py +36 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_point_list_response_display_labels_type_0.py +47 -0
- thrustlab-0.1.1/thrustlab/_models/portal_session_in.py → thrustlab-0.3.0/thrustlab/_models/sweep_point_patch.py +10 -10
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_point_resource.py +34 -7
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_point_resource_inputs.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_point_resource_rotors_type_0.py +66 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_point_resource_rotors_type_0_additional_property.py +47 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource.py +69 -4
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_error_type_0.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_resource_input_snapshot_type_0.py +47 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_plot_config_json_type_0_item.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_resource_status.py +13 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_summary_type_0.py +1 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_resource_sweep_config_type_0.py +1 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in.py +388 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in_esc_timing.py +11 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in_esc_type.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/sweep_rotor_group_in_motor_cooling_source.py +10 -0
- thrustlab-0.3.0/thrustlab/_models/termination_in.py +102 -0
- thrustlab-0.3.0/thrustlab/_models/termination_in_mode.py +9 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/user_resource.py +29 -1
- thrustlab-0.3.0/thrustlab/_models/user_resource_unit_system.py +9 -0
- thrustlab-0.3.0/thrustlab/_models/v1_custom_component_override.py +84 -0
- thrustlab-0.3.0/thrustlab/_models/v1_custom_component_override_spec_json.py +47 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/v1_health_v1_health_get_response_v1_health_v1_health_get.py +1 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_pagination.py +52 -11
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_version.py +1 -1
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/client.py +7 -9
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/exceptions.py +35 -0
- thrustlab-0.3.0/thrustlab/resources/components.py +116 -0
- thrustlab-0.3.0/thrustlab/resources/compute_units.py +45 -0
- thrustlab-0.3.0/thrustlab/resources/dynamic_simulations.py +133 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/simulations.py +8 -3
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/sweeps.py +16 -6
- thrustlab-0.1.1/CHANGELOG.md +0 -78
- thrustlab-0.1.1/examples/quickstart.py +0 -15
- thrustlab-0.1.1/examples/simulations/run_sync.py +0 -25
- thrustlab-0.1.1/examples/sweeps/run_and_poll.py +0 -30
- thrustlab-0.1.1/tests/test_client.py +0 -33
- thrustlab-0.1.1/tests/test_resources/test_api_keys.py +0 -45
- thrustlab-0.1.1/tests/test_resources/test_credits.py +0 -27
- thrustlab-0.1.1/tests/test_resources/test_subscriptions.py +0 -45
- thrustlab-0.1.1/tests/test_spec_coverage.py +0 -74
- thrustlab-0.1.1/thrustlab/_models/__init__.py +0 -211
- thrustlab-0.1.1/thrustlab/_models/api_key.py +0 -184
- thrustlab-0.1.1/thrustlab/_models/api_key_with_secret.py +0 -192
- thrustlab-0.1.1/thrustlab/_models/checkout_session_resource.py +0 -94
- thrustlab-0.1.1/thrustlab/_models/rotor_group_in.py +0 -107
- thrustlab-0.1.1/thrustlab/_models/simulation_create_body.py +0 -128
- thrustlab-0.1.1/thrustlab/_models/simulation_resource_result_type_0.py +0 -46
- thrustlab-0.1.1/thrustlab/_models/subscription_create.py +0 -58
- thrustlab-0.1.1/thrustlab/_models/subscription_resource.py +0 -168
- thrustlab-0.1.1/thrustlab/_models/subscription_resource_status.py +0 -12
- thrustlab-0.1.1/thrustlab/_models/subscription_resource_tier.py +0 -12
- thrustlab-0.1.1/thrustlab/_models/sweep_config_in.py +0 -172
- thrustlab-0.1.1/thrustlab/_models/sweep_create_body.py +0 -138
- thrustlab-0.1.1/thrustlab/_models/sweep_rotor_group_in.py +0 -95
- thrustlab-0.1.1/thrustlab/resources/api_keys.py +0 -43
- thrustlab-0.1.1/thrustlab/resources/components.py +0 -60
- thrustlab-0.1.1/thrustlab/resources/credits.py +0 -40
- thrustlab-0.1.1/thrustlab/resources/subscriptions.py +0 -42
- {thrustlab-0.1.1 → thrustlab-0.3.0}/.gitignore +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/LICENSE +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/Makefile +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/NOTICE +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/projects/create.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/projects/list.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/projects/retrieve.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/simulations/wait.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/examples/webhooks/verify_handler.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/pyproject.toml +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/__init__.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/conftest.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_exceptions.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_http.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/__init__.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_components.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_projects.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_starred_components.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_submissions.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_users.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_resources/test_webhook_endpoints.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/tests/test_webhooks.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_http.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_brand.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_brands_response.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_create_body.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_patch.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/component_resource.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_balance_resource.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_bucket_breakdown.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_bucket_breakdown_type.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_event_resource_type.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_list_response.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_related_resource.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/credit_usage_related_resource_object.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/http_validation_error.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_create_body.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_list_response.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/project_patch.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_inputs.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_list_response.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/simulation_patch.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/starred_component_create_body.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/starred_component_list_response.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/starred_component_resource.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_create_body.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_list_response.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_patch.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/submission_resource.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_cancel_body.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_inputs.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_list_response.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_param_range_in.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_param_range_in_mode.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/sweep_patch.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/types.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/validation_error.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/webhook_endpoint_create.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/_models/webhook_endpoint_patch.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/__init__.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/_base.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/projects.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/starred_components.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/submissions.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/users.py +0 -0
- {thrustlab-0.1.1 → thrustlab-0.3.0}/thrustlab/resources/webhook_endpoints.py +0 -0
- {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.
|
|
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
|
[](https://pypi.org/project/thrustlab/)
|
|
36
36
|
[](https://pypi.org/project/thrustlab/)
|
|
37
|
-
[](https://github.com/kylebedrich/thrustlab/blob/
|
|
37
|
+
[](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(
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|
|
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 `
|
|
143
|
-
terminal state (`
|
|
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"] == "
|
|
149
|
-
|
|
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
|
-
|
|
227
|
+
Apache-2.0. See [`LICENSE`](./LICENSE) and [`NOTICE`](./NOTICE).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/thrustlab/)
|
|
4
4
|
[](https://pypi.org/project/thrustlab/)
|
|
5
|
-
[](https://github.com/kylebedrich/thrustlab/blob/
|
|
5
|
+
[](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(
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|
|
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 `
|
|
111
|
-
terminal state (`
|
|
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"] == "
|
|
117
|
-
|
|
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
|
-
|
|
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}")
|