nextmv 0.26.3__py3-none-any.whl → 0.28.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/cloud/manifest.py CHANGED
@@ -1,4 +1,35 @@
1
- """Module with the logic for handling an app manifest."""
1
+ """Module with the logic for handling an app manifest.
2
+
3
+ This module provides classes and functions for managing Nextmv app manifests.
4
+ Manifest files (app.yaml) define how an application is built, run, and deployed
5
+ on the Nextmv Cloud platform.
6
+
7
+ Classes
8
+ -------
9
+ ManifestType
10
+ Enum for application types based on programming language.
11
+ ManifestRuntime
12
+ Enum for runtime environments where apps run on Nextmv Cloud.
13
+ ManifestBuild
14
+ Class for build-specific attributes in the manifest.
15
+ ManifestPythonModel
16
+ Class for model-specific instructions for Python apps.
17
+ ManifestPython
18
+ Class for Python-specific instructions in the manifest.
19
+ ManifestOption
20
+ Class representing an option for the decision model in the manifest.
21
+ ManifestOptions
22
+ Class containing a list of options for the decision model.
23
+ ManifestConfiguration
24
+ Class for configuration settings for the decision model.
25
+ Manifest
26
+ Main class representing an app manifest for Nextmv Cloud.
27
+
28
+ Constants
29
+ --------
30
+ FILE_NAME
31
+ Name of the app manifest file.
32
+ """
2
33
 
3
34
  import os
4
35
  from enum import Enum
@@ -11,13 +42,54 @@ from nextmv.base_model import BaseModel
11
42
  from nextmv.model import _REQUIREMENTS_FILE, ModelConfiguration
12
43
  from nextmv.options import Option, Options
13
44
 
14
- FILE_NAME = "app.yaml"
15
- """Name of the app manifest file."""
45
+ MANIFEST_FILE_NAME = "app.yaml"
46
+ """Name of the app manifest file.
47
+
48
+ This constant defines the standard filename for Nextmv app manifest files.
49
+
50
+ You can import the `FILE_NAME` constant directly from `cloud`:
51
+
52
+ ```python
53
+ from nextmv.cloud import FILE_NAME
54
+ ```
55
+
56
+ Notes
57
+ -----
58
+ All Nextmv applications must include an app.yaml file for proper deployment.
59
+ """
16
60
 
17
61
 
18
62
  class ManifestType(str, Enum):
19
- """Type of application in the manifest, based on the programming
20
- language."""
63
+ """
64
+ Type of application in the manifest, based on the programming language.
65
+
66
+ You can import the `ManifestType` class directly from `cloud`:
67
+
68
+ ```python
69
+ from nextmv.cloud import ManifestType
70
+ ```
71
+
72
+ This enum defines the supported programming languages for applications
73
+ that can be deployed on Nextmv Cloud.
74
+
75
+ Attributes
76
+ ----------
77
+ PYTHON : str
78
+ Python format, used for Python applications.
79
+ GO : str
80
+ Go format, used for Go applications.
81
+ JAVA : str
82
+ Java format, used for Java applications.
83
+
84
+ Examples
85
+ --------
86
+ >>> from nextmv.cloud import ManifestType
87
+ >>> manifest_type = ManifestType.PYTHON
88
+ >>> manifest_type
89
+ <ManifestType.PYTHON: 'python'>
90
+ >>> str(manifest_type)
91
+ 'python'
92
+ """
21
93
 
22
94
  PYTHON = "python"
23
95
  """Python format"""
@@ -28,7 +100,41 @@ class ManifestType(str, Enum):
28
100
 
29
101
 
30
102
  class ManifestRuntime(str, Enum):
31
- """Runtime (environment) where the app will be run on Nextmv Cloud."""
103
+ """
104
+ Runtime (environment) where the app will be run on Nextmv Cloud.
105
+
106
+ You can import the `ManifestRuntime` class directly from `cloud`:
107
+
108
+ ```python
109
+ from nextmv.cloud import ManifestRuntime
110
+ ```
111
+
112
+ This enum defines the supported runtime environments for applications
113
+ that can be deployed on Nextmv Cloud.
114
+
115
+ Attributes
116
+ ----------
117
+ DEFAULT : str
118
+ This runtime is used to run compiled applications such as Go binaries.
119
+ PYTHON : str
120
+ This runtime is used as the basis for all other Python runtimes and
121
+ Python applications.
122
+ JAVA : str
123
+ This runtime is used to run Java applications.
124
+ PYOMO : str
125
+ This runtime provisions Python packages to run Pyomo applications.
126
+ HEXALY : str
127
+ This runtime provisions Python packages to run Hexaly applications.
128
+
129
+ Examples
130
+ --------
131
+ >>> from nextmv.cloud import ManifestRuntime
132
+ >>> runtime = ManifestRuntime.PYTHON
133
+ >>> runtime
134
+ <ManifestRuntime.PYTHON: 'ghcr.io/nextmv-io/runtime/python:3.11'>
135
+ >>> str(runtime)
136
+ 'ghcr.io/nextmv-io/runtime/python:3.11'
137
+ """
32
138
 
