nextmv 0.29.4.dev1__tar.gz → 0.29.5.dev1__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.29.4.dev1 → nextmv-0.29.5.dev1}/PKG-INFO +1 -1
- nextmv-0.29.5.dev1/nextmv/__about__.py +1 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/__init__.py +1 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/application.py +209 -100
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/manifest.py +36 -2
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/run.py +98 -48
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/safe.py +30 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/model.py +0 -1
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/options.py +14 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/app.yaml +5 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/test_manifest.py +40 -25
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_entrypoint/test_entrypoint.py +2 -1
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_model.py +2 -1
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_options.py +2 -1
- nextmv-0.29.4.dev1/nextmv/__about__.py +0 -1
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/.gitignore +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/LICENSE +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/README.md +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/__entrypoint__.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/__init__.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/_serialization.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/base_model.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/acceptance_test.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/account.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/batch_experiment.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/client.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/input_set.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/instance.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/package.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/scenario.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/secrets.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/status.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/cloud/version.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/default_app/README.md +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/default_app/app.yaml +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/default_app/requirements.txt +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/default_app/src/__init__.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/default_app/src/main.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/default_app/src/visuals.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/deprecated.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/input.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/logger.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/nextmv/output.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/pyproject.toml +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/__init__.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/__init__.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/test_application.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/test_client.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/test_package.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/test_run.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/test_safe_name_id.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/cloud/test_scenario.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/__init__.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options1.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options2.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options3.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options4.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options5.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options6.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options7.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/scripts/options_deprecated.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_base_model.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_entrypoint/__init__.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_input.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_inputs/test_data.csv +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_inputs/test_data.json +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_inputs/test_data.txt +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_logger.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_output.py +1 -1
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_serialization.py +0 -0
- {nextmv-0.29.4.dev1 → nextmv-0.29.5.dev1}/tests/test_version.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "v0.29.5.dev.1"
|
|
@@ -46,6 +46,7 @@ from .run import ErrorLog as ErrorLog
|
|
|
46
46
|
from .run import ExternalRunResult as ExternalRunResult
|
|
47
47
|
from .run import Format as Format
|
|
48
48
|
from .run import FormatInput as FormatInput
|
|
49
|
+
from .run import FormatOutput as FormatOutput
|
|
49
50
|
from .run import Metadata as Metadata
|
|
50
51
|
from .run import RunConfiguration as RunConfiguration
|
|
51
52
|
from .run import RunInformation as RunInformation
|
|
@@ -56,13 +56,14 @@ from nextmv.cloud.run import (
|
|
|
56
56
|
ExternalRunResult,
|
|
57
57
|
Format,
|
|
58
58
|
FormatInput,
|
|
59
|
+
FormatOutput,
|
|
59
60
|
RunConfiguration,
|
|
60
61
|
RunInformation,
|
|
61
62
|
RunLog,
|
|
62
63
|
RunResult,
|
|
63
64
|
TrackedRun,
|
|
64
65
|
)
|
|
65
|
-
from nextmv.cloud.safe import _name_and_id
|
|
66
|
+
from nextmv.cloud.safe import _name_and_id, _safe_id
|
|
66
67
|
from nextmv.cloud.scenario import Scenario, ScenarioInputType, _option_sets, _scenarios_by_id
|
|
67
68
|
from nextmv.cloud.secrets import Secret, SecretsCollection, SecretsCollectionSummary
|
|
68
69
|
from nextmv.cloud.status import StatusV2
|
|
@@ -71,7 +72,7 @@ from nextmv.input import Input, InputFormat
|
|
|
71
72
|
from nextmv.logger import log
|
|
72
73
|
from nextmv.model import Model, ModelConfiguration
|
|
73
74
|
from nextmv.options import Options
|
|
74
|
-
from nextmv.output import Output
|
|
75
|
+
from nextmv.output import Output, OutputFormat
|
|
75
76
|
|
|
76
77
|
# Maximum size of the run input/output in bytes. This constant defines the
|
|
77
78
|
# maximum allowed size for run inputs and outputs. When the size exceeds this
|
|
@@ -1127,16 +1128,26 @@ class Application:
|
|
|
1127
1128
|
f"batch experiment {id} does not exist, input_set_id must be defined to create a new one"
|
|
1128
1129
|
) from e
|
|
1129
1130
|
else:
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1131
|
+
# Get all input IDs from the input set.
|
|
1132
|
+
input_set = self.input_set(input_set_id=input_set_id)
|
|
1133
|
+
if len(input_set.input_ids) == 0:
|
|
1134
|
+
raise ValueError(f"input set {input_set_id} does not contain any inputs")
|
|
1135
|
+
runs = []
|
|
1136
|
+
for input_id in input_set.input_ids:
|
|
1137
|
+
runs.append(
|
|
1138
|
+
BatchExperimentRun(
|
|
1139
|
+
instance_id=candidate_instance_id,
|
|
1140
|
+
input_set_id=input_set_id,
|
|
1141
|
+
input_id=input_id,
|
|
1142
|
+
)
|
|
1143
|
+
)
|
|
1144
|
+
runs.append(
|
|
1145
|
+
BatchExperimentRun(
|
|
1146
|
+
instance_id=baseline_instance_id,
|
|
1147
|
+
input_set_id=input_set_id,
|
|
1148
|
+
input_id=input_id,
|
|
1149
|
+
)
|
|
1150
|
+
)
|
|
1140
1151
|
batch_experiment_id = self.new_batch_experiment(
|
|
1141
1152
|
name=name,
|
|
1142
1153
|
description=description,
|
|
@@ -1588,7 +1599,10 @@ class Application:
|
|
|
1588
1599
|
if format is not None:
|
|
1589
1600
|
payload["format"] = format.to_dict() if isinstance(format, Format) else format
|
|
1590
1601
|
else:
|
|
1591
|
-
payload["format"] = Format(
|
|
1602
|
+
payload["format"] = Format(
|
|
1603
|
+
format_input=FormatInput(input_type=InputFormat.JSON),
|
|
1604
|
+
format_output=FormatOutput(output_type=OutputFormat.JSON),
|
|
1605
|
+
).to_dict()
|
|
1592
1606
|
|
|
1593
1607
|
response = self.client.request(
|
|
1594
1608
|
method="POST",
|
|
@@ -1610,7 +1624,7 @@ class Application:
|
|
|
1610
1624
|
batch_experiment_id: Optional[str] = None,
|
|
1611
1625
|
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1612
1626
|
json_configurations: Optional[dict[str, Any]] = None,
|
|
1613
|
-
|
|
1627
|
+
input_dir_path: Optional[str] = None,
|
|
1614
1628
|
) -> str:
|
|
1615
1629
|
"""
|
|
1616
1630
|
Submit an input to start a new run of the application. Returns the
|
|
@@ -1627,14 +1641,14 @@ class Application:
|
|
|
1627
1641
|
input data is extracted from the `.data` property.
|
|
1628
1642
|
|
|
1629
1643
|
If you want to work with `nextmv.InputFormat.CSV_ARCHIVE` or
|
|
1630
|
-
`nextmv.InputFormat.MULTI_FILE`, you should use the `
|
|
1644
|
+
`nextmv.InputFormat.MULTI_FILE`, you should use the `input_dir_path`
|
|
1631
1645
|
argument instead. This argument takes precedence over the `input`.
|
|
1632
|
-
If `
|
|
1646
|
+
If `input_dir_path` is specified, this function looks for files in that
|
|
1633
1647
|
directory and tars them, to later be uploaded using the
|
|
1634
|
-
`upload_large_input` method. If both the `
|
|
1648
|
+
`upload_large_input` method. If both the `input_dir_path` and `input`
|
|
1635
1649
|
arguments are provided, the `input` is ignored.
|
|
1636
1650
|
|
|
1637
|
-
When `
|
|
1651
|
+
When `input_dir_path` is specified, the `configuration` argument must
|
|
1638
1652
|
also be provided. More specifically, the
|
|
1639
1653
|
`RunConfiguration.format.format_input.input_type` parameter
|
|
1640
1654
|
dictates what kind of input is being submitted to the Nextmv Cloud.
|
|
@@ -1686,12 +1700,12 @@ class Application:
|
|
|
1686
1700
|
json_configurations: Optional[dict[str, Any]]
|
|
1687
1701
|
Optional configurations for JSON serialization. This is used to
|
|
1688
1702
|
customize the serialization before data is sent.
|
|
1689
|
-
|
|
1703
|
+
input_dir_path: Optional[str]
|
|
1690
1704
|
Path to a directory containing input files. If specified, the
|
|
1691
1705
|
function will package the files in the directory into a tar file
|
|
1692
1706
|
and upload it as a large input. This is useful for input formats
|
|
1693
1707
|
like `nextmv.InputFormat.CSV_ARCHIVE` or `nextmv.InputFormat.MULTI_FILE`.
|
|
1694
|
-
If both `input` and `
|
|
1708
|
+
If both `input` and `input_dir_path` are specified, the `input` is
|
|
1695
1709
|
ignored, and the files in the directory are used instead.
|
|
1696
1710
|
|
|
1697
1711
|
Returns
|
|
@@ -1708,17 +1722,17 @@ class Application:
|
|
|
1708
1722
|
not `JSON`. If the final `options` are not of type `dict[str,str]`.
|
|
1709
1723
|
"""
|
|
1710
1724
|
|
|
1711
|
-
self.__validate_dir_path_and_configuration(
|
|
1725
|
+
self.__validate_dir_path_and_configuration(input_dir_path, configuration)
|
|
1712
1726
|
|
|
1713
1727
|
tar_file = ""
|
|
1714
|
-
if
|
|
1715
|
-
if not os.path.exists(
|
|
1716
|
-
raise ValueError(f"Directory {
|
|
1728
|
+
if input_dir_path is not None and input_dir_path != "":
|
|
1729
|
+
if not os.path.exists(input_dir_path):
|
|
1730
|
+
raise ValueError(f"Directory {input_dir_path} does not exist.")
|
|
1717
1731
|
|
|
1718
|
-
if not os.path.isdir(
|
|
1719
|
-
raise ValueError(f"Path {
|
|
1732
|
+
if not os.path.isdir(input_dir_path):
|
|
1733
|
+
raise ValueError(f"Path {input_dir_path} is not a directory.")
|
|
1720
1734
|
|
|
1721
|
-
tar_file = self.__package_inputs(
|
|
1735
|
+
tar_file = self.__package_inputs(input_dir_path)
|
|
1722
1736
|
|
|
1723
1737
|
input_data = None
|
|
1724
1738
|
if isinstance(input, BaseModel):
|
|
@@ -1775,7 +1789,7 @@ class Application:
|
|
|
1775
1789
|
)
|
|
1776
1790
|
else:
|
|
1777
1791
|
configuration = RunConfiguration()
|
|
1778
|
-
configuration.resolve(input=input, dir_path=
|
|
1792
|
+
configuration.resolve(input=input, dir_path=input_dir_path)
|
|
1779
1793
|
configuration_dict = configuration.to_dict()
|
|
1780
1794
|
|
|
1781
1795
|
payload["configuration"] = configuration_dict
|
|
@@ -1814,7 +1828,8 @@ class Application:
|
|
|
1814
1828
|
batch_experiment_id: Optional[str] = None,
|
|
1815
1829
|
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1816
1830
|
json_configurations: Optional[dict[str, Any]] = None,
|
|
1817
|
-
|
|
1831
|
+
input_dir_path: Optional[str] = None,
|
|
1832
|
+
output_dir_path: Optional[str] = ".",
|
|
1818
1833
|
) -> RunResult:
|
|
1819
1834
|
"""
|
|
1820
1835
|
Submit an input to start a new run of the application and poll for the
|
|
@@ -1833,14 +1848,14 @@ class Application:
|
|
|
1833
1848
|
input data is extracted from the `.data` property.
|
|
1834
1849
|
|
|
1835
1850
|
If you want to work with `nextmv.InputFormat.CSV_ARCHIVE` or
|
|
1836
|
-
`nextmv.InputFormat.MULTI_FILE`, you should use the `
|
|
1851
|
+
`nextmv.InputFormat.MULTI_FILE`, you should use the `input_dir_path`
|
|
1837
1852
|
argument instead. This argument takes precedence over the `input`.
|
|
1838
|
-
If `
|
|
1853
|
+
If `input_dir_path` is specified, this function looks for files in that
|
|
1839
1854
|
directory and tars them, to later be uploaded using the
|
|
1840
|
-
`upload_large_input` method. If both the `
|
|
1855
|
+
`upload_large_input` method. If both the `input_dir_path` and `input`
|
|
1841
1856
|
arguments are provided, the `input` is ignored.
|
|
1842
1857
|
|
|
1843
|
-
When `
|
|
1858
|
+
When `input_dir_path` is specified, the `configuration` argument must
|
|
1844
1859
|
also be provided. More specifically, the
|
|
1845
1860
|
`RunConfiguration.format.format_input.input_type` parameter
|
|
1846
1861
|
dictates what kind of input is being submitted to the Nextmv Cloud.
|
|
@@ -1897,13 +1912,17 @@ class Application:
|
|
|
1897
1912
|
json_configurations: Optional[dict[str, Any]]
|
|
1898
1913
|
Optional configurations for JSON serialization. This is used to
|
|
1899
1914
|
customize the serialization before data is sent.
|
|
1900
|
-
|
|
1915
|
+
input_dir_path: Optional[str]
|
|
1901
1916
|
Path to a directory containing input files. If specified, the
|
|
1902
1917
|
function will package the files in the directory into a tar file
|
|
1903
1918
|
and upload it as a large input. This is useful for input formats
|
|
1904
1919
|
like `nextmv.InputFormat.CSV_ARCHIVE` or `nextmv.InputFormat.MULTI_FILE`.
|
|
1905
|
-
If both `input` and `
|
|
1920
|
+
If both `input` and `input_dir_path` are specified, the `input` is
|
|
1906
1921
|
ignored, and the files in the directory are used instead.
|
|
1922
|
+
output_dir_path : Optional[str], default="."
|
|
1923
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
1924
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
1925
|
+
will be created. Uses the current directory by default.
|
|
1907
1926
|
|
|
1908
1927
|
Returns
|
|
1909
1928
|
----------
|
|
@@ -1936,12 +1955,13 @@ class Application:
|
|
|
1936
1955
|
batch_experiment_id=batch_experiment_id,
|
|
1937
1956
|
external_result=external_result,
|
|
1938
1957
|
json_configurations=json_configurations,
|
|
1939
|
-
|
|
1958
|
+
input_dir_path=input_dir_path,
|
|
1940
1959
|
)
|
|
1941
1960
|
|
|
1942
1961
|
return self.run_result_with_polling(
|
|
1943
1962
|
run_id=run_id,
|
|
1944
1963
|
polling_options=polling_options,
|
|
1964
|
+
output_dir_path=output_dir_path,
|
|
1945
1965
|
)
|
|
1946
1966
|
|
|
1947
1967
|
def new_scenario_test(
|
|
@@ -2215,10 +2235,13 @@ class Application:
|
|
|
2215
2235
|
if exist_ok and self.version_exists(version_id=id):
|
|
2216
2236
|
return self.version(version_id=id)
|
|
2217
2237
|
|
|
2218
|
-
|
|
2238
|
+
if id is None:
|
|
2239
|
+
id = _safe_id(prefix="version")
|
|
2240
|
+
|
|
2241
|
+
payload = {
|
|
2242
|
+
"id": id,
|
|
2243
|
+
}
|
|
2219
2244
|
|
|
2220
|
-
if id is not None:
|
|
2221
|
-
payload["id"] = id
|
|
2222
2245
|
if name is not None:
|
|
2223
2246
|
payload["name"] = name
|
|
2224
2247
|
if description is not None:
|
|
@@ -2501,7 +2524,7 @@ class Application:
|
|
|
2501
2524
|
)
|
|
2502
2525
|
return RunLog.from_dict(response.json())
|
|
2503
2526
|
|
|
2504
|
-
def run_result(self, run_id: str) -> RunResult:
|
|
2527
|
+
def run_result(self, run_id: str, output_dir_path: Optional[str] = ".") -> RunResult:
|
|
2505
2528
|
"""
|
|
2506
2529
|
Get the result of a run.
|
|
2507
2530
|
|
|
@@ -2511,6 +2534,10 @@ class Application:
|
|
|
2511
2534
|
----------
|
|
2512
2535
|
run_id : str
|
|
2513
2536
|
ID of the run to get results for.
|
|
2537
|
+
output_dir_path : Optional[str], default="."
|
|
2538
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
2539
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
2540
|
+
will be created. Uses the current directory by default.
|
|
2514
2541
|
|
|
2515
2542
|
Returns
|
|
2516
2543
|
-------
|
|
@@ -2531,12 +2558,17 @@ class Application:
|
|
|
2531
2558
|
|
|
2532
2559
|
run_information = self.run_metadata(run_id=run_id)
|
|
2533
2560
|
|
|
2534
|
-
return self.__run_result(
|
|
2561
|
+
return self.__run_result(
|
|
2562
|
+
run_id=run_id,
|
|
2563
|
+
run_information=run_information,
|
|
2564
|
+
output_dir_path=output_dir_path,
|
|
2565
|
+
)
|
|
2535
2566
|
|
|
2536
2567
|
def run_result_with_polling(
|
|
2537
2568
|
self,
|
|
2538
2569
|
run_id: str,
|
|
2539
2570
|
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
2571
|
+
output_dir_path: Optional[str] = ".",
|
|
2540
2572
|
) -> RunResult:
|
|
2541
2573
|
"""
|
|
2542
2574
|
Get the result of a run with polling.
|
|
@@ -2551,6 +2583,10 @@ class Application:
|
|
|
2551
2583
|
ID of the run to retrieve the result for.
|
|
2552
2584
|
polling_options : PollingOptions, default=_DEFAULT_POLLING_OPTIONS
|
|
2553
2585
|
Options to use when polling for the run result.
|
|
2586
|
+
output_dir_path : Optional[str], default="."
|
|
2587
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
2588
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
2589
|
+
will be created. Uses the current directory by default.
|
|
2554
2590
|
|
|
2555
2591
|
Returns
|
|
2556
2592
|
-------
|
|
@@ -2592,7 +2628,11 @@ class Application:
|
|
|
2592
2628
|
|
|
2593
2629
|
run_information = poll(polling_options=polling_options, polling_func=polling_func)
|
|
2594
2630
|
|
|
2595
|
-
return self.__run_result(
|
|
2631
|
+
return self.__run_result(
|
|
2632
|
+
run_id=run_id,
|
|
2633
|
+
run_information=run_information,
|
|
2634
|
+
output_dir_path=output_dir_path,
|
|
2635
|
+
)
|
|
2596
2636
|
|
|
2597
2637
|
def scenario_test(self, scenario_test_id: str) -> BatchExperiment:
|
|
2598
2638
|
"""
|
|
@@ -2707,6 +2747,7 @@ class Application:
|
|
|
2707
2747
|
tracked_run: TrackedRun,
|
|
2708
2748
|
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
2709
2749
|
instance_id: Optional[str] = None,
|
|
2750
|
+
output_dir_path: Optional[str] = ".",
|
|
2710
2751
|
) -> RunResult:
|
|
2711
2752
|
"""
|
|
2712
2753
|
Track an external run and poll for the result. This is a convenience
|
|
@@ -2723,6 +2764,10 @@ class Application:
|
|
|
2723
2764
|
instance_id: Optional[str]
|
|
2724
2765
|
Optional instance ID if you want to associate your tracked run with
|
|
2725
2766
|
an instance.
|
|
2767
|
+
output_dir_path : Optional[str], default="."
|
|
2768
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
2769
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
2770
|
+
will be created. Uses the current directory by default.
|
|
2726
2771
|
|
|
2727
2772
|
Returns
|
|
2728
2773
|
-------
|
|
@@ -2747,12 +2792,13 @@ class Application:
|
|
|
2747
2792
|
return self.run_result_with_polling(
|
|
2748
2793
|
run_id=run_id,
|
|
2749
2794
|
polling_options=polling_options,
|
|
2795
|
+
output_dir_path=output_dir_path,
|
|
2750
2796
|
)
|
|
2751
2797
|
|
|
2752
2798
|
def update_instance(
|
|
2753
2799
|
self,
|
|
2754
2800
|
id: str,
|
|
2755
|
-
name: str,
|
|
2801
|
+
name: Optional[str] = None,
|
|
2756
2802
|
version_id: Optional[str] = None,
|
|
2757
2803
|
description: Optional[str] = None,
|
|
2758
2804
|
configuration: Optional[InstanceConfiguration] = None,
|
|
@@ -2764,14 +2810,14 @@ class Application:
|
|
|
2764
2810
|
----------
|
|
2765
2811
|
id : str
|
|
2766
2812
|
ID of the instance to update.
|
|
2767
|
-
name : str
|
|
2768
|
-
|
|
2813
|
+
name : Optional[str], default=None
|
|
2814
|
+
Optional name of the instance.
|
|
2769
2815
|
version_id : Optional[str], default=None
|
|
2770
|
-
ID of the version to associate the instance with.
|
|
2816
|
+
Optional ID of the version to associate the instance with.
|
|
2771
2817
|
description : Optional[str], default=None
|
|
2772
|
-
|
|
2818
|
+
Optional description of the instance.
|
|
2773
2819
|
configuration : Optional[InstanceConfiguration], default=None
|
|
2774
|
-
|
|
2820
|
+
Optional configuration to use for the instance.
|
|
2775
2821
|
|
|
2776
2822
|
Returns
|
|
2777
2823
|
-------
|
|
@@ -2784,12 +2830,21 @@ class Application:
|
|
|
2784
2830
|
If the response status code is not 2xx.
|
|
2785
2831
|
"""
|
|
2786
2832
|
|
|
2787
|
-
|
|
2833
|
+
# Get the instance as it currently exsits.
|
|
2834
|
+
instance = self.instance(id)
|
|
2835
|
+
instance_dict = instance.to_dict()
|
|
2836
|
+
|
|
2837
|
+
payload = {
|
|
2838
|
+
"name": instance_dict["name"],
|
|
2839
|
+
"version_id": instance_dict["version_id"],
|
|
2840
|
+
"description": instance_dict["description"],
|
|
2841
|
+
"configuration": instance_dict["configuration"],
|
|
2842
|
+
}
|
|
2788
2843
|
|
|
2789
|
-
if version_id is not None:
|
|
2790
|
-
payload["version_id"] = version_id
|
|
2791
2844
|
if name is not None:
|
|
2792
2845
|
payload["name"] = name
|
|
2846
|
+
if version_id is not None:
|
|
2847
|
+
payload["version_id"] = version_id
|
|
2793
2848
|
if description is not None:
|
|
2794
2849
|
payload["description"] = description
|
|
2795
2850
|
if configuration is not None:
|
|
@@ -2806,8 +2861,8 @@ class Application:
|
|
|
2806
2861
|
def update_batch_experiment(
|
|
2807
2862
|
self,
|
|
2808
2863
|
batch_experiment_id: str,
|
|
2809
|
-
name: str,
|
|
2810
|
-
description: str,
|
|
2864
|
+
name: Optional[str] = None,
|
|
2865
|
+
description: Optional[str] = None,
|
|
2811
2866
|
) -> BatchExperimentInformation:
|
|
2812
2867
|
"""
|
|
2813
2868
|
Update a batch experiment.
|
|
@@ -2816,10 +2871,10 @@ class Application:
|
|
|
2816
2871
|
----------
|
|
2817
2872
|
batch_experiment_id : str
|
|
2818
2873
|
ID of the batch experiment to update.
|
|
2819
|
-
name : str
|
|
2820
|
-
|
|
2821
|
-
description : str
|
|
2822
|
-
|
|
2874
|
+
name : Optional[str], default=None
|
|
2875
|
+
Optional name of the batch experiment.
|
|
2876
|
+
description : Optional[str], default=None
|
|
2877
|
+
Optional description of the batch experiment.
|
|
2823
2878
|
|
|
2824
2879
|
Returns
|
|
2825
2880
|
-------
|
|
@@ -2832,10 +2887,13 @@ class Application:
|
|
|
2832
2887
|
If the response status code is not 2xx.
|
|
2833
2888
|
"""
|
|
2834
2889
|
|
|
2835
|
-
payload = {
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2890
|
+
payload = {}
|
|
2891
|
+
|
|
2892
|
+
if name is not None:
|
|
2893
|
+
payload["name"] = name
|
|
2894
|
+
if description is not None:
|
|
2895
|
+
payload["description"] = description
|
|
2896
|
+
|
|
2839
2897
|
response = self.client.request(
|
|
2840
2898
|
method="PATCH",
|
|
2841
2899
|
endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
|
|
@@ -2847,9 +2905,9 @@ class Application:
|
|
|
2847
2905
|
def update_managed_input(
|
|
2848
2906
|
self,
|
|
2849
2907
|
managed_input_id: str,
|
|
2850
|
-
name: str,
|
|
2851
|
-
description: str,
|
|
2852
|
-
) ->
|
|
2908
|
+
name: Optional[str] = None,
|
|
2909
|
+
description: Optional[str] = None,
|
|
2910
|
+
) -> ManagedInput:
|
|
2853
2911
|
"""
|
|
2854
2912
|
Update a managed input.
|
|
2855
2913
|
|
|
@@ -2857,15 +2915,15 @@ class Application:
|
|
|
2857
2915
|
----------
|
|
2858
2916
|
managed_input_id : str
|
|
2859
2917
|
ID of the managed input to update.
|
|
2860
|
-
name : str
|
|
2861
|
-
|
|
2862
|
-
description : str
|
|
2863
|
-
|
|
2918
|
+
name : Optional[str], default=None
|
|
2919
|
+
Optional new name for the managed input.
|
|
2920
|
+
description : Optional[str], default=None
|
|
2921
|
+
Optional new description for the managed input.
|
|
2864
2922
|
|
|
2865
2923
|
Returns
|
|
2866
2924
|
-------
|
|
2867
|
-
|
|
2868
|
-
|
|
2925
|
+
ManagedInput
|
|
2926
|
+
The updated managed input.
|
|
2869
2927
|
|
|
2870
2928
|
Raises
|
|
2871
2929
|
------
|
|
@@ -2873,21 +2931,32 @@ class Application:
|
|
|
2873
2931
|
If the response status code is not 2xx.
|
|
2874
2932
|
"""
|
|
2875
2933
|
|
|
2934
|
+
managed_input = self.managed_input(managed_input_id)
|
|
2935
|
+
managed_input_dict = managed_input.to_dict()
|
|
2936
|
+
|
|
2876
2937
|
payload = {
|
|
2877
|
-
"name": name,
|
|
2878
|
-
"description": description,
|
|
2938
|
+
"name": managed_input_dict["name"],
|
|
2939
|
+
"description": managed_input_dict["description"],
|
|
2879
2940
|
}
|
|
2880
|
-
|
|
2941
|
+
|
|
2942
|
+
if name is not None:
|
|
2943
|
+
payload["name"] = name
|
|
2944
|
+
if description is not None:
|
|
2945
|
+
payload["description"] = description
|
|
2946
|
+
|
|
2947
|
+
response = self.client.request(
|
|
2881
2948
|
method="PUT",
|
|
2882
2949
|
endpoint=f"{self.endpoint}/inputs/{managed_input_id}",
|
|
2883
2950
|
payload=payload,
|
|
2884
2951
|
)
|
|
2885
2952
|
|
|
2953
|
+
return ManagedInput.from_dict(response.json())
|
|
2954
|
+
|
|
2886
2955
|
def update_scenario_test(
|
|
2887
2956
|
self,
|
|
2888
2957
|
scenario_test_id: str,
|
|
2889
|
-
name: str,
|
|
2890
|
-
description: str,
|
|
2958
|
+
name: Optional[str] = None,
|
|
2959
|
+
description: Optional[str] = None,
|
|
2891
2960
|
) -> BatchExperimentInformation:
|
|
2892
2961
|
"""
|
|
2893
2962
|
Update a scenario test.
|
|
@@ -2900,10 +2969,10 @@ class Application:
|
|
|
2900
2969
|
----------
|
|
2901
2970
|
scenario_test_id : str
|
|
2902
2971
|
ID of the scenario test to update.
|
|
2903
|
-
name : str
|
|
2904
|
-
|
|
2905
|
-
description : str
|
|
2906
|
-
|
|
2972
|
+
name : Optional[str], default=None
|
|
2973
|
+
Optional new name for the scenario test.
|
|
2974
|
+
description : Optional[str], default=None
|
|
2975
|
+
Optional new description for the scenario test.
|
|
2907
2976
|
|
|
2908
2977
|
Returns
|
|
2909
2978
|
-------
|
|
@@ -2935,9 +3004,9 @@ class Application:
|
|
|
2935
3004
|
def update_secrets_collection(
|
|
2936
3005
|
self,
|
|
2937
3006
|
secrets_collection_id: str,
|
|
2938
|
-
name: str,
|
|
2939
|
-
description: str,
|
|
2940
|
-
secrets: list[Secret],
|
|
3007
|
+
name: Optional[str] = None,
|
|
3008
|
+
description: Optional[str] = None,
|
|
3009
|
+
secrets: Optional[list[Secret]] = None,
|
|
2941
3010
|
) -> SecretsCollectionSummary:
|
|
2942
3011
|
"""
|
|
2943
3012
|
Update a secrets collection.
|
|
@@ -2950,13 +3019,13 @@ class Application:
|
|
|
2950
3019
|
----------
|
|
2951
3020
|
secrets_collection_id : str
|
|
2952
3021
|
ID of the secrets collection to update.
|
|
2953
|
-
name : str
|
|
2954
|
-
|
|
2955
|
-
description : str
|
|
2956
|
-
|
|
2957
|
-
secrets : list[Secret]
|
|
2958
|
-
|
|
2959
|
-
Secret class containing a key and value.
|
|
3022
|
+
name : Optional[str], default=None
|
|
3023
|
+
Optional new name for the secrets collection.
|
|
3024
|
+
description : Optional[str], default=None
|
|
3025
|
+
Optional new description for the secrets collection.
|
|
3026
|
+
secrets : Optional[list[Secret]], default=None
|
|
3027
|
+
Optional list of secrets to update. Each secret should be an
|
|
3028
|
+
instance of the Secret class containing a key and value.
|
|
2960
3029
|
|
|
2961
3030
|
Returns
|
|
2962
3031
|
-------
|
|
@@ -2988,14 +3057,22 @@ class Application:
|
|
|
2988
3057
|
'api-secrets'
|
|
2989
3058
|
"""
|
|
2990
3059
|
|
|
2991
|
-
|
|
2992
|
-
|
|
3060
|
+
collection = self.secrets_collection(secrets_collection_id)
|
|
3061
|
+
collection_dict = collection.to_dict()
|
|
2993
3062
|
|
|
2994
3063
|
payload = {
|
|
2995
|
-
"name": name,
|
|
2996
|
-
"description": description,
|
|
2997
|
-
"secrets": [
|
|
3064
|
+
"name": collection_dict["name"],
|
|
3065
|
+
"description": collection_dict["description"],
|
|
3066
|
+
"secrets": collection_dict["secrets"],
|
|
2998
3067
|
}
|
|
3068
|
+
|
|
3069
|
+
if name is not None:
|
|
3070
|
+
payload["name"] = name
|
|
3071
|
+
if description is not None:
|
|
3072
|
+
payload["description"] = description
|
|
3073
|
+
if secrets is not None and len(secrets) > 0:
|
|
3074
|
+
payload["secrets"] = [secret.to_dict() for secret in secrets]
|
|
3075
|
+
|
|
2999
3076
|
response = self.client.request(
|
|
3000
3077
|
method="PUT",
|
|
3001
3078
|
endpoint=f"{self.endpoint}/secrets/{secrets_collection_id}",
|
|
@@ -3229,6 +3306,7 @@ class Application:
|
|
|
3229
3306
|
self,
|
|
3230
3307
|
run_id: str,
|
|
3231
3308
|
run_information: RunInformation,
|
|
3309
|
+
output_dir_path: Optional[str] = ".",
|
|
3232
3310
|
) -> RunResult:
|
|
3233
3311
|
"""
|
|
3234
3312
|
Get the result of a run.
|
|
@@ -3245,6 +3323,10 @@ class Application:
|
|
|
3245
3323
|
ID of the run to retrieve the result for.
|
|
3246
3324
|
run_information : RunInformation
|
|
3247
3325
|
Information about the run, including metadata such as output size.
|
|
3326
|
+
output_dir_path : Optional[str], default="."
|
|
3327
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
3328
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
3329
|
+
will be created. Uses the current directory by default.
|
|
3248
3330
|
|
|
3249
3331
|
Returns
|
|
3250
3332
|
-------
|
|
@@ -3265,10 +3347,13 @@ class Application:
|
|
|
3265
3347
|
a download URL and fetch the output data separately.
|
|
3266
3348
|
"""
|
|
3267
3349
|
query_params = None
|
|
3268
|
-
|
|
3269
|
-
if
|
|
3350
|
+
use_presigned_url = False
|
|
3351
|
+
if (
|
|
3352
|
+
run_information.metadata.format.format_output.output_type != OutputFormat.JSON
|
|
3353
|
+
or run_information.metadata.output_size > _MAX_RUN_SIZE
|
|
3354
|
+
):
|
|
3270
3355
|
query_params = {"format": "url"}
|
|
3271
|
-
|
|
3356
|
+
use_presigned_url = True
|
|
3272
3357
|
|
|
3273
3358
|
response = self.client.request(
|
|
3274
3359
|
method="GET",
|
|
@@ -3278,7 +3363,7 @@ class Application:
|
|
|
3278
3363
|
result = RunResult.from_dict(response.json())
|
|
3279
3364
|
result.console_url = self.__console_url(result.id)
|
|
3280
3365
|
|
|
3281
|
-
if not
|
|
3366
|
+
if not use_presigned_url or result.metadata.status_v2 != StatusV2.succeeded:
|
|
3282
3367
|
return result
|
|
3283
3368
|
|
|
3284
3369
|
download_url = DownloadURL.from_dict(response.json()["output"])
|
|
@@ -3287,7 +3372,24 @@ class Application:
|
|
|
3287
3372
|
endpoint=download_url.url,
|
|
3288
3373
|
headers={"Content-Type": "application/json"},
|
|
3289
3374
|
)
|
|
3290
|
-
|
|
3375
|
+
|
|
3376
|
+
# See whether we can attach the output directly or need to save to the given
|
|
3377
|
+
# directory
|
|
3378
|
+
if run_information.metadata.format.format_output.output_type != OutputFormat.JSON:
|
|
3379
|
+
if not output_dir_path or output_dir_path == "":
|
|
3380
|
+
raise ValueError(
|
|
3381
|
+
"If the output format is not JSON, an output_dir_path must be provided.",
|
|
3382
|
+
)
|
|
3383
|
+
if not os.path.exists(output_dir_path):
|
|
3384
|
+
os.makedirs(output_dir_path, exist_ok=True)
|
|
3385
|
+
# Save .tar.gz file to a temp directory and extract contents to output_dir_path
|
|
3386
|
+
with tempfile.TemporaryDirectory() as tmpdirname:
|
|
3387
|
+
temp_tar_path = os.path.join(tmpdirname, f"{run_id}.tar.gz")
|
|
3388
|
+
with open(temp_tar_path, "wb") as f:
|
|
3389
|
+
f.write(download_response.content)
|
|
3390
|
+
shutil.unpack_archive(temp_tar_path, output_dir_path)
|
|
3391
|
+
else:
|
|
3392
|
+
result.output = download_response.json()
|
|
3291
3393
|
|
|
3292
3394
|
return result
|
|
3293
3395
|
|
|
@@ -3325,7 +3427,14 @@ class Application:
|
|
|
3325
3427
|
}
|
|
3326
3428
|
|
|
3327
3429
|
if manifest.configuration is not None and manifest.configuration.options is not None:
|
|
3328
|
-
|
|
3430
|
+
options = manifest.configuration.options.to_dict()
|
|
3431
|
+
if "format" in options and isinstance(options["format"], list):
|
|
3432
|
+
# the endpoint expects a dictionary with a template key having a list of strings
|
|
3433
|
+
# the app.yaml however defines format as a list of strings, so we need to convert it here
|
|
3434
|
+
options["format"] = {
|
|
3435
|
+
"template": options["format"],
|
|
3436
|
+
}
|
|
3437
|
+
activation_request["requirements"]["options"] = options
|
|
3329
3438
|
|
|
3330
3439
|
response = self.client.request(
|
|
3331
3440
|
method="PUT",
|