- ConnectionService: CRUD for DatabaseConnection in GlobalSettings with EncryptionManager-backed password encryption/decryption, test connectivity - DbExecutor: asyncpg (PostgreSQL) and clickhouse-connect (ClickHouse) drivers with per-connection connection pooling - config_models.py: promote GlobalSettings.connections from list[dict] stub to list[DatabaseConnection] with full Pydantic model validation - settings.py: +5 CRUD endpoints for connections (create, read, update, delete, test) - requirements.txt: add asyncpg>=0.29.0, clickhouse-connect>=0.7.0 - seed_permissions.py: register settings.connections.manage permission
314 lines
11 KiB
Python
Executable File
314 lines
11 KiB
Python
Executable File
# #region ConfigModels [C:3] [TYPE Module] [SEMANTICS pydantic, model, schedule]
|
|
# @defgroup Core Module group.
|
|
# @BRIEF Defines the data models for application configuration using Pydantic.
|
|
# @LAYER Core
|
|
# @RELATION IMPLEMENTS -> [CoreContracts]
|
|
# @RELATION IMPLEMENTS -> [ConnectionContracts]
|
|
#
|
|
# #region CoreContracts [C:2] [TYPE Interface]
|
|
# @ingroup Core
|
|
# @BRIEF Contract for core configuration models — Pydantic BaseModel subclasses with scheduler config.
|
|
# #endregion CoreContracts
|
|
#
|
|
# #region ConnectionContracts [C:2] [TYPE Interface]
|
|
# @ingroup Core
|
|
# @BRIEF Contract for database/environment connection configuration models.
|
|
# #endregion ConnectionContracts
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
from ..models.storage import StorageConfig
|
|
from ..services.llm_prompt_templates import (
|
|
DEFAULT_LLM_ASSISTANT_SETTINGS,
|
|
DEFAULT_LLM_PROMPTS,
|
|
DEFAULT_LLM_PROVIDER_BINDINGS,
|
|
)
|
|
|
|
|
|
# #region Schedule [TYPE DataClass]
|
|
# @ingroup Core
|
|
# @BRIEF Represents a backup schedule configuration.
|
|
class Schedule(BaseModel):
|
|
enabled: bool = False
|
|
cron_expression: str = "0 0 * * *" # Default: daily at midnight
|
|
|
|
|
|
# #endregion Schedule
|
|
|
|
|
|
# #region Environment [TYPE DataClass]
|
|
# @ingroup Core
|
|
# @BRIEF Represents a Superset environment configuration with async connection pool settings.
|
|
class Environment(BaseModel):
|
|
id: str
|
|
name: str
|
|
url: str
|
|
username: str
|
|
password: str # Will be masked in UI
|
|
stage: str = Field(default="DEV", pattern="^(DEV|PREPROD|PROD)$")
|
|
verify_ssl: bool = True
|
|
timeout: int = 30
|
|
is_default: bool = False
|
|
is_production: bool = False
|
|
backup_schedule: Schedule = Field(default_factory=Schedule)
|
|
|
|
# Async connection pool settings (per-environment)
|
|
connection_pool_size: int = Field(
|
|
default=20, ge=1, le=100,
|
|
description="Max concurrent connections to this Superset environment"
|
|
)
|
|
connection_pool_timeout: int = Field(
|
|
default=30, ge=5, le=300,
|
|
description="Seconds to wait for a connection pool slot before timeout"
|
|
)
|
|
request_timeout: int = Field(
|
|
default=30, ge=5, le=300,
|
|
description="Default HTTP request timeout for Superset API calls"
|
|
)
|
|
llm_timeout: int = Field(
|
|
default=120, ge=30, le=600,
|
|
description="Timeout for LLM API calls (seconds)"
|
|
)
|
|
|
|
|
|
# #endregion Environment
|
|
|
|
|
|
# #region AppAsyncRuntimeConfig [TYPE DataClass]
|
|
# @ingroup Core
|
|
# @BRIEF Global application-level async runtime configuration.
|
|
# @RATIONALE Separated from EnvironmentConfig because these settings are not per-env:
|
|
# executor workers, shutdown timeout, blocking queue timeout are app-wide.
|
|
class AppAsyncRuntimeConfig(BaseModel):
|
|
db_executor_workers: int = Field(
|
|
default=10, ge=1, le=50,
|
|
description="Max worker threads for database blocking operations"
|
|
)
|
|
file_executor_workers: int = Field(
|
|
default=10, ge=1, le=50,
|
|
description="Max worker threads for file I/O blocking operations"
|
|
)
|
|
git_executor_workers: int = Field(
|
|
default=5, ge=1, le=20,
|
|
description="Max worker threads for Git blocking operations"
|
|
)
|
|
graceful_shutdown_timeout: int = Field(
|
|
default=30, ge=10, le=120,
|
|
description="Graceful shutdown timeout in seconds"
|
|
)
|
|
blocking_queue_timeout: int = Field(
|
|
default=30, ge=5, le=120,
|
|
description="Timeout for acquiring blocking executor queue slot"
|
|
)
|
|
event_bus_maxsize: int = Field(
|
|
default=10000, ge=100, le=100000,
|
|
description="Max pending events in EventBus per subscriber queue"
|
|
)
|
|
|
|
|
|
# #endregion AppAsyncRuntimeConfig
|
|
|
|
|
|
# #region LoggingConfig [TYPE DataClass]
|
|
# @ingroup Core
|
|
# @BRIEF Defines the configuration for the application's logging system.
|
|
class LoggingConfig(BaseModel):
|
|
level: str = "INFO"
|
|
task_log_level: str = (
|
|
"INFO" # Minimum level for task-specific logs (DEBUG, INFO, WARNING, ERROR)
|
|
)
|
|
file_path: str | None = None
|
|
max_bytes: int = 10 * 1024 * 1024
|
|
backup_count: int = 5
|
|
enable_belief_state: bool = True
|
|
|
|
|
|
# #endregion LoggingConfig
|
|
|
|
|
|
# #region CleanReleaseConfig [TYPE DataClass]
|
|
# @ingroup Core
|
|
# @BRIEF Configuration for clean release compliance subsystem.
|
|
class CleanReleaseConfig(BaseModel):
|
|
active_policy_id: str | None = None
|
|
active_registry_id: str | None = None
|
|
|
|
|
|
# #endregion CleanReleaseConfig
|
|
|
|
|
|
# #region FeaturesConfig [C:1] [TYPE DataClass]
|
|
# @BRIEF Top-level feature flags that toggle entire project features on/off.
|
|
# @RATIONALE Features are read from environment variables on bootstrap and persisted in DB.
|
|
# DB is source of truth after initial bootstrap; env vars only seed defaults.
|
|
# @INVARIANT Each feature flag controls visibility of plugin/service in UI and API access.
|
|
class FeaturesConfig(BaseModel):
|
|
# ── Core Features ────────────────────────────────────────
|
|
dataset_review: bool = True
|
|
health_monitor: bool = True
|
|
|
|
# ── Plugin Features ──────────────────────────────────────
|
|
# Translation subsystem
|
|
translate: bool = True # LLM Table Translation (main)
|
|
translate_scheduling: bool = True # Cron-based translation scheduling
|
|
translate_dictionaries: bool = True # Terminology dictionary management
|
|
|
|
# Dashboard migration & version control
|
|
migration: bool = True # Superset Dashboard Migration
|
|
git_integration: bool = True # Git Integration
|
|
|
|
# System tools
|
|
backup: bool = True # Superset Dashboard Backup
|
|
debug: bool = True # System Diagnostics
|
|
storage_manager: bool = True # Storage Manager
|
|
dataset_mapper: bool = True # Dataset Column Mapper
|
|
search_datasets: bool = True # Search Datasets
|
|
maintenance_banner: bool = True # Maintenance Banner
|
|
|
|
# LLM analysis tools
|
|
llm_dashboard_validation: bool = True # Dashboard LLM Validation
|
|
llm_documentation: bool = True # Dataset LLM Documentation
|
|
|
|
|
|
# #endregion FeaturesConfig
|
|
|
|
|
|
# #region DatabaseConnection [C:1] [TYPE DataClass] [SEMANTICS settings,connections,database]
|
|
# @ingroup Core
|
|
# @BRIEF A reusable database connection configuration stored in GlobalSettings.connections.
|
|
# @RATIONALE Typed model prevents config corruption from ad-hoc dicts. Replaces the existing
|
|
# `connections: list[dict]` stub. connection_id references the UUID in this list — not a DB FK.
|
|
# @REJECTED Keeping `list[dict]` was rejected — would require per-field validation in every consumer.
|
|
# A separate DB table was rejected — connections are configuration, not domain data.
|
|
class DatabaseConnection(BaseModel):
|
|
id: str = Field(default_factory=lambda: uuid.uuid4().hex[:12])
|
|
name: str
|
|
host: str
|
|
port: int = 5432
|
|
database: str
|
|
username: str
|
|
password: str # Fernet-encrypted at rest; masked as "********" in API responses
|
|
dialect: str = "postgresql"
|
|
extra_params: dict = Field(default_factory=dict)
|
|
pool_size: int = Field(default=5, ge=1, le=100)
|
|
created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
updated_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
|
|
@field_validator("dialect")
|
|
@classmethod
|
|
def validate_dialect(cls, v: str) -> str:
|
|
allowed = {"postgresql", "clickhouse", "mysql"}
|
|
if v.lower() not in allowed:
|
|
raise ValueError(f"Unsupported dialect: {v}. Allowed: {', '.join(sorted(allowed))}")
|
|
return v.lower()
|
|
|
|
@field_validator("port")
|
|
@classmethod
|
|
def validate_port(cls, v: int) -> int:
|
|
if v < 1 or v > 65535:
|
|
raise ValueError(f"Port must be between 1 and 65535, got {v}")
|
|
return v
|
|
|
|
@field_validator("name")
|
|
@classmethod
|
|
def validate_name(cls, v: str) -> str:
|
|
if not v or not v.strip():
|
|
raise ValueError("Connection name must not be empty")
|
|
return v.strip()
|
|
|
|
|
|
# #endregion DatabaseConnection
|
|
|
|
|
|
# #region GlobalSettings [TYPE DataClass]
|
|
# @ingroup Core
|
|
# @BRIEF Represents global application settings.
|
|
class GlobalSettings(BaseModel):
|
|
storage: StorageConfig = Field(default_factory=StorageConfig)
|
|
clean_release: CleanReleaseConfig = Field(default_factory=CleanReleaseConfig)
|
|
default_environment_id: str | None = None
|
|
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
|
features: FeaturesConfig = Field(default_factory=FeaturesConfig)
|
|
connections: list[DatabaseConnection] = []
|
|
llm: dict = Field(
|
|
default_factory=lambda: {
|
|
"providers": [],
|
|
"default_provider": "",
|
|
"prompts": dict(DEFAULT_LLM_PROMPTS),
|
|
"provider_bindings": dict(DEFAULT_LLM_PROVIDER_BINDINGS),
|
|
**dict(DEFAULT_LLM_ASSISTANT_SETTINGS),
|
|
}
|
|
)
|
|
|
|
# Application timezone
|
|
app_timezone: str = "Europe/Moscow"
|
|
|
|
@field_validator("app_timezone")
|
|
@classmethod
|
|
def validate_app_timezone(cls, v: str) -> str:
|
|
from zoneinfo import ZoneInfo
|
|
try:
|
|
ZoneInfo(v)
|
|
except (KeyError, TypeError):
|
|
raise ValueError(f"Invalid IANA timezone: {v}") from None
|
|
return v
|
|
|
|
# Task retention settings
|
|
task_retention_days: int = 30
|
|
task_retention_limit: int = 100
|
|
pagination_limit: int = 10
|
|
|
|
# Migration sync settings
|
|
migration_sync_cron: str = "0 2 * * *"
|
|
|
|
# Dataset Review Feature Flags
|
|
ff_dataset_auto_review: bool = True
|
|
ff_dataset_clarification: bool = True
|
|
ff_dataset_execution: bool = True
|
|
|
|
# LLM validation retention (days)
|
|
LLM_SCREENSHOT_RETENTION_DAYS: int = 30
|
|
LLM_RAW_RESPONSE_RETENTION_DAYS: int = 30
|
|
|
|
# Global worker limit for concurrent validation runs
|
|
GLOBAL_VALIDATION_WORKER_LIMIT: int = 3
|
|
|
|
# Allowed languages for translation (BCP-47 codes)
|
|
allowed_languages: list[str] = Field(
|
|
default_factory=lambda: [
|
|
"ru", "en", "de", "fr", "es", "it", "pt", "zh", "ja", "ko",
|
|
"ar", "tr", "nl", "pl", "sv", "da", "fi", "cs", "hu", "ro",
|
|
"vi", "th", "he", "id", "ms",
|
|
]
|
|
)
|
|
|
|
@field_validator("allowed_languages")
|
|
@classmethod
|
|
def validate_allowed_languages(cls, v: list[str]) -> list[str]:
|
|
import re
|
|
for tag in v:
|
|
if not tag or not tag.strip():
|
|
raise ValueError("Empty language code is not allowed")
|
|
if not re.match(r'^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$', tag.strip()):
|
|
raise ValueError(f"Invalid BCP-47 language code: {tag}")
|
|
return v
|
|
|
|
|
|
# #endregion GlobalSettings
|
|
|
|
|
|
# #region AppConfig [TYPE DataClass]
|
|
# @ingroup Core
|
|
# @BRIEF The root configuration model containing all application settings.
|
|
class AppConfig(BaseModel):
|
|
environments: list[Environment] = []
|
|
settings: GlobalSettings
|
|
|
|
|
|
# #endregion AppConfig
|
|
|
|
# #endregion ConfigModels
|