Clui - Snailz Docs

Skip to content

# Clui

Command-line interface for snailz.

Each subcommand takes options --output (output file path), --params (parameter file), and --seed (random number seed) along with command-specific parameters. If a parameter file is given, it is read first and additional parameters override its values. If a parameter file is not given, all other parameters are required.

## `cli()`

Command-line interface for snailz.

Source code in `src/snailz/clui.py`

    29
    30
    31
    @click.group()
    def cli():
        """Command-line interface for snailz."""

## `assays(ctx, baseline=None, end_date=None, mutant=None, noise=None, output=None, params=None, people=None, plate_size=None, seed=None, specimens=None, start_date=None)`

Generate assays for specimens within a date range.

Source code in `src/snailz/clui.py`

     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    @cli.command()
    @click.option(
        "--baseline",
        type=float,
        help="Baseline reading value for non-susceptible specimens (must be > 0)",
    )
    @click.option("--end-date", callback=utils.validate_date, help="End date (YYYY-MM-DD)")
    @click.option(
        "--mutant", type=float, help="Reading value for susceptible specimens (must be > 0)"
    )
    @click.option("--noise", type=float, help="Noise level for readings (must be > 0)")
    @click.option("--output", type=click.Path(), help="Path to JSON output file")
    @click.option(
        "--params", type=click.Path(exists=True), help="Path to JSON parameter file"
    )
    @click.option("--people", type=click.Path(exists=True), help="Path to people JSON file")
    @click.option("--plate-size", type=int, help="Size of assay plate (must be > 0)")
    @click.option("--seed", type=int, help="Random seed")
    @click.option(
        "--specimens", type=click.Path(exists=True), help="Path to specimens JSON file"
    )
    @click.option(
        "--start-date", callback=utils.validate_date, help="Start date (YYYY-MM-DD)"
    )
    @click.pass_context
    def assays(
        ctx,
        baseline=None,
        end_date=None,
        mutant=None,
        noise=None,
        output=None,
        params=None,
        people=None,
        plate_size=None,
        seed=None,
        specimens=None,
        start_date=None,
    ):
        """Generate assays for specimens within a date range."""
        try:
            # Load previously-generated data.
            people = utils.load_data("people", people, People)
            specimens = utils.load_data("specimens", specimens, Specimens)

            # Get parameters for assay generation
            supplied = (
                ("baseline", baseline),
                ("end_date", end_date),
                ("mutant", mutant),
                ("noise", noise),
                ("plate_size", plate_size),
                ("seed", seed),
                ("start_date", start_date),
            )
            parameters = _get_params(
                "assays",
                AssayParams,
                supplied,
                params,
                end_date=date.fromisoformat,
                start_date=date.fromisoformat,
            )
            random.seed(parameters.seed)

            # Generate assays with specimens and people
            result = assays_generate(parameters, specimens, people)
            utils.report_result(output, result)
        except Exception as e:
            utils.fail(f"Error generating assays: {str(e)}")

## `convert(input, kind, output)`

Convert JSON data to CSV format.

Converts grid, specimens, or assays data from JSON to CSV format. If output is not specified, writes to standard output.

Source code in `src/snailz/clui.py`

    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    @cli.command()
    @click.option(
        "--input",
        type=click.Path(exists=True),
        required=True,
        help="Path to input JSON file",
    )
    @click.option(
        "--kind",
        type=click.Choice(["assays", "grid", "people", "specimens"]),
        required=True,
        help="Type of data to convert",
    )
    @click.option(
        "--output",
        type=click.Path(),
        help="Path to output CSV file (should be a directory for assays)",
    )
    def convert(input, kind, output):
        """Convert JSON data to CSV format.

        Converts grid, specimens, or assays data from JSON to CSV format.
        If output is not specified, writes to standard output.
        """
        try:
            # Load the input file based on kind
            if kind == "assays":
                assays_to_csv(input, output)
            elif kind == "grid":
                data = utils.load_data("grid", input, Grid)
                content = data.to_csv()
                _write_content(output, content)
            elif kind == "people":
                data = utils.load_data("people", input, People)
                content = data.to_csv()
                _write_content(output, content)
            elif kind == "specimens":
                data = utils.load_data("specimens", input, Specimens)
                content = data.to_csv()
                _write_content(output, content)
            else:
                raise ValueError(f"unknown kind {kind}")
        except Exception as e:
            utils.fail(f"Error converting data: {str(e)}")

## `grid(ctx, depth=None, output=None, params=None, seed=None, size=None)`

Generate grid.

Source code in `src/snailz/clui.py`

    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    @cli.command()
    @click.option("--depth", type=int, help="Grid depth")
    @click.option("--output", type=click.Path(), help="Path to JSON output file")
    @click.option(
        "--params", type=click.Path(exists=True), help="Path to JSON parameter file"
    )
    @click.option("--seed", type=int, help="Random seed")
    @click.option("--size", type=int, help="Grid size")
    @click.pass_context
    def grid(
        ctx,
        depth=None,
        output=None,
        params=None,
        seed=None,
        size=None,
    ):
        """Generate grid."""
        try:
            supplied = (
                ("depth", depth),
                ("seed", seed),
                ("size", size),
            )
            parameters = _get_params("grid", GridParams, supplied, params)
            random.seed(parameters.seed)
            result = grid_generate(parameters)
            utils.report_result(output, result)
        except Exception as e:
            utils.fail(f"Error generating grid: {str(e)}")

## `mangle(seed, dir, people)`

Modify assay files by reassigning people.

This command takes assay files in a directory and reassigns the people who performed the assays using the provided seed for random number generation.

