dataqe-framework 0.2.4__tar.gz → 0.2.5__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.
- {dataqe_framework-0.2.4/src/dataqe_framework.egg-info → dataqe_framework-0.2.5}/PKG-INFO +1 -1
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/pyproject.toml +1 -1
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/__init__.py +1 -1
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/cli.py +1 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/executor.py +54 -36
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/preprocessor.py +83 -3
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5/src/dataqe_framework.egg-info}/PKG-INFO +1 -1
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/LICENSE.txt +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/MANIFEST.in +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/README.md +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/setup.cfg +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/bigquery_client.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/comparison/comparator.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/comparison/threshold.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/config.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/config_loader.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/connectors/__init__.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/connectors/base_connector.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/connectors/bigquery_connector.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/connectors/mysql_connector.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/credentials_extractor.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/reporter.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/validator.py +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/SOURCES.txt +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/dependency_links.txt +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/entry_points.txt +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/requires.txt +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/top_level.txt +0 -0
- {dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/tests/test_validator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dataqe-framework
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Reusable Data Validation Framework for data migration, ETL validation, and cross-database reconciliation
|
|
5
5
|
Author-email: Khadar Shaik <khadarmohiddin.shaik@apree.health>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ShaikKhadarmohiddin/dataqe-framework
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dataqe-framework"
|
|
7
7
|
dynamic = []
|
|
8
|
-
version = "0.2.
|
|
8
|
+
version = "0.2.5"
|
|
9
9
|
description = "Reusable Data Validation Framework for data migration, ETL validation, and cross-database reconciliation"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.9"
|
|
@@ -113,6 +113,7 @@ def main():
|
|
|
113
113
|
|
|
114
114
|
# Get preprocessor queries path if specified
|
|
115
115
|
preprocessor_queries_path = config_block["other"].get("preprocessor_queries")
|
|
116
|
+
|
|
116
117
|
if preprocessor_queries_path:
|
|
117
118
|
# Resolve relative path if needed
|
|
118
119
|
if not os.path.isabs(preprocessor_queries_path):
|
|
@@ -17,11 +17,48 @@ class ValidationExecutor:
|
|
|
17
17
|
|
|
18
18
|
self.source_connector = None
|
|
19
19
|
self.target_connector = None
|
|
20
|
-
self.
|
|
20
|
+
self.source_preprocessor = None
|
|
21
|
+
self.target_preprocessor = None
|
|
21
22
|
|
|
22
|
-
# Initialize
|
|
23
|
+
# Initialize preprocessors if path is provided
|
|
23
24
|
if preprocessor_queries_path:
|
|
24
|
-
|
|
25
|
+
# Extract config_query_key from source config (under gcp/mysql/etc)
|
|
26
|
+
src_config = self._extract_preprocessor_config(source_config)
|
|
27
|
+
self.source_preprocessor = QueryPreprocessor(preprocessor_queries_path, src_config)
|
|
28
|
+
|
|
29
|
+
# Extract config_query_key from target config (under gcp/mysql/etc)
|
|
30
|
+
tgt_config = self._extract_preprocessor_config(target_config)
|
|
31
|
+
self.target_preprocessor = QueryPreprocessor(preprocessor_queries_path, tgt_config)
|
|
32
|
+
|
|
33
|
+
def _extract_preprocessor_config(self, config: dict) -> dict:
|
|
34
|
+
"""
|
|
35
|
+
Extract preprocessor config (config_query_key) from database-specific config.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
config: Source or target config block
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Dictionary with config_query_key if found, empty dict otherwise
|
|
42
|
+
"""
|
|
43
|
+
if not config:
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
# Get database type from config
|
|
47
|
+
db_type = config.get("database_type")
|
|
48
|
+
if not db_type:
|
|
49
|
+
return {}
|
|
50
|
+
|
|
51
|
+
# Extract database-specific config (gcp, mysql, etc.)
|
|
52
|
+
db_config = config.get(db_type)
|
|
53
|
+
if not db_config or not isinstance(db_config, dict):
|
|
54
|
+
return {}
|
|
55
|
+
|
|
56
|
+
# Extract config_query_key if present
|
|
57
|
+
config_query_key = db_config.get("config_query_key")
|
|
58
|
+
if config_query_key:
|
|
59
|
+
return {"config_query_key": config_query_key}
|
|
60
|
+
|
|
61
|
+
return {}
|
|
25
62
|
|
|
26
63
|
def setup_connectors(self):
|
|
27
64
|
if self.source_config:
|
|
@@ -59,11 +96,9 @@ class ValidationExecutor:
|
|
|
59
96
|
if "source" in test_config:
|
|
60
97
|
source_query = test_config["source"]["query"]
|
|
61
98
|
|
|
62
|
-
# Process query with preprocessor
|
|
99
|
+
# Process query with source preprocessor (automatic replacement of all release labels)
|
|
63
100
|
source_query = self._process_query_with_preprocessor(
|
|
64
|
-
source_query,
|
|
65
|
-
test_config["source"],
|
|
66
|
-
self.source_connector
|
|
101
|
+
source_query, self.source_connector, self.source_preprocessor
|
|
67
102
|
)
|
|
68
103
|
|
|
69
104
|
source_query_start = datetime.now()
|
|
@@ -75,11 +110,9 @@ class ValidationExecutor:
|
|
|
75
110
|
if "target" in test_config:
|
|
76
111
|
target_query = test_config["target"]["query"]
|
|
77
112
|
|
|
78
|
-
# Process query with preprocessor
|
|
113
|
+
# Process query with target preprocessor (automatic replacement of all release labels)
|
|
79
114
|
target_query = self._process_query_with_preprocessor(
|
|
80
|
-
target_query,
|
|
81
|
-
test_config["target"],
|
|
82
|
-
self.target_connector
|
|
115
|
+
target_query, self.target_connector, self.target_preprocessor
|
|
83
116
|
)
|
|
84
117
|
|
|
85
118
|
target_query_start = datetime.now()
|
|
@@ -128,43 +161,28 @@ class ValidationExecutor:
|
|
|
128
161
|
# assuming single value queries
|
|
129
162
|
return list(result[0].values())[0]
|
|
130
163
|
|
|
131
|
-
def _process_query_with_preprocessor(self, query: str,
|
|
164
|
+
def _process_query_with_preprocessor(self, query: str, connector, preprocessor) -> str:
|
|
132
165
|
"""
|
|
133
|
-
Process query with preprocessor
|
|
166
|
+
Process query with preprocessor to replace all release label placeholders.
|
|
167
|
+
|
|
168
|
+
Automatically replaces all SOURCE_CURR_WEEK and SOURCE_PREV_WEEK placeholders
|
|
169
|
+
without needing per-test configuration.
|
|
134
170
|
|
|
135
171
|
Args:
|
|
136
172
|
query: Original query string
|
|
137
|
-
config_block: Configuration block for source or target (should contain query and optionally config_query_key)
|
|
138
173
|
connector: Database connector to use
|
|
174
|
+
preprocessor: QueryPreprocessor instance (source or target)
|
|
139
175
|
|
|
140
176
|
Returns:
|
|
141
177
|
Processed query (with replacements if applicable) or original query
|
|
142
178
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if not config_query_key:
|
|
147
|
-
# No preprocessor query key, return original query
|
|
179
|
+
if not preprocessor or not connector:
|
|
180
|
+
# Preprocessor not initialized or no connector, return original query
|
|
148
181
|
return query
|
|
149
182
|
|
|
150
|
-
if not self.preprocessor:
|
|
151
|
-
# Preprocessor not initialized, return original query
|
|
152
|
-
logger.warning(
|
|
153
|
-
f"config_query_key '{config_query_key}' specified but preprocessor not initialized"
|
|
154
|
-
)
|
|
155
|
-
return query
|
|
156
|
-
|
|
157
|
-
# Get source_name from config block if provided
|
|
158
|
-
source_name = config_block.get("source_name")
|
|
159
|
-
|
|
160
183
|
try:
|
|
161
|
-
# Process query through preprocessor
|
|
162
|
-
processed_query =
|
|
163
|
-
query,
|
|
164
|
-
config_query_key,
|
|
165
|
-
source_name,
|
|
166
|
-
connector
|
|
167
|
-
)
|
|
184
|
+
# Process query through preprocessor with automatic replacement
|
|
185
|
+
processed_query = preprocessor.replace_release_labels(query, connector)
|
|
168
186
|
return processed_query
|
|
169
187
|
except Exception as e:
|
|
170
188
|
logger.error(f"Error processing query with preprocessor: {str(e)}")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import yaml
|
|
2
2
|
import os
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Dict, Any, Optional
|
|
4
|
+
from typing import Dict, Any, Optional, List
|
|
5
5
|
from dataqe_framework.connectors import get_connector
|
|
6
6
|
|
|
7
7
|
logger = logging.getLogger(__name__)
|
|
@@ -15,17 +15,19 @@ class QueryPreprocessor:
|
|
|
15
15
|
and replaces placeholders in test queries with actual dataset names.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
def __init__(self, preprocessor_queries_path: str = None):
|
|
18
|
+
def __init__(self, preprocessor_queries_path: str = None, preprocessor_config: Dict[str, Any] = None):
|
|
19
19
|
"""
|
|
20
20
|
Initialize the QueryPreprocessor.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
23
23
|
preprocessor_queries_path: Path to preprocessor_queries.yml file.
|
|
24
|
-
|
|
24
|
+
preprocessor_config: Configuration dict with config_query_key and other settings.
|
|
25
25
|
"""
|
|
26
26
|
self.preprocessor_queries_path = preprocessor_queries_path
|
|
27
|
+
self.preprocessor_config = preprocessor_config or {}
|
|
27
28
|
self.preprocessor_queries = {}
|
|
28
29
|
self.dataset_mappings = {}
|
|
30
|
+
self.release_labels_cache = None
|
|
29
31
|
|
|
30
32
|
if self.preprocessor_queries_path:
|
|
31
33
|
self._load_preprocessor_queries()
|
|
@@ -186,3 +188,81 @@ class QueryPreprocessor:
|
|
|
186
188
|
|
|
187
189
|
# Replace placeholders in query
|
|
188
190
|
return self.replace_placeholders_in_query(query, source_name, mappings)
|
|
191
|
+
|
|
192
|
+
def replace_release_labels(self, query: str, connector: Any) -> str:
|
|
193
|
+
"""
|
|
194
|
+
Automatically replace all release label placeholders in query without needing
|
|
195
|
+
source_name or config_query_key specified per query.
|
|
196
|
+
|
|
197
|
+
This method executes the preprocessor query defined in preprocessor_config,
|
|
198
|
+
gets all release label mappings, and replaces all SOURCE_CURR_WEEK and SOURCE_PREV_WEEK
|
|
199
|
+
placeholders in the query.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
query: Original query string with placeholders like SOURCE_CURR_WEEK, SOURCE_PREV_WEEK
|
|
203
|
+
connector: Database connector for executing preprocessor query
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Processed query with all placeholders replaced by actual dataset names
|
|
207
|
+
"""
|
|
208
|
+
# If no preprocessor config or config_query_key, return original query
|
|
209
|
+
if not self.preprocessor_config or not self.preprocessor_config.get("config_query_key"):
|
|
210
|
+
return query
|
|
211
|
+
|
|
212
|
+
# Get release labels (cache to avoid multiple queries)
|
|
213
|
+
if self.release_labels_cache is None:
|
|
214
|
+
config_query_key = self.preprocessor_config.get("config_query_key")
|
|
215
|
+
release_labels = self.get_dataset_mappings(config_query_key, connector)
|
|
216
|
+
|
|
217
|
+
if not release_labels:
|
|
218
|
+
return query
|
|
219
|
+
|
|
220
|
+
# Convert mappings to list format for easier iteration
|
|
221
|
+
self.release_labels_cache = [
|
|
222
|
+
{
|
|
223
|
+
"source": source,
|
|
224
|
+
"curr_release_label": mapping.get("current_release"),
|
|
225
|
+
"prev_release_label": mapping.get("previous_release")
|
|
226
|
+
}
|
|
227
|
+
for source, mapping in release_labels.items()
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
# Replace all placeholders in query
|
|
231
|
+
return self._replace_all_release_labels(query, self.release_labels_cache)
|
|
232
|
+
|
|
233
|
+
def _replace_all_release_labels(self, query: str, release_labels: List[Dict[str, str]]) -> str:
|
|
234
|
+
"""
|
|
235
|
+
Replace all SOURCE_CURR_WEEK and SOURCE_PREV_WEEK placeholders in query.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
query: Original query string
|
|
239
|
+
release_labels: List of release label mappings
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Query with all placeholders replaced
|
|
243
|
+
"""
|
|
244
|
+
modified_query = query
|
|
245
|
+
|
|
246
|
+
if not release_labels:
|
|
247
|
+
return modified_query
|
|
248
|
+
|
|
249
|
+
for label in release_labels:
|
|
250
|
+
source = label.get("source", "").upper()
|
|
251
|
+
curr_label = label.get("curr_release_label")
|
|
252
|
+
prev_label = label.get("prev_release_label")
|
|
253
|
+
|
|
254
|
+
if not source or not curr_label or not prev_label:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
# Replace placeholders
|
|
258
|
+
modified_query = modified_query.replace(
|
|
259
|
+
f"{source}_CURR_WEEK", curr_label
|
|
260
|
+
).replace(f"{source}_PREV_WEEK", prev_label)
|
|
261
|
+
|
|
262
|
+
logger.debug(
|
|
263
|
+
f"Replaced placeholders for '{source}': "
|
|
264
|
+
f"{source}_CURR_WEEK → {curr_label}, "
|
|
265
|
+
f"{source}_PREV_WEEK → {prev_label}"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return modified_query
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dataqe-framework
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Reusable Data Validation Framework for data migration, ETL validation, and cross-database reconciliation
|
|
5
5
|
Author-email: Khadar Shaik <khadarmohiddin.shaik@apree.health>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ShaikKhadarmohiddin/dataqe-framework
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/comparison/comparator.py
RENAMED
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/comparison/threshold.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/connectors/__init__.py
RENAMED
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/connectors/base_connector.py
RENAMED
|
File without changes
|
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/connectors/mysql_connector.py
RENAMED
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework/credentials_extractor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/requires.txt
RENAMED
|
File without changes
|
{dataqe_framework-0.2.4 → dataqe_framework-0.2.5}/src/dataqe_framework.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|