nextmv 0.26.3__py3-none-any.whl → 0.27.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.26.3"
1
+ __version__ = "v0.27.0"
nextmv/__entrypoint__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- When working in a notebook environment, we dont really create a `main.py` file
2
+ When working in a notebook environment, we don't really create a `main.py` file
3
3
  with the main entrypoint of the program. Because the logic is mostly encoded
4
4
  inside the `Model` class, we need to create a `main.py` file that we can run in
5
5
  Nextmv Cloud. This file is used as that entrypoint. It is not intended for a
@@ -19,9 +19,7 @@ def main() -> None:
19
19
  manifest = cloud.Manifest.from_yaml(".")
20
20
 
21
21
  # Load the options from the manifest.
22
- options = None
23
- if manifest.options is not None:
24
- options = manifest.extract_options()
22
+ options = manifest.extract_options()
25
23
 
26
24
  # Load the model.
27
25
  loaded_model = load_model(
@@ -29,7 +27,7 @@ def main() -> None:
29
27
  suppress_warnings=True,
30
28
  )
31
29
 
32
- # Load the input and solve the model by using mlflows inference API.
30
+ # Load the input and solve the model by using mlflow's inference API.
33
31
  input = nextmv.load(options=options)
34
32
  output = loaded_model.predict(input)
35
33
 
@@ -881,7 +881,7 @@ class Application:
881
881
  instance_id: Optional[str]
882
882
  ID of the instance to use for the input set. This is used to
883
883
  filter the runs associated with the input set. If not provided,
884
- the applications `default_instance_id` is used.
884
+ the application's `default_instance_id` is used.
885
885
  maximum_runs: Optional[int]
886
886
  Maximum number of runs to use for the input set. This is used to
887
887
  filter the runs associated with the input set. If not provided,
@@ -997,7 +997,7 @@ class Application:
997
997
  description: Optional[str] = None,
998
998
  upload_id: Optional[str] = None,
999
999
  run_id: Optional[str] = None,
1000
- format: Optional[Union[Format, dict[str, any]]] = None,
1000
+ format: Optional[Union[Format, dict[str, Any]]] = None,
1001
1001
  ) -> ManagedInput:
1002
1002
  """
1003
1003
  Create a new managed input. There are two methods for creating a
@@ -1076,9 +1076,9 @@ class Application:
1076
1076
  description: Optional[str] = None,
1077
1077
  upload_id: Optional[str] = None,
1078
1078
  options: Optional[Union[Options, dict[str, str]]] = None,
1079
- configuration: Optional[Union[RunConfiguration, dict[str, any]]] = None,
1079
+ configuration: Optional[Union[RunConfiguration, dict[str, Any]]] = None,
1080
1080
  batch_experiment_id: Optional[str] = None,
1081
- external_result: Optional[Union[ExternalRunResult, dict[str, any]]] = None,
1081
+ external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
1082
1082
  ) -> str:
1083
1083
  """
1084
1084
  Submit an input to start a new run of the application. Returns the
@@ -1112,7 +1112,7 @@ class Application:
1112
1112
  used, the options are extracted from the `.to_cloud_dict()` method.
1113
1113
  Note that specifying `options` overrides the `input.options` (if
1114
1114
  the `input` is of type `nextmv.Input`).
1115
- configuration: Optional[Union[RunConfiguration, dict[str, any]]]
1115
+ configuration: Optional[Union[RunConfiguration, dict[str, Any]]]
1116
1116
  Configuration to use for the run. This can be a
1117
1117
  `cloud.RunConfiguration` object or a dict. If the object is used,
1118
1118
  then the `.to_dict()` method is applied to extract the
@@ -1120,7 +1120,7 @@ class Application:
1120
1120
  batch_experiment_id: Optional[str]
1121
1121
  ID of a batch experiment to associate the run with. This is used
1122
1122
  when the run is part of a batch experiment.
1123
- external_result: Optional[Union[ExternalRunResult, dict[str, any]]]
1123
+ external_result: Optional[Union[ExternalRunResult, dict[str, Any]]]
1124
1124
  External result to use for the run. This can be a
1125
1125
  `cloud.ExternalRunResult` object or a dict. If the object is used,
1126
1126
  then the `.to_dict()` method is applied to extract the
@@ -1228,9 +1228,9 @@ class Application:
1228
1228
  upload_id: Optional[str] = None,
1229
1229
  run_options: Optional[Union[Options, dict[str, str]]] = None,
1230
1230
  polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
1231
- configuration: Optional[Union[RunConfiguration, dict[str, any]]] = None,
1231
+ configuration: Optional[Union[RunConfiguration, dict[str, Any]]] = None,
1232
1232
  batch_experiment_id: Optional[str] = None,
1233
- external_result: Optional[Union[ExternalRunResult, dict[str, any]]] = None,
1233
+ external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
1234
1234
  ) -> RunResult:
1235
1235
  """
1236
1236
  Submit an input to start a new run of the application and poll for the
@@ -1271,7 +1271,7 @@ class Application:
1271
1271
  convenience method that combines the `new_run` and
1272
1272
  `run_result_with_polling` methods, applying polling logic to check
1273
1273
  when the run succeeded.
1274
- configuration: Optional[Union[RunConfiguration, dict[str, any]]]
1274
+ configuration: Optional[Union[RunConfiguration, dict[str, Any]]]
1275
1275
  Configuration to use for the run. This can be a
1276
1276
  `cloud.RunConfiguration` object or a dict. If the object is used,
1277
1277
  then the `.to_dict()` method is applied to extract the
@@ -1279,7 +1279,7 @@ class Application:
1279
1279
  batch_experiment_id: Optional[str]
1280
1280
  ID of a batch experiment to associate the run with. This is used
1281
1281
  when the run is part of a batch experiment.
1282
- external_result: Optional[Union[ExternalRunResult, dict[str, any]]]
1282
+ external_result: Optional[Union[ExternalRunResult, dict[str, Any]]]
1283
1283
  External result to use for the run. This can be a
1284
1284
  `cloud.ExternalRunResult` object or a dict. If the object is used,
1285
1285
  then the `.to_dict()` method is applied to extract the
@@ -1555,7 +1555,7 @@ class Application:
1555
1555
  exception will be raised.
1556
1556
 
1557
1557
  There are two ways to push an app to Nextmv Cloud:
1558
- 1. Specifying `app_dir`, which is the path to an apps root directory.
1558
+ 1. Specifying `app_dir`, which is the path to an app's root directory.
1559
1559
  This acts as an external strategy, where the app is composed of files
1560
1560
  in a directory and those apps are packaged and pushed to Nextmv Cloud.
1561
1561
  2. Specifying a `model` and `model_configuration`. This acts as an
@@ -1566,7 +1566,7 @@ class Application:
1566
1566
  Examples
1567
1567
  -------
1568
1568
 
1569
- 1. Push an app using an external strategy, i.e., specifying the apps
1569
+ 1. Push an app using an external strategy, i.e., specifying the app's
1570
1570
  directory:
1571
1571
  ```python
1572
1572
  import os
@@ -1640,7 +1640,7 @@ class Application:
1640
1640
  manifest : Optional[Manifest], optional
1641
1641
  The manifest for the app, by default None.
1642
1642
  app_dir : Optional[str], optional
1643
- The path to the apps directory, by default None.
1643
+ The path to the app's directory, by default None.
1644
1644
  verbose : bool, optional
1645
1645
  Whether to print verbose output, by default False.
1646
1646
  """
