nextmv 0.21.1__py3-none-any.whl → 0.22.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 CHANGED
@@ -1 +1 @@
1
- __version__ = "v0.21.1"
1
+ __version__ = "v0.22.0"
@@ -4,6 +4,7 @@ import json
4
4
  import random
5
5
  import shutil
6
6
  import time
7
+ from collections.abc import Callable
7
8
  from dataclasses import dataclass
8
9
  from datetime import datetime
9
10
  from typing import Any, Optional, Union
@@ -22,9 +23,10 @@ from nextmv.cloud.run import ExternalRunResult, RunConfiguration, RunInformation
22
23
  from nextmv.cloud.secrets import Secret, SecretsCollection, SecretsCollectionSummary
23
24
  from nextmv.cloud.status import StatusV2
24
25
  from nextmv.cloud.version import Version
25
- from nextmv.input import Input
26
+ from nextmv.input import Input, InputFormat
26
27
  from nextmv.logger import log
27
28
  from nextmv.model import Model, ModelConfiguration
29
+ from nextmv.options import Options
28
30
  from nextmv.output import Output
29
31
 
30
32
  _MAX_RUN_SIZE: int = 5 * 1024 * 1024
@@ -39,7 +41,8 @@ class DownloadURL(BaseModel):
39
41
  """URL to use for downloading the file."""
40
42
 
41
43
 
42
- class PollingOptions(BaseModel):
44
+ @dataclass
45
+ class PollingOptions:
43
46
  """
44
47
  Options to use when polling for a run result.
45
48
 
@@ -90,6 +93,13 @@ class PollingOptions(BaseModel):
90
93
  """
91
94
  verbose: bool = False
92
95
  """Whether to log the polling strategy. This is useful for debugging."""
96
+ stop: Optional[Callable[[], bool]] = None
97
+ """
98
+ Function to call to check if the polling should stop. This is useful for
99
+ stopping the polling based on external conditions. The function should
100
+ return True to stop the polling and False to continue. The function does
101
+ not receive any arguments. The function is called before each poll.
102
+ """
93
103
 
94
104
 
95
105
  _DEFAULT_POLLING_OPTIONS: PollingOptions = PollingOptions()
@@ -784,84 +794,144 @@ class Application:
784
794
 
785
795
  return Instance.from_dict(response.json())
786
796
 
