""" Base Command Classes for Trixy Network Protocol This module defines the base classes for all network commands in the Trixy system. These classes provide: - Common interface for all commands - Serialization/deserialization support - Validation and error handling - Response management - Type safety and documentation All network commands inherit from these base classes to ensure consistency and proper integration with the Trixy protocol. """ import time import uuid from typing import Any, Dict, Optional, Union, List from dataclasses import dataclass, field from enum import Enum, IntEnum from abc import ABC, abstractmethod import json def pprint(message: str) -> None: """Base command logging function.""" print(f"[CMD.BASE] {message}") class CommandStatus(IntEnum): """Status codes for command execution.""" SUCCESS = 0 ERROR = 1 PENDING = 2 TIMEOUT = 3 INVALID = 4 UNAUTHORIZED = 5 NOT_FOUND = 6 CONFLICT = 7 class ResponseType(Enum): """Types of command responses.""" SUCCESS = "success" ERROR = "error" ACK = "acknowledgment" DATA = "data" PARTIAL = "partial" class CommandError(Exception): """Base exception for command-related errors.""" def __init__(self, message: str, status: CommandStatus = CommandStatus.ERROR): super().__init__(message) self.status = status class ValidationError(CommandError): """Raised when command validation fails.""" def __init__(self, message: str): super().__init__(message, CommandStatus.INVALID) @dataclass class TrixyCommand(ABC): """ Base class for all Trixy network commands. This abstract base class defines the common interface that all commands must implement. It provides basic functionality for: - Unique command identification - Timestamp tracking - Serialization support - Validation - Response handling All command classes must inherit from this class and implement the required abstract methods. """ # Core fields that all commands have command_id: str = field(default_factory=lambda: str(uuid.uuid4())) timestamp: float = field(default_factory=time.time) version: str = field(default="1.0.0") source: Optional[str] = field(default=None) destination: Optional[str] = field(default=None) def __post_init__(self): """Post-initialization validation and setup.""" self.validate() @abstractmethod def validate(self) -> bool: """ Validate the command data. Returns: bool: True if valid Raises: ValidationError: If validation fails """ pass @abstractmethod def get_command_name(self) -> str: """ Get the command name for protocol identification. Returns: str: Command name """ pass def to_dict(self) -> Dict[str, Any]: """ Convert command to dictionary for serialization. Returns: Dict[str, Any]: Command data as dictionary """ result = {} for key, value in self.__dict__.items(): if not key.startswith('_'): result[key] = value return result @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'TrixyCommand': """ Create command instance from dictionary. Args: data: Dictionary with command data Returns: TrixyCommand: Command instance """ return cls(**data) def to_json(self) -> str: """ Convert command to JSON string. Returns: str: JSON representation """ return json.dumps(self.to_dict()) @classmethod def from_json(cls, json_str: str) -> 'TrixyCommand': """ Create command from JSON string. Args: json_str: JSON string Returns: TrixyCommand: Command instance """ data = json.loads(json_str) return cls.from_dict(data) def get_response_class(self): """ Get the expected response class for this command. Returns: Class: Response class or None if no response expected """ return None def requires_response(self) -> bool: """ Check if this command requires a response. Returns: bool: True if response is required """ return self.get_response_class() is not None def get_timeout(self) -> float: """ Get the timeout for this command in seconds. Returns: float: Timeout in seconds """ return 30.0 # Default 30 second timeout def is_expired(self) -> bool: """ Check if this command has expired based on its timeout. Returns: bool: True if expired """ return time.time() - self.timestamp > self.get_timeout() def get_priority(self) -> int: """ Get command priority (lower numbers = higher priority). Returns: int: Priority value (0-100) """ return 50 # Normal priority def __str__(self) -> str: """String representation of the command.""" return f"{self.get_command_name()}({self.command_id[:8]}...)" def __repr__(self) -> str: """Detailed string representation.""" return f"{self.__class__.__name__}(id={self.command_id}, timestamp={self.timestamp})" @dataclass class CommandResponse(ABC): """ Base class for all command responses. This class provides the standard interface for responses to commands. It includes status information, timing data, and optional response data. """ # Response identification response_id: str = field(default_factory=lambda: str(uuid.uuid4())) command_id: str = field(default="") # Status and timing status: CommandStatus = field(default=CommandStatus.SUCCESS) response_type: ResponseType = field(default=ResponseType.SUCCESS) timestamp: float = field(default_factory=time.time) # Optional fields message: Optional[str] = field(default=None) error_details: Optional[Dict[str, Any]] = field(default=None) data: Optional[Dict[str, Any]] = field(default=None) def __post_init__(self): """Post-initialization setup.""" self.validate() @abstractmethod def validate(self) -> bool: """ Validate the response data. Returns: bool: True if valid Raises: ValidationError: If validation fails """ pass @abstractmethod def get_response_name(self) -> str: """ Get the response name for protocol identification. Returns: str: Response name """ pass def is_success(self) -> bool: """ Check if the response indicates success. Returns: bool: True if successful """ return self.status == CommandStatus.SUCCESS def is_error(self) -> bool: """ Check if the response indicates an error. Returns: bool: True if error """ return self.status != CommandStatus.SUCCESS def get_error_message(self) -> str: """ Get error message if response is an error. Returns: str: Error message or empty string """ if self.is_error(): return self.message or f"Command failed with status: {self.status.name}" return "" def to_dict(self) -> Dict[str, Any]: """ Convert response to dictionary. Returns: Dict[str, Any]: Response data as dictionary """ result = {} for key, value in self.__dict__.items(): if not key.startswith('_'): if isinstance(value, Enum): result[key] = value.value else: result[key] = value return result @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'CommandResponse': """ Create response from dictionary. Args: data: Dictionary with response data Returns: CommandResponse: Response instance """ # Handle enum fields if 'status' in data and isinstance(data['status'], (str, int)): data['status'] = CommandStatus(data['status']) if 'response_type' in data and isinstance(data['response_type'], str): data['response_type'] = ResponseType(data['response_type']) return cls(**data) def __str__(self) -> str: """String representation of the response.""" return f"{self.get_response_name()}({self.status.name})" # Concrete response implementations for common cases @dataclass class SuccessResponse(CommandResponse): """Generic success response.""" def validate(self) -> bool: """Validate success response.""" if self.status != CommandStatus.SUCCESS: raise ValidationError("SuccessResponse must have SUCCESS status") return True def get_response_name(self) -> str: """Get response name.""" return "SuccessResponse" @dataclass class ErrorResponse(CommandResponse): """Generic error response.""" def __post_init__(self): """Set error status if not already set.""" if self.status == CommandStatus.SUCCESS: self.status = CommandStatus.ERROR self.response_type = ResponseType.ERROR super().__post_init__() def validate(self) -> bool: """Validate error response.""" if self.status == CommandStatus.SUCCESS: raise ValidationError("ErrorResponse cannot have SUCCESS status") if not self.message: raise ValidationError("ErrorResponse must have an error message") return True def get_response_name(self) -> str: """Get response name.""" return "ErrorResponse" @dataclass class AckResponse(CommandResponse): """Simple acknowledgment response.""" def __post_init__(self): """Set ACK response type.""" self.response_type = ResponseType.ACK super().__post_init__() def validate(self) -> bool: """Validate ACK response.""" return True def get_response_name(self) -> str: """Get response name.""" return "AckResponse" # Utility functions def create_success_response( command_id: str, message: str = "Command completed successfully", data: Optional[Dict[str, Any]] = None ) -> SuccessResponse: """ Create a success response for a command. Args: command_id: ID of the command being responded to message: Success message data: Optional response data Returns: SuccessResponse: Success response """ return SuccessResponse( command_id=command_id, message=message, data=data ) def create_error_response( command_id: str, message: str, status: CommandStatus = CommandStatus.ERROR, error_details: Optional[Dict[str, Any]] = None ) -> ErrorResponse: """ Create an error response for a command. Args: command_id: ID of the command being responded to message: Error message status: Error status code error_details: Optional error details Returns: ErrorResponse: Error response """ return ErrorResponse( command_id=command_id, message=message, status=status, error_details=error_details ) def create_ack_response(command_id: str, message: str = "Command acknowledged") -> AckResponse: """ Create an acknowledgment response for a command. Args: command_id: ID of the command being responded to message: ACK message Returns: AckResponse: Acknowledgment response """ return AckResponse( command_id=command_id, message=message ) # Module exports __all__ = [ "TrixyCommand", "CommandResponse", "CommandStatus", "ResponseType", "CommandError", "ValidationError", "SuccessResponse", "ErrorResponse", "AckResponse", "create_success_response", "create_error_response", "create_ack_response", ]