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.
191 lines
6.5 KiB
Python
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
|