[Docs] Copy over config docs from red-api-docs (#899)

* Update config.Config docs

* Set group docstrings

* Update Value docs
This commit is contained in:
Will 2017-08-11 01:39:43 -04:00 committed by GitHub
parent 7e05903c61
commit cf8e11238c
2 changed files with 293 additions and 49 deletions

View File

@ -13,6 +13,21 @@ log = logging.getLogger("red.config")
class Value:
"""
A singular "value" of data.
.. py:attribute:: identifiers
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
self.default = default_value
@ -24,6 +39,26 @@ class Value:
return tuple(str(i) for i in self._identifiers)
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.
For example::
foo = conf.guild(some_guild).foo()
# Is equivalent to this
group_obj = conf.guild(some_guild)
value_obj = conf.foo
foo = value_obj()
: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]
"""
driver = self.spawner.get_driver()
try:
ret = driver.get(self.identifiers)
@ -32,11 +67,34 @@ class Value:
return ret
async def set(self, value):
"""
Sets the value of the data element indicate by :py:attr:`identifiers`.
For 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)
"""
driver = self.spawner.get_driver()
await driver.set(self.identifiers, 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.
.. py:attribute:: defaults
A dictionary of registered default values for this :py:class:`Group`.
.. py:attribute:: force_registration
See :py:attr:`.Config.force_registration`.
"""
def __init__(self, identifiers: Tuple[str],
defaults: dict,
spawner,
@ -50,13 +108,17 @@ class Group(Value):
# noinspection PyTypeChecker
def __getattr__(self, item: str) -> Union["Group", Value]:
"""
Takes in the next accessible item. If it's found to be a Group
we return another Group object. If it's found to be a Value
we return a Value object. If it is not found and
force_registration is True then we raise AttributeException,
otherwise return a Value object.
:param item:
:return:
Takes in the next accessible item.
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.
: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)
@ -98,18 +160,20 @@ class Group(Value):
def is_group(self, item: str) -> bool:
"""
Determines if an attribute access is pointing at a registered group.
:param item:
:return:
A helper method for :py:meth:`__getattr__`. Most developers will have no need to use this.
:param str item:
See :py:meth:`__getattr__`.
"""
default = self.defaults.get(item)
return isinstance(default, dict)
def is_value(self, item: str) -> bool:
"""
Determines if an attribute access is pointing at a registered value.
:param item:
:return:
A helper method for :py:meth:`__getattr__`. Most developers will have no need to use this.
:param str item:
See :py:meth:`__getattr__`.
"""
try:
default = self.defaults[item]
@ -120,12 +184,30 @@ class Group(Value):
def get_attr(self, item: str, default=None, resolve=True):
"""
You should avoid this function whenever possible.
:param item:
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.
.. note::
Use of this method should be avoided wherever possible.
A possible use case::
@commands.command()
async def some_command(self, ctx, item: str):
user = ctx.author
# Where the value of item is the name of the data field in Config
await ctx.send(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 True, actual data will be returned, if false a Group/Value will be returned.
:return:
If this is :code:`True` this function will return 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.
"""
value = getattr(self, item)
if resolve:
@ -135,17 +217,19 @@ class Group(Value):
def all(self) -> dict:
"""
Gets all data from current User/Member/Guild etc.
:return:
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.
:rtype: dict
"""
return self()
def all_from_kind(self) -> dict:
"""
Gets all entries of the given kind. If this kind is member
then this method returns all members from the same
server.
:return:
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.
:rtype: dict
"""
# noinspection PyTypeChecker
return self._super_group()
@ -159,31 +243,35 @@ class Group(Value):
async def set_attr(self, item: str, value):
"""
You should avoid this function whenever possible.
:param item:
:param value:
:return:
Please see :py:meth:`get_attr` for more information.
.. note::
Use of this method should be avoided wherever possible.
"""
value_obj = getattr(self, item)
await value_obj.set(value)
async def clear(self):
"""
Wipes out data for the given entry in this category
e.g. Guild/Role/User
:return:
Wipes all data from the given Guild/Channel/Role/Member/User. If used on a global group, it will wipe all global
data.
"""
await self.set({})
async def clear_all(self):
"""
Removes all data from all entries.
:return:
Wipes all data from all Guilds/Channels/Roles/Members/Users. If used on a global group, this method wipes all
data from everything.
"""
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`.
"""
@property
def _super_group(self) -> Group:
new_identifiers = self.identifiers[:2]
@ -206,23 +294,51 @@ class MemberGroup(Group):
def all_guilds(self) -> dict:
"""
Gets a dict of all guilds and members.
Returns a dict of :code:`GUILD_ID -> MEMBER_ID -> data`.
REMEMBER: ID's are stored in these dicts as STRINGS.
:return:
:rtype: dict
"""
# noinspection PyTypeChecker
return self._super_group()
def all(self) -> dict:
"""
Returns the dict of all members in the same guild.
:return:
"""
# noinspection PyTypeChecker
return self._guild_group()
class Config:
"""
You should always use :func:`get_conf` or :func:`get_core_conf` to initialize a Config object.
.. important::
Most config data should be accessed through its respective group method (e.g. :py:meth:`guild`)
however the process for accessing global data is a bit different. There is no :python:`global` method
because global data is accessed by normal attribute access::
conf.foo()
.. py:attribute:: cog_name
The name of the cog that has requested a :py:class:`.Config` object.
.. 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"
CHANNEL = "TEXTCHANNEL"
@ -247,12 +363,16 @@ class Config:
"""
Returns a Config instance based on a simplified set of initial
variables.
:param cog_instance:
:param identifier: Any random integer, used to keep your data
: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
:param force_registration:
Should config require registration
of data keys before allowing you to get/set values?
:return:
A new config object.
"""
cog_name = cog_instance.__class__.__name__
uuid = str(hash(identifier))
@ -264,6 +384,16 @@ 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`
:param int identifier:
See :py:meth:`get_conf`
:param force_registration:
See :py:attr:`force_registration`
:type force_registration: Optional[bool]
"""
core_data_path = Path.cwd() / 'core' / '.data'
driver_spawn = JSONDriver("Core", data_path_override=core_data_path)
return cls(cog_name="Core", driver_spawn=driver_spawn,
@ -340,22 +470,74 @@ 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.
You can register a single value or multiple values::
conf.register_global(
foo=True
)
conf.register_global(
bar=False,
baz=None
)
You can also now register nested values::
defaults = {
"foo": {
"bar": True,
"baz": False
}
}
# Will register `foo.bar` == True and `foo.baz` == False
conf.register_global(
**defaults
)
You can do the same thing without a :python:`defaults` dict by using double underscore as a variable
name separator::
# This is equivalent to the previous example
conf.register_global(
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.
"""
self._register_default(self.GUILD, **kwargs)
def register_channel(self, **kwargs):
"""
Registers default values on a per-guild level. See :py:meth:`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-guild level. See :py:meth:`register_global` for more details.
"""
self._register_default(self.ROLE, **kwargs)
def register_user(self, **kwargs):
"""
Registers default values on a per-guild level. See :py:meth:`register_global` for more details.
"""
self._register_default(self.USER, **kwargs)
def register_member(self, **kwargs):
"""
Registers default values on a per-guild level. See :py:meth:`register_global` for more details.
"""
self._register_default(self.MEMBER, **kwargs)
def _get_base_group(self, key: str, *identifiers: str,
@ -369,18 +551,44 @@ class Config:
)
def guild(self, guild: discord.Guild) -> Group:
"""
Returns a :py:class:`.Group` for the given guild.
: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.
: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.
: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.
: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.
: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,17 +1,45 @@
.. config shite
.. role:: python(code)
:language: python
======
Config
======
===========
Config was introduced in V3 as a way to make data storage easier and safer for all developers regardless of skill level.
It will take some getting used to as the syntax is entirely different from what Red has used before, but we believe
Config will be extremely beneficial to both cog developers and end users in the long run.
***********
Basic Usage
===========
***********
Stuff goes here
.. code-block:: python
=============
from core import Config
class MyCog:
def __init__(self):
self.config = Config.get_conf(self, identifier=1234567890)
self.config.register_global(
foo=True
)
@commands.command()
async def return_some_data(self, ctx):
await ctx.send(config.foo())
*************
API Reference
=============
*************
.. important::
Before we begin with the nitty gritty API Reference, you should know that there are tons of working code examples
inside the bot itself! Simply take a peek inside of the :code:`tests/core/test_config.py` file for examples of using
Config in all kinds of ways.
.. automodule:: core.config
@ -20,13 +48,21 @@ API Reference
.. autoclass:: Group
:members:
:special-members:
.. autoclass:: MemberGroup
:members:
.. autoclass:: Value
:members:
:special-members: __call__
================
****************
Driver Reference
================
****************
.. automodule:: core.drivers
.. autoclass:: red_base.BaseDriver
:members: