52 lines
1.6 KiB
Python

"""Basic utilities for structuring experiment configurations based on Pydantic schemas."""
import os
from abc import abstractmethod
from io import TextIOBase
from typing import Type, Dict, TextIO, Callable, cast
import yaml
from typing_extensions import Generic, overload
from benchmarks.core.experiments.experiments import TExperiment
from benchmarks.core.pydantic import SnakeCaseModel
class ExperimentBuilder(SnakeCaseModel, Generic[TExperiment]):
""":class:`ExperimentBuilders` can build real :class:`Experiment`s out of :class:`ConfigModel`s. """
@abstractmethod
def build(self) -> TExperiment:
pass
class ConfigParser:
"""
:class:`ConfigParser` is a utility class to parse configuration files into :class:`ExperimentBuilder`s.
Currently, each :class:`ExperimentBuilder` can appear at most once in the config file.
"""
def __init__(self):
self.experiment_types = {}
def register(self, root: Type[ExperimentBuilder[TExperiment]]):
self.experiment_types[root.alias()] = root
@overload
def parse(self, data: dict) -> Dict[str, ExperimentBuilder[TExperiment]]:
...
@overload
def parse(self, data: TextIO) -> Dict[str, ExperimentBuilder[TExperiment]]:
...
def parse(self, data: dict | TextIO) -> Dict[str, ExperimentBuilder[TExperiment]]:
if isinstance(data, TextIOBase):
entries = yaml.safe_load(os.path.expandvars(data.read()))
else:
entries = data
return {
tag: self.experiment_types[tag].model_validate(config)
for tag, config in entries.items()
}