Skip to content

tux.cog_loader

CogLoader: A robust cog loader for the Tux bot.

This module provides the CogLoader class, which is responsible for discovering, loading, and managing all cogs (Discord.py extensions) for the bot. It includes features for priority-based loading, performance tracking, and detailed error reporting with Sentry integration.

Classes:

Name Description
CogLoadError

Raised when a cog fails to load.

CogLoadResult

Encapsulates the result of a cog loading operation.

CogLoader

A robust cog loader with priority-based loading, performance tracking,

Classes

CogLoadError

Bases: Exception

Raised when a cog fails to load.

CogLoadResult(module: str, success: bool, load_time: float, error: Exception | None = None)

Encapsulates the result of a cog loading operation.

Attributes:

Name Type Description
module str

The full import path of the cog.

success bool

Whether the cog loaded successfully.

load_time float

The time taken to load the cog, in seconds.

error Exception | None

The exception raised during loading, if any.

Source code in tux/cog_loader.py
Python
def __init__(self, module: str, success: bool, load_time: float, error: Exception | None = None) -> None:
    self.module = module
    self.success = success
    self.load_time = load_time
    self.error = error

Attributes

load_time_ms: float property

Return the cog load time in milliseconds.

Returns:

Type Description
float

The load time in milliseconds.

CogLoader(bot: commands.Bot)

Bases: Cog

A robust cog loader with priority-based loading, performance tracking, and detailed Sentry integration.

Initializes the CogLoader.

Parameters:

Name Type Description Default
bot Bot

The bot instance.

required

Methods:

Name Description
reload_cog

Reloads a single cog with comprehensive error handling.

load_cogs

Recursively loads eligible cogs from a directory or a single file.

load_cogs_from_folder

Loads all cogs from a specified top-level folder.

setup

Sets up the cog loader and loads all initial cogs for the bot.

Source code in tux/cog_loader.py
Python
def __init__(self, bot: commands.Bot) -> None:
    """
    Initializes the CogLoader.

    Parameters
    ----------
    bot : commands.Bot
        The bot instance.
    """
    self.bot = bot
    self.cog_ignore_list: set[str] = set(CONFIG.COG_IGNORE_LIST)
    self.load_times: defaultdict[str, float] = defaultdict(float)
    self.load_priorities = {
        "services": 90,
        "admin": 80,
        "levels": 70,
        "moderation": 60,
        "snippets": 50,
        "guild": 40,
        "utility": 30,
        "info": 20,
        "fun": 10,
        "tools": 5,
    }

Functions

_path_to_module(path: Path) -> str staticmethod

Converts a Path object to a Python module path.

Example: tux/cogs/admin.py -> tux.cogs.admin

Parameters:

Name Type Description Default
path Path

The file path to convert.

required

Returns:

Type Description
str

The Python module import path.

Source code in tux/cog_loader.py
Python
@staticmethod
def _path_to_module(path: Path) -> str:
    """
    Converts a Path object to a Python module path.

    Example:
        tux/cogs/admin.py -> tux.cogs.admin

    Parameters
    ----------
    path : Path
        The file path to convert.

    Returns
    -------
    str
        The Python module import path.
    """
    return ".".join(path.parts).removesuffix(".py")
_is_eligible_cog_file(filepath: Path) -> bool

Checks if a file is an eligible cog for loading.

Parameters:

Name Type Description Default
filepath Path

The path to the file to check.

required

Returns:

Type Description
bool

True if the file is an eligible cog, False otherwise.

Source code in tux/cog_loader.py
Python
def _is_eligible_cog_file(self, filepath: Path) -> bool:
    """
    Checks if a file is an eligible cog for loading.

    Parameters
    ----------
    filepath : Path
        The path to the file to check.

    Returns
    -------
    bool
        True if the file is an eligible cog, False otherwise.
    """
    if filepath.suffix != ".py" or not filepath.is_file() or filepath.stem.startswith("_"):
        return False

    cog_name = filepath.stem
    if cog_name in self.cog_ignore_list:
        logger.trace(f"Skipping {cog_name} as it is in the ignore list.")
        return False

    return True
_get_cog_priority(path: Path) -> int

Gets the loading priority for a cog based on its parent directory.

Parameters:

Name Type Description Default
path Path

The path to the cog file.

required

Returns:

Type Description
int

The priority value, or 0 if not specified.

Source code in tux/cog_loader.py
Python
def _get_cog_priority(self, path: Path) -> int:
    """
    Gets the loading priority for a cog based on its parent directory.

    Parameters
    ----------
    path : Path
        The path to the cog file.

    Returns
    -------
    int
        The priority value, or 0 if not specified.
    """
    return self.load_priorities.get(path.parent.name, 0)
