Zuma - Python Workflow Management Framework
Author: CodeJunction
Description: Zuma is a powerful, lightweight and flexible workflow management framework for Python. Build and manage complex workflow pipelines with ease.
Keywords: Zuma, Python workflow, workflow management, async workflow, pipeline management, Python framework

# Effortlessly build workflow pipelines

The powerful, lightweight and flexible workflow management framework for Python

Documentation View on GitLab
simple_workflow.py

    import asyncio
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner

    class DataFetchStep(ZumaActionStep):
        async def execute(self, context):
            return {"data": "fetched_data"}

    class ProcessingStep(ZumaActionStep):
        async def execute(self, context):
            data = context.get("data")
            return {"processed": f"processed_{data}"}

    if __name__ == "__main__":
        asyncio.run(
            ZumaRunner().run_workflow(
                ZumaWorkflow(
                    "Simple Workflow",
                    steps=[
                        DataFetchStep(),
                        ProcessingStep(),
                    ],
                )
            )
        )

## Features

### Async First

Built with asyncio for maximum performance and concurrency

### Type Safe

Full type hints support for better IDE integration

### Parallel Execution

Execute multiple steps concurrently with controlled concurrency

### Error Handling

Comprehensive error handling with retries and failure strategies

### Conditional Flows

Dynamic workflow paths based on runtime conditions

### Monitoring

Detailed execution tracking and progress reporting

### Workflow Visualization

Generate clear Mermaid diagrams to visualize workflow paths, retries, and error handling.

## Examples

simple_example.py

    """Simple Sequential Workflow Example"""
    import asyncio
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner

    class DataFetchStep(ZumaActionStep):
        async def execute(self, context):
            return {"data": "fetched_data"}

    class ProcessingStep(ZumaActionStep):
        async def execute(self, context):
            data = context.get("data")
            return {"processed": f"processed_{data}"}

    if __name__ == "__main__":
        asyncio.run(
            ZumaRunner().run_workflow(
                ZumaWorkflow(
                    "Simple Workflow",
                    steps=[
                        DataFetchStep(),
                        ProcessingStep(),
                    ],
                )
            )
        )

parallel_example.py

    """Parallel Processing Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaParallelAction, ZumaRunner
    from typing import Dict, Any
    import asyncio

    class DataProcessStep(ZumaActionStep):
        """Processes a single file"""
        def __init__(self, name: str, file_type: str):
            super().__init__(name)
            self.file_type = file_type

        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            valid_files = context.get("valid_files", [])
            our_files = [f for f in valid_files if f.endswith(self.file_type)]

            results = []
            for file in our_files:
                print(f"[{self.name}] Processing {file}...")
                await asyncio.sleep(0.5)  # Simulate processing
                results.append({
                    "file": file,
                    "processor": self.name
                })

            return {"processed_files": len(results), "results": results}

    async def run_parallel_workflow():
        # Define parallel data processing
        parallel_processing = ZumaParallelAction(
            "Parallel Processing",
            steps=[
                DataProcessStep("Process CSV", file_type=".csv"),
                DataProcessStep("Process JSON", file_type=".json"),
                DataProcessStep("Process XML", file_type=".xml")
            ],
            max_concurrency=2  # Process 2 file types at a time
        )

        # Create workflow
        workflow = ZumaWorkflow(
            "Parallel Processing Workflow",
            steps=[parallel_processing]
        )

        # Run workflow
        runner = ZumaRunner()
        result = await runner.run_workflow(workflow)
        runner.print_execution_summary(result)
        return result

    if __name__ == "__main__":
        asyncio.run(run_parallel_workflow())

conditional_example.py

    """Conditional Workflow Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaConditionalStep, ZumaRunner
    from typing import Dict, Any
    import asyncio

    def check_data_size(context: Dict[str, Any]) -> bool:
        """Determines processing path based on data size"""
        return context.get("data_size", 0) > 1000

    class DataLoadStep(ZumaActionStep):
        """Loads and measures the size of input data"""
        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            data = context.get("input_data", list(range(2000)))
            print(f"[{self.name}] Loading {len(data)} items...")
            await asyncio.sleep(0.5)
            return {"data": data, "data_size": len(data)}

    class BatchProcessingStep(ZumaActionStep):
        """Handles large dataset processing in batches"""
        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            data = context.get("data", [])
            batch_size = 100
            print(f"[{self.name}] Processing {len(data)} items in batches...")
            processed = []
            for i in range(0, len(data), batch_size):
                batch = data[i : i + batch_size]
                await asyncio.sleep(0.1)  # Simulate batch processing
                processed.extend([f"processed_{item}" for item in batch])
            return {
                "processed_items": len(processed),
                "processing_type": "batch",
                "results": processed,
            }

    class SimpleProcessingStep(ZumaActionStep):
        """Handles small dataset processing"""
        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            data = context.get("data", [])
            print(f"[{self.name}] Processing {len(data)} items directly...")
            await asyncio.sleep(0.5)  # Simulate processing
            processed = [f"processed_{item}" for item in data]
            return {
                "processed_items": len(processed),
                "processing_type": "simple",
                "results": processed,
            }

    async def run_conditional_workflow():
        workflow = ZumaWorkflow(
            "Conditional Processing Workflow",
            steps=[
                DataLoadStep("Load Data"),
                ZumaConditionalStep(
                    "Processing Path Decision",
                    condition=check_data_size,
                    true_component=BatchProcessingStep("Batch Process"),
                    false_component=SimpleProcessingStep("Simple Process"),
                ),
            ],
        )

        # Run workflow with sample data
        initial_context = {"input_data": list(range(2000))}  # Large dataset
        runner = ZumaRunner()
        result = await runner.run_workflow(workflow, context=initial_context)
        runner.print_execution_summary(result)
        return result

    if __name__ == "__main__":
        asyncio.run(run_conditional_workflow())

