Skip to content

Commit 77bbf55

Browse files
committed
feat: add further validation and testing
1 parent 5a03bd1 commit 77bbf55

19 files changed

Lines changed: 399 additions & 196 deletions

src/pandas_term/cli/aggregate_commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
UseJsonOption,
1212
get_output_options,
1313
)
14-
from pandas_term.core import io_operations
1514
from pandas_term.cli.validators import get_columns
15+
from pandas_term.core import io_operations
1616

1717
app = typer.Typer(add_completion=False)
1818

src/pandas_term/cli/stats_commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
UseJsonOption,
1212
get_output_options,
1313
)
14-
from pandas_term.core import io_operations
1514
from pandas_term.cli.validators import validate_columns
15+
from pandas_term.core import io_operations
1616

1717
app = typer.Typer(add_completion=False)
1818

src/pandas_term/cli/transform_commands.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
UseJsonOption,
1515
get_output_options,
1616
)
17-
from pandas_term.cli.validators import get_columns, positive_int_list
17+
from pandas_term.cli.validators import (
18+
get_columns,
19+
positive_int_list,
20+
valid_batch_pattern,
21+
valid_rename_mapping,
22+
)
1823
from pandas_term.core import io_operations, transforms
1924

2025
app = typer.Typer(add_completion=False)
@@ -66,7 +71,10 @@ def sort(
6671

6772
@app.command()
6873
def rename(
69-
mapping: Annotated[str, typer.Argument(help="Rename mapping as 'old:new,old2:new2'")],
74+
mapping: Annotated[
75+
str,
76+
typer.Argument(help="Rename mapping as 'old:new,old2:new2'", callback=valid_rename_mapping),
77+
],
7078
input_file: InputFileArgument = "-",
7179
use_json: UseJsonOption = False,
7280
fmt: FormatOption = None,
@@ -171,7 +179,12 @@ def batch(
171179
] = "100",
172180
output_pattern: Annotated[
173181
str,
174-
typer.Option("--output", "-o", help="Output file pattern (e.g., 'batch_{}.csv')"),
182+
typer.Option(
183+
"--output",
184+
"-o",
185+
help="Output file pattern (e.g., 'batch_{}.csv')",
186+
callback=valid_batch_pattern,
187+
),
175188
] = "batch_{}.csv",
176189
) -> None:
177190
"""Split dataframe into batches and write to separate files."""

src/pandas_term/cli/validators.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,43 @@ def valid_output_file(value: str | None) -> str | None:
4242
"""Validate output file has supported extension."""
4343
if value is None:
4444
return None
45-
parts = value.split(".")
46-
ext = f".{parts[-1].lower()}"
47-
if len(parts) < 2:
45+
path = Path(value)
46+
ext = path.suffix.lower()
47+
if not ext:
4848
raise typer.BadParameter(f"Output file must have an extension. {VALID_MSG}")
4949
if ext not in VALID_EXTENSIONS:
5050
raise typer.BadParameter(f"Unsupported extension '{ext}'. {VALID_MSG}")
5151
return value
5252

5353

54+
def valid_batch_pattern(value: str) -> str:
55+
"""Validate batch output pattern has exactly one {} placeholder and a valid extension."""
56+
try:
57+
example_file = value.format(0)
58+
# Validate file extension
59+
valid_output_file(example_file)
60+
except (IndexError, KeyError) as e:
61+
raise typer.BadParameter(
62+
f"Invalid output pattern '{value}'. Must contain exactly one {{}} placeholder"
63+
) from e
64+
return value
65+
66+
67+
def valid_rename_mapping(value: str) -> str:
68+
"""Validate rename mapping format is 'old:new,old2:new2'."""
69+
for pair in value.split(","):
70+
parts = pair.strip().split(":")
71+
if len(parts) != 2:
72+
raise typer.BadParameter(
73+
f"Invalid mapping '{pair.strip()}'. Expected format: 'old:new,old2:new2'"
74+
)
75+
if not parts[0].strip() or not parts[1].strip():
76+
raise typer.BadParameter(
77+
f"Invalid mapping '{pair.strip()}'. Column names cannot be empty"
78+
)
79+
return value
80+
81+
5482
def _parse_columns(columns: str) -> list[str]:
5583
"""Parse a comma-separated string of column names into a list."""
5684
return [col.strip() for col in columns.split(",")]

tests/cli/snapshots/filter/dropna_commands.json

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,73 +3,80 @@
33
{
44
"name": "Alice",
55
"age": 25.0,
6-
"city": "NYC"
7-
},
8-
{
9-
"name": "Eve",
10-
"age": 45.0,
11-
"city": "LA"
6+
"city": "NYC",
7+
"salary": 50000.0
128
}
139
],
1410
"dropna_subset_age": [
1511
{
1612
"name": "Alice",
1713
"age": 25.0,
18-
"city": "NYC"
14+
"city": "NYC",
15+
"salary": 50000.0
1916
},
2017
{
2118
"name": "Bob",
2219
"age": 30.0,
23-
"city": null
20+
"city": null,
21+
"salary": 60000.0
2422
},
2523
{
2624
"name": null,
2725
"age": 35.0,
28-
"city": "Chicago"
26+
"city": "Chicago",
27+
"salary": 70000.0
2928
},
3029
{
3130
"name": "Eve",
3231
"age": 45.0,
33-
"city": "LA"
32+
"city": "LA",
33+
"salary": null
3434
}
3535
],
3636
"dropna_subset_city": [
3737
{
3838
"name": "Alice",
3939
"age": 25.0,
40-
"city": "NYC"
40+
"city": "NYC",
41+
"salary": 50000.0
4142
},
4243
{
4344
"name": null,
4445
"age": 35.0,
45-
"city": "Chicago"
46+
"city": "Chicago",
47+
"salary": 70000.0
4648
},
4749
{
4850
"name": "David",
4951
"age": null,
50-
"city": "NYC"
52+
"city": "NYC",
53+
"salary": 80000.0
5154
},
5255
{
5356
"name": "Eve",
5457
"age": 45.0,
55-
"city": "LA"
58+
"city": "LA",
59+
"salary": null
5660
}
5761
],
5862
"dropna_subset_multiple": [
5963
{
6064
"name": "Alice",
6165
"age": 25.0,
62-
"city": "NYC"
66+
"city": "NYC",
67+
"salary": 50000.0
6368
},
6469
{
6570
"name": null,
6671
"age": 35.0,
67-
"city": "Chicago"
72+
"city": "Chicago",
73+
"salary": 70000.0
6874
},
6975
{
7076
"name": "Eve",
7177
"age": 45.0,
72-
"city": "LA"
78+
"city": "LA",
79+
"salary": null
7380
}
7481
]
7582
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"empty_query": [],
3+
"empty_head": [],
4+
"empty_tail": []
5+
}

