Common Utilities¶
- deep_get(dictionary, deep_key, default=None, do_flatten=False, *, enable_undefined=False)[source]¶
Get a nested value from within a dictionary. Supports also nested lists, in which case the result is a a list of values
- Arguments:
- dictionary(dict):
the input
- deep_key(str):
nested key of the form aaa.bbb.ccc.ddd
- default:
the default value, in case the path does not exist
- do_flatten(bool): optional
flatten the outputs, in case the result is multiple outputs
- enable_undefined(bool): optional
if set, then keys that are not in the dictionary return Undefined. otherwise, returns None for undefined keys. Default is False.
- Returns:
the nested attribute(s) or the default value. For example:
For example:
example = {"a": {"b": [{"c": [None, {"d": [1]}]}, {"c": [None, {"d": [2]}, {"d": 3}]}, {"c": []}]}} assert deep_get(example, "a.b.c.d") == [[[1]], [[2], 3], []] assert deep_get(example, "a.b.c.d", do_flatten=True) == [1, 2, 3]
- nested(func, default=None)[source]¶
get a nested value if it exists, or return the given default if it doesn’t
- Arguments:
- func(function):
A function with no arguments that returns the nested value
- default:
the default value, in case the nested value does not exist
- Returns:
the nested attribute(s) or the default value. For example:
For example:
d = D(c=C(b=B(a=A(i=5)))) assert nested(lambda: d.c.b.a.i) == 5 d.c.foo = [1, 2, 3] assert nested(lambda: d.c.foo[100]) is None assert nested(lambda: d.c.foo[2]) == 3
- flatten(iterable, ignore_none=False) list[source]¶
Flatten an iterable completely, getting rid of None values
- Arguments:
- iterable(Iterable):
the input
- ignore_none(bool): optional
whether to skip None values or not. Default is False.
- Returns:
A flattened list
flatten( [ [[1]], [[2], 3, None, (5,)], [] ], ignore_none=True) == [1, 2, 3, 5]
- default_factories(func)[source]¶
A function decorator that allows to have default values that are generators of the actual default values to be used. This is useful when the default values are mutable, like dicts or lists
- Arguments:
- iterable(Iterable):
the input
- ignore_none(bool): optional
whether to skip None values or not. Default is False.
- Returns:
A flattened list
For example:
@default_factories def func(a, b = 0, c = list, d = dict): return a, b, c, d assert func(1) == 1, 0, [] , {}
Testing Utilities¶
- find_diff(first, second) dict | str[source]¶
Utility for testing to find the differences between two values that are “supposed” to be equal. This is useful to have more useful assertion error messages, especially with pytest, using pytest_assertrepr_compare.
- Arguments:
- first:
first value. Can be a Structure, list, dict, set
- second:
second value. Can be a Structure, list, dict, set
- Returns:
a dictionary with the differences, or the difference is trivial - a string. Note that this function does not employ any sophisticated algorithm and is meant just as a best-effort utility for testing.
Example of the output (taken from the unit tests):
actual = find_diff(bar2, bar1) assert actual == { "f": { "m['aaa']": {"age": "123 vs 12"}, "m": { "missing values": ["bbb"], "additional values": ["ccc"], }, }, "missing values": ["x"], }
Showing the Differences in Failed pytest Assertions¶
Typedpy includes a pytest hook that uses find_diff() to explain a failed assertion that two Structures are
equal. To enable it, import it in your conftest.py:
# conftest.py
from typedpy.testing import pytest_assertrepr_compare # noqa: F401
Then, a failing assert actual == expected between two Structures shows exactly what is different, including in
nested Structures and collections:
class Address(Structure):
city: str
zip_code: str
class Person(Structure):
name: str
age: int
address: Address
tags: list[str]
scores: Map[str, int]
def test_people_equal():
expected = Person(name="Joe", age=30, address=Address(city="NY", zip_code="10001"),
tags=["a", "b"], scores={"math": 90, "art": 80})
actual = Person(name="Joe", age=31, address=Address(city="NY", zip_code="10002"),
tags=["b", "a"], scores={"math": 90, "music": 70})
assert actual == expected
The failure message is:
found the following differences between the structures:
{"address": {"zip_code": "10002 vs 10001"}, "age": "31 vs 30", "scores": {"missing values": ["music"], "additional values": ["art"]}, "tags": {"different location": ["index 0 vs 1", "index 1 vs 0"]}}
Values that appear on the left side of the comparison but not on the right side are listed under “missing values”,
and values that appear on the right side but not on the left side are listed under “additional values”. In the
example above, “music” appears in actual but not in expected, so it is listed under “missing values”.
To print the differences as indented JSON, one entry per line, set:
# conftest.py
from typedpy.structures import TypedPyDefaults
TypedPyDefaults.pretty_print_pytest_diff = True
Then the failure message above becomes:
found the following differences between the structures:
{
"address": {
"zip_code": "10002 vs 10001"
},
"age": "31 vs 30",
"scores": {
"missing values": [
"music"
],
"additional values": [
"art"
]
},
"tags": {
"different location": [
"index 0 vs 1",
"index 1 vs 0"
]
}
}
Note that pytest truncates long assertion messages to about 8 lines, unless running with -vv, so the indented
format is most useful together with -vv.