Source code for maps.namedfixedkeymap

import abc
import collections
import maps.utils as utils
from maps.fixedkeymap import FixedKeyMap

[docs]class NamedFixedKeyMapMeta(abc.ABCMeta): '''Returns a new :class:`maps.FixedKeyMap` subclass named ``typename``. The new subclass is used to create :py:class:`dict`-like objects that have fields accessible by attribute lookup as well as being indexable by name and iterable. Instances of the subclass also have a helpful docstring (with typename and field_names) and a helpful __repr__() method which lists the mapping contents in a name=value format. ``field_names`` can be a sequence of strings such as ``['x', 'y']``. Any valid Python identifier may be used for a fieldname except for names starting with an underscore. Valid identifiers consist of letters, digits, and underscores but do not start with a digit or underscore and cannot be a keyword such as class, for, return, global, pass, or raise. This metaclass injects 3 methods into the subclass: ``__getattr__``, ``__setattr__``, and ``__repr__``. 1. ``__getattr__`` attempts to retrieve attributes from an instance's underlying ``_data`` dictionary, raising :py:exc:`AttributeError` if the attribute is not found. 2. ``__setattr__`` attempts to set the value for the specified attribute, but raises :py:exc:`TypeError` if the attribute is not part of the fixed key set. If the attribute name has a leading underscore, these checks are skipped and the value is set normally. 3. ``__repr__`` simply replaces ``FixedKeyMap`` with the name of the instantiated class. :func:`maps.namedfixedkey` provides a convenient alias for calling this metaclass. :param str typename: Name for the new class :param iterable fields: Names for the fields of the new class :param mapping defaults: Maps default values to fields of the new class :raises ValueError: if the type name or field names or defaults provided are not properly formatted :return: Newly created subclass of :class:`maps.FixedKeyMap` ''' @staticmethod
[docs] def _getattr(self, name): '''Retrieves attribute by name. :param str name: Name of the desired attribute :raises AttributeError: if an attribute with the specified name cannot be found :return: Desired attribute ''' try: return self._data[name] except KeyError: raise AttributeError( "'{}' object has no attribute {!r}".format(type(self).__name__, name))
@staticmethod
[docs] def _setattr(self, name, value): '''Sets the value for the specified attribute if the attribute name is part of the fixed key set. :raises TypeError: if the attribute name is not part of the fixed key set ''' if name.startswith('_'): super(type(self), self).__setattr__(name, value) elif name in self._data: self._data[name] = value else: raise TypeError( "'{}' object does not support new attribute assignment".format(type(self).__name__))
@staticmethod def _repr(self): # pragma: no cover kwargs = ', '.join('{}={!r}'.format(key, value) for key, value in self.items()) return '{}({})'.format(type(self).__name__, kwargs) def __new__(cls, typename, fields=[], defaults={}): fields = tuple(fields) # validate names for name in (typename,) + fields: utils._validate_name(name) utils._validate_fields(fields) utils._validate_defaults(fields, defaults) cls._fields = fields docstring = '''{typename}: A key-value mapping with a fixed set of keys whose items are accessible via bracket-notation (i.e. ``__getitem__`` and ``__setitem__``). Though the set of keys is immutable, the corresponding values can be edited. Has fields ({fields}) :param args: Position arguments in the same form as the :py:class:`dict` constructor. :param kwargs: Keyword arguments in the same form as the :py:class:`dict` constructor. '''.format(typename=typename, fields=cls._fields) methods = { '__doc__': docstring, '__getattr__': NamedFixedKeyMapMeta._getattr, '__repr__': NamedFixedKeyMapMeta._repr, '__setattr__': NamedFixedKeyMapMeta._setattr} # handle custom __init__ template = '\n'.join([ 'def __init__(_self, {args}):', ' _super(_type(_self), _self).__init__()', ' _self._data = _collections.OrderedDict({kwargs})']) args = ', '.join([ '{arg}={default}'.format(arg=field, default=defaults[field]) if field in defaults else field for field in fields]) kwargs = ', '.join(['{0}={0}'.format(i) for i in fields]) namespace = { '_collections': collections, '_super': super, '_type': type} exec(template.format(args=args, kwargs=kwargs), namespace) methods['__init__'] = namespace['__init__'] return type.__new__(cls, typename, (FixedKeyMap,), methods) def __init__(cls, typename, fields=[], defaults={}): super(type(cls), cls).__init__(cls)