nextmv 0.28.5__py3-none-any.whl → 0.29.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
@@ -16,10 +16,14 @@ ManifestPythonModel
16
16
  Class for model-specific instructions for Python apps.
17
17
  ManifestPython
18
18
  Class for Python-specific instructions in the manifest.
19
+ ManifestOptionUI
20
+ Class for UI attributes of options in the manifest.
19
21
  ManifestOption
20
22
  Class representing an option for the decision model in the manifest.
21
23
  ManifestOptions
22
24
  Class containing a list of options for the decision model.
25
+ ManifestValidation
26
+ Class for validation rules for options in the manifest.
23
27
  ManifestConfiguration
24
28
  Class for configuration settings for the decision model.
25
29
  Manifest
@@ -40,7 +44,7 @@ from pydantic import AliasChoices, Field
40
44
 
41
45
  from nextmv.base_model import BaseModel
42
46
  from nextmv.model import _REQUIREMENTS_FILE, ModelConfiguration
43
- from nextmv.options import Option, Options
47
+ from nextmv.options import Option, Options, OptionsEnforcement
44
48
 
45
49
  MANIFEST_FILE_NAME = "app.yaml"
46
50
  """Name of the app manifest file.
@@ -318,6 +322,43 @@ class ManifestPython(BaseModel):
318
322
  from the app bundle.
319
323
  """
320
324
 
325
+ class ManifestOptionUI(BaseModel):
326
+ """
327
+ UI attributes for an option in the manifest.
328
+
329
+ You can import the `ManifestOptionUI` class directly from `cloud`:
330
+
331
+ ```python
332
+ from nextmv.cloud import ManifestOptionUI
333
+ ```
334
+
335
+ Parameters
336
+ ----------
337
+ control_type : str, optional
338
+ The type of control to use for the option in the Nextmv Cloud UI. This is
339
+ useful for defining how the option should be presented in the Nextmv
340
+ Cloud UI. Current control types include "input", "select", "slider", and
341
+ "toggle". This attribute is not used in the local `Options` class, but
342
+ it is used in the Nextmv Cloud UI to define the type of control to use for
343
+ the option. This will be validated by the Nextmv Cloud, and availability
344
+ is based on options_type.
345
+ hidden_from : list[str], optional
346
+ A list of team roles to which this option will be hidden in the UI. For
347
+ example, if you want to hide an option from the "operator" role, you can
348
+ pass `hidden_from=["operator"]`.
349
+
350
+ Examples
351
+ --------
352
+ >>> from nextmv.cloud import ManifestOptionUI
353
+ >>> ui_config = ManifestOptionUI(control_type="input")
354
+ >>> ui_config.control_type
355
+ 'input'
356
+ """
357
+
358
+ control_type: Optional[str] = None
359
+ """The type of control to use for the option in the Nextmv Cloud UI."""
360
+ hidden_from: Optional[list[str]] = None
361
+ """A list of team roles for which this option will be hidden in the UI."""
321
362
 
322
363
  class ManifestOption(BaseModel):
323
364
  """
@@ -349,6 +390,12 @@ class ManifestOption(BaseModel):
349
390
  length of a string or the maximum value of an integer. These
350
391
  additional attributes will be shown in the help message of the
351
392
  `Options`.
393
+ ui : Optional[ManifestOptionUI], default=None
394
+ Optional UI attributes for the option. This is a dictionary that can
395
+ contain additional information about how the option should be displayed
396
+ in the Nextmv Cloud UI. This is not used in the local `Options` class,
397
+ but it is used in the Nextmv Cloud UI to define how the option should be
398
+ presented.
352
399
 
353
400
  Examples
354
401
  --------
@@ -378,12 +425,10 @@ class ManifestOption(BaseModel):
378
425
  required: bool = False
379
426
  """Whether the option is required or not"""
380
427
  additional_attributes: Optional[dict[str, Any]] = None
381
- """Optional additional attributes for the option.
428
+ """Optional additional attributes for the option."""
429
+ ui: Optional[ManifestOptionUI] = None
430
+ """Optional UI attributes for the option."""
382
431
 
