mageflow 0.3.2__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {mageflow-0.3.2 → mageflow-0.3.4}/PKG-INFO +8 -2
  2. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/__init__.py +6 -6
  3. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/callbacks.py +31 -12
  4. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/chain/workflows.py +0 -1
  5. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/clients/hatchet/adapter.py +32 -17
  6. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/clients/hatchet/mageflow.py +18 -12
  7. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/clients/hatchet/workflow.py +0 -1
  8. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/config.py +10 -6
  9. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/lifecycle/signature.py +0 -1
  10. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/lifecycle/task.py +0 -1
  11. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/swarm/workflows.py +0 -1
  12. mageflow-0.3.4/mageflow/testing/__init__.py +15 -0
  13. mageflow-0.3.4/mageflow/testing/_adapter.py +467 -0
  14. mageflow-0.3.4/mageflow/testing/_config.py +42 -0
  15. mageflow-0.3.4/mageflow/testing/_redis.py +23 -0
  16. mageflow-0.3.4/mageflow/testing/plugin.py +94 -0
  17. {mageflow-0.3.2 → mageflow-0.3.4}/pyproject.toml +20 -2
  18. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/models.py +19 -1
  19. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/signature/test_edge_case.py +25 -8
  20. mageflow-0.3.4/tests/integration/hatchet/test_retry_cache.py +65 -0
  21. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/test_ttl.py +1 -1
  22. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/worker.py +73 -6
  23. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/assertions.py +0 -1
  24. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/callbacks/conftest.py +6 -4
  25. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/callbacks/test_handle_task_callback.py +13 -19
  26. mageflow-0.3.4/tests/unit/callbacks/test_retry_cache.py +791 -0
  27. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/clients/test_hatchet_adapter.py +81 -5
  28. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/test_remove_ttl.py +17 -3
  29. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/test_ttl_config.py +13 -10
  30. mageflow-0.3.4/tests/unit/test_ttl_validation.py +48 -0
  31. mageflow-0.3.4/tests/unit/testing/conftest.py +22 -0
  32. mageflow-0.3.4/tests/unit/testing/test_chain_dispatch.py +169 -0
  33. mageflow-0.3.4/tests/unit/testing/test_integration_user_workflow.py +230 -0
  34. mageflow-0.3.4/tests/unit/testing/test_swarm_dispatch.py +138 -0
  35. mageflow-0.3.4/tests/unit/testing/test_task_dispatch.py +194 -0
  36. mageflow-0.3.4/tests/unit/workflows/__init__.py +0 -0
  37. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/test_chain_end.py +1 -4
  38. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/test_swarm_item_failed.py +5 -16
  39. {mageflow-0.3.2 → mageflow-0.3.4}/.gitignore +0 -0
  40. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/chain/__init__.py +0 -0
  41. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/chain/messages.py +0 -0
  42. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/client.py +1 -1
  43. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/clients/__init__.py +0 -0
  44. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/clients/hatchet/__init__.py +0 -0
  45. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/clients/inner_task_names.py +0 -0
  46. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/lifecycle/__init__.py +0 -0
  47. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/startup.py +1 -1
  48. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/swarm/__init__.py +0 -0
  49. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/swarm/consts.py +0 -0
  50. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/swarm/messages.py +0 -0
  51. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/utils/__init__.py +0 -0
  52. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/utils/mageflow.py +0 -0
  53. {mageflow-0.3.2 → mageflow-0.3.4}/mageflow/utils/pythonic.py +0 -0
  54. {mageflow-0.3.2 → mageflow-0.3.4}/tests/__init__.py +0 -0
  55. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/__init__.py +0 -0
  56. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/conftest.py +0 -0
  57. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/__init__.py +0 -0
  58. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/assertions.py +3 -3
  59. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/chain/__init__.py +0 -0
  60. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/chain/test__chain.py +1 -1
  61. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/chain/test_edge_cases.py +0 -0
  62. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/chain/test_stop_resume.py +1 -1
  63. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/conftest.py +1 -1
  64. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/docker-compose.hatchet.yml +0 -0
  65. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/signature/__init__.py +0 -0
  66. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/signature/test__signature.py +2 -2
  67. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/signature/test_stop_resume.py +1 -1
  68. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/swarm/__init__.py +0 -0
  69. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/swarm/test__swarm.py +2 -2
  70. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/swarm/test_edge_cases.py +2 -2
  71. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/swarm/test_stop_resume.py +2 -2
  72. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/swarm/test_workflow.py +0 -0
  73. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/test_complex_scenarios.py +3 -3
  74. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/hatchet/test_task_models.py +1 -1
  75. {mageflow-0.3.2 → mageflow-0.3.4}/tests/integration/test_redis_ttl.py +1 -1
  76. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/__init__.py +0 -0
  77. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/callbacks/__init__.py +0 -0
  78. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/clients/__init__.py +0 -0
  79. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/conftest.py +6 -6
  80. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/idempotency/__init__.py +0 -0
  81. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/idempotency/conftest.py +4 -4
  82. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/idempotency/test_chain_workflows_idempotent.py +3 -3
  83. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/idempotency/test_fill_running_tasks_idempotent.py +3 -3
  84. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/idempotency/test_fill_workflow_running_tasks_idempotent.py +3 -3
  85. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/idempotency/test_swarm_item_done_idempotent.py +1 -1
  86. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/idempotency/test_swarm_item_failed_idempotent.py +1 -1
  87. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/race_condition/__init__.py +0 -0
  88. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/race_condition/test_fill_running_tasks.py +1 -1
  89. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/test_client.py +0 -0
  90. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/test_client_signature_compatibility.py +0 -0
  91. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/test_param_config.py +0 -0
  92. {mageflow-0.3.2/tests/unit/workflows → mageflow-0.3.4/tests/unit/testing}/__init__.py +0 -0
  93. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/utils.py +0 -0
  94. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/conftest.py +4 -4
  95. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/test_chain_error.py +0 -0
  96. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/test_fill_running_tasks.py +3 -3
  97. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/test_fill_swarm_corrupted_callbacks.py +1 -1
  98. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/test_fill_swarm_running_tasks.py +2 -2
  99. {mageflow-0.3.2 → mageflow-0.3.4}/tests/unit/workflows/test_swarm_item_done.py +1 -1
  100. {mageflow-0.3.2 → mageflow-0.3.4}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mageflow
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Manage Graph Execution Flow - A unified interface for task orchestration across different task managers
5
5
  Project-URL: Homepage, https://imaginary-cherry.github.io/mageflow/