@@ -1793,7 +1793,7 @@ class Application:
1793
1793
  requests.HTTPError: If the response status code is not 2xx.
1794
1794
  """
1795
1795
 
1796
- def polling_func() -> tuple[any, bool]:
1796
+ def polling_func() -> tuple[Any, bool]:
1797
1797
  run_information = self.run_metadata(run_id=run_id)
1798
1798
  if run_information.metadata.status_v2 in {
1799
1799
  StatusV2.succeeded,
@@ -2327,6 +2327,10 @@ class Application:
2327
2327
  "runtime": manifest.runtime,
2328
2328
  },
2329
2329
  }
2330
+
2331
+ if manifest.configuration is not None and manifest.configuration.options is not None:
2332
+ activation_request["requirements"]["options"] = manifest.configuration.options.to_dict()
2333
+
2330
2334
  response = self.client.request(
2331
2335
  method="PUT",
2332
2336
  endpoint=endpoint,
@@ -2402,11 +2406,11 @@ class Application:
2402
2406
  raise ValueError(f"Unknown scenario input type: {scenario.scenario_input.scenario_input_type}")
2403
2407
 
2404
2408
 
2405
- def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[any, bool]]) -> any:
2409
+ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[Any, bool]]) -> Any:
2406
2410
  """
2407
2411
  Auxiliary function for polling.
2408
2412
 
2409
- The `polling_func` is a callable that must return a `tuple[any, bool]`
2413
+ The `polling_func` is a callable that must return a `tuple[Any, bool]`
2410
2414
  where the first element is the result of the polling and the second
2411
2415
  element is a boolean indicating if the polling was successful or should be
2412
2416
  retried.
@@ -2424,7 +2428,7 @@ def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[any,
2424
2428
 
2425
2429
  Returns
2426
2430
  -------
2427
- any
2431
+ Any
2428
2432
  Result of the polling function.
2429
2433
  """
2430
2434
 
nextmv/cloud/manifest.py CHANGED
@@ -16,8 +16,19 @@ FILE_NAME = "app.yaml"
16
16
 
17
17
 
18
18
  class ManifestType(str, Enum):
19
- """Type of application in the manifest, based on the programming
20
- language."""
19
+ """
20
+ Type of application in the manifest, based on the programming
21
+ language.
22
+
23
+ Attributes
24
+ ----------
25
+ PYTHON: str
26
+ Python format
27
+ GO: str
28
+ Go format
29
+ JAVA: str
30
+ Java format
31
+ """
21
32
 
22
33
  PYTHON = "python"
23
34
  """Python format"""
@@ -28,7 +39,23 @@ class ManifestType(str, Enum):
28
39
 
29
40
 
30
41
  class ManifestRuntime(str, Enum):
31
- """Runtime (environment) where the app will be run on Nextmv Cloud."""
42
+ """
43
+ Runtime (environment) where the app will be run on Nextmv Cloud.
44
+
45
+ Attributes
46
+ ----------
47
+ DEFAULT: str
48
+ This runtime is used to run compiled applications such as Go binaries.
49
+ PYTHON: str
50
+ This runtime is used as the basis for all other Python runtimes and
51
+ Python applications.
52
+ JAVA: str
53
+ This runtime is used to run Java applications.
54
+ PYOMO: str
55
+ This runtime provisions Python packages to run Pyomo applications.
56
+ HEXALY: str
57
+ This runtime provisions Python packages to run Hexaly applications.
58
+ """
32
59
 
33
60
  DEFAULT = "ghcr.io/nextmv-io/runtime/default:latest"
34
61
  """This runtime is used to run compiled applications such as Go binaries."""
@@ -49,7 +76,20 @@ class ManifestRuntime(str, Enum):
49
76
 
50
77
 
51
78
  class ManifestBuild(BaseModel):
52
- """Build-specific attributes."""
79
+ """
80
+ Build-specific attributes.
81
+
82
+ Attributes
83
+ ----------
84
+ command: Optional[str]
85
+ The command to run to build the app. This command will be executed
86
+ without a shell, i.e., directly. The command must exit with a status of
87
+ 0 to continue the push process of the app to Nextmv Cloud. This command
88
+ is executed prior to the pre-push command.
89
+ environment: Optional[dict[str, Any]]
90
+ Environment variables to set when running the build command given as
91
+ key-value pairs.
92
+ """
53
93
 
54
94
  command: Optional[str] = None
55
95
  """
@@ -82,7 +122,19 @@ class ManifestBuild(BaseModel):
82
122
 
83
123
 
84
124
  class ManifestPythonModel(BaseModel):
85
- """Model-specific instructions for a Python app."""
125
+ """
126
+ Model-specific instructions for a Python app.
127
+
128
+ Attributes
129
+ ----------
130
+ name: str
131
+ The name of the decision model.
132
+ options: Optional[list[dict[str, Any]]]
133
+ Options for the decision model. This is a data representation of the
134
+ `nextmv.Options` class. It consists of a list of dicts. Each dict
135
+ represents the `nextmv.Option` class. It is used to be able to
136
+ reconstruct an Options object from data when loading a decision model.
137
+ """
86
138
 
87
139
  name: str
88
140
  """The name of the decision model."""
@@ -96,7 +148,18 @@ class ManifestPythonModel(BaseModel):
96
148
 
97
149
 
98
150
  class ManifestPython(BaseModel):
99
- """Python-specific instructions."""
151
+ """
152
+ Python-specific instructions.
153
+
154
+ Attributes
155
+ ----------
156
+ pip_requirements: Optional[str]
157
+ Path to a requirements.txt file containing (additional) Python
158
+ dependencies that will be bundled with the app.
159
+ model: Optional[ManifestPythonModel]
160
+ Information about an encoded decision model as handlded via mlflow. This
161
+ information is used to load the decision model from the app bundle.
162
+ """
100
163
 
101
164
  pip_requirements: Optional[str] = Field(
102
165
  serialization_alias="pip-requirements",
@@ -115,7 +178,23 @@ class ManifestPython(BaseModel):
115
178
 
116
179
 
117
180
  class ManifestOption(BaseModel):
118
- """An option for the decision model that is recorded in the manifest."""
181
+ """
182
+ An option for the decision model that is recorded in the manifest.
183
+
184
+ Attributes
185
+ ----------
186
+ name: str
187
+ The name of the option.
188
+ option_type: str
189
+ The type of the option. This is a string representation of the
190
+ `nextmv.Option` class.
191
+ default: Optional[Any]
192
+ The default value of the option.
193
+ description: Optional[str]
194
+ The description of the option.
195
+ required: bool
196
+ Whether the option is required or not.
197
+ """
119
198
 
120
199
  name: str
121
200
  """The name of the option"""
@@ -131,8 +210,13 @@ class ManifestOption(BaseModel):
131
210
  """The description of the option"""
132
211
  required: bool = False
133
212
  """Whether the option is required or not"""
134
- choices: Optional[list[Any]] = None
135
- """The choices for the option"""
213
+ additional_attributes: Optional[dict[str, Any]] = None
214
+ """
215
+ Optional additional attributes for the option. The Nextmv Cloud may
216
+ perform validation on these attributes. For example, the maximum length of
217
+ a string or the maximum value of an integer. These additional attributes
218
+ will be shown in the help message of the `Options`.
219
+ """
136
220
 
137
221
  @classmethod
138
222
  def from_option(cls, option: Option) -> "ManifestOption":
@@ -153,9 +237,9 @@ class ManifestOption(BaseModel):
153
237
  if option_type is str:
154
238
  option_type = "string"
155
239
  elif option_type is bool:
156
- option_type = "boolean"
240
+ option_type = "bool"
157
241
  elif option_type is int:
158
- option_type = "integer"
242
+ option_type = "int"
159
243
  elif option_type is float:
160
244
  option_type = "float"
161
245
  else:
@@ -167,7 +251,7 @@ class ManifestOption(BaseModel):
167
251
  default=option.default,
168
252
  description=option.description,
169
253
  required=option.required,
170
- choices=option.choices,
254
+ additional_attributes=option.additional_attributes,
171
255
  )
172
256
 
173
257
  def to_option(self) -> Option:
@@ -183,9 +267,9 @@ class ManifestOption(BaseModel):
183
267
  option_type_string = self.option_type
184
268
  if option_type_string == "string":
185
269
  option_type = str
186
- elif option_type_string == "boolean":
270
+ elif option_type_string == "bool":
187
271
  option_type = bool
188
- elif option_type_string == "integer":
272
+ elif option_type_string == "int":
189
273
  option_type = int
190
274
  elif option_type_string == "float":
191
275
  option_type = float
@@ -198,10 +282,47 @@ class ManifestOption(BaseModel):
198
282
  default=self.default,
199
283
  description=self.description,
200
284
  required=self.required,
201
- choices=self.choices,
285
+ additional_attributes=self.additional_attributes,
202
286
  )
