import os
import json
from pathlib import Path
from typing import Optional, Union, Any
from .types import ValidationConfig, ValidationMode
from .errors import ConfigError
def load_config(
source: Union[str, Path, dict[str, Any]],
env_prefix: str = 'KYA_VALIDATOR_',
) -> ValidationConfig:
if isinstance(source, dict):
config_dict = source
elif isinstance(source, (str, Path)):
path = Path(source) if isinstance(source, str) else source
if not path.exists():
raise ConfigError(f'Configuration file not found: {path}')
try:
with open(path, 'r', encoding='utf-8') as f:
config_dict = json.load(f)
except json.JSONDecodeError as e:
raise ConfigError(f'Invalid JSON in configuration: {e}')
except IOError as e:
raise ConfigError(f'Failed to read configuration: {e}')
else:
raise ConfigError(
f'Invalid source type: {type(source)}. '
'Expected str, Path, or dict.'
)
env_config = _load_env_config(env_prefix)
if env_config:
config_dict = {**config_dict, **env_config}
try:
return ValidationConfig.from_json(json.dumps(config_dict))
except (KeyError, ValueError, TypeError) as e:
raise ConfigError(f'Failed to parse configuration: {e}')
def _load_env_config(prefix: str) -> dict[str, Any]:
config = {}
for key, value in os.environ.items():
if not key.startswith(prefix):
continue
config_key = key[len(prefix):].lower()
parts = config_key.split('_')
camel_key = parts[0] + ''.join(
p.capitalize() for p in parts[1:]
)
parsed_value = _parse_env_value(value)
config[camel_key] = parsed_value
return config
def _parse_env_value(value: str) -> Any:
if value.lower() in ('true', 'false'):
return value.lower() == 'true'
try:
return int(value)
except ValueError:
pass
try:
return float(value)
except ValueError:
pass
return value
def save_config(
config: ValidationConfig,
path: Union[str, Path],
) -> None:
path = Path(path) if isinstance(path, str) else path
try:
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
f.write(config.to_json())
except IOError as e:
raise ConfigError(f'Failed to write configuration: {e}')
def create_preset(
mode: str = 'self_audit',
) -> ValidationConfig:
if mode == 'self_audit':
return ValidationConfig.self_audit()
elif mode == 'client_audit':
return ValidationConfig.client_audit()
else:
raise ConfigError(
f'Unknown preset mode: {mode}. '
'Expected "self_audit" or "client_audit".'
)
def merge_configs(
*configs: ValidationConfig,
) -> ValidationConfig:
if not configs:
return ValidationConfig.default()
merged = configs[0]
for config in configs[1:]:
if config.allowed_kya_versions:
merged.allowed_kya_versions = config.allowed_kya_versions
if config.required_fields:
merged.required_fields = config.required_fields
if config.required_field_pairs:
merged.required_field_pairs = config.required_field_pairs
if config.allowed_controllers:
merged.allowed_controllers = config.allowed_controllers
if config.required_vc_types:
merged.required_vc_types = config.required_vc_types
merged.enforce_controller_match = config.enforce_controller_match
merged.check_external_links = config.check_external_links
merged.require_all_proofs = config.require_all_proofs
return merged