Command Line Config Overrides

Prefixing Overrides

Command line config overrides can sometimes clash. In this example, we have two configs, both of which define the same x attribute:

main.py
from dataclasses import dataclass

import coma

@dataclass
class Config1:
    x: int

@dataclass
class Config2:
    x: int

if __name__ == "__main__":
    coma.register("multiply", lambda c1, c2: print(c1.x * c2.x), Config1, Config2)
    coma.wake()

By default, coma enables the presence of x on the command line to override both configs at once:

$ python main.py multiply x=3
9

This lets multiply is essentially act as square. To prevent this, we can override a specific config by prefixing the override with its identifier:

$ python main.py multiply config1:x=3 config2:x=4
12

Note

See here for an alternative way to prevent these clashes.

By default, coma also supports prefix abbreviations: A prefix can be abbreviated as long as the abbreviation is unambiguous (i.e., matches only one config identifier):

main.py
from dataclasses import dataclass

import coma

@dataclass
class Config1:
    x: int

@dataclass
class Config2:
    x: int

if __name__ == "__main__":
    coma.register("multiply", lambda c1, c2: print(c1.x * c2.x),
                  some_long_identifier=Config1, another_long_identifier=Config2)
    coma.wake()

This is enables convenient shorthands for command line overrides:

$ python main.py multiply some_long_identifier:x=3 another_long_identifier:x=4
12
$ python main.py multiply s:x=3 a:x=4
12

Capturing Superfluous Overrides

For rapid prototyping, it is often beneficial to capture superfluous command line overrides. These can then be transferred to a proper config object once the codebase is solidifying. In this example, we name this superfluous config extras:

main.py
import coma


if __name__ == "__main__":
    coma.initiate(
        extras={},
        init_hook=coma.hooks.init_hook.positional_factory("extras"),
        post_run_hook=coma.hooks.hook(
            lambda configs: print("extras =", configs["extras"])
        ),
    )
    coma.register("greet", lambda: print("Hello World!"))
    coma.wake()

This works because, as a plain dict, extras will accept any non-prefixed arguments given on the command line:

$ python main.py greet
Hello World!
extras = {}
$ python main.py greet foo=1 bar=baz
Hello World!
extras = {'foo': 1, 'bar': 'baz'}

Note

We redefined the init_hook using positional_factory(). This factory skips the given config identifiers when instantiating the command. Without this hook redefinition, the lambda defining the command would need to accept 1 positional argument to accommodate extras.

Note

We added a new post_run_hook. This hook is simply added to print out the attributes of the extras config after the command is executed.