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.
bazarr/libs/bidict/_named.py

100 lines
4.0 KiB

# Copyright 2009-2022 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 ._base import BidictBase
from ._bidict import bidict
from ._typing import KT, VT
# pyright: reportPrivateUsage=false, reportUnnecessaryIsInstance=false
class NamedBidictBase:
"""Base class that namedbidicts derive from."""
def namedbidict(
typename: str,
keyname: str,
valname: str,
*,
base_type: t.Type[BidictBase[KT, VT]] = bidict,
) -> t.Type[BidictBase[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\`>`
(https://bidict.rtfd.io/other-bidict-types.html#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:`bidict.BidictBase` subclass.
Any of the concrete bidict types pictured in the
:ref:`other-bidict-types:Bidict Types Diagram` may be provided
(https://bidict.rtfd.io/other-bidict-types.html#bidict-types-diagram).
"""
if not issubclass(base_type, BidictBase):
raise TypeError(f'{base_type} is not a BidictBase subclass')
names = (typename, keyname, valname)
if not all(map(str.isidentifier, names)) or keyname == valname:
raise ValueError(names)
basename = base_type.__name__
get_keyname = property(lambda self: keyname, doc='The keyname of this namedbidict.')
get_valname = property(lambda self: valname, doc='The valname of this namedbidict.')
val_by_key_name = f'{valname}_for'
key_by_val_name = f'{keyname}_for'
val_by_key_doc = f'{typename} forward {basename}: {keyname} -> {valname}'
key_by_val_doc = f'{typename} inverse {basename}: {valname} -> {keyname}'
get_val_by_key = property(lambda self: self, doc=val_by_key_doc)
get_key_by_val = property(lambda self: self.inverse, doc=key_by_val_doc)
class NamedBidict(base_type, NamedBidictBase): # type: ignore [valid-type,misc] # https://github.com/python/mypy/issues/5865
"""NamedBidict."""
keyname = get_keyname
valname = get_valname
@classmethod
def _inv_cls_dict_diff(cls) -> t.Dict[str, t.Any]:
base_diff = super()._inv_cls_dict_diff()
return {
**base_diff,
'keyname': get_valname,
'valname': get_keyname,
val_by_key_name: get_key_by_val,
key_by_val_name: get_val_by_key,
}
NamedInv = NamedBidict._inv_cls
assert NamedInv is not NamedBidict, 'namedbidict classes are not their own inverses'
setattr(NamedBidict, val_by_key_name, get_val_by_key)
setattr(NamedBidict, key_by_val_name, get_key_by_val)
NamedBidict.__name__ = NamedBidict.__qualname__ = typename
NamedInv.__name__ = NamedInv.__qualname__ = f'{typename}Inv'
NamedBidict.__doc__ = f'NamedBidict({basename}) {typename!r}: {keyname} -> {valname}'
NamedInv.__doc__ = f'NamedBidictInv({basename}) {typename!r}: {valname} -> {keyname}'
caller_module = _getframe(1).f_globals.get('__name__', '__main__')
NamedBidict.__module__ = NamedInv.__module__ = caller_module
return NamedBidict # pyright: ignore [reportUnknownVariableType]