pytestflow 0.2.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 (63) hide show
  1. bootstrap_templates/__init__.py +0 -0
  2. bootstrap_templates/config.yaml +13 -0
  3. bootstrap_templates/custom_step_types/__init__.py +0 -0
  4. bootstrap_templates/custom_step_types/custom_step_template.py +7 -0
  5. bootstrap_templates/process_models/__init__.py +0 -0
  6. bootstrap_templates/process_models/reporting/README.md +48 -0
  7. bootstrap_templates/process_models/reporting/__init__.py +0 -0
  8. bootstrap_templates/process_models/reporting/default_report.html.j2 +122 -0
  9. bootstrap_templates/process_models/reporting/html_report.py +331 -0
  10. bootstrap_templates/process_models/sequential_model.py +141 -0
  11. bootstrap_templates/test_sequences/__init__.py +0 -0
  12. bootstrap_templates/test_sequences/basic_sequence.py +62 -0
  13. bootstrap_templates/test_sequences/message_box_and_flow_control.py +169 -0
  14. bootstrap_templates/test_sequences/motherboard_test_sequence.py +125 -0
  15. bootstrap_templates/test_sequences/step_types_quickstart.py +168 -0
  16. pytestflow/README.md +13 -0
  17. pytestflow/__init__.py +19 -0
  18. pytestflow/backend/__init__.py +2 -0
  19. pytestflow/backend/event_bus.py +27 -0
  20. pytestflow/backend/frontend/assets/full_logo-D1DRTUt8.svg +21 -0
  21. pytestflow/backend/frontend/assets/index-480TOyh4.js +2 -0
  22. pytestflow/backend/frontend/assets/index-qEI3VAQU.css +1 -0
  23. pytestflow/backend/frontend/index.html +14 -0
  24. pytestflow/backend/frontend/logo.svg +21 -0
  25. pytestflow/backend/handlers.py +214 -0
  26. pytestflow/backend/report_manager.py +15 -0
  27. pytestflow/backend/sequences_info.py +130 -0
  28. pytestflow/backend/start_backend.py +118 -0
  29. pytestflow/backend/uuids_handler.py +67 -0
  30. pytestflow/backend/websocket_gateway.py +91 -0
  31. pytestflow/cli.py +183 -0
  32. pytestflow/config/__init__.py +0 -0
  33. pytestflow/config/config_manager.py +44 -0
  34. pytestflow/core/README.md +110 -0
  35. pytestflow/core/__init__.py +15 -0
  36. pytestflow/core/context.py +41 -0
  37. pytestflow/core/core.py +112 -0
  38. pytestflow/core/pytestflow_states.py +88 -0
  39. pytestflow/core/runtime_control.py +164 -0
  40. pytestflow/core/seq_file_runner.py +38 -0
  41. pytestflow/core/sequence.py +404 -0
  42. pytestflow/core/utils.py +81 -0
  43. pytestflow/flow_utils/README.md +6 -0
  44. pytestflow/flow_utils/__init__.py +0 -0
  45. pytestflow/flow_utils/conditions.py +0 -0
  46. pytestflow/flow_utils/transitions.py +0 -0
  47. pytestflow/starter_here.md +43 -0
  48. pytestflow/steps/README.md +43 -0
  49. pytestflow/steps/__init__.py +15 -0
  50. pytestflow/steps/action_step.py +94 -0
  51. pytestflow/steps/common.py +51 -0
  52. pytestflow/steps/df_numeric_limits.py +151 -0
  53. pytestflow/steps/flow_control.py +86 -0
  54. pytestflow/steps/message_pop_up.py +76 -0
  55. pytestflow/steps/numeric_limit.py +109 -0
  56. pytestflow/steps/pass_fail.py +49 -0
  57. pytestflow/steps/string_check.py +104 -0
  58. pytestflow/steps/waveform_limit.py +170 -0
  59. pytestflow-0.2.0.dist-info/METADATA +73 -0
  60. pytestflow-0.2.0.dist-info/RECORD +63 -0
  61. pytestflow-0.2.0.dist-info/WHEEL +5 -0
  62. pytestflow-0.2.0.dist-info/entry_points.txt +2 -0
  63. pytestflow-0.2.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,110 @@
