nextmv 0.26.3__tar.gz → 0.27.0__tar.gz

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 (62) hide show
  1. {nextmv-0.26.3 → nextmv-0.27.0}/PKG-INFO +1 -1
  2. nextmv-0.27.0/nextmv/__about__.py +1 -0
  3. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/__entrypoint__.py +3 -5
  4. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/application.py +21 -17
  5. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/manifest.py +187 -25
  6. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/run.py +6 -6
  7. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/scenario.py +1 -1
  8. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/input.py +2 -2
  9. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/model.py +3 -3
  10. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/options.py +20 -3
  11. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/output.py +166 -128
  12. nextmv-0.27.0/tests/cloud/app.yaml +45 -0
  13. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/test_application.py +3 -2
  14. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/test_manifest.py +19 -16
  15. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_output.py +228 -57
  16. nextmv-0.26.3/nextmv/__about__.py +0 -1
  17. nextmv-0.26.3/tests/cloud/app.yaml +0 -43
  18. {nextmv-0.26.3 → nextmv-0.27.0}/.gitignore +0 -0
  19. {nextmv-0.26.3 → nextmv-0.27.0}/LICENSE +0 -0
  20. {nextmv-0.26.3 → nextmv-0.27.0}/README.md +0 -0
  21. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/__init__.py +0 -0
  22. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/base_model.py +0 -0
  23. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/__init__.py +0 -0
  24. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/acceptance_test.py +0 -0
  25. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/account.py +0 -0
  26. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/batch_experiment.py +0 -0
  27. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/client.py +0 -0
  28. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/input_set.py +0 -0
  29. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/instance.py +0 -0
  30. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/package.py +0 -0
  31. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/safe.py +0 -0
  32. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/secrets.py +0 -0
  33. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/status.py +0 -0
  34. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/cloud/version.py +0 -0
  35. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/deprecated.py +0 -0
  36. {nextmv-0.26.3 → nextmv-0.27.0}/nextmv/logger.py +0 -0
  37. {nextmv-0.26.3 → nextmv-0.27.0}/pyproject.toml +0 -0
  38. {nextmv-0.26.3 → nextmv-0.27.0}/requirements.txt +0 -0
  39. {nextmv-0.26.3 → nextmv-0.27.0}/tests/__init__.py +0 -0
  40. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/__init__.py +0 -0
  41. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/test_client.py +0 -0
  42. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/test_package.py +0 -0
  43. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/test_run.py +0 -0
  44. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/test_safe_name_id.py +0 -0
  45. {nextmv-0.26.3 → nextmv-0.27.0}/tests/cloud/test_scenario.py +0 -0
  46. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/__init__.py +0 -0
  47. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options1.py +0 -0
  48. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options2.py +0 -0
  49. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options3.py +0 -0
  50. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options4.py +0 -0
  51. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options5.py +0 -0
  52. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options6.py +0 -0
  53. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options7.py +0 -0
  54. {nextmv-0.26.3 → nextmv-0.27.0}/tests/scripts/options_deprecated.py +0 -0
  55. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_base_model.py +0 -0
  56. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_entrypoint/__init__.py +0 -0
  57. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_entrypoint/test_entrypoint.py +0 -0
  58. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_input.py +0 -0
  59. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_logger.py +0 -0
  60. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_model.py +0 -0
  61. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_options.py +0 -0
  62. {nextmv-0.26.3 → nextmv-0.27.0}/tests/test_version.py +0 -0
@@ -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
@@ -0,0 +1 @@
1
+ __version__ = "v0.27.0"
@@ -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
 
@@ -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
@@ -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
  """
@@ -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
 
@@ -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
 
@@ -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