Git Product home page Git Product logo

cogwatch's Introduction

Cog Watch

Automatic hot-reloading for your discord.py command files.

Version Python Version


cogwatch is a utility that you can plug into your discord.py bot (or various supported bot libraries) that will watch your command files directory (cogs) and automatically reload them as you modify or move them around in real-time.

No more reloading your commands manually every time you edit an embed just to make sure it looks perfect!


Features

  • Automatically reloads commands in real-time as you edit them (no !reload <command_name> needed).
  • Optionally handles the loading of all your commands on start-up (removes boilerplate).

Supported Libraries

cogwatch should work with any library that forked from discord.py. However, these libraries have been explicitly tested to work:

Getting Started

You can install the library with pip install cogwatch.

Import the watch decorator and apply it to your on_ready method and let the magic take effect.

See the examples directory for more details.

import asyncio
from discord.ext import commands
from cogwatch import watch


class ExampleBot(commands.Bot):
    def __init__(self):
        super().__init__(command_prefix='!')

    @watch(path='commands', preload=True) # This is all you need to add.
    async def on_ready(self):
        print('Bot ready.')

    async def on_message(self, message):
        if message.author.bot:
            return

        await self.process_commands(message)


async def main():
    client = ExampleBot()
    await client.start('YOUR_TOKEN_GOES_HERE')

if __name__ == '__main__':
    asyncio.run(main())

NOTE: If you're following the example command files in the examples directory, make sure you only use async/await on your setup function if the library you're using supports it. For example, discord.py uses async setup functions, but many other libraries do not. cogwatch supports both under-the-hood.

Configuration

These options can be passed to the decorator (or the class if manually initializing):

Option Type Description Default
path str Path of the directory where your command files exist; cogwatch will watch recursively within this directory. commands
preload bool Whether to detect and load all cogs on start. False
colors bool Whether to use colorized terminal outputs or not. True
default_logger bool Whether to use the default logger (to sys.stdout) or not. True
loop AbstractEventLoop Custom event loop. get_event_loop()
debug bool Whether to run the bot only when the Python __debug__ flag is True. True

NOTE: cogwatch will only run if the __debug__ flag is set on Python. You can read more about that here. In short, unless you run Python with the -O flag from your command line, __debug__ will be True. If you just want to bypass this feature, pass in debug=False and it won't matter if the flag is enabled or not.

Logging

By default, the utility has a logger configured so users can get output to the console. You can disable this by passing in default_logger=False. If you want to hook into the logger -- for example, to pipe your output to another terminal or tail a file -- you can set up a custom logger like so:

import logging
import sys

watch_log = logging.getLogger('cogwatch')
watch_log.setLevel(logging.INFO)
watch_handler = logging.StreamHandler(sys.stdout)
watch_handler.setFormatter(logging.Formatter('[%(name)s] %(message)s'))
watch_log.addHandler(watch_handler)

Contributing

cogwatch is open to all contributions. If you have a feature request or found a bug, please open an issue or submit a pull request. If your change is significant, please open an issue first to discuss it.

Check out the contributing guidelines for more details.

Integration Tests

In order to test cogwatch against all of the supported libraries, there is a small integration test suite built-in to this repository. These are not automatically checked tests, but rather a way to manually set up the environment for a specific library and run a bot with cogwatch to ensure it works as expected.

The available scripts are:

  • poetry run discord4py
  • poetry run discordpy
  • poetry run disnake
  • poetry run nextcord
  • poetry run pycord

License

cogwatch is available under the MIT License.

cogwatch's People

Contributors

1993zig avatar dependabot[bot] avatar quentiumyt avatar robertwayne avatar tdarnell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

cogwatch's Issues

Creating dotted paths breaks down when using nested project structures.

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.

Default cogs_path Directory

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.

Reloading fails when filenames have uppercase letters

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.

[Request] Try loading an extension on file change instead of reloading if the initial load failed

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.

Add instructions about required "setup" function on cog extensions

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). 🙂

Slight error in example

@watch(path='commands', debug=False)

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

Issues with pycord

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.

(Feature Request) Add ability to reload views

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?

'BotBase.load_extension' was never awaited (next)

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

[Possible] Colored Log Output

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.

logger not firing

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

Platform-specific path-as-string manipulation.

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.

How create a listener ?

This projet is very exacting and perfect for my projet but how I can create a cog listener ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.