-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(hot_reload.py): add hot reloading functionality for bot extensions
This commit introduces a new feature that allows for hot reloading of bot extensions. This is achieved by continuously checking for changes in extension files and reloading them if any modifications are detected. This feature improves the development workflow by eliminating the need to manually restart the bot whenever an extension is updated.
- Loading branch information
Showing
1 changed file
with
75 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import os | ||
from pathlib import Path | ||
|
||
from discord.ext import commands, tasks | ||
from loguru import logger | ||
|
||
from tux.bot import Tux | ||
|
||
|
||
def path_from_extension(extension: str) -> Path: | ||
"""Convert an extension notation to a file path.""" | ||
base_dir = Path(__file__).parent.parent | ||
relative_path = extension.replace(".", os.sep) + ".py" | ||
return (base_dir / relative_path).resolve() | ||
|
||
|
||
class HotReload(commands.Cog): | ||
def __init__(self, bot: Tux) -> None: | ||
self.bot = bot | ||
self.last_modified_time: dict[str, float] = {} | ||
self.hot_reload_loop.start() | ||
|
||
async def cog_unload(self) -> None: | ||
self.hot_reload_loop.stop() | ||
|
||
@tasks.loop(seconds=3) | ||
async def hot_reload_loop(self) -> None: | ||
"""Loop to check for changes in extension files and reload them if modified.""" | ||
for extension in list(self.bot.extensions.keys()): | ||
if extension == "jishaku": | ||
continue | ||
|
||
path: Path = path_from_extension(extension) | ||
|
||
try: | ||
modification_time: float = path.stat().st_mtime | ||
except FileNotFoundError: | ||
logger.error(f"File not found for extension {extension} at {path}") | ||
continue | ||
|
||
if self.last_modified_time.get(extension) == modification_time: | ||
continue | ||
|
||
# Reload the extension if it has been modified | ||
self.last_modified_time[extension] = modification_time | ||
|
||
try: | ||
await self.bot.reload_extension(extension) | ||
except commands.ExtensionNotLoaded: | ||
pass | ||
except commands.ExtensionError as e: | ||
logger.error(f"Failed to reload extension {extension}: {e}") | ||
else: | ||
logger.info(f"Reloaded {extension}") | ||
|
||
@hot_reload_loop.before_loop | ||
async def cache_last_modified_time(self) -> None: | ||
"""Cache the last modified time of all extensions before the loop starts.""" | ||
for extension in self.bot.extensions: | ||
if extension == "jishaku": | ||
continue | ||
|
||
path: Path = path_from_extension(extension) | ||
|
||
try: | ||
modification_time: float = path.stat().st_mtime | ||
except FileNotFoundError: | ||
logger.error(f"File not found for extension {extension} at {path}") | ||
continue | ||
|
||
self.last_modified_time[extension] = modification_time | ||
|
||
|
||
async def setup(bot: Tux) -> None: | ||
await bot.add_cog(HotReload(bot)) |