Inline types¶
Inline types allows complex class structures to be parsed as string. First, we will motivate the reader by a simple example:
import aclick, click, typing as t
class ModelA:
def __init__(self, name: str, n_layers: int):
self.name = name
self.n_layers = n_layers
def train(self):
click.echo(f'Training model A with {self.n_layers} layers.')
class ModelB(ModelA):
def __init__(self, *args, n_blocks: int = 5, **kwargs):
super().__init__(*args, **kwargs)
self.n_blocks = n_blocks
def train(self):
click.echo(f'Training model B with {self.n_blocks} blocks.')
@aclick.command(hierarchical=False)
def main(model: t.Union[ModelA, ModelB]):
model.train()
if __name__ == '__main__':
main()
We can invoke the script as follows:
$ python hello.py --model 'model-b(test, n-layers=2)'
Training model B with 5 blocks.
Note, that by default complex classes will be parsed using hierarchical parsing.
If you want all complex class types to be parsed using inline parsing,
simply turn off hierarchical parsing by passing hierarchical = False
to the
class constructor. If you want to keep using hierarchical parsing, but
use inline parsing for one type only, use the aclick.default_from_str()
decorator (or implement from_str()
and __str__()
methods).
Custom classes¶
In inline parsing, user-defined classes can be naturally parsed with the following structure:
class-name(arg1, ..., argM, named-arg-1=value1, ..., named-arg-n=valueN)
Where the class ClassName
accepts M
positional arguments and all named
arguments as well. Each argument value can itself be a complex structure of classes:
class Schedule:
def __init__(self, type: str, constants: t.Optional[t.List[float]] = None):
self.type = type
self.constants = constants
def __repr__(self):
return f'Schedule("{self.type}", constants={self.constants})'
@dataclass
class Model:
'''
:param learning_rate: Learning rate
:param num_features: Number of features
'''
learning_rate: Schedule
num_features: int = 5
@aclick.command(hierarchical=False)
def train(model: Model):
print(model)
This can be invoked as follows:
$ python train.py --model 'model(schedule("schedule 1", constants=[1.5, 2.0]))'
Model(learning_rate=Schedule("schedule 1", constants=[1.5, 2.0]), num_features=5)
Union of classes¶
We can specify multiple possible types for a parameter by using t.Union type. The specified type will then be constructed from the passed arguments. This scenario is illustrated in the following example:
@dataclass
class ModelA:
learning_rate: float = 0.1
num_features: int = 5
@dataclass
class ModelB:
learning_rate: float = 0.2
num_layers: int = 10
@aclick.command(hierarchical=False)
def train(model: t.Union[ModelA, ModelB]):
print(f'Training {model}')
The output will look as follows:
$ python train.py --model 'model-b(num-layers=3)'
Training ModelB(learning_rate=0.2, num_layers=3)
Container types¶
The inline class representation natively supports list, tuple, dict, and OrderedDict.
list or ‘tuple’ are represented as a comma-separated list of items inside of square brackets or parentheses:
["string val1", 'string val2', ..., string-val-N]
or:
("string val1", 'string val2', ..., string-val-N)
dict or OrderedDict are represented as a comma-separated list of pairs
key=value
inside of curly brackets:{key_1="string val1", key_2='string val2', ..., key_n=string-val-N}
We demonstrate all types in the following example:
@dataclass
class Example:
prop_list: t.List[str]
prop_tuple: t.Tuple[int, float]
prop_dict: t.Dict[str, t.Optional[int]]
@aclick.command(hierarchical=False)
def main(x: Example):
print(x)
With the corresponding output:
$ python main.py --x 'example(["str 1", "str 2"], (10, 3.14), {p1=1, p2=2})'
Example(prop_list=['str 1', 'str 2'], prop_tuple=(10, 3.14), prop_dict={'p1': 1, 'p2': 2})
Showing class examples¶
For the aclick command, examples can be shown for each inline type parameter. In order to enable examples, set the parameter num_inline_examples_help to a number greater to 0 or to -1. This number limits the number of shown examples. Note that the examples are generated by expanding all union types to all possible values and, therefore, can be a large number for classes with lots of Union properties. Therefore, use the value of -1 with care.
Also, by default if a value of an inline-typed parameter is incorrect, at most three examples are shown in the error message.
We show the inline type examples in the following code:
@dataclass
class Example:
prop_list: t.List[str]
prop_tuple: t.Tuple[int, float]
prop_dict: t.Dict[str, t.Optional[int]]
@aclick.command(hierarchical=False, num_inline_examples_help=-1)
def main(x: Example):
print(x)
With the corresponding output:
$ python main.py --help
Usage: main.py [OPTIONS]
Options:
--help Show this message and exit.
--x CLASS UNION Examples of allowed values:
example(
prop-list=({str},
...),
prop-tuple=({int},
{float}),
prop-dict={{str}={int},
...})
example(
prop-list=({str},
...),
prop-tuple=({int},
{float}),
prop-dict={{str}=None,
...})
[required]