mageflow 0.3.0__tar.gz → 0.3.2__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 (89) hide show
  1. {mageflow-0.3.0 → mageflow-0.3.2}/PKG-INFO +3 -2
  2. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/__init__.py +13 -6
  3. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/chain/workflows.py +1 -0
  4. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/client.py +22 -4
  5. mageflow-0.3.2/mageflow/clients/__init__.py +3 -0
  6. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/clients/hatchet/adapter.py +11 -12
  7. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/clients/hatchet/mageflow.py +34 -24
  8. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/clients/hatchet/workflow.py +1 -0
  9. mageflow-0.3.2/mageflow/config.py +47 -0
  10. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/lifecycle/signature.py +2 -1
  11. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/lifecycle/task.py +1 -0
  12. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/startup.py +19 -3
  13. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/swarm/workflows.py +2 -1
  14. mageflow-0.3.2/mageflow/utils/mageflow.py +2 -0
  15. {mageflow-0.3.0 → mageflow-0.3.2}/pyproject.toml +3 -2
  16. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/conftest.py +8 -0
  17. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/assertions.py +5 -5
  18. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/chain/test_stop_resume.py +4 -5
  19. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/conftest.py +5 -16
  20. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/models.py +10 -0
  21. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/swarm/test__swarm.py +8 -8
  22. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/swarm/test_edge_cases.py +5 -5
  23. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/swarm/test_stop_resume.py +5 -5
  24. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/test_task_models.py +2 -2
  25. mageflow-0.3.2/tests/integration/hatchet/test_ttl.py +64 -0
  26. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/worker.py +59 -5
  27. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/test_redis_ttl.py +12 -8
  28. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/assertions.py +7 -1
  29. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/callbacks/conftest.py +5 -5
  30. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/callbacks/test_handle_task_callback.py +3 -3
  31. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/clients/test_hatchet_adapter.py +7 -7
  32. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/conftest.py +9 -9
  33. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/idempotency/conftest.py +6 -6
  34. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/idempotency/test_chain_workflows_idempotent.py +4 -4
  35. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/idempotency/test_fill_running_tasks_idempotent.py +3 -3
  36. mageflow-0.3.2/tests/unit/race_condition/test_fill_running_tasks.py +54 -0
  37. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/test_client.py +2 -3
  38. mageflow-0.3.2/tests/unit/test_param_config.py +110 -0
  39. mageflow-0.3.2/tests/unit/test_remove_ttl.py +63 -0
  40. mageflow-0.3.2/tests/unit/test_ttl_config.py +68 -0
  41. mageflow-0.3.2/tests/unit/utils.py +8 -0
  42. mageflow-0.3.2/tests/unit/workflows/__init__.py +0 -0
  43. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/conftest.py +4 -4
  44. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/test_swarm_item_failed.py +2 -2
  45. mageflow-0.3.0/mageflow/clients/__init__.py +0 -7
  46. mageflow-0.3.0/mageflow/utils/mageflow.py +0 -2
  47. mageflow-0.3.0/uv.lock +0 -1508
  48. {mageflow-0.3.0 → mageflow-0.3.2}/.gitignore +0 -0
  49. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/callbacks.py +2 -2
  50. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/chain/__init__.py +0 -0
  51. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/chain/messages.py +0 -0
  52. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/clients/hatchet/__init__.py +0 -0
  53. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/clients/inner_task_names.py +0 -0
  54. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/lifecycle/__init__.py +0 -0
  55. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/swarm/__init__.py +0 -0
  56. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/swarm/consts.py +0 -0
  57. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/swarm/messages.py +0 -0
  58. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/utils/__init__.py +0 -0
  59. {mageflow-0.3.0 → mageflow-0.3.2}/mageflow/utils/pythonic.py +0 -0
  60. {mageflow-0.3.0 → mageflow-0.3.2}/tests/__init__.py +0 -0
  61. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/__init__.py +0 -0
  62. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/__init__.py +0 -0
  63. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/chain/__init__.py +0 -0
  64. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/chain/test__chain.py +6 -6
  65. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/chain/test_edge_cases.py +3 -3
  66. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/docker-compose.hatchet.yml +0 -0
  67. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/signature/__init__.py +0 -0
  68. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/signature/test__signature.py +10 -10
  69. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/signature/test_edge_case.py +9 -9
  70. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/signature/test_stop_resume.py +6 -6
  71. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/swarm/__init__.py +0 -0
  72. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/swarm/test_workflow.py +0 -0
  73. {mageflow-0.3.0 → mageflow-0.3.2}/tests/integration/hatchet/test_complex_scenarios.py +10 -10
  74. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/__init__.py +0 -0
  75. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/callbacks/__init__.py +0 -0
  76. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/clients/__init__.py +0 -0
  77. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/idempotency/__init__.py +0 -0
  78. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/idempotency/test_fill_workflow_running_tasks_idempotent.py +3 -3
  79. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/idempotency/test_swarm_item_done_idempotent.py +1 -1
  80. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/idempotency/test_swarm_item_failed_idempotent.py +1 -1
  81. {mageflow-0.3.0/tests/unit/workflows → mageflow-0.3.2/tests/unit/race_condition}/__init__.py +0 -0
  82. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/test_client_signature_compatibility.py +0 -0
  83. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/test_chain_end.py +0 -0
  84. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/test_chain_error.py +0 -0
  85. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/test_fill_running_tasks.py +3 -3
  86. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/test_fill_swarm_corrupted_callbacks.py +1 -1
  87. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/test_fill_swarm_running_tasks.py +2 -2
  88. {mageflow-0.3.0 → mageflow-0.3.2}/tests/unit/workflows/test_swarm_item_done.py +1 -1
  89. {mageflow-0.3.0 → mageflow-0.3.2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mageflow
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
29
+ Requires-Dist: thirdmagic<0.1.0,>=0.0.3
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'
@@ -36,5 +36,6 @@ Requires-Dist: psutil<8.0.0,>=7.1.3; extra == 'dev'
36
36
  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
+ Requires-Dist: ruff>=0.15.5; extra == 'dev'
39
40
  Provides-Extra: hatchet
40
41
  Requires-Dist: hatchet-sdk<1.24.0,>=1.22.5; extra == 'hatchet'
@@ -1,14 +1,16 @@
1
1
  import rapyer
2
2
  from rapyer.fields import RapyerKey
3
- from thirdmagic.chain.creator import chain as achain
4
- from thirdmagic.signature import Signature
5
- from thirdmagic.task import sign as asign, TaskSignature
6
- from thirdmagic.swarm.creator import swarm as aswarm
7
3
 
8
4
  from mageflow.callbacks import handle_task_callback
9
5
  from mageflow.client import Mageflow
6
+ from mageflow.config import MageflowConfig, SignatureTTLConfig, TTLConfig
7
+ from mageflow.startup import start_mageflow
8
+ from thirdmagic.chain.creator import chain as achain
9
+ from thirdmagic.signature import Signature
10
+ from thirdmagic.swarm.creator import swarm as aswarm
11
+ from thirdmagic.task import TaskSignature
12
+ from thirdmagic.task import sign as asign
10
13
 
11
- resume_task = TaskSignature.resume_from_key
12
14
  lock_task = TaskSignature.alock_from_key
13
15
  resume = TaskSignature.resume_from_key
14
16
  pause = TaskSignature.pause_from_key
@@ -20,11 +22,11 @@ async def load_sign(key: RapyerKey) -> Signature:
20
22
 
21
23
 
22
24
  load_signature = rapyer.afind_one
25
+ abounded_field = rapyer.apipeline
23
26
 
24
27
 
25
28
  __all__ = [
26
29
  "load_signature",
27
- "resume_task",
28
30
  "lock_task",
29
31
  "resume",
30
32
  "remove",
@@ -32,6 +34,11 @@ __all__ = [
32
34
  "asign",
33
35
  "handle_task_callback",
34
36
  "Mageflow",
37
+ "MageflowConfig",
38
+ "TTLConfig",
39
+ "SignatureTTLConfig",
35
40
  "achain",
36
41
  "aswarm",
42
+ "start_mageflow",
43
+ "abounded_field",
37
44
  ]
@@ -2,6 +2,7 @@ from logging import Logger
2
2
  from typing import Any
3
3
 
4
4
  from rapyer.fields import RapyerKey
5
+
5
6
  from thirdmagic.clients.lifecycle import BaseLifecycle
6
7
 
7
8
 
@@ -1,29 +1,47 @@
1
1
  import os
2
+ import warnings
2
3
  from typing import TypeVar, overload
3
4
 
4
5
  import redis
5
6
  from hatchet_sdk import Hatchet
6
7
  from redis.asyncio import Redis
7
- from thirdmagic.signature import Signature
8
8
 
9
9
  from mageflow.callbacks import AcceptParams
10
10
  from mageflow.clients.hatchet.adapter import HatchetClientAdapter
11
11
  from mageflow.clients.hatchet.mageflow import HatchetMageflow
12
+ from mageflow.config import MageflowConfig
13
+ from thirdmagic.signature import Signature
12
14
 
13
15
  T = TypeVar("T")
14
16
 
15
17
 
16
18
  @overload
17
19
  def Mageflow(
18
- hatchet_client: Hatchet, redis_client: Redis | str = None
20
+ hatchet_client: Hatchet,
21
+ redis_client: Redis | str = None,
22
+ param_config: AcceptParams = None,
23
+ config: MageflowConfig = None,
19
24
  ) -> HatchetMageflow: ...
20
25
 
21
26
 
22
27
  def Mageflow(
23
28
  hatchet_client: T = None,
24
29
  redis_client: Redis | str = None,
25
- param_config: AcceptParams = AcceptParams.NO_CTX,
30
+ param_config: AcceptParams = None,
31
+ config: MageflowConfig = None,
26
32
  ) -> T:
33
+ if config is None:
34
+ config = MageflowConfig()
35
+
36
+ if param_config is not None:
37
+ warnings.warn(
38
+ "Passing 'param_config' directly to Mageflow() is deprecated. "
39
+ "Set it via MageflowConfig(param_config=...) instead.",
40
+ DeprecationWarning,
41
+ stacklevel=2,
42
+ )
43
+ config.param_config = param_config
44
+
27
45
  if hatchet_client is None:
28
46
  hatchet_client = Hatchet()
29
47
 
@@ -35,4 +53,4 @@ def Mageflow(
35
53
  redis_client = redis.asyncio.from_url(redis_url, decode_responses=True)
36
54
  if isinstance(redis_client, str):
37
55
  redis_client = redis.asyncio.from_url(redis_client, decode_responses=True)
38
- return HatchetMageflow(hatchet_client, redis_client, param_config)
56
+ return HatchetMageflow(hatchet_client, redis_client, config)
@@ -0,0 +1,3 @@
1
+ from thirdmagic.clients.base import BaseClientAdapter, DefaultClientAdapter
2
+
3
+ __all__ = ["BaseClientAdapter", "DefaultClientAdapter"]
@@ -1,20 +1,13 @@
1
1
  from typing import Any, cast
2
2
 
3
3
  import rapyer
4
- from hatchet_sdk import Hatchet, NonRetryableException, Context
4
+ from hatchet_sdk import Context, Hatchet, NonRetryableException
5
5
  from hatchet_sdk.clients.admin import TriggerWorkflowOptions
6
6
  from hatchet_sdk.runnables.contextvars import ctx_additional_metadata
7
7
  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
18
11
 
19
12
  from mageflow.chain.messages import ChainCallbackMessage, ChainErrorMessage
20
13
  from mageflow.clients.hatchet.workflow import MageflowWorkflow
@@ -23,16 +16,22 @@ from mageflow.clients.inner_task_names import (
23
16
  ON_CHAIN_ERROR,
24
17
  ON_SWARM_ITEM_DONE,
25
18
  ON_SWARM_ITEM_ERROR,
19
+ SWARM_FILL_TASK,
26
20
  )
27
- from mageflow.clients.inner_task_names import SWARM_FILL_TASK
28
21
  from mageflow.lifecycle.signature import SignatureLifecycle
29
22
  from mageflow.lifecycle.task import TaskLifecycle
30
23
  from mageflow.swarm.messages import (
31
- SwarmMessage,
32
- SwarmResultsMessage,
33
- SwarmErrorMessage,
34
24
  FillSwarmMessage,
25
+ SwarmErrorMessage,
26
+ SwarmResultsMessage,
35
27
  )
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
36
35
 
37
36
 
38
37
  class HatchetClientAdapter(BaseClientAdapter):
@@ -3,43 +3,36 @@ import functools
3
3
  import inspect
4
4
  import random
5
5
  from datetime import timedelta
6
- from typing import Any, Unpack, Callable, TypedDict
6
+ from typing import Any, Callable, TypedDict, Unpack
7
7
 
8
- from hatchet_sdk import Hatchet, Worker, Context
8
+ from hatchet_sdk import Context, Hatchet, Worker
9
9
  from hatchet_sdk.labels import DesiredWorkerLabel
10
10
  from hatchet_sdk.rate_limit import RateLimit
11
11
  from hatchet_sdk.runnables.types import (
12
- StickyStrategy,
13
12
  ConcurrencyExpression,
14
- DefaultFilter,
15
13
  ConcurrencyLimitStrategy,
14
+ DefaultFilter,
15
+ StickyStrategy,
16
16
  )
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 sign, chain
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 TaskSignatureConvertible, TaskSignature, TaskInputType
26
- from thirdmagic.task_def import MageflowTaskDefinition
27
- from thirdmagic.utils import HatchetTaskType
28
20
  from typing_extensions import override
29
21
 
30
22
  from mageflow.callbacks import AcceptParams, handle_task_callback
31
23
  from mageflow.chain.messages import ChainCallbackMessage, ChainErrorMessage
32
24
  from mageflow.chain.workflows import chain_end_task, chain_error_task
33
25
  from mageflow.clients.inner_task_names import (
34
- ON_CHAIN_ERROR,
35
26
  ON_CHAIN_END,
36
- SWARM_FILL_TASK,
37
- ON_SWARM_ITEM_ERROR,
27
+ ON_CHAIN_ERROR,
38
28
  ON_SWARM_ITEM_DONE,
29
+ ON_SWARM_ITEM_ERROR,
30
+ SWARM_FILL_TASK,
39
31
  )
32
+ from mageflow.config import MageflowConfig
40
33
  from mageflow.startup import (
41
- lifespan_initialize,
42
34
  init_mageflow,
35
+ lifespan_initialize,
43
36
  teardown_mageflow,
44
37
  )
45
38
  from mageflow.swarm.consts import SWARM_TASK_ID_PARAM_NAME
@@ -50,10 +43,18 @@ from mageflow.swarm.messages import (
50
43
  )
51
44
  from mageflow.swarm.workflows import (
52
45
  fill_swarm_running_tasks,
53
- swarm_item_failed,
54
46
  swarm_item_done,
47
+ swarm_item_failed,
55
48
  )
56
49
  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
57
58
 
58
59
  Duration = timedelta | str
59
60
 
@@ -86,9 +87,12 @@ class WorkerOptions(TypedDict, total=False):
86
87
 
87
88
 
88
89
  async def merge_lifespan(
89
- redis: Redis, tasks: list[MageflowTaskDefinition], original_lifespan: LifespanFn
90
+ redis: Redis,
91
+ tasks: list[MageflowTaskDefinition],
92
+ config: MageflowConfig,
93
+ original_lifespan: LifespanFn,
90
94
  ):
91
- await init_mageflow(redis, tasks)
95
+ await init_mageflow(redis, tasks, config)
92
96
  async for res in original_lifespan():
93
97
  yield res
94
98
  await teardown_mageflow()
@@ -99,12 +103,12 @@ class HatchetMageflow(Hatchet):
99
103
  self,
100
104
  hatchet: Hatchet,
101
105
  redis_client: Redis,
102
- param_config: AcceptParams = AcceptParams.NO_CTX,
106
+ config: MageflowConfig = None,
103
107
  ):
104
108
  super().__init__(client=hatchet._client)
105
109
  self.hatchet = hatchet
106
110
  self.redis = redis_client
107
- self.param_config = param_config
111
+ self.mageflow_config = config or MageflowConfig()
108
112
  self._task_defs: list[MageflowTaskDefinition] = []
109
113
 
110
114
  @property
@@ -123,7 +127,9 @@ class HatchetMageflow(Hatchet):
123
127
 
124
128
  def task_decorator(self, func: Callable, hatchet_task):
125
129
  param_config = (
126
- AcceptParams.ALL if does_task_wants_ctx(func) else self.param_config
130
+ AcceptParams.ALL
131
+ if does_task_wants_ctx(func)
132
+ else self.mageflow_config.param_config
127
133
  )
128
134
  send_signature = getattr(func, "__send_signature__", False)
129
135
  handler_dec = handle_task_callback(param_config, send_signature=send_signature)
@@ -227,11 +233,15 @@ class HatchetMageflow(Hatchet):
227
233
  workflows += mageflow_flows
228
234
  if lifespan is None:
229
235
  lifespan = functools.partial(
230
- lifespan_initialize, self.redis, self._task_defs
236
+ lifespan_initialize, self.redis, self._task_defs, self.mageflow_config
231
237
  )
232
238
  else:
233
239
  lifespan = functools.partial(
234
- merge_lifespan, self.redis, self._task_defs, lifespan
240
+ merge_lifespan,
241
+ self.redis,
242
+ self._task_defs,
243
+ self.mageflow_config,
244
+ lifespan,
235
245
  )
236
246
 
237
247
  return super().worker(name, workflows=workflows, lifespan=lifespan, **kwargs)
@@ -3,6 +3,7 @@ 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
+
6
7
  from thirdmagic.utils import deep_merge
7
8
 
8
9
 
@@ -0,0 +1,47 @@
1
+ import dataclasses
2
+ from dataclasses import dataclass, field
3
+ from typing import Optional
4
+
5
+ from mageflow.callbacks import AcceptParams
6
+ from thirdmagic.chain import ChainTaskSignature
7
+ from thirdmagic.consts import REMOVED_TASK_TTL
8
+ from thirdmagic.signature import SignatureConfig
9
+ from thirdmagic.swarm import SwarmTaskSignature
10
+ from thirdmagic.swarm.state import PublishState
11
+ from thirdmagic.task import TaskSignature
12
+
13
+
14
+ @dataclass
15
+ class SignatureTTLConfig:
16
+ active_ttl: Optional[int] = None # seconds, None = use general
17
+ ttl_when_sign_done: Optional[int] = None
18
+
19
+
20
+ @dataclass
21
+ class TTLConfig:
22
+ 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)
24
+ task: SignatureTTLConfig = field(default_factory=SignatureTTLConfig)
25
+ chain: SignatureTTLConfig = field(default_factory=SignatureTTLConfig)
26
+ swarm: SignatureTTLConfig = field(default_factory=SignatureTTLConfig)
27
+
28
+
29
+ @dataclass
30
+ class MageflowConfig:
31
+ ttl: TTLConfig = field(default_factory=TTLConfig)
32
+ param_config: AcceptParams = AcceptParams.NO_CTX
33
+
34
+
35
+ def apply_ttl_config(ttl_config: TTLConfig):
36
+ config_mapping = {
37
+ TaskSignature: ttl_config.task,
38
+ ChainTaskSignature: ttl_config.chain,
39
+ SwarmTaskSignature: ttl_config.swarm,
40
+ PublishState: ttl_config.swarm,
41
+ }
42
+ for sig_type, sig_config in config_mapping.items():
43
+ active_ttl = sig_config.active_ttl or ttl_config.active_ttl
44
+ done_ttl = sig_config.ttl_when_sign_done or ttl_config.ttl_when_sign_done
45
+
46
+ sig_type.Meta = dataclasses.replace(sig_type.Meta, ttl=active_ttl)
47
+ sig_type.SignatureSettings = SignatureConfig(ttl_when_sign_done=done_ttl)
@@ -1,7 +1,8 @@
1
1
  import asyncio
2
- from typing import Optional, Any, cast
2
+ from typing import Any, Optional, cast
3
3
 
4
4
  import rapyer
5
+
5
6
  from thirdmagic.clients.lifecycle import BaseLifecycle
6
7
  from thirdmagic.container import ContainerTaskSignature
7
8
  from thirdmagic.signature import Signature
@@ -1,6 +1,7 @@
1
1
  from typing import Any
2
2
 
3
3
  from pydantic import BaseModel
4
+
4
5
  from thirdmagic.clients.lifecycle import BaseLifecycle
5
6
 
6
7
 
@@ -1,9 +1,21 @@
1
1
  import rapyer
2
2
  from redis.asyncio import Redis
3
+
4
+ from mageflow.config import MageflowConfig, apply_ttl_config
3
5
  from thirdmagic.task_def import MageflowTaskDefinition
4
6
 
5
7
 
6
- async def init_mageflow(redis: Redis, tasks: list[MageflowTaskDefinition]):
8
+ async def start_mageflow(redis: Redis, config: MageflowConfig = None):
9
+ await init_mageflow(redis, [], config)
10
+
11
+
12
+ async def init_mageflow(
13
+ redis: Redis,
14
+ tasks: list[MageflowTaskDefinition],
15
+ config: MageflowConfig = None,
16
+ ):
17
+ if config is not None:
18
+ apply_ttl_config(config.ttl)
7
19
  # Init redis in local async loop
8
20
  redis = Redis(**redis.connection_pool.connection_kwargs)
9
21
  await rapyer.init_rapyer(redis, prefer_normal_json_dump=True)
@@ -18,10 +30,14 @@ async def register_workflows(tasks: list[MageflowTaskDefinition]):
18
30
  await MageflowTaskDefinition.ainsert(*tasks)
19
31
 
20
32
 
21
- async def lifespan_initialize(redis: Redis, tasks: list[MageflowTaskDefinition]):
33
+ async def lifespan_initialize(
34
+ redis: Redis,
35
+ tasks: list[MageflowTaskDefinition],
36
+ config: MageflowConfig = None,
37
+ ):
22
38
  # Init redis in local async loop
23
39
  redis = Redis(**redis.connection_pool.connection_kwargs)
24
- await init_mageflow(redis, tasks)
40
+ await init_mageflow(redis, tasks, config)
25
41
  # yield makes the function usable as a Hatchet lifespan context manager (can also be used for FastAPI):
26
42
  # - code before yield runs at startup (init config, register workers, etc.)
27
43
  # - code after yield would run at shutdown
@@ -1,8 +1,9 @@
1
1
  from logging import Logger
2
- from typing import Optional, cast, Any
2
+ from typing import Any, Optional, cast
3
3
 
4
4
  import rapyer
5
5
  from rapyer.fields import RapyerKey
6
+
6
7
  from thirdmagic.clients.lifecycle import BaseLifecycle
7
8
  from thirdmagic.signature import Signature
8
9
  from thirdmagic.swarm import PublishState
@@ -0,0 +1,2 @@
1
+ def does_task_wants_ctx(func) -> bool:
2
+ return getattr(func, "__user_ctx__", False)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mageflow"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "Manage Graph Execution Flow - A unified interface for task orchestration across different task managers"
5
5
  authors = [
6
6
  {name = "imaginary-cherry", email = "yedidyakfir@gmail.com"}
@@ -41,7 +41,7 @@ classifiers = [
41
41
  "Operating System :: OS Independent",
42
42
  ]
43
43
  dependencies = [
44
- "thirdmagic",
44
+ "thirdmagic>=0.0.3, <0.1.0",
45
45
  ]
46
46
 
47
47
  [project.optional-dependencies]
@@ -57,6 +57,7 @@ dev = [
57
57
  "coverage[toml]>=7.0.0,<8.0.0",
58
58
  "fakeredis[json,lua]>=2.34.0,<3.0.0",
59
59
  "black>=26.1.0",
60
+ "ruff>=0.15.5",
60
61
  ]
61
62
 
62
63
  [project.urls]
@@ -2,6 +2,9 @@ import pytest_asyncio
2
2
  import redis.asyncio
3
3
  from dynaconf import Dynaconf
4
4
 
5
+ from mageflow import start_mageflow
6
+ from tests.integration.hatchet.worker import TEST_MAGEFLOW_CONFIG
7
+
5
8
  settings = Dynaconf(
6
9
  envvar_prefix="DYNACONF",
7
10
  settings_files=["settings.toml", ".secrets.toml"],
@@ -24,3 +27,8 @@ async def real_redis(redis_client):
24
27
  if delete_keys:
25
28
  await redis_client.delete(*delete_keys)
26
29
  await redis_client.aclose()
30
+
31
+
32
+ @pytest_asyncio.fixture(scope="function", loop_scope="session", autouse=True)
33
+ async def init_settings(real_redis):
34
+ await start_mageflow(real_redis, TEST_MAGEFLOW_CONFIG)
@@ -5,19 +5,19 @@ from hatchet_sdk import Hatchet
5
5
  from hatchet_sdk.clients.rest import V1LogLineList, V1TaskStatus, V1TaskSummary
6
6
  from hatchet_sdk.runnables.workflow import TaskRunRef
7
7
  from pydantic import BaseModel
8
+
9
+ from tests.integration.hatchet.conftest import extract_bad_keys_from_redis
10
+ from tests.integration.hatchet.worker import MAX_DONE_TTL
8
11
  from thirdmagic.chain.model import ChainTaskSignature
9
12
  from thirdmagic.consts import (
10
13
  MAGEFLOW_TASK_INITIALS,
11
14
  TASK_ID_PARAM_NAME,
12
- REMOVED_TASK_TTL,
13
15
  )
14
16
  from thirdmagic.signature import Signature
15
17
  from thirdmagic.swarm.model import SwarmTaskSignature
16
18
  from thirdmagic.task import TaskSignature
17
19
  from thirdmagic.utils import return_value_field
18
20
 
19
- from tests.integration.hatchet.conftest import extract_bad_keys_from_redis
20
-
21
21
  WF_MAPPING_TYPE = dict[str, V1TaskSummary]
22
22
  WF_MAPPING_BY_WF_ID_TYPE = dict[str, V1TaskSummary]
23
23
  HatchetRuns = list[V1TaskSummary]
@@ -209,11 +209,11 @@ async def assert_redis_is_clean(redis_client):
209
209
  keys_with_invalid_ttl = [
210
210
  (key, ttl)
211
211
  for key, ttl in zip(non_persistent_keys, ttls)
212
- if ttl == -1 or ttl > REMOVED_TASK_TTL
212
+ if ttl == -1 or ttl > MAX_DONE_TTL
213
213
  ]
214
214
  assert (
215
215
  len(keys_with_invalid_ttl) == 0
216
- ), f"Keys without proper TTL (should be <= {REMOVED_TASK_TTL}s): {keys_with_invalid_ttl}"
216
+ ), f"Keys without proper TTL (should be <= {MAX_DONE_TTL}s): {keys_with_invalid_ttl}"
217
217
 
218
218
 
219
219
  def assert_task_was_paused(runs: HatchetRuns, task: TaskSignature, with_resume=False):
@@ -1,20 +1,19 @@
1
1
  import asyncio
2
2
 
3
3
  import pytest
4
- from thirdmagic.task import TaskSignature
5
4
 
6
5
  import mageflow
7
6
  from tests.integration.hatchet.assertions import (
7
+ assert_chain_done,
8
+ assert_redis_is_clean,
8
9
  assert_signature_not_called,
9
10
  assert_task_was_paused,
10
- assert_redis_is_clean,
11
- assert_chain_done,
12
11
  get_runs,
13
12
  )
14
13
  from tests.integration.hatchet.conftest import HatchetInitData
15
14
  from tests.integration.hatchet.models import ContextMessage
16
- from tests.integration.hatchet.worker import sleep_task
17
- from tests.integration.hatchet.worker import task2_with_result
15
+ from tests.integration.hatchet.worker import sleep_task, task2_with_result
16
+ from thirdmagic.task import TaskSignature
18
17
 
19
18
 
20
19
  @pytest.mark.asyncio(loop_scope="session")
@@ -11,7 +11,7 @@ from datetime import datetime
11
11
  from io import BytesIO
12
12
  from pathlib import Path
13
13
  from threading import Thread
14
- from typing import Generator, Callable, AsyncGenerator
14
+ from typing import AsyncGenerator, Callable, Generator
15
15
 
16
16
  import psutil
17
17
  import pytest
@@ -23,21 +23,20 @@ from hatchet_sdk.clients.admin import TriggerWorkflowOptions
23
23
  from hatchet_sdk.clients.rest import V1TaskStatus
24
24
  from hatchet_sdk.features.runs import BulkCancelReplayOpts, RunFilter
25
25
  from redis.asyncio.client import Redis
26
- from thirdmagic.task_def import MageflowTaskDefinition
27
26
 
28
27
  import mageflow
29
28
  from mageflow import Mageflow
30
29
  from mageflow.client import HatchetMageflow
31
- from mageflow.startup import init_mageflow
32
30
  from tests.integration.hatchet.worker import (
31
+ chain_callback,
33
32
  config_obj,
33
+ fail_task,
34
34
  task1,
35
+ task1_callback,
35
36
  task2,
36
37
  task3,
37
- task1_callback,
38
- fail_task,
39
- chain_callback,
40
38
  )
39
+ from thirdmagic.task_def import MageflowTaskDefinition
41
40
 
42
41
  # If redis key starts with one of these, it shouldn't be removed
43
42
  STATIC_REDIS_PREFIX_KEYS = [MageflowTaskDefinition.__name__]
@@ -79,16 +78,6 @@ async def hatchet_client_init(
79
78
  await rapyer.teardown_rapyer()
80
79
 
81
80
 
82
- @pytest_asyncio.fixture(scope="function", loop_scope="session", autouse=True)
83
- async def init_settings(hatchet_client_init: HatchetInitData):
84
- redis_client, hatchet = (
85
- hatchet_client_init.redis_client,
86
- hatchet_client_init.hatchet,
87
- )
88
- # Load the subclasses of the task signature
89
- await init_mageflow(redis_client, [])
90
-
91
-
92
81
  @pytest_asyncio.fixture(scope="session", loop_scope="session", autouse=True)
93
82
  async def hatchet_worker_deploy(
94
83
  redis_client,
@@ -1,6 +1,7 @@
1
1
  from typing import Any
2
2
 
3
3
  from pydantic import BaseModel, Field
4
+
4
5
  from thirdmagic.message import ReturnValue
5
6
 
6
7
 
@@ -41,5 +42,14 @@ class SleepTaskMessage(ContextMessage):
41
42
  result: Any = None
42
43
 
43
44
 
45
+ class SignatureKeysResult(BaseModel):
46
+ task_keys: list[str]
47
+ chain_key: str
48
+ chain_sub_task_keys: list[str]
49
+ swarm_key: str
50
+ swarm_sub_task_keys: list[str]
51
+ publish_state_key: str
52
+
53
+
44
54
  class MageflowTestError(Exception):
45
55
  pass