fastbpmn 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.
- fastbpmn-0.1.0/LICENSE +21 -0
- fastbpmn-0.1.0/PKG-INFO +491 -0
- fastbpmn-0.1.0/README.md +456 -0
- fastbpmn-0.1.0/pyproject.toml +128 -0
- fastbpmn-0.1.0/src/fastbpmn/__init__.py +15 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/__init__.py +0 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/logonly.py +262 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/middleware/__init__.py +5 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/middleware/variables.py +90 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/__init__.py +0 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/__init__.py +45 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/decoder.py +214 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/encoder.py +129 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/middleware.py +50 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/producer.py +211 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/queue.py +34 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/server.py +309 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/settings.py +52 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/servers/camunda7/worker.py +448 -0
- fastbpmn-0.1.0/src/fastbpmn/aetpi/utils.py +214 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/__init__.py +37 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/asynchttp.py +340 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/config.py +11 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/datatypes.py +44 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/errors.py +110 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/json.py +37 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/models.py +55 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/processengine.py +735 -0
- fastbpmn-0.1.0/src/fastbpmn/camunda/utils.py +43 -0
- fastbpmn-0.1.0/src/fastbpmn/context/__init__.py +4 -0
- fastbpmn-0.1.0/src/fastbpmn/context/context.py +99 -0
- fastbpmn-0.1.0/src/fastbpmn/context/io.py +33 -0
- fastbpmn-0.1.0/src/fastbpmn/context/utils.py +32 -0
- fastbpmn-0.1.0/src/fastbpmn/errors/__init__.py +27 -0
- fastbpmn-0.1.0/src/fastbpmn/errors/base.py +42 -0
- fastbpmn-0.1.0/src/fastbpmn/errors/bpmn_error.py +35 -0
- fastbpmn-0.1.0/src/fastbpmn/errors/context.py +36 -0
- fastbpmn-0.1.0/src/fastbpmn/errors/process_engine.py +64 -0
- fastbpmn-0.1.0/src/fastbpmn/errors/task_failure_error.py +47 -0
- fastbpmn-0.1.0/src/fastbpmn/errors/types.py +5 -0
- fastbpmn-0.1.0/src/fastbpmn/fastbpmn.py +347 -0
- fastbpmn-0.1.0/src/fastbpmn/fastbpmn_types.py +35 -0
- fastbpmn-0.1.0/src/fastbpmn/lifespan/__init__.py +209 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/__init__.py +33 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/context/__init__.py +31 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/exceptions/__init__.py +178 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/exceptions/handler.py +167 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/exceptions/types.py +10 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/exceptions/utils.py +73 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/guard/__init__.py +33 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/guard/types.py +5 -0
- fastbpmn-0.1.0/src/fastbpmn/middleware/guard/utils.py +69 -0
- fastbpmn-0.1.0/src/fastbpmn/models/__init__.py +43 -0
- fastbpmn-0.1.0/src/fastbpmn/models/base.py +80 -0
- fastbpmn-0.1.0/src/fastbpmn/models/decoder.py +26 -0
- fastbpmn-0.1.0/src/fastbpmn/models/encoder.py +208 -0
- fastbpmn-0.1.0/src/fastbpmn/models/task.py +62 -0
- fastbpmn-0.1.0/src/fastbpmn/models/types/__init__.py +11 -0
- fastbpmn-0.1.0/src/fastbpmn/models/types/camunda7.py +237 -0
- fastbpmn-0.1.0/src/fastbpmn/process.py +124 -0
- fastbpmn-0.1.0/src/fastbpmn/processor/__init__.py +306 -0
- fastbpmn-0.1.0/src/fastbpmn/processor/routing.py +171 -0
- fastbpmn-0.1.0/src/fastbpmn/processor/task.py +78 -0
- fastbpmn-0.1.0/src/fastbpmn/readiness.py +13 -0
- fastbpmn-0.1.0/src/fastbpmn/result.py +162 -0
- fastbpmn-0.1.0/src/fastbpmn/task.py +114 -0
- fastbpmn-0.1.0/src/fastbpmn/task_group.py +93 -0
- fastbpmn-0.1.0/src/fastbpmn/types.py +11 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/__init__.py +0 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/asyncer.py +101 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/asyncio.py +30 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/concurrency.py +38 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/config.py +57 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/func.py +22 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/inspect.py +64 -0
- fastbpmn-0.1.0/src/fastbpmn/utils/names.py +21 -0
- fastbpmn-0.1.0/src/squirrel/__init__.py +9 -0
- fastbpmn-0.1.0/src/squirrel/__main__.py +3 -0
- fastbpmn-0.1.0/src/squirrel/app.py +7 -0
- fastbpmn-0.1.0/src/squirrel/arguments.py +25 -0
- fastbpmn-0.1.0/src/squirrel/flavours/__init__.py +5 -0
- fastbpmn-0.1.0/src/squirrel/flavours/camunda7/__init__.py +0 -0
- fastbpmn-0.1.0/src/squirrel/flavours/camunda7/entrypoint.py +92 -0
- fastbpmn-0.1.0/src/squirrel/flavours/camunda7/settings.py +15 -0
- fastbpmn-0.1.0/src/squirrel/parser.py +54 -0
- fastbpmn-0.1.0/src/squirrel/run.py +84 -0
- fastbpmn-0.1.0/src/squirrel/settings.py +23 -0
fastbpmn-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yio gmbh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fastbpmn-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastbpmn
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A framework allowing to write external tasks for various bpmn process engines.
|
|
5
|
+
Keywords: minions,yio,process engine,aetpi - async external task processing interfance
|
|
6
|
+
Author: Daniel Hofstetter, Fritz Diener
|
|
7
|
+
Author-email: Daniel Hofstetter <hello@yio.at>, Fritz Diener <hello@yio.at>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Dist: aetpiref>=0.1.6,<1.0.0
|
|
12
|
+
Requires-Dist: aiolimiter>=1.2.1
|
|
13
|
+
Requires-Dist: asyncer~=0.0.2
|
|
14
|
+
Requires-Dist: culsans>=0.11.0
|
|
15
|
+
Requires-Dist: email-validator<=2.2
|
|
16
|
+
Requires-Dist: httpx>=0.24.1
|
|
17
|
+
Requires-Dist: httpx-auth~=0.18
|
|
18
|
+
Requires-Dist: pydantic>=2.0,<3.0
|
|
19
|
+
Requires-Dist: pydantic-settings>=2.13.1
|
|
20
|
+
Requires-Dist: pymdown-extensions~=10.5
|
|
21
|
+
Requires-Dist: python-dotenv~=1.0
|
|
22
|
+
Requires-Dist: python-magic~=0.4
|
|
23
|
+
Requires-Dist: rich>=14.3.3
|
|
24
|
+
Requires-Dist: structlog>=25.5.0
|
|
25
|
+
Requires-Dist: structlog-config>=0.10.0
|
|
26
|
+
Requires-Dist: tenacity>=9.1.4
|
|
27
|
+
Requires-Dist: typer>=0.24.1,<0.25.0
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Project-URL: Homepage, https://yio.at
|
|
30
|
+
Project-URL: Documentation, https://docs.yio.at/products/yio-minions
|
|
31
|
+
Project-URL: Repository, https://github.com/yiogmbh/fastbpmn.git
|
|
32
|
+
Project-URL: Issues, https://github.com/yiogmbh/fastbpmn/issues
|
|
33
|
+
Project-URL: Changelog, https://github.com/yiogmbh/fastbpmn/blob/main/CHANGELOG.md
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# fastbpmn
|
|
37
|
+
A framework allowing to write external tasks for various bpmn process engines.
|
|
38
|
+
|
|
39
|
+
## Installation / Usage / Prerequisites
|
|
40
|
+
|
|
41
|
+
Ensure that the libmagic C library is installed on your system. See [python-magic](https://pypi.org/project/python-magic/) for installation on various systems.
|
|
42
|
+
|
|
43
|
+
On OSX use:
|
|
44
|
+
```
|
|
45
|
+
brew install libmagic
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage Example
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from fastbpmn import FastBPMN
|
|
52
|
+
from fastbpmn.camunda import ProcessEngine
|
|
53
|
+
from fastbpmn.models import BaseInputModel, BaseOutputModel
|
|
54
|
+
|
|
55
|
+
app = FastBPMN(name="Bob")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class OracleInput(BaseInputModel):
|
|
59
|
+
string_variable: str # Requires a process variable called string_variable
|
|
60
|
+
integer_variable: int # Requires a process variable called integer_variable
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class OracleOutput(BaseOutputModel):
|
|
64
|
+
win_or_loose: bool # Sets the process variable win_or_loose in the end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.external_task(
|
|
68
|
+
topic="ask-oracle-delphi",
|
|
69
|
+
input_class=OracleInput,
|
|
70
|
+
output_class=OracleOutput,
|
|
71
|
+
)
|
|
72
|
+
async def ask_oracle_delphi(input_data: OracleInput):
|
|
73
|
+
"""
|
|
74
|
+
External Task / DEMO
|
|
75
|
+
Delphi was a sacred precinct that served as the seat of Pythia, the major oracle
|
|
76
|
+
who was consulted about important decisions throughout the ancient classical world.
|
|
77
|
+
"""
|
|
78
|
+
dummy_number = input_data.integer_variable + len(input_data.string_variable)
|
|
79
|
+
|
|
80
|
+
win_or_loose = bool(dummy_number % 2)
|
|
81
|
+
|
|
82
|
+
return OracleOutput(win_or_loose=win_or_loose)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@app.external_task(
|
|
86
|
+
topic="ask-oracle-dodona",
|
|
87
|
+
input_class=OracleInput,
|
|
88
|
+
output_class=OracleOutput,
|
|
89
|
+
)
|
|
90
|
+
async def ask_oracle_dodona(input_data: OracleInput):
|
|
91
|
+
"""
|
|
92
|
+
External Task / DEMO
|
|
93
|
+
Dodona in Epirus in northwestern Greece was the oldest Hellenic oracle.
|
|
94
|
+
The earliest accounts in Homer describe Dodona as an oracle of Zeus.
|
|
95
|
+
"""
|
|
96
|
+
dummy_number = input_data.integer_variable + len(input_data.string_variable)
|
|
97
|
+
|
|
98
|
+
win_or_loose = not bool(dummy_number % 2)
|
|
99
|
+
|
|
100
|
+
return OracleOutput(win_or_loose=win_or_loose)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# start the fastbpmn application using the included squirrel
|
|
104
|
+
if __name__ == '__main__':
|
|
105
|
+
import squirrel
|
|
106
|
+
from structlog_config import configure_logger
|
|
107
|
+
log = configure_logger()
|
|
108
|
+
|
|
109
|
+
#structlog.stdlib.recreate_defaults(log_level=logging.INFO)
|
|
110
|
+
|
|
111
|
+
squirrel.run(
|
|
112
|
+
app,
|
|
113
|
+
flavour="camunda7",
|
|
114
|
+
|
|
115
|
+
name="bob",
|
|
116
|
+
workers=10,
|
|
117
|
+
camunda_url="<your pe url>",
|
|
118
|
+
camunda_username="<your username>",
|
|
119
|
+
camunda_password="<your password>",
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Lifespan Handler
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from fastbpmn import FastBPMN
|
|
127
|
+
from fastbpmn.camunda import ProcessEngine
|
|
128
|
+
from contextlib import asynccontextmanager
|
|
129
|
+
|
|
130
|
+
@asynccontextmanager
|
|
131
|
+
async def lifespan(app):
|
|
132
|
+
print("Done on startup ...")
|
|
133
|
+
yield
|
|
134
|
+
print("done on shutdown ...")
|
|
135
|
+
|
|
136
|
+
app = FastBPMN(
|
|
137
|
+
name="Bob",
|
|
138
|
+
lifespan=lifespan
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Retries
|
|
143
|
+
|
|
144
|
+
In order to handle errors with retries there is a special exception that should be
|
|
145
|
+
raised within your external tasks. The latter example shows the usage:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from fastbpmn import FastBPMN
|
|
149
|
+
from fastbpmn.errors import RetryExternalTask
|
|
150
|
+
from fastbpmn.camunda import ProcessEngine
|
|
151
|
+
|
|
152
|
+
app = FastBPMN(
|
|
153
|
+
name="Bob"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.external_task(
|
|
158
|
+
topic="last-forever",
|
|
159
|
+
input_class=None,
|
|
160
|
+
output_class=None,
|
|
161
|
+
)
|
|
162
|
+
async def retry_infinite(input_data: None) -> None:
|
|
163
|
+
print("I will last forever")
|
|
164
|
+
raise RetryExternalTask(retries=1) # No matter what happens we always tell the
|
|
165
|
+
# process engine to try once again ;-)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@app.external_task(
|
|
169
|
+
topic="try-5times",
|
|
170
|
+
input_class=None,
|
|
171
|
+
output_class=None,
|
|
172
|
+
retries=5
|
|
173
|
+
)
|
|
174
|
+
async def retry_infinite(input_data: None) -> None:
|
|
175
|
+
print("You should see me 5 or 6 times ...")
|
|
176
|
+
raise RetryExternalTask() # not specifying a number of retries within the
|
|
177
|
+
# exception leads to decrease of initial number
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### No Input Values
|
|
181
|
+
|
|
182
|
+
It's possible to omit all the arguments if your external-task won't depend on input data.
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from fastbpmn import FastBPMN
|
|
186
|
+
from fastbpmn.camunda import ProcessEngine
|
|
187
|
+
|
|
188
|
+
app = FastBPMN(
|
|
189
|
+
name="Bob"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@app.external_task(
|
|
194
|
+
topic="without-args"
|
|
195
|
+
)
|
|
196
|
+
async def no_args() -> None:
|
|
197
|
+
print("I'm almighty, I won't need any arguments. I will work anyway")
|
|
198
|
+
return
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Task/TaskProperties
|
|
202
|
+
|
|
203
|
+
Sometimes you might be interested in properties of the current Task or the TaskProperties in general.
|
|
204
|
+
You can declare an external task such that you will receive this objects for usage:
|
|
205
|
+
|
|
206
|
+
> **Attention** This is highly experimental and due to upcoming refactorings of the process engine interface the
|
|
207
|
+
> Task and TaskProperties Class are likely to change in the future.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from fastbpmn import FastBPMN
|
|
211
|
+
from fastbpmn.task import Task, TaskProperties
|
|
212
|
+
from fastbpmn.camunda import ProcessEngine
|
|
213
|
+
|
|
214
|
+
app = FastBPMN(
|
|
215
|
+
name="Bob"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@app.external_task(
|
|
220
|
+
topic="task-info",
|
|
221
|
+
input_class=None,
|
|
222
|
+
output_class=None,
|
|
223
|
+
)
|
|
224
|
+
async def print_taskinfo(input_data: None, task: Task, task_properties: TaskProperties) -> None:
|
|
225
|
+
print(f"TaskId: {task.id} - initial retries: {task_properties.retries}")
|
|
226
|
+
return
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Process
|
|
230
|
+
|
|
231
|
+
A Process is a useful Method to create more structured code.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from fastbpmn import FastBPMN, Process
|
|
235
|
+
from fastbpmn.task import Task, TaskProperties
|
|
236
|
+
from fastbpmn.camunda import ProcessEngine
|
|
237
|
+
|
|
238
|
+
app = FastBPMN(
|
|
239
|
+
name="Bob"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
process_a = Process(
|
|
243
|
+
process_definition_key="ProzessA"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@process_a.external_task("print_a")
|
|
248
|
+
async def print_a(input_data: None) -> None:
|
|
249
|
+
print("Hello from a Prozess A Only Task.")
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# put me in a different file if you want ;-)
|
|
254
|
+
process_b = Process(
|
|
255
|
+
process_definition_key="ProzessB"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@process_b.external_task("print_b")
|
|
260
|
+
async def print_b(input_data: None) -> None:
|
|
261
|
+
print("Hello from a Prozess B Only Task.")
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# You can also attach a TaskHandler method to multiple Processes
|
|
266
|
+
|
|
267
|
+
async def print_common(input_data: None, task: Task, task_properties: TaskProperties) -> None:
|
|
268
|
+
print(f"Hello from a common task, i was executed by process ... {task.process_definition_key}.")
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# Add the print common to processes wherever you like
|
|
273
|
+
# >-> The Topic can be different as well..
|
|
274
|
+
process_b.add_task("print_common", handler=print_common)
|
|
275
|
+
process_a.add_task("print_common", handler=print_common)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### Context
|
|
281
|
+
|
|
282
|
+
You can make use of a context within your external task. The context provides some useful features
|
|
283
|
+
(e.g. create of temporary files / directories).
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from fastbpmn import FastBPMN, Process
|
|
287
|
+
from fastbpmn.context import Context, Delete
|
|
288
|
+
from fastbpmn.camunda import ProcessEngine
|
|
289
|
+
|
|
290
|
+
app = FastBPMN(
|
|
291
|
+
name="Bob"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
process_a = Process(
|
|
295
|
+
process_definition_key="ProzessA"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@process_a.external_task("everlasting-file")
|
|
300
|
+
async def print_a(ctx: Context) -> None:
|
|
301
|
+
print("Hello from a Prozess A, I create a file that is not deleted ....")
|
|
302
|
+
file_path = ctx.temp_file(flags=Delete.NEVER)
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@process_a.external_task("deleted-file")
|
|
307
|
+
async def print_b(ctx: Context) -> None:
|
|
308
|
+
print("Hello from a Prozess A, I create a file that deleted when I'm done ....")
|
|
309
|
+
file_path = ctx.temp_file(flags=Delete.ALWAYS)
|
|
310
|
+
return
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### File Handling
|
|
314
|
+
|
|
315
|
+
There are several ways to deal with File variables in Camunda. The following example shows three use cases.
|
|
316
|
+
|
|
317
|
+
The assumption is the following:
|
|
318
|
+
|
|
319
|
+
- the process has two file variables called `pdf_file_var` and `png_file_var` in camunda
|
|
320
|
+
- there is an external task that needs to work with these files
|
|
321
|
+
- the `png_file_var` is only optional and might not be present
|
|
322
|
+
- there are three scenarios:
|
|
323
|
+
- the variable name in camunda are known
|
|
324
|
+
- the variable names in camunda are subject to change, but there are two other variables
|
|
325
|
+
holding the names (`pdf_file_var_name` and `png_file_var_name`)
|
|
326
|
+
- the variable names in camunda are subject to change as is the number of files, but there is
|
|
327
|
+
a list of variable names (`file_var_names`) that hold the names of the file variables
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
import asyncio
|
|
331
|
+
from functools import cached_property
|
|
332
|
+
|
|
333
|
+
from pydantic import computed_field, model_validator
|
|
334
|
+
|
|
335
|
+
from fastbpmn import FastBPMN, Process
|
|
336
|
+
from fastbpmn.context import Context
|
|
337
|
+
from fastbpmn.camunda import ProcessEngine
|
|
338
|
+
from fastbpmn.models import BaseInputModel, FileInfo, get_file_info_indirect
|
|
339
|
+
|
|
340
|
+
process_a = Process(
|
|
341
|
+
process_definition_key="ProzessA"
|
|
342
|
+
)
|
|
343
|
+
app = FastBPMN(name="Bob")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class Option1InputModel(BaseInputModel):
|
|
347
|
+
"""
|
|
348
|
+
This is the first option to deal with file variables.
|
|
349
|
+
|
|
350
|
+
The variable names are known and can be used directly, that means there is a process
|
|
351
|
+
variable called `pdf_file_var` and `png_file_var` in camunda with type file.
|
|
352
|
+
"""
|
|
353
|
+
pdf_file_var: FileInfo
|
|
354
|
+
png_file_var: FileInfo | None = None
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class Option2InputModel(BaseInputModel):
|
|
358
|
+
"""
|
|
359
|
+
This is the second option to deal with file variables.
|
|
360
|
+
|
|
361
|
+
The variable names are not known and are subject to change. But there are two other
|
|
362
|
+
variables (type string) that hold the names of the file variables.
|
|
363
|
+
|
|
364
|
+
Be aware of the computed properties and validators that are used to ensure that the
|
|
365
|
+
file variables are present as expected.
|
|
366
|
+
"""
|
|
367
|
+
# if `pdf_file_var_name` is set to 'other_pdf_var' then a process variable called
|
|
368
|
+
# `other_pdf_var` is expected to be present in camunda with type file. This variable
|
|
369
|
+
# is then used to compute the value of the computed_field `pdf_file`.
|
|
370
|
+
# if there is a process variable called pdf_file, then this variable is ignored (i guess)
|
|
371
|
+
pdf_file_var_name: str
|
|
372
|
+
png_file_var_name: str | None = None
|
|
373
|
+
|
|
374
|
+
@computed_field
|
|
375
|
+
@cached_property
|
|
376
|
+
def pdf_file(self) -> FileInfo:
|
|
377
|
+
return get_file_info_indirect(self, self.pdf_file_var, required=True)
|
|
378
|
+
|
|
379
|
+
@computed_field
|
|
380
|
+
@cached_property
|
|
381
|
+
def png_file(self) -> FileInfo | None:
|
|
382
|
+
return get_file_info_indirect(self, self.png_file_var, required=False)
|
|
383
|
+
|
|
384
|
+
@model_validator(mode='after')
|
|
385
|
+
def check_png_file(self):
|
|
386
|
+
"""
|
|
387
|
+
Checks if the file_info is present at the given key
|
|
388
|
+
"""
|
|
389
|
+
# this is a way to compute the value of the computed_field on initialization
|
|
390
|
+
# to trigger validation immediately
|
|
391
|
+
_ = self.png_file
|
|
392
|
+
return self
|
|
393
|
+
|
|
394
|
+
@model_validator(mode='after')
|
|
395
|
+
def check_pdf_file(self):
|
|
396
|
+
"""
|
|
397
|
+
Checks if the file_info is present at the given key
|
|
398
|
+
"""
|
|
399
|
+
# this is a way to compute the value of the computed_field on initialization
|
|
400
|
+
# to trigger validation immediately
|
|
401
|
+
_ = self.pdf_file
|
|
402
|
+
return self
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class Option3InputModel(BaseInputModel):
|
|
406
|
+
"""
|
|
407
|
+
This is the third option to deal with file variables.
|
|
408
|
+
|
|
409
|
+
The variable names are not known and are subject to change. But there is a list of
|
|
410
|
+
variable names (type string) that hold the names of the file variables.
|
|
411
|
+
"""
|
|
412
|
+
file_var_names: list[str]
|
|
413
|
+
|
|
414
|
+
@computed_field
|
|
415
|
+
@cached_property
|
|
416
|
+
def files(self) -> list[FileInfo]:
|
|
417
|
+
return [get_file_info_indirect(self, file_var_name, required=True) for file_var_name in self.file_var_names]
|
|
418
|
+
|
|
419
|
+
@model_validator(mode='after')
|
|
420
|
+
def check_files(self):
|
|
421
|
+
"""
|
|
422
|
+
Checks if the file_info is present at the given key
|
|
423
|
+
"""
|
|
424
|
+
# this is a way to compute the value of the computed_field on initialization
|
|
425
|
+
# to trigger validation immediately
|
|
426
|
+
_ = self.files
|
|
427
|
+
return self
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@process_a.external_task("option1")
|
|
431
|
+
async def option1(ctx: Context, input_data: Option1InputModel) -> None:
|
|
432
|
+
"""
|
|
433
|
+
This is the first option to deal with file variables.
|
|
434
|
+
|
|
435
|
+
The variable names are known and can be used directly, that means there is a process
|
|
436
|
+
variable called `pdf_file_var` and `png_file_var` in camunda with type file.
|
|
437
|
+
"""
|
|
438
|
+
pdf_file = await ctx.download_file(input_data.pdf_file)
|
|
439
|
+
if input_data.png_file:
|
|
440
|
+
png_file = await ctx.download_file(input_data.png_file)
|
|
441
|
+
# Or use gather ...
|
|
442
|
+
|
|
443
|
+
# ... do something with the files ...
|
|
444
|
+
return
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@process_a.external_task("option2")
|
|
448
|
+
async def option2(ctx: Context, input_data: Option2InputModel) -> None:
|
|
449
|
+
"""
|
|
450
|
+
This is the second option to deal with file variables.
|
|
451
|
+
|
|
452
|
+
See that implementation of the external task is the same as with option 1,
|
|
453
|
+
the difference is just that the variable names in camunda might differ as long
|
|
454
|
+
as there are string variables telling you the names of the file variables.
|
|
455
|
+
"""
|
|
456
|
+
pdf_file = await ctx.download_file(input_data.pdf_file)
|
|
457
|
+
if input_data.png_file:
|
|
458
|
+
png_file = await ctx.download_file(input_data.png_file)
|
|
459
|
+
# Or use gather ...
|
|
460
|
+
|
|
461
|
+
# ... do something with the files ...
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@process_a.external_task("option3")
|
|
466
|
+
async def option3(ctx: Context, input_data: Option3InputModel) -> None:
|
|
467
|
+
"""
|
|
468
|
+
This is the third option to deal with file variables.
|
|
469
|
+
|
|
470
|
+
See that implementation of the external task is the same as with option 1,
|
|
471
|
+
the difference is just that the variable names in camunda might differ as long
|
|
472
|
+
as there are string variables telling you the names of the file variables.
|
|
473
|
+
"""
|
|
474
|
+
files = await asyncio.gather(*[ctx.download_file(file) for file in input_data.files])
|
|
475
|
+
# ... do something with the files ... (the caveat is that you need to know the order and meaning of the files)
|
|
476
|
+
# but there might be use cases where this won't matter).
|
|
477
|
+
return
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
## Development
|
|
484
|
+
|
|
485
|
+
```shell
|
|
486
|
+
# setup evn ...
|
|
487
|
+
uv sync
|
|
488
|
+
|
|
489
|
+
# install commit hooks
|
|
490
|
+
prek install
|
|
491
|
+
```
|