hydra-zen (Python)
Consider a class with a hierarchical interface:
@dataclass class Character: name: str level: int inventory: object def __repr__(self): return f"{self.name}, lvl: {self.level}, has: {self.invetory}" def inventory(gold: int, weapon: str, costume: str): return {"gold":gold, "weapon":weapon, "costume":costume}
Note that the interface for Character
is hierarchical in nature. To make a configurable interface:
from hydra_zen import make_custom_builds_fn builds = make_custom_builds_fn(population_full_signature=True) # Configurable interface for inventory InventoryConf = builds(inventory) starter_gear = InventoryConf(gold=10, weapon="stick", consume="tunic") # Configurable interface for Character CharConf = builds(Character, inventory=starter_gear)
Note that the configurations can be nested, e.g. starter_gear
is used in CharConf
.
The top level configuration is then:
from hydra_zen import make_config Config = make_config(player=CharConf)
Now, we want a task function that uses the configuration:
from hydra_zen import instantiate Config = make_config(player=CharConf) def task_function(cfg: Config): # Build the actual object based on the configuration obj = instantiate(cfg) # The instantiated player player = obj.player # Can do whatever that uses the instantiation of the configuration return player
We can then launch programs which take the task functions and the configurations:
from hydra_zen import launch job = launch(Config, task_function, ["player.name=frodo"]) job = launch(Config, task_function, ["player.name=frodo", "player.level=2"]) job = launch(Config, task_function, ["player.name=frodo", "player.inventory.costume=robe"])
Note how we can access nested functions this way.
So, we need a configurable interface, here created via Config = make_config(player=CharConf)
, and a function that is configured by that interface, here task_function
, which runs whichever code we want to run.
To add a command line interface:
import hydra from hydra.core.config_store import ConfigStore Config = make_config(player=CharConf) cs = ConfigStore.instance() cs.store(name="my_app", node=Config) @hydra.main(config_path=None, config_name="my_app") def task_function(cfg: Config): # ... return if __name__ == "__main__": task_function()
We can register specific configurations per groups:
InventoryConf = builds(inventory) starter_gear = InventoryConf(gold=10, weapon="stick", costume="tunic") cs.store(group="player/inventory", name="starter", node=starter_gear) CharConf = builds(Character, inventory=starter_gear) alice_conf = CharConf(name="alice", level=42, inventory=InventoryConf(costume="cape", weapon="flute", gold=52)) cs.store(group="player", name="base", node=CharConf) cs.store(group="player", name="alice", node=alice_conf)