Access Control
The access control module provides comprehensive security and permission management for workflows.
Access control package with composition-based architecture.
This package provides clean, testable access control components: - Rule evaluators for RBAC, ABAC, and hybrid strategies - Composable access control managers - Backward compatibility with existing code
- class kailash.access_control.NodePermission(value)
Bases:
EnumNode-level permissions
- EXECUTE = 'execute'
- READ_OUTPUT = 'read_output'
- WRITE_INPUT = 'write_input'
- SKIP = 'skip'
- MASK_OUTPUT = 'mask_output'
- classmethod __contains__(member)
Return True if member is a member of this enum raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be returned if member is the value of a member in this enum
- classmethod __getitem__(name)
Return the member matching name.
- classmethod __iter__()
Return members in definition order.
- classmethod __len__()
Return the number of members (no aliases)
- class kailash.access_control.WorkflowPermission(value)
Bases:
EnumWorkflow-level permissions
- VIEW = 'view'
- EXECUTE = 'execute'
- MODIFY = 'modify'
- DELETE = 'delete'
- SHARE = 'share'
- ADMIN = 'admin'
- classmethod __contains__(member)
Return True if member is a member of this enum raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be returned if member is the value of a member in this enum
- classmethod __getitem__(name)
Return the member matching name.
- classmethod __iter__()
Return members in definition order.
- classmethod __len__()
Return the number of members (no aliases)
- class kailash.access_control.PermissionEffect(value)
Bases:
EnumEffect of a permission rule
- ALLOW = 'allow'
- DENY = 'deny'
- CONDITIONAL = 'conditional'
- classmethod __contains__(member)
Return True if member is a member of this enum raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be returned if member is the value of a member in this enum
- classmethod __getitem__(name)
Return the member matching name.
- classmethod __iter__()
Return members in definition order.
- classmethod __len__()
Return the number of members (no aliases)
- class kailash.access_control.PermissionRule(id: str, resource_type: str, resource_id: str, permission: WorkflowPermission | NodePermission, effect: PermissionEffect, user_id: str | None = None, role: str | None = None, tenant_id: str | None = None, conditions: dict[str, ~typing.Any]=<factory>, created_at: datetime = <factory>, created_by: str | None = None, expires_at: datetime | None = None, priority: int = 0)
Bases:
objectA single permission rule defining access control policies.
Represents a single access control rule that grants or denies permissions to users for specific resources (workflows or nodes) based on their identity, roles, and contextual conditions.
- Design Purpose:
Provides a flexible, declarative way to define access control policies. Supports role-based access control (RBAC), attribute-based access control (ABAC), and conditional permissions based on runtime context.
- Upstream Dependencies:
Administrative interfaces for rule creation
Policy management systems
Configuration files or databases
- Downstream Consumers:
AccessControlManager for rule evaluation
Audit systems for logging policy decisions
Policy analysis tools for rule validation
- Usage Patterns:
Created by administrators to define access policies
Evaluated during workflow and node execution
Cached for performance optimization
Updated when policies change
- Implementation Details:
Uses dataclass for efficient serialization and comparison. Supports priority-based rule ordering for conflict resolution. Includes expiration for time-limited permissions. Conditions enable complex policy logic.
Example
>>> rule = PermissionRule( ... id="allow_analysts_read", ... resource_type="node", ... resource_id="sensitive_data", ... permission=NodePermission.READ_OUTPUT, ... effect=PermissionEffect.ALLOW, ... role="analyst" ... ) >>> print(rule.id) allow_analysts_read
- Parameters:
id (str)
resource_type (str)
resource_id (str)
permission (WorkflowPermission | NodePermission)
effect (PermissionEffect)
user_id (str | None)
role (str | None)
tenant_id (str | None)
created_at (datetime)
created_by (str | None)
expires_at (datetime | None)
priority (int)
- __init__(id: str, resource_type: str, resource_id: str, permission: WorkflowPermission | NodePermission, effect: PermissionEffect, user_id: str | None = None, role: str | None = None, tenant_id: str | None = None, conditions: dict[str, ~typing.Any]=<factory>, created_at: datetime = <factory>, created_by: str | None = None, expires_at: datetime | None = None, priority: int = 0) None
- Parameters:
id (str)
resource_type (str)
resource_id (str)
permission (WorkflowPermission | NodePermission)
effect (PermissionEffect)
user_id (str | None)
role (str | None)
tenant_id (str | None)
created_at (datetime)
created_by (str | None)
expires_at (datetime | None)
priority (int)
- Return type:
None
- priority: int = 0
- id: str
- resource_type: str
- resource_id: str
- permission: WorkflowPermission | NodePermission
- effect: PermissionEffect
- created_at: datetime
- class kailash.access_control.UserContext(user_id: str, tenant_id: str, email: str, roles: list[str] = <factory>, permissions: list[str] = <factory>, attributes: dict[str, ~typing.Any]=<factory>, session_id: str | None = None, ip_address: str | None = None)
Bases:
objectUser context for access control decisions.
Contains all information needed to make access control decisions for a user, including identity, tenant membership, roles, and session information.
- Design Purpose:
Provides a standardized way to represent user identity and permissions across the entire access control system. Enables fine-grained access control based on user attributes, roles, and context.
- Upstream Dependencies:
Authentication systems (JWT, API keys)
User management systems
Tenant management systems
- Downstream Consumers:
AccessControlManager for permission checking
AccessControlledRuntime for workflow execution
Audit logging systems for tracking access
- Usage Patterns:
Created during authentication/login process
Passed to all access control functions
Used in workflow and node execution contexts
Logged for audit and compliance purposes
- Implementation Details:
Uses dataclass for efficient attribute access and comparison. Immutable once created to prevent privilege escalation. Supports custom attributes for extensible authorization.
Example
>>> user = UserContext( ... user_id="user123", ... tenant_id="tenant001", ... email="user@example.com", ... roles=["analyst", "viewer"] ... ) >>> print(user.user_id) user123
- Parameters:
- __init__(user_id: str, tenant_id: str, email: str, roles: list[str] = <factory>, permissions: list[str] = <factory>, attributes: dict[str, ~typing.Any]=<factory>, session_id: str | None = None, ip_address: str | None = None) None
- user_id: str
- tenant_id: str
- email: str
- class kailash.access_control.AccessDecision(allowed: bool, reason: str, applied_rules: list[PermissionRule] = <factory>, conditions_met: dict[str, bool]=<factory>, masked_fields: list[str] = <factory>, redirect_node: str | None = None)
Bases:
objectResult of an access control decision.
Contains the outcome of evaluating access control rules for a specific user and resource, including whether access is allowed, the reasoning, and any additional actions required (like data masking).
- Design Purpose:
Provides a comprehensive result object that captures not just the allow/deny decision, but also the context and reasoning behind it. Enables audit logging, debugging, and conditional execution.
- Upstream Dependencies:
AccessControlManager rule evaluation
Permission rule matching logic
Conditional evaluation systems
- Downstream Consumers:
AccessControlledRuntime for execution decisions
Audit logging systems for compliance
Error handling for access denial messages
Data masking systems for output filtering
- Usage Patterns:
Returned by all access control check methods
Logged for audit and debugging purposes
Used to determine execution flow
Provides user-friendly error messages
- Implementation Details:
Immutable after creation to ensure decision integrity. Includes applied rules for transparency and debugging. Supports conditional decisions for complex scenarios. Contains masking information for data protection.
Example
>>> decision = AccessDecision( ... allowed=True, ... reason="User has analyst role", ... masked_fields=["ssn", "phone"] ... ) >>> print(decision.allowed) True
- Parameters:
- __init__(allowed: bool, reason: str, applied_rules: list[PermissionRule] = <factory>, conditions_met: dict[str, bool]=<factory>, masked_fields: list[str] = <factory>, redirect_node: str | None = None) None
- allowed: bool
- reason: str
- applied_rules: list[PermissionRule]
- class kailash.access_control.ConditionEvaluator
Bases:
objectEvaluates conditions for conditional permissions
- __init__()
- class kailash.access_control.AccessControlManager(rule_evaluator: RuleEvaluator | None = None, strategy: str = 'hybrid', enabled: bool = True)[source]
Bases:
objectAccess control manager using composition pattern.
This manager separates rule storage from rule evaluation, allowing: - Easy testing with mock evaluators - Flexible evaluation strategies (RBAC, ABAC, Hybrid) - Clear separation of concerns - No inheritance-related bugs
Example
>>> # Create with hybrid evaluation (RBAC + ABAC) >>> manager = AccessControlManager()
>>> # Or specify evaluation strategy >>> rbac_manager = AccessControlManager(strategy="rbac") >>> abac_manager = AccessControlManager(strategy="abac")
>>> # Add rules >>> manager.add_rule(PermissionRule(...))
>>> # Check access >>> decision = manager.check_node_access(user, "node_id", NodePermission.EXECUTE)
- __init__(rule_evaluator: RuleEvaluator | None = None, strategy: str = 'hybrid', enabled: bool = True)[source]
Initialize access control manager.
- add_rule(rule: PermissionRule) None[source]
Add a permission rule.
- Parameters:
rule (PermissionRule) – Permission rule to add
- Return type:
None
- check_workflow_access(user: UserContext, workflow_id: str, permission: WorkflowPermission, runtime_context: Dict[str, Any] | None = None) AccessDecision[source]
Check if user has permission on workflow.
- Parameters:
user (UserContext) – User requesting access
workflow_id (str) – Workflow to access
permission (WorkflowPermission) – Permission being requested
runtime_context (Dict[str, Any] | None) – Additional runtime context
- Returns:
AccessDecision with allow/deny and reasoning
- Return type:
- check_node_access(user: UserContext, node_id: str, permission: NodePermission, runtime_context: Dict[str, Any] | None = None) AccessDecision[source]
Check if user has permission on node.
- Parameters:
user (UserContext) – User requesting access
node_id (str) – Node to access
permission (NodePermission) – Permission being requested
runtime_context (Dict[str, Any] | None) – Additional runtime context
- Returns:
AccessDecision with allow/deny and reasoning
- Return type:
- get_accessible_nodes(user: UserContext, workflow_id: str, permission: NodePermission) set[str][source]
Get all nodes user can access in a workflow.
- Parameters:
user (UserContext) – User to check access for
workflow_id (str) – Workflow containing nodes
permission (NodePermission) – Permission type to check
- Returns:
Set of accessible node IDs
- Return type:
- apply_data_masking(user: UserContext, node_id: str, data: Dict[str, Any]) Dict[str, Any][source]
Apply attribute-based data masking to node output.
- class kailash.access_control.RuleEvaluator[source]
Bases:
ABCAbstract base class for rule evaluation strategies.
- abstractmethod evaluate(rules: List[PermissionRule], user: UserContext, resource_type: str, resource_id: str, permission: NodePermission | WorkflowPermission, runtime_context: Dict[str, Any]) AccessDecision[source]
Evaluate a set of rules for a user’s access request.
- Parameters:
rules (List[PermissionRule]) – List of applicable permission rules
user (UserContext) – User making the request
resource_type (str) – Type of resource (node/workflow)
resource_id (str) – Specific resource identifier
permission (NodePermission | WorkflowPermission) – Permission being requested
runtime_context (Dict[str, Any]) – Additional runtime context
- Returns:
AccessDecision with allow/deny and reasoning
- Return type:
- evaluate_rules(*args, **kwargs)[source]
- class kailash.access_control.RBACRuleEvaluator[source]
Bases:
RuleEvaluatorRole-Based Access Control rule evaluator.
This evaluator handles traditional RBAC rules based on: - User roles - Direct user assignments - Tenant-based permissions
- __init__()[source]
Initialize RBAC evaluator.
- evaluate(rules: List[PermissionRule], user: UserContext, resource_type: str, resource_id: str, permission: NodePermission | WorkflowPermission, runtime_context: Dict[str, Any]) AccessDecision[source]
Evaluate RBAC rules.
- Parameters:
rules (List[PermissionRule])
user (UserContext)
resource_type (str)
resource_id (str)
permission (NodePermission | WorkflowPermission)
- Return type:
- evaluate_rules(*args, **kwargs)
- class kailash.access_control.ABACRuleEvaluator[source]
Bases:
RuleEvaluatorAttribute-Based Access Control rule evaluator.
This evaluator handles advanced ABAC rules with: - Complex attribute expressions - Dynamic condition evaluation - Hierarchical attribute matching
- __init__()[source]
Initialize ABAC evaluator.
- evaluate(rules: List[PermissionRule], user: UserContext, resource_type: str, resource_id: str, permission: NodePermission | WorkflowPermission, runtime_context: Dict[str, Any]) AccessDecision[source]
Evaluate ABAC rules with complex attribute conditions.
- Parameters:
rules (List[PermissionRule])
user (UserContext)
resource_type (str)
resource_id (str)
permission (NodePermission | WorkflowPermission)
- Return type:
- evaluate_rules(*args, **kwargs)
- class kailash.access_control.HybridRuleEvaluator[source]
Bases:
RuleEvaluatorHybrid evaluator that combines RBAC and ABAC evaluation.
This evaluator: 1. Uses RBAC for basic rules without conditions 2. Uses ABAC for complex conditional rules 3. Provides seamless transition between evaluation strategies
- __init__()[source]
Initialize hybrid evaluator with both RBAC and ABAC.
- evaluate(rules: List[PermissionRule], user: UserContext, resource_type: str, resource_id: str, permission: NodePermission | WorkflowPermission, runtime_context: Dict[str, Any]) AccessDecision[source]
Evaluate rules using appropriate strategy based on rule complexity.
- Parameters:
rules (List[PermissionRule])
user (UserContext)
resource_type (str)
resource_id (str)
permission (NodePermission | WorkflowPermission)
- Return type:
- supports_conditions() bool[source]
Hybrid evaluator supports conditions via ABAC component.
- Return type:
- evaluate_rules(*args, **kwargs)
- kailash.access_control.create_rule_evaluator(strategy: str = 'hybrid') RuleEvaluator[source]
Create a rule evaluator based on strategy.
- Parameters:
strategy (str) – One of ‘rbac’, ‘abac’, or ‘hybrid’
- Returns:
Appropriate RuleEvaluator instance
- Raises:
ValueError – If strategy is not recognized
- Return type:
RuleEvaluator
- kailash.access_control.get_access_control_manager() AccessControlManager
Get the global access control manager
- Return type:
AccessControlManager
- kailash.access_control.set_access_control_manager(manager: AccessControlManager)
Set a custom access control manager
- Parameters:
manager (AccessControlManager)
- kailash.access_control.create_attribute_condition(path: str, operator: str, value: Any, case_sensitive: bool = True) Dict[str, Any][source]
Create an attribute condition configuration.
Helper function to create properly formatted attribute conditions for use in permission rules.
- kailash.access_control.create_complex_condition(operator: str, conditions: List[Dict[str, Any]]) Dict[str, Any][source]
Create a complex attribute condition with logical operators.
Helper function to create AND/OR/NOT conditions.
Access Control Manager
- class kailash.access_control.AccessControlManager(rule_evaluator: RuleEvaluator | None = None, strategy: str = 'hybrid', enabled: bool = True)[source]
Bases:
objectAccess control manager using composition pattern.
This manager separates rule storage from rule evaluation, allowing: - Easy testing with mock evaluators - Flexible evaluation strategies (RBAC, ABAC, Hybrid) - Clear separation of concerns - No inheritance-related bugs
Example
>>> # Create with hybrid evaluation (RBAC + ABAC) >>> manager = AccessControlManager()
>>> # Or specify evaluation strategy >>> rbac_manager = AccessControlManager(strategy="rbac") >>> abac_manager = AccessControlManager(strategy="abac")
>>> # Add rules >>> manager.add_rule(PermissionRule(...))
>>> # Check access >>> decision = manager.check_node_access(user, "node_id", NodePermission.EXECUTE)
- __init__(rule_evaluator: RuleEvaluator | None = None, strategy: str = 'hybrid', enabled: bool = True)[source]
Initialize access control manager.
- add_rule(rule: PermissionRule) None[source]
Add a permission rule.
- Parameters:
rule (PermissionRule) – Permission rule to add
- Return type:
None
- check_workflow_access(user: UserContext, workflow_id: str, permission: WorkflowPermission, runtime_context: Dict[str, Any] | None = None) AccessDecision[source]
Check if user has permission on workflow.
- Parameters:
user (UserContext) – User requesting access
workflow_id (str) – Workflow to access
permission (WorkflowPermission) – Permission being requested
runtime_context (Dict[str, Any] | None) – Additional runtime context
- Returns:
AccessDecision with allow/deny and reasoning
- Return type:
- check_node_access(user: UserContext, node_id: str, permission: NodePermission, runtime_context: Dict[str, Any] | None = None) AccessDecision[source]
Check if user has permission on node.
- Parameters:
user (UserContext) – User requesting access
node_id (str) – Node to access
permission (NodePermission) – Permission being requested
runtime_context (Dict[str, Any] | None) – Additional runtime context
- Returns:
AccessDecision with allow/deny and reasoning
- Return type:
- get_accessible_nodes(user: UserContext, workflow_id: str, permission: NodePermission) set[str][source]
Get all nodes user can access in a workflow.
- Parameters:
user (UserContext) – User to check access for
workflow_id (str) – Workflow containing nodes
permission (NodePermission) – Permission type to check
- Returns:
Set of accessible node IDs
- Return type:
- add_masking_rule(node_id: str, rule: Any) None[source]
Add attribute-based masking rule for a node.
- apply_data_masking(user: UserContext, node_id: str, data: Dict[str, Any]) Dict[str, Any][source]
Apply attribute-based data masking to node output.
User Context
- class kailash.access_control.UserContext(user_id: str, tenant_id: str, email: str, roles: list[str] = <factory>, permissions: list[str] = <factory>, attributes: dict[str, ~typing.Any]=<factory>, session_id: str | None = None, ip_address: str | None = None)
Bases:
objectUser context for access control decisions.
Contains all information needed to make access control decisions for a user, including identity, tenant membership, roles, and session information.
- Design Purpose:
Provides a standardized way to represent user identity and permissions across the entire access control system. Enables fine-grained access control based on user attributes, roles, and context.
- Upstream Dependencies:
Authentication systems (JWT, API keys)
User management systems
Tenant management systems
- Downstream Consumers:
AccessControlManager for permission checking
AccessControlledRuntime for workflow execution
Audit logging systems for tracking access
- Usage Patterns:
Created during authentication/login process
Passed to all access control functions
Used in workflow and node execution contexts
Logged for audit and compliance purposes
- Implementation Details:
Uses dataclass for efficient attribute access and comparison. Immutable once created to prevent privilege escalation. Supports custom attributes for extensible authorization.
Example
>>> user = UserContext( ... user_id="user123", ... tenant_id="tenant001", ... email="user@example.com", ... roles=["analyst", "viewer"] ... ) >>> print(user.user_id) user123
- Parameters:
Permission Rules
- class kailash.access_control.PermissionRule(id: str, resource_type: str, resource_id: str, permission: WorkflowPermission | NodePermission, effect: PermissionEffect, user_id: str | None = None, role: str | None = None, tenant_id: str | None = None, conditions: dict[str, ~typing.Any]=<factory>, created_at: datetime = <factory>, created_by: str | None = None, expires_at: datetime | None = None, priority: int = 0)
Bases:
objectA single permission rule defining access control policies.
Represents a single access control rule that grants or denies permissions to users for specific resources (workflows or nodes) based on their identity, roles, and contextual conditions.
- Design Purpose:
Provides a flexible, declarative way to define access control policies. Supports role-based access control (RBAC), attribute-based access control (ABAC), and conditional permissions based on runtime context.
- Upstream Dependencies:
Administrative interfaces for rule creation
Policy management systems
Configuration files or databases
- Downstream Consumers:
AccessControlManager for rule evaluation
Audit systems for logging policy decisions
Policy analysis tools for rule validation
- Usage Patterns:
Created by administrators to define access policies
Evaluated during workflow and node execution
Cached for performance optimization
Updated when policies change
- Implementation Details:
Uses dataclass for efficient serialization and comparison. Supports priority-based rule ordering for conflict resolution. Includes expiration for time-limited permissions. Conditions enable complex policy logic.
Example
>>> rule = PermissionRule( ... id="allow_analysts_read", ... resource_type="node", ... resource_id="sensitive_data", ... permission=NodePermission.READ_OUTPUT, ... effect=PermissionEffect.ALLOW, ... role="analyst" ... ) >>> print(rule.id) allow_analysts_read
- Parameters:
id (str)
resource_type (str)
resource_id (str)
permission (WorkflowPermission | NodePermission)
effect (PermissionEffect)
user_id (str | None)
role (str | None)
tenant_id (str | None)
created_at (datetime)
created_by (str | None)
expires_at (datetime | None)
priority (int)
- __init__(id: str, resource_type: str, resource_id: str, permission: WorkflowPermission | NodePermission, effect: PermissionEffect, user_id: str | None = None, role: str | None = None, tenant_id: str | None = None, conditions: dict[str, ~typing.Any]=<factory>, created_at: datetime = <factory>, created_by: str | None = None, expires_at: datetime | None = None, priority: int = 0) None
- Parameters:
id (str)
resource_type (str)
resource_id (str)
permission (WorkflowPermission | NodePermission)
effect (PermissionEffect)
user_id (str | None)
role (str | None)
tenant_id (str | None)
created_at (datetime)
created_by (str | None)
expires_at (datetime | None)
priority (int)
- Return type:
None
- permission: WorkflowPermission | NodePermission
- effect: PermissionEffect
Access Decisions
- class kailash.access_control.AccessDecision(allowed: bool, reason: str, applied_rules: list[PermissionRule] = <factory>, conditions_met: dict[str, bool]=<factory>, masked_fields: list[str] = <factory>, redirect_node: str | None = None)
Bases:
objectResult of an access control decision.
Contains the outcome of evaluating access control rules for a specific user and resource, including whether access is allowed, the reasoning, and any additional actions required (like data masking).
- Design Purpose:
Provides a comprehensive result object that captures not just the allow/deny decision, but also the context and reasoning behind it. Enables audit logging, debugging, and conditional execution.
- Upstream Dependencies:
AccessControlManager rule evaluation
Permission rule matching logic
Conditional evaluation systems
- Downstream Consumers:
AccessControlledRuntime for execution decisions
Audit logging systems for compliance
Error handling for access denial messages
Data masking systems for output filtering
- Usage Patterns:
Returned by all access control check methods
Logged for audit and debugging purposes
Used to determine execution flow
Provides user-friendly error messages
- Implementation Details:
Immutable after creation to ensure decision integrity. Includes applied rules for transparency and debugging. Supports conditional decisions for complex scenarios. Contains masking information for data protection.
Example
>>> decision = AccessDecision( ... allowed=True, ... reason="User has analyst role", ... masked_fields=["ssn", "phone"] ... ) >>> print(decision.allowed) True
- Parameters:
- __init__(allowed: bool, reason: str, applied_rules: list[PermissionRule] = <factory>, conditions_met: dict[str, bool]=<factory>, masked_fields: list[str] = <factory>, redirect_node: str | None = None) None
- applied_rules: list[PermissionRule]
Enumerations
- class kailash.access_control.WorkflowPermission(value)
Bases:
EnumWorkflow-level permissions
- VIEW = 'view'
- EXECUTE = 'execute'
- MODIFY = 'modify'
- DELETE = 'delete'
- SHARE = 'share'
- ADMIN = 'admin'
- classmethod __contains__(member)
Return True if member is a member of this enum raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be returned if member is the value of a member in this enum
- classmethod __getitem__(name)
Return the member matching name.
- classmethod __iter__()
Return members in definition order.
- classmethod __len__()
Return the number of members (no aliases)
- class kailash.access_control.NodePermission(value)
Bases:
EnumNode-level permissions
- EXECUTE = 'execute'
- READ_OUTPUT = 'read_output'
- WRITE_INPUT = 'write_input'
- SKIP = 'skip'
- MASK_OUTPUT = 'mask_output'
- classmethod __contains__(member)
Return True if member is a member of this enum raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be returned if member is the value of a member in this enum
- classmethod __getitem__(name)
Return the member matching name.
- classmethod __iter__()
Return members in definition order.
- classmethod __len__()
Return the number of members (no aliases)
- class kailash.access_control.PermissionEffect(value)
Bases:
EnumEffect of a permission rule
- ALLOW = 'allow'
- DENY = 'deny'
- CONDITIONAL = 'conditional'
- classmethod __contains__(member)
Return True if member is a member of this enum raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be returned if member is the value of a member in this enum
- classmethod __getitem__(name)
Return the member matching name.
- classmethod __iter__()
Return members in definition order.
- classmethod __len__()
Return the number of members (no aliases)
Access Controlled Runtime
Access-Controlled Runtime for Kailash SDK
This module provides an access-controlled runtime that wraps the standard runtime to add permission checks. The standard runtime remains unchanged, ensuring complete backward compatibility.
Users who don’t need access control continue using LocalRuntime as normal. Users who need access control use AccessControlledRuntime instead.
- Example without access control (existing code):
>>> from kailash.runtime.local import LocalRuntime >>> from kailash.workflow import Workflow >>> runtime = LocalRuntime() >>> workflow = Workflow(workflow_id="test", name="Test") >>> result, run_id = runtime.execute(workflow) # Works exactly as before
- Example with access control (opt-in):
>>> from kailash.runtime.access_controlled import AccessControlledRuntime >>> from kailash.access_control import UserContext, get_access_control_manager >>> user = UserContext(user_id="123", tenant_id="abc", email="user@test.com", roles=["analyst"]) >>> runtime = AccessControlledRuntime(user_context=user) >>> # Access control manager is disabled by default for compatibility >>> acm = get_access_control_manager() >>> acm.enabled # Should be False by default False
- class kailash.runtime.access_controlled.AccessControlledRuntime(user_context: UserContext, base_runtime: LocalRuntime | None = None)[source]
Bases:
objectRuntime with transparent access control layer.
This runtime wraps the standard LocalRuntime and adds access control checks without modifying the original runtime or requiring any changes to existing nodes or workflows.
- Design Purpose:
Provides a drop-in replacement for LocalRuntime that adds security without breaking existing workflows. Enables role-based access control, data masking, and conditional execution based on user permissions.
- Upstream Dependencies:
AccessControlManager for permission evaluation
UserContext from authentication systems
LocalRuntime for actual workflow execution
PermissionRule definitions from configuration
- Downstream Consumers:
Applications requiring secure workflow execution
Multi-tenant systems with user isolation
Audit systems for compliance logging
Data governance systems for access tracking
- Usage Patterns:
Used as direct replacement for LocalRuntime
Configured with user context during initialization
Integrates with JWT authentication systems
Supports both workflow and node-level permissions
- Implementation Details:
Wraps LocalRuntime and intercepts workflow execution to add permission checks. Creates access-controlled node wrappers that evaluate permissions before execution. Supports data masking, conditional routing, and fallback execution.
- Error Handling:
Access denied raises PermissionError with clear messages
Missing permissions default to deny for security
Configuration errors are logged and treated as disabled
Evaluation errors fall back to base runtime behavior
- Side Effects:
Logs all access decisions for audit purposes
May redirect execution to alternative nodes
Applies data masking to sensitive outputs
Caches permission decisions for performance
Example
>>> from kailash.runtime.access_controlled import AccessControlledRuntime >>> from kailash.access_control import UserContext >>> from kailash.workflow import Workflow >>> >>> user = UserContext(user_id="123", tenant_id="abc", email="user@test.com", roles=["analyst"]) >>> runtime = AccessControlledRuntime(user_context=user) >>> # By default, access control is disabled for backward compatibility >>> workflow = Workflow(workflow_id="test", name="Test Workflow") >>> isinstance(runtime, AccessControlledRuntime) True
- Parameters:
user_context (UserContext)
base_runtime (LocalRuntime | None)
- __init__(user_context: UserContext, base_runtime: LocalRuntime | None = None)[source]
Initialize access-controlled runtime.
- Parameters:
user_context (UserContext) – The user context for access control decisions
base_runtime (LocalRuntime | None) – The underlying runtime to use (defaults to LocalRuntime)
- execute(workflow: Workflow, parameters: dict[str, Any] | None = None, *, soft_time_limit: float | None = None, time_limit: float | None = None, **kwargs: Any) tuple[Any, str | None][source]
Execute workflow with access control.
This method has the exact same signature as the standard runtime, ensuring complete compatibility.
- Parameters:
workflow (Workflow) – Workflow to execute.
parameters (dict[str, Any] | None) – Optional parameter overrides per node.
soft_time_limit (float | None) – Optional advisory deadline in seconds (#912 Shard 1 slot, forwarded to inner runtime).
time_limit (float | None) – Optional unconditional kill deadline in seconds.
**kwargs (Any) – Forward-compatibility kwargs forwarded to inner runtime.
- Return type:
- close() None[source]
Close the runtime and clean up resources.
Only closes the base runtime if it was created by this instance.
- Return type:
None
- __enter__() AccessControlledRuntime[source]
Enter context manager.
- Return type:
- class kailash.runtime.access_controlled.AccessControlConfig[source]
Bases:
objectConfiguration for access control in workflows.
Provides a declarative way to define access rules without modifying workflow code. Enables administrators to configure permissions externally from workflow definitions.
- Design Purpose:
Separates access control policy from workflow implementation, enabling dynamic permission changes without code modifications. Supports both workflow-level and node-level permission rules.
- Upstream Dependencies:
Administrative interfaces for rule creation
Configuration management systems
Policy definition templates
- Downstream Consumers:
AccessControlManager for rule application
AccessControlledRuntime for secure execution
Policy management tools for validation
- Usage Patterns:
Created by administrators or configuration systems
Applied to workflows before execution
Used for testing different access scenarios
Integrated with external policy management
- Implementation Details:
Maintains list of PermissionRule objects with helper methods for adding common rule types. Rules are applied to manager in batch for consistency.
Example
>>> config = AccessControlConfig() >>> config.add_workflow_permission( ... workflow_id="analytics", ... permission=WorkflowPermission.EXECUTE, ... role="analyst" ... ) >>> config.add_node_permission( ... workflow_id="analytics", ... node_id="sensitive_data", ... permission=NodePermission.READ_OUTPUT, ... role="admin" ... )
- add_workflow_permission(workflow_id: str, permission: WorkflowPermission, user_id: str | None = None, role: str | None = None, effect: PermissionEffect = PermissionEffect.ALLOW)[source]
Add a workflow-level permission rule
- Parameters:
workflow_id (str)
permission (WorkflowPermission)
user_id (str | None)
role (str | None)
effect (PermissionEffect)
- add_node_permission(workflow_id: str, node_id: str, permission: NodePermission, user_id: str | None = None, role: str | None = None, effect: PermissionEffect = PermissionEffect.ALLOW, masked_fields: list[str] | None = None, redirect_node: str | None = None)[source]
Add a node-level permission rule
- Parameters:
workflow_id (str)
node_id (str)
permission (NodePermission)
user_id (str | None)
role (str | None)
effect (PermissionEffect)
redirect_node (str | None)
- apply_to_manager(manager: AccessControlManager)[source]
Apply all rules to an access control manager
- Parameters:
manager (AccessControlManager)
- kailash.runtime.access_controlled.execute_with_access_control(workflow: Workflow, user_context: UserContext, parameters: dict[str, Any] | None = None, access_config: AccessControlConfig | None = None) tuple[Any, str | None][source]
Convenience function to execute a workflow with access control.
Provides a simple way to execute workflows with access control without manually creating runtime instances. Automatically applies access configuration and manages the runtime lifecycle.
- Parameters:
workflow (Workflow) – The workflow to execute
user_context (UserContext) – User context for access control decisions
parameters (dict[str, Any] | None) – Optional runtime parameters for workflow execution
access_config (AccessControlConfig | None) – Optional access control configuration to apply
- Returns:
result: The workflow execution result
run_id: Unique identifier for this execution run
- Return type:
Tuple containing
- Raises:
PermissionError – If user lacks permission to execute workflow
ValueError – If workflow or user_context is invalid
- Side Effects:
Applies access control rules to global manager if config provided
Logs audit events for access decisions
Enables access control globally during execution
Example
>>> from kailash.runtime.access_controlled import execute_with_access_control >>> from kailash.access_control import UserContext >>> from kailash.workflow import Workflow >>> >>> user = UserContext(user_id="123", tenant_id="abc", email="user@test.com", roles=["viewer"]) >>> workflow = Workflow(workflow_id="test", name="Test") >>> # Function exists and can be called >>> callable(execute_with_access_control) True
Base Nodes with ACL
Base Node with Optional Access Control Layer
This module extends the base Node class with optional access control capabilities. The access control is completely transparent and disabled by default, ensuring no interference with existing SDK usage.
Key Design Principles: - Access control is OFF by default - Zero performance impact when disabled - Fully backward compatible - Opt-in at workflow or node level - No changes required to existing code
- class kailash.nodes.base_with_acl.NodeWithAccessControl(**config)[source]
Bases:
NodeBase node class with optional access control capabilities.
Extends the standard Node class with transparent access control features that can be enabled on demand without affecting existing functionality. Access control is completely disabled by default for backward compatibility.
- Design Purpose:
Provides a foundation for nodes that need access control while maintaining complete backward compatibility. Enables fine-grained permissions, data masking, and conditional execution.
- Upstream Dependencies:
AccessControlManager for permission evaluation
UserContext from authentication systems
PermissionRule definitions from configuration
- Downstream Consumers:
AccessControlledRuntime for secure execution
Audit systems for logging access attempts
Data masking systems for output filtering
- Usage Patterns:
Extended by nodes requiring access control
Configured with permission requirements
Used in conjunction with AccessControlledRuntime
Transparent to existing node implementations
- Implementation Details:
Access control is evaluated only when explicitly enabled. Permissions checked before node execution. Output masking applied based on user roles. Fallback execution for denied access scenarios.
- Error Handling:
Access denied returns user-friendly error messages
Missing permissions default to deny
Configuration errors are logged and treated as disabled
Execution errors maintain standard Node behavior
- Side Effects:
Logs access attempts for audit purposes
May redirect execution to fallback nodes
Applies data masking to sensitive outputs
Example
>>> class SecureProcessorNode(NodeWithAccessControl): ... def _execute(self, **inputs): ... return {"result": "processed"} >>> >>> node = SecureProcessorNode( ... enable_access_control=True, ... required_permission=NodePermission.EXECUTE, ... mask_output_fields=["sensitive_data"] ... )
- __init__(**config)[source]
Initialize the node with configuration parameters.
This method performs the following initialization steps:
Sets the node ID (defaults to class name)
Creates metadata from provided arguments
Sets up logging for the node
Stores configuration in self.config
Validates configuration against parameters
The configuration is validated by calling _validate_config(), which checks that all required parameters are present and of the correct type.
- Parameters:
**kwargs – Configuration parameters including: - id: Optional custom node ID - name: Display name for the node - description: Node description - version: Node version - author: Node author - tags: Set of tags for discovery - Any parameters defined in get_parameters()
- Raises:
NodeConfigurationError – If configuration is invalid or if metadata validation fails
- Downstream effects:
Creates self.metadata for discovery
Sets up self.logger for execution logging
Stores self.config for runtime access
Validates parameters are correctly specified
- run(**inputs) Any[source]
Execute node with optional access control checks.
If access control is disabled or no user context is present, this behaves exactly like the standard Node.execute() method.
- Return type:
- classmethod __init_subclass__(**subclass_kwargs)
Install a per-subclass __init__ wrapper that captures bound init params.
Issue #929:
Workflow.to_dict() → Workflow.from_dict()silently strips every named/positional argument that a subclass__init__consumes WITHOUT re-injecting intoself.config.PythonCodeNode.__init__consumescode,input_types,output_type, etc. as named args; none of them flow intosuper().__init__(**kwargs)because they were peeled off the kwargs dict before the super call.The fix is applied here ONCE per subclass: wrap
cls.__init__so that AFTER the original init runs (andself.configis populated byNode.__init__), the bound init parameters are merged intoself.configfor every name that:is not in
_INIT_CAPTURE_EXCLUDE,is not already present in
self.config(subclass may have set it directly via**kwargsforwarding),has a non-sentinel value (positional defaults pass through; the sentinel for “user passed this” is “binding succeeded” — we keep the bound value verbatim, including
None, so round-trip is faithful).
The wrapper is installed exactly once per subclass tree leaf via the
_init_capture_installedmarker, so re-imports / multiple subclass definitions of the same class do not re-wrap.Round-trip contract:
cls(**self.config)afterto_dict/from_dictreconstructs an equivalent node, EXCEPT for params whose values are non-JSON-serializable runtime objects (callables, classes, file handles). Those are still captured intoself.config(so the dict carries them in-memory), butWorkflow.to_json()will skip or fail on them — that is a separate concern and matches existing behavior.
- execute(**runtime_inputs) dict[str, Any]
Execute the node with validation and error handling.
This is the main entry point for node execution that orchestrates the complete execution lifecycle:
Input validation (validate_inputs)
Execution (run)
Output validation (validate_outputs)
Error handling and logging
Performance metrics
Execution flow:
Logs execution start
Validates inputs against parameter schema
Calls run() with validated inputs
Validates outputs are JSON-serializable
Logs execution time
Returns validated outputs
Error handling strategy:
NodeValidationError: Re-raised as-is (input/output issues)
NodeExecutionError: Re-raised as-is (run() failures)
Other exceptions: Wrapped in NodeExecutionError
Performance tracking:
Records execution start/end times
Logs total execution duration
Includes timing in execution logs
- Returns:
Dictionary of validated outputs from run()
- Raises:
NodeExecutionError – If execution fails in run()
NodeValidationError – If input/output validation fails
- Return type:
- Called by:
LocalRuntime: During workflow execution
TaskManager: With execution tracking
Unit tests: For node testing
- Downstream effects:
Logs provide execution history
Metrics enable performance monitoring
Validation ensures data integrity
- get_cache_stats() dict[str, Any]
Get parameter cache statistics.
- Returns:
enabled: Whether caching is enabled
size: Current cache size
max_size: Maximum cache size
hits: Number of cache hits
misses: Number of cache misses
evictions: Number of cache evictions
hit_rate: Cache hit rate (0-1)
- Return type:
Dictionary containing cache statistics
- get_output_schema() dict[str, NodeParameter]
Define output parameters for this node.
This optional method allows nodes to specify their output schema for validation. If not overridden, outputs will only be validated for JSON-serializability.
Design purpose: - Enables static analysis of node outputs - Provides runtime validation of output types - Supports automatic documentation of outputs - Facilitates workflow validation and type checking
The output schema serves similar purposes as input parameters:
Type validation during execution
Documentation for downstream consumers
Workflow connection validation
Export manifest generation
Example
>>> def get_output_schema(self): ... return { ... 'dataframe': NodeParameter( ... name='dataframe', ... type=dict, ... required=True, ... description='Processed data as dictionary' ... ), ... 'row_count': NodeParameter( ... name='row_count', ... type=int, ... required=True, ... description='Number of rows processed' ... ), ... 'processing_time': NodeParameter( ... name='processing_time', ... type=float, ... required=False, ... description='Time taken to process in seconds' ... ) ... }
- Returns:
Dictionary mapping output names to their parameter definitions Empty dict by default (no schema validation)
- Return type:
- Used by:
validate_outputs(): Validates runtime outputs
Workflow.connect(): Validates connections between nodes
Documentation generators: Create output documentation
Export systems: Include output schemas in manifests
- abstractmethod get_parameters() dict[str, NodeParameter]
Define the parameters this node accepts.
This abstract method must be implemented by all concrete nodes to specify their input schema. The parameters define:
What inputs the node expects
Type requirements for each input
Whether inputs are required or optional
Default values for optional inputs
Documentation for each parameter
The returned dictionary is used throughout the node lifecycle:
During initialization: _validate_config() checks configuration
During execution: validate_inputs() validates runtime data
During workflow creation: Used for connection validation
During export: Included in workflow manifests
Example
>>> def get_parameters(self): ... return { ... 'input_file': NodeParameter( ... name='input_file', ... type=str, ... required=True, ... description='Path to input CSV file' ... ), ... 'delimiter': NodeParameter( ... name='delimiter', ... type=str, ... required=False, ... default=',', ... description='CSV delimiter character' ... ) ... }
- Returns:
Dictionary mapping parameter names to their definitions
- Return type:
- Used by:
_validate_config(): Validates configuration matches parameters
validate_inputs(): Validates runtime inputs
to_dict(): Includes parameters in serialization
Workflow.connect(): Validates compatible connections
- get_workflow_context(key: str, default: Any | None = None) Any
Get a value from the workflow context.
This method allows nodes to retrieve shared state from the workflow execution context. The workflow context is managed by the runtime and provides a way for nodes to share data within a single workflow execution.
- Parameters:
- Returns:
The value from the workflow context, or default if not found
- Return type:
Example
>>> # In a transaction node >>> connection = self.get_workflow_context('transaction_connection') >>> if connection: >>> # Use the shared connection >>> result = await connection.execute(query)
- property id: str
Backward compatibility property for node identifier.
Returns the node’s identifier (_node_id). This property maintains backward compatibility for code that accesses node.id.
The internal identifier is now _node_id to prevent namespace collision with user’s ‘id’ parameter.
- property metadata: NodeMetadata
Backward compatibility property for node metadata.
Returns the node’s internal NodeMetadata object (_node_metadata). This property maintains backward compatibility for code that accesses node.metadata.
The internal metadata is now _node_metadata to prevent namespace collision with user’s ‘metadata’ parameter.
- Returns:
NodeMetadata object containing node identification and documentation
Note
Users can now have parameters named “metadata” without conflicts. The parameter will be in node.config[‘metadata’], while this property returns the internal NodeMetadata object.
- set_workflow_context(key: str, value: Any) None
Set a value in the workflow context.
This method allows nodes to store shared state in the workflow execution context. Other nodes in the same workflow execution can retrieve this data using get_workflow_context().
- Parameters:
- Return type:
None
Example
>>> # In a transaction scope node >>> connection = await self.get_connection() >>> transaction = await connection.begin() >>> self.set_workflow_context('transaction_connection', connection) >>> self.set_workflow_context('active_transaction', transaction)
- to_dict() dict[str, Any]
Convert node to dictionary representation.
Serializes the node instance to a dictionary format suitable for:
Workflow export
Node persistence
API responses
Configuration sharing
The serialized format includes:
id: Unique node identifier
type: Node class name
metadata: Complete node metadata
config: Current configuration
parameters: Parameter definitions with types
Type serialization:
Python types are converted to string names
Complex types may require custom handling
Parameter defaults are included
- Returns:
Node identification and type
Complete metadata
Configuration values
Parameter schemas
- Return type:
Dictionary representation containing
- Raises:
NodeExecutionError – If serialization fails due to: - get_parameters() errors - Metadata serialization issues - Type conversion problems
- Used by:
WorkflowExporter: For workflow serialization
CLI: For node inspection
API: For node information endpoints
Debugging: For node state inspection
- validate_inputs(**kwargs) dict[str, Any]
Validate runtime inputs against node requirements.
This method validates inputs provided at execution time against the node’s parameter schema. It ensures type safety and provides helpful error messages for invalid inputs.
Validation steps:
Gets parameter definitions from get_parameters()
Checks each parameter for:
Presence (if required)
Type compatibility
Null handling for optional parameters
Attempts type conversion if needed
Applies default values for missing optional parameters
Key behaviors:
Required parameters must be provided or have defaults
Optional parameters can be None
Type mismatches attempt conversion before failing
Error messages include parameter descriptions
- Example flow:
# Node expects: {‘count’: int, ‘name’: str (optional)} inputs = {‘count’: ‘42’, ‘name’: None} validated = validate_inputs(**inputs) # Returns: {‘count’: 42} # Converted and None removed
- Parameters:
**kwargs – Runtime inputs to validate
- Returns:
Type conversions applied
Defaults for missing optional parameters
None values removed for optional parameters
- Return type:
Dictionary of validated inputs with
- Raises:
NodeValidationError – If inputs are invalid: - Missing required parameters - Type conversion failures - get_parameters() errors
- Called by:
execute(): Before passing inputs to run()
Workflow validation: During connection checks
- validate_outputs(outputs: dict[str, Any]) dict[str, Any]
Validate outputs against schema and JSON-serializability.
This enhanced method validates outputs in two ways:
Schema validation: If get_output_schema() is defined, validates types and required fields
JSON serialization: Ensures all outputs can be serialized
Validation process:
Check outputs is a dictionary
If output schema exists:
Validate required fields are present
Check type compatibility
Attempt type conversion if needed
Verify JSON-serializability
Return validated outputs
Schema validation features:
Required outputs must be present
Optional outputs can be None or missing
Type mismatches attempt conversion
Clear error messages with field details
- Parameters:
outputs (dict[str, Any]) – Outputs to validate from run() method
- Returns:
The same outputs dictionary if valid
- Raises:
NodeValidationError – If outputs are invalid: - Not a dictionary - Missing required outputs - Type validation failures - Non-serializable values
- Return type:
- Called by:
execute(): After run() completes
Test utilities: For output validation
- class kailash.nodes.base_with_acl.AsyncNodeWithAccessControl(**config)[source]
Bases:
AsyncNodeAsync version of NodeWithAccessControl
- __init__(**config)[source]
Initialize AsyncNode with all enterprise capabilities.
This calls the MRO chain to initialize all mixins and the base Node. The MRO ensures each mixin’s __init__ is called exactly once.
- Parameters:
**kwargs – Configuration parameters for node and mixins - All Node parameters (node_id, node_type, config, etc.) - security_config: Optional SecurityConfig for SecurityMixin - log_level: Log level for LoggingMixin (default: “INFO”) - enable_performance_tracking: Enable performance metrics (default: True)
- classmethod __init_subclass__(**subclass_kwargs)
Install a per-subclass __init__ wrapper that captures bound init params.
Issue #929:
Workflow.to_dict() → Workflow.from_dict()silently strips every named/positional argument that a subclass__init__consumes WITHOUT re-injecting intoself.config.PythonCodeNode.__init__consumescode,input_types,output_type, etc. as named args; none of them flow intosuper().__init__(**kwargs)because they were peeled off the kwargs dict before the super call.The fix is applied here ONCE per subclass: wrap
cls.__init__so that AFTER the original init runs (andself.configis populated byNode.__init__), the bound init parameters are merged intoself.configfor every name that:is not in
_INIT_CAPTURE_EXCLUDE,is not already present in
self.config(subclass may have set it directly via**kwargsforwarding),has a non-sentinel value (positional defaults pass through; the sentinel for “user passed this” is “binding succeeded” — we keep the bound value verbatim, including
None, so round-trip is faithful).
The wrapper is installed exactly once per subclass tree leaf via the
_init_capture_installedmarker, so re-imports / multiple subclass definitions of the same class do not re-wrap.Round-trip contract:
cls(**self.config)afterto_dict/from_dictreconstructs an equivalent node, EXCEPT for params whose values are non-JSON-serializable runtime objects (callables, classes, file handles). Those are still captured intoself.config(so the dict carries them in-memory), butWorkflow.to_json()will skip or fail on them — that is a separate concern and matches existing behavior.
- async audit_log(action: str, details: Dict[str, Any]) None
Log an audit event (async override).
Overrides SecurityMixin.audit_log to prevent blocking the event loop. Uses asyncio.to_thread() to offload print() to thread pool.
- async emit_node_completed(outputs: Dict[str, Any] | None = None, execution_time_ms: float | None = None)
Emit node completed event.
- async emit_node_progress(progress_percent: float, message: str | None = None)
Emit node progress event.
- execute(**runtime_inputs) dict[str, Any]
Execute the node synchronously by running async code with proper event loop handling.
This enhanced implementation handles all event loop scenarios: 1. No event loop: Create new one with asyncio.run() 2. Event loop running: Use ThreadPoolExecutor with isolated loop 3. Threaded contexts: Proper thread-safe execution 4. Windows compatibility: ProactorEventLoopPolicy support
- async execute_async(**runtime_inputs) dict[str, Any]
Execute the node asynchronously with validation and error handling.
This method follows the same pattern as execute() but supports asynchronous execution. It performs:
Input validation
Execution via async_run()
Output validation
Error handling and logging
- get_cache_stats() dict[str, Any]
Get parameter cache statistics.
- Returns:
enabled: Whether caching is enabled
size: Current cache size
max_size: Maximum cache size
hits: Number of cache hits
misses: Number of cache misses
evictions: Number of cache evictions
hit_rate: Cache hit rate (0-1)
- Return type:
Dictionary containing cache statistics
- get_output_schema() dict[str, NodeParameter]
Define output parameters for this node.
This optional method allows nodes to specify their output schema for validation. If not overridden, outputs will only be validated for JSON-serializability.
Design purpose: - Enables static analysis of node outputs - Provides runtime validation of output types - Supports automatic documentation of outputs - Facilitates workflow validation and type checking
The output schema serves similar purposes as input parameters:
Type validation during execution
Documentation for downstream consumers
Workflow connection validation
Export manifest generation
Example
>>> def get_output_schema(self): ... return { ... 'dataframe': NodeParameter( ... name='dataframe', ... type=dict, ... required=True, ... description='Processed data as dictionary' ... ), ... 'row_count': NodeParameter( ... name='row_count', ... type=int, ... required=True, ... description='Number of rows processed' ... ), ... 'processing_time': NodeParameter( ... name='processing_time', ... type=float, ... required=False, ... description='Time taken to process in seconds' ... ) ... }
- Returns:
Dictionary mapping output names to their parameter definitions Empty dict by default (no schema validation)
- Return type:
- Used by:
validate_outputs(): Validates runtime outputs
Workflow.connect(): Validates connections between nodes
Documentation generators: Create output documentation
Export systems: Include output schemas in manifests
- abstractmethod get_parameters() dict[str, NodeParameter]
Define the parameters this node accepts.
This abstract method must be implemented by all concrete nodes to specify their input schema. The parameters define:
What inputs the node expects
Type requirements for each input
Whether inputs are required or optional
Default values for optional inputs
Documentation for each parameter
The returned dictionary is used throughout the node lifecycle:
During initialization: _validate_config() checks configuration
During execution: validate_inputs() validates runtime data
During workflow creation: Used for connection validation
During export: Included in workflow manifests
Example
>>> def get_parameters(self): ... return { ... 'input_file': NodeParameter( ... name='input_file', ... type=str, ... required=True, ... description='Path to input CSV file' ... ), ... 'delimiter': NodeParameter( ... name='delimiter', ... type=str, ... required=False, ... default=',', ... description='CSV delimiter character' ... ) ... }
- Returns:
Dictionary mapping parameter names to their definitions
- Return type:
- Used by:
_validate_config(): Validates configuration matches parameters
validate_inputs(): Validates runtime inputs
to_dict(): Includes parameters in serialization
Workflow.connect(): Validates compatible connections
- get_workflow_context(key: str, default: Any | None = None) Any
Get a value from the workflow context.
This method allows nodes to retrieve shared state from the workflow execution context. The workflow context is managed by the runtime and provides a way for nodes to share data within a single workflow execution.
- Parameters:
- Returns:
The value from the workflow context, or default if not found
- Return type:
Example
>>> # In a transaction node >>> connection = self.get_workflow_context('transaction_connection') >>> if connection: >>> # Use the shared connection >>> result = await connection.execute(query)
- property id: str
Backward compatibility property for node identifier.
Returns the node’s identifier (_node_id). This property maintains backward compatibility for code that accesses node.id.
The internal identifier is now _node_id to prevent namespace collision with user’s ‘id’ parameter.
- async log_error(message: str, error: Exception | None = None, **extra) None
Log error message with context (async override).
Overrides LoggingMixin.log_error to prevent blocking.
- async log_error_with_traceback(error: Exception, operation: str = 'unknown') None
Log an error with full traceback information (async override).
Overrides LoggingMixin.log_error_with_traceback to prevent blocking.
- async log_info(message: str, **extra) None
Log info message with context (async override).
Overrides LoggingMixin.log_info to prevent blocking.
- Parameters:
message (str) – Log message
**extra – Additional context
- Return type:
None
- async log_node_execution(operation: str, **context) None
Log node execution information (async override).
Overrides LoggingMixin.log_node_execution to prevent blocking.
- Parameters:
operation (str) – Type of operation being performed
**context – Additional context
- Return type:
None
- async log_security_event(event: str, level: str = 'INFO') None
Log a security-related event (async override).
This method provides async logging for security events when audit logging is enabled in security_config.
- async log_warning(message: str, **extra) None
Log warning message with context (async override).
Overrides LoggingMixin.log_warning to prevent blocking.
- Parameters:
message (str) – Log message
**extra – Additional context
- Return type:
None
- async log_with_context(level: str, message: str, **context) None
Log a message with additional context (async override).
Overrides LoggingMixin.log_with_context to prevent blocking.
- property metadata: NodeMetadata
Backward compatibility property for node metadata.
Returns the node’s internal NodeMetadata object (_node_metadata). This property maintains backward compatibility for code that accesses node.metadata.
The internal metadata is now _node_metadata to prevent namespace collision with user’s ‘metadata’ parameter.
- Returns:
NodeMetadata object containing node identification and documentation
Note
Users can now have parameters named “metadata” without conflicts. The parameter will be in node.config[‘metadata’], while this property returns the internal NodeMetadata object.
- run(**kwargs) dict[str, Any]
Synchronous run is not supported for AsyncNode.
AsyncNode subclasses should implement async_run() instead of run(). This method exists to provide a clear error message if someone accidentally tries to implement run() on an async node.
- Raises:
NotImplementedError – Always, as async nodes must use async_run()
- Return type:
- set_event_context(event_stream: EventStream, session_id: str | None = None, workflow_id: str | None = None, execution_id: str | None = None)
Set the event context for this node.
- set_log_context(**context)
Set logging context.
- set_workflow_context(key: str, value: Any) None
Set a value in the workflow context.
This method allows nodes to store shared state in the workflow execution context. Other nodes in the same workflow execution can retrieve this data using get_workflow_context().
- Parameters:
- Return type:
None
Example
>>> # In a transaction scope node >>> connection = await self.get_connection() >>> transaction = await connection.begin() >>> self.set_workflow_context('transaction_connection', connection) >>> self.set_workflow_context('active_transaction', transaction)
- to_dict() dict[str, Any]
Convert node to dictionary representation.
Serializes the node instance to a dictionary format suitable for:
Workflow export
Node persistence
API responses
Configuration sharing
The serialized format includes:
id: Unique node identifier
type: Node class name
metadata: Complete node metadata
config: Current configuration
parameters: Parameter definitions with types
Type serialization:
Python types are converted to string names
Complex types may require custom handling
Parameter defaults are included
- Returns:
Node identification and type
Complete metadata
Configuration values
Parameter schemas
- Return type:
Dictionary representation containing
- Raises:
NodeExecutionError – If serialization fails due to: - get_parameters() errors - Metadata serialization issues - Type conversion problems
- Used by:
WorkflowExporter: For workflow serialization
CLI: For node inspection
API: For node information endpoints
Debugging: For node state inspection
- track_performance(func)
Decorator to track method performance.
- async validate_and_sanitize_inputs(inputs: Dict[str, Any]) Dict[str, Any]
Validate and sanitize input parameters (async override).
Overrides SecurityMixin.validate_and_sanitize_inputs when the full SecurityMixin from mixins.py is used (with logging).
- validate_inputs(**kwargs) dict[str, Any]
Validate runtime inputs against node requirements.
This method validates inputs provided at execution time against the node’s parameter schema. It ensures type safety and provides helpful error messages for invalid inputs.
Validation steps:
Gets parameter definitions from get_parameters()
Checks each parameter for:
Presence (if required)
Type compatibility
Null handling for optional parameters
Attempts type conversion if needed
Applies default values for missing optional parameters
Key behaviors:
Required parameters must be provided or have defaults
Optional parameters can be None
Type mismatches attempt conversion before failing
Error messages include parameter descriptions
- Example flow:
# Node expects: {‘count’: int, ‘name’: str (optional)} inputs = {‘count’: ‘42’, ‘name’: None} validated = validate_inputs(**inputs) # Returns: {‘count’: 42} # Converted and None removed
- Parameters:
**kwargs – Runtime inputs to validate
- Returns:
Type conversions applied
Defaults for missing optional parameters
None values removed for optional parameters
- Return type:
Dictionary of validated inputs with
- Raises:
NodeValidationError – If inputs are invalid: - Missing required parameters - Type conversion failures - get_parameters() errors
- Called by:
execute(): Before passing inputs to run()
Workflow validation: During connection checks
- validate_outputs(outputs: dict[str, Any]) dict[str, Any]
Validate outputs against schema and JSON-serializability.
This enhanced method validates outputs in two ways:
Schema validation: If get_output_schema() is defined, validates types and required fields
JSON serialization: Ensures all outputs can be serialized
Validation process:
Check outputs is a dictionary
If output schema exists:
Validate required fields are present
Check type compatibility
Attempt type conversion if needed
Verify JSON-serializability
Return validated outputs
Schema validation features:
Required outputs must be present
Optional outputs can be None or missing
Type mismatches attempt conversion
Clear error messages with field details
- Parameters:
outputs (dict[str, Any]) – Outputs to validate from run() method
- Returns:
The same outputs dictionary if valid
- Raises:
NodeValidationError – If outputs are invalid: - Not a dictionary - Missing required outputs - Type validation failures - Non-serializable values
- Return type:
- Called by:
execute(): After run() completes
Test utilities: For output validation
- kailash.nodes.base_with_acl.make_node_access_controlled(node_class, **acl_config)[source]
Factory function to add access control to any existing node class.
This allows adding access control to nodes without modifying their code:
>>> from kailash.nodes.data.readers import CSVReaderNode >>> SecureCSVReader = make_node_access_controlled( ... CSVReaderNode, ... enable_access_control=True, ... required_permission=NodePermission.READ_OUTPUT ... )
- kailash.nodes.base_with_acl.add_access_control(node_instance, **acl_config)[source]
Add access control to an existing node instance.
This function adds access control attributes to a node instance. For simplicity in this example, we’ll just add the attributes and let the AccessControlledRuntime handle the actual access control.
- Parameters:
node_instance – The node instance to wrap
**acl_config – Access control configuration - enable_access_control: Whether to enable access control (default: True) - required_permission: Permission required to execute the node - node_id: Unique identifier for access control rules - mask_output_fields: List of fields to mask in output for non-admin users - fallback_node: Node ID to execute if access is denied
- Returns:
Node instance with access control capabilities
Example
>>> reader = CSVReaderNode(file_path="data.csv") >>> secure_reader = add_access_control( ... reader, ... enable_access_control=True, ... required_permission=NodePermission.EXECUTE, ... node_id="secure_csv_reader" ... )
Examples
Basic RBAC Setup
from kailash.access_control import (
UserContext, PermissionRule, NodePermission,
WorkflowPermission, PermissionEffect, get_access_control_manager
)
from kailash.runtime.access_controlled import AccessControlledRuntime
# Create user context
user = UserContext(
user_id="john_doe",
tenant_id="acme_corp",
email="john@acme.com",
roles=["analyst", "viewer"]
)
# Configure access control
acm = get_access_control_manager()
acm.enabled = True
# Add permission rules
acm.add_rule(PermissionRule(
id="allow_analysts_execute",
resource_type="workflow",
resource_id="customer_analytics",
permission=WorkflowPermission.EXECUTE,
effect=PermissionEffect.ALLOW,
role="analyst"
))
# Use secure runtime
runtime = AccessControlledRuntime(user_context=user)
result, run_id = runtime.execute(workflow)
Multi-Tenant Isolation
# Create tenant-specific rules
tenant_rule = PermissionRule(
id="tenant_isolation",
resource_type="node",
resource_id="sensitive_data",
permission=NodePermission.READ_OUTPUT,
effect=PermissionEffect.ALLOW,
tenant_id="acme_corp" # Only ACME users can access
)
acm.add_rule(tenant_rule)
# Users from other tenants will be denied access
other_user = UserContext(
user_id="jane_smith",
tenant_id="other_corp",
email="jane@other.com",
roles=["admin"]
)
# This will be denied due to tenant mismatch
runtime = AccessControlledRuntime(user_context=other_user)
Data Masking
from kailash.nodes.base_with_acl import add_access_control
from kailash.nodes.data.readers import CSVReaderNode
# Create secure data reader with field masking
secure_reader = add_access_control(
CSVReaderNode(file_path="customers.csv"),
enable_access_control=True,
required_permission=NodePermission.READ_OUTPUT,
mask_output_fields=["ssn", "phone"] # Mask for non-admin users
)
workflow.add_node("secure_data", secure_reader)
Permission-Based Routing
# Different processing based on user permissions
admin_processor = PythonCodeNode.from_function(
lambda data: {"result": process_all_data(data)},
name="admin_processor"
)
viewer_processor = PythonCodeNode.from_function(
lambda data: {"result": process_summary_data(data)},
name="viewer_processor"
)
# Configure different permissions for each path
acm.add_rule(PermissionRule(
id="admin_full_access",
resource_type="node",
resource_id="admin_processor",
permission=NodePermission.EXECUTE,
effect=PermissionEffect.ALLOW,
role="admin"
))
acm.add_rule(PermissionRule(
id="viewer_limited_access",
resource_type="node",
resource_id="viewer_processor",
permission=NodePermission.EXECUTE,
effect=PermissionEffect.ALLOW,
role="viewer"
))
# Runtime will automatically route based on user permissions
workflow.add_node("admin_path", admin_processor)
workflow.add_node("viewer_path", viewer_processor)