camunda-python 0.1.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 (51) hide show
  1. camunda_python-0.1.0/LICENSE +200 -0
  2. camunda_python-0.1.0/PKG-INFO +377 -0
  3. camunda_python-0.1.0/README.md +348 -0
  4. camunda_python-0.1.0/camunda/__init__.py +10 -0
  5. camunda_python-0.1.0/camunda/api/__init__.py +14 -0
  6. camunda_python-0.1.0/camunda/api/app.py +80 -0
  7. camunda_python-0.1.0/camunda/api/deps.py +17 -0
  8. camunda_python-0.1.0/camunda/api/errors.py +84 -0
  9. camunda_python-0.1.0/camunda/api/pagination.py +74 -0
  10. camunda_python-0.1.0/camunda/api/routers/__init__.py +5 -0
  11. camunda_python-0.1.0/camunda/api/routers/decision.py +57 -0
  12. camunda_python-0.1.0/camunda/api/routers/deployment.py +139 -0
  13. camunda_python-0.1.0/camunda/api/routers/history.py +128 -0
  14. camunda_python-0.1.0/camunda/api/routers/process_definition.py +49 -0
  15. camunda_python-0.1.0/camunda/api/routers/process_instance.py +106 -0
  16. camunda_python-0.1.0/camunda/api/routers/task.py +92 -0
  17. camunda_python-0.1.0/camunda/api/schemas.py +200 -0
  18. camunda_python-0.1.0/camunda/common/__init__.py +19 -0
  19. camunda_python-0.1.0/camunda/common/clock.py +30 -0
  20. camunda_python-0.1.0/camunda/common/exceptions.py +34 -0
  21. camunda_python-0.1.0/camunda/common/idgen.py +23 -0
  22. camunda_python-0.1.0/camunda/common/timers.py +106 -0
  23. camunda_python-0.1.0/camunda/dmn/__init__.py +5 -0
  24. camunda_python-0.1.0/camunda/dmn/engine.py +219 -0
  25. camunda_python-0.1.0/camunda/dmn/feel.py +392 -0
  26. camunda_python-0.1.0/camunda/engine/__init__.py +9 -0
  27. camunda_python-0.1.0/camunda/engine/behavior.py +51 -0
  28. camunda_python-0.1.0/camunda/engine/expression.py +126 -0
  29. camunda_python-0.1.0/camunda/engine/process_engine.py +3237 -0
  30. camunda_python-0.1.0/camunda/job/__init__.py +9 -0
  31. camunda_python-0.1.0/camunda/job/executor.py +136 -0
  32. camunda_python-0.1.0/camunda/model/__init__.py +48 -0
  33. camunda_python-0.1.0/camunda/model/bpmn.py +326 -0
  34. camunda_python-0.1.0/camunda/model/dmn.py +101 -0
  35. camunda_python-0.1.0/camunda/model/execution.py +121 -0
  36. camunda_python-0.1.0/camunda/model/job.py +88 -0
  37. camunda_python-0.1.0/camunda/model/task.py +33 -0
  38. camunda_python-0.1.0/camunda/model/variable.py +35 -0
  39. camunda_python-0.1.0/camunda/parser/__init__.py +5 -0
  40. camunda_python-0.1.0/camunda/parser/bpmn_parser.py +646 -0
  41. camunda_python-0.1.0/camunda/parser/dmn_parser.py +225 -0
  42. camunda_python-0.1.0/camunda/persistence/__init__.py +21 -0
  43. camunda_python-0.1.0/camunda/persistence/entities.py +202 -0
  44. camunda_python-0.1.0/camunda/persistence/store.py +721 -0
  45. camunda_python-0.1.0/camunda_python.egg-info/PKG-INFO +377 -0
  46. camunda_python-0.1.0/camunda_python.egg-info/SOURCES.txt +49 -0
  47. camunda_python-0.1.0/camunda_python.egg-info/dependency_links.txt +1 -0
  48. camunda_python-0.1.0/camunda_python.egg-info/requires.txt +13 -0
  49. camunda_python-0.1.0/camunda_python.egg-info/top_level.txt +1 -0
  50. camunda_python-0.1.0/pyproject.toml +49 -0
  51. camunda_python-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,200 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for describing the origin of the Work and