_discover_and_sort_cogs(path: Path) -> list[Path]

Discovers all eligible cogs in a directory and sorts them by priority.

Parameters:

Name Type Description Default
path Path

The directory to search for cogs.

required

Returns:

Type Description
list[Path]

A list of cog file paths, sorted by priority (descending).

Source code in tux/cog_loader.py
Python
def _discover_and_sort_cogs(self, path: Path) -> list[Path]:
    """
    Discovers all eligible cogs in a directory and sorts them by priority.

    Parameters
    ----------
    path : Path
        The directory to search for cogs.

    Returns
    -------
    list[Path]
        A list of cog file paths, sorted by priority (descending).
    """
    if not path.is_dir():
        return []

    eligible_cogs = [f for f in path.rglob("*.py") if self._is_eligible_cog_file(f)]
    return sorted(eligible_cogs, key=lambda p: (self._get_cog_priority(p), p.name), reverse=True)
_create_load_result(path: Path, start_time: float, success: bool = True, error: Exception | None = None) -> CogLoadResult

Creates a standardized CogLoadResult object.

Parameters:

Name Type Description Default
path Path

The path to the cog file.

required
start_time float

The time when the loading process started.

required
success bool

Whether the load was successful, by default True.

True
error Exception | None

The error that occurred, if any, by default None.

None

Returns:

Type Description
CogLoadResult

The result object.

Source code in tux/cog_loader.py
Python
def _create_load_result(
    self,
    path: Path,
    start_time: float,
    success: bool = True,
    error: Exception | None = None,
) -> CogLoadResult:
    """
    Creates a standardized CogLoadResult object.

    Parameters
    ----------
    path : Path
        The path to the cog file.
    start_time : float
        The time when the loading process started.
    success : bool, optional
        Whether the load was successful, by default True.
    error : Exception | None, optional
        The error that occurred, if any, by default None.

    Returns
    -------
    CogLoadResult
        The result object.
    """
    module = self._path_to_module(path)
    load_time = time.perf_counter() - start_time
    return CogLoadResult(module, success, load_time, error)
_load_single_cog(path: Path) -> CogLoadResult async

Loads a single cog with comprehensive error handling and timing.

Parameters:

Name Type Description Default
path Path

The path to the cog file to load.

required

Returns:

Type Description
CogLoadResult

The result of the loading operation.

Raises:

Type Description
CogLoadError

If the cog fails to load.

Source code in tux/cog_loader.py
Python
@span("cog.load_single")
async def _load_single_cog(self, path: Path) -> CogLoadResult:
    """
    Loads a single cog with comprehensive error handling and timing.

    Parameters
    ----------
    path : Path
        The path to the cog file to load.

    Returns
    -------
    CogLoadResult
        The result of the loading operation.

    Raises
    ------
    CogLoadError
        If the cog fails to load.
    """
    start_time = time.perf_counter()
    module = self._path_to_module(path)
    cog_name = path.stem

    set_span_attributes({"cog.name": cog_name, "cog.path": str(path), "cog.module": module})

    try:
        await self.bot.load_extension(module)
    except Exception as e:
        result = self._create_load_result(path, start_time, success=False, error=e)
        capture_span_exception(e, cog_status="failed", cog_name=cog_name, cog_module=module)
        error_msg = f"Failed to load cog {module}."
        logger.error(f"{error_msg} Error: {e}")
        raise CogLoadError(error_msg) from e
    else:
        result = self._create_load_result(path, start_time)
        self.load_times[module] = result.load_time
        set_span_attributes({"cog.status": "loaded", "load_time_ms": result.load_time_ms})
        logger.debug(f"Successfully loaded cog {module} in {result.load_time_ms:.2f}ms")
        return result
_unload_single_cog(path: Path) -> bool async

Unloads a single cog with enhanced tracing.

Parameters:

Name Type Description Default
path Path

The path to the cog file to unload.

required

Returns:

Type Description
bool

True if the cog was unloaded successfully, False otherwise.

Source code in tux/cog_loader.py
Python
@span("cog.unload_single")
async def _unload_single_cog(self, path: Path) -> bool:
    """
    Unloads a single cog with enhanced tracing.

    Parameters
    ----------
    path : Path
        The path to the cog file to unload.

    Returns
    -------
    bool
        True if the cog was unloaded successfully, False otherwise.
    """
    module = self._path_to_module(path)
    set_span_attributes({"cog.module": module})

    try:
        await self.bot.unload_extension(module)
    except commands.ExtensionNotLoaded:
        logger.warning(f"Cog {module} is not loaded, cannot unload.")
        return False
    except Exception as e:
        capture_span_exception(e, operation="unload", cog_module=module)
        logger.error(f"Failed to unload cog {module}: {e}")
        return False
    else:
        logger.info(f"Successfully unloaded cog: {module}")
        return True
