Importing function from another cog with discord.py - python

I would like to import some functions from another cog so they can be used for several cogs in different .py files. How would I go about doing that? Here's what the documentation says:
class Economy(commands.Cog):
...
async def withdraw_money(self, member, money):
# implementation here
...
async def deposit_money(self, member, money):
# implementation here
...
class Gambling(commands.Cog):
def __init__(self, bot):
self.bot = bot
def coinflip(self):
return random.randint(0, 1)
#commands.command()
async def gamble(self, ctx, money: int):
"""Gambles some money."""
economy = self.bot.get_cog('Economy')
if economy is not None:
await economy.withdraw_money(ctx.author, money)
if self.coinflip() == 1:
await economy.deposit_money(ctx.author, money * 1.5)
as an example, but that means I have to define economy every time if I would like to call on it. Is there a more efficient method for calling a function in another cog?

If withdraw_money and deposit_money don't use any of Economy's attributes or methods, you could make them static methods and import Economy to use them or just make them functions outside the class/cog and import them directly.
Otherwise, you can look for a way to refactor those methods so that they don't rely on Economy so that you can make them static methods or independent functions.
If that's not possible, this is already the best way to do it.
Bot.get_cog is O(1) anyway, so there's very minimal impact efficiency-wise.

Related

How can I get a self variable from another class?