6
6
  Project-URL: Documentation, https://imaginary-cherry.github.io/mageflow/
@@ -26,7 +26,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
26
  Classifier: Topic :: System :: Distributed Computing
27
27
  Classifier: Typing :: Typed
28
28
  Requires-Python: <3.14,>=3.10
29
- Requires-Dist: thirdmagic<0.1.0,>=0.0.3
29
+ Requires-Dist: thirdmagic<0.1.0,>=0.0.5
30
30
  Provides-Extra: dev
31
31
  Requires-Dist: black>=26.1.0; extra == 'dev'
32
32
  Requires-Dist: coverage[toml]<8.0.0,>=7.0.0; extra == 'dev'
@@ -37,5 +37,11 @@ Requires-Dist: pytest-asyncio<2.0.0,>=1.2.0; extra == 'dev'
37
37
  Requires-Dist: pytest<10.0.0,>=9.0.2; extra == 'dev'
38
38
  Requires-Dist: requests<3.0.0,>=2.32.5; extra == 'dev'
39
39
  Requires-Dist: ruff>=0.15.5; extra == 'dev'
40
+ Requires-Dist: testcontainers[redis]<5.0.0,>=4.14.0; extra == 'dev'
40
41
  Provides-Extra: hatchet
41
42
  Requires-Dist: hatchet-sdk<1.24.0,>=1.22.5; extra == 'hatchet'
43
+ Provides-Extra: testing
44
+ Requires-Dist: fakeredis[json,lua]<3.0.0,>=2.34.0; extra == 'testing'
45
+ Requires-Dist: pytest-asyncio<2.0.0,>=1.2.0; extra == 'testing'
46
+ Requires-Dist: pytest<10.0.0,>=9.0.0; extra == 'testing'
47
+ Requires-Dist: testcontainers[redis]<5.0.0,>=4.14.0; extra == 'testing'
@@ -1,16 +1,17 @@
1
1
  import rapyer
2
2
  from rapyer.fields import RapyerKey
