Source code for maps.namedfrozenmap
import abc
import collections
import maps.utils as utils
from maps.frozenmap import FrozenMap
[docs]class NamedFrozenMapMeta(abc.ABCMeta):
'''Returns a new :class:`maps.FrozenMap` 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__`` raises :py:exc:`TypeError` unless the
attribute name has a leading underscore, in which case the attribute will be
set normally.
3. ``__repr__`` simply replaces ``FrozenMap`` with the name of the instantiated
class.
:func:`maps.namedfrozen` 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.FrozenMap`
'''
@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):
'''Raises a TypeError as attribute assignment is not supported.
:raises TypeError:
'''
if not name.startswith('_'):
raise TypeError(
"'{}' object does not support attribute assignment".format(type(self).__name__))
super(type(self), self).__setattr__(name, value)
@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}: An immutable, hashable key-value mapping
accessible via bracket-notation (i.e. ``__getitem__``). 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__': NamedFrozenMapMeta._getattr,
'__repr__': NamedFrozenMapMeta._repr,
'__setattr__': NamedFrozenMapMeta._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, (FrozenMap,), methods)
def __init__(cls, typename, fields=[], defaults={}):
super(type(cls), cls).__init__(cls)