diff --git a/sdmetrics/reports/base_report.py b/sdmetrics/reports/base_report.py index bb6a6b0a..8b02ec46 100644 --- a/sdmetrics/reports/base_report.py +++ b/sdmetrics/reports/base_report.py @@ -130,12 +130,14 @@ def _print_results(self, verbose): if verbose: sys.stdout.write(f'Overall Score (Average): {round(self._overall_score * 100, 2)}%\n\n') - def _get_skipped_properties(self, metadata): + def _get_skipped_properties(self, metadata, constraints=None): """Return properties that should not be computed for the metadata. Args: metadata (dict): The metadata dict. + constraints (list[dict] or None): + The constraints given to the report. Defaults to None. Returns: set[str]: @@ -143,7 +145,7 @@ def _get_skipped_properties(self, metadata): """ return set() - def generate(self, real_data, synthetic_data, metadata, verbose=True): + def generate(self, real_data, synthetic_data, metadata, constraints=None, verbose=True): """Generate report. This method generates the report by iterating through each property and calculating @@ -156,13 +158,16 @@ def generate(self, real_data, synthetic_data, metadata, verbose=True): The synthetic data. metadata (dict): The metadata, which contains each column's data type as well as relationships. + constraints (list[dict] or None): + A list of constraints to evaluate their adherence, each represented as a + dictionary with a ``class_name`` and a ``parameters`` key. Defaults to None. verbose (bool): Whether or not to print report summary and progress. """ real_data = deepcopy(real_data) synthetic_data = deepcopy(synthetic_data) self._validate(real_data, synthetic_data, metadata) - self._skipped_properties = self._get_skipped_properties(metadata) + self._skipped_properties = self._get_skipped_properties(metadata, constraints) self._original_datetime_columns = self.convert_datetimes( real_data, synthetic_data, metadata ) @@ -192,15 +197,22 @@ def generate(self, real_data, synthetic_data, metadata, verbose=True): property_instance.is_computed = False property_instance.details = pd.DataFrame() if verbose: - sys.stdout.write( - f'{property_description}: N/A\n{self._skipped_property_message}\n\n' + skipped_message = getattr( + property_instance, '_skipped_message', self._skipped_property_message ) + sys.stdout.write(f'{property_description}: N/A\n{skipped_message}\n\n') sys.stdout.flush() continue + property_arguments = {} + if property_name == 'Constraint Validity': + property_arguments['constraints'] = constraints + if verbose: - num_iterations = int(property_instance._get_num_iterations(metadata)) + num_iterations = int( + property_instance._get_num_iterations(metadata, **property_arguments) + ) progress_bar = tqdm.tqdm( total=num_iterations, file=sys.stdout, bar_format='{desc}|{bar}{r_bar}|' ) @@ -220,7 +232,7 @@ def generate(self, real_data, synthetic_data, metadata, verbose=True): self._properties[property_name].num_rows_subsample = self.num_rows_subsample score = self._properties[property_name].get_score( - real_data, synthetic_data, metadata, progress_bar=progress_bar + real_data, synthetic_data, metadata, progress_bar=progress_bar, **property_arguments ) scores.append(score) if verbose: diff --git a/sdmetrics/reports/base_unified_report.py b/sdmetrics/reports/base_unified_report.py index 50c39f57..02b0acd1 100644 --- a/sdmetrics/reports/base_unified_report.py +++ b/sdmetrics/reports/base_unified_report.py @@ -16,21 +16,26 @@ class BaseUnifiedReport(BaseMultiTableReport): }) _skipped_property_message = 'This property does not apply to single-table data.' - def _get_skipped_properties(self, metadata): + def _get_skipped_properties(self, metadata, constraints=None): """Return properties unavailable to single-table data. Args: metadata (dict): The metadata dict. + constraints (list[dict] or None): + The constraints given to the report. Defaults to None. Returns: set[str]: Names of properties to skip. """ + skipped_properties = super()._get_skipped_properties(metadata, constraints) if len(metadata.get('tables', {})) == 1: - return self._SINGLE_TABLE_SKIPPED_PROPERTIES + skipped_properties.update(self._SINGLE_TABLE_SKIPPED_PROPERTIES) + if not constraints: + skipped_properties.add('Constraint Validity') - return super()._get_skipped_properties(metadata) + return skipped_properties def _validate_data_format(self, real_data, synthetic_data): """Validate that the real and synthetic data have compatible formats. diff --git a/sdmetrics/reports/diagnostic_report.py b/sdmetrics/reports/diagnostic_report.py index 5077f41e..75e3160d 100644 --- a/sdmetrics/reports/diagnostic_report.py +++ b/sdmetrics/reports/diagnostic_report.py @@ -1,7 +1,12 @@ """Unified diagnostic report.""" from sdmetrics.reports.base_unified_report import BaseUnifiedReport -from sdmetrics.reports.multi_table._properties import DataValidity, RelationshipValidity, Structure +from sdmetrics.reports.multi_table._properties import ( + ConstraintValidity, + DataValidity, + RelationshipValidity, + Structure, +) class DiagnosticReport(BaseUnifiedReport): @@ -17,6 +22,7 @@ def __init__(self): 'Data Validity': DataValidity(), 'Data Structure': Structure(), 'Relationship Validity': RelationshipValidity(), + 'Constraint Validity': ConstraintValidity(), } def _validate_metadata_matches_data(self, real_data, synthetic_data, metadata): diff --git a/sdmetrics/reports/multi_table/_properties/constraint_validity.py b/sdmetrics/reports/multi_table/_properties/constraint_validity.py index 8bb5ed02..6ec343cf 100644 --- a/sdmetrics/reports/multi_table/_properties/constraint_validity.py +++ b/sdmetrics/reports/multi_table/_properties/constraint_validity.py @@ -17,6 +17,7 @@ class ConstraintValidity(BaseMultiTableProperty): """ _num_iteration_case = 'constraint' + _skipped_message = 'No constraints were provided.' def _get_num_iterations(self, metadata, constraints=None): """Get the number of iterations for the property, which is one per constraint.""" diff --git a/sdmetrics/reports/multi_table/base_multi_table_report.py b/sdmetrics/reports/multi_table/base_multi_table_report.py index e6b78a4c..a071012a 100644 --- a/sdmetrics/reports/multi_table/base_multi_table_report.py +++ b/sdmetrics/reports/multi_table/base_multi_table_report.py @@ -84,7 +84,7 @@ def _validate_metadata_matches_data(self, real_data, synthetic_data, metadata): self._validate_relationships(real_data, synthetic_data, metadata) - def generate(self, real_data, synthetic_data, metadata, verbose=True): + def generate(self, real_data, synthetic_data, metadata, constraints=None, verbose=True): """Generate report. This method generates the report by iterating through each property and calculating @@ -97,10 +97,12 @@ def generate(self, real_data, synthetic_data, metadata, verbose=True): The synthetic data. metadata (dict): The metadata, which contains each column's data type as well as relationships. + constraints (list[dict] or None): + A list of constraints to evaluate their adherence. Defaults to None. verbose (bool): Whether or not to print report summary and progress. """ - results = super().generate(real_data, synthetic_data, metadata, verbose) + results = super().generate(real_data, synthetic_data, metadata, constraints, verbose) self.table_names = list(metadata.get('tables', {}).keys()) return results @@ -173,6 +175,9 @@ def get_visualization(self, property_name, table_name=None): if property_name == 'Data Structure': return self._properties[property_name].get_visualization(table_name) + if property_name == 'Constraint Validity': + return self._properties[property_name].get_visualization() + if table_name is None: raise ValueError('Please provide a table name to get a visualization for the property.') diff --git a/tests/integration/reports/test_unified_reports.py b/tests/integration/reports/test_unified_reports.py index 4927915b..7420d37b 100644 --- a/tests/integration/reports/test_unified_reports.py +++ b/tests/integration/reports/test_unified_reports.py @@ -2,8 +2,10 @@ import numpy as np import pandas as pd +import pytest from sdmetrics.demos import load_multi_table_demo, load_single_table_demo +from sdmetrics.errors import VisualizationUnavailableError from sdmetrics.reports import DiagnosticReport, QualityReport from tests.utils import assert_report_scores_are_not_nan @@ -124,16 +126,31 @@ def _load_single_table_quality_report_data(): def test_unified_diagnostic_report_single_table(): # Setup + inequality = { + 'class_name': 'Inequality', + 'parameters': { + 'table_name': 'student_placements', + 'low_column_name': 'start_date', + 'high_column_name': 'end_date', + }, + } real_data, synthetic_data, metadata = load_single_table_demo() + synthetic_data['student_placements'].loc[[43, 93, 179], 'end_date'] = None # Run report = DiagnosticReport() - report.generate(real_data, synthetic_data, metadata, verbose=False) + report.generate(real_data, synthetic_data, metadata, [inequality], verbose=False) # Assert expected_properties = pd.DataFrame({ - 'Property': ['Data Validity', 'Data Structure'], - 'Score': [1.0, 1.0], + 'Property': ['Data Validity', 'Data Structure', 'Constraint Validity'], + 'Score': [1.0, 1.0, 1.0], + }) + expected_details_constraint_validity = pd.DataFrame({ + 'Constraint': ['Inequality'], + 'Metric': ['ConstraintAdherence'], + 'Parameters': [inequality['parameters']], + 'Score': [1.0], }) expected_details_data_validity = pd.DataFrame({ 'Table': ['student_placements'] * 20, @@ -196,6 +213,9 @@ def test_unified_diagnostic_report_single_table(): pd.testing.assert_frame_equal( report.get_details('Data Structure'), expected_details_data_structure ) + pd.testing.assert_frame_equal( + report.get_details('Constraint Validity'), expected_details_constraint_validity + ) assert report.get_score() == 1.0 assert_report_scores_are_not_nan(report) _assert_report_info( @@ -308,12 +328,14 @@ def test_unified_diagnostic_report_single_table_verbose_skips_relationship_valid # Assert expected_lines = [ 'Generating report ...', - '(1/3) Evaluating Data Validity:', + '(1/4) Evaluating Data Validity:', 'Data Validity Score: 100.0%', - '(2/3) Evaluating Data Structure:', + '(2/4) Evaluating Data Structure:', 'Data Structure Score: 100.0%', - '(3/3) Evaluating Relationship Validity: N/A', + '(3/4) Evaluating Relationship Validity: N/A', 'This property does not apply to single-table data.', + '(4/4) Evaluating Constraint Validity: N/A', + 'No constraints were provided.', 'Overall Score (Average): 100.0%', ] for line in expected_lines: @@ -322,10 +344,48 @@ def test_unified_diagnostic_report_single_table_verbose_skips_relationship_valid assert list(report.get_properties()['Property']) == [ 'Data Validity', 'Data Structure', + 'Constraint Validity', ] assert report.get_score() == 1.0 +def test_unified_diagnostic_report_single_table_verbose_with_constraints(capsys): + """Test unified diagnostic report prints the Constraint Validity progress for single-table.""" + # Setup + inequality = { + 'class_name': 'Inequality', + 'parameters': { + 'table_name': 'student_placements', + 'low_column_name': 'start_date', + 'high_column_name': 'end_date', + }, + } + real_data, synthetic_data, metadata = load_single_table_demo() + + # Run + report = DiagnosticReport() + report.generate(real_data, synthetic_data, metadata, [inequality], verbose=True) + output = capsys.readouterr().out + + # Assert + expected_lines = [ + 'Generating report ...', + '(1/4) Evaluating Data Validity:', + 'Data Validity Score: 100.0%', + '(2/4) Evaluating Data Structure:', + 'Data Structure Score: 100.0%', + '(3/4) Evaluating Relationship Validity: N/A', + 'This property does not apply to single-table data.', + '(4/4) Evaluating Constraint Validity:', + 'Constraint Validity Score: 98.6%', + 'Overall Score (Average): 99.53%', + ] + for line in expected_lines: + assert line in output + + assert report.get_score() >= 0.99 + + def test_diagnostic_report_with_ordinal_sdtype(): """Test diagnostic report handles ordinal sdtype correctly. @@ -394,16 +454,33 @@ def test_unified_quality_report_single_table_verbose_skips_relationship_properti def test_unified_diagnostic_report_multi_table(): # Setup + constraints = [ + { + 'class_name': 'FixedCombinations', + 'parameters': {'table_name': 'sessions', 'column_names': ['device', 'os']}, + }, + ] real_data, synthetic_data, metadata = load_multi_table_demo() # Run report = DiagnosticReport() - report.generate(real_data, synthetic_data, metadata, verbose=False) + report.generate(real_data, synthetic_data, metadata, constraints, verbose=False) # Assert expected_properties = pd.DataFrame({ - 'Property': ['Data Validity', 'Data Structure', 'Relationship Validity'], - 'Score': [1.0, 1.0, 1.0], + 'Property': [ + 'Data Validity', + 'Data Structure', + 'Relationship Validity', + 'Constraint Validity', + ], + 'Score': [1.0, 1.0, 1.0, 1.0], + }) + expected_details_constraint_validity = pd.DataFrame({ + 'Constraint': ['FixedCombinations'], + 'Metric': ['ConstraintAdherence'], + 'Parameters': [constraints[0]['parameters']], + 'Score': [1.0], }) expected_details_data_validity = pd.DataFrame({ 'Table': [ @@ -493,6 +570,12 @@ def test_unified_diagnostic_report_multi_table(): pd.testing.assert_frame_equal( report.get_details('Data Validity', 'users'), expected_details_users ) + pd.testing.assert_frame_equal( + report.get_details('Constraint Validity'), expected_details_constraint_validity + ) + pd.testing.assert_frame_equal( + report.get_details('Constraint Validity', 'users'), expected_details_constraint_validity + ) assert report.get_score() == 1.0 assert_report_scores_are_not_nan(report) _assert_report_info( @@ -706,12 +789,91 @@ def test_unified_diagnostic_report_multi_table_with_no_relationships_does_not_sk 'Data Validity', 'Data Structure', 'Relationship Validity', + 'Constraint Validity', ] assert pd.isna( properties.loc[properties['Property'] == 'Relationship Validity', 'Score'].iloc[0] ) +def test_unified_diagnostic_report_multi_table_verbose_with_constraints(capsys): + """Test the Constraint Validity property runs last with one progress step per constraint.""" + # Setup + constraints = [ + { + 'class_name': 'FixedCombinations', + 'parameters': {'table_name': 'sessions', 'column_names': ['device', 'os']}, + }, + ] + real_data, synthetic_data, metadata = load_multi_table_demo() + + # Run + report = DiagnosticReport() + report.generate(real_data, synthetic_data, metadata, constraints, verbose=True) + output = capsys.readouterr().out + + # Assert + expected_lines = [ + 'Generating report ...', + '(1/4) Evaluating Data Validity:', + 'Data Validity Score: 100.0%', + '(2/4) Evaluating Data Structure:', + 'Data Structure Score: 100.0%', + '(3/4) Evaluating Relationship Validity:', + 'Relationship Validity Score: 100.0%', + '(4/4) Evaluating Constraint Validity:', + 'Constraint Validity Score: 100.0%', + 'Overall Score (Average): 100.0%', + ] + for line in expected_lines: + assert line in output + + assert report.get_score() == 1.0 + + +@pytest.mark.parametrize('constraints', [None, []]) +def test_unified_diagnostic_report_without_constraints(constraints): + """Test the Constraint Validity score is NaN and ignored when there are no constraints.""" + # Setup + real_data, synthetic_data, metadata = load_multi_table_demo() + + # Run + report = DiagnosticReport() + report.generate(real_data, synthetic_data, metadata, constraints, verbose=False) + properties = report.get_properties() + details = report.get_details('Constraint Validity') + + # Assert + assert list(properties['Property']) == [ + 'Data Validity', + 'Data Structure', + 'Relationship Validity', + 'Constraint Validity', + ] + assert pd.isna(properties.loc[properties['Property'] == 'Constraint Validity', 'Score'].iloc[0]) + assert details.empty + assert report.get_score() == 1.0 + + +def test_unified_diagnostic_report_constraint_validity_visualization(): + """Test asking for the Constraint Validity visualization raises a friendly error.""" + # Setup + real_data, synthetic_data, metadata = load_multi_table_demo() + report = DiagnosticReport() + report.generate(real_data, synthetic_data, metadata, verbose=False) + expected_message = ( + 'Error: No visualization is available for Constraint Validity. To see the ' + "detailed score breakdowns, use the 'get_details' function." + ) + + # Run and Assert + with pytest.raises(VisualizationUnavailableError, match=expected_message): + report.get_visualization('Constraint Validity') + + with pytest.raises(VisualizationUnavailableError, match=expected_message): + report.get_visualization('Constraint Validity', 'users') + + def test_unified_quality_report_multi_table_with_no_relationships_does_not_skip_properties(): """Test unified quality reports do not skip properties (2 tables, no relationships).""" # Setup diff --git a/tests/unit/reports/multi_table/_properties/test_constraint_validity.py b/tests/unit/reports/multi_table/_properties/test_constraint_validity.py index c4417724..dd636128 100644 --- a/tests/unit/reports/multi_table/_properties/test_constraint_validity.py +++ b/tests/unit/reports/multi_table/_properties/test_constraint_validity.py @@ -80,6 +80,7 @@ def test__init__(self): # Assert assert isinstance(constraint_validity, BaseMultiTableProperty) assert constraint_validity._num_iteration_case == 'constraint' + assert constraint_validity._skipped_message == 'No constraints were provided.' assert constraint_validity.is_computed is False assert constraint_validity.details.empty diff --git a/tests/unit/reports/multi_table/test_base_multi_table_report.py b/tests/unit/reports/multi_table/test_base_multi_table_report.py index 63a7dbd1..db52179a 100644 --- a/tests/unit/reports/multi_table/test_base_multi_table_report.py +++ b/tests/unit/reports/multi_table/test_base_multi_table_report.py @@ -7,6 +7,8 @@ import pytest from sdmetrics.demos import load_demo +from sdmetrics.errors import VisualizationUnavailableError +from sdmetrics.reports.multi_table._properties import ConstraintValidity from sdmetrics.reports.multi_table.base_multi_table_report import BaseMultiTableReport from sdmetrics.reports.utils import DEFAULT_NUM_ROWS_SUBSAMPLE @@ -226,7 +228,7 @@ def test_generate(self, mock_generate): # Assert assert report.table_names == ['Table_1', 'Table_2'] - mock_generate.assert_called_once_with(real_data, synthetic_data, metadata, True) + mock_generate.assert_called_once_with(real_data, synthetic_data, metadata, None, True) def test__check_table_names(self): """Test the ``_check_table_names`` method.""" @@ -404,6 +406,21 @@ def test_get_visualization_without_table_name(self): with pytest.raises(ValueError, match=expected_error_message): report.get_visualization('Property_1') + @pytest.mark.parametrize('table_name', [None, 'Table_1']) + def test_get_visualization_for_constraint_validity_property(self, table_name): + """Test ``get_visualization`` raises the property error, with or without a table.""" + # Setup + report = BaseMultiTableReport() + report._properties = {'Constraint Validity': ConstraintValidity()} + expected_message = ( + 'Error: No visualization is available for Constraint Validity. To see the ' + "detailed score breakdowns, use the 'get_details' function." + ) + + # Run and Assert + with pytest.raises(VisualizationUnavailableError, match=expected_message): + report.get_visualization('Constraint Validity', table_name) + def test_get_visualization_for_structure_property(self): """Test the ``get_visualization`` method for the structure property.""" # Setup diff --git a/tests/unit/reports/multi_table/test_diagnostic_report.py b/tests/unit/reports/multi_table/test_diagnostic_report.py index 4cc35c87..be483989 100644 --- a/tests/unit/reports/multi_table/test_diagnostic_report.py +++ b/tests/unit/reports/multi_table/test_diagnostic_report.py @@ -4,7 +4,12 @@ import pytest from sdmetrics.reports import DiagnosticReport -from sdmetrics.reports.multi_table._properties import DataValidity, RelationshipValidity, Structure +from sdmetrics.reports.multi_table._properties import ( + ConstraintValidity, + DataValidity, + RelationshipValidity, + Structure, +) class TestDiagnosticReport: @@ -52,3 +57,4 @@ def test___init__unified(self): assert isinstance(report._properties['Data Validity'], DataValidity) assert isinstance(report._properties['Data Structure'], Structure) assert isinstance(report._properties['Relationship Validity'], RelationshipValidity) + assert isinstance(report._properties['Constraint Validity'], ConstraintValidity) diff --git a/tests/unit/reports/test_base_report.py b/tests/unit/reports/test_base_report.py index 32aa85d9..fdf8daa8 100644 --- a/tests/unit/reports/test_base_report.py +++ b/tests/unit/reports/test_base_report.py @@ -481,6 +481,7 @@ def test_generate_verbose_with_skipped_property(self, mock_tqdm, mock_write): base_report._skipped_properties = {'Old Property'} base_report._get_skipped_properties = Mock(return_value={'Property 1'}) base_report._properties['Property 1'] = Mock() + del base_report._properties['Property 1']._skipped_message base_report._properties['Property 1'].details = pd.DataFrame({'old': [1]}) base_report._properties['Property 1'].is_computed = True base_report._properties['Property 1']._compute_average.return_value = float('nan') @@ -507,7 +508,7 @@ def test_generate_verbose_with_skipped_property(self, mock_tqdm, mock_write): assert base_report._properties['Property 1'].details.empty assert not base_report._properties['Property 1'].is_computed assert base_report._skipped_properties == {'Property 1'} - base_report._get_skipped_properties.assert_called_once_with(metadata) + base_report._get_skipped_properties.assert_called_once_with(metadata, None) base_report._properties['Property 2'].get_score.assert_called_once_with( copied_real_data, copied_synthetic_data, metadata, progress_bar=mock_tqdm.return_value ) @@ -528,6 +529,68 @@ def test_generate_verbose_with_skipped_property(self, mock_tqdm, mock_write): ) base_report._print_results.assert_called_once_with(True) + @patch('tqdm.tqdm') + def test_generate_with_constraints(self, mock_tqdm): + """Test ``generate`` only passes the constraints to the Constraint Validity property.""" + # Setup + base_report = BaseReport() + base_report._validate = Mock() + base_report._print_results = Mock() + base_report._properties['Property 1'] = Mock() + base_report._properties['Property 1'].get_score.return_value = 1.0 + base_report._properties['Property 1']._get_num_iterations.return_value = 4 + base_report._properties['Constraint Validity'] = Mock() + base_report._properties['Constraint Validity'].get_score.return_value = 0.5 + base_report._properties['Constraint Validity']._get_num_iterations.return_value = 1 + real_data = pd.DataFrame({'column1': [1, 2, 3]}) + synthetic_data = pd.DataFrame({'column1': [1, 2, 3]}) + metadata = {'columns': {'column1': {'sdtype': 'numerical'}}} + constraints = [{'class_name': 'Range', 'parameters': {}}] + + # Run + base_report.generate(real_data, synthetic_data, metadata, constraints, verbose=True) + + # Assert + copied_real_data, copied_synthetic_data, _ = base_report._validate.call_args.args + base_report._properties['Property 1']._get_num_iterations.assert_called_once_with(metadata) + base_report._properties['Property 1'].get_score.assert_called_once_with( + copied_real_data, copied_synthetic_data, metadata, progress_bar=mock_tqdm.return_value + ) + base_report._properties['Constraint Validity']._get_num_iterations.assert_called_once_with( + metadata, constraints=constraints + ) + base_report._properties['Constraint Validity'].get_score.assert_called_once_with( + copied_real_data, + copied_synthetic_data, + metadata, + progress_bar=mock_tqdm.return_value, + constraints=constraints, + ) + assert base_report._overall_score == 0.75 + + @patch('sys.stdout.write') + @patch('tqdm.tqdm') + def test_generate_verbose_with_skipped_property_message(self, mock_tqdm, mock_write): + """Test a skipped property prints its own message when it defines one.""" + # Setup + base_report = BaseReport() + base_report._validate = Mock() + base_report.convert_datetimes = Mock() + base_report._print_results = Mock() + base_report._get_skipped_properties = Mock(return_value={'Property 1'}) + base_report._properties['Property 1'] = Mock(_skipped_message='Property message.') + base_report._properties['Property 1']._compute_average.return_value = float('nan') + real_data = pd.DataFrame({'column1': [1, 2, 3]}) + synthetic_data = pd.DataFrame({'column1': [1, 2, 3]}) + metadata = {'columns': {'column1': {'sdtype': 'numerical'}}} + + # Run + base_report.generate(real_data, synthetic_data, metadata, verbose=True) + + # Assert + mock_write.assert_any_call('(1/1) Evaluating Property 1: N/A\nProperty message.\n\n') + base_report._properties['Property 1'].get_score.assert_not_called() + def test__check_report_generated(self): """Test the ``check_report_generated`` method.""" # Setup diff --git a/tests/unit/reports/test_base_unified_report.py b/tests/unit/reports/test_base_unified_report.py index a6077037..3926a646 100644 --- a/tests/unit/reports/test_base_unified_report.py +++ b/tests/unit/reports/test_base_unified_report.py @@ -181,8 +181,10 @@ def test__get_skipped_properties_single_table(self): 'relationships': [], } + constraints = [{'class_name': 'Range', 'parameters': {}}] + # Run - skipped_properties = base_report._get_skipped_properties(metadata) + skipped_properties = base_report._get_skipped_properties(metadata, constraints) # Assert assert skipped_properties == {'Relationship Validity', 'Cardinality', 'Intertable Trends'} @@ -207,12 +209,50 @@ def test__get_skipped_properties_multi_table(self): 'relationships': [], } + constraints = [{'class_name': 'Range', 'parameters': {}}] + # Run - skipped_properties = base_report._get_skipped_properties(metadata) + skipped_properties = base_report._get_skipped_properties(metadata, constraints) # Assert assert skipped_properties == set() + @pytest.mark.parametrize('constraints', [None, []]) + def test__get_skipped_properties_without_constraints(self, constraints): + """Test Constraint Validity is skipped when no constraints are given.""" + # Setup + base_report = BaseUnifiedReport() + metadata = { + 'tables': { + 'table1': {'columns': {'column1': {'sdtype': 'numerical'}}}, + 'table2': {'columns': {'column2': {'sdtype': 'numerical'}}}, + }, + 'relationships': [], + } + + # Run + skipped_properties = base_report._get_skipped_properties(metadata, constraints) + + # Assert + assert skipped_properties == {'Constraint Validity'} + + def test__get_skipped_properties_single_table_without_constraints(self): + """Test the single-table skips are combined with the Constraint Validity skip.""" + # Setup + base_report = BaseUnifiedReport() + metadata = {'tables': {'table1': {'columns': {'column1': {'sdtype': 'numerical'}}}}} + + # Run + skipped_properties = base_report._get_skipped_properties(metadata) + + # Assert + assert skipped_properties == { + 'Relationship Validity', + 'Cardinality', + 'Intertable Trends', + 'Constraint Validity', + } + @patch('sdmetrics.reports.base_unified_report._validate_unified_metadata') def test__validate_metadata_error(self, mock__validate_metadata): """Test the ``_validate`` method when metadata validation fails."""