100 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			100 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| # Copyright 2009-2020 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
 | |
| 
 | |
|         __slots__ = ()
 | |
| 
 | |
|         def _getfwd(self) -> '_Named':
 | |
|             return self.inverse if self._isinv else self  # type: ignore
 | |
| 
 | |
|         def _getinv(self) -> '_Named':
 | |
|             return self if self._isinv else self.inverse  # type: ignore
 | |
| 
 | |
|         @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
 | |
|     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()
 |