bidict/_named.py
def namedbidict(typename, keyname, valname, base_type=bidict):
    r"""Create a new subclass of *base_type* with custom accessors.

    Analagous to :func:`collections.namedtuple`.

    The new class's ``__name__`` will be set to *typename*.

    Instances of it will provide access to their
    :attr:`inverse <BidirectionalMapping.inverse>`\s
    via the custom *keyname*\_for property,
    and access to themselves
    via the custom *valname*\_for property.

    *See also* the :ref:`namedbidict usage documentation
    <other-bidict-types:\:func\:\`~bidict.namedbidict\`>`

    :raises ValueError: if any of the *typename*, *keyname*, or *valname*
        strings does not match ``%s``, or if *keyname == valname*.

    :raises TypeError: if *base_type* is not a subclass of
        :class:`BidirectionalMapping`.
        (This function requires slightly more of *base_type*,
        e.g. the availability of an ``_isinv`` attribute,
        but all the :ref:`concrete bidict types
        <other-bidict-types:Bidict Types Diagram>`
        that the :mod:`bidict` module provides can be passed in.
        Check out the code if you actually need to pass in something else.)
    """
    # Re the `base_type` docs above:
    # The additional requirements (providing _isinv and __getstate__) do not belong in the
    # BidirectionalMapping interface, and it's overkill to create additional interface(s) for this.
    # On the other hand, it's overkill to require that base_type be a subclass of BidictBase, since
    # that's too specific. The BidirectionalMapping check along with the docs above should suffice.
    if not issubclass(base_type, BidirectionalMapping):
        raise TypeError(base_type)
    names = (typename, keyname, valname)
    if not all(map(_VALID_NAME.match, names)) or keyname == valname:
        raise ValueError(names)

    class _Named(base_type):  # pylint: disable=too-many-ancestors

        __slots__ = ()

        def _getfwd(self):
            return self.inverse if self._isinv else self

        def _getinv(self):
            return self if self._isinv else self.inverse

        @property
        def _keyname(self):
            return valname if self._isinv else keyname

        @property
        def _valname(self):
            return keyname if self._isinv else valname

        def __reduce__(self):
            return (_make_empty, (typename, keyname, valname, base_type), self.__getstate__())

    bname = base_type.__name__
    fname = valname + '_for'
    iname = keyname + '_for'
    names = dict(typename=typename, bname=bname, keyname=keyname, valname=valname)
    fdoc = u'{typename} forward {bname}: {keyname} → {valname}'.format(**names)
    idoc = u'{typename} inverse {bname}: {valname} → {keyname}'.format(**names)
    setattr(_Named, fname, property(_Named._getfwd, doc=fdoc))  # pylint: disable=protected-access
    setattr(_Named, iname, property(_Named._getinv, doc=idoc))  # pylint: disable=protected-access

    _Named.__name__ = typename
    return _Named
def namedbidict(typename, keyname, valname, base_type=bidict):
    r"""Create a new subclass of *base_type* with custom accessors.

    Analagous to :func:`collections.namedtuple`.

    The new class's ``__name__`` will be set to *typename*.

    Instances of it will provide access to their
    :attr:`inverse <BidirectionalMapping.inverse>`\s
    via the custom *keyname*\_for property,
    and access to themselves
    via the custom *valname*\_for property.

    *See also* the :ref:`namedbidict usage documentation
    <other-bidict-types:\:func\:\`~bidict.namedbidict\`>`

    :raises ValueError: if any of the *typename*, *keyname*, or *valname*
        strings does not match ``%s``, or if *keyname == valname*.

    :raises TypeError: if *base_type* is not a subclass of
        :class:`BidirectionalMapping`.
        (This function requires slightly more of *base_type*,
        e.g. the availability of an ``_isinv`` attribute,
        but all the :ref:`concrete bidict types
        <other-bidict-types:Bidict Types Diagram>`
        that the :mod:`bidict` module provides can be passed in.
        Check out the code if you actually need to pass in something else.)
    """
    # Re the `base_type` docs above:
    # The additional requirements (providing _isinv and __getstate__) do not belong in the
    # BidirectionalMapping interface, and it's overkill to create additional interface(s) for this.
    # On the other hand, it's overkill to require that base_type be a subclass of BidictBase, since
    # that's too specific. The BidirectionalMapping check along with the docs above should suffice.
    if not issubclass(base_type, BidirectionalMapping):
        raise TypeError(base_type)
    names = (typename, keyname, valname)
    if not all(map(_VALID_NAME.match, names)) or keyname == valname:
        raise ValueError(names)

    class _Named(base_type):  # pylint: disable=too-many-ancestors

        __slots__ = ()

        def _getfwd(self):
            return self.inverse if self._isinv else self

        def _getinv(self):
            return self if self._isinv else self.inverse

        @property
        def _keyname(self):
            return valname if self._isinv else keyname

        @property
        def _valname(self):
            return keyname if self._isinv else valname

        def __reduce__(self):
            return (_make_empty, (typename, keyname, valname, base_type), self.__getstate__())

    bname = base_type.__name__
    fname = valname + '_for'
    iname = keyname + '_for'
    names = dict(typename=typename, bname=bname, keyname=keyname, valname=valname)
    fdoc = u'{typename} forward {bname}: {keyname} → {valname}'.format(**names)
    idoc = u'{typename} inverse {bname}: {valname} → {keyname}'.format(**names)
    setattr(_Named, fname, property(_Named._getfwd, doc=fdoc))  # pylint: disable=protected-access
    setattr(_Named, iname, property(_Named._getinv, doc=idoc))  # pylint: disable=protected-access

    _Named.__name__ = typename
    return _Named
r"""Create a new subclass of *base_type* with custom accessors.

    Analagous to :func:`collections.namedtuple`.

    The new class's ``__name__`` will be set to *typename*.

    Instances of it will provide access to their
    :attr:`inverse <BidirectionalMapping.inverse>`\s
    via the custom *keyname*\_for property,
    and access to themselves
    via the custom *valname*\_for property.

    *See also* the :ref:`namedbidict usage documentation
    <other-bidict-types:\:func\:\`~bidict.namedbidict\`>`

    :raises ValueError: if any of the *typename*, *keyname*, or *valname*
        strings does not match ``%s``, or if *keyname == valname*.

    :raises TypeError: if *base_type* is not a subclass of
        :class:`BidirectionalMapping`.
        (This function requires slightly more of *base_type*,
        e.g. the availability of an ``_isinv`` attribute,
        but all the :ref:`concrete bidict types
        <other-bidict-types:Bidict Types Diagram>`
        that the :mod:`bidict` module provides can be passed in.
        Check out the code if you actually need to pass in something else.)