383
- The Nextmv Cloud may perform validation on these attributes. For example,
384
- the maximum length of a string or the maximum value of an integer. These
385
- additional attributes will be shown in the help message of the `Options`.
386
- """
387
432
 
388
433
  @classmethod
389
434
  def from_option(cls, option: Option) -> "ManifestOption":
@@ -435,6 +480,10 @@ class ManifestOption(BaseModel):
435
480
  description=option.description,
436
481
  required=option.required,
437
482
  additional_attributes=option.additional_attributes,
483
+ ui=ManifestOptionUI(
484
+ control_type=option.control_type,
485
+ hidden_from=option.hidden_from,
486
+ ) if option.control_type or option.hidden_from else None,
438
487
  )
439
488
 
440
489
  def to_option(self) -> Option:
@@ -481,8 +530,44 @@ class ManifestOption(BaseModel):
481
530
  description=self.description,
482
531
  required=self.required,
483
532
  additional_attributes=self.additional_attributes,
533
+ control_type=self.ui.control_type if self.ui else None,
534
+ hidden_from=self.ui.hidden_from if self.ui else None,
484
535
  )
485
536
 
537
+ class ManifestValidation(BaseModel):
538
+ """
539
+ Validation rules for options in the manifest.
540
+
541
+ You can import the `ManifestValidation` class directly from `cloud`:
542
+
543
+ ```python
544
+ from nextmv.cloud import ManifestValidation
545
+ ```
546
+
547
+ Parameters
548
+ ----------
549
+ enforce : str, default="none"
550
+ The enforcement level for the validation rules. This can be set to
551
+ "none" or "all". If set to "none", no validation will be performed
552
+ on the options prior to creating a run. If set to "all", all validation
553
+ rules will be enforced on the options, and runs will not be created
554
+ if any of the rules of the options are violated.
555
+
556
+ Examples
557
+ --------
558
+ >>> from nextmv.cloud import ManifestValidation
559
+ >>> validation = ManifestValidation(enforce="all")
560
+ >>> validation.enforce
561
+ 'all'
562
+ """
563
+
564
+ enforce: str = "none"
565
+ """The enforcement level for the validation rules.
566
+ This can be set to "none" or "all". If set to "none", no validation will
567
+ be performed on the options prior to creating a run. If set to "all", all
568
+ validation rules will be enforced on the options, and runs will not be
569
+ created if any of the rules of the options are violated.
570
+ """
486
571
 
487
572
  class ManifestOptions(BaseModel):
488
573
  """
@@ -501,12 +586,16 @@ class ManifestOptions(BaseModel):
501
586
  items : Optional[list[ManifestOption]], default=None
502
587
  The actual list of options for the decision model. An option
503
588
  is a parameter that configures the decision model.
589
+ validation: Optional[ManifestValidation], default=None
590
+ Optional validation rules for all options.
591
+
504
592
 
505
593
  Examples
506
594
  --------
507
595
  >>> from nextmv.cloud import ManifestOptions, ManifestOption
