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.
@@ -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,7 @@
1
+ from frameworthy._errors import FrameworthyAssertionError
2
+ from frameworthy._expectation import expect
3
+
4
+ __all__ = [
5
+ "FrameworthyAssertionError",
6
+ "expect",
7
+ ]
@@ -0,0 +1,3 @@
1
+ # needs to subclass AssertionError to work with pytest
2
+ class FrameworthyAssertionError(AssertionError):
3
+ """Raised when a Frameworthy expectation is not satisfied"""
@@ -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