Coverage for src/local_deep_research/database/models/settings.py: 95%
56 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-03 23:15 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-03 23:15 +0000
1"""
2Settings and configuration models.
3Stores user preferences and API keys in encrypted database.
4"""
6import enum
8from sqlalchemy import (
9 JSON,
10 Boolean,
11 Column,
12 Enum,
13 Float,
14 Integer,
15 String,
16 Text,
17)
18from sqlalchemy_utc import UtcDateTime, utcnow
20from .base import Base
23class UserSettings(Base):
24 """
25 User-specific settings stored in their encrypted database.
26 Replaces the need for config files or unencrypted storage.
27 """
29 __tablename__ = "user_settings"
31 id = Column(Integer, primary_key=True)
32 key = Column(String(255), unique=True, nullable=False, index=True)
33 value = Column(JSON)
34 category = Column(String(100))
35 description = Column(Text)
36 created_at = Column(UtcDateTime, default=utcnow())
37 updated_at = Column(UtcDateTime, default=utcnow(), onupdate=utcnow())
39 def __repr__(self):
40 return f"<UserSettings(key='{self.key}', category='{self.category}')>"
43class APIKey(Base):
44 """
45 Encrypted storage for API keys.
46 These are especially sensitive and benefit from database encryption.
47 """
49 __tablename__ = "api_keys"
51 id = Column(Integer, primary_key=True)
52 provider = Column(String(100), unique=True, nullable=False, index=True)
53 key = Column(Text, nullable=False) # Encrypted by SQLCipher
54 description = Column(Text)
55 is_active = Column(Boolean, default=True)
56 created_at = Column(UtcDateTime, default=utcnow())
57 updated_at = Column(UtcDateTime, default=utcnow(), onupdate=utcnow())
58 last_used = Column(UtcDateTime)
59 usage_count = Column(Integer, default=0)
61 def __repr__(self):
62 # Never show the actual key in repr
63 return f"<APIKey(provider='{self.provider}', active={self.is_active})>"
66class SettingType(str, enum.Enum):
67 """Types of settings.
69 Inherits from ``str`` so that instances are JSON-serializable and work
70 seamlessly with both SQLAlchemy columns and Pydantic models.
71 """
73 APP = "app"
74 CHAT = "chat"
75 LLM = "llm"
76 SEARCH = "search"
77 REPORT = "report"
78 DATABASE = "database"
81class Setting(Base):
82 """
83 Global application settings (shared across users).
84 For user-specific settings, use UserSettings.
85 """
87 __tablename__ = "settings"
89 id = Column(Integer, primary_key=True)
90 key = Column(String(255), nullable=False, unique=True, index=True)
91 value = Column(JSON, nullable=True)
92 type = Column(Enum(SettingType), nullable=False, index=True)
93 name = Column(String(255), nullable=False)
94 description = Column(Text, nullable=True)
95 category = Column(String(100), nullable=True, index=True)
96 ui_element = Column(String(50), default="text", nullable=False)
97 options = Column(JSON, nullable=True) # For select elements
98 min_value = Column(Float, nullable=True) # For numeric inputs
99 max_value = Column(Float, nullable=True) # For numeric inputs
100 step = Column(Float, nullable=True) # For sliders
101 visible = Column(Boolean, default=True, nullable=False)
102 editable = Column(Boolean, default=True, nullable=False)
103 env_var = Column(String(255), nullable=True) # Environment variable name
104 created_at = Column(UtcDateTime, server_default=utcnow(), nullable=False)
105 updated_at = Column(
106 UtcDateTime, server_default=utcnow(), onupdate=utcnow(), nullable=False
107 )
109 # unique=True on the key column already creates a unique constraint;
110 # the separate UniqueConstraint was removed to avoid Alembic autogenerate drift.
112 def __repr__(self):
113 return f"<Setting(key='{self.key}', type={self.type.value})>"