reload_cog(path: Path) -> bool async

Reloads a single cog with comprehensive error handling.

Parameters:

Name Type Description Default
path Path

The path to the cog file to reload.

required

Returns:

Type Description
bool

True if the cog was reloaded successfully, False otherwise.

Source code in tux/cog_loader.py
Python
@span("cog.reload_single")
async def reload_cog(self, path: Path) -> bool:
    """
    Reloads a single cog with comprehensive error handling.

    Parameters
    ----------
    path : Path
        The path to the cog file to reload.

    Returns
    -------
    bool
        True if the cog was reloaded successfully, False otherwise.
    """
    module = self._path_to_module(path)
    set_span_attributes({"cog.module": module})

    await self._unload_single_cog(path)

    try:
        await self._load_single_cog(path)
    except CogLoadError:
        return False
    else:
        logger.info(f"Successfully reloaded cog: {module}")
        return True
_load_cogs_from_directory(path: Path) -> list[CogLoadResult] async

Discovers, groups, and loads all eligible cogs from a directory.

Cogs are loaded by priority groups in descending order. Within each priority group, cogs are loaded sequentially to prevent race conditions and dependency issues. If a cog fails to load within a priority group, the remaining cogs in that group are skipped to prevent cascading failures.

Parameters:

Name Type Description Default
path Path

The directory to load cogs from.

required

Returns:

Type Description
list[CogLoadResult]

A list of results for each cog loaded.

Source code in tux/cog_loader.py
Python
@span("cog.load_directory")
async def _load_cogs_from_directory(self, path: Path) -> list[CogLoadResult]:
    """
    Discovers, groups, and loads all eligible cogs from a directory.

    Cogs are loaded by priority groups in descending order. Within each priority
    group, cogs are loaded sequentially to prevent race conditions and dependency
    issues. If a cog fails to load within a priority group, the remaining cogs
    in that group are skipped to prevent cascading failures.

    Parameters
    ----------
    path : Path
        The directory to load cogs from.

    Returns
    -------
    list[CogLoadResult]
        A list of results for each cog loaded.
    """
    eligible_cogs = self._discover_and_sort_cogs(path)
    if not eligible_cogs:
        return []

    set_span_attributes({"eligible_cog_count": len(eligible_cogs)})

    all_results: list[CogLoadResult] = []
    cogs_by_priority = groupby(eligible_cogs, key=self._get_cog_priority)

    for priority, cogs in cogs_by_priority:
        cogs_to_load = list(cogs)
        with enhanced_span("cog.load_priority_group", f"Loading priority {priority} cogs", priority=priority):
            categories = {cog.parent.name for cog in cogs_to_load}
            set_span_attributes({"cog_count": len(cogs_to_load), "categories": list(categories)})

            start_time = time.perf_counter()
            # Load cogs sequentially within priority group to avoid dependency issues
            # This prevents race conditions that could occur if cogs within the same
            # priority group depend on each other during import/initialization
            group_results: list[CogLoadResult] = []
            for cog in cogs_to_load:
                try:
                    result = await self._load_single_cog(cog)
                    group_results.append(result)
                except CogLoadError as e:
                    # Create a failed result for tracking
                    failed_result = self._create_load_result(cog, start_time, success=False, error=e)
                    group_results.append(failed_result)
                    # Stop loading remaining cogs in this priority group to prevent
                    # cascading failures from dependency issues
                    logger.warning(f"Skipping remaining cogs in priority {priority} due to failure: {e}")
                    break

            all_results.extend(group_results)

            set_span_attributes(
                {
                    "load_time_s": time.perf_counter() - start_time,
                    "success_count": len([r for r in group_results if r.success]),
                    "failure_count": len([r for r in group_results if not r.success]),
                },
            )
    return all_results
load_cogs(path: Path) -> list[CogLoadResult] async

Recursively loads eligible cogs from a directory or a single file.

Parameters:

Name Type Description Default
path Path

The path to the file or directory to load cogs from.

required

Returns:

Type Description
list[CogLoadResult]

A list of results for each cog loaded.

Raises:

Type Description
FileNotFoundError

If the specified path does not exist.

CogLoadError

If a fatal error occurs during the loading process.