33
139
  DEFAULT = "ghcr.io/nextmv-io/runtime/default:latest"
34
140
  """This runtime is used to run compiled applications such as Go binaries."""
@@ -49,19 +155,48 @@ class ManifestRuntime(str, Enum):
49
155
 
50
156
 
51
157
  class ManifestBuild(BaseModel):
52
- """Build-specific attributes."""
158
+ """
159
+ Build-specific attributes.
160
+
161
+ You can import the `ManifestBuild` class directly from `cloud`:
162
+
163
+ ```python
164
+ from nextmv.cloud import ManifestBuild
165
+ ```
166
+
167
+ Parameters
168
+ ----------
169
+ command : Optional[str], default=None
170
+ The command to run to build the app. This command will be executed
171
+ without a shell, i.e., directly. The command must exit with a status of
172
+ 0 to continue the push process of the app to Nextmv Cloud. This command
173
+ is executed prior to the pre-push command.
174
+ environment : Optional[dict[str, Any]], default=None
175
+ Environment variables to set when running the build command given as
176
+ key-value pairs.
177
+
178
+ Examples
179
+ --------
180
+ >>> from nextmv.cloud import ManifestBuild
181
+ >>> build_config = ManifestBuild(
182
+ ... command="make build",
183
+ ... environment={"DEBUG": "true"}
184
+ ... )
185
+ >>> build_config.command
186
+ 'make build'
187
+ """
53
188
 
54
189
  command: Optional[str] = None
55
- """
56
- The command to run to build the app. This command will be executed without
57
- a shell, i.e., directly. The command must exit with a status of 0 to
58
- continue the push process of the app to Nextmv Cloud. This command is
59
- executed prior to the pre-push command.
190
+ """The command to run to build the app.
191
+
192
+ This command will be executed without a shell, i.e., directly. The command
193
+ must exit with a status of 0 to continue the push process of the app to
194
+ Nextmv Cloud. This command is executed prior to the pre-push command.
60
195
  """
61
196
  environment: Optional[dict[str, Any]] = None
62
- """
63
- Environment variables to set when running the build command given as
64
- key-value pairs.
197
+ """Environment variables to set when running the build command.
198
+
199
+ Given as key-value pairs.
65
200
  """
66
201
 
67
202
  def environment_to_dict(self) -> dict[str, str]:
@@ -71,8 +206,18 @@ class ManifestBuild(BaseModel):
71
206
  Returns
72
207
  -------
73
208
  dict[str, str]
