Skip to content

Commit 556b182

Browse files
authored
Merge pull request #327 from posit-dev/feat-enable-report-footer-timings
feat: add options to enable/disable report footer sections
2 parents 6012b67 + 6051671 commit 556b182

File tree

2 files changed

+284
-10
lines changed

2 files changed

+284
-10
lines changed

pointblank/validate.py

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -363,12 +363,16 @@ class PointblankConfig:
363363

364364
report_incl_header: bool = True
365365
report_incl_footer: bool = True
366+
report_incl_footer_timings: bool = True
367+
report_incl_footer_notes: bool = True
366368
preview_incl_header: bool = True
367369

368370
def __repr__(self):
369371
return (
370372
f"PointblankConfig(report_incl_header={self.report_incl_header}, "
371373
f"report_incl_footer={self.report_incl_footer}, "
374+
f"report_incl_footer_timings={self.report_incl_footer_timings}, "
375+
f"report_incl_footer_notes={self.report_incl_footer_notes}, "
372376
f"preview_incl_header={self.preview_incl_header})"
373377
)
374378

@@ -380,6 +384,8 @@ def __repr__(self):
380384
def config(
381385
report_incl_header: bool = True,
382386
report_incl_footer: bool = True,
387+
report_incl_footer_timings: bool = True,
388+
report_incl_footer_notes: bool = True,
383389
preview_incl_header: bool = True,
384390
) -> PointblankConfig:
385391
"""
@@ -393,7 +399,13 @@ def config(
393399
threshold levels (if set).
394400
report_incl_footer
395401
Should the footer of the validation table report be displayed? The footer contains the
396-
starting and ending times of the interrogation.
402+
starting and ending times of the interrogation and any notes added to validation steps.
403+
report_incl_footer_timings
404+
Controls whether the validation timing information (start time, duration, and end time)
405+
should be displayed in the footer. Only applies when `report_incl_footer=True`.
406+
report_incl_footer_notes
407+
Controls whether the notes from validation steps should be displayed in the footer. Only
408+
applies when `report_incl_footer=True`.
397409
preview_incl_header
398410
Whether the header should be present in any preview table (generated via the
399411
[`preview()`](`pointblank.preview`) function).
@@ -407,6 +419,8 @@ def config(
407419
global global_config
408420
global_config.report_incl_header = report_incl_header # pragma: no cover
409421
global_config.report_incl_footer = report_incl_footer # pragma: no cover
422+
global_config.report_incl_footer_timings = report_incl_footer_timings # pragma: no cover
423+
global_config.report_incl_footer_notes = report_incl_footer_notes # pragma: no cover
410424
global_config.preview_incl_header = preview_incl_header # pragma: no cover
411425

412426

@@ -14986,7 +15000,12 @@ def get_note(self, i: int, key: str, format: str = "dict") -> dict[str, str] | s
1498615000
return None
1498715001

1498815002
def get_tabular_report(
14989-
self, title: str | None = ":default:", incl_header: bool = None, incl_footer: bool = None
15003+
self,
15004+
title: str | None = ":default:",
15005+
incl_header: bool = None,
15006+
incl_footer: bool = None,
15007+
incl_footer_timings: bool = None,
15008+
incl_footer_notes: bool = None,
1499015009
) -> GT:
1499115010
"""
1499215011
Validation report as a GT table.
@@ -15009,6 +15028,20 @@ def get_tabular_report(
1500915028
name of the table as the title for the report. If no title is wanted, then `":none:"`
1501015029
can be used. Aside from keyword options, text can be provided for the title. This will
1501115030
be interpreted as Markdown text and transformed internally to HTML.
15031+
incl_header
15032+
Controls whether the header section should be displayed. If `None`, uses the global
15033+
configuration setting. The header contains the table name, label, and threshold
15034+
information.
15035+
incl_footer
15036+
Controls whether the footer section should be displayed. If `None`, uses the global
15037+
configuration setting. The footer can contain validation timing information and notes.
15038+
incl_footer_timings
15039+
Controls whether validation timing information (start time, duration, end time) should
15040+
be displayed in the footer. If `None`, uses the global configuration setting. Only
15041+
applies when `incl_footer=True`.
15042+
incl_footer_notes
15043+
Controls whether notes from validation steps should be displayed in the footer. If
15044+
`None`, uses the global configuration setting. Only applies when `incl_footer=True`.
1501215045

1501315046
Returns
1501415047
-------
@@ -15068,6 +15101,10 @@ def get_tabular_report(
1506815101
incl_header = global_config.report_incl_header
1506915102
if incl_footer is None:
1507015103
incl_footer = global_config.report_incl_footer
15104+
if incl_footer_timings is None:
15105+
incl_footer_timings = global_config.report_incl_footer_timings
15106+
if incl_footer_notes is None:
15107+
incl_footer_notes = global_config.report_incl_footer_notes
1507115108

1507215109
# Do we have a DataFrame library to work with?
1507315110
_check_any_df_lib(method_used="get_tabular_report")
@@ -15860,13 +15897,15 @@ def get_tabular_report(
1586015897
gt_tbl = gt_tbl.tab_header(title=html(title_text), subtitle=html(combined_subtitle))
1586115898

1586215899
if incl_footer:
15863-
# Add table time as HTML source note
15864-
gt_tbl = gt_tbl.tab_source_note(source_note=html(table_time))
15865-
15866-
# Create notes markdown from validation steps and add as separate source note
15867-
notes_markdown = _create_notes_html(self.validation_info)
15868-
if notes_markdown:
15869-
gt_tbl = gt_tbl.tab_source_note(source_note=md(notes_markdown))
15900+
# Add table time as HTML source note if enabled
15901+
if incl_footer_timings:
15902+
gt_tbl = gt_tbl.tab_source_note(source_note=html(table_time))
15903+
15904+
# Create notes markdown from validation steps and add as separate source note if enabled
15905+
if incl_footer_notes:
15906+
notes_markdown = _create_notes_html(self.validation_info)
15907+
if notes_markdown:
15908+
gt_tbl = gt_tbl.tab_source_note(source_note=md(notes_markdown))
1587015909

1587115910
# If the interrogation has not been performed, then style the table columns dealing with
1587215911
# interrogation data as grayed out

tests/test_validate.py

Lines changed: 236 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,14 @@ class StrEnum(str, Enum):
8585
from pointblank.validate import (
8686
Actions,
8787
FinalActions,
88+
config,
8889
connect_to_table,
8990
get_action_metadata,
9091
get_column_count,
9192
get_data_path,
9293
get_row_count,
9394
get_validation_summary,
95+
global_config,
9496
load_dataset,
9597
missing_vals_tbl,
9698
PointblankConfig,
@@ -11018,7 +11020,7 @@ def test_pointblank_config_class():
1101811020

1101911021
assert (
1102011022
str(config)
11021-
== "PointblankConfig(report_incl_header=True, report_incl_footer=True, preview_incl_header=True)"
11023+
== "PointblankConfig(report_incl_header=True, report_incl_footer=True, report_incl_footer_timings=True, report_incl_footer_notes=True, preview_incl_header=True)"
1102211024
)
1102311025

1102411026

@@ -19865,3 +19867,236 @@ def test_threshold_notes_no_note_when_thresholds_match():
1986519867
# No threshold notes should appear
1986619868
assert "Step-specific thresholds set with" not in html
1986719869
assert "Global thresholds explicitly not used" not in html
19870+
19871+
19872+
def test_config_footer_timings_and_notes():
19873+
"""Test footer timings and notes configuration options."""
19874+
19875+
# Test default configuration includes selected fields
19876+
config = PointblankConfig()
19877+
assert config.report_incl_footer_timings is True
19878+
assert config.report_incl_footer_notes is True
19879+
19880+
# Test configuration with the two fields disabled
19881+
config_no_footer_details = PointblankConfig(
19882+
report_incl_header=True,
19883+
report_incl_footer=True,
19884+
report_incl_footer_timings=False,
19885+
report_incl_footer_notes=False,
19886+
preview_incl_header=True,
19887+
)
19888+
assert config_no_footer_details.report_incl_footer_timings is False
19889+
assert config_no_footer_details.report_incl_footer_notes is False
19890+
19891+
# Test string representation for inclusion of the fields
19892+
str_repr = str(config)
19893+
assert "report_incl_footer_timings=True" in str_repr
19894+
assert "report_incl_footer_notes=True" in str_repr
19895+
19896+
19897+
def test_get_tabular_report_footer_timings_control():
19898+
"""Test that incl_footer_timings= parameter controls timing display in reports."""
19899+
19900+
small_table = load_dataset(dataset="small_table")
19901+
19902+
# Create validation with an error to trigger a note
19903+
validation = (
19904+
Validate(data=small_table, label="Test Validation")
19905+
.col_vals_gt(columns="d", value=100)
19906+
.col_vals_regex(columns="invalid_column", pattern=r"test")
19907+
.interrogate()
19908+
)
19909+
19910+
# Test with default settings (timings should be present)
19911+
html_with_timings = validation.get_tabular_report()._repr_html_()
19912+
19913+
# Timing information is rendered with specific styling in _create_table_time_html
19914+
assert "font-variant-numeric: tabular-nums" in html_with_timings
19915+
assert "solid 1px #999999" in html_with_timings # Part of timing badge styling
19916+
19917+
# Test with timings disabled
19918+
html_no_timings = validation.get_tabular_report(incl_footer_timings=False)._repr_html_()
19919+
19920+
# When timings are disabled, there should be fewer timing-related style elements so
19921+
# count occurrences to verify reduction
19922+
timing_style_count_with = html_with_timings.count("font-variant-numeric: tabular-nums")
19923+
timing_style_count_without = html_no_timings.count("font-variant-numeric: tabular-nums")
19924+
19925+
assert timing_style_count_without < timing_style_count_with
19926+
19927+
19928+
def test_get_tabular_report_footer_notes_control():
19929+
"""Test that incl_footer_notes= parameter controls notes display in reports."""
19930+
19931+
small_table = load_dataset(dataset="small_table")
19932+
19933+
# Create validation with an error to trigger a note
19934+
validation = (
19935+
Validate(data=small_table, label="Test Validation")
19936+
.col_vals_gt(columns="d", value=100)
19937+
.col_vals_regex(columns="invalid_column", pattern=r"test")
19938+
.interrogate()
19939+
)
19940+
19941+
# Test with default settings (notes should be present)
19942+
html_with_notes = validation.get_tabular_report()._repr_html_()
19943+
19944+
assert "<strong>Notes</strong>" in html_with_notes
19945+
# Notes include step references with small caps formatting
19946+
assert "font-variant: small-caps" in html_with_notes or "Step" in html_with_notes.lower()
19947+
19948+
# Test with notes disabled
19949+
html_no_notes = validation.get_tabular_report(incl_footer_notes=False)._repr_html_()
19950+
19951+
assert "<strong>Notes</strong>" not in html_no_notes
19952+
19953+
19954+
def test_get_tabular_report_footer_controls_combined():
19955+
"""Test combinations of footer timing and notes controls."""
19956+
19957+
small_table = load_dataset(dataset="small_table")
19958+
19959+
validation = (
19960+
Validate(data=small_table, label="Test Validation")
19961+
.col_vals_gt(columns="d", value=100)
19962+
.col_vals_regex(columns="invalid_column", pattern=r"test")
19963+
.interrogate()
19964+
)
19965+
19966+
# Test with both timings and notes enabled (default)
19967+
html_both = validation.get_tabular_report()._repr_html_()
19968+
19969+
assert "font-variant-numeric: tabular-nums" in html_both
19970+
assert "<strong>Notes</strong>" in html_both
19971+
19972+
# Test with both disabled but footer still enabled
19973+
html_neither = validation.get_tabular_report(
19974+
incl_footer_timings=False, incl_footer_notes=False
19975+
)._repr_html_()
19976+
timing_count = html_neither.count("font-variant-numeric: tabular-nums")
19977+
19978+
assert "<strong>Notes</strong>" not in html_neither
19979+
assert timing_count < html_both.count("font-variant-numeric: tabular-nums")
19980+
19981+
# Test with timings enabled, notes disabled
19982+
html_timings_only = validation.get_tabular_report(incl_footer_notes=False)._repr_html_()
19983+
19984+
assert "font-variant-numeric: tabular-nums" in html_timings_only
19985+
assert "<strong>Notes</strong>" not in html_timings_only
19986+
19987+
# Test with notes enabled, timings disabled
19988+
html_notes_only = validation.get_tabular_report(incl_footer_timings=False)._repr_html_()
19989+
19990+
assert "<strong>Notes</strong>" in html_notes_only
19991+
19992+
19993+
def test_global_config_footer_controls():
19994+
"""Test that global config settings for footer controls work correctly."""
19995+
19996+
small_table = load_dataset(dataset="small_table")
19997+
19998+
validation = (
19999+
Validate(data=small_table, label="Test Validation")
20000+
.col_vals_gt(columns="d", value=100)
20001+
.col_vals_regex(columns="invalid_column", pattern=r"test")
20002+
.interrogate()
20003+
)
20004+
20005+
# Save original config
20006+
original_config = PointblankConfig(
20007+
report_incl_header=global_config.report_incl_header,
20008+
report_incl_footer=global_config.report_incl_footer,
20009+
report_incl_footer_timings=global_config.report_incl_footer_timings,
20010+
report_incl_footer_notes=global_config.report_incl_footer_notes,
20011+
preview_incl_header=global_config.preview_incl_header,
20012+
)
20013+
20014+
try:
20015+
# Set global config to disable timings
20016+
config(
20017+
report_incl_header=True,
20018+
report_incl_footer=True,
20019+
report_incl_footer_timings=False,
20020+
report_incl_footer_notes=True,
20021+
preview_incl_header=True,
20022+
)
20023+
20024+
# Report should respect global config
20025+
html = validation.get_tabular_report()._repr_html_()
20026+
timing_count = html.count("font-variant-numeric: tabular-nums")
20027+
assert "<strong>Notes</strong>" in html
20028+
# Should have fewer timing elements
20029+
assert timing_count < 3
20030+
20031+
# Set global config to disable notes
20032+
config(
20033+
report_incl_header=True,
20034+
report_incl_footer=True,
20035+
report_incl_footer_timings=True,
20036+
report_incl_footer_notes=False,
20037+
preview_incl_header=True,
20038+
)
20039+
20040+
html = validation.get_tabular_report()._repr_html_()
20041+
assert "font-variant-numeric: tabular-nums" in html
20042+
assert "<strong>Notes</strong>" not in html
20043+
20044+
finally:
20045+
# Restore original config
20046+
config(
20047+
report_incl_header=original_config.report_incl_header,
20048+
report_incl_footer=original_config.report_incl_footer,
20049+
report_incl_footer_timings=original_config.report_incl_footer_timings,
20050+
report_incl_footer_notes=original_config.report_incl_footer_notes,
20051+
preview_incl_header=original_config.preview_incl_header,
20052+
)
20053+
20054+
20055+
def test_footer_controls_override_global_config():
20056+
"""Test that method parameters override global config settings."""
20057+
20058+
small_table = load_dataset(dataset="small_table")
20059+
20060+
validation = (
20061+
Validate(data=small_table, label="Test Validation")
20062+
.col_vals_gt(columns="d", value=100)
20063+
.col_vals_regex(columns="invalid_column", pattern=r"test")
20064+
.interrogate()
20065+
)
20066+
20067+
# Save original config
20068+
original_config = PointblankConfig(
20069+
report_incl_header=global_config.report_incl_header,
20070+
report_incl_footer=global_config.report_incl_footer,
20071+
report_incl_footer_timings=global_config.report_incl_footer_timings,
20072+
report_incl_footer_notes=global_config.report_incl_footer_notes,
20073+
preview_incl_header=global_config.preview_incl_header,
20074+
)
20075+
20076+
try:
20077+
# Set global config to disable both
20078+
config(
20079+
report_incl_header=True,
20080+
report_incl_footer=True,
20081+
report_incl_footer_timings=False,
20082+
report_incl_footer_notes=False,
20083+
preview_incl_header=True,
20084+
)
20085+
20086+
# Override with method parameters to enable both
20087+
html = validation.get_tabular_report(
20088+
incl_footer_timings=True, incl_footer_notes=True
20089+
)._repr_html_()
20090+
20091+
assert "font-variant-numeric: tabular-nums" in html
20092+
assert "<strong>Notes</strong>" in html
20093+
20094+
finally:
20095+
# Restore original config
20096+
config(
20097+
report_incl_header=original_config.report_incl_header,
20098+
report_incl_footer=original_config.report_incl_footer,
20099+
report_incl_footer_timings=original_config.report_incl_footer_timings,
20100+
report_incl_footer_notes=original_config.report_incl_footer_notes,
20101+
preview_incl_header=original_config.preview_incl_header,
20102+
)

0 commit comments

Comments
 (0)