3
-
4
- from mageflow.callbacks import handle_task_callback
5
- from mageflow.client import Mageflow
6
- from mageflow.config import MageflowConfig, SignatureTTLConfig, TTLConfig
7
- from mageflow.startup import start_mageflow
3
+ from thirdmagic import abounded_field
8
4
  from thirdmagic.chain.creator import chain as achain
9
5
  from thirdmagic.signature import Signature
10
6
  from thirdmagic.swarm.creator import swarm as aswarm
11
7
  from thirdmagic.task import TaskSignature
12
8
  from thirdmagic.task import sign as asign
13
9
 
10
+ from mageflow.callbacks import handle_task_callback
11
+ from mageflow.client import Mageflow
12
+ from mageflow.config import MageflowConfig, SignatureTTLConfig, TTLConfig
13
+ from mageflow.startup import start_mageflow
14
+
14
15
  lock_task = TaskSignature.alock_from_key
15
16
  resume = TaskSignature.resume_from_key
16
17
  pause = TaskSignature.pause_from_key
@@ -22,7 +23,6 @@ async def load_sign(key: RapyerKey) -> Signature:
22
23
 
23
24
 
24
25
  load_signature = rapyer.afind_one
25
- abounded_field = rapyer.apipeline
26
26
 
27
27
 