787
- def new_run( # noqa: C901 # Lot of if statements, but clear logic.
797
+ def new_run( # noqa: C901 # Refactor this function at some point.
788
798
  self,
789
- input: Union[dict[str, Any], BaseModel, str] = None,
799
+ input: Union[Input, dict[str, Any], BaseModel, str] = None,
790
800
  instance_id: Optional[str] = None,
791
801
  name: Optional[str] = None,
792
802
  description: Optional[str] = None,
793
803
  upload_id: Optional[str] = None,
794
- options: Optional[dict[str, str]] = None,
795
- configuration: Optional[RunConfiguration] = None,
804
+ options: Optional[Union[Options, dict[str, str]]] = None,
805
+ configuration: Optional[Union[RunConfiguration, dict[str, any]]] = None,
796
806
  batch_experiment_id: Optional[str] = None,
797
- external_result: Optional[ExternalRunResult] = None,
807
+ external_result: Optional[Union[ExternalRunResult, dict[str, any]]] = None,
798
808
  ) -> str:
799
809
  """
800
810
  Submit an input to start a new run of the application. Returns the
801
- run_id of the submitted run.
811
+ `run_id` of the submitted run.
802
812
 
803
- Args:
804
- input: Input to use for the run. This can be JSON (given as dict
805
- or BaseModel) or text (given as str).
806
- instance_id: ID of the instance to use for the run. If not
807
- provided, the default_instance_id will be used.
808
- name: Name of the run.
809
- description: Description of the run.
810
- upload_id: ID to use when running a large input.
811
- options: Options to use for the run.
812
- configuration: Configuration to use for the run.
813
- batch_experiment_id: ID of a batch experiment to associate the run
814
- with.
815
- external_result: External result to use for the run, if this is an
816
- external run.
813
+ Parameters
814
+ ----------
815
+ input: Union[Input, dict[str, Any], BaseModel, str]
816
+ Input to use for the run. This can be a `nextmv.Input` object,
817
+ `dict`, `BaseModel` or `str`. If `nextmv.Input` is used, then the
818
+ input is extracted from the `.data` property. Note that for now,
819
+ `InputFormat.CSV_ARCHIVE` is not supported as an
820
+ `input.input_format`. If an input is too large, it will be uploaded
821
+ with the `upload_large_input` method.
822
+ instance_id: Optional[str]
823
+ ID of the instance to use for the run. If not provided, the default
824
+ instance ID associated to the Class (`default_instance_id`) is
825
+ used.
826
+ name: Optional[str]
827
+ Name of the run.
828
+ description: Optional[str]
829
+ Description of the run.
830
+ upload_id: Optional[str]
831
+ ID to use when running a large input. If the `input` exceeds the
832
+ maximum allowed size, then it is uploaded and the corresponding
833
+ `upload_id` is used.
834
+ options: Optional[Union[Options, dict[str, str]]]
835
+ Options to use for the run. This can be a `nextmv.Options` object
836
+ or a dict. If a dict is used, the keys must be strings and the
837
+ values must be strings as well. If a `nextmv.Options` object is
838
+ used, the options are extracted from the `.to_cloud_dict()` method.
839
+ Note that specifying `options` overrides the `input.options` (if
840
+ the `input` is of type `nextmv.Input`).
841
+ configuration: Optional[Union[RunConfiguration, dict[str, any]]]
842
+ Configuration to use for the run. This can be a
843
+ `cloud.RunConfiguration` object or a dict. If the object is used,
844
+ then the `.to_dict()` method is applied to extract the
845
+ configuration.
846
+ batch_experiment_id: Optional[str]
847
+ ID of a batch experiment to associate the run with. This is used
848
+ when the run is part of a batch experiment.
849
+ external_result: Optional[Union[ExternalRunResult, dict[str, any]]]
850
+ External result to use for the run. This can be a
851
+ `cloud.ExternalRunResult` object or a dict. If the object is used,
852
+ then the `.to_dict()` method is applied to extract the
853
+ configuration. This is used when the run is an external run. We
854
+ suggest that instead of specifying this parameter, you use the
855
+ `track_run` method of the class.
817
856
 
818
- Returns:
819
- ID of the submitted run.
857
+ Returns
858
+ ----------
859
+ str
860
+ ID (`run_id`) of the run that was submitted.
820
861
 
821
- Raises:
862
+ Raises
863
+ ----------
822
864
  requests.HTTPError: If the response status code is not 2xx.
865
+ ValueError:
866
+ If the `input` is of type `nextmv.Input` and the
867
+ `.input_format` is not `JSON`. If the final `options` are not
868
+ of type `dict[str,str]`.
823
869
  """
824
870
 
825
- input_size = 0
871
+ input_data = None
826
872
  if isinstance(input, BaseModel):
827
- input = input.to_dict()
828
- if input is not None:
829
- input_size = get_size(input)
830
- elif isinstance(input, dict):
831
- input_size = get_size(input)
873
+ input_data = input.to_dict()
874
+ elif isinstance(input, dict) or isinstance(input, str):
875
+ input_data = input
876
+ elif isinstance(input, Input):
877
+ if input.input_format == InputFormat.CSV_ARCHIVE:
878
+ raise ValueError("csv-archive is not supported")
879
+ input_data = input.data
832
880
 
833
- upload_url_required = isinstance(input, str) or input_size > _MAX_RUN_SIZE
881
+ input_size = 0
882
+ if input_data is not None:
883
+ input_size = get_size(input_data)
834
884
 
885
+ upload_url_required = input_size > _MAX_RUN_SIZE
835
886
  upload_id_used = upload_id is not None
887
+
836
888
  if not upload_id_used and upload_url_required:
837
889
  upload_url = self.upload_url()
838
- self.upload_large_input(input=input, upload_url=upload_url)
890
+ self.upload_large_input(input=input_data, upload_url=upload_url)
839
891
  upload_id = upload_url.upload_id
840
892
  upload_id_used = True
841
893
 
894
+ options_dict = {}
895
+ if isinstance(input, Input) and input.options is not None:
896
+ options_dict = input.options.to_cloud_dict()
897
+
842
898
  if options is not None:
843
- for key, value in options.items():
844
- if not isinstance(value, str):
845
- options[key] = json.dumps(value)
899
+ if isinstance(options, Options):
900
+ options_dict = options.to_cloud_dict()
901
+ elif isinstance(options, dict):
902
+ for k, v in options.items():
903
+ if isinstance(v, str):
904
+ options_dict[k] = v
905
+ else:
906
+ options_dict[k] = json.dumps(v)
846
907
 
847
908
  payload = {}
848
909
  if upload_id_used:
849
910
  payload["upload_id"] = upload_id
850
911
  else:
851
- payload["input"] = input
912
+ payload["input"] = input_data
852
913
 
853
914
  if name is not None:
854
915
  payload["name"] = name
855
916
  if description is not None:
856
917
  payload["description"] = description
857
- if options is not None:
858
- payload["options"] = options
918
+ if len(options_dict) > 0:
919
+ for k, v in options_dict.items():
920
+ if not isinstance(v, str):
921
+ raise ValueError(f"options must be dict[str,str], option {k} has type {type(v)} instead.")
922
+ payload["options"] = options_dict
859
923
  if configuration is not None:
860
- payload["configuration"] = configuration.to_dict()
924
+ configuration_dict = (
925
+ configuration.to_dict() if isinstance(configuration, RunConfiguration) else configuration
926
+ )
927
+ payload["configuration"] = configuration_dict
861
928
  if batch_experiment_id is not None:
862
929
  payload["batch_experiment_id"] = batch_experiment_id
863
930
  if external_result is not None:
864
- payload["result"] = external_result.to_dict()
931
+ external_dict = (
932
+ external_result.to_dict() if isinstance(external_result, ExternalRunResult) else external_result
933
+ )
934
+ payload["result"] = external_dict
865
935
 
866
936
  query_params = {
867
937
  "instance_id": instance_id if instance_id is not None else self.default_instance_id,
@@ -877,42 +947,83 @@ class Application:
877
947
 
878
948
  def new_run_with_result(
879
949
  self,
880
- input: Union[dict[str, Any], BaseModel] = None,
950
+ input: Union[Input, dict[str, Any], BaseModel, str] = None,
881
951
  instance_id: Optional[str] = None,
882
952
  name: Optional[str] = None,
883
953
  description: Optional[str] = None,
884
954
  upload_id: Optional[str] = None,
885
- run_options: Optional[dict[str, str]] = None,
955
+ run_options: Optional[Union[Options, dict[str, str]]] = None,
886
956
  polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
887
- configuration: Optional[RunConfiguration] = None,
957
+ configuration: Optional[Union[RunConfiguration, dict[str, any]]] = None,
888
958
  batch_experiment_id: Optional[str] = None,
889
- external_result: Optional[ExternalRunResult] = None,
959
+ external_result: Optional[Union[ExternalRunResult, dict[str, any]]] = None,
890
960
  ) -> RunResult:
891
961
  """
892
962
  Submit an input to start a new run of the application and poll for the
893
- result. This is a convenience method that combines the new_run and
894
- run_result_with_polling methods, applying polling logic to check when
963
+ result. This is a convenience method that combines the `new_run` and
964
+ `run_result_with_polling` methods, applying polling logic to check when
895
965
  the run succeeded.
896
966
 
897
- Args:
898
- input: Input to use for the run.
899
- instance_id: ID of the instance to use for the run. If not
900
- provided, the default_instance_id will be used.
901
- name: Name of the run.
902
- description: Description of the run.
903
- upload_id: ID to use when running a large input.
904
- run_options: Options to use for the run.
905
- polling_options: Options to use when polling for the run result.
906
- configuration: Configuration to use for the run.
907
- batch_experimemt_id: ID of a batch experiment to associate the run
908
- with.
909
- external_result: External result to use for the run, if this is an
910
- external run
967
+ Parameters
968
+ ----------
969
+ input: Union[Input, dict[str, Any], BaseModel, str]
970
+ Input to use for the run. This can be a `nextmv.Input` object,
971
+ `dict`, `BaseModel` or `str`. If `nextmv.Input` is used, then the
972
+ input is extracted from the `.data` property. Note that for now,
973
+ `InputFormat.CSV_ARCHIVE` is not supported as an
974
+ `input.input_format`. If an input is too large, it will be uploaded
975
+ with the `upload_large_input` method.
976
+ instance_id: Optional[str]
977
+ ID of the instance to use for the run. If not provided, the default
978
+ instance ID associated to the Class (`default_instance_id`) is
979
+ used.
980
+ name: Optional[str]
981
+ Name of the run.
982
+ description: Optional[str]
983
+ Description of the run.
984
+ upload_id: Optional[str]
985
+ ID to use when running a large input. If the `input` exceeds the
986
+ maximum allowed size, then it is uploaded and the corresponding
987
+ `upload_id` is used.
988
+ run_options: Optional[Union[Options, dict[str, str]]]
989
+ Options to use for the run. This can be a `nextmv.Options` object
990
+ or a dict. If a dict is used, the keys must be strings and the
991
+ values must be strings as well. If a `nextmv.Options` object is
992
+ used, the options are extracted from the `.to_cloud_dict()` method.
993
+ Note that specifying `options` overrides the `input.options` (if
994
+ the `input` is of type `nextmv.Input`).
995
+ polling_options: PollingOptions
996
+ Options to use when polling for the run result. This is a
997
+ convenience method that combines the `new_run` and
998
+ `run_result_with_polling` methods, applying polling logic to check
999
+ when the run succeeded.
1000
+ configuration: Optional[Union[RunConfiguration, dict[str, any]]]
1001
+ Configuration to use for the run. This can be a
1002
+ `cloud.RunConfiguration` object or a dict. If the object is used,
1003
+ then the `.to_dict()` method is applied to extract the
1004
+ configuration.
1005
+ batch_experiment_id: Optional[str]
1006
+ ID of a batch experiment to associate the run with. This is used
1007
+ when the run is part of a batch experiment.
1008
+ external_result: Optional[Union[ExternalRunResult, dict[str, any]]]
1009
+ External result to use for the run. This can be a
1010
+ `cloud.ExternalRunResult` object or a dict. If the object is used,
1011
+ then the `.to_dict()` method is applied to extract the
1012
+ configuration. This is used when the run is an external run. We
1013
+ suggest that instead of specifying this parameter, you use the
1014
+ `track_run_with_result` method of the class.
911
1015
 
912
- Returns:
1016
+ Returns
1017
+ ----------
1018
+ RunResult
913
1019
  Result of the run.
914
1020
 
915
- Raises:
1021
+ Raises
1022
+ ----------
1023
+ ValueError:
1024
+ If the `input` is of type `nextmv.Input` and the
1025
+ `.input_format` is not `JSON`.
1026
+ If the final `options` are not of type `dict[str,str]`.
916
1027
  requests.HTTPError: If the response status code is not 2xx.
917
1028
  TimeoutError: If the run does not succeed after the polling
918
1029
  strategy is exhausted based on time duration.
@@ -1656,7 +1767,7 @@ class Application:
1656
1767
  )
1657
1768
 
1658
1769
 
1659
- def poll(polling_options: PollingOptions, polling_func: callable) -> any:
1770
+ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[any, bool]]) -> any:
1660
1771
  """
1661
1772
  Auxiliary function for polling.
1662
1773
 
@@ -1689,9 +1800,16 @@ def poll(polling_options: PollingOptions, polling_func: callable) -> any:
1689
1800
  time.sleep(polling_options.initial_delay)
1690
1801
 
1691
1802
  start_time = time.time()
1803
+ stopped = False
1692
1804
 
1693
1805
  # Begin the polling process.
1694
1806
  for ix in range(polling_options.max_tries):
1807
+ # Check is we should stop polling according to the stop callback.
1808
+ if polling_options.stop is not None and polling_options.stop():
1809
+ stopped = True
1810
+
1811
+ break
1812
+
1695
1813
  # We check if we can stop polling.
1696
1814
  result, ok = polling_func()
1697
1815
  if polling_options.verbose:
@@ -1722,6 +1840,11 @@ def poll(polling_options: PollingOptions, polling_func: callable) -> any:
1722
1840
 
1723
1841
  time.sleep(sleep_duration)
1724
1842
 
1843
+ if stopped:
1844
+ log("polling | stop condition met, stopping polling")
1845
+
1846
+ return None
1847
+
1725
1848
  raise RuntimeError(
1726
1849
  f"polling did not succeed after {polling_options.max_tries} tries",
1727
1850
  )
nextmv/options.py CHANGED
@@ -3,6 +3,7 @@
3
3
  import argparse
4
4
  import builtins
5
5
  import copy
6
+ import json
6
7
  import os
7
8
  from dataclasses import dataclass
8
9
  from typing import Any, Optional
@@ -198,6 +199,32 @@ class Options:
198
199
 
199
200
  return m.to_dict()["config"]
200
201
 
202
+ def to_cloud_dict(self) -> dict[str, str]:
203
+ """
204
+ Converts the options to a dict that can be used in the Nextmv Cloud.
205
+ Cloud has a hard requirement that options are passed as strings. This
206
+ method converts the options to a dict with string values. This is
207
+ useful for passing options to the Nextmv Cloud.
208
+ As a side effect, this method parses the options if they have not been
209
+ parsed yet. See the `parse` method for more information.
210
+
211
+ Returns
212
+ -------
213
+ dict[str, str]
214
+ The options as a dict with string values.
215
+ """
216
+
217
+ options_dict = self.to_dict()
218
+
219
+ cloud_dict = {}
220
+ for k, v in options_dict.items():
221
+ if isinstance(v, str):
222
+ cloud_dict[k] = v
223
+ else:
224
+ cloud_dict[k] = json.dumps(v)
225
+
226
+ return cloud_dict
227
+
201
228
  def parameters_dict(self) -> list[dict[str, Any]]:
202
229
  """
203
230
  Converts the options to a list of dicts. Each dict is the dict
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 0.21.1
3
+ Version: 0.22.0
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://www.nextmv.io/docs/python-sdks/nextmv/installation
@@ -1,16 +1,16 @@
1
- nextmv/__about__.py,sha256=1EcNvwMaSDEkTRoL7sc_hMTild6kSCT-JOUHMHTrrw8,24
1
+ nextmv/__about__.py,sha256=m2IBVb3nBsm69PZPmYL-3F-v73dlOk3VgdBnwbuKFqo,24
2
2
  nextmv/__entrypoint__.py,sha256=o6xYGBBUgCY_htteW-qNJfp-S3Th8dzS4RreJcDCnzM,1333
3
3
  nextmv/__init__.py,sha256=rz9oP7JGyFQJdUtYX4j9xx4Gv4LMGfNvXoEID5qUqig,1354
4
4
  nextmv/base_model.py,sha256=mdaBe-epNK1cFgP4TxbOtn3So4pCi1vMTOrIBkCBp7A,1050
5
5
  nextmv/input.py,sha256=tYjiD9KM37Phqzm4faO4QLem8IXnU6HS160c1E-Yhc8,12732
6
6
  nextmv/logger.py,sha256=5qQ7E3Aaw3zzkIeiUuwYGzTcB7VhVqIzNZm5PHmdpUI,852
7
7
  nextmv/model.py,sha256=rwBdgmKSEp1DPv43zF0azj3QnbHO6O6wKs0PIGvVS40,9861
8
- nextmv/options.py,sha256=DZmjNxZyaTwnG2MEEKbIiBx3hI5F2DG70rY7DdeX44M,17336
8
+ nextmv/options.py,sha256=uLMGNt8MoBmtrcZSuinDZ7z8yq58bzHKohBRqC80r_s,18225
9
9
  nextmv/output.py,sha256=cxw7Z0dTj5u42w2MQLOz2gesj046tvYFSWmhSZtRSXU,20082
10
10
  nextmv/cloud/__init__.py,sha256=ojgA2vQRQnR8QO2tJme1TbGrjhr9yq9LIauxKJ5F40U,3305
11
11
  nextmv/cloud/acceptance_test.py,sha256=NtqGhj-UYibxGBbU2kfjr-lYcngojb_5VMvK2WZwibI,6620
12
12
  nextmv/cloud/account.py,sha256=mZUGzV-uMGBA5BC_FPtsiCMFuz5jxEZ3O1BbELZIm18,1841
13
- nextmv/cloud/application.py,sha256=VQfi7Acv19ezAUM6ODj4NsIka8XYB3ibgOkuKvkuQNw,55909
13
+ nextmv/cloud/application.py,sha256=XZvR0e2sx3HPLp6nhZEbwx7xytDSFRgDoyFDkIrGSWs,62362
14
14
  nextmv/cloud/batch_experiment.py,sha256=UxrMNAm2c7-RZ9TWRhZBtiVyEeUG1pjsLNhYkoT5Jog,2236
15
15
  nextmv/cloud/client.py,sha256=E4C8wOGOdgyMf-DCDt4YfI7ib8HwmUhAtKMdrSBJc2M,9004
16
16
  nextmv/cloud/input_set.py,sha256=ovkP17-jYs0yWrbqTM6Nl5ubWQabD_UrDqAHNo8aE2s,672
@@ -21,7 +21,7 @@ nextmv/cloud/run.py,sha256=4hEANGXysDiOI1SQtfs7t_IVk-bxCldIs2TOvCI7a3E,8689
21
21
  nextmv/cloud/secrets.py,sha256=kqlN4ceww_L4kVTrAU8BZykRzXINO3zhMT_BLYea6tk,1764
22
22
  nextmv/cloud/status.py,sha256=C-ax8cLw0jPeh7CPsJkCa0s4ImRyFI4NDJJxI0_1sr4,602
23
23
  nextmv/cloud/version.py,sha256=sjVRNRtohHA97j6IuyM33_DSSsXYkZPusYgpb6hlcrc,1244
24
- nextmv-0.21.1.dist-info/METADATA,sha256=fLNFHuNeJvQST1BsX0vY7UmqrkFtrb5h8sp0ptN6ew4,14557
25
- nextmv-0.21.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- nextmv-0.21.1.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
27
- nextmv-0.21.1.dist-info/RECORD,,
24
+ nextmv-0.22.0.dist-info/METADATA,sha256=ekIfM1lCU_4J-WOBWbYjY54MRq74rFp6xHGUWtcIaC4,14557
25
+ nextmv-0.22.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ nextmv-0.22.0.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
27
+ nextmv-0.22.0.dist-info/RECORD,,