data-validation-engine 0.8.5__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data_validation_engine-0.10.0/PKG-INFO +100 -0
- data_validation_engine-0.10.0/README.md +61 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/pyproject.toml +18 -14
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/common/error_utils.py +1 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/auditing.py +8 -8
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/contract.py +0 -4
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/reader.py +71 -24
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/rules.py +194 -3
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/exceptions.py +28 -16
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/auditing.py +8 -8
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/contract.py +7 -10
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/duckdb_helpers.py +37 -9
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/readers/csv.py +24 -21
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/readers/json.py +4 -2
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/readers/xml.py +10 -6
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/rules.py +107 -38
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/auditing.py +1 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/backend.py +1 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/contract.py +9 -8
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/readers/csv.py +6 -4
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/readers/json.py +2 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/readers/xml.py +9 -5
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/rules.py +127 -25
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/spark_helpers.py +52 -25
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/utilities.py +1 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/metadata/contract.py +3 -3
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/metadata/reporting.py +9 -7
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/metadata/rules.py +37 -30
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/readers/csv.py +9 -11
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/readers/utilities.py +15 -15
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/readers/xml.py +32 -20
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/readers/xml_linting.py +3 -2
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/utilities.py +39 -8
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/configuration/v1/__init__.py +102 -11
- data_validation_engine-0.10.0/src/dve/core_engine/configuration/v1/hierarchy.py +210 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/configuration/v1/steps.py +8 -9
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/constants.py +5 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/engine.py +47 -34
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/functions/implementations.py +0 -110
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/loggers.py +1 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/message.py +16 -16
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/models.py +35 -19
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/type_hints.py +10 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/validation.py +4 -5
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/domain_types.py +143 -93
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/function_library.py +2 -2
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/function_wrapper.py +17 -24
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/model_generator.py +7 -9
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/models.py +52 -45
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/utilities.py +12 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/implementations/file.py +4 -4
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/service.py +5 -2
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/pipeline/pipeline.py +187 -31
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/pipeline/utils.py +31 -5
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/reporting/__init__.py +2 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/reporting/error_report.py +1 -1
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/reporting/excel_report.py +20 -0
- data_validation_engine-0.8.5/PKG-INFO +0 -120
- data_validation_engine-0.8.5/README.md +0 -81
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/LICENSE +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/common/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/backend.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/core.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/reference_data.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/base/utilities.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/readers/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/reference_data.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/types.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/duckdb/utilities.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/readers/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/reference_data.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/implementations/spark/types.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/metadata/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/readers/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/backends/types.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/configuration/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/configuration/base.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/configuration/v1/filters.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/configuration/v1/rule_stores/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/configuration/v1/rule_stores/models.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/exceptions.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/functions/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/core_engine/templating.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/metadata_parser/exc.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/exceptions.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/helpers.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/implementations/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/implementations/base.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/implementations/dbfs.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/implementations/s3.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/log_handler.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/file_handling/utilities.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/type_hints.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/parser/utilities.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/pipeline/__init__.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/pipeline/duckdb_pipeline.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/pipeline/foundry_ddb_pipeline.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/pipeline/spark_pipeline.py +0 -0
- {data_validation_engine-0.8.5 → data_validation_engine-0.10.0}/src/dve/reporting/constants.py +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-validation-engine
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: `nhs data validation engine` is a framework used to validate data
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: NHS England
|
|
8
|
+
Author-email: england.contactus@nhs.net
|
|
9
|
+
Requires-Python: >=3.11,<3.13
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: Jinja2 (==3.1.6)
|
|
18
|
+
Requires-Dist: boto3 (>=1.34.162,<1.36)
|
|
19
|
+
Requires-Dist: botocore (>=1.34.162,<1.36)
|
|
20
|
+
Requires-Dist: delta-spark (>=3.0.0,<=3.2.0)
|
|
21
|
+
Requires-Dist: duckdb (>=1.4,<1.4.5)
|
|
22
|
+
Requires-Dist: lxml (==6.1.1)
|
|
23
|
+
Requires-Dist: numpy (==1.26.4)
|
|
24
|
+
Requires-Dist: openpyxl (==3.1.5)
|
|
25
|
+
Requires-Dist: pandas (==2.3.3)
|
|
26
|
+
Requires-Dist: polars (==0.20.31)
|
|
27
|
+
Requires-Dist: pyarrow (==23.0.1)
|
|
28
|
+
Requires-Dist: pydantic (==2.13.4)
|
|
29
|
+
Requires-Dist: pyspark (>=3.5.0,<=3.5.5)
|
|
30
|
+
Requires-Dist: typing_extensions (==4.16.0)
|
|
31
|
+
Requires-Dist: urllib3 (==2.8.0)
|
|
32
|
+
Project-URL: Changelog, https://github.com/NHSDigital/data-validation-engine/blob/main/CHANGELOG.md
|
|
33
|
+
Project-URL: Documentation, https://nhsdigital.github.io/data-validation-engine/
|
|
34
|
+
Project-URL: Homepage, https://github.com/NHSDigital/data-validation-engine
|
|
35
|
+
Project-URL: Issues, https://github.com/NHSDigital/data-validation-engine/issues
|
|
36
|
+
Project-URL: Repository, https://github.com/NHSDigital/data-validation-engine.git
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
<h1 style="display: flex; align-items: center; gap: 10px;">
|
|
40
|
+
<img src="https://raw.githubusercontent.com/NHSDigital/data-validation-engine/616b55890306db4546177f7effac48ca241857ec/overrides/.icons/nhseng.svg" alt="" width="5%" height="100%" align="left">
|
|
41
|
+
Data Validation Engine
|
|
42
|
+
</h1>
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+