74
- The environment variables as a dictionary.
75
-
209
+ The environment variables as a dictionary of string key-value pairs.
210
+ Returns an empty dictionary if no environment variables are set.
211
+
212
+ Examples
213
+ --------
214
+ >>> from nextmv.cloud import ManifestBuild
215
+ >>> build_config = ManifestBuild(environment={"COUNT": 1, "NAME": "test"})
216
+ >>> build_config.environment_to_dict()
217
+ {'COUNT': '1', 'NAME': 'test'}
218
+ >>> build_config_empty = ManifestBuild()
219
+ >>> build_config_empty.environment_to_dict()
220
+ {}
76
221
  """
77
222
 
78
223
  if self.environment is None:
@@ -82,7 +227,35 @@ class ManifestBuild(BaseModel):
82
227
 
83
228
 
84
229
  class ManifestPythonModel(BaseModel):
85
- """Model-specific instructions for a Python app."""
230
+ """
231
+ Model-specific instructions for a Python app.
232
+
233
+ You can import the `ManifestPythonModel` class directly from `cloud`:
234
+
235
+ ```python
236
+ from nextmv.cloud import ManifestPythonModel
237
+ ```
238
+
239
+ Parameters
240
+ ----------
241
+ name : str
242
+ The name of the decision model.
243
+ options : Optional[list[dict[str, Any]]], default=None
244
+ Options for the decision model. This is a data representation of the
245
+ `nextmv.Options` class. It consists of a list of dicts. Each dict
246
+ represents the `nextmv.Option` class. It is used to be able to
247
+ reconstruct an Options object from data when loading a decision model.
248
+
249
+ Examples
250
+ --------
251
+ >>> from nextmv.cloud import ManifestPythonModel
252
+ >>> python_model_config = ManifestPythonModel(
253
+ ... name="routing_model",
254
+ ... options=[{"name": "max_vehicles", "type": "int", "default": 10}]
255
+ ... )
256
+ >>> python_model_config.name
257
+ 'routing_model'
258
+ """
86
259
 
87
260
  name: str
88
261
  """The name of the decision model."""
@@ -96,26 +269,97 @@ class ManifestPythonModel(BaseModel):
96
269
 
97
270
 
98
271
  class ManifestPython(BaseModel):
99
- """Python-specific instructions."""
272
+ """
273
+ Python-specific instructions.
274
+
275
+ You can import the `ManifestPython` class directly from `cloud`:
276
+
277
+ ```python
278
+ from nextmv.cloud import ManifestPython
279
+ ```
280
+
281
+ Parameters
282
+ ----------
283
+ pip_requirements : Optional[str], default=None
284
+ Path to a requirements.txt file containing (additional) Python
285
+ dependencies that will be bundled with the app.
286
+ Aliases: `pip-requirements`.
287
+ model : Optional[ManifestPythonModel], default=None
288
+ Information about an encoded decision model as handled via mlflow. This
289
+ information is used to load the decision model from the app bundle.
290
+
291
+ Examples
292
+ --------
293
+ >>> from nextmv.cloud import ManifestPython, ManifestPythonModel
294
+ >>> python_config = ManifestPython(
295
+ ... pip_requirements="requirements.txt",
296
+ ... model=ManifestPythonModel(name="my_model")
297
+ ... )
298
+ >>> python_config.pip_requirements
299
+ 'requirements.txt'
300
+ """
100
301
 
101
302
  pip_requirements: Optional[str] = Field(
102
303
  serialization_alias="pip-requirements",
103
304
  validation_alias=AliasChoices("pip-requirements", "pip_requirements"),
104
305
  default=None,
105
306
  )
106
- """
107
- Path to a requirements.txt file containing (additional) Python
108
- dependencies that will be bundled with the app.
307
+ """Path to a requirements.txt file.
308
+
309
+ Contains (additional) Python dependencies that will be bundled with the
310
+ app.
109
311
  """
110
312
  model: Optional[ManifestPythonModel] = None
