frameworthy 0.1.0__tar.gz
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.
- frameworthy-0.1.0/PKG-INFO +20 -0
- frameworthy-0.1.0/README.md +10 -0
- frameworthy-0.1.0/pyproject.toml +25 -0
- frameworthy-0.1.0/src/frameworthy/__init__.py +7 -0
- frameworthy-0.1.0/src/frameworthy/_errors.py +3 -0
- frameworthy-0.1.0/src/frameworthy/_expectation.py +85 -0
- frameworthy-0.1.0/src/frameworthy/py.typed +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: frameworthy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: joypauls
|
|
6
|
+
Author-email: joypauls <joypaulsen3@gmail.com>
|
|
7
|
+
Requires-Dist: narwhals>=2.25.0
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# frameworthy [WIP]
|
|
12
|
+
|
|
13
|
+
Frameworthy is a lightweight Python testing library for DataFrames and analytical transformations, supporting both pandas and polars. Instead of requiring exact expected outputs, it lets you express the properties a transformation should preserve or change: rows, keys, columns, values, aggregates, and more.
|
|
14
|
+
|
|
15
|
+
Many DataFrame tests never get written because verifying a transformation means constructing a second expected dataset. The goal of Frameworthy is to make it easy to test the guarantees you actually care about.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frameworthy [WIP]
|
|
2
|
+
|
|
3
|
+
Frameworthy is a lightweight Python testing library for DataFrames and analytical transformations, supporting both pandas and polars. Instead of requiring exact expected outputs, it lets you express the properties a transformation should preserve or change: rows, keys, columns, values, aggregates, and more.
|
|
4
|
+
|
|
5
|
+
Many DataFrame tests never get written because verifying a transformation means constructing a second expected dataset. The goal of Frameworthy is to make it easy to test the guarantees you actually care about.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "frameworthy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "joypauls", email = "joypaulsen3@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"narwhals>=2.25.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["uv_build>=0.11.3,<0.12.0"]
|
|
16
|
+
build-backend = "uv_build"
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = [
|
|
20
|
+
"pandas>=3.0.5",
|
|
21
|
+
"polars>=1.44.0",
|
|
22
|
+
"pyright>=1.1.411",
|
|
23
|
+
"pytest>=9.1.1",
|
|
24
|
+
"ruff>=0.16.4",
|
|
25
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import narwhals.stable.v2 as nw
|
|
4
|
+
from narwhals.stable.v2.typing import IntoDataFrame
|
|
5
|
+
|
|
6
|
+
from frameworthy._errors import FrameworthyAssertionError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TransformationExpectation:
|
|
10
|
+
"""Assertions about the relationship between two DataFrame states"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
after: IntoDataFrame,
|
|
15
|
+
*,
|
|
16
|
+
relative_to: IntoDataFrame,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._before = nw.from_native(relative_to, eager_only=True)
|
|
19
|
+
self._after = nw.from_native(after, eager_only=True)
|
|
20
|
+
|
|
21
|
+
# should we store the native frames too?
|
|
22
|
+
# self._before_native = relative_to
|
|
23
|
+
# self._after_native = after
|
|
24
|
+
|
|
25
|
+
def preserves_rows(self) -> None:
|
|
26
|
+
"""Assert that the transformation preserves row count"""
|
|
27
|
+
before_rows = self._before.shape[0]
|
|
28
|
+
after_rows = self._after.shape[0]
|
|
29
|
+
|
|
30
|
+
if before_rows == after_rows:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
difference = after_rows - before_rows
|
|
34
|
+
|
|
35
|
+
raise FrameworthyAssertionError(
|
|
36
|
+
format_row_count_failure(
|
|
37
|
+
before_rows=before_rows,
|
|
38
|
+
after_rows=after_rows,
|
|
39
|
+
difference=difference,
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def expect(
|
|
45
|
+
after: IntoDataFrame,
|
|
46
|
+
*,
|
|
47
|
+
relative_to: IntoDataFrame,
|
|
48
|
+
) -> TransformationExpectation:
|
|
49
|
+
"""Create expectations about a DataFrame relative to an earlier state"""
|
|
50
|
+
return TransformationExpectation(
|
|
51
|
+
after,
|
|
52
|
+
relative_to=relative_to,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def format_row_count_failure(
|
|
57
|
+
*,
|
|
58
|
+
before_rows: int,
|
|
59
|
+
after_rows: int,
|
|
60
|
+
difference: int,
|
|
61
|
+
) -> str:
|
|
62
|
+
if before_rows == 0:
|
|
63
|
+
percentage = None
|
|
64
|
+
else:
|
|
65
|
+
percentage = difference / before_rows
|
|
66
|
+
|
|
67
|
+
direction = "introduced" if difference > 0 else "removed"
|
|
68
|
+
absolute_difference = abs(difference)
|
|
69
|
+
|
|
70
|
+
if percentage is None:
|
|
71
|
+
change_display = f"{difference:+,} rows"
|
|
72
|
+
else:
|
|
73
|
+
change_display = f"{difference:+,} rows ({percentage:+.2%})"
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
"Expected transformation to preserve row count.\n"
|
|
77
|
+
"\n"
|
|
78
|
+
f"before {before_rows:,} rows\n"
|
|
79
|
+
f"after {after_rows:,} rows\n"
|
|
80
|
+
f"change {change_display}\n"
|
|
81
|
+
"\n"
|
|
82
|
+
f"The transformation {direction} "
|
|
83
|
+
f"{absolute_difference:,} "
|
|
84
|
+
f"{'row' if absolute_difference == 1 else 'rows'}."
|
|
85
|
+
)
|
|
File without changes
|