fixed some more imports

This commit is contained in:
pacnpal
2024-11-17 20:43:55 +00:00
parent ad8a4e3dbe
commit 4fc2afc446
3 changed files with 93 additions and 107 deletions

View File

@@ -8,42 +8,42 @@ from typing import Dict, Any, Optional, TypedDict, ClassVar, List, Set, Union
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import discord # type: ignore import discord # type: ignore
from redbot.core.bot import Red # type: ignore from redbot.core.bot import Red # type: ignore
from redbot.core.commands import GroupCog, Context # type: ignore from redbot.core.commands import GroupCog, Context # type: ignore
from .settings import Settings from .settings import Settings
from .lifecycle import LifecycleManager, LifecycleState from .lifecycle import LifecycleManager, LifecycleState
from .component_manager import ComponentManager, ComponentState from .component_manager import ComponentManager, ComponentState
from .error_handler import error_manager, handle_command_error from .error_handler import error_manager, handle_command_error
from .response_handler import response_manager from .response_handler import ResponseManager
from .commands.archiver_commands import setup_archiver_commands from .commands.archiver_commands import setup_archiver_commands
from .commands.database_commands import setup_database_commands from .commands.database_commands import setup_database_commands
from .commands.settings_commands import setup_settings_commands from .commands.settings_commands import setup_settings_commands
from .events import setup_events, EventManager from .events import setup_events, EventManager
from ..processor.core import Processor from ..processor.core import VideoProcessor
from ..queue.manager import QueueManager from ..queue.manager import EnhancedVideoQueueManager
from ..ffmpeg.ffmpeg_manager import FFmpegManager from ..ffmpeg.ffmpeg_manager import FFmpegManager
from ..database.video_archive_db import VideoArchiveDB from ..database.video_archive_db import VideoArchiveDB
from ..config_manager import ConfigManager from ..config_manager import ConfigManager
from ..utils.exceptions import ( from ..utils.exceptions import CogError, ErrorContext, ErrorSeverity
CogError,
ErrorContext,
ErrorSeverity
)
logger = logging.getLogger("VideoArchiver") logger = logging.getLogger("VideoArchiver")
class CogHealthCheck(TypedDict): class CogHealthCheck(TypedDict):
"""Type definition for health check status""" """Type definition for health check status"""
name: str name: str
status: bool status: bool
last_check: str last_check: str
details: Optional[Dict[str, Any]] details: Optional[Dict[str, Any]]
class CogStatus(TypedDict): class CogStatus(TypedDict):
"""Type definition for cog status""" """Type definition for cog status"""
uptime: float uptime: float
last_error: Optional[str] last_error: Optional[str]
error_count: int error_count: int
@@ -53,6 +53,7 @@ class CogStatus(TypedDict):
state: str state: str
ready: bool ready: bool
class StatusTracker: class StatusTracker:
"""Tracks cog status and health""" """Tracks cog status and health"""
@@ -78,17 +79,14 @@ class StatusTracker:
self.last_command_time = datetime.utcnow() self.last_command_time = datetime.utcnow()
def update_health_check( def update_health_check(
self, self, name: str, status: bool, details: Optional[Dict[str, Any]] = None
name: str,
status: bool,
details: Optional[Dict[str, Any]] = None
) -> None: ) -> None:
"""Update health check status""" """Update health check status"""
self.health_checks[name] = CogHealthCheck( self.health_checks[name] = CogHealthCheck(
name=name, name=name,
status=status, status=status,
last_check=datetime.utcnow().isoformat(), last_check=datetime.utcnow().isoformat(),
details=details details=details,
) )
def get_status(self) -> CogStatus: def get_status(self) -> CogStatus:
@@ -98,10 +96,12 @@ class StatusTracker:
last_error=self.last_error, last_error=self.last_error,
error_count=self.error_count, error_count=self.error_count,
command_count=self.command_count, command_count=self.command_count,
last_command=self.last_command_time.isoformat() if self.last_command_time else None, last_command=(
self.last_command_time.isoformat() if self.last_command_time else None
),
health_checks=self.health_checks.copy(), health_checks=self.health_checks.copy(),
state="healthy" if self.is_healthy() else "unhealthy", state="healthy" if self.is_healthy() else "unhealthy",
ready=True ready=True,
) )
def is_healthy(self) -> bool: def is_healthy(self) -> bool:
@@ -110,6 +110,7 @@ class StatusTracker:
return False return False
return all(check["status"] for check in self.health_checks.values()) return all(check["status"] for check in self.health_checks.values())
class ComponentAccessor: class ComponentAccessor:
"""Provides safe access to components""" """Provides safe access to components"""
@@ -119,10 +120,10 @@ class ComponentAccessor:
def get_component(self, name: str) -> Optional[Any]: def get_component(self, name: str) -> Optional[Any]:
""" """
Get a component with state validation. Get a component with state validation.
Args: Args:
name: Component name name: Component name
Returns: Returns:
Component instance if ready, None otherwise Component instance if ready, None otherwise
""" """
@@ -134,15 +135,16 @@ class ComponentAccessor:
def get_component_status(self, name: str) -> Dict[str, Any]: def get_component_status(self, name: str) -> Dict[str, Any]:
""" """
Get component status. Get component status.
Args: Args:
name: Component name name: Component name
Returns: Returns:
Component status dictionary Component status dictionary
""" """
return self._component_manager.get_component_status().get(name, {}) return self._component_manager.get_component_status().get(name, {})
class VideoArchiver(GroupCog, Settings): class VideoArchiver(GroupCog, Settings):
"""Archive videos from Discord channels""" """Archive videos from Discord channels"""
@@ -151,7 +153,7 @@ class VideoArchiver(GroupCog, Settings):
super().__init__() super().__init__()
self.bot = bot self.bot = bot
self.ready = asyncio.Event() self.ready = asyncio.Event()
# Initialize managers # Initialize managers
self.lifecycle_manager = LifecycleManager(self) self.lifecycle_manager = LifecycleManager(self)
self.component_manager = ComponentManager(self) self.component_manager = ComponentManager(self)
@@ -164,7 +166,7 @@ class VideoArchiver(GroupCog, Settings):
self._cleanup_task: Optional[asyncio.Task] = None self._cleanup_task: Optional[asyncio.Task] = None
self._queue_task: Optional[asyncio.Task] = None self._queue_task: Optional[asyncio.Task] = None
self._health_tasks: Set[asyncio.Task] = set() self._health_tasks: Set[asyncio.Task] = set()
# Initialize component storage # Initialize component storage
self.components: Dict[int, Dict[str, Any]] = {} self.components: Dict[int, Dict[str, Any]] = {}
self.update_checker = None self.update_checker = None
@@ -184,7 +186,7 @@ class VideoArchiver(GroupCog, Settings):
async def cog_load(self) -> None: async def cog_load(self) -> None:
""" """
Handle cog loading. Handle cog loading.
Raises: Raises:
CogError: If loading fails CogError: If loading fails
""" """
@@ -198,17 +200,14 @@ class VideoArchiver(GroupCog, Settings):
raise CogError( raise CogError(
error, error,
context=ErrorContext( context=ErrorContext(
"VideoArchiver", "VideoArchiver", "cog_load", None, ErrorSeverity.CRITICAL
"cog_load", ),
None,
ErrorSeverity.CRITICAL
)
) )
async def cog_unload(self) -> None: async def cog_unload(self) -> None:
""" """
Handle cog unloading. Handle cog unloading.
Raises: Raises:
CogError: If unloading fails CogError: If unloading fails
""" """
@@ -226,18 +225,11 @@ class VideoArchiver(GroupCog, Settings):
raise CogError( raise CogError(
error, error,
context=ErrorContext( context=ErrorContext(
"VideoArchiver", "VideoArchiver", "cog_unload", None, ErrorSeverity.CRITICAL
"cog_unload", ),
None,
ErrorSeverity.CRITICAL
)
) )
async def cog_command_error( async def cog_command_error(self, ctx: Context, error: Exception) -> None:
self,
ctx: Context,
error: Exception
) -> None:
"""Handle command errors""" """Handle command errors"""
self.status_tracker.record_error(str(error)) self.status_tracker.record_error(str(error))
await handle_command_error(ctx, error) await handle_command_error(ctx, error)
@@ -249,12 +241,8 @@ class VideoArchiver(GroupCog, Settings):
async def _start_health_monitoring(self) -> None: async def _start_health_monitoring(self) -> None:
"""Start health monitoring tasks""" """Start health monitoring tasks"""
self._health_tasks.add( self._health_tasks.add(asyncio.create_task(self._monitor_component_health()))
asyncio.create_task(self._monitor_component_health()) self._health_tasks.add(asyncio.create_task(self._monitor_system_health()))
)
self._health_tasks.add(
asyncio.create_task(self._monitor_system_health())
)
async def _monitor_component_health(self) -> None: async def _monitor_component_health(self) -> None:
"""Monitor component health""" """Monitor component health"""
@@ -265,7 +253,7 @@ class VideoArchiver(GroupCog, Settings):
self.status_tracker.update_health_check( self.status_tracker.update_health_check(
f"component_{name}", f"component_{name}",
status["state"] == ComponentState.READY.name, status["state"] == ComponentState.READY.name,
status status,
) )
except Exception as e: except Exception as e:
logger.error(f"Error monitoring component health: {e}", exc_info=True) logger.error(f"Error monitoring component health: {e}", exc_info=True)
@@ -281,34 +269,28 @@ class VideoArchiver(GroupCog, Settings):
self.status_tracker.update_health_check( self.status_tracker.update_health_check(
"queue_health", "queue_health",
queue_status["active"] and not queue_status["stalled"], queue_status["active"] and not queue_status["stalled"],
queue_status queue_status,
) )
# Check processor health # Check processor health
if processor := self.processor: if processor := self.processor:
processor_status = await processor.get_status() processor_status = await processor.get_status()
self.status_tracker.update_health_check( self.status_tracker.update_health_check(
"processor_health", "processor_health", processor_status["active"], processor_status
processor_status["active"],
processor_status
) )
# Check database health # Check database health
if db := self.db: if db := self.db:
db_status = await db.get_status() db_status = await db.get_status()
self.status_tracker.update_health_check( self.status_tracker.update_health_check(
"database_health", "database_health", db_status["connected"], db_status
db_status["connected"],
db_status
) )
# Check event system health # Check event system health
if self.event_manager: if self.event_manager:
event_stats = self.event_manager.get_stats() event_stats = self.event_manager.get_stats()
self.status_tracker.update_health_check( self.status_tracker.update_health_check(
"event_health", "event_health", event_stats["health"], event_stats
event_stats["health"],
event_stats
) )
except Exception as e: except Exception as e:
@@ -334,7 +316,7 @@ class VideoArchiver(GroupCog, Settings):
def get_status(self) -> Dict[str, Any]: def get_status(self) -> Dict[str, Any]:
""" """
Get comprehensive cog status. Get comprehensive cog status.
Returns: Returns:
Dictionary containing cog status information Dictionary containing cog status information
""" """
@@ -343,17 +325,17 @@ class VideoArchiver(GroupCog, Settings):
"lifecycle": self.lifecycle_manager.get_status(), "lifecycle": self.lifecycle_manager.get_status(),
"components": self.component_manager.get_component_status(), "components": self.component_manager.get_component_status(),
"errors": error_manager.tracker.get_error_stats(), "errors": error_manager.tracker.get_error_stats(),
"events": self.event_manager.get_stats() if self.event_manager else None "events": self.event_manager.get_stats() if self.event_manager else None,
} }
# Component property accessors # Component property accessors
@property @property
def processor(self) -> Optional[Processor]: def processor(self) -> Optional[VideoProcessor]:
"""Get the processor component""" """Get the processor component"""
return self.component_accessor.get_component("processor") return self.component_accessor.get_component("processor")
@property @property
def queue_manager(self) -> Optional[QueueManager]: def queue_manager(self) -> Optional[EnhancedVideoQueueManager]:
"""Get the queue manager component""" """Get the queue manager component"""
return self.component_accessor.get_component("queue_manager") return self.component_accessor.get_component("queue_manager")

