nextmv 0.18.0__py3-none-any.whl → 1.0.0.dev2__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 +8 -13
- nextmv/__init__.py +53 -0
- nextmv/_serialization.py +96 -0
- nextmv/base_model.py +54 -9
- nextmv/cli/CONTRIBUTING.md +511 -0
- nextmv/cli/__init__.py +0 -0
- nextmv/cli/cloud/__init__.py +47 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +393 -0
- nextmv/cli/cloud/acceptance/delete.py +68 -0
- nextmv/cli/cloud/acceptance/get.py +104 -0
- nextmv/cli/cloud/acceptance/list.py +62 -0
- nextmv/cli/cloud/acceptance/update.py +95 -0
- nextmv/cli/cloud/account/__init__.py +28 -0
- nextmv/cli/cloud/account/create.py +83 -0
- nextmv/cli/cloud/account/delete.py +60 -0
- nextmv/cli/cloud/account/get.py +66 -0
- nextmv/cli/cloud/account/update.py +70 -0
- nextmv/cli/cloud/app/__init__.py +35 -0
- nextmv/cli/cloud/app/create.py +141 -0
- nextmv/cli/cloud/app/delete.py +58 -0
- nextmv/cli/cloud/app/exists.py +44 -0
- nextmv/cli/cloud/app/get.py +66 -0
- nextmv/cli/cloud/app/list.py +61 -0
- nextmv/cli/cloud/app/push.py +137 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +454 -0
- nextmv/cli/cloud/batch/delete.py +68 -0
- nextmv/cli/cloud/batch/get.py +104 -0
- nextmv/cli/cloud/batch/list.py +63 -0
- nextmv/cli/cloud/batch/metadata.py +66 -0
- nextmv/cli/cloud/batch/update.py +95 -0
- nextmv/cli/cloud/data/__init__.py +26 -0
- nextmv/cli/cloud/data/upload.py +162 -0
- nextmv/cli/cloud/ensemble/__init__.py +31 -0
- nextmv/cli/cloud/ensemble/create.py +414 -0
- nextmv/cli/cloud/ensemble/delete.py +67 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +30 -0
- nextmv/cli/cloud/input_set/create.py +170 -0
- nextmv/cli/cloud/input_set/get.py +63 -0
- nextmv/cli/cloud/input_set/list.py +63 -0
- nextmv/cli/cloud/input_set/update.py +123 -0
- nextmv/cli/cloud/instance/__init__.py +35 -0
- nextmv/cli/cloud/instance/create.py +290 -0
- nextmv/cli/cloud/instance/delete.py +62 -0
- nextmv/cli/cloud/instance/exists.py +39 -0
- nextmv/cli/cloud/instance/get.py +62 -0
- nextmv/cli/cloud/instance/list.py +60 -0
- nextmv/cli/cloud/instance/update.py +216 -0
- nextmv/cli/cloud/managed_input/__init__.py +31 -0
- nextmv/cli/cloud/managed_input/create.py +146 -0
- nextmv/cli/cloud/managed_input/delete.py +65 -0
- nextmv/cli/cloud/managed_input/get.py +63 -0
- nextmv/cli/cloud/managed_input/list.py +60 -0
- nextmv/cli/cloud/managed_input/update.py +97 -0
- nextmv/cli/cloud/run/__init__.py +37 -0
- nextmv/cli/cloud/run/cancel.py +37 -0
- nextmv/cli/cloud/run/create.py +530 -0
- nextmv/cli/cloud/run/get.py +199 -0
- nextmv/cli/cloud/run/input.py +86 -0
- nextmv/cli/cloud/run/list.py +80 -0
- nextmv/cli/cloud/run/logs.py +167 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +501 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +65 -0
- nextmv/cli/cloud/scenario/get.py +102 -0
- nextmv/cli/cloud/scenario/list.py +63 -0
- nextmv/cli/cloud/scenario/metadata.py +67 -0
- nextmv/cli/cloud/scenario/update.py +93 -0
- nextmv/cli/cloud/secrets/__init__.py +33 -0
- nextmv/cli/cloud/secrets/create.py +206 -0
- nextmv/cli/cloud/secrets/delete.py +67 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +147 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +68 -0
- nextmv/cli/cloud/shadow/get.py +61 -0
- nextmv/cli/cloud/shadow/list.py +63 -0
- nextmv/cli/cloud/shadow/metadata.py +66 -0
- nextmv/cli/cloud/shadow/start.py +43 -0
- nextmv/cli/cloud/shadow/stop.py +43 -0
- nextmv/cli/cloud/shadow/update.py +95 -0
- nextmv/cli/cloud/upload/__init__.py +22 -0
- nextmv/cli/cloud/upload/create.py +39 -0
- nextmv/cli/cloud/version/__init__.py +33 -0
- nextmv/cli/cloud/version/create.py +97 -0
- nextmv/cli/cloud/version/delete.py +62 -0
- nextmv/cli/cloud/version/exists.py +39 -0
- nextmv/cli/cloud/version/get.py +62 -0
- nextmv/cli/cloud/version/list.py +60 -0
- nextmv/cli/cloud/version/update.py +92 -0
- nextmv/cli/community/__init__.py +24 -0
- nextmv/cli/community/clone.py +270 -0
- nextmv/cli/community/list.py +265 -0
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +195 -0
- nextmv/cli/configuration/create.py +94 -0
- nextmv/cli/configuration/delete.py +67 -0
- nextmv/cli/configuration/list.py +77 -0
- nextmv/cli/main.py +188 -0
- nextmv/cli/message.py +153 -0
- nextmv/cli/options.py +206 -0
- nextmv/cli/version.py +38 -0
- nextmv/cloud/__init__.py +71 -17
- nextmv/cloud/acceptance_test.py +757 -51
- nextmv/cloud/account.py +406 -17
- nextmv/cloud/application/__init__.py +957 -0
- nextmv/cloud/application/_acceptance.py +419 -0
- nextmv/cloud/application/_batch_scenario.py +860 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +227 -0
- nextmv/cloud/application/_instance.py +289 -0
- nextmv/cloud/application/_managed_input.py +227 -0
- nextmv/cloud/application/_run.py +1393 -0
- nextmv/cloud/application/_secrets.py +294 -0
- nextmv/cloud/application/_shadow.py +314 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +303 -0
- nextmv/cloud/assets.py +48 -0
- nextmv/cloud/batch_experiment.py +294 -33
- nextmv/cloud/client.py +307 -66
- nextmv/cloud/ensemble.py +247 -0
- nextmv/cloud/input_set.py +120 -2
- nextmv/cloud/instance.py +133 -8
- nextmv/cloud/integration.py +533 -0
- nextmv/cloud/package.py +168 -53
- nextmv/cloud/scenario.py +410 -0
- nextmv/cloud/secrets.py +234 -0
- nextmv/cloud/shadow.py +190 -0
- nextmv/cloud/url.py +73 -0
- nextmv/cloud/version.py +132 -4
- nextmv/default_app/.gitignore +1 -0
- nextmv/default_app/README.md +32 -0
- nextmv/default_app/app.yaml +12 -0
- nextmv/default_app/input.json +5 -0
- nextmv/default_app/main.py +37 -0
- nextmv/default_app/requirements.txt +2 -0
- nextmv/default_app/src/__init__.py +0 -0
- nextmv/default_app/src/visuals.py +36 -0
- nextmv/deprecated.py +47 -0
- nextmv/input.py +861 -90
- nextmv/local/__init__.py +5 -0
- nextmv/local/application.py +1251 -0
- nextmv/local/executor.py +1042 -0
- nextmv/local/geojson_handler.py +323 -0
- nextmv/local/local.py +97 -0
- nextmv/local/plotly_handler.py +61 -0
- nextmv/local/runner.py +274 -0
- nextmv/logger.py +80 -9
- nextmv/manifest.py +1466 -0
- nextmv/model.py +241 -66
- nextmv/options.py +708 -115
- nextmv/output.py +1301 -274
- nextmv/polling.py +325 -0
- nextmv/run.py +1702 -0
- nextmv/safe.py +145 -0
- nextmv/status.py +122 -0
- nextmv-1.0.0.dev2.dist-info/METADATA +311 -0
- nextmv-1.0.0.dev2.dist-info/RECORD +170 -0
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/WHEEL +1 -1
- nextmv-1.0.0.dev2.dist-info/entry_points.txt +2 -0
- nextmv/cloud/application.py +0 -1405
- nextmv/cloud/manifest.py +0 -234
- nextmv/cloud/status.py +0 -29
- nextmv-0.18.0.dist-info/METADATA +0 -770
- nextmv-0.18.0.dist-info/RECORD +0 -25
- {nextmv-0.18.0.dist-info → nextmv-1.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
nextmv/options.py
CHANGED
|
@@ -1,18 +1,38 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for application runs.
|
|
3
|
+
|
|
4
|
+
This module provides classes for handling configuration options for
|
|
5
|
+
applications. It supports reading options from command-line arguments,
|
|
6
|
+
environment variables, and default values in a prioritized manner. The module
|
|
7
|
+
includes classes for defining individual options (`Option`) and managing
|
|
8
|
+
collections of options (`Options`).
|
|
9
|
+
|
|
10
|
+
Classes
|
|
11
|
+
-------
|
|
12
|
+
Option
|
|
13
|
+
Class for defining individual options for configuration.
|
|
14
|
+
Options
|
|
15
|
+
Class for managing collections of options.
|
|
16
|
+
"""
|
|
2
17
|
|
|
3
18
|
import argparse
|
|
4
19
|
import builtins
|
|
5
20
|
import copy
|
|
21
|
+
import json
|
|
6
22
|
import os
|
|
7
23
|
from dataclasses import dataclass
|
|
8
|
-
from typing import Any
|
|
24
|
+
from typing import Any
|
|
9
25
|
|
|
10
26
|
from nextmv.base_model import BaseModel
|
|
27
|
+
from nextmv.deprecated import deprecated
|
|
11
28
|
|
|
12
29
|
|
|
13
30
|
@dataclass
|
|
14
31
|
class Parameter:
|
|
15
32
|
"""
|
|
33
|
+
!!! warning
|
|
34
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
35
|
+
|
|
16
36
|
Parameter that is used in a `Configuration`. When a parameter is required,
|
|
17
37
|
it is a good practice to provide a default value for it. This is because
|
|
18
38
|
the configuration will raise an error if a required parameter is not
|
|
@@ -23,20 +43,30 @@ class Parameter:
|
|
|
23
43
|
----------
|
|
24
44
|
name : str
|
|
25
45
|
The name of the parameter.
|
|
46
|
+
|
|
26
47
|
param_type : type
|
|
27
48
|
The type of the parameter.
|
|
49
|
+
|
|
28
50
|
default : Any, optional
|
|
29
51
|
The default value of the parameter. Even though this is optional, it is
|
|
30
52
|
recommended to provide a default value for all parameters.
|
|
53
|
+
|
|
31
54
|
description : str, optional
|
|
32
55
|
An optional description of the parameter. This is useful for generating
|
|
33
56
|
help messages for the configuration.
|
|
57
|
+
|
|
34
58
|
required : bool, optional
|
|
35
59
|
Whether the parameter is required. If a parameter is required, it will
|
|
36
|
-
be an error to not provide a value for it, either
|
|
60
|
+
be an error to not provide a value for it, either through a command-line
|
|
37
61
|
argument, an environment variable or a default value.
|
|
62
|
+
|
|
38
63
|
choices : list[Optional[Any]], optional
|
|
39
64
|
Limits values to a specific set of choices.
|
|
65
|
+
|
|
66
|
+
Examples
|
|
67
|
+
--------
|
|
68
|
+
>>> from nextmv.options import Parameter
|
|
69
|
+
>>> parameter = Parameter("timeout", int, 60, "The maximum timeout in seconds", required=True)
|
|
40
70
|
"""
|
|
41
71
|
|
|
42
72
|
name: str
|
|
@@ -44,22 +74,38 @@ class Parameter:
|
|
|
44
74
|
param_type: type
|
|
45
75
|
"""The type of the parameter."""
|
|
46
76
|
|
|
47
|
-
default:
|
|
77
|
+
default: Any | None = None
|
|
48
78
|
"""The default value of the parameter. Even though this is optional, it is
|
|
49
79
|
recommended to provide a default value for all parameters."""
|
|
50
|
-
description:
|
|
80
|
+
description: str | None = None
|
|
51
81
|
"""An optional description of the parameter. This is useful for generating
|
|
52
82
|
help messages for the configuration."""
|
|
53
83
|
required: bool = False
|
|
54
84
|
"""Whether the parameter is required. If a parameter is required, it will
|
|
55
85
|
be an error to not provide a value for it, either trough a command-line
|
|
56
86
|
argument, an environment variable or a default value."""
|
|
57
|
-
choices: list[
|
|
87
|
+
choices: list[Any | None] = None
|
|
58
88
|
"""Limits values to a specific set of choices."""
|
|
59
89
|
|
|
90
|
+
def __post_init__(self):
|
|
91
|
+
"""
|
|
92
|
+
Post-initialization hook that marks this class as deprecated.
|
|
93
|
+
|
|
94
|
+
This method is automatically called after the object is initialized.
|
|
95
|
+
It displays a deprecation warning to inform users to use the `Option` class instead.
|
|
96
|
+
"""
|
|
97
|
+
deprecated(
|
|
98
|
+
name="Parameter",
|
|
99
|
+
reason="`Parameter` is deprecated, use `Option` instead",
|
|
100
|
+
)
|
|
101
|
+
|
|
60
102
|
@classmethod
|
|
61
103
|
def from_dict(cls, data: dict[str, Any]) -> "Parameter":
|
|
62
104
|
"""
|
|
105
|
+
!!! warning
|
|
106
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
107
|
+
`Parameter.from_dict` -> `Option.from_dict`
|
|
108
|
+
|
|
63
109
|
Creates an instance of `Parameter` from a dictionary.
|
|
64
110
|
|
|
65
111
|
Parameters
|
|
@@ -73,6 +119,11 @@ class Parameter:
|
|
|
73
119
|
An instance of `Parameter`.
|
|
74
120
|
"""
|
|
75
121
|
|
|
122
|
+
deprecated(
|
|
123
|
+
name="Parameter.from_dict",
|
|
124
|
+
reason="`Parameter` is deprecated, use `Option` instead. Parameter.from_dict -> Option.from_dict",
|
|
125
|
+
)
|
|
126
|
+
|
|
76
127
|
param_type_string = data["param_type"]
|
|
77
128
|
param_type = getattr(builtins, param_type_string.split("'")[1])
|
|
78
129
|
|
|
@@ -87,14 +138,33 @@ class Parameter:
|
|
|
87
138
|
|
|
88
139
|
def to_dict(self) -> dict[str, Any]:
|
|
89
140
|
"""
|
|
141
|
+
!!! warning
|
|
142
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
143
|
+
`Parameter.to_dict` -> `Option.to_dict`
|
|
144
|
+
|
|
90
145
|
Converts the parameter to a dict.
|
|
91
146
|
|
|
92
147
|
Returns
|
|
93
148
|
-------
|
|
94
149
|
dict[str, Any]
|
|
95
|
-
The parameter as a dict
|
|
150
|
+
The parameter as a dict with its name, type, default value,
|
|
151
|
+
description, required flag, and choices.
|
|
152
|
+
|
|
153
|
+
Examples
|
|
154
|
+
--------
|
|
155
|
+
>>> param = Parameter("timeout", int, 60, "Maximum time in seconds", True)
|
|
156
|
+
>>> param_dict = param.to_dict()
|
|
157
|
+
>>> param_dict["name"]
|
|
158
|
+
'timeout'
|
|
159
|
+
>>> param_dict["default"]
|
|
160
|
+
60
|
|
96
161
|
"""
|
|
97
162
|
|
|
163
|
+
deprecated(
|
|
164
|
+
name="Parameter.to_dict",
|
|
165
|
+
reason="`Parameter` is deprecated, use `Option` instead. Parameter.to_dict -> Option.to_dict",
|
|
166
|
+
)
|
|
167
|
+
|
|
98
168
|
return {
|
|
99
169
|
"name": self.name,
|
|
100
170
|
"param_type": str(self.param_type),
|
|
@@ -105,19 +175,219 @@ class Parameter:
|
|
|
105
175
|
}
|
|
106
176
|
|
|
107
177
|
|
|
178
|
+
@dataclass
|
|
179
|
+
class Option:
|
|
180
|
+
"""
|
|
181
|
+
An option that is used in `Options`.
|
|
182
|
+
|
|
183
|
+
You can import the `Option` class directly from `nextmv`:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from nextmv import Option
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Options provide a way to configure application behavior. When an `Option`
|
|
190
|
+
is required, it is a good practice to provide a default value for it. This
|
|
191
|
+
is because the `Options` will raise an error if a required `Option` is not
|
|
192
|
+
provided through a command-line argument, an environment variable or a
|
|
193
|
+
default value.
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
name : str
|
|
198
|
+
`name`. The name of the option.
|
|
199
|
+
option_type : type
|
|
200
|
+
The type of the option.
|
|
201
|
+
default : Any, optional
|
|
202
|
+
The default value of the option. Even though this is optional, it is
|
|
203
|
+
recommended to provide a default value for all options.
|
|
204
|
+
description : str, optional
|
|
205
|
+
An optional description of the option. This is useful for generating
|
|
206
|
+
help messages for the `Options`.
|
|
207
|
+
required : bool, optional
|
|
208
|
+
Whether the option is required. If an option is required, it will
|
|
209
|
+
be an error to not provide a value for it, either through a command-line
|
|
210
|
+
argument, an environment variable or a default value.
|
|
211
|
+
choices : list[Optional[Any]], optional
|
|
212
|
+
Limits values to a specific set of choices.
|
|
213
|
+
additional_attributes : dict[str, Any], optional
|
|
214
|
+
Optional additional attributes for the option. The Nextmv Cloud may
|
|
215
|
+
perform validation on these attributes. For example, the maximum length
|
|
216
|
+
of a string or the maximum value of an integer. These additional
|
|
217
|
+
attributes will be shown in the help message of the `Options`.
|
|
218
|
+
control_type : str, optional
|
|
219
|
+
The type of control to use for the option in the Nextmv Cloud UI. This is
|
|
220
|
+
useful for defining how the option should be presented in the Nextmv
|
|
221
|
+
Cloud UI. Current control types include "input", "select", "slider", and
|
|
222
|
+
"toggle". This attribute is not used in the local `Options` class, but '
|
|
223
|
+
it is used in the Nextmv Cloud UI to define the type of control to use for
|
|
224
|
+
the option. This will be validated by the Nextmv Cloud, and availability
|
|
225
|
+
is based on options_type.
|
|
226
|
+
hidden_from : list[str], optional
|
|
227
|
+
A list of team roles to which this option will be hidden in the UI. For
|
|
228
|
+
example, if you want to hide an option from the "operator" role, you can
|
|
229
|
+
pass `hidden_from=["operator"]`.
|
|
230
|
+
display_name : str, optional
|
|
231
|
+
An optional display name for the option. This is useful for making
|
|
232
|
+
the option more user-friendly in the UI.
|
|
233
|
+
|
|
234
|
+
Examples
|
|
235
|
+
--------
|
|
236
|
+
```python
|
|
237
|
+
from nextmv.options import Option
|
|
238
|
+
opt = Option("duration", str, "30s", description="solver duration", required=False)
|
|
239
|
+
opt.name
|
|
240
|
+
opt.default
|
|
241
|
+
```
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
name: str
|
|
245
|
+
"""The name of the option."""
|
|
246
|
+
option_type: type
|
|
247
|
+
"""The type of the option."""
|
|
248
|
+
|
|
249
|
+
default: Any | None = None
|
|
250
|
+
"""
|
|
251
|
+
The default value of the option. Even though this is optional, it is
|
|
252
|
+
recommended to provide a default value for all options.
|
|
253
|
+
"""
|
|
254
|
+
description: str | None = None
|
|
255
|
+
"""
|
|
256
|
+
An optional description of the option. This is useful for generating help
|
|
257
|
+
messages for the `Options`.
|
|
258
|
+
"""
|
|
259
|
+
required: bool = False
|
|
260
|
+
"""
|
|
261
|
+
Whether the option is required. If a option is required, it will be an
|
|
262
|
+
error to not provide a value for it, either trough a command-line argument,
|
|
263
|
+
an environment variable or a default value.
|
|
264
|
+
"""
|
|
265
|
+
choices: list[Any] | None = None
|
|
266
|
+
"""Limits values to a specific set of choices."""
|
|
267
|
+
additional_attributes: dict[str, Any] | None = None
|
|
268
|
+
"""
|
|
269
|
+
Optional additional attributes for the option. The Nextmv Cloud may
|
|
270
|
+
perform validation on these attributes. For example, the maximum length of
|
|
271
|
+
a string or the maximum value of an integer. These additional attributes
|
|
272
|
+
will be shown in the help message of the `Options`.
|
|
273
|
+
"""
|
|
274
|
+
control_type: str | None = None
|
|
275
|
+
"""
|
|
276
|
+
The type of control to use for the option in the Nextmv Cloud UI. This is
|
|
277
|
+
useful for defining how the option should be presented in the Nextmv
|
|
278
|
+
Cloud UI. Current control types include "input", "select", "slider", and
|
|
279
|
+
"toggle". This attribute is not used in the local `Options` class, but it
|
|
280
|
+
is used in the Nextmv Cloud UI to define the type of control to use for
|
|
281
|
+
the option. This will be validated by the Nextmv Cloud, and availability
|
|
282
|
+
is based on options_type.
|
|
283
|
+
"""
|
|
284
|
+
hidden_from: list[str] | None = None
|
|
285
|
+
"""
|
|
286
|
+
A list of team roles for which this option will be hidden in the UI. For
|
|
287
|
+
example, if you want to hide an option from the "operator" role, you can
|
|
288
|
+
pass `hidden_from=["operator"]`.
|
|
289
|
+
"""
|
|
290
|
+
display_name: str | None = None
|
|
291
|
+
"""
|
|
292
|
+
An optional display name for the option. This is useful for making
|
|
293
|
+
the option more user-friendly in the UI.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
@classmethod
|
|
297
|
+
def from_dict(cls, data: dict[str, Any]) -> "Option":
|
|
298
|
+
"""
|
|
299
|
+
Creates an instance of `Option` from a dictionary.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
|
|
304
|
+
data: dict[str, Any]
|
|
305
|
+
The dictionary representation of an option.
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
Option
|
|
310
|
+
An instance of `Option`.
|
|
311
|
+
|
|
312
|
+
Examples
|
|
313
|
+
--------
|
|
314
|
+
>>> opt_dict = {"name": "timeout", "option_type": "<class 'int'>", "default": 60}
|
|
315
|
+
>>> option = Option.from_dict(opt_dict)
|
|
316
|
+
>>> option.name
|
|
317
|
+
'timeout'
|
|
318
|
+
>>> option.default
|
|
319
|
+
60
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
option_type_string = data["option_type"]
|
|
323
|
+
option_type = getattr(builtins, option_type_string.split("'")[1])
|
|
324
|
+
|
|
325
|
+
return cls(
|
|
326
|
+
name=data["name"],
|
|
327
|
+
option_type=option_type,
|
|
328
|
+
default=data.get("default"),
|
|
329
|
+
description=data.get("description"),
|
|
330
|
+
required=data.get("required", False),
|
|
331
|
+
choices=data.get("choices"),
|
|
332
|
+
additional_attributes=data.get("additional_attributes"),
|
|
333
|
+
control_type=data.get("control_type"),
|
|
334
|
+
hidden_from=data.get("hidden_from"),
|
|
335
|
+
display_name=data.get("display_name"),
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def to_dict(self) -> dict[str, Any]:
|
|
339
|
+
"""
|
|
340
|
+
Converts the option to a dict.
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
dict[str, Any]
|
|
345
|
+
The option as a dict with all its attributes.
|
|
346
|
+
|
|
347
|
+
Examples
|
|
348
|
+
--------
|
|
349
|
+
>>> opt = Option("duration", str, "30s", description="solver duration")
|
|
350
|
+
>>> opt_dict = opt.to_dict()
|
|
351
|
+
>>> opt_dict["name"]
|
|
352
|
+
'duration'
|
|
353
|
+
>>> opt_dict["default"]
|
|
354
|
+
'30s'
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
"name": self.name,
|
|
359
|
+
"option_type": str(self.option_type),
|
|
360
|
+
"default": self.default,
|
|
361
|
+
"description": self.description,
|
|
362
|
+
"required": self.required,
|
|
363
|
+
"choices": self.choices,
|
|
364
|
+
"additional_attributes": self.additional_attributes,
|
|
365
|
+
"control_type": self.control_type,
|
|
366
|
+
"hidden_from": self.hidden_from,
|
|
367
|
+
"display_name": self.display_name,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
108
371
|
class Options:
|
|
109
372
|
"""
|
|
110
|
-
Options for
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
373
|
+
Options container for application configuration.
|
|
374
|
+
|
|
375
|
+
You can import the `Options` class directly from `nextmv`:
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
from nextmv import Options
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
To initialize options, pass in one or more `Option` objects. The options
|
|
382
|
+
will look for the values of the given parameters in the following order:
|
|
383
|
+
command-line arguments, environment variables, default values.
|
|
384
|
+
|
|
385
|
+
Once the `Options` are initialized, you can access the underlying options as
|
|
386
|
+
attributes of the `Options` object. For example, if you have an
|
|
387
|
+
`Option` object with the name "duration", you can access it as
|
|
118
388
|
`options.duration`.
|
|
119
389
|
|
|
120
|
-
If
|
|
390
|
+
If an option is required and not provided through a command-line
|
|
121
391
|
argument, an environment variable or a default value, an error will be
|
|
122
392
|
raised.
|
|
123
393
|
|
|
@@ -133,30 +403,30 @@ class Options:
|
|
|
133
403
|
|
|
134
404
|
Parameters
|
|
135
405
|
----------
|
|
136
|
-
*
|
|
137
|
-
The
|
|
138
|
-
|
|
406
|
+
*options : Option
|
|
407
|
+
The list of `Option` objects that are used in the options. At least one
|
|
408
|
+
option is required.
|
|
139
409
|
|
|
140
410
|
Examples
|
|
141
411
|
--------
|
|
142
412
|
>>> import nextmv
|
|
143
413
|
>>>
|
|
144
414
|
>>> options = nextmv.Options(
|
|
145
|
-
... nextmv.
|
|
146
|
-
... nextmv.
|
|
415
|
+
... nextmv.Option("duration", str, "30s", description="solver duration", required=False),
|
|
416
|
+
... nextmv.Option("threads", int, 4, description="computer threads", required=False),
|
|
147
417
|
... )
|
|
148
418
|
>>>
|
|
149
419
|
>>> print(options.duration, options.threads, options.to_dict())
|
|
150
|
-
|
|
151
420
|
30s 4 {"duration": "30s", "threads": 4}
|
|
152
421
|
|
|
153
422
|
Raises
|
|
154
423
|
------
|
|
155
424
|
ValueError
|
|
156
|
-
If a required
|
|
425
|
+
If a required option is not provided through a command-line
|
|
157
426
|
argument, an environment variable or a default value.
|
|
158
427
|
TypeError
|
|
159
|
-
If
|
|
428
|
+
If an option is not either an `Option` or `Parameter` (deprecated)
|
|
429
|
+
object.
|
|
160
430
|
ValueError
|
|
161
431
|
If an environment variable is not of the type of the corresponding
|
|
162
432
|
parameter.
|
|
@@ -164,10 +434,16 @@ class Options:
|
|
|
164
434
|
|
|
165
435
|
PARSED = False
|
|
166
436
|
|
|
167
|
-
def __init__(self, *
|
|
168
|
-
"""
|
|
437
|
+
def __init__(self, *options: Option):
|
|
438
|
+
"""
|
|
439
|
+
Initialize an Options instance with the provided option objects.
|
|
169
440
|
|
|
170
|
-
|
|
441
|
+
Parameters
|
|
442
|
+
----------
|
|
443
|
+
*options : Option
|
|
444
|
+
The option objects to include in this Options instance.
|
|
445
|
+
"""
|
|
446
|
+
self.options = copy.deepcopy(options)
|
|
171
447
|
|
|
172
448
|
def to_dict(self) -> dict[str, Any]:
|
|
173
449
|
"""
|
|
@@ -178,7 +454,17 @@ class Options:
|
|
|
178
454
|
Returns
|
|
179
455
|
-------
|
|
180
456
|
dict[str, Any]
|
|
181
|
-
The options as a dict
|
|
457
|
+
The options as a dict where keys are option names and values
|
|
458
|
+
are the corresponding option values.
|
|
459
|
+
|
|
460
|
+
Examples
|
|
461
|
+
--------
|
|
462
|
+
>>> options = Options(Option("duration", str, "30s"), Option("threads", int, 4))
|
|
463
|
+
>>> options_dict = options.to_dict()
|
|
464
|
+
>>> options_dict["duration"]
|
|
465
|
+
'30s'
|
|
466
|
+
>>> options_dict["threads"]
|
|
467
|
+
4
|
|
182
468
|
"""
|
|
183
469
|
|
|
184
470
|
if not self.PARSED:
|
|
@@ -189,7 +475,7 @@ class Options:
|
|
|
189
475
|
|
|
190
476
|
self_dict = copy.deepcopy(self.__dict__)
|
|
191
477
|
|
|
192
|
-
rm_keys = ["
|
|
478
|
+
rm_keys = ["PARSED", "options"]
|
|
193
479
|
for key in rm_keys:
|
|
194
480
|
if key in self_dict:
|
|
195
481
|
self_dict.pop(key)
|
|
@@ -198,8 +484,49 @@ class Options:
|
|
|
198
484
|
|
|
199
485
|
return m.to_dict()["config"]
|
|
200
486
|
|
|
487
|
+
def to_dict_cloud(self) -> dict[str, str]:
|
|
488
|
+
"""
|
|
489
|
+
Converts the options to a dict that can be used in the Nextmv Cloud.
|
|
490
|
+
|
|
491
|
+
Cloud has a hard requirement that options are passed as strings. This
|
|
492
|
+
method converts the options to a dict with string values. This is
|
|
493
|
+
useful for passing options to the Nextmv Cloud.
|
|
494
|
+
|
|
495
|
+
As a side effect, this method parses the options if they have not been
|
|
496
|
+
parsed yet. See the `parse` method for more information.
|
|
497
|
+
|
|
498
|
+
Returns
|
|
499
|
+
-------
|
|
500
|
+
dict[str, str]
|
|
501
|
+
The options as a dict with string values where non-string values
|
|
502
|
+
are JSON-encoded.
|
|
503
|
+
|
|
504
|
+
Examples
|
|
505
|
+
--------
|
|
506
|
+
>>> options = Options(Option("duration", str, "30s"), Option("threads", int, 4))
|
|
507
|
+
>>> cloud_dict = options.to_dict_cloud()
|
|
508
|
+
>>> cloud_dict["duration"]
|
|
509
|
+
'30s'
|
|
510
|
+
>>> cloud_dict["threads"]
|
|
511
|
+
'4'
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
options_dict = self.to_dict()
|
|
515
|
+
|
|
516
|
+
cloud_dict = {}
|
|
517
|
+
for k, v in options_dict.items():
|
|
518
|
+
if isinstance(v, str):
|
|
519
|
+
cloud_dict[k] = v
|
|
520
|
+
else:
|
|
521
|
+
cloud_dict[k] = json.dumps(v)
|
|
522
|
+
|
|
523
|
+
return cloud_dict
|
|
524
|
+
|
|
201
525
|
def parameters_dict(self) -> list[dict[str, Any]]:
|
|
202
526
|
"""
|
|
527
|
+
!!! warning
|
|
528
|
+
`Parameter` is deprecated, use `Option` instead. `Options.parameters_dict` -> `Options.options_dict`
|
|
529
|
+
|
|
203
530
|
Converts the options to a list of dicts. Each dict is the dict
|
|
204
531
|
representation of a `Parameter`.
|
|
205
532
|
|
|
@@ -209,7 +536,34 @@ class Options:
|
|
|
209
536
|
The list of dictionaries (parameter entries).
|
|
210
537
|
"""
|
|
211
538
|
|
|
212
|
-
|
|
539
|
+
deprecated(
|
|
540
|
+
name="Options.parameters_dict",
|
|
541
|
+
reason="`Parameter` is deprecated, use `Option` instead. Options.parameters_dict -> Options.options_dict",
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
return [param.to_dict() for param in self.options]
|
|
545
|
+
|
|
546
|
+
def options_dict(self) -> list[dict[str, Any]]:
|
|
547
|
+
"""
|
|
548
|
+
Converts the `Options` to a list of dicts. Each dict is the dict
|
|
549
|
+
representation of an `Option`.
|
|
550
|
+
|
|
551
|
+
Returns
|
|
552
|
+
-------
|
|
553
|
+
list[dict[str, Any]]
|
|
554
|
+
The list of dictionaries (`Option` entries).
|
|
555
|
+
|
|
556
|
+
Examples
|
|
557
|
+
--------
|
|
558
|
+
>>> options = Options(Option("duration", str, "30s"), Option("threads", int, 4))
|
|
559
|
+
>>> opt_dicts = options.options_dict()
|
|
560
|
+
>>> opt_dicts[0]["name"]
|
|
561
|
+
'duration'
|
|
562
|
+
>>> opt_dicts[1]["name"]
|
|
563
|
+
'threads'
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
return [opt.to_dict() for opt in self.options]
|
|
213
567
|
|
|
214
568
|
def parse(self):
|
|
215
569
|
"""
|
|
@@ -225,23 +579,21 @@ class Options:
|
|
|
225
579
|
After Options have been parsed, they cannot be merged with other
|
|
226
580
|
Options. If you need to merge Options, do so before parsing them.
|
|
227
581
|
|
|
228
|
-
|
|
582
|
+
Examples
|
|
229
583
|
-------
|
|
230
584
|
>>> import nextmv
|
|
231
585
|
>>>
|
|
232
586
|
>>> options = nextmv.Options(
|
|
233
|
-
... nextmv.
|
|
234
|
-
... nextmv.
|
|
587
|
+
... nextmv.Option("duration", str, "30s", description="solver duration", required=False),
|
|
588
|
+
... nextmv.Option("threads", int, 4, description="computer threads", required=False),
|
|
235
589
|
... )
|
|
236
590
|
>>> options.parse() # Does not raise an exception.
|
|
237
591
|
|
|
238
|
-
Example 2
|
|
239
|
-
-------
|
|
240
592
|
>>> import nextmv
|
|
241
593
|
>>>
|
|
242
594
|
>>> options = nextmv.Options(
|
|
243
|
-
... nextmv.
|
|
244
|
-
... nextmv.
|
|
595
|
+
... nextmv.Option("duration", str, "30s", description="solver duration", required=False),
|
|
596
|
+
... nextmv.Option("threads", int, 4, description="computer threads", required=False),
|
|
245
597
|
... )
|
|
246
598
|
>>> print(options.duration) # Parses the options.
|
|
247
599
|
>>> options.parse() # Raises an exception because the options have already been parsed.
|
|
@@ -251,10 +603,10 @@ class Options:
|
|
|
251
603
|
RuntimeError
|
|
252
604
|
If the options have already been parsed.
|
|
253
605
|
ValueError
|
|
254
|
-
If a required
|
|
606
|
+
If a required option is not provided through a command-line
|
|
255
607
|
argument, an environment variable or a default value.
|
|
256
608
|
TypeError
|
|
257
|
-
If
|
|
609
|
+
If an option is not an `Option` or `Parameter` (deprecated) object.
|
|
258
610
|
ValueError
|
|
259
611
|
If an environment variable is not of the type of the corresponding
|
|
260
612
|
parameter.
|
|
@@ -265,18 +617,28 @@ class Options:
|
|
|
265
617
|
|
|
266
618
|
self._parse()
|
|
267
619
|
|
|
268
|
-
def merge(self, new: "Options") -> "Options":
|
|
620
|
+
def merge(self, *new: "Options", skip_parse: bool = False) -> "Options":
|
|
269
621
|
"""
|
|
270
|
-
Merges the current options with the new options.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
622
|
+
Merges the current options with the new options.
|
|
623
|
+
|
|
624
|
+
This method cannot be used if any of the options have been parsed. When
|
|
625
|
+
options are parsed, values are read from the command-line arguments,
|
|
626
|
+
environment variables and default values. Merging options after parsing
|
|
627
|
+
would result in unpredictable behavior.
|
|
275
628
|
|
|
276
629
|
Parameters
|
|
277
630
|
----------
|
|
278
631
|
new : Options
|
|
279
|
-
The new options to merge.
|
|
632
|
+
The new options to merge with the current options. At least one new option set
|
|
633
|
+
is required to merge. Multiple `Options` instances can be passed.
|
|
634
|
+
skip_parse : bool, optional
|
|
635
|
+
If True, the merged options will not be parsed after merging. This is useful
|
|
636
|
+
if you want to merge further options after this merge. The default is False.
|
|
637
|
+
|
|
638
|
+
Returns
|
|
639
|
+
-------
|
|
640
|
+
Options
|
|
641
|
+
The merged options object (self).
|
|
280
642
|
|
|
281
643
|
Raises
|
|
282
644
|
------
|
|
@@ -285,10 +647,18 @@ class Options:
|
|
|
285
647
|
RuntimeError
|
|
286
648
|
If the new options have already been parsed.
|
|
287
649
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
Options
|
|
291
|
-
|
|
650
|
+
Examples
|
|
651
|
+
--------
|
|
652
|
+
>>> opt1 = Options(Option("duration", str, "30s"))
|
|
653
|
+
>>> opt2 = Options(Option("threads", int, 4))
|
|
654
|
+
>>> opt3 = Options(Option("verbose", bool, False))
|
|
655
|
+
>>> merged = opt1.merge(opt2, opt3)
|
|
656
|
+
>>> merged.duration
|
|
657
|
+
'30s'
|
|
658
|
+
>>> merged.threads
|
|
659
|
+
4
|
|
660
|
+
>>> merged.verbose
|
|
661
|
+
False
|
|
292
662
|
"""
|
|
293
663
|
|
|
294
664
|
if self.PARSED:
|
|
@@ -296,27 +666,40 @@ class Options:
|
|
|
296
666
|
"base options have already been parsed, cannot merge. See `Options.parse()` for more information."
|
|
297
667
|
)
|
|
298
668
|
|
|
299
|
-
if new
|
|
300
|
-
raise
|
|
301
|
-
"new options have already been parsed, cannot merge. See `Options.parse()` for more information."
|
|
302
|
-
)
|
|
669
|
+
if not new:
|
|
670
|
+
raise ValueError("at least one new Options instance is required to merge")
|
|
303
671
|
|
|
304
|
-
|
|
672
|
+
for i, opt in enumerate(new):
|
|
673
|
+
if not isinstance(opt, Options):
|
|
674
|
+
raise TypeError(f"expected an <Options> object, but got {type(opt)} in index {i}")
|
|
675
|
+
if opt.PARSED:
|
|
676
|
+
raise RuntimeError(
|
|
677
|
+
f"new options at index {i} have already been parsed, cannot merge. "
|
|
678
|
+
+ "See `Options.parse()` for more information."
|
|
679
|
+
)
|
|
305
680
|
|
|
306
|
-
|
|
681
|
+
# Add the new options to the current options.
|
|
682
|
+
for n in new:
|
|
683
|
+
self.options += n.options
|
|
684
|
+
|
|
685
|
+
if not skip_parse:
|
|
686
|
+
self.parse()
|
|
307
687
|
|
|
308
688
|
return self
|
|
309
689
|
|
|
310
690
|
@classmethod
|
|
311
691
|
def from_dict(cls, data: dict[str, Any]) -> "Options":
|
|
312
692
|
"""
|
|
313
|
-
Creates an instance of `Options` from a dictionary.
|
|
314
|
-
should have the following structure:
|
|
693
|
+
Creates an instance of `Options` from a dictionary.
|
|
315
694
|
|
|
695
|
+
The dictionary should have the following structure:
|
|
696
|
+
|
|
697
|
+
```python
|
|
316
698
|
{
|
|
317
699
|
"duration": "30",
|
|
318
700
|
"threads": 4,
|
|
319
701
|
}
|
|
702
|
+
```
|
|
320
703
|
|
|
321
704
|
Parameters
|
|
322
705
|
----------
|
|
@@ -326,25 +709,39 @@ class Options:
|
|
|
326
709
|
Returns
|
|
327
710
|
-------
|
|
328
711
|
Options
|
|
329
|
-
An instance of `Options
|
|
712
|
+
An instance of `Options` with options created from the dictionary.
|
|
713
|
+
|
|
714
|
+
Examples
|
|
715
|
+
--------
|
|
716
|
+
>>> data = {"duration": "30s", "threads": 4}
|
|
717
|
+
>>> options = Options.from_dict(data)
|
|
718
|
+
>>> options.duration
|
|
719
|
+
'30s'
|
|
720
|
+
>>> options.threads
|
|
721
|
+
4
|
|
330
722
|
"""
|
|
331
723
|
|
|
332
|
-
|
|
724
|
+
options = []
|
|
333
725
|
for key, value in data.items():
|
|
334
|
-
|
|
335
|
-
|
|
726
|
+
opt = Option(name=key, option_type=type(value), default=value)
|
|
727
|
+
options.append(opt)
|
|
336
728
|
|
|
337
|
-
return cls(*
|
|
729
|
+
return cls(*options)
|
|
338
730
|
|
|
339
731
|
@classmethod
|
|
340
732
|
def from_parameters_dict(cls, parameters_dict: list[dict[str, Any]]) -> "Options":
|
|
341
733
|
"""
|
|
734
|
+
!!! warning
|
|
735
|
+
|
|
736
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
737
|
+
`Options.from_parameters_dict` -> `Options.from_options_dict`
|
|
738
|
+
|
|
342
739
|
Creates an instance of `Options` from parameters in dict form. Each
|
|
343
740
|
entry is the dict representation of a `Parameter`.
|
|
344
741
|
|
|
345
742
|
Parameters
|
|
346
743
|
----------
|
|
347
|
-
|
|
744
|
+
parameters_dict : list[dict[str, Any]]
|
|
348
745
|
The list of dictionaries (parameter entries).
|
|
349
746
|
|
|
350
747
|
Returns
|
|
@@ -353,6 +750,12 @@ class Options:
|
|
|
353
750
|
An instance of `Options`.
|
|
354
751
|
"""
|
|
355
752
|
|
|
753
|
+
deprecated(
|
|
754
|
+
name="Options.from_parameters_dict",
|
|
755
|
+
reason="`Parameter` is deprecated, use `Option` instead. "
|
|
756
|
+
"Options.from_parameters_dict -> Options.from_options_dict",
|
|
757
|
+
)
|
|
758
|
+
|
|
356
759
|
parameters = []
|
|
357
760
|
for parameter_dict in parameters_dict:
|
|
358
761
|
parameter = Parameter.from_dict(parameter_dict)
|
|
@@ -360,10 +763,58 @@ class Options:
|
|
|
360
763
|
|
|
361
764
|
return cls(*parameters)
|
|
362
765
|
|
|
766
|
+
@classmethod
|
|
767
|
+
def from_options_dict(cls, options_dict: list[dict[str, Any]]) -> "Options":
|
|
768
|
+
"""
|
|
769
|
+
Creates an instance of `Options` from a list of `Option` objects in
|
|
770
|
+
dict form. Each entry is the dict representation of an `Option`.
|
|
771
|
+
|
|
772
|
+
Parameters
|
|
773
|
+
----------
|
|
774
|
+
options_dict : list[dict[str, Any]]
|
|
775
|
+
The list of dictionaries (`Option` entries).
|
|
776
|
+
|
|
777
|
+
Returns
|
|
778
|
+
-------
|
|
779
|
+
Options
|
|
780
|
+
An instance of `Options`.
|
|
781
|
+
|
|
782
|
+
Examples
|
|
783
|
+
--------
|
|
784
|
+
>>> options_dict = [
|
|
785
|
+
... {"name": "duration", "option_type": "<class 'str'>", "default": "30s"},
|
|
786
|
+
... {"name": "threads", "option_type": "<class 'int'>", "default": 4}
|
|
787
|
+
... ]
|
|
788
|
+
>>> options = Options.from_options_dict(options_dict)
|
|
789
|
+
>>> options.duration
|
|
790
|
+
'30s'
|
|
791
|
+
>>> options.threads
|
|
792
|
+
4
|
|
793
|
+
"""
|
|
794
|
+
|
|
795
|
+
options = []
|
|
796
|
+
for opt_dict in options_dict:
|
|
797
|
+
opt = Option.from_dict(opt_dict)
|
|
798
|
+
options.append(opt)
|
|
799
|
+
|
|
800
|
+
return cls(*options)
|
|
801
|
+
|
|
363
802
|
def __getattr__(self, name: str) -> Any:
|
|
364
803
|
"""
|
|
365
|
-
Gets an attribute of the options.
|
|
366
|
-
|
|
804
|
+
Gets an attribute of the options.
|
|
805
|
+
|
|
806
|
+
This is called when an attribute is accessed. It parses the options
|
|
807
|
+
if they have not been parsed yet.
|
|
808
|
+
|
|
809
|
+
Parameters
|
|
810
|
+
----------
|
|
811
|
+
name : str
|
|
812
|
+
The name of the attribute to get.
|
|
813
|
+
|
|
814
|
+
Returns
|
|
815
|
+
-------
|
|
816
|
+
Any
|
|
817
|
+
The value of the attribute.
|
|
367
818
|
"""
|
|
368
819
|
|
|
369
820
|
if not self.PARSED:
|
|
@@ -376,13 +827,17 @@ class Options:
|
|
|
376
827
|
Parses the options using command-line arguments, environment variables
|
|
377
828
|
and default values.
|
|
378
829
|
|
|
830
|
+
This is an internal method that is called by `parse()` and `__getattr__()`.
|
|
831
|
+
It sets the `PARSED` flag to True and sets the values of the options
|
|
832
|
+
based on command-line arguments, environment variables, and default values.
|
|
833
|
+
|
|
379
834
|
Raises
|
|
380
835
|
------
|
|
381
836
|
ValueError
|
|
382
|
-
If a required
|
|
837
|
+
If a required option is not provided through a command-line
|
|
383
838
|
argument, an environment variable or a default value.
|
|
384
839
|
TypeError
|
|
385
|
-
If
|
|
840
|
+
If an option is not an `Option` or `Parameter` (deprecated) object.
|
|
386
841
|
ValueError
|
|
387
842
|
If an environment variable is not of the type of the corresponding
|
|
388
843
|
parameter.
|
|
@@ -390,7 +845,7 @@ class Options:
|
|
|
390
845
|
|
|
391
846
|
self.PARSED = True
|
|
392
847
|
|
|
393
|
-
if not self.
|
|
848
|
+
if not self.options:
|
|
394
849
|
return
|
|
395
850
|
|
|
396
851
|
parser = argparse.ArgumentParser(
|
|
@@ -400,44 +855,46 @@ class Options:
|
|
|
400
855
|
+ "or environment variables.",
|
|
401
856
|
allow_abbrev=False,
|
|
402
857
|
)
|
|
403
|
-
|
|
858
|
+
options_by_field_name: dict[str, Option] = {}
|
|
404
859
|
|
|
405
|
-
for
|
|
406
|
-
if not isinstance(
|
|
407
|
-
raise TypeError(
|
|
860
|
+
for ix, option in enumerate(self.options):
|
|
861
|
+
if not isinstance(option, Option) and not isinstance(option, Parameter):
|
|
862
|
+
raise TypeError(
|
|
863
|
+
f"expected an <Option> (or deprecated <Parameter>) object, but got {type(option)} in index {ix}"
|
|
864
|
+
)
|
|
408
865
|
|
|
409
866
|
# See comment below about ipykernel adding a `-f` argument. We
|
|
410
|
-
# restrict
|
|
867
|
+
# restrict options from having the name 'f' or 'fff' for that
|
|
411
868
|
# reason.
|
|
412
|
-
if
|
|
413
|
-
raise ValueError("
|
|
869
|
+
if option.name == "f" or option.name == "fff":
|
|
870
|
+
raise ValueError("option names 'f', 'fff' are reserved for internal use")
|
|
414
871
|
|
|
415
|
-
if
|
|
416
|
-
raise ValueError("
|
|
872
|
+
if option.name == "PARSED":
|
|
873
|
+
raise ValueError("option name 'PARSED' is reserved for internal use")
|
|
417
874
|
|
|
418
875
|
# Remove any leading '-'. This is in line with argparse's behavior.
|
|
419
|
-
|
|
876
|
+
option.name = option.name.lstrip("-")
|
|
420
877
|
|
|
421
878
|
kwargs = {
|
|
422
|
-
"type":
|
|
423
|
-
"help": self._description(
|
|
879
|
+
"type": self._option_type(option) if self._option_type(option) is not bool else str,
|
|
880
|
+
"help": self._description(option),
|
|
424
881
|
}
|
|
425
882
|
|
|
426
|
-
if
|
|
427
|
-
kwargs["choices"] =
|
|
883
|
+
if option.choices is not None:
|
|
884
|
+
kwargs["choices"] = option.choices
|
|
428
885
|
|
|
429
886
|
parser.add_argument(
|
|
430
|
-
f"-{
|
|
431
|
-
f"--{
|
|
887
|
+
f"-{option.name}",
|
|
888
|
+
f"--{option.name}",
|
|
432
889
|
**kwargs,
|
|
433
890
|
)
|
|
434
891
|
|
|
435
|
-
# Store the
|
|
892
|
+
# Store the option by its field name for easy access later. argparse
|
|
436
893
|
# replaces '-' with '_', so we do the same here.
|
|
437
|
-
|
|
894
|
+
options_by_field_name[option.name.replace("-", "_")] = option
|
|
438
895
|
|
|
439
896
|
# The ipkyernel uses a `-f` argument by default that it passes to the
|
|
440
|
-
# execution. We don
|
|
897
|
+
# execution. We don't want to ignore this argument because we get an
|
|
441
898
|
# error. Fix source: https://stackoverflow.com/a/56349168
|
|
442
899
|
parser.add_argument(
|
|
443
900
|
"-f",
|
|
@@ -452,67 +909,119 @@ class Options:
|
|
|
452
909
|
if arg == "fff" or arg == "f":
|
|
453
910
|
continue
|
|
454
911
|
|
|
455
|
-
|
|
912
|
+
option = options_by_field_name[arg]
|
|
456
913
|
|
|
457
|
-
# First, attempt to set the value of
|
|
914
|
+
# First, attempt to set the value of an option from the
|
|
458
915
|
# command-line args.
|
|
459
916
|
arg_value = getattr(args, arg)
|
|
460
917
|
if arg_value is not None:
|
|
461
|
-
value = self.
|
|
918
|
+
value = self._option_value(option, arg_value)
|
|
462
919
|
setattr(self, arg, value)
|
|
463
920
|
continue
|
|
464
921
|
|
|
465
|
-
# Second, attempt to set the value of
|
|
922
|
+
# Second, attempt to set the value of am option from the
|
|
466
923
|
# environment variables.
|
|
467
924
|
upper_name = arg.upper()
|
|
468
925
|
env_value = os.getenv(upper_name)
|
|
469
926
|
if env_value is not None:
|
|
470
927
|
try:
|
|
471
|
-
typed_env_value =
|
|
928
|
+
typed_env_value = (
|
|
929
|
+
self._option_type(option)(env_value) if self._option_type(option) is not bool else env_value
|
|
930
|
+
)
|
|
472
931
|
except ValueError:
|
|
473
|
-
raise ValueError(
|
|
932
|
+
raise ValueError(
|
|
933
|
+
f'environment variable "{upper_name}" is not of type {self._option_type(option)}'
|
|
934
|
+
) from None
|
|
474
935
|
|
|
475
|
-
value = self.
|
|
936
|
+
value = self._option_value(option, typed_env_value)
|
|
476
937
|
setattr(self, arg, value)
|
|
477
938
|
continue
|
|
478
939
|
|
|
479
940
|
# Finally, attempt to set a default value. This is only allowed
|
|
480
|
-
# for non-required
|
|
481
|
-
if not
|
|
482
|
-
setattr(self, arg,
|
|
941
|
+
# for non-required options.
|
|
942
|
+
if not option.required:
|
|
943
|
+
setattr(self, arg, option.default)
|
|
483
944
|
continue
|
|
484
945
|
|
|
485
|
-
# At this point, the
|
|
946
|
+
# At this point, the option is required and no value was
|
|
486
947
|
# provided
|
|
487
948
|
raise ValueError(
|
|
488
|
-
f'
|
|
949
|
+
f'option "{arg}" is required but not provided through: command-line args, env vars, or default value'
|
|
489
950
|
)
|
|
490
951
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
952
|
+
def _description(self, option: Option) -> str:
|
|
953
|
+
"""
|
|
954
|
+
Returns a description for an option.
|
|
494
955
|
|
|
495
|
-
|
|
956
|
+
This is an internal method used to create the help text for options
|
|
957
|
+
in the command-line argument parser.
|
|
496
958
|
|
|
497
|
-
|
|
959
|
+
Parameters
|
|
960
|
+
----------
|
|
961
|
+
option : Option
|
|
962
|
+
The option to get the description for.
|
|
963
|
+
|
|
964
|
+
Returns
|
|
965
|
+
-------
|
|
966
|
+
str
|
|
967
|
+
A formatted description string for the option.
|
|
968
|
+
"""
|
|
969
|
+
|
|
970
|
+
description = ""
|
|
971
|
+
if isinstance(option, Parameter):
|
|
972
|
+
description = "DEPRECATED (initialized with <Parameter>, use <Option> instead) "
|
|
973
|
+
|
|
974
|
+
description += f"[env var: {option.name.upper()}]"
|
|
975
|
+
|
|
976
|
+
if option.required:
|
|
498
977
|
description += " (required)"
|
|
499
978
|
|
|
500
|
-
if
|
|
501
|
-
description += f" (default: {
|
|
979
|
+
if option.default is not None:
|
|
980
|
+
description += f" (default: {option.default})"
|
|
981
|
+
|
|
982
|
+
description += f" (type: {self._option_type(option).__name__})"
|
|
983
|
+
|
|
984
|
+
if isinstance(option, Option) and option.additional_attributes is not None:
|
|
985
|
+
description += f" (additional attributes: {option.additional_attributes})"
|
|
986
|
+
|
|
987
|
+
if isinstance(option, Option) and option.control_type is not None:
|
|
988
|
+
description += f" (control type: {option.control_type})"
|
|
502
989
|
|
|
503
|
-
|
|
990
|
+
if isinstance(option, Option) and option.hidden_from:
|
|
991
|
+
description += f" (hidden from: {', '.join(option.hidden_from)})"
|
|
504
992
|
|
|
505
|
-
if
|
|
506
|
-
description += f": {
|
|
993
|
+
if isinstance(option, Option) and option.display_name is not None:
|
|
994
|
+
description += f" (display name: {option.display_name})"
|
|
995
|
+
|
|
996
|
+
if option.description is not None and option.description != "":
|
|
997
|
+
description += f": {option.description}"
|
|
507
998
|
|
|
508
999
|
return description
|
|
509
1000
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
1001
|
+
def _option_value(self, option: Option, value: Any) -> Any:
|
|
1002
|
+
"""
|
|
1003
|
+
Handles how the value of an option is extracted.
|
|
513
1004
|
|
|
514
|
-
|
|
515
|
-
|
|
1005
|
+
This is an internal method that converts string values to boolean
|
|
1006
|
+
values for boolean options.
|
|
1007
|
+
|
|
1008
|
+
Parameters
|
|
1009
|
+
----------
|
|
1010
|
+
option : Option
|
|
1011
|
+
The option to extract the value for.
|
|
1012
|
+
value : Any
|
|
1013
|
+
The value to extract.
|
|
1014
|
+
|
|
1015
|
+
Returns
|
|
1016
|
+
-------
|
|
1017
|
+
Any
|
|
1018
|
+
The extracted value. For boolean options, string values like
|
|
1019
|
+
"true", "1", "t", "y", and "yes" are converted to True, and
|
|
1020
|
+
other values are converted to False.
|
|
1021
|
+
"""
|
|
1022
|
+
|
|
1023
|
+
opt_type = self._option_type(option)
|
|
1024
|
+
if opt_type is not bool:
|
|
516
1025
|
return value
|
|
517
1026
|
|
|
518
1027
|
value = str(value).lower()
|
|
@@ -521,3 +1030,87 @@ class Options:
|
|
|
521
1030
|
return True
|
|
522
1031
|
|
|
523
1032
|
return False
|
|
1033
|
+
|
|
1034
|
+
@staticmethod
|
|
1035
|
+
def _option_type(option: Option | Parameter) -> type:
|
|
1036
|
+
"""
|
|
1037
|
+
Get the type of an option.
|
|
1038
|
+
|
|
1039
|
+
This auxiliary function was introduced for backwards compatibility with
|
|
1040
|
+
the deprecated `Parameter` class. Once `Parameter` is removed, this function
|
|
1041
|
+
can be removed as well. When the function is removed, use the
|
|
1042
|
+
`option.option_type` attribute directly, instead of calling this function.
|
|
1043
|
+
|
|
1044
|
+
Parameters
|
|
1045
|
+
----------
|
|
1046
|
+
option : Union[Option, Parameter]
|
|
1047
|
+
The option to get the type for.
|
|
1048
|
+
|
|
1049
|
+
Returns
|
|
1050
|
+
-------
|
|
1051
|
+
type
|
|
1052
|
+
The type of the option.
|
|
1053
|
+
|
|
1054
|
+
Raises
|
|
1055
|
+
------
|
|
1056
|
+
TypeError
|
|
1057
|
+
If the option is not an `Option` or `Parameter` object.
|
|
1058
|
+
"""
|
|
1059
|
+
|
|
1060
|
+
if isinstance(option, Option):
|
|
1061
|
+
return option.option_type
|
|
1062
|
+
elif isinstance(option, Parameter):
|
|
1063
|
+
return option.param_type
|
|
1064
|
+
else:
|
|
1065
|
+
raise TypeError(f"expected an <Option> (or deprecated <Parameter>) object, but got {type(option)}")
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
class OptionsEnforcement:
|
|
1069
|
+
"""
|
|
1070
|
+
OptionsEnforcement is a class that provides rules for how the options
|
|
1071
|
+
are enforced on Nextmv Cloud.
|
|
1072
|
+
|
|
1073
|
+
This class is used to enforce options in the Nextmv Cloud. It is not used
|
|
1074
|
+
in the local `Options` class, but it is used to control validation when a run
|
|
1075
|
+
is submitted to the Nextmv Cloud.
|
|
1076
|
+
|
|
1077
|
+
Parameters
|
|
1078
|
+
----------
|
|
1079
|
+
strict: bool default = False
|
|
1080
|
+
If True, the options additional options that are configured will not
|
|
1081
|
+
pass validation. This means that only the options that are defined in the
|
|
1082
|
+
`Options` class will be allowed. If False, additional options that are
|
|
1083
|
+
not defined in the `Options` class will be allowed.
|
|
1084
|
+
validation_enforce: bool default = False
|
|
1085
|
+
If True, the options will be validated against your option configuration
|
|
1086
|
+
validation rules. If False, the options will not be validated.
|
|
1087
|
+
"""
|
|
1088
|
+
|
|
1089
|
+
strict: bool = False
|
|
1090
|
+
"""
|
|
1091
|
+
If True, the options additional options that are configured will not
|
|
1092
|
+
pass validation. This means that only the options that are defined in the
|
|
1093
|
+
`Options` class will be allowed. If False, additional options that are
|
|
1094
|
+
not defined in the `Options` class will be allowed.
|
|
1095
|
+
"""
|
|
1096
|
+
validation_enforce: bool = False
|
|
1097
|
+
"""
|
|
1098
|
+
If True, the options will be validated against your option configuration
|
|
1099
|
+
validation rules. If False, the options will not be validated.
|
|
1100
|
+
"""
|
|
1101
|
+
|
|
1102
|
+
def __init__(self, strict: bool = False, validation_enforce: bool = False):
|
|
1103
|
+
"""
|
|
1104
|
+
Initialize an OptionsEnforcement instance with the provided rules.
|
|
1105
|
+
|
|
1106
|
+
Parameters
|
|
1107
|
+
----------
|
|
1108
|
+
strict : bool, optional
|
|
1109
|
+
If True, only options defined in the `Options` class will be allowed.
|
|
1110
|
+
Defaults to False.
|
|
1111
|
+
validation_enforced : bool, optional
|
|
1112
|
+
If True, options will be validated against the configuration rules.
|
|
1113
|
+
Defaults to False.
|
|
1114
|
+
"""
|
|
1115
|
+
self.strict = strict
|
|
1116
|
+
self.validation_enforce = validation_enforce
|