import os from pathlib import Path from typing import Optional, List, Union, Dict, Any import pydantic import yaml from pydantic import BaseModel, validator def build_path(path: Union[Path, str], path_is_absolute: bool = False) -> Path: """ This function build Path instance from arguments and returns it. :param path: path :param path_is_absolute: if True, use path as absolute :return: Path instance """ if not path_is_absolute: return Path(os.getcwd()) / path if isinstance(path, str): return Path(path) return path def load_yaml(path: Union[Path, str] = None, path_is_absolute: bool = False): """ This method loads from YAML file to properties of self instance. Why doesn't load when __init__ is to make the following requirements compatible: 1. Access config as global 2. Independent on config for development or use config for unit testing when unit testing """ if path is None: path = 'config.yml' built_path = build_path(path, path_is_absolute) dictionary_config = yaml.full_load(built_path.read_text(encoding="UTF-8")) return dictionary_config class Modules: MIDDLEWARE: list = [] ROUTES: list = [] class AuthConf: AUTH_ACCESS_TOKEN_EXPIRE_MINUTES: int AUTH_REFRESH_TOKEN_EXPIRE_MINUTES: int AUTH_ALGORITHM: str AUTH_SECRET_KEY: str class MongoConf: MONGO_URL: str MONGO_DB: str AUTH_DB: str PROD_MONGO_URL: str PROD_MONGO_DB: str PROD_AUTH_DB: str class LinkerConf: LINKER_URL: str class DatabaseConfig(BaseModel): url: str db: str version: Optional[float] username: Optional[str] password: Optional[str] class DatabaseModeConfig(BaseModel): dev: Optional[DatabaseConfig] production: Optional[DatabaseConfig] class MySQLConfig(BaseModel): host: str port: int user: str password: str db: str class MySQLModeConfig(BaseModel): dev: Optional[MySQLConfig] production: Optional[MySQLConfig] class DependencyConfig(BaseModel): middleware: Optional[List[str]] routes: Optional[List[str]] class AuthConfig(BaseModel): secret_key: Optional[str] algorithm: Optional[str] access_token_expire_minutes: Optional[int] refresh_token_expire_minutes: Optional[int] url: Optional[str] cache_prefix_key: Optional[str] cache_ttl: Optional[int] = 360 class ServerConfig(BaseModel): base_api_doc_url: Optional[str] is_prod: Optional[bool] api_logo: Optional[str] = 'https://scm.hacoupian.net/resource/img/logo.svg' prefix_url: Optional[str] port: Optional[int] class CeleryTask(BaseModel): broker: Optional[str] backend: Optional[str] autodiscover_tasks: Optional[List[str]] class CorsConfig(BaseModel): origins: Optional[List[str]] allow_headers: Optional[List[str]] = ['*'] allow_credentials: Optional[bool] = True allow_methods: Optional[List[str]] = ['*'] class CoreConfig(pydantic.BaseModel): cors: Optional[CorsConfig] server: Optional[ServerConfig] auth: Optional[AuthConfig] dependency: Optional[DependencyConfig] database: Dict[str, Union[DatabaseConfig, DatabaseModeConfig]] mysql: Optional[MySQLModeConfig] = None static_files: dict[str, str] = dict() celery: Optional[CeleryTask] @validator('database', pre=True) def parse_database(cls, v: Any) -> Dict[str, Union[DatabaseConfig, DatabaseModeConfig]]: if not isinstance(v, dict): return v result: Dict[str, Union[DatabaseConfig, DatabaseModeConfig]] = {} for key, val in v.items(): if not isinstance(val, dict): result[key] = val continue if 'dev' in val or 'production' in val: dev = val.get('dev') prod = val.get('production') result[key] = DatabaseModeConfig( dev=DatabaseConfig(**(dev or {})) if dev else None, production=DatabaseConfig(**(prod or {})) if prod else None, ) else: result[key] = DatabaseConfig(**val) return result class Config: arbitrary_types_allowed = True # def load_modules_config(self): # # import dynamic routes in the modules # for i in self.dependency.routes: # if os.path.exists(Path(f'{i}/.env')): # values = dotenv_values(f'{i}/.env') # self.extra_conf = dict(self.extra_conf, **dict(values))