truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.1__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.
Files changed (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
  162. truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
  164. truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
  166. truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,401 @@
1
+ """Custom Validator Executor.
2
+
3
+ This module provides execution of custom validators in a safe,
4
+ sandboxed environment.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import uuid
11
+ from dataclasses import dataclass, field
12
+ from datetime import datetime
13
+ from typing import Any
14
+
15
+ from sqlalchemy.ext.asyncio import AsyncSession
16
+
17
+ from truthound_dashboard.db.models import CustomValidator, PluginExecutionLog
18
+
19
+ from .sandbox import create_sandbox, SandboxConfig, SandboxResult
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dataclass
25
+ class ValidatorResult:
26
+ """Result of custom validator execution.
27
+
28
+ Attributes:
29
+ passed: Whether validation passed.
30
+ issues: List of validation issues found.
31
+ message: Summary message.
32
+ details: Additional details (JSON-serializable).
33
+ execution_time_ms: Execution time in milliseconds.
34
+ """
35
+
36
+ passed: bool
37
+ issues: list[dict[str, Any]] = field(default_factory=list)
38
+ message: str = ""
39
+ details: dict[str, Any] = field(default_factory=dict)
40
+ execution_time_ms: float = 0
41
+
42
+
43
+ @dataclass
44
+ class ValidatorContext:
45
+ """Context provided to custom validators.
46
+
47
+ Attributes:
48
+ column_name: Name of the column being validated.
49
+ column_values: List of values in the column.
50
+ parameters: Validator parameters.
51
+ schema: Column schema information.
52
+ row_count: Total number of rows.
53
+ """
54
+
55
+ column_name: str
56
+ column_values: list[Any]
57
+ parameters: dict[str, Any] = field(default_factory=dict)
58
+ schema: dict[str, Any] = field(default_factory=dict)
59
+ row_count: int = 0
60
+
61
+ def to_dict(self) -> dict[str, Any]:
62
+ """Convert to dictionary for sandbox execution."""
63
+ return {
64
+ "column_name": self.column_name,
65
+ "values": self.column_values,
66
+ "params": self.parameters,
67
+ "schema": self.schema,
68
+ "row_count": self.row_count,
69
+ }
70
+
71
+
72
+ # Template code that wraps custom validator code
73
+ VALIDATOR_WRAPPER_CODE = '''
74
+ # Custom validator wrapper
75
+ import re
76
+ import math
77
+ import statistics
78
+ from datetime import datetime, date
79
+ from collections import Counter
80
+
81
+ # User-provided validator code
82
+ {user_code}
83
+
84
+ # Entry point
85
+ def _execute_validator(column_name, values, params, schema, row_count):
86
+ """Execute the custom validator.
87
+
88
+ Args:
89
+ column_name: Name of the column.
90
+ values: List of column values.
91
+ params: Validator parameters.
92
+ schema: Column schema.
93
+ row_count: Total row count.
94
+
95
+ Returns:
96
+ Dictionary with 'passed', 'issues', 'message', 'details'.
97
+ """
98
+ # Call the user-defined validate function
99
+ result = validate(column_name, values, params, schema, row_count)
100
+
101
+ # Ensure result is properly formatted
102
+ if isinstance(result, bool):
103
+ return {{
104
+ "passed": result,
105
+ "issues": [],
106
+ "message": "Validation passed" if result else "Validation failed",
107
+ "details": {{}}
108
+ }}
109
+ elif isinstance(result, dict):
110
+ return {{
111
+ "passed": result.get("passed", False),
112
+ "issues": result.get("issues", []),
113
+ "message": result.get("message", ""),
114
+ "details": result.get("details", {{}})
115
+ }}
116
+ else:
117
+ return {{
118
+ "passed": False,
119
+ "issues": [],
120
+ "message": f"Invalid result type: {{type(result).__name__}}",
121
+ "details": {{}}
122
+ }}
123
+ '''
124
+
125
+
126
+ class CustomValidatorExecutor:
127
+ """Executor for custom validators.
128
+
129
+ This class handles the execution of custom validators
130
+ in a sandboxed environment with logging and monitoring.
131
+
132
+ Attributes:
133
+ sandbox: Plugin sandbox for secure execution.
134
+ log_executions: Whether to log executions to database.
135
+ """
136
+
137
+ def __init__(
138
+ self,
139
+ sandbox_config: SandboxConfig | None = None,
140
+ log_executions: bool = True,
141
+ ) -> None:
142
+ """Initialize the executor.
143
+
144
+ Args:
145
+ sandbox_config: Sandbox configuration.
146
+ log_executions: Whether to log executions.
147
+ """
148
+ self.sandbox = create_sandbox(sandbox_config or SandboxConfig())
149
+ self.log_executions = log_executions
150
+
151
+ def validate_validator_code(self, code: str) -> tuple[bool, list[str]]:
152
+ """Validate custom validator code before execution.
153
+
154
+ Args:
155
+ code: Python code implementing the validator.
156
+
157
+ Returns:
158
+ Tuple of (is_valid, list of issues).
159
+ """
160
+ issues = []
161
+
162
+ # Check for required function
163
+ if "def validate(" not in code:
164
+ issues.append("Missing required 'validate' function")
165
+
166
+ # Check for dangerous patterns
167
+ dangerous = [
168
+ "os.system",
169
+ "subprocess",
170
+ "exec(",
171
+ "eval(",
172
+ "__import__",
173
+ "open(",
174
+ "file(",
175
+ ]
176
+ for pattern in dangerous:
177
+ if pattern in code:
178
+ issues.append(f"Dangerous pattern detected: {pattern}")
179
+
180
+ # Analyze with sandbox
181
+ sandbox_issues, _ = self.sandbox.analyze_code(code)
182
+ issues.extend(sandbox_issues)
183
+
184
+ return len(issues) == 0, issues
185
+
186
+ async def execute(
187
+ self,
188
+ validator: CustomValidator,
189
+ context: ValidatorContext,
190
+ session: AsyncSession | None = None,
191
+ source_id: str | None = None,
192
+ ) -> ValidatorResult:
193
+ """Execute a custom validator.
194
+
195
+ Args:
196
+ validator: CustomValidator model.
197
+ context: Validation context.
198
+ session: Optional database session for logging.
199
+ source_id: Optional source ID for logging.
200
+
201
+ Returns:
202
+ ValidatorResult with execution results.
203
+ """
204
+ execution_id = str(uuid.uuid4())
205
+ start_time = datetime.utcnow()
206
+
207
+ logger.debug(
208
+ f"Executing custom validator: {validator.name} "
209
+ f"on column: {context.column_name}"
210
+ )
211
+
212
+ # Create execution log if logging enabled
213
+ log_entry = None
214
+ if self.log_executions and session and validator.plugin_id:
215
+ log_entry = PluginExecutionLog(
216
+ plugin_id=validator.plugin_id,
217
+ validator_id=str(validator.id),
218
+ execution_id=execution_id,
219
+ source_id=source_id,
220
+ status="running",
221
+ )
222
+ session.add(log_entry)
223
+ await session.flush()
224
+
225
+ try:
226
+ # Prepare the code
227
+ wrapped_code = VALIDATOR_WRAPPER_CODE.format(user_code=validator.code)
228
+
229
+ # Execute in sandbox
230
+ sandbox_result = self.sandbox.execute(
231
+ code=wrapped_code,
232
+ entry_point="_execute_validator",
233
+ entry_args=context.to_dict(),
234
+ )
235
+
236
+ if sandbox_result.success and sandbox_result.result:
237
+ result = ValidatorResult(
238
+ passed=sandbox_result.result.get("passed", False),
239
+ issues=sandbox_result.result.get("issues", []),
240
+ message=sandbox_result.result.get("message", ""),
241
+ details=sandbox_result.result.get("details", {}),
242
+ execution_time_ms=sandbox_result.execution_time_ms,
243
+ )
244
+ else:
245
+ result = ValidatorResult(
246
+ passed=False,
247
+ message=sandbox_result.error or "Execution failed",
248
+ details={
249
+ "stdout": sandbox_result.stdout,
250
+ "stderr": sandbox_result.stderr,
251
+ },
252
+ execution_time_ms=sandbox_result.execution_time_ms,
253
+ )
254
+
255
+ # Update validator usage stats
256
+ validator.increment_usage()
257
+
258
+ # Update execution log
259
+ if log_entry:
260
+ if result.passed:
261
+ log_entry.mark_completed(
262
+ result={"passed": result.passed, "issues_count": len(result.issues)},
263
+ memory_used_mb=sandbox_result.memory_used_mb,
264
+ )
265
+ else:
266
+ log_entry.mark_failed(result.message)
267
+
268
+ if session:
269
+ await session.flush()
270
+
271
+ return result
272
+
273
+ except Exception as e:
274
+ logger.error(f"Custom validator execution failed: {e}")
275
+
276
+ if log_entry:
277
+ log_entry.mark_failed(str(e))
278
+ if session:
279
+ await session.flush()
280
+
281
+ return ValidatorResult(
282
+ passed=False,
283
+ message=f"Execution error: {e}",
284
+ )
285
+
286
+ async def test_validator(
287
+ self,
288
+ code: str,
289
+ parameters: list[dict[str, Any]],
290
+ test_data: dict[str, Any],
291
+ param_values: dict[str, Any] | None = None,
292
+ ) -> dict[str, Any]:
293
+ """Test a custom validator without saving.
294
+
295
+ Args:
296
+ code: Validator code.
297
+ parameters: Parameter definitions.
298
+ test_data: Test data (column_name and values).
299
+ param_values: Parameter values to use.
300
+
301
+ Returns:
302
+ Dictionary with test results.
303
+ """
304
+ # Validate code first
305
+ is_valid, issues = self.validate_validator_code(code)
306
+ if not is_valid:
307
+ return {
308
+ "success": False,
309
+ "passed": None,
310
+ "error": f"Code validation failed: {'; '.join(issues)}",
311
+ "execution_time_ms": 0,
312
+ "warnings": issues,
313
+ }
314
+
315
+ # Create context
316
+ context = ValidatorContext(
317
+ column_name=test_data.get("column_name", "test_column"),
318
+ column_values=test_data.get("values", []),
319
+ parameters=param_values or {},
320
+ schema=test_data.get("schema", {}),
321
+ row_count=len(test_data.get("values", [])),
322
+ )
323
+
324
+ # Execute
325
+ wrapped_code = VALIDATOR_WRAPPER_CODE.format(user_code=code)
326
+ sandbox_result = self.sandbox.execute(
327
+ code=wrapped_code,
328
+ entry_point="_execute_validator",
329
+ entry_args=context.to_dict(),
330
+ )
331
+
332
+ if sandbox_result.success and sandbox_result.result:
333
+ return {
334
+ "success": True,
335
+ "passed": sandbox_result.result.get("passed", False),
336
+ "result": sandbox_result.result,
337
+ "execution_time_ms": sandbox_result.execution_time_ms,
338
+ "warnings": sandbox_result.warnings,
339
+ }
340
+ else:
341
+ return {
342
+ "success": False,
343
+ "passed": None,
344
+ "error": sandbox_result.error,
345
+ "execution_time_ms": sandbox_result.execution_time_ms,
346
+ "warnings": sandbox_result.warnings,
347
+ }
348
+
349
+ def get_validator_template(self) -> str:
350
+ """Get a template for creating custom validators.
351
+
352
+ Returns:
353
+ Template code string.
354
+ """
355
+ return '''
356
+ def validate(column_name, values, params, schema, row_count):
357
+ """Custom validator function.
358
+
359
+ Args:
360
+ column_name: Name of the column being validated.
361
+ values: List of values in the column.
362
+ params: Dictionary of parameter values.
363
+ schema: Column schema information.
364
+ row_count: Total number of rows.
365
+
366
+ Returns:
367
+ Dictionary with:
368
+ - passed: bool - Whether validation passed
369
+ - issues: list - List of issue dictionaries
370
+ - message: str - Summary message
371
+ - details: dict - Additional details
372
+ """
373
+ issues = []
374
+
375
+ # Example: Check for null values
376
+ null_count = sum(1 for v in values if v is None)
377
+ if null_count > 0:
378
+ issues.append({
379
+ "row": None,
380
+ "message": f"Found {null_count} null values",
381
+ "severity": "warning"
382
+ })
383
+
384
+ # Example: Custom validation logic
385
+ # threshold = params.get("threshold", 0.1)
386
+ # ...
387
+
388
+ return {
389
+ "passed": len(issues) == 0,
390
+ "issues": issues,
391
+ "message": f"Validation completed with {len(issues)} issues",
392
+ "details": {
393
+ "null_count": null_count,
394
+ "total_values": len(values)
395
+ }
396
+ }
397
+ '''
398
+
399
+
400
+ # Default executor instance
401
+ validator_executor = CustomValidatorExecutor()
@@ -0,0 +1,51 @@
1
+ """Plugin Versioning and Dependency Management Module.
2
+
3
+ This module provides:
4
+ - Semantic versioning (semver) parsing and comparison
5
+ - Version constraint parsing (^, ~, >=, <, etc.)
6
+ - Dependency graph with cycle detection
7
+ - Topological sorting for load order
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from .semver import (
13
+ Version,
14
+ parse_version,
15
+ compare_versions,
16
+ )
17
+ from .constraints import (
18
+ VersionConstraint,
19
+ VersionRange,
20
+ parse_constraint,
21
+ satisfies,
22
+ find_best_version,
23
+ )
24
+ from .dependencies import (
25
+ DependencyType,
26
+ Dependency,
27
+ DependencyGraph,
28
+ DependencyResolver,
29
+ DependencyResolutionError,
30
+ CyclicDependencyError,
31
+ )
32
+
33
+ __all__ = [
34
+ # Semver
35
+ "Version",
36
+ "parse_version",
37
+ "compare_versions",
38
+ # Constraints
39
+ "VersionConstraint",
40
+ "VersionRange",
41
+ "parse_constraint",
42
+ "satisfies",
43
+ "find_best_version",
44
+ # Dependencies
45
+ "DependencyType",
46
+ "Dependency",
47
+ "DependencyGraph",
48
+ "DependencyResolver",
49
+ "DependencyResolutionError",
50
+ "CyclicDependencyError",
51
+ ]