error_handling_example.py

    """Error Handling Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner, ZumaExecutionError
    from typing import Dict, Any
    import asyncio

    class ValidatingStep(ZumaActionStep):
        """Step that validates input data"""
        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            data = context.get("data")
            print(f"[{self.name}] Validating data: {data}")

            if not self.validate_data(data):
                raise ZumaExecutionError("Data validation failed")

            return {"validated": True, "data": data}

        def validate_data(self, data):
            return isinstance(data, str) and len(data) > 0

    async def run_error_workflow():
        workflow = ZumaWorkflow(
            "Error Handling Workflow",
            steps=[
                ValidatingStep(
                    "Validate Data",
                    description="Validates input data format",
                    retries=3
                )
            ],
            continue_on_failure=True
        )

        runner = ZumaRunner()
        result = await runner.run_workflow(workflow)
        runner.print_execution_summary(result)
        return result

    if __name__ == "__main__":
        asyncio.run(run_error_workflow())

retry_mechanism.py

    """Retry Mechanism Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner, ZumaExecutionError
    from typing import Dict, Any
    import asyncio
    import random

    class UnreliableAPIStep(ZumaActionStep):
        def __init__(self, name: str):
            super().__init__(
                name=name,
                retries=3,  # Try up to 3 times
                retry_delay=1.0  # Wait 1 second between retries
            )

        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            # Simulated API call that might fail
            if random.random() < 0.7:  # 70% chance of failure
                raise ZumaExecutionError("API request failed")
            return {"api_response": "success"}

    async def run_retry_example():
        workflow = ZumaWorkflow(
            "Retry Mechanism Demo",
            steps=[UnreliableAPIStep("API Call")]
        )

        runner = ZumaRunner()
        result = await runner.run_workflow(
            workflow,
            generate_diagram=True,
            diagram_output="retry_mechanism"
        )
        return result

    if __name__ == "__main__":
        asyncio.run(run_retry_example())

