diff --git a/redbot/cogs/bank/bank.py b/redbot/cogs/bank/bank.py index 0317ad534..50f9c35bf 100644 --- a/redbot/cogs/bank/bank.py +++ b/redbot/cogs/bank/bank.py @@ -63,7 +63,7 @@ class Bank: If the bank is global, it will become per-guild If the bank is per-guild, it will become 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") diff --git a/redbot/core/bank.py b/redbot/core/bank.py index d4eb963ce..37049c4a2 100644 --- a/redbot/core/bank.py +++ b/redbot/core/bank.py @@ -322,7 +322,7 @@ async def get_guild_accounts(guild: discord.Guild) -> List[Account]: raise RuntimeError("The bank is currently global.") ret = [] - accs = await _conf.member(guild.owner).all_from_kind() + accs = await _conf.all_members(guild) for user_id, acc in accs.items(): acc_data = acc.copy() # There ya go kowlin 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.") 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(): acc_data = acc.copy() 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: """Set global status of the bank. - Requires the user parameter for technical reasons. - .. important:: 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 :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 ------- @@ -440,12 +436,9 @@ async def set_global(global_: bool, user: Union[discord.User, discord.Member]) - return global_ if is_global(): - await _conf.user(user).clear_all() - elif isinstance(user, discord.Member): - await _conf.member(user).clear_all() + await _conf.clear_all_users() else: - raise RuntimeError("You must provide a member if you're changing to global" - " bank mode.") + await _conf.clear_all_members() await _conf.is_global.set(global_) return global_ diff --git a/redbot/core/config.py b/redbot/core/config.py index 95d597d5d..68700f903 100644 --- a/redbot/core/config.py +++ b/redbot/core/config.py @@ -137,7 +137,7 @@ class Group(Value): # noinspection PyTypeChecker def __getattr__(self, item: str) -> Union["Group", Value]: """Get an attribute of this group. - + This special method is called whenever dot notation is used on this object. @@ -145,13 +145,13 @@ class Group(Value): ---------- 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 @@ -260,7 +260,7 @@ class Group(Value): 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` @@ -292,36 +292,6 @@ class Group(Value): defaults.update(await self()) 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): if not isinstance(value, dict): raise ValueError( @@ -331,14 +301,14 @@ class Group(Value): async def set_attr(self, item: str, value): """Set an attribute by its name. - + Similar to `get_attr` in the way it can be used to dynamically set attributes by name. Note ---- Use of this method should be avoided wherever possible. - + Parameters ---------- item : str @@ -358,17 +328,10 @@ class Group(Value): """ 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): """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`. """ @@ -392,41 +355,6 @@ class MemberGroup(Group): ) 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: """Configuration manager for cogs and Red. @@ -501,7 +429,7 @@ class Config: force_registration : `bool`, optional Should config require registration of data keys before allowing you to get/set values? See `force_registration`. - + Returns ------- Config @@ -519,11 +447,13 @@ class Config: @classmethod 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`. - identifier : int - See `get_conf`. + Parameters + ---------- force_registration : `bool`, optional See `force_registration`. @@ -535,17 +465,17 @@ class Config: def __getattr__(self, item: str) -> Union[Group, Value]: """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 @@ -659,7 +589,7 @@ class Config: def register_guild(self, **kwargs): """Register default values on a per-guild level. - + See :py:meth:`register_global` for more details. """ self._register_default(self.GUILD, **kwargs) @@ -681,16 +611,16 @@ class Config: def register_user(self, **kwargs): """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. - + This means that each user's data is guild-dependent. See `register_global` for more details. @@ -720,7 +650,7 @@ class Config: def channel(self, channel: discord.TextChannel) -> Group: """Returns a `Group` for the given channel. - + This does not discriminate between text and voice channels. Parameters @@ -765,3 +695,226 @@ class Config: return self._get_base_group(self.MEMBER, member.guild.id, member.id, 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) diff --git a/tests/core/test_config.py b/tests/core/test_config.py index 1139ffd69..184691cf0 100644 --- a/tests/core/test_config.py +++ b/tests/core/test_config.py @@ -234,16 +234,16 @@ async def test_get_dynamic_attr(config): async def test_membergroup_allguilds(config, empty_member): await config.member(empty_member).foo.set(False) - all_servers = await config.member(empty_member).all_guilds() - assert str(empty_member.guild.id) in all_servers + all_servers = await config.all_members() + assert empty_member.guild.id in all_servers @pytest.mark.asyncio async def test_membergroup_allmembers(config, empty_member): await config.member(empty_member).foo.set(False) - all_members = await config.member(empty_member).all_from_kind() - assert str(empty_member.id) in all_members + all_members = await config.all_members(empty_member.guild) + assert empty_member.id in all_members # Clearing testing @@ -291,11 +291,11 @@ async def test_member_clear_all(config, member_factory): server_ids.append(member.guild.id) 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 @@ -309,8 +309,7 @@ async def test_user_get_all_from_kind(config, user_factory): user = user_factory.get() await config.user(user).foo.set(True) - user = user_factory.get() - all_data = await config.user(user).all_from_kind() + all_data = await config.all_users() assert len(all_data) == 5