111
- """
112
- Information about an encoded decision model as handlded via mlflow. This
113
- information is used to load the decision model from the app bundle.
313
+ """Information about an encoded decision model.
314
+
315
+ As handled via mlflow. This information is used to load the decision model
316
+ from the app bundle.
114
317
  """
115
318
 
116
319
 
117
320
  class ManifestOption(BaseModel):
118
- """An option for the decision model that is recorded in the manifest."""
321
+ """
322
+ An option for the decision model that is recorded in the manifest.
323
+
324
+ You can import the `ManifestOption` class directly from `cloud`:
325
+
326
+ ```python
327
+ from nextmv.cloud import ManifestOption
328
+ ```
329
+
330
+ Parameters
331
+ ----------
332
+ name : str
333
+ The name of the option.
334
+ option_type : str
335
+ The type of the option. This is a string representation of the
336
+ `nextmv.Option` class (e.g., "string", "int", "bool", "float").
337
+ Aliases: `type`.
338
+ default : Optional[Any], default=None
339
+ The default value of the option.
340
+ description : Optional[str], default=""
341
+ The description of the option.
342
+ required : bool, default=False
343
+ Whether the option is required or not.
344
+ additional_attributes : Optional[dict[str, Any]], default=None
345
+ Optional additional attributes for the option. The Nextmv Cloud may
346
+ perform validation on these attributes. For example, the maximum
347
+ length of a string or the maximum value of an integer. These
348
+ additional attributes will be shown in the help message of the
349
+ `Options`.
350
+
351
+ Examples
352
+ --------
353
+ >>> from nextmv.cloud import ManifestOption
354
+ >>> option = ManifestOption(
355
+ ... name="solve.duration",
356
+ ... option_type="string",
357
+ ... default="30s",
358
+ ... description="Maximum duration for the solver."
359
+ ... )
360
+ >>> option.name
361
+ 'solve.duration'
362
+ """
119
363
 
120
364
  name: str
121
365
  """The name of the option"""
@@ -123,7 +367,7 @@ class ManifestOption(BaseModel):
123
367
  serialization_alias="type",
124
368
  validation_alias=AliasChoices("type", "option_type"),
125
369
  )
126
- """The type of the option"""
370
+ """The type of the option (e.g., "string", "int", "bool", "float)."""
127
371
 
128
372
  default: Optional[Any] = None
129
373
  """The default value of the option"""
@@ -131,8 +375,13 @@ class ManifestOption(BaseModel):
131
375
  """The description of the option"""
132
376
  required: bool = False
133
377
  """Whether the option is required or not"""
134
- choices: Optional[list[Any]] = None
135
- """The choices for the option"""
378
+ additional_attributes: Optional[dict[str, Any]] = None
379
+ """Optional additional attributes for the option.
380
+
381
+ The Nextmv Cloud may perform validation on these attributes. For example,
382
+ the maximum length of a string or the maximum value of an integer. These
383
+ additional attributes will be shown in the help message of the `Options`.
384
+ """
136
385
 
137
386
  @classmethod
138
387
  def from_option(cls, option: Option) -> "ManifestOption":
@@ -141,21 +390,37 @@ class ManifestOption(BaseModel):
141
390
 
142
391
  Parameters
143
392
  ----------
144
- option: Option
393
+ option : nextmv.options.Option
145
394
  The option to convert.
146
395
 
147
396
  Returns
148
397
  -------
149
398
  ManifestOption
150
399
  The converted option.
400
+
401
+ Raises
402
+ ------
403
+ ValueError
404
+ If the `option.option_type` is unknown.
405
+
406
+ Examples
407
+ --------
408
+ >>> from nextmv.options import Option
409
+ >>> from nextmv.cloud import ManifestOption
410
+ >>> sdk_option = Option(name="max_stops", option_type=int, default=100)
411
+ >>> manifest_opt = ManifestOption.from_option(sdk_option)
412
+ >>> manifest_opt.name
413
+ 'max_stops'
414
+ >>> manifest_opt.option_type
415
+ 'int'
151
416
  """
152
417
  option_type = option.option_type
153
418
  if option_type is str:
154
419
  option_type = "string"
155
420
  elif option_type is bool:
156
- option_type = "boolean"
421
+ option_type = "bool"
157
422
  elif option_type is int:
158
- option_type = "integer"
423
+ option_type = "int"
159
424
  elif option_type is float:
160
425
  option_type = "float"
161
426
  else:
@@ -167,7 +432,7 @@ class ManifestOption(BaseModel):
167
432
  default=option.default,
168
433
  description=option.description,
169
434
  required=option.required,
170
- choices=option.choices,
435
+ additional_attributes=option.additional_attributes,
171
436
  )
172
437
 
173
438
  def to_option(self) -> Option:
@@ -176,16 +441,31 @@ class ManifestOption(BaseModel):
176
441
 
177
442
  Returns
178
443
  -------
179
- Option
444
+ nextmv.options.Option
180
445
  The converted option.
446
+
447
+ Raises
448
+ ------
449
+ ValueError
450
+ If the `self.option_type` is unknown.
451
+
452
+ Examples
453
+ --------
454
+ >>> from nextmv.cloud import ManifestOption
455
+ >>> manifest_opt = ManifestOption(name="max_stops", option_type="int", default=100)
456
+ >>> sdk_option = manifest_opt.to_option()
457
+ >>> sdk_option.name
458
+ 'max_stops'
459
+ >>> sdk_option.option_type
460
+ <class 'int'>
181
461
  """
182
462
 
183
463
  option_type_string = self.option_type
184
464
  if option_type_string == "string":
185
465
  option_type = str
186
- elif option_type_string == "boolean":
466
+ elif option_type_string == "bool":
187
467
  option_type = bool
188
- elif option_type_string == "integer":
468
+ elif option_type_string == "int":
189
469
  option_type = int
190
470
  elif option_type_string == "float":
191
471
  option_type = float
@@ -198,62 +478,190 @@ class ManifestOption(BaseModel):
198
478
  default=self.default,
199
479
  description=self.description,
200
480
  required=self.required,
201
- choices=self.choices,
481
+ additional_attributes=self.additional_attributes,
202
482
  )
203
483
 
204
484
 
