netra-validator 0.1.0__py3-none-any.whl
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.
- netra_validator/__init__.py +13 -0
- netra_validator/cli/__init.py__ +0 -0
- netra_validator/cli/auditor.py +201 -0
- netra_validator/cli/components.py +932 -0
- netra_validator/cli/console.py +197 -0
- netra_validator/cli/main.py +283 -0
- netra_validator/cli/models.py +33 -0
- netra_validator/cli/theme.py +16 -0
- netra_validator/compiler/__init.py__ +0 -0
- netra_validator/compiler/duckdb_builder.py +112 -0
- netra_validator/compiler/polars_builder.py +81 -0
- netra_validator/config.py +111 -0
- netra_validator/engine/__init.py__ +0 -0
- netra_validator/engine/duckdb_runner.py +307 -0
- netra_validator/engine/polars_runner.py +402 -0
- netra_validator/engine/validator.py +363 -0
- netra_validator/exceptions.py +89 -0
- netra_validator/parser/__init.py__ +0 -0
- netra_validator/parser/models.py +221 -0
- netra_validator/parser/reader.py +81 -0
- netra_validator/report/__init.py__ +0 -0
- netra_validator/report/generator.py +205 -0
- netra_validator/types.py +101 -0
- netra_validator-0.1.0.dist-info/METADATA +334 -0
- netra_validator-0.1.0.dist-info/RECORD +28 -0
- netra_validator-0.1.0.dist-info/WHEEL +4 -0
- netra_validator-0.1.0.dist-info/entry_points.txt +2 -0
- netra_validator-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from importlib import metadata
|
|
2
|
+
|
|
3
|
+
# Single Source of Truth for Versioning
|
|
4
|
+
try:
|
|
5
|
+
__version__ = metadata.version("netra-validator")
|
|
6
|
+
except metadata.PackageNotFoundError:
|
|
7
|
+
__version__ = "0.0.0-dev"
|
|
8
|
+
|
|
9
|
+
# This allows users to do: "from netra_profiler import Profiler"
|
|
10
|
+
# instead of "from netra_profiler.core import Profiler"
|
|
11
|
+
from .engine.validator import Validator
|
|
12
|
+
|
|
13
|
+
__all__ = ["Validator", "__version__"]
|
|
File without changes
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Business logic for pre-flight environment auditing and contract validation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from netra_validator.cli.models import ExecutionManifest
|
|
12
|
+
from netra_validator.compiler.duckdb_builder import build_global_sql_rules
|
|
13
|
+
from netra_validator.config import ValidatorConfig
|
|
14
|
+
from netra_validator.engine.duckdb_runner import validate_sql_rules_preflight
|
|
15
|
+
from netra_validator.exceptions import (
|
|
16
|
+
ConfigNotFoundError,
|
|
17
|
+
ContractNotFoundError,
|
|
18
|
+
ContractSyntaxError,
|
|
19
|
+
ContractValidationError,
|
|
20
|
+
DatasetNotFoundError,
|
|
21
|
+
)
|
|
22
|
+
from netra_validator.parser.models import DataContract
|
|
23
|
+
from netra_validator.parser.reader import read_contract
|
|
24
|
+
|
|
25
|
+
ONE_GB = 1024
|
|
26
|
+
SAFE_RAM_UTILIZATION_RATIO = 0.80
|
|
27
|
+
|
|
28
|
+
# Estimated in-memory & hash-state expansion factor per on-disk file format
|
|
29
|
+
FORMAT_EXPANSION_FACTORS: dict[str, float] = {
|
|
30
|
+
".parquet": 5.2,
|
|
31
|
+
".json": 2.5,
|
|
32
|
+
".csv": 2.0,
|
|
33
|
+
".ipc": 1.5,
|
|
34
|
+
".arrow": 1.5,
|
|
35
|
+
}
|
|
36
|
+
DEFAULT_EXPANSION_FACTOR = 5.2
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_system_ram_mb() -> float:
|
|
40
|
+
"""Extracts total physical memory available on the host OS in MB."""
|
|
41
|
+
if sys.platform == "win32":
|
|
42
|
+
import psutil # type: ignore # noqa: PLC0415
|
|
43
|
+
|
|
44
|
+
return float(psutil.virtual_memory().total) / (1024 * 1024)
|
|
45
|
+
|
|
46
|
+
return float(os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")) / (1024 * 1024)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GlobalTargetChoice(str, Enum):
|
|
50
|
+
RAW = "raw"
|
|
51
|
+
VALIDATED = "validated"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class RunContext:
|
|
56
|
+
"""Encapsulates the resolved CLI arguments and paths for the execution session."""
|
|
57
|
+
|
|
58
|
+
dataset_path: Path
|
|
59
|
+
contract_path: Path
|
|
60
|
+
valid_path: Path
|
|
61
|
+
quarantine_path: Path
|
|
62
|
+
config_path: Path | None
|
|
63
|
+
global_target: GlobalTargetChoice | None
|
|
64
|
+
force: bool
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class PreFlightAuditor:
|
|
68
|
+
"""
|
|
69
|
+
Evaluates system I/O, parses the data contract, and calculates hardware heuristics
|
|
70
|
+
prior to running the validation engine.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, context: RunContext) -> None:
|
|
74
|
+
self.context = context
|
|
75
|
+
self.errors: list[tuple[str, str]] = []
|
|
76
|
+
self.warnings: list[str] = []
|
|
77
|
+
self.loaded_config = ValidatorConfig()
|
|
78
|
+
self._contract_payload: DataContract | None = None
|
|
79
|
+
|
|
80
|
+
def _check_dataset(self) -> tuple[str, bool, float]:
|
|
81
|
+
try:
|
|
82
|
+
if not self.context.dataset_path.exists():
|
|
83
|
+
raise DatasetNotFoundError(self.context.dataset_path.name)
|
|
84
|
+
|
|
85
|
+
size_mb = os.path.getsize(self.context.dataset_path) / (1024 * 1024)
|
|
86
|
+
dataset_meta = f"{size_mb / 1024:.2f} GB" if size_mb > ONE_GB else f"{size_mb:.2f} MB"
|
|
87
|
+
return dataset_meta, False, size_mb
|
|
88
|
+
|
|
89
|
+
except DatasetNotFoundError as e:
|
|
90
|
+
self.errors.append((e.__class__.__name__, str(e)))
|
|
91
|
+
return "NOT FOUND", True, 0.0
|
|
92
|
+
|
|
93
|
+
def _check_contract(self) -> tuple[str, bool]:
|
|
94
|
+
try:
|
|
95
|
+
self._contract_payload = read_contract(self.context.contract_path)
|
|
96
|
+
return f"v{self._contract_payload.version}", False
|
|
97
|
+
|
|
98
|
+
except ContractNotFoundError as e:
|
|
99
|
+
self.errors.append((e.__class__.__name__, str(e)))
|
|
100
|
+
return "NOT FOUND", True
|
|
101
|
+
|
|
102
|
+
except ContractSyntaxError as e:
|
|
103
|
+
self.errors.append((e.__class__.__name__, str(e)))
|
|
104
|
+
return "SYNTAX ERR", True
|
|
105
|
+
|
|
106
|
+
except ContractValidationError as e:
|
|
107
|
+
for schema_error in e.errors:
|
|
108
|
+
clean_error = schema_error.removeprefix("- ").replace(": Value error, ", ": ")
|
|
109
|
+
self.errors.append(("ContractValidationError", clean_error))
|
|
110
|
+
return "INVALID", True
|
|
111
|
+
|
|
112
|
+
def _check_config(self) -> tuple[str, str, bool]:
|
|
113
|
+
if not self.context.config_path:
|
|
114
|
+
return "Default", "FACTORY", False
|
|
115
|
+
|
|
116
|
+
config_source = self.context.config_path.name
|
|
117
|
+
try:
|
|
118
|
+
if not self.context.config_path.exists():
|
|
119
|
+
raise ConfigNotFoundError(config_source)
|
|
120
|
+
self.loaded_config = ValidatorConfig.from_yaml(self.context.config_path)
|
|
121
|
+
return config_source, "CUSTOM", False
|
|
122
|
+
|
|
123
|
+
except ConfigNotFoundError as e:
|
|
124
|
+
self.errors.append((e.__class__.__name__, str(e)))
|
|
125
|
+
return config_source, "NOT FOUND", True
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.errors.append(("ConfigParseError", str(e)))
|
|
128
|
+
return config_source, "INVALID", True
|
|
129
|
+
|
|
130
|
+
def _evaluate_heuristics(self, size_mb: float) -> tuple[str, str, bool]:
|
|
131
|
+
if self.loaded_config.spill.enabled:
|
|
132
|
+
limit_gb = self.loaded_config.spill.memory_limit_mb / ONE_GB
|
|
133
|
+
return "SPILL TO DISK", f"{limit_gb:.1f} GB LIMIT", False
|
|
134
|
+
|
|
135
|
+
ext = self.context.dataset_path.suffix.lower()
|
|
136
|
+
expansion_factor = FORMAT_EXPANSION_FACTORS.get(ext, DEFAULT_EXPANSION_FACTOR)
|
|
137
|
+
estimated_footprint_mb = size_mb * expansion_factor
|
|
138
|
+
safe_ram_threshold_mb = _get_system_ram_mb() * SAFE_RAM_UTILIZATION_RATIO
|
|
139
|
+
|
|
140
|
+
if estimated_footprint_mb >= safe_ram_threshold_mb:
|
|
141
|
+
est_gb = estimated_footprint_mb / ONE_GB
|
|
142
|
+
total_gb = _get_system_ram_mb() / ONE_GB
|
|
143
|
+
self.warnings.append(
|
|
144
|
+
f"Estimated in-memory footprint (~{est_gb:.1f} GB) exceeds 80% of system RAM "
|
|
145
|
+
f"({total_gb:.1f} GB) for IN-MEMORY STREAMING. Risk of severe latency or OOM crash."
|
|
146
|
+
)
|
|
147
|
+
return "IN-MEMORY STREAMING", "OOM RISK", True
|
|
148
|
+
|
|
149
|
+
return "IN-MEMORY STREAMING", "DEFAULT", False
|
|
150
|
+
|
|
151
|
+
def execute(self) -> ExecutionManifest:
|
|
152
|
+
dataset_meta, dataset_critical, size_mb = self._check_dataset()
|
|
153
|
+
contract_meta, contract_critical = self._check_contract()
|
|
154
|
+
config_source, config_meta, config_critical = self._check_config()
|
|
155
|
+
|
|
156
|
+
if not dataset_critical and not contract_critical and self._contract_payload is not None:
|
|
157
|
+
sql_rules = build_global_sql_rules(self._contract_payload)
|
|
158
|
+
sql_errors = validate_sql_rules_preflight(self.context.dataset_path, sql_rules)
|
|
159
|
+
if sql_errors:
|
|
160
|
+
contract_meta = "SQL_ERR"
|
|
161
|
+
contract_critical = True
|
|
162
|
+
for rule_name, err_msg in sql_errors:
|
|
163
|
+
self.errors.append((f"SQL Error ({rule_name})", err_msg))
|
|
164
|
+
|
|
165
|
+
if self.context.global_target is not None:
|
|
166
|
+
self.loaded_config.global_target = (
|
|
167
|
+
"validated" if self.context.global_target == GlobalTargetChoice.VALIDATED else "raw"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
active_target = self.loaded_config.global_target
|
|
171
|
+
global_target_meta = "DEFAULT" if active_target == "raw" else "POST-FILTER"
|
|
172
|
+
|
|
173
|
+
validated_protocol, validated_write_mode = "LOCAL", "APPEND"
|
|
174
|
+
quarantine_protocol, quarantine_write_mode = "LOCAL", "OVERWRITE"
|
|
175
|
+
|
|
176
|
+
engine_mode, engine_meta, engine_warning = self._evaluate_heuristics(size_mb)
|
|
177
|
+
|
|
178
|
+
return ExecutionManifest(
|
|
179
|
+
dataset_path=os.path.relpath(self.context.dataset_path),
|
|
180
|
+
dataset_meta=dataset_meta,
|
|
181
|
+
dataset_critical_error=dataset_critical,
|
|
182
|
+
contract_path=os.path.relpath(self.context.contract_path),
|
|
183
|
+
contract_meta=contract_meta,
|
|
184
|
+
contract_critical=contract_critical,
|
|
185
|
+
config_source=config_source,
|
|
186
|
+
config_meta=config_meta,
|
|
187
|
+
config_critical=config_critical,
|
|
188
|
+
global_target=active_target.upper(),
|
|
189
|
+
global_target_meta=global_target_meta,
|
|
190
|
+
validated_path=str(self.context.valid_path),
|
|
191
|
+
validated_protocol=validated_protocol,
|
|
192
|
+
validated_write_mode=validated_write_mode,
|
|
193
|
+
quarantine_path=str(self.context.quarantine_path),
|
|
194
|
+
quarantine_protocol=quarantine_protocol,
|
|
195
|
+
quarantine_write_mode=quarantine_write_mode,
|
|
196
|
+
engine_mode=engine_mode,
|
|
197
|
+
engine_meta=engine_meta,
|
|
198
|
+
engine_warning=engine_warning,
|
|
199
|
+
errors=self.errors,
|
|
200
|
+
warnings=self.warnings,
|
|
201
|
+
)
|