workflow_composition.py

    """Workflow Composition Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner
    from typing import Dict, Any
    import asyncio

    class DataFetchStep(ZumaActionStep):
        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            print(f"[{self.name}] Fetching data...")
            await asyncio.sleep(0.5)
            return {"data": "raw_data"}

    class ValidationWorkflow(ZumaWorkflow):
        def __init__(self):
            super().__init__(
                "Data Validation Workflow",
                steps=[
                    DataValidationStep("Format Check"),
                    SchemaValidationStep("Schema Check")
                ]
            )

    class ProcessingWorkflow(ZumaWorkflow):
        def __init__(self):
            super().__init__(
                "Data Processing Workflow",
                steps=[
                    DataTransformStep("Transform"),
                    DataEnrichStep("Enrich")
                ]
            )

    async def run_composed_workflow():
        # Create main workflow that combines other workflows
        workflow = ZumaWorkflow(
            "Main Workflow",
            steps=[
                DataFetchStep("Fetch Data"),
                ValidationWorkflow(),
                ProcessingWorkflow()
            ]
        )

        runner = ZumaRunner()
        result = await runner.run_workflow(workflow)
        runner.print_execution_summary(result)
        return result

    if __name__ == "__main__":
        asyncio.run(run_composed_workflow())

dynamic_workflow.py

    """Dynamic Workflow Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner
    from typing import Dict, Any, List
    import asyncio

    class DynamicStep(ZumaActionStep):
        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            # Generate steps based on context
            data_types = context.get("data_types", [])
            steps = []

            for data_type in data_types:
                steps.append(
                    ProcessingStep(f"Process {data_type}", data_type)
                )

            # Create and run dynamic workflow
            workflow = ZumaWorkflow(
                "Dynamic Sub-Workflow",
                steps=steps
            )

            runner = ZumaRunner()
            result = await runner.run_workflow(workflow, context)
            return result.context

    async def run_dynamic_workflow():
        workflow = ZumaWorkflow(
            "Dynamic Workflow",
            steps=[
                DynamicStep("Generate Steps")
            ]
        )

        # Run with different data types
        context = {"data_types": ["CSV", "JSON", "XML"]}

        runner = ZumaRunner()
        result = await runner.run_workflow(workflow, context)
        runner.print_execution_summary(result)
        return result

    if __name__ == "__main__":
        asyncio.run(run_dynamic_workflow())

custom_actions.py

    """Custom Actions Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner
    from typing import Dict, Any
    import asyncio

    class CustomRetryStep(ZumaActionStep):
        def __init__(self, name: str, max_retries: int = 3):
            super().__init__(
                name=name,
                retries=max_retries,
                retry_delay=1.0
            )
            self.attempt = 0

        async def before_retry(self, error: Exception) -> None:
            self.attempt += 1
            print(f"Retry attempt {self.attempt} after error: {error}")

        async def after_retry(self, result: Dict[str, Any]) -> None:
            print(f"Retry succeeded on attempt {self.attempt}")

        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            # Custom retry logic
            if self.attempt < 2:  # Succeed on third try
                raise Exception("Simulated failure")
            return {"status": "success"}

    async def run_custom_workflow():
        workflow = ZumaWorkflow(
            "Custom Actions Workflow",
            steps=[CustomRetryStep("Custom Retry")]
        )

        runner = ZumaRunner()
        result = await runner.run_workflow(workflow)
        runner.print_execution_summary(result)
        return result

    if __name__ == "__main__":
        asyncio.run(run_custom_workflow())

workflow_visualization.py

    """Workflow Visualization Example"""
    from zuma import ZumaWorkflow, ZumaActionStep, ZumaRunner, ZumaExecutionError
    from typing import Dict, Any
    import asyncio
    import random

    class UnreliableAPIStep(ZumaActionStep):
        def __init__(self, name: str):
            super().__init__(
                name=name,
                retries=3,  # Try up to 3 times
                retry_delay=1.0  # Wait 1 second between retries
            )

        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            # Simulated API call that might fail
            if random.random() < 0.7:  # 70% chance of failure
                raise ZumaExecutionError("API request failed")
            return {"api_response": "success"}

    class DataProcessingStep(ZumaActionStep):
        async def execute(self, context: Dict[str, Any]) -> Dict[str, Any]:
            api_response = context.get("api_response")
            print(f"[{self.name}] Processing response: {api_response}")
            await asyncio.sleep(0.5)  # Simulate processing
            return {"processed_data": f"processed_{api_response}"}

    async def run_visualization_example():
        # Create a workflow with multiple steps to demonstrate visualization
        workflow = ZumaWorkflow(
            "Visualization Demo",
            steps=[
                UnreliableAPIStep("API Call"),
                DataProcessingStep("Process Data")
            ]
        )

        runner = ZumaRunner()
        # Generate visualization diagram
        result = await runner.run_workflow(
            workflow,
            generate_diagram=True,
            diagram_output="workflow_diagram"
        )

        # The diagram will be generated as a Mermaid flowchart
        # showing the workflow structure, retries, and error paths
        return result

    if __name__ == "__main__":
        asyncio.run(run_visualization_example())