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.
Files changed (106) hide show
  1. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/PKG-INFO +24 -19
  2. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/README.md +10 -6
  3. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/pyproject.toml +28 -29
  4. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/reader.py +15 -2
  5. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/exceptions.py +27 -0
  6. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/csv.py +90 -55
  7. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/json.py +10 -2
  8. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/xml.py +7 -1
  9. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/rules.py +1 -1
  10. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/csv.py +27 -10
  11. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/json.py +6 -1
  12. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/xml.py +6 -1
  13. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/contract.py +2 -2
  14. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/csv.py +42 -0
  15. data_validation_engine-0.8.1/src/dve/core_engine/backends/readers/utilities.py +75 -0
  16. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/xml.py +1 -0
  17. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/utilities.py +1 -1
  18. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/__init__.py +9 -9
  19. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/engine.py +10 -10
  20. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/type_hints.py +1 -1
  21. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/domain_types.py +1 -1
  22. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/models.py +5 -5
  23. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/helpers.py +2 -2
  24. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/pipeline.py +7 -2
  25. data_validation_engine-0.7.6/src/dve/core_engine/backends/readers/utilities.py +0 -21
  26. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/LICENSE +0 -0
  27. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/__init__.py +0 -0
  28. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/common/__init__.py +0 -0
  29. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/common/error_utils.py +0 -0
  30. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/__init__.py +0 -0
  31. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/__init__.py +0 -0
  32. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/__init__.py +0 -0
  33. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/auditing.py +0 -0
  34. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/backend.py +0 -0
  35. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/contract.py +0 -0
  36. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/core.py +0 -0
  37. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/reference_data.py +0 -0
  38. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/rules.py +0 -0
  39. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/base/utilities.py +0 -0
  40. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/__init__.py +0 -0
  41. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/__init__.py +0 -0
  42. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/auditing.py +0 -0
  43. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/contract.py +0 -0
  44. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/duckdb_helpers.py +0 -0
  45. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/readers/__init__.py +0 -0
  46. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/reference_data.py +0 -0
  47. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/types.py +0 -0
  48. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/duckdb/utilities.py +0 -0
  49. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/__init__.py +0 -0
  50. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/auditing.py +0 -0
  51. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/backend.py +0 -0
  52. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/contract.py +0 -0
  53. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/readers/__init__.py +0 -0
  54. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/reference_data.py +0 -0
  55. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/rules.py +0 -0
  56. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/spark_helpers.py +0 -0
  57. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/types.py +0 -0
  58. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/implementations/spark/utilities.py +0 -0
  59. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/__init__.py +0 -0
  60. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/reporting.py +0 -0
  61. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/metadata/rules.py +0 -0
  62. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/__init__.py +0 -0
  63. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/readers/xml_linting.py +0 -0
  64. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/backends/types.py +0 -0
  65. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/__init__.py +0 -0
  66. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/base.py +0 -0
  67. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/filters.py +0 -0
  68. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/rule_stores/__init__.py +0 -0
  69. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/rule_stores/models.py +0 -0
  70. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/configuration/v1/steps.py +0 -0
  71. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/constants.py +0 -0
  72. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/exceptions.py +0 -0
  73. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/functions/__init__.py +0 -0
  74. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/functions/implementations.py +0 -0
  75. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/loggers.py +0 -0
  76. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/message.py +0 -0
  77. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/models.py +0 -0
  78. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/templating.py +0 -0
  79. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/core_engine/validation.py +0 -0
  80. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/__init__.py +0 -0
  81. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/exc.py +0 -0
  82. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/function_library.py +0 -0
  83. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/function_wrapper.py +0 -0
  84. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/model_generator.py +0 -0
  85. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/metadata_parser/utilities.py +0 -0
  86. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/__init__.py +0 -0
  87. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/exceptions.py +0 -0
  88. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/__init__.py +0 -0
  89. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/__init__.py +0 -0
  90. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/base.py +0 -0
  91. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/dbfs.py +0 -0
  92. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/file.py +0 -0
  93. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/implementations/s3.py +0 -0
  94. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/log_handler.py +0 -0
  95. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/service.py +0 -0
  96. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/file_handling/utilities.py +0 -0
  97. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/type_hints.py +0 -0
  98. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/parser/utilities.py +0 -0
  99. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/__init__.py +0 -0
  100. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/duckdb_pipeline.py +0 -0
  101. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/foundry_ddb_pipeline.py +0 -0
  102. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/spark_pipeline.py +0 -0
  103. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/pipeline/utils.py +0 -0
  104. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/reporting/__init__.py +0 -0
  105. {data_validation_engine-0.7.6 → data_validation_engine-0.8.1}/src/dve/reporting/error_report.py +0 -0
  106. {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.7.6
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.12
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 (==2.4.*)
20
- Requires-Dist: duckdb (==1.1.*)
21
- Requires-Dist: lxml (>=4.9.1,<5.0.0)
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 (>=3.1,<4.0)
24
- Requires-Dist: pandas (>=2.2.2,<3.0.0)
25
- Requires-Dist: polars (==0.20.*)
26
- Requires-Dist: pyarrow (>=17.0.0,<18.0.0)
27
- Requires-Dist: pydantic (==1.10.16)
28
- Requires-Dist: pyspark (==3.4.*)
29
- Requires-Dist: typing_extensions (>=4.6.2,<5.0.0)
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
  ![License](https://img.shields.io/github/license/NHSDigital/data-validation-engine)
43
- ![Version](https://img.shields.io/github/v/release/NHSDigital/data-validation-engine)
44
+ ![PyPi](https://img.shields.io/pypi/v/data-validation-engine)
45
+ ![Conda](https://anaconda.org/nhs/data-validation-engine/badges/version.svg)
44
46
  [![CI Unit Tests](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml/badge.svg)](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml)
45
47
  [![CI Formatting & Linting](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_linting.yml/badge.svg)](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 | TBA | No |
100
- | Allow DVE to run on Python 3.12+ | TBA | No |
101
- | Upgrade to Pydantic 2.0 | TBA | No |
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
- | Create a more user friendly interface for building and modifying dischema files | Not yet confirmed | No |
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
- Beyond the Python and Pydantic upgrade, we cannot confirm the other features will be made available anytime soon. Therefore, if you have the interest and desire to make these features available, then please read the [Contributing](#Contributing) section and get involved.
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
  ![License](https://img.shields.io/github/license/NHSDigital/data-validation-engine)
7
- ![Version](https://img.shields.io/github/v/release/NHSDigital/data-validation-engine)
7
+ ![PyPi](https://img.shields.io/pypi/v/data-validation-engine)
8
+ ![Conda](https://anaconda.org/nhs/data-validation-engine/badges/version.svg)
8
9
  [![CI Unit Tests](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml/badge.svg)](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_testing.yml)
9
10
  [![CI Formatting & Linting](https://github.com/NHSDigital/data-validation-engine/actions/workflows/ci_linting.yml/badge.svg)](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 | TBA | No |
64
- | Allow DVE to run on Python 3.12+ | TBA | No |
65
- | Upgrade to Pydantic 2.0 | TBA | No |
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
- | Create a more user friendly interface for building and modifying dischema files | Not yet confirmed | No |
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
- Beyond the Python and Pydantic upgrade, we cannot confirm the other features will be made available anytime soon. Therefore, if you have the interest and desire to make these features available, then please read the [Contributing](#Contributing) section and get involved.
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.7.6"
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.4.*"
37
- duckdb = "1.1.*" # breaking changes beyond 1.1
38
- Jinja2 = "3.1.*"
39
- lxml = "^4.9.1"
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 = "^3.1"
42
- pandas = "^2.2.2"
43
- polars = "0.20.*"
44
- pyarrow = "^17.0.0"
45
- pydantic = "1.10.16"
46
- pyspark = "3.4.*"
47
- typing_extensions = "^4.6.2"
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
- requests = "2.33.0"
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.0.13"}
70
+ moto = {extras = ["s3"], version = "4.2.14"}
71
+ requests = "2.33.0" # dependency of `moto`
71
72
  Werkzeug = "3.1.6"
72
- pytest = "8.4.2"
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.2"
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 = "2.14.2"
82
- isort = "5.11.5"
83
- pylint = "2.16.4"
84
- mypy = "0.991"
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 = "^1.6.1"
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.2"
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(resource, entity_name, schema) # type: ignore
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(self, resource, entity_name, schema)
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 DuckDBPyConnection, DuckDBPyRelation, StarExpression, read_csv
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 BaseFileReader, read_function
13
- from dve.core_engine.backends.exceptions import EmptyFileError, MessageBearingError
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.utilities import check_csv_header_expected
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(BaseFileReader):
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: Optional[str] = "ExpectedVsActualFieldMismatch",
51
- field_check_error_message: Optional[str] = "The submitted header is missing fields",
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
- def perform_field_check(
67
- self, resource: URI, entity_name: str, expected_schema: type[BaseModel]
68
- ):
69
- """Check that the header of the CSV aligns with the provided model"""
70
- if not self.header:
71
- raise ValueError("Cannot perform field check without a CSV header")
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, resource: URI, entity_name: EntityName, schema: type[BaseModel]
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(resource, entity_name, schema).pl().iter_rows(named=True)
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, resource: URI, entity_name: EntityName, schema: type[BaseModel]
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.delim,
109
- "quotechar": self.quotechar,
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
- rel = self.add_record_index(read_csv(resource, **reader_options, parallel=False))
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, resource: URI, entity_name: EntityName, schema: type[BaseModel]
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.delim,
153
- "quote_char": self.quotechar,
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
- df = self.add_record_index( # pylint: disable=W0612
165
- pl.scan_csv(resource, **reader_options).select( # type: ignore
166
- list(polars_types.keys())
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
- return self._connection.sql("SELECT * FROM df")
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, resource: URI, entity_name: EntityName, schema: type[BaseModel]
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, entity_name=entity_name, schema=schema
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, resource: URI, entity_name: EntityName, schema: type[BaseModel]
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, resource: URI, entity_name: EntityName, schema: type[BaseModel]
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(self, resource: URI, entity_name: str, schema: type[BaseModel]):
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: