From 0e4f9704ca6e6f9162c42a844c102a63d058542f Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 8 Jan 2026 11:57:27 -0500 Subject: [PATCH 1/5] reorder readme, fix tests --- .github/workflows/build-binary.yml | 22 +++-- README.md | 81 ++++++++-------- core.py | 146 +++++++++++++++++------------ scripts/run_validation.py | 2 +- 4 files changed, 143 insertions(+), 108 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index 2e8a529fa..ec10d0434 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -46,21 +46,29 @@ jobs: if ./core --help; then echo "test passed"; else echo "test failed"; exit 1; fi fi shell: bash - - name: Test Binary test-validate command + - name: Test Binary test-validate commands run: | cd dist/output/${{ inputs.name }}/core if [ "${{ runner.os }}" = "Windows" ]; then - ./core.exe test-validate + ./core.exe test-validate-json + json_exit=$? + ./core.exe test-validate-xpt + xpt_exit=$? else chmod +x core chmod -R 755 . chmod -R +r resources/ - ./core test-validate + ./core test-validate-json + json_exit=$? + ./core test-validate-xpt + xpt_exit=$? fi - exit_code=$? - if [ $exit_code -eq 0 ]; then - echo "Test validate succeeded" + + if [ $json_exit -eq 0 ] && [ $xpt_exit -eq 0 ]; then + echo "All validation tests succeeded" + exit 0 else - echo "Test validate failed with exit code $exit_code" + echo "Validation tests failed (JSON: $json_exit, XPT: $xpt_exit)" + exit 1 fi shell: bash diff --git a/README.md b/README.md index 9331483e4..682fb38c9 100644 --- a/README.md +++ b/README.md @@ -47,13 +47,51 @@ Once downloaded, simply unzip the file and run the following command based on yo ## Command-line Interface -**Note**: the following examples are applicable to the source code and have references to "`python core.py`". When using the executable version as described in the [Quick Start](#quick-start) above, instances of "`python cored.py`" should be replaced with "`.\core.exe`" (Windows) or "`./core`" (Linux/Mac). You can also run directly on the source code by following the [Cloning](#cloning) instructions. +**Note**: the following examples are applicable to the source code and have references to "`python core.py`". When using the executable version as described in the [Quick Start](#quick-start) above, instances of "`.\core.exe`" (Windows) or "`./core`" (Linux/Mac) should be replaced with "`python core.py`" . ### Running a validation (`validate`) -Clone the repository and run `python core.py --help` to see the full list of commands. +0. You will need [git](https://git-scm.com/install/) installed. -Run `python core.py validate --help` to see the list of validation options. +1. Clone the repository: + + In the terminal, navigate to the directory you intend to install CORE rules engine in + + ``` + git clone https://github.com/cdisc-org/cdisc-rules-engine + ``` + +2. Ensure you have Python 3.12 installed: + You can check your Python version with: + + ``` + python --version + ``` + + If you don't have Python 3.12, please download and install it from [python.org](https://www.python.org/downloads/) or using your system's package manager. + +3. Installing dependencies + + These steps should be run before running any tests or core commands using the non compiled version. + + - Create a virtual environment: + + `python -m venv ` + + **Note**: if you have multiple versions of python on your machine, you can call python 3.12 for the virtual environment's creation instead of the above command: + `python3.12 -m venv ` + + - Activate the virtual environment: + + `.//bin/activate` -- on linux/mac
+ `.\\Scripts\Activate` -- on windows + + - Install the requirements. + + `python -m pip install -r requirements-dev.txt` # From the root directory + +4. Validate + Run `python core.py validate --help` to see the list of validation options. ``` -ca, --cache TEXT Relative path to cache files containing pre @@ -431,43 +469,6 @@ pip install cdisc-rules-engine For implementation instructions, see [PYPI.md](PYPI.md). -### Cloning - -In the terminal, navigate to the directory you intend to install CORE rules engine in - -1. Clone the repository: - - ``` - git clone https://github.com/cdisc-org/cdisc-rules-engine - ``` - -2. Ensure you have Python 3.12 installed: - You can check your Python version with: - ``` - python --version - ``` - If you don't have Python 3.12, please download and install it from [python.org](https://www.python.org/downloads/) or using your system's package manager. - -### Installing dependencies - -These steps should be run before running any tests or core commands using the non compiled version. - -- Create a virtual environment: - - `python -m venv ` - -NOTE: if you have multiple versions of python on your machine, you can call python 3.12 for the virtual environment's creation instead of the above command: -`python3.12 -m venv ` - -- Activate the virtual environment: - -`.//bin/activate` -- on linux/mac
-`.\\Scripts\Activate` -- on windows - -- Install the requirements. - -`python -m pip install -r requirements-dev.txt` # From the root directory - ### Creating an executable version **Note:** Further directions to create your own executable are contained in [README_Build_Executable.md](README_Build_Executable.md) if you wish to build an unofficial release executable for your own use. diff --git a/core.py b/core.py index 681cdd85c..72448f346 100644 --- a/core.py +++ b/core.py @@ -787,8 +787,8 @@ def list_ct(cache_path: str, subsets: tuple[str]): @click.command() -def test_validate(): - """**Release Test** validate command for executable.""" +def test_validate_json(): + """**Release Test** JSON validation for executable.""" try: import sys import os @@ -803,67 +803,90 @@ def test_validate(): base_path = os.path.join("tests", "resources", "datasets") ts_path = os.path.join(base_path, "TS.json") - ae_path = os.path.join(base_path, "ae.xpt") - if not all(os.path.exists(path) for path in [ts_path, ae_path]): - raise FileNotFoundError( - "Test datasets not found in tests/resources/datasets" - ) + if not os.path.exists(ts_path): + raise FileNotFoundError(f"Test dataset not found: {ts_path}") + + cache_path = DEFAULT_CACHE_PATH + pool_size = 10 + log_level = "disabled" + standard = "sdtmig" + version = "3.4" + output_format = {ReportTypes.XLSX.value} + external_dictionaries = ExternalDictionariesContainer({}) + progress = ProgressParameterOptions.BAR.value + max_report_errors = (0, False) with tempfile.TemporaryDirectory() as temp_dir: - cache_path = DEFAULT_CACHE_PATH - pool_size = 10 - log_level = "disabled" - report_template = None - standard = "sdtmig" - version = "3.4" - substandard = None - controlled_terminology_package = set() - json_output = os.path.join(temp_dir, "json_validation_output") - xpt_output = os.path.join(temp_dir, "xpt_validation_output") - output_format = {ReportTypes.XLSX.value} - raw_report = False - define_version = None - external_dictionaries = ExternalDictionariesContainer({}) - rules = [] - exclude_rules = [] - local_rules = None - custom_standard = False - progress = ProgressParameterOptions.BAR.value - define_xml_path = None - validate_xml = False - max_report_rows = None - max_report_errors = (0, False) json_output = os.path.join(temp_dir, "json_validation_output") - jsonata_custom_functions = () run_validation( Validation_args( cache_path, pool_size, [ts_path], log_level, - report_template, + None, standard, version, - substandard, - controlled_terminology_package, + None, + set(), json_output, output_format, - raw_report, - define_version, + False, + None, external_dictionaries, - rules, - exclude_rules, - local_rules, - custom_standard, + [], + [], + None, + False, progress, - define_xml_path, - validate_xml, - jsonata_custom_functions, - max_report_rows, + None, + False, + (), + None, max_report_errors, ) ) print("JSON validation completed successfully!") + sys.exit(0) + except Exception as e: + import traceback + + print(f"JSON validation test failed: {str(e)}") + print(traceback.format_exc()) + sys.exit(1) + + +@click.command() +def test_validate_xpt(): + """**Release Test** XPT validation for executable.""" + try: + import sys + import os + from cdisc_rules_engine.models.validation_args import Validation_args + from cdisc_rules_engine.models.external_dictionaries_container import ( + ExternalDictionariesContainer, + ) + from cdisc_rules_engine.enums.report_types import ReportTypes + from cdisc_rules_engine.enums.progress_parameter_options import ( + ProgressParameterOptions, + ) + + base_path = os.path.join("tests", "resources", "datasets") + ae_path = os.path.join(base_path, "ae.xpt") + if not os.path.exists(ae_path): + raise FileNotFoundError(f"Test dataset not found: {ae_path}") + + cache_path = DEFAULT_CACHE_PATH + pool_size = 10 + log_level = "disabled" + standard = "sdtmig" + version = "3.4" + output_format = {ReportTypes.XLSX.value} + external_dictionaries = ExternalDictionariesContainer({}) + progress = ProgressParameterOptions.BAR.value + max_report_errors = (0, False) + + with tempfile.TemporaryDirectory() as temp_dir: xpt_output = os.path.join(temp_dir, "xpt_validation_output") run_validation( Validation_args( @@ -871,37 +894,40 @@ def test_validate(): pool_size, [ae_path], log_level, - report_template, + None, standard, version, - substandard, - controlled_terminology_package, + None, + set(), xpt_output, output_format, - raw_report, - define_version, + False, + None, external_dictionaries, - rules, - exclude_rules, - local_rules, - custom_standard, + [], + [], + None, + False, progress, - define_xml_path, - validate_xml, - jsonata_custom_functions, - max_report_rows, + None, + False, + (), + None, max_report_errors, ) ) print("XPT validation completed successfully!") - print("All validation tests completed successfully!") sys.exit(0) except Exception as e: - print(f"Validation test failed: {str(e)}") + import traceback + + print(f"XPT validation test failed: {str(e)}") + print(traceback.format_exc()) sys.exit(1) -cli.add_command(test_validate) +cli.add_command(test_validate_json) +cli.add_command(test_validate_xpt) cli.add_command(validate) cli.add_command(update_cache) cli.add_command(list_rules) diff --git a/scripts/run_validation.py b/scripts/run_validation.py index ba5cddf26..3e08fddfc 100644 --- a/scripts/run_validation.py +++ b/scripts/run_validation.py @@ -128,6 +128,7 @@ def run_validation(args: Validation_args): CacheManager.register("InMemoryCacheService", InMemoryCacheService) manager = CacheManager() manager.start() + created_files = [] try: shared_cache = get_cache_service(manager) engine_logger.info(f"Populating cache, cache path: {args.cache}") @@ -156,7 +157,6 @@ def run_validation(args: Validation_args): data_service.dataset_implementation != PandasDataset ) datasets = data_service.get_datasets() - created_files = [] if large_dataset_validation and data_service.standard != "usdm": # convert all files to parquet temp files engine_logger.warning( From 92eff152b6b23becae041d91a1fa228b4e3ece77 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 8 Jan 2026 12:41:04 -0500 Subject: [PATCH 2/5] merged to one test command --- .github/workflows/build-binary.yml | 8 +-- core.py | 100 +++++------------------------ 2 files changed, 21 insertions(+), 87 deletions(-) diff --git a/.github/workflows/build-binary.yml b/.github/workflows/build-binary.yml index ec10d0434..35deaa64a 100644 --- a/.github/workflows/build-binary.yml +++ b/.github/workflows/build-binary.yml @@ -50,17 +50,17 @@ jobs: run: | cd dist/output/${{ inputs.name }}/core if [ "${{ runner.os }}" = "Windows" ]; then - ./core.exe test-validate-json + ./core.exe test-validate json json_exit=$? - ./core.exe test-validate-xpt + ./core.exe test-validate xpt xpt_exit=$? else chmod +x core chmod -R 755 . chmod -R +r resources/ - ./core test-validate-json + ./core test-validate json json_exit=$? - ./core test-validate-xpt + ./core test-validate xpt xpt_exit=$? fi diff --git a/core.py b/core.py index 72448f346..3504abc8b 100644 --- a/core.py +++ b/core.py @@ -787,8 +787,9 @@ def list_ct(cache_path: str, subsets: tuple[str]): @click.command() -def test_validate_json(): - """**Release Test** JSON validation for executable.""" +@click.argument("filetype", type=click.Choice(["json", "xpt"], case_sensitive=False)) +def test_validate(filetype): + """**Release Test** validate command for executable.""" try: import sys import os @@ -802,80 +803,14 @@ def test_validate_json(): ) base_path = os.path.join("tests", "resources", "datasets") - ts_path = os.path.join(base_path, "TS.json") - if not os.path.exists(ts_path): - raise FileNotFoundError(f"Test dataset not found: {ts_path}") - - cache_path = DEFAULT_CACHE_PATH - pool_size = 10 - log_level = "disabled" - standard = "sdtmig" - version = "3.4" - output_format = {ReportTypes.XLSX.value} - external_dictionaries = ExternalDictionariesContainer({}) - progress = ProgressParameterOptions.BAR.value - max_report_errors = (0, False) - - with tempfile.TemporaryDirectory() as temp_dir: - json_output = os.path.join(temp_dir, "json_validation_output") - run_validation( - Validation_args( - cache_path, - pool_size, - [ts_path], - log_level, - None, - standard, - version, - None, - set(), - json_output, - output_format, - False, - None, - external_dictionaries, - [], - [], - None, - False, - progress, - None, - False, - (), - None, - max_report_errors, - ) - ) - print("JSON validation completed successfully!") - sys.exit(0) - except Exception as e: - import traceback - - print(f"JSON validation test failed: {str(e)}") - print(traceback.format_exc()) - sys.exit(1) - - -@click.command() -def test_validate_xpt(): - """**Release Test** XPT validation for executable.""" - try: - import sys - import os - from cdisc_rules_engine.models.validation_args import Validation_args - from cdisc_rules_engine.models.external_dictionaries_container import ( - ExternalDictionariesContainer, - ) - from cdisc_rules_engine.enums.report_types import ReportTypes - from cdisc_rules_engine.enums.progress_parameter_options import ( - ProgressParameterOptions, - ) - - base_path = os.path.join("tests", "resources", "datasets") - ae_path = os.path.join(base_path, "ae.xpt") - if not os.path.exists(ae_path): - raise FileNotFoundError(f"Test dataset not found: {ae_path}") - + if filetype.lower() == "json": + test_file = os.path.join(base_path, "TS.json") + output_name = "json_validation_output" + else: + test_file = os.path.join(base_path, "ae.xpt") + output_name = "xpt_validation_output" + if not os.path.exists(test_file): + raise FileNotFoundError(f"Test dataset not found: {test_file}") cache_path = DEFAULT_CACHE_PATH pool_size = 10 log_level = "disabled" @@ -887,19 +822,19 @@ def test_validate_xpt(): max_report_errors = (0, False) with tempfile.TemporaryDirectory() as temp_dir: - xpt_output = os.path.join(temp_dir, "xpt_validation_output") + output = os.path.join(temp_dir, output_name) run_validation( Validation_args( cache_path, pool_size, - [ae_path], + [test_file], log_level, None, standard, version, None, set(), - xpt_output, + output, output_format, False, None, @@ -916,18 +851,17 @@ def test_validate_xpt(): max_report_errors, ) ) - print("XPT validation completed successfully!") + print(f"{filetype.upper()} validation completed successfully!") sys.exit(0) except Exception as e: import traceback - print(f"XPT validation test failed: {str(e)}") + print(f"{filetype.upper()} validation test failed: {str(e)}") print(traceback.format_exc()) sys.exit(1) -cli.add_command(test_validate_json) -cli.add_command(test_validate_xpt) +cli.add_command(test_validate) cli.add_command(validate) cli.add_command(update_cache) cli.add_command(list_rules) From 96edf4f4343ee532265c1484a6d9ef7f7391c5ee Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 8 Jan 2026 12:54:48 -0500 Subject: [PATCH 3/5] Restore readme.md to match main --- README.md | 81 +++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 682fb38c9..9331483e4 100644 --- a/README.md +++ b/README.md @@ -47,51 +47,13 @@ Once downloaded, simply unzip the file and run the following command based on yo ## Command-line Interface -**Note**: the following examples are applicable to the source code and have references to "`python core.py`". When using the executable version as described in the [Quick Start](#quick-start) above, instances of "`.\core.exe`" (Windows) or "`./core`" (Linux/Mac) should be replaced with "`python core.py`" . +**Note**: the following examples are applicable to the source code and have references to "`python core.py`". When using the executable version as described in the [Quick Start](#quick-start) above, instances of "`python cored.py`" should be replaced with "`.\core.exe`" (Windows) or "`./core`" (Linux/Mac). You can also run directly on the source code by following the [Cloning](#cloning) instructions. ### Running a validation (`validate`) -0. You will need [git](https://git-scm.com/install/) installed. +Clone the repository and run `python core.py --help` to see the full list of commands. -1. Clone the repository: - - In the terminal, navigate to the directory you intend to install CORE rules engine in - - ``` - git clone https://github.com/cdisc-org/cdisc-rules-engine - ``` - -2. Ensure you have Python 3.12 installed: - You can check your Python version with: - - ``` - python --version - ``` - - If you don't have Python 3.12, please download and install it from [python.org](https://www.python.org/downloads/) or using your system's package manager. - -3. Installing dependencies - - These steps should be run before running any tests or core commands using the non compiled version. - - - Create a virtual environment: - - `python -m venv ` - - **Note**: if you have multiple versions of python on your machine, you can call python 3.12 for the virtual environment's creation instead of the above command: - `python3.12 -m venv ` - - - Activate the virtual environment: - - `.//bin/activate` -- on linux/mac
- `.\\Scripts\Activate` -- on windows - - - Install the requirements. - - `python -m pip install -r requirements-dev.txt` # From the root directory - -4. Validate - Run `python core.py validate --help` to see the list of validation options. +Run `python core.py validate --help` to see the list of validation options. ``` -ca, --cache TEXT Relative path to cache files containing pre @@ -469,6 +431,43 @@ pip install cdisc-rules-engine For implementation instructions, see [PYPI.md](PYPI.md). +### Cloning + +In the terminal, navigate to the directory you intend to install CORE rules engine in + +1. Clone the repository: + + ``` + git clone https://github.com/cdisc-org/cdisc-rules-engine + ``` + +2. Ensure you have Python 3.12 installed: + You can check your Python version with: + ``` + python --version + ``` + If you don't have Python 3.12, please download and install it from [python.org](https://www.python.org/downloads/) or using your system's package manager. + +### Installing dependencies + +These steps should be run before running any tests or core commands using the non compiled version. + +- Create a virtual environment: + + `python -m venv ` + +NOTE: if you have multiple versions of python on your machine, you can call python 3.12 for the virtual environment's creation instead of the above command: +`python3.12 -m venv ` + +- Activate the virtual environment: + +`.//bin/activate` -- on linux/mac
+`.\\Scripts\Activate` -- on windows + +- Install the requirements. + +`python -m pip install -r requirements-dev.txt` # From the root directory + ### Creating an executable version **Note:** Further directions to create your own executable are contained in [README_Build_Executable.md](README_Build_Executable.md) if you wish to build an unofficial release executable for your own use. From 0b5e23be5f8b0c41dab25dab8f9709455138bcfb Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 8 Jan 2026 12:55:56 -0500 Subject: [PATCH 4/5] spell mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9331483e4..73aa38ea9 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Once downloaded, simply unzip the file and run the following command based on yo ## Command-line Interface -**Note**: the following examples are applicable to the source code and have references to "`python core.py`". When using the executable version as described in the [Quick Start](#quick-start) above, instances of "`python cored.py`" should be replaced with "`.\core.exe`" (Windows) or "`./core`" (Linux/Mac). You can also run directly on the source code by following the [Cloning](#cloning) instructions. +**Note**: the following examples are applicable to the source code and have references to "`python core.py`". When using the executable version as described in the [Quick Start](#quick-start) above, instances of "`python core.py`" should be replaced with "`.\core.exe`" (Windows) or "`./core`" (Linux/Mac). You can also run directly on the source code by following the [Cloning](#cloning) instructions. ### Running a validation (`validate`) From afea82a64d97f6fd03b3def0088aee3be613d9ba Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Tue, 13 Jan 2026 13:33:20 -0500 Subject: [PATCH 5/5] update CI, remove operator from schema --- .github/workflows/automated-ci.yml | 2 ++ resources/schema/Operator.json | 7 ------ tests/conftest.py | 36 ------------------------------ 3 files changed, 2 insertions(+), 43 deletions(-) diff --git a/.github/workflows/automated-ci.yml b/.github/workflows/automated-ci.yml index d83ce3b9c..eeb4efa93 100644 --- a/.github/workflows/automated-ci.yml +++ b/.github/workflows/automated-ci.yml @@ -9,11 +9,13 @@ jobs: uses: ./.github/workflows/test_suite.yml secrets: inherit rule_tester: + if: github.event.action == 'opened' uses: ./.github/workflows/rule-tester.yml secrets: inherit with: environment: DEV test_rule_editor_preview: + if: github.event.action == 'opened' needs: rule_tester uses: ./.github/workflows/test_rule_editor_preview.yml secrets: inherit diff --git a/resources/schema/Operator.json b/resources/schema/Operator.json index 459a19607..6abac2bfd 100644 --- a/resources/schema/Operator.json +++ b/resources/schema/Operator.json @@ -101,13 +101,6 @@ "required": ["operator", "value"], "type": "object" }, - { - "properties": { - "operator": { "const": "does_not_use_valid_codelist_terms" } - }, - "required": ["operator", "value", "codelistlevel", "codelistcheck"], - "type": "object" - }, { "properties": { "operator": { "const": "empty" } }, "required": ["operator"], diff --git a/tests/conftest.py b/tests/conftest.py index b0bed96db..5c6e66f06 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -907,42 +907,6 @@ def dataset_rule_record_in_parent_domain_equal_to() -> dict: } -@pytest.fixture -def define_xml_allowed_terms_check_rule() -> dict: - return { - "core_id": "MockRule", - "rule_type": "Define-XML", - "standards": [{"Name": "SDTMIG", "Version": "3.3"}], - "classes": {"Include": [ALL_KEYWORD]}, - "domains": {"Include": [ALL_KEYWORD]}, - "conditions": ConditionCompositeFactory.get_condition_composite( - { - "all": [ - { - "name": "get_dataset", - "operator": "does_not_use_valid_codelist_terms", - "value": { - "target": "define_variable_ccode", - "comparator": "define_variable_allowed_terms", - }, - } - ] - } - ), - "actions": [ - { - "name": "generate_dataset_error_objects", - "params": {"message": "Define specifies invalid codelist terms"}, - } - ], - "output_variables": [ - "define_variable_ccode", - "define_variable_name", - "define_variable_allowed_terms", - ], - } - - @pytest.fixture def dataset_rule_inconsistent_enumerated_columns() -> dict: """