cognite-neat 0.103.1__py3-none-any.whl → 0.105.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.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_client/_api/data_modeling_loaders.py +83 -23
- cognite/neat/_client/_api/schema.py +2 -1
- cognite/neat/_client/data_classes/neat_sequence.py +261 -0
- cognite/neat/_client/data_classes/schema.py +5 -1
- cognite/neat/_client/testing.py +33 -0
- cognite/neat/_constants.py +56 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +6 -5
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +225 -11
- cognite/neat/_graph/extractors/_mock_graph_generator.py +2 -2
- cognite/neat/_graph/loaders/_rdf2dms.py +13 -2
- cognite/neat/_graph/transformers/__init__.py +3 -1
- cognite/neat/_graph/transformers/_base.py +109 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +6 -1
- cognite/neat/_graph/transformers/_prune_graph.py +103 -47
- cognite/neat/_graph/transformers/_rdfpath.py +41 -17
- cognite/neat/_graph/transformers/_value_type.py +188 -151
- cognite/neat/_issues/__init__.py +0 -2
- cognite/neat/_issues/_base.py +54 -43
- cognite/neat/_issues/warnings/__init__.py +4 -1
- cognite/neat/_issues/warnings/_general.py +7 -0
- cognite/neat/_issues/warnings/_resources.py +12 -1
- cognite/neat/_rules/_shared.py +18 -34
- cognite/neat/_rules/exporters/_base.py +28 -2
- cognite/neat/_rules/exporters/_rules2dms.py +39 -1
- cognite/neat/_rules/exporters/_rules2excel.py +13 -2
- cognite/neat/_rules/exporters/_rules2instance_template.py +4 -0
- cognite/neat/_rules/exporters/_rules2ontology.py +13 -1
- cognite/neat/_rules/exporters/_rules2yaml.py +4 -0
- cognite/neat/_rules/importers/_base.py +9 -0
- cognite/neat/_rules/importers/_dms2rules.py +80 -57
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +5 -2
- cognite/neat/_rules/importers/_rdf/_base.py +10 -8
- cognite/neat/_rules/importers/_rdf/_imf2rules.py +4 -0
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +7 -0
- cognite/neat/_rules/importers/_rdf/_owl2rules.py +4 -0
- cognite/neat/_rules/importers/_spreadsheet2rules.py +17 -8
- cognite/neat/_rules/importers/_yaml2rules.py +21 -7
- cognite/neat/_rules/models/_base_input.py +1 -1
- cognite/neat/_rules/models/_base_rules.py +9 -1
- cognite/neat/_rules/models/dms/_rules.py +4 -0
- cognite/neat/_rules/models/dms/_rules_input.py +9 -0
- cognite/neat/_rules/models/entities/_wrapped.py +10 -5
- cognite/neat/_rules/models/information/_rules.py +4 -0
- cognite/neat/_rules/models/information/_rules_input.py +9 -0
- cognite/neat/_rules/models/mapping/_classic2core.py +2 -5
- cognite/neat/_rules/models/mapping/_classic2core.yaml +239 -38
- cognite/neat/_rules/transformers/__init__.py +13 -6
- cognite/neat/_rules/transformers/_base.py +41 -65
- cognite/neat/_rules/transformers/_converters.py +404 -234
- cognite/neat/_rules/transformers/_mapping.py +93 -72
- cognite/neat/_rules/transformers/_verification.py +50 -38
- cognite/neat/_session/_base.py +32 -121
- cognite/neat/_session/_inspect.py +5 -3
- cognite/neat/_session/_mapping.py +17 -105
- cognite/neat/_session/_prepare.py +138 -268
- cognite/neat/_session/_read.py +39 -195
- cognite/neat/_session/_set.py +6 -30
- cognite/neat/_session/_show.py +40 -21
- cognite/neat/_session/_state.py +49 -107
- cognite/neat/_session/_to.py +44 -33
- cognite/neat/_shared.py +23 -2
- cognite/neat/_store/_provenance.py +3 -82
- cognite/neat/_store/_rules_store.py +368 -10
- cognite/neat/_store/exceptions.py +23 -0
- cognite/neat/_utils/graph_transformations_report.py +36 -0
- cognite/neat/_utils/rdf_.py +8 -0
- cognite/neat/_utils/reader/_base.py +27 -0
- cognite/neat/_utils/spreadsheet.py +5 -4
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/METADATA +3 -2
- cognite_neat-0.105.0.dist-info/RECORD +179 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/WHEEL +1 -1
- cognite/neat/_app/api/__init__.py +0 -0
- cognite/neat/_app/api/asgi/metrics.py +0 -4
- cognite/neat/_app/api/configuration.py +0 -98
- cognite/neat/_app/api/context_manager/__init__.py +0 -3
- cognite/neat/_app/api/context_manager/manager.py +0 -16
- cognite/neat/_app/api/data_classes/__init__.py +0 -0
- cognite/neat/_app/api/data_classes/rest.py +0 -59
- cognite/neat/_app/api/explorer.py +0 -66
- cognite/neat/_app/api/routers/configuration.py +0 -25
- cognite/neat/_app/api/routers/crud.py +0 -102
- cognite/neat/_app/api/routers/metrics.py +0 -10
- cognite/neat/_app/api/routers/workflows.py +0 -224
- cognite/neat/_app/api/utils/__init__.py +0 -0
- cognite/neat/_app/api/utils/data_mapping.py +0 -17
- cognite/neat/_app/api/utils/logging.py +0 -26
- cognite/neat/_app/api/utils/query_templates.py +0 -92
- cognite/neat/_app/main.py +0 -17
- cognite/neat/_app/monitoring/__init__.py +0 -0
- cognite/neat/_app/monitoring/metrics.py +0 -69
- cognite/neat/_app/ui/index.html +0 -1
- cognite/neat/_app/ui/neat-app/.gitignore +0 -23
- cognite/neat/_app/ui/neat-app/README.md +0 -70
- cognite/neat/_app/ui/neat-app/build/asset-manifest.json +0 -14
- cognite/neat/_app/ui/neat-app/build/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/build/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/build/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/build/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/build/index.html +0 -1
- cognite/neat/_app/ui/neat-app/build/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/build/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/build/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css +0 -2
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js +0 -3
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.LICENSE.txt +0 -88
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/media/logo.8093b84df9ed36a174c629d6fe0b730d.svg +0 -1
- cognite/neat/_app/ui/neat-app/package-lock.json +0 -18306
- cognite/neat/_app/ui/neat-app/package.json +0 -62
- cognite/neat/_app/ui/neat-app/public/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/public/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/public/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/public/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/public/index.html +0 -43
- cognite/neat/_app/ui/neat-app/public/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/public/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/public/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/src/App.css +0 -38
- cognite/neat/_app/ui/neat-app/src/App.js +0 -17
- cognite/neat/_app/ui/neat-app/src/App.test.js +0 -8
- cognite/neat/_app/ui/neat-app/src/MainContainer.tsx +0 -70
- cognite/neat/_app/ui/neat-app/src/components/JsonViewer.tsx +0 -43
- cognite/neat/_app/ui/neat-app/src/components/LocalUploader.tsx +0 -124
- cognite/neat/_app/ui/neat-app/src/components/OverviewComponentEditorDialog.tsx +0 -63
- cognite/neat/_app/ui/neat-app/src/components/StepEditorDialog.tsx +0 -511
- cognite/neat/_app/ui/neat-app/src/components/TabPanel.tsx +0 -36
- cognite/neat/_app/ui/neat-app/src/components/Utils.tsx +0 -56
- cognite/neat/_app/ui/neat-app/src/components/WorkflowDeleteDialog.tsx +0 -60
- cognite/neat/_app/ui/neat-app/src/components/WorkflowExecutionReport.tsx +0 -112
- cognite/neat/_app/ui/neat-app/src/components/WorkflowImportExportDialog.tsx +0 -67
- cognite/neat/_app/ui/neat-app/src/components/WorkflowMetadataDialog.tsx +0 -79
- cognite/neat/_app/ui/neat-app/src/index.css +0 -13
- cognite/neat/_app/ui/neat-app/src/index.js +0 -13
- cognite/neat/_app/ui/neat-app/src/logo.svg +0 -1
- cognite/neat/_app/ui/neat-app/src/reportWebVitals.js +0 -13
- cognite/neat/_app/ui/neat-app/src/setupTests.js +0 -5
- cognite/neat/_app/ui/neat-app/src/types/WorkflowTypes.ts +0 -388
- cognite/neat/_app/ui/neat-app/src/views/AboutView.tsx +0 -61
- cognite/neat/_app/ui/neat-app/src/views/ConfigView.tsx +0 -184
- cognite/neat/_app/ui/neat-app/src/views/GlobalConfigView.tsx +0 -180
- cognite/neat/_app/ui/neat-app/src/views/WorkflowView.tsx +0 -570
- cognite/neat/_app/ui/neat-app/tsconfig.json +0 -27
- cognite/neat/_rules/transformers/_pipelines.py +0 -70
- cognite/neat/_workflows/__init__.py +0 -17
- cognite/neat/_workflows/base.py +0 -590
- cognite/neat/_workflows/cdf_store.py +0 -393
- cognite/neat/_workflows/examples/Export_DMS/workflow.yaml +0 -89
- cognite/neat/_workflows/examples/Export_Semantic_Data_Model/workflow.yaml +0 -66
- cognite/neat/_workflows/examples/Import_DMS/workflow.yaml +0 -65
- cognite/neat/_workflows/examples/Validate_Rules/workflow.yaml +0 -67
- cognite/neat/_workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
- cognite/neat/_workflows/manager.py +0 -292
- cognite/neat/_workflows/model.py +0 -203
- cognite/neat/_workflows/steps/__init__.py +0 -0
- cognite/neat/_workflows/steps/data_contracts.py +0 -109
- cognite/neat/_workflows/steps/lib/__init__.py +0 -0
- cognite/neat/_workflows/steps/lib/current/__init__.py +0 -6
- cognite/neat/_workflows/steps/lib/current/graph_extractor.py +0 -100
- cognite/neat/_workflows/steps/lib/current/graph_loader.py +0 -51
- cognite/neat/_workflows/steps/lib/current/graph_store.py +0 -48
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -537
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +0 -398
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +0 -106
- cognite/neat/_workflows/steps/lib/io/__init__.py +0 -1
- cognite/neat/_workflows/steps/lib/io/io_steps.py +0 -393
- cognite/neat/_workflows/steps/step_model.py +0 -79
- cognite/neat/_workflows/steps_registry.py +0 -218
- cognite/neat/_workflows/tasks.py +0 -18
- cognite/neat/_workflows/triggers.py +0 -169
- cognite/neat/_workflows/utils.py +0 -19
- cognite_neat-0.103.1.dist-info/RECORD +0 -275
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.103.1.dist-info → cognite_neat-0.105.0.dist-info}/entry_points.txt +0 -0
cognite/neat/_workflows/base.py
DELETED
|
@@ -1,590 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
import os
|
|
4
|
-
import threading
|
|
5
|
-
import time
|
|
6
|
-
import traceback
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from threading import Event
|
|
9
|
-
|
|
10
|
-
import yaml
|
|
11
|
-
from cognite.client import ClientConfig, CogniteClient
|
|
12
|
-
from prometheus_client import Gauge
|
|
13
|
-
|
|
14
|
-
from cognite.neat._app.monitoring.metrics import NeatMetricsCollector
|
|
15
|
-
from cognite.neat._config import Config
|
|
16
|
-
from cognite.neat._issues.errors import WorkflowConfigurationNotSetError, WorkflowStepOutputError
|
|
17
|
-
from cognite.neat._utils.auxiliary import retry_decorator
|
|
18
|
-
from cognite.neat._workflows import cdf_store, utils
|
|
19
|
-
from cognite.neat._workflows.cdf_store import CdfStore
|
|
20
|
-
from cognite.neat._workflows.model import (
|
|
21
|
-
FlowMessage,
|
|
22
|
-
StepExecutionStatus,
|
|
23
|
-
StepType,
|
|
24
|
-
WorkflowConfigItem,
|
|
25
|
-
WorkflowConfigs,
|
|
26
|
-
WorkflowDefinition,
|
|
27
|
-
WorkflowFullStateReport,
|
|
28
|
-
WorkflowStartException,
|
|
29
|
-
WorkflowState,
|
|
30
|
-
WorkflowStepDefinition,
|
|
31
|
-
WorkflowStepEvent,
|
|
32
|
-
WorkflowSystemComponent,
|
|
33
|
-
)
|
|
34
|
-
from cognite.neat._workflows.steps.step_model import DataContract
|
|
35
|
-
from cognite.neat._workflows.steps_registry import StepsRegistry
|
|
36
|
-
from cognite.neat._workflows.tasks import WorkflowTaskBuilder
|
|
37
|
-
|
|
38
|
-
summary_metrics = Gauge("neat_workflow_summary_metrics", "Workflow execution summary metrics", ["wf_name", "name"])
|
|
39
|
-
steps_metrics = Gauge("neat_workflow_steps_metrics", "Workflow step level metrics", ["wf_name", "step_name", "name"])
|
|
40
|
-
timing_metrics = Gauge("neat_workflow_timing_metrics", "Workflow timing metrics", ["wf_name", "component", "name"])
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class BaseWorkflow:
|
|
44
|
-
def __init__(
|
|
45
|
-
self,
|
|
46
|
-
name: str,
|
|
47
|
-
client: CogniteClient,
|
|
48
|
-
steps_registry: StepsRegistry,
|
|
49
|
-
workflow_steps: list[WorkflowStepDefinition] | None = None,
|
|
50
|
-
default_dataset_id: int | None = None,
|
|
51
|
-
):
|
|
52
|
-
self.name = name
|
|
53
|
-
self.module_name = self.__class__.__module__
|
|
54
|
-
self.cdf_client = client
|
|
55
|
-
self.cdf_client_config: ClientConfig = client.config
|
|
56
|
-
self.default_dataset_id = default_dataset_id
|
|
57
|
-
self.state = WorkflowState.CREATED
|
|
58
|
-
self.instance_id = utils.generate_run_id()
|
|
59
|
-
self.run_id = ""
|
|
60
|
-
self.last_error = ""
|
|
61
|
-
self.elapsed_time: float = 0.0
|
|
62
|
-
self.start_time: float | None = None
|
|
63
|
-
self.end_time: float | None = None
|
|
64
|
-
self.execution_log: list[WorkflowStepEvent] = []
|
|
65
|
-
self.workflow_steps: list[WorkflowStepDefinition] = workflow_steps or []
|
|
66
|
-
self.workflow_system_components: list[WorkflowSystemComponent] = []
|
|
67
|
-
self.configs: list[WorkflowConfigItem] = []
|
|
68
|
-
self.flow_message: FlowMessage | None = None
|
|
69
|
-
self.task_builder: WorkflowTaskBuilder | None = None
|
|
70
|
-
self.rules_storage_path: Path | None = None
|
|
71
|
-
self.data_store_path: Path | None = None
|
|
72
|
-
self.user_steps_path: Path | None = None # path to user defined steps
|
|
73
|
-
self.cdf_store = (
|
|
74
|
-
cdf_store.CdfStore(self.cdf_client, data_set_id=self.default_dataset_id)
|
|
75
|
-
if self.default_dataset_id
|
|
76
|
-
else None
|
|
77
|
-
)
|
|
78
|
-
self.metrics = NeatMetricsCollector(self.name, self.cdf_client)
|
|
79
|
-
self.resume_event = Event()
|
|
80
|
-
self.is_ephemeral = False # if True, workflow will be deleted after completion
|
|
81
|
-
self.auto_workflow_cleanup = False
|
|
82
|
-
self.step_classes = None
|
|
83
|
-
self.data: dict[str, DataContract | FlowMessage | CdfStore | CogniteClient | None] = {}
|
|
84
|
-
self.steps_registry: StepsRegistry = steps_registry
|
|
85
|
-
|
|
86
|
-
def start(self, sync=False, is_ephemeral=False, **kwargs) -> FlowMessage | None:
|
|
87
|
-
"""Starts workflow execution.sync=True will block until workflow is completed and
|
|
88
|
-
return last workflow flow message, sync=False will start workflow in a separate thread and return None"""
|
|
89
|
-
if self.state not in [WorkflowState.CREATED, WorkflowState.COMPLETED, WorkflowState.FAILED]:
|
|
90
|
-
logging.error(f"Workflow {self.name} is already running")
|
|
91
|
-
return None
|
|
92
|
-
self.data["StartFlowMessage"] = kwargs.get("flow_message", None)
|
|
93
|
-
self.data["CdfStore"] = self.cdf_store
|
|
94
|
-
self.data["CogniteClient"] = self.cdf_client
|
|
95
|
-
self.state = WorkflowState.RUNNING
|
|
96
|
-
self.start_time = time.time()
|
|
97
|
-
self.end_time = None
|
|
98
|
-
self.run_id = utils.generate_run_id()
|
|
99
|
-
self.is_ephemeral = is_ephemeral
|
|
100
|
-
self.execution_log = []
|
|
101
|
-
|
|
102
|
-
if sync:
|
|
103
|
-
return self._run_workflow(**kwargs)
|
|
104
|
-
|
|
105
|
-
self.thread = threading.Thread(target=self._run_workflow, kwargs=kwargs)
|
|
106
|
-
self.thread.start()
|
|
107
|
-
return None
|
|
108
|
-
|
|
109
|
-
def _run_workflow(self, **kwargs) -> FlowMessage | None:
|
|
110
|
-
"""Run workflow and return last workflow flow message"""
|
|
111
|
-
summary_metrics.labels(wf_name=self.name, name="steps_count").set(len(self.workflow_steps))
|
|
112
|
-
logging.info(f"Starting workflow {self.name}")
|
|
113
|
-
if flow_message := kwargs.get("flow_message"):
|
|
114
|
-
self.flow_message = flow_message
|
|
115
|
-
self.data["FlowMessage"] = flow_message
|
|
116
|
-
start_time = time.perf_counter()
|
|
117
|
-
self.report_workflow_execution()
|
|
118
|
-
try:
|
|
119
|
-
start_step_id = kwargs.get("start_step_id")
|
|
120
|
-
logging.info(f" starting workflow from step {start_step_id}")
|
|
121
|
-
|
|
122
|
-
self.run_workflow_steps(start_step_id=start_step_id)
|
|
123
|
-
if self.state == WorkflowState.RUNNING:
|
|
124
|
-
self.state = WorkflowState.COMPLETED
|
|
125
|
-
summary_metrics.labels(wf_name=self.name, name="wf_completed_counter").inc()
|
|
126
|
-
except Exception:
|
|
127
|
-
trace = traceback.format_exc()
|
|
128
|
-
self.last_error = str(trace)
|
|
129
|
-
self.state = WorkflowState.FAILED
|
|
130
|
-
logging.error(f"Workflow failed with error {trace}")
|
|
131
|
-
summary_metrics.labels(wf_name=self.name, name="wf_failed_counter").inc()
|
|
132
|
-
|
|
133
|
-
self.elapsed_time = time.perf_counter() - start_time
|
|
134
|
-
self.end_time = time.time()
|
|
135
|
-
timing_metrics.labels(wf_name=self.name, component="workflow", name="workflow").set(self.elapsed_time)
|
|
136
|
-
logging.info(f"Workflow completed in {self.elapsed_time} seconds")
|
|
137
|
-
self.report_workflow_execution()
|
|
138
|
-
if self.auto_workflow_cleanup:
|
|
139
|
-
self.cleanup_workflow_context()
|
|
140
|
-
return self.flow_message
|
|
141
|
-
|
|
142
|
-
def cleanup_workflow_context(self):
|
|
143
|
-
"""
|
|
144
|
-
Cleans up the workflow context by closing and deleting graph stores and other structure.
|
|
145
|
-
It's a for of garbage collection to avoid memory leaks or other issues related to open handlers
|
|
146
|
-
or allocated resources
|
|
147
|
-
|
|
148
|
-
"""
|
|
149
|
-
if "SolutionGraph" in self.data:
|
|
150
|
-
self.data["SolutionGraph"].graph.close()
|
|
151
|
-
if "SourceGraph" in self.data:
|
|
152
|
-
self.data["SourceGraph"].graph.close()
|
|
153
|
-
self.data.clear()
|
|
154
|
-
|
|
155
|
-
def get_transition_step(self, transitions: list[str] | None) -> list[WorkflowStepDefinition]:
|
|
156
|
-
return [stp for stp in self.workflow_steps if stp.id in transitions and stp.enabled] if transitions else []
|
|
157
|
-
|
|
158
|
-
def run_workflow_steps(self, start_step_id: str | None = None) -> str:
|
|
159
|
-
if not start_step_id:
|
|
160
|
-
trigger_steps = list(filter(lambda x: x.trigger, self.workflow_steps))
|
|
161
|
-
else:
|
|
162
|
-
trigger_steps = list(filter(lambda x: x.id == start_step_id, self.workflow_steps))
|
|
163
|
-
|
|
164
|
-
if not trigger_steps:
|
|
165
|
-
logging.error(f"Workflow {self.name} has no trigger steps or start step {start_step_id} not found")
|
|
166
|
-
return "Workflow has no trigger steps"
|
|
167
|
-
|
|
168
|
-
self.execution_log.append(
|
|
169
|
-
WorkflowStepEvent(
|
|
170
|
-
id=trigger_steps[0].id,
|
|
171
|
-
state=StepExecutionStatus.SUCCESS,
|
|
172
|
-
elapsed_time=0,
|
|
173
|
-
timestamp=utils.get_iso8601_timestamp_now_unaware(),
|
|
174
|
-
data=self.flow_message.payload if self.flow_message else None,
|
|
175
|
-
)
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
step: WorkflowStepDefinition = trigger_steps[0]
|
|
179
|
-
transition_steps = self.get_transition_step(step.transition_to)
|
|
180
|
-
|
|
181
|
-
if len(transition_steps) == 0:
|
|
182
|
-
logging.error(f"Workflow {self.name} has no transition steps from step {step.id}")
|
|
183
|
-
return "Workflow has no transition steps"
|
|
184
|
-
|
|
185
|
-
step = transition_steps[0]
|
|
186
|
-
|
|
187
|
-
for _ in range(
|
|
188
|
-
1000
|
|
189
|
-
): # Max 1000 steps in a workflow is a reasonable limit and protection against infinite loops
|
|
190
|
-
new_flow_msg = None
|
|
191
|
-
if step.enabled:
|
|
192
|
-
new_flow_msg = self.run_step(step)
|
|
193
|
-
else:
|
|
194
|
-
logging.info(f"Skipping step workflow step {step.id}")
|
|
195
|
-
self.execution_log.append(
|
|
196
|
-
WorkflowStepEvent(
|
|
197
|
-
id=step.id,
|
|
198
|
-
system_component_id=step.system_component_id,
|
|
199
|
-
state=StepExecutionStatus.SKIPPED,
|
|
200
|
-
elapsed_time=0,
|
|
201
|
-
timestamp=utils.get_iso8601_timestamp_now_unaware(),
|
|
202
|
-
error="",
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
steps = self.get_transition_step(step.transition_to)
|
|
207
|
-
|
|
208
|
-
if new_flow_msg:
|
|
209
|
-
self.flow_message = new_flow_msg
|
|
210
|
-
# If the step returned a new flow message, use it to mutate execution flow
|
|
211
|
-
if new_flow_msg.next_step_ids:
|
|
212
|
-
steps = self.get_transition_step(new_flow_msg.next_step_ids)
|
|
213
|
-
|
|
214
|
-
if len(steps) == 0:
|
|
215
|
-
break
|
|
216
|
-
step = steps[0]
|
|
217
|
-
logging.debug(f"Transitioning to step {step.id}")
|
|
218
|
-
|
|
219
|
-
return "ok"
|
|
220
|
-
|
|
221
|
-
def configure(self, config: Config):
|
|
222
|
-
raise NotImplementedError()
|
|
223
|
-
|
|
224
|
-
def copy(self) -> "BaseWorkflow":
|
|
225
|
-
"""Create a copy of the workflow"""
|
|
226
|
-
new_instance = self.__class__(self.name, self.cdf_client, self.steps_registry)
|
|
227
|
-
new_instance.workflow_steps = self.workflow_steps
|
|
228
|
-
new_instance.configs = self.configs
|
|
229
|
-
new_instance.set_task_builder(self.task_builder)
|
|
230
|
-
new_instance.set_default_dataset_id(self.default_dataset_id)
|
|
231
|
-
new_instance.set_storage_path("transformation_rules", self.rules_storage_path)
|
|
232
|
-
if self.data_store_path:
|
|
233
|
-
new_instance.set_storage_path("data_store", self.data_store_path)
|
|
234
|
-
return new_instance
|
|
235
|
-
|
|
236
|
-
def run_step(self, step: WorkflowStepDefinition) -> FlowMessage | None:
|
|
237
|
-
step_name = step.id
|
|
238
|
-
system_component_id = step.system_component_id
|
|
239
|
-
|
|
240
|
-
steps_metrics.labels(wf_name=self.name, step_name=step_name, name="step_started_counter").inc()
|
|
241
|
-
flow_message = self.flow_message
|
|
242
|
-
if self.state != WorkflowState.RUNNING:
|
|
243
|
-
logging.error(f"Workflow {self.name} is not running , step {step_name} is skipped")
|
|
244
|
-
return None
|
|
245
|
-
|
|
246
|
-
logging.info(f"Running step {step_name}")
|
|
247
|
-
self.current_step = step_name
|
|
248
|
-
start_time = time.perf_counter()
|
|
249
|
-
stop_time = start_time
|
|
250
|
-
step_execution_status = StepExecutionStatus.STARTED
|
|
251
|
-
self.execution_log.append(
|
|
252
|
-
WorkflowStepEvent(
|
|
253
|
-
id=step_name,
|
|
254
|
-
system_component_id=system_component_id,
|
|
255
|
-
state=step_execution_status,
|
|
256
|
-
elapsed_time=0,
|
|
257
|
-
timestamp=utils.get_iso8601_timestamp_now_unaware(),
|
|
258
|
-
error="",
|
|
259
|
-
)
|
|
260
|
-
)
|
|
261
|
-
output_text = ""
|
|
262
|
-
error_text = ""
|
|
263
|
-
new_flow_message = None
|
|
264
|
-
try:
|
|
265
|
-
if step.stype == StepType.PYSTEP:
|
|
266
|
-
# Most likely will be discontinued in the future in favor of std steps
|
|
267
|
-
if step.method and hasattr(self, step.method):
|
|
268
|
-
method = getattr(self, step.method)
|
|
269
|
-
else:
|
|
270
|
-
method_name = f"step_{step.id}"
|
|
271
|
-
if hasattr(self, method_name):
|
|
272
|
-
method = getattr(self, method_name)
|
|
273
|
-
else:
|
|
274
|
-
logging.error(f"Workflow step {step.id} has no method {method_name}")
|
|
275
|
-
raise Exception(f"Workflow step {step.id} has no method {method_name}")
|
|
276
|
-
|
|
277
|
-
@retry_decorator(
|
|
278
|
-
max_retries=step.max_retries,
|
|
279
|
-
retry_delay=step.retry_delay,
|
|
280
|
-
component_name=f"wf step runner , step.id = {step.id}",
|
|
281
|
-
)
|
|
282
|
-
def method_runner():
|
|
283
|
-
return method(flow_message)
|
|
284
|
-
|
|
285
|
-
new_flow_message = method_runner()
|
|
286
|
-
elif step.stype == StepType.STD_STEP:
|
|
287
|
-
if self.steps_registry is None:
|
|
288
|
-
raise Exception(
|
|
289
|
-
f"Workflow step {step.id} can't be executed.Step registry is not configured or \
|
|
290
|
-
not set as parameter in BaseWorkflow constructor"
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
@retry_decorator(
|
|
294
|
-
max_retries=step.max_retries,
|
|
295
|
-
retry_delay=step.retry_delay,
|
|
296
|
-
component_name=f"wf step runner , step.id = {step.id}",
|
|
297
|
-
)
|
|
298
|
-
def method_runner():
|
|
299
|
-
return self.steps_registry.run_step(
|
|
300
|
-
step.method,
|
|
301
|
-
self.data,
|
|
302
|
-
metrics=self.metrics,
|
|
303
|
-
workflow_configs=self.get_configs(),
|
|
304
|
-
step_configs=step.configs,
|
|
305
|
-
workflow_id=self.name,
|
|
306
|
-
workflow_run_id=self.run_id,
|
|
307
|
-
step_complex_configs=step.complex_configs,
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
output = method_runner()
|
|
311
|
-
if output is not None:
|
|
312
|
-
outputs = output if isinstance(output, tuple) else (output,)
|
|
313
|
-
for out_obj in outputs:
|
|
314
|
-
if isinstance(out_obj, FlowMessage):
|
|
315
|
-
new_flow_message = out_obj
|
|
316
|
-
elif isinstance(out_obj, DataContract):
|
|
317
|
-
self.data[type(out_obj).__name__] = out_obj
|
|
318
|
-
else:
|
|
319
|
-
raise WorkflowStepOutputError(step_type=type(out_obj).__name__)
|
|
320
|
-
|
|
321
|
-
elif step.stype == StepType.START_WORKFLOW_TASK_STEP:
|
|
322
|
-
if self.task_builder:
|
|
323
|
-
sync_str = step.params.get("sync", "false")
|
|
324
|
-
sync = sync_str.lower() == "true" or sync_str == "1"
|
|
325
|
-
start_status = self.task_builder.start_workflow_task(
|
|
326
|
-
workflow_name=step.params.get("workflow_name", ""), sync=sync, flow_message=self.flow_message
|
|
327
|
-
)
|
|
328
|
-
if start_status.is_success and start_status.workflow_instance.state == WorkflowState.COMPLETED:
|
|
329
|
-
# It only works with sync = true
|
|
330
|
-
new_flow_message = start_status.workflow_instance.flow_message
|
|
331
|
-
else:
|
|
332
|
-
logging.error(f"Workflow step {step.id} failed to start workflow task")
|
|
333
|
-
if start_status.is_success:
|
|
334
|
-
raise WorkflowStartException(start_status.workflow_instance.last_error)
|
|
335
|
-
else:
|
|
336
|
-
raise WorkflowStartException(start_status.status_text)
|
|
337
|
-
|
|
338
|
-
else:
|
|
339
|
-
logging.error(f"Workflow step {step.id} has no task builder")
|
|
340
|
-
raise Exception(f"Workflow step {step.id} has no task builder")
|
|
341
|
-
elif step.stype == StepType.WAIT_FOR_EVENT:
|
|
342
|
-
# Pause workflow execution until event is received
|
|
343
|
-
if self.state != WorkflowState.RUNNING:
|
|
344
|
-
logging.error(f"Workflow {self.name} is not running , step {step_name} is skipped")
|
|
345
|
-
raise Exception(f"Workflow {self.name} is not running , step {step_name} is skipped")
|
|
346
|
-
self.state = WorkflowState.RUNNING_WAITING
|
|
347
|
-
timeout = 3000000.0
|
|
348
|
-
if step.params.get("wait_timeout"):
|
|
349
|
-
timeout = float(step.params["wait_timeout"])
|
|
350
|
-
# reporting workflow execution before waiting for event
|
|
351
|
-
logging.info(f"Workflow {self.name} is waiting for event")
|
|
352
|
-
self.resume_event.wait(timeout=timeout)
|
|
353
|
-
logging.info(f"Workflow {self.name} resumed after event")
|
|
354
|
-
self.state = WorkflowState.RUNNING
|
|
355
|
-
self.resume_event.clear()
|
|
356
|
-
else:
|
|
357
|
-
logging.error(f"Workflow step {step.id} has unsupported step type {step.stype}")
|
|
358
|
-
|
|
359
|
-
stop_time = time.perf_counter()
|
|
360
|
-
elapsed_time = stop_time - start_time
|
|
361
|
-
logging.info(f"Step {step_name} completed in {elapsed_time} seconds with ")
|
|
362
|
-
|
|
363
|
-
if new_flow_message:
|
|
364
|
-
logging.info(f"Step {self.name} completed with status {new_flow_message.step_execution_status}")
|
|
365
|
-
error_text = new_flow_message.error_text
|
|
366
|
-
output_text = new_flow_message.output_text
|
|
367
|
-
if new_flow_message.step_execution_status == StepExecutionStatus.ABORT_AND_FAIL:
|
|
368
|
-
new_flow_message.step_execution_status = StepExecutionStatus.FAILED
|
|
369
|
-
self.state = WorkflowState.FAILED
|
|
370
|
-
|
|
371
|
-
step_execution_status = (
|
|
372
|
-
StepExecutionStatus.SUCCESS
|
|
373
|
-
if new_flow_message.step_execution_status == StepExecutionStatus.UNKNOWN
|
|
374
|
-
else new_flow_message.step_execution_status
|
|
375
|
-
)
|
|
376
|
-
else:
|
|
377
|
-
step_execution_status = StepExecutionStatus.SUCCESS
|
|
378
|
-
steps_metrics.labels(wf_name=self.name, step_name=step_name, name="completed_counter").inc()
|
|
379
|
-
timing_metrics.labels(wf_name=self.name, component="step", name=step_name).set(elapsed_time)
|
|
380
|
-
except Exception:
|
|
381
|
-
self.state = WorkflowState.FAILED
|
|
382
|
-
step_execution_status = StepExecutionStatus.FAILED
|
|
383
|
-
trace = traceback.format_exc()
|
|
384
|
-
elapsed_time = stop_time - start_time
|
|
385
|
-
logging.error(f"Step {step_name} failed with error : {trace}")
|
|
386
|
-
error_text = str(trace)
|
|
387
|
-
self.last_error = error_text
|
|
388
|
-
traceback.print_exc()
|
|
389
|
-
steps_metrics.labels(wf_name=self.name, step_name=step_name, name="failed_counter").inc()
|
|
390
|
-
|
|
391
|
-
self.execution_log.append(
|
|
392
|
-
WorkflowStepEvent(
|
|
393
|
-
id=step_name,
|
|
394
|
-
system_component_id=system_component_id,
|
|
395
|
-
state=step_execution_status,
|
|
396
|
-
elapsed_time=round(elapsed_time, 3),
|
|
397
|
-
timestamp=utils.get_iso8601_timestamp_now_unaware(),
|
|
398
|
-
error=error_text,
|
|
399
|
-
output_text=output_text,
|
|
400
|
-
data=new_flow_message.payload if new_flow_message else None,
|
|
401
|
-
)
|
|
402
|
-
)
|
|
403
|
-
if new_flow_message:
|
|
404
|
-
self.flow_message = new_flow_message
|
|
405
|
-
|
|
406
|
-
self.report_step_execution()
|
|
407
|
-
return new_flow_message
|
|
408
|
-
|
|
409
|
-
def resume_workflow(self, flow_message: FlowMessage, step_id: str):
|
|
410
|
-
if step_id and self.current_step != step_id:
|
|
411
|
-
logging.error(f"Workflow {self.name} is not in step {step_id} , resume is skipped")
|
|
412
|
-
return
|
|
413
|
-
self.flow_message = flow_message
|
|
414
|
-
self.execution_log.append(
|
|
415
|
-
WorkflowStepEvent(
|
|
416
|
-
id=step_id,
|
|
417
|
-
state=StepExecutionStatus.SUCCESS,
|
|
418
|
-
elapsed_time=0,
|
|
419
|
-
timestamp=utils.get_iso8601_timestamp_now_unaware(),
|
|
420
|
-
data=self.flow_message.payload,
|
|
421
|
-
)
|
|
422
|
-
)
|
|
423
|
-
self.resume_event.set()
|
|
424
|
-
|
|
425
|
-
def report_step_execution(self):
|
|
426
|
-
pass
|
|
427
|
-
|
|
428
|
-
def report_workflow_execution(self):
|
|
429
|
-
reporting_type = "all_enabled"
|
|
430
|
-
if config_item := self.get_config_item("system.execution_reporting_type"):
|
|
431
|
-
reporting_type = config_item.value
|
|
432
|
-
if reporting_type == "all_disabled":
|
|
433
|
-
return
|
|
434
|
-
if self.cdf_store:
|
|
435
|
-
logging.debug("Reporting workflow execution to CDF")
|
|
436
|
-
try:
|
|
437
|
-
self.cdf_store.report_workflow_execution_to_cdf(self.get_state())
|
|
438
|
-
except Exception:
|
|
439
|
-
logging.error("Failed to report workflow execution to CDF")
|
|
440
|
-
traceback.print_exc()
|
|
441
|
-
|
|
442
|
-
def get_state(self):
|
|
443
|
-
return WorkflowFullStateReport(
|
|
444
|
-
workflow_name=self.name,
|
|
445
|
-
state=self.state,
|
|
446
|
-
run_id=self.run_id,
|
|
447
|
-
start_time=self.start_time,
|
|
448
|
-
end_time=self.end_time,
|
|
449
|
-
elapsed_time=round(self.elapsed_time, 3),
|
|
450
|
-
last_error=self.last_error,
|
|
451
|
-
execution_log=self.execution_log,
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
def set_cognite_client(self, client: CogniteClient):
|
|
455
|
-
self.cdf_client = client
|
|
456
|
-
|
|
457
|
-
def get_workflow_definition(self):
|
|
458
|
-
return WorkflowDefinition(
|
|
459
|
-
name=self.name,
|
|
460
|
-
steps=self.workflow_steps,
|
|
461
|
-
system_components=self.workflow_system_components,
|
|
462
|
-
configs=self.configs,
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
def add_step(self, step: WorkflowStepDefinition):
|
|
466
|
-
self.workflow_steps.append(step)
|
|
467
|
-
|
|
468
|
-
def enable_step(self, step_id: str, enabled: bool = True):
|
|
469
|
-
"""Enable or disable step in workflow. Primarily used for tests"""
|
|
470
|
-
for step in self.workflow_steps:
|
|
471
|
-
if step.id == step_id:
|
|
472
|
-
step.enabled = enabled
|
|
473
|
-
|
|
474
|
-
def add_system_component(self, system_components: WorkflowSystemComponent):
|
|
475
|
-
self.workflow_system_components.append(system_components)
|
|
476
|
-
|
|
477
|
-
def serialize_workflow(self, output_format: str = "json", custom_implementation_module: str | None = None) -> str:
|
|
478
|
-
workflow_definitions = WorkflowDefinition(
|
|
479
|
-
name=self.name,
|
|
480
|
-
steps=self.workflow_steps,
|
|
481
|
-
system_components=self.workflow_system_components,
|
|
482
|
-
configs=self.configs,
|
|
483
|
-
implementation_module=custom_implementation_module,
|
|
484
|
-
)
|
|
485
|
-
if output_format == "json":
|
|
486
|
-
return json.dumps(workflow_definitions.model_dump(), indent=4)
|
|
487
|
-
elif output_format == "yaml":
|
|
488
|
-
return yaml.dump(workflow_definitions.model_dump(), indent=4)
|
|
489
|
-
else:
|
|
490
|
-
raise NotImplementedError(f"Output format {output_format} is not supported.")
|
|
491
|
-
|
|
492
|
-
@classmethod
|
|
493
|
-
def deserialize_definition(cls, json_string: str, output_format: str = "json") -> WorkflowDefinition:
|
|
494
|
-
if output_format == "json":
|
|
495
|
-
workflow_definitions = WorkflowDefinition.model_validate(json.loads(json_string))
|
|
496
|
-
elif output_format == "yaml":
|
|
497
|
-
workflow_definitions = WorkflowDefinition.model_validate(yaml.load(json_string, Loader=yaml.Loader))
|
|
498
|
-
else:
|
|
499
|
-
raise NotImplementedError(f"Output format {output_format} is not supported.")
|
|
500
|
-
return workflow_definitions
|
|
501
|
-
|
|
502
|
-
def set_definition(self, workflow_definition: WorkflowDefinition):
|
|
503
|
-
self.workflow_steps = workflow_definition.steps
|
|
504
|
-
self.workflow_system_components = workflow_definition.system_components
|
|
505
|
-
self.configs = workflow_definition.configs
|
|
506
|
-
|
|
507
|
-
def set_storage_path(self, storage_type: str, storage_path: str | Path | None) -> None:
|
|
508
|
-
if storage_path is None:
|
|
509
|
-
return None
|
|
510
|
-
if storage_type == "transformation_rules":
|
|
511
|
-
self.rules_storage_path = Path(storage_path)
|
|
512
|
-
if self.default_dataset_id is None:
|
|
513
|
-
raise WorkflowConfigurationNotSetError("default_dataset_id")
|
|
514
|
-
self.cdf_store = cdf_store.CdfStore(
|
|
515
|
-
self.cdf_client, data_set_id=self.default_dataset_id, rules_storage_path=self.rules_storage_path
|
|
516
|
-
)
|
|
517
|
-
elif storage_type == "data_store":
|
|
518
|
-
self.data_store_path = Path(storage_path)
|
|
519
|
-
self.user_steps_path = Path(storage_path, "steps")
|
|
520
|
-
|
|
521
|
-
def set_task_builder(self, task_builder: WorkflowTaskBuilder | None):
|
|
522
|
-
self.task_builder = task_builder
|
|
523
|
-
|
|
524
|
-
def get_config_item(self, config_name: str) -> WorkflowConfigItem | None:
|
|
525
|
-
return next((item for item in self.configs if item.name == config_name), None)
|
|
526
|
-
|
|
527
|
-
def get_config_group_values_by_name(
|
|
528
|
-
self, group_name: str, remove_group_prefix: bool = True
|
|
529
|
-
) -> dict[str, str | None]:
|
|
530
|
-
return {
|
|
531
|
-
(item.name.removeprefix(item.group) if remove_group_prefix else item.name): item.value
|
|
532
|
-
for item in self.configs
|
|
533
|
-
if item.group == group_name
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
def get_config_item_value(self, config_name: str, default_value=None) -> str | None:
|
|
537
|
-
return config.value if (config := self.get_config_item(config_name)) else default_value
|
|
538
|
-
|
|
539
|
-
def set_default_dataset_id(self, default_dataset_id: int | None):
|
|
540
|
-
self.default_dataset_id = default_dataset_id
|
|
541
|
-
self.cdf_store = (
|
|
542
|
-
cdf_store.CdfStore(self.cdf_client, data_set_id=self.default_dataset_id)
|
|
543
|
-
if self.default_dataset_id
|
|
544
|
-
else None
|
|
545
|
-
)
|
|
546
|
-
|
|
547
|
-
def set_cdf_client_config(self, cdf_client_config: ClientConfig):
|
|
548
|
-
"""Set the CogniteClient configuration to be used by the workflow to create new CogniteClient instances."""
|
|
549
|
-
self.cdf_client_config = cdf_client_config
|
|
550
|
-
|
|
551
|
-
def get_new_cdf_client(self):
|
|
552
|
-
"""Get a new CogniteClient instance with the same configuration as the one used by the workflow .
|
|
553
|
-
Should be used from workflow steps to avoid sharing the same client instance between steps or
|
|
554
|
-
reset reference to old client instance.
|
|
555
|
-
Returns: CogniteClient
|
|
556
|
-
"""
|
|
557
|
-
return CogniteClient(self.cdf_client_config)
|
|
558
|
-
|
|
559
|
-
def get_step_by_id(self, step_id: str) -> WorkflowStepDefinition | None:
|
|
560
|
-
return next((step for step in self.workflow_steps if step.id == step_id), None)
|
|
561
|
-
|
|
562
|
-
def get_trigger_step(self, step_id: str | None = None) -> WorkflowStepDefinition | None:
|
|
563
|
-
if step_id:
|
|
564
|
-
return next((step for step in self.workflow_steps if step.id == step_id and step.enabled), None)
|
|
565
|
-
else:
|
|
566
|
-
return next((step for step in self.workflow_steps if step.trigger and step.enabled), None)
|
|
567
|
-
|
|
568
|
-
def get_context(self) -> dict[str, DataContract | FlowMessage | CdfStore | CogniteClient | None]:
|
|
569
|
-
return self.data
|
|
570
|
-
|
|
571
|
-
def get_configs(self) -> WorkflowConfigs:
|
|
572
|
-
return WorkflowConfigs(configs=self.configs)
|
|
573
|
-
|
|
574
|
-
def get_list_of_workflow_artifacts(self) -> list[Path]:
|
|
575
|
-
# create a list of all files under the data store path
|
|
576
|
-
|
|
577
|
-
file_list: list[Path] = []
|
|
578
|
-
if self.data_store_path is None:
|
|
579
|
-
raise WorkflowConfigurationNotSetError("data_store_path")
|
|
580
|
-
workflow_data_path = Path(self.data_store_path) / "workflows" / self.name
|
|
581
|
-
try:
|
|
582
|
-
for root, _dirs, files in os.walk(workflow_data_path):
|
|
583
|
-
for file in files:
|
|
584
|
-
full_path = Path(root) / file
|
|
585
|
-
file_list.append(Path(full_path).relative_to(workflow_data_path))
|
|
586
|
-
return file_list
|
|
587
|
-
except OSError as e:
|
|
588
|
-
# Handle exceptions like directory not found, permission denied, etc.
|
|
589
|
-
logging.error(f"Error while listing workflow artifacts for {self.name} : {e}")
|
|
590
|
-
return []
|