[V3 Config] Redesign "all_from_XXX" and "clear_all" methods (#1033)

* Added alternative to all_from_kind

* Returned dicts include default values

Also added docstrings

Also removed all_globals since it's kind of redundant and it wasn't working out for me

* Refactored clear_all

* Tests

* Tests again..

* Make all new methods coroutines
This commit is contained in:
Tobotimus 2017-10-20 14:22:58 +11:00 committed by Will
parent 13fef45e06
commit 815678584f
4 changed files with 257 additions and 112 deletions

View File

@ -63,7 +63,7 @@ class Bank:
If the bank is global, it will become per-guild If the bank is global, it will become per-guild
If the bank is per-guild, it will become global""" If the bank is per-guild, it will become global"""
cur_setting = await bank.is_global() cur_setting = await bank.is_global()
await bank.set_global(not cur_setting, ctx.author) await bank.set_global(not cur_setting)
word = _("per-guild") if cur_setting else _("global") word = _("per-guild") if cur_setting else _("global")

View File

@ -322,7 +322,7 @@ async def get_guild_accounts(guild: discord.Guild) -> List[Account]:
raise RuntimeError("The bank is currently global.") raise RuntimeError("The bank is currently global.")
ret = [] ret = []
accs = await _conf.member(guild.owner).all_from_kind() accs = await _conf.all_members(guild)
for user_id, acc in accs.items(): for user_id, acc in accs.items():
acc_data = acc.copy() # There ya go kowlin acc_data = acc.copy() # There ya go kowlin
acc_data['created_at'] = _decode_time(acc_data['created_at']) acc_data['created_at'] = _decode_time(acc_data['created_at'])
@ -353,7 +353,7 @@ async def get_global_accounts(user: discord.User) -> List[Account]:
raise RuntimeError("The bank is not currently global.") raise RuntimeError("The bank is not currently global.")
ret = [] ret = []
accs = await _conf.user(user).all_from_kind() # this is a dict of user -> acc accs = await _conf.all_users() # this is a dict of user -> acc
for user_id, acc in accs.items(): for user_id, acc in accs.items():
acc_data = acc.copy() acc_data = acc.copy()
acc_data['created_at'] = _decode_time(acc_data['created_at']) acc_data['created_at'] = _decode_time(acc_data['created_at'])
@ -412,8 +412,6 @@ async def is_global() -> bool:
async def set_global(global_: bool, user: Union[discord.User, discord.Member]) -> bool: async def set_global(global_: bool, user: Union[discord.User, discord.Member]) -> bool:
"""Set global status of the bank. """Set global status of the bank.
Requires the user parameter for technical reasons.
.. important:: .. important::
All accounts are reset when you switch! All accounts are reset when you switch!
@ -422,8 +420,6 @@ async def set_global(global_: bool, user: Union[discord.User, discord.Member]) -
---------- ----------
global_ : bool global_ : bool
:code:`True` will set bank to global mode. :code:`True` will set bank to global mode.
user : `discord.User` or `discord.Member`
Must be a Member object if changing TO global mode.
Returns Returns
------- -------
@ -440,12 +436,9 @@ async def set_global(global_: bool, user: Union[discord.User, discord.Member]) -
return global_ return global_
if is_global(): if is_global():
await _conf.user(user).clear_all() await _conf.clear_all_users()
elif isinstance(user, discord.Member):
await _conf.member(user).clear_all()
else: else:
raise RuntimeError("You must provide a member if you're changing to global" await _conf.clear_all_members()
" bank mode.")
await _conf.is_global.set(global_) await _conf.is_global.set(global_)
return global_ return global_

View File

@ -292,36 +292,6 @@ class Group(Value):
defaults.update(await self()) defaults.update(await self())
return defaults return defaults
async def all_from_kind(self) -> dict:
"""Get all data from this group and its siblings.
.. important::
This method is overridden in `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.
"""
# noinspection PyTypeChecker
all_from_kind = await self._super_group()
for k, v in all_from_kind.items():
defaults = self.defaults
defaults.update(v)
all_from_kind[k] = defaults
return all_from_kind
async def set(self, value): async def set(self, value):
if not isinstance(value, dict): if not isinstance(value, dict):
raise ValueError( raise ValueError(
@ -358,13 +328,6 @@ class Group(Value):
""" """
await self.set({}) await self.set({})
async def clear_all(self):
"""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): class MemberGroup(Group):
"""A specific group class for use with member data only. """A specific group class for use with member data only.
@ -392,41 +355,6 @@ class MemberGroup(Group):
) )
return group_obj return group_obj
async def all_guilds(self) -> dict:
"""Get a dict of :code:`GUILD_ID -> MEMBER_ID -> data`.
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 data from all members from all guilds.
"""
# noinspection PyTypeChecker
return await super().all_from_kind()
async def all_from_kind(self) -> dict:
"""Get a dict of all members from the same guild as the given one.
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:`MEMBER_ID -> data`.
"""
guild_member = await super().all_from_kind()
return guild_member.get(self.identifiers[-2], {})
class Config: class Config:
"""Configuration manager for cogs and Red. """Configuration manager for cogs and Red.
@ -519,11 +447,13 @@ class Config:
@classmethod @classmethod
def get_core_conf(cls, force_registration: bool=False): def get_core_conf(cls, force_registration: bool=False):
"""All core modules that require a config instance should use this """Get a Config instance for a core module.
All core modules that require a config instance should use this
classmethod instead of `get_conf`. classmethod instead of `get_conf`.
identifier : int Parameters
See `get_conf`. ----------
force_registration : `bool`, optional force_registration : `bool`, optional
See `force_registration`. See `force_registration`.
@ -765,3 +695,226 @@ class Config:
return self._get_base_group(self.MEMBER, member.guild.id, member.id, return self._get_base_group(self.MEMBER, member.guild.id, member.id,
group_class=MemberGroup) group_class=MemberGroup)
async def _all_from_scope(self, scope: str):
"""Get a dict of all values from a particular scope of data.
:code:`scope` must be one of the constants attributed to
this class, i.e. :code:`GUILD`, :code:`MEMBER` et cetera.
IDs as keys in the returned dict are casted to `int` for convenience.
Default values are also mixed into the data if they have not yet been
overwritten.
"""
group = self._get_base_group(scope)
dict_ = await group()
ret = {}
for k, v in dict_.items():
data = group.defaults
data.update(v)
ret[int(k)] = data
return ret
async def all_guilds(self) -> dict:
"""Get all guild data as a dict.
Note
----
The return value of this method will include registered defaults for
values which have not yet been set.
Returns
-------
dict
A dictionary in the form {`int`: `dict`} mapping
:code:`GUILD_ID -> data`.
"""
return await self._all_from_scope(self.GUILD)
async def all_channels(self) -> dict:
"""Get all channel data as a dict.
Note
----
The return value of this method will include registered defaults for
values which have not yet been set.
Returns
-------
dict
A dictionary in the form {`int`: `dict`} mapping
:code:`CHANNEL_ID -> data`.
"""
return await self._all_from_scope(self.CHANNEL)
async def all_roles(self) -> dict:
"""Get all role data as a dict.
Note
----
The return value of this method will include registered defaults for
values which have not yet been set.
Returns
-------
dict
A dictionary in the form {`int`: `dict`} mapping
:code:`ROLE_ID -> data`.
"""
return await self._all_from_scope(self.ROLE)
async def all_users(self) -> dict:
"""Get all user data as a dict.
Note
----
The return value of this method will include registered defaults for
values which have not yet been set.
Returns
-------
dict
A dictionary in the form {`int`: `dict`} mapping
:code:`USER_ID -> data`.
"""
return await self._all_from_scope(self.USER)
def _all_members_from_guild(self, group: Group, guild_data: dict) -> dict:
ret = {}
for member_id, member_data in guild_data.items():
new_member_data = group.defaults
new_member_data.update(member_data)
ret[int(member_id)] = new_member_data
return ret
async def all_members(self, guild: discord.Guild=None) -> dict:
"""Get data for all members.
If :code:`guild` is specified, only the data for the members of that
guild will be returned. As such, the dict will map
:code:`MEMBER_ID -> data`. Otherwise, the dict maps
:code:`GUILD_ID -> MEMBER_ID -> data`.
Note
----
The return value of this method will include registered defaults for
values which have not yet been set.
Parameters
----------
guild : `discord.Guild`, optional
The guild to get the member data from. Can be omitted if data
from every member of all guilds is desired.
Returns
-------
dict
A dictionary of all specified member data.
"""
ret = {}
if guild is None:
group = self._get_base_group(self.MEMBER)
dict_ = await group()
for guild_id, guild_data in dict_.items():
ret[int(guild_id)] = self._all_members_from_guild(
group, guild_data)
else:
group = self._get_base_group(self.MEMBER, guild.id)
guild_data = await group()
ret = self._all_members_from_guild(group, guild_data)
return ret
async def _clear_scope(self, *scopes: str):
"""Clear all data in a particular scope.
The only situation where a second scope should be passed in is if
member data from a specific guild is being cleared.
If no scopes are passed, then all data is cleared from every scope.
Parameters
----------
*scopes : str, optional
The scope of the data. Generally only one scope needs to be
provided, a second only necessary for clearing member data
of a specific guild.
**Leaving blank removes all data from this Config instance.**
"""
if not scopes:
group = Group(identifiers=(self.unique_identifier),
defaults={},
spawner=self.spawner)
else:
group = self._get_base_group(*scopes)
await group.set({})
async def clear_all(self):
"""Clear all data from this Config instance.
This resets all data to its registered defaults.
.. important::
This cannot be undone.
"""
await self._clear_scope()
async def clear_all_globals(self):
"""Clear all global data.
This resets all global data to its registered defaults.
"""
await self._clear_scope(self.GLOBAL)
async def clear_all_guilds(self):
"""Clear all guild data.
This resets all guild data to its registered defaults.
"""
await self._clear_scope(self.GUILD)
async def clear_all_channels(self):
"""Clear all channel data.
This resets all channel data to its registered defaults.
"""
await self._clear_scope(self.CHANNEL)
async def clear_all_roles(self):
"""Clear all role data.
This resets all role data to its registered defaults.
"""
await self._clear_scope(self.ROLE)
async def clear_all_users(self):
"""Clear all user data.
This resets all user data to its registered defaults.
"""
await self._clear_scope(self.USER)
async def clear_all_members(self, guild: discord.Guild=None):
"""Clear all member data.
This resets all specified member data to its registered defaults.
Parameters
----------
guild : `discord.Guild`, optional
The guild to clear member data from. Omit to clear member data from
all guilds.
"""
if guild is not None:
await self._clear_scope(self.MEMBER, guild.id)
return
await self._clear_scope(self.MEMBER)

View File

@ -234,16 +234,16 @@ async def test_get_dynamic_attr(config):
async def test_membergroup_allguilds(config, empty_member): async def test_membergroup_allguilds(config, empty_member):
await config.member(empty_member).foo.set(False) await config.member(empty_member).foo.set(False)
all_servers = await config.member(empty_member).all_guilds() all_servers = await config.all_members()
assert str(empty_member.guild.id) in all_servers assert empty_member.guild.id in all_servers
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_membergroup_allmembers(config, empty_member): async def test_membergroup_allmembers(config, empty_member):
await config.member(empty_member).foo.set(False) await config.member(empty_member).foo.set(False)
all_members = await config.member(empty_member).all_from_kind() all_members = await config.all_members(empty_member.guild)
assert str(empty_member.id) in all_members assert empty_member.id in all_members
# Clearing testing # Clearing testing
@ -291,11 +291,11 @@ async def test_member_clear_all(config, member_factory):
server_ids.append(member.guild.id) server_ids.append(member.guild.id)
member = member_factory.get() member = member_factory.get()
assert len(await config.member(member).all_guilds()) == len(server_ids) assert len(await config.all_members()) == len(server_ids)
await config.member(member).clear_all() await config.clear_all_members()
assert len(await config.member(member).all_guilds()) == 0 assert len(await config.all_members()) == 0
# Get All testing # Get All testing
@ -309,8 +309,7 @@ async def test_user_get_all_from_kind(config, user_factory):
user = user_factory.get() user = user_factory.get()
await config.user(user).foo.set(True) await config.user(user).foo.set(True)
user = user_factory.get() all_data = await config.all_users()
all_data = await config.user(user).all_from_kind()
assert len(all_data) == 5 assert len(all_data) == 5