robertwayne / cogwatch Goto Github PK
View Code? Open in Web Editor NEWHot-reloading for discord.py-based command files.
License: MIT License
Hot-reloading for discord.py-based command files.
License: MIT License
I am not sure if this is unique to the disnake library, but the logger.info calls in the else after the try blocks are not being called.
In fact any code added after the await self.client.reload_extension(cog_dir)
never gets run ...
I can confirm from log statements in my cog's setup and taredown functions that the extension is being reloaded, it is just strange that nothing after the await runs.
async def reload(self, cog_dir: str):
"""Attempts to atomically reload the file into the client."""
try:
logger.info(f'{self.CBOLD}{self.CGREEN}[Cog Reloaded]{self.CEND} Before') # This one will print correctly
await self.client.reload_extension(cog_dir)
logger.info(f'{self.CBOLD}{self.CGREEN}[Cog Reloaded]{self.CEND} After') # This line will never be run
except commands.NoEntryPointError:
logger.info(
f'{self.CBOLD}{self.CRED}[Error]{self.CEND} Failed to reload {self.CBOLD}{cog_dir}{self.CEND}; no entry point found.'
)
except commands.ExtensionNotLoaded:
logger.info(f'Cannot reload {cog_dir} because it is not loaded.')
except Exception as exc:
self.cog_error(exc)
else:
logger.info(f'{self.CBOLD}{self.CGREEN}[Cog Reloaded]{self.CEND} {cog_dir}') # This line will never be run
It would be a nice feature if there was a way to automatically reload views just like cogs are reloaded. I keep all of my views in a single folder, and import them wherever I need them.
I found a way to do this using the importlib
library. I reload the file in which the view is present, and then also reload the cog. And it seems to work. Would it possible to make it work?
Example structure:
project_name
-- project_name
---- client.py
-- pyproject.toml
The main problem starts in the get_dotted_cog_path
method when we try to join a list of indexes via a root path name, but that root is set to the cogs_path
attribute. That attribute would be a string like 'project_name/commands', which cannot be used to get an index in a list of tokens.
Definitely a bit of an edge case on this one, but a good nice-to-have!
For example, if you create a new empty file in your watch directory, it will trigger an Added event (which understandably fails to load the extension). However subsequent changes to that file (making it a valid extension) will permanently fail; the extension was never loaded, and Changed events trigger an extension reload which fails if the extension was never loaded in the first place.
I think changing around line 91 of cogwatch.py would be the key here, checking to see if the extension is currently loaded (via bot.extensions) and either calling load or reload based on that.
This is mostly just a nicety during dev for quick iteration as I'm often creating new files and iterating on them until they become a valid extension, so I don't think this is crucial at all for production deployments.
Hi, super awesome project! Was experimenting with this, and I found that I would always hit an extension reload error if any of my cog file names had an upper case letter as it looks like the reloading logic forces everything to be lower case. Not blocking as I can make sure all of my cog file names are lower but it took a decent bit of digging around to find that was the issue.
Currently I rely on command files being in a single directory, but perhaps it would be simpler to just start at the project root directory and watch all files recursively. Technically this works, but it does seem like it's a performance drain to watch a bunch of files that don't matter -- especially on a larger project. Would this even matter? Perhaps default to project root, and keep the argument for finer user control.
Likely will implement if I don't come up with a better solution. Very easy.
As I was getting myself acquainted with cogwatch (and Cogs themselves), unfortunately, I lost quite some time dealing with extension-related errors upon trying to reload a cog file; until finding out I needed to add the following function at the bottom of it:
def setup(bot: commands.Bot):
'''Add cog every time extension (module) is (re)loaded.'''
bot.add_cog(MyCog(bot))
However, there is no explicit reference to the Extension concept in cogwatch tutorial, even though discord.py extensions page mentions hot-reloading. Since it seems to be a lower-level concept that many users that arrive here aren't aware of (myself included), I think it might be interesting to add tutorial instructions regarding these points (especially the needed setup
function). 🙂
This library is currently working with discord4py.
just letting you know, you can add it to supported libs!
Library: https://github.com/Pycord-Development/pycord
could you add support for it?
Generally I'd leave this up to the user to configure, but with a utility like this, I think it makes sense to add colored logging baked into the default configuration -- similar to JS-land watchers used with Vue and nuxt.
This projet is very exacting and perfect for my projet but how I can create a cog listener ?
cogwatch/examples/subclassed_bot.py
Line 12 in e3037fe
I might be incorrect as I haven't tested it verbatim, but when I was implementing this into my bot and using the subclassed_bot as a reference, I noticed that preload
wasn't set to True
and there doesn't appear to be anything that loads ping
initially.
I'm not certain if this is a mistake or if I missed something obvious. Apologies if it's the latter
When a file change event is emit, the result is cast to a string and manipulated under the assumption of backslash. This will cause cogwatch to fail on non-Windows platforms. Need to re-implement these manipulations in a platform-agnostic way.
2022-04-11 09:02:34 | py.warnings | WARNING | ...\Python39\site-packages\cogwatch\cogwatch.py:152: RuntimeWarning: coroutine 'BotBase.load_extension' was never awaited
self.client.load_extension(cog_dir)
The cogs cant be update atm bc the load is not awaited
. This awaited
is needed for the new dpy-v2
After I added the await local the cog was noticed in the console, but not reloaded:
2022-04-11 09:07:03 | watchgod.main | DEBUG | ...\bot\cogs time=0ms debounced=400ms files=2 changes=1 (1)
2022-04-11 09:07:03 | watchgod.main | DEBUG | ...\bot\cogs time=0ms debounced=401ms files=2 changes=1 (0)
2022-04-11 09:07:03 | watchgod.main | DEBUG | ...\bot\cogs changes released debounced=401ms
2022-04-11 09:07:04 | watchgod.main | DEBUG | ...\bot\cogs time=0ms debounced=399ms files=1 changes=2 (2)
2022-04-11 09:07:04 | watchgod.main | DEBUG | ...\bot\cogs time=0ms debounced=400ms files=1 changes=2 (0)
2022-04-11 09:07:04 | watchgod.main | DEBUG | ...\bot\cogs changes released debounced=400ms
With a little more debug i found this
2022-04-11 09:26:14 | root | ERROR | Extension 'B:.Repos.Guido.bot.cogs.test' has not been loaded.
Traceback (most recent call last):
File "...\Python39\site-packages\cogwatch\cogwatch.py", line 159, in reload
await self.client.reload_extension(cog_dir)
File "...\\Python39\site-packages\discord\ext\commands\bot.py", line 995, in reload_extension
raise errors.ExtensionNotLoaded(name)
discord.ext.commands.errors.ExtensionNotLoaded: Extension 'B:.Repos.Guido.bot.cogs.test' has not been loaded.
So i guess the "cogname" is not prepared correct for a reload
When cogs contains slash commands, they have to be re-sync'ed, as seen here: https://discord.com/channels/881118111967883295/1201648612745166880
Would be great if cogwatch could support this ❤️
Hello,
pycord doesn't allow for asynchronous setup functions in cogs. Unless I am missing something I am not entirely sure how I could work around this as I presume internally the watcher requires the setup
function in a cog to be asynchronous.
Extra information:
main.py
import os
import discord
from cogwatch import Watcher
bot = discord.Bot()
@bot.event
async def on_ready():
print(f"We have logged in as {bot.user}")
watcher = Watcher(bot, path="cogs", preload=True, debug=False)
await watcher.start()
bot.run(os.getenv("BOT_TOKEN"))
cogs/misc.py:
from discord.ext import commands
class Misc(commands.Cog):
def __init__(self, bot):
self.bot = bot
self._last_member = None
@commands.slash_command(name = "ping")
async def ping(self, ctx):
await ctx.respond("Pong!")
# fails here
async def setup(bot):
await bot.add_cog(Misc(bot))
Note, the version I am using is 2.4.1. I am also debating on whether this issue should be specific to the pycord project and not this project, though, it does mention that this API supports pycord.
The watcher prints out a few times “cog.name has been reloaded”, “cog.name could not be reloaded. Something about no entry point”. Then, a trace back error is sent regarding ExstensionNotFound or something similar. Works fine when I save files directly on my server
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.