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.
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/PKG-INFO +57 -8
- tasklite_engine-1.0.0/tasklite_engine.egg-info/PKG-INFO → tasklite_engine-1.2.0/README.md +43 -23
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/pyproject.toml +26 -3
- tasklite_engine-1.2.0/tasklite/__init__.py +134 -0
- tasklite_engine-1.2.0/tasklite/backend/__init__.py +12 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/backend/base.py +3 -1
- tasklite_engine-1.2.0/tasklite/backend/memory.py +224 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/backend/sqlite_backend.py +2 -0
- tasklite_engine-1.2.0/tasklite/engine/__init__.py +20 -0
- tasklite_engine-1.2.0/tasklite/engine/channel.py +685 -0
- tasklite_engine-1.2.0/tasklite/engine/completion.py +352 -0
- tasklite_engine-1.2.0/tasklite/engine/config.py +146 -0
- tasklite_engine-1.2.0/tasklite/engine/console.py +183 -0
- tasklite_engine-1.2.0/tasklite/engine/dispatch.py +425 -0
- tasklite_engine-1.2.0/tasklite/engine/governor.py +389 -0
- tasklite_engine-1.2.0/tasklite/engine/inflight.py +219 -0
- tasklite_engine-1.2.0/tasklite/engine/pacing.py +61 -0
- tasklite_engine-1.2.0/tasklite/engine/policy.py +460 -0
- tasklite_engine-1.2.0/tasklite/engine/recovery.py +250 -0
- tasklite_engine-1.2.0/tasklite/engine/resource.py +617 -0
- tasklite_engine-1.2.0/tasklite/engine/runtime.py +486 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/engine/scheduler.py +135 -105
- tasklite_engine-1.2.0/tasklite/engine/session.py +112 -0
- tasklite_engine-1.2.0/tasklite/engine/store.py +706 -0
- tasklite_engine-1.2.0/tasklite/engine/types.py +114 -0
- tasklite_engine-1.2.0/tasklite/exceptions.py +65 -0
- tasklite_engine-1.2.0/tasklite/hooks.py +49 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/models/context.py +33 -80
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/models/job.py +241 -15
- tasklite_engine-1.2.0/tasklite/models/state.py +350 -0
- tasklite_engine-1.2.0/tasklite/pipeline.py +510 -0
- tasklite_engine-1.2.0/tasklite/taxonomy.py +876 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/utils/__init__.py +2 -1
- tasklite_engine-1.2.0/tasklite/utils/injective.py +146 -0
- tasklite_engine-1.2.0/tasklite/utils/ipc.py +551 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/utils/jsonutil.py +2 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/utils/lockfile.py +4 -49
- tasklite_engine-1.2.0/tasklite/wrappers/__init__.py +53 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/wrappers/discovery.py +3 -62
- tasklite_engine-1.2.0/tasklite/wrappers/http.py +1028 -0
- tasklite_engine-1.0.0/README.md → tasklite_engine-1.2.0/tasklite_engine.egg-info/PKG-INFO +72 -7
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite_engine.egg-info/SOURCES.txt +16 -9
- tasklite_engine-1.2.0/tests/test_packaging.py +33 -0
- tasklite_engine-1.0.0/tasklite/__init__.py +0 -55
- tasklite_engine-1.0.0/tasklite/backend/__init__.py +0 -1
- tasklite_engine-1.0.0/tasklite/engine/__init__.py +0 -16
- tasklite_engine-1.0.0/tasklite/engine/completion.py +0 -439
- tasklite_engine-1.0.0/tasklite/engine/deadlock.py +0 -33
- tasklite_engine-1.0.0/tasklite/engine/dispatch.py +0 -426
- tasklite_engine-1.0.0/tasklite/engine/executor.py +0 -1202
- tasklite_engine-1.0.0/tasklite/engine/failure.py +0 -509
- tasklite_engine-1.0.0/tasklite/engine/inflight.py +0 -25
- tasklite_engine-1.0.0/tasklite/engine/loop.py +0 -281
- tasklite_engine-1.0.0/tasklite/engine/recovery.py +0 -409
- tasklite_engine-1.0.0/tasklite/engine/resource.py +0 -240
- tasklite_engine-1.0.0/tasklite/engine/retry.py +0 -147
- tasklite_engine-1.0.0/tasklite/engine/runtime.py +0 -331
- tasklite_engine-1.0.0/tasklite/error_codes.py +0 -66
- tasklite_engine-1.0.0/tasklite/exceptions.py +0 -215
- tasklite_engine-1.0.0/tasklite/models/state.py +0 -413
- tasklite_engine-1.0.0/tasklite/pipeline.py +0 -914
- tasklite_engine-1.0.0/tasklite/pipeline_util.py +0 -204
- tasklite_engine-1.0.0/tasklite/utils/ipc.py +0 -88
- tasklite_engine-1.0.0/tasklite/utils/validation.py +0 -177
- tasklite_engine-1.0.0/tasklite/wrappers/__init__.py +0 -26
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/LICENSE +0 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/setup.cfg +0 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/contrib/__init__.py +0 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/models/__init__.py +0 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite/py.typed +0 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite_engine.egg-info/dependency_links.txt +0 -0
- {tasklite_engine-1.0.0 → tasklite_engine-1.2.0}/tasklite_engine.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
|
343
|
+
├── pipeline.py # TaskLite 核心门面与配置
|
|
344
|
+
├── hooks.py # 用户工具函数(job_ref / progress_hook / slice_list)
|
|
301
345
|
├── engine/ # 核心执行机器群
|
|
302
|
-
│ ├──
|
|
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
|
-
│ ├──
|
|
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
|
-
├──
|
|
313
|
-
├──
|
|
314
|
-
└── utils/ # jsonutil (禁NaN) / lockfile
|
|
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
|
|
314
|
+
├── pipeline.py # TaskLite 核心门面与配置
|
|
315
|
+
├── hooks.py # 用户工具函数(job_ref / progress_hook / slice_list)
|
|
301
316
|
├── engine/ # 核心执行机器群
|
|
302
|
-
│ ├──
|
|
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
|
-
│ ├──
|
|
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
|
-
├──
|
|
313
|
-
├──
|
|
314
|
-
└── utils/ # jsonutil (禁NaN) / lockfile
|
|
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
|
-
|
|
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 ..
|
|
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)
|
|
@@ -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
|
+
"""
|