My problem is that I need to get the self.tracker variable from the gift_bot.py file inside the cogs/invite_moduly.py file. But sadly, I don't know a way to do that. I tried a few methods like self.bot.tracker, tracker and more, but none worked.
How can I access this variable in invite_module.py?
File gift_bot.py
class GiftBot(commands.Bot):
def __init__(self):
self.tracker = InviteTracker(self)
super().__init__(command_prefix="*", intents=intents, case_insensitive=True)
async def on_ready(self):
try:
await self.tracker.cache_invites()
except:
pass
cogs/invite_module.py:
class InviteModule(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.last_member: list = []
#commands.Cog.listener("on_member_join")
async def on_member_join(self, member: discord.Member):
invite_info = await get_inviter(self.tracker, member)
With this code, I get:
Unresolved attribute reference 'tracker' for class 'InviteModule'
Assuming that you instantiate InviteModule with bot being a reference to an instance of GiftBot, it would be self.bot.tracker.
self is used to reference the instance of the class where's that method (by convention, it's not a keyword, but it's discouraged to use something other than self). So, inside InviteModule, self is an object of the class InviteModule, and inside GiftBot, it's an object the class GiftBot.
If you want to reference a property of GiftBot inside InviteModule, you must pass an instance of GiftBot to InviteModule¹, which I assume you're doing as the bot property. So, to access it inside InviteModule use self.bot, and hence, the tracker would be self.bot.tracker.
¹ Or, instead of passing the whole bot, you could pass the property itself. For instance, you could do:
class InviteModule(commands.Cog):
def __init__(self, tracker):
self.tracker = tracker
# rest of the code here
And use as:
giftBot = GiftBot()
inviteModule = InviteModule(giftBot.tracker)
I assume you want the first option, but I'm adding this for completeness.
I can be wrong, but I believe the error mentioned in the comments is actually from the IDE.
Per your comment, I suppose class Bot does not have the field tracker. So, you could either:
Change InviteModule init to accept GiftBot
Before trying to access the field tracker, check if the element is an instance of GiftBot.
If there's nothing usable in InviteModule without the field tracker, the first option is probably better:
class InviteModule(commands.Cog):
def __init__(self, bot: GiftBot): # or gift_bot.GiftBot, depending on how you're importing/connecting those files
self.bot = bot
# rest of the code
The second option would go something like:
class InviteModule(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
# rest of init
#commands.Cog.listener("on_member_join")
async def on_member_join(self, member: discord.Member):
if instanceof(self.bot, GiftBot):
invite_info = await get_inviter(self.bot.tracker, member)
# if not instance, define what's desired: ignore, log only, throw an error

Trouble with async context and structuring my code

I am using a library that requires async context(aioboto3).
My issue is that I can't call methods from outside the async with block on my custom S3StreamingFile instance. If I do so, python raises an exception, telling me that HttpClient is None.
I want to access the class methods of S3StreamingFile from an outer function, for example in a API route. I don't want to return anything more(from file_2.py) than the S3StreamingFile class instance to the caller(file_3.py). The aioboto3 related code can't be moved to file_3.py. file_1.py and file_2.py need to contain the aioboto3 related logic.
How can I solve this?
Example of not working code:
# file_1.py
class S3StreamingFile():
def __init__(self, s3_object):
self.s3_object = s3_object
async def size(self):
return await self.s3_object.content_length # raises exception, HttpClient is None
...
# file_2.py
async def get_file():
async with s3.resource(...) as resource:
s3_object = await resource.Object(...)
s3_file = S3StreamingFile(s3_object)
return s3_file
# file_3.py
async def main()
s3_file = await get_file()
size = await s3_file.size() # raises exception, HttpClient is None
Example of working code:
# file_1.py
class S3StreamingFile():
def __init__(self, s3_object):
self.s3_object = s3_object
async def size(self):
return await self.s3_object.content_length
...
# file_2.py
async def get_file():
async with s3.resource(...) as resource:
s3_object = await resource.Object(...)
s3_file = S3StreamingFile(s3_object)
size = await s3_file.size() # works OK here, HttpClient is available
return s3_file
# file_3.py
async def main()
s3_file = await get_file()
I want to access the class methods from an outer function... how do I solve this?
Don't. This library is using async context managers to handle resource acquisition/release. The whole point about the context manager is that things like s3_file.size() only make sense when you have acquired the relevant resource (here the s3 file instance).
But how do you use this data in the rest of your program? In general---since you haven't said what the rest of your program is or why you want this data---there are two approaches:
acquire the resource somewhere else, and then make it available in much larger scopes, or
make your other functions resource-aware.
In the first case, you'd acquire the resource before all the logic runs, and then hold on to it. (This might look like RAII.) This might well make sense in smaller scripts, or when a resource is designed to be held by only one process at a time. It's a poor fit for code which will spend most of its time doing nothing, or has to coexist with other users of the resource. (An extension of this is writing your own code as a context manager, and effectively moving the problem up the calling stack. If each code path only handles one resource, this might well be the way to go.)
In the second, you'd write your higher-level functions to be aware that they're accessing a resource. You might do this by passing the resource itself around:
def get_file(resource: AcquiredResource) -> FileResource:
...
def get_size(thing: AcquirableResource) -> int:
with thing as resource:
s3_file = get_file(resource)
return s3_file.size
(using made-up generic types here to illustrate the point).
Or you might want a static copy of (some) attrs of a particular resource, like a file here, and a step where you build that copy. Personally I would likely store those in a dict or local object to make it clear that I wasn't handling the resource itself.
The basic idea here is that the with block guards access to a potentially difficult-to-acquire resource. That safety is built into the library, but it comes at the cost of having to think about the acquisition and structure it into the flow of your code.

How can I load an extension inside a Cog in Discord.py?

I have a file, ticket.py, and I'm currently loading the command into my bot using client.load_extension(ticket).
ticket.py looks like this:
#commands.command(name='ticket')
async def ticket(ctx,*, args=None):
//do stuff
def setup(bot):
bot.add_command(ticket)
and I load it in the main file:
client=commands.Bot(command_prefix='!')
client.load_extension("ticket")
Which works fine, but currently, the ticket command is not associated with a Cog. I want to know how I can load it inside a Cog (for example, so that it is associated with the Utilities Cog).
Normally, I would simply define the command inside a class:
class Utilities(commands.Cog):
#commands.command(name='ticket')
async def ticket(self, ctx, *, args=None):
//do stuff
However, my code is rather long and tedious and I would prefer to deal with it in a separate file (hence ticket.py, which is outside of main.py).
How can I load the ticket command into the Utilities Cog? (while keeping ticket.py and main.py separate).
You can store the actual code for the command in the other file and simply call it directly from inside the cog.
Suppose this is called command_ticket.py and you have a bunch of random arguments:
async def _ticket(ctx, number: int, whatever: str, consume_rest: str):
# do things
Then, you just invoke that in the cog:
import command_ticket
class MyCog(commands.Cog):
#commands.command(name='ticket')
async def ticket(ctx, number: int, whatever: str, *, consume_rest: str = None):
command_ticket._ticket(ctx, number, whatever, consume_rest)
Note that in this case there would be no need to add a separate extension for the internal ticket function - you can simply register the extension with the cog.

retrieve variables from cog class

class Music(commands.Cog):
def __init__(self, client):
self.players = {}
async def destroy_player(self,ctx):
try:
self.players.pop(ctx.guild.id)
print(self.players)
except:
pass
def setup(client):
client.add_cog(Music(client))
this is a part of my code. how can I use self.players outside the class?
You can define a variable to store the class object before using it, like this:
def setup(client):
c = Music(client)
client.add_cog(c)
print(c.players)
Simple, use the get_cog method. Assuming you are using this in another cog
async def get_something(self, ctx): #this would be a cog method (command or any function inside a cog class)
Music = self.bot.get_cog('Music')
print(Music.players)
or if you are just using bot
async def get_something(ctx):
Music = bot.get_cog('Music') #or ctx.bot.get_cog('Music')
this will work if you have added the cog Music with add_cog(Music(bot)
References:
get_cog
Context
Examples:
You can see the discord.py's docs example here
You can either create the instance of a class (create instance outside of a function to use it outside of a function or in a function with return statement) which is a much simpler approach
Or if you want to avoid creating the instance of a class do sth like this:
class Music:
players = {}
#classmethod
def get_players(self):
return self.players
Music.get_players()
This will also return the attribute without creating the class instance, but the attribute can be within init statement.
If you want to learn more read about getter and setter in Python

Python Split Class to multiple files when access parent via decorator

I have this Discord.py Cog object that contains a bunch of methods that I would like to split into multiple files to browse easier. (most stuff cut out for brevity)
class VoteManager(commands.Cog):
def __init__(self, bot):
self.bot = bot
#commands.group(pass_context=True, invoke_without_command=True)
async def vote(self, ctx):
await ctx.send(f'Command not found. Use `{BOT_CALL_PREFIX}vote help` for commands.')
def new(self, channel_id):
pass # Actual logic for new_command, separated so it could be called without context
#vote.command(name='new')
async def new_command(self):
pass # Really long function, etc.
What I want is to be able to split definitions like new and new_command into their own file new.py.
The VoteManager class is in the __init__.py file of discordbot.voting module. So when used, I use bot.add_cog(discordbot.voting.VoteManager(bot)) to add it to the registry of commands for the bot.
The goal is to be able to just do something like from .voting.new import * in the VoteManager class and have it read the functions to be added.

Categories