cosmo/backend/app/jobs/registry.py

153 lines
4.7 KiB
Python

"""
Task Registry System for Scheduled Jobs
This module provides a decorator-based registration system for predefined tasks.
Tasks are registered with their metadata, parameters schema, and execution function.
"""
import logging
from typing import Dict, Callable, Any, List, Optional
from dataclasses import dataclass, field
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)
class TaskParameter(BaseModel):
"""Task parameter definition"""
name: str = Field(..., description="Parameter name")
type: str = Field(..., description="Parameter type (string, integer, array, boolean)")
description: str = Field(..., description="Parameter description")
required: bool = Field(default=False, description="Whether parameter is required")
default: Any = Field(default=None, description="Default value")
@dataclass
class TaskDefinition:
"""Registered task definition"""
name: str
function: Callable
description: str
parameters: List[TaskParameter] = field(default_factory=list)
category: str = "general"
class TaskRegistry:
"""Registry for predefined scheduled tasks"""
def __init__(self):
self._tasks: Dict[str, TaskDefinition] = {}
def register(
self,
name: str,
description: str,
parameters: Optional[List[Dict[str, Any]]] = None,
category: str = "general"
):
"""
Decorator to register a task function
Usage:
@task_registry.register(
name="sync_positions",
description="Sync celestial body positions",
parameters=[
{"name": "days", "type": "integer", "description": "Days to sync", "default": 7}
]
)
async def sync_positions_task(db, logger, params):
# Task implementation
pass
"""
def decorator(func: Callable):
# Parse parameters
param_list = []
if parameters:
for p in parameters:
param_list.append(TaskParameter(**p))
# Register the task
task_def = TaskDefinition(
name=name,
function=func,
description=description,
parameters=param_list,
category=category
)
self._tasks[name] = task_def
logger.debug(f"Registered task: {name}")
return func
return decorator
def get_task(self, name: str) -> Optional[TaskDefinition]:
"""Get a task definition by name"""
return self._tasks.get(name)
def list_tasks(self) -> List[Dict[str, Any]]:
"""List all registered tasks with their metadata"""
return [
{
"name": task.name,
"description": task.description,
"category": task.category,
"parameters": [
{
"name": p.name,
"type": p.type,
"description": p.description,
"required": p.required,
"default": p.default
}
for p in task.parameters
]
}
for task in self._tasks.values()
]
async def execute_task(
self,
name: str,
db: Any,
logger: logging.Logger,
params: Dict[str, Any]
) -> Any:
"""
Execute a registered task
Args:
name: Task function name
db: Database session
logger: Logger instance
params: Task parameters from function_params JSONB field
Returns:
Task execution result
Raises:
ValueError: If task not found
"""
task_def = self.get_task(name)
if not task_def:
raise ValueError(f"Task '{name}' not found in registry")
# Merge default parameters
merged_params = {}
for param in task_def.parameters:
if param.name in params:
merged_params[param.name] = params[param.name]
elif param.default is not None:
merged_params[param.name] = param.default
elif param.required:
raise ValueError(f"Required parameter '{param.name}' not provided")
# Execute the task function
logger.debug(f"Executing task '{name}' with params: {merged_params}")
result = await task_def.function(db=db, logger=logger, params=merged_params)
logger.debug(f"Task '{name}' completed successfully")
return result
# Global task registry instance
task_registry = TaskRegistry()