485
+ class ManifestOptions(BaseModel):
486
+ """
487
+ Options for the decision model.
488
+
489
+ You can import the `ManifestOptions` class directly from `cloud`:
490
+
491
+ ```python
492
+ from nextmv.cloud import ManifestOptions
493
+ ```
494
+
495
+ Parameters
496
+ ----------
497
+ strict : Optional[bool], default=False
498
+ If strict is set to `True`, only the listed options will be allowed.
499
+ items : Optional[list[ManifestOption]], default=None
500
+ The actual list of options for the decision model. An option
501
+ is a parameter that configures the decision model.
502
+
503
+ Examples
504
+ --------
505
+ >>> from nextmv.cloud import ManifestOptions, ManifestOption
506
+ >>> options_config = ManifestOptions(
507
+ ... strict=True,
508
+ ... items=[
509
+ ... ManifestOption(name="timeout", option_type="int", default=60),
510
+ ... ManifestOption(name="vehicle_capacity", option_type="float", default=100.0)
511
+ ... ]
512
+ ... )
513
+ >>> options_config.strict
514
+ True
515
+ >>> len(options_config.items)
516
+ 2
517
+ """
518
+
519
+ strict: Optional[bool] = False
520
+ """If strict is set to `True`, only the listed options will be allowed."""
521
+ items: Optional[list[ManifestOption]] = None
522
+ """The actual list of options for the decision model.
523
+
524
+ An option is a parameter that configures the decision model.
525
+ """
526
+
527
+
528
+ class ManifestConfiguration(BaseModel):
529
+ """
530
+ Configuration for the decision model.
531
+
532
+ You can import the `ManifestConfiguration` class directly from `cloud`:
533
+
534
+ ```python
535
+ from nextmv.cloud import ManifestConfiguration
536
+ ```
537
+
538
+ Parameters
539
+ ----------
540
+ options : ManifestOptions
541
+ Options for the decision model.
542
+
543
+ Examples
544
+ --------
545
+ >>> from nextmv.cloud import ManifestConfiguration, ManifestOptions, ManifestOption
546
+ >>> model_config = ManifestConfiguration(
547
+ ... options=ManifestOptions(
548
+ ... items=[ManifestOption(name="debug_mode", option_type="bool", default=False)]
549
+ ... )
550
+ ... )
551
+ >>> model_config.options.items[0].name
552
+ 'debug_mode'
553
+ """
554
+
555
+ options: ManifestOptions
556
+ """Options for the decision model."""
557
+
558
+
205
559
  class Manifest(BaseModel):
206
560
  """
561
+ Represents an app manifest (`app.yaml`) for Nextmv Cloud.
562
+
563
+ You can import the `Manifest` class directly from `cloud`:
564
+
565
+ ```python
566
+ from nextmv.cloud import Manifest
567
+ ```
568
+
207
569
  An application that runs on the Nextmv Platform must contain a file named
208
570
  `app.yaml` which is known as the app manifest. This file is used to specify
209
571
  the execution environment for the app.
210
572
 
211
573
  This class represents the app manifest and allows you to load it from a
212
574
  file or create it programmatically.
575
+
576
+ Parameters
577
+ ----------
578
+ files : list[str]
579
+ The files to include (or exclude) in the app. This is mandatory.
580
+ runtime : ManifestRuntime, default=ManifestRuntime.PYTHON
581
+ The runtime to use for the app, it provides the environment
582
+ in which the app runs. This is mandatory.
583
+ type : ManifestType, default=ManifestType.PYTHON
584
+ Type of application, based on the programming language. This is
585
+ mandatory.
586
+ build : Optional[ManifestBuild], default=None
587
+ Build-specific attributes. The `build.command` to run to build
588
+ the app. This command will be executed without a shell, i.e., directly.
589
+ The command must exit with a status of 0 to continue the push process of
590
+ the app to Nextmv Cloud. This command is executed prior to the pre-push
591
+ command. The `build.environment` is used to set environment variables when
592
+ running the build command given as key-value pairs.
593
+ pre_push : Optional[str], default=None
594
+ A command to run before the app is pushed to the Nextmv Cloud.
595
+ This command can be used to compile a binary, run tests or similar tasks.
596
+ One difference with what is specified under build, is that the command
597
+ will be executed via the shell (i.e., `bash -c` on Linux & macOS or
598
+ `cmd /c` on Windows). The command must exit with a status of 0 to
599
+ continue the push process. This command is executed just before the app
600
+ gets bundled and pushed (after the build command).
601
+ Aliases: `pre-push`.
602
+ python : Optional[ManifestPython], default=None
603
+ Only for Python apps. Contains further Python-specific
604
+ attributes.
605
+ configuration : Optional[ManifestConfiguration], default=None
606
+ A list of options for the decision model. An option is a
607
+ parameter that configures the decision model.
608
+
609
+ Examples
610
+ --------
611
+ >>> from nextmv.cloud import Manifest, ManifestRuntime, ManifestType
612
+ >>> manifest = Manifest(
613
+ ... files=["main.py", "model_logic/"],
614
+ ... runtime=ManifestRuntime.PYTHON,
615
+ ... type=ManifestType.PYTHON,
616
+ ... )
617
+ >>> manifest.files
618
+ ['main.py', 'model_logic/']
213
619
  """