203
287
 
204
288
 
289
+ class ManifestOptions(BaseModel):
290
+ """
291
+ Options for the decision model.
292
+
293
+ Attributes
294
+ ----------
295
+ strict: bool
296
+ If strict is set to `True`, only the listed options will be allowed.
297
+ items: list[ManifestOption]
298
+ Optional. The actual list of options for the decision model. An option
299
+ is a parameter that configures the decision model.
300
+ """
301
+
302
+ strict: Optional[bool] = False
303
+ """If strict is set to `True`, only the listed options will be allowed."""
304
+ items: Optional[list[ManifestOption]] = None
305
+ """
306
+ Optional. The actual list of options for the decision model. An option is a
307
+ parameter that configures the decision model.
308
+ """
309
+
310
+
311
+ class ManifestConfiguration(BaseModel):
312
+ """
313
+ Configuration for the decision model.
314
+
315
+ Attributes
316
+ ----------
317
+ options: ManifestOptions
318
+ Optional. The actual list of options for the decision model. An option
319
+ is a parameter that configures the decision model.
320
+ """
321
+
322
+ options: ManifestOptions
323
+ """Options for the decision model."""
324
+
325
+
205
326
  class Manifest(BaseModel):
206
327
  """
207
328
  An application that runs on the Nextmv Platform must contain a file named
@@ -210,6 +331,34 @@ class Manifest(BaseModel):
210
331
 
211
332
  This class represents the app manifest and allows you to load it from a
212
333
  file or create it programmatically.
334
+
335
+ Attributes
336
+ ----------
337
+ files: list[str]
338
+ Mandatory. The files to include (or exclude) in the app.
339
+ runtime: ManifestRuntime
340
+ Mandatory. The runtime to use for the app, it provides the environment
341
+ in which the app runs.
342
+ type: ManifestType
343
+ Mandatory. Type of application, based on the programming language.
344
+ build: Optional[ManifestBuild]
345
+ Optional. Build-specific attributes. The build.command to run to build
346
+ the app. This command will be executed without a shell, i.e., directly.
347
+ The command must exit with a status of 0 to continue the push process of
348
+ the app to Nextmv Cloud. This command is executed prior to the pre-push
349
+ command. The build.environment is used to set environment variables when
350
+ running the build command given as key-value pairs.
351
+ pre_push: Optional[str]
352
+ Optional. A command to run before the app is pushed to the Nextmv Cloud.
353
+ This command can be used to compile a binary, run tests or similar tasks.
354
+ One difference with what is specified under build, is that the command
355
+ will be executed via
356
+ python: Optional[ManifestPython]
357
+ Optional. Only for Python apps. Contains further Python-specific
358
+ attributes.
359
+ configuration: Optional[ManifestConfiguration]
360
+ Optional. A list of options for the decision model. An option is a
361
+ parameter that configures the decision model.
213
362
  """
214
363
 
215
364
  files: list[str]
@@ -250,7 +399,7 @@ class Manifest(BaseModel):
250
399
  Optional. Only for Python apps. Contains further Python-specific
251
400
  attributes.
252
401
  """
253
- options: Optional[list[ManifestOption]] = None
402
+ configuration: Optional[ManifestConfiguration] = None
254
403
  """
255
404
  Optional. A list of options for the decision model. An option is a
256
405
  parameter that configures the decision model.
@@ -292,20 +441,23 @@ class Manifest(BaseModel):
292
441
  with open(os.path.join(dirpath, FILE_NAME), "w") as file:
