semantics: complete DEF-to-region migration, fix regressions
- Convert legacy [DEF🆔Type] anchors to #region/#endregion across 329 files
- Reinstate _normalize_timestamp_value in sql_generator.py
- Fix MarkerLogger→logger migration in events.py (molecular CoT markers)
- Fix dataset_review orchestrator dependencies (_build_execution_snapshot)
- Fix config_manager stale-record deletion (moved to save path only)
- Add 77 missing [/DEF:] closers in 5 unbalanced test files
- Update assistant_chat.integration.test.js for #region format
- Apply molecular-cot-logging markers (REASON/REFLECT/EXPLORE) via logger.* methods
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
# #region SettingsRouter [C:3] [TYPE Module] [SEMANTICS settings, api, router, fastapi]
|
||||
#
|
||||
# @BRIEF Provides API endpoints for managing application settings and Superset environments.
|
||||
# @LAYER API
|
||||
# @INVARIANT All settings changes must be persisted via ConfigManager.
|
||||
# @LAYER: API
|
||||
# @RELATION DEPENDS_ON -> [ConfigManager]
|
||||
# @RELATION DEPENDS_ON -> [get_config_manager]
|
||||
# @RELATION DEPENDS_ON -> [has_permission]
|
||||
#
|
||||
#
|
||||
# @INVARIANT: All settings changes must be persisted via ConfigManager.
|
||||
# @PUBLIC_API: router
|
||||
|
||||
# [SECTION: IMPORTS]
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
@@ -17,10 +16,7 @@ from ...core.config_models import AppConfig, Environment, GlobalSettings, Loggin
|
||||
from ...models.storage import StorageConfig
|
||||
from ...dependencies import get_config_manager, has_permission
|
||||
from ...core.config_manager import ConfigManager
|
||||
from ...core.logger import belief_scope
|
||||
from ...core.cot_logger import MarkerLogger
|
||||
|
||||
log = MarkerLogger("Settings")
|
||||
from ...core.logger import logger, belief_scope
|
||||
from ...core.superset_client import SupersetClient
|
||||
from ...services.llm_prompt_templates import normalize_llm_settings
|
||||
from ...models.llm import ValidationPolicy
|
||||
@@ -32,7 +28,6 @@ from ...schemas.settings import (
|
||||
)
|
||||
from ...core.database import get_db
|
||||
from sqlalchemy.orm import Session
|
||||
# [/SECTION]
|
||||
|
||||
|
||||
# #region LoggingConfigResponse [C:1] [TYPE Class] [SEMANTICS logging, config, response]
|
||||
@@ -50,8 +45,8 @@ router = APIRouter()
|
||||
|
||||
# #region _normalize_superset_env_url [C:1] [TYPE Function]
|
||||
# @BRIEF Canonicalize Superset environment URL to base host/path without trailing /api/v1.
|
||||
# @PRE raw_url can be empty.
|
||||
# @POST Returns normalized base URL.
|
||||
# @PRE: raw_url can be empty.
|
||||
# @POST: Returns normalized base URL.
|
||||
def _normalize_superset_env_url(raw_url: str) -> str:
|
||||
normalized = str(raw_url or "").strip().rstrip("/")
|
||||
if normalized.lower().endswith("/api/v1"):
|
||||
@@ -64,8 +59,8 @@ def _normalize_superset_env_url(raw_url: str) -> str:
|
||||
|
||||
# #region _validate_superset_connection_fast [C:2] [TYPE Function]
|
||||
# @BRIEF Run lightweight Superset connectivity validation without full pagination scan.
|
||||
# @PRE env contains valid URL and credentials.
|
||||
# @POST Raises on auth/API failures; returns None on success.
|
||||
# @PRE: env contains valid URL and credentials.
|
||||
# @POST: Raises on auth/API failures; returns None on success.
|
||||
def _validate_superset_connection_fast(env: Environment) -> None:
|
||||
client = SupersetClient(env)
|
||||
# 1) Explicit auth check
|
||||
@@ -85,16 +80,15 @@ def _validate_superset_connection_fast(env: Environment) -> None:
|
||||
|
||||
# #region get_settings [C:2] [TYPE Function]
|
||||
# @BRIEF Retrieves all application settings.
|
||||
# @PRE Config manager is available.
|
||||
# @POST Returns masked AppConfig.
|
||||
# @RETURN AppConfig - The current configuration.
|
||||
# @PRE: Config manager is available.
|
||||
# @POST: Returns masked AppConfig.
|
||||
@router.get("", response_model=AppConfig)
|
||||
async def get_settings(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
_=Depends(has_permission("admin:settings", "READ")),
|
||||
):
|
||||
with belief_scope("get_settings"):
|
||||
log.reason("Fetching all settings")
|
||||
logger.info("[get_settings][Entry] Fetching all settings")
|
||||
config = config_manager.get_config().copy(deep=True)
|
||||
config.settings.llm = normalize_llm_settings(config.settings.llm)
|
||||
# Mask passwords
|
||||
@@ -109,9 +103,9 @@ async def get_settings(
|
||||
|
||||
# #region get_features [C:1] [TYPE Function]
|
||||
# @BRIEF Public endpoint returning feature flags for frontend sidebar filtering.
|
||||
# @PRE Config manager is available.
|
||||
# @POST Returns dict with dataset_review and health_monitor booleans.
|
||||
# @RATIONALE Unauthenticated because sidebar filtering must work for all users, not just admins.
|
||||
# @RATIONALE: Unauthenticated because sidebar filtering must work for all users, not just admins.
|
||||
# @PRE: Config manager is available.
|
||||
# @POST: Returns dict with dataset_review and health_monitor booleans.
|
||||
@router.get("/features")
|
||||
async def get_features(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
@@ -124,10 +118,8 @@ async def get_features(
|
||||
|
||||
# #region update_global_settings [C:2] [TYPE Function]
|
||||
# @BRIEF Updates global application settings.
|
||||
# @PRE New settings are provided.
|
||||
# @POST Global settings are updated.
|
||||
# @PARAM: settings (GlobalSettings) - The new global settings.
|
||||
# @RETURN: GlobalSettings - The updated settings.
|
||||
# @PRE: New settings are provided.
|
||||
# @POST: Global settings are updated.
|
||||
@router.patch("/global", response_model=GlobalSettings)
|
||||
async def update_global_settings(
|
||||
settings: GlobalSettings,
|
||||
@@ -135,7 +127,7 @@ async def update_global_settings(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("update_global_settings"):
|
||||
log.reason("Updating global settings")
|
||||
logger.info("[update_global_settings][Entry] Updating global settings")
|
||||
|
||||
config_manager.update_global_settings(settings)
|
||||
return settings
|
||||
@@ -146,7 +138,6 @@ async def update_global_settings(
|
||||
|
||||
# #region get_storage_settings [C:2] [TYPE Function]
|
||||
# @BRIEF Retrieves storage-specific settings.
|
||||
# @RETURN StorageConfig - The storage configuration.
|
||||
@router.get("/storage", response_model=StorageConfig)
|
||||
async def get_storage_settings(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
@@ -161,9 +152,7 @@ async def get_storage_settings(
|
||||
|
||||
# #region update_storage_settings [C:2] [TYPE Function]
|
||||
# @BRIEF Updates storage-specific settings.
|
||||
# @PARAM: storage (StorageConfig) - The new storage settings.
|
||||
# @POST: Storage settings are updated and saved.
|
||||
# @RETURN: StorageConfig - The updated storage settings.
|
||||
@router.put("/storage", response_model=StorageConfig)
|
||||
async def update_storage_settings(
|
||||
storage: StorageConfig,
|
||||
@@ -186,16 +175,15 @@ async def update_storage_settings(
|
||||
|
||||
# #region get_environments [C:2] [TYPE Function]
|
||||
# @BRIEF Lists all configured Superset environments.
|
||||
# @PRE Config manager is available.
|
||||
# @POST Returns list of environments.
|
||||
# @RETURN List[Environment] - List of environments.
|
||||
# @PRE: Config manager is available.
|
||||
# @POST: Returns list of environments.
|
||||
@router.get("/environments", response_model=List[Environment])
|
||||
async def get_environments(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
_=Depends(has_permission("admin:settings", "READ")),
|
||||
):
|
||||
with belief_scope("get_environments"):
|
||||
log.reason("Fetching environments")
|
||||
logger.info("[get_environments][Entry] Fetching environments")
|
||||
environments = config_manager.get_environments()
|
||||
return [
|
||||
env.copy(update={"url": _normalize_superset_env_url(env.url)})
|
||||
@@ -208,10 +196,8 @@ async def get_environments(
|
||||
|
||||
# #region add_environment [C:2] [TYPE Function]
|
||||
# @BRIEF Adds a new Superset environment.
|
||||
# @PRE Environment data is valid and reachable.
|
||||
# @POST Environment is added to config.
|
||||
# @PARAM: env (Environment) - The environment to add.
|
||||
# @RETURN: Environment - The added environment.
|
||||
# @PRE: Environment data is valid and reachable.
|
||||
# @POST: Environment is added to config.
|
||||
@router.post("/environments", response_model=Environment)
|
||||
async def add_environment(
|
||||
env: Environment,
|
||||
@@ -219,14 +205,16 @@ async def add_environment(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("add_environment"):
|
||||
log.reason("Adding environment", payload={"env_id": env.id})
|
||||
logger.info(f"[add_environment][Entry] Adding environment {env.id}")
|
||||
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
|
||||
|
||||
# Validate connection before adding (fast path)
|
||||
try:
|
||||
_validate_superset_connection_fast(env)
|
||||
except Exception as e:
|
||||
log.explore("Connection validation failed", payload={"env_id": env.id}, error=str(e))
|
||||
logger.error(
|
||||
f"[add_environment][Coherence:Failed] Connection validation failed: {e}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Connection validation failed: {e}"
|
||||
)
|
||||
@@ -240,11 +228,8 @@ async def add_environment(
|
||||
|
||||
# #region update_environment [C:2] [TYPE Function]
|
||||
# @BRIEF Updates an existing Superset environment.
|
||||
# @PRE ID and valid environment data are provided.
|
||||
# @POST Environment is updated in config.
|
||||
# @PARAM: id (str) - The ID of the environment to update.
|
||||
# @PARAM: env (Environment) - The updated environment data.
|
||||
# @RETURN: Environment - The updated environment.
|
||||
# @PRE: ID and valid environment data are provided.
|
||||
# @POST: Environment is updated in config.
|
||||
@router.put("/environments/{id}", response_model=Environment)
|
||||
async def update_environment(
|
||||
id: str,
|
||||
@@ -252,7 +237,7 @@ async def update_environment(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
with belief_scope("update_environment"):
|
||||
log.reason("Updating environment", payload={"id": id})
|
||||
logger.info(f"[update_environment][Entry] Updating environment {id}")
|
||||
|
||||
env = env.copy(update={"url": _normalize_superset_env_url(env.url)})
|
||||
|
||||
@@ -269,7 +254,9 @@ async def update_environment(
|
||||
try:
|
||||
_validate_superset_connection_fast(env_to_validate)
|
||||
except Exception as e:
|
||||
log.explore("Connection validation failed", payload={"id": id}, error=str(e))
|
||||
logger.error(
|
||||
f"[update_environment][Coherence:Failed] Connection validation failed: {e}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Connection validation failed: {e}"
|
||||
)
|
||||
@@ -284,15 +271,14 @@ async def update_environment(
|
||||
|
||||
# #region delete_environment [C:2] [TYPE Function]
|
||||
# @BRIEF Deletes a Superset environment.
|
||||
# @PRE ID is provided.
|
||||
# @POST Environment is removed from config.
|
||||
# @PARAM: id (str) - The ID of the environment to delete.
|
||||
# @PRE: ID is provided.
|
||||
# @POST: Environment is removed from config.
|
||||
@router.delete("/environments/{id}")
|
||||
async def delete_environment(
|
||||
id: str, config_manager: ConfigManager = Depends(get_config_manager)
|
||||
):
|
||||
with belief_scope("delete_environment"):
|
||||
log.reason("Deleting environment", payload={"id": id})
|
||||
logger.info(f"[delete_environment][Entry] Deleting environment {id}")
|
||||
config_manager.delete_environment(id)
|
||||
return {"message": f"Environment {id} deleted"}
|
||||
|
||||
@@ -302,16 +288,14 @@ async def delete_environment(
|
||||
|
||||
# #region test_environment_connection [C:2] [TYPE Function]
|
||||
# @BRIEF Tests the connection to a Superset environment.
|
||||
# @PRE ID is provided.
|
||||
# @POST Returns success or error status.
|
||||
# @PARAM: id (str) - The ID of the environment to test.
|
||||
# @RETURN: dict - Success message or error.
|
||||
# @PRE: ID is provided.
|
||||
# @POST: Returns success or error status.
|
||||
@router.post("/environments/{id}/test")
|
||||
async def test_environment_connection(
|
||||
id: str, config_manager: ConfigManager = Depends(get_config_manager)
|
||||
):
|
||||
with belief_scope("test_environment_connection"):
|
||||
log.reason("Testing environment connection", payload={"id": id})
|
||||
logger.info(f"[test_environment_connection][Entry] Testing environment {id}")
|
||||
|
||||
# Find environment
|
||||
env = next((e for e in config_manager.get_environments() if e.id == id), None)
|
||||
@@ -321,10 +305,14 @@ async def test_environment_connection(
|
||||
try:
|
||||
_validate_superset_connection_fast(env)
|
||||
|
||||
log.reflect("Connection successful", payload={"id": id})
|
||||
logger.info(
|
||||
f"[test_environment_connection][Coherence:OK] Connection successful for {id}"
|
||||
)
|
||||
return {"status": "success", "message": "Connection successful"}
|
||||
except Exception as e:
|
||||
log.explore("Connection failed", payload={"id": id}, error=str(e))
|
||||
logger.error(
|
||||
f"[test_environment_connection][Coherence:Failed] Connection failed for {id}: {e}"
|
||||
)
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
||||
@@ -333,9 +321,8 @@ async def test_environment_connection(
|
||||
|
||||
# #region get_logging_config [C:2] [TYPE Function]
|
||||
# @BRIEF Retrieves current logging configuration.
|
||||
# @PRE Config manager is available.
|
||||
# @POST Returns logging configuration.
|
||||
# @RETURN LoggingConfigResponse - The current logging config.
|
||||
# @PRE: Config manager is available.
|
||||
# @POST: Returns logging configuration.
|
||||
@router.get("/logging", response_model=LoggingConfigResponse)
|
||||
async def get_logging_config(
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
@@ -355,10 +342,8 @@ async def get_logging_config(
|
||||
|
||||
# #region update_logging_config [C:2] [TYPE Function]
|
||||
# @BRIEF Updates logging configuration.
|
||||
# @PRE New logging config is provided.
|
||||
# @POST Logging configuration is updated and saved.
|
||||
# @PARAM: config (LoggingConfig) - The new logging configuration.
|
||||
# @RETURN: LoggingConfigResponse - The updated logging config.
|
||||
# @PRE: New logging config is provided.
|
||||
# @POST: Logging configuration is updated and saved.
|
||||
@router.patch("/logging", response_model=LoggingConfigResponse)
|
||||
async def update_logging_config(
|
||||
config: LoggingConfig,
|
||||
@@ -366,7 +351,9 @@ async def update_logging_config(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("update_logging_config"):
|
||||
log.reason("Updating logging config", payload={"level": config.level, "task_log_level": config.task_log_level})
|
||||
logger.info(
|
||||
f"[update_logging_config][Entry] Updating logging config: level={config.level}, task_log_level={config.task_log_level}"
|
||||
)
|
||||
|
||||
# Get current settings and update logging config
|
||||
settings = config_manager.get_config().settings
|
||||
@@ -401,10 +388,10 @@ class ConsolidatedSettingsResponse(BaseModel):
|
||||
|
||||
# #region get_consolidated_settings [C:4] [TYPE Function]
|
||||
# @BRIEF Retrieves all settings categories in a single call.
|
||||
# @PRE Config manager is available and the caller holds admin settings read permission.
|
||||
# @POST Returns consolidated settings, provider metadata, and persisted notification payload in one stable response.
|
||||
# @SIDE_EFFECT Opens one database session to read LLM providers and config-backed notification payload, then closes it.
|
||||
# @DATA_CONTRACT Input[ConfigManager] -> Output[ConsolidatedSettingsResponse]
|
||||
# @PRE: Config manager is available and the caller holds admin settings read permission.
|
||||
# @POST: Returns consolidated settings, provider metadata, and persisted notification payload in one stable response.
|
||||
# @SIDE_EFFECT: Opens one database session to read LLM providers and config-backed notification payload, then closes it.
|
||||
# @DATA_CONTRACT: Input[ConfigManager] -> Output[ConsolidatedSettingsResponse]
|
||||
# @RELATION DEPENDS_ON -> [ConfigManager]
|
||||
# @RELATION DEPENDS_ON -> [LLMProviderService]
|
||||
# @RELATION DEPENDS_ON -> [AppConfigRecord]
|
||||
@@ -417,7 +404,7 @@ async def get_consolidated_settings(
|
||||
_=Depends(has_permission("admin:settings", "READ")),
|
||||
):
|
||||
with belief_scope("get_consolidated_settings"):
|
||||
log.reason("Fetching consolidated settings payload")
|
||||
logger.reason("Fetching consolidated settings payload")
|
||||
|
||||
config = config_manager.get_config()
|
||||
|
||||
@@ -464,9 +451,9 @@ async def get_consolidated_settings(
|
||||
notifications=notifications_payload,
|
||||
features=config.settings.features.model_dump(),
|
||||
)
|
||||
log.reflect(
|
||||
logger.reflect(
|
||||
"Consolidated settings payload assembled",
|
||||
payload={
|
||||
extra={
|
||||
"environment_count": len(response_payload.environments),
|
||||
"provider_count": len(response_payload.llm_providers),
|
||||
},
|
||||
@@ -479,8 +466,8 @@ async def get_consolidated_settings(
|
||||
|
||||
# #region update_consolidated_settings [C:2] [TYPE Function]
|
||||
# @BRIEF Bulk update application settings from the consolidated view.
|
||||
# @PRE User has admin permissions, config is valid.
|
||||
# @POST Settings are updated and saved via ConfigManager.
|
||||
# @PRE: User has admin permissions, config is valid.
|
||||
# @POST: Settings are updated and saved via ConfigManager.
|
||||
@router.patch("/consolidated")
|
||||
async def update_consolidated_settings(
|
||||
settings_patch: dict,
|
||||
@@ -488,7 +475,9 @@ async def update_consolidated_settings(
|
||||
_=Depends(has_permission("admin:settings", "WRITE")),
|
||||
):
|
||||
with belief_scope("update_consolidated_settings"):
|
||||
log.reason("Applying consolidated settings patch")
|
||||
logger.info(
|
||||
"[update_consolidated_settings][Entry] Applying consolidated settings patch"
|
||||
)
|
||||
|
||||
current_config = config_manager.get_config()
|
||||
current_settings = current_config.settings
|
||||
@@ -533,7 +522,6 @@ async def update_consolidated_settings(
|
||||
|
||||
# #region get_validation_policies [C:2] [TYPE Function]
|
||||
# @BRIEF Lists all validation policies.
|
||||
# @RETURN List[ValidationPolicyResponse] - List of policies.
|
||||
@router.get("/automation/policies", response_model=List[ValidationPolicyResponse])
|
||||
async def get_validation_policies(
|
||||
db: Session = Depends(get_db), _=Depends(has_permission("admin:settings", "READ"))
|
||||
@@ -547,8 +535,6 @@ async def get_validation_policies(
|
||||
|
||||
# #region create_validation_policy [C:2] [TYPE Function]
|
||||
# @BRIEF Creates a new validation policy.
|
||||
# @PARAM: policy (ValidationPolicyCreate) - The policy data.
|
||||
# @RETURN: ValidationPolicyResponse - The created policy.
|
||||
@router.post("/automation/policies", response_model=ValidationPolicyResponse)
|
||||
async def create_validation_policy(
|
||||
policy: ValidationPolicyCreate,
|
||||
@@ -568,9 +554,6 @@ async def create_validation_policy(
|
||||
|
||||
# #region update_validation_policy [C:2] [TYPE Function]
|
||||
# @BRIEF Updates an existing validation policy.
|
||||
# @PARAM: id (str) - The ID of the policy to update.
|
||||
# @PARAM: policy (ValidationPolicyUpdate) - The updated policy data.
|
||||
# @RETURN: ValidationPolicyResponse - The updated policy.
|
||||
@router.patch("/automation/policies/{id}", response_model=ValidationPolicyResponse)
|
||||
async def update_validation_policy(
|
||||
id: str,
|
||||
@@ -597,7 +580,6 @@ async def update_validation_policy(
|
||||
|
||||
# #region delete_validation_policy [C:2] [TYPE Function]
|
||||
# @BRIEF Deletes a validation policy.
|
||||
# @PARAM: id (str) - The ID of the policy to delete.
|
||||
@router.delete("/automation/policies/{id}")
|
||||
async def delete_validation_policy(
|
||||
id: str,
|
||||
|
||||
Reference in New Issue
Block a user