214
620
 
215
621
  files: list[str]
216
- """Mandatory. The files to include (or exclude) in the app."""
622
+ """The files to include (or exclude) in the app. This is mandatory."""
217
623
 
218
624
  runtime: ManifestRuntime = ManifestRuntime.PYTHON
219
- """
220
- Mandatory. The runtime to use for the app, it provides the environment in
221
- which the app runs.
625
+ """The runtime to use for the app.
626
+
627
+ It provides the environment in which the app runs. This is mandatory.
222
628
  """
223
629
  type: ManifestType = ManifestType.PYTHON
224
- """Mandatory. Type of application, based on the programming language."""
630
+ """Type of application, based on the programming language. This is mandatory."""
225
631
  build: Optional[ManifestBuild] = None
226
- """
227
- Optional. Build-specific attributes. The build.command to run to build the
228
- app. This command will be executed without a shell, i.e., directly. The
229
- command must exit with a status of 0 to continue the push process of the
230
- app to Nextmv Cloud. This command is executed prior to the pre-push
231
- command. The build.environment is used to set environment variables when
232
- running the build command given as key-value pairs.
632
+ """Build-specific attributes.
633
+
634
+ The `build.command` to run to build the app. This command will be executed
635
+ without a shell, i.e., directly. The command must exit with a status of 0
636
+ to continue the push process of the app to Nextmv Cloud. This command is
637
+ executed prior to the pre-push command. The `build.environment` is used to
638
+ set environment variables when running the build command given as key-value
639
+ pairs.
233
640
  """
234
641
  pre_push: Optional[str] = Field(
235
642
  serialization_alias="pre-push",
236
643
  validation_alias=AliasChoices("pre-push", "pre_push"),
237
644
  default=None,
238
645
  )
239
- """
240
- Optional. A command to run before the app is pushed to the Nextmv Cloud.
646
+ """A command to run before the app is pushed to the Nextmv Cloud.
647
+
241
648
  This command can be used to compile a binary, run tests or similar tasks.
242
649
  One difference with what is specified under build, is that the command will
