hatchet-sdk 1.18.1__py3-none-any.whl → 1.20.0__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.

Files changed (234) hide show
  1. hatchet_sdk/clients/dispatcher/action_listener.py +0 -1
  2. hatchet_sdk/clients/dispatcher/dispatcher.py +0 -30
  3. hatchet_sdk/hatchet.py +0 -20
  4. hatchet_sdk/opentelemetry/instrumentor.py +1 -27
  5. hatchet_sdk/runnables/action.py +2 -5
  6. hatchet_sdk/runnables/task.py +0 -1
  7. hatchet_sdk/utils/opentelemetry.py +0 -1
  8. hatchet_sdk/worker/action_listener_process.py +0 -29
  9. hatchet_sdk/worker/runner/runner.py +1 -105
  10. {hatchet_sdk-1.18.1.dist-info → hatchet_sdk-1.20.0.dist-info}/METADATA +2 -3
  11. {hatchet_sdk-1.18.1.dist-info → hatchet_sdk-1.20.0.dist-info}/RECORD +13 -234
  12. hatchet_sdk/v0/__init__.py +0 -251
  13. hatchet_sdk/v0/client.py +0 -119
  14. hatchet_sdk/v0/clients/admin.py +0 -541
  15. hatchet_sdk/v0/clients/dispatcher/action_listener.py +0 -422
  16. hatchet_sdk/v0/clients/dispatcher/dispatcher.py +0 -204
  17. hatchet_sdk/v0/clients/event_ts.py +0 -28
  18. hatchet_sdk/v0/clients/events.py +0 -182
  19. hatchet_sdk/v0/clients/rest/__init__.py +0 -307
  20. hatchet_sdk/v0/clients/rest/api/__init__.py +0 -19
  21. hatchet_sdk/v0/clients/rest/api/api_token_api.py +0 -858
  22. hatchet_sdk/v0/clients/rest/api/default_api.py +0 -2259
  23. hatchet_sdk/v0/clients/rest/api/event_api.py +0 -2548
  24. hatchet_sdk/v0/clients/rest/api/github_api.py +0 -331
  25. hatchet_sdk/v0/clients/rest/api/healthcheck_api.py +0 -483
  26. hatchet_sdk/v0/clients/rest/api/log_api.py +0 -449
  27. hatchet_sdk/v0/clients/rest/api/metadata_api.py +0 -728
  28. hatchet_sdk/v0/clients/rest/api/rate_limits_api.py +0 -423
  29. hatchet_sdk/v0/clients/rest/api/slack_api.py +0 -577
  30. hatchet_sdk/v0/clients/rest/api/sns_api.py +0 -872
  31. hatchet_sdk/v0/clients/rest/api/step_run_api.py +0 -2202
  32. hatchet_sdk/v0/clients/rest/api/tenant_api.py +0 -4430
  33. hatchet_sdk/v0/clients/rest/api/user_api.py +0 -2888
  34. hatchet_sdk/v0/clients/rest/api/worker_api.py +0 -858
  35. hatchet_sdk/v0/clients/rest/api/workflow_api.py +0 -6312
  36. hatchet_sdk/v0/clients/rest/api/workflow_run_api.py +0 -1932
  37. hatchet_sdk/v0/clients/rest/api/workflow_runs_api.py +0 -610
  38. hatchet_sdk/v0/clients/rest/api_client.py +0 -759
  39. hatchet_sdk/v0/clients/rest/api_response.py +0 -22
  40. hatchet_sdk/v0/clients/rest/configuration.py +0 -611
  41. hatchet_sdk/v0/clients/rest/exceptions.py +0 -200
  42. hatchet_sdk/v0/clients/rest/models/__init__.py +0 -274
  43. hatchet_sdk/v0/clients/rest/models/accept_invite_request.py +0 -83
  44. hatchet_sdk/v0/clients/rest/models/api_error.py +0 -102
  45. hatchet_sdk/v0/clients/rest/models/api_errors.py +0 -100
  46. hatchet_sdk/v0/clients/rest/models/api_meta.py +0 -144
  47. hatchet_sdk/v0/clients/rest/models/api_meta_auth.py +0 -85
  48. hatchet_sdk/v0/clients/rest/models/api_meta_integration.py +0 -88
  49. hatchet_sdk/v0/clients/rest/models/api_meta_posthog.py +0 -90
  50. hatchet_sdk/v0/clients/rest/models/api_resource_meta.py +0 -98
  51. hatchet_sdk/v0/clients/rest/models/api_token.py +0 -105
  52. hatchet_sdk/v0/clients/rest/models/bulk_create_event_request.py +0 -100
  53. hatchet_sdk/v0/clients/rest/models/bulk_create_event_response.py +0 -110
  54. hatchet_sdk/v0/clients/rest/models/cancel_event_request.py +0 -85
  55. hatchet_sdk/v0/clients/rest/models/cancel_step_run_request.py +0 -83
  56. hatchet_sdk/v0/clients/rest/models/concurrency_limit_strategy.py +0 -39
  57. hatchet_sdk/v0/clients/rest/models/create_api_token_request.py +0 -92
  58. hatchet_sdk/v0/clients/rest/models/create_api_token_response.py +0 -83
  59. hatchet_sdk/v0/clients/rest/models/create_cron_workflow_trigger_request.py +0 -98
  60. hatchet_sdk/v0/clients/rest/models/create_event_request.py +0 -95
  61. hatchet_sdk/v0/clients/rest/models/create_pull_request_from_step_run.py +0 -83
  62. hatchet_sdk/v0/clients/rest/models/create_sns_integration_request.py +0 -85
  63. hatchet_sdk/v0/clients/rest/models/create_tenant_alert_email_group_request.py +0 -83
  64. hatchet_sdk/v0/clients/rest/models/create_tenant_invite_request.py +0 -86
  65. hatchet_sdk/v0/clients/rest/models/create_tenant_request.py +0 -84
  66. hatchet_sdk/v0/clients/rest/models/cron_workflows.py +0 -131
  67. hatchet_sdk/v0/clients/rest/models/cron_workflows_list.py +0 -110
  68. hatchet_sdk/v0/clients/rest/models/cron_workflows_method.py +0 -37
  69. hatchet_sdk/v0/clients/rest/models/cron_workflows_order_by_field.py +0 -37
  70. hatchet_sdk/v0/clients/rest/models/event.py +0 -143
  71. hatchet_sdk/v0/clients/rest/models/event_data.py +0 -83
  72. hatchet_sdk/v0/clients/rest/models/event_key_list.py +0 -98
  73. hatchet_sdk/v0/clients/rest/models/event_list.py +0 -110
  74. hatchet_sdk/v0/clients/rest/models/event_order_by_direction.py +0 -37
  75. hatchet_sdk/v0/clients/rest/models/event_order_by_field.py +0 -36
  76. hatchet_sdk/v0/clients/rest/models/event_update_cancel200_response.py +0 -85
  77. hatchet_sdk/v0/clients/rest/models/event_workflow_run_summary.py +0 -116
  78. hatchet_sdk/v0/clients/rest/models/events.py +0 -110
  79. hatchet_sdk/v0/clients/rest/models/get_step_run_diff_response.py +0 -100
  80. hatchet_sdk/v0/clients/rest/models/github_app_installation.py +0 -107
  81. hatchet_sdk/v0/clients/rest/models/github_branch.py +0 -86
  82. hatchet_sdk/v0/clients/rest/models/github_repo.py +0 -86
  83. hatchet_sdk/v0/clients/rest/models/info_get_version200_response.py +0 -83
  84. hatchet_sdk/v0/clients/rest/models/job.py +0 -132
  85. hatchet_sdk/v0/clients/rest/models/job_run.py +0 -176
  86. hatchet_sdk/v0/clients/rest/models/job_run_status.py +0 -41
  87. hatchet_sdk/v0/clients/rest/models/link_github_repository_request.py +0 -106
  88. hatchet_sdk/v0/clients/rest/models/list_api_tokens_response.py +0 -110
  89. hatchet_sdk/v0/clients/rest/models/list_github_app_installations_response.py +0 -112
  90. hatchet_sdk/v0/clients/rest/models/list_pull_requests_response.py +0 -100
  91. hatchet_sdk/v0/clients/rest/models/list_slack_webhooks.py +0 -110
  92. hatchet_sdk/v0/clients/rest/models/list_sns_integrations.py +0 -110
  93. hatchet_sdk/v0/clients/rest/models/log_line.py +0 -94
  94. hatchet_sdk/v0/clients/rest/models/log_line_level.py +0 -39
  95. hatchet_sdk/v0/clients/rest/models/log_line_list.py +0 -110
  96. hatchet_sdk/v0/clients/rest/models/log_line_order_by_direction.py +0 -37
  97. hatchet_sdk/v0/clients/rest/models/log_line_order_by_field.py +0 -36
  98. hatchet_sdk/v0/clients/rest/models/pagination_response.py +0 -95
  99. hatchet_sdk/v0/clients/rest/models/pull_request.py +0 -112
  100. hatchet_sdk/v0/clients/rest/models/pull_request_state.py +0 -37
  101. hatchet_sdk/v0/clients/rest/models/queue_metrics.py +0 -97
  102. hatchet_sdk/v0/clients/rest/models/rate_limit.py +0 -117
  103. hatchet_sdk/v0/clients/rest/models/rate_limit_list.py +0 -110
  104. hatchet_sdk/v0/clients/rest/models/rate_limit_order_by_direction.py +0 -37
  105. hatchet_sdk/v0/clients/rest/models/rate_limit_order_by_field.py +0 -38
  106. hatchet_sdk/v0/clients/rest/models/recent_step_runs.py +0 -118
  107. hatchet_sdk/v0/clients/rest/models/reject_invite_request.py +0 -83
  108. hatchet_sdk/v0/clients/rest/models/replay_event_request.py +0 -85
  109. hatchet_sdk/v0/clients/rest/models/replay_workflow_runs_request.py +0 -85
  110. hatchet_sdk/v0/clients/rest/models/replay_workflow_runs_response.py +0 -100
  111. hatchet_sdk/v0/clients/rest/models/rerun_step_run_request.py +0 -83
  112. hatchet_sdk/v0/clients/rest/models/schedule_workflow_run_request.py +0 -92
  113. hatchet_sdk/v0/clients/rest/models/scheduled_run_status.py +0 -42
  114. hatchet_sdk/v0/clients/rest/models/scheduled_workflows.py +0 -149
  115. hatchet_sdk/v0/clients/rest/models/scheduled_workflows_list.py +0 -110
  116. hatchet_sdk/v0/clients/rest/models/scheduled_workflows_method.py +0 -37
  117. hatchet_sdk/v0/clients/rest/models/scheduled_workflows_order_by_field.py +0 -37
  118. hatchet_sdk/v0/clients/rest/models/semaphore_slots.py +0 -113
  119. hatchet_sdk/v0/clients/rest/models/slack_webhook.py +0 -127
  120. hatchet_sdk/v0/clients/rest/models/sns_integration.py +0 -114
  121. hatchet_sdk/v0/clients/rest/models/step.py +0 -123
  122. hatchet_sdk/v0/clients/rest/models/step_run.py +0 -202
  123. hatchet_sdk/v0/clients/rest/models/step_run_archive.py +0 -142
  124. hatchet_sdk/v0/clients/rest/models/step_run_archive_list.py +0 -110
  125. hatchet_sdk/v0/clients/rest/models/step_run_diff.py +0 -91
  126. hatchet_sdk/v0/clients/rest/models/step_run_event.py +0 -122
  127. hatchet_sdk/v0/clients/rest/models/step_run_event_list.py +0 -110
  128. hatchet_sdk/v0/clients/rest/models/step_run_event_reason.py +0 -52
  129. hatchet_sdk/v0/clients/rest/models/step_run_event_severity.py +0 -38
  130. hatchet_sdk/v0/clients/rest/models/step_run_status.py +0 -44
  131. hatchet_sdk/v0/clients/rest/models/tenant.py +0 -118
  132. hatchet_sdk/v0/clients/rest/models/tenant_alert_email_group.py +0 -98
  133. hatchet_sdk/v0/clients/rest/models/tenant_alert_email_group_list.py +0 -112
  134. hatchet_sdk/v0/clients/rest/models/tenant_alerting_settings.py +0 -143
  135. hatchet_sdk/v0/clients/rest/models/tenant_invite.py +0 -120
  136. hatchet_sdk/v0/clients/rest/models/tenant_invite_list.py +0 -110
  137. hatchet_sdk/v0/clients/rest/models/tenant_list.py +0 -110
  138. hatchet_sdk/v0/clients/rest/models/tenant_member.py +0 -123
  139. hatchet_sdk/v0/clients/rest/models/tenant_member_list.py +0 -110
  140. hatchet_sdk/v0/clients/rest/models/tenant_member_role.py +0 -38
  141. hatchet_sdk/v0/clients/rest/models/tenant_queue_metrics.py +0 -116
  142. hatchet_sdk/v0/clients/rest/models/tenant_resource.py +0 -40
  143. hatchet_sdk/v0/clients/rest/models/tenant_resource_limit.py +0 -135
  144. hatchet_sdk/v0/clients/rest/models/tenant_resource_policy.py +0 -102
  145. hatchet_sdk/v0/clients/rest/models/tenant_step_run_queue_metrics.py +0 -83
  146. hatchet_sdk/v0/clients/rest/models/trigger_workflow_run_request.py +0 -91
  147. hatchet_sdk/v0/clients/rest/models/update_tenant_alert_email_group_request.py +0 -83
  148. hatchet_sdk/v0/clients/rest/models/update_tenant_invite_request.py +0 -85
  149. hatchet_sdk/v0/clients/rest/models/update_tenant_request.py +0 -137
  150. hatchet_sdk/v0/clients/rest/models/update_worker_request.py +0 -87
  151. hatchet_sdk/v0/clients/rest/models/user.py +0 -126
  152. hatchet_sdk/v0/clients/rest/models/user_change_password_request.py +0 -88
  153. hatchet_sdk/v0/clients/rest/models/user_login_request.py +0 -86
  154. hatchet_sdk/v0/clients/rest/models/user_register_request.py +0 -91
  155. hatchet_sdk/v0/clients/rest/models/user_tenant_memberships_list.py +0 -110
  156. hatchet_sdk/v0/clients/rest/models/user_tenant_public.py +0 -86
  157. hatchet_sdk/v0/clients/rest/models/webhook_worker.py +0 -100
  158. hatchet_sdk/v0/clients/rest/models/webhook_worker_create_request.py +0 -94
  159. hatchet_sdk/v0/clients/rest/models/webhook_worker_create_response.py +0 -98
  160. hatchet_sdk/v0/clients/rest/models/webhook_worker_created.py +0 -102
  161. hatchet_sdk/v0/clients/rest/models/webhook_worker_list_response.py +0 -110
  162. hatchet_sdk/v0/clients/rest/models/webhook_worker_request.py +0 -102
  163. hatchet_sdk/v0/clients/rest/models/webhook_worker_request_list_response.py +0 -104
  164. hatchet_sdk/v0/clients/rest/models/webhook_worker_request_method.py +0 -38
  165. hatchet_sdk/v0/clients/rest/models/worker.py +0 -239
  166. hatchet_sdk/v0/clients/rest/models/worker_label.py +0 -102
  167. hatchet_sdk/v0/clients/rest/models/worker_list.py +0 -110
  168. hatchet_sdk/v0/clients/rest/models/worker_runtime_info.py +0 -103
  169. hatchet_sdk/v0/clients/rest/models/worker_runtime_sdks.py +0 -38
  170. hatchet_sdk/v0/clients/rest/models/worker_type.py +0 -38
  171. hatchet_sdk/v0/clients/rest/models/workflow.py +0 -165
  172. hatchet_sdk/v0/clients/rest/models/workflow_concurrency.py +0 -107
  173. hatchet_sdk/v0/clients/rest/models/workflow_deployment_config.py +0 -136
  174. hatchet_sdk/v0/clients/rest/models/workflow_kind.py +0 -38
  175. hatchet_sdk/v0/clients/rest/models/workflow_list.py +0 -120
  176. hatchet_sdk/v0/clients/rest/models/workflow_metrics.py +0 -97
  177. hatchet_sdk/v0/clients/rest/models/workflow_run.py +0 -188
  178. hatchet_sdk/v0/clients/rest/models/workflow_run_cancel200_response.py +0 -85
  179. hatchet_sdk/v0/clients/rest/models/workflow_run_list.py +0 -110
  180. hatchet_sdk/v0/clients/rest/models/workflow_run_order_by_direction.py +0 -37
  181. hatchet_sdk/v0/clients/rest/models/workflow_run_order_by_field.py +0 -39
  182. hatchet_sdk/v0/clients/rest/models/workflow_run_shape.py +0 -186
  183. hatchet_sdk/v0/clients/rest/models/workflow_run_status.py +0 -42
  184. hatchet_sdk/v0/clients/rest/models/workflow_run_triggered_by.py +0 -112
  185. hatchet_sdk/v0/clients/rest/models/workflow_runs_cancel_request.py +0 -85
  186. hatchet_sdk/v0/clients/rest/models/workflow_runs_metrics.py +0 -94
  187. hatchet_sdk/v0/clients/rest/models/workflow_runs_metrics_counts.py +0 -104
  188. hatchet_sdk/v0/clients/rest/models/workflow_tag.py +0 -84
  189. hatchet_sdk/v0/clients/rest/models/workflow_trigger_cron_ref.py +0 -86
  190. hatchet_sdk/v0/clients/rest/models/workflow_trigger_event_ref.py +0 -86
  191. hatchet_sdk/v0/clients/rest/models/workflow_triggers.py +0 -141
  192. hatchet_sdk/v0/clients/rest/models/workflow_update_request.py +0 -85
  193. hatchet_sdk/v0/clients/rest/models/workflow_version.py +0 -170
  194. hatchet_sdk/v0/clients/rest/models/workflow_version_concurrency.py +0 -114
  195. hatchet_sdk/v0/clients/rest/models/workflow_version_definition.py +0 -85
  196. hatchet_sdk/v0/clients/rest/models/workflow_version_meta.py +0 -123
  197. hatchet_sdk/v0/clients/rest/models/workflow_workers_count.py +0 -95
  198. hatchet_sdk/v0/clients/rest/rest.py +0 -187
  199. hatchet_sdk/v0/clients/rest/tenacity_utils.py +0 -39
  200. hatchet_sdk/v0/clients/rest_client.py +0 -622
  201. hatchet_sdk/v0/clients/run_event_listener.py +0 -260
  202. hatchet_sdk/v0/clients/workflow_listener.py +0 -277
  203. hatchet_sdk/v0/connection.py +0 -63
  204. hatchet_sdk/v0/context/__init__.py +0 -1
  205. hatchet_sdk/v0/context/context.py +0 -446
  206. hatchet_sdk/v0/context/worker_context.py +0 -28
  207. hatchet_sdk/v0/features/cron.py +0 -286
  208. hatchet_sdk/v0/features/scheduled.py +0 -248
  209. hatchet_sdk/v0/hatchet.py +0 -310
  210. hatchet_sdk/v0/labels.py +0 -10
  211. hatchet_sdk/v0/loader.py +0 -244
  212. hatchet_sdk/v0/metadata.py +0 -2
  213. hatchet_sdk/v0/opentelemetry/instrumentor.py +0 -393
  214. hatchet_sdk/v0/rate_limit.py +0 -126
  215. hatchet_sdk/v0/semver.py +0 -30
  216. hatchet_sdk/v0/token.py +0 -27
  217. hatchet_sdk/v0/utils/aio_utils.py +0 -137
  218. hatchet_sdk/v0/utils/backoff.py +0 -9
  219. hatchet_sdk/v0/utils/types.py +0 -8
  220. hatchet_sdk/v0/utils/typing.py +0 -12
  221. hatchet_sdk/v0/v2/callable.py +0 -202
  222. hatchet_sdk/v0/v2/concurrency.py +0 -47
  223. hatchet_sdk/v0/v2/hatchet.py +0 -224
  224. hatchet_sdk/v0/worker/__init__.py +0 -1
  225. hatchet_sdk/v0/worker/action_listener_process.py +0 -294
  226. hatchet_sdk/v0/worker/runner/run_loop_manager.py +0 -112
  227. hatchet_sdk/v0/worker/runner/runner.py +0 -460
  228. hatchet_sdk/v0/worker/runner/utils/capture_logs.py +0 -81
  229. hatchet_sdk/v0/worker/runner/utils/error_with_traceback.py +0 -6
  230. hatchet_sdk/v0/worker/worker.py +0 -391
  231. hatchet_sdk/v0/workflow.py +0 -261
  232. hatchet_sdk/v0/workflow_run.py +0 -59
  233. {hatchet_sdk-1.18.1.dist-info → hatchet_sdk-1.20.0.dist-info}/WHEEL +0 -0
  234. {hatchet_sdk-1.18.1.dist-info → hatchet_sdk-1.20.0.dist-info}/entry_points.txt +0 -0
