truthound-dashboard 1.4.3__py3-none-any.whl → 1.5.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.
- truthound_dashboard/api/alerts.py +75 -86
- truthound_dashboard/api/anomaly.py +7 -13
- truthound_dashboard/api/cross_alerts.py +38 -52
- truthound_dashboard/api/drift.py +49 -59
- truthound_dashboard/api/drift_monitor.py +234 -79
- truthound_dashboard/api/enterprise_sampling.py +498 -0
- truthound_dashboard/api/history.py +57 -5
- truthound_dashboard/api/lineage.py +3 -48
- truthound_dashboard/api/maintenance.py +104 -49
- truthound_dashboard/api/mask.py +1 -2
- truthound_dashboard/api/middleware.py +2 -1
- truthound_dashboard/api/model_monitoring.py +435 -311
- truthound_dashboard/api/notifications.py +227 -191
- truthound_dashboard/api/notifications_advanced.py +21 -20
- truthound_dashboard/api/observability.py +586 -0
- truthound_dashboard/api/plugins.py +2 -433
- truthound_dashboard/api/profile.py +199 -37
- truthound_dashboard/api/quality_reporter.py +701 -0
- truthound_dashboard/api/reports.py +7 -16
- truthound_dashboard/api/router.py +66 -0
- truthound_dashboard/api/rule_suggestions.py +5 -5
- truthound_dashboard/api/scan.py +17 -19
- truthound_dashboard/api/schedules.py +85 -50
- truthound_dashboard/api/schema_evolution.py +6 -6
- truthound_dashboard/api/schema_watcher.py +667 -0
- truthound_dashboard/api/sources.py +98 -27
- truthound_dashboard/api/tiering.py +1323 -0
- truthound_dashboard/api/triggers.py +14 -11
- truthound_dashboard/api/validations.py +12 -11
- truthound_dashboard/api/versioning.py +1 -6
- truthound_dashboard/core/__init__.py +129 -3
- truthound_dashboard/core/actions/__init__.py +62 -0
- truthound_dashboard/core/actions/custom.py +426 -0
- truthound_dashboard/core/actions/notifications.py +910 -0
- truthound_dashboard/core/actions/storage.py +472 -0
- truthound_dashboard/core/actions/webhook.py +281 -0
- truthound_dashboard/core/anomaly.py +262 -67
- truthound_dashboard/core/anomaly_explainer.py +4 -3
- truthound_dashboard/core/backends/__init__.py +67 -0
- truthound_dashboard/core/backends/base.py +299 -0
- truthound_dashboard/core/backends/errors.py +191 -0
- truthound_dashboard/core/backends/factory.py +423 -0
- truthound_dashboard/core/backends/mock_backend.py +451 -0
- truthound_dashboard/core/backends/truthound_backend.py +718 -0
- truthound_dashboard/core/checkpoint/__init__.py +87 -0
- truthound_dashboard/core/checkpoint/adapters.py +814 -0
- truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
- truthound_dashboard/core/checkpoint/runner.py +270 -0
- truthound_dashboard/core/connections.py +437 -10
- truthound_dashboard/core/converters/__init__.py +14 -0
- truthound_dashboard/core/converters/truthound.py +620 -0
- truthound_dashboard/core/cross_alerts.py +540 -320
- truthound_dashboard/core/datasource_factory.py +1672 -0
- truthound_dashboard/core/drift_monitor.py +216 -20
- truthound_dashboard/core/enterprise_sampling.py +1291 -0
- truthound_dashboard/core/interfaces/__init__.py +225 -0
- truthound_dashboard/core/interfaces/actions.py +652 -0
- truthound_dashboard/core/interfaces/base.py +247 -0
- truthound_dashboard/core/interfaces/checkpoint.py +676 -0
- truthound_dashboard/core/interfaces/protocols.py +664 -0
- truthound_dashboard/core/interfaces/reporters.py +650 -0
- truthound_dashboard/core/interfaces/routing.py +646 -0
- truthound_dashboard/core/interfaces/triggers.py +619 -0
- truthound_dashboard/core/lineage.py +407 -71
- truthound_dashboard/core/model_monitoring.py +431 -3
- truthound_dashboard/core/notifications/base.py +4 -0
- truthound_dashboard/core/notifications/channels.py +501 -1203
- truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
- truthound_dashboard/core/notifications/deduplication/service.py +131 -348
- truthound_dashboard/core/notifications/dispatcher.py +202 -11
- truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
- truthound_dashboard/core/notifications/escalation/engine.py +168 -358
- truthound_dashboard/core/notifications/routing/__init__.py +88 -128
- truthound_dashboard/core/notifications/routing/engine.py +90 -317
- truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
- truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
- truthound_dashboard/core/notifications/throttling/builder.py +117 -255
- truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
- truthound_dashboard/core/phase5/collaboration.py +1 -1
- truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
- truthound_dashboard/core/quality_reporter.py +1359 -0
- truthound_dashboard/core/report_history.py +0 -6
- truthound_dashboard/core/reporters/__init__.py +175 -14
- truthound_dashboard/core/reporters/adapters.py +943 -0
- truthound_dashboard/core/reporters/base.py +0 -3
- truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
- truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
- truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
- truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
- truthound_dashboard/core/reporters/compat.py +266 -0
- truthound_dashboard/core/reporters/csv_reporter.py +2 -35
- truthound_dashboard/core/reporters/factory.py +526 -0
- truthound_dashboard/core/reporters/interfaces.py +745 -0
- truthound_dashboard/core/reporters/registry.py +1 -10
- truthound_dashboard/core/scheduler.py +165 -0
- truthound_dashboard/core/schema_evolution.py +3 -3
- truthound_dashboard/core/schema_watcher.py +1528 -0
- truthound_dashboard/core/services.py +595 -76
- truthound_dashboard/core/store_manager.py +810 -0
- truthound_dashboard/core/streaming_anomaly.py +169 -4
- truthound_dashboard/core/tiering.py +1309 -0
- truthound_dashboard/core/triggers/evaluators.py +178 -8
- truthound_dashboard/core/truthound_adapter.py +2620 -197
- truthound_dashboard/core/unified_alerts.py +23 -20
- truthound_dashboard/db/__init__.py +8 -0
- truthound_dashboard/db/database.py +8 -2
- truthound_dashboard/db/models.py +944 -25
- truthound_dashboard/db/repository.py +2 -0
- truthound_dashboard/main.py +11 -0
- truthound_dashboard/schemas/__init__.py +177 -16
- truthound_dashboard/schemas/base.py +44 -23
- truthound_dashboard/schemas/collaboration.py +19 -6
- truthound_dashboard/schemas/cross_alerts.py +19 -3
- truthound_dashboard/schemas/drift.py +61 -55
- truthound_dashboard/schemas/drift_monitor.py +67 -23
- truthound_dashboard/schemas/enterprise_sampling.py +653 -0
- truthound_dashboard/schemas/lineage.py +0 -33
- truthound_dashboard/schemas/mask.py +10 -8
- truthound_dashboard/schemas/model_monitoring.py +89 -10
- truthound_dashboard/schemas/notifications_advanced.py +13 -0
- truthound_dashboard/schemas/observability.py +453 -0
- truthound_dashboard/schemas/plugins.py +0 -280
- truthound_dashboard/schemas/profile.py +154 -247
- truthound_dashboard/schemas/quality_reporter.py +403 -0
- truthound_dashboard/schemas/reports.py +2 -2
- truthound_dashboard/schemas/rule_suggestion.py +8 -1
- truthound_dashboard/schemas/scan.py +4 -24
- truthound_dashboard/schemas/schedule.py +11 -3
- truthound_dashboard/schemas/schema_watcher.py +727 -0
- truthound_dashboard/schemas/source.py +17 -2
- truthound_dashboard/schemas/tiering.py +822 -0
- truthound_dashboard/schemas/triggers.py +16 -0
- truthound_dashboard/schemas/unified_alerts.py +7 -0
- truthound_dashboard/schemas/validation.py +0 -13
- truthound_dashboard/schemas/validators/base.py +41 -21
- truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
- truthound_dashboard/schemas/validators/localization_validators.py +273 -0
- truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
- truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
- truthound_dashboard/schemas/validators/referential_validators.py +312 -0
- truthound_dashboard/schemas/validators/registry.py +93 -8
- truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
- truthound_dashboard/schemas/versioning.py +1 -6
- truthound_dashboard/static/index.html +2 -2
- truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
- truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
- truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
- truthound_dashboard/core/plugins/hooks/manager.py +0 -403
- truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
- truthound_dashboard/core/reporters/junit_reporter.py +0 -233
- truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
- truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
- truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
- truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
- truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
- truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
- truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
- truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
- truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
- truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
- truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
- truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
- truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
- truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
- truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
- truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
- truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
- truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
- truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
- truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
- truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
- truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
- truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
- truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
- truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
- truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
- truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
- truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
- truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
- truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
- truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
- truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
- truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
- truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
- truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
- truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
- truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
- truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
- truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
- truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
- truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
- truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
- truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
- truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
- truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
- truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
- truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
- truthound_dashboard-1.4.3.dist-info/METADATA +0 -505
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
"""Unified store management using truthound's store backends.
|
|
2
|
+
|
|
3
|
+
This module provides a unified interface for validation result storage
|
|
4
|
+
using truthound's enterprise store features:
|
|
5
|
+
- Retention policies (truthound.stores.retention)
|
|
6
|
+
- Caching (truthound.stores.caching)
|
|
7
|
+
- Versioning (truthound.stores.versioning)
|
|
8
|
+
- Storage tiering (truthound.stores.tiering)
|
|
9
|
+
- Observability (truthound.stores.observability)
|
|
10
|
+
|
|
11
|
+
Note: SQLite VACUUM is dashboard-specific and not part of truthound.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime, timedelta
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Callable
|
|
22
|
+
|
|
23
|
+
from truthound.stores import get_store
|
|
24
|
+
from truthound.stores.retention import (
|
|
25
|
+
CompositePolicy,
|
|
26
|
+
CountBasedPolicy,
|
|
27
|
+
RetentionConfig,
|
|
28
|
+
RetentionResult,
|
|
29
|
+
RetentionSchedule,
|
|
30
|
+
RetentionStore,
|
|
31
|
+
SizeBasedPolicy,
|
|
32
|
+
StatusBasedPolicy,
|
|
33
|
+
TagBasedPolicy,
|
|
34
|
+
TimeBasedPolicy,
|
|
35
|
+
)
|
|
36
|
+
from truthound.stores.caching import (
|
|
37
|
+
CachedStore,
|
|
38
|
+
CacheConfig,
|
|
39
|
+
CacheMode,
|
|
40
|
+
EvictionPolicy,
|
|
41
|
+
LFUCache,
|
|
42
|
+
LRUCache,
|
|
43
|
+
TTLCache,
|
|
44
|
+
)
|
|
45
|
+
from truthound.stores.versioning import (
|
|
46
|
+
VersionedStore,
|
|
47
|
+
VersionDiff,
|
|
48
|
+
VersionInfo,
|
|
49
|
+
VersioningConfig,
|
|
50
|
+
)
|
|
51
|
+
from truthound.stores.tiering import (
|
|
52
|
+
AccessBasedTierPolicy,
|
|
53
|
+
AgeBasedTierPolicy,
|
|
54
|
+
CompositeTierPolicy,
|
|
55
|
+
MigrationDirection,
|
|
56
|
+
SizeBasedTierPolicy,
|
|
57
|
+
StorageTier,
|
|
58
|
+
TieredStore,
|
|
59
|
+
TieringConfig,
|
|
60
|
+
TieringResult,
|
|
61
|
+
TierType,
|
|
62
|
+
)
|
|
63
|
+
from truthound.stores.observability import ObservableStore
|
|
64
|
+
from truthound.stores.observability import (
|
|
65
|
+
AuditConfig,
|
|
66
|
+
AuditEvent,
|
|
67
|
+
AuditEventType,
|
|
68
|
+
AuditLogger,
|
|
69
|
+
InMemoryAuditBackend,
|
|
70
|
+
InMemoryMetricsBackend,
|
|
71
|
+
JsonAuditBackend,
|
|
72
|
+
MetricsConfig,
|
|
73
|
+
MetricsRegistry,
|
|
74
|
+
ObservabilityConfig,
|
|
75
|
+
StoreMetrics,
|
|
76
|
+
TracingConfig,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Local definitions for types not exported by truthound
|
|
81
|
+
class PolicyMode(str, Enum):
|
|
82
|
+
"""Mode for composite retention policies."""
|
|
83
|
+
|
|
84
|
+
ALL = "all" # All policies must match
|
|
85
|
+
ANY = "any" # Any policy can match
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class RetentionAction(str, Enum):
|
|
89
|
+
"""Action to take when retention policy matches."""
|
|
90
|
+
|
|
91
|
+
DELETE = "delete"
|
|
92
|
+
ARCHIVE = "archive"
|
|
93
|
+
MOVE = "move"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class VersioningMode(str, Enum):
|
|
97
|
+
"""Mode for versioning strategy."""
|
|
98
|
+
|
|
99
|
+
SEMANTIC = "semantic"
|
|
100
|
+
INCREMENTAL = "incremental"
|
|
101
|
+
TIMESTAMP = "timestamp"
|
|
102
|
+
GIT_LIKE = "git_like"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AuditStatus(str, Enum):
|
|
106
|
+
"""Status of audit events."""
|
|
107
|
+
|
|
108
|
+
SUCCESS = "success"
|
|
109
|
+
FAILURE = "failure"
|
|
110
|
+
WARNING = "warning"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class DataRedactor:
|
|
114
|
+
"""Simple data redactor for audit logs."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, fields_to_redact: list[str] | None = None):
|
|
117
|
+
self.fields_to_redact = fields_to_redact or []
|
|
118
|
+
|
|
119
|
+
def redact(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
120
|
+
"""Redact sensitive fields from data."""
|
|
121
|
+
result = dict(data)
|
|
122
|
+
for field in self.fields_to_redact:
|
|
123
|
+
if field in result:
|
|
124
|
+
result[field] = "[REDACTED]"
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
from truthound_dashboard.config import get_settings
|
|
128
|
+
|
|
129
|
+
logger = logging.getLogger(__name__)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class CacheType(str, Enum):
|
|
133
|
+
"""Cache eviction policy types."""
|
|
134
|
+
LRU = "lru"
|
|
135
|
+
LFU = "lfu"
|
|
136
|
+
TTL = "ttl"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class DashboardStoreConfig:
|
|
141
|
+
"""Configuration for the dashboard store system.
|
|
142
|
+
|
|
143
|
+
Attributes:
|
|
144
|
+
base_path: Base path for file storage.
|
|
145
|
+
enable_caching: Enable caching layer.
|
|
146
|
+
cache_type: Type of cache (LRU, LFU, TTL).
|
|
147
|
+
cache_max_size: Maximum cache entries.
|
|
148
|
+
cache_ttl_seconds: TTL for cache entries.
|
|
149
|
+
enable_versioning: Enable result versioning.
|
|
150
|
+
max_versions: Maximum versions to keep per item.
|
|
151
|
+
enable_tiering: Enable storage tiering.
|
|
152
|
+
enable_observability: Enable audit/metrics/tracing.
|
|
153
|
+
audit_log_path: Path for audit logs.
|
|
154
|
+
"""
|
|
155
|
+
base_path: Path = field(default_factory=lambda: Path(".truthound/store"))
|
|
156
|
+
|
|
157
|
+
# Caching
|
|
158
|
+
enable_caching: bool = True
|
|
159
|
+
cache_type: CacheType = CacheType.LFU
|
|
160
|
+
cache_max_size: int = 1000
|
|
161
|
+
cache_ttl_seconds: int = 3600
|
|
162
|
+
|
|
163
|
+
# Versioning
|
|
164
|
+
enable_versioning: bool = True
|
|
165
|
+
max_versions: int = 10
|
|
166
|
+
|
|
167
|
+
# Tiering
|
|
168
|
+
enable_tiering: bool = False
|
|
169
|
+
hot_retention_days: int = 7
|
|
170
|
+
warm_retention_days: int = 30
|
|
171
|
+
cold_retention_days: int = 90
|
|
172
|
+
|
|
173
|
+
# Observability
|
|
174
|
+
enable_observability: bool = True
|
|
175
|
+
enable_audit: bool = True
|
|
176
|
+
enable_metrics: bool = True
|
|
177
|
+
enable_tracing: bool = False
|
|
178
|
+
audit_log_path: Path = field(default_factory=lambda: Path(".truthound/audit"))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass
|
|
182
|
+
class RetentionPolicySettings:
|
|
183
|
+
"""Retention policy settings using truthound policies.
|
|
184
|
+
|
|
185
|
+
Attributes:
|
|
186
|
+
validation_retention_days: Days to keep validations.
|
|
187
|
+
profile_keep_per_source: Profiles to keep per source.
|
|
188
|
+
notification_log_retention_days: Days to keep notification logs.
|
|
189
|
+
max_storage_mb: Maximum storage in MB (optional).
|
|
190
|
+
keep_failed_longer: Keep failed validations longer.
|
|
191
|
+
failed_retention_days: Days to keep failed validations.
|
|
192
|
+
protected_tags: Tags to never delete.
|
|
193
|
+
delete_tags: Tags to delete after retention.
|
|
194
|
+
"""
|
|
195
|
+
validation_retention_days: int = 90
|
|
196
|
+
profile_keep_per_source: int = 5
|
|
197
|
+
notification_log_retention_days: int = 30
|
|
198
|
+
max_storage_mb: int | None = None
|
|
199
|
+
keep_failed_longer: bool = True
|
|
200
|
+
failed_retention_days: int = 180
|
|
201
|
+
protected_tags: list[str] = field(default_factory=list)
|
|
202
|
+
delete_tags: list[str] = field(default_factory=list)
|
|
203
|
+
|
|
204
|
+
def to_truthound_config(self) -> RetentionConfig:
|
|
205
|
+
"""Convert to truthound RetentionConfig."""
|
|
206
|
+
policies = []
|
|
207
|
+
|
|
208
|
+
# Time-based policy for validations
|
|
209
|
+
policies.append(TimeBasedPolicy(max_age_days=self.validation_retention_days))
|
|
210
|
+
|
|
211
|
+
# Status-based policy for failed validations
|
|
212
|
+
if self.keep_failed_longer:
|
|
213
|
+
policies.append(
|
|
214
|
+
StatusBasedPolicy(
|
|
215
|
+
status="failure",
|
|
216
|
+
max_age_days=self.failed_retention_days,
|
|
217
|
+
retain=True,
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Size-based policy if configured
|
|
222
|
+
if self.max_storage_mb:
|
|
223
|
+
policies.append(SizeBasedPolicy(max_size_mb=self.max_storage_mb))
|
|
224
|
+
|
|
225
|
+
# Tag-based policies
|
|
226
|
+
if self.protected_tags:
|
|
227
|
+
policies.append(
|
|
228
|
+
TagBasedPolicy(
|
|
229
|
+
required_tags={tag: "*" for tag in self.protected_tags},
|
|
230
|
+
action=RetentionAction.ARCHIVE,
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if self.delete_tags:
|
|
235
|
+
policies.append(
|
|
236
|
+
TagBasedPolicy(
|
|
237
|
+
delete_tags={tag: "*" for tag in self.delete_tags},
|
|
238
|
+
action=RetentionAction.DELETE,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return RetentionConfig(
|
|
243
|
+
policies=policies,
|
|
244
|
+
mode=PolicyMode.ANY,
|
|
245
|
+
default_action=RetentionAction.DELETE,
|
|
246
|
+
schedule=RetentionSchedule(
|
|
247
|
+
enabled=True,
|
|
248
|
+
interval_hours=24,
|
|
249
|
+
batch_size=1000,
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class DashboardStoreManager:
|
|
255
|
+
"""Manager for truthound-based store with all enterprise features.
|
|
256
|
+
|
|
257
|
+
This manager wraps truthound's store system to provide:
|
|
258
|
+
- Caching with LRU/LFU/TTL eviction
|
|
259
|
+
- Versioning with diff and rollback
|
|
260
|
+
- Storage tiering (hot/warm/cold)
|
|
261
|
+
- Observability (audit, metrics, tracing)
|
|
262
|
+
- Retention policies
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
manager = DashboardStoreManager()
|
|
266
|
+
manager.initialize()
|
|
267
|
+
|
|
268
|
+
# Save with versioning
|
|
269
|
+
run_id = manager.save(result, message="Initial validation")
|
|
270
|
+
|
|
271
|
+
# Get version history
|
|
272
|
+
history = manager.get_version_history(run_id)
|
|
273
|
+
|
|
274
|
+
# Run retention cleanup
|
|
275
|
+
cleanup_result = manager.run_retention_cleanup()
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
def __init__(self, config: DashboardStoreConfig | None = None) -> None:
|
|
279
|
+
"""Initialize store manager.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
config: Store configuration. Uses defaults if not provided.
|
|
283
|
+
"""
|
|
284
|
+
self._config = config or DashboardStoreConfig()
|
|
285
|
+
self._store = None
|
|
286
|
+
self._base_store = None
|
|
287
|
+
self._versioned_store = None
|
|
288
|
+
self._cached_store = None
|
|
289
|
+
self._tiered_store = None
|
|
290
|
+
self._observable_store = None
|
|
291
|
+
self._retention_store = None
|
|
292
|
+
self._audit_logger = None
|
|
293
|
+
self._metrics = None
|
|
294
|
+
self._cache = None
|
|
295
|
+
self._initialized = False
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def config(self) -> DashboardStoreConfig:
|
|
299
|
+
"""Get store configuration."""
|
|
300
|
+
return self._config
|
|
301
|
+
|
|
302
|
+
def initialize(self) -> None:
|
|
303
|
+
"""Initialize store with all configured layers."""
|
|
304
|
+
if self._initialized:
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
logger.info("Initializing dashboard store manager...")
|
|
308
|
+
|
|
309
|
+
# Base store (filesystem)
|
|
310
|
+
self._base_store = get_store(
|
|
311
|
+
"filesystem",
|
|
312
|
+
base_path=str(self._config.base_path),
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
current_store = self._base_store
|
|
316
|
+
|
|
317
|
+
# Layer 1: Versioning
|
|
318
|
+
if self._config.enable_versioning:
|
|
319
|
+
versioning_config = VersioningConfig(
|
|
320
|
+
mode=VersioningMode.INCREMENTAL,
|
|
321
|
+
max_versions=self._config.max_versions,
|
|
322
|
+
auto_cleanup=True,
|
|
323
|
+
track_changes=True,
|
|
324
|
+
)
|
|
325
|
+
self._versioned_store = VersionedStore(current_store, versioning_config)
|
|
326
|
+
current_store = self._versioned_store
|
|
327
|
+
logger.info(f"Versioning enabled (max {self._config.max_versions} versions)")
|
|
328
|
+
|
|
329
|
+
# Layer 2: Caching
|
|
330
|
+
if self._config.enable_caching:
|
|
331
|
+
cache_config = CacheConfig(
|
|
332
|
+
max_size=self._config.cache_max_size,
|
|
333
|
+
ttl_seconds=self._config.cache_ttl_seconds,
|
|
334
|
+
enable_statistics=True,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if self._config.cache_type == CacheType.LRU:
|
|
338
|
+
self._cache = LRUCache(
|
|
339
|
+
max_size=self._config.cache_max_size,
|
|
340
|
+
ttl_seconds=self._config.cache_ttl_seconds,
|
|
341
|
+
config=cache_config,
|
|
342
|
+
)
|
|
343
|
+
elif self._config.cache_type == CacheType.LFU:
|
|
344
|
+
self._cache = LFUCache(
|
|
345
|
+
max_size=self._config.cache_max_size,
|
|
346
|
+
ttl_seconds=self._config.cache_ttl_seconds,
|
|
347
|
+
config=cache_config,
|
|
348
|
+
)
|
|
349
|
+
else:
|
|
350
|
+
self._cache = TTLCache(
|
|
351
|
+
ttl_seconds=self._config.cache_ttl_seconds,
|
|
352
|
+
max_size=self._config.cache_max_size,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
self._cached_store = CachedStore(
|
|
356
|
+
current_store,
|
|
357
|
+
self._cache,
|
|
358
|
+
mode=CacheMode.WRITE_THROUGH,
|
|
359
|
+
)
|
|
360
|
+
current_store = self._cached_store
|
|
361
|
+
logger.info(f"Caching enabled ({self._config.cache_type.value})")
|
|
362
|
+
|
|
363
|
+
# Layer 3: Tiering
|
|
364
|
+
if self._config.enable_tiering:
|
|
365
|
+
self._setup_tiering(current_store)
|
|
366
|
+
if self._tiered_store:
|
|
367
|
+
current_store = self._tiered_store
|
|
368
|
+
logger.info("Storage tiering enabled")
|
|
369
|
+
|
|
370
|
+
# Layer 4: Observability
|
|
371
|
+
if self._config.enable_observability:
|
|
372
|
+
self._setup_observability(current_store)
|
|
373
|
+
if self._observable_store:
|
|
374
|
+
current_store = self._observable_store
|
|
375
|
+
logger.info("Observability enabled")
|
|
376
|
+
|
|
377
|
+
self._store = current_store
|
|
378
|
+
self._initialized = True
|
|
379
|
+
logger.info("Dashboard store manager initialized")
|
|
380
|
+
|
|
381
|
+
def _setup_tiering(self, base_store) -> None:
|
|
382
|
+
"""Set up storage tiering."""
|
|
383
|
+
# Create tier stores
|
|
384
|
+
hot_store = get_store(
|
|
385
|
+
"filesystem",
|
|
386
|
+
base_path=str(self._config.base_path / "hot"),
|
|
387
|
+
)
|
|
388
|
+
warm_store = get_store(
|
|
389
|
+
"filesystem",
|
|
390
|
+
base_path=str(self._config.base_path / "warm"),
|
|
391
|
+
)
|
|
392
|
+
cold_store = get_store(
|
|
393
|
+
"filesystem",
|
|
394
|
+
base_path=str(self._config.base_path / "cold"),
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
tiers = [
|
|
398
|
+
StorageTier(
|
|
399
|
+
name="hot",
|
|
400
|
+
store=hot_store,
|
|
401
|
+
tier_type=TierType.HOT,
|
|
402
|
+
priority=1,
|
|
403
|
+
),
|
|
404
|
+
StorageTier(
|
|
405
|
+
name="warm",
|
|
406
|
+
store=warm_store,
|
|
407
|
+
tier_type=TierType.WARM,
|
|
408
|
+
priority=2,
|
|
409
|
+
),
|
|
410
|
+
StorageTier(
|
|
411
|
+
name="cold",
|
|
412
|
+
store=cold_store,
|
|
413
|
+
tier_type=TierType.COLD,
|
|
414
|
+
priority=3,
|
|
415
|
+
),
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
# Age-based tier policies
|
|
419
|
+
policies = [
|
|
420
|
+
AgeBasedTierPolicy(
|
|
421
|
+
from_tier="hot",
|
|
422
|
+
to_tier="warm",
|
|
423
|
+
after_days=self._config.hot_retention_days,
|
|
424
|
+
),
|
|
425
|
+
AgeBasedTierPolicy(
|
|
426
|
+
from_tier="warm",
|
|
427
|
+
to_tier="cold",
|
|
428
|
+
after_days=self._config.warm_retention_days,
|
|
429
|
+
),
|
|
430
|
+
]
|
|
431
|
+
|
|
432
|
+
tiering_config = TieringConfig(
|
|
433
|
+
policies=policies,
|
|
434
|
+
default_tier="hot",
|
|
435
|
+
check_interval_hours=24,
|
|
436
|
+
batch_size=100,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
self._tiered_store = TieredStore(tiers, tiering_config)
|
|
440
|
+
|
|
441
|
+
def _setup_observability(self, base_store) -> None:
|
|
442
|
+
"""Set up observability (audit, metrics, tracing)."""
|
|
443
|
+
# Audit backend
|
|
444
|
+
audit_backend = None
|
|
445
|
+
if self._config.enable_audit:
|
|
446
|
+
self._config.audit_log_path.mkdir(parents=True, exist_ok=True)
|
|
447
|
+
audit_config = AuditConfig(
|
|
448
|
+
enabled=True,
|
|
449
|
+
file_path=str(self._config.audit_log_path / "dashboard_audit.jsonl"),
|
|
450
|
+
redact_sensitive=True,
|
|
451
|
+
sensitive_fields=["password", "api_key", "token"],
|
|
452
|
+
)
|
|
453
|
+
audit_backend = JsonAuditBackend(
|
|
454
|
+
config=audit_config,
|
|
455
|
+
file_path=self._config.audit_log_path / "dashboard_audit.jsonl",
|
|
456
|
+
)
|
|
457
|
+
self._audit_logger = AuditLogger(
|
|
458
|
+
backend=audit_backend,
|
|
459
|
+
store_type="dashboard",
|
|
460
|
+
store_id="validation_store",
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Metrics backend
|
|
464
|
+
metrics_backend = None
|
|
465
|
+
if self._config.enable_metrics:
|
|
466
|
+
metrics_backend = InMemoryMetricsBackend()
|
|
467
|
+
self._metrics = StoreMetrics(
|
|
468
|
+
backend=metrics_backend,
|
|
469
|
+
store_type="dashboard",
|
|
470
|
+
store_id="validation_store",
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
obs_config = ObservabilityConfig(
|
|
474
|
+
audit=AuditConfig(enabled=self._config.enable_audit),
|
|
475
|
+
metrics=MetricsConfig(enabled=self._config.enable_metrics),
|
|
476
|
+
tracing=TracingConfig(enabled=self._config.enable_tracing),
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
self._observable_store = ObservableStore(base_store, obs_config)
|
|
480
|
+
|
|
481
|
+
# ----- Retention Operations -----
|
|
482
|
+
|
|
483
|
+
def create_retention_store(
|
|
484
|
+
self,
|
|
485
|
+
settings: RetentionPolicySettings,
|
|
486
|
+
) -> RetentionStore:
|
|
487
|
+
"""Create a retention store with the given settings.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
settings: Retention policy settings.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Configured RetentionStore.
|
|
494
|
+
"""
|
|
495
|
+
if not self._store:
|
|
496
|
+
self.initialize()
|
|
497
|
+
|
|
498
|
+
config = settings.to_truthound_config()
|
|
499
|
+
return RetentionStore(self._store, config)
|
|
500
|
+
|
|
501
|
+
async def run_retention_cleanup(
|
|
502
|
+
self,
|
|
503
|
+
settings: RetentionPolicySettings,
|
|
504
|
+
dry_run: bool = False,
|
|
505
|
+
) -> RetentionResult:
|
|
506
|
+
"""Run retention cleanup with truthound's retention system.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
settings: Retention policy settings.
|
|
510
|
+
dry_run: If True, don't actually delete items.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
RetentionResult with cleanup details.
|
|
514
|
+
"""
|
|
515
|
+
if not self._store:
|
|
516
|
+
self.initialize()
|
|
517
|
+
|
|
518
|
+
config = settings.to_truthound_config()
|
|
519
|
+
config.dry_run = dry_run
|
|
520
|
+
|
|
521
|
+
retention_store = RetentionStore(self._store, config)
|
|
522
|
+
result = await retention_store.apply_retention()
|
|
523
|
+
|
|
524
|
+
logger.info(
|
|
525
|
+
f"Retention cleanup: scanned={result.items_scanned}, "
|
|
526
|
+
f"deleted={result.items_deleted}, freed={result.bytes_freed} bytes"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
return result
|
|
530
|
+
|
|
531
|
+
# ----- Cache Operations -----
|
|
532
|
+
|
|
533
|
+
def get_cache_stats(self) -> dict[str, Any]:
|
|
534
|
+
"""Get cache statistics from truthound's cache.
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Cache statistics dictionary.
|
|
538
|
+
"""
|
|
539
|
+
if not self._cache:
|
|
540
|
+
return {
|
|
541
|
+
"total_entries": 0,
|
|
542
|
+
"expired_entries": 0,
|
|
543
|
+
"valid_entries": 0,
|
|
544
|
+
"max_size": 0,
|
|
545
|
+
"hits": 0,
|
|
546
|
+
"misses": 0,
|
|
547
|
+
"hit_rate": 0.0,
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
metrics = self._cache.metrics
|
|
551
|
+
return {
|
|
552
|
+
"total_entries": metrics.size,
|
|
553
|
+
"expired_entries": metrics.expirations,
|
|
554
|
+
"valid_entries": metrics.size,
|
|
555
|
+
"max_size": self._config.cache_max_size,
|
|
556
|
+
"hits": metrics.hits,
|
|
557
|
+
"misses": metrics.misses,
|
|
558
|
+
"hit_rate": metrics.hit_rate,
|
|
559
|
+
"evictions": metrics.evictions,
|
|
560
|
+
"average_get_time_ms": metrics.average_get_time_ms,
|
|
561
|
+
"average_set_time_ms": metrics.average_set_time_ms,
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
def clear_cache(self, pattern: str | None = None) -> None:
|
|
565
|
+
"""Clear cache entries.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
pattern: Optional pattern to match keys (prefix match).
|
|
569
|
+
"""
|
|
570
|
+
if not self._cache:
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
if pattern:
|
|
574
|
+
self._cache.delete_many([
|
|
575
|
+
key for key in self._cache._cache.keys()
|
|
576
|
+
if key.startswith(pattern)
|
|
577
|
+
])
|
|
578
|
+
else:
|
|
579
|
+
self._cache.clear()
|
|
580
|
+
|
|
581
|
+
# ----- Versioning Operations -----
|
|
582
|
+
|
|
583
|
+
def get_version_history(
|
|
584
|
+
self,
|
|
585
|
+
item_id: str,
|
|
586
|
+
limit: int | None = None,
|
|
587
|
+
) -> list[VersionInfo]:
|
|
588
|
+
"""Get version history for an item.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
item_id: Item identifier.
|
|
592
|
+
limit: Maximum versions to return.
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
List of VersionInfo objects.
|
|
596
|
+
"""
|
|
597
|
+
if not self._versioned_store:
|
|
598
|
+
return []
|
|
599
|
+
|
|
600
|
+
return self._versioned_store.get_version_history(item_id, limit=limit)
|
|
601
|
+
|
|
602
|
+
def get_version_diff(
|
|
603
|
+
self,
|
|
604
|
+
item_id: str,
|
|
605
|
+
version_a: int,
|
|
606
|
+
version_b: int | None = None,
|
|
607
|
+
) -> VersionDiff:
|
|
608
|
+
"""Get diff between versions.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
item_id: Item identifier.
|
|
612
|
+
version_a: First version.
|
|
613
|
+
version_b: Second version (latest if None).
|
|
614
|
+
|
|
615
|
+
Returns:
|
|
616
|
+
VersionDiff with changes.
|
|
617
|
+
"""
|
|
618
|
+
if not self._versioned_store:
|
|
619
|
+
raise ValueError("Versioning not enabled")
|
|
620
|
+
|
|
621
|
+
return self._versioned_store.diff(item_id, version_a, version_b)
|
|
622
|
+
|
|
623
|
+
def rollback_version(
|
|
624
|
+
self,
|
|
625
|
+
item_id: str,
|
|
626
|
+
version: int,
|
|
627
|
+
message: str | None = None,
|
|
628
|
+
) -> Any:
|
|
629
|
+
"""Rollback to a previous version.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
item_id: Item identifier.
|
|
633
|
+
version: Version to rollback to.
|
|
634
|
+
message: Optional rollback message.
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
Rolled back item.
|
|
638
|
+
"""
|
|
639
|
+
if not self._versioned_store:
|
|
640
|
+
raise ValueError("Versioning not enabled")
|
|
641
|
+
|
|
642
|
+
return self._versioned_store.rollback(item_id, version, message=message)
|
|
643
|
+
|
|
644
|
+
# ----- Tiering Operations -----
|
|
645
|
+
|
|
646
|
+
async def run_tiering(self, dry_run: bool = False) -> TieringResult | None:
|
|
647
|
+
"""Run storage tiering migration.
|
|
648
|
+
|
|
649
|
+
Args:
|
|
650
|
+
dry_run: If True, don't actually migrate items.
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
TieringResult or None if tiering not enabled.
|
|
654
|
+
"""
|
|
655
|
+
if not self._tiered_store:
|
|
656
|
+
return None
|
|
657
|
+
|
|
658
|
+
result = await self._tiered_store.run_migration(dry_run=dry_run)
|
|
659
|
+
|
|
660
|
+
logger.info(
|
|
661
|
+
f"Tiering migration: scanned={result.items_scanned}, "
|
|
662
|
+
f"migrated={result.items_migrated}, bytes={result.bytes_migrated}"
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
return result
|
|
666
|
+
|
|
667
|
+
def get_tier_stats(self) -> dict[str, Any]:
|
|
668
|
+
"""Get storage tier statistics.
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Tier statistics dictionary.
|
|
672
|
+
"""
|
|
673
|
+
if not self._tiered_store:
|
|
674
|
+
return {}
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
tier.name: {
|
|
678
|
+
"type": tier.tier_type.value,
|
|
679
|
+
"priority": tier.priority,
|
|
680
|
+
"item_count": len(list(tier.store.list_ids())),
|
|
681
|
+
}
|
|
682
|
+
for tier in self._tiered_store._tiers
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
# ----- Observability Operations -----
|
|
686
|
+
|
|
687
|
+
def get_audit_events(
|
|
688
|
+
self,
|
|
689
|
+
event_type: AuditEventType | None = None,
|
|
690
|
+
start_time: datetime | None = None,
|
|
691
|
+
end_time: datetime | None = None,
|
|
692
|
+
limit: int = 100,
|
|
693
|
+
) -> list[AuditEvent]:
|
|
694
|
+
"""Query audit events.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
event_type: Filter by event type.
|
|
698
|
+
start_time: Filter events after this time.
|
|
699
|
+
end_time: Filter events before this time.
|
|
700
|
+
limit: Maximum events to return.
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
List of AuditEvent objects.
|
|
704
|
+
"""
|
|
705
|
+
if not self._audit_logger:
|
|
706
|
+
return []
|
|
707
|
+
|
|
708
|
+
return self._audit_logger.backend.query(
|
|
709
|
+
event_type=event_type,
|
|
710
|
+
start_time=start_time,
|
|
711
|
+
end_time=end_time,
|
|
712
|
+
limit=limit,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
def get_store_metrics(self) -> dict[str, Any]:
|
|
716
|
+
"""Get store metrics.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
Store metrics dictionary.
|
|
720
|
+
"""
|
|
721
|
+
if not self._metrics:
|
|
722
|
+
return {}
|
|
723
|
+
|
|
724
|
+
try:
|
|
725
|
+
backend = self._metrics.backend
|
|
726
|
+
if hasattr(backend, 'get_metrics'):
|
|
727
|
+
return backend.get_metrics()
|
|
728
|
+
# InMemoryMetricsBackend doesn't have get_metrics, return empty
|
|
729
|
+
return {}
|
|
730
|
+
except Exception:
|
|
731
|
+
return {}
|
|
732
|
+
|
|
733
|
+
# ----- Standard Store Operations -----
|
|
734
|
+
|
|
735
|
+
def save(self, result: Any, **kwargs) -> str:
|
|
736
|
+
"""Save a validation result.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
result: Validation result to save.
|
|
740
|
+
**kwargs: Additional arguments (message, created_by, etc.)
|
|
741
|
+
|
|
742
|
+
Returns:
|
|
743
|
+
Run ID.
|
|
744
|
+
"""
|
|
745
|
+
if not self._store:
|
|
746
|
+
self.initialize()
|
|
747
|
+
|
|
748
|
+
return self._store.save(result, **kwargs)
|
|
749
|
+
|
|
750
|
+
def get(self, item_id: str, version: int | None = None) -> Any:
|
|
751
|
+
"""Get a validation result.
|
|
752
|
+
|
|
753
|
+
Args:
|
|
754
|
+
item_id: Item identifier.
|
|
755
|
+
version: Optional version (if versioning enabled).
|
|
756
|
+
|
|
757
|
+
Returns:
|
|
758
|
+
Validation result.
|
|
759
|
+
"""
|
|
760
|
+
if not self._store:
|
|
761
|
+
self.initialize()
|
|
762
|
+
|
|
763
|
+
if version and self._versioned_store:
|
|
764
|
+
return self._versioned_store.get(item_id, version=version)
|
|
765
|
+
|
|
766
|
+
return self._store.get(item_id)
|
|
767
|
+
|
|
768
|
+
def delete(self, item_id: str) -> bool:
|
|
769
|
+
"""Delete a validation result.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
item_id: Item identifier.
|
|
773
|
+
|
|
774
|
+
Returns:
|
|
775
|
+
True if deleted.
|
|
776
|
+
"""
|
|
777
|
+
if not self._store:
|
|
778
|
+
self.initialize()
|
|
779
|
+
|
|
780
|
+
return self._store.delete(item_id)
|
|
781
|
+
|
|
782
|
+
def close(self) -> None:
|
|
783
|
+
"""Close all store connections."""
|
|
784
|
+
if self._store:
|
|
785
|
+
self._store.close()
|
|
786
|
+
self._initialized = False
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
# Singleton instance
|
|
790
|
+
_store_manager: DashboardStoreManager | None = None
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def get_store_manager() -> DashboardStoreManager:
|
|
794
|
+
"""Get store manager singleton.
|
|
795
|
+
|
|
796
|
+
Returns:
|
|
797
|
+
DashboardStoreManager instance.
|
|
798
|
+
"""
|
|
799
|
+
global _store_manager
|
|
800
|
+
if _store_manager is None:
|
|
801
|
+
_store_manager = DashboardStoreManager()
|
|
802
|
+
return _store_manager
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def reset_store_manager() -> None:
|
|
806
|
+
"""Reset store manager singleton (for testing)."""
|
|
807
|
+
global _store_manager
|
|
808
|
+
if _store_manager:
|
|
809
|
+
_store_manager.close()
|
|
810
|
+
_store_manager = None
|