I can run simple workflow

This commit is contained in:
2025-07-06 22:53:18 +02:00
parent e183584f52
commit 14be07720f
9 changed files with 282 additions and 8 deletions

View File

@@ -184,11 +184,13 @@ class WorkflowDesigner(BaseComponent):
WorkflowPlayer,
settings_manager=self._settings_manager,
tabs_manager=self.tabs_manager,
player_settings=WorkflowsPlayerSettings(workflow_name),
player_settings=WorkflowsPlayerSettings(workflow_name,
list(self._state.components.values())),
boundaries=boundaries)
player.run()
self.tabs_manager.add_tab(f"Workflow {workflow_name}", player, player.key)
# player.start(self._state.selected_component_id)
return self.tabs_manager.refresh()
def on_processor_details_event(self, component_id: str, event_name: str, details: dict):
@@ -436,9 +438,9 @@ class WorkflowDesigner(BaseComponent):
Fieldset(
Legend("Presenter", cls="fieldset-legend"),
Input(type="text",
name="presenter",
value=component.properties.get("filter", ""),
placeholder="Enter filter expression",
name="columns",
value=component.properties.get("columns", ""),
placeholder="Columns to display, separated by comma",
cls="input w-full"),
P("Comma separated list of columns to display. Use * to display all columns, source=dest to rename columns."),
cls="fieldset bg-base-200 border-base-300 rounded-box border p-4"

View File

@@ -1,12 +1,14 @@
import pandas as pd
from fasthtml.components import *
from components.BaseComponent import BaseComponent
from components.datagrid_new.components.DataGrid import DataGrid
from components.datagrid_new.settings import DataGridSettings
from components.workflows.commands import WorkflowPlayerCommandManager
from components.workflows.constants import WORKFLOW_PLAYER_INSTANCE_ID
from components.workflows.constants import WORKFLOW_PLAYER_INSTANCE_ID, ProcessorTypes
from components.workflows.db_management import WorkflowsPlayerSettings
from core.utils import get_unique_id, make_safe_id
from workflow.engine import WorkflowEngine, TableDataProducer, DefaultDataPresenter
grid_settings = DataGridSettings(
header_visible=True,
@@ -36,6 +38,20 @@ class WorkflowPlayer(BaseComponent):
grid_settings=grid_settings,
boundaries=boundaries)
def run(self):
engine = WorkflowEngine()
for component in self._player_settings.components:
if component.type == ProcessorTypes.Producer and component.properties["processor_name"] == "Repository":
engine.add_processor(TableDataProducer(self._session, self._settings_manager, component.properties["repository"], component.properties["table"]))
elif component.type == ProcessorTypes.Presenter and component.properties["processor_name"] == "Default":
engine.add_processor(DefaultDataPresenter(component.properties["columns"]))
res = engine.run_to_list()
data = [row.as_dict() for row in res]
df = pd.DataFrame(data)
self._datagrid.init_from_dataframe(df)
def __ft__(self):
return Div(
self._datagrid,

View File

@@ -44,6 +44,7 @@ class WorkflowsDesignerState:
@dataclass
class WorkflowsPlayerSettings:
workflow_name: str = "No Name"
components: list[WorkflowComponent] = None
@dataclass

70
src/core/Expando.py Normal file
View File

@@ -0,0 +1,70 @@
class Expando:
"""
Readonly dynamic class that eases the access to attributes and sub attributes
It is initialized with a dict
You can then access the property using dot '.' (ex. obj.prop1.prop2)
"""
def __init__(self, props):
self._props = props
def __getattr__(self, item):
if item not in self._props:
raise AttributeError(item)
current = self._props[item]
return Expando(current) if isinstance(current, dict) else current
def __setitem__(self, key, value):
self._props[key] = value
def get(self, path):
"""
returns the value, from a string with represents the path
:param path:
:return:
"""
current = self._props
for attr in path.split("."):
if isinstance(current, list):
temp = []
for value in current:
if value and attr in value:
temp.append(value[attr])
current = temp
else:
if current is None or attr not in current:
return None
current = current[attr]
return current
def as_dict(self):
"""
Return the information as a dictionary
:return:
"""
return self._props.copy()
def to_dict(self, mappings: dict) -> dict:
return {prop_name: self.get(path) for path, prop_name in mappings.items() if prop_name is not None}
def __repr__(self):
if "key" in self._props:
return f"Expando(key={self._props["key"]})"
props_as_str = str(self._props)
if len(props_as_str) > 50:
props_as_str = props_as_str[:50] + "..."
return f"Expando({props_as_str})"
def __eq__(self, other):
if not isinstance(other, Expando):
return False
return self._props == other._props
def __hash__(self):
return hash(tuple(sorted(self._props.items())))

View File

@@ -87,7 +87,7 @@ class MemoryDbEngine:
obj.update(items)
def exists(self, user_id: str, entry: str):
return user_id in entry and entry in self.db[user_id]
return user_id in self.db and entry in self.db[user_id]
class SettingsManager:

View File

@@ -1,6 +1,7 @@
from dataclasses import is_dataclass
from components.datagrid_new.db_management import DataGridDbManager
from core.Expando import Expando
class DataHelper:
@@ -16,6 +17,8 @@ class DataHelper:
if object_type:
if is_dataclass(object_type):
return [object_type(**row) for row in dataframe.to_dict(orient="records")]
elif object_type is Expando:
return [Expando(row) for row in dataframe.to_dict(orient="records")]
else:
raise ValueError("object_type must be a dataclass type")

View File

@@ -1,6 +1,9 @@
from abc import ABC, abstractmethod
from typing import Any, Generator
from core.Expando import Expando
from utils.Datahelper import DataHelper
class DataProcessor(ABC):
"""Base class for all data processing components."""
@@ -47,6 +50,45 @@ class DataPresenter(DataProcessor):
yield self.present(data)
class TableDataProducer(DataProducer):
"""Base class for data producers that emit data from a repository."""
def __init__(self, session, settings_manager, repository_name, table_name):
self._session = session
self.settings_manager = settings_manager
self.repository_name = repository_name
self.table_name = table_name
def emit(self, data: Any = None) -> Generator[Any, None, None]:
yield from DataHelper.get(self._session, self.settings_manager, self.repository_name, self.table_name, Expando)
class DefaultDataPresenter(DataPresenter):
"""Default data presenter that returns the input data unchanged."""
def __init__(self, columns_as_str: str):
super().__init__()
if not columns_as_str or columns_as_str == "*":
self.mappings = None
else:
self.mappings = {}
temp_mappings = [col.strip() for col in columns_as_str.split(",")]
for mapping in temp_mappings:
if "=" in mapping:
key, value = mapping.split("=")
self.mappings[key] = value
else:
self.mappings[mapping] = mapping
def present(self, data: Any) -> Any:
if self.mappings is None:
return data
return Expando(data.to_dict(self.mappings))
class WorkflowEngine:
"""Orchestrates the data processing pipeline using generators."""
@@ -93,4 +135,3 @@ class WorkflowEngine:
Use this method when you need all results at once.
"""
return list(self.run())