dataframe-mutator 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.
- dataframe_mutator-0.1.0/LICENSE +21 -0
- dataframe_mutator-0.1.0/PKG-INFO +613 -0
- dataframe_mutator-0.1.0/README.md +569 -0
- dataframe_mutator-0.1.0/dataframe_mutator.egg-info/PKG-INFO +613 -0
- dataframe_mutator-0.1.0/dataframe_mutator.egg-info/SOURCES.txt +12 -0
- dataframe_mutator-0.1.0/dataframe_mutator.egg-info/dependency_links.txt +1 -0
- dataframe_mutator-0.1.0/dataframe_mutator.egg-info/requires.txt +20 -0
- dataframe_mutator-0.1.0/dataframe_mutator.egg-info/top_level.txt +1 -0
- dataframe_mutator-0.1.0/pyproject.toml +75 -0
- dataframe_mutator-0.1.0/setup.cfg +4 -0
- dataframe_mutator-0.1.0/src/dataframe_mutator/__init__.py +7 -0
- dataframe_mutator-0.1.0/tests/test_polars_100_coverage.py +640 -0
- dataframe_mutator-0.1.0/tests/test_polars_new_operators.py +398 -0
- dataframe_mutator-0.1.0/tests/test_polars_operators.py +414 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dataframe Mutator Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataframe-mutator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-grade mutation testing for Polars dataframes. Validate test suite quality by detecting which mutations your tests catch.
|
|
5
|
+
Author-email: Dataframe Mutator Contributors <suhrusai@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/suhrusai/dataframe-mutator
|
|
8
|
+
Project-URL: Documentation, https://github.com/suhrusai/dataframe-mutator#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/suhrusai/dataframe-mutator.git
|
|
10
|
+
Project-URL: Issues, https://github.com/suhrusai/dataframe-mutator/issues
|
|
11
|
+
Keywords: mutation-testing,polars,dataframe,testing,test-quality,data-pipeline
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: mutmut>=2.4.0
|
|
29
|
+
Provides-Extra: polars
|
|
30
|
+
Requires-Dist: polars>=0.19.0; extra == "polars"
|
|
31
|
+
Provides-Extra: pyspark
|
|
32
|
+
Requires-Dist: pyspark>=3.0.0; extra == "pyspark"
|
|
33
|
+
Provides-Extra: pandas
|
|
34
|
+
Requires-Dist: pandas>=1.0.0; extra == "pandas"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: dataframe-mutator[dev,pandas,polars,pyspark]; extra == "all"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# ๐งฌ dataframe-mutator
|
|
46
|
+
|
|
47
|
+
**Production-grade mutation testing for Polars dataframes.** Validate your test suite quality by automatically detecting which mutations (logic bugs) your tests actually catch.
|
|
48
|
+
|
|
49
|
+
> **Mutation testing** runs your tests against intentionally mutated code. If tests pass despite the mutation, your test is weak. This framework makes it easy to find gaps in data pipeline test coverage.
|
|
50
|
+
|
|
51
|
+
[]()
|
|
52
|
+
[]()
|
|
53
|
+
[]()
|
|
54
|
+
[]()
|
|
55
|
+
[]()
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## โก Quick Start (2 minutes)
|
|
60
|
+
|
|
61
|
+
### Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install dataframe-mutator[polars]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Basic Usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import polars as pl
|
|
71
|
+
from dataframe_mutator.polars import SmartPolarsTestRunner
|
|
72
|
+
|
|
73
|
+
# Your data pipeline
|
|
74
|
+
def process_sales(df: pl.DataFrame) -> pl.DataFrame:
|
|
75
|
+
return (
|
|
76
|
+
df
|
|
77
|
+
.filter(pl.col("amount") > 100) # โ Tests should catch mutations here
|
|
78
|
+
.group_by("region")
|
|
79
|
+
.agg(pl.col("amount").sum())
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Run mutation testing
|
|
83
|
+
tester = SmartPolarsTestRunner(
|
|
84
|
+
test_command="pytest tests/test_pipeline.py"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
results = tester.analyze_mutation_efficiency("pipeline.py")
|
|
88
|
+
print(f"High-value mutations: {results['high_value_mutations']}")
|
|
89
|
+
print(f"False positives avoided: {results['potential_false_positives_avoided']:.1f}%")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Output:**
|
|
93
|
+
```
|
|
94
|
+
High-value mutations: 12
|
|
95
|
+
False positives avoided: 98.5%
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## ๐ฏ Features
|
|
101
|
+
|
|
102
|
+
### โ
109 Production Operators (100% of Core Polars API) ๐
|
|
103
|
+
**Complete coverage** of all major Polars operations:
|
|
104
|
+
|
|
105
|
+
**Categories (100% Complete):**
|
|
106
|
+
- **Filtering & Selection (12/12)** - filter, select, exclude, nth, filter_by_dtypes, select_by_dtype, head, tail, slice, limit, gather, where
|
|
107
|
+
- **Aggregations (15/15)** - sum, mean, min, max, std, var, median, mode, skew, kurtosis, count, unique, cum_sum, cum_prod, cum_count
|
|
108
|
+
- **Joins (7/7)** - inner, left, right, outer, cross, semi, anti, asof_join
|
|
109
|
+
- **Null Handling (10/10)** - drop_nulls, fill_null, fill_nan, is_null, is_not_null, coalesce, interpolate, compact, forward_fill, backward_fill
|
|
110
|
+
- **Strings (14/14)** - case conversion, trim, replace, contains, starts_with, ends_with, split, extract, pad, zfill, slice, concat, to_date, to_datetime, to_integer, to_float
|
|
111
|
+
- **Lists (8/8)** - explosion, length, contains, join, reverse, min, max, unique, sort, sum, mean
|
|
112
|
+
- **DateTime (8/8)** - year, month, day, hour, truncate, extract operations
|
|
113
|
+
- **Numerics (12/12)** - floor, ceil, round, abs, sqrt, clip, arithmetic operators, type casting
|
|
114
|
+
- **Structured (9/9)** - with_columns, drop, rename, select, melt, pivot, unpivot, unnest, concat
|
|
115
|
+
- **Sorting (5/5)** - sort, reverse, arg_sort, arg_max, arg_min, sort_by_exprs
|
|
116
|
+
- **Window Functions (15/15)** - over, partition_by, rolling, shift, quantile, sample, value_counts, n_unique, rank, density_rank, with_context, group_by_dynamic
|
|
117
|
+
- **I/O (5/5)** - read_csv, read_parquet, read_json, write_csv, write_parquet
|
|
118
|
+
- **Metadata (7/7)** - dtypes, columns, schema, shape, describe, info, null_count
|
|
119
|
+
- **Advanced (15/15)** - when/then, is_in, is_not_in, fold, reduce, apply, cache, lazy, collect, fetch, scan, item, row, rows, distinct with maintain_order
|
|
120
|
+
- **Type Ops (3/3)** - dtype checking and type casting
|
|
121
|
+
|
|
122
|
+
**[See detailed coverage status โ](docs/POLARS_FUNCTION_COVERAGE.md)**
|
|
123
|
+
|
|
124
|
+
### ๐ง Smart Analysis (No False Positives)
|
|
125
|
+
- **AST-aware filtering** - Knows Polars semantics
|
|
126
|
+
- **Avoids column name mutations** - Skips ColumnNotFoundError traps
|
|
127
|
+
- **High-value only** - Focus on semantic logic bugs
|
|
128
|
+
- **99%+ false-positive elimination** - 6-10x faster testing
|
|
129
|
+
|
|
130
|
+
### ๐ Real-World Insights
|
|
131
|
+
Test for actual data science bugs:
|
|
132
|
+
- โ
Wrong boundaries (> vs >=, off-by-one)
|
|
133
|
+
- โ
Wrong aggregation (sum vs mean)
|
|
134
|
+
- โ
Data loss in joins (inner vs left)
|
|
135
|
+
- โ
Null handling mistakes
|
|
136
|
+
- โ
Boolean logic errors (& vs |)
|
|
137
|
+
- โ
Lazy evaluation bugs
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## ๐ Usage Patterns
|
|
142
|
+
|
|
143
|
+
### Pattern 1: Quick Test Quality Check
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from dataframe_mutator.polars import SmartPolarsTestRunner
|
|
147
|
+
|
|
148
|
+
tester = SmartPolarsTestRunner(test_command="pytest tests/")
|
|
149
|
+
results = tester.analyze_mutation_efficiency("src/pipeline.py")
|
|
150
|
+
|
|
151
|
+
# Check if test suite is strong
|
|
152
|
+
if results['high_value_mutations'] > 50:
|
|
153
|
+
print("โ
Good mutation coverage")
|
|
154
|
+
else:
|
|
155
|
+
print("โ ๏ธ Weak test suite - need more tests")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Pattern 2: Full Mutation Testing Run
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from dataframe_mutator.polars import get_all_polars_operators
|
|
162
|
+
|
|
163
|
+
# Use all 43+ operators (including basic ones)
|
|
164
|
+
tester = SmartPolarsTestRunner(
|
|
165
|
+
operators=get_all_polars_operators(),
|
|
166
|
+
test_command="pytest tests/",
|
|
167
|
+
skip_low_value_mutations=True # Smart filtering ON
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Run against your pipeline
|
|
171
|
+
results = tester.mutate_and_test("src/pipeline.py")
|
|
172
|
+
summary = tester.get_summary()
|
|
173
|
+
|
|
174
|
+
print(f"Mutation score: {summary['survival_rate']:.1f}%")
|
|
175
|
+
# 90%+ = Excellent test coverage
|
|
176
|
+
# 70-89% = Good coverage
|
|
177
|
+
# 50-69% = Fair - add more tests
|
|
178
|
+
# <50% = Weak - significant gaps
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Pattern 3: Selective Testing (High-Value Only)
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# Focus on risky operations: joins, nulls, aggregations
|
|
185
|
+
from dataframe_mutator.polars import (
|
|
186
|
+
SmartPolarsTestRunner,
|
|
187
|
+
PolarsJoinMutation,
|
|
188
|
+
PolarsAggregationMutation,
|
|
189
|
+
PolarsFillNullMutation,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
tester = SmartPolarsTestRunner(
|
|
193
|
+
operators=[
|
|
194
|
+
PolarsJoinMutation,
|
|
195
|
+
PolarsAggregationMutation,
|
|
196
|
+
PolarsFillNullMutation,
|
|
197
|
+
],
|
|
198
|
+
test_command="pytest tests/"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
results = tester.mutate_and_test("src/pipeline.py")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Pattern 4: Validate Semantic Changes
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from dataframe_mutator.polars import PolarsSemanticMutationValidator
|
|
208
|
+
|
|
209
|
+
validator = PolarsSemanticMutationValidator()
|
|
210
|
+
|
|
211
|
+
original = 'df.filter(pl.col("age") > 18)'
|
|
212
|
+
mutated = 'df.filter(pl.col("age") < 18)'
|
|
213
|
+
|
|
214
|
+
# Ensure this is a real logic change, not a false positive
|
|
215
|
+
if validator.is_semantic_mutation(original, mutated):
|
|
216
|
+
category = validator.categorize_mutation(original, mutated)
|
|
217
|
+
print(f"This is a {category} mutation - tests should catch it!")
|
|
218
|
+
# Output: "This is a comparison_flip mutation - tests should catch it!"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## ๐งช Real-World Example
|
|
224
|
+
|
|
225
|
+
### Your Pipeline
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# src/sales_pipeline.py
|
|
229
|
+
import polars as pl
|
|
230
|
+
|
|
231
|
+
def process_sales_data(df: pl.DataFrame) -> pl.DataFrame:
|
|
232
|
+
"""Process sales and return customer summaries."""
|
|
233
|
+
return (
|
|
234
|
+
df
|
|
235
|
+
.filter(pl.col("sale_amount") > 0) # Only valid sales
|
|
236
|
+
.filter(pl.col("date") >= "2024-01-01") # Recent only
|
|
237
|
+
.group_by("customer_id")
|
|
238
|
+
.agg([
|
|
239
|
+
pl.col("sale_amount").sum().alias("total_spent"),
|
|
240
|
+
pl.col("sale_amount").count().alias("purchase_count"),
|
|
241
|
+
])
|
|
242
|
+
.filter(pl.col("total_spent") >= 100) # Qualified customers
|
|
243
|
+
.sort("total_spent", descending=True)
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Your Tests
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
# tests/test_sales_pipeline.py
|
|
251
|
+
import pytest
|
|
252
|
+
import polars as pl
|
|
253
|
+
from sales_pipeline import process_sales_data
|
|
254
|
+
|
|
255
|
+
def test_filters_zero_amounts():
|
|
256
|
+
"""Ensure zero/negative sales are excluded."""
|
|
257
|
+
df = pl.DataFrame({
|
|
258
|
+
"customer_id": [1, 2, 3],
|
|
259
|
+
"sale_amount": [100, 0, -50],
|
|
260
|
+
"date": ["2024-01-01", "2024-01-01", "2024-01-01"],
|
|
261
|
+
})
|
|
262
|
+
result = process_sales_data(df)
|
|
263
|
+
assert len(result) == 1
|
|
264
|
+
assert result["customer_id"][0] == 1
|
|
265
|
+
|
|
266
|
+
def test_filters_recent_only():
|
|
267
|
+
"""Ensure old sales are excluded."""
|
|
268
|
+
df = pl.DataFrame({
|
|
269
|
+
"customer_id": [1, 2],
|
|
270
|
+
"sale_amount": [100, 200],
|
|
271
|
+
"date": ["2023-12-31", "2024-01-01"],
|
|
272
|
+
})
|
|
273
|
+
result = process_sales_data(df)
|
|
274
|
+
assert len(result) == 1
|
|
275
|
+
|
|
276
|
+
def test_aggregates_correctly():
|
|
277
|
+
"""Verify totals are calculated correctly."""
|
|
278
|
+
df = pl.DataFrame({
|
|
279
|
+
"customer_id": [1, 1, 2],
|
|
280
|
+
"sale_amount": [100, 50, 200],
|
|
281
|
+
"date": ["2024-01-01", "2024-01-02", "2024-01-01"],
|
|
282
|
+
})
|
|
283
|
+
result = process_sales_data(df)
|
|
284
|
+
# Customer 1: 150 total, 2 purchases
|
|
285
|
+
# Customer 2: 200 total, 1 purchase
|
|
286
|
+
assert len(result) == 2
|
|
287
|
+
customer_1 = result.filter(pl.col("customer_id") == 1)
|
|
288
|
+
assert customer_1["total_spent"][0] == 150
|
|
289
|
+
assert customer_1["purchase_count"][0] == 2
|
|
290
|
+
|
|
291
|
+
def test_filters_low_spend_customers():
|
|
292
|
+
"""Ensure low-spend customers are excluded."""
|
|
293
|
+
df = pl.DataFrame({
|
|
294
|
+
"customer_id": [1, 2, 3],
|
|
295
|
+
"sale_amount": [50, 150, 200],
|
|
296
|
+
"date": ["2024-01-01", "2024-01-01", "2024-01-01"],
|
|
297
|
+
})
|
|
298
|
+
result = process_sales_data(df)
|
|
299
|
+
# Only customers with 100+ total
|
|
300
|
+
assert len(result) == 2
|
|
301
|
+
assert all(result["total_spent"] >= 100)
|
|
302
|
+
|
|
303
|
+
def test_sorts_descending():
|
|
304
|
+
"""Ensure sorting is descending by amount."""
|
|
305
|
+
df = pl.DataFrame({
|
|
306
|
+
"customer_id": [1, 2, 3],
|
|
307
|
+
"sale_amount": [150, 300, 200],
|
|
308
|
+
"date": ["2024-01-01", "2024-01-01", "2024-01-01"],
|
|
309
|
+
})
|
|
310
|
+
result = process_sales_data(df)
|
|
311
|
+
amounts = result["total_spent"].to_list()
|
|
312
|
+
assert amounts == sorted(amounts, reverse=True)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Run Mutation Testing
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
# Check test quality
|
|
319
|
+
python -c "
|
|
320
|
+
from dataframe_mutator.polars import SmartPolarsTestRunner
|
|
321
|
+
|
|
322
|
+
tester = SmartPolarsTestRunner(test_command='pytest tests/')
|
|
323
|
+
results = tester.analyze_mutation_efficiency('src/sales_pipeline.py')
|
|
324
|
+
|
|
325
|
+
print(f'Mutations found: {results[\"high_value_mutations\"]}')
|
|
326
|
+
print(f'False positives avoided: {results[\"potential_false_positives_avoided\"]:.1f}%')
|
|
327
|
+
print()
|
|
328
|
+
print('Mutation categories:')
|
|
329
|
+
for cat, count in results['mutation_categories'].items():
|
|
330
|
+
if count > 0:
|
|
331
|
+
print(f' โข {cat}: {count}')
|
|
332
|
+
"
|
|
333
|
+
|
|
334
|
+
# Output:
|
|
335
|
+
# Mutations found: 18
|
|
336
|
+
# False positives avoided: 98.2%
|
|
337
|
+
#
|
|
338
|
+
# Mutation categories:
|
|
339
|
+
# โข filter_boundaries: 6
|
|
340
|
+
# โข aggregation_swaps: 3
|
|
341
|
+
# โข data_integrity: 2
|
|
342
|
+
# โข boolean_logic: 1
|
|
343
|
+
# โข filter_boundary: 6
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**What mutations your tests catch:**
|
|
347
|
+
|
|
348
|
+
โ
`> 0` โ `< 0` (catches wrong boundary)
|
|
349
|
+
โ
`>= "2024-01-01"` โ `< "2024-01-01"` (catches date filter)
|
|
350
|
+
โ
`.sum()` โ `.mean()` (catches wrong aggregation)
|
|
351
|
+
โ
`.count()` โ `.sum()` (catches wrong function)
|
|
352
|
+
โ
`.sort(descending=True)` โ `.sort(descending=False)` (catches sort direction)
|
|
353
|
+
|
|
354
|
+
**What mutations slip through:**
|
|
355
|
+
|
|
356
|
+
โ Column name mutations (e.g., "customer_id" โ "XXXX") - Smart filtering skips these
|
|
357
|
+
โ Unrelated string mutations - Not domain-relevant
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## ๐ Available Operators
|
|
362
|
+
|
|
363
|
+
### Core Operations (5)
|
|
364
|
+
`PolarsFilterOperatorMutation`, `PolarsSelectColumnsMutation`, `PolarsWithColumnsMutation`, `PolarsDropColumnsMutation`, `PolarsRenameMutation`
|
|
365
|
+
|
|
366
|
+
### Aggregations (2)
|
|
367
|
+
`PolarsAggregationMutation`, `PolarsGroupByMutation`
|
|
368
|
+
|
|
369
|
+
### Joins (3)
|
|
370
|
+
`PolarsJoinMutation`, `PolarsCrossJoinMutation`, `PolarsConcatMutation`
|
|
371
|
+
|
|
372
|
+
### Nulls (5)
|
|
373
|
+
`PolarsFillNullMutation`, `PolarsDropNullMutation`, `PolarsDistinctMutation`, `PolarsIsNullMutation`, `PolarsInterpolationMutation`
|
|
374
|
+
|
|
375
|
+
### Strings (1)
|
|
376
|
+
`PolarsStringOperationsMutation`
|
|
377
|
+
|
|
378
|
+
### DateTime (1)
|
|
379
|
+
`PolarsDatetimeOperationsMutation`
|
|
380
|
+
|
|
381
|
+
### Numerical (3)
|
|
382
|
+
`PolarsNumericalOperationsMutation`, `PolarsArithmeticOperatorMutation`, `PolarsClipMutation`
|
|
383
|
+
|
|
384
|
+
### Lists (2)
|
|
385
|
+
`PolarsListOperationsMutation`, `PolarsExplosionMutation`
|
|
386
|
+
|
|
387
|
+
### Structural (4)
|
|
388
|
+
`PolarsMeltMutation`, `PolarsPivotMutation`, `PolarsUnnestMutation`, `PolarsCompactMutation`
|
|
389
|
+
|
|
390
|
+
### Conditional (2)
|
|
391
|
+
`PolarsWhenThenMutation`, `PolarsIsInMutation`
|
|
392
|
+
|
|
393
|
+
### Boolean (1)
|
|
394
|
+
`PolarsBooleanOperatorMutation`
|
|
395
|
+
|
|
396
|
+
### Window/Advanced (7+)
|
|
397
|
+
`PolarsWindowFunctionsMutation`, `PolarsRollingMutation`, `PolarsQuantileMutation`, `PolarsSampleMutation`, `PolarsValueCountsMutation`, `PolarsNUniqueMutation`, `PolarsBinarySearchMutation`, `PolarsSumSqMutation`
|
|
398
|
+
|
|
399
|
+
### Slicing (3)
|
|
400
|
+
`PolarsSliceMutation`, `PolarsLimitMutation`, `PolarsGatherMutation`
|
|
401
|
+
|
|
402
|
+
**โ [See all 43+ operators](docs/POLARS_OPERATORS.md)**
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## ๐๏ธ Integration with CI/CD
|
|
407
|
+
|
|
408
|
+
### GitHub Actions
|
|
409
|
+
|
|
410
|
+
```yaml
|
|
411
|
+
name: Test Quality
|
|
412
|
+
|
|
413
|
+
on: [push, pull_request]
|
|
414
|
+
|
|
415
|
+
jobs:
|
|
416
|
+
mutation-testing:
|
|
417
|
+
runs-on: ubuntu-latest
|
|
418
|
+
steps:
|
|
419
|
+
- uses: actions/checkout@v3
|
|
420
|
+
|
|
421
|
+
- name: Set up Python
|
|
422
|
+
uses: actions/setup-python@v4
|
|
423
|
+
with:
|
|
424
|
+
python-version: '3.10'
|
|
425
|
+
|
|
426
|
+
- name: Install dependencies
|
|
427
|
+
run: |
|
|
428
|
+
pip install -e ".[dev,polars]"
|
|
429
|
+
|
|
430
|
+
- name: Run unit tests
|
|
431
|
+
run: pytest tests/ -v
|
|
432
|
+
|
|
433
|
+
- name: Run mutation testing
|
|
434
|
+
run: |
|
|
435
|
+
python -c "
|
|
436
|
+
from dataframe_mutator.polars import SmartPolarsTestRunner
|
|
437
|
+
tester = SmartPolarsTestRunner(test_command='pytest tests/')
|
|
438
|
+
results = tester.analyze_mutation_efficiency('src/pipeline.py')
|
|
439
|
+
if results['high_value_mutations'] < 10:
|
|
440
|
+
raise Exception('Insufficient test coverage for mutations')
|
|
441
|
+
"
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### GitLab CI
|
|
445
|
+
|
|
446
|
+
```yaml
|
|
447
|
+
test:mutation:
|
|
448
|
+
image: python:3.10
|
|
449
|
+
script:
|
|
450
|
+
- pip install -e ".[dev,polars]"
|
|
451
|
+
- pytest tests/
|
|
452
|
+
- python scripts/mutation_testing.py
|
|
453
|
+
only:
|
|
454
|
+
- merge_requests
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## ๐ How to Interpret Results
|
|
460
|
+
|
|
461
|
+
### Mutation Score
|
|
462
|
+
|
|
463
|
+
- **90-100%**: โญโญโญ Excellent - Strong test suite
|
|
464
|
+
- **70-89%**: โญโญ Good - Most logic bugs caught
|
|
465
|
+
- **50-69%**: โญ Fair - Noticeable gaps
|
|
466
|
+
- **< 50%**: โ Weak - Significant coverage gaps
|
|
467
|
+
|
|
468
|
+
### Common Issues
|
|
469
|
+
|
|
470
|
+
**Issue:** Mutation score < 70%
|
|
471
|
+
**Solution:**
|
|
472
|
+
- Add assertions for boundary conditions (>, >=, <, <=)
|
|
473
|
+
- Test edge cases (nulls, empty data, duplicates)
|
|
474
|
+
- Verify exact values, not just existence
|
|
475
|
+
|
|
476
|
+
**Issue:** Many mutations slip through in joins
|
|
477
|
+
**Solution:**
|
|
478
|
+
- Test both inner and left joins
|
|
479
|
+
- Verify row counts don't change unexpectedly
|
|
480
|
+
- Test for NULL values in join keys
|
|
481
|
+
|
|
482
|
+
**Issue:** Aggregation mutations not caught
|
|
483
|
+
**Solution:**
|
|
484
|
+
- Test actual values, not just row counts
|
|
485
|
+
- Use multiple aggregation functions in tests
|
|
486
|
+
- Verify both totals and counts
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## ๐ Advanced Usage
|
|
491
|
+
|
|
492
|
+
### Custom Mutation Operators
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
from dataframe_mutator.core import MutationOperator
|
|
496
|
+
|
|
497
|
+
class MyCustomMutation(MutationOperator):
|
|
498
|
+
name = "my_custom_mutation"
|
|
499
|
+
description = "My specific logic test"
|
|
500
|
+
|
|
501
|
+
def matches(self, node) -> bool:
|
|
502
|
+
if isinstance(node, str):
|
|
503
|
+
return ".my_operation(" in node
|
|
504
|
+
return False
|
|
505
|
+
|
|
506
|
+
def mutate(self, node) -> str:
|
|
507
|
+
return self.mutate_code(node)
|
|
508
|
+
|
|
509
|
+
def mutate_code(self, code: str) -> str:
|
|
510
|
+
# Your mutation logic
|
|
511
|
+
return code.replace(".my_operation(", ".different_operation(")
|
|
512
|
+
|
|
513
|
+
# Use it
|
|
514
|
+
from dataframe_mutator.polars import SmartPolarsTestRunner
|
|
515
|
+
tester = SmartPolarsTestRunner(operators=[MyCustomMutation])
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Efficiency Analysis
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
from dataframe_mutator.polars import SmartPolarsTestRunner
|
|
522
|
+
|
|
523
|
+
tester = SmartPolarsTestRunner()
|
|
524
|
+
results = tester.analyze_mutation_efficiency("pipeline.py")
|
|
525
|
+
|
|
526
|
+
# Detailed breakdown
|
|
527
|
+
for category, count in results['mutation_categories'].items():
|
|
528
|
+
print(f"{category}: {count} high-value mutations")
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## ๐ Documentation
|
|
534
|
+
|
|
535
|
+
- **[POLARS_FUNCTION_COVERAGE.md](docs/POLARS_FUNCTION_COVERAGE.md)** - Coverage status, roadmap, and which functions are supported
|
|
536
|
+
- **[SMART_POLARS_APPROACH.md](docs/SMART_POLARS_APPROACH.md)** - Why smart filtering matters (6-10x faster)
|
|
537
|
+
- **[POLARS_OPERATORS.md](docs/POLARS_OPERATORS.md)** - Complete operator reference (57 operators)
|
|
538
|
+
- **[INTEGRATION_GUIDE.md](docs/INTEGRATION_GUIDE.md)** - Setup, CI/CD, best practices
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## ๐งช Testing
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
# Install with test dependencies
|
|
546
|
+
pip install -e ".[dev,polars]"
|
|
547
|
+
|
|
548
|
+
# Run tests
|
|
549
|
+
pytest tests/ -v
|
|
550
|
+
|
|
551
|
+
# With coverage
|
|
552
|
+
pytest tests/ --cov=src/dataframe_mutator
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Current Status:** โ
44 tests passing
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## โ๏ธ Why Mutation Testing?
|
|
560
|
+
|
|
561
|
+
Unit tests verify **expected behavior**.
|
|
562
|
+
Mutation tests verify **tests catch bugs**.
|
|
563
|
+
|
|
564
|
+
```
|
|
565
|
+
Traditional testing: Mutation testing:
|
|
566
|
+
โ assert result == expected โ assert tests fail when code changes
|
|
567
|
+
โ catches obvious bugs โ catches logic bugs
|
|
568
|
+
โ validates test quality
|
|
569
|
+
โ finds coverage gaps
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**Real example:**
|
|
573
|
+
```python
|
|
574
|
+
# Original code
|
|
575
|
+
if age > 18:
|
|
576
|
+
adult = True
|
|
577
|
+
|
|
578
|
+
# Mutated code
|
|
579
|
+
if age >= 18: # Bug: includes exactly 18-year-olds
|
|
580
|
+
adult = True
|
|
581
|
+
|
|
582
|
+
# Traditional test: PASSES (doesn't test age==18)
|
|
583
|
+
# Mutation test: FAILS (catches the boundary change)
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## ๐ค Contributing
|
|
589
|
+
|
|
590
|
+
Contributions welcome! To add support for other libraries:
|
|
591
|
+
|
|
592
|
+
1. Create operator classes inheriting from `MutationOperator`
|
|
593
|
+
2. Add tests in `tests/`
|
|
594
|
+
3. Update documentation
|
|
595
|
+
4. Submit PR
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
## ๐ License
|
|
600
|
+
|
|
601
|
+
MIT - See LICENSE file
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## ๐ Support & Questions
|
|
606
|
+
|
|
607
|
+
- ๐ Read the [docs](docs/)
|
|
608
|
+
- ๐ฌ Check [examples](examples/)
|
|
609
|
+
- ๐ Report issues on GitHub
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
**Built for data scientists and engineers who need confidence in their data pipelines.** ๐
|