141
+ reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file name and extension be included on the same "printed" page as
186
+ the copyright notice for easier identification within third-party archives.
187
+
188
+ Copyright 2026 yingwang (movingheart000@gmail.com)
189
+
190
+ Licensed under the Apache License, Version 2.0 (the "License");
191
+ you may not use this file except in compliance with the License.
192
+ You may obtain a copy of the License at
193
+
194
+ http://www.apache.org/licenses/LICENSE-2.0
195
+
196
+ Unless required by applicable law or agreed to in writing, software
197
+ distributed under the License is distributed on an "AS IS" BASIS,
198
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
199
+ See the License for the specific language governing permissions and
200
+ limitations under the License.
@@ -0,0 +1,377 @@
1
+ Metadata-Version: 2.4
2
+ Name: camunda-python
3
+ Version: 0.1.0
4
+ Summary: Python3 reimplementation of Camunda 7 BPMN engine (semantic-aligned, Apache-2.0 independent implementation)
5
+ Author: yingwang
6
+ License-Expression: Apache-2.0
7
+ Keywords: bpmn,camunda,workflow,process-engine,workflow-engine
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: lxml>=5.0
18
+ Requires-Dist: sqlalchemy>=2.0
19
+ Requires-Dist: croniter>=2.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8.0; extra == "dev"
22
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
23
+ Provides-Extra: api
24
+ Requires-Dist: fastapi>=0.115; extra == "api"
25
+ Requires-Dist: uvicorn>=0.30; extra == "api"
26
+ Requires-Dist: python-multipart>=0.0.9; extra == "api"
27
+ Requires-Dist: httpx>=0.27; extra == "api"
28
+ Dynamic: license-file
29
+
30
+ # camunda-python
31
+
32
+ 用 Python 3 语义对齐重写 Camunda 7 BPMN 引擎(Apache-2.0 独立实现,不搬运 Java 源码)。
33
+
34
+ **第一次来?** 先看 [docs/USER_GUIDE.md](docs/USER_GUIDE.md)(从「5 分钟跑通 hello world」到「生产部署」)。
35
+ **想知道项目覆盖了哪些特性 / 跑测试 / 怎么用每个 API?** 看本文档。
36
+ **想了解设计决策 / 与 Camunda 7 的差异?** 看 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。
37
+
38
+ ## 状态
39
+
40
+ - **M0~M8 已交付**(M4 全家族 + M5 DMN + M6 REST + M7 多 JobExecutor 抢锁 + M8 REST 列表分页):工程骨架 + BPMN 模型层 + lxml 解析器 + 内存版引擎核心流转 +
41
+ SQLAlchemy 2.0 持久化(ACT_RE/RU/HI 三套表)+ 崩溃恢复 + 历史写入 +
42
+ 作业执行器(Timer Start / Timer Catch / asyncBefore / asyncAfter / 失败重试 / 轮询)+
43
+ timer 边界事件(中断式,宿主 userTask / asyncBefore 节点)+ **内嵌子流程**
44
+ (embedded SubProcess:容器递归解析 / 展开与收束复活 / 边界 timer 中断整段 scope)+
45
+ **事件子流程**(error end 冒泡捕获 / timer start 订阅触发)+ **非中断式边界事件**
46
+ (cancelActivity=false:宿主不取消、spawn 并发线;root 停驻等待并发子树收束)+
47
+ **多实例**(MultiInstanceLoopCharacteristics:userTask / serviceTask / subProcess 三宿主 +
48
+ completionCondition 提前终止 + loopCounter/元素变量 + 持久化恢复)+ **消息/信号事件**
49
+ (correlate_message 1:1 关联 + throw_signal 跨实例广播 + catch/边界/事件子流程 start/中间
50
+ 与结束抛出全形态 + 非中断订阅常驻 + 恢复重推导)+ **DMN 决策引擎**
51
+ (决策表解析 + FEEL 子集求值 + 六种 hitPolicy 收敛 + businessRuleTask 集成)+ **REST 兼容层**
52
+ (FastAPI 对齐 Camunda engine-rest:部署 / 流程定义 / 流程实例 / 任务 / 历史 / 决策六类端点 +
53
+ 9 个列表端点 firstResult + maxResults 分页)+ **多 JobExecutor 抢锁**
54
+ (DB CAS lease:同一作业不会被多节点重复执行 + lease 过期可被接管 + 续约)
55
+ - 参考基线:Camunda 7.23.0(社区版最后开源稳定版)
56
+ - 架构与里程碑见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
57
+
58
+ ## 快速开始
59
+
60
+ ```bash
61
+ cd /Users/yingwang/CodeSpace/camunda
62
+ python3 -m venv .venv && source .venv/bin/activate
63
+ pip install -e ".[dev]" # 跑测试
64
+ pip install -e ".[api]" # 用 REST(M6,含 fastapi/uvicorn)
65
+
66
+ # 跑贷款审批示例(排他网关 + 用户任务 + 服务任务)
67
+ python examples/run_demo.py
68
+
69
+ # 跑定时器/作业示例(真实时钟 ~15 秒:自动定时启动 + async 拆分 + 冷却等待 + 崩溃恢复)
70
+ python examples/run_timer_demo.py
71
+
72
+ # 跑边界事件/asyncAfter 示例(真实时钟 ~6 秒:审批超时自动降级 + asyncAfter 审计日志)
73
+ python examples/run_boundary_demo.py
74
+
75
+ # 跑内嵌子流程示例(真实时钟 ~5 秒:订单履约子流程 + 边界超时整段中断退款)
76
+ python examples/run_subprocess_demo.py
77
+
78
+ # 跑非中断式边界示例(真实时钟 ~6 秒:工单催办宿主保留 + 并发复核收束)
79
+ python examples/run_ni_demo.py
80
+
81
+ # 跑多实例三宿主示例(纯同步驱动:serviceTask 同步推送 / subProcess 顺序逐店 /
82
+ # subProcess 并行 + completionCondition 抢跑终止)
83
+ python examples/run_mi_demo.py
84
+
85
+ # 跑消息/信号事件示例(纯同步驱动:跨实例消息接力 / 信号广播通知 / 崩溃恢复)
86
+ python examples/run_msg_sig_demo.py
87
+
88
+ # 跑 DMN 决策示例(纯同步驱动:直接求值 / 决策联动 COLLECT+SUM / businessRuleTask 集成)
89
+ python examples/run_dmn_demo.py
90
+
91
+ # 跑 REST API 示例(真实起 uvicorn + 真实 HTTP:部署/启动/任务/历史/决策/异常)
92
+ python examples/run_api_demo.py
93
+
94
+ # 跑多 JobExecutor 抢锁示例(同步驱动 + 真实 sleep:双 owner 共享 store,
95
+ # 同一 due job 只被其中一个执行 + lease 过期可被另一 owner 接管)
96
+ python examples/run_lock_demo.py
97
+
98
+ # 跑测试(268 个单测:M1 流转 + M2 持久化 + M3 作业 + M4-1 边界/asyncAfter +
99
+ # M4-2a 子流程 + M4-2b 事件子流程/非中断式边界 + M4-2c 多实例 +
100
+ # M4-2d 消息/信号事件 + 持久化恢复 + M5 DMN 解析/FEEL/引擎/businessRuleTask +
101
+ # M6 REST 端点与异常映射 + M7 store CAS lease 抢锁原语 + 多 executor 防双执行 +
102
+ # M8 REST 列表分页(9 个端点 + clamp/边界))
103
+ pytest
104
+ ```
105
+
106
+ ## 目录结构
107
+
108
+ ```
109
+ camunda/
110
+ ├── model/ # 纯数据模型:BpmnModel / Process / FlowNode / Execution / Task / Variable
111
+ ├── parser/ # lxml 自研 BPMN 2.0 解析器
112
+ ├── engine/ # 引擎门面 + 节点行为(M1 内存版)
113
+ ├── job/ # 作业执行器(M3)
114
+ ├── persistence/ # SQLAlchemy 持久层(M2)
115
+ ├── api/ # REST 兼容层(M6)
116
+ ├── dmn/ # DMN 决策引擎(M5)
117
+ └── common/ # 异常层次、ID 生成器
118
+ ```
119
+
120
+ ## 引擎用法
121
+
122
+ **内存版(M1)**——无 DB 依赖:
123
+
124
+ ```python
125
+ from camunda.parser import parse_bpmn_xml
126
+ from camunda.engine import ProcessEngine
127
+
128
+ engine = ProcessEngine()
129
+ with open("examples/loan-approval.bpmn") as f:
130
+ model = parse_bpmn_xml(f.read(), source_name="loan-approval.bpmn")
131
+ engine.deploy(model) # 部署 -> ["loan-approval"]
132
+
133
+ pi = engine.start_process_instance_by_key(
134
+ "loan-approval", {"applicant": "张三", "amount": 20000})
135
+ tasks = engine.create_task_query(process_instance_id=pi.id) # [人工审批]
136
+ engine.complete_task(tasks[0].id, {"approved": True}) # -> COMPLETED
137
+ ```
138
+
139
+ **持久化版(M2)**——每个命令边界全量同步 ACT 表;进程崩溃后 `from_database` 恢复:
140
+
141
+ ```python
142
+ from camunda.persistence.store import Store
143
+
144
+ # 裸文件路径即可,Store 自动归一化为 sqlite:/// 绝对路径
145
+ engine = ProcessEngine(store=Store("/tmp/camunda.db"))
146
+ engine.deploy(model)
147
+ pi = engine.start_process_instance_by_key("loan-approval", {"amount": 20000})
148
+ # …… 进程在此崩溃 ……
149
+
150
+ # 重启:恢复部署定义 + 运行中实例(execution 树/task/变量/join 等待登记)
151
+ engine2 = ProcessEngine.from_database("/tmp/camunda.db")
152
+ pi2 = engine2.get_process_instance(pi.id)
153
+ engine2.register_delegate("checkCredit", lambda v: v.update(credit_ok=True)) # delegate 不落库
154
+ engine2.complete_task(engine2.create_task_query(process_instance_id=pi.id)[0].id, {"approved": False})
155
+ ```
156
+
157
+ **作业执行器版(M3)**——定时启动/定时等待/async 拆分 + 失败重试,`JobExecutor` 后台轮询
158
+ (也可手动 `execute_due_jobs()` / 单步 `tick()` 拨钟测试):
159
+
160
+ ```python
161
+ from camunda.engine import ProcessEngine
162
+ from camunda.job import JobExecutor
163
+
164
+ engine = ProcessEngine()
165
+ engine.deploy(model) # 流程含 timer start / timer catch / camunda:asyncBefore 节点
166
+ engine.create_job_query() # [timer-start job(定义级)] 等
167
+
168
+ ex = JobExecutor(engine, poll_interval=0.5) # 后台轮询到期作业(用法见 examples/run_timer_demo.py)
169
+ ex.start() # 也可 ex.tick() 单步 / ex.shutdown() 停表
170
+ # 到期自动:timer-start 启动流程、timer-catch 继续流转、async 执行节点行为;
171
+ # 失败自动 retries-1 并顺延 duedate,耗尽即死信;引擎时间可注入 clock 便于测试
172
+ ```
173
+
174
+ **边界事件 / asyncAfter(M4-1)**——宿主等待期内 timer 到期中断,或行为完成后异步离开:
175
+
176
+ ```python
177
+ # BPMN:<bpmn:userTask id="approve"/> + 平级 <bpmn:boundaryEvent id="timeout"
178
+ # attachedToRef="approve"><bpmn:timerEventDefinition><bpmn:timeDuration
179
+ # xsi:type="bpmn:tFormalExpression">PT2S</bpmn:timeDuration>...
180
+ pi = engine.start_process_instance_by_key("approval-timeout") # 停在审批任务
181
+ # 2 秒无人 complete -> JobExecutor 触发 timer-boundary:取消审批任务,token 沿
182
+ # 边界事件出边走超时路径;期间 complete_task 则正常走主路、边界作业自动撤销
183
+ # asyncAfter:<bpmn:serviceTask camunda:asyncAfter="true" .../> delegate 同步执行,
184
+ # 「离开推进」拆成 async-after 作业(XOR 离开时重新求值条件);asyncBefore+asyncAfter 可链式
185
+ ```
186
+
187
+ **内嵌子流程(M4-2a)**——token 进入 subProcess 展开为 SCOPE,内部走完自动收束复活:
188
+ ```python
189
+ # BPMN:<bpmn:subProcess id="fulfill"> 内含独立 startEvent/task/endEvent 与连线
190
+ pi = engine.start_process_instance_by_key("order-fulfillment") # 停在子流程内部质检任务
191
+ # - 容器递归解析:跨容器连线引用部署即报错;嵌套子流程逐层收束
192
+ # - 边界 timer 挂 subProcess:等待窗口 = 整段内部执行;到期中断整段 scope(内部
193
+ # 任务归档/actinst 结算/作业清理),token 沿边界路径走;正常走完则边界撤销
194
+ # - 变量沿用实例级(文档化差异);asyncBefore 可用于 subProcess(展开前异步窗口)
195
+ ```
196
+
197
+ **非中断式边界事件(M4-2b4,cancelActivity=false)**——到期不打断宿主,spawn 并发线:
198
+ ```python
199
+ # BPMN:<bpmn:userTask id="handle"/> + <bpmn:boundaryEvent id="esc"
200
+ # attachedToRef="handle" cancelActivity="false"> <timer PT2S ...>
201
+ pi = engine.start_process_instance_by_key("ticket-support") # 停处理任务
202
+ # 2 秒无人处理 -> JobExecutor 触发 NI 边界:处理任务保留不取消(cancelActivity=false),
203
+ # 并发线从边界出边推进(如自动催办)后收束;宿主随后完成 -> 实例收束完成。
204
+ # 若主线先到 end 而并发线(如复核任务)未收束:实例不提前结束,root 转 SCOPE 停驻,
205
+ # 等并发线全部收束后实例才完成;subProcess 宿主 + NI 明确报错(文档化差异)
206
+ ```
207
+
208
+ **多实例(M4-2c)**——MultiInstanceLoopCharacteristics,三种宿主 userTask/serviceTask/
209
+ subProcess(并行/顺序):
210
+ ```python
211
+ # BPMN:<bpmn:userTask id="review"> <bpmn:multiInstanceLoopCharacteristics
212
+ # camunda:collection="${reviewers}" camunda:elementVariable="reviewer"/>
213
+ pi = engine.start_process_instance_by_key("mi-flow", {"reviewers": ["a", "b", "c"]})
214
+ # 并行:N 实例任务并存(execution 树 = SCOPE 容器 + N child);顺序:同一时刻仅 1 实例。
215
+ # serviceTask 宿主 = 同步 delegate 无等待窗口(start 返回即收束);
216
+ # subProcess 宿主 = 实例各自进内部流转,内部走完由收束链驱动实例完成、容器收束离开。
217
+ # completionCondition(如 ${nrOfCompletedInstances >= 2}):满足即终止剩余实例(任务归档/
218
+ # actinst 结算/整树清理);空集合零实例直接通过;loopCounter/elementVariable 行为期注入、
219
+ # 容器收尾清理;宿主组合 asyncBefore/边界事件运行时明确报错(文档化差异)。
220
+ ```
221
+
222
+ **消息 / 信号事件(M4-2d)**——correlate_message 1:1 精准关联,throw_signal 跨实例广播:
223
+ ```python
224
+ # BPMN:<bpmn:intermediateCatchEvent><bpmn:messageEventDefinition messageRef="M1"/>
225
+ # 配套 <bpmn:message id="M1" name="orderReady"/> 声明(signal 同型)
226
+ pi = engine.start_process_instance_by_key("order-relay") # 停在消息 catch 停等
227
+ engine.correlate_message("orderReady", variables={"paid": True}) # 1:1 投递(未限定实例取注册序最早)
228
+ # 边界事件:<bpmn:boundaryEvent attachedToRef="ut" cancelActivity="false">
229
+ # <bpmn:signalEventDefinition signalRef="S1"/>
230
+ hits = engine.throw_signal("maintenance") # 跨实例广播全部命中订阅,返回命中数
231
+ # 非中断式边界/事件子流程 start:订阅常驻可重复触发;中断式:触发即消费、宿主被接管
232
+ # 实例内 throw:<bpmn:intermediateThrowEvent>/<bpmn:endEvent> + message/signal 定义,
233
+ # 触发同实例 catch(未命中静默丢弃);订阅为纯内存派生态,崩溃恢复自动重推导
234
+ ```
235
+
236
+ **DMN 决策表(M5)**——独立决策引擎 + BPMN businessRuleTask 集成:
237
+
238
+ ```python
239
+ from camunda.dmn.engine import DmnEngine
240
+ from camunda.parser.dmn_parser import parse_dmn_file
241
+
242
+ dmn = DmnEngine()
243
+ dmn.deploy(parse_dmn_file("examples/loan-grading.dmn")) # -> ["loan-grading", "rate-discount"]
244
+ dmn.evaluate_decision("loan-grading", {"amount": 9000, "credit_score": 750}) # -> "B"
245
+
246
+ # BPMN 集成:<bpmn:businessRuleTask id="grade" camunda:decisionRef="loan-grading"
247
+ # camunda:resultVariable="grade"/>
248
+ engine.deploy_dmn(parse_dmn_file("examples/loan-grading.dmn"))
249
+ engine.deploy(parse_bpmn_file("examples/loan-grading-flow.bpmn"))
250
+ pi = engine.start_process_instance_by_key("loan-process", {"amount": 20000, "credit_score": 500})
251
+ pi.variables["grade"] # "C" -> 决策结果写入 resultVariable,可驱动排他网关选路
252
+ ```
253
+
254
+ - **FEEL 子集**(unaryTests 输入单元格):比较 `= != < <= > >=`、区间 `[a..b]` / `(a..b)` /
255
+ `]a..b[`(DMN 双标记法)、逗号列表 = OR、`not(...)`、`null` 判缺变量、空文本 / `-` = 通配;
256
+ expression(输出单元格 / inputExpression)支持字面量、变量引用(未定义 -> null)、
257
+ 算术 `+ - * /` 与括号、字符串 `+` 拼接。不支持函数调用 / between / in / 日期时间 / 路径表达式
258
+ (运行时明确报错)。
259
+ - **hitPolicy**:`UNIQUE`(多行命中运行时报错)/ `FIRST` / `ANY`(各行输出不一致报错)/
260
+ `PRIORITY`(按 output 的 outputValues 优先级序取最高,仅单输出列)/ `RULE ORDER`(行结果列表)/
261
+ `COLLECT`(列表,可叠 `SUM` / `MIN` / `MAX` / `COUNT` 聚合)。无命中不报错,返回空结果。
262
+ - **已知限制**:DMN 部署不落库(对齐 delegate 注册先例)——崩溃恢复后须重新 `deploy_dmn`,
263
+ 否则 businessRuleTask 求值报未部署。
264
+
265
+ **REST API(M6)**——FastAPI 对齐 Camunda engine-rest,总前缀 `/engine-rest`:
266
+
267
+ ```bash
268
+ pip install -e ".[api]"
269
+ uvicorn camunda.api.app:create_app --factory --port 8080
270
+ # 交互式文档:http://127.0.0.1:8080/docs
271
+ ```
272
+
273
+ ```python
274
+ from camunda.api import create_app
275
+ app = create_app() # 内存引擎(demo/测试)
276
+ app = create_app(engine=engine) # 复用既有引擎(含 Store / JobExecutor)
277
+ ```
278
+
279
+ ```bash
280
+ # 部署(multipart 对齐 Camunda;另有 JSON 便捷通道 POST /deployment/create/xml)
281
+ curl -F "data=@examples/loan-approval.bpmn" \
282
+ http://localhost:8080/engine-rest/deployment/create
283
+
284
+ # 启动实例(变量兼容包装形态与裸值)
285
+ curl -XPOST http://localhost:8080/engine-rest/process-instance \
286
+ -H 'Content-Type: application/json' \
287
+ -d '{"definitionKey":"loan-approval","variables":{"amount":{"value":20000,"type":"Long"}}}'
288
+
289
+ # 任务:列表 -> 认领 -> 完成
290
+ curl http://localhost:8080/engine-rest/task
291
+ curl -XPOST http://localhost:8080/engine-rest/task/{id}/claim \
292
+ -H 'Content-Type: application/json' -d '{"userId":"lisi"}'
293
+ curl -XPOST http://localhost:8080/engine-rest/task/{id}/complete \
294
+ -H 'Content-Type: application/json' -d '{"variables":{"approved":true}}'
295
+ ```
296
+
297
+ | 端点 | 方法 | 说明 |
298
+ |---|---|---|
299
+ | `/deployment/create` | POST | multipart 部署(字段名 `data`,BPMN/DMN 自动分派) |
300
+ | `/deployment/create/xml` | POST | JSON 便捷部署(本项目扩展) |
301
+ | `/deployment` | GET | 部署列表 |
302
+ | `/process-definition[ /key/{key}[/xml] ]` | GET | 流程定义列表 / 单个 / XML |
303
+ | `/process-instance` | POST/GET | 启动 / 列表(按 key、businessKey、active 过滤) |
304
+ | `/process-instance/{id}` | GET/DELETE | 查询 / 删除(历史保留,HI 置 DELETED) |
305
+ | `/process-instance/{id}/variables[ /{name} ]` | GET/PUT | 变量读写 |
306
+ | `/task` | GET | 任务列表(按 assignee / candidateUser / unassigned 过滤) |
307
+ | `/task/{id}` | GET | 任务详情 |
308
+ | `/task/{id}/claim` `unclaim` `assignee` `complete` | POST | 认领 / 取消 / 指派 / 完成 |
309
+ | `/history/process-instance` `task` `activity-instance` `variable-instance` | GET | 四类历史查询 |
310
+ | `/decision-definition[ /key/{key} ]` | GET | 决策定义列表 / 单个 |
311
+ | `/decision-definition/key/{key}/evaluate` | POST | 决策表求值 |
312
+
313
+ - **变量形态**:入参兼容包装形态 `{"amount": {"value": 20000}}` 与裸值 `{"amount": 20000}`;
314
+ 出参默认包装形态(对齐 Camunda),带 `?bare=true` 退化为裸值 map。
315
+
316
+ **REST 列表分页(M8)**——9 个列表端点统一支持 `firstResult` + `maxResults`(对齐 Camunda 7 REST):
317
+
318
+ ```bash
319
+ # 翻页:先 0..2,再 2..4,最后 4..200
320
+ curl 'http://localhost:8080/engine-rest/process-instance?firstResult=0&maxResults=2'
321
+ curl 'http://localhost:8080/engine-rest/process-instance?firstResult=2&maxResults=2'
322
+ curl 'http://localhost:8080/engine-rest/process-instance?firstResult=4&maxResults=200'
323
+ # 末页判定:返回数 < maxResults 即末页(响应仍为裸数组,不包 count/total)
324
+ # 非法 firstResult(<0)/ maxResults(<1)由 FastAPI 422 拒绝
325
+ # maxResults > 1000 自动 clamp 到 1000(防脚本误用拉空内存)
326
+ ```
327
+
328
+ 支持的端点:`/process-instance`、`/task`、`/process-definition`、`/decision-definition`、
329
+ `/deployment`、`/history/process-instance`、`/history/task`、`/history/activity-instance`、
330
+ `/history/variable-instance`(共 9 个,统一默认 `maxResults=200`)。
331
+ - **错误响应体**:统一 `{"type": "<异常类名>", "message": "<异常消息>"}`;
332
+ 状态码 —— 未找到 404、部署/参数非法 400、实例状态冲突 409、其余引擎异常 500。
333
+
334
+ **多 JobExecutor 抢锁(M7)**—— DB CAS lease 模式,多节点 / 多进程安全并发:
335
+
336
+ ```python
337
+ from camunda.engine.process_engine import ProcessEngine
338
+ from camunda.job.executor import JobExecutor
339
+ from camunda.persistence.store import Store
340
+
341
+ engine = ProcessEngine(store=Store("sqlite:///shared.db"))
342
+ engine.deploy(model)
343
+
344
+ exec_a = JobExecutor(engine, name="exec-a", lease_seconds=300)
345
+ exec_b = JobExecutor(engine, name="exec-b", lease_seconds=300)
346
+ exec_a.start() # 后台轮询
347
+ exec_b.start() # 同一个 engine + store,但 lock_owner 不同
348
+ # engine.execute_due_jobs(lock_owner=...) 由 JobExecutor.tick() 自动注入
349
+ ```
350
+
351
+ - **抢锁语义**:JobExecutor 启动时按 `name-pid-hostname-uuid8` 生成唯一
352
+ `lock_owner`,调 `engine.execute_due_jobs(lock_owner=me, lease_seconds=300)`
353
+ —— 引擎走 `_execute_due_jobs_db`,从 `store.acquire_due_jobs` 拿一批
354
+ due job:每条 `UPDATE ACT_RU_JOB SET LOCK_OWNER=me, LOCK_EXP_TIME=now+lease
355
+ WHERE id=:id AND (LOCK_OWNER_ IS NULL OR LOCK_EXP_TIME_ < :now)`,affected_rows > 0
356
+ 即抢到。
357
+ - **崩溃恢复**:JobExecutor.shutdown 不主动释放 lease(让其自然过期),
358
+ 便于模拟崩溃场景;其他 JobExecutor 等 lease 过期后 `LOCK_EXP_TIME_ < :now`
359
+ 命中,可重新抢到。
360
+ - **续约**:长作业执行期间调 `store.extend_lock(job_id, owner, lease_seconds, now)`
361
+ 把 lease 延后;CAS 失败 = 锁已被别人接管,当前执行应中止提交。
362
+ - **监控**:`store.list_locks(lock_owner=None)` 查看当前持锁情况(调试 / 运维)。
363
+ - **兼容性**:engine 无 Store 时 JobExecutor 自动走单进程内存路径(向后兼容旧用法)。
364
+
365
+ ## 里程碑
366
+
367
+ | 版本 | 内容 | 状态 |
368
+ |---|---|---|
369
+ | M0 | 骨架 + 模型 + 解析器 | ✅ |
370
+ | M1 | 内存版流转(网关/任务/服务任务) | ✅ |
371
+ | M2 | SQLAlchemy 持久化 + 崩溃恢复(ACT_RE/RU/HI) | ✅ |
372
+ | M3 | 作业执行器(Timer / async continuation) | ✅ |
373
+ | M4 | BPMN 全元素补齐 | 🔄 M4-1 边界事件 + asyncAfter、M4-2a 内嵌子流程、M4-2b 事件子流程 + 非中断式边界、M4-2c 多实例三宿主、M4-2d 消息/信号事件已交付 |
374
+ | M5 | DMN + FEEL 子集 | ✅ 决策表解析 + FEEL 子集求值 + 六种 hitPolicy 收敛 + businessRuleTask 集成已交付 |
375
+ | M6 | REST API(FastAPI) | ✅ 六类端点(部署/定义/实例/任务/历史/决策)+ 变量双形态 + 异常映射已交付 |
376
+ | M7 | 多 JobExecutor 抢锁 | ✅ DB CAS lease:跨 JobExecutor / 跨进程唯一执行同一作业 + lease 过期可被接管 + 续约已交付 |
377
+ | M8 | REST 列表分页 | ✅ 9 个列表端点统一 firstResult + maxResults(对齐 Camunda 7 REST),maxResults 默认 200 / 硬上限 1000 |