@@ -1,422 +0,0 @@
1
- import asyncio
2
- import json
3
- import time
4
- from dataclasses import dataclass, field
5
- from typing import Any, AsyncGenerator, List, Optional
6
-
7
- import grpc
8
- from grpc._cython import cygrpc
9
-
10
- from hatchet_sdk.contracts.dispatcher_pb2 import (
11
- ActionType,
12
- AssignedAction,
13
- HeartbeatRequest,
14
- WorkerLabels,
15
- WorkerListenRequest,
16
- WorkerUnsubscribeRequest,
17
- )
18
- from hatchet_sdk.contracts.dispatcher_pb2_grpc import DispatcherStub
19
- from hatchet_sdk.logger import logger
20
- from hatchet_sdk.v0.clients.event_ts import ThreadSafeEvent, read_with_interrupt
21
- from hatchet_sdk.v0.clients.run_event_listener import (
22
- DEFAULT_ACTION_LISTENER_RETRY_INTERVAL,
23
- )
24
- from hatchet_sdk.v0.connection import new_conn
25
- from hatchet_sdk.v0.utils.backoff import exp_backoff_sleep
26
-
27
- from ...loader import ClientConfig
28
- from ...metadata import get_metadata
29
- from ..events import proto_timestamp_now
30
-
31
- DEFAULT_ACTION_TIMEOUT = 600 # seconds
32
-
33
-
34
- DEFAULT_ACTION_LISTENER_RETRY_INTERVAL = 5 # seconds
35
- DEFAULT_ACTION_LISTENER_RETRY_COUNT = 15
36
-
37
-
38
- @dataclass
39
- class GetActionListenerRequest:
40
- worker_name: str
41
- services: List[str]
42
- actions: List[str]
43
- max_runs: Optional[int] = None
44
- _labels: dict[str, str | int] = field(default_factory=dict)
45
-
46
- labels: dict[str, WorkerLabels] = field(init=False)
47
-
48
- def __post_init__(self):
49
- self.labels = {}
50
-
51
- for key, value in self._labels.items():
52
- if isinstance(value, int):
53
- self.labels[key] = WorkerLabels(intValue=value)
54
- else:
55
- self.labels[key] = WorkerLabels(strValue=str(value))
56
-
57
-
58
- @dataclass
59
- class Action:
60
- worker_id: str
61
- tenant_id: str
62
- workflow_run_id: str
63
- get_group_key_run_id: str
64
- job_id: str
65
- job_name: str
66
- job_run_id: str
67
- step_id: str
68
- step_run_id: str
69
- action_id: str
70
- action_payload: str
71
- action_type: ActionType
72
- retry_count: int
73
- additional_metadata: dict[str, str] | None = None
74
-
75
- child_workflow_index: int | None = None
76
- child_workflow_key: str | None = None
77
- parent_workflow_run_id: str | None = None
78
-
79
- def __post_init__(self):
80
- if isinstance(self.additional_metadata, str) and self.additional_metadata != "":
81
- try:
82
- self.additional_metadata = json.loads(self.additional_metadata)
83
- except json.JSONDecodeError:
84
- # If JSON decoding fails, keep the original string
85
- pass
86
-
87
- # Ensure additional_metadata is always a dictionary
88
- if not isinstance(self.additional_metadata, dict):
89
- self.additional_metadata = {}
90
-
91
- @property
92
- def otel_attributes(self) -> dict[str, str | int]:
93
- try:
94
- payload_str = json.dumps(self.action_payload, default=str)
95
- except Exception:
96
- payload_str = str(self.action_payload)
97
-
98
- attrs: dict[str, str | int | None] = {
99
- "hatchet.tenant_id": self.tenant_id,
100
- "hatchet.worker_id": self.worker_id,
101
- "hatchet.workflow_run_id": self.workflow_run_id,
102
- "hatchet.step_id": self.step_id,
103
- "hatchet.step_run_id": self.step_run_id,
104
- "hatchet.retry_count": self.retry_count,
105
- "hatchet.parent_workflow_run_id": self.parent_workflow_run_id,
106
- "hatchet.child_workflow_index": self.child_workflow_index,
107
- "hatchet.child_workflow_key": self.child_workflow_key,
108
- "hatchet.action_payload": payload_str,
109
- "hatchet.workflow_name": self.job_name,
110
- "hatchet.action_name": self.action_id,
111
- "hatchet.get_group_key_run_id": self.get_group_key_run_id,
112
- }
113
-
114
- return {k: v for k, v in attrs.items() if v}
115
-
116
-
117
- START_STEP_RUN = 0
118
- CANCEL_STEP_RUN = 1
119
- START_GET_GROUP_KEY = 2
120
-
121
-
122
- @dataclass
123
- class ActionListener:
124
- config: ClientConfig
125
- worker_id: str
126
-
127
- client: DispatcherStub = field(init=False)
128
- aio_client: DispatcherStub = field(init=False)
129
- token: str = field(init=False)
130
- retries: int = field(default=0, init=False)
131
- last_connection_attempt: float = field(default=0, init=False)
132
- last_heartbeat_succeeded: bool = field(default=True, init=False)
133
- time_last_hb_succeeded: float = field(default=9999999999999, init=False)
134
- heartbeat_task: Optional[asyncio.Task] = field(default=None, init=False)
135
- run_heartbeat: bool = field(default=True, init=False)
136
- listen_strategy: str = field(default="v2", init=False)
137
- stop_signal: bool = field(default=False, init=False)
138
-
139
- missed_heartbeats: int = field(default=0, init=False)
140
-
141
- def __post_init__(self):
142
- self.client = DispatcherStub(new_conn(self.config))
143
- self.aio_client = DispatcherStub(new_conn(self.config, True))
144
- self.token = self.config.token
145
-
146
- def is_healthy(self):
147
- return self.last_heartbeat_succeeded
148
-
149
- async def heartbeat(self):
150
- # send a heartbeat every 4 seconds
151
- heartbeat_delay = 4
152
-
153
- while True:
154
- if not self.run_heartbeat:
155
- break
156
-
157
- try:
158
- logger.debug("sending heartbeat")
159
- await self.aio_client.Heartbeat(
160
- HeartbeatRequest(
161
- workerId=self.worker_id,
162
- heartbeatAt=proto_timestamp_now(),
163
- ),
164
- timeout=5,
165
- metadata=get_metadata(self.token),
166
- )
167
-
168
- if self.last_heartbeat_succeeded is False:
169
- logger.info("listener established")
170
-
171
- now = time.time()
172
- diff = now - self.time_last_hb_succeeded
173
- if diff > heartbeat_delay + 1:
174
- logger.warn(
175
- f"time since last successful heartbeat: {diff:.2f}s, expects {heartbeat_delay}s"
176
- )
177
-
178
- self.last_heartbeat_succeeded = True
179
- self.time_last_hb_succeeded = now
180
- self.missed_heartbeats = 0
181
- except grpc.RpcError as e:
182
- self.missed_heartbeats = self.missed_heartbeats + 1
183
- self.last_heartbeat_succeeded = False
184
-
185
- if (
186
- e.code() == grpc.StatusCode.UNAVAILABLE
187
- or e.code() == grpc.StatusCode.FAILED_PRECONDITION
188
- ):
189
- # todo case on "recvmsg:Connection reset by peer" for updates?
190
- if self.missed_heartbeats >= 3:
191
- # we don't reraise the error here, as we don't want to stop the heartbeat thread
192
- logger.error(
193
- f"⛔️ failed heartbeat ({self.missed_heartbeats}): {e.details()}"
194
- )
195
- elif self.missed_heartbeats > 1:
196
- logger.warning(
197
- f"failed to send heartbeat ({self.missed_heartbeats}): {e.details()}"
198
- )
199
- else:
200
- logger.error(f"failed to send heartbeat: {e}")
201
-
202
- if self.interrupt is not None:
203
- self.interrupt.set()
204
-
205
- if e.code() == grpc.StatusCode.UNIMPLEMENTED:
206
- break
207
- await asyncio.sleep(heartbeat_delay)
208
-
209
- async def start_heartbeater(self):
210
- if self.heartbeat_task is not None:
211
- return
212
-
213
- try:
214
- loop = asyncio.get_event_loop()
215
- except RuntimeError as e:
216
- if str(e).startswith("There is no current event loop in thread"):
217
- loop = asyncio.new_event_loop()
218
- asyncio.set_event_loop(loop)
219
- else:
220
- raise e
221
- self.heartbeat_task = loop.create_task(self.heartbeat())
222
-
223
- def __aiter__(self):
224
- return self._generator()
225
-
226
- async def _generator(self) -> AsyncGenerator[Action, None]:
227
- listener = None
228
-
229
- while not self.stop_signal:
230
- if listener is not None:
231
- listener.cancel()
232
-
233
- try:
234
- listener = await self.get_listen_client()
235
- except Exception:
236
- logger.info("closing action listener loop")
237
- yield None
238
-
239
- try:
240
- while not self.stop_signal:
241
- self.interrupt = ThreadSafeEvent()
242
- t = asyncio.create_task(
243
- read_with_interrupt(listener, self.interrupt)
244
- )
245
- await self.interrupt.wait()
246
-
247
- if not t.done():
248
- # print a warning
249
- logger.warning(
250
- "Interrupted read_with_interrupt task of action listener"
251
- )
252
-
253
- t.cancel()
254
- listener.cancel()
255
- break
256
-
257
- assigned_action = t.result()
258
-
259
- if assigned_action is cygrpc.EOF:
260
- self.retries = self.retries + 1
261
- break
262
-
263
- self.retries = 0
264
- assigned_action: AssignedAction
265
-
266
- # Process the received action
267
- action_type = self.map_action_type(assigned_action.actionType)
268
-
269
- if (
270
- assigned_action.actionPayload is None
271
- or assigned_action.actionPayload == ""
272
- ):
273
- action_payload = None
274
- else:
275
- action_payload = self.parse_action_payload(
276
- assigned_action.actionPayload
277
- )
278
-
279
- action = Action(
280
- tenant_id=assigned_action.tenantId,
281
- worker_id=self.worker_id,
282
- workflow_run_id=assigned_action.workflowRunId,
283
- get_group_key_run_id=assigned_action.getGroupKeyRunId,
284
- job_id=assigned_action.jobId,
285
- job_name=assigned_action.jobName,
286
- job_run_id=assigned_action.jobRunId,
287
- step_id=assigned_action.stepId,
288
- step_run_id=assigned_action.stepRunId,
289
- action_id=assigned_action.actionId,
290
- action_payload=action_payload,
291
- action_type=action_type,
292
- retry_count=assigned_action.retryCount,
293
- additional_metadata=assigned_action.additional_metadata,
294
- child_workflow_index=assigned_action.child_workflow_index,
295
- child_workflow_key=assigned_action.child_workflow_key,
296
- parent_workflow_run_id=assigned_action.parent_workflow_run_id,
297
- )
298
-
299
- yield action
300
- except grpc.RpcError as e:
301
- self.last_heartbeat_succeeded = False
302
-
303
- # Handle different types of errors
304
- if e.code() == grpc.StatusCode.CANCELLED:
305
- # Context cancelled, unsubscribe and close
306
- logger.debug("Context cancelled, closing listener")
307
- elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
308
- logger.info("Deadline exceeded, retrying subscription")
309
- elif (
310
- self.listen_strategy == "v2"
311
- and e.code() == grpc.StatusCode.UNIMPLEMENTED
312
- ):
313
- # ListenV2 is not available, fallback to Listen
314
- self.listen_strategy = "v1"
315
- self.run_heartbeat = False
316
- logger.info("ListenV2 not available, falling back to Listen")
317
- else:
318
- # TODO retry
319
- if e.code() == grpc.StatusCode.UNAVAILABLE:
320
- logger.error(f"action listener error: {e.details()}")
321
- else:
322
- # Unknown error, report and break
323
- logger.error(f"action listener error: {e}")
324
-
325
- self.retries = self.retries + 1
326
-
327
- def parse_action_payload(self, payload: str):
328
- try:
329
- payload_data = json.loads(payload)
330
- except json.JSONDecodeError as e:
331
- raise ValueError(f"Error decoding payload: {e}")
332
- return payload_data
333
-
334
- def map_action_type(self, action_type):
335
- if action_type == ActionType.START_STEP_RUN:
336
- return START_STEP_RUN
337
- elif action_type == ActionType.CANCEL_STEP_RUN:
338
- return CANCEL_STEP_RUN
339
- elif action_type == ActionType.START_GET_GROUP_KEY:
340
- return START_GET_GROUP_KEY
341
- else:
342
- # logger.error(f"Unknown action type: {action_type}")
343
- return None
344
-
345
- async def get_listen_client(self):
346
- current_time = int(time.time())
347
-
348
- if (
349
- current_time - self.last_connection_attempt
350
- > DEFAULT_ACTION_LISTENER_RETRY_INTERVAL
351
- ):
352
- # reset retries if last connection was long lived
353
- self.retries = 0
354
-
355
- if self.retries > DEFAULT_ACTION_LISTENER_RETRY_COUNT:
356
- # TODO this is the problem case...
357
- logger.error(
358
- f"could not establish action listener connection after {DEFAULT_ACTION_LISTENER_RETRY_COUNT} retries"
359
- )
360
- self.run_heartbeat = False
361
- raise Exception("retry_exhausted")
362
- elif self.retries >= 1:
363
- # logger.info
364
- # if we are retrying, we wait for a bit. this should eventually be replaced with exp backoff + jitter
365
- await exp_backoff_sleep(
366
- self.retries, DEFAULT_ACTION_LISTENER_RETRY_INTERVAL
367
- )
368
-
369
- logger.info(
370
- f"action listener connection interrupted, retrying... ({self.retries}/{DEFAULT_ACTION_LISTENER_RETRY_COUNT})"
371
- )
372
-
373
- self.aio_client = DispatcherStub(new_conn(self.config, True))
374
-
375
- if self.listen_strategy == "v2":
376
- # we should await for the listener to be established before
377
- # starting the heartbeater
378
- listener = self.aio_client.ListenV2(
379
- WorkerListenRequest(workerId=self.worker_id),
380
- timeout=self.config.listener_v2_timeout,
381
- metadata=get_metadata(self.token),
382
- )
383
- await self.start_heartbeater()
384
- else:
385
- # if ListenV2 is not available, fallback to Listen
386
- listener = self.aio_client.Listen(
387
- WorkerListenRequest(workerId=self.worker_id),
388
- timeout=DEFAULT_ACTION_TIMEOUT,
389
- metadata=get_metadata(self.token),
390
- )
391
-
392
- self.last_connection_attempt = current_time
393
-
394
- return listener
395
-
396
- def cleanup(self):
397
- self.run_heartbeat = False
398
- self.heartbeat_task.cancel()
399
-
400
- try:
401
- self.unregister()
402
- except Exception as e:
403
- logger.error(f"failed to unregister: {e}")
404
-
405
- if self.interrupt:
406
- self.interrupt.set()
407
-
408
- def unregister(self):
409
- self.run_heartbeat = False
410
- self.heartbeat_task.cancel()
411
-
412
- try:
413
- req = self.aio_client.Unsubscribe(
414
- WorkerUnsubscribeRequest(workerId=self.worker_id),
415
- timeout=5,
416
- metadata=get_metadata(self.token),
417
- )
418
- if self.interrupt is not None:
419
- self.interrupt.set()
420
- return req
421
- except grpc.RpcError as e:
422
- raise Exception(f"Failed to unsubscribe: {e}")
@@ -1,204 +0,0 @@
1
- from typing import Any, cast
2
-
3
- from google.protobuf.timestamp_pb2 import Timestamp
4
-
5
- from hatchet_sdk.contracts.dispatcher_pb2 import (
6
- STEP_EVENT_TYPE_COMPLETED,
7
- STEP_EVENT_TYPE_FAILED,
8
- ActionEventResponse,
9
- GroupKeyActionEvent,
10
- GroupKeyActionEventType,
11
- OverridesData,
12
- RefreshTimeoutRequest,
13
- ReleaseSlotRequest,
14
- StepActionEvent,
15
- StepActionEventType,
16
- UpsertWorkerLabelsRequest,
17
- WorkerLabels,
18
- WorkerRegisterRequest,
19
- WorkerRegisterResponse,
20
- )
21
- from hatchet_sdk.contracts.dispatcher_pb2_grpc import DispatcherStub
22
- from hatchet_sdk.v0.clients.dispatcher.action_listener import (
23
- Action,
24
- ActionListener,
25
- GetActionListenerRequest,
26
- )
27
- from hatchet_sdk.v0.clients.rest.tenacity_utils import tenacity_retry
28
- from hatchet_sdk.v0.connection import new_conn
29
-
30
- from ...loader import ClientConfig
31
- from ...metadata import get_metadata
32
-
33
- DEFAULT_REGISTER_TIMEOUT = 30
34
-
35
-
36
- def new_dispatcher(config: ClientConfig) -> "DispatcherClient":
37
- return DispatcherClient(config=config)
38
-
39
-
40
- class DispatcherClient:
41
- config: ClientConfig
42
-
43
- def __init__(self, config: ClientConfig):
44
- conn = new_conn(config)
45
- self.client = DispatcherStub(conn) # type: ignore[no-untyped-call]
46
-
47
- aio_conn = new_conn(config, True)
48
- self.aio_client = DispatcherStub(aio_conn) # type: ignore[no-untyped-call]
49
- self.token = config.token
50
- self.config = config
51
-
52
- async def get_action_listener(
53
- self, req: GetActionListenerRequest
54
- ) -> ActionListener:
55
-
56
- # Override labels with the preset labels
57
- preset_labels = self.config.worker_preset_labels
58
-
59
- for key, value in preset_labels.items():
60
- req.labels[key] = WorkerLabels(strValue=str(value))
61
-
62
- # Register the worker
63
- response: WorkerRegisterResponse = await self.aio_client.Register(
64
- WorkerRegisterRequest(
65
- workerName=req.worker_name,
66
- actions=req.actions,
67
- services=req.services,
68
- maxRuns=req.max_runs,
69
- labels=req.labels,
70
- ),
71
- timeout=DEFAULT_REGISTER_TIMEOUT,
72
- metadata=get_metadata(self.token),
73
- )
74
-
75
- return ActionListener(self.config, response.workerId)
76
-
77
- async def send_step_action_event(
78
- self, action: Action, event_type: StepActionEventType, payload: str
79
- ) -> Any:
80
- try:
81
- return await self._try_send_step_action_event(action, event_type, payload)
82
- except Exception as e:
83
- # for step action events, send a failure event when we cannot send the completed event
84
- if (
85
- event_type == STEP_EVENT_TYPE_COMPLETED
86
- or event_type == STEP_EVENT_TYPE_FAILED
87
- ):
88
- await self._try_send_step_action_event(
89
- action,
90
- STEP_EVENT_TYPE_FAILED,
91
- "Failed to send finished event: " + str(e),
92
- )
93
-
94
- return
95
-
96
- @tenacity_retry
97
- async def _try_send_step_action_event(
98
- self, action: Action, event_type: StepActionEventType, payload: str
99
- ) -> Any:
100
- eventTimestamp = Timestamp()
101
- eventTimestamp.GetCurrentTime()
102
-
103
- event = StepActionEvent(
104
- workerId=action.worker_id,
105
- jobId=action.job_id,
106
- jobRunId=action.job_run_id,
107
- stepId=action.step_id,
108
- stepRunId=action.step_run_id,
109
- actionId=action.action_id,
110
- eventTimestamp=eventTimestamp,
111
- eventType=event_type,
112
- eventPayload=payload,
113
- retryCount=action.retry_count,
114
- )
115
-
116
- ## TODO: What does this return?
117
- return await self.aio_client.SendStepActionEvent(
118
- event,
119
- metadata=get_metadata(self.token),
120
- )
121
-
122
- async def send_group_key_action_event(
123
- self, action: Action, event_type: GroupKeyActionEventType, payload: str
124
- ) -> Any:
125
- eventTimestamp = Timestamp()
126
- eventTimestamp.GetCurrentTime()
127
-
128
- event = GroupKeyActionEvent(
129
- workerId=action.worker_id,
130
- workflowRunId=action.workflow_run_id,
131
- getGroupKeyRunId=action.get_group_key_run_id,
132
- actionId=action.action_id,
133
- eventTimestamp=eventTimestamp,
134
- eventType=event_type,
135
- eventPayload=payload,
136
- )
137
-
138
- ## TODO: What does this return?
139
- return await self.aio_client.SendGroupKeyActionEvent(
140
- event,
141
- metadata=get_metadata(self.token),
142
- )
143
-
144
- def put_overrides_data(self, data: OverridesData) -> ActionEventResponse:
145
- return cast(
146
- ActionEventResponse,
147
- self.client.PutOverridesData(
148
- data,
149
- metadata=get_metadata(self.token),
150
- ),
151
- )
152
-
153
- def release_slot(self, step_run_id: str) -> None:
154
- self.client.ReleaseSlot(
155
- ReleaseSlotRequest(stepRunId=step_run_id),
156
- timeout=DEFAULT_REGISTER_TIMEOUT,
157
- metadata=get_metadata(self.token),
158
- )
159
-
160
- def refresh_timeout(self, step_run_id: str, increment_by: str) -> None:
161
- self.client.RefreshTimeout(
162
- RefreshTimeoutRequest(
163
- stepRunId=step_run_id,
164
- incrementTimeoutBy=increment_by,
165
- ),
166
- timeout=DEFAULT_REGISTER_TIMEOUT,
167
- metadata=get_metadata(self.token),
168
- )
169
-
170
- def upsert_worker_labels(
171
- self, worker_id: str | None, labels: dict[str, str | int]
172
- ) -> None:
173
- worker_labels = {}
174
-
175
- for key, value in labels.items():
176
- if isinstance(value, int):
177
- worker_labels[key] = WorkerLabels(intValue=value)
178
- else:
179
- worker_labels[key] = WorkerLabels(strValue=str(value))
180
-
181
- self.client.UpsertWorkerLabels(
182
- UpsertWorkerLabelsRequest(workerId=worker_id, labels=worker_labels),
183
- timeout=DEFAULT_REGISTER_TIMEOUT,
184
- metadata=get_metadata(self.token),
185
- )
186
-
187
- async def async_upsert_worker_labels(
188
- self,
189
- worker_id: str | None,
190
- labels: dict[str, str | int],
191
- ) -> None:
192
- worker_labels = {}
193
-
194
- for key, value in labels.items():
195
- if isinstance(value, int):
196
- worker_labels[key] = WorkerLabels(intValue=value)
197
- else:
198
- worker_labels[key] = WorkerLabels(strValue=str(value))
199
-
200
- await self.aio_client.UpsertWorkerLabels(
201
- UpsertWorkerLabelsRequest(workerId=worker_id, labels=worker_labels),
202
- timeout=DEFAULT_REGISTER_TIMEOUT,
203
- metadata=get_metadata(self.token),
204
- )
@@ -1,28 +0,0 @@
1
- import asyncio
2
- from typing import Any
3
-
4
-
5
- class ThreadSafeEvent(asyncio.Event):
6
- """
7
- ThreadSafeEvent is a subclass of asyncio.Event that allows for thread-safe setting and clearing of the event.
8
- """
9
-
10
- def __init__(self, *args, **kwargs):
11
- super().__init__(*args, **kwargs)
12
- if self._loop is None:
13
- self._loop = asyncio.get_event_loop()
14
-
15
- def set(self):
16
- if not self._loop.is_closed():
17
- self._loop.call_soon_threadsafe(super().set)
18
-
19
- def clear(self):
20
- self._loop.call_soon_threadsafe(super().clear)
21
-
22
-
23
- async def read_with_interrupt(listener: Any, interrupt: ThreadSafeEvent):
24
- try:
25
- result = await listener.read()
26
- return result
27
- finally:
28
- interrupt.set()