1
+ # PyTestFlow Core
2
+
3
+ The `pytestflow.core` package hosts the building blocks of the framework: the
4
+ Prefect-integrated `Step` wrapper, shared execution context, sequence and
5
+ process-model flows, and the state objects returned by each step. These pieces
6
+ realize PyTestFlow's philosophy of **steps-as-tasks**, a **shared context**, and
7
+ **composable flows**.
8
+
9
+ See the [project README](../../readme.md) for an overview and
10
+ [steps documentation](../steps/README.md) for available step implementations.
11
+
12
+ ## Context management
13
+
14
+ `ptf_context` is a singleton `TestContext` that exposes four useful attributes:
15
+
16
+ - `globals` – values shared across the entire process model run.
17
+ - `locals` – values scoped to the currently executing sequence.
18
+ - `results` – optional history of step execution results.
19
+ - `current_step` – pointer to the `Step` instance currently executing.
20
+
21
+ Steps can access the context implicitly through autowired parameters or
22
+ explicitly by importing `ptf_context`:
23
+
24
+ ```python
25
+ from pytestflow.core import ptf_context, step
26
+
27
+
28
+ @step(name="load_config")
29
+ def load_config():
30
+ ptf_context.globals["serial"] = "ABC123"
31
+ ```
32
+
33
+ ## Step wrapper
34
+
35
+ `@step` wraps a plain function with the `Step` class, which in turn wraps the
36
+ function with Prefect's `@task`. The wrapper collects metadata (`get_meta_info`),
37
+ allows enrichment (`add_additional_info`), and exposes Prefect task helpers like
38
+ `.submit()`.
39
+
40
+ ```python
41
+ from pytestflow.core import step
42
+ from pytestflow.core.pytestflow_states import PyTestflowDone
43
+
44
+
45
+ @step(name="greet")
46
+ def greet(user: str) -> PyTestflowDone:
47
+ print(f"Hello {user}")
48
+ return PyTestflowDone(ptf_result={"step_status": "done", "output": user})
49
+ ```
50
+
51
+ If `user` is not provided explicitly, the decorator will look for
52
+ `ptf_context.locals["user"]` or `ptf_context.globals["user"]`.
53
+
54
+ Specialized decorators like `@numeric_limit_step` or `@action_step` rely on this
55
+ core wrapper and simply post-process the return value into the appropriate
56
+ `PyTestflowState` subclass.
57
+
58
+ ## Sequences as flows
59
+
60
+ Steps execute through the `Sequence` and `TestSequence` classes, both declared as
61
+ Prefect flows. A `Sequence` runs an ordered list of steps, manages context scope,
62
+ and aggregates child `PyTestflowState` instances. `TestSequence` extends this
63
+ behavior with setup/main/cleanup sections.
64
+
65
+ ```python
66
+ from pytestflow.core import Sequence, step
67
+
68
+
69
+ @step(name="measure")
70
+ def measure():
71
+ return 42
72
+
73
+
74
+ seq = Sequence("demo", [measure])
75
+ state = seq.run()
76
+ ```
77
+
78
+ Sequences accept `default_parameters` merged with runtime overrides. Nested
79
+ sequences execute in isolated local namespaces; constructing a subsequence with
80
+ `allow_parent_mutation=True` grants controlled write access to the parent's
81
+ locals via `ptf_context.locals`.
82
+
83
+ ## Process models
84
+
85
+ Process models inherit from `Sequence` and coordinate the broader UUT lifecycle.
86
+ `SequentialProcessModel` wires callbacks for `pre_uut`, `post_uut`, `report`, and
87
+ `database_logging` around a main sequence and stores intermediate results in
88
+ `ptf_context.locals` for later callbacks.
89
+
90
+ ## State objects
91
+
92
+ Every step returns a subclass of `PyTestflowState`. The most common ones are:
93
+
94
+ - `PyTestflowPassed`
95
+ - `PyTestflowFailed`
96
+ - `PyTestflowDone`
97
+ - `PyTestflowError`
98
+
99
+ Each state carries a `ptf_result` dictionary and optional child states when
100
+ returned from sequences or process models.
101
+
102
+ ```python
103
+ result = seq.run()
104
+ print(result.ptf_result["step_status"]) # sequence_passed/failed
105
+ for name, child in result.children:
106
+ print(name, child.ptf_result)
107
+ ```
108
+
109
+ Back to [steps documentation](../steps/README.md) or the
110
+ [project README](../../readme.md).
@@ -0,0 +1,15 @@
1
+ from .core import step,StepWrapper
2
+ from .context import ptf_context
3
+ from .pytestflow_states import PyTestflowPassed, PyTestflowFailed, PyTestflowDone, PyTestflowError
4
+ from .sequence import Sequence
5
+
6
+ __all__ = [
7
+ "step",
8
+ "StepWrapper",
9
+ "ptf_context",
10
+ "PyTestflowPassed",
11
+ "PyTestflowFailed",
12
+ "PyTestflowDone",
13
+ "PyTestflowError",
14
+ "Sequence",
15
+ ]
@@ -0,0 +1,41 @@
1
+ from datetime import datetime
2
+ from collections import defaultdict
3
+ from typing import Any, Dict, List, Optional
4
+
5
+
6
+ class TestContext:
7
+ """Manages local/global variables and step results for test execution."""
8
+
9
+ def __init__(self) -> None:
10
+ self.locals: Dict[str, Any] = {} # Per-sequence or per-step local vars
11
+ self.globals: Dict[str, Any] = {} # Shared across all steps
12
+ self.results: Dict[str, List[Dict[str, Any]]] = defaultdict(list) # step_name -> list of result dicts
13
+ self.this_context: "Context" = self # Self-ref (TestStand-style)
14
+ self.contex_created_timestamp: datetime = datetime.now()
15
+
16
+ # # The following methods are commented out as they are not used in the current context.
17
+ # def log_result(self, step_name: str, result_dict: Dict[str, Any]) -> None:
18
+ # """Append a structured result to the results dict."""
19
+ # self.results[step_name].append(result_dict)
20
+
21
+ # def get_last_result(self, step_name: str) -> Optional[Dict[str, Any]]:
22
+ # """Get last execution result for a given step."""
23
+ # return self.results[step_name][-1] if self.results[step_name] else None
24
+
25
+ # def __repr__(self) -> str:
26
+ # return (
27
+ # f"<Context locals={list(self.locals.keys())} "
28
+ # f"globals={list(self.globals.keys())} "
29
+ # f"results={len(self.results)}>"
30
+ # )
31
+ # <=== End of commented-out methods ===>
32
+
33
+ def __repr__(self) -> str:
34
+ return (
35
+ f"<Context locals={list(self.locals.keys())} "
36
+ f"globals={list(self.globals.keys())}>"
37
+ )
38
+
39
+
40
+ # Global singleton instance
41
+ ptf_context = TestContext() # ptf_context = PyTestFlow execution context
@@ -0,0 +1,112 @@
1
+ # core/core.py
2
+ from functools import wraps
3
+ from datetime import datetime
4
+ import uuid
5
+ from prefect import task
6
+ from pytestflow.core.utils import get_data_for_gui, resolve_args
7
+ from pytestflow.core.context import ptf_context # Import ptf_context for step injection
8
+
9
+
10
+ def safe_getattr(obj, attr):
11
+ """Safe getattr that returns None if attribute is missing or context unavailable."""
12
+ try:
13
+ return getattr(obj, attr, None)
14
+ except Exception:
15
+ return None
16
+
17
+
18
+ class StepWrapper:
19
+ """
20
+ A callable wrapper around a user function that integrates with Prefect @task,
21
+ while capturing runtime metadata (start time, duration, flow/task IDs).
22
+ Also supports user-supplied additional_info for inclusion in ptf_result.
23
+ """
24
+
25
+ def __init__(self, fn, name=None, autowire=True, **task_kwargs):
26
+ self.fn = fn
27
+ self.name = name or fn.__name__
28
+ self.static_uuid: uuid.UUID | None = None
29
+ self.autowire = autowire
30
+ self.task_kwargs = task_kwargs
31
+
32
+ # Metadata and additional info
33
+ self._stepmeta = {}
34
+ self.additional_info = {}
35
+
36
+ # Wrap the function with Prefect's @task
37
+ self.prefect_task = task(name=self.name, **task_kwargs)(self._run)
38
+
39
+ def _run(self, *args, **kwargs):
40
+ # Reset metadata before every run
41
+ self._stepmeta = {}
42
+ self.additional_info = {}
43
+
44
+ # Start time
45
+ start_time = datetime.utcnow()
46
+
47
+ # Inject the current step into ptf_context
48
+ ptf_context.current_step = self
49
+
50
+ # Send start to GUI
51
+ get_data_for_gui(self, start_time)
52
+
53
+ try:
54
+ # Resolve arguments if autowire is enabled
55
+ if self.autowire:
56
+ args, kwargs = resolve_args(self.fn, args, kwargs)
57
+
58
+ # Execute the original function
59
+ result = self.fn(*args, **kwargs)
60
+
61
+ finally:
62
+ # Always clear the pointer to avoid leaking across steps
63
+ ptf_context.current_step = None
64
+
65
+ # End time and duration
66
+ end_time = datetime.utcnow()
67
+ duration = (end_time - start_time).total_seconds()
68
+
69
+ # Collect core metadata
70
+ self._stepmeta = {
71
+ "start_time": start_time.isoformat(),
72
+ "end_time": end_time.isoformat(),
73
+ "duration_s": duration,
74
+ }
75
+
76
+ return result
77
+
78
+ def get_meta_info(self):
79
+ """Return collected metadata + any user-supplied additional_info."""
80
+ meta = dict(self._stepmeta) if self._stepmeta else {}
81
+ if self.additional_info:
82
+ meta["additional_info"] = dict(self.additional_info)
83
+ return meta
84
+
85
+ def add_additional_info(self, key, value):
86
+ """Add additional information to the step (will be included in ptf_result)."""
87
+ self.additional_info[key] = value
88
+
89
+ def __call__(self, *args, **kwargs):
90
+ """Delegate execution to the Prefect task."""
91
+ return self.prefect_task(*args, **kwargs)
92
+
93
+ def __getattr__(self, name):
94
+ """
95
+ Delegate attribute access to the Prefect task.
96
+ This allows the StepWrapper to behave like a Prefect task
97
+ (e.g., .fn, .name, .submit, etc.).
98
+ """
99
+ return getattr(self.prefect_task, name)
100
+
101
+
102
+ def step(name=None, autowire=True, **task_kwargs):
103
+ """
104
+ Decorator factory that produces a StepWrapper object.
105
+ Usage:
106
+ @step(name="my_step")
107
+ def foo():
108
+ return 42
109
+ """
110
+ def decorator(fn):
111
+ return StepWrapper(fn, name=name, autowire=autowire, **task_kwargs)
112
+ return decorator
@@ -0,0 +1,88 @@
1
+ from prefect.states import State
2
+ from pydantic import Field
3
+ from typing import Optional, List, Tuple, Dict
4
+
5
+ class PyTestflowState(State):
6
+ """
7
+ Base class for all PyTestFlow states.
8
+ Uses `ptf_result` for internal step data and sets Prefect `.data` field
9
+ to `ptf_result["output"]` if available.
10
+ """
11
+ ptf_result: Dict = Field(default_factory=dict)
12
+ children: Optional[List[Tuple[str, State]]] = Field(default_factory=list)
13
+
14
+ def __init__(self, *, ptf_result=None, message=None, children=None, **kwargs):
15
+ ptf_result = ptf_result or {}
16
+
17
+ # Set internal .data result from ptf_result["output"]
18
+ data: Any = ptf_result.get("output", None)
19
+ kwargs.setdefault("data", data) # directly sets self.data
20
+
21
+ # Set default type if missing
22
+ kwargs.setdefault("type", "COMPLETED")
23
+
24
+ super().__init__(
25
+ message=message,
26
+ **kwargs
27
+ )
28
+
29
+ object.__setattr__(self, "ptf_result", ptf_result)
30
+ object.__setattr__(self, "children", children or [])
31
+
32
+ def add_child(self, name: str, state: State):
33
+ self.children.append((name, state))
34
+
35
+ def summarize(self):
36
+ return {
37
+ "name": self.name,
38
+ "status": self.ptf_result.get("step_status"),
39
+ "children": [name for name, _ in self.children]
40
+ }
41
+
42
+
43
+ class PyTestflowPassed(PyTestflowState):
44
+ def __init__(self, ptf_result=None, message="Step passed", children=None, **kwargs):
45
+ super().__init__(
46
+ name="Passed",
47
+ type="COMPLETED",
48
+ ptf_result=ptf_result,
49
+ message=message,
50
+ children=children,
51
+ **kwargs
52
+ )
53
+
54
+
55
+ class PyTestflowFailed(PyTestflowState):
56
+ def __init__(self, ptf_result=None, message="Step failed", children=None, **kwargs):
57
+ super().__init__(
58
+ name="Failed",
59
+ type="COMPLETED",
60
+ ptf_result=ptf_result,
61
+ message=message,
62
+ children=children,
63
+ **kwargs
64
+ )
65
+
66
+
67
+ class PyTestflowDone(PyTestflowState):
68
+ def __init__(self, ptf_result=None, message="Step done", children=None, **kwargs):
69
+ super().__init__(
70
+ name="Done",
71
+ type="COMPLETED",
72
+ ptf_result=ptf_result,
73
+ message=message,
74
+ children=children,
75
+ **kwargs
76
+ )
77
+
78
+
79
+ class PyTestflowError(PyTestflowState):
80
+ def __init__(self, ptf_result=None, message="Unhandled error", children=None, **kwargs):
81
+ super().__init__(
82
+ name="Error",
83
+ type="CRASHED",
84
+ ptf_result=ptf_result,
85
+ message=message,
86
+ children=children,
87
+ **kwargs
88
+ )
@@ -0,0 +1,164 @@
1
+ import threading
2
+ import time
3
+ import uuid
4
+ import asyncio
5
+ import queue
6
+
7
+
8
+ class RuntimeControl:
9
+ def __init__(self, default_throttle_ms: float = 5.0):
10
+ self._shutdown = False
11
+ # --- PAUSE / THROTTLE / STOP DOMAIN ---
12
+ self._lock = threading.Lock()
13
+ self._resume_condition = threading.Condition(self._lock)
14
+ self._paused = False
15
+ self._throttle_ms = max(0.0, float(default_throttle_ms))
16
+ self._stop_requested = False
17
+
18
+ # --- POPUP DOMAIN (another lock) ---
19
+ self._popup_lock = threading.Lock()
20
+ self._popup_condition = threading.Condition(self._popup_lock)
21
+ self._pending_popups = {} # request_id -> selected
22
+
23
+ # GUI update queue for in-process communication (step -> backend)
24
+ self._gui_update_queue = queue.Queue()
25
+
26
+ def shutdown(self):
27
+ with self._popup_condition:
28
+ self._shutdown = True
29
+ self._popup_condition.notify_all()
30
+
31
+ with self._resume_condition:
32
+ self._resume_condition.notify_all()
33
+
34
+ # =========================================================
35
+ # PAUSE / THROTTLE / STOP
36
+ # =========================================================
37
+
38
+ def set_stop_requested(self, stop_requested: bool) -> None:
39
+ with self._lock:
40
+ self._stop_requested = bool(stop_requested)
41
+
42
+ def set_paused(self, paused: bool) -> None:
43
+ with self._resume_condition:
44
+ self._paused = bool(paused)
45
+ if not self._paused:
46
+ self._resume_condition.notify_all()
47
+
48
+ def is_paused(self) -> bool:
49
+ with self._lock:
50
+ return self._paused
51
+
52
+ def wait_until_resumed(self) -> None:
53
+ with self._resume_condition:
54
+ while self._paused:
55
+ self._resume_condition.wait(timeout=0.2)
56
+
57
+ def set_throttle_ms(self, throttle_ms: float) -> None:
58
+ with self._lock:
59
+ self._throttle_ms = max(0.0, float(throttle_ms))
60
+
61
+ def get_throttle_ms(self) -> float:
62
+ with self._lock:
63
+ return self._throttle_ms
64
+
65
+ def get_throttle_seconds(self) -> float:
66
+ return self.get_throttle_ms() / 1000.0
67
+
68
+ def sleep_with_pause(self, delay_seconds: float) -> None:
69
+ if delay_seconds <= 0:
70
+ return
71
+
72
+ end_time = time.monotonic() + delay_seconds
73
+ while True:
74
+ self.wait_until_resumed()
75
+ remaining = end_time - time.monotonic()
76
+ if remaining <= 0:
77
+ break
78
+ time.sleep(min(remaining, 0.05))
79
+
80
+ def checkpoint_before_step(self, step_index: int, apply_throttle: bool = True) -> None:
81
+ if self._stop_requested:
82
+ raise RuntimeError("Stop requested")
83
+
84
+ self.wait_until_resumed()
85
+ if apply_throttle and step_index > 0:
86
+ self.sleep_with_pause(self.get_throttle_seconds())
87
+ self.wait_until_resumed()
88
+
89
+ def snapshot(self) -> dict:
90
+ with self._lock:
91
+ return {
92
+ "paused": self._paused,
93
+ "throttle_ms": self._throttle_ms,
94
+ "stop_requested": self._stop_requested
95
+ }
96
+
97
+ # =========================================================
98
+ # POPUP (lock separato)
99
+ # =========================================================
100
+
101
+ def show_popup(self, popup_data: dict = None) -> dict:
102
+ request_id = str(uuid.uuid4())
103
+
104
+ with self._popup_condition:
105
+ self._pending_popups[request_id] = None
106
+
107
+ payload = {
108
+ "cmd": "display_modal_popup",
109
+ "args": {
110
+ "request_id": request_id,
111
+ "popup_data": popup_data
112
+ }
113
+ }
114
+
115
+ # Always enqueue payload so backend can deliver it when a GUI connects
116
+ self.send_gui_update(payload)
117
+
118
+ # Also try immediate in-process emit to reach already-connected clients
119
+ try:
120
+ import pytestflow.backend.event_bus as _eb
121
+ if getattr(_eb.event_bus, "loop", None):
122
+ print("[RuntimeControl] Enqueued popup and sending immediate in-process event_bus emit")
123
+ asyncio.run_coroutine_threadsafe(
124
+ _eb.event_bus.emit("outbound", payload),
125
+ _eb.event_bus.loop
126
+ )
127
+ except Exception as e:
128
+ print("[RuntimeControl] immediate event_bus emit failed (will rely on queue). Error:", e)
129
+
130
+ while self._pending_popups.get(request_id) is None and not self._shutdown:
131
+ self._popup_condition.wait(timeout=0.2)
132
+
133
+ if self._shutdown:
134
+ self._pending_popups.pop(request_id, None)
135
+ raise RuntimeError("Shutdown requested")
136
+
137
+ return self._pending_popups.pop(request_id)
138
+
139
+ def set_popup_response(self, request_id, button_pressed, response_text=None):
140
+ with self._popup_condition:
141
+ if request_id in self._pending_popups:
142
+ self._pending_popups[request_id] = {
143
+ "button": button_pressed,
144
+ "text": response_text
145
+ }
146
+ self._popup_condition.notify_all()
147
+
148
+ # GUI update queue API
149
+ def send_gui_update(self, update_data: dict) -> None:
150
+ """Called by steps/other threads to enqueue a GUI update payload."""
151
+ self._gui_update_queue.put(update_data)
152
+
153
+ def get_gui_update(self, timeout: float = 0.5):
154
+ """Called by backend consumer (in executor) to dequeue a GUI update.
155
+
156
+ Returns the update dict or None if timeout expires.
157
+ """
158
+ try:
159
+ return self._gui_update_queue.get(timeout=timeout)
160
+ except queue.Empty:
161
+ return None
162
+
163
+
164
+ runtime_control = RuntimeControl()
@@ -0,0 +1,38 @@
1
+ import importlib.util
2
+ import sys
3
+ from pathlib import Path
4
+ from prefect import flow
5
+ from prefect.context import get_run_context
6
+ from pytestflow.config.config_manager import ConfigManager
7
+
8
+ config = ConfigManager()
9
+ process_models_dir = config.get_path("process_models")
10
+
11
+ def load_process_model(process_models_dir: str, model_name: str):
12
+ file_path = Path(process_models_dir) / f"{model_name}.py"
13
+
14
+ spec = importlib.util.spec_from_file_location(
15
+ f"user_{model_name}",
16
+ file_path
17
+ )
18
+ module = importlib.util.module_from_spec(spec)
19
+ sys.modules[f"user_{model_name}"] = module
20
+ spec.loader.exec_module(module)
21
+
22
+ return module.PROCESS_MODEL
23
+
24
+
25
+ @flow()
26
+ def run_sequence_file(process_model_callbacks, model_name):
27
+ # Following line are because we cannot do @flow(name=test_sequence.name)
28
+ ctx = get_run_context()
29
+
30
+ ctx.flow_run.name = process_model_callbacks["main_sequence"].name
31
+
32
+ my_proc_model = load_process_model(process_models_dir, model_name)
33
+
34
+ process_model = my_proc_model(
35
+ name=process_model_callbacks["main_sequence"].name,
36
+ callbacks=process_model_callbacks
37
+ )
38
+ return process_model.run(return_state=True)