Skip to content

Commit 1bf29db

Browse files
committed
Changed schema error vs warning strategy
1 parent 300b5cb commit 1bf29db

6 files changed

Lines changed: 124 additions & 13 deletions

File tree

.github/workflows/ci_cov.yaml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,7 @@ jobs:
3737
3838
# Install dependencies
3939
- name: Install dependencies
40-
run: uv pip install ruff -e ".[docs,test]"
41-
42-
# Run ruff
43-
- name: Lint with ruff
44-
run: |
45-
ruff check . --output-format=github
40+
run: uv pip install -e ".[docs,test]"
4641

4742
# Run unittest with coverage
4843
- name: Test with unittest and coverage

.github/workflows/ci_windows.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ jobs:
2525
with:
2626
python-version: ${{ matrix.python-version }}
2727
enable-cache: true
28+
cache-dependency-glob: "**/pyproject.toml"
2829

2930
- name: Create virtual environment
3031
shell: pwsh

hed/errors/error_reporter.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,22 @@ def filter_issues_by_severity(issues_list: list[dict], severity: int) -> list[di
288288
"""
289289
return [issue for issue in issues_list if issue["severity"] <= severity]
290290

291+
@staticmethod
292+
def separate_issues(issues_list: list[dict]) -> tuple[list[dict], list[dict]]:
293+
"""Separate a list of issues into errors and warnings.
294+
295+
Parameters:
296+
issues_list (list[dict]): A list of issue dictionaries, each containing a 'severity' key.
297+
298+
Returns:
299+
tuple[list[dict], list[dict]]: A tuple of (errors, warnings) where errors contains
300+
issues with severity <= ErrorSeverity.ERROR and warnings contains issues with
301+
severity >= ErrorSeverity.WARNING.
302+
"""
303+
errors = [issue for issue in issues_list if issue["severity"] <= ErrorSeverity.ERROR]
304+
warnings = [issue for issue in issues_list if issue["severity"] >= ErrorSeverity.WARNING]
305+
return errors, warnings
306+
291307
@staticmethod
292308
def format_error(error_type: str, *args, actual_error=None, **kwargs) -> list[dict]:
293309
"""Format an error based on the parameters, which vary based on what type of error this is.

hed/scripts/schema_script_util.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def _is_prerelease_partner(base_schema) -> bool:
3232
return hed_cache.get_hed_version_path(with_standard, check_prerelease=False) is None
3333

3434

35-
def validate_schema_object(base_schema, schema_name):
35+
def validate_schema_object(base_schema, schema_name, check_warnings=True):
3636
"""Validate a schema object by checking non-warning compliance and roundtrip conversion.
3737
3838
Tests the schema for non-warning compliance issues and validates that it can be successfully
@@ -41,14 +41,16 @@ def validate_schema_object(base_schema, schema_name):
4141
Parameters:
4242
base_schema (HedSchema): The schema object to validate.
4343
schema_name (str): The name/path of the schema for error reporting.
44+
check_warnings (bool): If True, include warnings in the validation. Default is True.
4445
4546
Returns:
4647
list: A list of validation issue strings. Empty if no issues found.
4748
"""
4849
validation_issues = []
4950
try:
5051
issues = base_schema.check_compliance()
51-
issues = [issue for issue in issues if issue.get("severity", ErrorSeverity.ERROR) == ErrorSeverity.ERROR]
52+
if not check_warnings:
53+
issues = [issue for issue in issues if issue.get("severity", ErrorSeverity.ERROR) == ErrorSeverity.ERROR]
5254
if issues:
5355
error_message = get_printable_issue_string(issues, title=schema_name)
5456
validation_issues.append(error_message)
@@ -74,15 +76,19 @@ def validate_schema_object(base_schema, schema_name):
7476
return validation_issues
7577

7678

77-
def validate_schema(file_path):
79+
def validate_schema(file_path, check_warnings=False):
7880
"""Validate a schema file, ensuring it can save/load and passes validation.
7981
8082
Loads the schema from file, checks the file extension is lowercase,
81-
and validates the schema object for compliance and roundtrip conversion.
83+
and validates the schema object for compliance errors and roundtrip conversion.
84+
Does not check for warnings.
8285
8386
Parameters:
8487
file_path (str): The path to the schema file to validate.
85-
88+
If loading a TSV file, this should be a single filename where:
89+
Template: basename.tsv, where files are named basename_Struct.tsv, basename_Tag.tsv, etc.
90+
Alternatively, you can point to a directory containing the .tsv files.
91+
check_warnings (bool): If True, include warnings in the validation. Default is False.
8692
Returns:
8793
list: A list of validation issue strings. Empty if no issues found.
8894
"""
@@ -96,7 +102,7 @@ def validate_schema(file_path):
96102
validation_issues = []
97103
try:
98104
base_schema = load_schema(file_path)
99-
validation_issues = validate_schema_object(base_schema, file_path)
105+
validation_issues = validate_schema_object(base_schema, file_path, check_warnings=check_warnings)
100106
except HedFileError as e:
101107
print(f"Saving/loading error: {file_path} {e.message}")
102108
error_text = e.message

tests/errors/test_error_reporter.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,43 @@ def test_get_code_counts(self):
299299
result_with_missing = ErrorHandler.get_code_counts(issues_with_missing_code)
300300
expected_with_missing = {"VALID_CODE": 2, "UNKNOWN": 1} # Default for missing code
301301
self.assertEqual(result_with_missing, expected_with_missing)
302+
303+
304+
class TestSeparateIssues(unittest.TestCase):
305+
"""Tests for ErrorHandler.separate_issues."""
306+
307+
@staticmethod
308+
def _make_issue(severity):
309+
return {"severity": severity, "message": "test"}
310+
311+
def test_empty_list(self):
312+
errors, warnings = ErrorHandler.separate_issues([])
313+
self.assertEqual(errors, [])
314+
self.assertEqual(warnings, [])
315+
316+
def test_only_errors(self):
317+
issues = [self._make_issue(ErrorSeverity.ERROR), self._make_issue(ErrorSeverity.ERROR)]
318+
errors, warnings = ErrorHandler.separate_issues(issues)
319+
self.assertEqual(len(errors), 2)
320+
self.assertEqual(len(warnings), 0)
321+
322+
def test_only_warnings(self):
323+
issues = [self._make_issue(ErrorSeverity.WARNING), self._make_issue(ErrorSeverity.WARNING)]
324+
errors, warnings = ErrorHandler.separate_issues(issues)
325+
self.assertEqual(len(errors), 0)
326+
self.assertEqual(len(warnings), 2)
327+
328+
def test_mixed(self):
329+
issues = [
330+
self._make_issue(ErrorSeverity.ERROR),
331+
self._make_issue(ErrorSeverity.WARNING),
332+
self._make_issue(ErrorSeverity.ERROR),
333+
]
334+
errors, warnings = ErrorHandler.separate_issues(issues)
335+
self.assertEqual(len(errors), 2)
336+
self.assertEqual(len(warnings), 1)
337+
338+
def test_original_list_unchanged(self):
339+
issues = [self._make_issue(ErrorSeverity.ERROR), self._make_issue(ErrorSeverity.WARNING)]
340+
ErrorHandler.separate_issues(issues)
341+
self.assertEqual(len(issues), 2)

tests/scripts/test_script_util.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
import contextlib
2+
import copy
13
import unittest
24
import os
35
import shutil
6+
47
from hed import load_schema_version
58
from hed.scripts.schema_script_util import (
69
add_extension,
710
sort_base_schemas,
811
validate_all_schema_formats,
912
validate_schema,
13+
validate_schema_object,
1014
validate_all_schemas,
1115
)
12-
import contextlib
1316

1417

1518
class TestAddExtension(unittest.TestCase):
@@ -265,3 +268,53 @@ def test_uppercase_extension_policy_enforcement(self):
265268
# Clean up
266269
if os.path.exists(uppercase_file):
267270
os.remove(uppercase_file)
271+
272+
273+
class TestCheckWarnings(unittest.TestCase):
274+
"""Tests for the check_warnings parameter in validate_schema_object and validate_schema."""
275+
276+
@classmethod
277+
def setUpClass(cls):
278+
clean = load_schema_version("8.3.0")
279+
cls.clean_schema = clean
280+
# Deep-copy so the cached shared instance is not mutated.
281+
# Setting version to a future value triggers SCHEMA_PRERELEASE_VERSION_USED (warning only).
282+
cls.warning_schema = copy.deepcopy(clean)
283+
cls.warning_schema.header_attributes["version"] = "999.0.0"
284+
285+
def test_clean_schema_check_warnings_false(self):
286+
"""A fully compliant schema produces no issues with check_warnings=False."""
287+
with contextlib.redirect_stdout(None):
288+
issues = validate_schema_object(self.clean_schema, "test", check_warnings=False)
289+
self.assertEqual(issues, [])
290+
291+
def test_clean_schema_check_warnings_true(self):
292+
"""A fully compliant schema produces no issues with check_warnings=True."""
293+
with contextlib.redirect_stdout(None):
294+
issues = validate_schema_object(self.clean_schema, "test", check_warnings=True)
295+
self.assertEqual(issues, [])
296+
297+
def test_warning_schema_check_warnings_true_reports_issues(self):
298+
"""A prerelease version generates a warning that is reported when check_warnings=True."""
299+
with contextlib.redirect_stdout(None):
300+
issues = validate_schema_object(self.warning_schema, "test", check_warnings=True)
301+
self.assertTrue(issues, "Expected at least one issue for prerelease version warning")
302+
303+
def test_warning_schema_check_warnings_false_suppresses_warnings(self):
304+
"""Warnings are suppressed and validation passes when check_warnings=False."""
305+
with contextlib.redirect_stdout(None):
306+
issues = validate_schema_object(self.warning_schema, "test", check_warnings=False)
307+
self.assertEqual(issues, [])
308+
309+
def test_validate_schema_default_is_warnings_false(self):
310+
"""validate_schema default check_warnings=False matches explicit False."""
311+
schema_path = os.path.join(
312+
os.path.dirname(os.path.dirname(__file__)),
313+
"data",
314+
"schema_tests",
315+
"HED8.2.0.mediawiki",
316+
)
317+
with contextlib.redirect_stdout(None):
318+
default_issues = validate_schema(schema_path)
319+
explicit_issues = validate_schema(schema_path, check_warnings=False)
320+
self.assertEqual(default_issues, explicit_issues)

0 commit comments

Comments
 (0)