Skip to content

Commit f6bb544

Browse files
authored
Merge pull request #230 from posit-dev/fix-pb-validate-default-messages
fix: improve messages with `pb validate <data>` default; add `--list-checks` option
2 parents cb8ab83 + cfb004b commit f6bb544

1 file changed

Lines changed: 156 additions & 55 deletions

File tree

pointblank/cli.py

Lines changed: 156 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,9 @@ def format_value(
548548
console.print(f"[red]Error displaying table: {str(e)}[/red]")
549549

550550

551-
def _rich_print_gt_table(gt_table: Any, preview_info: dict | None = None) -> None:
551+
def _rich_print_gt_table(
552+
gt_table: Any, preview_info: dict | None = None, show_summary: bool = True
553+
) -> None:
552554
"""Convert a GT table to Rich table and display it in the terminal.
553555
554556
Args:
@@ -558,6 +560,7 @@ def _rich_print_gt_table(gt_table: Any, preview_info: dict | None = None) -> Non
558560
- head_rows: Number of head rows shown
559561
- tail_rows: Number of tail rows shown
560562
- is_complete: Whether the entire dataset is shown
563+
show_summary: Whether to show the row count summary at the bottom
561564
"""
562565
try:
563566
# Try to extract the underlying data from the GT table
@@ -861,44 +864,45 @@ def _rich_print_gt_table(gt_table: Any, preview_info: dict | None = None) -> Non
861864
console.print()
862865
console.print(rich_table)
863866

864-
# Show summary info
865-
total_rows = len(rows)
867+
# Show summary info (conditionally)
868+
if show_summary:
869+
total_rows = len(rows)
866870

867-
# Use preview info if available, otherwise fall back to old logic
868-
if preview_info:
869-
total_dataset_rows = preview_info.get("total_rows", total_rows)
870-
head_rows = preview_info.get("head_rows", 0)
871-
tail_rows = preview_info.get("tail_rows", 0)
872-
is_complete = preview_info.get("is_complete", False)
871+
# Use preview info if available, otherwise fall back to old logic
872+
if preview_info:
873+
total_dataset_rows = preview_info.get("total_rows", total_rows)
874+
head_rows = preview_info.get("head_rows", 0)
875+
tail_rows = preview_info.get("tail_rows", 0)
876+
is_complete = preview_info.get("is_complete", False)
873877

874-
if is_complete:
875-
console.print(f"\n[dim]Showing all {total_rows} rows.[/dim]")
876-
elif head_rows > 0 and tail_rows > 0:
877-
console.print(
878-
f"\n[dim]Showing first {head_rows} and last {tail_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
879-
)
880-
elif head_rows > 0:
881-
console.print(
882-
f"\n[dim]Showing first {head_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
883-
)
884-
elif tail_rows > 0:
885-
console.print(
886-
f"\n[dim]Showing last {tail_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
887-
)
888-
else:
889-
# Fallback for other cases
890-
console.print(
891-
f"\n[dim]Showing {total_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
892-
)
893-
else:
894-
# Original logic as fallback
895-
max_rows = 50 # This should match the limit used above
896-
if total_rows > max_rows:
897-
console.print(
898-
f"\n[dim]Showing first {max_rows} of {total_rows} rows. Use --output-html to see all data.[/dim]"
899-
)
878+
if is_complete:
879+
console.print(f"\n[dim]Showing all {total_rows} rows.[/dim]")
880+
elif head_rows > 0 and tail_rows > 0:
881+
console.print(
882+
f"\n[dim]Showing first {head_rows} and last {tail_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
883+
)
884+
elif head_rows > 0:
885+
console.print(
886+
f"\n[dim]Showing first {head_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
887+
)
888+
elif tail_rows > 0:
889+
console.print(
890+
f"\n[dim]Showing last {tail_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
891+
)
892+
else:
893+
# Fallback for other cases
894+
console.print(
895+
f"\n[dim]Showing {total_rows} rows from {total_dataset_rows:,} total rows.[/dim]"
896+
)
900897
else:
901-
console.print(f"\n[dim]Showing all {total_rows} rows.[/dim]")
898+
# Original logic as fallback
899+
max_rows = 50 # This should match the limit used above
900+
if total_rows > max_rows:
901+
console.print(
902+
f"\n[dim]Showing first {max_rows} of {total_rows} rows. Use --output-html to see all data.[/dim]"
903+
)
904+
else:
905+
console.print(f"\n[dim]Showing all {total_rows} rows.[/dim]")
902906

903907
else:
904908
# If we can't extract data, show the success message
@@ -1468,8 +1472,9 @@ def missing(data_source: str, output_html: str | None):
14681472
]
14691473
),
14701474
default="rows-distinct",
1471-
help="Type of validation check to perform",
1475+
help="Type of validation check to perform. Use --list-checks to see all options.",
14721476
)
1477+
@click.option("--list-checks", is_flag=True, help="List available validation checks and exit")
14731478
@click.option(
14741479
"--column",
14751480
help="Column name to validate (required for col-vals-not-null, col-exists, col-vals-in-set, col-vals-gt, col-vals-ge, col-vals-lt, and col-vals-le checks)",
@@ -1481,7 +1486,7 @@ def missing(data_source: str, output_html: str | None):
14811486
help="Numeric value for comparison (required for col-vals-gt, col-vals-ge, col-vals-lt, and col-vals-le checks)",
14821487
)
14831488
@click.option(
1484-
"--show-extract", is_flag=True, help="Show preview of failing rows if validation fails"
1489+
"--show-extract", is_flag=True, help="Show extract of failing rows if validation fails"
14851490
)
14861491
@click.option(
14871492
"--write-extract", type=str, help="Save failing rows to folder. Provide base name for folder."
@@ -1490,7 +1495,9 @@ def missing(data_source: str, output_html: str | None):
14901495
"--limit", "-l", default=10, help="Maximum number of failing rows to show/save (default: 10)"
14911496
)
14921497
@click.option("--exit-code", is_flag=True, help="Exit with non-zero code if validation fails")
1498+
@click.pass_context
14931499
def validate(
1500+
ctx: click.Context,
14941501
data_source: str,
14951502
check: str,
14961503
column: str | None,
@@ -1500,6 +1507,7 @@ def validate(
15001507
write_extract: str | None,
15011508
limit: int,
15021509
exit_code: bool,
1510+
list_checks: bool,
15031511
):
15041512
"""
15051513
Perform simple, single-step data validations.
@@ -1517,6 +1525,10 @@ def validate(
15171525
15181526
AVAILABLE CHECKS:
15191527
1528+
Use --list-checks to see all available validation methods with examples.
1529+
1530+
The default check is 'rows-distinct' which checks for duplicate rows.
1531+
15201532
\b
15211533
- rows-distinct: Check if all rows in the dataset are unique (no duplicates)
15221534
- rows-complete: Check if all rows are complete (no missing values in any column)
@@ -1531,6 +1543,8 @@ def validate(
15311543
Examples:
15321544
15331545
\b
1546+
pb validate data.csv # Uses default validation (rows-distinct)
1547+
pb validate data.csv --list-checks # Show all available checks
15341548
pb validate data.csv --check rows-distinct
15351549
pb validate data.csv --check rows-distinct --show-extract
15361550
pb validate data.csv --check rows-distinct --write-extract failing_rows_folder
@@ -1542,6 +1556,67 @@ def validate(
15421556
pb validate data.csv --check col-vals-in-set --column status --set "active,inactive,pending"
15431557
"""
15441558
try:
1559+
# Handle --list-checks option
1560+
if list_checks:
1561+
console.print("[bold bright_cyan]Available Validation Checks:[/bold bright_cyan]")
1562+
console.print()
1563+
console.print("[bold magenta]Basic checks:[/bold magenta]")
1564+
console.print(
1565+
" • [bold cyan]rows-distinct[/bold cyan] Check for duplicate rows [yellow](default)[/yellow]"
1566+
)
1567+
console.print(
1568+
" • [bold cyan]rows-complete[/bold cyan] Check for missing values in any column"
1569+
)
1570+
console.print()
1571+
console.print(
1572+
"[bold magenta]Column-specific checks [bright_black](require --column)[/bright_black]:[/bold magenta]"
1573+
)
1574+
console.print(" • [bold cyan]col-exists[/bold cyan] Check if a column exists")
1575+
console.print(
1576+
" • [bold cyan]col-vals-not-null[/bold cyan] Check for null values in a column"
1577+
)
1578+
console.print()
1579+
console.print(
1580+
"[bold magenta]Value comparison checks [bright_black](require --column and --value)[/bright_black]:[/bold magenta]"
1581+
)
1582+
console.print(
1583+
" • [bold cyan]col-vals-gt[/bold cyan] Values greater than threshold"
1584+
)
1585+
console.print(
1586+
" • [bold cyan]col-vals-ge[/bold cyan] Values greater than or equal to threshold"
1587+
)
1588+
console.print(" • [bold cyan]col-vals-lt[/bold cyan] Values less than threshold")
1589+
console.print(
1590+
" • [bold cyan]col-vals-le[/bold cyan] Values less than or equal to threshold"
1591+
)
1592+
console.print()
1593+
console.print(
1594+
"[bold magenta]Set validation check [bright_black](requires --column and --set)[/bright_black]:[/bold magenta]"
1595+
)
1596+
console.print(
1597+
" • [bold cyan]col-vals-in-set[/bold cyan] Values must be in allowed set"
1598+
)
1599+
console.print()
1600+
console.print("[bold bright_yellow]Examples:[/bold bright_yellow]")
1601+
console.print(
1602+
f" [bright_blue]pb validate {data_source} --check rows-distinct[/bright_blue]"
1603+
)
1604+
console.print(
1605+
f" [bright_blue]pb validate {data_source} --check col-vals-not-null --column price[/bright_blue]"
1606+
)
1607+
console.print(
1608+
f" [bright_blue]pb validate {data_source} --check col-vals-gt --column age --value 18[/bright_blue]"
1609+
)
1610+
import sys
1611+
1612+
sys.exit(0)
1613+
1614+
# Check if --check option was explicitly provided by the user
1615+
# If --check is not in the command line args, it means we're using the default
1616+
import sys
1617+
1618+
is_using_default_check = check == "rows-distinct" and "--check" not in sys.argv
1619+
15451620
# Validate required parameters for different check types
15461621
if check == "col-vals-not-null" and not column:
15471622
console.print(f"[red]Error:[/red] --column is required for {check} check")
@@ -1640,7 +1715,12 @@ def validate(
16401715
# Get the result
16411716
all_passed = validation.all_passed()
16421717

1643-
console.print(f"[green]✓[/green] {check} validation completed")
1718+
if is_using_default_check:
1719+
console.print(
1720+
f"[green]✓[/green] {check} validation completed [dim](default validation)[/dim]"
1721+
)
1722+
else:
1723+
console.print(f"[green]✓[/green] {check} validation completed")
16441724
elif check == "col-vals-not-null":
16451725
# Create validation for not null values in specified column
16461726
validation = (
@@ -1908,10 +1988,10 @@ def validate(
19081988

19091989
# Dynamic message based on check type
19101990
if check == "rows-distinct":
1911-
extract_message = "[yellow]Preview of failing rows (duplicates):[/yellow]"
1991+
extract_message = "[yellow]Extract of failing rows (duplicates):[/yellow]"
19121992
row_type = "duplicate rows"
19131993
elif check == "rows-complete":
1914-
extract_message = "[yellow]Preview of failing rows (incomplete rows):[/yellow]"
1994+
extract_message = "[yellow]Extract of failing rows (incomplete rows):[/yellow]"
19151995
row_type = "incomplete rows"
19161996
elif check == "col-exists":
19171997
extract_message = (
@@ -1920,21 +2000,21 @@ def validate(
19202000
row_type = "missing column"
19212001
elif check == "col-vals-in-set":
19222002
extract_message = (
1923-
f"[yellow]Preview of failing rows (invalid values in '{column}'):[/yellow]"
2003+
f"[yellow]Extract of failing rows (invalid values in '{column}'):[/yellow]"
19242004
)
19252005
row_type = "rows with invalid values"
19262006
elif check == "col-vals-gt":
19272007
extract_message = (
1928-
f"[yellow]Preview of failing rows (values in '{column}' <= {value}):[/yellow]"
2008+
f"[yellow]Extract of failing rows (values in '{column}' <= {value}):[/yellow]"
19292009
)
19302010
row_type = f"rows with values <= {value}"
19312011
elif check == "col-vals-ge":
19322012
extract_message = (
1933-
f"[yellow]Preview of failing rows (values in '{column}' < {value}):[/yellow]"
2013+
f"[yellow]Extract of failing rows (values in '{column}' < {value}):[/yellow]"
19342014
)
19352015
row_type = f"rows with values < {value}"
19362016
else:
1937-
extract_message = "[yellow]Preview of failing rows:[/yellow]"
2017+
extract_message = "[yellow]Extract of failing rows:[/yellow]"
19382018
row_type = "failing rows"
19392019

19402020
if show_extract:
@@ -1980,7 +2060,7 @@ def validate(
19802060
)
19812061

19822062
# Display using our Rich table function
1983-
_rich_print_gt_table(preview_table)
2063+
_rich_print_gt_table(preview_table, show_summary=False)
19842064

19852065
if write_extract:
19862066
try:
@@ -2103,9 +2183,7 @@ def validate(
21032183

21042184
# Add hint about --show-extract if not already used (except for col-exists which has no rows to show)
21052185
if not show_extract and check != "col-exists":
2106-
failure_message += (
2107-
"\n[dim]💡 Tip: Use --show-extract to see the failing rows[/dim]"
2108-
)
2186+
failure_message += "\n[bright_blue]💡 Tip:[/bright_blue] [cyan]Use --show-extract to see the failing rows[/cyan]"
21092187

21102188
console.print(
21112189
Panel(
@@ -2129,9 +2207,7 @@ def validate(
21292207

21302208
# Add hint about --show-extract if not already used
21312209
if not show_extract:
2132-
failure_message += (
2133-
"\n[dim]💡 Tip: Use --show-extract to see the failing rows[/dim]"
2134-
)
2210+
failure_message += "\n[bright_blue]💡 Tip:[/bright_blue] [cyan]Use --show-extract to see the failing rows[/cyan]"
21352211

21362212
console.print(
21372213
Panel(
@@ -2140,6 +2216,31 @@ def validate(
21402216
)
21412217
)
21422218

2219+
# Add informational hints when using default validation
2220+
if is_using_default_check:
2221+
console.print()
2222+
console.print("[bold blue]ℹ️ Information:[/bold blue] Using default validation method")
2223+
console.print("To specify a different validation, use the --check option.")
2224+
console.print()
2225+
console.print("[bold magenta]Common validation options:[/bold magenta]")
2226+
console.print(
2227+
" • [bold cyan]--check rows-complete[/bold cyan] Check for rows with missing values"
2228+
)
2229+
console.print(
2230+
" • [bold cyan]--check col-vals-not-null[/bold cyan] Check for null values in a column [bright_black](requires --column)[/bright_black]"
2231+
)
2232+
console.print(
2233+
" • [bold cyan]--check col-exists[/bold cyan] Check if a column exists [bright_black](requires --column)[/bright_black]"
2234+
)
2235+
console.print()
2236+
console.print("[bold bright_yellow]Examples:[/bold bright_yellow]")
2237+
console.print(
2238+
f" [bright_blue]pb validate {data_source} --check rows-complete[/bright_blue]"
2239+
)
2240+
console.print(
2241+
f" [bright_blue]pb validate {data_source} --check col-vals-not-null --column price[/bright_blue]"
2242+
)
2243+
21432244
# Exit with appropriate code if requested
21442245
if exit_code and not all_passed:
21452246
console.print("[dim]Exiting with non-zero code due to validation failure[/dim]")
@@ -2735,7 +2836,7 @@ def make_template(output_file: str):
27352836
@click.option("--output-html", type=click.Path(), help="Save HTML validation report to file")
27362837
@click.option("--output-json", type=click.Path(), help="Save JSON validation summary to file")
27372838
@click.option(
2738-
"--show-extract", is_flag=True, help="Show preview of failing rows if validation fails"
2839+
"--show-extract", is_flag=True, help="Show extract of failing rows if validation fails"
27392840
)
27402841
@click.option(
27412842
"--write-extract",
@@ -2892,7 +2993,7 @@ def run(
28922993
console.print()
28932994

28942995
if show_extract:
2895-
extract_title = "Preview of failing rows from validation steps"
2996+
extract_title = "Extract of failing rows from validation steps"
28962997
if len(validations) > 1:
28972998
extract_title += f" (Validation {i})"
28982999
console.print(f"[yellow]{extract_title}:[/yellow]")
@@ -2928,7 +3029,7 @@ def run(
29283029
)
29293030

29303031
# Display using our Rich table function
2931-
_rich_print_gt_table(preview_table)
3032+
_rich_print_gt_table(preview_table, show_summary=False)
29323033
else:
29333034
console.print(
29343035
f"\n[cyan]Step {step_num}:[/cyan] {step_info.assertion_type}"

0 commit comments

Comments
 (0)