508
596
  >>> options_config = ManifestOptions(
509
597
  ... strict=True,
598
+ ... validation=ManifestValidation(enforce="all"),
510
599
  ... items=[
511
600
  ... ManifestOption(name="timeout", option_type="int", default=60),
512
601
  ... ManifestOption(name="vehicle_capacity", option_type="float", default=100.0)
@@ -520,12 +609,48 @@ class ManifestOptions(BaseModel):
520
609
 
521
610
  strict: Optional[bool] = False
522
611
  """If strict is set to `True`, only the listed options will be allowed."""
612
+ validation: Optional[ManifestValidation] = None
613
+ """Optional validation rules for all options."""
523
614
  items: Optional[list[ManifestOption]] = None
524
615
  """The actual list of options for the decision model.
525
616
 
526
617
  An option is a parameter that configures the decision model.
527
618
  """
528
619
 
620
+ @classmethod
621
+ def from_options(cls, options: Options, validation: OptionsEnforcement = None) -> "ManifestOptions":
622
+ """
623
+ Create a `ManifestOptions` from a `nextmv.Options`.
624
+
625
+ Parameters
626
+ ----------
627
+ options : nextmv.options.Options
628
+ The options to convert.
629
+ validation : Optional[OptionsEnforcement], default=None
630
+ Optional validation rules for the options. If provided, it will be
631
+ used to set the `validation` attribute of the `ManifestOptions`.
632
+
633
+ Returns
634
+ -------
635
+ ManifestOptions
636
+ The converted options.
637
+
638
+ Examples
639
+ --------
640
+ >>> from nextmv.options import Options, Option
641
+ >>> from nextmv.cloud import ManifestOptions
642
+ >>> sdk_options = Options(Option("max_vehicles", int, 5))
643
+ >>> manifest_options = ManifestOptions.from_options(sdk_options)
644
+ >>> manifest_options.items[0].name
645
+ 'max_vehicles'
646
+ """
647
+
648
+ items = [ManifestOption.from_option(option) for option in options.options]
649
+ return cls(
650
+ strict=validation.strict if validation else False,
651
+ validation=ManifestValidation(enforce="all" if validation and validation.validation_enforce else "none"),
652
+ items=items
653
+ )
529
654
 
530
655
  class ManifestConfiguration(BaseModel):
531
656
  """
@@ -852,16 +977,16 @@ class Manifest(BaseModel):
852
977
 
853
978
  if model_configuration.options is not None:
854
979
  manifest.configuration = ManifestConfiguration(
855
- options=ManifestOptions(
856
- strict=False,
857
- items=[ManifestOption.from_option(opt) for opt in model_configuration.options.options],
980
+ options=ManifestOptions.from_options(
981
+ options=model_configuration.options,
982
+ validation=model_configuration.options_enforcement,
858
983
  ),
859
984
  )
860
985
 
861
986
  return manifest
862
987
 
863
988
  @classmethod
864
- def from_options(cls, options: Options) -> "Manifest":
989
+ def from_options(cls, options: Options, validation: OptionsEnforcement = None) -> "Manifest":
865
990
  """
866
991
  Create a basic Python manifest from `nextmv.options.Options`.
867
992
 
@@ -882,6 +1007,9 @@ class Manifest(BaseModel):
882
1007
  ----------
883
1008
  options : nextmv.options.Options
884
1009
  The options to include in the manifest.
1010
+ validation : nextmv.options.OptionsEnforcement default=None
1011
+ The validation rules for the options. This is used to set the
1012
+ `validation` attribute of the `ManifestOptions`.
885
1013
 
886
1014
  Returns
887
1015
  -------
@@ -913,11 +1041,11 @@ class Manifest(BaseModel):
913
1041
  type=ManifestType.PYTHON,
914
1042
  python=ManifestPython(pip_requirements="requirements.txt"),
915
1043
  configuration=ManifestConfiguration(
916
- options=ManifestOptions(
917
- strict=False,
918
- items=[ManifestOption.from_option(opt) for opt in options.options],
1044
+ options= ManifestOptions.from_options(
1045
+ options=options,
1046
+ validation=validation
919
1047
  ),
920
- ),
1048
+ )
921
1049
  )
922
1050
 
923
1051
  return manifest
nextmv/cloud/package.py CHANGED
@@ -28,7 +28,7 @@ def _package(
28
28
  model_configuration: Optional[ModelConfiguration] = None,
29
29
  verbose: bool = False,
30
30
  ) -> tuple[str, str]:
31
- """Package the app into a tarball.."""
31
+ """Package the app into a tarball."""
32
32
 
33
33
  with tempfile.TemporaryDirectory(prefix="nextmv-temp-") as temp_dir:
34
34
  if manifest.type == ManifestType.PYTHON:
nextmv/cloud/run.py CHANGED
@@ -470,6 +470,40 @@ class RunConfiguration(BaseModel):
470
470
  queuing: Optional[RunQueuing] = None
471
471
  """Queuing configuration for the run."""
472
472
 
473
+ def resolve(
474
+ self,
475
+ input: Union[Input, dict[str, Any], BaseModel, str],
476
+ dir_path: Optional[str] = None,
477
+ ) -> None:
478
+ """
479
+ Resolves the run configuration by modifying or setting the `format`,
480
+ based on the type of input that is provided.
481
+
482
+ Parameters
483
+ ----------
484
+ input : Input or dict[str, Any] or BaseModel or str, optional
485
+ The input to use for resolving the run configuration.
486
+ dir_path : str, optional
487
+ The directory path where inputs can be loaded from.
488
+ """
489
+
490
+ # If the value is set by the user, do not change it.
491
+ if self.format is not None:
492
+ return
493
+
494
+ self.format = Format(format_input=FormatInput(input_type=InputFormat.JSON))
495
+
496
+ if isinstance(input, dict):
497
+ self.format.format_input.input_type = InputFormat.JSON
498
+ elif isinstance(input, str):
499
+ self.format.format_input.input_type = InputFormat.TEXT
500
+ elif dir_path is not None and dir_path != "":
501
+ # Kinda hard to detect if we should be working with CSV_ARCHIVE or
502
+ # MULTI_FILE, so we default to MULTI_FILE.
503
+ self.format.format_input.input_type = InputFormat.MULTI_FILE
504
+ elif isinstance(input, Input):
505
+ self.format.format_input.input_type = input.input_format
506
+
473
507
 
474
508
  class ExternalRunResult(BaseModel):
475
509
  """