Source code for pydrobert.speech.alias

# Copyright 2022 Sean Robertson

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Functionality to do with alias factories"""

import abc
from typing import Any, Mapping, Set, Type, TypeVar, Union, Type

__all__ = [
    "alias_factory_subclass_from_arg",
    "AliasedFactory",
]

T = TypeVar("T", bound="AliasedFactory", covariant=True)


[docs] class AliasedFactory(abc.ABC): """An abstract interface for initialing concrete subclasses with aliases""" aliases: Set[str] = set() """class aliases for :func:`from_alias`"""
[docs] @classmethod def from_alias(cls: Type[T], alias: str, *args, **kwargs) -> T: """Factory method for initializing a subclass that goes by an alias All subclasses of this class have the class attribute `aliases`. This method matches `alias` to an element in some subclass' `aliases` and initializes it. Aliases of this class are included in the search. Alias conflicts are resolved by always trying to initialize the last registered subclass that matches the alias. Parameters ---------- alias Alias of the subclass *args Positional arguments to initialize the subclass **kwargs Keyword arguments to initialize the subclass Raises ------ ValueError Alias can't be found """ stack = [cls] pushed_children = set() while stack: parent = stack.pop() if parent not in pushed_children: children = parent.__subclasses__() stack.append(parent) stack.extend(children) pushed_children.add(parent) elif alias in parent.aliases: return parent(*args, **kwargs) raise ValueError(f"Cannot find subclass with alias '{alias}'")
[docs] def alias_factory_subclass_from_arg( factory_class: Type[T], arg: Union[T, str, Mapping[str, Any]] ) -> T: """Boilerplate for getting an instance of an AliasedFactory Rather than an instance itself, a function could receive the arguments to initialize an :class:`AliasedFactory` with :func:`AliasedFactory.from_alias`. This function uses the following strategy to try and do so 1. If `arg` is an instance of `factory_class`, return `arg` 2. If `arg` is a :class:`str`, use it as the alias 3. a. Copy `arg` to a dictionary b. Pop the key :obj:`'alias'` and treat the rest as keyword arguments c. If the key :obj:`'alias'` is not found, try :obj:`'name'` This function is intentionally limited in order to work nicely with JSON config files. """ if isinstance(arg, factory_class): return arg elif isinstance(arg, str): return factory_class.from_alias(arg) else: arg = dict(arg) try: alias = arg.pop("alias") except KeyError: alias = arg.pop("name") return factory_class.from_alias(alias, **arg)