Source code for thingy

import re
from collections import OrderedDict


class classproperty(property):
    def __init__(self, function):
        property.__init__(self, function)
        self.function_name = function.__name__

    def __get__(self, instance, cls):
        if instance:
            try:
                return instance.__dict__[self.function_name]
            except KeyError:
                raise AttributeError(
                    "'{}' object has no attribute '{}' (but its class has!)".format(
                        cls.__name__, self.function_name
                    )
                )
        return self.fget(cls)


[docs]class View(object): """Transform an :class:`object` into a dict :param bool defaults: Include attributes of object :param list include: A list of properties to include :param list exclude: A list of attributes to exclude :param bool ordered: Use an :class:`OrderedDict` instead """ def __init__(self, defaults=False, include=None, exclude=None, ordered=False): self.defaults = defaults if isinstance(include, str): include = [include] self.include = include or [] if isinstance(exclude, str): exclude = [exclude] self.exclude = exclude or [] self.ordered = ordered def __call__(self, thingy): if self.ordered: d = OrderedDict() else: d = dict() if not isinstance(thingy, Thingy): return d for attr in self.include: key = attr if isinstance(attr, tuple): attr, key = attr d.update({key: getattr(thingy, attr)}) if self.defaults: for key, value in thingy.__dict__.items(): d.setdefault(key, value) for field in self.exclude: d.pop(field, None) return d
registry = [] class ThingyMetaClass(type): def __new__(cls, name, bases, attrs): attrs.setdefault("_views", {}) klass = type.__new__(cls, name, bases, attrs) if "defaults" not in klass._views: klass.add_view("defaults", defaults=True) registry.append(klass) return klass def getclassattr(instance, attr): for cls in type(instance).mro(): try: return cls.__dict__[attr] except KeyError: pass
[docs]class Thingy(object, metaclass=ThingyMetaClass): """Allows you to use object notation instead of dict notation""" _view_cls = View _silent = True def __init__(self, *args, **kwargs): self._update(*args, **kwargs) def __setattr__(self, attr, value): try: object.__setattr__(self, attr, value) except AttributeError: if type(getclassattr(self, attr)) is not classproperty: raise self.__dict__[attr] = value def __getattribute__(self, attr): try: return object.__getattribute__(self, attr) except AttributeError: if type(getclassattr(self, attr)) is not property and self._silent: return None raise def __eq__(self, other): if isinstance(other, Thingy): return self.__dict__ == other.__dict__ return super().__eq__(other) def __repr__(self): return "{}({})".format(self.__class__.__name__, self.__dict__)
[docs] @classmethod def add_view(cls, name, *args, **kwargs): cls._views.update({name: cls._view_cls(*args, **kwargs)})
def _update(self, *args, **kwargs): for arg in args: self.__dict__.update(**arg) for k in kwargs: setattr(self, k, kwargs[k])
[docs] def update(self, *args, **kwargs): self._update(*args, **kwargs)
[docs] def view(self, name="defaults"): return self._views[name](self)
names_regex = re.compile("([A-Z]+(?![a-z])|[A-Z][a-z]+)") class NamesMixin(object): @classmethod def get_names(cls): names = names_regex.findall(cls.__name__) return [name.lower() for name in names] @classproperty def names(cls): return cls.get_names()
[docs]class DatabaseThingy(NamesMixin, Thingy): _database = None _table = None _database_name = None _table_name = None @classmethod def _get_database(cls, table, name): raise AttributeError("Undefined database.") @classmethod def _get_table(cls, database, name): raise AttributeError("Undefined table.") @classmethod def _get_database_name(cls, database): pass @classmethod def _get_table_name(cls, table): pass
[docs] @classmethod def get_database(cls): if cls._database is not None: return cls._database return cls._get_database(cls._table, cls.database_name)
[docs] @classmethod def get_table(cls): if cls._table is not None: return cls._table return cls._get_table(cls.database, cls.table_name)
[docs] @classmethod def get_database_name(cls): if cls._database is not None: return cls._get_database_name(cls._database) if cls._database_name: return cls._database_name try: return cls.names[-2] except IndexError: pass
[docs] @classmethod def get_table_name(cls): if cls._table is not None: return cls._get_table_name(cls._table) if cls._table_name: return cls._table_name if cls._database is not None or cls._database_name: return "_".join(cls.names) return cls.names[-1]
@classproperty def database(cls): return cls.get_database() @classproperty def table(cls): return cls.get_table() @classproperty def database_name(cls): return cls.get_database_name() @classproperty def table_name(cls): return cls.get_table_name()
__all__ = ["View", "registry", "Thingy", "DatabaseThingy"]