nextmv 0.23.0__py3-none-any.whl → 0.24.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.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +7 -10
- nextmv/__init__.py +3 -0
- nextmv/cloud/__init__.py +7 -1
- nextmv/cloud/application.py +541 -47
- nextmv/cloud/batch_experiment.py +58 -22
- nextmv/cloud/client.py +2 -0
- nextmv/cloud/input_set.py +26 -0
- nextmv/cloud/manifest.py +157 -8
- nextmv/cloud/run.py +8 -7
- nextmv/cloud/safe.py +83 -0
- nextmv/cloud/scenario.py +229 -0
- nextmv/deprecated.py +13 -0
- nextmv/input.py +74 -0
- nextmv/options.py +293 -78
- nextmv/output.py +64 -7
- {nextmv-0.23.0.dist-info → nextmv-0.24.0.dist-info}/METADATA +1 -1
- nextmv-0.24.0.dist-info/RECORD +30 -0
- nextmv-0.23.0.dist-info/RECORD +0 -27
- {nextmv-0.23.0.dist-info → nextmv-0.24.0.dist-info}/WHEEL +0 -0
- {nextmv-0.23.0.dist-info → nextmv-0.24.0.dist-info}/licenses/LICENSE +0 -0
nextmv/options.py
CHANGED
|
@@ -6,14 +6,19 @@ import copy
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
-
from typing import Any, Optional
|
|
9
|
+
from typing import Any, Optional, Union
|
|
10
10
|
|
|
11
11
|
from nextmv.base_model import BaseModel
|
|
12
|
+
from nextmv.deprecated import deprecated
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@dataclass
|
|
15
16
|
class Parameter:
|
|
16
17
|
"""
|
|
18
|
+
DEPRECATION WARNING
|
|
19
|
+
----------
|
|
20
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
21
|
+
|
|
17
22
|
Parameter that is used in a `Configuration`. When a parameter is required,
|
|
18
23
|
it is a good practice to provide a default value for it. This is because
|
|
19
24
|
the configuration will raise an error if a required parameter is not
|
|
@@ -58,9 +63,20 @@ class Parameter:
|
|
|
58
63
|
choices: list[Optional[Any]] = None
|
|
59
64
|
"""Limits values to a specific set of choices."""
|
|
60
65
|
|
|
66
|
+
def __post_init__(self):
|
|
67
|
+
deprecated(
|
|
68
|
+
name="Parameter",
|
|
69
|
+
reason="`Parameter` is deprecated, use `Option` instead.",
|
|
70
|
+
)
|
|
71
|
+
|
|
61
72
|
@classmethod
|
|
62
73
|
def from_dict(cls, data: dict[str, Any]) -> "Parameter":
|
|
63
74
|
"""
|
|
75
|
+
DEPRECATION WARNING
|
|
76
|
+
----------
|
|
77
|
+
`Parameter` is deprecated, use `Option` instead. Parameter.from_dict ->
|
|
78
|
+
Option.from_dict
|
|
79
|
+
|
|
64
80
|
Creates an instance of `Parameter` from a dictionary.
|
|
65
81
|
|
|
66
82
|
Parameters
|
|
@@ -74,6 +90,11 @@ class Parameter:
|
|
|
74
90
|
An instance of `Parameter`.
|
|
75
91
|
"""
|
|
76
92
|
|
|
93
|
+
deprecated(
|
|
94
|
+
name="Parameter.from_dict",
|
|
95
|
+
reason="`Parameter` is deprecated, use `Option` instead. Parameter.from_dict -> Option.from_dict",
|
|
96
|
+
)
|
|
97
|
+
|
|
77
98
|
param_type_string = data["param_type"]
|
|
78
99
|
param_type = getattr(builtins, param_type_string.split("'")[1])
|
|
79
100
|
|
|
@@ -88,6 +109,11 @@ class Parameter:
|
|
|
88
109
|
|
|
89
110
|
def to_dict(self) -> dict[str, Any]:
|
|
90
111
|
"""
|
|
112
|
+
DEPRECATION WARNING
|
|
113
|
+
----------
|
|
114
|
+
`Parameter` is deprecated, use `Option` instead. Parameter.to_dict ->
|
|
115
|
+
Option.to_dict
|
|
116
|
+
|
|
91
117
|
Converts the parameter to a dict.
|
|
92
118
|
|
|
93
119
|
Returns
|
|
@@ -96,6 +122,11 @@ class Parameter:
|
|
|
96
122
|
The parameter as a dict.
|
|
97
123
|
"""
|
|
98
124
|
|
|
125
|
+
deprecated(
|
|
126
|
+
name="Parameter.to_dict",
|
|
127
|
+
reason="`Parameter` is deprecated, use `Option` instead. Parameter.to_dict -> Option.to_dict",
|
|
128
|
+
)
|
|
129
|
+
|
|
99
130
|
return {
|
|
100
131
|
"name": self.name,
|
|
101
132
|
"param_type": str(self.param_type),
|
|
@@ -106,19 +137,120 @@ class Parameter:
|
|
|
106
137
|
}
|
|
107
138
|
|
|
108
139
|
|
|
140
|
+
@dataclass
|
|
141
|
+
class Option:
|
|
142
|
+
"""
|
|
143
|
+
`Option` that is used in `Options`. When an `Option` is required,
|
|
144
|
+
it is a good practice to provide a default value for it. This is because
|
|
145
|
+
the `Options` will raise an error if a required `Option` is not
|
|
146
|
+
provided through a command-line argument, an environment variable or a
|
|
147
|
+
default value.
|
|
148
|
+
|
|
149
|
+
Attributes
|
|
150
|
+
----------
|
|
151
|
+
name : str
|
|
152
|
+
The name of the option.
|
|
153
|
+
option_type : type
|
|
154
|
+
The type of the option.
|
|
155
|
+
default : Any, optional
|
|
156
|
+
The default value of the option. Even though this is optional, it is
|
|
157
|
+
recommended to provide a default value for all options.
|
|
158
|
+
description : str, optional
|
|
159
|
+
An optional description of the option. This is useful for generating
|
|
160
|
+
help messages for the `Options`.
|
|
161
|
+
required : bool, optional
|
|
162
|
+
Whether the option is required. If an option is required, it will
|
|
163
|
+
be an error to not provide a value for it, either trough a command-line
|
|
164
|
+
argument, an environment variable or a default value.
|
|
165
|
+
choices : list[Optional[Any]], optional
|
|
166
|
+
Limits values to a specific set of choices.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
name: str
|
|
170
|
+
"""The name of the option."""
|
|
171
|
+
option_type: type
|
|
172
|
+
"""The type of the option."""
|
|
173
|
+
|
|
174
|
+
default: Optional[Any] = None
|
|
175
|
+
"""
|
|
176
|
+
The default value of the option. Even though this is optional, it is
|
|
177
|
+
recommended to provide a default value for all options.
|
|
178
|
+
"""
|
|
179
|
+
description: Optional[str] = None
|
|
180
|
+
"""
|
|
181
|
+
An optional description of the option. This is useful for generating help
|
|
182
|
+
messages for the `Options`.
|
|
183
|
+
"""
|
|
184
|
+
required: bool = False
|
|
185
|
+
"""
|
|
186
|
+
Whether the option is required. If a option is required, it will be an
|
|
187
|
+
error to not provide a value for it, either trough a command-line argument,
|
|
188
|
+
an environment variable or a default value.
|
|
189
|
+
"""
|
|
190
|
+
choices: Optional[list[Any]] = None
|
|
191
|
+
"""Limits values to a specific set of choices."""
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def from_dict(cls, data: dict[str, Any]) -> "Option":
|
|
195
|
+
"""
|
|
196
|
+
Creates an instance of `Option` from a dictionary.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
data : dict[str, Any]
|
|
201
|
+
The dictionary representation of an option.
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
Option
|
|
206
|
+
An instance of `Option`.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
option_type_string = data["option_type"]
|
|
210
|
+
option_type = getattr(builtins, option_type_string.split("'")[1])
|
|
211
|
+
|
|
212
|
+
return Option(
|
|
213
|
+
name=data["name"],
|
|
214
|
+
option_type=option_type,
|
|
215
|
+
default=data.get("default"),
|
|
216
|
+
description=data.get("description"),
|
|
217
|
+
required=data.get("required", False),
|
|
218
|
+
choices=data.get("choices"),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def to_dict(self) -> dict[str, Any]:
|
|
222
|
+
"""
|
|
223
|
+
Converts the option to a dict.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
dict[str, Any]
|
|
228
|
+
The option as a dict.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
"name": self.name,
|
|
233
|
+
"option_type": str(self.option_type),
|
|
234
|
+
"default": self.default,
|
|
235
|
+
"description": self.description,
|
|
236
|
+
"required": self.required,
|
|
237
|
+
"choices": self.choices,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
109
241
|
class Options:
|
|
110
242
|
"""
|
|
111
|
-
Options for a run. To initialize options, pass in one or more `
|
|
243
|
+
Options for a run. To initialize options, pass in one or more `Option`
|
|
112
244
|
objects. The options will look for the values of the given parameters in
|
|
113
245
|
the following order: command-line arguments, environment variables, default
|
|
114
246
|
values.
|
|
115
247
|
|
|
116
|
-
Once the
|
|
117
|
-
attributes of the `Options` object. For example, if you have
|
|
118
|
-
`
|
|
248
|
+
Once the `Options` are initialized, you can access the underlying options as
|
|
249
|
+
attributes of the `Options` object. For example, if you have an
|
|
250
|
+
`Option` object with the name "duration", you can access it as
|
|
119
251
|
`options.duration`.
|
|
120
252
|
|
|
121
|
-
If
|
|
253
|
+
If an option is required and not provided through a command-line
|
|
122
254
|
argument, an environment variable or a default value, an error will be
|
|
123
255
|
raised.
|
|
124
256
|
|
|
@@ -132,19 +264,19 @@ class Options:
|
|
|
132
264
|
be merged with other options. After options are parsed, you may get the
|
|
133
265
|
help message by running the script with the `-h/--help` flag.
|
|
134
266
|
|
|
135
|
-
|
|
267
|
+
Attributes
|
|
136
268
|
----------
|
|
137
|
-
*
|
|
138
|
-
The
|
|
139
|
-
|
|
269
|
+
*options : Option
|
|
270
|
+
The list of `Option` objects that are used in the options. At least one
|
|
271
|
+
option is required.
|
|
140
272
|
|
|
141
273
|
Examples
|
|
142
274
|
--------
|
|
143
275
|
>>> import nextmv
|
|
144
276
|
>>>
|
|
145
277
|
>>> options = nextmv.Options(
|
|
146
|
-
... nextmv.
|
|
147
|
-
... nextmv.
|
|
278
|
+
... nextmv.Option("duration", str, "30s", description="solver duration", required=False),
|
|
279
|
+
... nextmv.Option("threads", int, 4, description="computer threads", required=False),
|
|
148
280
|
... )
|
|
149
281
|
>>>
|
|
150
282
|
>>> print(options.duration, options.threads, options.to_dict())
|
|
@@ -154,10 +286,11 @@ class Options:
|
|
|
154
286
|
Raises
|
|
155
287
|
------
|
|
156
288
|
ValueError
|
|
157
|
-
If a required
|
|
289
|
+
If a required option is not provided through a command-line
|
|
158
290
|
argument, an environment variable or a default value.
|
|
159
291
|
TypeError
|
|
160
|
-
If
|
|
292
|
+
If an option is not either an `Option` or `Parameter` (deprecated)
|
|
293
|
+
object.
|
|
161
294
|
ValueError
|
|
162
295
|
If an environment variable is not of the type of the corresponding
|
|
163
296
|
parameter.
|
|
@@ -165,10 +298,10 @@ class Options:
|
|
|
165
298
|
|
|
166
299
|
PARSED = False
|
|
167
300
|
|
|
168
|
-
def __init__(self, *
|
|
301
|
+
def __init__(self, *options: Option):
|
|
169
302
|
"""Initializes the options."""
|
|
170
303
|
|
|
171
|
-
self.
|
|
304
|
+
self.options = copy.deepcopy(options)
|
|
172
305
|
|
|
173
306
|
def to_dict(self) -> dict[str, Any]:
|
|
174
307
|
"""
|
|
@@ -190,7 +323,7 @@ class Options:
|
|
|
190
323
|
|
|
191
324
|
self_dict = copy.deepcopy(self.__dict__)
|
|
192
325
|
|
|
193
|
-
rm_keys = ["
|
|
326
|
+
rm_keys = ["PARSED", "options"]
|
|
194
327
|
for key in rm_keys:
|
|
195
328
|
if key in self_dict:
|
|
196
329
|
self_dict.pop(key)
|
|
@@ -199,7 +332,7 @@ class Options:
|
|
|
199
332
|
|
|
200
333
|
return m.to_dict()["config"]
|
|
201
334
|
|
|
202
|
-
def
|
|
335
|
+
def to_dict_cloud(self) -> dict[str, str]:
|
|
203
336
|
"""
|
|
204
337
|
Converts the options to a dict that can be used in the Nextmv Cloud.
|
|
205
338
|
Cloud has a hard requirement that options are passed as strings. This
|
|
@@ -227,6 +360,11 @@ class Options:
|
|
|
227
360
|
|
|
228
361
|
def parameters_dict(self) -> list[dict[str, Any]]:
|
|
229
362
|
"""
|
|
363
|
+
DEPRECATION WARNING
|
|
364
|
+
----------
|
|
365
|
+
`Parameter` is deprecated, use `Option` instead. Options.parameters_dict
|
|
366
|
+
-> Options.options_dict
|
|
367
|
+
|
|
230
368
|
Converts the options to a list of dicts. Each dict is the dict
|
|
231
369
|
representation of a `Parameter`.
|
|
232
370
|
|
|
@@ -236,7 +374,25 @@ class Options:
|
|
|
236
374
|
The list of dictionaries (parameter entries).
|
|
237
375
|
"""
|
|
238
376
|
|
|
239
|
-
|
|
377
|
+
deprecated(
|
|
378
|
+
name="Options.parameters_dict",
|
|
379
|
+
reason="`Parameter` is deprecated, use `Option` instead. Options.parameters_dict -> Options.options_dict",
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return [param.to_dict() for param in self.options]
|
|
383
|
+
|
|
384
|
+
def options_dict(self) -> list[dict[str, Any]]:
|
|
385
|
+
"""
|
|
386
|
+
Converts the `Options` to a list of dicts. Each dict is the dict
|
|
387
|
+
representation of an `Option`.
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
list[dict[str, Any]]
|
|
392
|
+
The list of dictionaries (`Option` entries).
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
return [opt.to_dict() for opt in self.options]
|
|
240
396
|
|
|
241
397
|
def parse(self):
|
|
242
398
|
"""
|
|
@@ -257,8 +413,8 @@ class Options:
|
|
|
257
413
|
>>> import nextmv
|
|
258
414
|
>>>
|
|
259
415
|
>>> options = nextmv.Options(
|
|
260
|
-
... nextmv.
|
|
261
|
-
... nextmv.
|
|
416
|
+
... nextmv.Option("duration", str, "30s", description="solver duration", required=False),
|
|
417
|
+
... nextmv.Option("threads", int, 4, description="computer threads", required=False),
|
|
262
418
|
... )
|
|
263
419
|
>>> options.parse() # Does not raise an exception.
|
|
264
420
|
|
|
@@ -267,8 +423,8 @@ class Options:
|
|
|
267
423
|
>>> import nextmv
|
|
268
424
|
>>>
|
|
269
425
|
>>> options = nextmv.Options(
|
|
270
|
-
... nextmv.
|
|
271
|
-
... nextmv.
|
|
426
|
+
... nextmv.Option("duration", str, "30s", description="solver duration", required=False),
|
|
427
|
+
... nextmv.Option("threads", int, 4, description="computer threads", required=False),
|
|
272
428
|
... )
|
|
273
429
|
>>> print(options.duration) # Parses the options.
|
|
274
430
|
>>> options.parse() # Raises an exception because the options have already been parsed.
|
|
@@ -278,10 +434,10 @@ class Options:
|
|
|
278
434
|
RuntimeError
|
|
279
435
|
If the options have already been parsed.
|
|
280
436
|
ValueError
|
|
281
|
-
If a required
|
|
437
|
+
If a required option is not provided through a command-line
|
|
282
438
|
argument, an environment variable or a default value.
|
|
283
439
|
TypeError
|
|
284
|
-
If
|
|
440
|
+
If an option is not an `Option` or `Parameter` (deprecated) object.
|
|
285
441
|
ValueError
|
|
286
442
|
If an environment variable is not of the type of the corresponding
|
|
287
443
|
parameter.
|
|
@@ -328,7 +484,7 @@ class Options:
|
|
|
328
484
|
"new options have already been parsed, cannot merge. See `Options.parse()` for more information."
|
|
329
485
|
)
|
|
330
486
|
|
|
331
|
-
self.
|
|
487
|
+
self.options += new.options
|
|
332
488
|
|
|
333
489
|
self._parse()
|
|
334
490
|
|
|
@@ -356,16 +512,21 @@ class Options:
|
|
|
356
512
|
An instance of `Options`.
|
|
357
513
|
"""
|
|
358
514
|
|
|
359
|
-
|
|
515
|
+
options = []
|
|
360
516
|
for key, value in data.items():
|
|
361
|
-
|
|
362
|
-
|
|
517
|
+
opt = Option(name=key, option_type=type(value), default=value)
|
|
518
|
+
options.append(opt)
|
|
363
519
|
|
|
364
|
-
return cls(*
|
|
520
|
+
return cls(*options)
|
|
365
521
|
|
|
366
522
|
@classmethod
|
|
367
523
|
def from_parameters_dict(cls, parameters_dict: list[dict[str, Any]]) -> "Options":
|
|
368
524
|
"""
|
|
525
|
+
DEPRECATION WARNING
|
|
526
|
+
----------
|
|
527
|
+
`Parameter` is deprecated, use `Option` instead. Options.from_parameters_dict
|
|
528
|
+
-> Options.from_options_dict
|
|
529
|
+
|
|
369
530
|
Creates an instance of `Options` from parameters in dict form. Each
|
|
370
531
|
entry is the dict representation of a `Parameter`.
|
|
371
532
|
|
|
@@ -380,6 +541,12 @@ class Options:
|
|
|
380
541
|
An instance of `Options`.
|
|
381
542
|
"""
|
|
382
543
|
|
|
544
|
+
deprecated(
|
|
545
|
+
name="Options.from_parameters_dict",
|
|
546
|
+
reason="`Parameter` is deprecated, use `Option` instead. "
|
|
547
|
+
"Options.from_parameters_dict -> Options.from_options_dict",
|
|
548
|
+
)
|
|
549
|
+
|
|
383
550
|
parameters = []
|
|
384
551
|
for parameter_dict in parameters_dict:
|
|
385
552
|
parameter = Parameter.from_dict(parameter_dict)
|
|
@@ -387,6 +554,30 @@ class Options:
|
|
|
387
554
|
|
|
388
555
|
return cls(*parameters)
|
|
389
556
|
|
|
557
|
+
@classmethod
|
|
558
|
+
def from_options_dict(cls, options_dict: list[dict[str, Any]]) -> "Options":
|
|
559
|
+
"""
|
|
560
|
+
Creates an instance of `Options` from a list of `Option` objects in
|
|
561
|
+
dict form. Each entry is the dict representation of an `Option`.
|
|
562
|
+
|
|
563
|
+
Parameters
|
|
564
|
+
----------
|
|
565
|
+
data : list[dict[str, Any]]
|
|
566
|
+
The list of dictionaries (`Option` entries).
|
|
567
|
+
|
|
568
|
+
Returns
|
|
569
|
+
-------
|
|
570
|
+
Options
|
|
571
|
+
An instance of `Options`.
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
options = []
|
|
575
|
+
for opt_dict in options_dict:
|
|
576
|
+
opt = Option.from_dict(opt_dict)
|
|
577
|
+
options.append(opt)
|
|
578
|
+
|
|
579
|
+
return cls(*options)
|
|
580
|
+
|
|
390
581
|
def __getattr__(self, name: str) -> Any:
|
|
391
582
|
"""
|
|
392
583
|
Gets an attribute of the options. This is called when an attribute
|
|
@@ -406,10 +597,10 @@ class Options:
|
|
|
406
597
|
Raises
|
|
407
598
|
------
|
|
408
599
|
ValueError
|
|
409
|
-
If a required
|
|
600
|
+
If a required option is not provided through a command-line
|
|
410
601
|
argument, an environment variable or a default value.
|
|
411
602
|
TypeError
|
|
412
|
-
If
|
|
603
|
+
If an option is not an `Option` or `Parameter` (deprecated) object.
|
|
413
604
|
ValueError
|
|
414
605
|
If an environment variable is not of the type of the corresponding
|
|
415
606
|
parameter.
|
|
@@ -417,7 +608,7 @@ class Options:
|
|
|
417
608
|
|
|
418
609
|
self.PARSED = True
|
|
419
610
|
|
|
420
|
-
if not self.
|
|
611
|
+
if not self.options:
|
|
421
612
|
return
|
|
422
613
|
|
|
423
614
|
parser = argparse.ArgumentParser(
|
|
@@ -427,41 +618,43 @@ class Options:
|
|
|
427
618
|
+ "or environment variables.",
|
|
428
619
|
allow_abbrev=False,
|
|
429
620
|
)
|
|
430
|
-
|
|
621
|
+
options_by_field_name: dict[str, Option] = {}
|
|
431
622
|
|
|
432
|
-
for
|
|
433
|
-
if not isinstance(
|
|
434
|
-
raise TypeError(
|
|
623
|
+
for ix, option in enumerate(self.options):
|
|
624
|
+
if not isinstance(option, Option) and not isinstance(option, Parameter):
|
|
625
|
+
raise TypeError(
|
|
626
|
+
f"expected an <Option> (or deprecated <Parameter>) object, but got {type(option)} in index {ix}"
|
|
627
|
+
)
|
|
435
628
|
|
|
436
629
|
# See comment below about ipykernel adding a `-f` argument. We
|
|
437
|
-
# restrict
|
|
630
|
+
# restrict options from having the name 'f' or 'fff' for that
|
|
438
631
|
# reason.
|
|
439
|
-
if
|
|
440
|
-
raise ValueError("
|
|
632
|
+
if option.name == "f" or option.name == "fff":
|
|
633
|
+
raise ValueError("option names 'f', 'fff' are reserved for internal use")
|
|
441
634
|
|
|
442
|
-
if
|
|
443
|
-
raise ValueError("
|
|
635
|
+
if option.name == "PARSED":
|
|
636
|
+
raise ValueError("option name 'PARSED' is reserved for internal use")
|
|
444
637
|
|
|
445
638
|
# Remove any leading '-'. This is in line with argparse's behavior.
|
|
446
|
-
|
|
639
|
+
option.name = option.name.lstrip("-")
|
|
447
640
|
|
|
448
641
|
kwargs = {
|
|
449
|
-
"type":
|
|
450
|
-
"help": self._description(
|
|
642
|
+
"type": self._option_type(option) if self._option_type(option) is not bool else str,
|
|
643
|
+
"help": self._description(option),
|
|
451
644
|
}
|
|
452
645
|
|
|
453
|
-
if
|
|
454
|
-
kwargs["choices"] =
|
|
646
|
+
if option.choices is not None:
|
|
647
|
+
kwargs["choices"] = option.choices
|
|
455
648
|
|
|
456
649
|
parser.add_argument(
|
|
457
|
-
f"-{
|
|
458
|
-
f"--{
|
|
650
|
+
f"-{option.name}",
|
|
651
|
+
f"--{option.name}",
|
|
459
652
|
**kwargs,
|
|
460
653
|
)
|
|
461
654
|
|
|
462
|
-
# Store the
|
|
655
|
+
# Store the option by its field name for easy access later. argparse
|
|
463
656
|
# replaces '-' with '_', so we do the same here.
|
|
464
|
-
|
|
657
|
+
options_by_field_name[option.name.replace("-", "_")] = option
|
|
465
658
|
|
|
466
659
|
# The ipkyernel uses a `-f` argument by default that it passes to the
|
|
467
660
|
# execution. We don’t want to ignore this argument because we get an
|
|
@@ -479,67 +672,73 @@ class Options:
|
|
|
479
672
|
if arg == "fff" or arg == "f":
|
|
480
673
|
continue
|
|
481
674
|
|
|
482
|
-
|
|
675
|
+
option = options_by_field_name[arg]
|
|
483
676
|
|
|
484
|
-
# First, attempt to set the value of
|
|
677
|
+
# First, attempt to set the value of an option from the
|
|
485
678
|
# command-line args.
|
|
486
679
|
arg_value = getattr(args, arg)
|
|
487
680
|
if arg_value is not None:
|
|
488
|
-
value = self.
|
|
681
|
+
value = self._option_value(option, arg_value)
|
|
489
682
|
setattr(self, arg, value)
|
|
490
683
|
continue
|
|
491
684
|
|
|
492
|
-
# Second, attempt to set the value of
|
|
685
|
+
# Second, attempt to set the value of am option from the
|
|
493
686
|
# environment variables.
|
|
494
687
|
upper_name = arg.upper()
|
|
495
688
|
env_value = os.getenv(upper_name)
|
|
496
689
|
if env_value is not None:
|
|
497
690
|
try:
|
|
498
|
-
typed_env_value =
|
|
691
|
+
typed_env_value = (
|
|
692
|
+
self._option_type(option)(env_value) if self._option_type(option) is not bool else env_value
|
|
693
|
+
)
|
|
499
694
|
except ValueError:
|
|
500
|
-
raise ValueError(
|
|
695
|
+
raise ValueError(
|
|
696
|
+
f'environment variable "{upper_name}" is not of type {self._option_type(option)}'
|
|
697
|
+
) from None
|
|
501
698
|
|
|
502
|
-
value = self.
|
|
699
|
+
value = self._option_value(option, typed_env_value)
|
|
503
700
|
setattr(self, arg, value)
|
|
504
701
|
continue
|
|
505
702
|
|
|
506
703
|
# Finally, attempt to set a default value. This is only allowed
|
|
507
|
-
# for non-required
|
|
508
|
-
if not
|
|
509
|
-
setattr(self, arg,
|
|
704
|
+
# for non-required options.
|
|
705
|
+
if not option.required:
|
|
706
|
+
setattr(self, arg, option.default)
|
|
510
707
|
continue
|
|
511
708
|
|
|
512
|
-
# At this point, the
|
|
709
|
+
# At this point, the option is required and no value was
|
|
513
710
|
# provided
|
|
514
711
|
raise ValueError(
|
|
515
|
-
f'
|
|
712
|
+
f'option "{arg}" is required but not provided through: command-line args, env vars, or default value'
|
|
516
713
|
)
|
|
517
714
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
715
|
+
def _description(self, option: Option) -> str:
|
|
716
|
+
"""Returns a description for an option."""
|
|
717
|
+
|
|
718
|
+
description = ""
|
|
719
|
+
if isinstance(option, Parameter):
|
|
720
|
+
description = "DEPRECATED (initialized with <Parameter>, use <Option> instead) "
|
|
521
721
|
|
|
522
|
-
description
|
|
722
|
+
description += f"[env var: {option.name.upper()}]"
|
|
523
723
|
|
|
524
|
-
if
|
|
724
|
+
if option.required:
|
|
525
725
|
description += " (required)"
|
|
526
726
|
|
|
527
|
-
if
|
|
528
|
-
description += f" (default: {
|
|
727
|
+
if option.default is not None:
|
|
728
|
+
description += f" (default: {option.default})"
|
|
529
729
|
|
|
530
|
-
description += f" (type: {
|
|
730
|
+
description += f" (type: {self._option_type(option).__name__})"
|
|
531
731
|
|
|
532
|
-
if
|
|
533
|
-
description += f": {
|
|
732
|
+
if option.description is not None and option.description != "":
|
|
733
|
+
description += f": {option.description}"
|
|
534
734
|
|
|
535
735
|
return description
|
|
536
736
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
"""Handles how the value of a parameter is extracted."""
|
|
737
|
+
def _option_value(self, option: Option, value: Any) -> Any:
|
|
738
|
+
"""Handles how the value of an option is extracted."""
|
|
540
739
|
|
|
541
|
-
|
|
542
|
-
if
|
|
740
|
+
opt_type = self._option_type(option)
|
|
741
|
+
if opt_type is not bool:
|
|
543
742
|
return value
|
|
544
743
|
|
|
545
744
|
value = str(value).lower()
|
|
@@ -548,3 +747,19 @@ class Options:
|
|
|
548
747
|
return True
|
|
549
748
|
|
|
550
749
|
return False
|
|
750
|
+
|
|
751
|
+
@staticmethod
|
|
752
|
+
def _option_type(option: Union[Option, Parameter]) -> type:
|
|
753
|
+
"""Auxiliary function for handling the type of an option. This function
|
|
754
|
+
was introduced for backwards compatibility with the deprecated
|
|
755
|
+
`Parameter` class. Once `Parameter` is removed, this function can be removed
|
|
756
|
+
as well. When the function is removed, use the `option.option_type`
|
|
757
|
+
attribute directly, instead of calling this function.
|
|
758
|
+
"""
|
|
759
|
+
|
|
760
|
+
if isinstance(option, Option):
|
|
761
|
+
return option.option_type
|
|
762
|
+
elif isinstance(option, Parameter):
|
|
763
|
+
return option.param_type
|
|
764
|
+
else:
|
|
765
|
+
raise TypeError(f"expected an <Option> (or deprecated <Parameter>) object, but got {type(option)}")
|