Source code for gemma._bearings

import re
from typing import (
    Any,
    TypeVar,
    Generic,
    Pattern,
    Union,
    Type,
    List,
    Tuple,
    Optional,
    Iterable,
    Dict,
)

from ._exceptions import NullNameError


_NameType = TypeVar("_NameType")
FactoryType = TypeVar("FactoryType")


[docs]class BearingAbstract(Generic[_NameType]): REGEX: Pattern = re.compile(".+") NAME_TYPES: List[Union[Type, Any]] = [str] def __new__( cls, name: Union[_NameType, "BearingAbstract[_NameType]"], *args: Iterable, factory: Optional[Type[FactoryType]] = None, **kwargs: dict, ) -> "BearingAbstract": if isinstance(name, BearingAbstract): name = name.name if not cls.is_compatible(name): raise TypeError(f"type {type(name)} not allowed as {cls}") new_bearing = object.__new__(cls) return new_bearing
[docs] def __init__( self, name: Union[_NameType, "BearingAbstract[_NameType]"], factory: Optional[Type[FactoryType]] = None, ): """ Abstract base class for :class:`Course` bearings. **inherits from:** ``Generic[_NameType]`` :param name: name of key/index/attribute/method/etc to act on. :param factory: ``type`` to be used as default value if :func:`Course.place` hits empty or non-existent value so structure can be built Class-level Attributes - **REGEX**: ( ``re.Pattern`` ) - Regex pattern to match string shorthand - **NAME_TYPES** ( ``List[Union[Type, Any]]`` ) - ``type`` ( or ``tuple`` of types ) that ``name`` can be. """ if isinstance(name, BearingAbstract): name = name.name self._name: _NameType = name self._factory: Optional[Type[FactoryType]] = factory
def __eq__(self, other: Any) -> bool: if not isinstance(other, BearingAbstract): raise TypeError("bearings cannot be compared to other types") if not other.name == self.name: return False if isinstance(other, Fallback) and type(self) in other.BEARING_CLASSES: return True if isinstance(self, Fallback) and type(other) in self.BEARING_CLASSES: return True if isinstance(other, type(self)): return True if isinstance(self, type(other)): return True else: return False def __lt__(self, other: "BearingAbstract") -> bool: return self._sort_key(self) < self._sort_key(other) def __le__(self, other: "BearingAbstract") -> bool: return self._sort_key(self) <= self._sort_key(other) def __gt__(self, other: "BearingAbstract") -> bool: return self._sort_key(self) > self._sort_key(other) def __ge__(self, other: "BearingAbstract") -> bool: return self._sort_key(self) >= self._sort_key(other) def __repr__(self) -> str: if isinstance(self.name, (int, float)): name_print = str(self.name) else: name_print = repr(self.name) name_print = f"<{type(self).__name__}: {name_print}" if self.factory_type is not None: name_print += f", factory={self.factory_type.__name__}" name_print += ">" return name_print def __str__(self) -> str: """:returns: string value for use in Data path string""" raise NotImplementedError @staticmethod def _sort_key(bearing_obj: "BearingAbstract") -> Tuple[int, str, str, Any]: """ Key that bearings are sorted by. Sorts: 1. By class: Item, Attr, Call, [Custom Implementation by name], Bearing 2. Bearing name type (alphabetical by class, ex: float, int, str) 3. value of bearing name """ try: type_value = TYPE_SORT_ORDER.index(type(bearing_obj)) except ValueError: type_value = TYPE_SORT_ORDER.index("other") return ( type_value, type(bearing_obj).__name__, type(bearing_obj.name).__name__, bearing_obj.name, ) @property def name(self) -> "_NameType": """ Read-only property. :return: ``name`` passed to ``__init__``. >>> from gemma import Attr >>> attribute = Attr('a') >>> attribute.name a """ return self._name @property def factory_type(self) -> Optional[Type[FactoryType]]: """ Read-only property. :return: ``factory`` passed to ``__init__``. >>> from gemma import Attr >>> attribute = Attr('a', factory=dict) >>> attribute.factory_type dict """ return self._factory
[docs] def init_factory(self) -> FactoryType: """ **MAY BE IMPLEMENTED** Returns an initialized version of :func:`BearingAbstract.factory`. :return: initialized data object :raises TypeError: is factory is not callable DEFAULT IMPLEMENTATION: initializes object with no parameters. >>> from gemma import Attr >>> attribute = Attr('a', factory=dict) >>> attribute.init_factory() {} """ if self.factory_type is None: raise TypeError("factory type is None") # There is a mypy bug here. This typing is correct. return self.factory_type() # type: ignore
[docs] def fetch(self, target: Any) -> Any: """ **MUST BE IMPLEMENTED** Fetches value from target object by :func:`BearingAbstract.name` :param target: object to fetch value from :return: value to be fetched :raises NullNameError: generic error when bearing cannot be found :raises TypeError: when ``target`` is wrong type for bearing See documentation of the default Bearing implementations for examples: - :func:`Attr.fetch` - :func:`Item.fetch` - :func:`Call.fetch` - :func:`Fallback.fetch` """ raise NotImplementedError
[docs] def place(self, target: Any, value: Any, **kwargs: dict) -> None: """ **MUST BE IMPLEMENTED** Sets ``value`` at :func:`BearingAbstract.name` of target object. :param target: target object to set value :param value: value to set :returns None: method should not return anything :raises NullNameError: :class:`NullNameError` should be raised if bearing cannot be placed :raises TypeError: When ``target`` is wrong type for Bearing See documentation of the default Bearing implementations for examples. - :func:`Attr.place` - :func:`Item.place` - :func:`Call.place` - :func:`Fallback.place` """ raise NotImplementedError
[docs] @classmethod def name_from_str(cls, text: str) -> Any: """ **MAY BE IMPLEMENTED** Casts string to appropriate type for ``name`` param of ``__init__``. :param text: text to cast :return: cast value. :raises ValueError: if ``text`` is not cast-able. DEFAULT IMPLEMENTATION: Tests if the ``text`` matches to the regex pattern in ``BearingAbstract.REGEX``, then extracts name from pattern. Raises ``ValueError`` if no match. """ match = re.match(cls.REGEX, text) if match is None: raise ValueError("text does not match regex") try: return match.group(1) except IndexError: return match.string
[docs] @classmethod def is_compatible(cls, name: Any) -> bool: """ **MAY BE IMPLEMENTED** Checks whether the ``name`` can be cast to current type. :param name: value to be cast :return: - ``True``: Can be cast. - ``False``: Cannot be cast DEFAULT IMPLEMENTATION: checks whether the type of ``name`` is in ``cls.NAME_TYPES``. Most Bearing implementations will not need to override this method, instead supplying a list of acceptable types to ``cls.NAME_TYPES``. However, some Bearings may depend on criteria other than type and override this method, possibly making ``cls.NAME_TYPES`` irrelevant. For example, a bearing used to fetch and set data from the ``dataclasses`` module might re-implement :func:`BearingAbstract.is_compatible` as a wrapper for ``dataclasses.is_dataclass()``. """ for kind in cls.NAME_TYPES: if kind is Any: return True elif isinstance(name, kind): return True return False
[docs]class Attr(BearingAbstract[str]): REGEX = re.compile("@(.+)") def __str__(self) -> str: return f"@{self.name}"
[docs] def fetch(self, target: Any) -> Any: """ Fetches attribute of target. :param target: Object to fetch attribute from :return: Value of attribute :raises NullNameError: if attribute does not exist Equivalent to: >>> getattr(target, self.name) Example: >>> from gemma import Attr >>> from dataclasses import dataclass >>> >>> @dataclass ... class Data: ... a: str = 'value' ... >>> data = Data() >>> Attr('a').fetch(data) 'value' If target does not have an attribute of bearing.name: >>> Attr('b').fetch(data) Traceback (most recent call last): ... gemma._exceptions.NullNameError: @b> """ try: return getattr(target, self.name) except AttributeError: raise NullNameError(str(self))
[docs] def place(self, target: Any, value: Any, **kwargs: dict) -> None: """ Sets Attribute of target to ``value``. :param target: Object to set attribute on :param value: Value to set on attribute :return: None :raises NullNameError: if attribute does not exist Equivalent to: >>> setattr(target, self.name, value) Example, using ``data`` from the fetch example above: >>> data.a 'value' >>> Attr('a').place(data, 'changed') >>> data.a 'changed' If target does not have an attribute of bearing.name: >>> Attr('b').place(data, 'changed again') Traceback (most recent call last): ... gemma._exceptions.NullNameError: @b Unlike ``setattr()``, :func:`Attr.place` cannot be used to declare arbitrary attributes. Non-existent attributes will raise a NullNameError. """ try: attr = getattr(target, self.name) except AttributeError: raise NullNameError(str(self)) if callable(attr): raise TypeError(f"values set through Attr cannot be callable") setattr(target, self.name, value)
[docs]class Item(BearingAbstract[Any]): REGEX = re.compile(r"\[(.+)\]") NAME_TYPES = [Any] def __str__(self) -> str: return f"[{str(self.name)}]"
[docs] def fetch(self, target: Any) -> Any: """ Fetches data at index or key of ``target`` :param target: object to fetch data from :return: value of index/key :raises NullNameError: if index/key does not exist :raises TypeError: if Target does not have a valid ``__getitem__`` method Fetch data from a list: >>> from gemma import Item >>> >>> data_list = ["zero", "one", "two"] >>> item = Item(0) >>> item.fetch(data_list) 'zero' Fetch data from a dict: >>> data_dict = {"a": "a value", "b": "b value"} >>> item = Item("b") >>> item.fetch(data_dict) 'b value' Fetching an Index / Item that does not exist raises :class:`NullNameError` >>> out_of_index = Item(5) >>> out_of_index.fetch(data_list) Traceback (most recent call last): ... gemma._exceptions.NullNameError: [5] >>> >>> bad_key = Item("c") >>> bad_key.fetch(data_dict) Traceback (most recent call last): ... gemma._exceptions.NullNameError: [c] An invalid name raises a :class:`NullNameError` regardless of whether the ``target`` object would normally raise a ``KeyError`` or ``IndexError``. Fetching from a ``target`` which does not support ``__getitem__`` raises a ``TypeError``. >>> no_get_item = int(1) >>> bad_key.fetch(no_get_item) Traceback (most recent call last): ... TypeError: 'int' object is not subscriptable """ try: return target[self.name] except (KeyError, IndexError): raise NullNameError(str(self))
[docs] def place(self, target: Any, value: Any, **kwargs: dict) -> None: """ Sets ``value`` at Index/Key of ``target`` :param target: object to set ``value`` on. :param value: value to set. :return: None :raises NullNameError: When Index/Key cannot be set :raises TypeError: When ``target`` does not support ``__setitem__`` Changing an existing dict key: >>> from gemma import Item >>> >>> data_dict = {"a": "a value", "b": "b value"} >>> existing_item = Item("b") >>> >>> existing_item.place(data_dict, "changed") >>> data_dict {'a': 'a value', 'b': 'changed'} Setting a new dict key: >>> new_item = Item("c") >>> new_item.place(data_dict, "new") >>> data_dict {'a': 'a value', 'b': 'changed', 'c': 'new'} Changing an existing list index: >>> data_list = ["zero", "one", "two"] >>> item = Item(0) >>> >>> item.place(data_list, "changed") >>> data_list ['changed', 'one', 'two'] Changing an index out of range does not result in :class:`NullNameError`, as it does with :func:`Item.fetch`. >>> out_of_index = Item(5) >>> out_of_index.place(data_list, "new value") >>> data_list ['changed', 'one', 'two', None, None, 'new value'] ``None`` is inserted in any missing indexes between the last existing index and the new index. Attempting to place a value on a ``target`` that does not support ``__setitem__`` raises a ``TypeError``: >>> data_tuple = ("zero", "one", "two") >>> cannot_set = Item(0) >>> >>> cannot_set.place(data_tuple, "changed") Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment If the object would normally raise a ``KeyError`` or ``IndexError``, it is cast to a :class:`NullNameError`. Let us create a dict class that raises a ``KeyError`` when attempting to set any key that is not present upon initialization: >>> class StrictDict(dict): ... def __setitem__(self, item, value): ... if not item in self: ... raise KeyError ... super().__setitem__(item, value) ... >>> strict = StrictDict({"a": "a value", "b": "b value"}) >>> strict["c"] = "changed" Traceback (most recent call last): ... KeyError :class:`Item` 's place method will cast the ``KeyError`` to a :class:`NullNameError`. >>> raises_key = Item("c") >>> raises_key.place(strict, "changed") Traceback (most recent call last): ... gemma._exceptions.NullNameError: [c] """ try: target[self.name] = value except KeyError: raise NullNameError(str(self)) except IndexError: if isinstance(target, List) and isinstance(self.name, int): for i in range(self.name + 1 - len(target)): target.append(None) target[self.name] = value return raise NullNameError(str(self))
[docs]class Call(BearingAbstract[str]): REGEX = re.compile(r"(.+?)\(\)") # we are going to use this to see when we need to sub out args for value VALUE_ARG = object()
[docs] def __init__( self, name: str, func_args: Optional[Iterable] = None, func_kwargs: Optional[Dict[str, Any]] = None, ): """ :param name: method name to act on :param func_args: ``*args`` to pass to method when fetching or placing :param func_kwargs: ``**kwargs`` to pass to method when fetching or placing Class Attributes: - **VALUE_ARG (** ``Any`` **):** object to act as placeholder in ``func_args`` and ``func_kwargs`` """ super().__init__(name) if func_args is None: func_args = tuple() else: func_args = tuple(func_args) if func_kwargs is None: func_kwargs = dict() self._func_args: tuple = func_args self._func_kwargs: dict = func_kwargs
def __str__(self) -> str: return f"{self.name}()"
[docs] def fetch(self, target: Any) -> Any: """ Fetches value from method of ``target`` :param target: Object to call method on. :return: return of method Getting the keys of a dict: >>> from gemma import Call >>> >>> data_dict = {"a": "a value", "b": "b value"} >>> method = Call("keys") >>> >>> method.fetch(data_dict) dict_keys(['a', 'b']) If the method does not exist, a :class:`NullNameError` is raised. >>> invalid_method = Call("does_not_exist") >>> invalid_method.fetch(data_dict) Traceback (most recent call last): ... gemma._exceptions.NullNameError: does_not_exist() When fetching a value, call will pass the ``func_args`` and ``func_kwargs`` passed to ``__init__`` as ``*args`` and ``*kwargs`` >>> data_list = ["repeat", "one", "two", "repeat"] >>> method = Call("index", func_args=("repeat", 1)) >>> method.fetch(data_list) 3 This is equivalent to >>> data_list.index("repeat", 1) 3 """ try: method = getattr(target, self.name) except AttributeError: raise NullNameError(str(self)) return method(*self._func_args, **self._func_kwargs)
def _replace_args(self, value: Any) -> Tuple[list, dict]: """ Replaces CALL.VALUE in self._func_args and kwargs with ``value``. :param value: :return: (*args, *kwargs) """ value_replaced = False kwargs = self._func_kwargs.copy() for key, arg in kwargs.items(): if arg is Call.VALUE_ARG: kwargs[key] = value value_replaced = True args = list() for arg in self._func_args: if arg is not Call.VALUE_ARG: args.append(arg) else: args.append(value) value_replaced = True if not value_replaced: args.insert(0, value) return args, kwargs
[docs] def place(self, target: Any, value: Any, **kwargs: dict) -> None: """ Places value with method of ``target``. :param target: Object to call method on :param value: value to pass :return: None By default, ``value`` is passed as the first argument to the given method. Appending value at end of list: >>> from gemma import Call >>> >>> data_list = ["zero", "one", "two"] >>> >>> adds_value = Call("append") >>> adds_value.place(data_list, "three") >>> >>> data_list ['zero', 'one', 'two', 'three'] Any additional args are passed as positional arguments, *after* ``value``. Let us create a list class with a method that will attempt to cast a value to a given type before appending it to the list: >>> class MultiArg(list): ... def cast_append(self, value, cast_type=None): ... try: ... value = cast_type(value) ... except (TypeError, ValueError): ... pass ... self.append(value) ... ... def args_reversed(self, cast_type=None, value=None): ... self.cast_append(value, cast_type) ... We can pass ``str`` to the ``cast_type`` parameter by setting it as the first additional argument to :class:`Call`. >>> data_list = MultiArg(('zero', 'one', 'two')) >>> data_list ['zero', 'one', 'two'] >>> casts_str = Call('cast_append', func_args=(str,)) >>> casts_str.place(data_list, 3) >>> data_list ['zero', 'one', 'two', '3'] This is equivalent to: >>> data_list.cast_append(3, str) We can also pass ``cast_type`` as a keyword argument. >>> casts_str = Call('cast_append', func_kwargs={'cast_type': str}) >>> casts_str.place(data_list, 4) >>> data_list ['zero', 'one', 'two', '3', '4'] For most setter methods, it is likely that the value will be the first argument passed. However, this is not always the case. Take ``list.insert()`` -- the first argument is the index where the value is inserted. >>> data_list = ["zero", "one", "two"] >>> data_list.insert(0, "new") >>> data_list ['new', 'zero', 'one', 'two'] In this case, if we want a bearing that places ``value`` at the head of a ``list``, we need to pass ``value`` as the *second* argument. We can use the ``Call.VALUE_ARG`` object to indicate that instead of being placed as the first argument, ``value`` should replace the ``Call.VALUE_ARG`` whenever a new value is passed to the bearing. >>> data_list = ["zero", "one", "two"] >>> >>> inserts_head = Call("insert", func_args=(0, Call.VALUE_ARG)) >>> inserts_head.place(data_list, "new") >>> >>> data_list ['new', 'zero', 'one', 'two'] This also works with ``**kwarg values``. Using the ``MultiArg``, class from above: >>> data_list = MultiArg(('zero', 'one', 'two')) >>> >>> kwargs = {'value': Call.VALUE_ARG, 'cast_type': str} >>> inserts_head = Call("args_reversed", func_kwargs=kwargs) >>> >>> inserts_head.place(data_list, 3) >>> inserts_head ['zero', 'one', 'two', '3'] """ try: method = getattr(target, self.name) except AttributeError: raise NullNameError(str(self)) args, kwargs = self._replace_args(value) method(*args, **kwargs)
_BEARING_CLASSES: List[Type[BearingAbstract]] = [Item, Call, Attr]
[docs]class Fallback(BearingAbstract[Any]): NAME_TYPES = [Any] BEARING_CLASSES: List[Type[BearingAbstract]] = _BEARING_CLASSES
[docs] def __init__(self, name: Any): """ Attempts to fetch or place data on a target using other bearing class' methods. :param name: name of bearing. **inherits from:** :class:`BearingAbstract` **name types:** ``Any``. **shorthand:** ``"name"`` Class Attributes: - **BEARING_CLASSES (** ``List[Type[BearingAbstract]]`` **):** bearing classes to cycle through when attempting to fetch or place data. The available methods and order of attempts is determined by the list of types in ``Fallback.BEARING_CLASSES``. This makes bearing less performant than invoking one of its test classes directly, but does make setting up :class:`Course` objects easier and less tedious. The performance hit may or may not be worth it depending on your workflow. The default ``BEARING_CLASSES`` are :class:`Item`, :class:`Call`, and :class:`Attr`, though that can be extended by inheriting this class Meant as a generic class when the bearing type is not well defined in a string ( Allows for more compact, generic :class:`Course` declarations ). """ super().__init__(name)
def __str__(self) -> str: return str(self.name)
[docs] def fetch(self, target: Any) -> Any: """ Attempts to fetch data from ``target`` object. :param target: object to fetch data from. :return: value Cycles through the classes in ``Fallback.BEARING_CLASSES``, casting the current Bearing to each type, and invoking their ``fetch()`` method. If the cast or fetch results in a :class:``NullNameError``, ``TypeError``, or ``ValueError``, the exception is caught and the next class is tried. By default, :class:`Fallback` cycles through ``fetch()`` on :class:`Item`, :class:`Attr`, and :class:`Call`, in that order. With the default classes, if an attribute exists, it will be fetched. >>> from gemma import Fallback >>> >>> class TestData: ... pass ... >>> test_data = TestData() >>> test_data.a = 'a value' >>> >>> to_fetch = Fallback('a') >>> to_fetch.fetch(test_data) 'a value' Same with an Index. >>> data_dict = {"a": "a dict"} >>> to_fetch.fetch(data_dict) 'a dict' Same with a method. >>> class FetchMethod: ... def a(self): ... return 'a method' ... >>> test_data = FetchMethod() >>> to_fetch.fetch(test_data) 'a method' When an name has multiple compatible bearings, it takes the first method's value that does not throw an exception. >>> class TwoValid(dict): ... def a(self): ... return 'a method' ... >>> data_dict = TwoValid({'a': 'a item'}) >>> to_fetch.fetch(data_dict) 'a item' Both ``Item('a')`` and ``Call('a')`` would return valid values -- ``"a method"`` and ``"a item"``, respectively -- but since :class:`Item` is tried first and gets a valid response, the key value is returned. """ for bearing_type in self.BEARING_CLASSES: try: cast_bearing = bearing_type(self) except TypeError: continue try: return cast_bearing.fetch(target) except (NullNameError, TypeError, ValueError): pass raise NullNameError(repr(self))
[docs] def place(self, target: Any, value: Any, **kwargs: dict) -> None: """ Attempts to set ``value`` on ``target``. :param target: Object to set ``value`` on. :param value: Value to set. :return: None Attempts to place data on a given name of ``target`` by cycling through the classes in ``Fallback.BEARING_CLASSES``, casting the current Bearing to each type, and invoking their ``place()`` method. If the cast or place results in a :class:`NullNameError`, ``TypeError``, or ``ValueError``, the exception is caught and the next class is tried. By default, :class:`Fallback` cycles through ``place()`` on :class:`Item`, :class:`Attr`, and :class:`Call`, in that order. With the default classes, if an attribute exists, it will be placed. >>> from gemma import Fallback >>> >>> class TestData: ... a = None ... >>> test_data = TestData() >>> >>> to_place = Fallback("a") >>> to_place.place(test_data, "a attr") >>> >>> test_data.a 'a attr' Same with an Index. >>> data_dict = dict() >>> to_place.place(data_dict, "a dict") >>> data_dict {'a': 'a dict'} Same with a method. >>> class FetchMethod: ... def a(self, value): ... self.a = value ... >>> test_data = FetchMethod() >>> to_place.place(test_data, "a method") >>> test_data.a 'a method' When an name has multiple compatible bearings, it takes the first method's value that does not throw an exception. >>> class TwoValid(dict): ... def a(self, value): ... self.a = value ... >>> data_dict = TwoValid() >>> to_place.place(data_dict, 'a value') >>> data_dict {'a': 'a value'} >>> data_dict.a <Item: 'a'> Both ``Item('a')`` and ``Call('a')`` would set valid values -- (to a key and attribute respectively) -- but since :class:`Item` is tried first and does not return an error, it is set to the dict's key rather than overriding it's ``a`` method. """ for bearing_type in self.BEARING_CLASSES: try: cast_bearing = bearing_type(self) except TypeError: continue try: cast_bearing.place(target, value) except (NullNameError, TypeError, ValueError): pass else: return raise NullNameError(repr(self))
def _order_bearing_classes( bearing_classes: Optional[List[Type[BearingAbstract]]] = None, bearing_classes_extra: Optional[List[Type[BearingAbstract]]] = None, ) -> List[Type[BearingAbstract]]: """combines bearing_classes and bearing_classes_extra""" if bearing_classes is None: bearing_classes = _BEARING_CLASSES + [Fallback] if bearing_classes_extra is None: bearing_classes_extra = list() bearing_classes = bearing_classes_extra + bearing_classes return bearing_classes def attempt_name_to_bearing( name: Any, bearing_type: Type[BearingAbstract] ) -> BearingAbstract: try: name = bearing_type.name_from_str(name) new = bearing_type(name) except TypeError: try: new = bearing_type(name) except TypeError: raise ValueError() else: return new else: return new
[docs]def bearing( name: Any, bearing_classes: Optional[List[Type[BearingAbstract]]] = None, bearing_classes_extra: Optional[List[Type[BearingAbstract]]] = None, ) -> BearingAbstract: """ Factory function for bearing classes. :param name: value for Bearing.name :param bearing_classes: list and order of bearing classes to attempt casting :param bearing_classes_extra: additional classes to add to bearing classes. Used to add to defaults when ``bearing_classes`` is set to ``None``. :return: Bearing object If ``name`` is a string, bearing will attempt to cast using each class' :func:`BearingAbstract.from_string` method. Otherwise, bearing attempts to cast to each class normally, passing ``name`` as a single argument to each ``__init__`` method. The default list of classes to attempt are: :class:`Item`, :class:`Call`, :class:`Attr`, and :class:`Fallback` -- in that order. Any bearing types passed to ``bearing_classes_extra`` are put *ahead* of the bearings in ``bearing_classes``. Casting formatted strings: >>> from gemma import bearing >>> >>> bearing("[item_name]") <Item: 'item_name'> >>> >>> bearing("@attr_name") <Attr: 'attr_name'> >>> >>> bearing("call_name()") <Call: 'call_name'> Passing a string which does not match any of the above class' string conventions will result in a generic :class:`Fallback`. >>> bearing("unknown_name") <Fallback: 'unknown_name'> This Fallback class will be loaded with the methods of the other classes, if ``bearing_classes`` includes custom Bearings, those class' methods will be added to the list. No need to make a custom subclass to extend the pool methods available to :class:`Fallback` when using this factory. Passing a non-string will always result in an :class:`Item` object with the default list, since :class:`Item` accepts all types, and is attempted first. >>> bearing(5) <Item: 5> >>> >>> bearing((5, 4)) <Item: (5, 4)> """ classes_loaded = _order_bearing_classes(bearing_classes, bearing_classes_extra) new = None for this_type in classes_loaded: try: new = attempt_name_to_bearing(name, this_type) except ValueError: continue else: break if new is None: raise TypeError # load fallback class with class types. if isinstance(new, Fallback): classes_loaded = [x for x in classes_loaded if not issubclass(x, Fallback)] new.BEARING_CLASSES = classes_loaded return new
TYPE_SORT_ORDER: List[Union[Type[BearingAbstract], str]] = [ Item, Attr, Call, "other", Fallback, ]