Source code in `src/snailz/clui.py`

    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    @cli.command()
    @click.option(
        "--dir",
        type=click.Path(exists=True, file_okay=False, dir_okay=True),
        required=True,
        help="Directory containing assay CSV files",
    )
    @click.option(
        "--people",
        type=click.Path(exists=True),
        required=True,
        help="Path to people.csv file",
    )
    @click.option(
        "--seed", type=int, required=True, help="Random seed for assigning people"
    )
    def mangle(seed, dir, people):
        """Modify assay files by reassigning people.

        This command takes assay files in a directory and reassigns the people
        who performed the assays using the provided seed for random number generation.
        """
        try:
            random.seed(seed)
            mangle_assays(dir, people)
        except Exception as e:
            utils.fail(f"Error mangling assays: {str(e)}")

## `people(ctx, locale=None, number=None, output=None, params=None, seed=None)`

Generate people.

Source code in `src/snailz/clui.py`

    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    @cli.command()
    @click.option("--locale", type=str, help="Locale for generating people")
    @click.option("--number", type=int, help="Number of people to generate")
    @click.option("--output", type=click.Path(), help="Path to JSON output file")
    @click.option(
        "--params", type=click.Path(exists=True), help="Path to JSON parameter file"
    )
    @click.option("--seed", type=int, help="Random seed")
    @click.pass_context
    def people(
        ctx,
        locale=None,
        number=None,
        output=None,
        params=None,
        seed=None,
    ):
        """Generate people."""
        try:
            supplied = (
                ("locale", locale),
                ("number", number),
                ("seed", seed),
            )
            parameters = _get_params("people", PeopleParams, supplied, params)
            random.seed(parameters.seed)
            result = people_generate(parameters)
            utils.report_result(output, result)
        except Exception as e:
            utils.fail(f"Error generating people: {str(e)}")

## `specimens(ctx, grid=None, length=None, max_mass=None, min_mass=None, mut_scale=None, mutations=None, number=None, output=None, params=None, seed=None)`

Generate specimens.

Source code in `src/snailz/clui.py`

    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    @cli.command()
    @click.option("--grid", type=str, help="Path to grid JSON file")
    @click.option("--length", type=int, help="Length of each genome")
    @click.option("--max-mass", type=float, help="Maximum specimen mass")
    @click.option("--min-mass", type=float, help="Minimum specimen mass")
    @click.option("--mut-scale", type=float, help="Mutation scaling factor")
    @click.option("--mutations", type=int, help="Number of possible mutation loci")
    @click.option("--number", type=int, help="Number of specimens to generate")
    @click.option("--output", type=click.Path(), help="Path to JSON output file")
    @click.option(
        "--params", type=click.Path(exists=True), help="Path to JSON parameter file"
    )
    @click.option("--seed", type=int, help="Random seed")
    @click.pass_context
    def specimens(
        ctx,
        grid=None,
        length=None,
        max_mass=None,
        min_mass=None,
        mut_scale=None,
        mutations=None,
        number=None,
        output=None,
        params=None,
        seed=None,
    ):
        """Generate specimens."""
        try:
            # Load previously-generated data.
            grid = utils.load_data("grid", grid, Grid)

            # Get parameters for specimen generation.
            supplied = (
                ("length", length),
                ("max_mass", max_mass),
                ("min_mass", min_mass),
                ("mut_scale", mut_scale),
                ("mutations", mutations),
                ("number", number),
                ("seed", seed),
            )
            parameters = _get_params("specimens", SpecimenParams, supplied, params)
            random.seed(parameters.seed)
            result = specimens_generate(parameters, grid)
            utils.report_result(output, result)
        except Exception as e:
            utils.fail(f"Error generating specimens: {str(e)}")

## `_get_params(caller, param_class, supplied, params_file, **converters)`

Get and check parameter values.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`caller` | `str` | Name of the calling function for error messages | required
`param_class` | `BaseModel` | Pydantic parameter class | required
`supplied` | `tuple[tuple]` | Tuple of (name, value) pairs for CLI parameters | required
`params_file` | `str | None` | Path to JSON parameter file, or None | required
`converters` | `dict[str, callable]` | Optional converters for specific parameter values | `{}`

Returns:

Type | Description
--- | ---
`BaseModel` | An instance of the param_class with validated parameters

Source code in `src/snailz/clui.py`

    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    def _get_params(
        caller: str,
        param_class: BaseModel,
        supplied: tuple[tuple],
        params_file: str | None,
        **converters: dict[str, callable],
    ) -> BaseModel:
        """Get and check parameter values.

        Parameters:
            caller: Name of the calling function for error messages
            param_class: Pydantic parameter class
            supplied: Tuple of (name, value) pairs for CLI parameters
            params_file: Path to JSON parameter file, or None
            converters: Optional converters for specific parameter values

        Returns:
            An instance of the param_class with validated parameters
        """
        # Read parameter file if given.
        result = {}
        if params_file:
            with open(params_file, "r") as f:
                result = json.load(f)
                for name, conv in converters.items():
                    if name in result:
                        result[name] = conv(result[name])

        # Override with extra parameters
        for name, value in supplied:
            if value is not None:
                result[name] = value

        # Create and return parameter object (does validation)
        return param_class(**result)

## `_write_content(output, content)`

Write content to standard output or a file.

Source code in `src/snailz/clui.py`

    332
    333
    334
    335
    336
    337
    338
    def _write_content(output: str | None, content: str) -> None:
        """Write content to standard output or a file."""
        if output:
            with open(output, "w") as writer:
                writer.write(content)
        else:
            print(content, end="")