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,67 @@
1
+ """Database module.
2
+
3
+ This module provides database connectivity, models, and repository patterns
4
+ for the truthound dashboard.
5
+
6
+ Exports:
7
+ - Database connection: get_session, get_db_session, init_db
8
+ - Base classes: Base, UUIDMixin, TimestampMixin
9
+ - Models: Source, Schema, Rule, Validation, Profile, Schedule, DriftComparison, AppSettings
10
+ - Repository: BaseRepository
11
+ """
12
+
13
+ from .base import Base, SoftDeleteMixin, TimestampMixin, UUIDMixin
14
+ from .database import (
15
+ get_db_session,
16
+ get_engine,
17
+ get_session,
18
+ get_session_factory,
19
+ init_db,
20
+ reset_connection,
21
+ reset_db,
22
+ )
23
+ from .models import (
24
+ AppSettings,
25
+ DriftComparison,
26
+ NotificationChannel,
27
+ NotificationLog,
28
+ NotificationRule,
29
+ Profile,
30
+ Rule,
31
+ Schedule,
32
+ Schema,
33
+ Source,
34
+ Validation,
35
+ )
36
+ from .repository import BaseRepository
37
+
38
+ __all__ = [
39
+ # Base classes
40
+ "Base",
41
+ "UUIDMixin",
42
+ "TimestampMixin",
43
+ "SoftDeleteMixin",
44
+ # Database functions
45
+ "get_session",
46
+ "get_db_session",
47
+ "get_engine",
48
+ "get_session_factory",
49
+ "init_db",
50
+ "reset_db",
51
+ "reset_connection",
52
+ # Models
53
+ "Source",
54
+ "Schema",
55
+ "Rule",
56
+ "Validation",
57
+ "Profile",
58
+ "Schedule",
59
+ "DriftComparison",
60
+ "AppSettings",
61
+ # Notification models (Phase 3)
62
+ "NotificationChannel",
63
+ "NotificationRule",
64
+ "NotificationLog",
65
+ # Repository
66
+ "BaseRepository",
67
+ ]
@@ -0,0 +1,108 @@
1
+ """Database base classes and mixins for extensibility.
2
+
3
+ This module provides reusable base classes and mixins for SQLAlchemy models,
4
+ enabling consistent patterns across all database entities.
5
+
6
+ Example:
7
+ class MyModel(Base, TimestampMixin, UUIDMixin):
8
+ __tablename__ = "my_models"
9
+ name: Mapped[str] = mapped_column(String(255))
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from datetime import datetime
15
+ from typing import Any
16
+ from uuid import uuid4
17
+
18
+ from sqlalchemy import DateTime, String, func
19
+ from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column
20
+
21
+
22
+ class Base(DeclarativeBase):
23
+ """Base class for all SQLAlchemy models.
24
+
25
+ Provides common functionality like automatic table naming
26
+ and serialization methods.
27
+ """
28
+
29
+ @declared_attr.directive
30
+ @classmethod
31
+ def __tablename__(cls) -> str:
32
+ """Generate table name from class name (snake_case)."""
33
+ import re
34
+
35
+ name = cls.__name__
36
+ return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower() + "s"
37
+
38
+ def to_dict(self) -> dict[str, Any]:
39
+ """Convert model instance to dictionary.
40
+
41
+ Returns:
42
+ Dictionary representation of the model.
43
+ """
44
+ return {
45
+ column.name: getattr(self, column.name) for column in self.__table__.columns
46
+ }
47
+
48
+
49
+ class UUIDMixin:
50
+ """Mixin that adds a UUID primary key.
51
+
52
+ Automatically generates a UUID4 string as the primary key.
53
+ """
54
+
55
+ id: Mapped[str] = mapped_column(
56
+ String(36),
57
+ primary_key=True,
58
+ default=lambda: str(uuid4()),
59
+ )
60
+
61
+
62
+ class TimestampMixin:
63
+ """Mixin that adds created_at and updated_at timestamps.
64
+
65
+ Automatically sets created_at on insert and updates
66
+ updated_at on every modification.
67
+ """
68
+
69
+ created_at: Mapped[datetime] = mapped_column(
70
+ DateTime,
71
+ default=datetime.utcnow,
72
+ server_default=func.now(),
73
+ nullable=False,
74
+ )
75
+ updated_at: Mapped[datetime] = mapped_column(
76
+ DateTime,
77
+ default=datetime.utcnow,
78
+ onupdate=datetime.utcnow,
79
+ server_default=func.now(),
80
+ nullable=False,
81
+ )
82
+
83
+
84
+ class SoftDeleteMixin:
85
+ """Mixin that adds soft delete functionality.
86
+
87
+ Instead of actually deleting records, sets deleted_at timestamp.
88
+ Use with query filters to exclude deleted records.
89
+ """
90
+
91
+ deleted_at: Mapped[datetime | None] = mapped_column(
92
+ DateTime,
93
+ nullable=True,
94
+ default=None,
95
+ )
96
+
97
+ @property
98
+ def is_deleted(self) -> bool:
99
+ """Check if record is soft-deleted."""
100
+ return self.deleted_at is not None
101
+
102
+ def soft_delete(self) -> None:
103
+ """Mark record as deleted."""
104
+ self.deleted_at = datetime.utcnow()
105
+
106
+ def restore(self) -> None:
107
+ """Restore soft-deleted record."""
108
+ self.deleted_at = None
@@ -0,0 +1,196 @@
1
+ """Database connection and session management.
2
+
3
+ This module provides async database connection handling using SQLAlchemy 2.0.
4
+ It supports both production SQLite and in-memory databases for testing.
5
+
6
+ Example:
7
+ # Using context manager
8
+ async with get_session() as session:
9
+ result = await session.execute(select(Source))
10
+ sources = result.scalars().all()
11
+
12
+ # Using FastAPI dependency
13
+ @router.get("/sources")
14
+ async def list_sources(session: AsyncSession = Depends(get_db_session)):
15
+ ...
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import AsyncGenerator
21
+ from contextlib import asynccontextmanager
22
+
23
+ from sqlalchemy.ext.asyncio import (
24
+ AsyncEngine,
25
+ AsyncSession,
26
+ async_sessionmaker,
27
+ create_async_engine,
28
+ )
29
+
30
+ from truthound_dashboard.config import get_settings
31
+
32
+ from .base import Base
33
+
34
+ # Global engine and session factory
35
+ _engine: AsyncEngine | None = None
36
+ _session_factory: async_sessionmaker[AsyncSession] | None = None
37
+
38
+
39
+ def get_database_url(in_memory: bool = False) -> str:
40
+ """Get database URL.
41
+
42
+ Args:
43
+ in_memory: If True, use in-memory SQLite for testing.
44
+
45
+ Returns:
46
+ SQLAlchemy async database URL.
47
+ """
48
+ if in_memory:
49
+ return "sqlite+aiosqlite:///:memory:"
50
+
51
+ settings = get_settings()
52
+ settings.ensure_directories()
53
+ return f"sqlite+aiosqlite:///{settings.database_path}"
54
+
55
+
56
+ def get_engine(in_memory: bool = False) -> AsyncEngine:
57
+ """Get or create database engine.
58
+
59
+ Args:
60
+ in_memory: If True, create in-memory database.
61
+
62
+ Returns:
63
+ AsyncEngine instance.
64
+ """
65
+ global _engine
66
+
67
+ if _engine is None or in_memory:
68
+ url = get_database_url(in_memory)
69
+ engine = create_async_engine(
70
+ url,
71
+ echo=False,
72
+ pool_pre_ping=True,
73
+ connect_args={"check_same_thread": False} if "sqlite" in url else {},
74
+ )
75
+ if not in_memory:
76
+ _engine = engine
77
+ return engine
78
+
79
+ return _engine
80
+
81
+
82
+ def get_session_factory(
83
+ engine: AsyncEngine | None = None,
84
+ ) -> async_sessionmaker[AsyncSession]:
85
+ """Get or create session factory.
86
+
87
+ Args:
88
+ engine: Optional engine to use. If None, uses default engine.
89
+
90
+ Returns:
91
+ Session factory for creating database sessions.
92
+ """
93
+ global _session_factory
94
+
95
+ if engine is not None:
96
+ # Create new factory for provided engine (used in testing)
97
+ return async_sessionmaker(
98
+ engine,
99
+ class_=AsyncSession,
100
+ expire_on_commit=False,
101
+ autoflush=False,
102
+ )
103
+
104
+ if _session_factory is None:
105
+ _session_factory = async_sessionmaker(
106
+ get_engine(),
107
+ class_=AsyncSession,
108
+ expire_on_commit=False,
109
+ autoflush=False,
110
+ )
111
+
112
+ return _session_factory
113
+
114
+
115
+ @asynccontextmanager
116
+ async def get_session() -> AsyncGenerator[AsyncSession, None]:
117
+ """Get database session as async context manager.
118
+
119
+ Yields:
120
+ AsyncSession for database operations.
121
+
122
+ Example:
123
+ async with get_session() as session:
124
+ result = await session.execute(select(Source))
125
+ """
126
+ factory = get_session_factory()
127
+ async with factory() as session:
128
+ try:
129
+ yield session
130
+ await session.commit()
131
+ except Exception:
132
+ await session.rollback()
133
+ raise
134
+
135
+
136
+ async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
137
+ """FastAPI dependency for database sessions.
138
+
139
+ Yields:
140
+ AsyncSession for use in route handlers.
141
+
142
+ Example:
143
+ @router.get("/sources")
144
+ async def get_sources(session: AsyncSession = Depends(get_db_session)):
145
+ ...
146
+ """
147
+ async with get_session() as session:
148
+ yield session
149
+
150
+
151
+ async def init_db(engine: AsyncEngine | None = None) -> None:
152
+ """Initialize database tables.
153
+
154
+ Creates all tables defined in models if they don't exist.
155
+
156
+ Args:
157
+ engine: Optional engine to use. If None, uses default engine.
158
+ """
159
+ target_engine = engine or get_engine()
160
+ async with target_engine.begin() as conn:
161
+ await conn.run_sync(Base.metadata.create_all)
162
+
163
+
164
+ async def drop_db(engine: AsyncEngine | None = None) -> None:
165
+ """Drop all database tables.
166
+
167
+ Warning: This will delete all data!
168
+
169
+ Args:
170
+ engine: Optional engine to use. If None, uses default engine.
171
+ """
172
+ target_engine = engine or get_engine()
173
+ async with target_engine.begin() as conn:
174
+ await conn.run_sync(Base.metadata.drop_all)
175
+
176
+
177
+ async def reset_db(engine: AsyncEngine | None = None) -> None:
178
+ """Reset database by dropping and recreating all tables.
179
+
180
+ Warning: This will delete all data!
181
+
182
+ Args:
183
+ engine: Optional engine to use. If None, uses default engine.
184
+ """
185
+ await drop_db(engine)
186
+ await init_db(engine)
187
+
188
+
189
+ def reset_connection() -> None:
190
+ """Reset global engine and session factory.
191
+
192
+ Useful for testing or when configuration changes.
193
+ """
194
+ global _engine, _session_factory
195
+ _engine = None
196
+ _session_factory = None