Files
next-tjwater-server/app/core/config.py
T
jiang 33df25f22b fix(tasks): isolate worker execution scopes
Persist a required Server-owned execution scope and enforce it for listing, claiming, lease recovery and SCADA verification. Backfill existing tasks explicitly through migration 034 so local development workers no longer claim production tasks from the shared database.
2026-11-08 01:54:48 +08:00

191 lines
6.5 KiB
Python

from pathlib import Path
from typing import Optional
from urllib.parse import quote_plus
from pydantic import AliasChoices, Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
PROJECT_NAME: str = "TJWater Server"
ENVIRONMENT: str = "production"
OPERATIONAL_TASK_EXECUTION_SCOPE: str = ""
@property
def operational_task_execution_scope(self) -> str:
return self.OPERATIONAL_TASK_EXECUTION_SCOPE.strip() or self.ENVIRONMENT
API_V1_STR: str = "/api/v1"
NETWORK_NAME: str = "default_network"
# 敏感配置加密密钥 (Fernet)
DATABASE_ENCRYPTION_KEY: str = ""
# Database Config (PostgreSQL)
DB_NAME: str = "tjwater"
DB_HOST: str = "localhost"
DB_PORT: str = "5432"
DB_USER: str = "postgres"
DB_PASSWORD: str = "password"
# Database Config (TimescaleDB)
TIMESCALEDB_DB_NAME: str = "tjwater"
TIMESCALEDB_DB_HOST: str = "localhost"
TIMESCALEDB_DB_PORT: str = "5433"
TIMESCALEDB_DB_USER: str = "postgres"
TIMESCALEDB_DB_PASSWORD: str = "password"
TIMESCALEDB_REALTIME_MAX_DECOMPRESSED_TUPLES: int = Field(
default=1_000_000, ge=1, le=2_147_483_647,
)
# Metadata Database Config (PostgreSQL)
METADATA_DB_NAME: str = "system_hub"
METADATA_DB_HOST: str = "localhost"
METADATA_DB_PORT: str = "5432"
METADATA_DB_USER: str = "postgres"
METADATA_DB_PASSWORD: str = "password"
METADATA_DB_POOL_SIZE: int = 5
METADATA_DB_MAX_OVERFLOW: int = 10
PROJECT_PG_CACHE_SIZE: int = 16
PROJECT_TS_CACHE_SIZE: int = 16
PROJECT_PG_POOL_MIN_SIZE: int = 0
PROJECT_PG_POOL_SIZE: int = 4
PROJECT_PG_MAX_OVERFLOW: int = 2
PROJECT_TS_POOL_MIN_SIZE: int = 0
PROJECT_TS_POOL_MAX_SIZE: int = 4
WNDB_SCHEMA_TEMPLATE_DB_NAME: str = "tjwater_v2_schema_template"
TIMESCALEDB_SCHEMA_TEMPLATE_DB_NAME: str = "tjwater_v2_timescale_template"
WNDB_TEMP_DB_MAX_COUNT: int = 8
# GeoServer project provisioning
GEOSERVER_URL: str = "http://localhost:8080/geoserver"
GEOSERVER_USERNAME: str = ""
GEOSERVER_PASSWORD: str = ""
GEOSERVER_DB_HOST: str = ""
GEOSERVER_DB_PORT: str = ""
GEOSERVER_DB_USER: str = ""
GEOSERVER_DB_PASSWORD: str = ""
GEOSERVER_CLIENT_CACHE_SECONDS: int = 30 * 24 * 60 * 60
# Keycloak access token verification
KEYCLOAK_PUBLIC_KEY: str = ""
KEYCLOAK_ALGORITHM: str = "RS256"
KEYCLOAK_AUDIENCE: str = ""
KEYCLOAK_ACCESS_TOKEN_MAX_AGE_SECONDS: int = 900
# Authenticated workbench synchronization.
WORKBENCH_EVENTS_ENABLED: bool = True
WORKBENCH_EVENT_RETENTION_DAYS: int = 7
CORS_ALLOWED_ORIGINS: str = (
"http://127.0.0.1:5173,http://localhost:5173,"
"http://127.0.0.1:15173,http://localhost:15173"
)
@property
def cors_allowed_origins(self) -> list[str]:
origins = [value.strip() for value in self.CORS_ALLOWED_ORIGINS.split(",") if value.strip()]
if "*" in origins:
raise ValueError("CORS_ALLOWED_ORIGINS must list explicit origins")
return origins
# One shared Tianditu key for geocoding, place search and other services.
TIANDITU_TOKEN: str = Field(
"",
validation_alias=AliasChoices(
"TIANDITU_TOKEN", "TJWATER_TIANDITU_TOKEN", "TIANDITU_GEOCODER_TOKEN"
),
)
TIANDITU_GEOCODER_URL: str = "https://api.tianditu.gov.cn/geocoder"
TIANDITU_GEOCODER_TIMEOUT_SECONDS: float = 30.0
TIANDITU_SEARCH_URL: str = "https://api.tianditu.gov.cn/v2/search"
TIANDITU_SEARCH_TIMEOUT_SECONDS: float = 15.0
@property
def SQLALCHEMY_DATABASE_URI(self) -> str:
db_password = quote_plus(self.DB_PASSWORD)
return f"postgresql://{self.DB_USER}:{db_password}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
@property
def METADATA_DATABASE_URI(self) -> str:
metadata_password = quote_plus(self.METADATA_DB_PASSWORD)
return (
f"postgresql+psycopg://{self.METADATA_DB_USER}:{metadata_password}"
f"@{self.METADATA_DB_HOST}:{self.METADATA_DB_PORT}/{self.METADATA_DB_NAME}"
)
model_config = SettingsConfigDict(
env_file=Path(__file__).resolve().parents[2] / ".env",
extra="ignore",
)
settings = Settings()
def get_pgconn_string(
db_name: Optional[str] = None,
db_host: Optional[str] = None,
db_port: Optional[str] = None,
db_user: Optional[str] = None,
db_password: Optional[str] = None,
) -> str:
"""Return PostgreSQL connection string in psycopg conninfo format."""
resolved_db_name = db_name or settings.DB_NAME
resolved_db_host = db_host or settings.DB_HOST
resolved_db_port = db_port or settings.DB_PORT
resolved_db_user = db_user or settings.DB_USER
resolved_db_password = db_password or settings.DB_PASSWORD
return (
f"dbname={resolved_db_name} host={resolved_db_host} port={resolved_db_port} "
f"user={resolved_db_user} password={resolved_db_password}"
)
def get_pg_config() -> dict:
"""Return PostgreSQL configuration except password."""
return {
"name": settings.DB_NAME,
"host": settings.DB_HOST,
"port": settings.DB_PORT,
"user": settings.DB_USER,
}
def get_pg_password() -> str:
"""Return PostgreSQL password (use with care)."""
return settings.DB_PASSWORD
def get_timescaledb_pgconn_string(
db_name: Optional[str] = None,
db_host: Optional[str] = None,
db_port: Optional[str] = None,
db_user: Optional[str] = None,
db_password: Optional[str] = None,
) -> str:
"""Return TimescaleDB connection string in psycopg conninfo format."""
resolved_db_name = db_name or settings.TIMESCALEDB_DB_NAME
resolved_db_host = db_host or settings.TIMESCALEDB_DB_HOST
resolved_db_port = db_port or settings.TIMESCALEDB_DB_PORT
resolved_db_user = db_user or settings.TIMESCALEDB_DB_USER
resolved_db_password = db_password or settings.TIMESCALEDB_DB_PASSWORD
return (
f"dbname={resolved_db_name} host={resolved_db_host} port={resolved_db_port} "
f"user={resolved_db_user} password={resolved_db_password}"
)
def get_timescaledb_pg_config() -> dict:
"""Return TimescaleDB configuration except password."""
return {
"name": settings.TIMESCALEDB_DB_NAME,
"host": settings.TIMESCALEDB_DB_HOST,
"port": settings.TIMESCALEDB_DB_PORT,
"user": settings.TIMESCALEDB_DB_USER,
}
def get_timescaledb_pg_password() -> str:
"""Return TimescaleDB password (use with care)."""
return settings.TIMESCALEDB_DB_PASSWORD