tasklite-engine 1.0.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.
tasklite/__init__.py ADDED
@@ -0,0 +1,55 @@
1
+ """TaskLite — 零外部依赖、物理进程隔离的轻量高可靠任务编排引擎。
2
+
3
+ A lightweight, battle-hardened task orchestration engine with zero external
4
+ dependencies, process isolation, and ACID persistence.
5
+ """
6
+
7
+ __version__ = "1.0.0"
8
+
9
+ # Public API - Core
10
+ from .models.context import TaskContext
11
+ from .exceptions import (
12
+ FatalError, FATAL_EXCEPTIONS, PipelineError, RateLimitHit, RetryError,
13
+ TRANSIENT_EXCEPTIONS, TransientRegistry, classify_exception,
14
+ is_transient_exception,
15
+ )
16
+ from .models.job import Job, JobRuntimeState
17
+ from .pipeline import DLQEntry, TaskLite, WORKER_RESOURCE
18
+
19
+ # Public API - Contrib ecosystem
20
+ from . import contrib
21
+
22
+ # Public API - Generic pipeline scaffolding (run/revive/job-id/progress)
23
+ from . import pipeline_util
24
+
25
+ # Public API - Resources
26
+ from .engine.resource import CapacityResource, RateLimitResource, Resource
27
+
28
+ __all__ = [
29
+ # Core
30
+ "TaskLite",
31
+ "Job",
32
+ "JobRuntimeState",
33
+ "TaskContext",
34
+ "RetryError",
35
+ "FatalError",
36
+ "PipelineError",
37
+ "RateLimitHit",
38
+ "FATAL_EXCEPTIONS",
39
+ "TRANSIENT_EXCEPTIONS",
40
+ "TransientRegistry",
41
+ "classify_exception",
42
+ "is_transient_exception",
43
+ "WORKER_RESOURCE",
44
+ "DLQEntry",
45
+ # Contrib
46
+ "contrib",
47
+ # Generic pipeline scaffolding
48
+ "pipeline_util",
49
+ # Resources
50
+ "Resource",
51
+ "RateLimitResource",
52
+ "CapacityResource",
53
+ # Version
54
+ "__version__",
55
+ ]
@@ -0,0 +1 @@
1
+ """State persistence backends for tasklite."""
@@ -0,0 +1,199 @@
1
+ """Abstract state backend interface for tasklite."""
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any, Dict, List, Optional, Tuple
4
+
5
+ from ..error_codes import (
6
+ ERR_COMMIT_FAILURE_DLQ,
7
+ ERR_DEADLOCK_GAP,
8
+ ERR_DEPENDENCY_DEADLOCK,
9
+ ERR_DISPATCH_FAILURE,
10
+ ERR_JOB_DEPENDENCY,
11
+ ERR_MALFORMED_JOB,
12
+ ERR_MAX_RETRIES,
13
+ ERR_NO_HANDLER,
14
+ ERR_PAYLOAD_VALIDATION,
15
+ ERR_RESOURCE_DEADLOCK,
16
+ ERROR_TYPE_COMMIT_FAILURE,
17
+ ERROR_TYPE_DEADLOCK,
18
+ ERROR_TYPE_DEPENDENCY,
19
+ ERROR_TYPE_DISPATCH,
20
+ ERROR_TYPE_FATAL,
21
+ ERROR_TYPE_NO_HANDLER,
22
+ ERROR_TYPE_TRANSIENT_EXHAUSTED,
23
+ ERROR_TYPE_UNKNOWN,
24
+ ERROR_TYPE_VALIDATION,
25
+ classify_error_type,
26
+ )
27
+
28
+
29
+
30
+ class AbstractStateBackend(ABC):
31
+ """Abstract interface for pipeline state persistence.
32
+
33
+ Implementations are called only from the single-threaded parent process;
34
+ subprocess workers do not touch the backend.
35
+ """
36
+
37
+ @abstractmethod
38
+ def load_wall(self) -> Dict[str, Dict[str, Any]]: ...
39
+
40
+ @abstractmethod
41
+ def load_failed(self) -> Dict[str, Dict[str, Any]]: ...
42
+
43
+ @abstractmethod
44
+ def load_cursors(self) -> Dict[str, str]: ...
45
+
46
+ @abstractmethod
47
+ def load_queue(self) -> List[Dict[str, Any]]: ...
48
+
49
+ @abstractmethod
50
+ def save_queue(self, jobs: List[Dict[str, Any]]) -> None: ...
51
+
52
+ @abstractmethod
53
+ def commit_job_success(
54
+ self,
55
+ uid: str,
56
+ result_meta: dict,
57
+ *,
58
+ spawned_jobs: List[Dict[str, Any]] = (),
59
+ cursor_updates: Optional[Dict[str, str]] = None,
60
+ ) -> bool:
61
+ """Persist a successful job as a delta, atomically.
62
+
63
+ Effects (all in one transaction):
64
+ - wall: INSERT/REPLACE uid -> result_meta
65
+ - queue: DELETE the popped uid
66
+ - queue: INSERT spawned_jobs at FRONT (preserving their order)
67
+ - cursors: merge cursor_updates
68
+
69
+ Returns True on success. Returns False if any step failed — on failure
70
+ the backend MUST leave the on-disk queue unchanged (the popped uid
71
+ remains on disk); the caller is responsible for deciding whether to
72
+ re-queue the job. Callers MUST NOT update in-memory wall/cursor state
73
+ when False is returned.
74
+ """
75
+
76
+ @abstractmethod
77
+ def commit_job_failure(
78
+ self,
79
+ uid: str,
80
+ result_meta: dict,
81
+ ) -> bool:
82
+ """Persist a failed job to the DLQ as a delta, atomically.
83
+
84
+ Effects: failed_dlq INSERT/REPLACE uid -> result_meta; queue DELETE uid.
85
+ Returns True on success. Returns False if persistence failed — on
86
+ failure the backend MUST leave the on-disk queue unchanged (the popped
87
+ uid remains on disk); the caller is responsible for deciding whether to
88
+ re-queue the job. Callers MUST NOT update in-memory failed state when
89
+ False is returned.
90
+ """
91
+
92
+ @abstractmethod
93
+ def commit_retry(
94
+ self,
95
+ popped_uid: str,
96
+ requeued_job: Dict[str, Any],
97
+ *,
98
+ front: bool = False,
99
+ ) -> bool:
100
+ """Persist a retry requeue as a delta, atomically.
101
+
102
+ Effects: queue DELETE popped_uid; queue INSERT requeued_job at front (if
103
+ ``front=True``) or back. No wall/DLQ writes. Returns True on success.
104
+ Returns False on failure — on-disk queue unchanged (popped uid remains).
105
+ """
106
+
107
+ @abstractmethod
108
+ def commit_bulk_failure(
109
+ self,
110
+ uids_metas: List[Tuple[str, dict]],
111
+ ) -> bool:
112
+ """Best-effort bulk mark-failed (deadlock case), as a delta.
113
+
114
+ Effects: failed_dlq INSERT/REPLACE each uid -> meta; queue DELETE each
115
+ uid. Returns True if all DLQ writes succeeded and the deletions
116
+ committed. Returns False if ANY DLQ write failed — on-disk queue is
117
+ preserved. SQLite backend is atomic, so it either returns True or
118
+ raises (-> False). Remaining (non-deleted) rows keep their order.
119
+ """
120
+
121
+ @abstractmethod
122
+ def append_failed(self, uid: str, payload: Optional[Dict[str, Any]] = None) -> None:
123
+ """Append a record to the DLQ (failed log) without touching the queue.
124
+
125
+ Used for out-of-band DLQ appends (e.g. user-driven inspection).
126
+ Implementations should be idempotent on ``uid``.
127
+ """
128
+
129
+ @abstractmethod
130
+ def commit_skip(self, uid: str) -> bool:
131
+ """Delta 删除队列中已完成/已失败(重复)的 uid。
132
+
133
+ 去重命中(uid 已在 wall/failed)时调用——磁盘队列中该 uid 是残留
134
+ 条目,直接删除以同步内存/磁盘(内存 = 磁盘 − in-flight)。
135
+ 不写 wall/failed(重复条目不改变任何状态,只是清理)。
136
+ 返回 True 成功;False 时 on-disk 队列不变,调用方走崩溃契约。
137
+ """
138
+
139
+ @abstractmethod
140
+ def get_meta(self, key: str) -> Optional[str]:
141
+ """读取一条框架级元数据(如 fencing 的 last_run_id)。无则返回 None。"""
142
+
143
+ @abstractmethod
144
+ def set_meta(self, key: str, value: str) -> None:
145
+ """写入一条框架级元数据(UPSERT 语义)。失败抛异常(不吞)。"""
146
+
147
+ @abstractmethod
148
+ def enqueue_jobs(self, jobs: List[Dict[str, Any]], *, front: bool = False) -> List[str]:
149
+ """批量增量入队(enqueue 与 run 并发时不做全量覆盖)。
150
+
151
+ Effects: 按 front 在队列头/尾插入 jobs(保序),**单事务原子**。
152
+ 返回实际插入的 uid 列表(跳过与现有队列重复的 uid)。
153
+ 失败时事务回滚并抛异常(不吞)。
154
+
155
+ 与 commit_* 的 delta 语义对齐:不做 ``DELETE FROM queue`` 全表
156
+ 重写——运行中 run() 的 delta commit 与该入队并发时,不会因
157
+ 全量 save_queue 覆盖而丢失 run() 已提交的变更
158
+ (写入侧不覆盖,读-改-写窗口仍在)。
159
+ """
160
+
161
+ @abstractmethod
162
+ def delete_failed(self, uids: List[str]) -> int:
163
+ """从 DLQ 批量删除指定 uid(clear_dlq/clear_history 的后端实现)。
164
+
165
+ 返回实际删除行数。不触碰 queue/wall。仅限 run() 之外调用
166
+ (改变 is_known 判定基础,与 enqueue 同纪律)。
167
+ """
168
+
169
+ @abstractmethod
170
+ def delete_wall(self, uids: List[str]) -> int:
171
+ """从 wall 批量删除指定 uid(clear_history 的后端实现 + commit 路径事务内清理)。返回实际删除行数。
172
+
173
+ 两种调用上下文:
174
+ - ``clear_history(...)``(运行外管理 API)批量删除;
175
+ - commit_job_failure / commit_bulk_failure 的同一事务内逐 uid 删除
176
+ (rerun 任务重跑失败时 wall 旧记录作废,最终状态唯一)。
177
+
178
+ **运行中**的调用仅限 commit 事务内部(随 DLQ 写入原子提交,不改变
179
+ 进行中的派发判定);独立调用(clear_history)仅限 run 之外(改变
180
+ is_known 判定基础,与 enqueue 同纪律)。
181
+ """
182
+
183
+ @abstractmethod
184
+ def seed_wall(self, uids: List[str]) -> int:
185
+ """把 uid 批量写入 wall(meta 为空 dict)——存档迁移标记「已处理」。
186
+
187
+ 用途:媒体/数据资产项目的存档迁移(硬链接 + wall 种子),
188
+ 不必裸 SQL INSERT 框架内部表。返回实际写入行数。
189
+ 幂等:已存在的 uid 被覆盖(meta 重置为空)。
190
+ """
191
+
192
+ @abstractmethod
193
+ def seed_cursor(self, key: str, value: str) -> None:
194
+ """预填一个 cursor(UPSERT 语义,幂等)——存档迁移/进度书签恢复。
195
+
196
+ 注意:discovery 已见判定走 wall/failed(见 discovery.py
197
+ 头部),不再使用 cursor——新代码的「已见预填」请用 ``seed_wall``
198
+ (把 process 任务的 uid 写入 wall)。本方法服务通用业务 cursor。
199
+ """