Automatically raise exception on validation failure:
# Validate and raise if invalidresult = validate_sequence(workflow)result.raise_if_invalid() # Raises ValueError if validation failed# If we get here, validation passedworkflow.run(data)
result = validate_sequence(workflow)# Print formatted summaryprint(result.get_summary())# Output:# Validation Status: INVALID# Errors: 1# - ERROR: Cycle detected: A -> B -> C -> A# Warnings: 1# - WARNING: Node appears to be a dead end# Suggestions: 0
from egregore.core.workflow import node, decision@node("A")def node_a(data): return data@node("B")def node_b(data): return data@node("C")def node_c(data): return data# Create cycle: A -> B -> C -> A@decision("route")def route(data): return "A" # Always returns to A# This will fail validationworkflow = Sequence(node_a >> node_b >> node_c >> route)result = validate_sequence(workflow)# Result:# ERROR: Cycle detected: A -> B -> C -> route -> A# Suggestion: Remove one of the connections in the cycle or add max_iter to decision nodes
Cycle Detection Algorithm:
Uses DFS (Depth-First Search) with color coding (WHITE, GRAY, BLACK)
WHITE: Unvisited nodes
GRAY: Currently being explored (in recursion stack)
# Empty sequence (error)empty_workflow = Sequence(None)result = validate_sequence(empty_workflow)# ERROR: Sequence has no start node# Suggestion: Provide a start node when creating the sequence# Single-node workflow (warning)single_node = Sequence(processor)result = validate_sequence(single_node)# WARNING: Sequence contains only one node# Suggestion: Consider if this should be a simple function call instead
ValidationError( message="Cycle detected in workflow", severity=ValidationSeverity.ERROR, location=node, suggestion="Break the cycle or add max_iter")
WARNING - Execution allowed but issues exist:
ValidationWarning( message="Node appears to be a dead end", severity=ValidationSeverity.WARNING, location=node, suggestion="Consider connecting to another node")
INFO - Suggestions for improvement:
ValidationSuggestion( message="Consider using parallel execution", severity=ValidationSeverity.INFO, location=node, suggestion="These nodes could run concurrently")
from egregore.core.workflow import validate_sequence, Sequencedef run_workflow_safely(workflow: Sequence, data: dict) -> dict: """Run workflow with validation.""" # Validate first result = validate_sequence(workflow) if not result.is_valid: raise ValueError(f"Workflow validation failed:\n{result.get_summary()}") # Show warnings if result.warnings: print("Warnings:") for warning in result.warnings: print(f" - {warning}") # Execute return workflow.run(data)
import os# Strict validation in developmentif os.getenv("ENV") == "development": result = validate_sequence(workflow) result.raise_if_invalid() # Show all warnings for warning in result.warnings: print(f"WARNING: {warning}")# Lenient in production (log warnings only)else: result = validate_sequence(workflow) if not result.is_valid: # Log errors but continue if non-critical for error in result.errors: logger.error(f"Validation error: {error}") for warning in result.warnings: logger.warning(f"Validation warning: {warning}")
# Good: Validate before deploymentdef deploy_workflow(workflow: Sequence): result = validate_sequence(workflow) if not result.is_valid: raise ValueError("Cannot deploy invalid workflow") # Deploy production_system.deploy(workflow)# Bad: Deploy without validationdef deploy_workflow(workflow: Sequence): production_system.deploy(workflow) # May fail at runtime
Use validation in tests
# Good: Test validation passesdef test_workflow_is_valid(): workflow = create_workflow() result = validate_sequence(workflow) assert result.is_valid# Bad: No validation testingdef test_workflow(): workflow = create_workflow() result = workflow.run(test_data) # Fails at runtime
Create custom validators for domain rules
# Good: Domain-specific validationclass BusinessRuleValidator(BaseValidator): def validate(self, sequence): # Validate business logic # - Required approval nodes # - Data sensitivity checks # - Compliance requirements pass# Bad: No domain validationresult = validate_sequence(workflow) # Only structural checks
Log validation results
# Good: Comprehensive loggingresult = validate_sequence(workflow)logger.info(f"Validation result: {result.get_summary()}")if not result.is_valid: for error in result.errors: logger.error(f"Validation error: {error}")# Bad: Silent validationresult = validate_sequence(workflow)if result.is_valid: workflow.run(data)
Handle warnings appropriately
# Good: Review warningsresult = validate_sequence(workflow)if result.warnings: print("Review these warnings:") for warning in result.warnings: print(f" {warning}") if warning.suggestion: print(f" Suggestion: {warning.suggestion}")# Bad: Ignore warningsresult = validate_sequence(workflow)if result.is_valid: workflow.run(data) # Warnings ignored
# Validate once at startup (recommended)workflow = create_workflow()validate_sequence(workflow).raise_if_invalid()# Run many times without re-validationfor data in dataset: workflow.run(data)# Don't validate on every run (slow)for data in dataset: validate_sequence(workflow) # SLOW - unnecessary workflow.run(data)