Coverage for src / local_deep_research / database / models / settings.py: 95%
55 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:55 +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 LLM = "llm"
75 SEARCH = "search"
76 REPORT = "report"
77 DATABASE = "database"
80class Setting(Base):
81 """
82 Global application settings (shared across users).
83 For user-specific settings, use UserSettings.
84 """
86 __tablename__ = "settings"
88 id = Column(Integer, primary_key=True, index=True)
89 key = Column(String(255), nullable=False, unique=True, index=True)
90 value = Column(JSON, nullable=True)
91 type = Column(Enum(SettingType), nullable=False, index=True)
92 name = Column(String(255), nullable=False)
93 description = Column(Text, nullable=True)
94 category = Column(String(100), nullable=True, index=True)
95 ui_element = Column(String(50), default="text", nullable=False)
96 options = Column(JSON, nullable=True) # For select elements
97 min_value = Column(Float, nullable=True) # For numeric inputs
98 max_value = Column(Float, nullable=True) # For numeric inputs
99 step = Column(Float, nullable=True) # For sliders
100 visible = Column(Boolean, default=True, nullable=False)
101 editable = Column(Boolean, default=True, nullable=False)
102 env_var = Column(String(255), nullable=True) # Environment variable name
103 created_at = Column(UtcDateTime, server_default=utcnow(), nullable=False)
104 updated_at = Column(
105 UtcDateTime, server_default=utcnow(), onupdate=utcnow(), nullable=False
106 )
108 # unique=True on the key column already creates a unique constraint;
109 # the separate UniqueConstraint was removed to avoid Alembic autogenerate drift.
111 def __repr__(self):
112 return f"<Setting(key='{self.key}', type={self.type.value})>"