293
442
  yaml.dump(self.to_dict(), file)
294
443
 
295
- def extract_options(self) -> Options:
444
+ def extract_options(self) -> Optional[Options]:
296
445
  """
297
- Convert the manifest options to a `nextmv.Options` object.
446
+ Convert the manifest options to a `nextmv.Options` object. If the
447
+ manifest does not have valid options defined in
448
+ `.configuration.options.items`, this method simply returns a `None`.
298
449
 
299
450
  Returns
300
451
  -------
301
- Options
302
- The converted options.
452
+ Optional[Options]
453
+ The options extracted from the manifest. If no options are found,
454
+ `None` is returned.
303
455
  """
304
456
 
305
- if self.options is None:
306
- raise ValueError("No options found in the manifest")
457
+ if self.configuration is None or self.configuration.options is None or self.configuration.options.items is None:
458
+ return None
307
459
 
308
- options = [option.to_option() for option in self.options]
460
+ options = [option.to_option() for option in self.configuration.options.items]
309
461
 
310
462
  return Options(*options)
311
463
 
@@ -348,7 +500,12 @@ class Manifest(BaseModel):
348
500
  )
349
501
 
350
502
  if model_configuration.options is not None:
351
- manifest.options = [ManifestOption.from_option(opt) for opt in model_configuration.options.options]
503
+ manifest.configuration = ManifestConfiguration(
504
+ options=ManifestOptions(
505
+ strict=False,
506
+ items=[ManifestOption.from_option(opt) for opt in model_configuration.options.options],
507
+ ),
508
+ )
352
509
 
353
510
  return manifest
354
511
 
@@ -377,7 +534,12 @@ class Manifest(BaseModel):
377
534
  runtime=ManifestRuntime.PYTHON,
378
535
  type=ManifestType.PYTHON,
379
536
  python=ManifestPython(pip_requirements="requirements.txt"),
380
- options=[ManifestOption.from_option(opt) for opt in options.options],
537
+ configuration=ManifestConfiguration(
538
+ options=ManifestOptions(
539
+ strict=False,
540
+ items=[ManifestOption.from_option(opt) for opt in options.options],
541
+ ),
542
+ ),
381
543
  )
382
544
 
383
545
  return manifest
nextmv/cloud/run.py CHANGED
@@ -248,11 +248,11 @@ class TrackedRun:
248
248
 
249
249
  Attributes
250
250
  ----------
251
- input : Union[Input, dict[str, any], str]
251
+ input : Union[Input, dict[str, Any], str]
252
252
  The input of the run being tracked. Please note that if the input
253
253
  format is JSON, then the input data must be JSON serializable. This
254
254
  field is required.
255
- output : Union[Output, dict[str, any], str]
255
+ output : Union[Output, dict[str, Any], str]
256
256
  The output of the run being tracked. Please note that if the output
257
257
  format is JSON, then the output data must be JSON serializable. This
258
258
  field is required.
@@ -270,9 +270,9 @@ class TrackedRun:
270
270
  the log. This field is optional.
271
271
  """
272
272
 
273
- input: Union[Input, dict[str, any], str]
273
+ input: Union[Input, dict[str, Any], str]
274
274
  """The input of the run being tracked."""
275
- output: Union[Output, dict[str, any], str]
275
+ output: Union[Output, dict[str, Any], str]
276
276
  """The output of the run being tracked. Only JSON output_format is supported."""
277
277
  status: TrackedRunStatus
278
278
  """The status of the run being tracked"""
@@ -303,7 +303,7 @@ class TrackedRun:
303
303
  try:
304
304
  _ = json.dumps(self.input)
305
305
  except (TypeError, OverflowError) as e:
306
- raise ValueError("Input is dict[str, any] but it is not JSON serializable") from e
306
+ raise ValueError("Input is dict[str, Any] but it is not JSON serializable") from e
307
307
 
308
308
  if isinstance(self.output, Output):
309
309
  if self.output.output_format != OutputFormat.JSON:
@@ -312,7 +312,7 @@ class TrackedRun:
312
312
  try:
313
313
  _ = json.dumps(self.output)
314
314
  except (TypeError, OverflowError) as e:
315
- raise ValueError("Output is dict[str, any] but it is not JSON serializable") from e
315
+ raise ValueError("Output is dict[str, Any] but it is not JSON serializable") from e
316
316
 
317
317
  def logs_text(self) -> str:
318
318
  """
