Initial commit: Add OpenAPI Mock Server project
This commit is contained in:
204
.src/openapi_mock/core/config.py
Normal file
204
.src/openapi_mock/core/config.py
Normal file
@@ -0,0 +1,204 @@
|
||||
"""Configuration file support for OpenAPI Mock Server."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class ConfigError(Exception):
|
||||
"""Base exception for configuration errors."""
|
||||
pass
|
||||
|
||||
|
||||
class ConfigFileNotFoundError(ConfigError):
|
||||
"""Raised when config file is not found."""
|
||||
pass
|
||||
|
||||
|
||||
class ConfigParseError(ConfigError):
|
||||
"""Raised when config file has invalid YAML."""
|
||||
pass
|
||||
|
||||
|
||||
DEFAULT_CONFIG_FILES = [
|
||||
".openapi-mock.yaml",
|
||||
".openapi-mock.yml",
|
||||
"mock-config.yaml",
|
||||
"mock-config.yml",
|
||||
]
|
||||
|
||||
|
||||
def find_config_file(config_dir: Optional[str] = None) -> Optional[Path]:
|
||||
"""Find a configuration file in the given or current directory.
|
||||
|
||||
Args:
|
||||
config_dir: Directory to search for config file.
|
||||
|
||||
Returns:
|
||||
Path to config file or None if not found.
|
||||
"""
|
||||
search_dir = Path(config_dir) if config_dir else Path.cwd()
|
||||
|
||||
for config_name in DEFAULT_CONFIG_FILES:
|
||||
config_path = search_dir / config_name
|
||||
if config_path.exists():
|
||||
return config_path
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Load configuration from a YAML file.
|
||||
|
||||
Args:
|
||||
config_path: Path to config file. If None, searches default locations.
|
||||
|
||||
Returns:
|
||||
Configuration dictionary.
|
||||
|
||||
Raises:
|
||||
ConfigFileNotFoundError: If config file not found.
|
||||
ConfigParseError: If config file has invalid YAML.
|
||||
"""
|
||||
if config_path is None:
|
||||
config_path = find_config_file()
|
||||
if config_path is None:
|
||||
return {}
|
||||
else:
|
||||
config_path = Path(config_path)
|
||||
if not config_path.exists():
|
||||
raise ConfigFileNotFoundError(f"Config file not found: {config_path}")
|
||||
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
except yaml.YAMLError as e:
|
||||
raise ConfigParseError(f"Invalid YAML in config: {e}")
|
||||
except IOError as e:
|
||||
raise ConfigError(f"Error reading config: {e}")
|
||||
|
||||
return config or {}
|
||||
|
||||
|
||||
def apply_env_overrides(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Apply environment variable overrides to configuration.
|
||||
|
||||
Args:
|
||||
config: Base configuration dictionary.
|
||||
|
||||
Returns:
|
||||
Configuration with environment overrides applied.
|
||||
"""
|
||||
result = config.copy()
|
||||
|
||||
if "OPENAPI_MOCK_PORT" in os.environ:
|
||||
try:
|
||||
result["port"] = int(os.environ["OPENAPI_MOCK_PORT"])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if "OPENAPI_MOCK_HOST" in os.environ:
|
||||
result["host"] = os.environ["OPENAPI_MOCK_HOST"]
|
||||
|
||||
if "OPENAPI_MOCK_DELAY" in os.environ:
|
||||
delay_str = os.environ["OPENAPI_MOCK_DELAY"]
|
||||
try:
|
||||
if "," in delay_str:
|
||||
parts = delay_str.split(",")
|
||||
result["delay"] = (float(parts[0]), float(parts[1]))
|
||||
else:
|
||||
result["delay"] = (float(delay_str), float(delay_str))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if "OPENAPI_MOCK_AUTH" in os.environ:
|
||||
result["auth"] = os.environ["OPENAPI_MOCK_AUTH"]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_cli_overrides(
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
delay: Optional[tuple] = None,
|
||||
auth: Optional[str] = None,
|
||||
watch: Optional[bool] = None,
|
||||
reload: Optional[bool] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get CLI overrides as a configuration dictionary.
|
||||
|
||||
Args:
|
||||
host: Host override.
|
||||
port: Port override.
|
||||
delay: Delay override.
|
||||
auth: Auth type override.
|
||||
watch: Watch flag override.
|
||||
reload: Reload flag override.
|
||||
|
||||
Returns:
|
||||
Configuration dictionary with non-None values.
|
||||
"""
|
||||
config: Dict[str, Any] = {}
|
||||
|
||||
if host is not None:
|
||||
config["host"] = host
|
||||
if port is not None:
|
||||
config["port"] = port
|
||||
if delay is not None:
|
||||
config["delay"] = delay
|
||||
if auth is not None:
|
||||
config["auth"] = auth
|
||||
if watch is not None:
|
||||
config["watch"] = watch
|
||||
if reload is not None:
|
||||
config["reload"] = reload
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def merge_configs(
|
||||
base_config: Dict[str, Any],
|
||||
override_config: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Merge two configuration dictionaries, with override taking precedence.
|
||||
|
||||
Args:
|
||||
base_config: Base configuration.
|
||||
override_config: Override configuration.
|
||||
|
||||
Returns:
|
||||
Merged configuration.
|
||||
"""
|
||||
result = base_config.copy()
|
||||
|
||||
for key, value in override_config.items():
|
||||
if isinstance(value, dict) and key in result and isinstance(result[key], dict):
|
||||
result[key] = merge_configs(result[key], value)
|
||||
else:
|
||||
result[key] = value
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_server_config(
|
||||
config_path: Optional[str] = None,
|
||||
cli_overrides: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get complete server configuration.
|
||||
|
||||
Args:
|
||||
config_path: Path to config file.
|
||||
cli_overrides: CLI argument overrides.
|
||||
|
||||
Returns:
|
||||
Complete server configuration.
|
||||
"""
|
||||
config = load_config(config_path)
|
||||
config = apply_env_overrides(config)
|
||||
|
||||
if cli_overrides:
|
||||
config = merge_configs(config, cli_overrides)
|
||||
|
||||
return config
|
||||
Reference in New Issue
Block a user