front.util

Utils

front.util.annotate_func_arguments(func: Callable, *, ignore_existing_annot: bool = False, annot_for_argname: Union[Dict[str, Any], Iterable[Tuple[str, Any]]] = (), annot_for_dflt_type: Union[Dict[type, Any], Iterable[Tuple[type, Any]]] = (), dflt_annot: Any)[source]

Annotate

Parameters
  • func – The function whose args we want to annotate

  • ignore_existing_annot – Set to True to ignore existing annots.

  • annot_for_argname – Annotation for specific argnames

  • annot_for_dflt_type – Annotation for specific types. Arg defaults will be compared (with isinstance(dflt_val, types)) to types and the annotation (value) of the the first matching type (key) will be injected

  • dflt_annot – Default annotation to use if no match found earlier. The default is inspect.Parameter.empty, which means “don’t annotate”. If you want all your params to be annotated no matter what, you might consider typing.Any, or in the case of command line interfaces, str.

Returns

A wrapped function with the desired signature changes, if any changes need to be made, or the same function untouched if not.

>>> from inspect import signature
>>> from functools import partial
>>> from typing import Any
>>>
>>>
>>> def foo(a, b, c, aa: int=1, bb: int=1.0, cc: int=None, aaa=1, bbb=1.0, ccc=None):
...     pass
...

If nothing changes, you just get back the same function:

>>> assert str(signature(annotate_func_arguments(foo))) == (
...     "(a, b, c, "
...     "aa: int = 1, bb: int = 1.0, cc: int = None, "
...     "aaa=1, bbb=1.0, ccc=None)"
... )

In the following:

  • b: str through the argname rule, but bb (as well as aa and bb)

didn’t change because ignore_existing_annot=False by default.

  • aaa: float (even though default is 1) and ccc: 'NoneAnnot' because of

the annot_for_dflt_type rules.

>>> annotator = partial(
...     annotate_func_arguments,
...     annot_for_argname = {'b': str, 'bb': str},
...     # don't confuse following with dict(int=float), which means {'int': float}
...     annot_for_dflt_type = {int: float, type(None): 'NoneAnnot'},
... )
>>>
>>> wrapped_func = annotator(foo)
>>> assert str(signature(wrapped_func)) == (
... "(a, b: str, c, "
... "aa: int = 1, bb: int = 1.0, cc: int = None, "
... "aaa: float = 1, bbb=1.0, ccc: 'NoneAnnot' = None)"
... )

See in the following what happens if we ask the default annotation to be Any and ignore_existing_annot=True:

>>> another_annotator = partial(
...     annotator,  # use the previous one, but...
...     dflt_annot=Any,  # and specify a default annotation
...     ignore_existing_annot=True  # now ignore any existing annotations
... )
>>>
>>> wrapped_func = another_annotator(foo)
>>> assert str(signature(wrapped_func)) == (
... "(a: Any, b: str, c: Any, "
... "aa: float = 1, bb: str = 1.0, cc: 'NoneAnnot' = None, "
... "aaa: float = 1, bbb: Any = 1.0, ccc: 'NoneAnnot' = None)"
... )
front.util.deep_merge(a: Mapping, b: Mapping)[source]

Merges b into a

front.util.incremental_str_maker(str_format='{:03.f}')[source]

Make a function that will produce a (incrementally) new string at every call.

front.util.obj_name(func)[source]

The func.__name__ of a callable func, or makes and returns one if that fails. To make one, it calls unamed_func_name which produces incremental names to reduce the chances of clashing

front.util.subdict(d: Mapping, keys=None)[source]

Gets a sub-dict from a Mapping d, extracting only those keys that are both in keys and d. Note that the dict will be ordered as keys are, so can be used for reordering a Mapping.

>>> subdict({'a': 1, 'b': 2, 'c': 3, 'd': 4}, keys=['b', 'a', 'd'])
{'b': 2, 'a': 1, 'd': 4}