nextmv/cloud/scenario.py CHANGED
@@ -211,7 +211,7 @@ def _option_sets(scenarios: list[Scenario]) -> dict[str, dict[str, dict[str, str
211
211
  def _scenarios_by_id(scenarios: list[Scenario]) -> dict[str, Scenario]:
212
212
  """
213
213
  This function maps a scenario to its ID. A scenario ID is created if it
214
- wasnt defined. This function also checks that there are no duplicate
214
+ wasn't defined. This function also checks that there are no duplicate
215
215
  scenario IDs.
216
216
  """
217
217
 
nextmv/input.py CHANGED
@@ -92,7 +92,7 @@ class Input:
92
92
  new_options = copy.deepcopy(init_options)
93
93
  self.options = new_options
94
94
 
95
- def to_dict(self) -> dict[str, any]:
95
+ def to_dict(self) -> dict[str, Any]:
96
96
  """
97
97
  Convert the input to a dictionary.
98
98
 
@@ -102,7 +102,7 @@ class Input:
102
102
 
103
103
  Returns
104
104
  -------
105
- dict[str, any]
105
+ dict[str, Any]
106
106
  The input as a dictionary.
107
107
  """
108
108
 
nextmv/model.py CHANGED
@@ -47,7 +47,7 @@ warnings.showwarning = custom_showwarning
47
47
  # the model requires and that we install and bundle with the app.
48
48
  _REQUIREMENTS_FILE = "model_requirements.txt"
49
49
 
50
- # When working in a notebook environment, we dont really create a `main.py`
50
+ # When working in a notebook environment, we don't really create a `main.py`
51
51
  # file with the main entrypoint of the program. Because the logic is mostly
52
52
  # encoded inside the `Model` class, we need to create a `main.py` file that we
53
53
  # can run in Nextmv Cloud. This file is used as that entrypoint.
@@ -148,7 +148,7 @@ class Model:
148
148
  saved and loaded.
149
149
  """
150
150
 
151
- # mlflow is a big package. We dont want to make it a dependency of
151
+ # mlflow is a big package. We don't want to make it a dependency of
152
152
  # `nextmv` because it is not always needed. We only need it if we are
153
153
  # working with the "app from model" logic, which involves working with
154
154
  # this `Model` class.
@@ -180,7 +180,7 @@ class Model:
180
180
  params: Optional[dict[str, Any]] = None,
181
181
  ) -> Any:
182
182
  """
183
- The predict method allows us to work with mlflows [python_function]
183
+ The predict method allows us to work with mlflow's [python_function]
184
184
  model flavor. Warning: This method should not be used or overridden
185
185
  directly. Instead, you should implement the `solve` method.
186
186
 
nextmv/options.py CHANGED
@@ -66,7 +66,7 @@ class Parameter:
66
66
  def __post_init__(self):
67
67
  deprecated(
68
68
  name="Parameter",
69
- reason="`Parameter` is deprecated, use `Option` instead.",
69
+ reason="`Parameter` is deprecated, use `Option` instead",
70
70
  )
71
71
 
72
72
  @classmethod
@@ -164,6 +164,11 @@ class Option:
164
164
  argument, an environment variable or a default value.
165
165
  choices : list[Optional[Any]], optional
166
166
  Limits values to a specific set of choices.
167
+ additional_attributes : dict[str, Any], optional
168
+ Optional additional attributes for the option. The Nextmv Cloud may
169
+ perform validation on these attributes. For example, the maximum length
170
+ of a string or the maximum value of an integer. These additional
171
+ attributes will be shown in the help message of the `Options`.
167
172
  """
168
173
 
169
174
  name: str
@@ -189,6 +194,13 @@ class Option:
189
194
  """
190
195
  choices: Optional[list[Any]] = None
191
196
  """Limits values to a specific set of choices."""
197
+ additional_attributes: Optional[dict[str, Any]] = None
198
+ """
199
+ Optional additional attributes for the option. The Nextmv Cloud may
200
+ perform validation on these attributes. For example, the maximum length of
201
+ a string or the maximum value of an integer. These additional attributes
202
+ will be shown in the help message of the `Options`.
203
+ """
192
204
 
193
205
  @classmethod
194
206
  def from_dict(cls, data: dict[str, Any]) -> "Option":
@@ -209,13 +221,14 @@ class Option:
209
221
  option_type_string = data["option_type"]
210
222
  option_type = getattr(builtins, option_type_string.split("'")[1])
211
223
 
212
- return Option(
224
+ return cls(
213
225
  name=data["name"],
214
226
  option_type=option_type,
215
227
  default=data.get("default"),
216
228
  description=data.get("description"),
217
229
  required=data.get("required", False),
218
230
  choices=data.get("choices"),
231
+ additional_attributes=data.get("additional_attributes"),
219
232
  )
220
233
 
221
234
  def to_dict(self) -> dict[str, Any]:
@@ -235,6 +248,7 @@ class Option:
235
248
  "description": self.description,
236
249
  "required": self.required,
237
250
  "choices": self.choices,
251
+ "additional_attributes": self.additional_attributes,
238
252
  }
239
253
 
240
254
 
@@ -657,7 +671,7 @@ class Options:
657
671
  options_by_field_name[option.name.replace("-", "_")] = option
658
672
 
659
673
  # The ipkyernel uses a `-f` argument by default that it passes to the
660
- # execution. We dont want to ignore this argument because we get an
674
+ # execution. We don't want to ignore this argument because we get an
661
675
  # error. Fix source: https://stackoverflow.com/a/56349168
662
676
  parser.add_argument(
663
677
  "-f",
@@ -729,6 +743,9 @@ class Options:
729
743
 
730
744
  description += f" (type: {self._option_type(option).__name__})"
731
745
 
746
+ if isinstance(option, Option) and option.additional_attributes is not None:
747
+ description += f" (additional attributes: {option.additional_attributes})"
748
+
732
749
  if option.description is not None and option.description != "":
733
750
  description += f": {option.description}"
734
751
 
nextmv/output.py CHANGED
@@ -249,10 +249,10 @@ class Output:
249
249
  serialized to the write location.
250
250
 
251
251
  The most important part of the output is the solution, which represents the
252
- result of the decision problem. The solutions type must match the
252
+ result of the decision problem. The solution's type must match the
253
253
  `output_format`:
254
254
 
255
- - `OutputFormat.JSON`: the data must be `dict[str, Any]`.
255
+ - `OutputFormat.JSON`: the data must be `dict[str, Any]`, or `Any`.
256
256
  - `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str, list[dict[str,
257
257
  Any]]]`. The keys represent the file names where the data should be
258
258
  written. The values are lists of dictionaries, where each dictionary
@@ -263,20 +263,73 @@ class Output:
263
263
  dictionary, we recommend using the `Statistics` class to ensure that the
264
264
  data is correctly formatted.
265
265
 
266
- Parameters
266
+ The assets are used to keep track of different downloadable information that
267
+ is part of the output. The assets can be of type `Asset` or a simple
268
+ dictionary, but we recommend using the `Asset` class to ensure that the data is
269
+ correctly formatted.
270
+
271
+ Attributes
267
272
  ----------
268
- options : Options, optional
269
- Options that the `Input` were created with.
270
- output_format : OutputFormat, optional
273
+ options : Optional[Union[Options, dict[str, Any]]]
274
+ Options that the `Output` was created with. These options can be of type
275
+ `Options` or a simple dictionary. If the options are of type `Options`,
276
+ they will be serialized to a dictionary using the `to_dict` method. If
277
+ they are a dictionary, they will be used as is. If the options are not
278
+ provided, an empty dictionary will be used. If the options are of type
279
+ `dict`, then the dictionary should have the following structure:
280
+ ```
281
+ {
282
+ "duration": "30",
283
+ "threads": 4,
284
+ }
285
+ ```
286
+ output_format : Optional[OutputFormat]
271
287
  Format of the output data. Default is `OutputFormat.JSON`.
272
- solution : Union[dict[str, Any], dict[str, list[dict[str, Any]]], optional
273
- The solution to the decision problem.
274
- statistics : Union[Statistics, dict[str, Any], optional
275
- Statistics of the solution.
288
+ solution : Optional[Union[dict[str, Any], dict[str, list[dict[str, Any]]]]
289
+ The solution to the decision problem. The type must match the
290
+ `output_format`:
291
+ - `OutputFormat.JSON`: the data must be `dict[str, Any]`.
292
+ - `OutputFormat.CSV_ARCHIVE`: the data must be `dict[str,
293
+ list[dict[str, Any]]]`. The keys represent the file names where the
294
+ data should be written. The values are lists of dictionaries, where
295
+ each dictionary represents a row in the CSV file.
296
+ statistics : Optional[Union[Statistics, dict[str, Any]]]
297
+ Statistics of the solution. These statistics can be of type
298
+ `Statistics` or a simple dictionary. If the statistics are of type
299
+ `Statistics`, they will be serialized to a dictionary using the
300
+ `to_dict` method. If they are a dictionary, they will be used as is. If
301
+ the statistics are not provided, an empty dictionary will be used.
302
+ csv_configurations : Optional[dict[str, Any]]
303
+ Optional configuration for writing CSV files, to be used when the
304
+ `output_format` is `OutputFormat.CSV_ARCHIVE`. These configurations are
305
+ passed as kwargs to the `DictWriter` class from the `csv` module.
306
+ json_configurations : Optional[dict[str, Any]]
307
+ Optional configuration for writing JSON files, to be used when the
308
+ `output_format` is `OutputFormat.JSON`. These configurations are passed
309
+ as kwargs to the `json.dumps` function.
310
+ assets : Optional[list[Union[Asset, dict[str, Any]]]]
311
+ Optional list of assets to be included in the output. These assets can
312
+ be of type `Asset` or a simple dictionary. If the assets are of type
313
+ `Asset`, they will be serialized to a dictionary using the `to_dict`
314
+ method. If they are a dictionary, they will be used as is. If the
315
+ assets are not provided, an empty list will be used.
276
316
  """
277
317
 
278
- options: Optional[Options] = None
279
- """Options that the `Input` were created with."""
318
+ options: Optional[Union[Options, dict[str, Any]]] = None
319
+ """
320
+ Options that the `Output` was created with. These options can be of type
321
+ `Options` or a simple dictionary. If the options are of type `Options`,
322
+ they will be serialized to a dictionary using the `to_dict` method. If
323
+ they are a dictionary, they will be used as is. If the options are not
324
+ provided, an empty dictionary will be used. If the options are of type
325
+ `dict`, then the dictionary should have the following structure:
326
+ ```
327
+ {
328
+ "duration": "30",
329
+ "threads": 4,
330
+ }
331
+ ```
332
+ """
280
333
  output_format: Optional[OutputFormat] = OutputFormat.JSON
281
334
  """Format of the output data. Default is `OutputFormat.JSON`."""
282
335
  solution: Optional[
@@ -287,17 +340,33 @@ class Output:
287
340
  ] = None
288
341
  """The solution to the decision problem."""
289
342
  statistics: Optional[Union[Statistics, dict[str, Any]]] = None
290
- """Statistics of the solution."""
343
+ """
344
+ Statistics of the solution. These statistics can be of type `Statistics` or a
345
+ simple dictionary. If the statistics are of type `Statistics`, they will be
346
+ serialized to a dictionary using the `to_dict` method. If they are a
347
+ dictionary, they will be used as is. If the statistics are not provided, an
348
+ empty dictionary will be used.
349
+ """
291
350
  csv_configurations: Optional[dict[str, Any]] = None
292
- """Optional configuration for writing CSV files, to be used when the
293
- `output_format` is OutputFormat.CSV_ARCHIVE. These configurations are
294
- passed as kwargs to the `DictWriter` class from the `csv` module."""
351
+ """
352
+ Optional configuration for writing CSV files, to be used when the
353
+ `output_format` is `OutputFormat.CSV_ARCHIVE`. These configurations are
354
+ passed as kwargs to the `DictWriter` class from the `csv` module.
355
+ """
295
356
  json_configurations: Optional[dict[str, Any]] = None
296
- """Optional configuration for writing JSON files, to be used when the
297
- `output_format` is OutputFormat.JSON. These configurations are passed as
298
- kwargs to the `json.dumps` function."""
299
- assets: Optional[list[Asset]] = None
300
- """Optional list of assets to be included in the output."""
357
+ """
358
+ Optional configuration for writing JSON files, to be used when the
359
+ `output_format` is `OutputFormat.JSON`. These configurations are passed as
360
+ kwargs to the `json.dumps` function.
361
+ """
362
+ assets: Optional[list[Union[Asset, dict[str, Any]]]] = None
363
+ """
364
+ Optional list of assets to be included in the output. These assets can be of
365
+ type `Asset` or a simple dictionary. If the assets are of type `Asset`, they
366
+ will be serialized to a dictionary using the `to_dict` method. If they are a
367
+ dictionary, they will be used as is. If the assets are not provided, an
368
+ empty list will be used.
369
+ """
301
370
 
302
371
  def __post_init__(self):
303
372
  """Check that the solution matches the format given to initialize the
@@ -327,26 +396,76 @@ class Output:
327
396
  "output_format OutputFormat.CSV_ARCHIVE, supported type is `dict`"
328
397
  )
329
398
 
330
- def to_dict(self) -> dict[str, any]:
399
+ def to_dict(self) -> dict[str, Any]: # noqa: C901
331
400
  """
332
401
  Convert the `Output` object to a dictionary.
333
402
 
334
403
  Returns
335
404
  -------
336
- dict[str, any]
405
+ dict[str, Any]
337
406
  The dictionary representation of the `Output` object.
338
407
  """
339
408
 
409
+ # Options need to end up as a dict, so we achieve that based on the
410
+ # type of options that were used to create the class.
411
+ if self.options is None:
412
+ options = {}
413
+ elif isinstance(self.options, Options):
414
+ options = self.options.to_dict()
415
+ elif isinstance(self.options, dict):
416
+ options = self.options
417
+ else:
418
+ raise TypeError(f"unsupported options type: {type(self.options)}, supported types are `Options` or `dict`")
419
+
420
+ # Statistics need to end up as a dict, so we achieve that based on the
421
+ # type of statistics that were used to create the class.
422
+ if self.statistics is None:
423
+ statistics = {}
424
+ elif isinstance(self.statistics, Statistics):
425
+ statistics = self.statistics.to_dict()
426
+ elif isinstance(self.statistics, dict):
427
+ statistics = self.statistics
428
+ else:
429
+ raise TypeError(
430
+ f"unsupported statistics type: {type(self.statistics)}, supported types are `Statistics` or `dict`"
431
+ )
432
+
433
+ # Assets need to end up as a list of dicts, so we achieve that based on
434
+ # the type of each asset in the list.
435
+ assets = []
436
+ if isinstance(self.assets, list):
437
+ for ix, asset in enumerate(self.assets):
438
+ if isinstance(asset, Asset):
439
+ assets.append(asset.to_dict())
440
+ elif isinstance(asset, dict):
441
+ assets.append(asset)
442
+ else:
443
+ raise TypeError(
444
+ f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`"
445
+ )
446
+ elif self.assets is not None:
447
+ raise TypeError(f"unsupported assets type: {type(self.assets)}, supported types are `list`")
448
+
340
449
  output_dict = {
341
- "options": self.options.to_dict() if self.options is not None else {},
450
+ "options": options,
342
451
  "solution": self.solution if self.solution is not None else {},
343
- "statistics": self.statistics.to_dict() if self.statistics is not None else {},
344
- "assets": [asset.to_dict() for asset in self.assets] if self.assets is not None else [],
452
+ "statistics": statistics,
453
+ "assets": assets,
345
454
  }
346
455
 
347
- if self.output_format == OutputFormat.CSV_ARCHIVE:
456
+ # Add the auxiliary configurations to the output dictionary if they are
457
+ # defined and not empty.
458
+ if (
459
+ self.output_format == OutputFormat.CSV_ARCHIVE
460
+ and self.csv_configurations is not None
461
+ and self.csv_configurations != {}
462
+ ):
348
463
  output_dict["csv_configurations"] = self.csv_configurations
349
- elif self.output_format == OutputFormat.JSON:
464
+ elif (
465
+ self.output_format == OutputFormat.JSON
466
+ and self.json_configurations is not None
467
+ and self.json_configurations != {}
468
+ ):
350
469
  output_dict["json_configurations"] = self.json_configurations
351
470
 
352
471
  return output_dict
@@ -371,24 +490,9 @@ class LocalOutputWriter(OutputWriter):
371
490
 
372
491
  def _write_json(
373
492
  output: Union[Output, dict[str, Any], BaseModel],
374
- options: dict[str, Any],
375
- statistics: dict[str, Any],
376
- assets: list[dict[str, Any]],
493
+ output_dict: dict[str, Any],
377
494
  path: Optional[str] = None,
378
495
  ) -> None:
379
- if isinstance(output, dict):
380
- final_output = output
381
- elif isinstance(output, BaseModel):
382
- final_output = output.to_dict()
383
- else:
384
- solution = output.solution if output.solution is not None else {}
385
- final_output = {
386
- "options": options,
387
- "solution": solution,
388
- "statistics": statistics,
389
- "assets": assets,
390
- }
391
-
392
496
  json_configurations = {}
393
497
  if hasattr(output, "json_configurations") and output.json_configurations is not None:
394
498
  json_configurations = output.json_configurations
@@ -402,7 +506,7 @@ class LocalOutputWriter(OutputWriter):
402
506
  del json_configurations["default"]
403
507
 
404
508
  serialized = json.dumps(
405
- final_output,
509
+ output_dict,
406
510
  indent=indent,
407
511
  default=custom_serial,
408
512
  **json_configurations,
@@ -416,10 +520,8 @@ class LocalOutputWriter(OutputWriter):
416
520
  file.write(serialized + "\n")
417
521
 
418
522
  def _write_archive(
419
- output: Output,
420
- options: dict[str, Any],
421
- statistics: dict[str, Any],
422
- assets: list[dict[str, Any]],
523
+ output: Union[Output, dict[str, Any], BaseModel],
524
+ output_dict: dict[str, Any],
423
525
  path: Optional[str] = None,
424
526
  ) -> None:
425
527
  dir_path = "output"
@@ -434,9 +536,9 @@ class LocalOutputWriter(OutputWriter):
434
536
 
435
537
  serialized = json.dumps(
436
538
  {
437
- "options": options,
438
- "statistics": statistics,
439
- "assets": assets,
539
+ "options": output_dict.get("options", {}),
540
+ "statistics": output_dict.get("statistics", {}),
541
+ "assets": output_dict.get("assets", []),
440
542
  },
441
543
  indent=2,
442
544
  )
@@ -525,88 +627,24 @@ class LocalOutputWriter(OutputWriter):
525
627
  f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
526
628
  )
527
629
 
528
- statistics = self._extract_statistics(output)
529
- options = self._extract_options(output)
530
- assets = self._extract_assets(output)
630
+ output_dict = {}
631
+ if isinstance(output, Output):
632
+ output_dict = output.to_dict()
633
+ elif isinstance(output, BaseModel):
634
+ output_dict = output.to_dict()
635
+ elif isinstance(output, dict):
636
+ output_dict = output
637
+ else:
638
+ raise TypeError(
639
+ f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
640
+ )
531
641
 
532
642
  self.FILE_WRITERS[output_format](
533
643
  output=output,
534
- options=options,
535
- statistics=statistics,
536
- assets=assets,
644
+ output_dict=output_dict,
537
645
  path=path,
538
646
  )
539
647
 
540
- @staticmethod
541
- def _extract_statistics(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
542
- """Extract JSON-serializable statistics."""
543
-
544
- statistics = {}
545
-
546
- if not isinstance(output, Output):
547
- return statistics
548
-
549
- stats = output.statistics
550
-
551
- if stats is None:
552
- return statistics
553
-
554
- if isinstance(stats, Statistics):
555
- statistics = stats.to_dict()
556
- elif isinstance(stats, dict):
557
- statistics = stats
558
- else:
559
- raise TypeError(f"unsupported statistics type: {type(stats)}, supported types are `Statistics` or `dict`")
560
-
561
- return statistics
562
-
563
- @staticmethod
564
- def _extract_options(output: Union[Output, dict[str, Any]]) -> dict[str, Any]:
565
- """Extract JSON-serializable options."""
566
-
567
- options = {}
568
-
569
- if not isinstance(output, Output):
570
- return options
571
-
572
- opt = output.options
573
-
574
- if opt is None:
575
- return options
576
-
577
- if isinstance(opt, Options):
578
- options = opt.to_dict()
579
- elif isinstance(opt, dict):
580
- options = opt
581
- else:
582
- raise TypeError(f"unsupported options type: {type(opt)}, supported types are `Options` or `dict`")
583
-
584
- return options
585
-
586
- @staticmethod
587
- def _extract_assets(output: Union[Output, dict[str, Any]]) -> list[dict[str, Any]]:
588
- """Extract JSON-serializable assets."""
589
-
590
- assets = []
591
-
592
- if not isinstance(output, Output):
593
- return assets
594
-
595
- assts = output.assets
596
-
597
- if assts is None:
598
- return assets
599
-
600
- for ix, asset in enumerate(assts):
601
- if isinstance(asset, Asset):
602
- assets.append(asset.to_dict())
603
- elif isinstance(asset, dict):
604
- assets.append(asset)
605
- else:
606
- raise TypeError(f"unsupported asset {ix}, type: {type(asset)}; supported types are `Asset` or `dict`")
607
-
608
- return assets
609
-
610
648
 
611
649
  def write_local(
612
650
  output: Union[Output, dict[str, Any]],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 0.26.3
3
+ Version: 0.27.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,30 +1,30 @@
1
- nextmv/__about__.py,sha256=gt8p7ptfCJk0tI0Kji01wC2fj-JYIvRQm_MEOwdRLY8,24
2
- nextmv/__entrypoint__.py,sha256=5K058PICm5sx4sNMqx56auMh9yWgdIESVLzfvyIXdjs,1158
1
+ nextmv/__about__.py,sha256=dPbfXnVUa1GzXrrlZ_hOg9cbB0hO_UUubb09KvnQt_g,24
2
+ nextmv/__entrypoint__.py,sha256=dA0iwwHtrq6Z9w9FxmxKLoBGLyhe7jWtUAU-Y3PEgHg,1094
3
3
  nextmv/__init__.py,sha256=QN5e_BFkIdBkR8DiGr9T06N6mXtowT84eRUJj3_Vfrg,1459
4
4
  nextmv/base_model.py,sha256=mdaBe-epNK1cFgP4TxbOtn3So4pCi1vMTOrIBkCBp7A,1050
5
5
  nextmv/deprecated.py,sha256=ctE39pmJEfoicR-w0EdT7FPtd0cWmP3GKth-FsNn_Ks,406
6
- nextmv/input.py,sha256=tppXHiJM_EzR9Z1yJPc37joBvgC0RmiAFo0Ab1X5nPA,15480
6
+ nextmv/input.py,sha256=H3Al-VhsiTuSdYL6ld7O7AP-MGHnr8uXq-WENIC76jU,15480
7
7
  nextmv/logger.py,sha256=Jo_AmYJ02WqXmsz9XTTOXZ9uenVHrPanMMqhBogxGCw,1075
8
- nextmv/model.py,sha256=rwBdgmKSEp1DPv43zF0azj3QnbHO6O6wKs0PIGvVS40,9861
9
- nextmv/options.py,sha256=IPqAIUjoKMWmoPx_e5zDcnxu7S2HVxw-SLjjUduVvZM,25302
10
- nextmv/output.py,sha256=nOdPJ7yBcEteUsSD6BXYwXcyV4ROKbATQXHh43Sl-6s,23221
8
+ nextmv/model.py,sha256=hE0BgeGfIsnR7H624VVX0Zys5utUtrH_xK1sERT0GTQ,9855
9
+ nextmv/options.py,sha256=5NjZbllCPNKzCBfyx-8XXnsz0wkph0WFfngJMAcYjtQ,26314
10
+ nextmv/output.py,sha256=qvFdEoNs-bhT4xjkcLS_6aHN1iU1dCILEhAf1Tafzak,26719
11
11
  nextmv/cloud/__init__.py,sha256=EEKJdSMMjW2yc9GwTpz-cmpgtu-Qs7p09k6NXIQPV0U,3726
12
12
  nextmv/cloud/acceptance_test.py,sha256=NtqGhj-UYibxGBbU2kfjr-lYcngojb_5VMvK2WZwibI,6620
13
13
  nextmv/cloud/account.py,sha256=mZUGzV-uMGBA5BC_FPtsiCMFuz5jxEZ3O1BbELZIm18,1841
14
- nextmv/cloud/application.py,sha256=0mTMSIze3uXMlWY7K7OYicMVox0IaC0PapLx41iVP2c,84098
14
+ nextmv/cloud/application.py,sha256=Go9DD1eWC7zMV0s76hld0VNS5QN7GWhu0Kk2Ci_wgFU,84287
15
15
  nextmv/cloud/batch_experiment.py,sha256=Ngm1XDvvAMaU9VYU5JFNxXFnt8xjE_34H5W54kO9V5c,3768
16
16
  nextmv/cloud/client.py,sha256=JUE3vD767_FFICl1vov5Mxmircci03TBL3KmT1BOZY4,9096
17
17
  nextmv/cloud/input_set.py,sha256=HTLA2acJridZbBCAVJNom8ldNo2Rfy40YQ8Coyz3bdo,1482
18
18
  nextmv/cloud/instance.py,sha256=UfyfZXfL1ugCGAB6zwZJIAi8qxI1JCUGsluwaGdjfN4,1223
19
- nextmv/cloud/manifest.py,sha256=lB3rwFmIKNAncaA00DelT4tDkMkIUCWEY4QJKJsx_-U,12116
19
+ nextmv/cloud/manifest.py,sha256=dwATuTRFJehXj2WdF3ZvEGH0gE96mK1zikkol38lMgk,18005
20
20
  nextmv/cloud/package.py,sha256=Y3RethLiXXW7Y6_QBJDJdd7mFNaYaSBMyasIeGLav8o,12287
21
- nextmv/cloud/run.py,sha256=BpaH5L-Vfb2NFs1YIJfi1jYEOqitiAa-5BkdTtLBue0,10906
21
+ nextmv/cloud/run.py,sha256=0UUTmDzwI47styBeLR0MIhn4yXmOHNFt3YfbGAZVRB8,10906
22
22
  nextmv/cloud/safe.py,sha256=IUQUOlx4ulIIyP2-Fx3-gNBm9nyWOgZgaOAlRrzqaZE,2515
23
- nextmv/cloud/scenario.py,sha256=9gbdnQuvmerPUBCcJ-5QbLCwgbsIfBwKXE8c5359S8E,8120
23
+ nextmv/cloud/scenario.py,sha256=Z2Kkaar45IJgkFiVCCpLqe6O3Y3AxqWVl2BGfUp3I_8,8118
24
24
  nextmv/cloud/secrets.py,sha256=kqlN4ceww_L4kVTrAU8BZykRzXINO3zhMT_BLYea6tk,1764
25
25
  nextmv/cloud/status.py,sha256=C-ax8cLw0jPeh7CPsJkCa0s4ImRyFI4NDJJxI0_1sr4,602
26
26
  nextmv/cloud/version.py,sha256=sjVRNRtohHA97j6IuyM33_DSSsXYkZPusYgpb6hlcrc,1244
27
- nextmv-0.26.3.dist-info/METADATA,sha256=u_djskL-Aa1rqrooo1H3qY8_8qlwSfEYoSMBKn7RwNc,14557
28
- nextmv-0.26.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
- nextmv-0.26.3.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
30
- nextmv-0.26.3.dist-info/RECORD,,
27
+ nextmv-0.27.0.dist-info/METADATA,sha256=e5ip9F8XzZ5tqjBOpGo41GIfZm2HeuZX1JxuktkpDSY,14557
28
+ nextmv-0.27.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
29
+ nextmv-0.27.0.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
30
+ nextmv-0.27.0.dist-info/RECORD,,