Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Boilerplate functions or doc for boilerplate #3629

Open
1 of 3 tasks
Stonedestroyer opened this issue Feb 29, 2020 · 1 comment
Open
1 of 3 tasks

[Feature] Boilerplate functions or doc for boilerplate #3629

Stonedestroyer opened this issue Feb 29, 2020 · 1 comment
Labels
Category: Core - API - Commands Package This is related to the `redbot.core.commands` package or `redbot.core.checks` module. Category: Core - Bot Class This is related to the `redbot.core.bot.Red` class. Status: Needs Discussion Needs more discussion. Type: Feature New feature or request.

Comments

@Stonedestroyer
Copy link
Contributor

Feature request

Select the type of feature you are requesting:

  • Cog
  • Command
  • API functionality

Describe your requested feature

As discussed on Discord there should be either documentation or helper functions for various things. An example is some user may wanna load a task before loading cog. For example can be to load a file. Audio and downloader uses start task as this example. Now if we had a special method called start_task or something similar it would help cut down on a lot of code in cogs and also make it more uniform. There is more examples but one was this start task.

@Stonedestroyer Stonedestroyer added the Type: Feature New feature or request. label Feb 29, 2020
@github-actions github-actions bot added the Status: Needs Triage This has not been labeled or discussed for handling yet. label Feb 29, 2020
@Jackenmen
Copy link
Member

Jackenmen commented Mar 14, 2020

About the boilerplate that is needed for start tasks, depending on how much you want (or need) to handle, it can definitely end up being quite big:

__init__.py file:

from .cogname import CogName

def setup(bot):
    cog = CogName()
    bot.add_cog(cog)
    cog.create_init_task()

cogname.py file:

import asyncio

class CogName:
    def __init__(self, bot):
        self._ready = asyncio.Event()
        self._init_task = None
        self._ready_raised = False

    def create_init_task(self):
        def _done_callback(task):
            exc = task.exception()
            if exc is not None:
                log.error(
                    "An unexpected error occurred during CogName's initialization.",
                    exc_info=exc
                )
                self._ready_raised = True
            self._ready.set()

        self._init_task = asyncio.create_task(self.initialize())
        self._init_task.add_done_callback(_done_callback)

    async def initialize(self):
        # alternatively use wait_until_red_ready()
        # if you need some stuff that happens > in our post-connection startup
        await self.bot.wait_until_ready()
        # do what you need

    def cog_unload(self):
        if self._init_task is not None:
            self._init_task.cancel()

    async def cog_before_invoke(self, ctx):
        # use if commands need initialize() to finish
        async with ctx.typing():
            await self._ready.wait()
        if self._ready_raised:
            await ctx.send(
                "There was an error during CogName's initialization.
                " Check logs for > more information."
            )
            raise commands.CheckFailure()

    @commands.command()
    async def example(self, ctx):
        # commands don't have to care about initialize()
        # cause we have cog-wide before_invoke
        pass

    @commands.Cog.listener()
    async def on_message(self, message):
        # use if listener needs initialize() to finish
        await self._ready.wait()
        if self._ready_raised:
            return
        # do everything

If we would want to make it easier for cog creators, we would of course need to make its behaviour configurable while not making it overcomplicated.
Making decorators or reserving method names that could be used for init task could certainly make it a lot easier.

The way that made the most sense to me is using decorators. This way, everything related to initializing can be referred to with red_initialize.some_name_here which is in my opinion rather friendly to developers:

class CogName(commands.Cog):
    # name to be determined
    @commands.Cog.red_init_task()
    async def red_initialize(self):
        """
        When cog is added with `bot.add_cog()`, this coroutine would be
        scheduled as a task and before any command is ran, it would have to wait
        for this scheduled task to finish.
        """

    # name to be determined
    @red_initialize.error
    async def red_initialize_error(self, ctx):
        """
        If the init task fails, running any cog's command will pre-emptively fail.
        If the init task failed and this function is defined,
        it will be called each time any command from this cog is called.

        This can for example be used to let the user know that there was an initialization error:
        """
        await ctx.send(
            "There was an error during CogName's initialization. Check logs for more information."
        )

    @commands.Cog.listener()
    # name to be determined
    @red_initialize.require
    async def on_message(self, message):
        """
        If there is a function that requires the init task to finish
        and it isn't a command nor is it called by a command
        (for example a listener or background loop), it is possible
        to make it wait for the init task using a special decorator.

        Alternatively, developers would be able to use wait method:
        """
        if not await self.red_initialize.wait():
            # init task failed
            return

To not overcomplicate handling required for this, I think it would be fair to limit this decorator to one per cog class.

The alternative that I was initially thinking of were using special method names. I don't have compelling reason to choose this over decorators, using special method names seems less explicit and makes it harder to know what's going on. But since this is the way I was experimenting with when I was looking at how this could be handled internally, I think it's fair to at least include it here:

class CogName(commands.Cog):
    # name to be determined
    async def red_initialize(self):
        # same as in the approach above, with the use of special method name instead

    # name to be determined
    async def red_initialize_error(self, ctx):
        # same as in the approach above, with the use of special method name instead

    @commands.Cog.listener()
    # name to be determined
    @commands.Cog.require_init_task()
    async def on_message(self, message):
        # same as in the approach above, but different decorator

I can definitely see how this could be helpful for Red developers, so I wanted to share some of my own ideas on this since I had some time, but I haven't talked about this with anyone in the team yet, so for now this is all just a (cool) concept for now.

@Jackenmen Jackenmen added Category: Bot Core Category: Core - API - Commands Package This is related to the `redbot.core.commands` package or `redbot.core.checks` module. Status: Needs Discussion Needs more discussion. and removed Status: Needs Triage This has not been labeled or discussed for handling yet. labels Mar 14, 2020
@Jackenmen Jackenmen added Category: Core - Bot Class This is related to the `redbot.core.bot.Red` class. and removed Category: Bot Core labels Feb 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Core - API - Commands Package This is related to the `redbot.core.commands` package or `redbot.core.checks` module. Category: Core - Bot Class This is related to the `redbot.core.bot.Red` class. Status: Needs Discussion Needs more discussion. Type: Feature New feature or request.
Projects
None yet
Development

No branches or pull requests

2 participants