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.
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/PKG-INFO +1 -1
- nextmv-0.28.2/nextmv/__about__.py +1 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/manifest.py +5 -3
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/package.py +16 -2
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/options.py +26 -9
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_options.py +63 -0
- nextmv-0.28.1.dev0/nextmv/__about__.py +0 -1
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/.gitignore +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/LICENSE +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/README.md +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/__entrypoint__.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/__init__.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/base_model.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/__init__.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/acceptance_test.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/account.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/application.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/batch_experiment.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/client.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/input_set.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/instance.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/run.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/safe.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/scenario.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/secrets.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/status.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/cloud/version.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/deprecated.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/input.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/logger.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/model.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/nextmv/output.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/pyproject.toml +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/requirements.txt +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/__init__.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/__init__.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/app.yaml +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_application.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_client.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_manifest.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_package.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_run.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_safe_name_id.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/cloud/test_scenario.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/__init__.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options1.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options2.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options3.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options4.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options5.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options6.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options7.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/scripts/options_deprecated.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_base_model.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_entrypoint/__init__.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_entrypoint/test_entrypoint.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_input.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_logger.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_model.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_output.py +0 -0
- {nextmv-0.28.1.dev0 → nextmv-0.28.2}/tests/test_version.py +0 -0
|
@@ -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
|
|
240
|
-
|
|
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
|
-
>>>
|
|
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
|
|
621
|
-
raise
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|