Source code for b2luigi.cli.parameter_generator
"""Parameter generator classes for automatic WrapperTask expansion.
:Description: Provides :class:`ParameterGenerator` and
:class:`ZippedParameterGenerator` for use in ``parameters.py`` ``config``
dicts. When detected by the CLI, these trigger automatic expansion into
a :class:`b2luigi.WrapperTask` covering all parameter combinations.
"""
from typing import Any
from b2luigi.cli.errors import CliUserError
[docs]
class ParameterGenerator:
"""Declare a list of values for a single parameter to be expanded via cartesian product.
Place as a value in the ``parameters.py`` ``config`` dict. Multiple
``ParameterGenerator`` values in the same config are crossed with each
other (and with any :class:`ZippedParameterGenerator` groups) to produce
all combinations.
Example::
from b2luigi import ParameterGenerator
config = {"split": ParameterGenerator([1, 2, 3])}
:param values: Non-empty list of concrete parameter values.
:type values: list[Any]
:raises CliUserError: If ``values`` is empty.
"""
def __init__(self, values: list[Any]) -> None:
if not values:
raise CliUserError("ParameterGenerator requires at least one value.")
self.values: list[Any] = values
[docs]
class ZippedParameterGenerator:
"""Declare multiple parameters to be expanded in lockstep (zip).
Place as a value in the ``parameters.py`` ``config`` dict under any
sentinel key (the key is ignored; parameter names come from keyword
arguments). Multiple ``ZippedParameterGenerator`` instances are
cartesian-crossed with each other and with any :class:`ParameterGenerator`
values.
Example::
from b2luigi import ZippedParameterGenerator
config = {
"zipped": ZippedParameterGenerator(alpha=[1, 2], beta=["a", "b"]),
}
# expands to: (alpha=1, beta="a"), (alpha=2, beta="b")
:param kwargs: Each keyword argument names a task parameter; its value is
the list of concrete values for that parameter. All lists must have the
same length.
:type kwargs: dict[str, list]
:raises CliUserError: If no keyword arguments are given, list lengths differ, or all lists are empty.
"""
def __init__(self, **kwargs: list[Any]) -> None:
if not kwargs:
raise CliUserError("ZippedParameterGenerator requires at least one keyword argument.")
lengths = {k: len(v) for k, v in kwargs.items()}
if len(set(lengths.values())) > 1:
raise CliUserError(f"ZippedParameterGenerator: all lists must have the same length, got {lengths}.")
if next(iter(lengths.values()), 1) == 0:
raise CliUserError("ZippedParameterGenerator requires non-empty lists.")
self.pairs: dict[str, list] = dict(kwargs)