diff --git a/disco/bot/providers/base.py b/disco/bot/providers/base.py index 033b020..0f14f3d 100644 --- a/disco/bot/providers/base.py +++ b/disco/bot/providers/base.py @@ -1,9 +1,7 @@ import six import pickle -from six.moves import map - -from UserDict import UserDict +from six.moves import map, UserDict ROOT_SENTINEL = u'\u200B' diff --git a/disco/gateway/events.py b/disco/gateway/events.py index f0726b3..a18efb5 100644 --- a/disco/gateway/events.py +++ b/disco/gateway/events.py @@ -9,7 +9,7 @@ from disco.types.message import Message from disco.types.voice import VoiceState from disco.types.guild import Guild, GuildMember, Role -from disco.types.base import SlottedModel, ModelMeta, Field, snowflake, listof, lazy_datetime +from disco.types.base import Model, ModelMeta, Field, snowflake, listof, lazy_datetime # Mapping of discords event name to our event classes EVENTS_MAP = {} @@ -25,7 +25,7 @@ class GatewayEventMeta(ModelMeta): return obj -class GatewayEvent(six.with_metaclass(GatewayEventMeta, SlottedModel)): +class GatewayEvent(six.with_metaclass(GatewayEventMeta, Model)): """ The GatewayEvent class wraps various functionality for events passed to us over the gateway websocket, and serves as a simple proxy to inner values for @@ -92,7 +92,6 @@ def wraps_model(model, alias=None): def deco(cls): cls._fields[alias] = Field(model) cls._fields[alias].set_name(alias) - cls.__slots__ = cls.__slots__ + (alias, ) cls._wraps_model = (alias, model) return cls return deco diff --git a/disco/state.py b/disco/state.py index 2130787..ae50698 100644 --- a/disco/state.py +++ b/disco/state.py @@ -263,7 +263,6 @@ class State(object): member.guild_id = guild.id guild.members[member.id] = member self.users[member.id] = member.user - guild.synced = True def on_guild_role_create(self, event): if event.guild_id not in self.guilds: diff --git a/disco/types/base.py b/disco/types/base.py index 4c98170..87f2aab 100644 --- a/disco/types/base.py +++ b/disco/types/base.py @@ -67,7 +67,7 @@ class Field(FieldType): try: return self.typ(raw, client) except Exception as e: - raise ConversionError(self, raw, e) + six.raise_from(ConversionError(self, raw, e), e) class _Dict(FieldType): @@ -159,7 +159,7 @@ def binary(obj): return obj.decode('utf-8') return unicode(obj) else: - return bytes(obj) + return bytes(obj, 'utf-8') def with_equality(field): @@ -196,26 +196,33 @@ class ModelMeta(type): v.set_name(k) fields[k] = v - dct = {k: v for k, v in six.iteritems(dct) if k not in fields} - if SlottedModel and any(map(lambda k: issubclass(k, SlottedModel), parents)): - bases = set(k for k, v in six.iteritems(dct) if isinstance(v, CachedSlotProperty)) + bases = set(v.stored_name for v in six.itervalues(dct) if isinstance(v, CachedSlotProperty)) + if '__slots__' in dct: dct['__slots__'] = tuple(set(dct['__slots__']) | set(fields.keys()) | bases) else: dct['__slots__'] = tuple(fields.keys()) + tuple(bases) + dct = {k: v for k, v in six.iteritems(dct) if k not in dct['__slots__']} + else: + dct = {k: v for k, v in six.iteritems(dct) if k not in fields} + dct['_fields'] = fields return super(ModelMeta, cls).__new__(cls, name, parents, dct) class AsyncChainable(object): + __slots__ = [] + def after(self, delay): gevent.sleep(delay) return self class Model(six.with_metaclass(ModelMeta, AsyncChainable)): + __slots__ = ['client'] + def __init__(self, *args, **kwargs): self.client = kwargs.pop('client', None) @@ -257,8 +264,8 @@ class Model(six.with_metaclass(ModelMeta, AsyncChainable)): @classmethod def create(cls, client, data, **kwargs): + data.update(kwargs) inst = cls(data, client) - inst.__dict__.update(kwargs) return inst @classmethod @@ -272,7 +279,6 @@ class Model(six.with_metaclass(ModelMeta, AsyncChainable)): try: setattr(item, k, v) except: - # TODO: wtf pass diff --git a/disco/types/channel.py b/disco/types/channel.py index faabdcf..9781ee0 100644 --- a/disco/types/channel.py +++ b/disco/types/channel.py @@ -51,6 +51,8 @@ class PermissionOverwrite(ChannelSubType): allow = Field(PermissionValue) deny = Field(PermissionValue) + channel_id = Field(snowflake) + @classmethod def create(cls, channel, entity, allow=0, deny=0): from disco.types.guild import Role diff --git a/disco/types/guild.py b/disco/types/guild.py index 2f4069b..9708e3e 100644 --- a/disco/types/guild.py +++ b/disco/types/guild.py @@ -84,6 +84,9 @@ class Role(GuildSubType): position = Field(int) mentionable = Field(bool) + def delete(self): + self.guild.delete_role(self) + def save(self): self.guild.update_role(self) @@ -146,7 +149,7 @@ class GuildMember(GuildSubType): delete_message_days : int The number of days to retroactively delete messages for. """ - self.client.api.guilds_bans_create(self.guild.id, self.user.id, delete_message_days) + self.guild.create_ban(self, delete_message_days) def set_nickname(self, nickname=None): """ @@ -173,7 +176,7 @@ class GuildMember(GuildSubType): return '<@!{}>'.format(self.id) return self.user.mention - @cached_property + @property def id(self): """ Alias to the guild members user id @@ -320,6 +323,12 @@ class Guild(SlottedModel, Permissible): """ return self.client.api.guilds_roles_create(self.id) + def delete_role(self, role): + """ + Delete a role. + """ + self.client.api.guilds_roles_delete(self.id, to_snowflake(role)) + def update_role(self, role): return self.client.api.guilds_roles_modify(self.id, role.id, **{ 'name': role.name, @@ -334,8 +343,18 @@ class Guild(SlottedModel, Permissible): if self.synced: return + self.synced = True self.client.gw.send(OPCode.REQUEST_GUILD_MEMBERS, { 'guild_id': self.id, 'query': '', 'limit': 0, }) + + def get_bans(self): + return self.client.api.guilds_bans_list(self.id) + + def delete_ban(self, user): + self.client.api.guilds_bans_delete(self.id, to_snowflake(user)) + + def create_ban(self, user, delete_message_days=0): + self.client.api.guilds_bans_create(self.id, to_snowflake(user), delete_message_days) diff --git a/disco/types/permissions.py b/disco/types/permissions.py index b66e6cc..aa7260c 100644 --- a/disco/types/permissions.py +++ b/disco/types/permissions.py @@ -105,6 +105,8 @@ class PermissionValue(object): class Permissible(object): + __slots__ = [] + def can(self, user, *args): perms = self.get_permissions(user) return perms.administrator or perms.can(*args) diff --git a/disco/util/functional.py b/disco/util/functional.py index af72be3..ee0ac02 100644 --- a/disco/util/functional.py +++ b/disco/util/functional.py @@ -49,10 +49,10 @@ def one_or_many(f): class CachedSlotProperty(object): - __slots__ = ['name', 'function', '__doc__'] + __slots__ = ['stored_name', 'function', '__doc__'] def __init__(self, name, function): - self.name = name + self.stored_name = '_' + name self.function = function self.__doc__ = getattr(function, '__doc__') @@ -60,9 +60,12 @@ class CachedSlotProperty(object): if instance is None: return self - value = self.function(instance) - setattr(instance, self.name, value) - return value + try: + return getattr(instance, self.stored_name) + except AttributeError: + value = self.function(instance) + setattr(instance, self.stored_name, value) + return value def cached_property(f): diff --git a/disco/util/hashmap.py b/disco/util/hashmap.py index 72f008a..ef32647 100644 --- a/disco/util/hashmap.py +++ b/disco/util/hashmap.py @@ -1,11 +1,13 @@ import six -from six.moves import filter, map +from six.moves import filter, map, UserDict from collections import defaultdict -from UserDict import IterableUserDict -class HashMap(IterableUserDict): +class HashMap(UserDict): + def iter(self): + return iter(self.data) + def items(self): return six.iteritems(self.data) @@ -28,7 +30,7 @@ class HashMap(IterableUserDict): def select(self, *args, **kwargs): if kwargs: - args += [kwargs] + args += tuple([kwargs]) for obj in self.values(): for check in args: