axilio 0.1.1__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.
- axilio-0.1.1/.github/workflows/publish.yml +80 -0
- axilio-0.1.1/.github/workflows/quality.yml +53 -0
- axilio-0.1.1/.github/workflows/regen.yml +171 -0
- axilio-0.1.1/.github/workflows/tag-on-merge.yml +67 -0
- axilio-0.1.1/.github/workflows/test.yml +24 -0
- axilio-0.1.1/.gitignore +32 -0
- axilio-0.1.1/PKG-INFO +128 -0
- axilio-0.1.1/README.md +113 -0
- axilio-0.1.1/pyproject.toml +86 -0
- axilio-0.1.1/scripts/regen.sh +71 -0
- axilio-0.1.1/specs/production/openapi.json +8123 -0
- axilio-0.1.1/specs/staging/openapi.json +8123 -0
- axilio-0.1.1/src/axilio/__init__.py +50 -0
- axilio-0.1.1/src/axilio/_errors.py +92 -0
- axilio-0.1.1/src/axilio/_generated/__init__.py +8 -0
- axilio-0.1.1/src/axilio/_generated/api/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/api_keys/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/api_keys/apikeys_create.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/api_keys/apikeys_delete.py +161 -0
- axilio-0.1.1/src/axilio/_generated/api/api_keys/apikeys_list_get.py +185 -0
- axilio-0.1.1/src/axilio/_generated/api/api_keys/apikeys_list_post.py +167 -0
- axilio-0.1.1/src/axilio/_generated/api/api_keys/apikeys_regenerate.py +165 -0
- axilio-0.1.1/src/axilio/_generated/api/auth/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/auth/user_sign_in.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/auth/user_sign_up.py +175 -0
- axilio-0.1.1/src/axilio/_generated/api/auth/user_validate_invite.py +189 -0
- axilio-0.1.1/src/axilio/_generated/api/auth/user_waitlist.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_add_funds.py +167 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_cancel_downgrade.py +134 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_cancel_rental_subscriptions.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_cancel_subscription.py +134 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_create_checkout.py +167 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_create_rental_checkout.py +173 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_customer_portal.py +138 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_download_invoice.py +167 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_get_balance.py +134 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_get_history.py +271 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_get_rental_subscriptions.py +136 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_get_subscription.py +134 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_initiate_downgrade.py +167 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_renew_rental_subscriptions.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_sync_history.py +138 -0
- axilio-0.1.1/src/axilio/_generated/api/billing/billing_validate_plan_change.py +170 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_allocate.py +175 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_allocation_status.py +193 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_available.py +170 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_available_by_location.py +138 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_connect.py +175 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_counts.py +170 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_deallocate_user.py +174 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_for_workflow.py +170 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_get.py +165 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_interaction.py +165 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_my.py +170 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_nickname.py +187 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_supported_apps.py +185 -0
- axilio-0.1.1/src/axilio/_generated/api/devices/device_wipe.py +169 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_add_member.py +187 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_create.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_create_invitation.py +187 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_get.py +161 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_list.py +134 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_list_invitations.py +161 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_list_members.py +161 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_remove_member.py +179 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_revoke_invitation.py +179 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_update.py +187 -0
- axilio-0.1.1/src/axilio/_generated/api/organizations/organization_update_member_role.py +201 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/run_cancel.py +165 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/run_create.py +191 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/run_get_for_user.py +159 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/run_list.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/run_list_events.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/run_list_historic.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/runs/run_stats.py +161 -0
- axilio-0.1.1/src/axilio/_generated/api/usage/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/usage/usage_get_metrics.py +235 -0
- axilio-0.1.1/src/axilio/_generated/api/usage/usage_list_sessions.py +175 -0
- axilio-0.1.1/src/axilio/_generated/api/usage/usage_post_metrics.py +167 -0
- axilio-0.1.1/src/axilio/_generated/api/user/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/user/user_delete_me.py +138 -0
- axilio-0.1.1/src/axilio/_generated/api/user/user_get_me.py +134 -0
- axilio-0.1.1/src/axilio/_generated/api/user/user_provision.py +142 -0
- axilio-0.1.1/src/axilio/_generated/api/user_settings/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/user_settings/user_settings_get.py +138 -0
- axilio-0.1.1/src/axilio/_generated/api/user_settings/user_settings_update.py +175 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/__init__.py +1 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_create.py +171 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_delete.py +161 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_get.py +165 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_get_code.py +165 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_get_revision.py +179 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_list_get.py +246 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_list_post.py +167 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_list_revisions.py +201 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_restore_revision.py +187 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_save_code.py +187 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_update.py +187 -0
- axilio-0.1.1/src/axilio/_generated/api/workflows/workflow_update_run.py +183 -0
- axilio-0.1.1/src/axilio/_generated/client.py +268 -0
- axilio-0.1.1/src/axilio/_generated/errors.py +16 -0
- axilio-0.1.1/src/axilio/_generated/models/__init__.py +257 -0
- axilio-0.1.1/src/axilio/_generated/models/apikey_api_key_create_request.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/apikey_api_key_create_response.py +79 -0
- axilio-0.1.1/src/axilio/_generated/models/apikey_api_key_list_item.py +95 -0
- axilio-0.1.1/src/axilio/_generated/models/apikey_api_key_list_request.py +146 -0
- axilio-0.1.1/src/axilio/_generated/models/apikey_api_key_list_response.py +110 -0
- axilio-0.1.1/src/axilio/_generated/models/apikey_api_key_regenerate_response.py +87 -0
- axilio-0.1.1/src/axilio/_generated/models/apikey_api_key_sort_spec.py +50 -0
- axilio-0.1.1/src/axilio/_generated/models/billing_history_billing_history_item.py +176 -0
- axilio-0.1.1/src/axilio/_generated/models/billing_history_billing_history_response.py +101 -0
- axilio-0.1.1/src/axilio/_generated/models/billing_history_invoice_download_request.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/billing_history_invoice_download_response.py +63 -0
- axilio-0.1.1/src/axilio/_generated/models/billing_sync_history_response.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/delete_api_key_output_body.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/device_allocate_device_request.py +70 -0
- axilio-0.1.1/src/axilio/_generated/models/device_allocate_device_response.py +88 -0
- axilio-0.1.1/src/axilio/_generated/models/device_allocation_status_response.py +115 -0
- axilio-0.1.1/src/axilio/_generated/models/device_available_devices_by_location_response.py +94 -0
- axilio-0.1.1/src/axilio/_generated/models/device_available_devices_response.py +101 -0
- axilio-0.1.1/src/axilio/_generated/models/device_connect_device_request.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/device_deallocate_device_response.py +87 -0
- axilio-0.1.1/src/axilio/_generated/models/device_device_app_summary.py +259 -0
- axilio-0.1.1/src/axilio/_generated/models/device_device_app_summary_app_metadata.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/device_device_counts_response.py +69 -0
- axilio-0.1.1/src/axilio/_generated/models/device_device_counts_response_android.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/device_device_counts_response_iphone.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/device_device_summary.py +456 -0
- axilio-0.1.1/src/axilio/_generated/models/device_device_summary_device_metadata.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/device_location_device_count.py +82 -0
- axilio-0.1.1/src/axilio/_generated/models/device_private_devices_response.py +94 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_cancel_device_rental_subscriptions_request.py +72 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_cancel_device_rental_subscriptions_response.py +121 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_create_device_rental_checkout_request.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_create_device_rental_checkout_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_device_rental_subscription_list_response.py +90 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_device_rental_subscription_response.py +161 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_renew_device_rental_subscriptions_request.py +72 -0
- axilio-0.1.1/src/axilio/_generated/models/device_rental_renew_device_rental_subscriptions_response.py +121 -0
- axilio-0.1.1/src/axilio/_generated/models/device_success_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/device_supported_device_apps_response.py +94 -0
- axilio-0.1.1/src/axilio/_generated/models/device_update_nickname_request.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/message_output_body.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_add_org_member_request.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_create_invitation_request.py +70 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_invitation_list_response.py +86 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_invitation_response.py +121 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_organization_create_request.py +78 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_organization_list_response.py +88 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_organization_member_list_response.py +86 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_organization_member_response.py +113 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_organization_response.py +128 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_organization_update_request.py +69 -0
- axilio-0.1.1/src/axilio/_generated/models/organization_update_org_member_role_request.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/run_historic_run.py +166 -0
- axilio-0.1.1/src/axilio/_generated/models/run_historic_runs_request.py +128 -0
- axilio-0.1.1/src/axilio/_generated/models/run_historic_runs_response.py +109 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_config.py +86 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_config_variables_type_0_item.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_create_request.py +103 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_create_response.py +72 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_event_summary.py +69 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_events_request.py +100 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_events_response.py +110 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_list_request.py +186 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_list_response.py +109 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_response.py +270 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_response_run_metadata.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_response_variables.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_sort_spec.py +50 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_stats_response.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/run_run_time_config.py +69 -0
- axilio-0.1.1/src/axilio/_generated/models/run_success_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_add_funds_request.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_add_funds_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_balance_response.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_cancel_downgrade_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_cancel_subscription_response.py +63 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_create_checkout_session_request.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_create_checkout_session_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_customer_portal_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_downgrade_request.py +62 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_downgrade_response.py +71 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_downgrade_validation.py +163 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_limit_impact.py +74 -0
- axilio-0.1.1/src/axilio/_generated/models/subscription_subscription_response.py +248 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_billing_session.py +169 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_billing_session_session_metadata.py +46 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_billing_sessions_request.py +235 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_billing_sessions_response.py +109 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_chart_data_point.py +68 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_compute_minutes.py +90 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_get_metrics_granularity.py +9 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_infrastructure_costs.py +98 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_session_sort_spec.py +50 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_usage_metrics_request.py +79 -0
- axilio-0.1.1/src/axilio/_generated/models/usage_usage_metrics_response.py +95 -0
- axilio-0.1.1/src/axilio/_generated/models/user_auth_response.py +70 -0
- axilio-0.1.1/src/axilio/_generated/models/user_invite_code_validation_response.py +62 -0
- axilio-0.1.1/src/axilio/_generated/models/user_provision_response.py +62 -0
- axilio-0.1.1/src/axilio/_generated/models/user_settings_user_settings_response.py +60 -0
- axilio-0.1.1/src/axilio/_generated/models/user_settings_user_settings_update_request.py +60 -0
- axilio-0.1.1/src/axilio/_generated/models/user_settings_user_settings_update_response.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/user_sign_in_request.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/user_sign_up_request.py +86 -0
- axilio-0.1.1/src/axilio/_generated/models/user_user_response.py +93 -0
- axilio-0.1.1/src/axilio/_generated/models/user_waitlist_request.py +62 -0
- axilio-0.1.1/src/axilio/_generated/models/user_waitlist_response.py +61 -0
- axilio-0.1.1/src/axilio/_generated/models/v2_error_detail.py +60 -0
- axilio-0.1.1/src/axilio/_generated/models/v2_error_model.py +133 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_get_code_response.py +85 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_list_revisions_response.py +86 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_restore_revision_request.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_revision_detail.py +117 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_revision_summary.py +98 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_save_code_request.py +62 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_save_code_response.py +69 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_create_request.py +77 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_create_response.py +53 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_list_request.py +177 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_list_response.py +109 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_response.py +69 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_run_update_request.py +60 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_sort_spec.py +50 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_stats.py +50 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_summary.py +155 -0
- axilio-0.1.1/src/axilio/_generated/models/workflow_workflow_update_request.py +78 -0
- axilio-0.1.1/src/axilio/_generated/py.typed +1 -0
- axilio-0.1.1/src/axilio/_generated/types.py +54 -0
- axilio-0.1.1/src/axilio/_http.py +100 -0
- axilio-0.1.1/src/axilio/_mode.py +43 -0
- axilio-0.1.1/src/axilio/_resource.py +35 -0
- axilio-0.1.1/src/axilio/api_keys/__init__.py +8 -0
- axilio-0.1.1/src/axilio/api_keys/resource.py +33 -0
- axilio-0.1.1/src/axilio/api_keys/types.py +32 -0
- axilio-0.1.1/src/axilio/billing/__init__.py +11 -0
- axilio-0.1.1/src/axilio/billing/resource.py +45 -0
- axilio-0.1.1/src/axilio/billing/types.py +52 -0
- axilio-0.1.1/src/axilio/client.py +126 -0
- axilio-0.1.1/src/axilio/devices/__init__.py +37 -0
- axilio-0.1.1/src/axilio/devices/allocation.py +64 -0
- axilio-0.1.1/src/axilio/devices/device.py +200 -0
- axilio-0.1.1/src/axilio/devices/resource.py +89 -0
- axilio-0.1.1/src/axilio/devices/types.py +73 -0
- axilio-0.1.1/src/axilio/org/__init__.py +11 -0
- axilio-0.1.1/src/axilio/org/resource.py +29 -0
- axilio-0.1.1/src/axilio/org/types.py +45 -0
- axilio-0.1.1/src/axilio/runs/__init__.py +8 -0
- axilio-0.1.1/src/axilio/runs/resource.py +39 -0
- axilio-0.1.1/src/axilio/runs/types.py +46 -0
- axilio-0.1.1/src/axilio/usage/__init__.py +8 -0
- axilio-0.1.1/src/axilio/usage/resource.py +38 -0
- axilio-0.1.1/src/axilio/usage/types.py +45 -0
- axilio-0.1.1/src/axilio/workflows/__init__.py +8 -0
- axilio-0.1.1/src/axilio/workflows/resource.py +39 -0
- axilio-0.1.1/src/axilio/workflows/types.py +33 -0
- axilio-0.1.1/tests/test_client.py +124 -0
- axilio-0.1.1/uv.lock +245 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Fires when a backend/vX.Y.Z tag lands on the repo. Builds the wheel
|
|
4
|
+
# + sdist for that revision and uploads them to PyPI.
|
|
5
|
+
#
|
|
6
|
+
# Tag origin: the tag-on-merge.yml workflow creates the tag on every
|
|
7
|
+
# push to main that moves pyproject.toml's version (AXI-590). The
|
|
8
|
+
# version was itself stamped by the regen workflow from
|
|
9
|
+
# backend/internal/version/version.go via the spec — so by the time
|
|
10
|
+
# this workflow fires, SDK version, tag, and the backend revision
|
|
11
|
+
# that produced the spec are all in lockstep.
|
|
12
|
+
#
|
|
13
|
+
# Auth: `PYPI_API_TOKEN` repo secret. Generated at
|
|
14
|
+
# pypi.org/manage/account/token, scoped to the `axilio` project.
|
|
15
|
+
# The first publish (0.1.0) registers the project name on PyPI; once
|
|
16
|
+
# that's done, subsequent uploads inherit project-scope.
|
|
17
|
+
#
|
|
18
|
+
# If the secret is unset, the upload step fails — visible failure
|
|
19
|
+
# beats a silent skip that masks a broken publish path forever.
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
tags:
|
|
24
|
+
- 'backend/v*.*.*'
|
|
25
|
+
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
publish:
|
|
31
|
+
name: Build + upload ${{ github.ref_name }}
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment: pypi
|
|
34
|
+
steps:
|
|
35
|
+
- name: Checkout tag
|
|
36
|
+
uses: actions/checkout@v4
|
|
37
|
+
with:
|
|
38
|
+
# The push event targets the tag itself; checkout@v4 reads
|
|
39
|
+
# `github.ref` by default which is `refs/tags/backend/vX.Y.Z`.
|
|
40
|
+
# No extra config needed — just verify.
|
|
41
|
+
ref: ${{ github.ref }}
|
|
42
|
+
|
|
43
|
+
- name: Set up Python
|
|
44
|
+
uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: '3.12'
|
|
47
|
+
|
|
48
|
+
- name: Verify tag version matches pyproject.toml
|
|
49
|
+
# Belt-and-braces: tag-on-merge.yml derives the tag FROM
|
|
50
|
+
# pyproject.toml, so they should always agree. If a future
|
|
51
|
+
# workflow change breaks that invariant we'd rather fail
|
|
52
|
+
# loudly here than ship a mis-versioned wheel.
|
|
53
|
+
run: |
|
|
54
|
+
# Tag is like `backend/v0.1.0`; strip the prefix.
|
|
55
|
+
TAG_VERSION="${GITHUB_REF_NAME#backend/v}"
|
|
56
|
+
FILE_VERSION=$(awk -F'"' '/^version = ".+"/{print $2; exit}' pyproject.toml)
|
|
57
|
+
if [[ "$TAG_VERSION" != "$FILE_VERSION" ]]; then
|
|
58
|
+
echo "::error::Tag version ($TAG_VERSION) does not match pyproject.toml ($FILE_VERSION)"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
echo "tag $GITHUB_REF_NAME matches pyproject.toml $FILE_VERSION ✓"
|
|
62
|
+
|
|
63
|
+
- name: Install build tools
|
|
64
|
+
run: pip install --upgrade build twine
|
|
65
|
+
|
|
66
|
+
- name: Build wheel + sdist
|
|
67
|
+
run: python -m build
|
|
68
|
+
|
|
69
|
+
- name: Validate artifacts
|
|
70
|
+
run: twine check dist/*
|
|
71
|
+
|
|
72
|
+
- name: Upload to PyPI
|
|
73
|
+
env:
|
|
74
|
+
TWINE_USERNAME: __token__
|
|
75
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
76
|
+
# twine returns nonzero if PyPI rejects (incl. duplicate file
|
|
77
|
+
# uploads — re-tagging the same version is operator error,
|
|
78
|
+
# not silent-success territory). Caller can re-run the
|
|
79
|
+
# workflow after fixing the underlying issue.
|
|
80
|
+
run: twine upload --non-interactive dist/*
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Quality
|
|
2
|
+
|
|
3
|
+
# Black + ruff + mypy with versions pinned in pyproject.toml's [dev]
|
|
4
|
+
# extras. Versions match axilioai/axilio's python_checks.yml so a PR
|
|
5
|
+
# that's clean here is clean there and vice versa — bumping in one
|
|
6
|
+
# place without the other will drift; see AXI-588's ticket body for
|
|
7
|
+
# why we mirror manually rather than sharing a script.
|
|
8
|
+
#
|
|
9
|
+
# Generated code under src/axilio/_generated/ is excluded from all
|
|
10
|
+
# three tools via pyproject.toml — the regen workflow replaces it
|
|
11
|
+
# wholesale on each backend deploy, so any formatting opinions we
|
|
12
|
+
# inflict on it get reverted next regen.
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
branches: [main]
|
|
17
|
+
pull_request:
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
quality:
|
|
24
|
+
name: Black + Ruff + MyPy
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Set up Python
|
|
30
|
+
uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: '3.12'
|
|
33
|
+
cache: pip
|
|
34
|
+
cache-dependency-path: pyproject.toml
|
|
35
|
+
|
|
36
|
+
- name: Install dev deps
|
|
37
|
+
run: pip install -e '.[dev]'
|
|
38
|
+
|
|
39
|
+
# No --auto-fix in CI; humans run the fixer locally and commit.
|
|
40
|
+
# All three checks run even if an earlier one fails (`if: always()`)
|
|
41
|
+
# so a single CI run surfaces every category at once instead of
|
|
42
|
+
# forcing the contributor to push-fix-push-fix once per tool.
|
|
43
|
+
|
|
44
|
+
- name: black --check
|
|
45
|
+
run: black --check src/axilio tests
|
|
46
|
+
|
|
47
|
+
- name: ruff check
|
|
48
|
+
if: always()
|
|
49
|
+
run: ruff check src/axilio tests
|
|
50
|
+
|
|
51
|
+
- name: mypy
|
|
52
|
+
if: always()
|
|
53
|
+
run: mypy src/axilio tests
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
name: SDK Regenerate
|
|
2
|
+
|
|
3
|
+
# Fires when the axilio monorepo's publish-sdk-spec job lands a new
|
|
4
|
+
# *production* spec. Steps:
|
|
5
|
+
#
|
|
6
|
+
# 1. Pull the just-committed specs/production/openapi.json.
|
|
7
|
+
# 2. Run scripts/regen.sh against it — replaces src/axilio/_generated/
|
|
8
|
+
# with the freshly regenerated package.
|
|
9
|
+
# 3. Stamp pyproject.toml's version field from info.version.
|
|
10
|
+
# 4. If anything moved, open a PR. Merging it tags + publishes
|
|
11
|
+
# via tag-on-merge.yml + publish.yml.
|
|
12
|
+
#
|
|
13
|
+
# Staging dispatches are intentionally ignored. The publish-sdk-spec
|
|
14
|
+
# job on axilio still PUTs specs/staging/openapi.json on each staging
|
|
15
|
+
# deploy for audit + visibility, but no regen PR opens — staging may
|
|
16
|
+
# carry endpoints prod doesn't have yet, and publishing a staging-
|
|
17
|
+
# driven SDK would give customers types for endpoints that 404 in
|
|
18
|
+
# production. The SDK should reflect what's actually live behind
|
|
19
|
+
# api.axilio.ai, which is the production deploy.
|
|
20
|
+
#
|
|
21
|
+
# Two triggers reach this workflow on each spec sync:
|
|
22
|
+
# - The Contents-API push from axilio commits a file under specs/**,
|
|
23
|
+
# which surfaces here as a normal push.
|
|
24
|
+
# - The explicit repository_dispatch openapi-spec-updated arrives
|
|
25
|
+
# immediately after, carrying axilio_sha for the regen PR.
|
|
26
|
+
#
|
|
27
|
+
# We listen for the dispatch only and ignore the push — the dispatch
|
|
28
|
+
# carries the upstream SHA we want in the PR title / body. Without it
|
|
29
|
+
# the PR would just say "spec changed" with no link back to which
|
|
30
|
+
# backend revision drove the change.
|
|
31
|
+
|
|
32
|
+
on:
|
|
33
|
+
repository_dispatch:
|
|
34
|
+
types: [openapi-spec-updated]
|
|
35
|
+
# Manual trigger only allows production. Local staging regen is
|
|
36
|
+
# `bash scripts/regen.sh staging` on a developer laptop — useful for
|
|
37
|
+
# eyeballing what's coming, doesn't produce a PR or publish.
|
|
38
|
+
workflow_dispatch: {}
|
|
39
|
+
|
|
40
|
+
permissions:
|
|
41
|
+
contents: write
|
|
42
|
+
pull-requests: write
|
|
43
|
+
|
|
44
|
+
jobs:
|
|
45
|
+
regenerate:
|
|
46
|
+
name: Regenerate production SDK
|
|
47
|
+
# Skip everything when the dispatch carries any environment other
|
|
48
|
+
# than production. Job-level `if` short-circuits before the rest
|
|
49
|
+
# of the workflow consumes any minutes.
|
|
50
|
+
if: |
|
|
51
|
+
github.event_name == 'workflow_dispatch' ||
|
|
52
|
+
github.event.client_payload.environment == 'production'
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
|
|
57
|
+
- name: Set up Python
|
|
58
|
+
uses: actions/setup-python@v5
|
|
59
|
+
with:
|
|
60
|
+
python-version: '3.12'
|
|
61
|
+
|
|
62
|
+
- name: Install dev deps
|
|
63
|
+
run: pip install -e '.[dev]'
|
|
64
|
+
|
|
65
|
+
- name: Regenerate SDK
|
|
66
|
+
run: bash scripts/regen.sh production
|
|
67
|
+
|
|
68
|
+
- name: Stamp pyproject.toml version from spec
|
|
69
|
+
id: stamp
|
|
70
|
+
# Reads info.version from specs/production/openapi.json (driven
|
|
71
|
+
# by backend/internal/version/version.go via axilio's wireRoutes
|
|
72
|
+
# — see AXI-589) and writes it into pyproject.toml so the SDK
|
|
73
|
+
# version matches the backend revision that produced the spec.
|
|
74
|
+
#
|
|
75
|
+
# Reads from the file rather than from client_payload.backend_version
|
|
76
|
+
# so manual workflow_dispatch runs work without needing the
|
|
77
|
+
# dispatch payload — the spec on disk is the source of truth.
|
|
78
|
+
run: |
|
|
79
|
+
BACKEND_VERSION=$(jq -r '.info.version' "specs/production/openapi.json")
|
|
80
|
+
if [[ "$BACKEND_VERSION" == "null" || -z "$BACKEND_VERSION" ]]; then
|
|
81
|
+
echo "::error::Could not read info.version from specs/production/openapi.json"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
python3 - <<PY
|
|
85
|
+
import pathlib, re
|
|
86
|
+
v = "$BACKEND_VERSION"
|
|
87
|
+
p = pathlib.Path("pyproject.toml")
|
|
88
|
+
text = p.read_text()
|
|
89
|
+
# Use subn so we can tell "regex didn't match" (count == 0,
|
|
90
|
+
# an error — file lost its version line somehow) apart from
|
|
91
|
+
# "regex matched but replacement was a no-op" (count == 1,
|
|
92
|
+
# `new == text`, which is the common case where the spec's
|
|
93
|
+
# version equals what's already in pyproject.toml and there's
|
|
94
|
+
# nothing to do).
|
|
95
|
+
new, count = re.subn(r'^version = ".*"$', f'version = "{v}"', text, count=1, flags=re.MULTILINE)
|
|
96
|
+
if count == 0:
|
|
97
|
+
raise SystemExit("pyproject.toml has no top-level version line to stamp")
|
|
98
|
+
if new != text:
|
|
99
|
+
p.write_text(new)
|
|
100
|
+
PY
|
|
101
|
+
echo "backend_version=$BACKEND_VERSION" >> "$GITHUB_OUTPUT"
|
|
102
|
+
echo "Stamped pyproject.toml version = $BACKEND_VERSION"
|
|
103
|
+
|
|
104
|
+
- name: Detect changes
|
|
105
|
+
id: diff
|
|
106
|
+
# Open a PR if EITHER the generated code OR the stamped version
|
|
107
|
+
# moved. pyproject.toml-only changes happen when the backend's
|
|
108
|
+
# version constant bumped without altering the public surface
|
|
109
|
+
# (rare but possible — e.g. a meaningful internal refactor the
|
|
110
|
+
# engineer wanted to mark). Still worth a release.
|
|
111
|
+
run: |
|
|
112
|
+
if git diff --quiet src/axilio/_generated/ pyproject.toml; then
|
|
113
|
+
echo "Generated code + version unchanged; nothing to PR."
|
|
114
|
+
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
115
|
+
else
|
|
116
|
+
echo "Changes detected; opening PR."
|
|
117
|
+
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
- name: Open regen PR
|
|
121
|
+
if: steps.diff.outputs.changed == 'true'
|
|
122
|
+
# peter-evans/create-pull-request finds an existing PR on the
|
|
123
|
+
# same branch and updates it instead of opening duplicates, so
|
|
124
|
+
# successive dispatches for the same backend version don't
|
|
125
|
+
# accumulate PRs. Branch name keys on the backend version
|
|
126
|
+
# because the merged regen PR will be tagged backend/vX.Y.Z
|
|
127
|
+
# and a second commit on top would tag the same version —
|
|
128
|
+
# confusing.
|
|
129
|
+
uses: peter-evans/create-pull-request@v6
|
|
130
|
+
env:
|
|
131
|
+
BACKEND_VERSION: ${{ steps.stamp.outputs.backend_version }}
|
|
132
|
+
AXILIO_SHA: ${{ github.event.client_payload.axilio_sha || 'manual' }}
|
|
133
|
+
AXILIO_REF: ${{ github.event.client_payload.axilio_ref || 'manual' }}
|
|
134
|
+
with:
|
|
135
|
+
branch: regen/v${{ env.BACKEND_VERSION }}
|
|
136
|
+
base: main
|
|
137
|
+
title: "chore(sdk): regenerate from backend v${{ env.BACKEND_VERSION }}"
|
|
138
|
+
commit-message: |
|
|
139
|
+
chore(sdk): regenerate from backend v${{ env.BACKEND_VERSION }}
|
|
140
|
+
|
|
141
|
+
Auto-generated by .github/workflows/regen.yml.
|
|
142
|
+
Source: axilio@${{ env.AXILIO_SHA }} (${{ env.AXILIO_REF }})
|
|
143
|
+
body: |
|
|
144
|
+
Auto-generated by the SDK regen pipeline. Merging this PR
|
|
145
|
+
tags `backend/v${{ env.BACKEND_VERSION }}` on main and
|
|
146
|
+
publishes that tag to PyPI via publish.yml.
|
|
147
|
+
|
|
148
|
+
| | |
|
|
149
|
+
|---|---|
|
|
150
|
+
| Backend version | `${{ env.BACKEND_VERSION }}` |
|
|
151
|
+
| Axilio source SHA | `${{ env.AXILIO_SHA }}` |
|
|
152
|
+
| Axilio source ref | `${{ env.AXILIO_REF }}` |
|
|
153
|
+
| Spec path | `specs/production/openapi.json` |
|
|
154
|
+
| Generator | `openapi-python-client` |
|
|
155
|
+
|
|
156
|
+
## What changed
|
|
157
|
+
|
|
158
|
+
The production OpenAPI spec moved since the last regen.
|
|
159
|
+
`src/axilio/_generated/` and `pyproject.toml`'s version
|
|
160
|
+
field have been updated to match. Nothing hand-written
|
|
161
|
+
changed in this PR.
|
|
162
|
+
|
|
163
|
+
## What still needs human attention
|
|
164
|
+
|
|
165
|
+
Resource methods in `src/axilio/billing/`,
|
|
166
|
+
`src/axilio/devices/`, etc. don't change automatically
|
|
167
|
+
when the spec moves — those are the hand-written wrappers
|
|
168
|
+
around the generated typed call functions. If this regen
|
|
169
|
+
surfaces new endpoints (or removes some), the corresponding
|
|
170
|
+
resource wrapper needs to be updated by hand in a
|
|
171
|
+
follow-up PR.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: Tag on Version Bump
|
|
2
|
+
|
|
3
|
+
# Fires when a push to main moves pyproject.toml. Reads the version
|
|
4
|
+
# out of the file and creates an annotated `backend/vX.Y.Z` tag
|
|
5
|
+
# pointing at the merge commit.
|
|
6
|
+
#
|
|
7
|
+
# The receiver workflow (regen.yml) stamps pyproject.toml's version
|
|
8
|
+
# field to match the backend revision that produced the spec. Each
|
|
9
|
+
# merged regen PR is therefore a single deterministic version bump,
|
|
10
|
+
# and this workflow turns that into a tag — the PyPI publish workflow
|
|
11
|
+
# (AXI-591) hooks the tag to ship to PyPI.
|
|
12
|
+
#
|
|
13
|
+
# Idempotent: if a tag for the current version already exists, the
|
|
14
|
+
# workflow skips quietly. Important because someone might land a
|
|
15
|
+
# README-only PR that re-touches pyproject.toml's body without moving
|
|
16
|
+
# the version line — we don't want to fail those.
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
branches: [main]
|
|
21
|
+
paths:
|
|
22
|
+
- pyproject.toml
|
|
23
|
+
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
tag:
|
|
29
|
+
name: Tag backend/vX.Y.Z
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
|
|
36
|
+
- name: Read version from pyproject.toml
|
|
37
|
+
id: ver
|
|
38
|
+
run: |
|
|
39
|
+
# awk over a regex is enough for the single canonical
|
|
40
|
+
# `version = "..."` line. Avoids depending on tomllib /
|
|
41
|
+
# tomlkit and works on every runner Python.
|
|
42
|
+
VERSION=$(awk -F'"' '/^version = ".+"/{print $2; exit}' pyproject.toml)
|
|
43
|
+
if [[ -z "$VERSION" ]]; then
|
|
44
|
+
echo "::error::Could not read version from pyproject.toml"
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
48
|
+
echo "Read version: $VERSION"
|
|
49
|
+
|
|
50
|
+
- name: Create + push tag (idempotent)
|
|
51
|
+
env:
|
|
52
|
+
VERSION: ${{ steps.ver.outputs.version }}
|
|
53
|
+
run: |
|
|
54
|
+
TAG="backend/v${VERSION}"
|
|
55
|
+
|
|
56
|
+
# Check remote first — local clone post-checkout may or may
|
|
57
|
+
# not have fetched all tags depending on the runner cache.
|
|
58
|
+
if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q "${TAG}"; then
|
|
59
|
+
echo "tag ${TAG} already exists on origin; nothing to do"
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
git config user.name "axilio-bot"
|
|
64
|
+
git config user.email "bot@axilio.ai"
|
|
65
|
+
git tag -a "${TAG}" -m "Backend version ${VERSION}"
|
|
66
|
+
git push origin "${TAG}"
|
|
67
|
+
echo "tagged ${TAG} on $(git rev-parse HEAD)"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pytest:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: |
|
|
21
|
+
python -m pip install --upgrade pip
|
|
22
|
+
python -m pip install -e '.[dev]'
|
|
23
|
+
- name: Test
|
|
24
|
+
run: pytest -q
|
axilio-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
|
|
5
|
+
# Build / dist
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
*.egg
|
|
10
|
+
|
|
11
|
+
# Test / coverage
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
.coverage.*
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# Environments
|
|
18
|
+
.venv/
|
|
19
|
+
venv/
|
|
20
|
+
env/
|
|
21
|
+
|
|
22
|
+
# Tooling
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
|
|
26
|
+
# OS
|
|
27
|
+
.DS_Store
|
|
28
|
+
Thumbs.db
|
|
29
|
+
|
|
30
|
+
# IDE
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
axilio-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: axilio
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Axilio Python SDK — manage workflows, runs, devices, usage, and control devices remotely
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: httpx>=0.27
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
Requires-Dist: black==25.1.0; extra == 'dev'
|
|
9
|
+
Requires-Dist: mypy==1.16.0; extra == 'dev'
|
|
10
|
+
Requires-Dist: openapi-python-client>=0.21; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: ruff==0.12.1; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Axilio Python SDK
|
|
17
|
+
|
|
18
|
+
The official Python SDK for [Axilio](https://axilio.ai). One pip package covers account management, device discovery, allocation, and the full remote control surface (tap, swipe, OCR, screenshot, ...).
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install axilio
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from axilio import Client
|
|
30
|
+
|
|
31
|
+
# Reads AXILIO_API_KEY from the environment.
|
|
32
|
+
client = Client()
|
|
33
|
+
|
|
34
|
+
# One-shot device session — allocate, connect, control, clean up.
|
|
35
|
+
with client.devices.session(platform="android") as device:
|
|
36
|
+
device.tap(540, 1200)
|
|
37
|
+
device.type("hello world")
|
|
38
|
+
device.screenshot("after.png")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The same code runs unmodified inside an Axilio sandbox VM — when you schedule a workflow in the dashboard, Hephaestus injects scoped auth and the paired Atlas endpoint at boot, and the Client auto-detects sandbox mode. No `if local: ... else: ...` branches in your code.
|
|
42
|
+
|
|
43
|
+
## Authentication
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
export AXILIO_API_KEY=ax_live_...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or pass explicitly:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
client = Client(api_key="ax_...")
|
|
53
|
+
client = Client(api_key="ax_...", base_url="https://staging-api.axilio.ai")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Generate keys from the [Axilio dashboard](https://app.axilio.ai/settings/api-keys). API keys are scoped to one organization — multi-org users mint one key per org.
|
|
57
|
+
|
|
58
|
+
## Resources
|
|
59
|
+
|
|
60
|
+
| Resource | Methods | Notes |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `client.billing` | `balance()`, `subscription()`, `invoices()`, `upgrade()`, `downgrade()` | Reads everywhere; upgrade/downgrade local-only |
|
|
63
|
+
| `client.usage` | `metrics()`, `sessions()` | Both modes |
|
|
64
|
+
| `client.api_keys` | `list()`, `create()`, `revoke()` | Local-only |
|
|
65
|
+
| `client.org` | `current()`, `members()`, `invite()`, `remove_member()` | Reads everywhere; member writes local-only |
|
|
66
|
+
| `client.devices` | `available()`, `locations()`, `supported_apps()`, `allocate()`, `connect()`, `deallocate()`, `session()` | Sandbox mode short-circuits `allocate()` |
|
|
67
|
+
| `client.workflows` | `list()`, `get()`, `create()`, `update()`, `delete()` | Both modes |
|
|
68
|
+
| `client.runs` | `list()`, `get()`, `create()`, `cancel()` | Both modes |
|
|
69
|
+
|
|
70
|
+
For the full method surface, type signatures, and the device control vocabulary, see the [SDK design doc](https://github.com/axilioai/axilio/blob/main/.docs/sdk-design.md) in the monorepo.
|
|
71
|
+
|
|
72
|
+
## Errors
|
|
73
|
+
|
|
74
|
+
All errors raised by the SDK subclass `axilio.AxilioError`:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from axilio import Client, NotFoundError, RateLimitError, SandboxPermissionError
|
|
78
|
+
|
|
79
|
+
client = Client()
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
run = client.runs.get("run_does_not_exist")
|
|
83
|
+
except NotFoundError:
|
|
84
|
+
print("run not found")
|
|
85
|
+
except RateLimitError:
|
|
86
|
+
# backoff + retry
|
|
87
|
+
...
|
|
88
|
+
except SandboxPermissionError:
|
|
89
|
+
# tried a local-only write from inside a sandbox VM
|
|
90
|
+
...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Exception | HTTP | Notes |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `UnauthorizedError` | 401 | API key missing, malformed, or rejected |
|
|
96
|
+
| `NotFoundError` | 404 | Resource doesn't exist or isn't visible |
|
|
97
|
+
| `RateLimitError` | 429 | Retry after backoff |
|
|
98
|
+
| `ServerError` | 5xx | Transient; safe to retry |
|
|
99
|
+
| `SandboxPermissionError` | — | Local-only operation attempted from sandbox |
|
|
100
|
+
| `AllocationMismatchError` | — | Sandbox kwargs don't match the bound allocation |
|
|
101
|
+
| `AxilioError` | other | Catch-all |
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
| Env var | Description |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `AXILIO_API_KEY` | API key for authentication. Required in local mode. |
|
|
108
|
+
| `AXILIO_BASE_URL` | Override the API host (defaults to `https://api.axilio.ai`). |
|
|
109
|
+
| `AXILIO_SANDBOX_TOKEN` | Scoped sandbox token. Injected by Hephaestus inside a sandbox VM; not set in local mode. |
|
|
110
|
+
| `AXILIO_ATLAS_ENDPOINT` | Paired Atlas endpoint inside a sandbox VM. Injected by Hephaestus. |
|
|
111
|
+
|
|
112
|
+
| Constructor kwarg | Default | Description |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `api_key` | `AXILIO_API_KEY` env, then `AXILIO_SANDBOX_TOKEN` (sandbox mode) | Bearer token |
|
|
115
|
+
| `base_url` | `AXILIO_BASE_URL` env, then `https://api.axilio.ai` | API host |
|
|
116
|
+
| `timeout` | `30.0` | Request timeout in seconds |
|
|
117
|
+
| `max_retries` | `3` | Retry budget for 429 + 5xx (TODO: wire) |
|
|
118
|
+
| `retry_base_delay` | `0.5` | Exponential backoff base |
|
|
119
|
+
|
|
120
|
+
## Status
|
|
121
|
+
|
|
122
|
+
This is the v0 scaffold. The public surface (Client + resource classes) matches the design doc; method bodies are stubs (`NotImplementedError`) until codegen lands the typed wire calls. The codegen pipeline is driven by `repository_dispatch` from the monorepo's `publish-sdk-spec` job on each backend deploy — see `.github/workflows/sdk-regenerate.yml` (landing next).
|
|
123
|
+
|
|
124
|
+
## What's not here
|
|
125
|
+
|
|
126
|
+
- **Async client** — sync only for v0.
|
|
127
|
+
- **In-sandbox device drivers** (the implementation that actually drives a phone screen) — those live inside Atlas + the runner image, not in this SDK.
|
|
128
|
+
- **SSE / WebSocket subscriptions** for live run events — REST only; SSE lands when a consumer needs it.
|
axilio-0.1.1/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Axilio Python SDK
|
|
2
|
+
|
|
3
|
+
The official Python SDK for [Axilio](https://axilio.ai). One pip package covers account management, device discovery, allocation, and the full remote control surface (tap, swipe, OCR, screenshot, ...).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install axilio
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from axilio import Client
|
|
15
|
+
|
|
16
|
+
# Reads AXILIO_API_KEY from the environment.
|
|
17
|
+
client = Client()
|
|
18
|
+
|
|
19
|
+
# One-shot device session — allocate, connect, control, clean up.
|
|
20
|
+
with client.devices.session(platform="android") as device:
|
|
21
|
+
device.tap(540, 1200)
|
|
22
|
+
device.type("hello world")
|
|
23
|
+
device.screenshot("after.png")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The same code runs unmodified inside an Axilio sandbox VM — when you schedule a workflow in the dashboard, Hephaestus injects scoped auth and the paired Atlas endpoint at boot, and the Client auto-detects sandbox mode. No `if local: ... else: ...` branches in your code.
|
|
27
|
+
|
|
28
|
+
## Authentication
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export AXILIO_API_KEY=ax_live_...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or pass explicitly:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
client = Client(api_key="ax_...")
|
|
38
|
+
client = Client(api_key="ax_...", base_url="https://staging-api.axilio.ai")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Generate keys from the [Axilio dashboard](https://app.axilio.ai/settings/api-keys). API keys are scoped to one organization — multi-org users mint one key per org.
|
|
42
|
+
|
|
43
|
+
## Resources
|
|
44
|
+
|
|
45
|
+
| Resource | Methods | Notes |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `client.billing` | `balance()`, `subscription()`, `invoices()`, `upgrade()`, `downgrade()` | Reads everywhere; upgrade/downgrade local-only |
|
|
48
|
+
| `client.usage` | `metrics()`, `sessions()` | Both modes |
|
|
49
|
+
| `client.api_keys` | `list()`, `create()`, `revoke()` | Local-only |
|
|
50
|
+
| `client.org` | `current()`, `members()`, `invite()`, `remove_member()` | Reads everywhere; member writes local-only |
|
|
51
|
+
| `client.devices` | `available()`, `locations()`, `supported_apps()`, `allocate()`, `connect()`, `deallocate()`, `session()` | Sandbox mode short-circuits `allocate()` |
|
|
52
|
+
| `client.workflows` | `list()`, `get()`, `create()`, `update()`, `delete()` | Both modes |
|
|
53
|
+
| `client.runs` | `list()`, `get()`, `create()`, `cancel()` | Both modes |
|
|
54
|
+
|
|
55
|
+
For the full method surface, type signatures, and the device control vocabulary, see the [SDK design doc](https://github.com/axilioai/axilio/blob/main/.docs/sdk-design.md) in the monorepo.
|
|
56
|
+
|
|
57
|
+
## Errors
|
|
58
|
+
|
|
59
|
+
All errors raised by the SDK subclass `axilio.AxilioError`:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from axilio import Client, NotFoundError, RateLimitError, SandboxPermissionError
|
|
63
|
+
|
|
64
|
+
client = Client()
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
run = client.runs.get("run_does_not_exist")
|
|
68
|
+
except NotFoundError:
|
|
69
|
+
print("run not found")
|
|
70
|
+
except RateLimitError:
|
|
71
|
+
# backoff + retry
|
|
72
|
+
...
|
|
73
|
+
except SandboxPermissionError:
|
|
74
|
+
# tried a local-only write from inside a sandbox VM
|
|
75
|
+
...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Exception | HTTP | Notes |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `UnauthorizedError` | 401 | API key missing, malformed, or rejected |
|
|
81
|
+
| `NotFoundError` | 404 | Resource doesn't exist or isn't visible |
|
|
82
|
+
| `RateLimitError` | 429 | Retry after backoff |
|
|
83
|
+
| `ServerError` | 5xx | Transient; safe to retry |
|
|
84
|
+
| `SandboxPermissionError` | — | Local-only operation attempted from sandbox |
|
|
85
|
+
| `AllocationMismatchError` | — | Sandbox kwargs don't match the bound allocation |
|
|
86
|
+
| `AxilioError` | other | Catch-all |
|
|
87
|
+
|
|
88
|
+
## Configuration
|
|
89
|
+
|
|
90
|
+
| Env var | Description |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `AXILIO_API_KEY` | API key for authentication. Required in local mode. |
|
|
93
|
+
| `AXILIO_BASE_URL` | Override the API host (defaults to `https://api.axilio.ai`). |
|
|
94
|
+
| `AXILIO_SANDBOX_TOKEN` | Scoped sandbox token. Injected by Hephaestus inside a sandbox VM; not set in local mode. |
|
|
95
|
+
| `AXILIO_ATLAS_ENDPOINT` | Paired Atlas endpoint inside a sandbox VM. Injected by Hephaestus. |
|
|
96
|
+
|
|
97
|
+
| Constructor kwarg | Default | Description |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `api_key` | `AXILIO_API_KEY` env, then `AXILIO_SANDBOX_TOKEN` (sandbox mode) | Bearer token |
|
|
100
|
+
| `base_url` | `AXILIO_BASE_URL` env, then `https://api.axilio.ai` | API host |
|
|
101
|
+
| `timeout` | `30.0` | Request timeout in seconds |
|
|
102
|
+
| `max_retries` | `3` | Retry budget for 429 + 5xx (TODO: wire) |
|
|
103
|
+
| `retry_base_delay` | `0.5` | Exponential backoff base |
|
|
104
|
+
|
|
105
|
+
## Status
|
|
106
|
+
|
|
107
|
+
This is the v0 scaffold. The public surface (Client + resource classes) matches the design doc; method bodies are stubs (`NotImplementedError`) until codegen lands the typed wire calls. The codegen pipeline is driven by `repository_dispatch` from the monorepo's `publish-sdk-spec` job on each backend deploy — see `.github/workflows/sdk-regenerate.yml` (landing next).
|
|
108
|
+
|
|
109
|
+
## What's not here
|
|
110
|
+
|
|
111
|
+
- **Async client** — sync only for v0.
|
|
112
|
+
- **In-sandbox device drivers** (the implementation that actually drives a phone screen) — those live inside Atlas + the runner image, not in this SDK.
|
|
113
|
+
- **SSE / WebSocket subscriptions** for live run events — REST only; SSE lands when a consumer needs it.
|