looper 1.7.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,293 @@
1
+ """
2
+ Argument definitions via a thin wrapper around `pydantic.fields.FieldInfo`
3
+ """
4
+
5
+ import enum
6
+ import os
7
+ from copy import copy
8
+ from typing import Any, List
9
+
10
+ import pydantic.v1 as pydantic
11
+
12
+
13
+ class Argument(pydantic.fields.FieldInfo):
14
+ """
15
+ CLI argument / flag definition
16
+
17
+ This class is designed to define CLI arguments or flags. It leverages
18
+ Pydantic for data validation and serves as a source of truth for multiple
19
+ interfaces, including a CLI.
20
+
21
+ Naively, one would think one could just subclass `pydantic.Field`,
22
+ but actually `pydantic.Field` is a function, and not a class.
23
+ `pydantic.Field()` returns a validated `FieldInfo` instance,
24
+ so we instead subclass `FieldInfo` directly and validate it in the
25
+ constructor.
26
+
27
+ :param str name: argument name, e.g. "ignore-args"
28
+ :param Any default: a tuple of the form (type, default_value). If the
29
+ default value is `...` (Ellipsis), then the argument is required.
30
+ :param str description: argument description, which will appear as the
31
+ help text for this argument
32
+ :param dict kwargs: additional keyword arguments supported by
33
+ `FieldInfo`. These are passed along as they are.
34
+ """
35
+
36
+ def __init__(
37
+ self, name: str, default: Any, description: str, alias: str = None, **kwargs
38
+ ) -> None:
39
+ self._name = name
40
+ super().__init__(
41
+ default=default, description=description, alias=alias, **kwargs
42
+ )
43
+ self._validate()
44
+
45
+ @property
46
+ def name(self):
47
+ """
48
+ Argument name as used in the CLI, e.g. "ignore-args"
49
+ """
50
+ return self._name
51
+
52
+ def with_reduced_default(self) -> pydantic.fields.FieldInfo:
53
+ """
54
+ Convert to a `FieldInfo` instance with reduced default value
55
+
56
+ Returns a copy of an instance, but with the `default` attribute
57
+ replaced by only the default value, without the type information.
58
+ This is required when using an instance in a direct `pydantic`
59
+ model definition, instead of creating a model dynamically using
60
+ `pydantic.create_model`.
61
+
62
+ TODO: this is due to this issue:
63
+ https://github.com/pydantic/pydantic/issues/2248#issuecomment-757448447
64
+ and it's a bit tedious.
65
+
66
+ """
67
+ c = copy(self)
68
+ _, default_value = self.default
69
+ c.default = default_value
70
+ return c
71
+
72
+
73
+ class ArgumentEnum(enum.Enum):
74
+ """
75
+ Lists all available arguments
76
+
77
+ Having a single "repository" of arguments allows us to re-use them easily across different commands.
78
+
79
+ TODO: not sure whether an enum is the ideal data structure for that
80
+ """
81
+
82
+ IGNORE_FLAGS = Argument(
83
+ name="ignore_flags",
84
+ alias="-i",
85
+ default=(bool, False),
86
+ description="Ignore run status flags",
87
+ )
88
+ FORCE_YES = Argument(
89
+ name="force_yes",
90
+ alias="-f",
91
+ default=(bool, False),
92
+ description="Provide upfront confirmation of destruction intent, to skip console query. Default=False",
93
+ )
94
+
95
+ DESCRIBE_CODES = Argument(
96
+ name="describe_codes",
97
+ default=(bool, False),
98
+ description="Show status codes description. Default=False",
99
+ )
100
+
101
+ ITEMIZED = Argument(
102
+ name="itemized",
103
+ default=(bool, False),
104
+ description="Show detailed overview of sample statuses. Default=False",
105
+ )
106
+
107
+ FLAGS = Argument(
108
+ name="flags",
109
+ alias="-f",
110
+ default=(List, []),
111
+ description="Only check samples based on these status flags.",
112
+ )
113
+
114
+ TIME_DELAY = Argument(
115
+ name="time_delay",
116
+ alias="-t",
117
+ default=(int, 0),
118
+ description="Time delay in seconds between job submissions (min: 0, max: 30)",
119
+ )
120
+ DRY_RUN = Argument(
121
+ name="dry_run",
122
+ alias="-d",
123
+ default=(bool, False),
124
+ description="Don't actually submit jobs",
125
+ )
126
+ COMMAND_EXTRA = Argument(
127
+ name="command_extra",
128
+ alias="-x",
129
+ default=(str, ""),
130
+ description="String to append to every command",
131
+ )
132
+ COMMAND_EXTRA_OVERRIDE = Argument(
133
+ name="command_extra_override",
134
+ alias="-y",
135
+ default=(str, ""),
136
+ description="Same as command-extra, but overrides values in PEP",
137
+ )
138
+ LUMP = Argument(
139
+ name="lump",
140
+ alias="-u",
141
+ default=(float, None),
142
+ description="Total input file size (GB) to batch into one job",
143
+ )
144
+ LUMPN = Argument(
145
+ name="lump_n",
146
+ alias="-n",
147
+ default=(int, None),
148
+ description="Number of commands to batch into one job",
149
+ )
150
+ LUMPJ = Argument(
151
+ name="lump_j",
152
+ alias="-j",
153
+ default=(int, None),
154
+ description="Lump samples into number of jobs.",
155
+ )
156
+ LIMIT = Argument(
157
+ name="limit", alias="-l", default=(int, None), description="Limit to n samples"
158
+ )
159
+ SKIP = Argument(
160
+ name="skip",
161
+ alias="-k",
162
+ default=(int, None),
163
+ description="Skip samples by numerical index",
164
+ )
165
+ CONFIG = Argument(
166
+ name="config",
167
+ alias="-c",
168
+ default=(str, None),
169
+ description="Looper configuration file (YAML)",
170
+ )
171
+ SETTINGS = Argument(
172
+ name="settings",
173
+ default=(str, ""),
174
+ description="Path to a YAML settings file with compute settings",
175
+ )
176
+ PEP_CONFIG = Argument(
177
+ name="pep_config",
178
+ default=(str, None),
179
+ description="PEP configuration file",
180
+ )
181
+ OUTPUT_DIR = Argument(
182
+ name="output_dir",
183
+ alias="-o",
184
+ default=(str, None),
185
+ description="Output directory",
186
+ )
187
+ REPORT_OUTPUT_DIR = Argument(
188
+ name="report_dir",
189
+ alias="-r",
190
+ default=(str, None),
191
+ description="Set location for looper report and looper table outputs",
192
+ )
193
+
194
+ GENERIC = Argument(
195
+ name="generic",
196
+ alias="-g",
197
+ default=(bool, False),
198
+ description="Use generic looper config?",
199
+ )
200
+
201
+ SAMPLE_PIPELINE_INTERFACES = Argument(
202
+ name="sample_pipeline_interfaces",
203
+ alias="-S",
204
+ default=(List, []),
205
+ description="Paths to looper sample pipeline interfaces",
206
+ )
207
+ PROJECT_PIPELINE_INTERFACES = Argument(
208
+ name="project_pipeline_interfaces",
209
+ alias="-P",
210
+ default=(List, []),
211
+ description="Paths to looper project pipeline interfaces",
212
+ )
213
+ AMEND = Argument(
214
+ name="amend", default=(List, []), description="List of amendments to activate"
215
+ )
216
+ SEL_ATTR = Argument(
217
+ name="sel_attr",
218
+ default=(str, "toggle"),
219
+ description="Attribute for sample exclusion OR inclusion",
220
+ )
221
+ SEL_INCL = Argument(
222
+ name="sel_incl",
223
+ default=(List, []),
224
+ description="Include only samples with these values",
225
+ )
226
+ SEL_EXCL = Argument(
227
+ name="sel_excl",
228
+ default=(str, ""),
229
+ description="Exclude samples with these values",
230
+ )
231
+ SEL_FLAG = Argument(
232
+ name="sel_flag", default=(List, []), description="Sample selection flag"
233
+ )
234
+ EXC_FLAG = Argument(
235
+ name="exc_flag", default=(List, []), description="Sample exclusion flag"
236
+ )
237
+ SKIP_FILE_CHECKS = Argument(
238
+ name="skip_file_checks",
239
+ alias="-f",
240
+ default=(bool, False),
241
+ description="Do not perform input file checks",
242
+ )
243
+ PACKAGE = Argument(
244
+ name="package",
245
+ alias="-p",
246
+ default=(str, None),
247
+ description="Name of computing resource package to use",
248
+ )
249
+ COMPUTE = Argument(
250
+ name="compute",
251
+ default=(List, []),
252
+ description="List of key-value pairs (k1=v1)",
253
+ )
254
+ DIVVY = Argument(
255
+ name="divvy",
256
+ default=(str, os.getenv("DIVCFG", None)),
257
+ description=(
258
+ "Path to divvy configuration file. Default=$DIVCFG env "
259
+ "variable. Currently: {}".format(os.getenv("DIVCFG") or "not set")
260
+ ),
261
+ )
262
+ # Arguments for logger compatible with logmuse
263
+ SILENT = Argument(
264
+ name="silent", default=(bool, False), description="Whether to silence logging"
265
+ )
266
+ VERBOSITY = Argument(
267
+ name="verbosity",
268
+ default=(int, None),
269
+ description="Alternate mode of expression for logging level that better "
270
+ "accords with intuition about how to convey this.",
271
+ )
272
+ LOGDEV = Argument(
273
+ name="logdev",
274
+ default=(bool, False),
275
+ description="Whether to log in development mode; possibly among other "
276
+ "behavioral changes to logs handling, use a more information-rich "
277
+ "message format template.",
278
+ )
279
+ PIPESTAT = Argument(
280
+ name="pipestat",
281
+ default=(str, None),
282
+ description="Path to pipestat files.",
283
+ )
284
+ PORTABLE = Argument(
285
+ name="portable",
286
+ default=(bool, False),
287
+ description="Makes html report portable.",
288
+ )
289
+ PROJECT_LEVEL = Argument(
290
+ name="project",
291
+ default=(bool, False),
292
+ description="Is this command executed for project-level?",
293
+ )
@@ -0,0 +1,335 @@
1
+ """
2
+ `pydantic` models for `looper` commands and a wrapper class.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import List, Optional, Type, Union
7
+
8
+ import pydantic.v1 as pydantic
9
+
10
+ from ..const import MESSAGE_BY_SUBCOMMAND
11
+ from .arguments import Argument, ArgumentEnum
12
+ from pydantic_argparse import ArgumentParser
13
+
14
+
15
+ @dataclass
16
+ class Command:
17
+ """
18
+ Representation of a command
19
+
20
+ :param str name: command name
21
+ :param str description: command description
22
+ :param list[Argument] arguments: list of arguments supported by this command
23
+ """
24
+
25
+ name: str
26
+ description: str
27
+ arguments: List[Argument]
28
+
29
+ def create_model(self) -> Type[pydantic.BaseModel]:
30
+ """
31
+ Creates a `pydantic` model for this command
32
+ """
33
+ arguments = dict()
34
+ for arg in self.arguments:
35
+ # These gymnastics are necessary because of
36
+ # https://github.com/pydantic/pydantic/issues/2248#issuecomment-757448447
37
+ arg_type, arg_default_value = arg.default
38
+ arguments[arg.name] = (
39
+ arg_type,
40
+ pydantic.Field(arg_default_value, description=arg.description),
41
+ )
42
+ return pydantic.create_model(self.name, **arguments)
43
+
44
+
45
+ SHARED_ARGUMENTS = [
46
+ ArgumentEnum.SETTINGS.value,
47
+ ArgumentEnum.EXC_FLAG.value,
48
+ ArgumentEnum.SEL_FLAG.value,
49
+ ArgumentEnum.SEL_ATTR.value,
50
+ ArgumentEnum.SEL_INCL.value,
51
+ ArgumentEnum.SEL_EXCL.value,
52
+ ArgumentEnum.LIMIT.value,
53
+ ArgumentEnum.SKIP.value,
54
+ ArgumentEnum.PEP_CONFIG.value,
55
+ ArgumentEnum.OUTPUT_DIR.value,
56
+ ArgumentEnum.CONFIG.value,
57
+ ArgumentEnum.SAMPLE_PIPELINE_INTERFACES.value,
58
+ ArgumentEnum.PROJECT_PIPELINE_INTERFACES.value,
59
+ ArgumentEnum.PIPESTAT.value,
60
+ ArgumentEnum.SETTINGS.value,
61
+ ArgumentEnum.AMEND.value,
62
+ ArgumentEnum.PROJECT_LEVEL.value,
63
+ ]
64
+
65
+ RunParser = Command(
66
+ "run",
67
+ MESSAGE_BY_SUBCOMMAND["run"],
68
+ [
69
+ ArgumentEnum.IGNORE_FLAGS.value,
70
+ ArgumentEnum.TIME_DELAY.value,
71
+ ArgumentEnum.DRY_RUN.value,
72
+ ArgumentEnum.COMMAND_EXTRA.value,
73
+ ArgumentEnum.COMMAND_EXTRA_OVERRIDE.value,
74
+ ArgumentEnum.LUMP.value,
75
+ ArgumentEnum.LUMPN.value,
76
+ ArgumentEnum.LUMPJ.value,
77
+ ArgumentEnum.DIVVY.value,
78
+ ArgumentEnum.SKIP_FILE_CHECKS.value,
79
+ ArgumentEnum.COMPUTE.value,
80
+ ArgumentEnum.PACKAGE.value,
81
+ ],
82
+ )
83
+
84
+ # RERUN
85
+ RerunParser = Command(
86
+ "rerun",
87
+ MESSAGE_BY_SUBCOMMAND["rerun"],
88
+ [
89
+ ArgumentEnum.IGNORE_FLAGS.value,
90
+ ArgumentEnum.TIME_DELAY.value,
91
+ ArgumentEnum.DRY_RUN.value,
92
+ ArgumentEnum.COMMAND_EXTRA.value,
93
+ ArgumentEnum.COMMAND_EXTRA_OVERRIDE.value,
94
+ ArgumentEnum.LUMP.value,
95
+ ArgumentEnum.LUMPN.value,
96
+ ArgumentEnum.LUMPJ.value,
97
+ ArgumentEnum.DIVVY.value,
98
+ ArgumentEnum.SKIP_FILE_CHECKS.value,
99
+ ArgumentEnum.COMPUTE.value,
100
+ ArgumentEnum.PACKAGE.value,
101
+ ],
102
+ )
103
+
104
+ # RUNP
105
+ RunProjectParser = Command(
106
+ "runp",
107
+ MESSAGE_BY_SUBCOMMAND["runp"],
108
+ [
109
+ ArgumentEnum.IGNORE_FLAGS.value,
110
+ ArgumentEnum.TIME_DELAY.value,
111
+ ArgumentEnum.DRY_RUN.value,
112
+ ArgumentEnum.COMMAND_EXTRA.value,
113
+ ArgumentEnum.COMMAND_EXTRA_OVERRIDE.value,
114
+ ArgumentEnum.LUMP.value,
115
+ ArgumentEnum.LUMPN.value,
116
+ ArgumentEnum.DIVVY.value,
117
+ ArgumentEnum.SKIP_FILE_CHECKS.value,
118
+ ArgumentEnum.COMPUTE.value,
119
+ ArgumentEnum.PACKAGE.value,
120
+ ],
121
+ )
122
+
123
+ # TABLE
124
+ TableParser = Command(
125
+ "table",
126
+ MESSAGE_BY_SUBCOMMAND["table"],
127
+ [
128
+ ArgumentEnum.REPORT_OUTPUT_DIR.value,
129
+ ],
130
+ )
131
+
132
+
133
+ # REPORT
134
+ ReportParser = Command(
135
+ "report",
136
+ MESSAGE_BY_SUBCOMMAND["report"],
137
+ [
138
+ ArgumentEnum.PORTABLE.value,
139
+ ArgumentEnum.REPORT_OUTPUT_DIR.value,
140
+ ],
141
+ )
142
+
143
+ # DESTROY
144
+ DestroyParser = Command(
145
+ "destroy",
146
+ MESSAGE_BY_SUBCOMMAND["destroy"],
147
+ [
148
+ ArgumentEnum.DRY_RUN.value,
149
+ ArgumentEnum.FORCE_YES.value,
150
+ ],
151
+ )
152
+
153
+ # CHECK
154
+ CheckParser = Command(
155
+ "check",
156
+ MESSAGE_BY_SUBCOMMAND["check"],
157
+ [
158
+ ArgumentEnum.DESCRIBE_CODES.value,
159
+ ArgumentEnum.ITEMIZED.value,
160
+ ArgumentEnum.FLAGS.value,
161
+ ],
162
+ )
163
+
164
+ # CLEAN
165
+ CleanParser = Command(
166
+ "clean",
167
+ MESSAGE_BY_SUBCOMMAND["clean"],
168
+ [
169
+ ArgumentEnum.DRY_RUN.value,
170
+ ArgumentEnum.FORCE_YES.value,
171
+ ],
172
+ )
173
+
174
+ # INSPECT
175
+ InspectParser = Command(
176
+ "inspect",
177
+ MESSAGE_BY_SUBCOMMAND["inspect"],
178
+ [],
179
+ )
180
+
181
+
182
+ # INIT
183
+ InitParser = Command(
184
+ "init",
185
+ MESSAGE_BY_SUBCOMMAND["init"],
186
+ [
187
+ # Original command has force flag which is technically a different flag, but we should just use FORCE_YES
188
+ ArgumentEnum.FORCE_YES.value,
189
+ ArgumentEnum.OUTPUT_DIR.value,
190
+ ArgumentEnum.PEP_CONFIG.value,
191
+ ArgumentEnum.SAMPLE_PIPELINE_INTERFACES.value,
192
+ ArgumentEnum.PROJECT_PIPELINE_INTERFACES.value,
193
+ ArgumentEnum.GENERIC.value,
194
+ ],
195
+ )
196
+
197
+
198
+ # INIT-PIFACE
199
+ InitPifaceParser = Command(
200
+ "init_piface",
201
+ MESSAGE_BY_SUBCOMMAND["init-piface"],
202
+ [],
203
+ )
204
+
205
+
206
+ # LINK
207
+ LinkParser = Command(
208
+ "link",
209
+ MESSAGE_BY_SUBCOMMAND["link"],
210
+ [],
211
+ )
212
+
213
+
214
+ # Add shared arguments for all commands that use them
215
+ for arg in SHARED_ARGUMENTS:
216
+ RunParser.arguments.append(arg)
217
+ RerunParser.arguments.append(arg)
218
+ RunProjectParser.arguments.append(arg)
219
+ ReportParser.arguments.append(arg)
220
+ DestroyParser.arguments.append(arg)
221
+ CheckParser.arguments.append(arg)
222
+ CleanParser.arguments.append(arg)
223
+ TableParser.arguments.append(arg)
224
+ LinkParser.arguments.append(arg)
225
+ InspectParser.arguments.append(arg)
226
+
227
+ # Create all Models
228
+ RunParserModel = RunParser.create_model()
229
+ RerunParserModel = RerunParser.create_model()
230
+ RunProjectParserModel = RunProjectParser.create_model()
231
+ ReportParserModel = ReportParser.create_model()
232
+ DestroyParserModel = DestroyParser.create_model()
233
+ CheckParserModel = CheckParser.create_model()
234
+ CleanParserModel = CleanParser.create_model()
235
+ TableParserModel = TableParser.create_model()
236
+ LinkParserModel = LinkParser.create_model()
237
+ InspectParserModel = InspectParser.create_model()
238
+ InitParserModel = InitParser.create_model()
239
+ InitPifaceParserModel = InitPifaceParser.create_model()
240
+
241
+
242
+ def add_short_arguments(
243
+ parser: ArgumentParser, argument_enums: Type[ArgumentEnum]
244
+ ) -> ArgumentParser:
245
+ """
246
+ This function takes a parser object created under pydantic argparse and adds the short arguments AFTER the initial creation.
247
+ This is a workaround as pydantic-argparse does not currently support this during initial parser creation.
248
+
249
+ :param ArgumentParser parser: parser before adding short arguments
250
+ :param Type[ArgumentEnum] argument_enums: enumeration of arguments that contain names and aliases
251
+ :return ArgumentParser parser: parser after short arguments have been added
252
+ """
253
+
254
+ for cmd in parser._subcommands.choices.keys():
255
+
256
+ for argument_enum in list(argument_enums):
257
+ # First check there is an alias for the argument otherwise skip
258
+ if argument_enum.value.alias:
259
+ short_key = argument_enum.value.alias
260
+ long_key = "--" + argument_enum.value.name.replace(
261
+ "_", "-"
262
+ ) # We must do this because the ArgumentEnum names are transformed during parser creation
263
+ if long_key in parser._subcommands.choices[cmd]._option_string_actions:
264
+ argument = parser._subcommands.choices[cmd]._option_string_actions[
265
+ long_key
266
+ ]
267
+ argument.option_strings = (short_key, long_key)
268
+ parser._subcommands.choices[cmd]._option_string_actions[
269
+ short_key
270
+ ] = argument
271
+
272
+ return parser
273
+
274
+
275
+ SUPPORTED_COMMANDS = [
276
+ RunParser,
277
+ RerunParser,
278
+ RunProjectParser,
279
+ TableParser,
280
+ ReportParser,
281
+ DestroyParser,
282
+ CheckParser,
283
+ CleanParser,
284
+ InitParser,
285
+ InitPifaceParser,
286
+ LinkParser,
287
+ InspectParser,
288
+ ]
289
+
290
+
291
+ class TopLevelParser(pydantic.BaseModel):
292
+ """
293
+ Top level parser that takes
294
+ - commands (run, runp, check...)
295
+ - arguments that are required no matter the subcommand
296
+ """
297
+
298
+ # commands
299
+ run: Optional[RunParserModel] = pydantic.Field(description=RunParser.description)
300
+ rerun: Optional[RerunParserModel] = pydantic.Field(
301
+ description=RerunParser.description
302
+ )
303
+ runp: Optional[RunProjectParserModel] = pydantic.Field(
304
+ description=RunProjectParser.description
305
+ )
306
+ table: Optional[TableParserModel] = pydantic.Field(
307
+ description=TableParser.description
308
+ )
309
+ report: Optional[ReportParserModel] = pydantic.Field(
310
+ description=ReportParser.description
311
+ )
312
+ destroy: Optional[DestroyParserModel] = pydantic.Field(
313
+ description=DestroyParser.description
314
+ )
315
+ check: Optional[CheckParserModel] = pydantic.Field(
316
+ description=CheckParser.description
317
+ )
318
+ clean: Optional[CleanParserModel] = pydantic.Field(
319
+ description=CleanParser.description
320
+ )
321
+ init: Optional[InitParserModel] = pydantic.Field(description=InitParser.description)
322
+ init_piface: Optional[InitPifaceParserModel] = pydantic.Field(
323
+ description=InitPifaceParser.description
324
+ )
325
+ link: Optional[LinkParserModel] = pydantic.Field(description=LinkParser.description)
326
+
327
+ inspect: Optional[InspectParserModel] = pydantic.Field(
328
+ description=InspectParser.description
329
+ )
330
+
331
+ # Additional arguments for logging, added to ALL commands
332
+ # These must be used before the command
333
+ silent: Optional[bool] = ArgumentEnum.SILENT.value.with_reduced_default()
334
+ verbosity: Optional[int] = ArgumentEnum.VERBOSITY.value.with_reduced_default()
335
+ logdev: Optional[bool] = ArgumentEnum.LOGDEV.value.with_reduced_default()