assertical 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
assertical/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,19 @@
1
+ from typing import Any, Optional
2
+
3
+ from assertical.fake.generator import check_class_instance_equality
4
+
5
+
6
+ def assert_class_instance_equality(
7
+ t: type,
8
+ expected: Any,
9
+ actual: Any,
10
+ ignored_properties: Optional[set[str]] = None,
11
+ ) -> None:
12
+ """Given a type t and two instances. Run through the public members of t and assert that the values all match up.
13
+ This will only compare properties whose type passes is_generatable_type.
14
+
15
+ Any "private" members beginning with '_' will be skipped
16
+
17
+ ignored properties are a set of property names that will NOT be asserted for equality"""
18
+ errors = check_class_instance_equality(t, expected, actual, ignored_properties)
19
+ assert len(errors) == 0, "\n".join(errors)
@@ -0,0 +1,77 @@
1
+ from typing import Any, Optional, Union
2
+
3
+ try:
4
+ import pandas as pd
5
+ except ImportError:
6
+ pd = None # type: ignore
7
+
8
+
9
+ def assert_dataframe(
10
+ df: Any, assert_has_data: Optional[bool] = None, index: Optional[Union[list[str], str]] = None
11
+ ) -> None:
12
+ """Asserts that an unknown object is a DataFrame and optionally whether it's empty or not
13
+
14
+ assert_has_data: If None - no assertion, If True, assert df has >0 rows If False, assert df has 0 rows
15
+ index: If set - asserts that the index on df is on the specified column names (as a list or single)
16
+ """
17
+ assert pd is not None, "Install assertical[pandas]"
18
+ assert df is not None, "dataframe is None"
19
+ assert type(df) == pd.DataFrame, f"df type is {type(df)} instead of {type(pd.DataFrame)}" # noqa: E721
20
+
21
+ if assert_has_data is True:
22
+ assert len(df.index) != 0, "assert_has_data is True and the dataframe is empty"
23
+ elif assert_has_data is False:
24
+ assert len(df.index) == 0, "assert_has_data is False and the dataframe has data"
25
+
26
+ if index is not None:
27
+ if isinstance(index, str):
28
+ index = [index]
29
+
30
+ actual_index = None
31
+ if hasattr(df.index, "name"):
32
+ actual_index = [df.index.name]
33
+ else:
34
+ actual_index = df.index.names
35
+ assert index == actual_index
36
+
37
+
38
+ def assert_dataframe_contains(
39
+ df: Any, col_values: dict[str, Any], expected_min_count: Optional[int] = 1, expected_max_count: Optional[int] = None
40
+ ) -> None:
41
+ """Asserts that the dataframe contains at least 1 row with the specified column values (comparison on ==)
42
+
43
+ Ranges of valid counts can be additionally asserted by setting expected_min_count and expected_max_count (None
44
+ will mean unbounded)
45
+
46
+ Other column values will NOT be considered"""
47
+ assert pd is not None, "Install assertical[pandas]"
48
+
49
+ def print_val(v: Any) -> str:
50
+ if v is None:
51
+ return "NONE"
52
+ elif type(v) == str: # noqa: E721
53
+ return f"'{v}'"
54
+ elif v < 0:
55
+ return f"(0-{-v})" # workaround https://github.com/pandas-dev/pandas/issues/16363
56
+ else:
57
+ return str(v)
58
+
59
+ if len(col_values) == 0:
60
+ query = "N/A"
61
+ count = len(df.index)
62
+ else:
63
+ query = " & ".join([f"`{k}`=={print_val(v)}" for k, v in col_values.items()])
64
+ try:
65
+ count = len(df.query(query))
66
+ except Exception:
67
+ raise AssertionError(f"Column(s) don't exist. col_values: {col_values}")
68
+
69
+ if expected_min_count is not None:
70
+ assert (
71
+ count >= expected_min_count
72
+ ), f"Expected at least {expected_min_count} match(es) for {query}\n{df[list(col_values.keys())].to_string()}"
73
+
74
+ if expected_max_count is not None:
75
+ assert (
76
+ count <= expected_max_count
77
+ ), f"Expected at most {expected_max_count} match(es) for {query}\n{df[list(col_values.keys())].to_string()}"
@@ -0,0 +1,43 @@
1
+ from datetime import datetime, timezone
2
+ from typing import Optional, Union
3
+
4
+
5
+ def assert_fuzzy_datetime_match(
6
+ expected_time: Union[int, float, datetime], actual_time: Union[int, float, datetime], fuzziness_seconds: int = 2
7
+ ) -> None:
8
+ """Asserts that two datetimes are within fuzziness_seconds of each other. If the times are numbers then they
9
+ will be interpreted as a timestamp"""
10
+ if not isinstance(expected_time, datetime):
11
+ expected_time = datetime.fromtimestamp(float(expected_time))
12
+
13
+ if not isinstance(actual_time, datetime):
14
+ actual_time = datetime.fromtimestamp(float(actual_time))
15
+
16
+ delta_seconds = expected_time.timestamp() - actual_time.timestamp()
17
+ assert (
18
+ abs(delta_seconds) <= fuzziness_seconds
19
+ ), f"Expected {expected_time} to be within {fuzziness_seconds} of {actual_time} but it was {delta_seconds}"
20
+
21
+
22
+ def assert_nowish(expected_time: Union[int, float, datetime], fuzziness_seconds: int = 20) -> None:
23
+ """Asserts that datetime is within fuzziness_seconds of now. Number values will be interpreted as a timestamp"""
24
+
25
+ if not isinstance(expected_time, datetime) or expected_time.tzinfo is None:
26
+ now = datetime.now()
27
+ else:
28
+ now = datetime.now(timezone.utc)
29
+
30
+ assert_fuzzy_datetime_match(expected_time, now, fuzziness_seconds=fuzziness_seconds)
31
+
32
+
33
+ def assert_datetime_equal(a: Optional[Union[datetime, int, float]], b: Optional[Union[datetime, int, float]]) -> None:
34
+ """Asserts datetime equality based on timestamp (handles None too). If the times are numbers then they
35
+ will be interpreted as a timestamp"""
36
+ if a is None or b is None:
37
+ assert a is None and b is None
38
+ else:
39
+ if not isinstance(a, datetime):
40
+ a = datetime.fromtimestamp(float(a))
41
+ if not isinstance(b, datetime):
42
+ b = datetime.fromtimestamp(float(b))
43
+ assert a.timestamp() == b.timestamp(), f"Comparing {a} ({a.timestamp()}) to {b} ({b.timestamp()})"
@@ -0,0 +1,46 @@
1
+ from typing import Any, Optional, get_origin
2
+
3
+
4
+ def assert_list_type(expected_element_type: type, obj: Any, count: Optional[int] = None) -> None:
5
+ """Asserts that obj is not None, is a list and every element is expected_element_type
6
+
7
+ if count is specified - an additional assert will be made on the count of elements in obj"""
8
+ assert obj is not None
9
+ assert (
10
+ isinstance(obj, list) or get_origin(type(obj)) == list
11
+ ), f"Expected a list type for obj but got {type(obj)} instead"
12
+ assert_iterable_type(expected_element_type, obj, count=count)
13
+
14
+
15
+ def assert_dict_type(expected_key_type: type, expected_value_type: type, obj: Any, count: Optional[int] = None) -> None:
16
+ """Asserts that obj is not None, is a dict and every key is expected_key_type and every value is expected_value_type
17
+
18
+ if count is specified - an additional assert will be made on the count of elements in obj"""
19
+ assert obj is not None
20
+ assert (
21
+ isinstance(obj, dict) or get_origin(type(obj)) == dict
22
+ ), f"Expected a dict type for obj but got {type(obj)} instead"
23
+ assert_iterable_type(expected_key_type, obj.keys(), count=count)
24
+ assert_iterable_type(expected_value_type, obj.values(), count=count)
25
+
26
+
27
+ def assert_iterable_type(expected_element_type: type, obj: Any, count: Optional[int] = None) -> None:
28
+ """Asserts that obj is not None, is iterable and every element is expected_element_type
29
+
30
+ if count is specified - an additional assert will be made on the count of elements in obj"""
31
+ assert obj is not None
32
+
33
+ try:
34
+ iter(obj)
35
+ except TypeError as ex:
36
+ assert False, f"Expected {type(obj)} to be iterable but calling iter(obj) raises {ex}"
37
+
38
+ enumerated_item_count = 0
39
+ for i, val in enumerate(obj):
40
+ enumerated_item_count += 1
41
+ assert isinstance(
42
+ val, expected_element_type
43
+ ), f"obj[{i}]: Element has type {type(val)} instead of {expected_element_type}"
44
+
45
+ if count is not None:
46
+ assert enumerated_item_count == count
@@ -0,0 +1 @@
1
+ """Contains utilities for generating fake data"""
@@ -0,0 +1,9 @@
1
+ from asyncio import Future
2
+ from typing import Any
3
+
4
+
5
+ def create_async_result(result: Any) -> Future:
6
+ """Creates an awaitable result (as a Future) that will return immediately"""
7
+ f: Future = Future()
8
+ f.set_result(result)
9
+ return f