import enum import logging from urllib.parse import quote import colorlog from dotenv import load_dotenv from pydantic import ( AnyUrl, MySQLDsn, computed_field, model_validator, ) from pydantic_settings import BaseSettings, SettingsConfigDict from typing_extensions import Self load_dotenv() handler = colorlog.StreamHandler() handler.setFormatter( colorlog.ColoredFormatter( "%(log_color)s%(asctime)s - %(name)s - %(levelname)s - %(message)s", log_colors={ "DEBUG": "cyan", "INFO": "green", "WARNING": "yellow", "ERROR": "red", "CRITICAL": "red,bg_white", }, ) ) logger = logging.getLogger() logger.setLevel(logging.INFO) logger.addHandler(handler) class Environment(str, enum.Enum): LOCAL = "local" STAGING = "staging" PRODUCTION = "production" class FetchBugInfoMethod(str, enum.Enum): TI_RELEASE = "ti-release" GITHUB = "github" class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_ignore_empty=True, extra="ignore" ) PROJECT_NAME: str = "pingcap-support-copilot" ENVIRONMENT: Environment = Environment.LOCAL API_V1_STR: str = "/api/v1" DOMAIN: str = "localhost" @computed_field # type: ignore[misc] @property def server_host(self) -> str: # Use HTTPS for anything other than local development if self.ENVIRONMENT == Environment.LOCAL: return f"http://{self.DOMAIN}" return f"https://{self.DOMAIN}" SESSION_COOKIE_NAME: str = "session" SESSION_COOKIE_MAX_AGE: int = 3600 * 24 * 90 # 90 days SESSION_COOKIE_SECURE: bool = False SECRET_KEY: str @model_validator(mode="after") def _validate_secrets(self) -> Self: secret = self.SECRET_KEY if not secret: raise ValueError( f"Please set a secret key using the SECRET_KEY environment variable." ) min_length = 32 if len(secret.encode()) < min_length: message = ( "The SECRET_KEY is too short, " f"please use a longer secret, at least {min_length} characters." ) raise ValueError(message) return self # TiDB (Database) TIDB_HOST: str = "127.0.0.1" TIDB_PORT: int = 4000 TIDB_USER: str = "root" TIDB_PASSWORD: str = "" TIDB_DATABASE: str TIDB_SSL: bool = False @computed_field # type: ignore[misc] @property def SQLALCHEMY_DATABASE_URI(self) -> MySQLDsn: return MySQLDsn.build( scheme="mysql+pymysql", username=self.TIDB_USER, password=quote(self.TIDB_PASSWORD), host=self.TIDB_HOST, port=self.TIDB_PORT, path=self.TIDB_DATABASE, query="ssl_verify_cert=true&ssl_verify_identity=true" if self.TIDB_SSL else None, ) @computed_field # type: ignore[misc] @property def SQLALCHEMY_ASYNC_DATABASE_URI(self) -> MySQLDsn: return MySQLDsn.build( scheme="mysql+asyncmy", username=self.TIDB_USER, password=quote(self.TIDB_PASSWORD), host=self.TIDB_HOST, port=self.TIDB_PORT, path=self.TIDB_DATABASE, ) # OpenAI OPENAI_API_KEY: str # GitHub GITHUB_TOKEN: str # Langfuse LANGFUSE_SECRET_KEY: str LANGFUSE_PUBLIC_KEY: str LANGFUSE_HOST: str = "https://us.cloud.langfuse.com" # BUG information FETCH_BUG_INFO_METHOD: FetchBugInfoMethod = FetchBugInfoMethod.GITHUB # Celery CELERY_BROKER_URL: str = "redis://127.0.0.1:6379/1" @computed_field # type: ignore[misc] @property def CELERY_RESULT_BACKEND(self) -> str: return str( AnyUrl.build( scheme="db+mysql+pymysql", username=self.TIDB_USER, password=quote(self.TIDB_PASSWORD), host=self.TIDB_HOST, port=self.TIDB_PORT, path=self.TIDB_DATABASE, query="ssl_verify_cert=true&ssl_verify_identity=true" if self.TIDB_SSL else None, ) ) settings = Settings() # type: ignore