You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
100 lines
3.8 KiB
100 lines
3.8 KiB
# -*- coding: utf-8 -*-
|
|
# Copyright 2009-2021 Joshua Bronson. All Rights Reserved.
|
|
#
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
"""Provide :func:`bidict.namedbidict`."""
|
|
|
|
import typing as _t
|
|
from sys import _getframe
|
|
|
|
from ._abc import BidirectionalMapping, KT, VT
|
|
from ._bidict import bidict
|
|
|
|
|
|
def namedbidict(
|
|
typename: str,
|
|
keyname: str,
|
|
valname: str,
|
|
*,
|
|
base_type: _t.Type[BidirectionalMapping[KT, VT]] = bidict,
|
|
) -> _t.Type[BidirectionalMapping[KT, VT]]:
|
|
r"""Create a new subclass of *base_type* with custom accessors.
|
|
|
|
Like :func:`collections.namedtuple` for bidicts.
|
|
|
|
The new class's ``__name__`` and ``__qualname__`` will be set to *typename*,
|
|
and its ``__module__`` will be set to the caller's module.
|
|
|
|
Instances of the new class will provide access to their
|
|
:attr:`inverse <BidirectionalMapping.inverse>` instances
|
|
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 is not a valid Python identifier, or if *keyname == valname*.
|
|
|
|
:raises TypeError: if *base_type* is not a :class:`BidirectionalMapping` subclass
|
|
that provides ``_isinv`` and :meth:`~object.__getstate__` attributes.
|
|
(Any :class:`~bidict.BidictBase` subclass can be passed in, including all the
|
|
concrete bidict types pictured in the :ref:`other-bidict-types:Bidict Types Diagram`.
|
|
"""
|
|
if not issubclass(base_type, BidirectionalMapping) or not all(hasattr(base_type, i) for i in ('_isinv', '__getstate__')):
|
|
raise TypeError(base_type)
|
|
names = (typename, keyname, valname)
|
|
if not all(map(str.isidentifier, names)) or keyname == valname:
|
|
raise ValueError(names)
|
|
|
|
class _Named(base_type): # type: ignore [valid-type,misc]
|
|
|
|
__slots__ = ()
|
|
|
|
def _getfwd(self) -> '_Named':
|
|
return self.inverse if self._isinv else self # type: ignore [no-any-return]
|
|
|
|
def _getinv(self) -> '_Named':
|
|
return self if self._isinv else self.inverse # type: ignore [no-any-return]
|
|
|
|
@property
|
|
def _keyname(self) -> str:
|
|
return valname if self._isinv else keyname
|
|
|
|
@property
|
|
def _valname(self) -> str:
|
|
return keyname if self._isinv else valname
|
|
|
|
def __reduce__(self) -> '_t.Tuple[_t.Callable[[str, str, str, _t.Type[BidirectionalMapping]], BidirectionalMapping], _t.Tuple[str, str, str, _t.Type[BidirectionalMapping]], dict]':
|
|
return (_make_empty, (typename, keyname, valname, base_type), self.__getstate__())
|
|
|
|
bname = base_type.__name__
|
|
fname = valname + '_for'
|
|
iname = keyname + '_for'
|
|
fdoc = f'{typename} forward {bname}: {keyname} → {valname}'
|
|
idoc = f'{typename} inverse {bname}: {valname} → {keyname}'
|
|
setattr(_Named, fname, property(_Named._getfwd, doc=fdoc))
|
|
setattr(_Named, iname, property(_Named._getinv, doc=idoc))
|
|
|
|
_Named.__name__ = typename
|
|
_Named.__qualname__ = typename
|
|
_Named.__module__ = _getframe(1).f_globals.get('__name__') # type: ignore [assignment]
|
|
return _Named
|
|
|
|
|
|
def _make_empty(
|
|
typename: str,
|
|
keyname: str,
|
|
valname: str,
|
|
base_type: _t.Type[BidirectionalMapping] = bidict,
|
|
) -> BidirectionalMapping:
|
|
"""Create a named bidict with the indicated arguments and return an empty instance.
|
|
Used to make :func:`bidict.namedbidict` instances picklable.
|
|
"""
|
|
cls = namedbidict(typename, keyname, valname, base_type=base_type)
|
|
return cls()
|