28
28
  __all__ = [
@@ -7,11 +7,16 @@ from typing import Any
7
7
  from hatchet_sdk import Context
8
8
  from hatchet_sdk.runnables.types import EmptyModel
9
9
  from pydantic import BaseModel
10
-
11
- from mageflow.utils.pythonic import flexible_call
10
+ from thirdmagic.signature.retry_cache import (
11
+ retry_cache_ctx,
12
+ setup_retry_cache,
13
+ teardown_retry_cache,
14
+ )
12
15
  from thirdmagic.task.model import TaskSignature
13
16
  from thirdmagic.task_def import MageflowTaskDefinition
14
17
 
18
+ from mageflow.utils.pythonic import flexible_call
19
+
15
20
 
16
21
  class AcceptParams(Enum):
17
22
  JUST_MESSAGE = 1
@@ -27,6 +32,7 @@ def handle_task_callback(
27
32
  expected_params: AcceptParams = AcceptParams.NO_CTX,
28
33
  wrap_res: bool = True,
29
34
  send_signature: bool = False,
35
+ is_idempotent: bool = False,
30
36
  ):
31
37
  def task_decorator(func):
32
38
  @functools.wraps(func)
@@ -40,8 +46,18 @@ def handle_task_callback(
40
46
  # NOTE: This should not run, the task should cancel, but just in case
41
47
  return {"Error": "Task should have been canceled"}
42
48
  is_normal_run = lifecycle.is_vanilla_run()
49
+ is_task_finish = False
43
50
  signature = await lifecycle.start_task()
44
51
 
52
+ # Setup retry cache for signature idempotency on retries (durable tasks only)
53
+ cache_token = None
54
+ cache_state = None
55
+ if is_idempotent:
56
+ cache_state = await setup_retry_cache(
57
+ ctx.workflow_id, ctx.attempt_number
58
+ )
59
+ cache_token = retry_cache_ctx.set(cache_state)
60
+
45
61
  # Add params if user requires
46
62
  if send_signature:
47
63
  kwargs["signature"] = signature
@@ -53,20 +69,18 @@ def handle_task_callback(
53
69
  result = await flexible_call(func, message, *args, **kwargs)
54
70
  else:
55
71
  result = await flexible_call(func, message, ctx, *args, **kwargs)
56
- except asyncio.CancelledError as e:
57
- if not is_normal_run:
58
- await lifecycle.task_failed(msg_data, e)
59
- raise
60
- except Exception as e:
61
- if is_normal_run:
62
- raise
63
- if not TaskSignature.ClientAdapter.should_task_retry(
72
+ except (Exception, asyncio.CancelledError) as e:
73
+ will_retry = TaskSignature.ClientAdapter.should_task_retry(
64
74
  task_model, ctx.attempt_number, e
65
- ):
66
- await lifecycle.task_failed(msg_data, e)
75
+ )
76
+ if not will_retry:
77
+ is_task_finish = True
78
+ if not is_normal_run:
79
+ await lifecycle.task_failed(msg_data, e)
67
80
  raise
68
81
  else:
69
82
  # If this is a simple task, no signature, then we dont do any manipulation
83
+ is_task_finish = True
70
84
  if is_normal_run:
71
85
  return result
72
86
  task_results = HatchetResult(hatchet_results=result)
@@ -76,6 +90,11 @@ def handle_task_callback(
76
90
  return task_results
77
91
  else:
78
92
  return result
93
+ finally:
94
+ if cache_token is not None:
95
+ retry_cache_ctx.reset(cache_token)
96
+ if is_task_finish and cache_state:
97
+ await teardown_retry_cache(cache_state)
79
98
 
80
99
  wrapper.__signature__ = inspect.signature(func)
81
100
  return wrapper
@@ -2,7 +2,6 @@ from logging import Logger
2
2
  from typing import Any
3
3
 
4
4
  from rapyer.fields import RapyerKey
5
-
6
5
  from thirdmagic.clients.lifecycle import BaseLifecycle
7
6
 
8
7
 
@@ -8,6 +8,13 @@ from hatchet_sdk.runnables.types import EmptyModel
8
8
  from hatchet_sdk.runnables.workflow import BaseWorkflow
9
9
  from pydantic import BaseModel, TypeAdapter
10
10
  from rapyer.fields import RapyerKey
11
+ from thirdmagic.chain import ChainTaskSignature
12
+ from thirdmagic.clients.base import BaseClientAdapter
13
+ from thirdmagic.consts import TASK_ID_PARAM_NAME
14
+ from thirdmagic.signature import Signature
15
+ from thirdmagic.swarm import SwarmTaskSignature
16
+ from thirdmagic.task import TaskSignature
17
+ from thirdmagic.task_def import MageflowTaskDefinition
11
18
 
12
19
  from mageflow.chain.messages import ChainCallbackMessage, ChainErrorMessage
13
20
  from mageflow.clients.hatchet.workflow import MageflowWorkflow
@@ -25,13 +32,6 @@ from mageflow.swarm.messages import (
25
32
  SwarmErrorMessage,
26
33
  SwarmResultsMessage,
27
34
  )
28
- from thirdmagic.chain import ChainTaskSignature
29
- from thirdmagic.clients.base import BaseClientAdapter
30
- from thirdmagic.consts import TASK_ID_PARAM_NAME
31
- from thirdmagic.signature import Signature
32
- from thirdmagic.swarm import SwarmTaskSignature
33
- from thirdmagic.task import TaskSignature
34
- from thirdmagic.task_def import MageflowTaskDefinition
35
35
 
36
36
 
37
37
  class HatchetClientAdapter(BaseClientAdapter):
@@ -123,6 +123,15 @@ class HatchetClientAdapter(BaseClientAdapter):
123
123
  def extract_retries(self, client_task: BaseWorkflow) -> int:
124
124
  return client_task.tasks[0].retries
125
125
 
126
+ def _prepare_wf(self, signature: TaskSignature, set_return_field: bool, **kwargs):
127
+ total_kwargs = signature.kwargs | kwargs
128
+ workflow = self.hatchet.workflow(
129
+ name=signature.task_name, input_validator=signature.model_validators
130
+ )
131
+ return_field_name = signature.return_field_name if set_return_field else None
132
+ mageflow_wf = MageflowWorkflow(workflow, total_kwargs, return_field_name)
133
+ return mageflow_wf
134
+
126
135
  async def acall_signature(
127
136
  self,
128
137
  signature: TaskSignature,
@@ -134,17 +143,23 @@ class HatchetClientAdapter(BaseClientAdapter):
134
143
  if msg is None:
135
144
  msg = EmptyModel()
136
145
  options = self._update_options(signature, options)
137
- total_kwargs = signature.kwargs | kwargs
138
- workflow = self.hatchet.workflow(
139
- name=signature.task_name, input_validator=signature.model_validators
140
- )
141
- mageflow_wf = MageflowWorkflow(
142
- workflow,
143
- total_kwargs,
144
- signature.return_field_name if set_return_field else None,
145
- )
146
+ mageflow_wf = self._prepare_wf(signature, set_return_field, **kwargs)
146
147
  return await mageflow_wf.aio_run_no_wait(msg, options)
147
148
 
149
+ async def await_signature(
150
+ self,
151
+ signature: "TaskSignature",
152
+ msg: Any,
153
+ set_return_field: bool,
154
+ options: TriggerWorkflowOptions = None,
155
+ **kwargs,
156
+ ):
157
+ if msg is None:
158
+ msg = EmptyModel()
159
+ options = self._update_options(signature, options)
160
+ mageflow_wf = self._prepare_wf(signature, set_return_field, **kwargs)
161
+ return await mageflow_wf.aio_run(msg, options)
162
+
148
163
  def should_task_retry(
149
164
  self,
150
165
  task_definition: MageflowTaskDefinition,
@@ -153,7 +168,7 @@ class HatchetClientAdapter(BaseClientAdapter):
153
168
  ) -> bool:
154
169
  finish_retry = (
155
170
  task_definition.retries is not None
156
- and attempt_num < task_definition.retries
171
+ and attempt_num <= task_definition.retries
157
172
  )
158
173
  return finish_retry and not isinstance(e, NonRetryableException)
159
174
 
@@ -17,6 +17,14 @@ from hatchet_sdk.runnables.types import (
17
17
  from hatchet_sdk.runnables.workflow import BaseWorkflow, Standalone
18
18
  from hatchet_sdk.worker.worker import LifespanFn
19
19
  from redis.asyncio import Redis
20
+ from thirdmagic import chain, sign
21
+ from thirdmagic.chain import ChainTaskSignature
22
+ from thirdmagic.signature import Signature
23
+ from thirdmagic.swarm import SwarmTaskSignature
24
+ from thirdmagic.swarm.creator import SignatureOptions, swarm
25
+ from thirdmagic.task import TaskInputType, TaskSignature, TaskSignatureConvertible
26
+ from thirdmagic.task_def import MageflowTaskDefinition
27
+ from thirdmagic.utils import HatchetTaskType
20
28
  from typing_extensions import override
21
29
 
22
30
  from mageflow.callbacks import AcceptParams, handle_task_callback
@@ -47,14 +55,6 @@ from mageflow.swarm.workflows import (
47
55
  swarm_item_failed,
48
56
  )
49
57
  from mageflow.utils.mageflow import does_task_wants_ctx
50
- from thirdmagic import chain, sign
51
- from thirdmagic.chain import ChainTaskSignature
52
- from thirdmagic.signature import Signature
53
- from thirdmagic.swarm import SwarmTaskSignature
54
- from thirdmagic.swarm.creator import SignatureOptions, swarm
55
- from thirdmagic.task import TaskInputType, TaskSignature, TaskSignatureConvertible
56
- from thirdmagic.task_def import MageflowTaskDefinition
57
- from thirdmagic.utils import HatchetTaskType
58
58
 
59
59
  Duration = timedelta | str
60
60
 
@@ -125,14 +125,16 @@ class HatchetMageflow(Hatchet):
125
125
  )
126
126
  )
127
127
 
128
- def task_decorator(self, func: Callable, hatchet_task):
128
+ def task_decorator(self, func: Callable, hatchet_task, is_idempotent: bool = False):
129
129
  param_config = (
130
130
  AcceptParams.ALL
131
131
  if does_task_wants_ctx(func)
132
132
  else self.mageflow_config.param_config
133
133
  )
134
134
  send_signature = getattr(func, "__send_signature__", False)
135
- handler_dec = handle_task_callback(param_config, send_signature=send_signature)
135
+ handler_dec = handle_task_callback(
136
+ param_config, send_signature=send_signature, is_idempotent=is_idempotent
137
+ )
136
138
  func = handler_dec(func)
137
139
  wf = hatchet_task(func)
138
140
  self._add_task_def(wf)
@@ -146,7 +148,9 @@ class HatchetMageflow(Hatchet):
146
148
  """
147
149
  hatchet_task = super().task(name=name, **kwargs)
148
150
 
149
- decorator = functools.partial(self.task_decorator, hatchet_task=hatchet_task)
151
+ decorator = functools.partial(
152
+ self.task_decorator, hatchet_task=hatchet_task, is_idempotent=False
153
+ )
150
154
  return decorator
151
155
 
152
156
  @override
@@ -156,7 +160,9 @@ class HatchetMageflow(Hatchet):
156
160
  """
157
161
  hatchet_task = super().durable_task(name=name, **kwargs)
158
162
 
159
- decorator = functools.partial(self.task_decorator, hatchet_task=hatchet_task)
163
+ decorator = functools.partial(
164
+ self.task_decorator, hatchet_task=hatchet_task, is_idempotent=True
165
+ )
160
166
 
161
167
  return decorator
162
168
 
@@ -3,7 +3,6 @@ from typing import Any
3
3
  from hatchet_sdk.runnables.workflow import Workflow
4
4
  from hatchet_sdk.utils.typing import JSONSerializableMapping
5
5
  from pydantic import BaseModel
6
-
7
6
  from thirdmagic.utils import deep_merge
8
7
 
9
8
 
@@ -1,26 +1,29 @@
1
1
  import dataclasses
2
- from dataclasses import dataclass, field
2
+ from dataclasses import field
3
3
  from typing import Optional
4
4
 
5
- from mageflow.callbacks import AcceptParams
5
+ from pydantic import Field
6
+ from pydantic.dataclasses import dataclass
6
7
  from thirdmagic.chain import ChainTaskSignature
7
8
  from thirdmagic.consts import REMOVED_TASK_TTL
8
- from thirdmagic.signature import SignatureConfig
9
+ from thirdmagic.signature import Signature, SignatureConfig
9
10
  from thirdmagic.swarm import SwarmTaskSignature
10
11
  from thirdmagic.swarm.state import PublishState
11
12
  from thirdmagic.task import TaskSignature
12
13
 
14
+ from mageflow.callbacks import AcceptParams
15
+
13
16
 
14
17
  @dataclass
15
18
  class SignatureTTLConfig:
16
19
  active_ttl: Optional[int] = None # seconds, None = use general
17
- ttl_when_sign_done: Optional[int] = None
20
+ ttl_when_sign_done: Optional[int] = Field(default=None, ge=REMOVED_TASK_TTL)
18
21
 
19
22
 
20
23
  @dataclass
21
24
  class TTLConfig:
22
25
  active_ttl: int = 24 * 60 * 60 # general active TTL (default 24h)
23
- ttl_when_sign_done: int = REMOVED_TASK_TTL # general done TTL (default 5min)
26
+ ttl_when_sign_done: int = Field(default=REMOVED_TASK_TTL, ge=REMOVED_TASK_TTL)
24
27
  task: SignatureTTLConfig = field(default_factory=SignatureTTLConfig)
25
28
  chain: SignatureTTLConfig = field(default_factory=SignatureTTLConfig)
26
29
  swarm: SignatureTTLConfig = field(default_factory=SignatureTTLConfig)
@@ -44,4 +47,5 @@ def apply_ttl_config(ttl_config: TTLConfig):
44
47
  done_ttl = sig_config.ttl_when_sign_done or ttl_config.ttl_when_sign_done
45
48
 
46
49
  sig_type.Meta = dataclasses.replace(sig_type.Meta, ttl=active_ttl)
47
- sig_type.SignatureSettings = SignatureConfig(ttl_when_sign_done=done_ttl)
50
+ if issubclass(sig_type, Signature):
51
+ sig_type.SignatureSettings = SignatureConfig(ttl_when_sign_done=done_ttl)
@@ -2,7 +2,6 @@ import asyncio
2
2
  from typing import Any, Optional, cast
3
3
 
4
4
  import rapyer
5
-
6
5
  from thirdmagic.clients.lifecycle import BaseLifecycle
7
6
  from thirdmagic.container import ContainerTaskSignature
8
7
  from thirdmagic.signature import Signature
@@ -1,7 +1,6 @@
1
1
  from typing import Any
2
2
 
3
3
  from pydantic import BaseModel
4
-
5
4
  from thirdmagic.clients.lifecycle import BaseLifecycle
6
5
 
7
6
 
@@ -3,7 +3,6 @@ from typing import Any, Optional, cast
3
3
 
4
4
  import rapyer
5
5
  from rapyer.fields import RapyerKey
6
-
7
6
  from thirdmagic.clients.lifecycle import BaseLifecycle
8
7
  from thirdmagic.signature import Signature
9
8
  from thirdmagic.swarm import PublishState
@@ -0,0 +1,15 @@
1
+ from mageflow.testing._adapter import (
2
+ ChainDispatchRecord,
3
+ RecordedDispatch,
4
+ SwarmDispatchRecord,
5
+ TaskDispatchRecord,
6
+ TestClientAdapter,
7
+ )
8
+
9
+ __all__ = [
10
+ "TestClientAdapter",
11
+ "RecordedDispatch",
12
+ "TaskDispatchRecord",
13
+ "SwarmDispatchRecord",
14
+ "ChainDispatchRecord",
15
+ ]