truthound-dashboard 1.0.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.
Files changed (62) hide show
  1. truthound_dashboard/__init__.py +11 -0
  2. truthound_dashboard/__main__.py +6 -0
  3. truthound_dashboard/api/__init__.py +15 -0
  4. truthound_dashboard/api/deps.py +153 -0
  5. truthound_dashboard/api/drift.py +179 -0
  6. truthound_dashboard/api/error_handlers.py +287 -0
  7. truthound_dashboard/api/health.py +78 -0
  8. truthound_dashboard/api/history.py +62 -0
  9. truthound_dashboard/api/middleware.py +626 -0
  10. truthound_dashboard/api/notifications.py +561 -0
  11. truthound_dashboard/api/profile.py +52 -0
  12. truthound_dashboard/api/router.py +83 -0
  13. truthound_dashboard/api/rules.py +277 -0
  14. truthound_dashboard/api/schedules.py +329 -0
  15. truthound_dashboard/api/schemas.py +136 -0
  16. truthound_dashboard/api/sources.py +229 -0
  17. truthound_dashboard/api/validations.py +125 -0
  18. truthound_dashboard/cli.py +226 -0
  19. truthound_dashboard/config.py +132 -0
  20. truthound_dashboard/core/__init__.py +264 -0
  21. truthound_dashboard/core/base.py +185 -0
  22. truthound_dashboard/core/cache.py +479 -0
  23. truthound_dashboard/core/connections.py +331 -0
  24. truthound_dashboard/core/encryption.py +409 -0
  25. truthound_dashboard/core/exceptions.py +627 -0
  26. truthound_dashboard/core/logging.py +488 -0
  27. truthound_dashboard/core/maintenance.py +542 -0
  28. truthound_dashboard/core/notifications/__init__.py +56 -0
  29. truthound_dashboard/core/notifications/base.py +390 -0
  30. truthound_dashboard/core/notifications/channels.py +557 -0
  31. truthound_dashboard/core/notifications/dispatcher.py +453 -0
  32. truthound_dashboard/core/notifications/events.py +155 -0
  33. truthound_dashboard/core/notifications/service.py +744 -0
  34. truthound_dashboard/core/sampling.py +626 -0
  35. truthound_dashboard/core/scheduler.py +311 -0
  36. truthound_dashboard/core/services.py +1531 -0
  37. truthound_dashboard/core/truthound_adapter.py +659 -0
  38. truthound_dashboard/db/__init__.py +67 -0
  39. truthound_dashboard/db/base.py +108 -0
  40. truthound_dashboard/db/database.py +196 -0
  41. truthound_dashboard/db/models.py +732 -0
  42. truthound_dashboard/db/repository.py +237 -0
  43. truthound_dashboard/main.py +309 -0
  44. truthound_dashboard/schemas/__init__.py +150 -0
  45. truthound_dashboard/schemas/base.py +96 -0
  46. truthound_dashboard/schemas/drift.py +118 -0
  47. truthound_dashboard/schemas/history.py +74 -0
  48. truthound_dashboard/schemas/profile.py +91 -0
  49. truthound_dashboard/schemas/rule.py +199 -0
  50. truthound_dashboard/schemas/schedule.py +88 -0
  51. truthound_dashboard/schemas/schema.py +121 -0
  52. truthound_dashboard/schemas/source.py +138 -0
  53. truthound_dashboard/schemas/validation.py +192 -0
  54. truthound_dashboard/static/assets/index-BqJMyAHX.js +110 -0
  55. truthound_dashboard/static/assets/index-DMDxHCTs.js +465 -0
  56. truthound_dashboard/static/assets/index-Dm2D11TK.css +1 -0
  57. truthound_dashboard/static/index.html +15 -0
  58. truthound_dashboard/static/mockServiceWorker.js +349 -0
  59. truthound_dashboard-1.0.0.dist-info/METADATA +218 -0
  60. truthound_dashboard-1.0.0.dist-info/RECORD +62 -0
  61. truthound_dashboard-1.0.0.dist-info/WHEEL +4 -0
  62. truthound_dashboard-1.0.0.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,132 @@
