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.
Files changed (163) hide show
  1. nextmv/__about__.py +1 -1
  2. nextmv/__entrypoint__.py +1 -2
  3. nextmv/__init__.py +2 -4
  4. nextmv/cli/CONTRIBUTING.md +583 -0
  5. nextmv/cli/cloud/__init__.py +49 -0
  6. nextmv/cli/cloud/acceptance/__init__.py +27 -0
  7. nextmv/cli/cloud/acceptance/create.py +391 -0
  8. nextmv/cli/cloud/acceptance/delete.py +64 -0
  9. nextmv/cli/cloud/acceptance/get.py +103 -0
  10. nextmv/cli/cloud/acceptance/list.py +62 -0
  11. nextmv/cli/cloud/acceptance/update.py +95 -0
  12. nextmv/cli/cloud/account/__init__.py +28 -0
  13. nextmv/cli/cloud/account/create.py +83 -0
  14. nextmv/cli/cloud/account/delete.py +59 -0
  15. nextmv/cli/cloud/account/get.py +66 -0
  16. nextmv/cli/cloud/account/update.py +70 -0
  17. nextmv/cli/cloud/app/__init__.py +35 -0
  18. nextmv/cli/cloud/app/create.py +140 -0
  19. nextmv/cli/cloud/app/delete.py +57 -0
  20. nextmv/cli/cloud/app/exists.py +44 -0
  21. nextmv/cli/cloud/app/get.py +66 -0
  22. nextmv/cli/cloud/app/list.py +61 -0
  23. nextmv/cli/cloud/app/push.py +432 -0
  24. nextmv/cli/cloud/app/update.py +124 -0
  25. nextmv/cli/cloud/batch/__init__.py +29 -0
  26. nextmv/cli/cloud/batch/create.py +452 -0
  27. nextmv/cli/cloud/batch/delete.py +64 -0
  28. nextmv/cli/cloud/batch/get.py +104 -0
  29. nextmv/cli/cloud/batch/list.py +63 -0
  30. nextmv/cli/cloud/batch/metadata.py +66 -0
  31. nextmv/cli/cloud/batch/update.py +95 -0
  32. nextmv/cli/cloud/data/__init__.py +26 -0
  33. nextmv/cli/cloud/data/upload.py +162 -0
  34. nextmv/cli/cloud/ensemble/__init__.py +33 -0
  35. nextmv/cli/cloud/ensemble/create.py +413 -0
  36. nextmv/cli/cloud/ensemble/delete.py +63 -0
  37. nextmv/cli/cloud/ensemble/get.py +65 -0
  38. nextmv/cli/cloud/ensemble/list.py +63 -0
  39. nextmv/cli/cloud/ensemble/update.py +103 -0
  40. nextmv/cli/cloud/input_set/__init__.py +32 -0
  41. nextmv/cli/cloud/input_set/create.py +168 -0
  42. nextmv/cli/cloud/input_set/delete.py +64 -0
  43. nextmv/cli/cloud/input_set/get.py +63 -0
  44. nextmv/cli/cloud/input_set/list.py +63 -0
  45. nextmv/cli/cloud/input_set/update.py +123 -0
  46. nextmv/cli/cloud/instance/__init__.py +35 -0
  47. nextmv/cli/cloud/instance/create.py +289 -0
  48. nextmv/cli/cloud/instance/delete.py +61 -0
  49. nextmv/cli/cloud/instance/exists.py +39 -0
  50. nextmv/cli/cloud/instance/get.py +62 -0
  51. nextmv/cli/cloud/instance/list.py +60 -0
  52. nextmv/cli/cloud/instance/update.py +216 -0
  53. nextmv/cli/cloud/managed_input/__init__.py +31 -0
  54. nextmv/cli/cloud/managed_input/create.py +144 -0
  55. nextmv/cli/cloud/managed_input/delete.py +64 -0
  56. nextmv/cli/cloud/managed_input/get.py +63 -0
  57. nextmv/cli/cloud/managed_input/list.py +60 -0
  58. nextmv/cli/cloud/managed_input/update.py +97 -0
  59. nextmv/cli/cloud/run/__init__.py +37 -0
  60. nextmv/cli/cloud/run/cancel.py +37 -0
  61. nextmv/cli/cloud/run/create.py +524 -0
  62. nextmv/cli/cloud/run/get.py +199 -0
  63. nextmv/cli/cloud/run/input.py +86 -0
  64. nextmv/cli/cloud/run/list.py +80 -0
  65. nextmv/cli/cloud/run/logs.py +166 -0
  66. nextmv/cli/cloud/run/metadata.py +67 -0
  67. nextmv/cli/cloud/run/track.py +500 -0
  68. nextmv/cli/cloud/scenario/__init__.py +29 -0
  69. nextmv/cli/cloud/scenario/create.py +451 -0
  70. nextmv/cli/cloud/scenario/delete.py +61 -0
  71. nextmv/cli/cloud/scenario/get.py +102 -0
  72. nextmv/cli/cloud/scenario/list.py +63 -0
  73. nextmv/cli/cloud/scenario/metadata.py +67 -0
  74. nextmv/cli/cloud/scenario/update.py +93 -0
  75. nextmv/cli/cloud/secrets/__init__.py +33 -0
  76. nextmv/cli/cloud/secrets/create.py +206 -0
  77. nextmv/cli/cloud/secrets/delete.py +63 -0
  78. nextmv/cli/cloud/secrets/get.py +66 -0
  79. nextmv/cli/cloud/secrets/list.py +60 -0
  80. nextmv/cli/cloud/secrets/update.py +144 -0
  81. nextmv/cli/cloud/shadow/__init__.py +33 -0
  82. nextmv/cli/cloud/shadow/create.py +184 -0
  83. nextmv/cli/cloud/shadow/delete.py +64 -0
  84. nextmv/cli/cloud/shadow/get.py +61 -0
  85. nextmv/cli/cloud/shadow/list.py +63 -0
  86. nextmv/cli/cloud/shadow/metadata.py +66 -0
  87. nextmv/cli/cloud/shadow/start.py +43 -0
  88. nextmv/cli/cloud/shadow/stop.py +53 -0
  89. nextmv/cli/cloud/shadow/update.py +96 -0
  90. nextmv/cli/cloud/switchback/__init__.py +33 -0
  91. nextmv/cli/cloud/switchback/create.py +151 -0
  92. nextmv/cli/cloud/switchback/delete.py +64 -0
  93. nextmv/cli/cloud/switchback/get.py +62 -0
  94. nextmv/cli/cloud/switchback/list.py +63 -0
  95. nextmv/cli/cloud/switchback/metadata.py +68 -0
  96. nextmv/cli/cloud/switchback/start.py +43 -0
  97. nextmv/cli/cloud/switchback/stop.py +53 -0
  98. nextmv/cli/cloud/switchback/update.py +96 -0
  99. nextmv/cli/cloud/upload/__init__.py +22 -0
  100. nextmv/cli/cloud/upload/create.py +39 -0
  101. nextmv/cli/cloud/version/__init__.py +33 -0
  102. nextmv/cli/cloud/version/create.py +96 -0
  103. nextmv/cli/cloud/version/delete.py +61 -0
  104. nextmv/cli/cloud/version/exists.py +39 -0
  105. nextmv/cli/cloud/version/get.py +62 -0
  106. nextmv/cli/cloud/version/list.py +60 -0
  107. nextmv/cli/cloud/version/update.py +92 -0
  108. nextmv/cli/community/__init__.py +24 -0
  109. nextmv/cli/community/clone.py +20 -204
  110. nextmv/cli/community/list.py +61 -126
  111. nextmv/cli/configuration/__init__.py +23 -0
  112. nextmv/cli/configuration/config.py +103 -6
  113. nextmv/cli/configuration/create.py +17 -18
  114. nextmv/cli/configuration/delete.py +25 -13
  115. nextmv/cli/configuration/list.py +4 -4
  116. nextmv/cli/confirm.py +34 -0
  117. nextmv/cli/main.py +68 -36
  118. nextmv/cli/message.py +170 -0
  119. nextmv/cli/options.py +196 -0
  120. nextmv/cli/version.py +20 -1
  121. nextmv/cloud/__init__.py +17 -38
  122. nextmv/cloud/acceptance_test.py +20 -83
  123. nextmv/cloud/account.py +269 -30
  124. nextmv/cloud/application/__init__.py +898 -0
  125. nextmv/cloud/application/_acceptance.py +424 -0
  126. nextmv/cloud/application/_batch_scenario.py +845 -0
  127. nextmv/cloud/application/_ensemble.py +251 -0
  128. nextmv/cloud/application/_input_set.py +263 -0
  129. nextmv/cloud/application/_instance.py +289 -0
  130. nextmv/cloud/application/_managed_input.py +227 -0
  131. nextmv/cloud/application/_run.py +1393 -0
  132. nextmv/cloud/application/_secrets.py +294 -0
  133. nextmv/cloud/application/_shadow.py +320 -0
  134. nextmv/cloud/application/_switchback.py +332 -0
  135. nextmv/cloud/application/_utils.py +54 -0
  136. nextmv/cloud/application/_version.py +304 -0
  137. nextmv/cloud/batch_experiment.py +6 -2
  138. nextmv/cloud/community.py +446 -0
  139. nextmv/cloud/instance.py +11 -1
  140. nextmv/cloud/integration.py +8 -5
  141. nextmv/cloud/package.py +50 -9
  142. nextmv/cloud/shadow.py +254 -0
  143. nextmv/cloud/switchback.py +228 -0
  144. nextmv/deprecated.py +5 -3
  145. nextmv/input.py +20 -88
  146. nextmv/local/application.py +3 -15
  147. nextmv/local/runner.py +1 -1
  148. nextmv/model.py +50 -11
  149. nextmv/options.py +11 -256
  150. nextmv/output.py +0 -62
  151. nextmv/polling.py +54 -16
  152. nextmv/run.py +84 -37
  153. nextmv/status.py +1 -51
  154. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/METADATA +37 -11
  155. nextmv-1.0.0.dist-info/RECORD +185 -0
  156. nextmv-1.0.0.dist-info/entry_points.txt +2 -0
  157. nextmv/cli/community/community.py +0 -24
  158. nextmv/cli/configuration/configuration.py +0 -23
  159. nextmv/cli/error.py +0 -22
  160. nextmv/cloud/application.py +0 -4204
  161. nextmv-0.40.0.dist-info/RECORD +0 -66
  162. {nextmv-0.40.0.dist-info → nextmv-1.0.0.dist-info}/WHEEL +0 -0
  163. {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
- InputFormat.CSV: _read_csv,
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`, `InputFormat.TEXT`, and `InputFormat.CSV`, the data
710
- can be streamed from stdin or read from a file. When the `path`
711
- argument is provided (and valid), the input data is read from the file
712
- specified by `path`, otherwise, it is streamed from stdin. For
713
- `InputFormat.CSV_ARCHIVE`, the input data is read from the directory
714
- specified by `path`. If the `path` is not provided, the default
715
- location `input` is used. The directory should contain one or more
716
- files, where each file in the directory is a CSV file.
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.CSV`: the data is a `list[dict[str, Any]]`.
724
- - `InputFormat.CSV_ARCHIVE`: the data is a `dict[str, list[dict[str, Any]]]`.
725
- Each key is the name of the CSV file, minus the `.csv` extension.
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 return
749
- the data read from the file. The `loader` can also accept additional
750
- positional and keyword arguments, which can be provided through the
751
- `loader_args` and `loader_kwargs` attributes of the `DataFile`
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, InputFormat.CSV]:
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=InputFormat.CSV,
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
  """
@@ -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, to later be
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, to later be
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.status_v2 in {
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
- 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
- )
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, # Allows us to work with our own `Options` class.
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
- mlruns_path = os.path.join(model_dir, "mlruns")
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