tests/cli/snapshots/filter/filter_commands.json

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,125 +4,144 @@
44
"name": "Charlie",
55
"age": 35,
66
"city": "Chicago",
7-
"salary": 70000
7+
"salary": 70000,
8+
"department": "Engineering"
89
},
910
{
1011
"name": "David",
1112
"age": 40,
1213
"city": "NYC",
13-
"salary": 80000
14+
"salary": 80000,
15+
"department": "Sales"
1416
},
1517
{
1618
"name": "Eve",
1719
"age": 45,
1820
"city": "LA",
19-
"salary": 90000
21+
"salary": 90000,
22+
"department": "Engineering"
2023
}
2124
],
2225
"query_complex": [
2326
{
2427
"name": "David",
2528
"age": 40,
2629
"city": "NYC",
27-
"salary": 80000
30+
"salary": 80000,
31+
"department": "Sales"
2832
}
2933
],
3034
"head_default": [
3135
{
3236
"name": "Alice",
3337
"age": 25,
3438
"city": "NYC",
35-
"salary": 50000
39+
"salary": 50000,
40+
"department": "Engineering"
3641
},
3742
{
3843
"name": "Bob",
3944
"age": 30,
4045
"city": "LA",
41-
"salary": 60000
46+
"salary": 60000,
47+
"department": "Sales"
4248
},
4349
{
4450
"name": "Charlie",
4551
"age": 35,
4652
"city": "Chicago",
47-
"salary": 70000
53+
"salary": 70000,
54+
"department": "Engineering"
4855
},
4956
{
5057
"name": "David",
5158
"age": 40,
5259
"city": "NYC",
53-
"salary": 80000
60+
"salary": 80000,
61+
"department": "Sales"
5462
},
5563
{
5664
"name": "Eve",
5765
"age": 45,
5866
"city": "LA",
59-
"salary": 90000
67+
"salary": 90000,
68+
"department": "Engineering"
6069
}
6170
],
6271
"head_n3": [
6372
{
6473
"name": "Alice",
6574
"age": 25,
6675
"city": "NYC",
67-
"salary": 50000
76+
"salary": 50000,
77+
"department": "Engineering"
6878
},
6979
{
7080
"name": "Bob",
7181
"age": 30,
7282
"city": "LA",
73-
"salary": 60000
83+
"salary": 60000,
84+
"department": "Sales"
7485
},
7586
{
7687
"name": "Charlie",
7788
"age": 35,
7889
"city": "Chicago",
79-
"salary": 70000
90+
"salary": 70000,
91+
"department": "Engineering"
8092
}
8193
],
8294
"tail_default": [
8395
{
8496
"name": "Alice",
8597
"age": 25,
8698
"city": "NYC",
87-
"salary": 50000
99+
"salary": 50000,
100+
"department": "Engineering"
88101
},
89102
{
90103
"name": "Bob",
91104
"age": 30,
92105
"city": "LA",
93-
"salary": 60000
106+
"salary": 60000,
107+
"department": "Sales"
94108
},
95109
{
96110
"name": "Charlie",
97111
"age": 35,
98112
"city": "Chicago",
99-
"salary": 70000
113+
"salary": 70000,
114+
"department": "Engineering"
100115
},
101116
{
102117
"name": "David",
103118
"age": 40,
104119
"city": "NYC",
105-
"salary": 80000
120+
"salary": 80000,
121+
"department": "Sales"
106122
},
107123
{
108124
"name": "Eve",
109125
"age": 45,
110126
"city": "LA",
111-
"salary": 90000
127+
"salary": 90000,
128+
"department": "Engineering"
112129
}
113130
],
114131
"tail_n2": [
115132
{
116133
"name": "David",
117134
"age": 40,
118135
"city": "NYC",
119-
"salary": 80000
136+
"salary": 80000,
137+
"department": "Sales"
120138
},
121139
{
122140
"name": "Eve",
123141
"age": 45,
124142
"city": "LA",
125-
"salary": 90000
143+
"salary": 90000,
144+
"department": "Engineering"
126145
}
127146
]
128147
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"empty_shape": "0 rows x 5 columns\n",
3+
"empty_columns": "name\nage\ncity\nsalary\ndepartment\n",
4+
"empty_dtypes": "name: object\nage: object\ncity: object\nsalary: object\ndepartment: object\n"
5+
}

tests/cli/snapshots/stats/stats_commands.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@
3636
"unique_city": "NYC\nLA\nChicago\n",
3737
"unique_department": "Engineering\nSales\n",
3838
"shape": "5 rows x 5 columns\n",
39-
"columns": "name\nage\ncity\nsalary\ndepartment\n"
39+
"columns": "name\nage\ncity\nsalary\ndepartment\n",
40+
"dtypes": "name: object\nage: int64\ncity: object\nsalary: int64\ndepartment: object\n"
4041
}

0 commit comments

Comments
 (0)