|
|
46
|
+

|
|
47
|
+
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml)
|
|
48
|
+
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_linting.yml)
|
|
49
|
+
|
|
50
|
+
The Data Validation Engine (DVE) is a configuration-driven data validation library created and used by NHS England. It lets users define validation rules once and apply them across multiple dataset collections - supporting consistent, accurate data checks.
|
|
51
|
+
|
|
52
|
+
__The DVE offers__:
|
|
53
|
+
|
|
54
|
+
- SQL configuration-based validations
|
|
55
|
+
- Format normalization to Parquet for a unified data representation
|
|
56
|
+
- Data modelling and typecasting
|
|
57
|
+
- Business-rule validations executed on supported backends such as Spark and DuckDB, with the option to add custom backends
|
|
58
|
+
- Validate and enforce referential integrity checks with minimal configuration
|
|
59
|
+
- Deriving new fields and entities
|
|
60
|
+
- Clear validation reporting, including summary insights and record-level error messages
|
|
61
|
+
|
|
62
|
+
As mentioned above, the DVE is "configuration driven" which means the majority of development for you as a user will be building a JSON document to describe how the data will be validated. The JSON document is known as a `dischema` (data ingest schema) file and example files can be accessed [here ↗️](https://github.com/NHSDigital/data-validation-engine/tree/main/tests/testdata). If you'd like to learn more about JSON document and how to build one from scratch, then please read the documentation [here ↗️](https://nhsdigital.github.io/data-validation-engine/).
|
|
63
|
+
|
|
64
|
+
## Installation and usage
|
|
65
|
+
|
|
66
|
+
Please see the documentation [here ↗️](https://nhsdigital.github.io/data-validation-engine/user_guidance/install/).
|
|
67
|
+
|
|
68
|
+
## Requesting new features and raising bug reports
|
|
69
|
+
**Before creating new issues, please check to see if the same bug/feature has been created already. Where a duplicate is created, the ticket will be closed and referenced to an existing issue.**
|
|
70
|
+
|
|
71
|
+
If you have spotted a bug with the DVE then please raise an issue [here ↗️](https://github.com/nhsengland/Data-Validation-Engine/issues) using the "bug template".
|
|
72
|
+
|
|
73
|
+
If you have feature request then please follow the same process whilst using the "Feature request template".
|
|
74
|
+
|
|
75
|
+
## Upcoming features
|
|
76
|
+
Below is a list of features that we would like to implement or have been requested.
|
|
77
|
+
| Feature | Release Version | Released? |
|
|
78
|
+
| ------------------------------------------------------------------------------- | ----------------- | --------- |
|
|
79
|
+
| Open source release | 0.1.0 | Yes |
|
|
80
|
+
| Uplift to Python 3.11 | 0.2.0 | Yes |
|
|
81
|
+
| Uplift Pyspark to 3.5 | 0.8.0 | Yes |
|
|
82
|
+
| Allow DVE to run on Python 3.12+ | 0.8.0 | Yes |
|
|
83
|
+
| Upgrade to Pydantic 2.0 | 0.9.0 | Yes |
|
|
84
|
+
| Upgrade DuckDB to v1.4 | 0.10.0 | Yes |
|
|
85
|
+
| Uplift Pyspark to 4.0+ | TBA | No |
|
|
86
|
+
| Polars upgrade to v1+ | TBA | No |
|
|
87
|
+
| DuckDB upgrade to v1.5+ | TBA | No |
|
|
88
|
+
| Python 3.13 & 3.14 upgrade | TBA | No |
|
|
89
|
+
| Create a more user friendly interface for building and modifying dischema files | TBA | No |
|
|
90
|
+
|
|
91
|
+
If you are interested in getting any of the unreleased features listed above available, then please read the [Contributing](#Contributing) section and then submit us a pull request.
|
|
92
|
+
|
|
93
|
+
## Contributing
|
|
94
|
+
Please see guidance [here ↗️](https://github.com/NHSDigital/data-validation-engine/blob/main/CONTRIBUTE.md).
|
|
95
|
+
|
|
96
|
+
## Legal
|
|
97
|
+
This codebase is released under the MIT License. This covers both the codebase and any sample code in the documentation.
|
|
98
|
+
|
|
99
|
+
Any HTML or Markdown documentation is [© Crown copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/) and available under the terms of the [Open Government 3.0 licence](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
|
|
100
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<h1 style="display: flex; align-items: center; gap: 10px;">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/NHSDigital/data-validation-engine/616b55890306db4546177f7effac48ca241857ec/overrides/.icons/nhseng.svg" alt="" width="5%" height="100%" align="left">
|
|
3
|
+
Data Validation Engine
|
|
4
|
+
</h1>
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml)
|
|
10
|
+
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_linting.yml)
|
|
11
|
+
|
|
12
|
+
The Data Validation Engine (DVE) is a configuration-driven data validation library created and used by NHS England. It lets users define validation rules once and apply them across multiple dataset collections - supporting consistent, accurate data checks.
|
|
13
|
+
|
|
14
|
+
__The DVE offers__:
|
|
15
|
+
|
|
16
|
+
- SQL configuration-based validations
|
|
17
|
+
- Format normalization to Parquet for a unified data representation
|
|
18
|
+
- Data modelling and typecasting
|
|
19
|
+
- Business-rule validations executed on supported backends such as Spark and DuckDB, with the option to add custom backends
|
|
20
|
+
- Validate and enforce referential integrity checks with minimal configuration
|
|
21
|
+
- Deriving new fields and entities
|
|
22
|
+
- Clear validation reporting, including summary insights and record-level error messages
|
|
23
|
+
|
|
24
|
+
As mentioned above, the DVE is "configuration driven" which means the majority of development for you as a user will be building a JSON document to describe how the data will be validated. The JSON document is known as a `dischema` (data ingest schema) file and example files can be accessed [here ↗️](https://github.com/NHSDigital/data-validation-engine/tree/main/tests/testdata). If you'd like to learn more about JSON document and how to build one from scratch, then please read the documentation [here ↗️](https://nhsdigital.github.io/data-validation-engine/).
|
|
25
|
+
|
|
26
|
+
## Installation and usage
|
|
27
|
+
|
|
28
|
+
Please see the documentation [here ↗️](https://nhsdigital.github.io/data-validation-engine/user_guidance/install/).
|
|
29
|
+
|
|
30
|
+
## Requesting new features and raising bug reports
|
|
31
|
+
**Before creating new issues, please check to see if the same bug/feature has been created already. Where a duplicate is created, the ticket will be closed and referenced to an existing issue.**
|
|
32
|
+
|
|
33
|
+
If you have spotted a bug with the DVE then please raise an issue [here ↗️](https://github.com/nhsengland/Data-Validation-Engine/issues) using the "bug template".
|
|
34
|
+
|
|
35
|
+
If you have feature request then please follow the same process whilst using the "Feature request template".
|
|
36
|
+
|
|
37
|
+
## Upcoming features
|
|
38
|
+
Below is a list of features that we would like to implement or have been requested.
|
|
39
|
+
| Feature | Release Version | Released? |
|
|
40
|
+
| ------------------------------------------------------------------------------- | ----------------- | --------- |
|
|
41
|
+
| Open source release | 0.1.0 | Yes |
|
|
42
|
+
| Uplift to Python 3.11 | 0.2.0 | Yes |
|
|
43
|
+
| Uplift Pyspark to 3.5 | 0.8.0 | Yes |
|
|
44
|
+
| Allow DVE to run on Python 3.12+ | 0.8.0 | Yes |
|
|
45
|
+
| Upgrade to Pydantic 2.0 | 0.9.0 | Yes |
|
|
46
|
+
| Upgrade DuckDB to v1.4 | 0.10.0 | Yes |
|
|
47
|
+
| Uplift Pyspark to 4.0+ | TBA | No |
|
|
48
|
+
| Polars upgrade to v1+ | TBA | No |
|
|
49
|
+
| DuckDB upgrade to v1.5+ | TBA | No |
|
|
50
|
+
| Python 3.13 & 3.14 upgrade | TBA | No |
|
|
51
|
+
| Create a more user friendly interface for building and modifying dischema files | TBA | No |
|
|
52
|
+
|
|
53
|
+
If you are interested in getting any of the unreleased features listed above available, then please read the [Contributing](#Contributing) section and then submit us a pull request.
|
|
54
|
+
|
|
55
|
+
## Contributing
|
|
56
|
+
Please see guidance [here ↗️](https://github.com/NHSDigital/data-validation-engine/blob/main/CONTRIBUTE.md).
|
|
57
|
+
|
|
58
|
+
## Legal
|
|
59
|
+
This codebase is released under the MIT License. This covers both the codebase and any sample code in the documentation.
|
|
60
|
+
|
|
61
|
+
Any HTML or Markdown documentation is [© Crown copyright](https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/) and available under the terms of the [Open Government 3.0 licence](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
|
|
@@ -7,11 +7,11 @@ authors = [
|
|
|
7
7
|
]
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
10
11
|
"Programming Language :: Python :: 3",
|
|
11
|
-
"Programming Language :: Python :: 3.10",
|
|
12
12
|
"Programming Language :: Python :: 3.11",
|
|
13
13
|
"Programming Language :: Python :: 3.12",
|
|
14
|
-
"Operating System ::
|
|
14
|
+
"Operating System :: POSIX :: Linux",
|
|
15
15
|
"Topic :: Software Development :: Libraries",
|
|
16
16
|
"Typing :: Typed",
|
|
17
17
|
]
|
|
@@ -25,17 +25,17 @@ Issues = "https://github.com/NHSDigital/data-validation-engine/issues"
|
|
|
25
25
|
Changelog = "https://github.com/NHSDigital/data-validation-engine/blob/main/CHANGELOG.md"
|
|
26
26
|
|
|
27
27
|
[tool.poetry]
|
|
28
|
-
version = "0.
|
|
28
|
+
version = "0.10.0"
|
|
29
29
|
packages = [
|
|
30
30
|
{ include = "dve", from = "src" },
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[tool.poetry.dependencies]
|
|
34
|
-
python = ">=3.
|
|
34
|
+
python = ">=3.11,<3.13" # breaking changes beyond 3.12
|
|
35
35
|
boto3 = ">=1.34.162,<1.36" # breaking change beyond 1.36
|
|
36
36
|
botocore = ">=1.34.162,<1.36" # breaking change beyond 1.36
|
|
37
37
|
delta-spark = ">=3.0.0,<=3.2.0"
|
|
38
|
-
duckdb = "1.1.
|
|
38
|
+
duckdb = ">=1.4,<1.4.5"
|
|
39
39
|
Jinja2 = "3.1.6"
|
|
40
40
|
lxml = "6.1.1"
|
|
41
41
|
numpy = "1.26.4"
|
|
@@ -43,10 +43,10 @@ openpyxl = "3.1.5"
|
|
|
43
43
|
pandas = "2.3.3"
|
|
44
44
|
polars = "0.20.31"
|
|
45
45
|
pyarrow = "23.0.1"
|
|
46
|
-
pydantic = "
|
|
46
|
+
pydantic = "2.13.4"
|
|
47
47
|
pyspark = ">=3.5.0,<=3.5.5"
|
|
48
|
-
typing_extensions = "4.
|
|
49
|
-
urllib3 = "2.
|
|
48
|
+
typing_extensions = "4.16.0"
|
|
49
|
+
urllib3 = "2.8.0" # dependency of boto3 & botocore
|
|
50
50
|
|
|
51
51
|
[tool.poetry.group.dev]
|
|
52
52
|
optional = true
|
|
@@ -59,8 +59,11 @@ include-groups = [
|
|
|
59
59
|
commitizen = "4.9.1"
|
|
60
60
|
pre-commit = "4.3.0"
|
|
61
61
|
charset-normalizer = "3.4.6"
|
|
62
|
-
python-discovery = "1.
|
|
62
|
+
python-discovery = "1.6.1"
|
|
63
63
|
idna = "3.15" # dependency of requests
|
|
64
|
+
virtualenv = "21.14.9"
|
|
65
|
+
certifi = "2026.10.23"
|
|
66
|
+
filelock = "4.2.1"
|
|
64
67
|
|
|
65
68
|
[tool.poetry.group.test]
|
|
66
69
|
optional = true
|
|
@@ -71,7 +74,7 @@ coverage = "7.11.0"
|
|
|
71
74
|
moto = {extras = ["s3"], version = "4.2.14"}
|
|
72
75
|
cryptography = "50.0.0" # dependency of `moto`
|
|
73
76
|
requests = "2.33.0" # dependency of `moto`
|
|
74
|
-
Werkzeug = "3.1.
|
|
77
|
+
Werkzeug = "3.1.9"
|
|
75
78
|
pytest = "9.0.3"
|
|
76
79
|
pytest-lazy-fixtures = "1.4.0" # switched from https://github.com/TvoroG/pytest-lazy-fixture as it's no longer supported
|
|
77
80
|
xlsx2csv = "0.8.4" # polars requirement
|
|
@@ -80,11 +83,12 @@ xlsx2csv = "0.8.4" # polars requirement
|
|
|
80
83
|
optional = true
|
|
81
84
|
|
|
82
85
|
[tool.poetry.group.lint.dependencies]
|
|
83
|
-
black = "26.
|
|
86
|
+
black = "26.10.1"
|
|
84
87
|
astroid = "3.3.9"
|
|
85
88
|
isort = "5.13.2"
|
|
86
89
|
pylint = "3.3.9"
|
|
87
|
-
mypy = "1.
|
|
90
|
+
mypy = "1.20.2"
|
|
91
|
+
librt = "0.11.0" # mypy dependency
|
|
88
92
|
boto3-stubs = {extras = ["essential"], version = "1.26.72"}
|
|
89
93
|
botocore-stubs = "1.29.72"
|
|
90
94
|
pandas-stubs = "1.2.0.62"
|
|
@@ -107,7 +111,7 @@ mkdocs = "1.6.1"
|
|
|
107
111
|
mkdocstrings = { version = "1.0.3", extras = ["python"] }
|
|
108
112
|
griffelib = "2.0.1"
|
|
109
113
|
pymdown-extensions = "11.0.1"
|
|
110
|
-
zensical = "0.0.
|
|
114
|
+
zensical = "0.0.63"
|
|
111
115
|
|
|
112
116
|
[tool.ruff]
|
|
113
117
|
line-length = 100
|
|
@@ -291,7 +295,7 @@ redefining-builtins-modules = [
|
|
|
291
295
|
|
|
292
296
|
[tool.commitizen]
|
|
293
297
|
name = "cz_conventional_commits"
|
|
294
|
-
tag_format = "v$major.$minor.$patch"
|
|
298
|
+
tag_format = "v$major.$minor.${patch}$prerelease"
|
|
295
299
|
version_scheme = "pep440"
|
|
296
300
|
version_provider = "poetry"
|
|
297
301
|
update_changelog_on_bump = true
|
|
@@ -15,7 +15,7 @@ from queue import Queue as ThreadQueue
|
|
|
15
15
|
from types import TracebackType
|
|
16
16
|
from typing import Any, ClassVar, Generic, Optional, TypeVar, Union
|
|
17
17
|
|
|
18
|
-
from pydantic import ValidationError,
|
|
18
|
+
from pydantic import ValidationError, validate_call
|
|
19
19
|
from typing_extensions import Literal, get_origin
|
|
20
20
|
|
|
21
21
|
from dve.core_engine.models import (
|
|
@@ -99,8 +99,8 @@ class BaseAuditor(Generic[AuditReturnType]):
|
|
|
99
99
|
def schema(self) -> dict[str, type]:
|
|
100
100
|
"""Determine python schema of auditor"""
|
|
101
101
|
return {
|
|
102
|
-
fld: str if get_origin(mdl.
|
|
103
|
-
for fld, mdl in self._record_type.
|
|
102
|
+
fld: str if get_origin(mdl.annotation) == Literal else mdl.annotation # type: ignore
|
|
103
|
+
for fld, mdl in self._record_type.model_fields.items()
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
@staticmethod
|
|
@@ -196,7 +196,7 @@ class BaseAuditingManager(
|
|
|
196
196
|
"""Convert AuditReturnType to iterable of dictionaries"""
|
|
197
197
|
raise NotImplementedError()
|
|
198
198
|
|
|
199
|
-
@
|
|
199
|
+
@validate_call
|
|
200
200
|
def add_processing_records(self, processing_records: list[ProcessingStatusRecord]):
|
|
201
201
|
"""Add an entry to the processing_status auditor."""
|
|
202
202
|
if self.pool:
|
|
@@ -208,7 +208,7 @@ class BaseAuditingManager(
|
|
|
208
208
|
records=[dict(rec) for rec in processing_records]
|
|
209
209
|
)
|
|
210
210
|
|
|
211
|
-
@
|
|
211
|
+
@validate_call
|
|
212
212
|
def add_submission_statistics_records(self, sub_stats: list[SubmissionStatisticsRecord]):
|
|
213
213
|
"""Add an entry to the submission statistics auditor."""
|
|
214
214
|
if self.pool:
|
|
@@ -218,7 +218,7 @@ class BaseAuditingManager(
|
|
|
218
218
|
)
|
|
219
219
|
return self._submission_statistics.add_records(records=[dict(rec) for rec in sub_stats])
|
|
220
220
|
|
|
221
|
-
@
|
|
221
|
+
@validate_call
|
|
222
222
|
def add_transfer_records(self, transfer_records: list[TransferRecord]):
|
|
223
223
|
"""Add an entry to the transfers auditor"""
|
|
224
224
|
if self.pool:
|
|
@@ -227,7 +227,7 @@ class BaseAuditingManager(
|
|
|
227
227
|
)
|
|
228
228
|
return self._transfers.add_records(records=[dict(rec) for rec in transfer_records])
|
|
229
229
|
|
|
230
|
-
@
|
|
230
|
+
@validate_call
|
|
231
231
|
def add_new_submissions(
|
|
232
232
|
self,
|
|
233
233
|
submissions: list[SubmissionMetadata],
|
|
@@ -250,7 +250,7 @@ class BaseAuditingManager(
|
|
|
250
250
|
processing_status="received",
|
|
251
251
|
job_run_id=job_run_id,
|
|
252
252
|
**ts_info,
|
|
253
|
-
).
|
|
253
|
+
).model_dump(),
|
|
254
254
|
}
|
|
255
255
|
processing_status_recs.append(processing_rec)
|
|
256
256
|
if sub_info:
|
|
@@ -443,10 +443,6 @@ class BaseDataContract(Generic[EntityType], ABC):
|
|
|
443
443
|
],
|
|
444
444
|
)
|
|
445
445
|
|
|
446
|
-
if contract_metadata.cache_originals:
|
|
447
|
-
for entity_name in list(entities):
|
|
448
|
-
entities[f"Original{entity_name}"] = entities[entity_name]
|
|
449
|
-
|
|
450
446
|
return entities, feedback_errors_uri, successful, processing_errors_uri
|
|
451
447
|
|
|
452
448
|
def read_parquet(self, path: URI, **kwargs) -> EntityType:
|
|
@@ -8,8 +8,16 @@ from typing import Any, ClassVar, Optional, TypeVar
|
|
|
8
8
|
from pydantic import BaseModel
|
|
9
9
|
from typing_extensions import Protocol
|
|
10
10
|
|
|
11
|
-
from dve.core_engine.backends.exceptions import
|
|
11
|
+
from dve.core_engine.backends.exceptions import (
|
|
12
|
+
CriticalMessageBearingError,
|
|
13
|
+
MessageBearingError,
|
|
14
|
+
ReaderLacksEntityTypeSupport,
|
|
15
|
+
)
|
|
12
16
|
from dve.core_engine.backends.types import EntityName, EntityType
|
|
17
|
+
from dve.core_engine.configuration.v1 import (
|
|
18
|
+
AllowedAdditionalReaderChecks,
|
|
19
|
+
_ReaderAdditionalChecksConfig,
|
|
20
|
+
)
|
|
13
21
|
from dve.core_engine.message import FeedbackMessage
|
|
14
22
|
from dve.core_engine.type_hints import URI, ArbitraryFunction, WrapDecorator
|
|
15
23
|
from dve.parser.file_handling.service import open_stream
|
|
@@ -65,6 +73,10 @@ class BaseFileReader(ABC):
|
|
|
65
73
|
decorated with the '@read_function' decorator, and is used in `read_entity_type`.
|
|
66
74
|
|
|
67
75
|
"""
|
|
76
|
+
ft_error_code: Optional[str] = "MalformedFile"
|
|
77
|
+
"""Default error code for when a file/submission cannot be parsed succesfully."""
|
|
78
|
+
ft_error_message: Optional[str] = "The resource doesn't seem to be a valid text file"
|
|
79
|
+
"""Default error message for when a file/submission cannot be parsed succesfully."""
|
|
68
80
|
|
|
69
81
|
def __init_subclass__(cls, *_, **__) -> None:
|
|
70
82
|
"""When this class is subclassed, create and populate the `__read_methods__`
|
|
@@ -109,7 +121,10 @@ class BaseFileReader(ABC):
|
|
|
109
121
|
entity_name: EntityName,
|
|
110
122
|
schema: type[BaseModel],
|
|
111
123
|
all_model_fields: Optional[set[str]] = None,
|
|
112
|
-
|
|
124
|
+
additional_checks: Optional[
|
|
125
|
+
dict[AllowedAdditionalReaderChecks, _ReaderAdditionalChecksConfig]
|
|
126
|
+
] = None,
|
|
127
|
+
):
|
|
113
128
|
"""Read to the specified entity type, if supported.
|
|
114
129
|
|
|
115
130
|
NOTE: Simple types should either be returned as strings (if present) or
|
|
@@ -117,21 +132,47 @@ class BaseFileReader(ABC):
|
|
|
117
132
|
data contract.
|
|
118
133
|
|
|
119
134
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
additional_checks = additional_checks or {}
|
|
136
|
+
|
|
137
|
+
self.raise_if_not_sensible_file(resource, entity_name)
|
|
138
|
+
|
|
139
|
+
if entity_type == Iterator[dict[str, Any]]:
|
|
140
|
+
entity = self.read_to_py_iterator(
|
|
122
141
|
resource, entity_name, schema, all_model_fields # type: ignore
|
|
123
142
|
)
|
|
124
143
|
|
|
125
|
-
|
|
144
|
+
else:
|
|
126
145
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
146
|
+
try:
|
|
147
|
+
reader_func = self.__read_methods__[entity_type]
|
|
148
|
+
except KeyError as err:
|
|
149
|
+
raise ReaderLacksEntityTypeSupport(entity_type=entity_type) from err
|
|
131
150
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
entity = reader_func(
|
|
152
|
+
self,
|
|
153
|
+
resource,
|
|
154
|
+
entity_name,
|
|
155
|
+
schema,
|
|
156
|
+
all_model_fields=all_model_fields, # type: ignore
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if config := additional_checks.get("check_empty"):
|
|
160
|
+
if self.check_entity_empty(entity):
|
|
161
|
+
raise MessageBearingError(
|
|
162
|
+
f"The mandatory entity {entity_name} is empty",
|
|
163
|
+
messages=[
|
|
164
|
+
FeedbackMessage(
|
|
165
|
+
entity=entity_name,
|
|
166
|
+
record=None,
|
|
167
|
+
failure_type="submission",
|
|
168
|
+
error_location=entity_name,
|
|
169
|
+
error_code=config.error_code,
|
|
170
|
+
error_message=config.error_message,
|
|
171
|
+
)
|
|
172
|
+
],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return entity
|
|
135
176
|
|
|
136
177
|
def add_record_index(self, entity: EntityType, **kwargs) -> EntityType:
|
|
137
178
|
"""Add a record index to the entity"""
|
|
@@ -141,6 +182,10 @@ class BaseFileReader(ABC):
|
|
|
141
182
|
"""Drop a record index to the entity"""
|
|
142
183
|
raise NotImplementedError(f"drop_record_index not implemented in {self.__class__}")
|
|
143
184
|
|
|
185
|
+
def check_entity_empty(self, entity: EntityType) -> bool:
|
|
186
|
+
"""Determine if the entity supplied is empty"""
|
|
187
|
+
raise NotImplementedError(f"check_entity_empty not implemented in {self.__class__}")
|
|
188
|
+
|
|
144
189
|
def write_parquet(
|
|
145
190
|
self,
|
|
146
191
|
entity: EntityType,
|
|
@@ -171,20 +216,22 @@ class BaseFileReader(ABC):
|
|
|
171
216
|
return False
|
|
172
217
|
return True
|
|
173
218
|
|
|
174
|
-
def raise_if_not_sensible_file(
|
|
219
|
+
def raise_if_not_sensible_file(
|
|
220
|
+
self,
|
|
221
|
+
resource: URI,
|
|
222
|
+
entity_name: str,
|
|
223
|
+
):
|
|
175
224
|
"""Sense check that the file is a text file. Raise error if doesn't
|
|
176
225
|
appear to be the case."""
|
|
177
226
|
if not self._check_likely_text_file(resource):
|
|
178
|
-
raise
|
|
227
|
+
raise CriticalMessageBearingError(
|
|
179
228
|
"The submitted file doesn't appear to be text",
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
)
|
|
189
|
-
],
|
|
229
|
+
message=FeedbackMessage(
|
|
230
|
+
entity=entity_name,
|
|
231
|
+
record=None,
|
|
232
|
+
failure_type="submission",
|
|
233
|
+
error_location="Whole File",
|
|
234
|
+
error_code=self.ft_error_code,
|
|
235
|
+
error_message=self.ft_error_message,
|
|
236
|
+
),
|
|
190
237
|
)
|