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 +0 -0
- assertical/asserts/__init__.py +0 -0
- assertical/asserts/generator.py +19 -0
- assertical/asserts/pandas.py +77 -0
- assertical/asserts/time.py +43 -0
- assertical/asserts/type.py +46 -0
- assertical/fake/__init__.py +1 -0
- assertical/fake/asyncio.py +9 -0
- assertical/fake/generator.py +525 -0
- assertical/fake/http.py +219 -0
- assertical/fake/sqlalchemy.py +25 -0
- assertical/fixtures/__init__.py +1 -0
- assertical/fixtures/environment.py +57 -0
- assertical/fixtures/fastapi.py +89 -0
- assertical/fixtures/postgres.py +44 -0
- assertical/py.typed +0 -0
- assertical-0.0.1.dist-info/LICENSE.txt +22 -0
- assertical-0.0.1.dist-info/METADATA +318 -0
- assertical-0.0.1.dist-info/RECORD +21 -0
- assertical-0.0.1.dist-info/WHEEL +5 -0
- assertical-0.0.1.dist-info/top_level.txt +1 -0
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"""
|