"""Configuration introspection utilities for Confii.
This module provides utilities for querying and inspecting configuration
structure, types, and values.
"""
from typing import Any, Dict, List
[docs]
def get_all_keys(config: Dict[str, Any], prefix: str = "") -> List[str]:
"""Recursively get all keys from a configuration dictionary.
Args:
config: Configuration dictionary
prefix: Optional prefix for nested keys (e.g., "database" for nested keys)
Returns:
List of dot-separated key paths
Example:
>>> config = {"database": {"host": "localhost", "port": 5432}}
>>> get_all_keys(config)
['database', 'database.host', 'database.port']
"""
keys: List[str] = []
for key, value in config.items():
full_key = f"{prefix}.{key}" if prefix else key
keys.append(full_key)
if isinstance(value, dict):
keys.extend(get_all_keys(value, full_key))
return keys
[docs]
def get_nested_value(config: Dict[str, Any], key_path: str, default: Any = None) -> Any:
"""Get a nested value from configuration using dot notation.
Args:
config: Configuration dictionary
key_path: Dot-separated key path (e.g., "database.host")
default: Default value if key not found
Returns:
Configuration value or default if not found
Example:
>>> config = {"database": {"host": "localhost"}}
>>> get_nested_value(config, "database.host")
'localhost'
>>> get_nested_value(config, "database.port", default=5432)
5432
"""
keys = key_path.split(".")
current = config
try:
for key in keys:
if isinstance(current, dict):
current = current[key]
else:
return default
return current
except (KeyError, TypeError):
return default
[docs]
def has_key(config: Dict[str, Any], key_path: str) -> bool:
"""Check if a configuration key exists.
Args:
config: Configuration dictionary
key_path: Dot-separated key path (e.g., "database.host")
Returns:
True if key exists, False otherwise
Example:
>>> config = {"database": {"host": "localhost"}}
>>> has_key(config, "database.host")
True
>>> has_key(config, "database.port")
False
"""
keys = key_path.split(".")
current = config
try:
for key in keys:
if isinstance(current, dict):
current = current[key]
else:
return False
return True
except (KeyError, TypeError):
return False
[docs]
def infer_type(value: Any) -> str:
"""Infer the type name of a configuration value.
Args:
value: Configuration value
Returns:
Type name as string (e.g., "str", "int", "dict", "list")
Example:
>>> infer_type("hello")
'str'
>>> infer_type(42)
'int'
>>> infer_type({"key": "value"})
'dict'
"""
type_name = type(value).__name__
# Map some common types to more descriptive names
type_map = {
"str": "str",
"int": "int",
"float": "float",
"bool": "bool",
"list": "list",
"dict": "dict",
"NoneType": "None",
}
return type_map.get(type_name, type_name)
[docs]
def get_schema_info(config: Dict[str, Any], key_path: str = "") -> Dict[str, Any]:
"""Get schema information for a configuration key or entire config.
Args:
config: Configuration dictionary
key_path: Optional dot-separated key path. If empty, returns schema for entire config.
Returns:
Dictionary with schema information (type, required, nested keys, etc.)
Example:
>>> config = {"database": {"host": "localhost", "port": 5432}}
>>> schema = get_schema_info(config, "database")
>>> schema["type"] # 'dict'
>>> schema["keys"] # ['host', 'port']
"""
if key_path:
keys = key_path.split(".")
current = config
try:
for key in keys:
if isinstance(current, dict):
current = current[key]
else:
return {"type": "unknown", "exists": False}
except (KeyError, TypeError):
return {"type": "unknown", "exists": False}
value = current
else:
value = config
schema: Dict[str, Any] = {
"type": infer_type(value),
"exists": True,
}
if isinstance(value, dict):
schema["keys"] = list(value.keys())
schema["nested"] = {k: infer_type(v) for k, v in value.items()}
elif isinstance(value, list):
schema["length"] = len(value)
if value:
schema["item_type"] = infer_type(value[0])
return schema