nextmv 0.28.1.dev0__tar.gz → 0.28.2__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 (61) hide show
  1. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/PKG-INFO +1 -1
  2. nextmv-0.28.2/nextmv/__about__.py +1 -0
  3. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/manifest.py +5 -3
  4. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/package.py +16 -2
  5. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/options.py +26 -9
  6. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_options.py +63 -0
  7. nextmv-0.28.1.dev0/nextmv/__about__.py +0 -1
  8. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/.gitignore +0 -0
  9. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/LICENSE +0 -0
  10. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/README.md +0 -0
  11. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/__entrypoint__.py +0 -0
  12. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/__init__.py +0 -0
  13. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/base_model.py +0 -0
  14. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/__init__.py +0 -0
  15. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/acceptance_test.py +0 -0
  16. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/account.py +0 -0
  17. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/application.py +0 -0
  18. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/batch_experiment.py +0 -0
  19. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/client.py +0 -0
  20. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/input_set.py +0 -0
  21. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/instance.py +0 -0
  22. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/run.py +0 -0
  23. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/safe.py +0 -0
  24. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/scenario.py +0 -0
  25. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/secrets.py +0 -0
  26. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/status.py +0 -0
  27. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/version.py +0 -0
  28. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/deprecated.py +0 -0
  29. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/input.py +0 -0
  30. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/logger.py +0 -0
  31. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/model.py +0 -0
  32. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/output.py +0 -0
  33. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/pyproject.toml +0 -0
  34. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/requirements.txt +0 -0
  35. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/__init__.py +0 -0
  36. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/__init__.py +0 -0
  37. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/app.yaml +0 -0
  38. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_application.py +0 -0
  39. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_client.py +0 -0
  40. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_manifest.py +0 -0
  41. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_package.py +0 -0
  42. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_run.py +0 -0
  43. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_safe_name_id.py +0 -0
  44. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_scenario.py +0 -0
  45. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/__init__.py +0 -0
  46. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options1.py +0 -0
  47. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options2.py +0 -0
  48. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options3.py +0 -0
  49. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options4.py +0 -0
  50. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options5.py +0 -0
  51. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options6.py +0 -0
  52. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options7.py +0 -0
  53. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options_deprecated.py +0 -0
  54. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_base_model.py +0 -0
  55. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_entrypoint/__init__.py +0 -0
  56. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_entrypoint/test_entrypoint.py +0 -0
  57. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_input.py +0 -0
  58. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_logger.py +0 -0
  59. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_model.py +0 -0
  60. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_output.py +0 -0
  61. {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 0.28.1.dev0
3
+ Version: 0.28.2
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.28.2"
@@ -33,7 +33,7 @@ FILE_NAME
33
33
 
34
34
  import os
35
35
  from enum import Enum
36
- from typing import Any, Optional
36
+ from typing import Any, Optional, Union
37
37
 
38
38
  import yaml
39
39
  from pydantic import AliasChoices, Field
@@ -282,7 +282,9 @@ class ManifestPython(BaseModel):
282
282
  ----------
283
283
  pip_requirements : Optional[str], default=None
284
284
  Path to a requirements.txt file containing (additional) Python
285
- dependencies that will be bundled with the app.
285
+ dependencies that will be bundled with the app. Alternatively, you can provide a
286
+ list of strings, each representing a package to install, e.g.,
287
+ `["nextmv==0.28.2", "ortools==9.12.4544"]`.
286
288
  Aliases: `pip-requirements`.
287
289
  model : Optional[ManifestPythonModel], default=None
288
290
  Information about an encoded decision model as handled via mlflow. This
@@ -299,7 +301,7 @@ class ManifestPython(BaseModel):
299
301
  'requirements.txt'
300
302
  """
301
303
 
302
- pip_requirements: Optional[str] = Field(
304
+ pip_requirements: Optional[Union[str, list[str]]] = Field(
303
305
  serialization_alias="pip-requirements",
304
306
  validation_alias=AliasChoices("pip-requirements", "pip_requirements"),
305
307
  default=None,
@@ -233,11 +233,25 @@ def __install_dependencies(
233
233
  return
234
234
 
235
235
  pip_requirements = manifest.python.pip_requirements
236
+
236
237
  if pip_requirements is None or pip_requirements == "":
238
+ # If no pip requirements are specified, we do not install any dependencies.
237
239
  return
238
240
 
239
- if not os.path.isfile(os.path.join(app_dir, pip_requirements)):
240
- raise FileNotFoundError(f"pip requirements file '{pip_requirements}' not found in '{app_dir}'")
241
+ if isinstance(pip_requirements, list):
242
+ # If pip_requirements is a list, we write it to a temporary file so that we can
243
+ # pass it to pip.
244
+ pip_requirements_file = os.path.join(temp_dir, "requirements.txt")
245
+ with open(pip_requirements_file, "w") as f:
246
+ for requirement in pip_requirements:
247
+ f.write(requirement + "\n")
248
+ pip_requirements = pip_requirements_file
249
+ elif isinstance(pip_requirements, str):
250
+ # If pip_requirements is a string, we expect it to be a file path to a
251
+ # requirements file.
252
+ pip_requirements = pip_requirements.strip()
253
+ if not os.path.isfile(os.path.join(app_dir, pip_requirements)):
254
+ raise FileNotFoundError(f"pip requirements file '{pip_requirements}' not found in '{app_dir}'")
241
255
 
242
256
  py_cmd = __get_python_command()
243
257
  dep_dir = os.path.join(".nextmv", "python", "deps")
@@ -575,7 +575,7 @@ class Options:
575
575
 
576
576
  self._parse()
577
577
 
578
- def merge(self, new: "Options") -> "Options":
578
+ def merge(self, *new: "Options", skip_parse: bool = False) -> "Options":
579
579
  """
580
580
  Merges the current options with the new options.
581
581
 
@@ -587,7 +587,11 @@ class Options:
587
587
  Parameters
588
588
  ----------
589
589
  new : Options
590
- The new options to merge with the current options.
590
+ The new options to merge with the current options. At least one new option set
591
+ is required to merge. Multiple `Options` instances can be passed.
592
+ skip_parse : bool, optional
593
+ If True, the merged options will not be parsed after merging. This is useful
594
+ if you want to merge further options after this merge. The default is False.
591
595
 
592
596
  Returns
593
597
  -------
@@ -605,11 +609,14 @@ class Options:
605
609
  --------
606
610
  >>> opt1 = Options(Option("duration", str, "30s"))
607
611
  >>> opt2 = Options(Option("threads", int, 4))
608
- >>> merged = opt1.merge(opt2)
612
+ >>> opt3 = Options(Option("verbose", bool, False))
613
+ >>> merged = opt1.merge(opt2, opt3)
609
614
  >>> merged.duration
610
615
  '30s'
611
616
  >>> merged.threads
612
617
  4
618
+ >>> merged.verbose
619
+ False
613
620
  """
614
621
 
615
622
  if self.PARSED:
@@ -617,14 +624,24 @@ class Options:
617
624
  "base options have already been parsed, cannot merge. See `Options.parse()` for more information."
618
625
  )
619
626
 
620
- if new.PARSED:
621
- raise RuntimeError(
622
- "new options have already been parsed, cannot merge. See `Options.parse()` for more information."
623
- )
627
+ if not new:
628
+ raise ValueError("at least one new Options instance is required to merge")
624
629
 
625
- self.options += new.options
630
+ for i, opt in enumerate(new):
631
+ if not isinstance(opt, Options):
632
+ raise TypeError(f"expected an <Options> object, but got {type(opt)} in index {i}")
633
+ if opt.PARSED:
634
+ raise RuntimeError(
635
+ f"new options at index {i} have already been parsed, cannot merge. "
636
+ + "See `Options.parse()` for more information."
637
+ )
626
638
 
627
- self._parse()
639
+ # Add the new options to the current options.
640
+ for n in new:
641
+ self.options += n.options
642
+
643
+ if not skip_parse:
644
+ self.parse()
628
645
 
629
646
  return self
630
647
 
@@ -588,6 +588,69 @@ class TestParameter(unittest.TestCase):
588
588
  self.assertEqual(opt.foo2, 3)
589
589
  self.assertEqual(opt.bar2, 4)
590
590
 
591
+ def test_merge_lazy(self):
592
+ opt1 = nextmv.Options(
593
+ nextmv.Parameter("foo1", int, default=1),
594
+ nextmv.Parameter("bar1", int, default=2),
595
+ )
596
+ self.assertFalse(opt1.PARSED)
597
+
598
+ opt2 = nextmv.Options(
599
+ nextmv.Parameter("foo2", int, default=3),
600
+ nextmv.Parameter("bar2", int, default=4),
601
+ )
602
+ self.assertFalse(opt2.PARSED)
603
+
604
+ opt3 = nextmv.Options(
605
+ nextmv.Parameter("foo3", int, default=5),
606
+ nextmv.Parameter("bar3", int, default=6),
607
+ )
608
+ self.assertFalse(opt3.PARSED)
609
+
610
+ opt = opt1.merge(opt2, skip_parse=True)
611
+ self.assertFalse(opt.PARSED)
612
+
613
+ opt = opt.merge(opt3, skip_parse=True)
614
+ self.assertFalse(opt.PARSED)
615
+
616
+ opt.parse()
617
+ self.assertTrue(opt.PARSED)
618
+
619
+ self.assertEqual(opt.foo1, 1)
620
+ self.assertEqual(opt.bar1, 2)
621
+ self.assertEqual(opt.foo2, 3)
622
+ self.assertEqual(opt.bar2, 4)
623
+ self.assertEqual(opt.foo3, 5)
624
+ self.assertEqual(opt.bar3, 6)
625
+
626
+ def test_merge_multi(self):
627
+ opt1 = nextmv.Options(
628
+ nextmv.Parameter("foo1", int, default=1),
629
+ nextmv.Parameter("bar1", int, default=2),
630
+ )
631
+ self.assertFalse(opt1.PARSED)
632
+
633
+ opt2 = nextmv.Options(
634
+ nextmv.Parameter("foo2", int, default=3),
635
+ nextmv.Parameter("bar2", int, default=4),
636
+ )
637
+ self.assertFalse(opt2.PARSED)
638
+
639
+ opt3 = nextmv.Options(
640
+ nextmv.Parameter("foo3", int, default=5),
641
+ nextmv.Parameter("bar3", int, default=6),
642
+ )
643
+ self.assertFalse(opt3.PARSED)
644
+
645
+ opt1.merge(opt2, opt3)
646
+ self.assertTrue(opt1.PARSED)
647
+ self.assertEqual(opt1.foo1, 1)
648
+ self.assertEqual(opt1.bar1, 2)
649
+ self.assertEqual(opt1.foo2, 3)
650
+ self.assertEqual(opt1.bar2, 4)
651
+ self.assertEqual(opt1.foo3, 5)
652
+ self.assertEqual(opt1.bar3, 6)
653
+
591
654
  def test_cant_merge(self):
592
655
  opt1 = nextmv.Options(
593
656
  nextmv.Parameter("foo1", int, default=1),
@@ -1 +0,0 @@
1
- __version__ = "v0.28.1.dev0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes