camunda-python 0.1.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.
Files changed (46) hide show
  1. camunda/__init__.py +10 -0
  2. camunda/api/__init__.py +14 -0
  3. camunda/api/app.py +80 -0
  4. camunda/api/deps.py +17 -0
  5. camunda/api/errors.py +84 -0
  6. camunda/api/pagination.py +74 -0
  7. camunda/api/routers/__init__.py +5 -0
  8. camunda/api/routers/decision.py +57 -0
  9. camunda/api/routers/deployment.py +139 -0
  10. camunda/api/routers/history.py +128 -0
  11. camunda/api/routers/process_definition.py +49 -0
  12. camunda/api/routers/process_instance.py +106 -0
  13. camunda/api/routers/task.py +92 -0
  14. camunda/api/schemas.py +200 -0
  15. camunda/common/__init__.py +19 -0
  16. camunda/common/clock.py +30 -0
  17. camunda/common/exceptions.py +34 -0
  18. camunda/common/idgen.py +23 -0
  19. camunda/common/timers.py +106 -0
  20. camunda/dmn/__init__.py +5 -0
  21. camunda/dmn/engine.py +219 -0
  22. camunda/dmn/feel.py +392 -0
  23. camunda/engine/__init__.py +9 -0
  24. camunda/engine/behavior.py +51 -0
  25. camunda/engine/expression.py +126 -0
  26. camunda/engine/process_engine.py +3237 -0
  27. camunda/job/__init__.py +9 -0
  28. camunda/job/executor.py +136 -0
  29. camunda/model/__init__.py +48 -0
  30. camunda/model/bpmn.py +326 -0
  31. camunda/model/dmn.py +101 -0
  32. camunda/model/execution.py +121 -0
  33. camunda/model/job.py +88 -0
  34. camunda/model/task.py +33 -0
  35. camunda/model/variable.py +35 -0
  36. camunda/parser/__init__.py +5 -0
  37. camunda/parser/bpmn_parser.py +646 -0
  38. camunda/parser/dmn_parser.py +225 -0
  39. camunda/persistence/__init__.py +21 -0
  40. camunda/persistence/entities.py +202 -0
  41. camunda/persistence/store.py +721 -0
  42. camunda_python-0.1.0.dist-info/METADATA +377 -0
  43. camunda_python-0.1.0.dist-info/RECORD +46 -0
  44. camunda_python-0.1.0.dist-info/WHEEL +5 -0
  45. camunda_python-0.1.0.dist-info/licenses/LICENSE +200 -0
  46. camunda_python-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,225 @@
1
+ """DMN 1.1/1.3 XML 解析器(lxml,M5-1)。
2
+
3
+ 职责(对齐 Camunda dmn-model 的解析部分、与 bpmn_parser 同风格):
4
+ 1. 解析 XML -> DmnModel(含多个 Decision)
5
+ 2. decisionTable 结构:inputs(inputExpression 文本)/ outputs / rules
6
+ (inputEntry/outputEntry 的 dmn:text 原文)
7
+ 3. 校验:hitPolicy 合法、COLLECT aggregator 合法、entry 数量与列对齐、
8
+ 非 decisionTable 形态(literalExpression 等)明确报错
9
+
10
+ 命名空间处理策略:只用 localName 分派(DMN 1.1/1.3 同构),不校验版本。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import List, Optional
16
+
17
+ from lxml import etree
18
+
19
+ from camunda.common.exceptions import DeploymentException
20
+ from camunda.model.dmn import (
21
+ AGGREGATORS,
22
+ HIT_POLICIES,
23
+ Decision,
24
+ DecisionTable,
25
+ DmnInput,
26
+ DmnModel,
27
+ DmnOutput,
28
+ DmnRule,
29
+ )
30
+
31
+
32
+ def _local(tag: str) -> str:
33
+ """lxml tag 形如 {ns}localName -> localName。"""
34
+ return tag.rsplit("}", 1)[-1]
35
+
36
+
37
+ def _children(el: etree._Element, local_name: str) -> List[etree._Element]:
38
+ return [c for c in el if isinstance(c.tag, str) and _local(c.tag) == local_name]
39
+
40
+
41
+ def _text_of(el: Optional[etree._Element]) -> str:
42
+ """取元素文本(None 安全)。"""
43
+ return (el.text or "") if el is not None else ""
44
+
45
+
46
+ def _normalize_entry(text: str) -> Optional[str]:
47
+ """单元格文本归一:空 / '-' -> None(通配或空输出);其余 strip。"""
48
+ t = text.strip()
49
+ if t in ("", "-"):
50
+ return None
51
+ return t
52
+
53
+
54
+ def parse_dmn_xml(xml_text: str, source_name: Optional[str] = None) -> DmnModel:
55
+ """解析 DMN XML 文本 -> DmnModel。失败抛 DeploymentException。"""
56
+ try:
57
+ root = etree.fromstring(xml_text.encode("utf-8"))
58
+ except etree.XMLSyntaxError as e:
59
+ raise DeploymentException(f"DMN XML 语法错误: {e}") from e
60
+
61
+ if _local(root.tag) != "definitions":
62
+ raise DeploymentException(
63
+ f"DMN 根元素须为 definitions,实际 {_local(root.tag)!r}"
64
+ )
65
+
66
+ model = DmnModel(source_name=source_name, source_xml=xml_text)
67
+ for el in root:
68
+ if not isinstance(el.tag, str):
69
+ continue
70
+ if _local(el.tag) == "decision":
71
+ model.decisions.append(_parse_decision(el))
72
+ if not model.decisions:
73
+ raise DeploymentException("DMN definitions 未包含任何 decision")
74
+ return model
75
+
76
+
77
+ def parse_dmn_file(path: str) -> DmnModel:
78
+ """解析 .dmn 文件(demo/测试便捷入口)。"""
79
+ with open(path, "r", encoding="utf-8") as f:
80
+ return parse_dmn_xml(f.read(), source_name=path.rsplit("/", 1)[-1])
81
+
82
+
83
+ def _parse_decision(el: etree._Element) -> Decision:
84
+ dec_id = el.get("id")
85
+ if not dec_id:
86
+ raise DeploymentException("decision 缺少 id 属性")
87
+ dec = Decision(id=dec_id, name=el.get("name"))
88
+
89
+ for child in el:
90
+ if not isinstance(child.tag, str):
91
+ continue
92
+ ln = _local(child.tag)
93
+ if ln == "decisionTable":
94
+ dec.decision_table = _parse_decision_table(child)
95
+ elif ln == "variable":
96
+ continue # decision 输出类型声明,M5 不消费
97
+ else:
98
+ # literalExpression / relation / invocation / context -> 明确报错
99
+ raise DeploymentException(
100
+ f"decision {dec_id!r} 仅支持 decisionTable 形态,"
101
+ f"遇到不支持的子元素 {ln!r}(M5 文档化差异)"
102
+ )
103
+ if dec.decision_table is None:
104
+ raise DeploymentException(f"decision {dec_id!r} 缺少 decisionTable")
105
+ return dec
106
+
107
+
108
+ def _parse_decision_table(el: etree._Element) -> DecisionTable:
109
+ hit_policy = (el.get("hitPolicy") or "UNIQUE").strip().upper()
110
+ if hit_policy not in HIT_POLICIES:
111
+ raise DeploymentException(
112
+ f"未知 hitPolicy: {el.get('hitPolicy')!r}(支持 {sorted(HIT_POLICIES)})"
113
+ )
114
+ aggregator = el.get("aggregation")
115
+ aggregator = aggregator.strip().upper() if aggregator else None
116
+ if aggregator is not None and aggregator not in AGGREGATORS:
117
+ raise DeploymentException(
118
+ f"未知 aggregation: {el.get('aggregation')!r}(支持 {sorted(AGGREGATORS)})"
119
+ )
120
+ if aggregator is not None and hit_policy != "COLLECT":
121
+ raise DeploymentException(
122
+ f"aggregation 仅在 hitPolicy=COLLECT 下合法(当前 {hit_policy!r})"
123
+ )
124
+
125
+ table = DecisionTable(
126
+ id=el.get("id"), hit_policy=hit_policy, aggregator=aggregator
127
+ )
128
+ for child in el:
129
+ if not isinstance(child.tag, str):
130
+ continue
131
+ ln = _local(child.tag)
132
+ if ln == "input":
133
+ table.inputs.append(_parse_input(child))
134
+ elif ln == "output":
135
+ table.outputs.append(_parse_output(child))
136
+ elif ln == "rule":
137
+ table.rules.append(_parse_rule(child))
138
+ # annotation / informationRequirement 等忽略
139
+
140
+ if not table.outputs:
141
+ raise DeploymentException(
142
+ f"decisionTable {table.id!r} 至少需要一个 output 列"
143
+ )
144
+ _validate_entry_alignment(table)
145
+ return table
146
+
147
+
148
+ def _parse_input(el: etree._Element) -> DmnInput:
149
+ expr_el = None
150
+ for c in _children(el, "inputExpression"):
151
+ expr_el = c
152
+ break
153
+ if expr_el is None:
154
+ raise DeploymentException(f"input {el.get('id')!r} 缺少 inputExpression")
155
+ return DmnInput(
156
+ id=el.get("id"),
157
+ name=el.get("label"),
158
+ expression=_text_of(expr_el).strip(),
159
+ type_ref=expr_el.get("typeRef"),
160
+ )
161
+
162
+
163
+ def _parse_output(el: etree._Element) -> DmnOutput:
164
+ return DmnOutput(
165
+ id=el.get("id"),
166
+ name=el.get("name"),
167
+ label=el.get("label"),
168
+ type_ref=el.get("typeRef"),
169
+ output_values=_parse_output_values(el),
170
+ )
171
+
172
+
173
+ def _parse_output_values(el: etree._Element) -> List[str]:
174
+ """outputValues 子元素:逗号分隔的 FEEL 字面量列表(尊重引号内的逗号)。"""
175
+ for child in el:
176
+ if isinstance(child.tag, str) and _local(child.tag) == "outputValues":
177
+ texts = _children(child, "text")
178
+ raw = _text_of(texts[0]) if texts else ""
179
+ parts: List[str] = []
180
+ buf: List[str] = []
181
+ in_quote = False
182
+ for ch in raw:
183
+ if ch == '"':
184
+ in_quote = not in_quote
185
+ buf.append(ch)
186
+ elif ch == "," and not in_quote:
187
+ parts.append("".join(buf).strip())
188
+ buf = []
189
+ else:
190
+ buf.append(ch)
191
+ parts.append("".join(buf).strip())
192
+ return [p for p in parts if p]
193
+ return []
194
+
195
+
196
+ def _parse_rule(el: etree._Element) -> DmnRule:
197
+ rule = DmnRule(id=el.get("id"))
198
+ for child in el:
199
+ if not isinstance(child.tag, str):
200
+ continue
201
+ ln = _local(child.tag)
202
+ if ln == "inputEntry":
203
+ rule.input_entries.append(_normalize_entry(_text_of(_children(child, "text")[0]) if _children(child, "text") else ""))
204
+ elif ln == "outputEntry":
205
+ texts = _children(child, "text")
206
+ rule.output_entries.append(
207
+ _normalize_entry(_text_of(texts[0]) if texts else "")
208
+ )
209
+ # description / annotationEntry 忽略
210
+ return rule
211
+
212
+
213
+ def _validate_entry_alignment(table: DecisionTable) -> None:
214
+ """规则行 entry 数量与列对齐校验(DMN 规范要求严格对应)。"""
215
+ for rule in table.rules:
216
+ if len(rule.input_entries) != len(table.inputs):
217
+ raise DeploymentException(
218
+ f"rule {rule.id!r} 的 inputEntry 数 {len(rule.input_entries)}"
219
+ f" 与 input 列数 {len(table.inputs)} 不一致"
220
+ )
221
+ if len(rule.output_entries) != len(table.outputs):
222
+ raise DeploymentException(
223
+ f"rule {rule.id!r} 的 outputEntry 数 {len(rule.output_entries)}"
224
+ f" 与 output 列数 {len(table.outputs)} 不一致"
225
+ )
@@ -0,0 +1,21 @@
1
+ """persistence 包:SQLAlchemy 2.0 持久层(M2 里程碑交付)。
2
+
3
+ - entities.py ACT_RE_*/ACT_RU_*/ACT_HI_* ORM 实体(对齐 Camunda 表契约)
4
+ - store.py 快照同步 + 存取门面(SQLite/PostgreSQL url 均可)
5
+ """
6
+
7
+ from camunda.persistence.store import (
8
+ ActivitySnap,
9
+ ExecutionSnap,
10
+ ProcInstSnap,
11
+ Store,
12
+ TaskSnap,
13
+ )
14
+
15
+ __all__ = [
16
+ "Store",
17
+ "ProcInstSnap",
18
+ "ExecutionSnap",
19
+ "TaskSnap",
20
+ "ActivitySnap",
21
+ ]
@@ -0,0 +1,202 @@
1
+ """SQLAlchemy 2.0 ORM 实体:对齐 Camunda ACT_* 表契约。
2
+
3
+ 表契约(对齐 Camunda 7 的 MyBatis 表):
4
+ - ACT_RE_DEPLOYMENT / ACT_RE_PROCDEF 静态定义(M2 简化:资源 xml 直接存 prodef 行)
5
+ - ACT_RU_EXECUTION / ACT_RU_TASK /
6
+ ACT_RU_VARIABLE 运行时瞬时态(RU = running)
7
+ - ACT_HI_PROCINST / ACT_HI_ACTINST /
8
+ ACT_HI_TASKINST / ACT_HI_VARINST 历史归档
9
+
10
+ M2 差异说明(文档化):
11
+ - Camunda 用 bytearray 表存资源 -> M2 直接存 prodef.resource_xml 文本
12
+ - Camunda HI_VARINST 每次变更追加版本 -> M2 按实例快照(每实例每变量一行)
13
+ - Camunda 变量挂 execution -> M2 引擎变量为实例级,VARIABLE 表挂 proc_inst
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from sqlalchemy import Integer, String, Text
19
+ from sqlalchemy.dialects.mysql import MEDIUMTEXT
20
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
21
+
22
+ # MySQL 方言下 TEXT 上限 64KB(65535 字节),BPMN 资源 XML 与变量 JSON 可能
23
+ # 超限(MySQL 报 Data too long);此处统一把大文本列在 MySQL 声明为
24
+ # MEDIUMTEXT(16MB)。SQLite/PostgreSQL 的 Text 无长度限制,with_variant
25
+ # 不影响这两者。无迁移工具:MySQL 上需 DROP 重建或手动 ALTER 已有列。
26
+ BIG_TEXT = Text().with_variant(MEDIUMTEXT(), "mysql")
27
+
28
+
29
+ class Base(DeclarativeBase):
30
+ pass
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # 静态定义(RE)
35
+ # ---------------------------------------------------------------------------
36
+ class DeploymentEntity(Base):
37
+ """一次部署(对齐 ACT_RE_DEPLOYMENT)。"""
38
+
39
+ __tablename__ = "ACT_RE_DEPLOYMENT"
40
+
41
+ id_: Mapped[str] = mapped_column("ID_", String(64), primary_key=True)
42
+ name_: Mapped[str | None] = mapped_column("NAME_", String(255), nullable=True)
43
+ deploy_time_: Mapped[str] = mapped_column("DEPLOY_TIME_", String(32))
44
+
45
+
46
+ class ProcDefEntity(Base):
47
+ """流程定义(对齐 ACT_RE_PROCDEF)。
48
+
49
+ Camunda 主键是 ID_,同 KEY_ 可多版本(VERSION_ 递增)。M2 主键 = f"{key}:{version}"。
50
+ """
51
+
52
+ __tablename__ = "ACT_RE_PROCDEF"
53
+
54
+ id_: Mapped[str] = mapped_column("ID_", String(128), primary_key=True)
55
+ key_: Mapped[str] = mapped_column("KEY_", String(128), index=True)
56
+ name_: Mapped[str | None] = mapped_column("NAME_", String(255), nullable=True)
57
+ version_: Mapped[int] = mapped_column("VERSION_", default=1)
58
+ deployment_id_: Mapped[str] = mapped_column("DEPLOYMENT_ID_", String(64))
59
+ resource_xml_: Mapped[str] = mapped_column("RESOURCE_XML_", BIG_TEXT)
60
+ is_executable_: Mapped[bool] = mapped_column("IS_EXECUTABLE_", default=True)
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # 运行时(RU)
65
+ # ---------------------------------------------------------------------------
66
+ class ExecutionEntity(Base):
67
+ """执行树节点(对齐 ACT_RU_EXECUTION)。
68
+
69
+ 只保存 ACTIVE 的 execution(ENDED 的随历史归档,RU 表语义)。
70
+ """
71
+
72
+ __tablename__ = "ACT_RU_EXECUTION"
73
+
74
+ id_: Mapped[str] = mapped_column("ID_", String(64), primary_key=True)
75
+ process_instance_id_: Mapped[str] = mapped_column("PROC_INST_ID_", String(64), index=True)
76
+ parent_id_: Mapped[str | None] = mapped_column("PARENT_ID_", String(64), nullable=True)
77
+ activity_id_: Mapped[str | None] = mapped_column("ACT_ID_", String(255), nullable=True)
78
+ role_: Mapped[str] = mapped_column("ROLE_", String(16), default="TOKEN")
79
+ # M4-2c4:多实例状态 JSON(容器 total/active/completed/next_index/... 或实例
80
+ # {"index": i})。Camunda 以 loopCounter 等 ACT_RU_VARIABLE + IS_SCOPE_ 关联
81
+ # 表达;M2 简化:实例级变量 + 此列直存容器状态(崩溃恢复必需)。
82
+ mi_: Mapped[str | None] = mapped_column("MI_", BIG_TEXT, nullable=True)
83
+ # Camunda 还有 IS_CONCURRENT_ / IS_SCOPE_ 等;role 已覆盖 M1 语义
84
+
85
+
86
+ class TaskEntity(Base):
87
+ """待办任务(对齐 ACT_RU_TASK)。"""
88
+
89
+ __tablename__ = "ACT_RU_TASK"
90
+
91
+ id_: Mapped[str] = mapped_column("ID_", String(64), primary_key=True)
92
+ name_: Mapped[str | None] = mapped_column("NAME_", String(255), nullable=True)
93
+ process_instance_id_: Mapped[str] = mapped_column("PROC_INST_ID_", String(64), index=True)
94
+ execution_id_: Mapped[str] = mapped_column("EXECUTION_ID_", String(64))
95
+ task_definition_key_: Mapped[str] = mapped_column("TASK_DEF_KEY_", String(255))
96
+ assignee_: Mapped[str | None] = mapped_column("ASSIGNEE_", String(255), nullable=True)
97
+ create_time_: Mapped[str] = mapped_column("CREATE_TIME_", String(32))
98
+
99
+
100
+ class VariableEntity(Base):
101
+ """流程变量(对齐 ACT_RU_VARIABLE;M2 实例级,复合主键 proc_inst+name)。"""
102
+
103
+ __tablename__ = "ACT_RU_VARIABLE"
104
+
105
+ id_: Mapped[str] = mapped_column("ID_", String(96), primary_key=True)
106
+ process_instance_id_: Mapped[str] = mapped_column("PROC_INST_ID_", String(64), index=True)
107
+ name_: Mapped[str] = mapped_column("NAME_", String(255))
108
+ type_: Mapped[str] = mapped_column("TYPE_", String(32)) # Java 类型名
109
+ text_: Mapped[str | None] = mapped_column("TEXT_", BIG_TEXT, nullable=True) # JSON 序列化
110
+
111
+
112
+ class JobEntity(Base):
113
+ """可调度作业(对齐 ACT_RU_JOB,M3 核心子集)。
114
+
115
+ - 实例级 job(timer-catch / async-continuation):PROC_INST_ID_ 有值,
116
+ 随实例 RU 快照全量重写
117
+ - 定义级 job(timer-start):PROC_INST_ID_ 为 NULL,PROC_DEF_KEY_ + ACT_ID_
118
+ 指向流程与 startEvent;部署新版本时整组重建
119
+ - LOCK_OWNER_ / LOCK_EXP_TIME_ 预留多实例抢占(M3 单进程不使用)
120
+ """
121
+
122
+ __tablename__ = "ACT_RU_JOB"
123
+
124
+ id_: Mapped[str] = mapped_column("ID_", String(64), primary_key=True)
125
+ job_type_: Mapped[str] = mapped_column("TYPE_", String(32))
126
+ process_instance_id_: Mapped[str | None] = mapped_column(
127
+ "PROC_INST_ID_", String(64), nullable=True, index=True
128
+ )
129
+ execution_id_: Mapped[str | None] = mapped_column(
130
+ "EXECUTION_ID_", String(64), nullable=True
131
+ )
132
+ process_definition_key_: Mapped[str | None] = mapped_column(
133
+ "PROC_DEF_KEY_", String(128), nullable=True, index=True
134
+ )
135
+ node_id_: Mapped[str | None] = mapped_column("ACT_ID_", String(255), nullable=True)
136
+ duedate_: Mapped[str] = mapped_column("DUEDATE_", String(32), index=True)
137
+ created_: Mapped[str] = mapped_column("CREATED_", String(32))
138
+ retries_: Mapped[int] = mapped_column("RETRIES_", Integer, default=3)
139
+ repeat_: Mapped[str | None] = mapped_column("REPEAT_", BIG_TEXT, nullable=True) # JSON
140
+ lock_owner_: Mapped[str | None] = mapped_column(
141
+ "LOCK_OWNER_", String(255), nullable=True
142
+ )
143
+ lock_expire_time_: Mapped[str | None] = mapped_column(
144
+ "LOCK_EXP_TIME_", String(32), nullable=True
145
+ )
146
+
147
+
148
+ # ---------------------------------------------------------------------------
149
+ # 历史(HI)
150
+ # ---------------------------------------------------------------------------
151
+ class HistProcInstEntity(Base):
152
+ """流程实例历史(对齐 ACT_HI_PROCINST)。"""
153
+
154
+ __tablename__ = "ACT_HI_PROCINST"
155
+
156
+ id_: Mapped[str] = mapped_column("ID_", String(64), primary_key=True)
157
+ process_definition_key_: Mapped[str] = mapped_column("PROC_DEF_KEY_", String(128), index=True)
158
+ business_key_: Mapped[str | None] = mapped_column("BUSINESS_KEY_", String(255), nullable=True)
159
+ state_: Mapped[str] = mapped_column("STATE_", String(16)) # ACTIVE/COMPLETED
160
+ start_time_: Mapped[str] = mapped_column("START_TIME_", String(32))
161
+ end_time_: Mapped[str | None] = mapped_column("END_TIME_", String(32), nullable=True)
162
+
163
+
164
+ class HistActInstEntity(Base):
165
+ """活动实例历史(对齐 ACT_HI_ACTINST)。"""
166
+
167
+ __tablename__ = "ACT_HI_ACTINST"
168
+
169
+ id_: Mapped[str] = mapped_column("ID_", String(64), primary_key=True)
170
+ process_instance_id_: Mapped[str] = mapped_column("PROC_INST_ID_", String(64), index=True)
171
+ activity_id_: Mapped[str] = mapped_column("ACT_ID_", String(255))
172
+ activity_name_: Mapped[str | None] = mapped_column("ACT_NAME_", String(255), nullable=True)
173
+ execution_id_: Mapped[str] = mapped_column("EXECUTION_ID_", String(64))
174
+ start_time_: Mapped[str] = mapped_column("START_TIME_", String(32))
175
+ end_time_: Mapped[str | None] = mapped_column("END_TIME_", String(32), nullable=True)
176
+
177
+
178
+ class HistTaskInstEntity(Base):
179
+ """任务实例历史(对齐 ACT_HI_TASKINST;M2 在 complete 时写入快照)。"""
180
+
181
+ __tablename__ = "ACT_HI_TASKINST"
182
+
183
+ id_: Mapped[str] = mapped_column("ID_", String(64), primary_key=True)
184
+ process_instance_id_: Mapped[str] = mapped_column("PROC_INST_ID_", String(64), index=True)
185
+ task_definition_key_: Mapped[str] = mapped_column("TASK_DEF_KEY_", String(255))
186
+ name_: Mapped[str | None] = mapped_column("NAME_", String(255), nullable=True)
187
+ execution_id_: Mapped[str] = mapped_column("EXECUTION_ID_", String(64))
188
+ assignee_: Mapped[str | None] = mapped_column("ASSIGNEE_", String(255), nullable=True)
189
+ create_time_: Mapped[str] = mapped_column("CREATE_TIME_", String(32))
190
+ end_time_: Mapped[str | None] = mapped_column("END_TIME_", String(32), nullable=True)
191
+
192
+
193
+ class HistVarInstEntity(Base):
194
+ """变量历史(对齐 ACT_HI_VARINST;M2 实例快照语义)。"""
195
+
196
+ __tablename__ = "ACT_HI_VARINST"
197
+
198
+ id_: Mapped[str] = mapped_column("ID_", String(96), primary_key=True)
199
+ process_instance_id_: Mapped[str] = mapped_column("PROC_INST_ID_", String(64), index=True)
200
+ name_: Mapped[str] = mapped_column("NAME_", String(255))
201
+ type_: Mapped[str] = mapped_column("TYPE_", String(32))
202
+ text_: Mapped[str | None] = mapped_column("TEXT_", BIG_TEXT, nullable=True)