View File

@@ -4,13 +4,13 @@ import logging
from enum import Enum, auto from enum import Enum, auto
from typing import Optional, Any, Dict, TypedDict, Callable, Awaitable from typing import Optional, Any, Dict, TypedDict, Callable, Awaitable
import discord # type: ignore import discord # type: ignore
from discord import app_commands # type: ignore from discord import app_commands # type: ignore
from redbot.core import commands # type: ignore from redbot.core import commands # type: ignore
from redbot.core.commands import Context, hybrid_group, guild_only, admin_or_permissions # type: ignore from redbot.core.commands import Context, hybrid_group, guild_only, admin_or_permissions # type: ignore
from core.response_handler import handle_response, ResponseType from ...core.response_handler import handle_response, ResponseType
from utils.exceptions import CommandError, ErrorContext, ErrorSeverity from ...utils.exceptions import CommandError, ErrorContext, ErrorSeverity
logger = logging.getLogger("VideoArchiver") logger = logging.getLogger("VideoArchiver")

View File

@@ -4,15 +4,17 @@ import logging
from enum import Enum, auto from enum import Enum, auto
from typing import Optional, Union, Dict, Any, TypedDict, ClassVar from typing import Optional, Union, Dict, Any, TypedDict, ClassVar
from datetime import datetime from datetime import datetime
import discord # type: ignore import discord # type: ignore
from redbot.core.commands import Context # type: ignore from redbot.core.commands import Context # type: ignore
from ..utils.exceptions import ErrorSeverity from ..utils.exceptions import ErrorSeverity
logger = logging.getLogger("VideoArchiver") logger = logging.getLogger("VideoArchiver")
class ResponseType(Enum): class ResponseType(Enum):
"""Types of responses""" """Types of responses"""
NORMAL = auto() NORMAL = auto()
SUCCESS = auto() SUCCESS = auto()
ERROR = auto() ERROR = auto()
@@ -20,17 +22,22 @@ class ResponseType(Enum):
INFO = auto() INFO = auto()
DEBUG = auto() DEBUG = auto()
class ResponseTheme(TypedDict): class ResponseTheme(TypedDict):
"""Type definition for response theme""" """Type definition for response theme"""
emoji: str emoji: str
color: discord.Color color: discord.Color
class ResponseFormat(TypedDict): class ResponseFormat(TypedDict):
"""Type definition for formatted response""" """Type definition for formatted response"""
content: str content: str
color: discord.Color color: discord.Color
timestamp: str timestamp: str
class ResponseFormatter: class ResponseFormatter:
"""Formats responses for consistency""" """Formats responses for consistency"""
@@ -39,29 +46,27 @@ class ResponseFormatter:
ResponseType.ERROR: ResponseTheme(emoji="", color=discord.Color.red()), ResponseType.ERROR: ResponseTheme(emoji="", color=discord.Color.red()),
ResponseType.WARNING: ResponseTheme(emoji="⚠️", color=discord.Color.gold()), ResponseType.WARNING: ResponseTheme(emoji="⚠️", color=discord.Color.gold()),
ResponseType.INFO: ResponseTheme(emoji="", color=discord.Color.blue()), ResponseType.INFO: ResponseTheme(emoji="", color=discord.Color.blue()),
ResponseType.DEBUG: ResponseTheme(emoji="🔧", color=discord.Color.greyple()) ResponseType.DEBUG: ResponseTheme(emoji="🔧", color=discord.Color.greyple()),
} }
SEVERITY_MAPPING: ClassVar[Dict[ErrorSeverity, ResponseType]] = { SEVERITY_MAPPING: ClassVar[Dict[ErrorSeverity, ResponseType]] = {
ErrorSeverity.LOW: ResponseType.INFO, ErrorSeverity.LOW: ResponseType.INFO,
ErrorSeverity.MEDIUM: ResponseType.WARNING, ErrorSeverity.MEDIUM: ResponseType.WARNING,
ErrorSeverity.HIGH: ResponseType.ERROR, ErrorSeverity.HIGH: ResponseType.ERROR,
ErrorSeverity.CRITICAL: ResponseType.ERROR ErrorSeverity.CRITICAL: ResponseType.ERROR,
} }
@classmethod @classmethod
def format_response( def format_response(
cls, cls, message: str, response_type: ResponseType = ResponseType.NORMAL
message: str,
response_type: ResponseType = ResponseType.NORMAL
) -> ResponseFormat: ) -> ResponseFormat:
""" """
Format a response message. Format a response message.
Args: Args:
message: Message to format message: Message to format
response_type: Type of response response_type: Type of response
Returns: Returns:
Formatted response dictionary Formatted response dictionary
""" """
@@ -69,28 +74,29 @@ class ResponseFormatter:
if theme: if theme:
return ResponseFormat( return ResponseFormat(
content=f"{theme['emoji']} {message}", content=f"{theme['emoji']} {message}",
color=theme['color'], color=theme["color"],
timestamp=datetime.utcnow().isoformat() timestamp=datetime.utcnow().isoformat(),
) )
return ResponseFormat( return ResponseFormat(
content=message, content=message,
color=discord.Color.default(), color=discord.Color.default(),
timestamp=datetime.utcnow().isoformat() timestamp=datetime.utcnow().isoformat(),
) )
@classmethod @classmethod
def get_response_type(cls, severity: ErrorSeverity) -> ResponseType: def get_response_type(cls, severity: ErrorSeverity) -> ResponseType:
""" """
Get response type for error severity. Get response type for error severity.
Args: Args:
severity: Error severity level severity: Error severity level
Returns: Returns:
Appropriate response type Appropriate response type
""" """
return cls.SEVERITY_MAPPING.get(severity, ResponseType.ERROR) return cls.SEVERITY_MAPPING.get(severity, ResponseType.ERROR)
class InteractionHandler: class InteractionHandler:
"""Handles slash command interactions""" """Handles slash command interactions"""
@@ -98,45 +104,49 @@ class InteractionHandler:
self, self,
interaction: discord.Interaction, interaction: discord.Interaction,
content: Optional[str] = None, content: Optional[str] = None,
embed: Optional[discord.Embed] = None embed: Optional[discord.Embed] = None,
) -> bool: ) -> bool:
""" """
Send initial interaction response. Send initial interaction response.
Args: Args:
interaction: Discord interaction interaction: Discord interaction
content: Optional message content content: Optional message content
embed: Optional embed embed: Optional embed
Returns: Returns:
True if response was sent successfully True if response was sent successfully
""" """
try: try:
if not interaction.response.is_done(): if not interaction.response.is_done():
if embed: if embed:
await interaction.response.send_message(content=content, embed=embed) await interaction.response.send_message(
content=content, embed=embed
)
else: else:
await interaction.response.send_message(content=content) await interaction.response.send_message(content=content)
return True return True
return False return False
except Exception as e: except Exception as e:
logger.error(f"Error sending initial interaction response: {e}", exc_info=True) logger.error(
f"Error sending initial interaction response: {e}", exc_info=True
)
return False return False
async def send_followup( async def send_followup(
self, self,
interaction: discord.Interaction, interaction: discord.Interaction,
content: Optional[str] = None, content: Optional[str] = None,
embed: Optional[discord.Embed] = None embed: Optional[discord.Embed] = None,
) -> bool: ) -> bool:
""" """
Send interaction followup. Send interaction followup.
Args: Args:
interaction: Discord interaction interaction: Discord interaction
content: Optional message content content: Optional message content
embed: Optional embed embed: Optional embed
Returns: Returns:
True if followup was sent successfully True if followup was sent successfully
""" """
@@ -150,6 +160,7 @@ class InteractionHandler:
logger.error(f"Error sending interaction followup: {e}", exc_info=True) logger.error(f"Error sending interaction followup: {e}", exc_info=True)
return False return False
class ResponseManager: class ResponseManager:
"""Manages command responses""" """Manages command responses"""
@@ -162,11 +173,11 @@ class ResponseManager:
ctx: Context, ctx: Context,
content: Optional[str] = None, content: Optional[str] = None,
embed: Optional[discord.Embed] = None, embed: Optional[discord.Embed] = None,
response_type: Union[ResponseType, str, ErrorSeverity] = ResponseType.NORMAL response_type: Union[ResponseType, str, ErrorSeverity] = ResponseType.NORMAL,
) -> None: ) -> None:
""" """
Send a response to a command. Send a response to a command.
Args: Args:
ctx: Command context ctx: Command context
content: Optional message content content: Optional message content
@@ -191,7 +202,7 @@ class ResponseManager:
if not embed: if not embed:
embed = discord.Embed( embed = discord.Embed(
color=formatted["color"], color=formatted["color"],
timestamp=datetime.fromisoformat(formatted["timestamp"]) timestamp=datetime.fromisoformat(formatted["timestamp"]),
) )
# Handle response # Handle response
@@ -209,10 +220,7 @@ class ResponseManager:
return hasattr(ctx, "interaction") and ctx.interaction is not None return hasattr(ctx, "interaction") and ctx.interaction is not None
async def _handle_interaction_response( async def _handle_interaction_response(
self, self, ctx: Context, content: Optional[str], embed: Optional[discord.Embed]
ctx: Context,
content: Optional[str],
embed: Optional[discord.Embed]
) -> None: ) -> None:
"""Handle interaction response""" """Handle interaction response"""
try: try:
@@ -236,10 +244,7 @@ class ResponseManager:
await self._send_fallback_response(ctx, content, embed) await self._send_fallback_response(ctx, content, embed)
async def _handle_regular_response( async def _handle_regular_response(
self, self, ctx: Context, content: Optional[str], embed: Optional[discord.Embed]
ctx: Context,
content: Optional[str],
embed: Optional[discord.Embed]
) -> None: ) -> None:
"""Handle regular command response""" """Handle regular command response"""
try: try:
@@ -252,10 +257,7 @@ class ResponseManager:
await self._send_fallback_response(ctx, content, embed) await self._send_fallback_response(ctx, content, embed)
async def _send_fallback_response( async def _send_fallback_response(
self, self, ctx: Context, content: Optional[str], embed: Optional[discord.Embed]
ctx: Context,
content: Optional[str],
embed: Optional[discord.Embed]
) -> None: ) -> None:
"""Send fallback response when other methods fail""" """Send fallback response when other methods fail"""
try: try:
@@ -266,18 +268,20 @@ class ResponseManager:
except Exception as e: except Exception as e:
logger.error(f"Failed to send fallback response: {e}", exc_info=True) logger.error(f"Failed to send fallback response: {e}", exc_info=True)
# Global response manager instance # Global response manager instance
response_manager = ResponseManager() response_manager = ResponseManager()
async def handle_response( async def handle_response(
ctx: Context, ctx: Context,
content: Optional[str] = None, content: Optional[str] = None,
embed: Optional[discord.Embed] = None, embed: Optional[discord.Embed] = None,
response_type: Union[ResponseType, str, ErrorSeverity] = ResponseType.NORMAL response_type: Union[ResponseType, str, ErrorSeverity] = ResponseType.NORMAL,
) -> None: ) -> None:
""" """
Helper function to handle responses using the response manager. Helper function to handle responses using the response manager.
Args: Args:
ctx: Command context ctx: Command context
content: Optional message content content: Optional message content