nextmv 0.25.0__tar.gz → 0.26.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.
- {nextmv-0.25.0 → nextmv-0.26.0}/PKG-INFO +1 -1
- nextmv-0.26.0/nextmv/__about__.py +1 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/application.py +84 -1
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/batch_experiment.py +3 -3
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/output.py +11 -5
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_output.py +28 -1
- nextmv-0.25.0/nextmv/__about__.py +0 -1
- {nextmv-0.25.0 → nextmv-0.26.0}/.gitignore +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/LICENSE +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/README.md +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/__entrypoint__.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/__init__.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/base_model.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/__init__.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/acceptance_test.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/account.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/client.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/input_set.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/instance.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/manifest.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/package.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/run.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/safe.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/scenario.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/secrets.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/status.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/cloud/version.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/deprecated.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/input.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/logger.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/model.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/nextmv/options.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/pyproject.toml +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/requirements.txt +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/__init__.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/__init__.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/app.yaml +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/test_application.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/test_client.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/test_manifest.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/test_package.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/test_safe_name_id.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/cloud/test_scenario.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/__init__.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options1.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options2.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options3.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options4.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options5.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options6.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options7.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/scripts/options_deprecated.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_base_model.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_entrypoint/__init__.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_entrypoint/test_entrypoint.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_input.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_logger.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_model.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_options.py +0 -0
- {nextmv-0.25.0 → nextmv-0.26.0}/tests/test_version.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v0.26.0"
|
|
@@ -14,7 +14,12 @@ import requests
|
|
|
14
14
|
from nextmv.base_model import BaseModel
|
|
15
15
|
from nextmv.cloud import package
|
|
16
16
|
from nextmv.cloud.acceptance_test import AcceptanceTest, ExperimentStatus, Metric
|
|
17
|
-
from nextmv.cloud.batch_experiment import
|
|
17
|
+
from nextmv.cloud.batch_experiment import (
|
|
18
|
+
BatchExperiment,
|
|
19
|
+
BatchExperimentInformation,
|
|
20
|
+
BatchExperimentMetadata,
|
|
21
|
+
BatchExperimentRun,
|
|
22
|
+
)
|
|
18
23
|
from nextmv.cloud.client import Client, get_size
|
|
19
24
|
from nextmv.cloud.input_set import InputSet, ManagedInput
|
|
20
25
|
from nextmv.cloud.instance import Instance, InstanceConfiguration
|
|
@@ -1955,6 +1960,47 @@ class Application:
|
|
|
1955
1960
|
|
|
1956
1961
|
return Instance.from_dict(response.json())
|
|
1957
1962
|
|
|
1963
|
+
def update_batch_experiment(
|
|
1964
|
+
self,
|
|
1965
|
+
batch_experiment_id: str,
|
|
1966
|
+
name: str,
|
|
1967
|
+
description: str,
|
|
1968
|
+
) -> BatchExperimentInformation:
|
|
1969
|
+
"""
|
|
1970
|
+
Update a batch experiment.
|
|
1971
|
+
|
|
1972
|
+
Parameters
|
|
1973
|
+
----------
|
|
1974
|
+
batch_experiment_id : str
|
|
1975
|
+
ID of the batch experiment to update.
|
|
1976
|
+
name : str
|
|
1977
|
+
Name of the batch experiment.
|
|
1978
|
+
description : str
|
|
1979
|
+
Description of the batch experiment.
|
|
1980
|
+
|
|
1981
|
+
Returns
|
|
1982
|
+
-------
|
|
1983
|
+
BatchExperimentInformation
|
|
1984
|
+
The information with the updated batch experiment.
|
|
1985
|
+
|
|
1986
|
+
Raises
|
|
1987
|
+
------
|
|
1988
|
+
requests.HTTPError
|
|
1989
|
+
If the response status code is not 2xx.
|
|
1990
|
+
"""
|
|
1991
|
+
|
|
1992
|
+
payload = {
|
|
1993
|
+
"name": name,
|
|
1994
|
+
"description": description,
|
|
1995
|
+
}
|
|
1996
|
+
response = self.client.request(
|
|
1997
|
+
method="PATCH",
|
|
1998
|
+
endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
|
|
1999
|
+
payload=payload,
|
|
2000
|
+
)
|
|
2001
|
+
|
|
2002
|
+
return BatchExperimentInformation.from_dict(response.json())
|
|
2003
|
+
|
|
1958
2004
|
def update_managed_input(
|
|
1959
2005
|
self,
|
|
1960
2006
|
managed_input_id: str,
|
|
@@ -1994,6 +2040,43 @@ class Application:
|
|
|
1994
2040
|
payload=payload,
|
|
1995
2041
|
)
|
|
1996
2042
|
|
|
2043
|
+
def update_scenario_test(
|
|
2044
|
+
self,
|
|
2045
|
+
scenario_test_id: str,
|
|
2046
|
+
name: str,
|
|
2047
|
+
description: str,
|
|
2048
|
+
) -> BatchExperimentInformation:
|
|
2049
|
+
"""
|
|
2050
|
+
Update a scenario test. Scenario tests use the batch experiments API,
|
|
2051
|
+
so this method calls the `update_batch_experiment` method, and thus the
|
|
2052
|
+
return type is the same.
|
|
2053
|
+
|
|
2054
|
+
Parameters
|
|
2055
|
+
----------
|
|
2056
|
+
scenario_test_id : str
|
|
2057
|
+
ID of the scenario test to update.
|
|
2058
|
+
name : str
|
|
2059
|
+
Name of the scenario test.
|
|
2060
|
+
description : str
|
|
2061
|
+
Description of the scenario test.
|
|
2062
|
+
|
|
2063
|
+
Returns
|
|
2064
|
+
-------
|
|
2065
|
+
BatchExperimentInformation
|
|
2066
|
+
The information with the updated scenario test.
|
|
2067
|
+
|
|
2068
|
+
Raises
|
|
2069
|
+
------
|
|
2070
|
+
requests.HTTPError
|
|
2071
|
+
If the response status code is not 2xx.
|
|
2072
|
+
"""
|
|
2073
|
+
|
|
2074
|
+
return self.update_batch_experiment(
|
|
2075
|
+
batch_experiment_id=scenario_test_id,
|
|
2076
|
+
name=name,
|
|
2077
|
+
description=description,
|
|
2078
|
+
)
|
|
2079
|
+
|
|
1997
2080
|
def update_secrets_collection(
|
|
1998
2081
|
self,
|
|
1999
2082
|
secrets_collection_id: str,
|
|
@@ -18,9 +18,9 @@ class BatchExperimentInformation(BaseModel):
|
|
|
18
18
|
"""Creation date of the batch experiment."""
|
|
19
19
|
updated_at: datetime
|
|
20
20
|
"""Last update date of the batch experiment."""
|
|
21
|
-
status: str
|
|
22
|
-
"""Status of the batch experiment."""
|
|
23
21
|
|
|
22
|
+
status: Optional[str] = None
|
|
23
|
+
"""Status of the batch experiment."""
|
|
24
24
|
description: Optional[str] = None
|
|
25
25
|
"""Description of the batch experiment."""
|
|
26
26
|
number_of_requested_runs: Optional[int] = None
|
|
@@ -101,5 +101,5 @@ class BatchExperimentRun(BaseModel):
|
|
|
101
101
|
class BatchExperimentMetadata(BatchExperimentInformation):
|
|
102
102
|
"""Metadata of a batch experiment."""
|
|
103
103
|
|
|
104
|
-
app_id: str
|
|
104
|
+
app_id: Optional[str] = None
|
|
105
105
|
"""ID of the application used for the batch experiment."""
|
|
@@ -349,7 +349,7 @@ class Output:
|
|
|
349
349
|
class OutputWriter:
|
|
350
350
|
"""Base class for writing outputs."""
|
|
351
351
|
|
|
352
|
-
def write(self, output: Output, *args, **kwargs) -> None:
|
|
352
|
+
def write(self, output: Union[Output, dict[str, Any], BaseModel], *args, **kwargs) -> None:
|
|
353
353
|
"""
|
|
354
354
|
Write the output data. This method should be implemented by subclasses.
|
|
355
355
|
"""
|
|
@@ -364,7 +364,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
364
364
|
"""
|
|
365
365
|
|
|
366
366
|
def _write_json(
|
|
367
|
-
output: Union[Output, dict[str, Any]],
|
|
367
|
+
output: Union[Output, dict[str, Any], BaseModel],
|
|
368
368
|
options: dict[str, Any],
|
|
369
369
|
statistics: dict[str, Any],
|
|
370
370
|
assets: list[dict[str, Any]],
|
|
@@ -372,6 +372,8 @@ class LocalOutputWriter(OutputWriter):
|
|
|
372
372
|
) -> None:
|
|
373
373
|
if isinstance(output, dict):
|
|
374
374
|
final_output = output
|
|
375
|
+
elif isinstance(output, BaseModel):
|
|
376
|
+
final_output = output.to_dict()
|
|
375
377
|
else:
|
|
376
378
|
solution = output.solution if output.solution is not None else {}
|
|
377
379
|
final_output = {
|
|
@@ -447,7 +449,7 @@ class LocalOutputWriter(OutputWriter):
|
|
|
447
449
|
|
|
448
450
|
def write(
|
|
449
451
|
self,
|
|
450
|
-
output: Union[Output, dict[str, Any]],
|
|
452
|
+
output: Union[Output, dict[str, Any], BaseModel],
|
|
451
453
|
path: Optional[str] = None,
|
|
452
454
|
skip_stdout_reset: bool = False,
|
|
453
455
|
) -> None:
|
|
@@ -497,8 +499,12 @@ class LocalOutputWriter(OutputWriter):
|
|
|
497
499
|
output_format = output.output_format
|
|
498
500
|
elif isinstance(output, dict):
|
|
499
501
|
output_format = OutputFormat.JSON
|
|
502
|
+
elif isinstance(output, BaseModel):
|
|
503
|
+
output_format = OutputFormat.JSON
|
|
500
504
|
else:
|
|
501
|
-
raise TypeError(
|
|
505
|
+
raise TypeError(
|
|
506
|
+
f"unsupported output type: {type(output)}, supported types are `Output`, `dict`, `BaseModel`"
|
|
507
|
+
)
|
|
502
508
|
|
|
503
509
|
statistics = self._extract_statistics(output)
|
|
504
510
|
options = self._extract_options(output)
|
|
@@ -640,7 +646,7 @@ _LOCAL_OUTPUT_WRITER = LocalOutputWriter()
|
|
|
640
646
|
|
|
641
647
|
|
|
642
648
|
def write(
|
|
643
|
-
output: Union[Output, dict[str, Any]],
|
|
649
|
+
output: Union[Output, dict[str, Any], BaseModel],
|
|
644
650
|
path: Optional[str] = None,
|
|
645
651
|
skip_stdout_reset: bool = False,
|
|
646
652
|
writer: Optional[OutputWriter] = _LOCAL_OUTPUT_WRITER,
|
|
@@ -4,10 +4,11 @@ import os
|
|
|
4
4
|
import shutil
|
|
5
5
|
import unittest
|
|
6
6
|
from io import StringIO
|
|
7
|
-
from typing import Optional
|
|
7
|
+
from typing import Any, Optional
|
|
8
8
|
from unittest.mock import patch
|
|
9
9
|
|
|
10
10
|
import nextmv
|
|
11
|
+
from nextmv.base_model import BaseModel
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class TestOutput(unittest.TestCase):
|
|
@@ -294,6 +295,32 @@ class TestOutput(unittest.TestCase):
|
|
|
294
295
|
|
|
295
296
|
self.assertDictEqual(got, expected)
|
|
296
297
|
|
|
298
|
+
def test_local_write_base_model(self):
|
|
299
|
+
class myClass(BaseModel):
|
|
300
|
+
output: dict[str, Any]
|
|
301
|
+
|
|
302
|
+
output = {
|
|
303
|
+
"i_am": "a_crazy_object",
|
|
304
|
+
"with": [
|
|
305
|
+
{"nested": "values"},
|
|
306
|
+
{"and": "more_craziness"},
|
|
307
|
+
],
|
|
308
|
+
}
|
|
309
|
+
custom_class = myClass(output=output)
|
|
310
|
+
|
|
311
|
+
output_writer = nextmv.LocalOutputWriter()
|
|
312
|
+
|
|
313
|
+
with patch("sys.stdout", new=StringIO()) as mock_stdout:
|
|
314
|
+
output_writer.write(custom_class, skip_stdout_reset=True)
|
|
315
|
+
|
|
316
|
+
got = json.loads(mock_stdout.getvalue())
|
|
317
|
+
|
|
318
|
+
# We test that the `write` method calls the `.to_dict()` method if
|
|
319
|
+
# it detects the output type to be an instance of `BaseModel`.
|
|
320
|
+
expected = {"output": output}
|
|
321
|
+
|
|
322
|
+
self.assertDictEqual(got, expected)
|
|
323
|
+
|
|
297
324
|
def test_local_write_empty_output(self):
|
|
298
325
|
output = nextmv.Output()
|
|
299
326
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "v0.25.0"
|
|
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
|