| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920 |
- """
- Schedule Entry Module for Trixy Scheduler System
- This module provides the ScheduleEntry class that represents individual schedule entries
- with unique names, triggers, actions, and validation. Each schedule entry can have
- multiple triggers and actions, enabling complex scheduling scenarios.
- Features:
- - Unique name validation and constraint enforcement
- - Multiple trigger support (date, time, event, weekday, interval, cron)
- - Multiple action support (events, ML training, functions)
- - Enable/disable functionality
- - Execution history tracking
- - Thread-safe operations
- - Comprehensive validation and error handling
- - Integration with Trixy event system
- Usage:
- from trixy_core.scheduler.schedule_entry import ScheduleEntry
- from trixy_core.scheduler.triggers import DateTrigger
- from trixy_core.scheduler.actions import EventAction
-
- # Create a schedule entry
- entry = ScheduleEntry(
- name="daily_backup",
- description="Daily backup at 2 AM",
- enabled=True
- )
-
- # Add triggers and actions
- entry.add_trigger(DateTrigger(hour=2, minute=0))
- entry.add_action(EventAction("system_backup", {"type": "full"}))
-
- # Check if schedule should execute
- if entry.should_execute():
- entry.execute()
- """
- import threading
- import time
- import uuid
- from datetime import datetime, timezone
- from typing import List, Dict, Any, Optional, Set, Callable, Union
- from dataclasses import dataclass, field
- from enum import Enum
- import json
- from pprint import pformat
- class ScheduleStatus(Enum):
- """Status of a schedule entry."""
- ENABLED = "enabled"
- DISABLED = "disabled"
- EXECUTING = "executing"
- ERROR = "error"
- COMPLETED = "completed"
- class ExecutionResult(Enum):
- """Result of schedule execution."""
- SUCCESS = "success"
- FAILURE = "failure"
- PARTIAL = "partial"
- SKIPPED = "skipped"
- TIMEOUT = "timeout"
- @dataclass
- class ExecutionHistory:
- """History entry for schedule execution."""
- execution_id: str
- timestamp: float
- duration_seconds: float
- result: ExecutionResult
- triggers_fired: List[str] = field(default_factory=list)
- actions_executed: List[str] = field(default_factory=list)
- actions_failed: List[str] = field(default_factory=list)
- error_message: Optional[str] = None
- metadata: Dict[str, Any] = field(default_factory=dict)
-
- def to_dict(self) -> Dict[str, Any]:
- """Convert execution history to dictionary."""
- return {
- 'execution_id': self.execution_id,
- 'timestamp': self.timestamp,
- 'duration_seconds': self.duration_seconds,
- 'result': self.result.value,
- 'triggers_fired': self.triggers_fired,
- 'actions_executed': self.actions_executed,
- 'actions_failed': self.actions_failed,
- 'error_message': self.error_message,
- 'metadata': self.metadata
- }
-
- @classmethod
- def from_dict(cls, data: Dict[str, Any]) -> 'ExecutionHistory':
- """Create execution history from dictionary."""
- return cls(
- execution_id=data['execution_id'],
- timestamp=data['timestamp'],
- duration_seconds=data['duration_seconds'],
- result=ExecutionResult(data['result']),
- triggers_fired=data.get('triggers_fired', []),
- actions_executed=data.get('actions_executed', []),
- actions_failed=data.get('actions_failed', []),
- error_message=data.get('error_message'),
- metadata=data.get('metadata', {})
- )
- class ScheduleEntryError(Exception):
- """Base exception for schedule entry errors."""
- pass
- class NameConflictError(ScheduleEntryError):
- """Raised when schedule name conflicts with existing entry."""
- pass
- class ValidationError(ScheduleEntryError):
- """Raised when schedule validation fails."""
- pass
- class ExecutionError(ScheduleEntryError):
- """Raised when schedule execution fails."""
- pass
- def pprint(message: str) -> None:
- """
- Schedule entry logging function that adapts based on mode.
- Uses the same pattern as specified in CLAUDE.md.
- """
- print(f"[SCHEDULER.ENTRY] {message}")
- class ScheduleEntry:
- """
- Represents a single schedule entry with unique name, triggers, and actions.
-
- A schedule entry is the fundamental unit of the Trixy scheduler system.
- Each entry has:
- - A unique name that serves as the primary identifier
- - One or more triggers that determine when the schedule should execute
- - One or more actions that are performed when triggers fire
- - Configuration options for execution behavior
- - History tracking for monitoring and debugging
-
- The schedule entry is thread-safe and can be safely accessed from multiple
- threads, making it suitable for use in the multi-satellite Trixy environment.
- """
-
- # Class-level registry to enforce unique names
- _name_registry: Set[str] = set()
- _registry_lock = threading.RLock()
-
- def __init__(
- self,
- name: str,
- description: str = "",
- enabled: bool = True,
- max_history: int = 100,
- execution_timeout: float = 300.0, # 5 minutes default
- allow_concurrent: bool = False,
- priority: int = 0,
- tags: Optional[List[str]] = None,
- metadata: Optional[Dict[str, Any]] = None
- ):
- """
- Initialize a new schedule entry.
-
- Args:
- name: Unique name for the schedule entry
- description: Human-readable description
- enabled: Whether the schedule is enabled
- max_history: Maximum number of execution history entries to keep
- execution_timeout: Maximum execution time in seconds
- allow_concurrent: Whether to allow concurrent executions
- priority: Execution priority (higher values execute first)
- tags: Optional tags for categorization
- metadata: Additional metadata for the schedule
-
- Raises:
- NameConflictError: If name is already in use
- ValidationError: If parameters are invalid
- """
- # Validate and register name
- self._register_name(name)
-
- # Basic properties
- self._name = name
- self._description = description
- self._enabled = enabled
- self._max_history = max_history
- self._execution_timeout = execution_timeout
- self._allow_concurrent = allow_concurrent
- self._priority = priority
- self._tags = tags or []
- self._metadata = metadata or {}
-
- # Internal state
- self._status = ScheduleStatus.ENABLED if enabled else ScheduleStatus.DISABLED
- self._created_at = time.time()
- self._last_modified = self._created_at
- self._last_executed = None
- self._next_execution = None
- self._execution_count = 0
- self._error_count = 0
-
- # Thread safety
- self._lock = threading.RLock()
- self._currently_executing = False
- self._execution_thread = None
-
- # Triggers and actions storage
- self._triggers = []
- self._actions = []
-
- # Execution history
- self._execution_history: List[ExecutionHistory] = []
-
- # Event handlers (will be set by scheduler)
- self._on_execution_start: Optional[Callable] = None
- self._on_execution_complete: Optional[Callable] = None
- self._on_execution_error: Optional[Callable] = None
-
- pprint(f"Created schedule entry: {self._name}")
-
- def _register_name(self, name: str) -> None:
- """
- Register the schedule name to ensure uniqueness.
-
- Args:
- name: Name to register
-
- Raises:
- NameConflictError: If name is already in use
- ValidationError: If name is invalid
- """
- if not name or not isinstance(name, str):
- raise ValidationError("Schedule name must be a non-empty string")
-
- if len(name) > 255:
- raise ValidationError("Schedule name must be 255 characters or less")
-
- # Check for valid characters (alphanumeric, underscore, hyphen, dot)
- if not all(c.isalnum() or c in '_-.' for c in name):
- raise ValidationError("Schedule name contains invalid characters")
-
- with self._registry_lock:
- if name in self._name_registry:
- raise NameConflictError(f"Schedule name '{name}' is already in use")
- self._name_registry.add(name)
-
- def _unregister_name(self) -> None:
- """Unregister the schedule name when entry is destroyed."""
- with self._registry_lock:
- self._name_registry.discard(self._name)
-
- @property
- def name(self) -> str:
- """Get the schedule name."""
- return self._name
-
- @property
- def description(self) -> str:
- """Get the schedule description."""
- return self._description
-
- @description.setter
- def description(self, value: str) -> None:
- """Set the schedule description."""
- with self._lock:
- self._description = value
- self._last_modified = time.time()
-
- @property
- def enabled(self) -> bool:
- """Check if the schedule is enabled."""
- return self._enabled
-
- @enabled.setter
- def enabled(self, value: bool) -> None:
- """Enable or disable the schedule."""
- with self._lock:
- if self._enabled != value:
- self._enabled = value
- self._status = ScheduleStatus.ENABLED if value else ScheduleStatus.DISABLED
- self._last_modified = time.time()
- pprint(f"Schedule '{self._name}' {'enabled' if value else 'disabled'}")
-
- @property
- def status(self) -> ScheduleStatus:
- """Get the current schedule status."""
- return self._status
-
- @property
- def priority(self) -> int:
- """Get the schedule priority."""
- return self._priority
-
- @priority.setter
- def priority(self, value: int) -> None:
- """Set the schedule priority."""
- with self._lock:
- self._priority = value
- self._last_modified = time.time()
-
- @property
- def tags(self) -> List[str]:
- """Get the schedule tags."""
- return self._tags.copy()
-
- def add_tag(self, tag: str) -> None:
- """Add a tag to the schedule."""
- with self._lock:
- if tag not in self._tags:
- self._tags.append(tag)
- self._last_modified = time.time()
-
- def remove_tag(self, tag: str) -> None:
- """Remove a tag from the schedule."""
- with self._lock:
- if tag in self._tags:
- self._tags.remove(tag)
- self._last_modified = time.time()
-
- @property
- def metadata(self) -> Dict[str, Any]:
- """Get the schedule metadata."""
- return self._metadata.copy()
-
- def set_metadata(self, key: str, value: Any) -> None:
- """Set metadata value."""
- with self._lock:
- self._metadata[key] = value
- self._last_modified = time.time()
-
- def get_metadata(self, key: str, default: Any = None) -> Any:
- """Get metadata value."""
- return self._metadata.get(key, default)
-
- @property
- def created_at(self) -> float:
- """Get creation timestamp."""
- return self._created_at
-
- @property
- def last_modified(self) -> float:
- """Get last modification timestamp."""
- return self._last_modified
-
- @property
- def last_executed(self) -> Optional[float]:
- """Get last execution timestamp."""
- return self._last_executed
-
- @property
- def next_execution(self) -> Optional[float]:
- """Get next scheduled execution timestamp."""
- return self._next_execution
-
- @property
- def execution_count(self) -> int:
- """Get total execution count."""
- return self._execution_count
-
- @property
- def error_count(self) -> int:
- """Get total error count."""
- return self._error_count
-
- @property
- def is_executing(self) -> bool:
- """Check if schedule is currently executing."""
- return self._currently_executing
-
- def add_trigger(self, trigger) -> None:
- """
- Add a trigger to the schedule.
-
- Args:
- trigger: Trigger instance to add
-
- Raises:
- ValidationError: If trigger is invalid
- """
- if not hasattr(trigger, 'should_fire') or not callable(trigger.should_fire):
- raise ValidationError("Trigger must have a 'should_fire' method")
-
- with self._lock:
- self._triggers.append(trigger)
- self._last_modified = time.time()
- pprint(f"Added trigger to schedule '{self._name}': {type(trigger).__name__}")
-
- def remove_trigger(self, trigger) -> bool:
- """
- Remove a trigger from the schedule.
-
- Args:
- trigger: Trigger instance to remove
-
- Returns:
- True if trigger was removed, False if not found
- """
- with self._lock:
- try:
- self._triggers.remove(trigger)
- self._last_modified = time.time()
- pprint(f"Removed trigger from schedule '{self._name}': {type(trigger).__name__}")
- return True
- except ValueError:
- return False
-
- def get_triggers(self) -> List:
- """Get all triggers for the schedule."""
- with self._lock:
- return self._triggers.copy()
-
- def clear_triggers(self) -> None:
- """Remove all triggers from the schedule."""
- with self._lock:
- self._triggers.clear()
- self._last_modified = time.time()
- pprint(f"Cleared all triggers for schedule '{self._name}'")
-
- def add_action(self, action) -> None:
- """
- Add an action to the schedule.
-
- Args:
- action: Action instance to add
-
- Raises:
- ValidationError: If action is invalid
- """
- if not hasattr(action, 'execute') or not callable(action.execute):
- raise ValidationError("Action must have an 'execute' method")
-
- with self._lock:
- self._actions.append(action)
- self._last_modified = time.time()
- pprint(f"Added action to schedule '{self._name}': {type(action).__name__}")
-
- def remove_action(self, action) -> bool:
- """
- Remove an action from the schedule.
-
- Args:
- action: Action instance to remove
-
- Returns:
- True if action was removed, False if not found
- """
- with self._lock:
- try:
- self._actions.remove(action)
- self._last_modified = time.time()
- pprint(f"Removed action from schedule '{self._name}': {type(action).__name__}")
- return True
- except ValueError:
- return False
-
- def get_actions(self) -> List:
- """Get all actions for the schedule."""
- with self._lock:
- return self._actions.copy()
-
- def clear_actions(self) -> None:
- """Remove all actions from the schedule."""
- with self._lock:
- self._actions.clear()
- self._last_modified = time.time()
- pprint(f"Cleared all actions for schedule '{self._name}'")
-
- def should_execute(self, current_time: Optional[float] = None) -> bool:
- """
- Check if the schedule should execute based on its triggers.
-
- Args:
- current_time: Current timestamp (defaults to now)
-
- Returns:
- True if any trigger indicates execution should occur
- """
- if not self._enabled or self._status != ScheduleStatus.ENABLED:
- return False
-
- if not self._triggers:
- return False
-
- if self._currently_executing and not self._allow_concurrent:
- return False
-
- current_time = current_time or time.time()
-
- # Check if any trigger should fire
- with self._lock:
- for trigger in self._triggers:
- try:
- if trigger.should_fire(current_time):
- return True
- except Exception as e:
- pprint(f"Error checking trigger in schedule '{self._name}': {e}")
-
- return False
-
- def get_next_execution_time(self, after_time: Optional[float] = None) -> Optional[float]:
- """
- Get the next scheduled execution time.
-
- Args:
- after_time: Time after which to find next execution (defaults to now)
-
- Returns:
- Next execution timestamp or None if no future execution
- """
- if not self._enabled or not self._triggers:
- return None
-
- after_time = after_time or time.time()
- next_times = []
-
- with self._lock:
- for trigger in self._triggers:
- try:
- if hasattr(trigger, 'get_next_fire_time'):
- next_time = trigger.get_next_fire_time(after_time)
- if next_time:
- next_times.append(next_time)
- except Exception as e:
- pprint(f"Error getting next fire time from trigger in schedule '{self._name}': {e}")
-
- return min(next_times) if next_times else None
-
- def execute(self, execution_context: Optional[Dict[str, Any]] = None) -> ExecutionResult:
- """
- Execute the schedule's actions.
-
- Args:
- execution_context: Optional context data for execution
-
- Returns:
- ExecutionResult indicating the outcome
-
- Raises:
- ExecutionError: If execution fails catastrophically
- """
- if not self._enabled:
- return ExecutionResult.SKIPPED
-
- if self._currently_executing and not self._allow_concurrent:
- pprint(f"Schedule '{self._name}' is already executing and concurrent execution is disabled")
- return ExecutionResult.SKIPPED
-
- execution_id = str(uuid.uuid4())
- start_time = time.time()
- execution_context = execution_context or {}
-
- # Create execution history entry
- history_entry = ExecutionHistory(
- execution_id=execution_id,
- timestamp=start_time,
- duration_seconds=0.0,
- result=ExecutionResult.SUCCESS,
- metadata=execution_context.copy()
- )
-
- try:
- with self._lock:
- if self._currently_executing and not self._allow_concurrent:
- return ExecutionResult.SKIPPED
-
- self._currently_executing = True
- self._status = ScheduleStatus.EXECUTING
- self._execution_count += 1
-
- pprint(f"Executing schedule '{self._name}' (execution_id: {execution_id})")
-
- # Call execution start handler
- if self._on_execution_start:
- try:
- self._on_execution_start(self, execution_id, execution_context)
- except Exception as e:
- pprint(f"Error in execution start handler: {e}")
-
- # Record which triggers fired
- fired_triggers = []
- current_time = time.time()
-
- with self._lock:
- for i, trigger in enumerate(self._triggers):
- try:
- if trigger.should_fire(current_time):
- trigger_name = getattr(trigger, 'name', f"trigger_{i}")
- fired_triggers.append(trigger_name)
- except Exception as e:
- pprint(f"Error checking trigger firing: {e}")
-
- history_entry.triggers_fired = fired_triggers
-
- # Execute actions
- actions_executed = []
- actions_failed = []
- overall_result = ExecutionResult.SUCCESS
-
- with self._lock:
- actions_to_execute = self._actions.copy()
-
- for i, action in enumerate(actions_to_execute):
- action_name = getattr(action, 'name', f"action_{i}")
-
- try:
- pprint(f"Executing action '{action_name}' in schedule '{self._name}'")
-
- # Execute with timeout
- action_start = time.time()
- result = action.execute(execution_context)
- action_duration = time.time() - action_start
-
- actions_executed.append(action_name)
- pprint(f"Action '{action_name}' completed in {action_duration:.2f}s")
-
- except Exception as e:
- actions_failed.append(action_name)
- overall_result = ExecutionResult.PARTIAL if actions_executed else ExecutionResult.FAILURE
- pprint(f"Action '{action_name}' failed: {e}")
-
- # Check if this should be a complete failure
- if hasattr(action, 'is_critical') and action.is_critical:
- overall_result = ExecutionResult.FAILURE
- break
-
- history_entry.actions_executed = actions_executed
- history_entry.actions_failed = actions_failed
- history_entry.result = overall_result
-
- # Update execution statistics
- with self._lock:
- self._last_executed = start_time
- if overall_result in [ExecutionResult.FAILURE, ExecutionResult.PARTIAL]:
- self._error_count += 1
-
- pprint(f"Schedule '{self._name}' execution completed with result: {overall_result.value}")
-
- return overall_result
-
- except Exception as e:
- # Handle catastrophic execution failure
- overall_result = ExecutionResult.FAILURE
- history_entry.result = overall_result
- history_entry.error_message = str(e)
-
- with self._lock:
- self._error_count += 1
- self._status = ScheduleStatus.ERROR
-
- pprint(f"Schedule '{self._name}' execution failed catastrophically: {e}")
-
- # Call error handler
- if self._on_execution_error:
- try:
- self._on_execution_error(self, execution_id, e)
- except Exception as handler_error:
- pprint(f"Error in execution error handler: {handler_error}")
-
- raise ExecutionError(f"Schedule execution failed: {e}") from e
-
- finally:
- # Finalize execution
- end_time = time.time()
- history_entry.duration_seconds = end_time - start_time
-
- with self._lock:
- self._currently_executing = False
- if self._status == ScheduleStatus.EXECUTING:
- self._status = ScheduleStatus.ENABLED if self._enabled else ScheduleStatus.DISABLED
-
- # Add to history
- self._execution_history.append(history_entry)
-
- # Trim history if needed
- if len(self._execution_history) > self._max_history:
- self._execution_history = self._execution_history[-self._max_history:]
-
- # Call completion handler
- if self._on_execution_complete:
- try:
- self._on_execution_complete(self, execution_id, overall_result)
- except Exception as e:
- pprint(f"Error in execution complete handler: {e}")
-
- def get_execution_history(self, limit: Optional[int] = None) -> List[ExecutionHistory]:
- """
- Get execution history for the schedule.
-
- Args:
- limit: Maximum number of entries to return
-
- Returns:
- List of execution history entries (most recent first)
- """
- with self._lock:
- history = self._execution_history.copy()
- history.reverse() # Most recent first
- if limit:
- history = history[:limit]
- return history
-
- def get_last_execution_result(self) -> Optional[ExecutionResult]:
- """Get the result of the last execution."""
- with self._lock:
- if self._execution_history:
- return self._execution_history[-1].result
- return None
-
- def clear_execution_history(self) -> None:
- """Clear all execution history."""
- with self._lock:
- self._execution_history.clear()
- pprint(f"Cleared execution history for schedule '{self._name}'")
-
- def set_event_handlers(
- self,
- on_start: Optional[Callable] = None,
- on_complete: Optional[Callable] = None,
- on_error: Optional[Callable] = None
- ) -> None:
- """
- Set event handlers for execution lifecycle events.
-
- Args:
- on_start: Called when execution starts
- on_complete: Called when execution completes
- on_error: Called when execution encounters an error
- """
- with self._lock:
- self._on_execution_start = on_start
- self._on_execution_complete = on_complete
- self._on_execution_error = on_error
-
- def validate(self) -> List[str]:
- """
- Validate the schedule configuration.
-
- Returns:
- List of validation errors (empty if valid)
- """
- errors = []
-
- # Check basic properties
- if not self._name:
- errors.append("Schedule name is required")
-
- if not self._triggers:
- errors.append("At least one trigger is required")
-
- if not self._actions:
- errors.append("At least one action is required")
-
- # Validate triggers
- for i, trigger in enumerate(self._triggers):
- if not hasattr(trigger, 'should_fire'):
- errors.append(f"Trigger {i} missing 'should_fire' method")
-
- if hasattr(trigger, 'validate'):
- try:
- trigger_errors = trigger.validate()
- for error in trigger_errors:
- errors.append(f"Trigger {i}: {error}")
- except Exception as e:
- errors.append(f"Trigger {i} validation failed: {e}")
-
- # Validate actions
- for i, action in enumerate(self._actions):
- if not hasattr(action, 'execute'):
- errors.append(f"Action {i} missing 'execute' method")
-
- if hasattr(action, 'validate'):
- try:
- action_errors = action.validate()
- for error in action_errors:
- errors.append(f"Action {i}: {error}")
- except Exception as e:
- errors.append(f"Action {i} validation failed: {e}")
-
- return errors
-
- def to_dict(self) -> Dict[str, Any]:
- """
- Convert schedule entry to dictionary for serialization.
-
- Returns:
- Dictionary representation of the schedule
- """
- with self._lock:
- return {
- 'name': self._name,
- 'description': self._description,
- 'enabled': self._enabled,
- 'status': self._status.value,
- 'priority': self._priority,
- 'tags': self._tags.copy(),
- 'metadata': self._metadata.copy(),
- 'created_at': self._created_at,
- 'last_modified': self._last_modified,
- 'last_executed': self._last_executed,
- 'execution_count': self._execution_count,
- 'error_count': self._error_count,
- 'max_history': self._max_history,
- 'execution_timeout': self._execution_timeout,
- 'allow_concurrent': self._allow_concurrent,
- 'triggers': [
- trigger.to_dict() if hasattr(trigger, 'to_dict') else str(trigger)
- for trigger in self._triggers
- ],
- 'actions': [
- action.to_dict() if hasattr(action, 'to_dict') else str(action)
- for action in self._actions
- ],
- 'execution_history': [
- entry.to_dict() for entry in self._execution_history
- ]
- }
-
- def get_info(self) -> Dict[str, Any]:
- """
- Get summary information about the schedule entry.
-
- Returns:
- Dictionary with schedule information
- """
- with self._lock:
- next_exec = self.get_next_execution_time()
- last_result = self.get_last_execution_result()
-
- return {
- 'name': self._name,
- 'description': self._description,
- 'enabled': self._enabled,
- 'status': self._status.value,
- 'priority': self._priority,
- 'tags': len(self._tags),
- 'triggers': len(self._triggers),
- 'actions': len(self._actions),
- 'execution_count': self._execution_count,
- 'error_count': self._error_count,
- 'last_executed': self._last_executed,
- 'last_result': last_result.value if last_result else None,
- 'next_execution': next_exec,
- 'is_executing': self._currently_executing,
- 'uptime_hours': (time.time() - self._created_at) / 3600,
- 'success_rate': (
- (self._execution_count - self._error_count) / self._execution_count * 100
- if self._execution_count > 0 else 0
- )
- }
-
- def __str__(self) -> str:
- """String representation of the schedule entry."""
- return f"ScheduleEntry(name='{self._name}', enabled={self._enabled}, triggers={len(self._triggers)}, actions={len(self._actions)})"
-
- def __repr__(self) -> str:
- """Detailed representation of the schedule entry."""
- return (
- f"ScheduleEntry("
- f"name='{self._name}', "
- f"description='{self._description}', "
- f"enabled={self._enabled}, "
- f"status={self._status.value}, "
- f"triggers={len(self._triggers)}, "
- f"actions={len(self._actions)}, "
- f"executions={self._execution_count})"
- )
-
- def __del__(self):
- """Cleanup when schedule entry is destroyed."""
- try:
- self._unregister_name()
- except:
- pass # Ignore errors during cleanup
- # Utility functions for name management
- def get_registered_names() -> Set[str]:
- """Get all currently registered schedule names."""
- with ScheduleEntry._registry_lock:
- return ScheduleEntry._name_registry.copy()
- def is_name_available(name: str) -> bool:
- """Check if a schedule name is available."""
- with ScheduleEntry._registry_lock:
- return name not in ScheduleEntry._name_registry
- def clear_name_registry() -> None:
- """Clear the name registry (for testing purposes)."""
- with ScheduleEntry._registry_lock:
- ScheduleEntry._name_registry.clear()
- # Module exports
- __all__ = [
- 'ScheduleEntry',
- 'ScheduleStatus',
- 'ExecutionResult',
- 'ExecutionHistory',
- 'ScheduleEntryError',
- 'NameConflictError',
- 'ValidationError',
- 'ExecutionError',
- 'get_registered_names',
- 'is_name_available',
- 'clear_name_registry',
- 'pprint'
- ]
|