"""
=======================
Configuration Utilities
=======================
A set of functions for turning model specification files into programmatic
representations of :term:`model specifications <Model Specification>` and
:term:`configurations <Configuration>`.
"""
from pathlib import Path
from typing import Any
import yaml
from layered_config_tree import ConfigurationError, LayeredConfigTree
from vivarium.framework.plugins import DEFAULT_PLUGINS
[docs]
def build_model_specification(
model_specification: str | Path | LayeredConfigTree | None = None,
component_configuration: dict[str, Any] | LayeredConfigTree | None = None,
configuration: dict[str, Any] | LayeredConfigTree | None = None,
plugin_configuration: dict[str, dict[str, dict[str, str]]]
| LayeredConfigTree
| None = None,
) -> LayeredConfigTree:
if isinstance(model_specification, (str, Path)):
validate_model_specification_file(model_specification)
source = str(model_specification)
else:
source = "user_supplied_args"
output_spec = _get_default_specification()
output_spec.update(model_specification, layer="model_override", source=source)
output_spec.components.update(
component_configuration, layer="override", source="user_supplied_args"
)
output_spec.configuration.update(
configuration, layer="override", source="user_supplied_args"
)
output_spec.plugins.update(
plugin_configuration, layer="override", source="user_supplied_args"
)
return output_spec
[docs]
def validate_model_specification_file(file_path: str | Path) -> None:
"""Ensures the provided file is a yaml file"""
file_path = Path(file_path)
if not file_path.exists():
raise ConfigurationError(
"If you provide a model specification file, it must be a file. "
f"You provided {str(file_path)}",
value_name=None,
)
if file_path.suffix not in [".yaml", ".yml"]:
raise ConfigurationError(
f"Model specification files must be in a yaml format. You provided {file_path.suffix}",
value_name=None,
)
# Attempt to load
with file_path.open() as f:
raw_spec = yaml.full_load(f)
top_keys = set(raw_spec.keys())
valid_keys = {"plugins", "components", "configuration"}
if not top_keys <= valid_keys:
raise ConfigurationError(
f"Model specification contains additional top level "
f"keys {valid_keys.difference(top_keys)}.",
value_name=None,
)
[docs]
def build_simulation_configuration() -> LayeredConfigTree:
return _get_default_specification().get_tree("configuration")
def _get_default_specification() -> LayeredConfigTree:
default_config_layers = [
"base",
"user_configs",
"component_configs",
"model_override",
"override",
]
default_metadata = {"layer": "base", "source": "vivarium_defaults"}
model_specification = LayeredConfigTree(layers=default_config_layers)
model_specification.update(DEFAULT_PLUGINS, **default_metadata)
model_specification.update({"components": {}, "configuration": {}})
user_config_path = Path("~/vivarium.yaml").expanduser()
if user_config_path.exists():
model_specification.configuration.update(user_config_path, layer="user_configs")
return model_specification