[V3] NumPy Docstrings (#1032)

* ALL THE DOCSTRINGS

* Remove imports in drivers package

* Fixed build warnings
This commit is contained in:
Tobotimus 2017-10-18 13:01:59 +11:00 committed by Will
parent 684004d614
commit c80684a129
14 changed files with 976 additions and 560 deletions

View File

@ -37,6 +37,7 @@ extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
'sphinx.ext.napoleon',
'sphinxcontrib.asyncio'
]
@ -84,6 +85,9 @@ pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# Role which is assigned when you make a simple reference within backticks
default_role = "any"
# -- Options for HTML output ----------------------------------------------

View File

@ -192,5 +192,4 @@ Driver Reference
.. automodule:: redbot.core.drivers
.. autoclass:: red_base.BaseDriver
:members:
.. autoclass:: redbot.core.drivers.red_base.BaseDriver

View File

@ -27,11 +27,15 @@ Installing Red
1. Open a command prompt (open Start, search for "command prompt", then click it)
2. Run the appropriate command, depending on if you want audio or not
* No audio: :code:`python -m pip install Red-DiscordBot`
* Audio: :code:`python -m pip install Red-DiscordBot[voice]`
3. Once that has completed, run :code:`redbot-setup` to set up your instance
* This will set the location where data will be stored, as well as your
storage backend and the name of the instance (which will be used for
running the bot)
4. Once done setting up the instance, run :code:`redbot <your instance name>` to run Red.
It will walk through the initial setup, asking for your token and a prefix

View File

