ju.util

Ju base utils.

Functions

apply(func, obj)

Calls a function to an object, returing the result

asis(obj)

display_dag_of_code(func, *args, **kwargs)

ensure_callable_mapper(mapper, *[, default])

Will return a CallableMapper from the specification of a Mapper, which could be a Mapping itself, a callable, or (key, value) pairs.

ensure_mapping_mapper(mapper, *[, default])

Will return a MappingMapper (a dict really) from the specification of a Mapper, which could be a Mapping itself, a callable, or (key, value) pairs.

feature_based_search(...)

Returns a function that searches for a feature in a list of feature_processor_pairs

feature_similarity_search(obj, *, ...[, ...])

Returns the output of a feature based on a featurizer and a list of feature_output_pairs

feature_switch(obj, *, featurizer, ...)

Returns the output of a feature based on a featurizer and a mapping

is_jsonable(x)

is_type(param, type_)

Checks if the type of a parameter's default value or its annotation matches a given type.

mk_function_returning(_obj)

resolve_annotations(func, parameters)

Returns parameters (a mapping or iterable of Parameter) with the string annotations of func evaluated -- all of them under from __future__ import annotations.

switch_case(mapping, default)

Returns a function that switches between cases based on a feature

truncate_dict_values(d, *[, max_list_size, ...])

Returns a new dictionary with the same nested keys structure, where:

unwrap_optional(annotation)

Strips None out of an Optional/union annotation; returns the annotation unchanged if it is not a union, and the single remaining member if there is one.

Classes

GetWithCaller(getter_func[, default, ...])

Implements the [.] operator with a backend caller.

Gettable(*args, **kwargs)

The missing type for objects that can be fetched from.

class ju.util.GetWithCaller(getter_func, default=Sentinel('NotSpecified'), handle_exceptions=(<class 'KeyError'>, ))[source]

Bases: object

Implements the [.] operator with a backend caller.

>>> g = GetWithCaller(lambda x: x + 1)
>>> g[4]
5
class ju.util.Gettable(*args, **kwargs)[source]

Bases: Protocol

The missing type for objects that can be fetched from. The contract is that we can fetch an element from obj with brackets: obj[k]. That is, obj has a __getitem__ method.

>>> isinstance(3, Gettable)  # 3 is not Gettable (can't do 3[...])
False

But dict, list, and str are Gettable:

>>> isinstance([1, 2, 3], Gettable)
True
>>> isinstance({'foo': 'bar'}, Gettable)
True
>>> isinstance('foo', Gettable)
True

Note that so are their types:

>>> all(isinstance(c, Gettable) for c in (list, dict, str))
True
ju.util.apply(func, obj)[source]

Calls a function to an object, returing the result

ju.util.ensure_callable_mapper(mapper, *, default=Sentinel('NotSpecified'))[source]

Will return a CallableMapper from the specification of a Mapper, which could be a Mapping itself, a callable, or (key, value) pairs.

Further, if default is specified, the mapper will return that default if the requested key is not found.

Return type:

Callable[[TypeVar(KT)], TypeVar(VT)]

>>> mapping = {'a': 1, 'b': 2}
>>> func = ensure_callable_mapper(mapping, default=3)
>>> func('a')
1
>>> func('c')
3
>>> items = [('a', 1), ('b', 2)]
>>> func = ensure_callable_mapper(items, default=3)
>>> func('a')
1
>>> func('c')
3
ju.util.ensure_mapping_mapper(mapper, *, default=Sentinel('NotSpecified'))[source]

Will return a MappingMapper (a dict really) from the specification of a Mapper, which could be a Mapping itself, a callable, or (key, value) pairs.

Further, if default is specified, the mapper will return that default if the requested key is not found.

Return type:

Mapping[TypeVar(KT), TypeVar(VT)]

>>> func = lambda k: {'a': 1, 'b': 2}[k]
>>> d = ensure_mapping_mapper(func, default=3)
>>> d['a']
1
>>> d['c']
3
>>> items = [('a', 1), ('b', 2)]
>>> d = ensure_mapping_mapper(items, default=3)
>>> d['a']
1
>>> d['c']
3

Returns a function that searches for a feature in a list of feature_processor_pairs

Returns the output of a feature based on a featurizer and a list of feature_output_pairs

ju.util.feature_switch(obj, *, featurizer, feature_to_output_mapping, default)[source]

Returns the output of a feature based on a featurizer and a mapping

