Source code for candv.base

# -*- coding: utf-8 -*-
"""
This module defines base constant and base container for constants. All other
stuff must be derived from them.

Each container has :attr:`~ConstantsContainer.constant_class` attribute. It
specifies class of constants which will be defined within contaner.
"""
import six

from collections import OrderedDict


def _constant_repr(full_name):
    return "<constant '{0}'>".format(full_name)


class _LazyConstantsGroup(object):

    def __init__(self, constant, group_class, **group_members):
        for name, obj in group_members.items():
            if not isinstance(obj, (Constant, _LazyConstantsGroup)):
                raise TypeError("{0} cannot be a member of a constants group"
                                .format(obj.__class__))
        self.constant = constant
        self.group_class = group_class
        self.group_members = group_members

    def _evaluate(self, parent, name):
        group_class, self.group_class = self.group_class, None
        group_members, self.group_members = self.group_members, None

        cls_name = "{0}.{1}".format(parent.__name__, name)
        group_repr = _constant_repr(cls_name)

        group_members.update({
            '__new__': object.__new__, # Remove singleton protection
            '__repr__': lambda self: group_repr,
            '__name__': name,
            'name': name,
        })

        cls = type(cls_name, (group_class, ), group_members)
        group = cls()
        self.constant.merge_into_group(group)
        return group


[docs]class Constant(object): """ Base class for all constants. Can be merged into a container instance. :ivar str name: constant's name. Is set up automatically and is equal to the name of container's attribute """ # Tracks each time a Constant instance is created. Used to retain order. _creation_counter = 0 def __init__(self): self.name = None self._repr = '' self._container = None # Increase the creation counter, and save our local copy. self._creation_counter = Constant._creation_counter Constant._creation_counter += 1 def _post_init(self, container, name): """ Called automatically by container after container's class construction. """ self.name = name self._repr = _constant_repr("{0}.{1}".format(container.__name__, name)) self._container = container
[docs] def to_group(self, group_class, **group_members): """ Convert a constant into a constants group. :param class group_class: a class of group container which will be created :param group_members: unpacked dict which defines group members. :returns: a lazy constants group which will be evaluated by container. During group evaluation :meth:`merge_into_group` will be called. **Example**:: from candv import Constants, SimpleConstant class FOO(Constants): A = SimpleConstant() B = SimpleConstant().to_group(Constants, B2=SimpleConstant(), B0=SimpleConstant(), B1=SimpleConstant(), ) """ return _LazyConstantsGroup(self, group_class, **group_members)
[docs] def merge_into_group(self, group): """ Called automatically by container after group construction. .. note:: Redefine this method in all derived classes. Attach all custom attributes and methods to the group here. :param group: an instance of :class:`ConstantsContainer` or it's subclass this constant will be merged into :returns: ``None`` """ group._creation_counter = self._creation_counter
def __repr__(self): """ Return text identifying both which constant this is and which collection it belongs to. """ return self._repr
class _ConstantsContainerMeta(type): """ Metaclass for creating constants container classes. """ def __new__(self, class_name, bases, attributes): cls = super(_ConstantsContainerMeta, self).__new__( self, class_name, bases, attributes) constant_class = getattr(cls, 'constant_class', None) if constant_class is None: return cls elif not issubclass(constant_class, Constant): raise TypeError( "Constant class {0} must be derived from {1}".format( constant_class.__name__, Constant.__name__)) constants = [] for name, obj in list(six.iteritems(attributes)): if isinstance(obj, _LazyConstantsGroup): new_obj = obj._evaluate(cls, name) del obj.constant setattr(cls, name, new_obj) constants.append((name, new_obj)) elif isinstance(obj, constant_class): if obj._container is not None: raise ValueError( "Cannot use {0} as the value of an attribute {1} on {2}" .format(obj, name, cls.__name__)) obj._post_init(cls, name) constants.append((name, obj)) constants.sort(key=lambda name_obj: name_obj[1]._creation_counter) cls._constants = OrderedDict(constants) return cls @six.add_metaclass(_ConstantsContainerMeta)
[docs]class ConstantsContainer(object): """ Base class for creating constants containers. Each constant defined within container will remember it's creation order. See an example in :meth:`constants`. :cvar constant_class: stores a class of constants which can be stored by container. This attribute **MUST** be set up manually when you define a new container type. Otherwise container will not be initialized. Default: ``None`` :raises TypeError: if you try to create an instance of container. Containers are singletons and they cannot be instantiated. Their attributes must be used directly. """ #: Defines a top-level class of constants which can be stored by container constant_class = None def __new__(cls): """ Classes representing constants containers are not intended to be instantiated. The class object itself is used directly. """ raise TypeError("'{0}' may not be instantiated".format(cls.__name__)) @classmethod
[docs] def contains(cls, name): """ Check if container has a constant with a given name. :param str name: a constant's name to check :returns: ``True`` if given name belongs to container, ``False`` otherwise :rtype: :class:`bool` """ return name in cls.names()
@classmethod
[docs] def names(cls): """ List all names of constants within container. :returns: a list of constant names in order constants were defined :rtype: :class:`list` of strings **Example**:: >>> from candv import Constants, SimpleConstant >>> class FOO(Constants): ... foo = SimpleConstant() ... bar = SimpleConstant() ... >>> FOO.names() ['foo', 'bar'] """ return list(cls.iternames())
@classmethod
[docs] def iternames(cls): """ Same as :meth:`names` but returns an interator. """ return six.iterkeys(cls._constants)
@classmethod
[docs] def constants(cls): """ List all constants in container. :returns: list of constants in order they were defined :rtype: :class:`list` **Example**:: >>> from candv import Constants, SimpleConstant >>> class FOO(Constants): ... foo = SimpleConstant() ... bar = SimpleConstant() ... >>> [x.name for x in FOO.constants()] ['foo', 'bar'] """ return list(cls.iterconstants())
@classmethod
[docs] def iterconstants(cls): """ Same as :meth:`constants` but returns an interator. """ return six.itervalues(cls._constants)
@classmethod
[docs] def items(cls): """ Get list of constants with their names. :returns: list of constants with their names in order they were defined. Each element in list is a :class:`tuple` in format ``(name, constant)``. :rtype: :class:`list` **Example**:: >>> from candv import Constants, SimpleConstant >>> class FOO(Constants): ... foo = SimpleConstant() ... bar = SimpleConstant() ... >>> FOO.items() [('foo', <constant 'FOO.foo'>), ('bar', <constant 'FOO.bar'>)] """ return list(cls.iteritems())
@classmethod
[docs] def iteritems(cls): """ Same as :meth:`items` but returns an interator. """ return six.iteritems(cls._constants)
@classmethod
[docs] def get_by_name(cls, name): """ Try to get constant by it's name. :param str name: name of constant to search for :returns: a constant :rtype: a class specified by :attr:`constant_class` which is :class:`Constant` or it's subclass :raises KeyError: if constant name ``name`` is not present in container **Example**:: >>> from candv import Constants, SimpleConstant >>> class FOO(Constants): ... foo = SimpleConstant() ... bar = SimpleConstant() ... >>> FOO.get_by_name('foo') <constant 'FOO.foo'> """ try: return cls._constants[name] except KeyError: raise KeyError("Constant with name '{0}' is not present in '{1}'" .format(name, cls.__name__))