From c80684a129db178f9b4ae7df5955ecfd8b5b1502 Mon Sep 17 00:00:00 2001 From: Tobotimus Date: Wed, 18 Oct 2017 13:01:59 +1100 Subject: [PATCH] [V3] NumPy Docstrings (#1032) * ALL THE DOCSTRINGS * Remove imports in drivers package * Fixed build warnings --- docs/conf.py | 4 + docs/framework_config.rst | 3 +- docs/install_windows.rst | 4 + redbot/cogs/downloader/downloader.py | 96 +++-- redbot/cogs/downloader/installable.py | 47 ++- redbot/cogs/downloader/repo_manager.py | 224 ++++++++---- redbot/core/bank.py | 473 ++++++++++++++---------- redbot/core/bot.py | 23 +- redbot/core/cog_manager.py | 143 +++++--- redbot/core/config.py | 479 +++++++++++++++---------- redbot/core/context.py | 33 +- redbot/core/drivers/red_base.py | 1 + redbot/core/drivers/red_json.py | 2 + redbot/core/drivers/red_mongo.py | 4 + 14 files changed, 976 insertions(+), 560 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 81db3e54b..5198f1ade 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 ---------------------------------------------- diff --git a/docs/framework_config.rst b/docs/framework_config.rst index 13b031774..d7429bc55 100644 --- a/docs/framework_config.rst +++ b/docs/framework_config.rst @@ -192,5 +192,4 @@ Driver Reference .. automodule:: redbot.core.drivers -.. autoclass:: red_base.BaseDriver - :members: +.. autoclass:: redbot.core.drivers.red_base.BaseDriver diff --git a/docs/install_windows.rst b/docs/install_windows.rst index dc3cb2699..9ec0b49e2 100644 --- a/docs/install_windows.rst +++ b/docs/install_windows.rst @@ -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 ` to run Red. It will walk through the initial setup, asking for your token and a prefix \ No newline at end of file diff --git a/redbot/cogs/downloader/downloader.py b/redbot/cogs/downloader/downloader.py index fc5a1e486..c95b5c9d1 100644 --- a/redbot/cogs/downloader/downloader.py +++ b/redbot/cogs/downloader/downloader.py @@ -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] diff --git a/redbot/cogs/downloader/installable.py b/redbot/cogs/downloader/installable.py index c7234e3a1..3284c4e40 100644 --- a/redbot/cogs/downloader/installable.py +++ b/redbot/cogs/downloader/installable.py @@ -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: diff --git a/redbot/cogs/downloader/repo_manager.py b/redbot/cogs/downloader/repo_manager.py index 2c8959f15..593d6ec3a 100644 --- a/redbot/cogs/downloader/repo_manager.py +++ b/redbot/cogs/downloader/repo_manager.py @@ -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(): diff --git a/redbot/core/bank.py b/redbot/core/bank.py index 5efcaa874..d4eb963ce 100644 --- a/redbot/core/bank.py +++ b/redbot/core/bank.py @@ -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: diff --git a/redbot/core/bot.py b/redbot/core/bot.py index f9173e519..99e121dab 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -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: diff --git a/redbot/core/cog_manager.py b/redbot/core/cog_manager.py index f6d9bee5d..12cccc10b 100644 --- a/redbot/core/cog_manager.py +++ b/redbot/core/cog_manager.py @@ -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() diff --git a/redbot/core/config.py b/redbot/core/config.py index 72897b206..95d597d5d 100644 --- a/redbot/core/config.py +++ b/redbot/core/config.py @@ -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) diff --git a/redbot/core/context.py b/redbot/core/context.py index af7c96a64..482d29588 100644 --- a/redbot/core/context.py +++ b/redbot/core/context.py @@ -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 `. + This class inherits from `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) diff --git a/redbot/core/drivers/red_base.py b/redbot/core/drivers/red_base.py index 708063003..50ec86132 100644 --- a/redbot/core/drivers/red_base.py +++ b/redbot/core/drivers/red_base.py @@ -1,5 +1,6 @@ from typing import Tuple +__all__ = ["BaseDriver"] class BaseDriver: def get_driver(self): diff --git a/redbot/core/drivers/red_json.py b/redbot/core/drivers/red_json.py index c3dc7bec7..fbe4186ca 100644 --- a/redbot/core/drivers/red_json.py +++ b/redbot/core/drivers/red_json.py @@ -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, diff --git a/redbot/core/drivers/red_mongo.py b/redbot/core/drivers/red_mongo.py index 4bf83ca2c..38f4816cf 100644 --- a/redbot/core/drivers/red_mongo.py +++ b/redbot/core/drivers/red_mongo.py @@ -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