Source code in tux/cog_loader.py
Python
@span("cog.load_path")
async def load_cogs(self, path: Path) -> list[CogLoadResult]:
    """
    Recursively loads eligible cogs from a directory or a single file.

    Parameters
    ----------
    path : Path
        The path to the file or directory to load cogs from.

    Returns
    -------
    list[CogLoadResult]
        A list of results for each cog loaded.

    Raises
    ------
    FileNotFoundError
        If the specified path does not exist.
    CogLoadError
        If a fatal error occurs during the loading process.
    """
    set_span_attributes({"cog.path": str(path)})

    if not path.exists():
        logger.error(f"Cog path not found: {path}")
        msg = f"Cog path not found: {path}"
        raise FileNotFoundError(msg)

    try:
        if path.is_dir():
            return await self._load_cogs_from_directory(path)
        if self._is_eligible_cog_file(path):
            return [await self._load_single_cog(path)]
    except Exception as e:
        capture_span_exception(e, path=str(path), operation="load_cogs")
        logger.error(f"An error occurred while processing {path.as_posix()}: {e}")
        msg = f"Failed to load from {path.as_posix()}"
        raise CogLoadError(msg) from e

    logger.debug(f"Path {path} is not an eligible cog file or directory.")
    return []
load_cogs_from_folder(folder_name: str) -> list[CogLoadResult] async

Loads all cogs from a specified top-level folder.

Parameters:

Name Type Description Default
folder_name str

The name of the folder to load cogs from (e.g., "tux/cogs").

required

Returns:

Type Description
list[CogLoadResult]

A list of results for each cog loaded.

Raises:

Type Description
CogLoadError

Propagates errors from the underlying load_cogs call.

Source code in tux/cog_loader.py
Python
@transaction("cog.load_folder", description="Loading all cogs from a folder")
async def load_cogs_from_folder(self, folder_name: str) -> list[CogLoadResult]:
    """
    Loads all cogs from a specified top-level folder.

    Parameters
    ----------
    folder_name : str
        The name of the folder to load cogs from (e.g., "tux/cogs").

    Returns
    -------
    list[CogLoadResult]
        A list of results for each cog loaded.

    Raises
    ------
    CogLoadError
        Propagates errors from the underlying `load_cogs` call.
    """
    cog_path = Path(folder_name)
    with enhanced_span("cog.folder_processing", f"Processing {folder_name}", folder=folder_name):
        start_time = time.perf_counter()
        try:
            results = await self.load_cogs(path=cog_path)
        except FileNotFoundError as e:
            # Handle missing folders gracefully but log as error for visibility
            capture_span_exception(e, folder=folder_name, operation="load_folder")
            logger.error(f"Cog folder not found: {folder_name} - {e}")
            return []
        except CogLoadError as e:
            capture_span_exception(e, folder=folder_name, operation="load_folder")
            logger.error(f"Failed to load cogs from folder {folder_name}: {e}")
            raise
        else:
            load_time = time.perf_counter() - start_time
            success_count = sum(r.success for r in results)
            logger.info(
                f"Loaded {success_count}/{len(results)} cogs from {folder_name} in {load_time * 1000:.2f}ms",
            )
            return results
setup(bot: commands.Bot) -> CogLoader async classmethod

Sets up the cog loader and loads all initial cogs for the bot.

Parameters:

Name Type Description Default
bot Bot

The bot instance to set up.

required

Returns:

Type Description
CogLoader

The initialized CogLoader instance.

Raises:

Type Description
CogLoadError

If a fatal error occurs during the setup process.

Source code in tux/cog_loader.py
Python
@classmethod
@transaction("cog.setup", name="CogLoader Setup", description="Initialize and load all cogs")
async def setup(cls, bot: commands.Bot) -> CogLoader:
    """
    Sets up the cog loader and loads all initial cogs for the bot.

    Parameters
    ----------
    bot : commands.Bot
        The bot instance to set up.

    Returns
    -------
    CogLoader
        The initialized CogLoader instance.

    Raises
    ------
    CogLoadError
        If a fatal error occurs during the setup process.
    """
    with enhanced_span("cog.loader_init", "Initializing CogLoader", bot_id=bot.user.id if bot.user else "unknown"):
        start_time = time.perf_counter()
        cog_loader = cls(bot)
        cog_folders = ["tux/handlers", "tux/cogs", "tux/extensions"]
        try:
            all_results: list[CogLoadResult] = []
            for folder in cog_folders:
                folder_results = await cog_loader.load_cogs_from_folder(folder_name=folder)
                all_results.extend(folder_results)
            total_time = time.perf_counter() - start_time
            total_cogs = len(all_results)
            successful_cogs = sum(r.success for r in all_results)
            logger.info(
                f"Cog loading complete: {successful_cogs}/{total_cogs} cogs loaded in {total_time * 1000:.2f}ms",
            )
        except Exception as e:
            capture_span_exception(e, operation="cog_setup")
            logger.opt(exception=e).critical("Failed to set up cog loader.")
            msg = "Failed to initialize CogLoader"
            raise CogLoadError(msg) from e
        else:
            await bot.add_cog(cog_loader)
            return cog_loader

Functions