A robust Python package for building and executing asynchronous workflow tasks in a directed acyclic graph (DAG) pattern. This package provides a type-safe, thread-safe framework for defining, organizing, and running dependent tasks with comprehensive validation and error handling.
Project description
At Common Workflow
Description
At Common Workflow is a workflow management system that allows users to define and execute tasks in a directed acyclic graph (DAG) structure. It supports parallel execution and provides a context manager for managing task inputs and outputs.
Installation
To set up the project, follow these steps:
- Clone the repository:
git clone <repository-url> cd at-common-workflow
- Install the dependencies:
pip install -r requirements.txt
Usage
There are two ways to define tasks: using class inheritance or using the builder pattern.
Class-based Approach
from at_common_workflow.core.task.processing_task import ProcessingTask
from pydantic import BaseModel
class AddInputModel(BaseModel):
a: int
b: int
class AddOutputModel(BaseModel):
result: int
class AddTask(ProcessingTask[AddInputModel, AddOutputModel]):
def __init__(self, name: str):
super().__init__(
name=name,
input_model=AddInputModel,
output_model=AddOutputModel,
processor_function=self._execute
)
async def _execute(self, input: AddInputModel) -> AddOutputModel:
return AddOutputModel(result=input.a + input.b)
# Run workflow
from at_common_workflow.core.workflow.base import Workflow
workflow = Workflow()
task = AddTask("add_numbers")
workflow.add_task(task, argument_mappings={"a": 5, "b": 3}, result_mapping="result")
async for event in workflow.execute():
pass
print(workflow.context.get("result").result) # Output: 8
Builder Pattern Approach (Recommended)
from at_common_workflow.core.workflow.builder import WorkflowBuilder
from pydantic import BaseModel
class AddInputModel(BaseModel):
a: int
b: int
class AddOutputModel(BaseModel):
result: int
async def execute_add(input: AddInputModel) -> AddOutputModel:
return AddOutputModel(result=input.a + input.b)
# Create and execute workflow
workflow = (WorkflowBuilder()
.task("add_numbers")
.input_model(AddInputModel)
.output_model(AddOutputModel)
.processor(execute_add)
.arg("a", 5)
.arg("b", 3)
.output("result")
.build())
async for event in workflow.execute():
pass
print(workflow.context.get("result").result) # Output: 8
Tasks with No Output
For tasks that perform actions but don't need to store their result in the workflow context (like sending notifications or logging), you can call output() with no parameters:
workflow = (WorkflowBuilder()
.task("send_notification")
.input_model(NotificationInput)
# output_model is optional when not storing results
.processor(send_notification)
.arg("message", "Hello!")
.arg("recipient", "user@example.com")
.output() # Don't store the output
.build())
This will execute the task but won't store its result in the workflow context. This is useful for tasks that have side effects (like sending notifications) but don't produce meaningful output that other tasks need.
For tasks that don't produce meaningful output, you can also skip defining an output model entirely or explicitly set it to None:
# Task function that doesn't produce meaningful output
async def send_notification(input: NotificationInput) -> None:
print(f"Sending notification to {input.recipient}: {input.message}")
# Some side effect...
return None # or just omit the return statement
Note that output models are still required when you need to store results in the workflow context.
Tasks with No Input
The workflow system allows you to define tasks that don't require any input. This can be useful for tasks that:
- Generate data without external input (like current time, random values, etc.)
- Perform standalone operations not dependent on previous task outputs
- Initialize resources or set up environments
Creating Tasks with No Input
When defining a task with no input, you have two options for your processor function:
Option 1: Processor with Empty Input Parameter
You can define a processor function that has an input parameter, which will receive an empty dictionary ({}):
async def processor_with_empty_input(input: dict) -> YourOutputModel:
# The input parameter is an empty dict
# Process without input
return YourOutputModel(...)
Option 2: Processor with No Parameters
Alternatively, you can define a processor function with no parameters at all:
async def processor_with_no_parameters() -> YourOutputModel:
# No input parameter
# Process without input
return YourOutputModel(...)
Both approaches are valid and supported by the framework.
Building a Task with No Input
When using the workflow builder, simply omit the input_model method call:
workflow = (WorkflowBuilder()
.task("no_input_task")
# No input_model call here
.output_model(YourOutputModel)
.processor(processor_with_no_parameters) # or processor_with_empty_input
.output("result")
.build()
)
Example
Here is a complete example of a task with no input that returns the current time:
from pydantic import BaseModel
from at_common_workflow.core.workflow.builder import WorkflowBuilder
import asyncio
from datetime import datetime
# Define the output model
class TimeInfo(BaseModel):
current_time: str
# Define a processor function with no parameters
async def get_current_time() -> TimeInfo:
return TimeInfo(
current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
# Build and run the workflow
async def main():
workflow = (WorkflowBuilder()
.task("get_time")
.output_model(TimeInfo)
.processor(get_current_time)
.output("time_info")
.build()
)
# Execute workflow
async for event in workflow.execute():
pass
# Access the result
time_info = workflow.context.get("time_info")
print(f"Current time: {time_info.current_time}")
if __name__ == "__main__":
asyncio.run(main())
Notes
- Task functions without input parameters must still be async functions.
- All other task configuration (output model, progress model, etc.) remains the same.
- The framework continues to support legacy code that uses the empty dict approach.
Examples
Check out the examples directory for more in-depth examples:
example1_basic_workflow.py- Basic workflow setup and executionexample2_progress_updates.py- Using progress updates in tasksexample3_parallel_tasks.py- Running tasks in parallelexample4_nested_data.py- Working with nested data structuresexample5_error_handling.py- Handling errors in workflowsexample6_arithmetic_operations.py- Performing arithmetic operationsexample7_no_output.py- Tasks that don't store their outputexample8_no_input_parameter.py- Tasks with no input parameters
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file at_common_workflow-1.5.8.tar.gz.
File metadata
- Download URL: at_common_workflow-1.5.8.tar.gz
- Upload date:
- Size: 47.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58a51481898763273863c65de43af55dac74c8d530a7c434d42a48a40ce18249
|
|
| MD5 |
460ce4c16021b8e2fe99c842e86657a6
|
|
| BLAKE2b-256 |
c37f937dc3df874ff896ef65e89915d6ab319d317f0510f081cdc9978c5ef337
|
File details
Details for the file at_common_workflow-1.5.8-py3-none-any.whl.
File metadata
- Download URL: at_common_workflow-1.5.8-py3-none-any.whl
- Upload date:
- Size: 38.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c065cd31ca21395e3185de02268d86fa612594ce62663b63a480e3c8ce480a0a
|
|
| MD5 |
5c93de7c5bb7fb8e4e673950c15a853a
|
|
| BLAKE2b-256 |
bd727406e61c5fa3cff7089912b2dde84731030f84c035ac35514e2183aeaf5f
|