1
+ """Configuration settings with validation and extensibility.
2
+
3
+ This module provides a centralized configuration management system using
4
+ pydantic-settings for type-safe environment variable handling.
5
+
6
+ Example:
7
+ # Get settings singleton
8
+ settings = get_settings()
9
+
10
+ # Access configuration
11
+ print(settings.database_path)
12
+
13
+ # Override via environment variables
14
+ # TRUTHOUND_DATA_DIR=/custom/path
15
+ # TRUTHOUND_PORT=9000
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from functools import lru_cache
21
+ from pathlib import Path
22
+ from typing import Literal
23
+
24
+ from pydantic import Field, field_validator
25
+ from pydantic_settings import BaseSettings, SettingsConfigDict
26
+
27
+
28
+ class Settings(BaseSettings):
29
+ """Dashboard configuration settings.
30
+
31
+ All settings can be overridden via environment variables with
32
+ the TRUTHOUND_ prefix.
33
+
34
+ Attributes:
35
+ data_dir: Directory for storing database and cache files.
36
+ host: Server host address.
37
+ port: Server port number.
38
+ log_level: Logging verbosity level.
39
+ auth_enabled: Whether authentication is required.
40
+ auth_password: Password for basic auth (if enabled).
41
+ sample_size: Default sample size for validation.
42
+ max_failed_rows: Maximum failed rows to store.
43
+ default_timeout: Default timeout for operations in seconds.
44
+ """
45
+
46
+ model_config = SettingsConfigDict(
47
+ env_prefix="TRUTHOUND_",
48
+ env_file=".env",
49
+ env_file_encoding="utf-8",
50
+ extra="ignore",
51
+ )
52
+
53
+ # Data storage
54
+ data_dir: Path = Field(
55
+ default_factory=lambda: Path.home() / ".truthound",
56
+ description="Directory for database and cache files",
57
+ )
58
+
59
+ # Server configuration
60
+ host: str = Field(default="127.0.0.1", description="Server host address")
61
+ port: int = Field(default=8765, ge=1, le=65535, description="Server port")
62
+ log_level: Literal["debug", "info", "warning", "error"] = Field(
63
+ default="info", description="Logging level"
64
+ )
65
+
66
+ # Authentication (optional)
67
+ auth_enabled: bool = Field(default=False, description="Enable authentication")
68
+ auth_password: str | None = Field(
69
+ default=None, description="Password for basic auth"
70
+ )
71
+
72
+ # Validation defaults
73
+ sample_size: int = Field(
74
+ default=10000, ge=100, description="Default sample size for validation"
75
+ )
76
+ max_failed_rows: int = Field(
77
+ default=1000, ge=10, description="Maximum failed rows to store"
78
+ )
79
+ default_timeout: int = Field(
80
+ default=300, ge=10, description="Default operation timeout in seconds"
81
+ )
82
+
83
+ # Worker configuration
84
+ max_workers: int = Field(
85
+ default=4, ge=1, le=32, description="Maximum worker threads"
86
+ )
87
+
88
+ @field_validator("data_dir", mode="before")
89
+ @classmethod
90
+ def expand_data_dir(cls, v: str | Path) -> Path:
91
+ """Expand user home directory and resolve path."""
92
+ return Path(v).expanduser().resolve()
93
+
94
+ @property
95
+ def database_path(self) -> Path:
96
+ """Get SQLite database file path."""
97
+ return self.data_dir / "dashboard.db"
98
+
99
+ @property
100
+ def cache_dir(self) -> Path:
101
+ """Get cache directory path."""
102
+ return self.data_dir / "cache"
103
+
104
+ @property
105
+ def schema_dir(self) -> Path:
106
+ """Get schema storage directory path."""
107
+ return self.data_dir / "schemas"
108
+
109
+ def ensure_directories(self) -> None:
110
+ """Create all required directories if they don't exist."""
111
+ self.data_dir.mkdir(parents=True, exist_ok=True)
112
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
113
+ self.schema_dir.mkdir(parents=True, exist_ok=True)
114
+
115
+
116
+ @lru_cache
117
+ def get_settings() -> Settings:
118
+ """Get cached settings singleton.
119
+
120
+ Returns:
121
+ Settings: The application settings instance.
122
+
123
+ Note:
124
+ This function is cached, so the settings are only loaded once.
125
+ To reload settings, clear the cache with get_settings.cache_clear().
126
+ """
127
+ return Settings()
128
+
129
+
130
+ def reset_settings() -> None:
131
+ """Reset settings cache for testing purposes."""
132
+ get_settings.cache_clear()
@@ -0,0 +1,264 @@
1
+ """Core business logic module.
2
+
3
+ This module contains the core business logic for the dashboard,
4
+ including services, adapters, and domain models.
5
+
6
+ Exports:
7
+ - Adapter: TruthoundAdapter, get_adapter
8
+ - Services: SourceService, ValidationService, SchemaService, RuleService, ProfileService,
9
+ HistoryService, DriftService, ScheduleService
10
+ - Result types: CheckResult, LearnResult, ProfileResult, CompareResult
11
+ - Scheduler: ValidationScheduler, get_scheduler, start_scheduler, stop_scheduler
12
+ - Notifications: NotificationDispatcher, create_dispatcher, get_dispatcher
13
+ - Cache: CacheBackend, MemoryCache, FileCache, get_cache, get_cache_manager
14
+ - Maintenance: MaintenanceManager, get_maintenance_manager, cleanup_old_validations
15
+ - Sampling: DataSampler, SamplingStrategy, get_sampler (Large Dataset Handling)
16
+ - Exceptions: TruthoundDashboardError, SourceNotFoundError, ValidationError, etc.
17
+ - Encryption: encrypt_value, decrypt_value, encrypt_config, decrypt_config
18
+ - Logging: setup_logging, get_logger, get_audit_logger
19
+ """
20
+
21
+ from .base import BaseService, CRUDService
22
+ from .cache import (
23
+ CacheBackend,
24
+ CacheManager,
25
+ FileCache,
26
+ MemoryCache,
27
+ get_cache,
28
+ get_cache_manager,
29
+ reset_cache,
30
+ )
31
+ from .encryption import (
32
+ EncryptionError,
33
+ Encryptor,
34
+ decrypt_config,
35
+ decrypt_value,
36
+ encrypt_config,
37
+ encrypt_value,
38
+ get_encryptor,
39
+ is_sensitive_field,
40
+ mask_sensitive_value,
41
+ )
42
+ from .exceptions import (
43
+ AuthenticationFailedError,
44
+ AuthenticationRequiredError,
45
+ AuthorizationError,
46
+ DatabaseConnectionError,
47
+ DatabaseError,
48
+ DatabaseIntegrityError,
49
+ ErrorCode,
50
+ NotificationChannelNotFoundError,
51
+ NotificationError,
52
+ NotificationInvalidConfigError,
53
+ NotificationRuleNotFoundError,
54
+ NotificationSendError,
55
+ RateLimitExceededError,
56
+ RuleError,
57
+ RuleInvalidError,
58
+ RuleNotFoundError,
59
+ RuleParseError,
60
+ ScheduleConflictError,
61
+ ScheduleError,
62
+ ScheduleInvalidCronError,
63
+ ScheduleNotFoundError,
64
+ SchemaError,
65
+ SchemaInvalidError,
66
+ SchemaNotFoundError,
67
+ SchemaParseError,
68
+ SecurityError,
69
+ SourceAccessDeniedError,
70
+ SourceConnectionError,
71
+ SourceError,
72
+ SourceInvalidConfigError,
73
+ SourceNotFoundError,
74
+ TruthoundDashboardError,
75
+ ValidationError,
76
+ ValidationFailedError,
77
+ ValidationNotFoundError,
78
+ ValidationTimeoutError,
79
+ get_error_message,
80
+ )
81
+ from .logging import (
82
+ AuditLogger,
83
+ LogConfig,
84
+ LoggerAdapter,
85
+ get_audit_logger,
86
+ get_logger,
87
+ setup_logging,
88
+ )
89
+ from .maintenance import (
90
+ CleanupResult,
91
+ CleanupStrategy,
92
+ MaintenanceConfig,
93
+ MaintenanceManager,
94
+ MaintenanceReport,
95
+ cleanup_notification_logs,
96
+ cleanup_old_profiles,
97
+ cleanup_old_validations,
98
+ get_maintenance_manager,
99
+ reset_maintenance_manager,
100
+ vacuum_database,
101
+ )
102
+ from .notifications import (
103
+ NotificationDispatcher,
104
+ create_dispatcher,
105
+ get_dispatcher,
106
+ )
107
+ from .sampling import (
108
+ DataSampler,
109
+ HeadSamplingStrategy,
110
+ RandomSamplingStrategy,
111
+ SamplingConfig,
112
+ SamplingMethod,
113
+ SamplingResult,
114
+ SamplingStrategy,
115
+ StratifiedSamplingStrategy,
116
+ TailSamplingStrategy,
117
+ get_sampler,
118
+ reset_sampler,
119
+ )
120
+ from .scheduler import (
121
+ ValidationScheduler,
122
+ get_scheduler,
123
+ start_scheduler,
124
+ stop_scheduler,
125
+ )
126
+ from .services import (
127
+ DriftService,
128
+ HistoryService,
129
+ ProfileService,
130
+ RuleService,
131
+ ScheduleService,
132
+ SchemaService,
133
+ SourceService,
134
+ ValidationService,
135
+ )
136
+ from .truthound_adapter import (
137
+ CheckResult,
138
+ CompareResult,
139
+ LearnResult,
140
+ ProfileResult,
141
+ TruthoundAdapter,
142
+ get_adapter,
143
+ reset_adapter,
144
+ )
145
+
146
+ __all__ = [
147
+ # Base classes
148
+ "BaseService",
149
+ "CRUDService",
150
+ # Services
151
+ "SourceService",
152
+ "ValidationService",
153
+ "SchemaService",
154
+ "RuleService",
155
+ "ProfileService",
156
+ "HistoryService",
157
+ "DriftService",
158
+ "ScheduleService",
159
+ # Adapter
160
+ "TruthoundAdapter",
161
+ "get_adapter",
162
+ "reset_adapter",
163
+ # Result types
164
+ "CheckResult",
165
+ "LearnResult",
166
+ "ProfileResult",
167
+ "CompareResult",
168
+ # Scheduler
169
+ "ValidationScheduler",
170
+ "get_scheduler",
171
+ "start_scheduler",
172
+ "stop_scheduler",
173
+ # Notifications
174
+ "NotificationDispatcher",
175
+ "create_dispatcher",
176
+ "get_dispatcher",
177
+ # Cache (Phase 4)
178
+ "CacheBackend",
179
+ "MemoryCache",
180
+ "FileCache",
181
+ "CacheManager",
182
+ "get_cache",
183
+ "get_cache_manager",
184
+ "reset_cache",
185
+ # Maintenance (Phase 4)
186
+ "MaintenanceManager",
187
+ "MaintenanceConfig",
188
+ "MaintenanceReport",
189
+ "CleanupResult",
190
+ "CleanupStrategy",
191
+ "get_maintenance_manager",
192
+ "reset_maintenance_manager",
193
+ "cleanup_old_validations",
194
+ "cleanup_old_profiles",
195
+ "cleanup_notification_logs",
196
+ "vacuum_database",
197
+ # Exceptions (Phase 4)
198
+ "TruthoundDashboardError",
199
+ "ErrorCode",
200
+ "get_error_message",
201
+ "SourceError",
202
+ "SourceNotFoundError",
203
+ "SourceConnectionError",
204
+ "SourceInvalidConfigError",
205
+ "SourceAccessDeniedError",
206
+ "SchemaError",
207
+ "SchemaNotFoundError",
208
+ "SchemaInvalidError",
209
+ "SchemaParseError",
210
+ "RuleError",
211
+ "RuleNotFoundError",
212
+ "RuleInvalidError",
213
+ "RuleParseError",
214
+ "ValidationError",
215
+ "ValidationNotFoundError",
216
+ "ValidationFailedError",
217
+ "ValidationTimeoutError",
218
+ "ScheduleError",
219
+ "ScheduleNotFoundError",
220
+ "ScheduleInvalidCronError",
221
+ "ScheduleConflictError",
222
+ "NotificationError",
223
+ "NotificationChannelNotFoundError",
224
+ "NotificationRuleNotFoundError",
225
+ "NotificationSendError",
226
+ "NotificationInvalidConfigError",
227
+ "SecurityError",
228
+ "AuthenticationRequiredError",
229
+ "AuthenticationFailedError",
230
+ "AuthorizationError",
231
+ "RateLimitExceededError",
232
+ "DatabaseError",
233
+ "DatabaseConnectionError",
234
+ "DatabaseIntegrityError",
235
+ # Encryption (Phase 4)
236
+ "Encryptor",
237
+ "EncryptionError",
238
+ "get_encryptor",
239
+ "encrypt_value",
240
+ "decrypt_value",
241
+ "encrypt_config",
242
+ "decrypt_config",
243
+ "is_sensitive_field",
244
+ "mask_sensitive_value",
245
+ # Logging (Phase 4)
246
+ "LogConfig",
247
+ "LoggerAdapter",
248
+ "AuditLogger",
249
+ "setup_logging",
250
+ "get_logger",
251
+ "get_audit_logger",
252
+ # Sampling (Phase 4 - Large Dataset Handling)
253
+ "DataSampler",
254
+ "SamplingConfig",
255
+ "SamplingMethod",
256
+ "SamplingResult",
257
+ "SamplingStrategy",
258
+ "RandomSamplingStrategy",
259
+ "HeadSamplingStrategy",
260
+ "TailSamplingStrategy",
261
+ "StratifiedSamplingStrategy",
262
+ "get_sampler",
263
+ "reset_sampler",
264
+ ]
@@ -0,0 +1,185 @@
1
+ """Base classes for core services.
2
+
3
+ This module provides abstract base classes and protocols for
4
+ implementing core business logic services with consistent patterns.
5
+
6
+ The service pattern separates business logic from API handlers,
7
+ enabling better testability and reusability.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from abc import ABC, abstractmethod
13
+ from typing import Any, Generic, TypeVar
14
+
15
+ from sqlalchemy.ext.asyncio import AsyncSession
16
+
17
+ from truthound_dashboard.db.repository import BaseRepository
18
+
19
+ # Type variables
20
+ ModelT = TypeVar("ModelT")
21
+ CreateSchemaT = TypeVar("CreateSchemaT")
22
+ UpdateSchemaT = TypeVar("UpdateSchemaT")
23
+ ResponseSchemaT = TypeVar("ResponseSchemaT")
24
+
25
+
26
+ class BaseService(ABC, Generic[ModelT]):
27
+ """Abstract base class for services.
28
+
29
+ Services encapsulate business logic and orchestrate
30
+ operations between repositories and external systems.
31
+
32
+ Type Parameters:
33
+ ModelT: The model type this service manages.
34
+ """
35
+
36
+ def __init__(self, session: AsyncSession) -> None:
37
+ """Initialize service with database session.
38
+
39
+ Args:
40
+ session: Async database session.
41
+ """
42
+ self.session = session
43
+
44
+ @abstractmethod
45
+ async def get_by_id(self, id: str) -> ModelT | None:
46
+ """Get entity by ID."""
47
+ ...
48
+
49
+ @abstractmethod
50
+ async def list(self, *, offset: int = 0, limit: int = 100) -> list[ModelT]:
51
+ """List entities with pagination."""
52
+ ...
53
+
54
+
55
+ class CRUDService(BaseService[ModelT], Generic[ModelT, CreateSchemaT, UpdateSchemaT]):
56
+ """Base service with full CRUD operations.
57
+
58
+ Extends BaseService with create, update, and delete operations.
59
+
60
+ Type Parameters:
61
+ ModelT: The model type.
62
+ CreateSchemaT: Pydantic schema for creation.
63
+ UpdateSchemaT: Pydantic schema for updates.
64
+ """
65
+
66
+ repository_class: type[BaseRepository[ModelT]]
67
+
68
+ def __init__(self, session: AsyncSession) -> None:
69
+ super().__init__(session)
70
+ self.repository = self.repository_class(session)
71
+
72
+ async def get_by_id(self, id: str) -> ModelT | None:
73
+ """Get entity by ID.
74
+
75
+ Args:
76
+ id: Entity's unique identifier.
77
+
78
+ Returns:
79
+ Model instance or None if not found.
80
+ """
81
+ return await self.repository.get_by_id(id)
82
+
83
+ async def list(
84
+ self,
85
+ *,
86
+ offset: int = 0,
87
+ limit: int = 100,
88
+ **filters: Any,
89
+ ) -> list[ModelT]:
90
+ """List entities with pagination and filtering.
91
+
92
+ Args:
93
+ offset: Number of records to skip.
94
+ limit: Maximum records to return.
95
+ **filters: Additional filter criteria.
96
+
97
+ Returns:
98
+ List of model instances.
99
+ """
100
+ filter_conditions = self._build_filters(**filters)
101
+ result = await self.repository.list(
102
+ offset=offset,
103
+ limit=limit,
104
+ filters=filter_conditions,
105
+ )
106
+ return list(result)
107
+
108
+ async def create(self, data: CreateSchemaT) -> ModelT:
109
+ """Create new entity.
110
+
111
+ Args:
112
+ data: Pydantic schema with creation data.
113
+
114
+ Returns:
115
+ Created model instance.
116
+ """
117
+ create_data = self._prepare_create_data(data)
118
+ return await self.repository.create(**create_data)
119
+
120
+ async def update(self, id: str, data: UpdateSchemaT) -> ModelT | None:
121
+ """Update existing entity.
122
+
123
+ Args:
124
+ id: Entity's unique identifier.
125
+ data: Pydantic schema with update data.
126
+
127
+ Returns:
128
+ Updated model instance or None if not found.
129
+ """
130
+ update_data = self._prepare_update_data(data)
131
+ return await self.repository.update(id, **update_data)
132
+
133
+ async def delete(self, id: str) -> bool:
134
+ """Delete entity by ID.
135
+
136
+ Args:
137
+ id: Entity's unique identifier.
138
+
139
+ Returns:
140
+ True if deleted, False if not found.
141
+ """
142
+ return await self.repository.delete(id)
143
+
144
+ def _prepare_create_data(self, data: CreateSchemaT) -> dict[str, Any]:
145
+ """Prepare data for creation.
146
+
147
+ Override to customize creation data processing.
148
+
149
+ Args:
150
+ data: Pydantic schema with creation data.
151
+
152
+ Returns:
153
+ Dictionary of field values.
154
+ """
155
+ if hasattr(data, "model_dump"):
156
+ return data.model_dump(exclude_unset=True) # type: ignore
157
+ return dict(data) # type: ignore
158
+
159
+ def _prepare_update_data(self, data: UpdateSchemaT) -> dict[str, Any]:
160
+ """Prepare data for update.
161
+
162
+ Override to customize update data processing.
163
+
164
+ Args:
165
+ data: Pydantic schema with update data.
166
+
167
+ Returns:
168
+ Dictionary of field values (excluding None).
169
+ """
170
+ if hasattr(data, "model_dump"):
171
+ return data.model_dump(exclude_unset=True, exclude_none=True) # type: ignore
172
+ return {k: v for k, v in dict(data).items() if v is not None} # type: ignore
173
+
174
+ def _build_filters(self, **_filters: Any) -> list[Any]:
175
+ """Build SQLAlchemy filter conditions.
176
+
177
+ Override to customize filter building.
178
+
179
+ Args:
180
+ **filters: Filter criteria.
181
+
182
+ Returns:
183
+ List of SQLAlchemy filter conditions.
184
+ """
185
+ return []