mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-26 17:06:22 +02:00
release/v1.4 -> master (#548)
This commit is contained in:
parent
3ec2cd54f9
commit
2bd68ed7f4
94 changed files with 8571 additions and 1740 deletions
|
|
@ -10,6 +10,95 @@ class FlowConfig:
|
|||
def __init__(self, config):
|
||||
|
||||
self.config = config
|
||||
# Cache for parameter type definitions to avoid repeated lookups
|
||||
self.param_type_cache = {}
|
||||
|
||||
async def resolve_parameters(self, flow_class, user_params):
|
||||
"""
|
||||
Resolve parameters by merging user-provided values with defaults.
|
||||
|
||||
Args:
|
||||
flow_class: The flow class definition dict
|
||||
user_params: User-provided parameters dict (may be None or empty)
|
||||
|
||||
Returns:
|
||||
Complete parameter dict with user values and defaults merged (all values as strings)
|
||||
"""
|
||||
# If the flow class has no parameters section, return user params as-is (stringified)
|
||||
if "parameters" not in flow_class:
|
||||
if not user_params:
|
||||
return {}
|
||||
# Ensure all values are strings
|
||||
return {k: str(v) for k, v in user_params.items()}
|
||||
|
||||
resolved = {}
|
||||
flow_params = flow_class["parameters"]
|
||||
user_params = user_params if user_params else {}
|
||||
|
||||
# First pass: resolve parameters with explicit values or defaults
|
||||
for param_name, param_meta in flow_params.items():
|
||||
# Check if user provided a value
|
||||
if param_name in user_params:
|
||||
# Store as string
|
||||
resolved[param_name] = str(user_params[param_name])
|
||||
else:
|
||||
# Look up the parameter type definition
|
||||
param_type = param_meta.get("type")
|
||||
if param_type:
|
||||
# Check cache first
|
||||
if param_type not in self.param_type_cache:
|
||||
try:
|
||||
# Fetch parameter type definition from config store
|
||||
type_def = await self.config.get("parameter-types").get(param_type)
|
||||
if type_def:
|
||||
self.param_type_cache[param_type] = json.loads(type_def)
|
||||
else:
|
||||
logger.warning(f"Parameter type '{param_type}' not found in config")
|
||||
self.param_type_cache[param_type] = {}
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching parameter type '{param_type}': {e}")
|
||||
self.param_type_cache[param_type] = {}
|
||||
|
||||
# Apply default from type definition (as string)
|
||||
type_def = self.param_type_cache[param_type]
|
||||
if "default" in type_def:
|
||||
default_value = type_def["default"]
|
||||
# Convert to string based on type
|
||||
if isinstance(default_value, bool):
|
||||
resolved[param_name] = "true" if default_value else "false"
|
||||
else:
|
||||
resolved[param_name] = str(default_value)
|
||||
elif type_def.get("required", False):
|
||||
# Required parameter with no default and no user value
|
||||
raise RuntimeError(f"Required parameter '{param_name}' not provided and has no default")
|
||||
|
||||
# Second pass: handle controlled-by relationships
|
||||
for param_name, param_meta in flow_params.items():
|
||||
if param_name not in resolved and "controlled-by" in param_meta:
|
||||
controller = param_meta["controlled-by"]
|
||||
if controller in resolved:
|
||||
# Inherit value from controlling parameter (already a string)
|
||||
resolved[param_name] = resolved[controller]
|
||||
else:
|
||||
# Controller has no value, try to get default from type definition
|
||||
param_type = param_meta.get("type")
|
||||
if param_type and param_type in self.param_type_cache:
|
||||
type_def = self.param_type_cache[param_type]
|
||||
if "default" in type_def:
|
||||
default_value = type_def["default"]
|
||||
# Convert to string based on type
|
||||
if isinstance(default_value, bool):
|
||||
resolved[param_name] = "true" if default_value else "false"
|
||||
else:
|
||||
resolved[param_name] = str(default_value)
|
||||
|
||||
# Include any extra parameters from user that weren't in flow class definition
|
||||
# This allows for forward compatibility (ensure they're strings)
|
||||
for key, value in user_params.items():
|
||||
if key not in resolved:
|
||||
resolved[key] = str(value)
|
||||
|
||||
return resolved
|
||||
|
||||
async def handle_list_classes(self, msg):
|
||||
|
||||
|
|
@ -68,11 +157,14 @@ class FlowConfig:
|
|||
|
||||
async def handle_get_flow(self, msg):
|
||||
|
||||
flow = await self.config.get("flows").get(msg.flow_id)
|
||||
flow_data = await self.config.get("flows").get(msg.flow_id)
|
||||
flow = json.loads(flow_data)
|
||||
|
||||
return FlowResponse(
|
||||
error = None,
|
||||
flow = flow,
|
||||
flow = flow_data,
|
||||
description = flow.get("description", ""),
|
||||
parameters = flow.get("parameters", {}),
|
||||
)
|
||||
|
||||
async def handle_start_flow(self, msg):
|
||||
|
|
@ -83,45 +175,65 @@ class FlowConfig:
|
|||
if msg.flow_id is None:
|
||||
raise RuntimeError("No flow ID")
|
||||
|
||||
if msg.flow_id in await self.config.get("flows").values():
|
||||
if msg.flow_id in await self.config.get("flows").keys():
|
||||
raise RuntimeError("Flow already exists")
|
||||
|
||||
if msg.description is None:
|
||||
raise RuntimeError("No description")
|
||||
|
||||
if msg.class_name not in await self.config.get("flow-classes").values():
|
||||
if msg.class_name not in await self.config.get("flow-classes").keys():
|
||||
raise RuntimeError("Class does not exist")
|
||||
|
||||
def repl_template(tmp):
|
||||
return tmp.replace(
|
||||
"{class}", msg.class_name
|
||||
).replace(
|
||||
"{id}", msg.flow_id
|
||||
)
|
||||
|
||||
cls = json.loads(
|
||||
await self.config.get("flow-classes").get(msg.class_name)
|
||||
)
|
||||
|
||||
# Resolve parameters by merging user-provided values with defaults
|
||||
user_params = msg.parameters if msg.parameters else {}
|
||||
parameters = await self.resolve_parameters(cls, user_params)
|
||||
|
||||
# Log the resolved parameters for debugging
|
||||
logger.debug(f"User provided parameters: {user_params}")
|
||||
logger.debug(f"Resolved parameters (with defaults): {parameters}")
|
||||
|
||||
# Apply parameter substitution to template replacement function
|
||||
def repl_template_with_params(tmp):
|
||||
|
||||
result = tmp.replace(
|
||||
"{class}", msg.class_name
|
||||
).replace(
|
||||
"{id}", msg.flow_id
|
||||
)
|
||||
# Apply parameter substitutions
|
||||
for param_name, param_value in parameters.items():
|
||||
result = result.replace(f"{{{param_name}}}", str(param_value))
|
||||
|
||||
return result
|
||||
|
||||
for kind in ("class", "flow"):
|
||||
|
||||
for k, v in cls[kind].items():
|
||||
|
||||
processor, variant = k.split(":", 1)
|
||||
|
||||
variant = repl_template(variant)
|
||||
variant = repl_template_with_params(variant)
|
||||
|
||||
v = {
|
||||
repl_template(k2): repl_template(v2)
|
||||
repl_template_with_params(k2): repl_template_with_params(v2)
|
||||
for k2, v2 in v.items()
|
||||
}
|
||||
|
||||
flac = await self.config.get("flows-active").values()
|
||||
if processor in flac:
|
||||
target = json.loads(flac[processor])
|
||||
flac = await self.config.get("flows-active").get(processor)
|
||||
if flac is not None:
|
||||
target = json.loads(flac)
|
||||
else:
|
||||
target = {}
|
||||
|
||||
# The condition if variant not in target: means it only adds
|
||||
# the configuration if the variant doesn't already exist.
|
||||
# If "everything" already exists in the target with old
|
||||
# values, they won't update.
|
||||
|
||||
if variant not in target:
|
||||
target[variant] = v
|
||||
|
||||
|
|
@ -131,10 +243,10 @@ class FlowConfig:
|
|||
|
||||
def repl_interface(i):
|
||||
if isinstance(i, str):
|
||||
return repl_template(i)
|
||||
return repl_template_with_params(i)
|
||||
else:
|
||||
return {
|
||||
k: repl_template(v)
|
||||
k: repl_template_with_params(v)
|
||||
for k, v in i.items()
|
||||
}
|
||||
|
||||
|
|
@ -152,6 +264,7 @@ class FlowConfig:
|
|||
"description": msg.description,
|
||||
"class-name": msg.class_name,
|
||||
"interfaces": interfaces,
|
||||
"parameters": parameters,
|
||||
})
|
||||
)
|
||||
|
||||
|
|
@ -177,15 +290,20 @@ class FlowConfig:
|
|||
raise RuntimeError("Internal error: flow has no flow class")
|
||||
|
||||
class_name = flow["class-name"]
|
||||
parameters = flow.get("parameters", {})
|
||||
|
||||
cls = json.loads(await self.config.get("flow-classes").get(class_name))
|
||||
|
||||
def repl_template(tmp):
|
||||
return tmp.replace(
|
||||
result = tmp.replace(
|
||||
"{class}", class_name
|
||||
).replace(
|
||||
"{id}", msg.flow_id
|
||||
)
|
||||
# Apply parameter substitutions
|
||||
for param_name, param_value in parameters.items():
|
||||
result = result.replace(f"{{{param_name}}}", str(param_value))
|
||||
return result
|
||||
|
||||
for kind in ("flow",):
|
||||
|
||||
|
|
@ -195,10 +313,10 @@ class FlowConfig:
|
|||
|
||||
variant = repl_template(variant)
|
||||
|
||||
flac = await self.config.get("flows-active").values()
|
||||
flac = await self.config.get("flows-active").get(processor)
|
||||
|
||||
if processor in flac:
|
||||
target = json.loads(flac[processor])
|
||||
if flac is not None:
|
||||
target = json.loads(flac)
|
||||
else:
|
||||
target = {}
|
||||
|
||||
|
|
@ -209,7 +327,7 @@ class FlowConfig:
|
|||
processor, json.dumps(target)
|
||||
)
|
||||
|
||||
if msg.flow_id in await self.config.get("flows").values():
|
||||
if msg.flow_id in await self.config.get("flows").keys():
|
||||
await self.config.get("flows").delete(msg.flow_id)
|
||||
|
||||
await self.config.inc_version()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue