nextmv 1.0.0__py3-none-any.whl → 1.0.0.dev0__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 +2 -1
- nextmv/__init__.py +4 -0
- nextmv/cli/CONTRIBUTING.md +40 -112
- nextmv/cli/cloud/__init__.py +0 -4
- nextmv/cli/cloud/acceptance/create.py +22 -20
- nextmv/cli/cloud/acceptance/delete.py +12 -8
- nextmv/cli/cloud/acceptance/get.py +10 -9
- nextmv/cli/cloud/acceptance/list.py +3 -3
- nextmv/cli/cloud/acceptance/update.py +6 -6
- nextmv/cli/cloud/account/__init__.py +3 -3
- nextmv/cli/cloud/account/create.py +11 -11
- nextmv/cli/cloud/account/delete.py +8 -7
- nextmv/cli/cloud/account/get.py +3 -3
- nextmv/cli/cloud/account/update.py +5 -5
- nextmv/cli/cloud/app/create.py +26 -25
- nextmv/cli/cloud/app/delete.py +7 -6
- nextmv/cli/cloud/app/exists.py +2 -2
- nextmv/cli/cloud/app/get.py +2 -2
- nextmv/cli/cloud/app/list.py +3 -3
- nextmv/cli/cloud/app/push.py +54 -349
- nextmv/cli/cloud/app/update.py +12 -12
- nextmv/cli/cloud/batch/create.py +28 -26
- nextmv/cli/cloud/batch/delete.py +10 -6
- nextmv/cli/cloud/batch/get.py +9 -9
- nextmv/cli/cloud/batch/list.py +3 -3
- nextmv/cli/cloud/batch/metadata.py +4 -4
- nextmv/cli/cloud/batch/update.py +6 -6
- nextmv/cli/cloud/data/__init__.py +1 -1
- nextmv/cli/cloud/data/upload.py +15 -15
- nextmv/cli/cloud/ensemble/__init__.py +0 -2
- nextmv/cli/cloud/ensemble/create.py +22 -21
- nextmv/cli/cloud/ensemble/delete.py +10 -6
- nextmv/cli/cloud/ensemble/get.py +4 -4
- nextmv/cli/cloud/ensemble/update.py +9 -9
- nextmv/cli/cloud/input_set/__init__.py +0 -2
- nextmv/cli/cloud/input_set/create.py +22 -22
- nextmv/cli/cloud/input_set/get.py +3 -3
- nextmv/cli/cloud/input_set/list.py +3 -3
- nextmv/cli/cloud/input_set/update.py +24 -24
- nextmv/cli/cloud/instance/create.py +15 -14
- nextmv/cli/cloud/instance/delete.py +7 -6
- nextmv/cli/cloud/instance/exists.py +2 -2
- nextmv/cli/cloud/instance/get.py +2 -2
- nextmv/cli/cloud/instance/list.py +3 -3
- nextmv/cli/cloud/instance/update.py +14 -14
- nextmv/cli/cloud/managed_input/create.py +16 -14
- nextmv/cli/cloud/managed_input/delete.py +8 -7
- nextmv/cli/cloud/managed_input/get.py +3 -3
- nextmv/cli/cloud/managed_input/list.py +3 -3
- nextmv/cli/cloud/managed_input/update.py +9 -9
- nextmv/cli/cloud/run/cancel.py +2 -2
- nextmv/cli/cloud/run/create.py +40 -34
- nextmv/cli/cloud/run/get.py +8 -8
- nextmv/cli/cloud/run/input.py +4 -4
- nextmv/cli/cloud/run/list.py +6 -6
- nextmv/cli/cloud/run/logs.py +10 -9
- nextmv/cli/cloud/run/metadata.py +4 -4
- nextmv/cli/cloud/run/track.py +33 -32
- nextmv/cli/cloud/scenario/create.py +21 -21
- nextmv/cli/cloud/scenario/delete.py +10 -6
- nextmv/cli/cloud/scenario/get.py +9 -9
- nextmv/cli/cloud/scenario/list.py +3 -3
- nextmv/cli/cloud/scenario/metadata.py +4 -4
- nextmv/cli/cloud/scenario/update.py +6 -6
- nextmv/cli/cloud/secrets/create.py +17 -17
- nextmv/cli/cloud/secrets/delete.py +10 -6
- nextmv/cli/cloud/secrets/get.py +4 -4
- nextmv/cli/cloud/secrets/list.py +3 -3
- nextmv/cli/cloud/secrets/update.py +20 -17
- nextmv/cli/cloud/upload/create.py +2 -2
- nextmv/cli/cloud/version/create.py +10 -9
- nextmv/cli/cloud/version/delete.py +7 -6
- nextmv/cli/cloud/version/exists.py +2 -2
- nextmv/cli/cloud/version/get.py +2 -2
- nextmv/cli/cloud/version/list.py +3 -3
- nextmv/cli/cloud/version/update.py +8 -8
- nextmv/cli/community/__init__.py +1 -1
- nextmv/cli/community/clone.py +204 -20
- nextmv/cli/community/list.py +125 -60
- nextmv/cli/configuration/config.py +10 -43
- nextmv/cli/configuration/create.py +7 -7
- nextmv/cli/configuration/delete.py +8 -8
- nextmv/cli/configuration/list.py +3 -3
- nextmv/cli/main.py +36 -26
- nextmv/cli/message.py +54 -71
- nextmv/cli/options.py +0 -28
- nextmv/cli/version.py +1 -1
- nextmv/cloud/__init__.py +38 -14
- nextmv/cloud/acceptance_test.py +65 -1
- nextmv/cloud/account.py +6 -1
- nextmv/cloud/application/__init__.py +75 -18
- nextmv/cloud/application/_acceptance.py +8 -13
- nextmv/cloud/application/_batch_scenario.py +19 -4
- nextmv/cloud/application/_input_set.py +6 -42
- nextmv/cloud/application/_instance.py +3 -3
- nextmv/cloud/application/_managed_input.py +2 -2
- nextmv/cloud/application/_version.py +3 -4
- nextmv/cloud/batch_experiment.py +1 -3
- nextmv/cloud/integration.py +4 -7
- nextmv/deprecated.py +3 -5
- nextmv/input.py +52 -0
- nextmv/local/runner.py +1 -1
- nextmv/model.py +11 -50
- nextmv/options.py +256 -11
- nextmv/output.py +62 -0
- nextmv/run.py +10 -1
- nextmv/status.py +51 -1
- {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/METADATA +4 -5
- nextmv-1.0.0.dev0.dist-info/RECORD +158 -0
- nextmv/cli/cloud/ensemble/list.py +0 -63
- nextmv/cli/cloud/input_set/delete.py +0 -64
- nextmv/cli/cloud/shadow/__init__.py +0 -33
- nextmv/cli/cloud/shadow/create.py +0 -184
- nextmv/cli/cloud/shadow/delete.py +0 -64
- nextmv/cli/cloud/shadow/get.py +0 -61
- nextmv/cli/cloud/shadow/list.py +0 -63
- nextmv/cli/cloud/shadow/metadata.py +0 -66
- nextmv/cli/cloud/shadow/start.py +0 -43
- nextmv/cli/cloud/shadow/stop.py +0 -53
- nextmv/cli/cloud/shadow/update.py +0 -96
- nextmv/cli/cloud/switchback/__init__.py +0 -33
- nextmv/cli/cloud/switchback/create.py +0 -151
- nextmv/cli/cloud/switchback/delete.py +0 -64
- nextmv/cli/cloud/switchback/get.py +0 -62
- nextmv/cli/cloud/switchback/list.py +0 -63
- nextmv/cli/cloud/switchback/metadata.py +0 -68
- nextmv/cli/cloud/switchback/start.py +0 -43
- nextmv/cli/cloud/switchback/stop.py +0 -53
- nextmv/cli/cloud/switchback/update.py +0 -96
- nextmv/cli/confirm.py +0 -34
- nextmv/cloud/application/_shadow.py +0 -320
- nextmv/cloud/application/_switchback.py +0 -332
- nextmv/cloud/community.py +0 -446
- nextmv/cloud/shadow.py +0 -254
- nextmv/cloud/switchback.py +0 -228
- nextmv-1.0.0.dist-info/RECORD +0 -185
- nextmv-1.0.0.dist-info/entry_points.txt +0 -2
- {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dist-info → nextmv-1.0.0.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/model.py
CHANGED
|
@@ -18,8 +18,6 @@ deployed to Nextmv Cloud for execution.
|
|
|
18
18
|
import logging
|
|
19
19
|
import os
|
|
20
20
|
import shutil
|
|
21
|
-
import sqlite3
|
|
22
|
-
import time
|
|
23
21
|
import warnings
|
|
24
22
|
from dataclasses import dataclass
|
|
25
23
|
from typing import Any
|
|
@@ -86,9 +84,6 @@ def _custom_showwarning(message, category, filename, lineno, file=None, line=Non
|
|
|
86
84
|
if "mlflow/pyfunc/__init__.py" in filename:
|
|
87
85
|
return
|
|
88
86
|
|
|
89
|
-
if "/mlflow/tracing/provider.py" in filename:
|
|
90
|
-
return
|
|
91
|
-
|
|
92
87
|
_original_showwarning(message, category, filename, lineno, file, line)
|
|
93
88
|
|
|
94
89
|
|
|
@@ -293,6 +288,7 @@ class Model:
|
|
|
293
288
|
) from e
|
|
294
289
|
|
|
295
290
|
finally:
|
|
291
|
+
from mlflow.models import infer_signature
|
|
296
292
|
from mlflow.pyfunc import PythonModel, save_model
|
|
297
293
|
|
|
298
294
|
class MLFlowModel(PythonModel):
|
|
@@ -346,16 +342,12 @@ class Model:
|
|
|
346
342
|
|
|
347
343
|
_cleanup_python_model(model_dir, configuration, verbose=False)
|
|
348
344
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
# signature = infer_signature(
|
|
356
|
-
# model_input={},
|
|
357
|
-
# params=options_dict,
|
|
358
|
-
# )
|
|
345
|
+
signature = None
|
|
346
|
+
if configuration.options is not None:
|
|
347
|
+
options_dict = configuration.options.to_dict()
|
|
348
|
+
signature = infer_signature(
|
|
349
|
+
params=options_dict,
|
|
350
|
+
)
|
|
359
351
|
|
|
360
352
|
# We use mlflow to save the model to the local filesystem, to be able to
|
|
361
353
|
# load it later on.
|
|
@@ -364,7 +356,7 @@ class Model:
|
|
|
364
356
|
path=model_path, # Customize the name of the model location.
|
|
365
357
|
infer_code_paths=True, # Makes the imports portable.
|
|
366
358
|
python_model=MLFlowModel(),
|
|
367
|
-
|
|
359
|
+
signature=signature, # Allows us to work with our own `Options` class.
|
|
368
360
|
)
|
|
369
361
|
|
|
370
362
|
# Create an auxiliary requirements file with the model dependencies.
|
|
@@ -423,7 +415,9 @@ def _cleanup_python_model(
|
|
|
423
415
|
if os.path.exists(model_path):
|
|
424
416
|
shutil.rmtree(model_path)
|
|
425
417
|
|
|
426
|
-
|
|
418
|
+
mlruns_path = os.path.join(model_dir, "mlruns")
|
|
419
|
+
if os.path.exists(mlruns_path):
|
|
420
|
+
shutil.rmtree(mlruns_path)
|
|
427
421
|
|
|
428
422
|
requirements_file = os.path.join(model_dir, _REQUIREMENTS_FILE)
|
|
429
423
|
if os.path.exists(requirements_file):
|
|
@@ -435,36 +429,3 @@ def _cleanup_python_model(
|
|
|
435
429
|
|
|
436
430
|
if verbose:
|
|
437
431
|
log("🧹 Cleaned up Python model artifacts.")
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
def _cleanup_mlflow_db(model_dir: str) -> None:
|
|
441
|
-
"""
|
|
442
|
-
Clean up the mlflow.db file created during model packaging.
|
|
443
|
-
|
|
444
|
-
Parameters
|
|
445
|
-
----------
|
|
446
|
-
model_dir : str
|
|
447
|
-
The directory where the model was saved.
|
|
448
|
-
"""
|
|
449
|
-
mlflow_db_path = os.path.join(model_dir, "mlflow.db")
|
|
450
|
-
if not os.path.exists(mlflow_db_path):
|
|
451
|
-
return
|
|
452
|
-
|
|
453
|
-
# Try to close any open SQLite connections and retry deletion
|
|
454
|
-
max_retries = 5
|
|
455
|
-
for attempt in range(max_retries):
|
|
456
|
-
try:
|
|
457
|
-
# Attempt to connect and close to release any locks
|
|
458
|
-
try:
|
|
459
|
-
conn = sqlite3.connect(mlflow_db_path)
|
|
460
|
-
conn.close()
|
|
461
|
-
except Exception:
|
|
462
|
-
pass
|
|
463
|
-
os.remove(mlflow_db_path)
|
|
464
|
-
break
|
|
465
|
-
except PermissionError:
|
|
466
|
-
if attempt < max_retries - 1:
|
|
467
|
-
time.sleep(0.5)
|
|
468
|
-
else:
|
|
469
|
-
log(f"Could not delete {mlflow_db_path} after {max_retries} retries due to file lock.")
|
|
470
|
-
break
|
nextmv/options.py
CHANGED
|
@@ -24,6 +24,155 @@ from dataclasses import dataclass
|
|
|
24
24
|
from typing import Any
|
|
25
25
|
|
|
26
26
|
from nextmv.base_model import BaseModel
|
|
27
|
+
from nextmv.deprecated import deprecated
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Parameter:
|
|
32
|
+
"""
|
|
33
|
+
!!! warning
|
|
34
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
35
|
+
|
|
36
|
+
Parameter that is used in a `Configuration`. When a parameter is required,
|
|
37
|
+
it is a good practice to provide a default value for it. This is because
|
|
38
|
+
the configuration will raise an error if a required parameter is not
|
|
39
|
+
provided through a command-line argument, an environment variable or a
|
|
40
|
+
default value.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
name : str
|
|
45
|
+
The name of the parameter.
|
|
46
|
+
|
|
47
|
+
param_type : type
|
|
48
|
+
The type of the parameter.
|
|
49
|
+
|
|
50
|
+
default : Any, optional
|
|
51
|
+
The default value of the parameter. Even though this is optional, it is
|
|
52
|
+
recommended to provide a default value for all parameters.
|
|
53
|
+
|
|
54
|
+
description : str, optional
|
|
55
|
+
An optional description of the parameter. This is useful for generating
|
|
56
|
+
help messages for the configuration.
|
|
57
|
+
|
|
58
|
+
required : bool, optional
|
|
59
|
+
Whether the parameter is required. If a parameter is required, it will
|
|
60
|
+
be an error to not provide a value for it, either through a command-line
|
|
61
|
+
argument, an environment variable or a default value.
|
|
62
|
+
|
|
63
|
+
choices : list[Optional[Any]], optional
|
|
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)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
name: str
|
|
73
|
+
"""The name of the parameter."""
|
|
74
|
+
param_type: type
|
|
75
|
+
"""The type of the parameter."""
|
|
76
|
+
|
|
77
|
+
default: Any | None = None
|
|
78
|
+
"""The default value of the parameter. Even though this is optional, it is
|
|
79
|
+
recommended to provide a default value for all parameters."""
|
|
80
|
+
description: str | None = None
|
|
81
|
+
"""An optional description of the parameter. This is useful for generating
|
|
82
|
+
help messages for the configuration."""
|
|
83
|
+
required: bool = False
|
|
84
|
+
"""Whether the parameter is required. If a parameter is required, it will
|
|
85
|
+
be an error to not provide a value for it, either trough a command-line
|
|
86
|
+
argument, an environment variable or a default value."""
|
|
87
|
+
choices: list[Any | None] = None
|
|
88
|
+
"""Limits values to a specific set of choices."""
|
|
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
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def from_dict(cls, data: dict[str, Any]) -> "Parameter":
|
|
104
|
+
"""
|
|
105
|
+
!!! warning
|
|
106
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
107
|
+
`Parameter.from_dict` -> `Option.from_dict`
|
|
108
|
+
|
|
109
|
+
Creates an instance of `Parameter` from a dictionary.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
data : dict[str, Any]
|
|
114
|
+
The dictionary representation of a parameter.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
Parameter
|
|
119
|
+
An instance of `Parameter`.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
deprecated(
|
|
123
|
+
name="Parameter.from_dict",
|
|
124
|
+
reason="`Parameter` is deprecated, use `Option` instead. Parameter.from_dict -> Option.from_dict",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
param_type_string = data["param_type"]
|
|
128
|
+
param_type = getattr(builtins, param_type_string.split("'")[1])
|
|
129
|
+
|
|
130
|
+
return Parameter(
|
|
131
|
+
name=data["name"],
|
|
132
|
+
param_type=param_type,
|
|
133
|
+
default=data.get("default"),
|
|
134
|
+
description=data.get("description"),
|
|
135
|
+
required=data.get("required", False),
|
|
136
|
+
choices=data.get("choices"),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def to_dict(self) -> dict[str, Any]:
|
|
140
|
+
"""
|
|
141
|
+
!!! warning
|
|
142
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
143
|
+
`Parameter.to_dict` -> `Option.to_dict`
|
|
144
|
+
|
|
145
|
+
Converts the parameter to a dict.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
dict[str, Any]
|
|
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
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
deprecated(
|
|
164
|
+
name="Parameter.to_dict",
|
|
165
|
+
reason="`Parameter` is deprecated, use `Option` instead. Parameter.to_dict -> Option.to_dict",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
"name": self.name,
|
|
170
|
+
"param_type": str(self.param_type),
|
|
171
|
+
"default": self.default,
|
|
172
|
+
"description": self.description,
|
|
173
|
+
"required": self.required,
|
|
174
|
+
"choices": self.choices,
|
|
175
|
+
}
|
|
27
176
|
|
|
28
177
|
|
|
29
178
|
@dataclass
|
|
@@ -276,7 +425,8 @@ class Options:
|
|
|
276
425
|
If a required option is not provided through a command-line
|
|
277
426
|
argument, an environment variable or a default value.
|
|
278
427
|
TypeError
|
|
279
|
-
If an option is not an `Option`
|
|
428
|
+
If an option is not either an `Option` or `Parameter` (deprecated)
|
|
429
|
+
object.
|
|
280
430
|
ValueError
|
|
281
431
|
If an environment variable is not of the type of the corresponding
|
|
282
432
|
parameter.
|
|
@@ -372,6 +522,27 @@ class Options:
|
|
|
372
522
|
|
|
373
523
|
return cloud_dict
|
|
374
524
|
|
|
525
|
+
def parameters_dict(self) -> list[dict[str, Any]]:
|
|
526
|
+
"""
|
|
527
|
+
!!! warning
|
|
528
|
+
`Parameter` is deprecated, use `Option` instead. `Options.parameters_dict` -> `Options.options_dict`
|
|
529
|
+
|
|
530
|
+
Converts the options to a list of dicts. Each dict is the dict
|
|
531
|
+
representation of a `Parameter`.
|
|
532
|
+
|
|
533
|
+
Returns
|
|
534
|
+
-------
|
|
535
|
+
list[dict[str, Any]]
|
|
536
|
+
The list of dictionaries (parameter entries).
|
|
537
|
+
"""
|
|
538
|
+
|
|
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
|
+
|
|
375
546
|
def options_dict(self) -> list[dict[str, Any]]:
|
|
376
547
|
"""
|
|
377
548
|
Converts the `Options` to a list of dicts. Each dict is the dict
|
|
@@ -435,7 +606,7 @@ class Options:
|
|
|
435
606
|
If a required option is not provided through a command-line
|
|
436
607
|
argument, an environment variable or a default value.
|
|
437
608
|
TypeError
|
|
438
|
-
If an option is not an `Option` object.
|
|
609
|
+
If an option is not an `Option` or `Parameter` (deprecated) object.
|
|
439
610
|
ValueError
|
|
440
611
|
If an environment variable is not of the type of the corresponding
|
|
441
612
|
parameter.
|
|
@@ -557,6 +728,41 @@ class Options:
|
|
|
557
728
|
|
|
558
729
|
return cls(*options)
|
|
559
730
|
|
|
731
|
+
@classmethod
|
|
732
|
+
def from_parameters_dict(cls, parameters_dict: list[dict[str, Any]]) -> "Options":
|
|
733
|
+
"""
|
|
734
|
+
!!! warning
|
|
735
|
+
|
|
736
|
+
`Parameter` is deprecated, use `Option` instead.
|
|
737
|
+
`Options.from_parameters_dict` -> `Options.from_options_dict`
|
|
738
|
+
|
|
739
|
+
Creates an instance of `Options` from parameters in dict form. Each
|
|
740
|
+
entry is the dict representation of a `Parameter`.
|
|
741
|
+
|
|
742
|
+
Parameters
|
|
743
|
+
----------
|
|
744
|
+
parameters_dict : list[dict[str, Any]]
|
|
745
|
+
The list of dictionaries (parameter entries).
|
|
746
|
+
|
|
747
|
+
Returns
|
|
748
|
+
-------
|
|
749
|
+
Options
|
|
750
|
+
An instance of `Options`.
|
|
751
|
+
"""
|
|
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
|
+
|
|
759
|
+
parameters = []
|
|
760
|
+
for parameter_dict in parameters_dict:
|
|
761
|
+
parameter = Parameter.from_dict(parameter_dict)
|
|
762
|
+
parameters.append(parameter)
|
|
763
|
+
|
|
764
|
+
return cls(*parameters)
|
|
765
|
+
|
|
560
766
|
@classmethod
|
|
561
767
|
def from_options_dict(cls, options_dict: list[dict[str, Any]]) -> "Options":
|
|
562
768
|
"""
|
|
@@ -631,7 +837,7 @@ class Options:
|
|
|
631
837
|
If a required option is not provided through a command-line
|
|
632
838
|
argument, an environment variable or a default value.
|
|
633
839
|
TypeError
|
|
634
|
-
If an option is not an `Option` object.
|
|
840
|
+
If an option is not an `Option` or `Parameter` (deprecated) object.
|
|
635
841
|
ValueError
|
|
636
842
|
If an environment variable is not of the type of the corresponding
|
|
637
843
|
parameter.
|
|
@@ -652,8 +858,10 @@ class Options:
|
|
|
652
858
|
options_by_field_name: dict[str, Option] = {}
|
|
653
859
|
|
|
654
860
|
for ix, option in enumerate(self.options):
|
|
655
|
-
if not isinstance(option, Option):
|
|
656
|
-
raise TypeError(
|
|
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
|
+
)
|
|
657
865
|
|
|
658
866
|
# See comment below about ipykernel adding a `-f` argument. We
|
|
659
867
|
# restrict options from having the name 'f' or 'fff' for that
|
|
@@ -668,7 +876,7 @@ class Options:
|
|
|
668
876
|
option.name = option.name.lstrip("-")
|
|
669
877
|
|
|
670
878
|
kwargs = {
|
|
671
|
-
"type": option
|
|
879
|
+
"type": self._option_type(option) if self._option_type(option) is not bool else str,
|
|
672
880
|
"help": self._description(option),
|
|
673
881
|
}
|
|
674
882
|
|
|
@@ -695,7 +903,7 @@ class Options:
|
|
|
695
903
|
help=argparse.SUPPRESS,
|
|
696
904
|
default="1",
|
|
697
905
|
)
|
|
698
|
-
args
|
|
906
|
+
args = parser.parse_args()
|
|
699
907
|
|
|
700
908
|
for arg in vars(args):
|
|
701
909
|
if arg == "fff" or arg == "f":
|
|
@@ -717,10 +925,12 @@ class Options:
|
|
|
717
925
|
env_value = os.getenv(upper_name)
|
|
718
926
|
if env_value is not None:
|
|
719
927
|
try:
|
|
720
|
-
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
|
+
)
|
|
721
931
|
except ValueError:
|
|
722
932
|
raise ValueError(
|
|
723
|
-
f'environment variable "{upper_name}" is not of type {option
|
|
933
|
+
f'environment variable "{upper_name}" is not of type {self._option_type(option)}'
|
|
724
934
|
) from None
|
|
725
935
|
|
|
726
936
|
value = self._option_value(option, typed_env_value)
|
|
@@ -758,6 +968,8 @@ class Options:
|
|
|
758
968
|
"""
|
|
759
969
|
|
|
760
970
|
description = ""
|
|
971
|
+
if isinstance(option, Parameter):
|
|
972
|
+
description = "DEPRECATED (initialized with <Parameter>, use <Option> instead) "
|
|
761
973
|
|
|
762
974
|
description += f"[env var: {option.name.upper()}]"
|
|
763
975
|
|
|
@@ -767,7 +979,7 @@ class Options:
|
|
|
767
979
|
if option.default is not None:
|
|
768
980
|
description += f" (default: {option.default})"
|
|
769
981
|
|
|
770
|
-
description += f" (type: {option.
|
|
982
|
+
description += f" (type: {self._option_type(option).__name__})"
|
|
771
983
|
|
|
772
984
|
if isinstance(option, Option) and option.additional_attributes is not None:
|
|
773
985
|
description += f" (additional attributes: {option.additional_attributes})"
|
|
@@ -808,7 +1020,7 @@ class Options:
|
|
|
808
1020
|
other values are converted to False.
|
|
809
1021
|
"""
|
|
810
1022
|
|
|
811
|
-
opt_type = option
|
|
1023
|
+
opt_type = self._option_type(option)
|
|
812
1024
|
if opt_type is not bool:
|
|
813
1025
|
return value
|
|
814
1026
|
|
|
@@ -819,6 +1031,39 @@ class Options:
|
|
|
819
1031
|
|
|
820
1032
|
return False
|
|
821
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
|
+
|
|
822
1067
|
|
|
823
1068
|
class OptionsEnforcement:
|
|
824
1069
|
"""
|
nextmv/output.py
CHANGED
|
@@ -66,6 +66,7 @@ from pydantic import AliasChoices, Field
|
|
|
66
66
|
|
|
67
67
|
from nextmv._serialization import serialize_json
|
|
68
68
|
from nextmv.base_model import BaseModel
|
|
69
|
+
from nextmv.deprecated import deprecated
|
|
69
70
|
from nextmv.logger import reset_stdout
|
|
70
71
|
from nextmv.options import Options
|
|
71
72
|
|
|
@@ -1517,6 +1518,67 @@ class LocalOutputWriter(OutputWriter):
|
|
|
1517
1518
|
)
|
|
1518
1519
|
|
|
1519
1520
|
|
|
1521
|
+
def write_local(
|
|
1522
|
+
output: Output | dict[str, Any],
|
|
1523
|
+
path: str | None = None,
|
|
1524
|
+
skip_stdout_reset: bool = False,
|
|
1525
|
+
) -> None:
|
|
1526
|
+
"""
|
|
1527
|
+
!!! warning
|
|
1528
|
+
`write_local` is deprecated, use `write` instead.
|
|
1529
|
+
|
|
1530
|
+
Write the output to the local filesystem or stdout.
|
|
1531
|
+
|
|
1532
|
+
This is a convenience function for instantiating a `LocalOutputWriter` and
|
|
1533
|
+
calling its `write` method.
|
|
1534
|
+
|
|
1535
|
+
Parameters
|
|
1536
|
+
----------
|
|
1537
|
+
output : Union[Output, dict[str, Any]]
|
|
1538
|
+
Output data to write. Can be an Output object or a dictionary.
|
|
1539
|
+
path : str, optional
|
|
1540
|
+
Path to write the output data to. The interpretation depends on the
|
|
1541
|
+
output format:
|
|
1542
|
+
|
|
1543
|
+
- For `OutputFormat.JSON`: File path for the JSON output. If None or
|
|
1544
|
+
empty, writes to stdout.
|
|
1545
|
+
- For `OutputFormat.CSV_ARCHIVE`: Directory path for CSV files. If None
|
|
1546
|
+
or empty, writes to a directory named "output" in the current working
|
|
1547
|
+
directory.
|
|
1548
|
+
skip_stdout_reset : bool, optional
|
|
1549
|
+
Skip resetting stdout before writing the output data. Default is False.
|
|
1550
|
+
|
|
1551
|
+
Raises
|
|
1552
|
+
------
|
|
1553
|
+
ValueError
|
|
1554
|
+
If the Output.output_format is not supported.
|
|
1555
|
+
TypeError
|
|
1556
|
+
If the output is of an unsupported type.
|
|
1557
|
+
|
|
1558
|
+
Notes
|
|
1559
|
+
-----
|
|
1560
|
+
This function detects if stdout was redirected and resets it to avoid
|
|
1561
|
+
unexpected behavior. If you want to skip this behavior, set the
|
|
1562
|
+
skip_stdout_reset parameter to True.
|
|
1563
|
+
|
|
1564
|
+
Examples
|
|
1565
|
+
--------
|
|
1566
|
+
>>> from nextmv.output import write_local, Output
|
|
1567
|
+
>>> # Write JSON to a file
|
|
1568
|
+
>>> write_local(Output(solution={"result": 42}), path="result.json")
|
|
1569
|
+
>>> # Write JSON to stdout
|
|
1570
|
+
>>> write_local({"simple": "data"})
|
|
1571
|
+
"""
|
|
1572
|
+
|
|
1573
|
+
deprecated(
|
|
1574
|
+
name="write_local",
|
|
1575
|
+
reason="`write_local` is deprecated, use `write` instead",
|
|
1576
|
+
)
|
|
1577
|
+
|
|
1578
|
+
writer = LocalOutputWriter()
|
|
1579
|
+
writer.write(output, path, skip_stdout_reset)
|
|
1580
|
+
|
|
1581
|
+
|
|
1520
1582
|
_LOCAL_OUTPUT_WRITER = LocalOutputWriter()
|
|
1521
1583
|
"""Default LocalOutputWriter instance used by the write function."""
|
|
1522
1584
|
|
nextmv/run.py
CHANGED
|
@@ -53,7 +53,7 @@ from nextmv._serialization import serialize_json
|
|
|
53
53
|
from nextmv.base_model import BaseModel
|
|
54
54
|
from nextmv.input import Input, InputFormat
|
|
55
55
|
from nextmv.output import Asset, Output, OutputFormat, Statistics
|
|
56
|
-
from nextmv.status import StatusV2
|
|
56
|
+
from nextmv.status import Status, StatusV2
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def run_duration(start: datetime | float, end: datetime | float) -> int:
|
|
@@ -520,6 +520,8 @@ class Run(BaseModel):
|
|
|
520
520
|
Class name for the execution of a job.
|
|
521
521
|
runtime : str
|
|
522
522
|
Runtime environment for the run.
|
|
523
|
+
status : Status
|
|
524
|
+
Deprecated, use status_v2 instead.
|
|
523
525
|
status_v2 : StatusV2
|
|
524
526
|
Status of the run.
|
|
525
527
|
queuing_priority : int, optional
|
|
@@ -596,6 +598,8 @@ class Run(BaseModel):
|
|
|
596
598
|
status_v2: StatusV2
|
|
597
599
|
"""Status of the run."""
|
|
598
600
|
|
|
601
|
+
status: Status | None = None
|
|
602
|
+
"""Deprecated, use status_v2 instead."""
|
|
599
603
|
queuing_priority: int | None = None
|
|
600
604
|
"""Priority of the run in the queue."""
|
|
601
605
|
queuing_disabled: bool | None = None
|
|
@@ -652,6 +656,8 @@ class Metadata(BaseModel):
|
|
|
652
656
|
Size of the output in bytes.
|
|
653
657
|
format : Format
|
|
654
658
|
Format of the input and output of the run.
|
|
659
|
+
status : Status
|
|
660
|
+
Deprecated: use status_v2.
|
|
655
661
|
status_v2 : StatusV2
|
|
656
662
|
Status of the run.
|
|
657
663
|
"""
|
|
@@ -676,6 +682,8 @@ class Metadata(BaseModel):
|
|
|
676
682
|
"""Format of the input and output of the run."""
|
|
677
683
|
status_v2: StatusV2
|
|
678
684
|
"""Status of the run."""
|
|
685
|
+
status: Status | None = None
|
|
686
|
+
"""Deprecated: use status_v2."""
|
|
679
687
|
statistics: dict[str, Any] | None = None
|
|
680
688
|
"""User defined statistics of the run."""
|
|
681
689
|
|
|
@@ -859,6 +867,7 @@ class RunInformation(BaseModel):
|
|
|
859
867
|
run_type=RunTypeConfiguration(), # Default empty configuration
|
|
860
868
|
execution_class="", # Not available in RunInformation
|
|
861
869
|
runtime="", # Not available in RunInformation
|
|
870
|
+
status=self.metadata.status,
|
|
862
871
|
status_v2=self.metadata.status_v2,
|
|
863
872
|
# Optional fields that are not available in RunInformation
|
|
864
873
|
queuing_priority=None,
|
nextmv/status.py
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
Provides status enums for Nextmv application runs.
|
|
3
3
|
|
|
4
4
|
This module defines enumerations for representing the status of a run in a
|
|
5
|
-
Nextmv application.
|
|
5
|
+
Nextmv application. It includes a deprecated `Status` enum and the current
|
|
6
|
+
`StatusV2` enum.
|
|
6
7
|
|
|
7
8
|
Classes
|
|
8
9
|
-------
|
|
10
|
+
Status
|
|
11
|
+
Deprecated status of a run.
|
|
9
12
|
StatusV2
|
|
10
13
|
Represents the status of a run.
|
|
11
14
|
"""
|
|
@@ -13,6 +16,53 @@ StatusV2
|
|
|
13
16
|
from enum import Enum
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
class Status(str, Enum):
|
|
20
|
+
"""
|
|
21
|
+
!!! warning
|
|
22
|
+
`Status` is deprecated, use `StatusV2` instead.
|
|
23
|
+
|
|
24
|
+
Status of a run.
|
|
25
|
+
|
|
26
|
+
You can import the `Status` class directly from `nextmv`:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from nextmv import Status
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This enum represents the possible states of a run. It is deprecated and
|
|
33
|
+
`StatusV2` should be used for new implementations.
|
|
34
|
+
|
|
35
|
+
Attributes
|
|
36
|
+
----------
|
|
37
|
+
failed : str
|
|
38
|
+
Run failed.
|
|
39
|
+
running : str
|
|
40
|
+
Run is running.
|
|
41
|
+
succeeded : str
|
|
42
|
+
Run succeeded.
|
|
43
|
+
|
|
44
|
+
Examples
|
|
45
|
+
--------
|
|
46
|
+
>>> from nextmv.cloud import Status
|
|
47
|
+
>>> current_status = Status.running
|
|
48
|
+
>>> if current_status == Status.succeeded:
|
|
49
|
+
... print("Run completed successfully.")
|
|
50
|
+
... elif current_status == Status.failed:
|
|
51
|
+
... print("Run failed.")
|
|
52
|
+
... else:
|
|
53
|
+
... print(f"Run is currently {current_status.value}.")
|
|
54
|
+
Run is currently running.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
failed = "failed"
|
|
59
|
+
"""Run failed."""
|
|
60
|
+
running = "running"
|
|
61
|
+
"""Run is running."""
|
|
62
|
+
succeeded = "succeeded"
|
|
63
|
+
"""Run succeeded."""
|
|
64
|
+
|
|
65
|
+
|
|
16
66
|
class StatusV2(str, Enum):
|
|
17
67
|
"""
|
|
18
68
|
Status of a run.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nextmv
|
|
3
|
-
Version: 1.0.0
|
|
3
|
+
Version: 1.0.0.dev0
|
|
4
4
|
Summary: The all-purpose Python SDK for Nextmv
|
|
5
5
|
Project-URL: Homepage, https://www.nextmv.io
|
|
6
6
|
Project-URL: Documentation, https://nextmv-py.docs.nextmv.io/en/latest/nextmv/
|
|
@@ -229,20 +229,19 @@ Requires-Dist: plotly>=6.0.1; extra == 'all'
|
|
|
229
229
|
Provides-Extra: dev
|
|
230
230
|
Requires-Dist: build>=1.0.3; extra == 'dev'
|
|
231
231
|
Requires-Dist: folium>=0.20.0; extra == 'dev'
|
|
232
|
-
Requires-Dist: mlflow>=
|
|
233
|
-
Requires-Dist:
|
|
232
|
+
Requires-Dist: mlflow>=2.19.0; extra == 'dev'
|
|
233
|
+
Requires-Dist: nextroute>=1.11.1; extra == 'dev'
|
|
234
234
|
Requires-Dist: openpyxl>=3.1.5; extra == 'dev'
|
|
235
235
|
Requires-Dist: pandas>=2.2.3; extra == 'dev'
|
|
236
236
|
Requires-Dist: plotly>=6.0.1; extra == 'dev'
|
|
237
237
|
Requires-Dist: pydantic>=2.5.2; extra == 'dev'
|
|
238
|
-
Requires-Dist: pytest>=9.0.2; extra == 'dev'
|
|
239
238
|
Requires-Dist: pyyaml>=6.0.1; extra == 'dev'
|
|
240
239
|
Requires-Dist: requests>=2.31.0; extra == 'dev'
|
|
241
240
|
Requires-Dist: ruff>=0.1.7; extra == 'dev'
|
|
242
241
|
Requires-Dist: twine>=4.0.2; extra == 'dev'
|
|
243
242
|
Requires-Dist: urllib3>=2.1.0; extra == 'dev'
|
|
244
243
|
Provides-Extra: notebook
|
|
245
|
-
Requires-Dist: mlflow>=
|
|
244
|
+
Requires-Dist: mlflow>=2.19.0; extra == 'notebook'
|
|
246
245
|
Description-Content-Type: text/markdown
|
|
247
246
|
|
|
248
247
|
# Nextmv Python SDK
|