tasklite-engine 1.0.0__tar.gz → 1.2.0__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 (73) hide show
  1. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/PKG-INFO +57 -8
  2. tasklite_engine-1.0.0/tasklite_engine.egg-info/PKG-INFO → tasklite_engine-1.2.0/README.md +43 -23
  3. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/pyproject.toml +26 -3
  4. tasklite_engine-1.2.0/tasklite/__init__.py +134 -0
  5. tasklite_engine-1.2.0/tasklite/backend/__init__.py +12 -0
  6. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/backend/base.py +3 -1
  7. tasklite_engine-1.2.0/tasklite/backend/memory.py +224 -0
  8. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/backend/sqlite_backend.py +2 -0
  9. tasklite_engine-1.2.0/tasklite/engine/__init__.py +20 -0
  10. tasklite_engine-1.2.0/tasklite/engine/channel.py +685 -0
  11. tasklite_engine-1.2.0/tasklite/engine/completion.py +352 -0
  12. tasklite_engine-1.2.0/tasklite/engine/config.py +146 -0
  13. tasklite_engine-1.2.0/tasklite/engine/console.py +183 -0
  14. tasklite_engine-1.2.0/tasklite/engine/dispatch.py +425 -0
  15. tasklite_engine-1.2.0/tasklite/engine/governor.py +389 -0
  16. tasklite_engine-1.2.0/tasklite/engine/inflight.py +219 -0
  17. tasklite_engine-1.2.0/tasklite/engine/pacing.py +61 -0
  18. tasklite_engine-1.2.0/tasklite/engine/policy.py +460 -0
  19. tasklite_engine-1.2.0/tasklite/engine/recovery.py +250 -0
  20. tasklite_engine-1.2.0/tasklite/engine/resource.py +617 -0
  21. tasklite_engine-1.2.0/tasklite/engine/runtime.py +486 -0
  22. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/engine/scheduler.py +135 -105
  23. tasklite_engine-1.2.0/tasklite/engine/session.py +112 -0
  24. tasklite_engine-1.2.0/tasklite/engine/store.py +706 -0
  25. tasklite_engine-1.2.0/tasklite/engine/types.py +114 -0
  26. tasklite_engine-1.2.0/tasklite/exceptions.py +65 -0
  27. tasklite_engine-1.2.0/tasklite/hooks.py +49 -0
  28. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/models/context.py +33 -80
  29. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/models/job.py +241 -15
  30. tasklite_engine-1.2.0/tasklite/models/state.py +350 -0
  31. tasklite_engine-1.2.0/tasklite/pipeline.py +510 -0
  32. tasklite_engine-1.2.0/tasklite/taxonomy.py +876 -0
  33. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/utils/__init__.py +2 -1
  34. tasklite_engine-1.2.0/tasklite/utils/injective.py +146 -0
  35. tasklite_engine-1.2.0/tasklite/utils/ipc.py +551 -0
  36. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/utils/jsonutil.py +2 -0
  37. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/utils/lockfile.py +4 -49
  38. tasklite_engine-1.2.0/tasklite/wrappers/__init__.py +53 -0
  39. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/wrappers/discovery.py +3 -62
  40. tasklite_engine-1.2.0/tasklite/wrappers/http.py +1028 -0
  41. tasklite_engine-1.0.0/README.md → tasklite_engine-1.2.0/tasklite_engine.egg-info/PKG-INFO +72 -7
  42. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite_engine.egg-info/SOURCES.txt +16 -9
  43. tasklite_engine-1.2.0/tests/test_packaging.py +33 -0
  44. tasklite_engine-1.0.0/tasklite/__init__.py +0 -55
  45. tasklite_engine-1.0.0/tasklite/backend/__init__.py +0 -1
  46. tasklite_engine-1.0.0/tasklite/engine/__init__.py +0 -16
  47. tasklite_engine-1.0.0/tasklite/engine/completion.py +0 -439
  48. tasklite_engine-1.0.0/tasklite/engine/deadlock.py +0 -33
  49. tasklite_engine-1.0.0/tasklite/engine/dispatch.py +0 -426
  50. tasklite_engine-1.0.0/tasklite/engine/executor.py +0 -1202
  51. tasklite_engine-1.0.0/tasklite/engine/failure.py +0 -509
  52. tasklite_engine-1.0.0/tasklite/engine/inflight.py +0 -25
  53. tasklite_engine-1.0.0/tasklite/engine/loop.py +0 -281
  54. tasklite_engine-1.0.0/tasklite/engine/recovery.py +0 -409
  55. tasklite_engine-1.0.0/tasklite/engine/resource.py +0 -240
  56. tasklite_engine-1.0.0/tasklite/engine/retry.py +0 -147
  57. tasklite_engine-1.0.0/tasklite/engine/runtime.py +0 -331
  58. tasklite_engine-1.0.0/tasklite/error_codes.py +0 -66
  59. tasklite_engine-1.0.0/tasklite/exceptions.py +0 -215
  60. tasklite_engine-1.0.0/tasklite/models/state.py +0 -413
  61. tasklite_engine-1.0.0/tasklite/pipeline.py +0 -914
  62. tasklite_engine-1.0.0/tasklite/pipeline_util.py +0 -204
  63. tasklite_engine-1.0.0/tasklite/utils/ipc.py +0 -88
  64. tasklite_engine-1.0.0/tasklite/utils/validation.py +0 -177
  65. tasklite_engine-1.0.0/tasklite/wrappers/__init__.py +0 -26
  66. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/LICENSE +0 -0
  67. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/setup.cfg +0 -0
  68. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/contrib/__init__.py +0 -0
  69. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/models/__init__.py +0 -0
  70. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/py.typed +0 -0
  71. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite_engine.egg-info/dependency_links.txt +0 -0
  72. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite_engine.egg-info/requires.txt +0 -0
  73. {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite_engine.egg-info/top_level.txt +0 -0
@@ -1,8 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tasklite-engine
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: A lightweight task orchestration engine with zero external dependencies, process isolation, and ACID persistence
5
5
  License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/CaptainLi321/tasklite
7
+ Project-URL: Repository, https://github.com/CaptainLi321/tasklite
8
+ Keywords: task-queue,task-orchestration,background-jobs,subprocess,process-isolation,acid,workflow
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
6
19
  Requires-Python: >=3.9
7
20
  Description-Content-Type: text/markdown
8
21
  License-File: LICENSE
@@ -125,8 +138,15 @@ flowchart TD
125
138
  - `ctx.declare_output()` 声明产出文件,内置路径遍历防御 (`../` 拒绝);
126
139
  - 任务成功时校验物理产出完整性,任务失败时自动清理半成品文件,避免残留。
127
140
 
141
+ ### 7. 🌐 组合式 HTTP 网络工具与快照守卫 (HTTP Wrappers)
142
+ - 开放 Callable 设计,支持原生 `urllib`、`requests`、`httpx` 或平台自定义签名 SDK;
143
+ - `http_guard` 自动捕获 429、解析 `Retry-After` 并触发 `ctx.suspend_resource` 全管线退避,收敛异常三分类;
144
+ - `SQLiteSnapshotStore` 提供单射键原始响应快照持久化,支持全离线幂等重放;
145
+ - 内置标准 Netscape `cookies.txt` 解析工具。
146
+
128
147
  ---
129
148
 
149
+
130
150
  ## 🚀 快速上手 (Quickstart)
131
151
 
132
152
  ### 安装
@@ -273,8 +293,31 @@ pipeline.enqueue([
273
293
  pipeline.run()
274
294
  ```
275
295
 
296
+ ### Recipe 5: 官方 HTTP 守卫与原始响应快照重放 (HTTP Wrappers)
297
+
298
+ ```python
299
+ from tasklite import TaskLite, RateLimitResource
300
+ from tasklite.wrappers.http import SQLiteSnapshotStore, http_guard, fetch_urllib
301
+
302
+ pipeline = TaskLite("crawler", state_dir="./states")
303
+ pipeline.add_resource(RateLimitResource("api", interval_seconds=1.0))
304
+
305
+ # 原始 HTTP 响应持久化快照(独立 snapshots.db,支持全离线重放与反爬保护)
306
+ snapshot_store = SQLiteSnapshotStore("./snapshots.db")
307
+ cached_fetch = snapshot_store.cached(fetch_urllib)
308
+
309
+ def crawler_handler(job, ctx):
310
+ # 429 时自动挂起 api 资源、5xx 瞬态重试、4xx 直接 DLQ
311
+ with http_guard(ctx=ctx, resource="api", default_suspend_ttl=60.0):
312
+ resp = cached_fetch(job.payload["url"])
313
+ return resp.json()
314
+
315
+ pipeline.register_handler("fetch", crawler_handler, default_resources={"api": 1.0})
316
+ ```
317
+
276
318
  ---
277
319
 
320
+
278
321
  ## 🛠️ 死信队列与运维 (DLQ & Ops)
279
322
 
280
323
  当任务重试超限或发生致命错误时,任务进入持久化死信队列 (DLQ)。可以通过标准 API 检查与清理:
@@ -297,21 +340,27 @@ pipeline.clear_history("download::")
297
340
 
298
341
  ```
299
342
  tasklite/
300
- ├── pipeline.py # TaskLite / TaskLite 核心门面与配置
343
+ ├── pipeline.py # TaskLite 核心门面与配置
344
+ ├── hooks.py # 用户工具函数(job_ref / progress_hook / slice_list)
301
345
  ├── engine/ # 核心执行机器群
302
- │ ├── loop.py # 主调度事件循环 (LoopRunner)
346
+ │ ├── runtime.py # 核心运行期深模块、主循环事件泵 (EngineRuntime)
347
+ │ ├── config.py # RunConfig 静态装配快照、默认值唯一解析点
348
+ │ ├── session.py # RunSession 单次 run 生命周期状态与钩子单一出口
349
+ │ ├── pacing.py # 事件泵等待/空闲决策纯函数(decide_wait)
350
+ │ ├── types.py # 引擎公共值对象单一真相源
351
+ │ ├── store.py # 状态事务、3-strike 崩溃与死锁归因 (StateStore)
352
+ │ ├── console.py # OpsConsole:run() 外纯运维接缝(管理 API 委托目标)
303
353
  │ ├── dispatch.py # 任务派发状态机 (DispatchMachine)
304
354
  │ ├── completion.py # 任务完成与提交 (CompletionMachine)
305
355
  │ ├── recovery.py # 崩溃检测与恢复 (RecoveryMachine)
306
- │ ├── failure.py # 失败收敛与死信归因 (FailureMachine)
356
+ │ ├── channel.py # IPC 与子进程执行通道 (ExecutionChannel / WorkerLaunchSpec)
307
357
  │ ├── scheduler.py # 资源调度与 DAG 依赖 (JobScheduler)
308
- │ ├── executor.py # 子进程隔离执行器与看门狗
309
358
  │ └── resource.py # 令牌桶限速与并发容量资源
310
359
  ├── backend/ # SQLite WAL 强一致事务持久化后端
311
360
  ├── models/ # Job / TaskContext / PipelineState 数据模型
312
- ├── wrappers/ # discovery.py(增量扫描封装)
313
- ├── pipeline_util.py # 通用脚手架(任务指纹 / 进度钩子 / 瞬态错误注册)
314
- └── utils/ # jsonutil (禁NaN) / lockfile / validation
361
+ ├── taxonomy.py # 错误分类法与校验分类深模块 (ErrorTaxonomy)
362
+ ├── wrappers/ # discovery.py(增量扫描)/ http.py(网络守卫与快照)
363
+ └── utils/ # jsonutil (禁NaN) / lockfile
315
364
  ```
316
365
 
317
366
  ---
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: tasklite-engine
3
- Version: 1.0.0
4
- Summary: A lightweight task orchestration engine with zero external dependencies, process isolation, and ACID persistence
5
- License-Expression: MIT
6
- Requires-Python: >=3.9
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Provides-Extra: dev
10
- Requires-Dist: pytest; extra == "dev"
11
- Requires-Dist: hypothesis>=6.0; extra == "dev"
12
- Requires-Dist: mutmut>=3.0; extra == "dev"
13
- Requires-Dist: pytest-timeout>=2.0; extra == "dev"
14
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
15
- Dynamic: license-file
16
-
17
1
  # ⚡ TaskLite — 零依赖、进程隔离的轻量任务编排引擎
18
2
 
19
3
  > **A Lightweight Task Orchestration Engine for Agent & Batch Pipelines.**
@@ -125,8 +109,15 @@ flowchart TD
125
109
  - `ctx.declare_output()` 声明产出文件,内置路径遍历防御 (`../` 拒绝);
126
110
  - 任务成功时校验物理产出完整性,任务失败时自动清理半成品文件,避免残留。
127
111
 
112
+ ### 7. 🌐 组合式 HTTP 网络工具与快照守卫 (HTTP Wrappers)
113
+ - 开放 Callable 设计,支持原生 `urllib`、`requests`、`httpx` 或平台自定义签名 SDK;
114
+ - `http_guard` 自动捕获 429、解析 `Retry-After` 并触发 `ctx.suspend_resource` 全管线退避,收敛异常三分类;
115
+ - `SQLiteSnapshotStore` 提供单射键原始响应快照持久化,支持全离线幂等重放;
116
+ - 内置标准 Netscape `cookies.txt` 解析工具。
117
+
128
118
  ---
129
119
 
120
+
130
121
  ## 🚀 快速上手 (Quickstart)
131
122
 
132
123
  ### 安装
@@ -273,8 +264,31 @@ pipeline.enqueue([
273
264
  pipeline.run()
274
265
  ```
275
266
 
267
+ ### Recipe 5: 官方 HTTP 守卫与原始响应快照重放 (HTTP Wrappers)
268
+
269
+ ```python
270
+ from tasklite import TaskLite, RateLimitResource
271
+ from tasklite.wrappers.http import SQLiteSnapshotStore, http_guard, fetch_urllib
272
+
273
+ pipeline = TaskLite("crawler", state_dir="./states")
274
+ pipeline.add_resource(RateLimitResource("api", interval_seconds=1.0))
275
+
276
+ # 原始 HTTP 响应持久化快照(独立 snapshots.db,支持全离线重放与反爬保护)
277
+ snapshot_store = SQLiteSnapshotStore("./snapshots.db")
278
+ cached_fetch = snapshot_store.cached(fetch_urllib)
279
+
280
+ def crawler_handler(job, ctx):
281
+ # 429 时自动挂起 api 资源、5xx 瞬态重试、4xx 直接 DLQ
282
+ with http_guard(ctx=ctx, resource="api", default_suspend_ttl=60.0):
283
+ resp = cached_fetch(job.payload["url"])
284
+ return resp.json()
285
+
286
+ pipeline.register_handler("fetch", crawler_handler, default_resources={"api": 1.0})
287
+ ```
288
+
276
289
  ---
277
290
 
291
+
278
292
  ## 🛠️ 死信队列与运维 (DLQ & Ops)
279
293
 
280
294
  当任务重试超限或发生致命错误时,任务进入持久化死信队列 (DLQ)。可以通过标准 API 检查与清理:
@@ -297,21 +311,27 @@ pipeline.clear_history("download::")
297
311
 
298
312
  ```
299
313
  tasklite/
300
- ├── pipeline.py # TaskLite / TaskLite 核心门面与配置
314
+ ├── pipeline.py # TaskLite 核心门面与配置
315
+ ├── hooks.py # 用户工具函数(job_ref / progress_hook / slice_list)
301
316
  ├── engine/ # 核心执行机器群
302
- │ ├── loop.py # 主调度事件循环 (LoopRunner)
317
+ │ ├── runtime.py # 核心运行期深模块、主循环事件泵 (EngineRuntime)
318
+ │ ├── config.py # RunConfig 静态装配快照、默认值唯一解析点
319
+ │ ├── session.py # RunSession 单次 run 生命周期状态与钩子单一出口
320
+ │ ├── pacing.py # 事件泵等待/空闲决策纯函数(decide_wait)
321
+ │ ├── types.py # 引擎公共值对象单一真相源
322
+ │ ├── store.py # 状态事务、3-strike 崩溃与死锁归因 (StateStore)
323
+ │ ├── console.py # OpsConsole:run() 外纯运维接缝(管理 API 委托目标)
303
324
  │ ├── dispatch.py # 任务派发状态机 (DispatchMachine)
304
325
  │ ├── completion.py # 任务完成与提交 (CompletionMachine)
305
326
  │ ├── recovery.py # 崩溃检测与恢复 (RecoveryMachine)
306
- │ ├── failure.py # 失败收敛与死信归因 (FailureMachine)
327
+ │ ├── channel.py # IPC 与子进程执行通道 (ExecutionChannel / WorkerLaunchSpec)
307
328
  │ ├── scheduler.py # 资源调度与 DAG 依赖 (JobScheduler)
308
- │ ├── executor.py # 子进程隔离执行器与看门狗
309
329
  │ └── resource.py # 令牌桶限速与并发容量资源
310
330
  ├── backend/ # SQLite WAL 强一致事务持久化后端
311
331
  ├── models/ # Job / TaskContext / PipelineState 数据模型
312
- ├── wrappers/ # discovery.py(增量扫描封装)
313
- ├── pipeline_util.py # 通用脚手架(任务指纹 / 进度钩子 / 瞬态错误注册)
314
- └── utils/ # jsonutil (禁NaN) / lockfile / validation
332
+ ├── taxonomy.py # 错误分类法与校验分类深模块 (ErrorTaxonomy)
333
+ ├── wrappers/ # discovery.py(增量扫描)/ http.py(网络守卫与快照)
334
+ └── utils/ # jsonutil (禁NaN) / lockfile
315
335
  ```
316
336
 
317
337
  ---
@@ -1,15 +1,35 @@
1
1
  [build-system]
2
- requires = ["setuptools"]
2
+ requires = ["setuptools>=77"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tasklite-engine"
7
- version = "1.0.0"
7
+ dynamic = ["version"]
8
+
8
9
  description = "A lightweight task orchestration engine with zero external dependencies, process isolation, and ACID persistence"
9
10
  requires-python = ">=3.9"
10
11
  license = "MIT"
11
12
  readme = "README.md"
12
13
 
14
+ # PEP 639:license 以 SPDX 表达式声明,与 License 分类器互斥,不得再列 MIT classifier
15
+ keywords = ["task-queue", "task-orchestration", "background-jobs", "subprocess", "process-isolation", "acid", "workflow"]
16
+ classifiers = [
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Typing :: Typed",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/CaptainLi321/tasklite"
31
+ Repository = "https://github.com/CaptainLi321/tasklite"
32
+
13
33
  [project.optional-dependencies]
14
34
  dev = ["pytest", "hypothesis>=6.0", "mutmut>=3.0", "pytest-timeout>=2.0", "pytest-cov>=4.0"]
15
35
 
@@ -17,6 +37,10 @@ dev = ["pytest", "hypothesis>=6.0", "mutmut>=3.0", "pytest-timeout>=2.0", "pytes
17
37
  include = ["tasklite", "tasklite.*"]
18
38
  exclude = ["tests"]
19
39
 
40
+ # 版本单一事实源:tasklite/__init__.py 的 __version__,构建时动态读取
41
+ [tool.setuptools.dynamic]
42
+ version = { attr = "tasklite.__version__" }
43
+
20
44
  # PEP 561:py.typed 标记随包分发,下游 mypy/pyright 可消费内联类型注解
21
45
  [tool.setuptools.package-data]
22
46
  tasklite = ["py.typed"]
@@ -47,7 +71,6 @@ source_paths = [
47
71
  "tasklite/wrappers/",
48
72
  "tasklite/exceptions.py",
49
73
  "tasklite/pipeline.py",
50
- "tasklite/pipeline_util.py",
51
74
  ]
52
75
  also_copy = ["tasklite/"]
53
76
  pytest_add_cli_args = ["--import-mode=importlib"]
@@ -0,0 +1,134 @@
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.2.0"
8
+
9
+
10
+ # Public API - Core
11
+ from .models.context import TaskContext
12
+ from .exceptions import (
13
+ FatalError, PipelineError, RateLimitHit, RetryError,
14
+ )
15
+ from .models.job import Job, JobRuntimeState
16
+ from .hooks import job_ref, progress_hook, slice_list
17
+ from .pipeline import DLQEntry, TaskLite, WORKER_RESOURCE
18
+ from .utils.injective import (
19
+ content_fingerprint,
20
+ escape_injective,
21
+ safe_uid_filename,
22
+ sanitize_content_id,
23
+ sanitize_identifier,
24
+ sanitize_job_component,
25
+ )
26
+
27
+ # Public API - Taxonomy & Validation
28
+ from .taxonomy import (
29
+ ERR_COMMIT_FAILURE_DLQ,
30
+ ERR_DEADLOCK_GAP,
31
+ ERR_DEPENDENCY_DEADLOCK,
32
+ ERR_DISPATCH_FAILURE,
33
+ ERR_JOB_DEPENDENCY,
34
+ ERR_MALFORMED_JOB,
35
+ ERR_MAX_RETRIES,
36
+ ERR_NO_HANDLER,
37
+ ERR_PAYLOAD_VALIDATION,
38
+ ERR_RESOURCE_DEADLOCK,
39
+ ERROR_TYPE_COMMIT_FAILURE,
40
+ ERROR_TYPE_DEADLOCK,
41
+ ERROR_TYPE_DEPENDENCY,
42
+ ERROR_TYPE_DISPATCH,
43
+ ERROR_TYPE_FATAL,
44
+ ERROR_TYPE_NO_HANDLER,
45
+ ERROR_TYPE_TRANSIENT_EXHAUSTED,
46
+ ERROR_TYPE_UNKNOWN,
47
+ ERROR_TYPE_VALIDATION,
48
+ FATAL_EXCEPTIONS,
49
+ TRANSIENT_EXCEPTIONS,
50
+ ErrorCategory,
51
+ ErrorClassification,
52
+ ErrorTaxonomy,
53
+ TransientRegistry,
54
+ ValidationErrorItem,
55
+ ValidationResult,
56
+ classify_error_type,
57
+ classify_exception,
58
+ is_transient_exception,
59
+ validate_payload,
60
+ validate_resource_amounts,
61
+ )
62
+
63
+ # Public API - Contrib ecosystem
64
+ from . import contrib
65
+
66
+ # Public API - Resources
67
+ from .engine.resource import CapacityResource, RateLimitResource, Resource
68
+
69
+ __all__ = [
70
+ # Core
71
+ "TaskLite",
72
+ "Job",
73
+ "JobRuntimeState",
74
+ "TaskContext",
75
+ "RetryError",
76
+ "FatalError",
77
+ "PipelineError",
78
+ "RateLimitHit",
79
+ "FATAL_EXCEPTIONS",
80
+ "TRANSIENT_EXCEPTIONS",
81
+ "TransientRegistry",
82
+ "classify_exception",
83
+ "is_transient_exception",
84
+ "WORKER_RESOURCE",
85
+ "DLQEntry",
86
+ "job_ref",
87
+ "progress_hook",
88
+ "slice_list",
89
+ # Taxonomy, Errors & Validation
90
+ "ErrorTaxonomy",
91
+ "ErrorCategory",
92
+ "ErrorClassification",
93
+ "ValidationErrorItem",
94
+ "ValidationResult",
95
+ "classify_error_type",
96
+ "validate_payload",
97
+ "validate_resource_amounts",
98
+ "ERR_DEPENDENCY_DEADLOCK",
99
+ "ERR_JOB_DEPENDENCY",
100
+ "ERR_PAYLOAD_VALIDATION",
101
+ "ERR_MAX_RETRIES",
102
+ "ERR_NO_HANDLER",
103
+ "ERR_RESOURCE_DEADLOCK",
104
+ "ERR_MALFORMED_JOB",
105
+ "ERR_COMMIT_FAILURE_DLQ",
106
+ "ERR_DISPATCH_FAILURE",
107
+ "ERR_DEADLOCK_GAP",
108
+ "ERROR_TYPE_FATAL",
109
+ "ERROR_TYPE_TRANSIENT_EXHAUSTED",
110
+ "ERROR_TYPE_DEPENDENCY",
111
+ "ERROR_TYPE_DEADLOCK",
112
+ "ERROR_TYPE_NO_HANDLER",
113
+ "ERROR_TYPE_VALIDATION",
114
+ "ERROR_TYPE_COMMIT_FAILURE",
115
+ "ERROR_TYPE_DISPATCH",
116
+ "ERROR_TYPE_UNKNOWN",
117
+ # Injective & Fingerprint
118
+ "content_fingerprint",
119
+ "escape_injective",
120
+ "sanitize_identifier",
121
+ "sanitize_job_component",
122
+ "sanitize_content_id",
123
+ "safe_uid_filename",
124
+ # Contrib
125
+ "contrib",
126
+ # Resources
127
+ "Resource",
128
+ "RateLimitResource",
129
+ "CapacityResource",
130
+ # Version
131
+ "__version__",
132
+ ]
133
+
134
+
@@ -0,0 +1,12 @@
1
+ """State persistence backends for tasklite."""
2
+
3
+ from .base import AbstractStateBackend, classify_error_type
4
+ from .memory import InMemoryStateBackend
5
+ from .sqlite_backend import SQLiteStateBackend
6
+
7
+ __all__ = [
8
+ "AbstractStateBackend",
9
+ "InMemoryStateBackend",
10
+ "SQLiteStateBackend",
11
+ "classify_error_type",
12
+ ]
@@ -1,8 +1,10 @@
1
1
  """Abstract state backend interface for tasklite."""
2
+ from __future__ import annotations
3
+
2
4
  from abc import ABC, abstractmethod
3
5
  from typing import Any, Dict, List, Optional, Tuple
4
6
 
5
- from ..error_codes import (
7
+ from ..taxonomy import (
6
8
  ERR_COMMIT_FAILURE_DLQ,
7
9
  ERR_DEADLOCK_GAP,
8
10
  ERR_DEPENDENCY_DEADLOCK,
@@ -0,0 +1,224 @@
1
+ """纯内存状态后端适配器(InMemoryStateBackend)。
2
+
3
+ 实现 AbstractStateBackend 完整契约,提供零文件系统 IO 的纯内存状态存储。
4
+ 适用于瞬态管线、单元测试、沙盒执行与 CI 矩阵测试。
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import copy
9
+ import logging
10
+ import threading
11
+ from datetime import datetime, timezone
12
+ from typing import Any, Dict, List, Optional, Tuple
13
+
14
+ from .base import AbstractStateBackend, classify_error_type
15
+ from ..models.state import uid_from_job_dict
16
+
17
+ logger = logging.getLogger("tasklite")
18
+
19
+
20
+ class InMemoryStateBackend(AbstractStateBackend):
21
+ """纯内存状态后端。提供快照隔离的 delta 事务语义,与 SQLite 后端行为完全对齐。"""
22
+
23
+ def __init__(self) -> None:
24
+ self._lock = threading.RLock()
25
+ self._wall: Dict[str, Dict[str, Any]] = {}
26
+ self._failed: Dict[str, Dict[str, Any]] = {}
27
+ self._cursors: Dict[str, str] = {}
28
+ self._queue: List[Dict[str, Any]] = []
29
+ self._meta: Dict[str, str] = {}
30
+
31
+ def load_wall(self) -> Dict[str, Dict[str, Any]]:
32
+ with self._lock:
33
+ return copy.deepcopy(self._wall)
34
+
35
+ def load_failed(self) -> Dict[str, Dict[str, Any]]:
36
+ with self._lock:
37
+ return copy.deepcopy(self._failed)
38
+
39
+ def load_cursors(self) -> Dict[str, str]:
40
+ with self._lock:
41
+ return dict(self._cursors)
42
+
43
+ def load_queue(self) -> List[Dict[str, Any]]:
44
+ with self._lock:
45
+ return copy.deepcopy(self._queue)
46
+
47
+ def save_queue(self, jobs: List[Dict[str, Any]]) -> None:
48
+ with self._lock:
49
+ seen = set()
50
+ clean = []
51
+ for j in jobs:
52
+ u = uid_from_job_dict(j)
53
+ if u in seen:
54
+ logger.warning(f"save_queue: duplicate uid {u} dropped (kept first).")
55
+ continue
56
+ seen.add(u)
57
+ clean.append(copy.deepcopy(j))
58
+ self._queue = clean
59
+
60
+ def _write_dlq_entry(self, uid: str, meta: dict) -> None:
61
+ """DLQ 写入单一出口:维护 _attempt 计数、error_type 与 failed_at。"""
62
+ new_meta = copy.deepcopy(meta or {})
63
+ prev = self._failed.get(uid, {})
64
+ prev_attempts = prev.get("_attempt", 0) if isinstance(prev, dict) else 0
65
+ new_meta["_attempt"] = prev_attempts + 1
66
+
67
+ if "error_type" not in new_meta:
68
+ new_meta["error_type"] = classify_error_type(new_meta)
69
+
70
+ if "failed_at" not in new_meta:
71
+ new_meta["failed_at"] = datetime.now(timezone.utc).isoformat()
72
+
73
+ self._failed[uid] = new_meta
74
+
75
+ def commit_job_success(
76
+ self,
77
+ uid: str,
78
+ result_meta: dict,
79
+ *,
80
+ spawned_jobs: List[Dict[str, Any]] = (),
81
+ cursor_updates: Optional[Dict[str, str]] = None,
82
+ ) -> bool:
83
+ with self._lock:
84
+ try:
85
+ self._wall[uid] = copy.deepcopy(result_meta or {})
86
+ self._queue = [j for j in self._queue if uid_from_job_dict(j) != uid]
87
+ self._failed.pop(uid, None)
88
+
89
+ if spawned_jobs:
90
+ existing_uids = {uid_from_job_dict(j) for j in self._queue}
91
+ for sj in spawned_jobs:
92
+ suid = uid_from_job_dict(sj)
93
+ if suid in existing_uids:
94
+ logger.critical(
95
+ f"commit_job_success for {uid}: spawned job {suid} "
96
+ f"conflicts with queue (drift). Returning False."
97
+ )
98
+ return False
99
+ # 队首插入 spawned_jobs 并保序
100
+ self._queue[0:0] = [copy.deepcopy(j) for j in spawned_jobs]
101
+
102
+ if cursor_updates:
103
+ for k, v in cursor_updates.items():
104
+ if v is None:
105
+ self._cursors.pop(k, None)
106
+ else:
107
+ self._cursors[k] = str(v)
108
+ return True
109
+ except Exception as e:
110
+ logger.critical(f"Failed to commit job success for {uid}: {e}")
111
+ return False
112
+
113
+ def commit_job_failure(self, uid: str, result_meta: dict) -> bool:
114
+ with self._lock:
115
+ try:
116
+ self._write_dlq_entry(uid, result_meta)
117
+ self._queue = [j for j in self._queue if uid_from_job_dict(j) != uid]
118
+ self._wall.pop(uid, None)
119
+ return True
120
+ except Exception as e:
121
+ logger.critical(f"Failed to commit job failure for {uid}: {e}")
122
+ return False
123
+
124
+ def commit_retry(
125
+ self,
126
+ popped_uid: str,
127
+ requeued_job: Dict[str, Any],
128
+ *,
129
+ front: bool = False,
130
+ ) -> bool:
131
+ with self._lock:
132
+ try:
133
+ self._queue = [j for j in self._queue if uid_from_job_dict(j) != popped_uid]
134
+ if front:
135
+ self._queue.insert(0, copy.deepcopy(requeued_job))
136
+ else:
137
+ self._queue.append(copy.deepcopy(requeued_job))
138
+ return True
139
+ except Exception as e:
140
+ logger.critical(f"Failed to commit retry for {popped_uid}: {e}")
141
+ return False
142
+
143
+ def commit_bulk_failure(self, uids_metas: List[Tuple[str, dict]]) -> bool:
144
+ with self._lock:
145
+ try:
146
+ fail_uids = {u for u, _ in uids_metas}
147
+ self._queue = [j for j in self._queue if uid_from_job_dict(j) not in fail_uids]
148
+ for uid, meta in uids_metas:
149
+ self._write_dlq_entry(uid, meta)
150
+ self._wall.pop(uid, None)
151
+ return True
152
+ except Exception as e:
153
+ logger.critical(f"Failed to commit bulk failure: {e}")
154
+ return False
155
+
156
+ def append_failed(self, uid: str, payload: Optional[Dict[str, Any]] = None) -> None:
157
+ with self._lock:
158
+ self._write_dlq_entry(uid, payload or {})
159
+
160
+ def commit_skip(self, uid: str) -> bool:
161
+ with self._lock:
162
+ self._queue = [j for j in self._queue if uid_from_job_dict(j) != uid]
163
+ return True
164
+
165
+ def get_meta(self, key: str) -> Optional[str]:
166
+ with self._lock:
167
+ return self._meta.get(key)
168
+
169
+ def set_meta(self, key: str, value: str) -> None:
170
+ with self._lock:
171
+ self._meta[key] = str(value)
172
+
173
+ def enqueue_jobs(self, jobs: List[Dict[str, Any]], *, front: bool = False) -> List[str]:
174
+ if not jobs:
175
+ return []
176
+ with self._lock:
177
+ existing = {uid_from_job_dict(j) for j in self._queue}
178
+ fresh: List[Dict[str, Any]] = []
179
+ batch_seen = set()
180
+ for j in jobs:
181
+ u = uid_from_job_dict(j)
182
+ if u in existing or u in batch_seen:
183
+ continue
184
+ batch_seen.add(u)
185
+ fresh.append(copy.deepcopy(j))
186
+ if not fresh:
187
+ return []
188
+ if front:
189
+ self._queue[0:0] = fresh
190
+ else:
191
+ self._queue.extend(fresh)
192
+ return [uid_from_job_dict(j) for j in fresh]
193
+
194
+ def delete_failed(self, uids: List[str]) -> int:
195
+ with self._lock:
196
+ del_set = set(uids)
197
+ count = 0
198
+ for u in del_set:
199
+ if u in self._failed:
200
+ del self._failed[u]
201
+ count += 1
202
+ return count
203
+
204
+ def delete_wall(self, uids: List[str]) -> int:
205
+ with self._lock:
206
+ del_set = set(uids)
207
+ count = 0
208
+ for u in del_set:
209
+ if u in self._wall:
210
+ del self._wall[u]
211
+ count += 1
212
+ return count
213
+
214
+ def seed_wall(self, uids: List[str]) -> int:
215
+ with self._lock:
216
+ count = 0
217
+ for u in uids:
218
+ self._wall[u] = {}
219
+ count += 1
220
+ return count
221
+
222
+ def seed_cursor(self, key: str, value: str) -> None:
223
+ with self._lock:
224
+ self._cursors[key] = str(value)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  import logging
3
5
  import sqlite3
@@ -0,0 +1,20 @@
1
+ """Engine subpackage for tasklite.
2
+
3
+ 核心执行与深模块架构:
4
+ - ``types``: 引擎公共值对象单一真相源(StepOutcome / RunSummary / TaskStats / StopMode 等);
5
+ - ``config``: RunConfig 静态装配快照与 resolve_tuning 默认值唯一解析点;
6
+ - ``runtime``: 核心运行期深模块与主循环事件泵 (EngineRuntime);
7
+ - ``session``: 单次 run 生命周期状态与钩子单一出口 (RunSession);
8
+ - ``pacing``: 等待/空闲决策纯函数 (decide_wait / LoopFacts);
9
+ - ``store``: 统一状态事务、3-strike 崩溃与死锁归因深模块 (StateStore);
10
+ - ``dispatch``: 派发预检关与子进程派发编排 (DispatchMachine);
11
+ - ``completion``: 结果提交、清理、释放与恢复收尾 (CompletionMachine);
12
+ - ``recovery``: 崩溃恢复、TOCTOU 闭环 abort 与信号排空 (RecoveryMachine);
13
+ - ``channel``: 子进程生命周期、阶梯看门狗、IPC 通道与产物清理 (ExecutionChannel);
14
+ - ``scheduler``: 队列只读扫描与不可变投影缓存 (JobScheduler);
15
+ - ``resource``: 限速与容量资源抽象与挂起语义 (ResourceManager / Resource);
16
+ - ``governor``: 死锁归因与依赖宽限治理 (DeadlockGovernor);
17
+ - ``policy``: Rerun 决策、指纹缓存与退避状态机 (ExecutionPolicy);
18
+ - ``inflight``: 在途作业生命周期跟踪与资源租约 (InFlightTracker);
19
+ - ``console``: run() 外管理 API 运维接缝 (OpsConsole)。
20
+ """