data-validation-engine 0.7.6__tar.gz → 0.8.1__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.7.6 → data_validation_engine-0.8.1}/PKG-INFO +24 -19
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/README.md +10 -6
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/pyproject.toml +28 -29
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/reader.py +15 -2
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/exceptions.py +27 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/csv.py +90 -55
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/json.py +10 -2
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/xml.py +7 -1
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/rules.py +1 -1
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/csv.py +27 -10
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/json.py +6 -1
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/xml.py +6 -1
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/contract.py +2 -2
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/csv.py +42 -0
- data_validation_engine-0.8.1/src/dve/core_engine/backends/readers/utilities.py +75 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/xml.py +1 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/utilities.py +1 -1
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/__init__.py +9 -9
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/engine.py +10 -10
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/type_hints.py +1 -1
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/domain_types.py +1 -1
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/models.py +5 -5
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/helpers.py +2 -2
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/pipeline.py +7 -2
- data_validation_engine-0.7.6/src/dve/core_engine/backends/readers/utilities.py +0 -21
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/LICENSE +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/common/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/common/error_utils.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/auditing.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/backend.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/contract.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/core.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/reference_data.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/rules.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/utilities.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/auditing.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/contract.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/duckdb_helpers.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/reference_data.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/types.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/utilities.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/auditing.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/backend.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/contract.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/reference_data.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/rules.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/spark_helpers.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/types.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/utilities.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/reporting.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/rules.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/xml_linting.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/types.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/base.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/filters.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/rule_stores/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/rule_stores/models.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/steps.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/constants.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/exceptions.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/functions/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/functions/implementations.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/loggers.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/message.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/models.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/templating.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/validation.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/exc.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/function_library.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/function_wrapper.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/model_generator.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/utilities.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/exceptions.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/base.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/dbfs.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/file.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/s3.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/log_handler.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/service.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/utilities.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/type_hints.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/utilities.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/duckdb_pipeline.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/foundry_ddb_pipeline.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/spark_pipeline.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/utils.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/reporting/__init__.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/reporting/error_report.py +0 -0
- {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/reporting/excel_report.py +0 -0
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: data-validation-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: `nhs data validation engine` is a framework used to validate data
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Author: NHS England
|
|
8
8
|
Author-email: england.contactus@nhs.net
|
|
9
|
-
Requires-Python: >=3.10,<3.
|
|
9
|
+
Requires-Python: >=3.10,<3.13
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Topic :: Software Development :: Libraries
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
|
-
Requires-Dist: Jinja2 (==3.1
|
|
16
|
+
Requires-Dist: Jinja2 (==3.1.6)
|
|
17
17
|
Requires-Dist: boto3 (>=1.34.162,<1.36)
|
|
18
18
|
Requires-Dist: botocore (>=1.34.162,<1.36)
|
|
19
|
-
Requires-Dist: delta-spark (
|
|
20
|
-
Requires-Dist: duckdb (==1.1
|
|
21
|
-
Requires-Dist: lxml (
|
|
19
|
+
Requires-Dist: delta-spark (>=3.0.0,<=3.2.0)
|
|
20
|
+
Requires-Dist: duckdb (==1.1.3)
|
|
21
|
+
Requires-Dist: lxml (==6.1.1)
|
|
22
22
|
Requires-Dist: numpy (==1.26.4)
|
|
23
|
-
Requires-Dist: openpyxl (
|
|
24
|
-
Requires-Dist: pandas (
|
|
25
|
-
Requires-Dist: polars (==0.20
|
|
26
|
-
Requires-Dist: pyarrow (
|
|
27
|
-
Requires-Dist: pydantic (==1.10.
|
|
28
|
-
Requires-Dist: pyspark (
|
|
29
|
-
Requires-Dist: typing_extensions (
|
|
23
|
+
Requires-Dist: openpyxl (==3.1.5)
|
|
24
|
+
Requires-Dist: pandas (==2.3.3)
|
|
25
|
+
Requires-Dist: polars (==0.20.31)
|
|
26
|
+
Requires-Dist: pyarrow (==23.0.1)
|
|
27
|
+
Requires-Dist: pydantic (==1.10.19)
|
|
28
|
+
Requires-Dist: pyspark (>=3.0.0,<=3.5.2)
|
|
29
|
+
Requires-Dist: typing_extensions (==4.15.0)
|
|
30
|
+
Requires-Dist: urllib3 (==2.7.0)
|
|
30
31
|
Project-URL: Changelog, https://github.com/NHSDigital/data-validation-engine/blob/main/CHANGELOG.md
|
|
31
32
|
Project-URL: Documentation, https://nhsdigital.github.io/data-validation-engine/
|
|
32
33
|
Project-URL: Homepage, https://github.com/NHSDigital/data-validation-engine
|
|
@@ -40,7 +41,8 @@ Description-Content-Type: text/markdown
|
|
|
40
41
|
</h1>
|
|
41
42
|
|
|
42
43
|

|
|
43
|
-

|
|
45
|
+

|
|
44
46
|
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml)
|
|
45
47
|
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_linting.yml)
|
|
46
48
|
|
|
@@ -96,13 +98,16 @@ Below is a list of features that we would like to implement or have been request
|
|
|
96
98
|
| ------------------------------------------------------------------------------- | ----------------- | --------- |
|
|
97
99
|
| Open source release | 0.1.0 | Yes |
|
|
98
100
|
| Uplift to Python 3.11 | 0.2.0 | Yes |
|
|
99
|
-
| Uplift Pyspark to 3.5 |
|
|
100
|
-
| Allow DVE to run on Python 3.12+ |
|
|
101
|
-
| Upgrade to Pydantic 2.0 |
|
|
101
|
+
| Uplift Pyspark to 3.5 | 0.8.0 | Yes |
|
|
102
|
+
| Allow DVE to run on Python 3.12+ | 0.8.0 | Yes |
|
|
103
|
+
| Upgrade to Pydantic 2.0 | 0.9.0 | No |
|
|
102
104
|
| Uplift Pyspark to 4.0+ | TBA | No |
|
|
103
|
-
|
|
|
105
|
+
| Polars upgrade to v1+ | TBA | No |
|
|
106
|
+
| DuckDB upgrade to v1.5+ | TBA | No |
|
|
107
|
+
| Python 3.13 & 3.14 upgrade | TBA | No |
|
|
108
|
+
| Create a more user friendly interface for building and modifying dischema files | TBA | No |
|
|
104
109
|
|
|
105
|
-
|
|
110
|
+
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.
|
|
106
111
|
|
|
107
112
|
## Contributing
|
|
108
113
|
Please see guidance [here](https://github.com/NHSDigital/data-validation-engine/blob/main/CONTRIBUTE.md).
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
</h1>
|
|
5
5
|
|
|
6
6
|

|
|
7
|
-

|
|
8
|
+

|
|
8
9
|
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml)
|
|
9
10
|
[](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_linting.yml)
|
|
10
11
|
|
|
@@ -60,13 +61,16 @@ Below is a list of features that we would like to implement or have been request
|
|
|
60
61
|
| ------------------------------------------------------------------------------- | ----------------- | --------- |
|
|
61
62
|
| Open source release | 0.1.0 | Yes |
|
|
62
63
|
| Uplift to Python 3.11 | 0.2.0 | Yes |
|
|
63
|
-
| Uplift Pyspark to 3.5 |
|
|
64
|
-
| Allow DVE to run on Python 3.12+ |
|
|
65
|
-
| Upgrade to Pydantic 2.0 |
|
|
64
|
+
| Uplift Pyspark to 3.5 | 0.8.0 | Yes |
|
|
65
|
+
| Allow DVE to run on Python 3.12+ | 0.8.0 | Yes |
|
|
66
|
+
| Upgrade to Pydantic 2.0 | 0.9.0 | No |
|
|
66
67
|
| Uplift Pyspark to 4.0+ | TBA | No |
|
|
67
|
-
|
|
|
68
|
+
| Polars upgrade to v1+ | TBA | No |
|
|
69
|
+
| DuckDB upgrade to v1.5+ | TBA | No |
|
|
70
|
+
| Python 3.13 & 3.14 upgrade | TBA | No |
|
|
71
|
+
| Create a more user friendly interface for building and modifying dischema files | TBA | No |
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
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.
|
|
70
74
|
|
|
71
75
|
## Contributing
|
|
72
76
|
Please see guidance [here](https://github.com/NHSDigital/data-validation-engine/blob/main/CONTRIBUTE.md).
|
|
@@ -24,27 +24,28 @@ Issues = "https://github.com/NHSDigital/data-validation-engine/issues"
|
|
|
24
24
|
Changelog = "https://github.com/NHSDigital/data-validation-engine/blob/main/CHANGELOG.md"
|
|
25
25
|
|
|
26
26
|
[tool.poetry]
|
|
27
|
-
version = "0.
|
|
27
|
+
version = "0.8.1"
|
|
28
28
|
packages = [
|
|
29
29
|
{ include = "dve", from = "src" },
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[tool.poetry.dependencies]
|
|
33
|
-
python = ">=3.10,<3.12
|
|
33
|
+
python = ">=3.10,<3.13" # breaking changes beyond 3.12
|
|
34
34
|
boto3 = ">=1.34.162,<1.36" # breaking change beyond 1.36
|
|
35
35
|
botocore = ">=1.34.162,<1.36" # breaking change beyond 1.36
|
|
36
|
-
delta-spark = "2.
|
|
37
|
-
duckdb = "1.1
|
|
38
|
-
Jinja2 = "3.1
|
|
39
|
-
lxml = "
|
|
36
|
+
delta-spark = ">=3.0.0,<=3.2.0"
|
|
37
|
+
duckdb = "1.1.3" # breaking changes beyond 1.1
|
|
38
|
+
Jinja2 = "3.1.6"
|
|
39
|
+
lxml = "6.1.1"
|
|
40
40
|
numpy = "1.26.4"
|
|
41
|
-
openpyxl = "
|
|
42
|
-
pandas = "
|
|
43
|
-
polars = "0.20
|
|
44
|
-
pyarrow = "
|
|
45
|
-
pydantic = "1.10.
|
|
46
|
-
pyspark = "3.
|
|
47
|
-
typing_extensions = "
|
|
41
|
+
openpyxl = "3.1.5"
|
|
42
|
+
pandas = "2.3.3"
|
|
43
|
+
polars = "0.20.31"
|
|
44
|
+
pyarrow = "23.0.1"
|
|
45
|
+
pydantic = "1.10.19"
|
|
46
|
+
pyspark = ">=3.0.0,<=3.5.2"
|
|
47
|
+
typing_extensions = "4.15.0"
|
|
48
|
+
urllib3 = "2.7.0" # dependency of boto3 & botocore
|
|
48
49
|
|
|
49
50
|
[tool.poetry.group.dev]
|
|
50
51
|
optional = true
|
|
@@ -58,30 +59,30 @@ commitizen = "4.9.1"
|
|
|
58
59
|
pre-commit = "4.3.0"
|
|
59
60
|
charset-normalizer = "3.4.6"
|
|
60
61
|
python-discovery = "1.2.0"
|
|
61
|
-
|
|
62
|
+
idna = "3.15" # dependency of requests
|
|
62
63
|
|
|
63
64
|
[tool.poetry.group.test]
|
|
64
65
|
optional = true
|
|
65
66
|
|
|
66
67
|
[tool.poetry.group.test.dependencies]
|
|
67
|
-
faker = "18.11.1"
|
|
68
68
|
behave = "1.3.3"
|
|
69
69
|
coverage = "7.11.0"
|
|
70
|
-
moto = {extras = ["s3"], version = "4.
|
|
70
|
+
moto = {extras = ["s3"], version = "4.2.14"}
|
|
71
|
+
requests = "2.33.0" # dependency of `moto`
|
|
71
72
|
Werkzeug = "3.1.6"
|
|
72
|
-
pytest = "
|
|
73
|
+
pytest = "9.0.3"
|
|
73
74
|
pytest-lazy-fixtures = "1.4.0" # switched from https://github.com/TvoroG/pytest-lazy-fixture as it's no longer supported
|
|
74
|
-
xlsx2csv = "0.8.
|
|
75
|
+
xlsx2csv = "0.8.4" # polars requirement
|
|
75
76
|
|
|
76
77
|
[tool.poetry.group.lint]
|
|
77
78
|
optional = true
|
|
78
79
|
|
|
79
80
|
[tool.poetry.group.lint.dependencies]
|
|
80
81
|
black = "24.3.0"
|
|
81
|
-
astroid = "
|
|
82
|
-
isort = "5.
|
|
83
|
-
pylint = "
|
|
84
|
-
mypy = "
|
|
82
|
+
astroid = "3.3.9"
|
|
83
|
+
isort = "5.13.2"
|
|
84
|
+
pylint = "3.3.9"
|
|
85
|
+
mypy = "1.11.2"
|
|
85
86
|
boto3-stubs = {extras = ["essential"], version = "1.26.72"}
|
|
86
87
|
botocore-stubs = "1.29.72"
|
|
87
88
|
pandas-stubs = "1.2.0.62"
|
|
@@ -100,10 +101,10 @@ optional = true
|
|
|
100
101
|
|
|
101
102
|
[tool.poetry.group.docs.dependencies]
|
|
102
103
|
click = "8.2.1"
|
|
103
|
-
mkdocs = "
|
|
104
|
+
mkdocs = "1.6.1"
|
|
104
105
|
mkdocstrings = { version = "1.0.3", extras = ["python"] }
|
|
105
106
|
griffelib = "2.0.1"
|
|
106
|
-
pymdown-extensions = "10.21.
|
|
107
|
+
pymdown-extensions = "10.21.3"
|
|
107
108
|
zensical = "0.0.31"
|
|
108
109
|
|
|
109
110
|
[tool.ruff]
|
|
@@ -142,10 +143,6 @@ source_pkgs = [
|
|
|
142
143
|
[tool.coverage.report]
|
|
143
144
|
show_missing = true
|
|
144
145
|
|
|
145
|
-
[tool.pylint]
|
|
146
|
-
init-hook = "import sys; sys.path.append('./pylint_checkers')"
|
|
147
|
-
load-plugins = "check_typing_imports"
|
|
148
|
-
|
|
149
146
|
[tool.pylint.main]
|
|
150
147
|
extension-pkg-allow-list = ["pyspark", "lxml", "pydantic"]
|
|
151
148
|
fail-under = 10.0
|
|
@@ -194,7 +191,7 @@ max-statements = 50
|
|
|
194
191
|
min-public-methods = 2
|
|
195
192
|
|
|
196
193
|
[tool.pylint.exceptions]
|
|
197
|
-
overgeneral-exceptions = ["BaseException", "Exception"]
|
|
194
|
+
overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
|
|
198
195
|
|
|
199
196
|
[tool.pylint.format]
|
|
200
197
|
ignore-long-lines = "^\\s*(# )?<?https?://\\S+>?$"
|
|
@@ -229,6 +226,8 @@ disable = [
|
|
|
229
226
|
"use-symbolic-message-instead",
|
|
230
227
|
"logging-fstring-interpolation",
|
|
231
228
|
"fixme",
|
|
229
|
+
"too-many-positional-arguments",
|
|
230
|
+
"too-many-arguments",
|
|
232
231
|
]
|
|
233
232
|
enable = ["c-extension-no-member"]
|
|
234
233
|
|
|
@@ -90,6 +90,7 @@ class BaseFileReader(ABC):
|
|
|
90
90
|
resource: URI,
|
|
91
91
|
entity_name: EntityName,
|
|
92
92
|
schema: type[BaseModel],
|
|
93
|
+
all_model_fields: Optional[set[str]] = None,
|
|
93
94
|
) -> Iterator[dict[str, Any]]:
|
|
94
95
|
"""Iterate through the contents of the resource, yielding dicts
|
|
95
96
|
representing each record.
|
|
@@ -107,6 +108,7 @@ class BaseFileReader(ABC):
|
|
|
107
108
|
resource: URI,
|
|
108
109
|
entity_name: EntityName,
|
|
109
110
|
schema: type[BaseModel],
|
|
111
|
+
all_model_fields: Optional[set[str]] = None,
|
|
110
112
|
) -> EntityType:
|
|
111
113
|
"""Read to the specified entity type, if supported.
|
|
112
114
|
|
|
@@ -116,7 +118,12 @@ class BaseFileReader(ABC):
|
|
|
116
118
|
|
|
117
119
|
"""
|
|
118
120
|
if entity_name == Iterator[dict[str, Any]]:
|
|
119
|
-
return self.read_to_py_iterator(
|
|
121
|
+
return self.read_to_py_iterator(
|
|
122
|
+
resource,
|
|
123
|
+
entity_name,
|
|
124
|
+
schema, # type: ignore
|
|
125
|
+
all_model_fields
|
|
126
|
+
)
|
|
120
127
|
|
|
121
128
|
self.raise_if_not_sensible_file(resource, entity_name)
|
|
122
129
|
|
|
@@ -125,7 +132,13 @@ class BaseFileReader(ABC):
|
|
|
125
132
|
except KeyError as err:
|
|
126
133
|
raise ReaderLacksEntityTypeSupport(entity_type=entity_type) from err
|
|
127
134
|
|
|
128
|
-
return reader_func(
|
|
135
|
+
return reader_func(
|
|
136
|
+
self,
|
|
137
|
+
resource,
|
|
138
|
+
entity_name,
|
|
139
|
+
schema,
|
|
140
|
+
all_model_fields=all_model_fields # type: ignore
|
|
141
|
+
)
|
|
129
142
|
|
|
130
143
|
def add_record_index(self, entity: EntityType, **kwargs) -> EntityType:
|
|
131
144
|
"""Add a record index to the entity"""
|
|
@@ -33,6 +33,33 @@ class MessageBearingError(BackendError):
|
|
|
33
33
|
"""The messages to be returned as part of the error."""
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
class UnableToParseCSVError(MessageBearingError):
|
|
37
|
+
"""An error raised when unable to parse a CSV file"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
entity_name: str,
|
|
42
|
+
field_check_error_message: str,
|
|
43
|
+
field_check_error_code: str
|
|
44
|
+
):
|
|
45
|
+
super().__init__(
|
|
46
|
+
messages=[
|
|
47
|
+
FeedbackMessage(
|
|
48
|
+
entity="csv_structure",
|
|
49
|
+
record={
|
|
50
|
+
entity_name: "Unable to parse file. Please check the structure of the file."
|
|
51
|
+
},
|
|
52
|
+
failure_type="submission",
|
|
53
|
+
is_informational=False,
|
|
54
|
+
error_type="csv read",
|
|
55
|
+
error_location=entity_name,
|
|
56
|
+
error_message=field_check_error_message,
|
|
57
|
+
error_code=field_check_error_code,
|
|
58
|
+
)
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
36
63
|
class BackendErrorMixin(ABC, BackendError):
|
|
37
64
|
"""A mixin used to create backend error type."""
|
|
38
65
|
|
|
@@ -6,18 +6,28 @@ from typing import Any, Optional
|
|
|
6
6
|
|
|
7
7
|
import duckdb as ddb
|
|
8
8
|
import polars as pl
|
|
9
|
-
from duckdb import
|
|
9
|
+
from duckdb import (
|
|
10
|
+
DuckDBPyConnection,
|
|
11
|
+
DuckDBPyRelation,
|
|
12
|
+
InvalidInputException,
|
|
13
|
+
StarExpression,
|
|
14
|
+
read_csv,
|
|
15
|
+
)
|
|
10
16
|
from pydantic import BaseModel
|
|
11
17
|
|
|
12
|
-
from dve.core_engine.backends.base.reader import
|
|
13
|
-
from dve.core_engine.backends.exceptions import
|
|
18
|
+
from dve.core_engine.backends.base.reader import read_function
|
|
19
|
+
from dve.core_engine.backends.exceptions import (
|
|
20
|
+
EmptyFileError,
|
|
21
|
+
MessageBearingError,
|
|
22
|
+
UnableToParseCSVError,
|
|
23
|
+
)
|
|
14
24
|
from dve.core_engine.backends.implementations.duckdb.duckdb_helpers import (
|
|
15
25
|
duckdb_record_index,
|
|
16
26
|
duckdb_write_parquet,
|
|
17
27
|
get_duckdb_type_from_annotation,
|
|
18
28
|
)
|
|
19
29
|
from dve.core_engine.backends.implementations.duckdb.types import SQLType
|
|
20
|
-
from dve.core_engine.backends.readers.
|
|
30
|
+
from dve.core_engine.backends.readers.csv import CSVFileReader
|
|
21
31
|
from dve.core_engine.backends.utilities import get_polars_type_from_annotation, polars_record_index
|
|
22
32
|
from dve.core_engine.constants import RECORD_INDEX_COLUMN_NAME
|
|
23
33
|
from dve.core_engine.message import FeedbackMessage
|
|
@@ -27,7 +37,7 @@ from dve.parser.file_handling import get_content_length
|
|
|
27
37
|
|
|
28
38
|
@duckdb_record_index
|
|
29
39
|
@duckdb_write_parquet
|
|
30
|
-
class DuckDBCSVReader(
|
|
40
|
+
class DuckDBCSVReader(CSVFileReader):
|
|
31
41
|
"""A reader for CSV files including the ability to compare the passed model
|
|
32
42
|
to the file header, if it exists.
|
|
33
43
|
|
|
@@ -47,66 +57,57 @@ class DuckDBCSVReader(BaseFileReader):
|
|
|
47
57
|
quotechar: str = '"',
|
|
48
58
|
connection: Optional[DuckDBPyConnection] = None,
|
|
49
59
|
field_check: bool = False,
|
|
50
|
-
field_check_error_code:
|
|
51
|
-
field_check_error_message:
|
|
60
|
+
field_check_error_code: str = "ExpectedVsActualFieldMismatch",
|
|
61
|
+
field_check_error_message: str = "The submitted header is missing fields",
|
|
52
62
|
null_empty_strings: bool = False,
|
|
53
63
|
**_,
|
|
54
64
|
):
|
|
55
|
-
self.header = header
|
|
56
|
-
self.delim = delim
|
|
57
|
-
self.quotechar = quotechar
|
|
58
65
|
self._connection = connection if connection else ddb.connect(":memory:")
|
|
59
|
-
self.field_check = field_check
|
|
60
|
-
self.field_check_error_code = field_check_error_code
|
|
61
|
-
self.field_check_error_message = field_check_error_message
|
|
62
66
|
self.null_empty_strings = null_empty_strings
|
|
63
67
|
|
|
64
|
-
super().__init__(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if missing := check_csv_header_expected(resource, expected_schema, self.delim):
|
|
74
|
-
raise MessageBearingError(
|
|
75
|
-
"The CSV header doesn't match what is expected",
|
|
76
|
-
messages=[
|
|
77
|
-
FeedbackMessage(
|
|
78
|
-
entity=entity_name,
|
|
79
|
-
record={"missing_fields": missing},
|
|
80
|
-
failure_type="submission",
|
|
81
|
-
error_location="Whole File",
|
|
82
|
-
reporting_field="missing_fields",
|
|
83
|
-
error_code=self.field_check_error_code,
|
|
84
|
-
error_message=f"{self.field_check_error_message}", # pylint: disable=line-too-long
|
|
85
|
-
)
|
|
86
|
-
],
|
|
87
|
-
)
|
|
68
|
+
super().__init__(
|
|
69
|
+
header=header,
|
|
70
|
+
delimiter=delim,
|
|
71
|
+
quote_char=quotechar,
|
|
72
|
+
field_check=field_check,
|
|
73
|
+
field_check_error_code=field_check_error_code,
|
|
74
|
+
field_check_error_message=field_check_error_message
|
|
75
|
+
)
|
|
88
76
|
|
|
89
77
|
def read_to_py_iterator(
|
|
90
|
-
self,
|
|
78
|
+
self,
|
|
79
|
+
resource: URI,
|
|
80
|
+
entity_name: EntityName,
|
|
81
|
+
schema: type[BaseModel],
|
|
82
|
+
all_model_fields: Optional[set[str]] = None,
|
|
91
83
|
) -> Iterator[dict[str, Any]]:
|
|
92
84
|
"""Creates an iterable object of rows as dictionaries"""
|
|
93
|
-
yield from self.read_to_relation(
|
|
85
|
+
yield from self.read_to_relation(
|
|
86
|
+
resource,
|
|
87
|
+
entity_name,
|
|
88
|
+
schema,
|
|
89
|
+
all_model_fields,
|
|
90
|
+
).pl().iter_rows(named=True)
|
|
94
91
|
|
|
95
92
|
@read_function(DuckDBPyRelation)
|
|
96
93
|
def read_to_relation( # pylint: disable=unused-argument
|
|
97
|
-
self,
|
|
94
|
+
self,
|
|
95
|
+
resource: URI,
|
|
96
|
+
entity_name: EntityName,
|
|
97
|
+
schema: type[BaseModel],
|
|
98
|
+
all_model_fields: Optional[set[str]] = None,
|
|
98
99
|
) -> DuckDBPyRelation:
|
|
99
100
|
"""Returns a relation object from the source csv"""
|
|
100
101
|
if get_content_length(resource) == 0:
|
|
101
102
|
raise EmptyFileError(f"File at {resource} is empty.")
|
|
102
103
|
|
|
103
104
|
if self.field_check:
|
|
104
|
-
self.perform_field_check(resource, entity_name, schema)
|
|
105
|
+
self.perform_field_check(resource, entity_name, schema, all_model_fields)
|
|
105
106
|
|
|
106
107
|
reader_options: dict[str, Any] = {
|
|
107
108
|
"header": self.header,
|
|
108
|
-
"delimiter": self.
|
|
109
|
-
"quotechar": self.
|
|
109
|
+
"delimiter": self.delimiter,
|
|
110
|
+
"quotechar": self.quote_char,
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
ddb_schema: dict[str, SQLType] = {
|
|
@@ -116,7 +117,14 @@ class DuckDBCSVReader(BaseFileReader):
|
|
|
116
117
|
|
|
117
118
|
reader_options["columns"] = ddb_schema
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
try:
|
|
121
|
+
rel = self.add_record_index(read_csv(resource, **reader_options, parallel=False))
|
|
122
|
+
except InvalidInputException as exc:
|
|
123
|
+
raise UnableToParseCSVError(
|
|
124
|
+
entity_name="csv_structure",
|
|
125
|
+
field_check_error_message=self.field_check_error_message,
|
|
126
|
+
field_check_error_code=self.field_check_error_code,
|
|
127
|
+
) from exc
|
|
120
128
|
|
|
121
129
|
if self.null_empty_strings:
|
|
122
130
|
cleaned_cols = ",".join(
|
|
@@ -138,19 +146,23 @@ class PolarsToDuckDBCSVReader(DuckDBCSVReader):
|
|
|
138
146
|
|
|
139
147
|
@read_function(DuckDBPyRelation)
|
|
140
148
|
def read_to_relation( # pylint: disable=unused-argument
|
|
141
|
-
self,
|
|
149
|
+
self,
|
|
150
|
+
resource: URI,
|
|
151
|
+
entity_name: EntityName,
|
|
152
|
+
schema: type[BaseModel],
|
|
153
|
+
all_model_fields: Optional[set[str]] = None,
|
|
142
154
|
) -> DuckDBPyRelation:
|
|
143
155
|
"""Returns a relation object from the source csv"""
|
|
144
156
|
if get_content_length(resource) == 0:
|
|
145
157
|
raise EmptyFileError(f"File at {resource} is empty.")
|
|
146
158
|
|
|
147
159
|
if self.field_check:
|
|
148
|
-
self.perform_field_check(resource, entity_name, schema)
|
|
160
|
+
self.perform_field_check(resource, entity_name, schema, all_model_fields)
|
|
149
161
|
|
|
150
162
|
reader_options: dict[str, Any] = {
|
|
151
163
|
"has_header": self.header,
|
|
152
|
-
"separator": self.
|
|
153
|
-
"quote_char": self.
|
|
164
|
+
"separator": self.delimiter,
|
|
165
|
+
"quote_char": self.quote_char,
|
|
154
166
|
}
|
|
155
167
|
|
|
156
168
|
polars_types = {
|
|
@@ -161,11 +173,18 @@ class PolarsToDuckDBCSVReader(DuckDBCSVReader):
|
|
|
161
173
|
|
|
162
174
|
# there is a raise_if_empty arg for 0.18+. Future reference when upgrading. Makes L85
|
|
163
175
|
# redundant
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
try:
|
|
177
|
+
df = self.add_record_index( # pylint: disable=W0612
|
|
178
|
+
pl.scan_csv(resource, **reader_options).select( # type: ignore
|
|
179
|
+
list(polars_types.keys())
|
|
180
|
+
)
|
|
167
181
|
)
|
|
168
|
-
|
|
182
|
+
except pl.exceptions.PolarsError as exc:
|
|
183
|
+
raise UnableToParseCSVError(
|
|
184
|
+
entity_name="csv_structure",
|
|
185
|
+
field_check_error_message=self.field_check_error_message,
|
|
186
|
+
field_check_error_code=self.field_check_error_code,
|
|
187
|
+
) from exc
|
|
169
188
|
|
|
170
189
|
if self.null_empty_strings:
|
|
171
190
|
pl_exprs = [
|
|
@@ -175,7 +194,16 @@ class PolarsToDuckDBCSVReader(DuckDBCSVReader):
|
|
|
175
194
|
] + [pl.col(RECORD_INDEX_COLUMN_NAME)]
|
|
176
195
|
df = df.select(pl_exprs)
|
|
177
196
|
|
|
178
|
-
|
|
197
|
+
entity = self._connection.sql("SELECT * FROM df")
|
|
198
|
+
|
|
199
|
+
if entity.pl().shape[0] == 0:
|
|
200
|
+
raise UnableToParseCSVError(
|
|
201
|
+
entity_name="csv_structure",
|
|
202
|
+
field_check_error_message=self.field_check_error_message,
|
|
203
|
+
field_check_error_code=self.field_check_error_code,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return entity
|
|
179
207
|
|
|
180
208
|
|
|
181
209
|
class DuckDBCSVRepeatingHeaderReader(PolarsToDuckDBCSVReader):
|
|
@@ -216,10 +244,17 @@ class DuckDBCSVRepeatingHeaderReader(PolarsToDuckDBCSVReader):
|
|
|
216
244
|
|
|
217
245
|
@read_function(DuckDBPyRelation)
|
|
218
246
|
def read_to_relation( # pylint: disable=unused-argument
|
|
219
|
-
self,
|
|
247
|
+
self,
|
|
248
|
+
resource: URI,
|
|
249
|
+
entity_name: EntityName,
|
|
250
|
+
schema: type[BaseModel],
|
|
251
|
+
all_model_fields: Optional[set[str]] = None,
|
|
220
252
|
) -> DuckDBPyRelation:
|
|
221
253
|
entity: DuckDBPyRelation = super().read_to_relation(
|
|
222
|
-
resource=resource,
|
|
254
|
+
resource=resource,
|
|
255
|
+
entity_name=entity_name,
|
|
256
|
+
schema=schema,
|
|
257
|
+
all_model_fields=all_model_fields
|
|
223
258
|
)
|
|
224
259
|
entity = entity.select(StarExpression(exclude=[RECORD_INDEX_COLUMN_NAME])).distinct()
|
|
225
260
|
no_records = entity.shape[0]
|
|
@@ -36,14 +36,22 @@ class DuckDBJSONReader(BaseFileReader):
|
|
|
36
36
|
super().__init__()
|
|
37
37
|
|
|
38
38
|
def read_to_py_iterator(
|
|
39
|
-
self,
|
|
39
|
+
self,
|
|
40
|
+
resource: URI,
|
|
41
|
+
entity_name: EntityName,
|
|
42
|
+
schema: type[BaseModel],
|
|
43
|
+
all_model_fields: Optional[set[str]] = None,
|
|
40
44
|
) -> Iterator[dict[str, Any]]:
|
|
41
45
|
"""Creates an iterable object of rows as dictionaries"""
|
|
42
46
|
return self.read_to_relation(resource, entity_name, schema).pl().iter_rows(named=True)
|
|
43
47
|
|
|
44
48
|
@read_function(DuckDBPyRelation)
|
|
45
49
|
def read_to_relation( # pylint: disable=unused-argument
|
|
46
|
-
self,
|
|
50
|
+
self,
|
|
51
|
+
resource: URI,
|
|
52
|
+
entity_name: EntityName,
|
|
53
|
+
schema: type[BaseModel],
|
|
54
|
+
**_,
|
|
47
55
|
) -> DuckDBPyRelation:
|
|
48
56
|
"""Returns a relation object from the source json"""
|
|
49
57
|
|
|
@@ -30,7 +30,13 @@ class DuckDBXMLStreamReader(XMLStreamReader):
|
|
|
30
30
|
super().__init__(**kwargs)
|
|
31
31
|
|
|
32
32
|
@read_function(DuckDBPyRelation)
|
|
33
|
-
def read_to_relation(
|
|
33
|
+
def read_to_relation(
|
|
34
|
+
self,
|
|
35
|
+
resource: URI,
|
|
36
|
+
entity_name: str,
|
|
37
|
+
schema: type[BaseModel],
|
|
38
|
+
**_,
|
|
39
|
+
):
|
|
34
40
|
"""Returns a relation object from the source xml"""
|
|
35
41
|
if self.xsd_location:
|
|
36
42
|
msg = self._run_xmllint(file_uri=resource)
|
|
@@ -152,7 +152,7 @@ class DuckDBStepImplementations(BaseStepImplementations[DuckDBPyRelation]):
|
|
|
152
152
|
group_pl = entity.pl().pivot(
|
|
153
153
|
columns=[config.pivot_column],
|
|
154
154
|
values=agg_cols,
|
|
155
|
-
index=(group_cols or [const_fld]),
|
|
155
|
+
index=(group_cols or [const_fld]), # pylint: disable=E0606
|
|
156
156
|
aggregate_function=config.agg_function,
|
|
157
157
|
)
|
|
158
158
|
if const_fld in group_pl.columns:
|