Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 45
Adding triggers to graphs#428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
19bc77837b484372ff4b154ff19b082ff4e4a8d88d831b8ab582db19c955bc5eb5acabb83fc227f52fe39fdda8845cb95d6b2bf85155f7cc5042c7aFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| from pydantic import Field | ||
| from beanie import Document | ||
| from typing import Optional | ||
| from pymongo import IndexModel | ||
| from ..trigger_models import TriggerTypeEnum, TriggerStatusEnum | ||
| from datetime import datetime | ||
| class DatabaseTriggers(Document): | ||
| type: TriggerTypeEnum = Field(..., description="Type of the trigger") | ||
| expression: Optional[str] = Field(default=None, description="Expression of the trigger") | ||
| graph_name: str = Field(..., description="Name of the graph") | ||
| namespace: str = Field(..., description="Namespace of the graph") | ||
| trigger_time: datetime = Field(..., description="Trigger time of the trigger") | ||
| trigger_status: TriggerStatusEnum = Field(..., description="Status of the trigger") | ||
| class Settings: | ||
| indexes = [ | ||
| IndexModel( | ||
| [ | ||
| ("trigger_time", -1), | ||
| ], | ||
| name="idx_trigger_time" | ||
| ), | ||
| IndexModel( | ||
| [ | ||
| ("type", 1), | ||
| ("expression", 1), | ||
| ("graph_name", 1), | ||
| ("namespace", 1), | ||
| ("trigger_time", 1), | ||
| ], | ||
| name="uniq_graph_type_expr_time", | ||
| unique=True | ||
| ) | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ] | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| from pydantic import BaseModel, Field, field_validator, model_validator | ||
| from enum import Enum | ||
| from croniter import croniter | ||
| from typing import Self | ||
| class TriggerTypeEnum(str, Enum): | ||
| CRON = "CRON" | ||
| class TriggerStatusEnum(str, Enum): | ||
| PENDING = "PENDING" | ||
| FAILED = "FAILED" | ||
| CANCELLED = "CANCELLED" | ||
| TRIGGERED = "TRIGGERED" | ||
| TRIGGERING = "TRIGGERING" | ||
| class CronTrigger(BaseModel): | ||
| expression: str = Field(..., description="Cron expression for the trigger") | ||
| @field_validator("expression") | ||
| @classmethod | ||
| def validate_expression(cls, v: str) -> str: | ||
| if not croniter.is_valid(v): | ||
| raise ValueError("Invalid cron expression") | ||
| return v | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| class Trigger(BaseModel): | ||
| type: TriggerTypeEnum = Field(..., description="Type of the trigger") | ||
| value: dict = Field(default_factory=dict, description="Value of the trigger") | ||
| @model_validator(mode="after") | ||
| def validate_trigger(self) -> Self: | ||
| if self.type == TriggerTypeEnum.CRON: | ||
| CronTrigger.model_validate(self.value) | ||
| else: | ||
| raise ValueError(f"Unsupported trigger type: {self.type}") | ||
| return self | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| from datetime import datetime | ||
| from uuid import uuid4 | ||
| from app.models.db.trigger import DatabaseTriggers | ||
| from app.models.trigger_models import TriggerStatusEnum, TriggerTypeEnum | ||
| from app.singletons.logs_manager import LogsManager | ||
| from app.controller.trigger_graph import trigger_graph | ||
| from app.models.trigger_graph_model import TriggerGraphRequestModel | ||
| from pymongo import ReturnDocument | ||
| from app.config.settings import get_settings | ||
| import croniter | ||
| import asyncio | ||
| logger = LogsManager().get_logger() | ||
| async def get_due_triggers(cron_time: datetime) -> DatabaseTriggers | None: | ||
| data = await DatabaseTriggers.get_pymongo_collection().find_one_and_update( | ||
| { | ||
| "trigger_time": {"$lte": cron_time}, | ||
| "trigger_status": TriggerStatusEnum.PENDING | ||
| }, | ||
| { | ||
| "$set": {"trigger_status": TriggerStatusEnum.TRIGGERING} | ||
| }, | ||
| return_document=ReturnDocument.AFTER | ||
| ) | ||
| return DatabaseTriggers(**data) if data else None | ||
| async def call_trigger_graph(trigger: DatabaseTriggers): | ||
| await trigger_graph( | ||
| namespace_name=trigger.namespace, | ||
| graph_name=trigger.graph_name, | ||
| body=TriggerGraphRequestModel(), | ||
| x_exosphere_request_id=str(uuid4()) | ||
| ) | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| async def mark_as_failed(trigger: DatabaseTriggers): | ||
| await DatabaseTriggers.get_pymongo_collection().update_one( | ||
| {"_id": trigger.id}, | ||
| {"$set": {"trigger_status": TriggerStatusEnum.FAILED}} | ||
| ) | ||
| async def create_next_triggers(trigger: DatabaseTriggers, cron_time: datetime): | ||
| assert trigger.expression is not None | ||
| iter = croniter.croniter(trigger.expression, cron_time) | ||
| next_trigger_time = iter.get_next(datetime) | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| await DatabaseTriggers( | ||
| type=TriggerTypeEnum.CRON, | ||
| expression=trigger.expression, | ||
| graph_name=trigger.graph_name, | ||
| namespace=trigger.namespace, | ||
| trigger_time=next_trigger_time, | ||
| trigger_status=TriggerStatusEnum.PENDING | ||
| ).insert() | ||
NiveditJain marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| async def mark_as_triggered(trigger: DatabaseTriggers): | ||
| await DatabaseTriggers.get_pymongo_collection().update_one( | ||
| {"_id": trigger.id}, | ||
| {"$set": {"trigger_status": TriggerStatusEnum.TRIGGERED}} | ||
| ) | ||
| async def handle_trigger(cron_time: datetime): | ||
| while(trigger:= await get_due_triggers(cron_time)): | ||
| try: | ||
| await call_trigger_graph(trigger) | ||
| await create_next_triggers(trigger, cron_time) | ||
| await mark_as_triggered(trigger) | ||
| except Exception as e: | ||
| await mark_as_failed(trigger) | ||
| logger.error(f"Error calling trigger graph: {e}") | ||
| async def trigger_cron(): | ||
| cron_time = datetime.now() | ||
| logger.info(f"starting trigger_cron: {cron_time}") | ||
| await asyncio.gather(*[handle_trigger(cron_time) for _ in range(get_settings().trigger_workers)]) | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.