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)

Emacs 29.4 (Org mode 9.6.15)