nextmv 0.40.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nextmv/__about__.py +1 -1
- nextmv/__entrypoint__.py +1 -2
- nextmv/__init__.py +2 -4
- nextmv/cli/CONTRIBUTING.md +583 -0
- nextmv/cli/cloud/__init__.py +49 -0
- nextmv/cli/cloud/acceptance/__init__.py +27 -0
- nextmv/cli/cloud/acceptance/create.py +391 -0
- nextmv/cli/cloud/acceptance/delete.py +64 -0
- nextmv/cli/cloud/acceptance/get.py +103 -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 +59 -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 +140 -0
- nextmv/cli/cloud/app/delete.py +57 -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 +432 -0
- nextmv/cli/cloud/app/update.py +124 -0
- nextmv/cli/cloud/batch/__init__.py +29 -0
- nextmv/cli/cloud/batch/create.py +452 -0
- nextmv/cli/cloud/batch/delete.py +64 -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 +33 -0
- nextmv/cli/cloud/ensemble/create.py +413 -0
- nextmv/cli/cloud/ensemble/delete.py +63 -0
- nextmv/cli/cloud/ensemble/get.py +65 -0
- nextmv/cli/cloud/ensemble/list.py +63 -0
- nextmv/cli/cloud/ensemble/update.py +103 -0
- nextmv/cli/cloud/input_set/__init__.py +32 -0
- nextmv/cli/cloud/input_set/create.py +168 -0
- nextmv/cli/cloud/input_set/delete.py +64 -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 +289 -0
- nextmv/cli/cloud/instance/delete.py +61 -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 +144 -0
- nextmv/cli/cloud/managed_input/delete.py +64 -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 +524 -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 +166 -0
- nextmv/cli/cloud/run/metadata.py +67 -0
- nextmv/cli/cloud/run/track.py +500 -0
- nextmv/cli/cloud/scenario/__init__.py +29 -0
- nextmv/cli/cloud/scenario/create.py +451 -0
- nextmv/cli/cloud/scenario/delete.py +61 -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 +63 -0
- nextmv/cli/cloud/secrets/get.py +66 -0
- nextmv/cli/cloud/secrets/list.py +60 -0
- nextmv/cli/cloud/secrets/update.py +144 -0
- nextmv/cli/cloud/shadow/__init__.py +33 -0
- nextmv/cli/cloud/shadow/create.py +184 -0
- nextmv/cli/cloud/shadow/delete.py +64 -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 +53 -0
- nextmv/cli/cloud/shadow/update.py +96 -0
- nextmv/cli/cloud/switchback/__init__.py +33 -0
- nextmv/cli/cloud/switchback/create.py +151 -0
- nextmv/cli/cloud/switchback/delete.py +64 -0
- nextmv/cli/cloud/switchback/get.py +62 -0
- nextmv/cli/cloud/switchback/list.py +63 -0
- nextmv/cli/cloud/switchback/metadata.py +68 -0
- nextmv/cli/cloud/switchback/start.py +43 -0
- nextmv/cli/cloud/switchback/stop.py +53 -0
- nextmv/cli/cloud/switchback/update.py +96 -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 +96 -0
- nextmv/cli/cloud/version/delete.py +61 -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 +20 -204
- nextmv/cli/community/list.py +61 -126
- nextmv/cli/configuration/__init__.py +23 -0
- nextmv/cli/configuration/config.py +103 -6
- nextmv/cli/configuration/create.py +17 -18
- nextmv/cli/configuration/delete.py +25 -13
- nextmv/cli/configuration/list.py +4 -4
- nextmv/cli/confirm.py +34 -0
- nextmv/cli/main.py +68 -36
- nextmv/cli/message.py +170 -0
- nextmv/cli/options.py +196 -0
- nextmv/cli/version.py +20 -1
- nextmv/cloud/__init__.py +17 -38
- nextmv/cloud/acceptance_test.py +20 -83
- nextmv/cloud/account.py +269 -30
- nextmv/cloud/application/__init__.py +898 -0
- nextmv/cloud/application/_acceptance.py +424 -0
- nextmv/cloud/application/_batch_scenario.py +845 -0
- nextmv/cloud/application/_ensemble.py +251 -0
- nextmv/cloud/application/_input_set.py +263 -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 +320 -0
- nextmv/cloud/application/_switchback.py +332 -0
- nextmv/cloud/application/_utils.py +54 -0
- nextmv/cloud/application/_version.py +304 -0
- nextmv/cloud/batch_experiment.py +6 -2
- nextmv/cloud/community.py +446 -0
- nextmv/cloud/instance.py +11 -1
- nextmv/cloud/integration.py +8 -5
- nextmv/cloud/package.py +50 -9
- nextmv/cloud/shadow.py +254 -0
- nextmv/cloud/switchback.py +228 -0
- nextmv/deprecated.py +5 -3
- nextmv/input.py +20 -88
- nextmv/local/application.py +3 -15
- nextmv/local/runner.py +1 -1
- nextmv/model.py +50 -11
- nextmv/options.py +11 -256
- nextmv/output.py +0 -62
- nextmv/polling.py +54 -16
- nextmv/run.py +84 -37
- nextmv/status.py +1 -51
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
- nextmv-1.0.0.dist-info/RECORD +185 -0
- nextmv-1.0.0.dist-info/entry_points.txt +2 -0
- nextmv/cli/community/community.py +0 -24
- nextmv/cli/configuration/configuration.py +0 -23
- nextmv/cli/error.py +0 -22
- nextmv/cloud/application.py +0 -4204
- nextmv-0.40.0.dist-info/RECORD +0 -66
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
- {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/licenses/LICENSE +0 -0
nextmv/input.py
CHANGED
|
@@ -38,7 +38,6 @@ from enum import Enum
|
|
|
38
38
|
from typing import Any
|
|
39
39
|
|
|
40
40
|
from nextmv._serialization import serialize_json
|
|
41
|
-
from nextmv.deprecated import deprecated
|
|
42
41
|
from nextmv.options import Options
|
|
43
42
|
|
|
44
43
|
INPUTS_KEY = "inputs"
|
|
@@ -77,8 +76,6 @@ class InputFormat(str, Enum):
|
|
|
77
76
|
"""JSON format, utf-8 encoded."""
|
|
78
77
|
TEXT = "text"
|
|
79
78
|
"""Text format, utf-8 encoded."""
|
|
80
|
-
CSV = "csv"
|
|
81
|
-
"""CSV format, utf-8 encoded."""
|
|
82
79
|
CSV_ARCHIVE = "csv-archive"
|
|
83
80
|
"""CSV archive format: multiple CSV files."""
|
|
84
81
|
MULTI_FILE = "multi-file"
|
|
@@ -376,8 +373,6 @@ class Input:
|
|
|
376
373
|
means that the data must be JSON-deserializable, which includes dicts and
|
|
377
374
|
lists.
|
|
378
375
|
- `InputFormat.TEXT`: the data is `str`, and it must be utf-8 encoded.
|
|
379
|
-
- `InputFormat.CSV`: the data is `list[dict[str, Any]]`, where each dict
|
|
380
|
-
represents a row in the CSV.
|
|
381
376
|
- `InputFormat.CSV_ARCHIVE`: the data is `dict[str, list[dict[str, Any]]]`,
|
|
382
377
|
where each key is the name of a CSV file and the value is a list of dicts
|
|
383
378
|
representing the rows in that CSV file.
|
|
@@ -466,12 +461,6 @@ class Input:
|
|
|
466
461
|
"input_format InputFormat.TEXT, supported type is `str`"
|
|
467
462
|
)
|
|
468
463
|
|
|
469
|
-
elif self.input_format == InputFormat.CSV and not isinstance(self.data, list):
|
|
470
|
-
raise ValueError(
|
|
471
|
-
f"unsupported Input.data type: {type(self.data)} with "
|
|
472
|
-
"input_format InputFormat.CSV, supported type is `list`"
|
|
473
|
-
)
|
|
474
|
-
|
|
475
464
|
elif self.input_format == InputFormat.CSV_ARCHIVE and not isinstance(self.data, dict):
|
|
476
465
|
raise ValueError(
|
|
477
466
|
f"unsupported Input.data type: {type(self.data)} with "
|
|
@@ -607,8 +596,6 @@ class LocalInputLoader(InputLoader):
|
|
|
607
596
|
>>> loader = LocalInputLoader()
|
|
608
597
|
>>> # Load JSON from stdin or file
|
|
609
598
|
>>> input_obj = loader.load(input_format=InputFormat.JSON, path="data.json")
|
|
610
|
-
>>> # Load CSV from a file
|
|
611
|
-
>>> input_obj = loader.load(input_format=InputFormat.CSV, path="data.csv")
|
|
612
599
|
"""
|
|
613
600
|
|
|
614
601
|
def _read_text(path: str, _) -> str:
|
|
@@ -672,7 +659,6 @@ class LocalInputLoader(InputLoader):
|
|
|
672
659
|
STDIN_READERS = {
|
|
673
660
|
InputFormat.JSON: lambda _: json.load(sys.stdin),
|
|
674
661
|
InputFormat.TEXT: lambda _: sys.stdin.read().rstrip("\n"),
|
|
675
|
-
InputFormat.CSV: lambda csv_configurations: list(csv.DictReader(sys.stdin, **csv_configurations)),
|
|
676
662
|
}
|
|
677
663
|
"""
|
|
678
664
|
Dictionary of functions to read from standard input.
|
|
@@ -687,7 +673,7 @@ class LocalInputLoader(InputLoader):
|
|
|
687
673
|
FILE_READERS = {
|
|
688
674
|
InputFormat.JSON: _read_json,
|
|
689
675
|
InputFormat.TEXT: _read_text,
|
|
690
|
-
|
|
676
|
+
"CSV": _read_csv,
|
|
691
677
|
}
|
|
692
678
|
"""
|
|
693
679
|
Dictionary of functions to read from files.
|
|
@@ -706,23 +692,23 @@ class LocalInputLoader(InputLoader):
|
|
|
706
692
|
) -> Input:
|
|
707
693
|
"""
|
|
708
694
|
Load the input data. The input data can be in various formats. For
|
|
709
|
-
`InputFormat.JSON
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
695
|
+
`InputFormat.JSON` and `InputFormat.TEXT`, the data can be streamed
|
|
696
|
+
from stdin or read from a file. When the `path` argument is provided
|
|
697
|
+
(and valid), the input data is read from the file specified by `path`,
|
|
698
|
+
otherwise, it is streamed from stdin. For `InputFormat.CSV_ARCHIVE`,
|
|
699
|
+
the input data is read from the directory specified by `path`. If the
|
|
700
|
+
`path` is not provided, the default location `input` is used. The
|
|
701
|
+
directory should contain one or more files, where each file in the
|
|
702
|
+
directory is a CSV file.
|
|
717
703
|
|
|
718
704
|
The `Input` that is returned contains the `data` attribute. This data
|
|
719
705
|
can be of different types, depending on the provided `input_format`:
|
|
720
706
|
|
|
721
707
|
- `InputFormat.JSON`: the data is a `dict[str, Any]`.
|
|
722
708
|
- `InputFormat.TEXT`: the data is a `str`.
|
|
723
|
-
- `InputFormat.
|
|
724
|
-
|
|
725
|
-
|
|
709
|
+
- `InputFormat.CSV_ARCHIVE`: the data is a `dict[str, list[dict[str,
|
|
710
|
+
Any]]]`. Each key is the name of the CSV file, minus the `.csv`
|
|
711
|
+
extension.
|
|
726
712
|
- `InputFormat.MULTI_FILE`: the data is a `dict[str, Any]`, where each
|
|
727
713
|
key is the file name (with extension) and the value is the data read
|
|
728
714
|
from the file. The data can be of any type, depending on the file
|
|
@@ -745,11 +731,11 @@ class LocalInputLoader(InputLoader):
|
|
|
745
731
|
`input_format` is set to `InputFormat.MULTI_FILE`. Each `DataFile`
|
|
746
732
|
instance should have a `name` (the file name with extension) and a
|
|
747
733
|
`loader` function that reads the data from the file. The `loader`
|
|
748
|
-
function should accept the file path as its first argument and
|
|
749
|
-
the data read from the file. The `loader` can also accept
|
|
750
|
-
positional and keyword arguments, which can be provided
|
|
751
|
-
`loader_args` and `loader_kwargs` attributes of the
|
|
752
|
-
instance.
|
|
734
|
+
function should accept the file path as its first argument and
|
|
735
|
+
return the data read from the file. The `loader` can also accept
|
|
736
|
+
additional positional and keyword arguments, which can be provided
|
|
737
|
+
through the `loader_args` and `loader_kwargs` attributes of the
|
|
738
|
+
`DataFile` instance.
|
|
753
739
|
|
|
754
740
|
Returns
|
|
755
741
|
-------
|
|
@@ -766,7 +752,7 @@ class LocalInputLoader(InputLoader):
|
|
|
766
752
|
if csv_configurations is None:
|
|
767
753
|
csv_configurations = {}
|
|
768
754
|
|
|
769
|
-
if input_format in [InputFormat.JSON, InputFormat.TEXT
|
|
755
|
+
if input_format in [InputFormat.JSON, InputFormat.TEXT]:
|
|
770
756
|
data = self._load_utf8_encoded(path=path, input_format=input_format, csv_configurations=csv_configurations)
|
|
771
757
|
elif input_format == InputFormat.CSV_ARCHIVE:
|
|
772
758
|
data = self._load_archive(path=path, csv_configurations=csv_configurations)
|
|
@@ -785,7 +771,7 @@ class LocalInputLoader(InputLoader):
|
|
|
785
771
|
self,
|
|
786
772
|
csv_configurations: dict[str, Any] | None,
|
|
787
773
|
path: str | None = None,
|
|
788
|
-
input_format: InputFormat | None = InputFormat.JSON,
|
|
774
|
+
input_format: InputFormat | str | None = InputFormat.JSON,
|
|
789
775
|
use_file_reader: bool = False,
|
|
790
776
|
) -> dict[str, Any] | str | list[dict[str, Any]]:
|
|
791
777
|
"""
|
|
@@ -871,7 +857,7 @@ class LocalInputLoader(InputLoader):
|
|
|
871
857
|
stripped = file.removesuffix(csv_ext)
|
|
872
858
|
data[stripped] = self._load_utf8_encoded(
|
|
873
859
|
path=os.path.join(dir_path, file),
|
|
874
|
-
input_format=
|
|
860
|
+
input_format="CSV",
|
|
875
861
|
use_file_reader=True,
|
|
876
862
|
csv_configurations=csv_configurations,
|
|
877
863
|
)
|
|
@@ -954,57 +940,6 @@ class LocalInputLoader(InputLoader):
|
|
|
954
940
|
return data
|
|
955
941
|
|
|
956
942
|
|
|
957
|
-
def load_local(
|
|
958
|
-
input_format: InputFormat | None = InputFormat.JSON,
|
|
959
|
-
options: Options | None = None,
|
|
960
|
-
path: str | None = None,
|
|
961
|
-
csv_configurations: dict[str, Any] | None = None,
|
|
962
|
-
) -> Input:
|
|
963
|
-
"""
|
|
964
|
-
!!! warning
|
|
965
|
-
`load_local` is deprecated, use `load` instead.
|
|
966
|
-
|
|
967
|
-
Load input data from local sources.
|
|
968
|
-
|
|
969
|
-
This is a convenience function for instantiating a `LocalInputLoader`
|
|
970
|
-
and calling its `load` method.
|
|
971
|
-
|
|
972
|
-
Parameters
|
|
973
|
-
----------
|
|
974
|
-
input_format : InputFormat, optional
|
|
975
|
-
Format of the input data. Default is `InputFormat.JSON`.
|
|
976
|
-
options : Options, optional
|
|
977
|
-
Options for loading the input data.
|
|
978
|
-
path : str, optional
|
|
979
|
-
Path to the input data.
|
|
980
|
-
csv_configurations : dict[str, Any], optional
|
|
981
|
-
Configurations for loading CSV files. Custom kwargs for
|
|
982
|
-
Python's `csv.DictReader`.
|
|
983
|
-
|
|
984
|
-
Returns
|
|
985
|
-
-------
|
|
986
|
-
Input
|
|
987
|
-
The loaded input data in an Input object.
|
|
988
|
-
|
|
989
|
-
Raises
|
|
990
|
-
------
|
|
991
|
-
ValueError
|
|
992
|
-
If the path is invalid or data format is incorrect.
|
|
993
|
-
|
|
994
|
-
See Also
|
|
995
|
-
--------
|
|
996
|
-
load : The recommended function to use instead.
|
|
997
|
-
"""
|
|
998
|
-
|
|
999
|
-
deprecated(
|
|
1000
|
-
name="load_local",
|
|
1001
|
-
reason="`load_local` is deprecated, use `load` instead",
|
|
1002
|
-
)
|
|
1003
|
-
|
|
1004
|
-
loader = LocalInputLoader()
|
|
1005
|
-
return loader.load(input_format, options, path, csv_configurations)
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
943
|
_LOCAL_INPUT_LOADER = LocalInputLoader()
|
|
1009
944
|
"""Default instance of LocalInputLoader used by the load function."""
|
|
1010
945
|
|
|
@@ -1034,7 +969,6 @@ def load(
|
|
|
1034
969
|
|
|
1035
970
|
- `InputFormat.JSON`: the data is a `dict[str, Any]`
|
|
1036
971
|
- `InputFormat.TEXT`: the data is a `str`
|
|
1037
|
-
- `InputFormat.CSV`: the data is a `list[dict[str, Any]]`
|
|
1038
972
|
- `InputFormat.CSV_ARCHIVE`: the data is a `dict[str, list[dict[str, Any]]]`
|
|
1039
973
|
Each key is the name of the CSV file, minus the `.csv` extension.
|
|
1040
974
|
- `InputFormat.MULTI_FILE`: the data is a `dict[str, Any]`
|
|
@@ -1119,8 +1053,6 @@ def load(
|
|
|
1119
1053
|
>>> from nextmv.input import load, InputFormat
|
|
1120
1054
|
>>> # Load JSON from stdin
|
|
1121
1055
|
>>> input_obj = load(input_format=InputFormat.JSON)
|
|
1122
|
-
>>> # Load CSV from a file
|
|
1123
|
-
>>> input_obj = load(input_format=InputFormat.CSV, path="data.csv")
|
|
1124
1056
|
>>> # Load CSV archive from a directory
|
|
1125
1057
|
>>> input_obj = load(input_format=InputFormat.CSV_ARCHIVE, path="input_dir")
|
|
1126
1058
|
"""
|
nextmv/local/application.py
CHANGED
|
@@ -256,8 +256,7 @@ class Application:
|
|
|
256
256
|
`nextmv.InputFormat.MULTI_FILE`, you should use the
|
|
257
257
|
`input_dir_path` argument instead. This argument takes precedence
|
|
258
258
|
over the `input`. If `input_dir_path` is specified, this function
|
|
259
|
-
looks for files in that directory and tars them
|
|
260
|
-
uploaded using the `upload_large_input` method. If both the
|
|
259
|
+
looks for files in that directory and tars them. If both the
|
|
261
260
|
`input_dir_path` and `input` arguments are provided, the `input`
|
|
262
261
|
is ignored.
|
|
263
262
|
|
|
@@ -273,9 +272,6 @@ class Application:
|
|
|
273
272
|
|
|
274
273
|
When working with JSON or text data, use the `input` argument
|
|
275
274
|
directly.
|
|
276
|
-
|
|
277
|
-
In general, if an input is too large, it will be uploaded with the
|
|
278
|
-
`upload_large_input` method.
|
|
279
275
|
name: Optional[str]
|
|
280
276
|
Name of the local run.
|
|
281
277
|
description: Optional[str]
|
|
@@ -399,8 +395,7 @@ class Application:
|
|
|
399
395
|
`nextmv.InputFormat.MULTI_FILE`, you should use the
|
|
400
396
|
`input_dir_path` argument instead. This argument takes precedence
|
|
401
397
|
over the `input`. If `input_dir_path` is specified, this function
|
|
402
|
-
looks for files in that directory and tars them
|
|
403
|
-
uploaded using the `upload_large_input` method. If both the
|
|
398
|
+
looks for files in that directory and tars them. If both the
|
|
404
399
|
`input_dir_path` and `input` arguments are provided, the `input` is
|
|
405
400
|
ignored.
|
|
406
401
|
|
|
@@ -416,9 +411,6 @@ class Application:
|
|
|
416
411
|
|
|
417
412
|
When working with JSON or text data, use the `input` argument
|
|
418
413
|
directly.
|
|
419
|
-
|
|
420
|
-
In general, if an input is too large, it will be uploaded with the
|
|
421
|
-
`upload_large_input` method.
|
|
422
414
|
name: Optional[str]
|
|
423
415
|
Name of the local run.
|
|
424
416
|
description: Optional[str]
|
|
@@ -699,11 +691,7 @@ class Application:
|
|
|
699
691
|
|
|
700
692
|
def polling_func() -> tuple[Any, bool]:
|
|
701
693
|
run_information = self.run_metadata(run_id=run_id)
|
|
702
|
-
if run_information.metadata.
|
|
703
|
-
StatusV2.succeeded,
|
|
704
|
-
StatusV2.failed,
|
|
705
|
-
StatusV2.canceled,
|
|
706
|
-
}:
|
|
694
|
+
if run_information.metadata.run_is_finalized():
|
|
707
695
|
return run_information, True
|
|
708
696
|
|
|
709
697
|
return None, False
|
nextmv/local/runner.py
CHANGED
|
@@ -204,7 +204,7 @@ def new_run(
|
|
|
204
204
|
if description is None:
|
|
205
205
|
description = f"Local run created at {created_at.isoformat().replace('+00:00', 'Z')}"
|
|
206
206
|
|
|
207
|
-
if name is None:
|
|
207
|
+
if name is None or name == "":
|
|
208
208
|
name = f"local run {run_id}"
|
|
209
209
|
|
|
210
210
|
information = RunInformation(
|
nextmv/model.py
CHANGED
|
@@ -18,6 +18,8 @@ deployed to Nextmv Cloud for execution.
|
|
|
18
18
|
import logging
|
|
19
19
|
import os
|
|
20
20
|
import shutil
|
|
21
|
+
import sqlite3
|
|
22
|
+
import time
|
|
21
23
|
import warnings
|
|
22
24
|
from dataclasses import dataclass
|
|
23
25
|
from typing import Any
|
|
@@ -84,6 +86,9 @@ def _custom_showwarning(message, category, filename, lineno, file=None, line=Non
|
|
|
84
86
|
if "mlflow/pyfunc/__init__.py" in filename:
|
|
85
87
|
return
|
|
86
88
|
|
|
89
|
+
if "/mlflow/tracing/provider.py" in filename:
|
|
90
|
+
return
|
|
91
|
+
|
|
87
92
|
_original_showwarning(message, category, filename, lineno, file, line)
|
|
88
93
|
|
|
89
94
|
|
|
@@ -288,7 +293,6 @@ class Model:
|
|
|
288
293
|
) from e
|
|
289
294
|
|
|
290
295
|
finally:
|
|
291
|
-
from mlflow.models import infer_signature
|
|
292
296
|
from mlflow.pyfunc import PythonModel, save_model
|
|
293
297
|
|
|
294
298
|
class MLFlowModel(PythonModel):
|
|
@@ -342,12 +346,16 @@ class Model:
|
|
|
342
346
|
|
|
343
347
|
_cleanup_python_model(model_dir, configuration, verbose=False)
|
|
344
348
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
# Removing this seems to make the "apps from models" experience once
|
|
350
|
+
# again. I am not removing it entirely because we might want to
|
|
351
|
+
# re-introduce it later on.
|
|
352
|
+
# signature = None
|
|
353
|
+
# if configuration.options is not None:
|
|
354
|
+
# options_dict = configuration.options.to_dict()
|
|
355
|
+
# signature = infer_signature(
|
|
356
|
+
# model_input={},
|
|
357
|
+
# params=options_dict,
|
|
358
|
+
# )
|
|
351
359
|
|
|
352
360
|
# We use mlflow to save the model to the local filesystem, to be able to
|
|
353
361
|
# load it later on.
|
|
@@ -356,7 +364,7 @@ class Model:
|
|
|
356
364
|
path=model_path, # Customize the name of the model location.
|
|
357
365
|
infer_code_paths=True, # Makes the imports portable.
|
|
358
366
|
python_model=MLFlowModel(),
|
|
359
|
-
signature=signature, #
|
|
367
|
+
# signature=signature, # Please see comment above about keeping this in case we need to go back.
|
|
360
368
|
)
|
|
361
369
|
|
|
362
370
|
# Create an auxiliary requirements file with the model dependencies.
|
|
@@ -415,9 +423,7 @@ def _cleanup_python_model(
|
|
|
415
423
|
if os.path.exists(model_path):
|
|
416
424
|
shutil.rmtree(model_path)
|
|
417
425
|
|
|
418
|
-
|
|
419
|
-
if os.path.exists(mlruns_path):
|
|
420
|
-
shutil.rmtree(mlruns_path)
|
|
426
|
+
_cleanup_mlflow_db(model_dir)
|
|
421
427
|
|
|
422
428
|
requirements_file = os.path.join(model_dir, _REQUIREMENTS_FILE)
|
|
423
429
|
if os.path.exists(requirements_file):
|
|
@@ -429,3 +435,36 @@ def _cleanup_python_model(
|
|
|
429
435
|
|
|
430
436
|
if verbose:
|
|
431
437
|
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
|