@ -52,27 +52,37 @@ class Downloader:
self._repo_manager = RepoManager(self.conf)
async def cog_install_path(self):
"""
Returns the current cog install path.
:return:
"""Get the current cog install path.
Returns
-------
pathlib.Path
The default cog install path.
"""
return await self.bot.cog_mgr.install_path()
async def installed_cogs(self) -> Tuple[Installable]:
"""
Returns the dictionary mapping cog name to install location
and repo name.
:return:
"""Get info on installed cogs.
Returns
-------
`tuple` of `Installable`
All installed cogs / shared lib directories.
"""
installed = await self.conf.installed()
# noinspection PyTypeChecker
return tuple(Installable.from_json(v) for v in installed)
async def _add_to_installed(self, cog: Installable):
"""
Marks a cog as installed.
:param cog:
:return:
"""Mark a cog as installed.
Parameters
----------
cog : Installable
The cog to check off.
"""
installed = await self.conf.installed()
cog_json = cog.to_json()
@ -82,10 +92,13 @@ class Downloader:
await self.conf.installed.set(installed)
async def _remove_from_installed(self, cog: Installable):
"""
Removes a cog from the saved list of installed cogs.
:param cog:
:return:
"""Remove a cog from the saved list of installed cogs.
Parameters
----------
cog : Installable
The cog to remove.
"""
installed = await self.conf.installed()
cog_json = cog.to_json()
@ -326,11 +339,19 @@ class Downloader:
await ctx.send(box(msg))
async def is_installed(self, cog_name: str) -> (bool, Union[Installable, None]):
"""
Checks to see if a cog with the given name was installed
through Downloader.
:param cog_name:
:return: is_installed, Installable
"""Check to see if a cog has been installed through Downloader.
Parameters
----------
cog_name : str
The name of the cog to check for.
Returns
-------
`tuple` of (`bool`, `Installable`)
:code:`(True, Installable)` if the cog is installed, else
:code:`(False, None)`.
"""
for installable in await self.installed_cogs():
if installable.name == cog_name:
@ -339,11 +360,20 @@ class Downloader:
def format_findcog_info(self, command_name: str,
cog_installable: Union[Installable, object]=None) -> str:
"""
Formats the info for output to discord
:param command_name:
:param cog_installable: Can be an Installable instance or a Cog instance.
:return: str
"""Format a cog's info for output to discord.
Parameters
----------
command_name : str
Name of the command which belongs to the cog.
cog_installable : `Installable` or `object`
Can be an `Installable` instance or a Cog instance.
Returns
-------
str
A formatted message for the user.
"""
if isinstance(cog_installable, Installable):
made_by = ", ".join(cog_installable.author) or _("Missing from info.json")
@ -360,12 +390,20 @@ class Downloader:
return msg.format(command_name, made_by, repo_url, cog_name)
def cog_name_from_instance(self, instance: object) -> str:
"""
Determines the cog name that Downloader knows from the cog instance.
"""Determines the cog name that Downloader knows from the cog instance.
Probably.
:param instance:
:return:
Parameters
----------
instance : object
The cog instance.
Returns
-------
str
The name of the cog according to Downloader..
"""
splitted = instance.__module__.split('.')
return splitted[-2]

View File

@ -16,11 +16,38 @@ class InstallableType(Enum):
class Installable(RepoJSONMixin):
"""
Base class for anything the Downloader cog can install.
- Modules
- Repo Libraries
- Other stuff?
"""Base class for anything the Downloader cog can install.
- Modules
- Repo Libraries
- Other stuff?
The attributes of this class will mostly come from the installation's
info.json.
Attributes
----------
repo_name : `str`
Name of the repository which this package belongs to.
author : `tuple` of `str`, optional
Name(s) of the author(s).
bot_version : `tuple` of `int`
The minimum bot version required for this installation. Right now
this is always :code:`3.0.0`.
hidden : `bool`
Whether or not this cog will be hidden from the user when they use
`Downloader`'s commands.
required_cogs : `dict`
In the form :code:`{cog_name : repo_url}`, these are cogs which are
required for this installation.
requirements : `tuple` of `str`
Required libraries for this installation.
tags : `tuple` of `str`
List of tags to assist in searching.
type : `int`
The type of this installation, as specified by
:class:`InstallationType`.
"""
INFO_FILE_DESCRIPTION = """
@ -28,10 +55,13 @@ class Installable(RepoJSONMixin):
"""
def __init__(self, location: Path):
"""
Base installable initializer.
"""Base installable initializer.
Parameters
----------
location : pathlib.Path
Location (file or folder) to the installable.
:param location: Location (file or folder) to the installable.
"""
super().__init__(location)
@ -62,6 +92,7 @@ class Installable(RepoJSONMixin):
@property
def name(self):
"""`str` : The name of this package."""
return self._location.stem
async def copy_to(self, target_dir: Path) -> bool:

View File

@ -164,10 +164,13 @@ class Repo(RepoJSONMixin):
)
async def clone(self) -> Tuple[str]:
"""
Clones a new repo.
"""Clone a new repo.
Returns
-------
`tuple` of `str`
All available module names from this repo.
:return: List of available module names from this repo.
"""
exists, path = self._existing_git_repo()
if exists:
@ -202,10 +205,13 @@ class Repo(RepoJSONMixin):
return self._update_available_modules()
async def current_branch(self) -> str:
"""
Determines the current branch using git commands.
"""Determine the current branch using git commands.
Returns
-------
str
The current branch name.
:return: Current branch name
"""
exists, _ = self._existing_git_repo()
if not exists:
@ -226,11 +232,18 @@ class Repo(RepoJSONMixin):
return p.stdout.decode().strip()
async def current_commit(self, branch: str=None) -> str:
"""
Determines the current commit hash of the repo.
"""Determine the current commit hash of the repo.
Parameters
----------
branch : `str`, optional
Override for repo's branch attribute.
Returns
-------
str
The requested commit hash.
:param branch: Override for repo's branch attribute
:return: Commit hash string
"""
if branch is None:
branch = self.branch
@ -254,10 +267,13 @@ class Repo(RepoJSONMixin):
return p.stdout.decode().strip()
async def hard_reset(self, branch: str=None) -> None:
"""
Performs a hard reset on the current repo.
"""Perform a hard reset on the current repo.
Parameters
----------
branch : `str`, optional
Override for repo branch attribute.
:param branch: Override for repo branch attribute.
"""
if branch is None:
branch = self.branch
@ -281,11 +297,13 @@ class Repo(RepoJSONMixin):
" the following path: {}".format(self.folder_path))
async def update(self) -> (str, str):
"""
Updates the current branch of this repo.
"""Update the current branch of this repo.
Returns
-------
`tuple` of `str`
:py:code`(old commit hash, new commit hash)`
:return: tuple of (old commit hash, new commit hash)
:rtype: tuple
"""
curr_branch = await self.current_branch()
old_commit = await self.current_commit(branch=curr_branch)
@ -310,13 +328,20 @@ class Repo(RepoJSONMixin):
return old_commit, new_commit
async def install_cog(self, cog: Installable, target_dir: Path) -> bool:
"""
Copies a cog to the target directory.
"""Install a cog to the target directory.
Parameters
----------
cog : Installable
The package to install.
target_dir : pathlib.Path
The target directory for the cog installation.
Returns
-------
bool
The success of the installation.
:param Installable cog: Cog to install.
:param pathlib.Path target_dir: Directory to install the cog in.
:return: Installation success status.
:rtype: bool
"""
if cog not in self.available_cogs:
raise DownloaderException("That cog does not exist in this repo")
@ -330,14 +355,23 @@ class Repo(RepoJSONMixin):
return await cog.copy_to(target_dir=target_dir)
async def install_libraries(self, target_dir: Path, libraries: Tuple[Installable]=()) -> bool:
"""
Copies all shared libraries (or a given subset) to the target
directory.
"""Install shared libraries to the target directory.
If :code:`libraries` is not specified, all shared libraries in the repo
will be installed.
Parameters
----------
target_dir : pathlib.Path
Directory to install shared libraries to.
libraries : `tuple` of `Installable`
A subset of available libraries.
Returns
-------
bool
The success of the installation.
:param pathlib.Path target_dir: Directory to install shared libraries to.
:param tuple(Installable) libraries: A subset of available libraries.
:return: Status of all installs.
:rtype: bool
"""
if libraries:
if not all([i in self.available_libraries for i in libraries]):
@ -350,15 +384,23 @@ class Repo(RepoJSONMixin):
return True
async def install_requirements(self, cog: Installable, target_dir: Path) -> bool:
"""
Installs the requirements defined by the requirements
attribute on the cog object and puts them in the given
target directory.
"""Install a cog's requirements.
Requirements will be installed via pip directly into
:code:`target_dir`.
Parameters
----------
cog : Installable
Cog for which to install requirements.
target_dir : pathlib.Path
Path to directory where requirements are to be installed.
Returns
-------
bool
Success of the installation.
:param Installable cog: Cog for which to install requirements.
:param pathlib.Path target_dir: Path to which to install requirements.
:return: Status of requirements install.
:rtype: bool
"""
if not target_dir.is_dir():
raise ValueError("Target directory is not a directory.")
@ -367,14 +409,20 @@ class Repo(RepoJSONMixin):
return await self.install_raw_requirements(cog.requirements, target_dir)
async def install_raw_requirements(self, requirements: Tuple[str], target_dir: Path) -> bool:
"""
Installs a list of requirements using pip and places them into
the given target directory.
"""Install a list of requirements using pip.
Parameters
----------
requirements : `tuple` of `str`
List of requirement names to install via pip.
target_dir : pathlib.Path
Path to directory where requirements are to be installed.
Returns
-------
bool
Success of the installation
:param tuple(str) requirements: List of requirement names to install via pip.
:param pathlib.Path target_dir: Directory to install requirements to.
:return: Status of all requirements install.
:rtype: bool
"""
if len(requirements) == 0:
return True
@ -398,10 +446,9 @@ class Repo(RepoJSONMixin):
@property
def available_cogs(self) -> Tuple[Installable]:
"""
Returns a list of available cogs (not shared libraries and not hidden).
:rtype: tuple(Installable)
"""`tuple` of `installable` : All available cogs in this Repo.
This excludes hidden or shared packages.
"""
# noinspection PyTypeChecker
return tuple(
@ -411,10 +458,8 @@ class Repo(RepoJSONMixin):
@property
def available_libraries(self) -> Tuple[Installable]:
"""
Returns a list of available shared libraries in this repo.
:rtype: tuple(Installable)
"""`tuple` of `installable` : All available shared libraries in this
Repo.
"""
# noinspection PyTypeChecker
return tuple(
@ -463,14 +508,22 @@ class RepoManager:
return name.lower()
async def add_repo(self, url: str, name: str, branch: str="master") -> Repo:
"""
Adds a repo and clones it.
"""Add and clone a git repository.
Parameters
----------
url : str
URL to the git repository.
name : str
Internal name of the repository.
branch : str
Name of the default branch to checkout into.
Returns
-------
Repo
New Repo object representing the cloned repository.
:param url: URL of git repo to clone.
:param name: Internal name of repo.
:param branch: Branch to clone.
:return: New repo object representing cloned repo.
:rtype: Repo
"""
name = self.validate_and_normalize_repo_name(name)
if self.does_repo_exist(name):
@ -490,30 +543,45 @@ class RepoManager:
return r
def get_repo(self, name: str) -> Union[Repo, None]:
"""
Returns a repo object with the given name.
"""Get a Repo object for a repository.
Parameters
----------
name : str
The name of the repository to retrieve.
Returns
-------
`Repo` or `None`
Repo object for the repository, if it exists.
:param name: Repo name
:return: Repo object or ``None`` if repo does not exist.
:rtype: Union[Repo, None]
"""
return self._repos.get(name, None)
def get_all_repo_names(self) -> Tuple[str]:
"""
Returns a tuple of all repo names
"""Get all repo names.
Returns
-------
`tuple` of `str`
:rtype: tuple(str)
"""
# noinspection PyTypeChecker
return tuple(self._repos.keys())
async def delete_repo(self, name: str):
"""
Deletes a repo and its folders with the given name.
"""Delete a repository and its folders.
Parameters
----------
name : str
The name of the repository to delete.
Raises
------
MissingGitRepo
If the repo does not exist.
:param name: Name of the repo to delete.
:raises MissingGitRepo: If the repo does not exist.
"""
repo = self.get_repo(name)
if repo is None:
@ -529,12 +597,14 @@ class RepoManager:
await self._save_repos()
async def update_all_repos(self) -> MutableMapping[Repo, Tuple[str, str]]:
"""
Calls :py:meth:`Repo.update` on all repos.
"""Call `Repo.update` on all repositories.
Returns
-------
dict
A mapping of `Repo` objects that received new commits to a `tuple`
of `str` containing old and new commit hashes.
:return:
A mapping of :py:class:`Repo` objects that received new commits to a tuple containing old and
new commit hashes.
"""
ret = {}
for _, repo in self._repos.items():

View File

@ -37,7 +37,9 @@ _bank_type = type("Bank", (object,), {})
class Account:
"""A single account. This class should ONLY be instantiated by the bank itself."""
"""A single account.
This class should ONLY be instantiated by the bank itself."""
def __init__(self, name: str, balance: int, created_at: datetime.datetime):
self.name = name
@ -57,60 +59,87 @@ if not os.environ.get('BUILDING_DOCS'):
def _encoded_current_time() -> int:
"""
Encoded current timestamp in UTC.
:return:
"""Get the current UTC time as a timestamp.
Returns
-------
int
The current UTC timestamp.
"""
now = datetime.datetime.utcnow()
return _encode_time(now)
def _encode_time(time: datetime.datetime) -> int:
"""
Goes from datetime object to serializable int.
:param time:
:return:
"""Convert a datetime object to a serializable int.
Parameters
----------
time : datetime.datetime
The datetime to convert.
Returns
-------
int
The timestamp of the datetime object.
"""
ret = int(time.timestamp())
return ret
def _decode_time(time: int) -> datetime.datetime:
"""
Returns decoded timestamp in UTC.
:param time:
:return:
"""Convert a timestamp to a datetime object.
Parameters
----------
time : int
The timestamp to decode.
Returns
-------
datetime.datetime
The datetime object from the timestamp.
"""
return datetime.datetime.utcfromtimestamp(time)
async def get_balance(member: discord.Member) -> int:
"""
Gets the current balance of a member.
"""Get the current balance of a member.
:param discord.Member member:
Parameters
----------
member : discord.Member
The member whose balance to check.
:return:
Returns
-------
int
The member's balance
:rtype:
int
"""
acc = await get_account(member)
return acc.balance
async def can_spend(member: discord.Member, amount: int) -> bool:
"""
Determines if a member can spend the given amount.
"""Determine if a member can spend the given amount.
:param discord.Member member:
Parameters
----------
member : discord.Member
The member wanting to spend.
:param int amount:
amount : int
The amount the member wants to spend.
:return:
:code:`True` if the member has a sufficient balance to spend the amount, else :code:`False`.
:rtype:
bool
Returns
-------
bool
:code:`True` if the member has a sufficient balance to spend the
amount, else :code:`False`.
"""
if _invalid_amount(amount):
return False
@ -118,21 +147,25 @@ async def can_spend(member: discord.Member, amount: int) -> bool:
async def set_balance(member: discord.Member, amount: int) -> int:
"""
Sets an account balance.
"""Set an account balance.
May raise ValueError if amount is invalid.
:param discord.Member member:
Parameters
----------
member : discord.Member
The member whose balance to set.
:param int amount:
amount : int
The amount to set the balance to.
:return:
Returns
-------
int
New account balance.
:rtype:
int
:raises ValueError:
If attempting to set the balance to a negative number
Raises
------
ValueError
If attempting to set the balance to a negative number.
"""
if amount < 0:
raise ValueError("Not allowed to have negative balance.")
@ -157,22 +190,26 @@ def _invalid_amount(amount: int) -> bool:
async def withdraw_credits(member: discord.Member, amount: int) -> int:
"""
Removes a certain amount of credits from an account.
"""Remove a certain amount of credits from an account.
May raise ValueError if the amount is invalid or if the account has
insufficient funds.
:param discord.Member member:
Parameters
----------
member : discord.Member
The member to withdraw credits from.
:param int amount:
amount : int
The amount to withdraw.
:return:
Returns
-------
int
New account balance.
:rtype:
int
:raises ValueError:
if the withdrawal amount is invalid or if the account has insufficient funds
Raises
------
ValueError
If the withdrawal amount is invalid or if the account has insufficient
funds.
"""
if _invalid_amount(amount):
raise ValueError("Invalid withdrawal amount {} <= 0".format(amount))
@ -185,21 +222,25 @@ async def withdraw_credits(member: discord.Member, amount: int) -> int:
async def deposit_credits(member: discord.Member, amount: int) -> int:
"""
Adds a given amount of credits to an account.
"""Add a given amount of credits to an account.
May raise ValueError if the amount is invalid.
:param discord.Member member:
Parameters
----------
member : discord.Member
The member to deposit credits to.
:param int amount:
amount : int
The amount to deposit.
:return:
The new balance
:rtype:
int
:raises ValueError:
Returns
-------
int
The new balance.
Raises
------
ValueError
If the deposit amount is invalid.
"""
if _invalid_amount(amount):
raise ValueError("Invalid withdrawal amount {} <= 0".format(amount))
@ -209,24 +250,27 @@ async def deposit_credits(member: discord.Member, amount: int) -> int:
async def transfer_credits(from_: discord.Member, to: discord.Member, amount: int):
"""
Transfers a given amount of credits from one account to another.
"""Transfer a given amount of credits from one account to another.
May raise ValueError if the amount is invalid or if the :code:`from_`
account has insufficient funds.
:param discord.Member from_:
Parameters
----------
from_: discord.Member
The member to transfer from.
:param discord.Member to:
to : discord.Member
The member to transfer to.
:param int amount:
amount : int
The amount to transfer.
:return:
Returns
-------
int
The new balance.
:rtype:
int
:raises ValueError:
If the amount is invalid or if :code:`from_` has insufficient funds.
Raises
------
ValueError
If the amount is invalid or if ``from_`` has insufficient funds.
"""
if _invalid_amount(amount):
raise ValueError("Invalid transfer amount {} <= 0".format(amount))
@ -236,18 +280,18 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
async def wipe_bank(user: Union[discord.User, discord.Member]):
"""
Deletes all accounts from the bank.
"""Delete all accounts from the bank.
.. important::
A member is required if the bank is currently guild specific.
:param user:
Parameters
----------
user : `discord.User` or `discord.Member`
A user to be used in clearing the bank, this is required for technical
reasons and it does not matter which user/member is used.
:type user:
discord.User or discord.Member
"""
if await is_global():
await _conf.user(user).clear()
@ -256,19 +300,23 @@ async def wipe_bank(user: Union[discord.User, discord.Member]):
async def get_guild_accounts(guild: discord.Guild) -> List[Account]:
"""
Gets all account data for the given guild.
"""Get all account data for the given guild.
May raise RuntimeError if the bank is currently global.
:param discord.Guild guild:
Parameters
----------
guild : discord.Guild
The guild to get accounts for.
:return:
A generator for all guild accounts.
:rtype:
generator
:raises RuntimeError:
If the bank is global.
Returns
-------
`list` of `Account`
A list of all guild accounts.
Raises
------
RuntimeError
If the bank is currently global.
"""
if is_global():
raise RuntimeError("The bank is currently global.")
@ -283,19 +331,23 @@ async def get_guild_accounts(guild: discord.Guild) -> List[Account]:
async def get_global_accounts(user: discord.User) -> List[Account]:
"""
Gets all global account data.
"""Get all global account data.
May raise RuntimeError if the bank is currently guild specific.
:param discord.User user:
Parameters
----------
user : discord.User
A user to be used for getting accounts.
:return:
A generator of all global accounts.
:rtype:
generator
:raises RuntimeError:
If the bank is guild specific.
Returns
-------
`list` of `Account`
A list of all global accounts.
Raises
------
RuntimeError
If the bank is currently guild specific.
"""
if not is_global():
raise RuntimeError("The bank is not currently global.")
@ -311,18 +363,20 @@ async def get_global_accounts(user: discord.User) -> List[Account]:
async def get_account(member: Union[discord.Member, discord.User]) -> Account:
"""
Gets the appropriate account for the given user or member. A member is
required if the bank is currently guild specific.
"""Get the appropriate account for the given user or member.
:param member:
A member is required if the bank is currently guild specific.
Parameters
----------
member : `discord.User` or `discord.Member`
The user whose account to get.
:type member:
discord.User or discord.Member
:return:
Returns
-------
Account
The user's account.
:rtype:
:py:class:`Account`
"""
if await is_global():
acc_data = (await _conf.user(member)()).copy()
@ -344,36 +398,43 @@ async def get_account(member: Union[discord.Member, discord.User]) -> Account:
async def is_global() -> bool:
"""
Determines if the bank is currently global.
"""Determine if the bank is currently global.
:return:
Returns
-------
bool
:code:`True` if the bank is global, otherwise :code:`False`.
:rtype:
bool
"""
return await _conf.is_global()
async def set_global(global_: bool, user: Union[discord.User, discord.Member]) -> bool:
"""
Sets global status of the bank, requires the user parameter for technical reasons.
"""Set global status of the bank.
Requires the user parameter for technical reasons.
.. important::
All accounts are reset when you switch!
:param global_:
Parameters
----------
global_ : bool
:code:`True` will set bank to global mode.
:param user:
user : `discord.User` or `discord.Member`
Must be a Member object if changing TO global mode.
:type user:
discord.User or discord.Member
:return:
Returns
-------
bool
New bank mode, :code:`True` is global.
:rtype:
bool
:raises RuntimeError:
If bank is becoming global and :py:class:`discord.Member` was not provided.
Raises
------
RuntimeError
If bank is becoming global and a `discord.Member` was not provided.
"""
if (await is_global()) is global_:
return global_
@ -391,20 +452,24 @@ async def set_global(global_: bool, user: Union[discord.User, discord.Member]) -
async def get_bank_name(guild: discord.Guild=None) -> str:
"""
Gets the current bank name. If the bank is guild-specific the
guild parameter is required.
"""Get the current bank name.
May raise RuntimeError if guild is missing and required.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the bank name for (required if bank is
guild-specific).
:param discord.Guild guild:
The guild to get the bank name for (required if bank is guild-specific).
:return:
Returns
-------
str
The bank's name.
:rtype:
str
:raises RuntimeError:
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if await is_global():
return await _conf.bank_name()
@ -415,22 +480,26 @@ async def get_bank_name(guild: discord.Guild=None) -> str:
async def set_bank_name(name: str, guild: discord.Guild=None) -> str:
"""
Sets the bank name, if bank is guild specific the guild parameter is
required.
"""Set the bank name.
May throw RuntimeError if guild is required and missing.
Parameters
----------
name : str
The new name for the bank.
guild : `discord.Guild`, optional
The guild to set the bank name for (required if bank is
guild-specific).
:param str name:
Returns
-------
str
The new name for the bank.
:param discord.Guild guild:
The guild to set the bank name for (required if bank is guild-specific).
:return:
The new name for the bank.
:rtype:
str
:raises RuntimeError:
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if await is_global():
await _conf.bank_name.set(name)
@ -443,20 +512,24 @@ async def set_bank_name(name: str, guild: discord.Guild=None) -> str:
async def get_currency_name(guild: discord.Guild=None) -> str:
"""
Gets the currency name of the bank. The guild parameter is required if
the bank is guild-specific.
"""Get the currency name of the bank.
May raise RuntimeError if the guild is missing and required.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the currency name for (required if bank is
guild-specific).
:param discord.Guild guild:
The guild to get the currency name for (required if bank is guild-specific).
:return:
Returns
-------
str
The currency name.
:rtype:
str
:raises RuntimeError:
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if await is_global():
return await _conf.currency()
@ -467,22 +540,26 @@ async def get_currency_name(guild: discord.Guild=None) -> str:
async def set_currency_name(name: str, guild: discord.Guild=None) -> str:
"""
Sets the currency name for the bank, if bank is guild specific the
guild parameter is required.
"""Set the currency name for the bank.
May raise RuntimeError if guild is missing and required.
Parameters
----------
name : str
The new name for the currency.
guild : `discord.Guild`, optional
The guild to set the currency name for (required if bank is
guild-specific).
:param str name:
Returns
-------
str
The new name for the currency.
:param discord.Guild guild:
The guild to set the currency name for (required if bank is guild-specific).
:return:
The new name for the currency.
:rtype:
str
:raises RuntimeError:
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if await is_global():
await _conf.currency.set(name)
@ -495,20 +572,24 @@ async def set_currency_name(name: str, guild: discord.Guild=None) -> str:
async def get_default_balance(guild: discord.Guild=None) -> int:
"""
Gets the current default balance amount. If the bank is guild-specific
you must pass guild.
"""Get the current default balance amount.
May raise RuntimeError if guild is missing and required.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the default balance for (required if bank is
guild-specific).
:param discord.Guild guild:
The guild to get the default balance for (required if bank is guild-specific).
:return:
Returns
-------
int
The bank's default balance.
:rtype:
str
:raises RuntimeError:
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
"""
if await is_global():
return await _conf.default_balance()
@ -519,26 +600,28 @@ async def get_default_balance(guild: discord.Guild=None) -> int:
async def set_default_balance(amount: int, guild: discord.Guild=None) -> int:
"""
Sets the default balance amount. Guild is required if the bank is
guild-specific.
"""Set the default balance amount.
May raise RuntimeError if guild is missing and required.
May raise ValueError if amount is invalid.
:param int amount:
Parameters
----------
amount : int
The new default balance.
:param discord.Guild guild:
The guild to set the default balance for (required if bank is guild-specific).
:return:
guild : `discord.Guild`, optional
The guild to set the default balance for (required if bank is
guild-specific).
Returns
-------
int
The new default balance.
:rtype:
str
:raises RuntimeError:
Raises
------
RuntimeError
If the bank is guild-specific and guild was not provided.
:raises ValueError:
ValueError
If the amount is invalid.
"""
amount = int(amount)
if amount < 0:

View File

@ -14,6 +14,14 @@ from . import Config, i18n, RedContext
class RedBase(BotBase):
"""Mixin for the main bot class.
This exists because `Red` inherits from `discord.AutoShardedClient`, which
is something other bot classes (namely selfbots) may not want to have as
a parent class.
Selfbots should inherit from this mixin along with `discord.Client`.
"""
def __init__(self, cli_flags, bot_dir: Path=Path.cwd(), **kwargs):
self._shutdown_mode = ExitCodes.CRITICAL
self.db = Config.get_core_conf(force_registration=True)
@ -168,11 +176,18 @@ class Red(RedBase, discord.AutoShardedClient):
"""
You're welcome Caleb.
"""
async def shutdown(self, *, restart=False):
"""Gracefully quits Red with exit code 0
async def shutdown(self, *, restart: bool=False):
"""Gracefully quit Red.
The program will exit with code :code:`0` by default.
If restart is True, the exit code will be 26 instead
Upon receiving that exit code, the launcher restarts Red"""
Parameters
----------
restart : bool
If :code:`True`, the program will exit with code :code:`26`. If the
launcher sees this, it will attempt to restart the bot.
"""
if not restart:
self._shutdown_mode = ExitCodes.SHUTDOWN
else:

View File

@ -16,10 +16,12 @@ __all__ = ["CogManager"]
class CogManager:
"""
This module allows you to load cogs from multiple directories and even from outside the bot
directory. You may also set a directory for downloader to install new cogs to, the default
being the :code:`cogs/` folder in the root bot directory.
"""Directory manager for Red's cogs.
This module allows you to load cogs from multiple directories and even from
outside the bot directory. You may also set a directory for downloader to
install new cogs to, the default being the :code:`cogs/` folder in the root
bot directory.
"""
def __init__(self, paths: Tuple[str]=()):
self.conf = Config.get_conf(self, 2938473984732, True)
@ -33,8 +35,13 @@ class CogManager:
self._paths = list(paths)
async def paths(self) -> Tuple[Path, ...]:
"""
All currently valid path directories.
"""Get all currently valid path directories.
Returns
-------
`tuple` of `pathlib.Path`
All valid cog paths.
"""
conf_paths = await self.conf.paths()
other_paths = self._paths
@ -47,26 +54,40 @@ class CogManager:
return tuple(p.resolve() for p in paths if p.is_dir())
async def install_path(self) -> Path:
"""
The install path for 3rd party cogs.
"""Get the install path for 3rd party cogs.
Returns
-------
pathlib.Path
The path to the directory where 3rd party cogs are stored.
"""
p = Path(await self.conf.install_path())
return p.resolve()
async def set_install_path(self, path: Path) -> Path:
"""
Install path setter, will return the absolute path to
the given path.
"""Set the install path for 3rd party cogs.
.. note::
Note
----
The bot will not remember your old cog install path which means
that **all previously installed cogs** will no longer be found.
The bot will not remember your old cog install path which means
that ALL PREVIOUSLY INSTALLED COGS will now be unfindable.
:param pathlib.Path path:
Parameters
----------
path : pathlib.Path
The new directory for cog installs.
:raises ValueError:
Returns
-------
pathlib.Path
Absolute path to the new install directory.
Raises
------
ValueError
If :code:`path` is not an existing directory.
"""
if not path.is_dir():
raise ValueError("The install path must be an existing directory.")
@ -76,14 +97,16 @@ class CogManager:
@staticmethod
def _ensure_path_obj(path: Union[Path, str]) -> Path:
"""
Guarantees an object will be a path object.
"""Guarantee an object will be a path object.
Parameters
----------
path : `pathlib.Path` or `str`
Returns
-------
pathlib.Path
:param path:
:type path:
pathlib.Path or str
:rtype:
pathlib.Path
"""
try:
path.exists()
@ -92,17 +115,21 @@ class CogManager:
return path
async def add_path(self, path: Union[Path, str]):
"""
Adds a cog path to current list, will ignore duplicates. Does have
a side effect of removing all invalid paths from the saved path
list.
"""Add a cog path to current list.
:param path:
This will ignore duplicates. Does have a side effect of removing all
invalid paths from the saved path list.
Parameters
----------
path : `pathlib.Path` or `str`
Path to add.
:type path:
pathlib.Path or str
:raises ValueError:
Raises
------
ValueError
If :code:`path` does not resolve to an existing directory.
"""
path = self._ensure_path_obj(path)
@ -121,15 +148,18 @@ class CogManager:
await self.set_paths(all_paths)
async def remove_path(self, path: Union[Path, str]) -> Tuple[Path, ...]:
"""
Removes a path from the current paths list.
"""Remove a path from the current paths list.
:param path: Path to remove.
:type path:
pathlib.Path or str
:return:
Parameters
----------
path : `pathlib.Path` or `str`
Path to remove.
Returns
-------
`tuple` of `pathlib.Path`
Tuple of new valid paths.
:rtype: tuple
"""
path = self._ensure_path_obj(path)
all_paths = list(await self.paths())
@ -139,27 +169,35 @@ class CogManager:
return tuple(all_paths)
async def set_paths(self, paths_: List[Path]):
"""
Sets the current paths list.
"""Set the current paths list.
:param List[pathlib.Path] paths_:
Parameters
----------
paths_ : `list` of `pathlib.Path`
List of paths to set.
"""
str_paths = [str(p) for p in paths_]
await self.conf.paths.set(str_paths)
async def find_cog(self, name: str) -> ModuleSpec:
"""
Finds a cog in the list of available paths.
"""Find a cog in the list of available paths.
:param name:
Parameters
----------
name : str
Name of the cog to find.
:raises RuntimeError:
If there is no cog with the given name.
:return:
Returns
-------
importlib.machinery.ModuleSpec
A module spec to be used for specialized cog loading.
:rtype:
importlib.machinery.ModuleSpec
Raises
------
RuntimeError
If there is no cog with the given name.
"""
resolved_paths = [str(p.resolve()) for p in await self.paths()]
for finder, module_name, _ in pkgutil.iter_modules(resolved_paths):
@ -173,11 +211,10 @@ class CogManager:
@staticmethod
def invalidate_caches():
"""
"""Re-evaluate modules in the py cache.
This is an alias for an importlib internal and should be called
any time that a new module has been installed to a cog directory.
*I think.*
"""
invalidate_caches()

View File

@ -11,20 +11,18 @@ log = logging.getLogger("red.config")
class Value:
"""
A singular "value" of data.
"""A singular "value" of data.
.. py:attribute:: identifiers
Attributes
----------
identifiers : `tuple` of `str`
This attribute provides all the keys necessary to get a specific data
element from a json document.
default
The default value for the data element that `identifiers` points at.
spawner : `redbot.core.drivers.red_base.BaseDriver`
A reference to `Config.spawner`.
This attribute provides all the keys necessary to get a specific data element from a json document.
.. py:attribute:: default
The default value for the data element that :py:attr:`identifiers` points at.
.. py:attribute:: spawner
A reference to :py:attr:`.Config.spawner`.
"""
def __init__(self, identifiers: Tuple[str], default_value, spawner):
self._identifiers = identifiers
@ -45,12 +43,15 @@ class Value:
return ret
def __call__(self, default=None):
"""
Each :py:class:`Value` object is created by the :py:meth:`Group.__getattr__` method.
The "real" data of the :py:class:`Value` object is accessed by this method. It is a replacement for a
:python:`get()` method.
"""Get the literal value of this data element.
For example::
Each `Value` object is created by the `Group.__getattr__` method. The
"real" data of the `Value` object is accessed by this method. It is a
replacement for a :code:`get()` method.
Example
-------
::
foo = await conf.guild(some_guild).foo()
@ -64,26 +65,39 @@ class Value:
This is now, for all intents and purposes, a coroutine.
:param default:
This argument acts as an override for the registered default provided by :py:attr:`default`. This argument
is ignored if its value is :python:`None`.
:type default: Optional[object]
:return:
Parameters
----------
default : `object`, optional
This argument acts as an override for the registered default
provided by `default`. This argument is ignored if its
value is :code:`None`.
Returns
-------
types.coroutine
A coroutine object that must be awaited.
"""
return self._get(default)
async def set(self, value):
"""
Sets the value of the data element indicate by :py:attr:`identifiers`.
"""Set the value of the data elements pointed to by `identifiers`.
For example::
Example
-------
::
# Sets global value "foo" to False
await conf.foo.set(False)
# Sets guild specific value of "bar" to True
await conf.guild(some_guild).bar.set(True)
Parameters
----------
value
The new literal value of this attribute.
"""
driver = self.spawner.get_driver()
await driver.set(self.identifiers, value)
@ -91,16 +105,20 @@ class Value:
class Group(Value):
"""
A "group" of data, inherits from :py:class:`.Value` which means that all of the attributes and methods available
in :py:class:`.Value` are also available when working with a :py:class:`.Group` object.
Represents a group of data, composed of more `Group` or `Value` objects.
.. py:attribute:: defaults
Inherits from `Value` which means that all of the attributes and methods
available in `Value` are also available when working with a `Group` object.
A dictionary of registered default values for this :py:class:`Group`.
Attributes
----------
defaults : `dict`
All registered default values for this Group.
force_registration : `bool`
Same as `Config.force_registration`.
spawner : `redbot.core.drivers.red_base.BaseDriver`
A reference to `Config.spawner`.
.. py:attribute:: force_registration
See :py:attr:`.Config.force_registration`.
"""
def __init__(self, identifiers: Tuple[str],
defaults: dict,
@ -118,18 +136,28 @@ class Group(Value):
# noinspection PyTypeChecker
def __getattr__(self, item: str) -> Union["Group", Value]:
"""
Takes in the next accessible item.
"""Get an attribute of this group.
This special method is called whenever dot notation is used on this
object.
1. If it's found to be a group of data we return another :py:class:`Group` object.
2. If it's found to be a data value we return a :py:class:`.Value` object.
3. If it is not found and :py:attr:`force_registration` is :python:`True` then we raise
:py:exc:`AttributeError`.
4. Otherwise return a :py:class:`.Value` object.
Parameters
----------
item : str
The name of the attribute being accessed.
Returns
-------
`Group` or `Value`
A child value of this Group. This, of course, can be another
`Group`, due to Config's composite pattern.
Raises
------
AttributeError
If the attribute has not been registered and `force_registration`
is set to :code:`True`.
:param str item:
The name of the item a cog is attempting to access through normal Python attribute
access.
"""
is_group = self.is_group(item)
is_value = not is_group and self.is_value(item)
@ -170,21 +198,27 @@ class Group(Value):
return super_group
def is_group(self, item: str) -> bool:
"""
A helper method for :py:meth:`__getattr__`. Most developers will have no need to use this.
"""A helper method for `__getattr__`. Most developers will have no need
to use this.
Parameters
----------
item : str
See `__getattr__`.
:param str item:
See :py:meth:`__getattr__`.
"""
default = self._defaults.get(item)
return isinstance(default, dict)
def is_value(self, item: str) -> bool:
"""
A helper method for :py:meth:`__getattr__`. Most developers will have no need to use this.
"""A helper method for `__getattr__`. Most developers will have no need
to use this.
Parameters
----------
item : str
See `__getattr__`.
:param str item:
See :py:meth:`__getattr__`.
"""
try:
default = self._defaults[item]
@ -194,14 +228,18 @@ class Group(Value):
return not isinstance(default, dict)
def get_attr(self, item: str, default=None, resolve=True):
"""
This is available to use as an alternative to using normal Python attribute access. It is required if you find
a need for dynamic attribute access.
"""Manually get an attribute of this Group.
.. note::
This is available to use as an alternative to using normal Python
attribute access. It is required if you find a need for dynamic
attribute access.
Use of this method should be avoided wherever possible.
Note
----
Use of this method should be avoided wherever possible.
Example
-------
A possible use case::
@commands.command()
@ -211,16 +249,24 @@ class Group(Value):
# Where the value of item is the name of the data field in Config
await ctx.send(await self.conf.user(user).get_attr(item))
:param str item:
The name of the data field in :py:class:`.Config`.
:param default:
This is an optional override to the registered default for this item.
:param resolve:
If this is :code:`True` this function will return a coroutine that resolves to a "real" data value,
if :code:`False` this function will return an instance of :py:class:`Group` or :py:class:`Value`
depending on the type of the "real" data value.
:rtype:
Coroutine or Value
Parameters
----------
item : str
The name of the data field in `Config`.
default
This is an optional override to the registered default for this
item.
resolve : bool
If this is :code:`True` this function will return a coroutine that
resolves to a "real" data value when awaited. If :code:`False`,
this method acts the same as `__getattr__`.
Returns
-------
`types.coroutine` or `Value` or `Group`
The attribute which was requested, its type depending on the value
of :code:`resolve`.
"""
value = getattr(self, item)
if resolve:
@ -229,36 +275,42 @@ class Group(Value):
return value
async def all(self) -> dict:
"""
This method allows you to get "all" of a particular group of data. It will return the dictionary of all data
for a particular Guild/Channel/Role/User/Member etc.
"""Get a dictionary representation of this group's data.
.. note::
Note
----
The return value of this method will include registered defaults for
values which have not yet been set.
Any values that have not been set from the registered defaults will have their default values
added to the dictionary that this method returns.
Returns
-------
dict
All of this Group's attributes, resolved as raw data values.
:rtype: dict
"""
defaults = self.defaults
defaults.update(await self())
return defaults
async def all_from_kind(self) -> dict:
"""
This method allows you to get all data from all entries in a given Kind. It will return a dictionary of Kind
ID's -> data.
.. note::
Any values that have not been set from the registered defaults will have their default values
added to the dictionary that this method returns.
"""Get all data from this group and its siblings.
.. important::
This method is overridden in `MemberGroup.all_from_kind` and
functions slightly differently.
This method is overridden in :py:meth:`.MemberGroup.all_from_kind` and functions slightly differently.
Note
----
The return value of this method will include registered defaults
for groups which have not had their values set.
Returns
-------
dict
A dict of :code:`ID -> data`, with the data being a dict
of the group's raw values.
:rtype: dict
"""
# noinspection PyTypeChecker
all_from_kind = await self._super_group()
@ -278,35 +330,47 @@ class Group(Value):
await super().set(value)
async def set_attr(self, item: str, value):
"""
Please see :py:meth:`get_attr` for more information.
"""Set an attribute by its name.
Similar to `get_attr` in the way it can be used to dynamically set
attributes by name.
.. note::
Note
----
Use of this method should be avoided wherever possible.
Parameters
----------
item : str
The name of the attribute being set.
value
The raw data value to set the attribute as.
Use of this method should be avoided wherever possible.
"""
value_obj = getattr(self, item)
await value_obj.set(value)
async def clear(self):
"""
Wipes all data from the given Guild/Channel/Role/Member/User. If used on a global group, it will wipe all global
data.
"""Wipe all data from this group.
If used on a global group, it will wipe all global data, but not
local data.
"""
await self.set({})
async def clear_all(self):
"""
Wipes all data from all Guilds/Channels/Roles/Members/Users. If used on a global group, this method wipes all
data from everything.
"""Wipe all data from this group and its siblings.
If used on a global group, this method wipes all data from all groups.
"""
await self._super_group.set({})
class MemberGroup(Group):
"""
A specific group class for use with member data only. Inherits from :py:class:`.Group`. In this group data is
stored as :code:`GUILD_ID -> MEMBER_ID -> data`.
"""A specific group class for use with member data only.
Inherits from `Group`. In this group data is stored as
:code:`GUILD_ID -> MEMBER_ID -> data`.
"""
@property
def _super_group(self) -> Group:
@ -329,29 +393,35 @@ class MemberGroup(Group):
return group_obj
async def all_guilds(self) -> dict:
"""
Returns a dict of :code:`GUILD_ID -> MEMBER_ID -> data`.
"""Get a dict of :code:`GUILD_ID -> MEMBER_ID -> data`.
.. note::
Note
----
The return value of this method will include registered defaults
for groups which have not had their values set.
Any values that have not been set from the registered defaults will have their default values
added to the dictionary that this method returns.
Returns
-------
dict
A dict of data from all members from all guilds.
:rtype: dict
"""
# noinspection PyTypeChecker
return await super().all_from_kind()
async def all_from_kind(self) -> dict:
"""
Returns a dict of all members from the same guild as the given one.
"""Get a dict of all members from the same guild as the given one.
.. note::
Note
----
The return value of this method will include registered defaults
for groups which have not had their values set.
Any values that have not been set from the registered defaults will have their default values
added to the dictionary that this method returns.
Returns
-------
dict
A dict of :code:`MEMBER_ID -> data`.
:rtype: dict
"""
guild_member = await super().all_from_kind()
return guild_member.get(self.identifiers[-2], {})
@ -359,8 +429,10 @@ class MemberGroup(Group):
class Config:
"""
You should always use :func:`get_conf` or :func:`get_core_conf` to initialize a Config object.
"""Configuration manager for cogs and Red.
You should always use `get_conf` or to instantiate a Config object. Use
`get_core_conf` for Config used in the core package.
.. important::
Most config data should be accessed through its respective group method (e.g. :py:meth:`guild`)
@ -369,27 +441,26 @@ class Config:
await conf.foo()
.. py:attribute:: cog_name
Attributes
----------
cog_name : `str`
The name of the cog that has requested a `Config` object.
unique_identifier : `int`
Unique identifier provided to differentiate cog data when name
conflicts occur.
spawner
A callable object that returns some driver that implements
`redbot.core.drivers.red_base.BaseDriver`.
force_registration : `bool`
Determines if Config should throw an error if a cog attempts to access
an attribute which has not been previously registered.
The name of the cog that has requested a :py:class:`.Config` object.
Note
----
**You should use this.** By enabling force registration you give Config
the ability to alert you instantly if you've made a typo when
attempting to access data.
.. py:attribute:: unique_identifier
Unique identifier provided to differentiate cog data when name conflicts occur.
.. py:attribute:: spawner
A callable object that returns some driver that implements :py:class:`.drivers.red_base.BaseDriver`.
.. py:attribute:: force_registration
A boolean that determines if :py:class:`.Config` should throw an error if a cog attempts to access an attribute
which has not been previously registered.
.. note::
**You should use this.** By enabling force registration you give :py:class:`.Config` the ability to alert
you instantly if you've made a typo when attempting to access data.
"""
GLOBAL = "GLOBAL"
GUILD = "GUILD"
@ -416,19 +487,26 @@ class Config:
@classmethod
def get_conf(cls, cog_instance, identifier: int,
force_registration=False):
"""
Returns a Config instance based on a simplified set of initial
variables.
"""Get a Config instance for your cog.
Parameters
----------
cog_instance
This is an instance of your cog after it has been instantiated. If
you're calling this method from within your cog's :code:`__init__`,
this is just :code:`self`.
identifier : int
A (hard-coded) random integer, used to keep your data distinct from
any other cog with the same name.
force_registration : `bool`, optional
Should config require registration of data keys before allowing you
to get/set values? See `force_registration`.
Returns
-------
Config
A new Config object.
:param cog_instance:
:param identifier:
Any random integer, used to keep your data
distinct from any other cog with the same name.
:param force_registration:
Should config require registration
of data keys before allowing you to get/set values?
:return:
A new config object.
"""
cog_path_override = cog_data_path(cog_instance)
cog_name = cog_path_override.stem
@ -441,15 +519,14 @@ class Config:
@classmethod
def get_core_conf(cls, force_registration: bool=False):
"""
All core modules that require a config instance should use this classmethod instead of
:py:meth:`get_conf`
"""All core modules that require a config instance should use this
classmethod instead of `get_conf`.
identifier : int
See `get_conf`.
force_registration : `bool`, optional
See `force_registration`.
:param int identifier:
See :py:meth:`get_conf`
:param force_registration:
See :py:attr:`force_registration`
:type force_registration: Optional[bool]
"""
driver_spawn = JSONDriver("Core", data_path_override=core_data_path())
return cls(cog_name="Core", driver_spawn=driver_spawn,
@ -457,11 +534,23 @@ class Config:
force_registration=force_registration)
def __getattr__(self, item: str) -> Union[Group, Value]:
"""
This is used to generate Value or Group objects for global
values.
:param item:
:return:
"""Same as `group.__getattr__` except for global data.
Parameters
----------
item : str
The attribute you want to get.
Returns
-------
`Group` or `Value`
The value for the attribute you want to retrieve
Raises
------
AttributeError
If there is no global attribute by the given name and
`force_registration` is set to :code:`True`.
"""
global_group = self._get_base_group(self.GLOBAL)
return getattr(global_group, item)
@ -526,9 +615,11 @@ class Config:
self._update_defaults(to_add, self._defaults[key])
def register_global(self, **kwargs):
"""
Registers default values for attributes you wish to store in :py:class:`.Config` at a global level.
"""Register default values for attributes you wish to store in `Config`
at a global level.
Examples
--------
You can register a single value or multiple values::
conf.register_global(
@ -562,37 +653,47 @@ class Config:
foo__bar=True,
foo__baz=False
)
"""
self._register_default(self.GLOBAL, **kwargs)
def register_guild(self, **kwargs):
"""
Registers default values on a per-guild level. See :py:meth:`register_global` for more details.
"""Register default values on a per-guild level.
See :py:meth:`register_global` for more details.
"""
self._register_default(self.GUILD, **kwargs)
def register_channel(self, **kwargs):
"""
Registers default values on a per-channel level. See :py:meth:`register_global` for more details.
"""Register default values on a per-channel level.
See `register_global` for more details.
"""
# We may need to add a voice channel category later
self._register_default(self.CHANNEL, **kwargs)
def register_role(self, **kwargs):
"""
Registers default values on a per-role level. See :py:meth:`register_global` for more details.
"""Registers default values on a per-role level.
See `register_global` for more details.
"""
self._register_default(self.ROLE, **kwargs)
def register_user(self, **kwargs):
"""
Registers default values on a per-user level (i.e. server-independent). See :py:meth:`register_global` for more details.
"""Registers default values on a per-user level.
This means that each user's data is guild-independent.
See `register_global` for more details.
"""
self._register_default(self.USER, **kwargs)
def register_member(self, **kwargs):
"""
Registers default values on a per-member level (i.e. server-dependent). See :py:meth:`register_global` for more details.
"""Registers default values on a per-member level.
This means that each user's data is guild-dependent.
See `register_global` for more details.
"""
self._register_default(self.MEMBER, **kwargs)
@ -607,43 +708,59 @@ class Config:
)
def guild(self, guild: discord.Guild) -> Group:
"""
Returns a :py:class:`.Group` for the given guild.
"""Returns a `Group` for the given guild.
Parameters
----------
guild : discord.Guild
A guild object.
:param discord.Guild guild: A discord.py guild object.
"""
return self._get_base_group(self.GUILD, guild.id)
def channel(self, channel: discord.TextChannel) -> Group:
"""
Returns a :py:class:`.Group` for the given channel. This does not currently support differences between
text and voice channels.
"""Returns a `Group` for the given channel.
This does not discriminate between text and voice channels.
Parameters
----------
channel : `discord.abc.GuildChannel`
A channel object.
:param discord.TextChannel channel: A discord.py text channel object.
"""
return self._get_base_group(self.CHANNEL, channel.id)
def role(self, role: discord.Role) -> Group:
"""
Returns a :py:class:`.Group` for the given role.
"""Returns a `Group` for the given role.
Parameters
----------
role : discord.Role
A role object.
:param discord.Role role: A discord.py role object.
"""
return self._get_base_group(self.ROLE, role.id)
def user(self, user: discord.User) -> Group:
"""
Returns a :py:class:`.Group` for the given user.
"""Returns a `Group` for the given user.
Parameters
----------
user : discord.User
A user object.
:param discord.User user: A discord.py user object.
"""
return self._get_base_group(self.USER, user.id)
def member(self, member: discord.Member) -> MemberGroup:
"""
Returns a :py:class:`.MemberGroup` for the given member.
"""Returns a `Group` for the given member.
Parameters
----------
member : discord.Member
A member object.
:param discord.Member member: A discord.py member object.
"""
return self._get_base_group(self.MEMBER, member.guild.id, member.id,
group_class=MemberGroup)

View File

@ -1,6 +1,7 @@
"""The purpose of this module is to allow for Red to
further customise the command invocation context provided
by discord.py.
"""Module for Red's Context class
The purpose of this module is to allow for Red to further customise the command
invocation context provided by discord.py.
"""
import discord
@ -12,27 +13,37 @@ TICK = "\N{WHITE HEAVY CHECK MARK}"
class RedContext(commands.Context):
"""
Command invocation context for Red.
"""Command invocation context for Red.
All context passed into commands will be of this type.
This class inherits from
:py:class:`commands.Context <discord.ext.commands.Context>`.
This class inherits from `commands.Context <discord.ext.commands.Context>`.
"""
async def send_help(self):
"""Send the command help message."""
"""Send the command help message.
Returns
-------
`list` of `discord.Message`
A list of help messages which were sent to the user.
"""
command = self.invoked_subcommand or self.command
pages = await self.bot.formatter.format_help_for(self, command)
ret = []
for page in pages:
await self.send(page)
ret.append(await self.send(page))
return ret
async def tick(self):
"""Add a tick reaction to the command message.
:return: ``True`` if adding the reaction succeeded.
:rtype: bool
Returns
-------
bool
:code:`True` if adding the reaction succeeded.
"""
try:
await self.message.add_reaction(TICK)

View File

@ -1,5 +1,6 @@
from typing import Tuple
__all__ = ["BaseDriver"]
class BaseDriver:
def get_driver(self):

View File

@ -5,6 +5,8 @@ from ..json_io import JsonIO
from .red_base import BaseDriver
__all__ = ["JSON"]
class JSON(BaseDriver):
def __init__(self, cog_name, *, data_path_override: Path=None,

View File

@ -2,6 +2,10 @@ import pymongo as m
from .red_base import BaseDriver
__all__ = ["Mongo", "RedMongoException", "MultipleMatches",
"MissingCollection"]
class RedMongoException(Exception):
"""Base Red Mongo Exception class"""
pass