First version of DefaultDataFilter
This commit is contained in:
@@ -7,8 +7,9 @@ from components.datagrid_new.settings import DataGridSettings
|
||||
from components.workflows.commands import WorkflowPlayerCommandManager
|
||||
from components.workflows.constants import WORKFLOW_PLAYER_INSTANCE_ID, ProcessorTypes
|
||||
from components.workflows.db_management import WorkflowsPlayerSettings
|
||||
from core.instance_manager import InstanceManager
|
||||
from core.utils import get_unique_id, make_safe_id
|
||||
from workflow.engine import WorkflowEngine, TableDataProducer, DefaultDataPresenter
|
||||
from workflow.engine import WorkflowEngine, TableDataProducer, DefaultDataPresenter, DefaultDataFilter
|
||||
|
||||
grid_settings = DataGridSettings(
|
||||
header_visible=True,
|
||||
@@ -32,17 +33,22 @@ class WorkflowPlayer(BaseComponent):
|
||||
self._player_settings = player_settings
|
||||
self._boundaries = boundaries
|
||||
self.commands = WorkflowPlayerCommandManager(self)
|
||||
self._datagrid = DataGrid(self._session,
|
||||
DataGrid.create_component_id(session),
|
||||
self.key,
|
||||
grid_settings=grid_settings,
|
||||
boundaries=boundaries)
|
||||
self._datagrid = InstanceManager.get(self._session,
|
||||
DataGrid.create_component_id(session),
|
||||
DataGrid,
|
||||
key=self.key,
|
||||
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"]))
|
||||
engine.add_processor(
|
||||
TableDataProducer(self._session, self._settings_manager, component.properties["repository"],
|
||||
component.properties["table"]))
|
||||
elif component.type == ProcessorTypes.Filter and component.properties["processor_name"] == "Default":
|
||||
engine.add_processor(DefaultDataFilter(component.properties["filter"]))
|
||||
elif component.type == ProcessorTypes.Presenter and component.properties["processor_name"] == "Default":
|
||||
engine.add_processor(DefaultDataPresenter(component.properties["columns"]))
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ class Expando:
|
||||
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 __hasattr__(self, item):
|
||||
return item in self._props
|
||||
|
||||
def __repr__(self):
|
||||
if "key" in self._props:
|
||||
return f"Expando(key={self._props["key"]})"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ast
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib
|
||||
@@ -417,3 +418,50 @@ def split_host_port(url):
|
||||
port = None
|
||||
|
||||
return host, port
|
||||
|
||||
|
||||
class UnreferencedNamesVisitor(ast.NodeVisitor):
|
||||
"""
|
||||
Try to find symbols that will be requested by the ast
|
||||
It can be variable names, but also function names
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.names = set()
|
||||
|
||||
def get_names(self, node):
|
||||
self.visit(node)
|
||||
return self.names
|
||||
|
||||
def visit_Name(self, node):
|
||||
self.names.add(node.id)
|
||||
|
||||
def visit_For(self, node: ast.For):
|
||||
self.visit_selected(node, ["body", "orelse"])
|
||||
|
||||
def visit_selected(self, node, to_visit):
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for field in to_visit:
|
||||
value = getattr(node, field)
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
if isinstance(item, ast.AST):
|
||||
self.visit(item)
|
||||
elif isinstance(value, ast.AST):
|
||||
self.visit(value)
|
||||
|
||||
def visit_Call(self, node: ast.Call):
|
||||
self.visit_selected(node, ["args", "keywords"])
|
||||
|
||||
def visit_keyword(self, node: ast.keyword):
|
||||
"""
|
||||
Keywords are parameters that are defined with a double star (**) in function / method definition
|
||||
ex: def fun(positional, *args, **keywords)
|
||||
:param node:
|
||||
:type node:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
self.names.add(node.arg)
|
||||
self.visit_selected(node, ["value"])
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import ast
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Generator
|
||||
|
||||
from core.Expando import Expando
|
||||
from core.utils import UnreferencedNamesVisitor
|
||||
from utils.Datahelper import DataHelper
|
||||
|
||||
|
||||
@@ -88,11 +90,21 @@ class DefaultDataPresenter(DataPresenter):
|
||||
|
||||
return Expando(data.to_dict(self.mappings))
|
||||
|
||||
|
||||
class DefaultDataFilter(DataFilter):
|
||||
def __init__(self, filter_expression: str):
|
||||
super().__init__()
|
||||
self.filter_expression = filter_expression
|
||||
self._ast_tree = ast.parse(filter_expression, "<user input>", 'eval')
|
||||
self._compiled = compile(self._ast_tree, "<string>", "eval")
|
||||
visitor = UnreferencedNamesVisitor()
|
||||
self._unreferenced_names = visitor.get_names(self._ast_tree)
|
||||
|
||||
"""Default data filter that returns True for all data items."""
|
||||
|
||||
def filter(self, data: Any) -> bool:
|
||||
return True
|
||||
my_locals = {name: data.get(name) for name in self._unreferenced_names if hasattr(data, name)}
|
||||
return eval(self._compiled, globals(), my_locals)
|
||||
|
||||
|
||||
class WorkflowEngine:
|
||||
|
||||
Reference in New Issue
Block a user