hatchet-sdk 0.47.1__py3-none-any.whl → 1.0.0a1__py3-none-any.whl
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.
Potentially problematic release.
This version of hatchet-sdk might be problematic. Click here for more details.
- hatchet_sdk/__init__.py +45 -25
- hatchet_sdk/client.py +19 -94
- hatchet_sdk/clients/admin.py +309 -389
- hatchet_sdk/clients/dispatcher/action_listener.py +131 -109
- hatchet_sdk/clients/dispatcher/dispatcher.py +39 -37
- hatchet_sdk/clients/durable_event_listener.py +327 -0
- hatchet_sdk/clients/event_ts.py +23 -10
- hatchet_sdk/clients/events.py +96 -99
- hatchet_sdk/clients/rest/__init__.py +35 -0
- hatchet_sdk/clients/rest/api/__init__.py +2 -0
- hatchet_sdk/clients/rest/api/log_api.py +258 -0
- hatchet_sdk/clients/rest/api/task_api.py +2200 -0
- hatchet_sdk/clients/rest/api/workflow_runs_api.py +1274 -116
- hatchet_sdk/clients/rest/api_client.py +1 -1
- hatchet_sdk/clients/rest/configuration.py +8 -1
- hatchet_sdk/clients/rest/exceptions.py +21 -0
- hatchet_sdk/clients/rest/models/__init__.py +33 -0
- hatchet_sdk/clients/rest/models/tenant.py +4 -0
- hatchet_sdk/clients/rest/models/tenant_version.py +37 -0
- hatchet_sdk/clients/rest/models/update_tenant_request.py +7 -0
- hatchet_sdk/clients/rest/models/v1_cancel_task_request.py +104 -0
- hatchet_sdk/clients/rest/models/v1_dag_children.py +102 -0
- hatchet_sdk/clients/rest/models/v1_log_line.py +94 -0
- hatchet_sdk/clients/rest/models/v1_log_line_level.py +39 -0
- hatchet_sdk/clients/rest/models/v1_log_line_list.py +110 -0
- hatchet_sdk/clients/rest/models/v1_replay_task_request.py +104 -0
- hatchet_sdk/clients/rest/models/v1_task.py +174 -0
- hatchet_sdk/clients/rest/models/v1_task_event.py +118 -0
- hatchet_sdk/clients/rest/models/v1_task_event_list.py +110 -0
- hatchet_sdk/clients/rest/models/v1_task_event_type.py +55 -0
- hatchet_sdk/clients/rest/models/v1_task_filter.py +106 -0
- hatchet_sdk/clients/rest/models/v1_task_point_metric.py +92 -0
- hatchet_sdk/clients/rest/models/v1_task_point_metrics.py +100 -0
- hatchet_sdk/clients/rest/models/v1_task_run_metric.py +88 -0
- hatchet_sdk/clients/rest/models/v1_task_run_status.py +40 -0
- hatchet_sdk/clients/rest/models/v1_task_status.py +40 -0
- hatchet_sdk/clients/rest/models/v1_task_summary.py +228 -0
- hatchet_sdk/clients/rest/models/v1_task_summary_list.py +110 -0
- hatchet_sdk/clients/rest/models/v1_trigger_workflow_run_request.py +95 -0
- hatchet_sdk/clients/rest/models/v1_workflow_run.py +171 -0
- hatchet_sdk/clients/rest/models/v1_workflow_run_details.py +145 -0
- hatchet_sdk/clients/rest/models/v1_workflow_run_display_name.py +98 -0
- hatchet_sdk/clients/rest/models/v1_workflow_run_display_name_list.py +114 -0
- hatchet_sdk/clients/rest/models/v1_workflow_type.py +37 -0
- hatchet_sdk/clients/rest/models/workflow_run_shape_item_for_workflow_run_details.py +104 -0
- hatchet_sdk/clients/rest/rest.py +37 -26
- hatchet_sdk/clients/rest/tenacity_utils.py +1 -1
- hatchet_sdk/clients/rest_client.py +153 -116
- hatchet_sdk/clients/run_event_listener.py +65 -60
- hatchet_sdk/clients/workflow_listener.py +75 -66
- hatchet_sdk/config.py +117 -0
- hatchet_sdk/connection.py +27 -13
- hatchet_sdk/context/__init__.py +0 -1
- hatchet_sdk/context/context.py +118 -280
- hatchet_sdk/contracts/dispatcher_pb2_grpc.py +1 -1
- hatchet_sdk/contracts/events_pb2.py +2 -2
- hatchet_sdk/contracts/events_pb2_grpc.py +1 -1
- hatchet_sdk/contracts/v1/dispatcher_pb2.py +36 -0
- hatchet_sdk/contracts/v1/dispatcher_pb2.pyi +38 -0
- hatchet_sdk/contracts/v1/dispatcher_pb2_grpc.py +145 -0
- hatchet_sdk/contracts/v1/shared/condition_pb2.py +39 -0
- hatchet_sdk/contracts/v1/shared/condition_pb2.pyi +72 -0
- hatchet_sdk/contracts/v1/shared/condition_pb2_grpc.py +29 -0
- hatchet_sdk/contracts/v1/workflows_pb2.py +67 -0
- hatchet_sdk/contracts/v1/workflows_pb2.pyi +228 -0
- hatchet_sdk/contracts/v1/workflows_pb2_grpc.py +234 -0
- hatchet_sdk/contracts/workflows_pb2_grpc.py +1 -1
- hatchet_sdk/features/cron.py +43 -57
- hatchet_sdk/features/scheduled.py +60 -74
- hatchet_sdk/hatchet.py +491 -218
- hatchet_sdk/labels.py +4 -6
- hatchet_sdk/metadata.py +1 -1
- hatchet_sdk/opentelemetry/instrumentor.py +17 -18
- hatchet_sdk/rate_limit.py +40 -55
- hatchet_sdk/runnables/contextvars.py +12 -0
- hatchet_sdk/runnables/standalone.py +194 -0
- hatchet_sdk/runnables/task.py +144 -0
- hatchet_sdk/runnables/types.py +138 -0
- hatchet_sdk/runnables/workflow.py +764 -0
- hatchet_sdk/token.py +13 -9
- hatchet_sdk/utils/aio_utils.py +0 -119
- hatchet_sdk/utils/proto_enums.py +47 -0
- hatchet_sdk/utils/timedelta_to_expression.py +23 -0
- hatchet_sdk/utils/typing.py +10 -2
- hatchet_sdk/v0/__init__.py +251 -0
- hatchet_sdk/v0/client.py +119 -0
- hatchet_sdk/v0/clients/admin.py +541 -0
- hatchet_sdk/v0/clients/dispatcher/action_listener.py +422 -0
- hatchet_sdk/v0/clients/dispatcher/dispatcher.py +204 -0
- hatchet_sdk/v0/clients/event_ts.py +28 -0
- hatchet_sdk/v0/clients/events.py +182 -0
- hatchet_sdk/v0/clients/rest/__init__.py +307 -0
- hatchet_sdk/v0/clients/rest/api/__init__.py +19 -0
- hatchet_sdk/v0/clients/rest/api/api_token_api.py +858 -0
- hatchet_sdk/v0/clients/rest/api/default_api.py +2259 -0
- hatchet_sdk/v0/clients/rest/api/event_api.py +2548 -0
- hatchet_sdk/v0/clients/rest/api/github_api.py +331 -0
- hatchet_sdk/v0/clients/rest/api/healthcheck_api.py +483 -0
- hatchet_sdk/v0/clients/rest/api/log_api.py +449 -0
- hatchet_sdk/v0/clients/rest/api/metadata_api.py +728 -0
- hatchet_sdk/v0/clients/rest/api/rate_limits_api.py +423 -0
- hatchet_sdk/v0/clients/rest/api/slack_api.py +577 -0
- hatchet_sdk/v0/clients/rest/api/sns_api.py +872 -0
- hatchet_sdk/v0/clients/rest/api/step_run_api.py +2202 -0
- hatchet_sdk/v0/clients/rest/api/tenant_api.py +4430 -0
- hatchet_sdk/v0/clients/rest/api/user_api.py +2888 -0
- hatchet_sdk/v0/clients/rest/api/worker_api.py +858 -0
- hatchet_sdk/v0/clients/rest/api/workflow_api.py +6312 -0
- hatchet_sdk/v0/clients/rest/api/workflow_run_api.py +1932 -0
- hatchet_sdk/v0/clients/rest/api/workflow_runs_api.py +610 -0
- hatchet_sdk/v0/clients/rest/api_client.py +759 -0
- hatchet_sdk/v0/clients/rest/api_response.py +22 -0
- hatchet_sdk/v0/clients/rest/configuration.py +611 -0
- hatchet_sdk/v0/clients/rest/exceptions.py +200 -0
- hatchet_sdk/v0/clients/rest/models/__init__.py +274 -0
- hatchet_sdk/v0/clients/rest/models/accept_invite_request.py +83 -0
- hatchet_sdk/v0/clients/rest/models/api_error.py +102 -0
- hatchet_sdk/v0/clients/rest/models/api_errors.py +100 -0
- hatchet_sdk/v0/clients/rest/models/api_meta.py +144 -0
- hatchet_sdk/v0/clients/rest/models/api_meta_auth.py +85 -0
- hatchet_sdk/v0/clients/rest/models/api_meta_integration.py +88 -0
- hatchet_sdk/v0/clients/rest/models/api_meta_posthog.py +90 -0
- hatchet_sdk/v0/clients/rest/models/api_resource_meta.py +98 -0
- hatchet_sdk/v0/clients/rest/models/api_token.py +105 -0
- hatchet_sdk/v0/clients/rest/models/bulk_create_event_request.py +100 -0
- hatchet_sdk/v0/clients/rest/models/bulk_create_event_response.py +110 -0
- hatchet_sdk/v0/clients/rest/models/cancel_event_request.py +85 -0
- hatchet_sdk/v0/clients/rest/models/cancel_step_run_request.py +83 -0
- hatchet_sdk/v0/clients/rest/models/concurrency_limit_strategy.py +39 -0
- hatchet_sdk/v0/clients/rest/models/create_api_token_request.py +92 -0
- hatchet_sdk/v0/clients/rest/models/create_api_token_response.py +83 -0
- hatchet_sdk/v0/clients/rest/models/create_cron_workflow_trigger_request.py +98 -0
- hatchet_sdk/v0/clients/rest/models/create_event_request.py +95 -0
- hatchet_sdk/v0/clients/rest/models/create_pull_request_from_step_run.py +83 -0
- hatchet_sdk/v0/clients/rest/models/create_sns_integration_request.py +85 -0
- hatchet_sdk/v0/clients/rest/models/create_tenant_alert_email_group_request.py +83 -0
- hatchet_sdk/v0/clients/rest/models/create_tenant_invite_request.py +86 -0
- hatchet_sdk/v0/clients/rest/models/create_tenant_request.py +84 -0
- hatchet_sdk/v0/clients/rest/models/cron_workflows.py +131 -0
- hatchet_sdk/v0/clients/rest/models/cron_workflows_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/cron_workflows_method.py +37 -0
- hatchet_sdk/v0/clients/rest/models/cron_workflows_order_by_field.py +37 -0
- hatchet_sdk/v0/clients/rest/models/event.py +143 -0
- hatchet_sdk/v0/clients/rest/models/event_data.py +83 -0
- hatchet_sdk/v0/clients/rest/models/event_key_list.py +98 -0
- hatchet_sdk/v0/clients/rest/models/event_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/event_order_by_direction.py +37 -0
- hatchet_sdk/v0/clients/rest/models/event_order_by_field.py +36 -0
- hatchet_sdk/v0/clients/rest/models/event_update_cancel200_response.py +85 -0
- hatchet_sdk/v0/clients/rest/models/event_workflow_run_summary.py +116 -0
- hatchet_sdk/v0/clients/rest/models/events.py +110 -0
- hatchet_sdk/v0/clients/rest/models/get_step_run_diff_response.py +100 -0
- hatchet_sdk/v0/clients/rest/models/github_app_installation.py +107 -0
- hatchet_sdk/v0/clients/rest/models/github_branch.py +86 -0
- hatchet_sdk/v0/clients/rest/models/github_repo.py +86 -0
- hatchet_sdk/v0/clients/rest/models/info_get_version200_response.py +83 -0
- hatchet_sdk/v0/clients/rest/models/job.py +132 -0
- hatchet_sdk/v0/clients/rest/models/job_run.py +176 -0
- hatchet_sdk/v0/clients/rest/models/job_run_status.py +41 -0
- hatchet_sdk/v0/clients/rest/models/link_github_repository_request.py +106 -0
- hatchet_sdk/v0/clients/rest/models/list_api_tokens_response.py +110 -0
- hatchet_sdk/v0/clients/rest/models/list_github_app_installations_response.py +112 -0
- hatchet_sdk/v0/clients/rest/models/list_pull_requests_response.py +100 -0
- hatchet_sdk/v0/clients/rest/models/list_slack_webhooks.py +110 -0
- hatchet_sdk/v0/clients/rest/models/list_sns_integrations.py +110 -0
- hatchet_sdk/v0/clients/rest/models/log_line.py +94 -0
- hatchet_sdk/v0/clients/rest/models/log_line_level.py +39 -0
- hatchet_sdk/v0/clients/rest/models/log_line_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/log_line_order_by_direction.py +37 -0
- hatchet_sdk/v0/clients/rest/models/log_line_order_by_field.py +36 -0
- hatchet_sdk/v0/clients/rest/models/pagination_response.py +95 -0
- hatchet_sdk/v0/clients/rest/models/pull_request.py +112 -0
- hatchet_sdk/v0/clients/rest/models/pull_request_state.py +37 -0
- hatchet_sdk/v0/clients/rest/models/queue_metrics.py +97 -0
- hatchet_sdk/v0/clients/rest/models/rate_limit.py +117 -0
- hatchet_sdk/v0/clients/rest/models/rate_limit_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/rate_limit_order_by_direction.py +37 -0
- hatchet_sdk/v0/clients/rest/models/rate_limit_order_by_field.py +38 -0
- hatchet_sdk/v0/clients/rest/models/recent_step_runs.py +118 -0
- hatchet_sdk/v0/clients/rest/models/reject_invite_request.py +83 -0
- hatchet_sdk/v0/clients/rest/models/replay_event_request.py +85 -0
- hatchet_sdk/v0/clients/rest/models/replay_workflow_runs_request.py +85 -0
- hatchet_sdk/v0/clients/rest/models/replay_workflow_runs_response.py +100 -0
- hatchet_sdk/v0/clients/rest/models/rerun_step_run_request.py +83 -0
- hatchet_sdk/v0/clients/rest/models/schedule_workflow_run_request.py +92 -0
- hatchet_sdk/v0/clients/rest/models/scheduled_run_status.py +42 -0
- hatchet_sdk/v0/clients/rest/models/scheduled_workflows.py +149 -0
- hatchet_sdk/v0/clients/rest/models/scheduled_workflows_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/scheduled_workflows_method.py +37 -0
- hatchet_sdk/v0/clients/rest/models/scheduled_workflows_order_by_field.py +37 -0
- hatchet_sdk/v0/clients/rest/models/semaphore_slots.py +113 -0
- hatchet_sdk/v0/clients/rest/models/slack_webhook.py +127 -0
- hatchet_sdk/v0/clients/rest/models/sns_integration.py +114 -0
- hatchet_sdk/v0/clients/rest/models/step.py +123 -0
- hatchet_sdk/v0/clients/rest/models/step_run.py +202 -0
- hatchet_sdk/v0/clients/rest/models/step_run_archive.py +142 -0
- hatchet_sdk/v0/clients/rest/models/step_run_archive_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/step_run_diff.py +91 -0
- hatchet_sdk/v0/clients/rest/models/step_run_event.py +122 -0
- hatchet_sdk/v0/clients/rest/models/step_run_event_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/step_run_event_reason.py +52 -0
- hatchet_sdk/v0/clients/rest/models/step_run_event_severity.py +38 -0
- hatchet_sdk/v0/clients/rest/models/step_run_status.py +44 -0
- hatchet_sdk/v0/clients/rest/models/tenant.py +118 -0
- hatchet_sdk/v0/clients/rest/models/tenant_alert_email_group.py +98 -0
- hatchet_sdk/v0/clients/rest/models/tenant_alert_email_group_list.py +112 -0
- hatchet_sdk/v0/clients/rest/models/tenant_alerting_settings.py +143 -0
- hatchet_sdk/v0/clients/rest/models/tenant_invite.py +120 -0
- hatchet_sdk/v0/clients/rest/models/tenant_invite_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/tenant_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/tenant_member.py +123 -0
- hatchet_sdk/v0/clients/rest/models/tenant_member_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/tenant_member_role.py +38 -0
- hatchet_sdk/v0/clients/rest/models/tenant_queue_metrics.py +116 -0
- hatchet_sdk/v0/clients/rest/models/tenant_resource.py +40 -0
- hatchet_sdk/v0/clients/rest/models/tenant_resource_limit.py +135 -0
- hatchet_sdk/v0/clients/rest/models/tenant_resource_policy.py +102 -0
- hatchet_sdk/v0/clients/rest/models/tenant_step_run_queue_metrics.py +83 -0
- hatchet_sdk/v0/clients/rest/models/trigger_workflow_run_request.py +91 -0
- hatchet_sdk/v0/clients/rest/models/update_tenant_alert_email_group_request.py +83 -0
- hatchet_sdk/v0/clients/rest/models/update_tenant_invite_request.py +85 -0
- hatchet_sdk/v0/clients/rest/models/update_tenant_request.py +137 -0
- hatchet_sdk/v0/clients/rest/models/update_worker_request.py +87 -0
- hatchet_sdk/v0/clients/rest/models/user.py +126 -0
- hatchet_sdk/v0/clients/rest/models/user_change_password_request.py +88 -0
- hatchet_sdk/v0/clients/rest/models/user_login_request.py +86 -0
- hatchet_sdk/v0/clients/rest/models/user_register_request.py +91 -0
- hatchet_sdk/v0/clients/rest/models/user_tenant_memberships_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/user_tenant_public.py +86 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker.py +100 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker_create_request.py +94 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker_create_response.py +98 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker_created.py +102 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker_list_response.py +110 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker_request.py +102 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker_request_list_response.py +104 -0
- hatchet_sdk/v0/clients/rest/models/webhook_worker_request_method.py +38 -0
- hatchet_sdk/v0/clients/rest/models/worker.py +239 -0
- hatchet_sdk/v0/clients/rest/models/worker_label.py +102 -0
- hatchet_sdk/v0/clients/rest/models/worker_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/worker_runtime_info.py +103 -0
- hatchet_sdk/v0/clients/rest/models/worker_runtime_sdks.py +38 -0
- hatchet_sdk/v0/clients/rest/models/worker_type.py +38 -0
- hatchet_sdk/v0/clients/rest/models/workflow.py +165 -0
- hatchet_sdk/v0/clients/rest/models/workflow_concurrency.py +107 -0
- hatchet_sdk/v0/clients/rest/models/workflow_deployment_config.py +136 -0
- hatchet_sdk/v0/clients/rest/models/workflow_kind.py +38 -0
- hatchet_sdk/v0/clients/rest/models/workflow_list.py +120 -0
- hatchet_sdk/v0/clients/rest/models/workflow_metrics.py +97 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run.py +188 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run_cancel200_response.py +85 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run_list.py +110 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run_order_by_direction.py +37 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run_order_by_field.py +39 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run_shape.py +186 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run_status.py +42 -0
- hatchet_sdk/v0/clients/rest/models/workflow_run_triggered_by.py +112 -0
- hatchet_sdk/v0/clients/rest/models/workflow_runs_cancel_request.py +85 -0
- hatchet_sdk/v0/clients/rest/models/workflow_runs_metrics.py +94 -0
- hatchet_sdk/v0/clients/rest/models/workflow_runs_metrics_counts.py +104 -0
- hatchet_sdk/v0/clients/rest/models/workflow_tag.py +84 -0
- hatchet_sdk/v0/clients/rest/models/workflow_trigger_cron_ref.py +86 -0
- hatchet_sdk/v0/clients/rest/models/workflow_trigger_event_ref.py +86 -0
- hatchet_sdk/v0/clients/rest/models/workflow_triggers.py +141 -0
- hatchet_sdk/v0/clients/rest/models/workflow_update_request.py +85 -0
- hatchet_sdk/v0/clients/rest/models/workflow_version.py +170 -0
- hatchet_sdk/v0/clients/rest/models/workflow_version_concurrency.py +114 -0
- hatchet_sdk/v0/clients/rest/models/workflow_version_definition.py +85 -0
- hatchet_sdk/v0/clients/rest/models/workflow_version_meta.py +123 -0
- hatchet_sdk/v0/clients/rest/models/workflow_workers_count.py +95 -0
- hatchet_sdk/v0/clients/rest/rest.py +187 -0
- hatchet_sdk/v0/clients/rest/tenacity_utils.py +39 -0
- hatchet_sdk/v0/clients/rest_client.py +622 -0
- hatchet_sdk/v0/clients/run_event_listener.py +260 -0
- hatchet_sdk/v0/clients/workflow_listener.py +277 -0
- hatchet_sdk/v0/connection.py +63 -0
- hatchet_sdk/v0/context/__init__.py +1 -0
- hatchet_sdk/v0/context/context.py +446 -0
- hatchet_sdk/v0/context/worker_context.py +28 -0
- hatchet_sdk/v0/contracts/dispatcher_pb2.py +102 -0
- hatchet_sdk/v0/contracts/dispatcher_pb2.pyi +387 -0
- hatchet_sdk/v0/contracts/dispatcher_pb2_grpc.py +621 -0
- hatchet_sdk/v0/contracts/events_pb2.py +46 -0
- hatchet_sdk/v0/contracts/events_pb2.pyi +87 -0
- hatchet_sdk/v0/contracts/events_pb2_grpc.py +274 -0
- hatchet_sdk/v0/contracts/workflows_pb2.py +80 -0
- hatchet_sdk/v0/contracts/workflows_pb2.pyi +312 -0
- hatchet_sdk/v0/contracts/workflows_pb2_grpc.py +277 -0
- hatchet_sdk/v0/features/cron.py +286 -0
- hatchet_sdk/v0/features/scheduled.py +248 -0
- hatchet_sdk/v0/hatchet.py +310 -0
- hatchet_sdk/v0/labels.py +10 -0
- hatchet_sdk/v0/logger.py +13 -0
- hatchet_sdk/v0/metadata.py +2 -0
- hatchet_sdk/v0/opentelemetry/instrumentor.py +396 -0
- hatchet_sdk/v0/rate_limit.py +126 -0
- hatchet_sdk/v0/token.py +27 -0
- hatchet_sdk/v0/utils/aio_utils.py +137 -0
- hatchet_sdk/v0/utils/backoff.py +9 -0
- hatchet_sdk/v0/utils/typing.py +12 -0
- hatchet_sdk/{v2 → v0/v2}/callable.py +8 -8
- hatchet_sdk/{v2 → v0/v2}/concurrency.py +2 -2
- hatchet_sdk/{v2 → v0/v2}/hatchet.py +10 -10
- hatchet_sdk/v0/worker/__init__.py +1 -0
- hatchet_sdk/v0/worker/action_listener_process.py +294 -0
- hatchet_sdk/v0/worker/runner/run_loop_manager.py +112 -0
- hatchet_sdk/v0/worker/runner/runner.py +460 -0
- hatchet_sdk/v0/worker/runner/utils/capture_logs.py +81 -0
- hatchet_sdk/v0/worker/worker.py +391 -0
- hatchet_sdk/{workflow.py → v0/workflow.py} +4 -4
- hatchet_sdk/v0/workflow_run.py +59 -0
- hatchet_sdk/waits.py +120 -0
- hatchet_sdk/worker/__init__.py +0 -1
- hatchet_sdk/worker/action_listener_process.py +80 -55
- hatchet_sdk/worker/runner/run_loop_manager.py +46 -34
- hatchet_sdk/worker/runner/runner.py +82 -87
- hatchet_sdk/worker/runner/utils/capture_logs.py +26 -23
- hatchet_sdk/worker/worker.py +172 -149
- hatchet_sdk/workflow_run.py +7 -21
- {hatchet_sdk-0.47.1.dist-info → hatchet_sdk-1.0.0a1.dist-info}/METADATA +2 -2
- hatchet_sdk-1.0.0a1.dist-info/RECORD +505 -0
- {hatchet_sdk-0.47.1.dist-info → hatchet_sdk-1.0.0a1.dist-info}/entry_points.txt +2 -0
- hatchet_sdk/utils/serialization.py +0 -18
- hatchet_sdk-0.47.1.dist-info/RECORD +0 -237
- /hatchet_sdk/{loader.py → v0/loader.py} +0 -0
- /hatchet_sdk/{semver.py → v0/semver.py} +0 -0
- /hatchet_sdk/{utils → v0/utils}/types.py +0 -0
- /hatchet_sdk/{worker → v0/worker}/runner/utils/error_with_traceback.py +0 -0
- {hatchet_sdk-0.47.1.dist-info → hatchet_sdk-1.0.0a1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
from typing import Any, Callable, Collection, Coroutine
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from opentelemetry.context import Context
|
|
6
|
+
from opentelemetry.instrumentation.instrumentor import ( # type: ignore[attr-defined]
|
|
7
|
+
BaseInstrumentor,
|
|
8
|
+
)
|
|
9
|
+
from opentelemetry.instrumentation.utils import unwrap
|
|
10
|
+
from opentelemetry.metrics import MeterProvider, NoOpMeterProvider, get_meter
|
|
11
|
+
from opentelemetry.trace import (
|
|
12
|
+
NoOpTracerProvider,
|
|
13
|
+
StatusCode,
|
|
14
|
+
TracerProvider,
|
|
15
|
+
get_tracer,
|
|
16
|
+
get_tracer_provider,
|
|
17
|
+
)
|
|
18
|
+
from opentelemetry.trace.propagation.tracecontext import (
|
|
19
|
+
TraceContextTextMapPropagator,
|
|
20
|
+
)
|
|
21
|
+
from wrapt import wrap_function_wrapper # type: ignore[import-untyped]
|
|
22
|
+
except (RuntimeError, ImportError, ModuleNotFoundError):
|
|
23
|
+
raise ModuleNotFoundError(
|
|
24
|
+
"To use the HatchetInstrumentor, you must install Hatchet's `otel` extra using (e.g.) `pip install hatchet-sdk[otel]`"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
import hatchet_sdk
|
|
28
|
+
from hatchet_sdk.v0.clients.admin import (
|
|
29
|
+
AdminClient,
|
|
30
|
+
TriggerWorkflowOptions,
|
|
31
|
+
WorkflowRunDict,
|
|
32
|
+
)
|
|
33
|
+
from hatchet_sdk.v0.clients.dispatcher.action_listener import Action
|
|
34
|
+
from hatchet_sdk.v0.clients.events import (
|
|
35
|
+
BulkPushEventWithMetadata,
|
|
36
|
+
EventClient,
|
|
37
|
+
PushEventOptions,
|
|
38
|
+
)
|
|
39
|
+
from hatchet_sdk.v0.contracts.events_pb2 import Event
|
|
40
|
+
from hatchet_sdk.v0.worker.runner.runner import Runner
|
|
41
|
+
from hatchet_sdk.v0.workflow_run import WorkflowRunRef
|
|
42
|
+
|
|
43
|
+
hatchet_sdk_version = version("hatchet-sdk")
|
|
44
|
+
|
|
45
|
+
InstrumentKwargs = TracerProvider | MeterProvider | None
|
|
46
|
+
|
|
47
|
+
OTEL_TRACEPARENT_KEY = "traceparent"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def create_traceparent() -> str | None:
|
|
51
|
+
"""
|
|
52
|
+
Creates and returns a W3C traceparent header value using OpenTelemetry's context propagation.
|
|
53
|
+
|
|
54
|
+
The traceparent header is used to propagate context information across service boundaries
|
|
55
|
+
in distributed tracing systems. It follows the W3C Trace Context specification.
|
|
56
|
+
|
|
57
|
+
:returns: A W3C-formatted traceparent header value if successful, None if the context
|
|
58
|
+
injection fails or no active span exists.\n
|
|
59
|
+
Example: `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01`
|
|
60
|
+
:rtype: str | None:
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
carrier: dict[str, str] = {}
|
|
64
|
+
TraceContextTextMapPropagator().inject(carrier)
|
|
65
|
+
|
|
66
|
+
return carrier.get("traceparent")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_carrier_from_metadata(metadata: dict[str, str] | None) -> Context | None:
|
|
70
|
+
"""
|
|
71
|
+
Parses OpenTelemetry trace context from a metadata dictionary.
|
|
72
|
+
|
|
73
|
+
Extracts the trace context from metadata using the W3C Trace Context format,
|
|
74
|
+
specifically looking for the `traceparent` header.
|
|
75
|
+
|
|
76
|
+
:param metadata: A dictionary containing metadata key-value pairs,
|
|
77
|
+
potentially including the `traceparent` header. Can be None.
|
|
78
|
+
:type metadata: dict[str, str] | None
|
|
79
|
+
:returns: The extracted OpenTelemetry Context object if a valid `traceparent`
|
|
80
|
+
is found in the metadata, otherwise None.
|
|
81
|
+
:rtype: Context | None
|
|
82
|
+
|
|
83
|
+
:Example:
|
|
84
|
+
|
|
85
|
+
>>> metadata = {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"}
|
|
86
|
+
>>> context = parse_carrier_from_metadata(metadata)
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
if not metadata:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
traceparent = metadata.get(OTEL_TRACEPARENT_KEY)
|
|
93
|
+
|
|
94
|
+
if not traceparent:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
return TraceContextTextMapPropagator().extract({OTEL_TRACEPARENT_KEY: traceparent})
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def inject_traceparent_into_metadata(
|
|
101
|
+
metadata: dict[str, str], traceparent: str | None = None
|
|
102
|
+
) -> dict[str, str]:
|
|
103
|
+
"""
|
|
104
|
+
Injects OpenTelemetry `traceparent` into a metadata dictionary.
|
|
105
|
+
|
|
106
|
+
Takes a metadata dictionary and an optional `traceparent` string,
|
|
107
|
+
returning a new metadata dictionary with the `traceparent` added under the
|
|
108
|
+
`OTEL_TRACEPARENT_KEY`. If no `traceparent` is provided, it attempts to create one.
|
|
109
|
+
|
|
110
|
+
:param metadata: The metadata dictionary to inject the `traceparent` into.
|
|
111
|
+
:type metadata: dict[str, str]
|
|
112
|
+
:param traceparent: The `traceparent` string to inject. If None, attempts to use
|
|
113
|
+
the current span.
|
|
114
|
+
:type traceparent: str | None, optional
|
|
115
|
+
:returns: A new metadata dictionary containing the original metadata plus
|
|
116
|
+
the injected `traceparent`, if one was available or could be created.
|
|
117
|
+
:rtype: dict[str, str]
|
|
118
|
+
|
|
119
|
+
:Example:
|
|
120
|
+
|
|
121
|
+
>>> metadata = {"key": "value"}
|
|
122
|
+
>>> new_metadata = inject_traceparent(metadata, "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
|
|
123
|
+
>>> print(new_metadata)
|
|
124
|
+
{"key": "value", "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"}
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
if not traceparent:
|
|
128
|
+
traceparent = create_traceparent()
|
|
129
|
+
|
|
130
|
+
if not traceparent:
|
|
131
|
+
return metadata
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
**metadata,
|
|
135
|
+
OTEL_TRACEPARENT_KEY: traceparent,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class HatchetInstrumentor(BaseInstrumentor): # type: ignore[misc]
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
tracer_provider: TracerProvider | None = None,
|
|
143
|
+
meter_provider: MeterProvider | None = None,
|
|
144
|
+
):
|
|
145
|
+
"""
|
|
146
|
+
Hatchet OpenTelemetry instrumentor.
|
|
147
|
+
|
|
148
|
+
The instrumentor provides an OpenTelemetry integration for Hatchet by setting up
|
|
149
|
+
tracing and metrics collection.
|
|
150
|
+
|
|
151
|
+
:param tracer_provider: TracerProvider | None: The OpenTelemetry TracerProvider to use.
|
|
152
|
+
If not provided, the global tracer provider will be used.
|
|
153
|
+
:param meter_provider: MeterProvider | None: The OpenTelemetry MeterProvider to use.
|
|
154
|
+
If not provided, a no-op meter provider will be used.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
self.tracer_provider = tracer_provider or get_tracer_provider()
|
|
158
|
+
self.meter_provider = meter_provider or NoOpMeterProvider()
|
|
159
|
+
|
|
160
|
+
super().__init__()
|
|
161
|
+
|
|
162
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
|
163
|
+
return tuple()
|
|
164
|
+
|
|
165
|
+
def _instrument(self, **kwargs: InstrumentKwargs) -> None:
|
|
166
|
+
self._tracer = get_tracer(__name__, hatchet_sdk_version, self.tracer_provider)
|
|
167
|
+
self._meter = get_meter(__name__, hatchet_sdk_version, self.meter_provider)
|
|
168
|
+
|
|
169
|
+
wrap_function_wrapper(
|
|
170
|
+
hatchet_sdk,
|
|
171
|
+
"worker.runner.runner.Runner.handle_start_step_run",
|
|
172
|
+
self._wrap_handle_start_step_run,
|
|
173
|
+
)
|
|
174
|
+
wrap_function_wrapper(
|
|
175
|
+
hatchet_sdk,
|
|
176
|
+
"worker.runner.runner.Runner.handle_start_group_key_run",
|
|
177
|
+
self._wrap_handle_get_group_key_run,
|
|
178
|
+
)
|
|
179
|
+
wrap_function_wrapper(
|
|
180
|
+
hatchet_sdk,
|
|
181
|
+
"worker.runner.runner.Runner.handle_cancel_action",
|
|
182
|
+
self._wrap_handle_cancel_action,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
wrap_function_wrapper(
|
|
186
|
+
hatchet_sdk,
|
|
187
|
+
"clients.events.EventClient.push",
|
|
188
|
+
self._wrap_push_event,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
wrap_function_wrapper(
|
|
192
|
+
hatchet_sdk,
|
|
193
|
+
"clients.events.EventClient.bulk_push",
|
|
194
|
+
self._wrap_bulk_push_event,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
wrap_function_wrapper(
|
|
198
|
+
hatchet_sdk,
|
|
199
|
+
"clients.admin.AdminClient.run_workflow",
|
|
200
|
+
self._wrap_run_workflow,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
wrap_function_wrapper(
|
|
204
|
+
hatchet_sdk,
|
|
205
|
+
"clients.admin.AdminClientAioImpl.run_workflow",
|
|
206
|
+
self._wrap_async_run_workflow,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
wrap_function_wrapper(
|
|
210
|
+
hatchet_sdk,
|
|
211
|
+
"clients.admin.AdminClient.run_workflows",
|
|
212
|
+
self._wrap_run_workflows,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
wrap_function_wrapper(
|
|
216
|
+
hatchet_sdk,
|
|
217
|
+
"clients.admin.AdminClientAioImpl.run_workflows",
|
|
218
|
+
self._wrap_async_run_workflows,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
222
|
+
async def _wrap_handle_start_step_run(
|
|
223
|
+
self,
|
|
224
|
+
wrapped: Callable[[Action], Coroutine[None, None, Exception | None]],
|
|
225
|
+
instance: Runner,
|
|
226
|
+
args: tuple[Action],
|
|
227
|
+
kwargs: Any,
|
|
228
|
+
) -> Exception | None:
|
|
229
|
+
action = args[0]
|
|
230
|
+
traceparent = parse_carrier_from_metadata(action.additional_metadata)
|
|
231
|
+
|
|
232
|
+
with self._tracer.start_as_current_span(
|
|
233
|
+
"hatchet.start_step_run",
|
|
234
|
+
attributes=action.otel_attributes,
|
|
235
|
+
context=traceparent,
|
|
236
|
+
) as span:
|
|
237
|
+
result = await wrapped(*args, **kwargs)
|
|
238
|
+
|
|
239
|
+
if isinstance(result, Exception):
|
|
240
|
+
span.set_status(StatusCode.ERROR, str(result))
|
|
241
|
+
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
245
|
+
async def _wrap_handle_get_group_key_run(
|
|
246
|
+
self,
|
|
247
|
+
wrapped: Callable[[Action], Coroutine[None, None, Exception | None]],
|
|
248
|
+
instance: Runner,
|
|
249
|
+
args: tuple[Action],
|
|
250
|
+
kwargs: Any,
|
|
251
|
+
) -> Exception | None:
|
|
252
|
+
action = args[0]
|
|
253
|
+
|
|
254
|
+
with self._tracer.start_as_current_span(
|
|
255
|
+
"hatchet.get_group_key_run",
|
|
256
|
+
attributes=action.otel_attributes,
|
|
257
|
+
) as span:
|
|
258
|
+
result = await wrapped(*args, **kwargs)
|
|
259
|
+
|
|
260
|
+
if isinstance(result, Exception):
|
|
261
|
+
span.set_status(StatusCode.ERROR, str(result))
|
|
262
|
+
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
266
|
+
async def _wrap_handle_cancel_action(
|
|
267
|
+
self,
|
|
268
|
+
wrapped: Callable[[str], Coroutine[None, None, Exception | None]],
|
|
269
|
+
instance: Runner,
|
|
270
|
+
args: tuple[str],
|
|
271
|
+
kwargs: Any,
|
|
272
|
+
) -> Exception | None:
|
|
273
|
+
step_run_id = args[0]
|
|
274
|
+
|
|
275
|
+
with self._tracer.start_as_current_span(
|
|
276
|
+
"hatchet.cancel_step_run",
|
|
277
|
+
attributes={
|
|
278
|
+
"hatchet.step_run_id": step_run_id,
|
|
279
|
+
},
|
|
280
|
+
):
|
|
281
|
+
return await wrapped(*args, **kwargs)
|
|
282
|
+
|
|
283
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
284
|
+
def _wrap_push_event(
|
|
285
|
+
self,
|
|
286
|
+
wrapped: Callable[[str, dict[str, Any], PushEventOptions | None], Event],
|
|
287
|
+
instance: EventClient,
|
|
288
|
+
args: tuple[
|
|
289
|
+
str,
|
|
290
|
+
dict[str, Any],
|
|
291
|
+
PushEventOptions | None,
|
|
292
|
+
],
|
|
293
|
+
kwargs: dict[str, str | dict[str, Any] | PushEventOptions | None],
|
|
294
|
+
) -> Event:
|
|
295
|
+
with self._tracer.start_as_current_span(
|
|
296
|
+
"hatchet.push_event",
|
|
297
|
+
):
|
|
298
|
+
return wrapped(*args, **kwargs)
|
|
299
|
+
|
|
300
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
301
|
+
def _wrap_bulk_push_event(
|
|
302
|
+
self,
|
|
303
|
+
wrapped: Callable[
|
|
304
|
+
[list[BulkPushEventWithMetadata], PushEventOptions | None], list[Event]
|
|
305
|
+
],
|
|
306
|
+
instance: EventClient,
|
|
307
|
+
args: tuple[
|
|
308
|
+
list[BulkPushEventWithMetadata],
|
|
309
|
+
PushEventOptions | None,
|
|
310
|
+
],
|
|
311
|
+
kwargs: dict[str, list[BulkPushEventWithMetadata] | PushEventOptions | None],
|
|
312
|
+
) -> list[Event]:
|
|
313
|
+
with self._tracer.start_as_current_span(
|
|
314
|
+
"hatchet.bulk_push_event",
|
|
315
|
+
):
|
|
316
|
+
return wrapped(*args, **kwargs)
|
|
317
|
+
|
|
318
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
319
|
+
def _wrap_run_workflow(
|
|
320
|
+
self,
|
|
321
|
+
wrapped: Callable[[str, Any, TriggerWorkflowOptions | None], WorkflowRunRef],
|
|
322
|
+
instance: AdminClient,
|
|
323
|
+
args: tuple[str, Any, TriggerWorkflowOptions | None],
|
|
324
|
+
kwargs: dict[str, str | Any | TriggerWorkflowOptions | None],
|
|
325
|
+
) -> WorkflowRunRef:
|
|
326
|
+
with self._tracer.start_as_current_span(
|
|
327
|
+
"hatchet.run_workflow",
|
|
328
|
+
):
|
|
329
|
+
return wrapped(*args, **kwargs)
|
|
330
|
+
|
|
331
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
332
|
+
async def _wrap_async_run_workflow(
|
|
333
|
+
self,
|
|
334
|
+
wrapped: Callable[
|
|
335
|
+
[str, Any, TriggerWorkflowOptions | None],
|
|
336
|
+
Coroutine[None, None, WorkflowRunRef],
|
|
337
|
+
],
|
|
338
|
+
instance: AdminClient,
|
|
339
|
+
args: tuple[str, Any, TriggerWorkflowOptions | None],
|
|
340
|
+
kwargs: dict[str, str | Any | TriggerWorkflowOptions | None],
|
|
341
|
+
) -> WorkflowRunRef:
|
|
342
|
+
with self._tracer.start_as_current_span(
|
|
343
|
+
"hatchet.run_workflow",
|
|
344
|
+
):
|
|
345
|
+
return await wrapped(*args, **kwargs)
|
|
346
|
+
|
|
347
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
348
|
+
def _wrap_run_workflows(
|
|
349
|
+
self,
|
|
350
|
+
wrapped: Callable[
|
|
351
|
+
[list[WorkflowRunDict], TriggerWorkflowOptions | None], list[WorkflowRunRef]
|
|
352
|
+
],
|
|
353
|
+
instance: AdminClient,
|
|
354
|
+
args: tuple[
|
|
355
|
+
list[WorkflowRunDict],
|
|
356
|
+
TriggerWorkflowOptions | None,
|
|
357
|
+
],
|
|
358
|
+
kwargs: dict[str, list[WorkflowRunDict] | TriggerWorkflowOptions | None],
|
|
359
|
+
) -> list[WorkflowRunRef]:
|
|
360
|
+
with self._tracer.start_as_current_span(
|
|
361
|
+
"hatchet.run_workflows",
|
|
362
|
+
):
|
|
363
|
+
return wrapped(*args, **kwargs)
|
|
364
|
+
|
|
365
|
+
## IMPORTANT: Keep these types in sync with the wrapped method's signature
|
|
366
|
+
async def _wrap_async_run_workflows(
|
|
367
|
+
self,
|
|
368
|
+
wrapped: Callable[
|
|
369
|
+
[list[WorkflowRunDict], TriggerWorkflowOptions | None],
|
|
370
|
+
Coroutine[None, None, list[WorkflowRunRef]],
|
|
371
|
+
],
|
|
372
|
+
instance: AdminClient,
|
|
373
|
+
args: tuple[
|
|
374
|
+
list[WorkflowRunDict],
|
|
375
|
+
TriggerWorkflowOptions | None,
|
|
376
|
+
],
|
|
377
|
+
kwargs: dict[str, list[WorkflowRunDict] | TriggerWorkflowOptions | None],
|
|
378
|
+
) -> list[WorkflowRunRef]:
|
|
379
|
+
with self._tracer.start_as_current_span(
|
|
380
|
+
"hatchet.run_workflows",
|
|
381
|
+
):
|
|
382
|
+
return await wrapped(*args, **kwargs)
|
|
383
|
+
|
|
384
|
+
def _uninstrument(self, **kwargs: InstrumentKwargs) -> None:
|
|
385
|
+
self.tracer_provider = NoOpTracerProvider()
|
|
386
|
+
self.meter_provider = NoOpMeterProvider()
|
|
387
|
+
|
|
388
|
+
unwrap(hatchet_sdk, "worker.runner.runner.Runner.handle_start_step_run")
|
|
389
|
+
unwrap(hatchet_sdk, "worker.runner.runner.Runner.handle_start_group_key_run")
|
|
390
|
+
unwrap(hatchet_sdk, "worker.runner.runner.Runner.handle_cancel_action")
|
|
391
|
+
unwrap(hatchet_sdk, "clients.events.EventClient.push")
|
|
392
|
+
unwrap(hatchet_sdk, "clients.events.EventClient.bulk_push")
|
|
393
|
+
unwrap(hatchet_sdk, "clients.admin.AdminClient.run_workflow")
|
|
394
|
+
unwrap(hatchet_sdk, "clients.admin.AdminClientAioImpl.run_workflow")
|
|
395
|
+
unwrap(hatchet_sdk, "clients.admin.AdminClient.run_workflows")
|
|
396
|
+
unwrap(hatchet_sdk, "clients.admin.AdminClientAioImpl.run_workflows")
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from celpy import CELEvalError, Environment
|
|
5
|
+
|
|
6
|
+
from hatchet_sdk.v0.contracts.workflows_pb2 import CreateStepRateLimit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate_cel_expression(expr: str) -> bool:
|
|
10
|
+
env = Environment()
|
|
11
|
+
try:
|
|
12
|
+
env.compile(expr)
|
|
13
|
+
return True
|
|
14
|
+
except CELEvalError:
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RateLimitDuration:
|
|
19
|
+
SECOND = "SECOND"
|
|
20
|
+
MINUTE = "MINUTE"
|
|
21
|
+
HOUR = "HOUR"
|
|
22
|
+
DAY = "DAY"
|
|
23
|
+
WEEK = "WEEK"
|
|
24
|
+
MONTH = "MONTH"
|
|
25
|
+
YEAR = "YEAR"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class RateLimit:
|
|
30
|
+
"""
|
|
31
|
+
Represents a rate limit configuration for a step in a workflow.
|
|
32
|
+
|
|
33
|
+
This class allows for both static and dynamic rate limiting based on various parameters.
|
|
34
|
+
It supports both simple integer values and Common Expression Language (CEL) expressions
|
|
35
|
+
for dynamic evaluation.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
static_key (str, optional): A static key for rate limiting.
|
|
39
|
+
dynamic_key (str, optional): A CEL expression for dynamic key evaluation.
|
|
40
|
+
units (int or str, default=1): The number of units or a CEL expression for dynamic unit calculation.
|
|
41
|
+
limit (int or str, optional): The rate limit value or a CEL expression for dynamic limit calculation.
|
|
42
|
+
duration (str, default=RateLimitDuration.MINUTE): The window duration of the rate limit.
|
|
43
|
+
key (str, optional): Deprecated. Use static_key instead.
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
1. Static rate limit:
|
|
47
|
+
rate_limit = RateLimit(static_key="external-api", units=100)
|
|
48
|
+
> NOTE: if you want to use a static key, you must first put the rate limit: hatchet.admin.put_rate_limit("external-api", 200, RateLimitDuration.SECOND)
|
|
49
|
+
|
|
50
|
+
2. Dynamic rate limit with CEL expressions:
|
|
51
|
+
rate_limit = RateLimit(
|
|
52
|
+
dynamic_key="input.user_id",
|
|
53
|
+
units="input.units",
|
|
54
|
+
limit="input.limit * input.user_tier"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
Note:
|
|
58
|
+
- Either static_key or dynamic_key must be set, but not both.
|
|
59
|
+
- When using dynamic_key, limit must also be set.
|
|
60
|
+
- CEL expressions are validated upon instantiation.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If invalid combinations of attributes are provided or if CEL expressions are invalid.
|
|
64
|
+
DeprecationWarning: If the deprecated 'key' attribute is used.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
key: Union[str, None] = None
|
|
68
|
+
static_key: Union[str, None] = None
|
|
69
|
+
dynamic_key: Union[str, None] = None
|
|
70
|
+
units: Union[int, str] = 1
|
|
71
|
+
limit: Union[int, str, None] = None
|
|
72
|
+
duration: RateLimitDuration = RateLimitDuration.MINUTE
|
|
73
|
+
|
|
74
|
+
_req: CreateStepRateLimit = None
|
|
75
|
+
|
|
76
|
+
def __post_init__(self):
|
|
77
|
+
# juggle the key and key_expr fields
|
|
78
|
+
key = self.static_key
|
|
79
|
+
key_expression = self.dynamic_key
|
|
80
|
+
|
|
81
|
+
if self.key is not None:
|
|
82
|
+
DeprecationWarning(
|
|
83
|
+
"key is deprecated and will be removed in a future release, please use static_key instead"
|
|
84
|
+
)
|
|
85
|
+
key = self.key
|
|
86
|
+
|
|
87
|
+
if key_expression is not None:
|
|
88
|
+
if key is not None:
|
|
89
|
+
raise ValueError("Cannot have both static key and dynamic key set")
|
|
90
|
+
|
|
91
|
+
key = key_expression
|
|
92
|
+
if not validate_cel_expression(key_expression):
|
|
93
|
+
raise ValueError(f"Invalid CEL expression: {key_expression}")
|
|
94
|
+
|
|
95
|
+
# juggle the units and units_expr fields
|
|
96
|
+
units = None
|
|
97
|
+
units_expression = None
|
|
98
|
+
if isinstance(self.units, int):
|
|
99
|
+
units = self.units
|
|
100
|
+
else:
|
|
101
|
+
if not validate_cel_expression(self.units):
|
|
102
|
+
raise ValueError(f"Invalid CEL expression: {self.units}")
|
|
103
|
+
units_expression = self.units
|
|
104
|
+
|
|
105
|
+
# juggle the limit and limit_expr fields
|
|
106
|
+
limit_expression = None
|
|
107
|
+
|
|
108
|
+
if self.limit:
|
|
109
|
+
if isinstance(self.limit, int):
|
|
110
|
+
limit_expression = f"{self.limit}"
|
|
111
|
+
else:
|
|
112
|
+
if not validate_cel_expression(self.limit):
|
|
113
|
+
raise ValueError(f"Invalid CEL expression: {self.limit}")
|
|
114
|
+
limit_expression = self.limit
|
|
115
|
+
|
|
116
|
+
if key_expression is not None and limit_expression is None:
|
|
117
|
+
raise ValueError("CEL based keys requires limit to be set")
|
|
118
|
+
|
|
119
|
+
self._req = CreateStepRateLimit(
|
|
120
|
+
key=key,
|
|
121
|
+
key_expr=key_expression,
|
|
122
|
+
units=units,
|
|
123
|
+
units_expr=units_expression,
|
|
124
|
+
limit_values_expr=limit_expression,
|
|
125
|
+
duration=self.duration,
|
|
126
|
+
)
|
hatchet_sdk/v0/token.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_tenant_id_from_jwt(token: str) -> str:
|
|
6
|
+
claims = extract_claims_from_jwt(token)
|
|
7
|
+
|
|
8
|
+
return claims.get("sub")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_addresses_from_jwt(token: str) -> (str, str):
|
|
12
|
+
claims = extract_claims_from_jwt(token)
|
|
13
|
+
|
|
14
|
+
return claims.get("server_url"), claims.get("grpc_broadcast_address")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_claims_from_jwt(token: str):
|
|
18
|
+
parts = token.split(".")
|
|
19
|
+
if len(parts) != 3:
|
|
20
|
+
raise ValueError("Invalid token format")
|
|
21
|
+
|
|
22
|
+
claims_part = parts[1]
|
|
23
|
+
claims_part += "=" * ((4 - len(claims_part) % 4) % 4) # Padding for base64 decoding
|
|
24
|
+
claims_data = base64.urlsafe_b64decode(claims_part)
|
|
25
|
+
claims = json.loads(claims_data)
|
|
26
|
+
|
|
27
|
+
return claims
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
from concurrent.futures import Executor
|
|
4
|
+
from functools import partial, wraps
|
|
5
|
+
from threading import Thread
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## TODO: Stricter typing here
|
|
10
|
+
def sync_to_async(func: Any) -> Any:
|
|
11
|
+
"""
|
|
12
|
+
A decorator to run a synchronous function or coroutine in an asynchronous context with added
|
|
13
|
+
asyncio loop safety.
|
|
14
|
+
|
|
15
|
+
This decorator allows you to safely call synchronous functions or coroutines from an
|
|
16
|
+
asynchronous function by running them in an executor.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
func (callable): The synchronous function or coroutine to be run asynchronously.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
callable: An asynchronous wrapper function that runs the given function in an executor.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
@sync_to_async
|
|
26
|
+
def sync_function(x, y):
|
|
27
|
+
return x + y
|
|
28
|
+
|
|
29
|
+
@sync_to_async
|
|
30
|
+
async def async_function(x, y):
|
|
31
|
+
return x + y
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def undecorated_function(x, y):
|
|
35
|
+
return x + y
|
|
36
|
+
|
|
37
|
+
async def main():
|
|
38
|
+
result1 = await sync_function(1, 2)
|
|
39
|
+
result2 = await async_function(3, 4)
|
|
40
|
+
result3 = await sync_to_async(undecorated_function)(5, 6)
|
|
41
|
+
print(result1, result2, result3)
|
|
42
|
+
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
## TODO: Stricter typing here
|
|
47
|
+
@wraps(func)
|
|
48
|
+
async def run(
|
|
49
|
+
*args: Any,
|
|
50
|
+
loop: asyncio.AbstractEventLoop | None = None,
|
|
51
|
+
executor: Executor | None = None,
|
|
52
|
+
**kwargs: Any
|
|
53
|
+
) -> Any:
|
|
54
|
+
"""
|
|
55
|
+
The asynchronous wrapper function that runs the given function in an executor.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
*args: Positional arguments to pass to the function.
|
|
59
|
+
loop (asyncio.AbstractEventLoop, optional): The event loop to use. If None, the current running loop is used.
|
|
60
|
+
executor (concurrent.futures.Executor, optional): The executor to use. If None, the default executor is used.
|
|
61
|
+
**kwargs: Keyword arguments to pass to the function.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The result of the function call.
|
|
65
|
+
"""
|
|
66
|
+
if loop is None:
|
|
67
|
+
loop = asyncio.get_running_loop()
|
|
68
|
+
|
|
69
|
+
if inspect.iscoroutinefunction(func):
|
|
70
|
+
# Wrap the coroutine to run it in an executor
|
|
71
|
+
async def wrapper() -> Any:
|
|
72
|
+
return await func(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
pfunc = partial(asyncio.run, wrapper())
|
|
75
|
+
return await loop.run_in_executor(executor, pfunc)
|
|
76
|
+
else:
|
|
77
|
+
# Run the synchronous function in an executor
|
|
78
|
+
pfunc = partial(func, *args, **kwargs)
|
|
79
|
+
return await loop.run_in_executor(executor, pfunc)
|
|
80
|
+
|
|
81
|
+
return run
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class EventLoopThread:
|
|
85
|
+
"""A class that manages an asyncio event loop running in a separate thread."""
|
|
86
|
+
|
|
87
|
+
def __init__(self) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Initializes the EventLoopThread by creating an event loop
|
|
90
|
+
and setting up a thread to run the loop.
|
|
91
|
+
"""
|
|
92
|
+
self.loop = asyncio.new_event_loop()
|
|
93
|
+
self.thread = Thread(target=self.run_loop_in_thread, args=(self.loop,))
|
|
94
|
+
|
|
95
|
+
def __enter__(self, *a, **kw) -> asyncio.AbstractEventLoop:
|
|
96
|
+
"""
|
|
97
|
+
Starts the thread running the event loop when entering the context.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
asyncio.AbstractEventLoop: The event loop running in the separate thread.
|
|
101
|
+
"""
|
|
102
|
+
self.thread.start()
|
|
103
|
+
return self.loop
|
|
104
|
+
|
|
105
|
+
def __exit__(self, *a, **kw) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Stops the event loop and joins the thread when exiting the context.
|
|
108
|
+
"""
|
|
109
|
+
self.loop.call_soon_threadsafe(self.loop.stop)
|
|
110
|
+
self.thread.join()
|
|
111
|
+
|
|
112
|
+
def run_loop_in_thread(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Sets the event loop for the current thread and runs it forever.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
loop (asyncio.AbstractEventLoop): The event loop to run.
|
|
118
|
+
"""
|
|
119
|
+
asyncio.set_event_loop(loop)
|
|
120
|
+
loop.run_forever()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_active_event_loop() -> asyncio.AbstractEventLoop | None:
|
|
124
|
+
"""
|
|
125
|
+
Get the active event loop.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
asyncio.AbstractEventLoop: The active event loop, or None if there is no active
|
|
129
|
+
event loop in the current thread.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
return asyncio.get_event_loop()
|
|
133
|
+
except RuntimeError as e:
|
|
134
|
+
if str(e).startswith("There is no current event loop in thread"):
|
|
135
|
+
return None
|
|
136
|
+
else:
|
|
137
|
+
raise e
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def exp_backoff_sleep(attempt: int, max_sleep_time: float = 5) -> None:
|
|
6
|
+
base_time = 0.1 # starting sleep time in seconds (100 milliseconds)
|
|
7
|
+
jitter = random.uniform(0, base_time) # add random jitter
|
|
8
|
+
sleep_time = min(base_time * (2**attempt) + jitter, max_sleep_time)
|
|
9
|
+
await asyncio.sleep(sleep_time)
|