243
- be executed via the shell (i.e., bash -c on Linux & macOS or cmd /c on
650
+ be executed via the shell (i.e., `bash -c` on Linux & macOS or `cmd /c` on
244
651
  Windows). The command must exit with a status of 0 to continue the push
245
652
  process. This command is executed just before the app gets bundled and
246
653
  pushed (after the build command).
247
654
  """
248
655
  python: Optional[ManifestPython] = None
656
+ """Python-specific attributes.
657
+
658
+ Only for Python apps. Contains further Python-specific attributes.
249
659
  """
250
- Optional. Only for Python apps. Contains further Python-specific
251
- attributes.
252
- """
253
- options: Optional[list[ManifestOption]] = None
254
- """
255
- Optional. A list of options for the decision model. An option is a
256
- parameter that configures the decision model.
660
+ configuration: Optional[ManifestConfiguration] = None
661
+ """Configuration for the decision model.
662
+
663
+ A list of options for the decision model. An option is a parameter that
664
+ configures the decision model.
257
665
  """
258
666
 
259
667
  @classmethod
@@ -261,19 +669,44 @@ class Manifest(BaseModel):
261
669
  """
262
670
  Load a manifest from a YAML file.
263
671
 
672
+ The YAML file is expected to be named `app.yaml` and located in the
673
+ specified directory.
674
+
264
675
  Parameters
265
676
  ----------
266
- dirpath: str
267
- Path to the directory containing the app.yaml file.
677
+ dirpath : str
678
+ Path to the directory containing the `app.yaml` file.
268
679
 
269
680
  Returns
270
681
  -------
271
682
  Manifest
272
683
  The loaded manifest.
273
684
 
685
+ Raises
686
+ ------
687
+ FileNotFoundError
688
+ If the `app.yaml` file is not found in `dirpath`.
689
+ yaml.YAMLError
690
+ If there is an error parsing the YAML file.
691
+
692
+ Examples
693
+ --------
694
+ Assuming an `app.yaml` file exists in `./my_app_dir`:
695
+
696
+ ```yaml
697
+ # ./my_app_dir/app.yaml
698
+ files:
699
+ - main.py
700
+ runtime: ghcr.io/nextmv-io/runtime/python:3.11
701
+ type: python
702
+ ```
703
+
704
+ >>> from nextmv.cloud import Manifest
705
+ >>> # manifest = Manifest.from_yaml("./my_app_dir") # This would be run
706
+ >>> # assert manifest.type == "python"
274
707
  """
275
708
 
276
- with open(os.path.join(dirpath, FILE_NAME)) as file:
709
+ with open(os.path.join(dirpath, MANIFEST_FILE_NAME)) as file:
277
710
  raw_manifest = yaml.safe_load(file)
278
711
 
279
712
  return cls.from_dict(raw_manifest)
@@ -282,51 +715,119 @@ class Manifest(BaseModel):
282
715
  """
283
716
  Write the manifest to a YAML file.
284
717
 
718
+ The manifest will be written to a file named `app.yaml` in the
719
+ specified directory.
720
+
285
721
  Parameters
286
722
  ----------
287
- dirpath: str
288
- Path to the directory where the app.yaml file will be written.
289
-
723
+ dirpath : str
724
+ Path to the directory where the `app.yaml` file will be written.
725
+
726
+ Raises
727
+ ------
728
+ IOError
729
+ If there is an error writing the file.
730
+ yaml.YAMLError
731
+ If there is an error serializing the manifest to YAML.
732
+
733
+ Examples
734
+ --------
735
+ >>> from nextmv.cloud import Manifest
736
+ >>> manifest = Manifest(files=["solver.py"], type="python")
737
+ >>> # manifest.to_yaml("./output_dir") # This would create ./output_dir/app.yaml
290
738
  """
291
739
 
292
- with open(os.path.join(dirpath, FILE_NAME), "w") as file:
740
+ with open(os.path.join(dirpath, MANIFEST_FILE_NAME), "w") as file:
293
741
  yaml.dump(self.to_dict(), file)
294
742
 
295
- def extract_options(self) -> Options:
743
+ def extract_options(self) -> Optional[Options]:
296
744
  """
297
745
  Convert the manifest options to a `nextmv.Options` object.
298
746
 
747
+ If the manifest does not have valid options defined in
748
+ `.configuration.options.items`, this method returns `None`.
749
+
299
750
  Returns
300
751
  -------
301
- Options
302
- The converted options.
752
+ Optional[nextmv.options.Options]
753
+ The options extracted from the manifest. If no options are found,
754
+ `None` is returned.
755
+
756
+ Examples
757
+ --------
758
+ >>> from nextmv.cloud import Manifest, ManifestConfiguration, ManifestOptions, ManifestOption
759
+ >>> manifest = Manifest(
760
+ ... files=["main.py"],
761
+ ... configuration=ManifestConfiguration(
762
+ ... options=ManifestOptions(
763
+ ... items=[
764
+ ... ManifestOption(name="duration", option_type="string", default="10s")
765
+ ... ]
766
+ ... )
767
+ ... )
768
+ ... )
769
+ >>> sdk_options = manifest.extract_options()
770
+ >>> sdk_options.get_option("duration").default
771
+ '10s'
772
+ >>> empty_manifest = Manifest(files=["main.py"])
773
+ >>> empty_manifest.extract_options() is None
774
+ True
303
775
  """
304
776
 
305
- if self.options is None:
306
- raise ValueError("No options found in the manifest")
777
+ if self.configuration is None or self.configuration.options is None or self.configuration.options.items is None:
778
+ return None
307
779
 
308
- options = [option.to_option() for option in self.options]
780
+ options = [option.to_option() for option in self.configuration.options.items]
309
781
 
310
782
  return Options(*options)
311
783
 
312
784
  @classmethod
313
- def from_model_configuration(cls, model_configuration: ModelConfiguration) -> "Manifest":
785
+ def from_model_configuration(
786
+ cls,
787
+ model_configuration: ModelConfiguration,
788
+ ) -> "Manifest":
314
789
  """
315
- Create a Python manifest from a Python model configuration. Note that
316
- the `ModelConfiguration` is almost always used in conjunction with the
317
- `nextmv.Model` class. If you are not implementing an instance of
318
- `nextmv.Model`, maybe you should use the `from_options` method instead,
319
- to initialize the manifest with the options of the model.
790
+ Create a Python manifest from a `nextmv.model.ModelConfiguration`.
791
+
792
+ Note that the `ModelConfiguration` is almost always used in
793
+ conjunction with the `nextmv.Model` class. If you are not
794
+ implementing an instance of `nextmv.Model`, consider using the
795
+ `from_options` method instead to initialize the manifest with the
796
+ options of the model.
797
+
798
+ The resulting manifest will have:
799
+
800
+ - `files` set to `["main.py", f"{model_configuration.name}/**"]`
801
+ - `runtime` set to `ManifestRuntime.PYTHON`
802
+ - `type` set to `ManifestType.PYTHON`
803
+ - `python.pip_requirements` set to the default requirements file name.
804
+ - `python.model.name` set to `model_configuration.name`.
805
+ - `python.model.options` populated from `model_configuration.options`.
806
+ - `configuration.options` populated from `model_configuration.options`.
320
807
 
321
808
  Parameters
322
809
  ----------
323
- model_configuration: ModelConfiguration
810
+ model_configuration : nextmv.model.ModelConfiguration
324
811
  The model configuration.
325
812
 
326
813
  Returns
327
814
  -------
328
815
  Manifest
329
816
  The Python manifest.
817
+
818
+ Examples
819
+ --------
820
+ >>> from nextmv.model import ModelConfiguration, Options, Option
821
+ >>> from nextmv.cloud import Manifest
822
+ >>> opts = Options(Option(name="vehicle_count", option_type=int, default=5))
823
+ >>> mc = ModelConfiguration(name="vehicle_router", options=opts)
824
+ >>> manifest = Manifest.from_model_configuration(mc)
825
+ >>> manifest.python.model.name
826
+ 'vehicle_router'
827
+ >>> manifest.files
828
+ ['main.py', 'vehicle_router/**']
829
+ >>> manifest.configuration.options.items[0].name
830
+ 'vehicle_count'
330
831
  """
331
832
 
332
833
  manifest_python_dict = {
@@ -348,28 +849,60 @@ class Manifest(BaseModel):
348
849
  )
349
850
 
350
851
  if model_configuration.options is not None:
351
- manifest.options = [ManifestOption.from_option(opt) for opt in model_configuration.options.options]
852
+ manifest.configuration = ManifestConfiguration(
853
+ options=ManifestOptions(
854
+ strict=False,
855
+ items=[ManifestOption.from_option(opt) for opt in model_configuration.options.options],
856
+ ),
857
+ )
352
858
 
353
859
  return manifest
354
860
 
355
861
  @classmethod
356
862
  def from_options(cls, options: Options) -> "Manifest":
357
863
  """
358
- Create a basic Python manifest from `Options`. If you have more files
359
- than just a `main.py`, make sure you modify the `.files` attribute of
360
- the resulting manifest. This method assumes that requirements are
361
- specified in a `requirements.txt` file. You may also specify a
362
- different requirements file once you instantiate the manifest.
864
+ Create a basic Python manifest from `nextmv.options.Options`.
865
+
866
+ If you have more files than just a `main.py`, make sure you modify
867
+ the `.files` attribute of the resulting manifest. This method assumes
868
+ that requirements are specified in a `requirements.txt` file. You may
869
+ also specify a different requirements file once you instantiate the
870
+ manifest.
871
+
872
+ The resulting manifest will have:
873
+ - `files` set to `["main.py"]`
874
+ - `runtime` set to `ManifestRuntime.PYTHON`
875
+ - `type` set to `ManifestType.PYTHON`
876
+ - `python.pip_requirements` set to `"requirements.txt"`.
877
+ - `configuration.options` populated from the provided `options`.
363
878
 
364
879
  Parameters
365
880
  ----------
366
- options: Options
881
+ options : nextmv.options.Options
367
882
  The options to include in the manifest.
368
883
 
369
884
  Returns
370
885
  -------
371
886
  Manifest
372
887
  The manifest with the given options.
888
+
889
+ Examples
890
+ --------
891
+ >>> from nextmv.options import Options, Option
892
+ >>> from nextmv.cloud import Manifest
893
+ >>> opts = Options(
894
+ ... Option(name="max_runtime", option_type=str, default="60s"),
895
+ ... Option(name="use_heuristic", option_type=bool, default=True)
896
+ ... )
897
+ >>> manifest = Manifest.from_options(opts)
898
+ >>> manifest.files
899
+ ['main.py']
900
+ >>> manifest.python.pip_requirements
901
+ 'requirements.txt'
902
+ >>> len(manifest.configuration.options.items)
903
+ 2
904
+ >>> manifest.configuration.options.items[0].name
905
+ 'max_runtime'
373
906
  """
374
907
 
375
908
  manifest = cls(
@@ -377,7 +910,12 @@ class Manifest(BaseModel):
377
910
  runtime=ManifestRuntime.PYTHON,
378
911
  type=ManifestType.PYTHON,
379
912
  python=ManifestPython(pip_requirements="requirements.txt"),
380
- options=[ManifestOption.from_option(opt) for opt in options.options],
913
+ configuration=ManifestConfiguration(
914
+ options=ManifestOptions(
915
+ strict=False,
916
+ items=[ManifestOption.from_option(opt) for opt in options.options],
917
+ ),
918
+ ),
381
919
  )
382
920
 
383
921
  return manifest