nextmv 0.24.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.24.0 → nextmv-0.26.0}/PKG-INFO +1 -1
- nextmv-0.26.0/nextmv/__about__.py +1 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/application.py +98 -4
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/batch_experiment.py +3 -3
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/output.py +11 -5
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_output.py +28 -1
- nextmv-0.24.0/nextmv/__about__.py +0 -1
- {nextmv-0.24.0 → nextmv-0.26.0}/.gitignore +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/LICENSE +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/README.md +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/__entrypoint__.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/__init__.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/base_model.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/__init__.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/acceptance_test.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/account.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/client.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/input_set.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/instance.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/manifest.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/package.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/run.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/safe.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/scenario.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/secrets.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/status.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/cloud/version.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/deprecated.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/input.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/logger.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/model.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/nextmv/options.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/pyproject.toml +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/requirements.txt +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/__init__.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/__init__.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/app.yaml +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/test_application.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/test_client.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/test_manifest.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/test_package.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/test_safe_name_id.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/cloud/test_scenario.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/__init__.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options1.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options2.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options3.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options4.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options5.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options6.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options7.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/scripts/options_deprecated.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_base_model.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_entrypoint/__init__.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_entrypoint/test_entrypoint.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_input.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_logger.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_model.py +0 -0
- {nextmv-0.24.0 → nextmv-0.26.0}/tests/test_options.py +0 -0
- {nextmv-0.24.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
|
|
@@ -1799,7 +1804,7 @@ class Application:
|
|
|
1799
1804
|
|
|
1800
1805
|
return self.batch_experiment(batch_id=scenario_test_id)
|
|
1801
1806
|
|
|
1802
|
-
def track_run(self, tracked_run: TrackedRun) -> str:
|
|
1807
|
+
def track_run(self, tracked_run: TrackedRun, instance_id: Optional[str] = None) -> str:
|
|
1803
1808
|
"""
|
|
1804
1809
|
Track an external run.
|
|
1805
1810
|
|
|
@@ -1812,6 +1817,9 @@ class Application:
|
|
|
1812
1817
|
----------
|
|
1813
1818
|
tracked_run : TrackedRun
|
|
1814
1819
|
The run to track.
|
|
1820
|
+
instance_id: Optional[str]
|
|
1821
|
+
Optional instance ID if you want to associate your tracked run with
|
|
1822
|
+
an instance.
|
|
1815
1823
|
|
|
1816
1824
|
Returns
|
|
1817
1825
|
-------
|
|
@@ -1855,12 +1863,17 @@ class Application:
|
|
|
1855
1863
|
if tracked_run.error is not None and tracked_run.error != "":
|
|
1856
1864
|
external_result.error_message = tracked_run.error
|
|
1857
1865
|
|
|
1858
|
-
return self.new_run(
|
|
1866
|
+
return self.new_run(
|
|
1867
|
+
upload_id=url_input.upload_id,
|
|
1868
|
+
external_result=external_result,
|
|
1869
|
+
instance_id=instance_id,
|
|
1870
|
+
)
|
|
1859
1871
|
|
|
1860
1872
|
def track_run_with_result(
|
|
1861
1873
|
self,
|
|
1862
1874
|
tracked_run: TrackedRun,
|
|
1863
1875
|
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
1876
|
+
instance_id: Optional[str] = None,
|
|
1864
1877
|
) -> RunResult:
|
|
1865
1878
|
"""
|
|
1866
1879
|
Track an external run and poll for the result. This is a convenience
|
|
@@ -1874,6 +1887,9 @@ class Application:
|
|
|
1874
1887
|
The run to track.
|
|
1875
1888
|
polling_options : PollingOptions
|
|
1876
1889
|
Options to use when polling for the run result.
|
|
1890
|
+
instance_id: Optional[str]
|
|
1891
|
+
Optional instance ID if you want to associate your tracked run with
|
|
1892
|
+
an instance.
|
|
1877
1893
|
|
|
1878
1894
|
Returns
|
|
1879
1895
|
-------
|
|
@@ -1893,7 +1909,7 @@ class Application:
|
|
|
1893
1909
|
If the run does not succeed after the polling strategy is
|
|
1894
1910
|
exhausted based on number of tries.
|
|
1895
1911
|
"""
|
|
1896
|
-
run_id = self.track_run(tracked_run=tracked_run)
|
|
1912
|
+
run_id = self.track_run(tracked_run=tracked_run, instance_id=instance_id)
|
|
1897
1913
|
|
|
1898
1914
|
return self.run_result_with_polling(
|
|
1899
1915
|
run_id=run_id,
|
|
@@ -1944,6 +1960,47 @@ class Application:
|
|
|
1944
1960
|
|
|
1945
1961
|
return Instance.from_dict(response.json())
|
|
1946
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
|
+
|
|
1947
2004
|
def update_managed_input(
|
|
1948
2005
|
self,
|
|
1949
2006
|
managed_input_id: str,
|
|
@@ -1983,6 +2040,43 @@ class Application:
|
|
|
1983
2040
|
payload=payload,
|
|
1984
2041
|
)
|
|
1985
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
|
+
|
|
1986
2080
|
def update_secrets_collection(
|
|
1987
2081
|
self,
|
|
1988
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.24.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
|