import os import sys from pathlib import Path from typing import Optional, Dict, Any from dotenv import load_dotenv, find_dotenv import logging logger = logging.getLogger(__name__) class EnvironmentLoader: """Класс для загрузки переменных окружения из .env файлов""" # Порядок загрузки файлов .env (от более специфичного к общему) ENV_FILES_ORDER = [ '.env.local', # Локальные переопределения (не в git) f'.env.{os.getenv("ENV", "development")}', # Окружение: .env.test, .env.production '.env', # Основной файл ] @classmethod def load_environment(cls, env_file: Optional[str] = None): """ Загрузка переменных окружения. Args: env_file: Конкретный файл для загрузки (если None, используется порядок из ENV_FILES_ORDER) """ if env_file: # Загрузка конкретного файла env_path = Path(env_file) if env_path.exists(): logger.info(f"Loading environment from specified file: {env_path}") load_dotenv(dotenv_path=env_path, override=True) else: logger.warning(f"Specified env file not found: {env_path}") return # Автоматическая загрузка в определенном порядке env_loaded = False for env_filename in cls.ENV_FILES_ORDER: env_path = find_dotenv(env_filename, usecwd=True) if env_path: logger.info(f"Loading environment from: {env_path}") load_dotenv(dotenv_path=env_path, override=True) env_loaded = True if not env_loaded: logger.warning("No .env files found, using system environment variables") @classmethod def get_env_variable(cls, key: str, default: Any = None, required: bool = False) -> Any: """ Получение переменной окружения с проверкой. Args: key: Ключ переменной окружения default: Значение по умолчанию, если переменная не найдена required: Обязательна ли переменная (вызывает исключение, если не найдена) Returns: Значение переменной окружения Raises: ValueError: Если переменная required=True и не найдена """ value = os.getenv(key) if value is None: if required: raise ValueError(f"Required environment variable '{key}' is not set") return default # Автоматическое преобразование типов if value.lower() in ('true', 'false'): return value.lower() == 'true' elif value.isdigit(): return int(value) elif cls._is_float(value): return float(value) elif value.startswith('[') and value.endswith(']'): # Список значений, разделенных запятыми return [item.strip() for item in value[1:-1].split(',') if item.strip()] return value @staticmethod def _is_float(value: str) -> bool: """Проверка, можно ли преобразовать строку в float""" try: float(value) return True except ValueError: return False @classmethod def validate_environment(cls): """Валидация обязательных переменных окружения""" required_vars = [ # 'API_BASE_URL', # 'UI_BASE_URL', ] missing_vars = [] for var in required_vars: if not os.getenv(var): missing_vars.append(var) if missing_vars: raise EnvironmentError( f"Missing required environment variables: {', '.join(missing_vars)}\n" f"Please set them in .env file or system environment." ) @classmethod def print_environment_info(cls): """Вывод информации о текущем окружении (для отладки)""" env_info = { 'ENV': os.getenv('ENV', 'development'), 'API_BASE_URL': os.getenv('API_BASE_URL'), 'UI_BASE_URL': os.getenv('UI_BASE_URL'), 'DEBUG': os.getenv('DEBUG', 'False'), 'LOG_LEVEL': os.getenv('LOG_LEVEL', 'INFO'), } logger.info("Current environment configuration:") for key, value in env_info.items(): logger.info(f" {key}: {value}") @classmethod def get_all_env_variables(cls, prefix: str = '') -> Dict[str, Any]: """ Получение всех переменных окружения с опциональным префиксом. Args: prefix: Фильтр по префиксу (например, 'API_' для всех API настроек) Returns: Словарь переменных окружения """ env_vars = {} for key, _ in os.environ.items(): if len(prefix) == 0 and not key.startswith(prefix): continue env_vars[key] = cls.get_env_variable(key) return env_vars # Инициализация при импорте модуля EnvironmentLoader.load_environment()