ju.util.is_type(param, type_)[source]

Checks if the type of a parameter’s default value or its annotation matches a given type.

This function handles both regular types and subscripted generics.

Parameters:
Returns:

True if the parameter’s type matches the given type, False otherwise.

Return type:

bool

Doctests:

>>> from inspect import Parameter
>>> param = Parameter('p', Parameter.KEYWORD_ONLY, default=3.14)
>>> is_type(param, float)
True
>>> is_type(param, int)
False
>>> param = Parameter('p', Parameter.KEYWORD_ONLY, default=[1, 2, 3])
>>> is_type(param, list)
True
>>> from typing import List, Union
>>> is_type(param, List[int])
True
>>> is_type(param, List[str])
False
>>> is_type(param, Union[int, List[int]])
True

A parameterized generic built from an collections.abc type (e.g. Sequence[str]) is itself an instance of type – unlike typing generics such as List[str] – so it must still fall through to the __origin__-based branch below rather than being handed to isinstance(param.default, type_), which raises TypeError for a parameterized generic:

>>> from collections.abc import Sequence
>>> is_type(param, Sequence[int])
True
>>> is_type(param, Sequence[str])
False

An annotation alone is enough, bare or subscripted, optional or not – it matches type_ or any subclass of it:

>>> annotated = Parameter('p', Parameter.KEYWORD_ONLY, annotation=list[int])
>>> is_type(annotated, Sequence)
True
>>> is_type(annotated, list)
True
>>> is_type(annotated, List[int])
True
>>> is_type(annotated, List[str])
False
>>> is_type(annotated, str)
False
>>> optional = Parameter('p', Parameter.KEYWORD_ONLY, annotation=list | None)
>>> is_type(optional, Sequence)
True
ju.util.resolve_annotations(func, parameters)[source]

Returns parameters (a mapping or iterable of Parameter) with the string annotations of func evaluated – all of them under from __future__ import annotations. An annotation that can’t be resolved (e.g. a name only imported under TYPE_CHECKING) is left as it was.

>>> def f(a: "int", b: "list[float]" = None, c: "Nope" = 1): ...
>>> sig = inspect.signature(f)
>>> [(n, p.annotation) for n, p in resolve_annotations(f, sig.parameters).items()]
[('a', <class 'int'>), ('b', list[float]), ('c', 'Nope')]
ju.util.switch_case(mapping, default)[source]

Returns a function that switches between cases based on a feature

ju.util.truncate_dict_values(d, *, max_list_size=2, max_string_size=66, middle_marker='...')[source]

Returns a new dictionary with the same nested keys structure, where:

  • List values are reduced to a maximum size of max_list_size.

  • String values longer than max_string_size are truncated in the middle.

Parameters

d (dict): The input dictionary. max_list_size (int, optional): Maximum size for lists. Defaults to 2. max_string_size (int, optional): Maximum length for strings. Defaults to None (no truncation). middle_marker (str, optional): String to insert in the middle of truncated strings. Defaults to ‘…’.

Returns

dict: A new dictionary with truncated lists and strings.

This can be useful when you have a large dictionary that you want to investigate, but printing/logging it takes too much space.

Example

>>> large_dict = {'a': [1, 2, 3, 4, 5], 'b': {'c': [6, 7, 8, 9], 'd': 'A string like this that is too long'}, 'e': [10, 11]}
>>> truncate_dict_values(large_dict, max_list_size=3, max_string_size=20)
{'a': [1, 2, 3], 'b': {'c': [6, 7, 8], 'd': 'A string...too long'}, 'e': [10, 11]}

You can use None to indicate “no max”:

>>> assert (
...     truncate_dict_values(large_dict, max_list_size=None, max_string_size=None)
...     == large_dict
... )
Return type:

dict

ju.util.unwrap_optional(annotation)[source]

Strips None out of an Optional/union annotation; returns the annotation unchanged if it is not a union, and the single remaining member if there is one. Annotated and NewType wrappers are peeled off too (also on the members).

>>> from typing import Optional, Annotated, NewType
>>> unwrap_optional(Optional[list[int]])
list[int]
>>> unwrap_optional(int | None)
<class 'int'>
>>> unwrap_optional(int | str | None)
typing.Union[int, str]
>>> unwrap_optional(list[int])
list[int]
>>> unwrap_optional(Annotated[Optional[list[int]], "meta"])
list[int]
>>> unwrap_optional(Optional[NewType("UserId", int)])
<class 'int'>