nextmv 0.29.4.dev0__py3-none-any.whl → 0.29.5.dev0__py3-none-any.whl
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/__about__.py +1 -1
- nextmv/cloud/__init__.py +1 -0
- nextmv/cloud/application.py +189 -90
- nextmv/cloud/manifest.py +36 -2
- nextmv/cloud/run.py +98 -48
- nextmv/cloud/safe.py +30 -0
- nextmv/model.py +0 -1
- nextmv/options.py +14 -0
- {nextmv-0.29.4.dev0.dist-info → nextmv-0.29.5.dev0.dist-info}/METADATA +1 -1
- {nextmv-0.29.4.dev0.dist-info → nextmv-0.29.5.dev0.dist-info}/RECORD +12 -12
- {nextmv-0.29.4.dev0.dist-info → nextmv-0.29.5.dev0.dist-info}/WHEEL +0 -0
- {nextmv-0.29.4.dev0.dist-info → nextmv-0.29.5.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "v0.29.
|
|
1
|
+
__version__ = "v0.29.5.dev.0"
|
nextmv/cloud/__init__.py
CHANGED
|
@@ -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
|
nextmv/cloud/application.py
CHANGED
|
@@ -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
|
|
@@ -1588,7 +1589,10 @@ class Application:
|
|
|
1588
1589
|
if format is not None:
|
|
1589
1590
|
payload["format"] = format.to_dict() if isinstance(format, Format) else format
|
|
1590
1591
|
else:
|
|
1591
|
-
payload["format"] = Format(
|
|
1592
|
+
payload["format"] = Format(
|
|
1593
|
+
format_input=FormatInput(input_type=InputFormat.JSON),
|
|
1594
|
+
format_output=FormatOutput(output_type=OutputFormat.JSON),
|
|
1595
|
+
).to_dict()
|
|
1592
1596
|
|
|
1593
1597
|
response = self.client.request(
|
|
1594
1598
|
method="POST",
|
|
@@ -1610,7 +1614,7 @@ class Application:
|
|
|
1610
1614
|
batch_experiment_id: Optional[str] = None,
|
|
1611
1615
|
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1612
1616
|
json_configurations: Optional[dict[str, Any]] = None,
|
|
1613
|
-
|
|
1617
|
+
input_dir_path: Optional[str] = None,
|
|
1614
1618
|
) -> str:
|
|
1615
1619
|
"""
|
|
1616
1620
|
Submit an input to start a new run of the application. Returns the
|
|
@@ -1627,14 +1631,14 @@ class Application:
|
|
|
1627
1631
|
input data is extracted from the `.data` property.
|
|
1628
1632
|
|
|
1629
1633
|
If you want to work with `nextmv.InputFormat.CSV_ARCHIVE` or
|
|
1630
|
-
`nextmv.InputFormat.MULTI_FILE`, you should use the `
|
|
1634
|
+
`nextmv.InputFormat.MULTI_FILE`, you should use the `input_dir_path`
|
|
1631
1635
|
argument instead. This argument takes precedence over the `input`.
|
|
1632
|
-
If `
|
|
1636
|
+
If `input_dir_path` is specified, this function looks for files in that
|
|
1633
1637
|
directory and tars them, to later be uploaded using the
|
|
1634
|
-
`upload_large_input` method. If both the `
|
|
1638
|
+
`upload_large_input` method. If both the `input_dir_path` and `input`
|
|
1635
1639
|
arguments are provided, the `input` is ignored.
|
|
1636
1640
|
|
|
1637
|
-
When `
|
|
1641
|
+
When `input_dir_path` is specified, the `configuration` argument must
|
|
1638
1642
|
also be provided. More specifically, the
|
|
1639
1643
|
`RunConfiguration.format.format_input.input_type` parameter
|
|
1640
1644
|
dictates what kind of input is being submitted to the Nextmv Cloud.
|
|
@@ -1686,12 +1690,12 @@ class Application:
|
|
|
1686
1690
|
json_configurations: Optional[dict[str, Any]]
|
|
1687
1691
|
Optional configurations for JSON serialization. This is used to
|
|
1688
1692
|
customize the serialization before data is sent.
|
|
1689
|
-
|
|
1693
|
+
input_dir_path: Optional[str]
|
|
1690
1694
|
Path to a directory containing input files. If specified, the
|
|
1691
1695
|
function will package the files in the directory into a tar file
|
|
1692
1696
|
and upload it as a large input. This is useful for input formats
|
|
1693
1697
|
like `nextmv.InputFormat.CSV_ARCHIVE` or `nextmv.InputFormat.MULTI_FILE`.
|
|
1694
|
-
If both `input` and `
|
|
1698
|
+
If both `input` and `input_dir_path` are specified, the `input` is
|
|
1695
1699
|
ignored, and the files in the directory are used instead.
|
|
1696
1700
|
|
|
1697
1701
|
Returns
|
|
@@ -1708,17 +1712,17 @@ class Application:
|
|
|
1708
1712
|
not `JSON`. If the final `options` are not of type `dict[str,str]`.
|
|
1709
1713
|
"""
|
|
1710
1714
|
|
|
1711
|
-
self.__validate_dir_path_and_configuration(
|
|
1715
|
+
self.__validate_dir_path_and_configuration(input_dir_path, configuration)
|
|
1712
1716
|
|
|
1713
1717
|
tar_file = ""
|
|
1714
|
-
if
|
|
1715
|
-
if not os.path.exists(
|
|
1716
|
-
raise ValueError(f"Directory {
|
|
1718
|
+
if input_dir_path is not None and input_dir_path != "":
|
|
1719
|
+
if not os.path.exists(input_dir_path):
|
|
1720
|
+
raise ValueError(f"Directory {input_dir_path} does not exist.")
|
|
1717
1721
|
|
|
1718
|
-
if not os.path.isdir(
|
|
1719
|
-
raise ValueError(f"Path {
|
|
1722
|
+
if not os.path.isdir(input_dir_path):
|
|
1723
|
+
raise ValueError(f"Path {input_dir_path} is not a directory.")
|
|
1720
1724
|
|
|
1721
|
-
tar_file = self.__package_inputs(
|
|
1725
|
+
tar_file = self.__package_inputs(input_dir_path)
|
|
1722
1726
|
|
|
1723
1727
|
input_data = None
|
|
1724
1728
|
if isinstance(input, BaseModel):
|
|
@@ -1775,7 +1779,7 @@ class Application:
|
|
|
1775
1779
|
)
|
|
1776
1780
|
else:
|
|
1777
1781
|
configuration = RunConfiguration()
|
|
1778
|
-
configuration.resolve(input=input, dir_path=
|
|
1782
|
+
configuration.resolve(input=input, dir_path=input_dir_path)
|
|
1779
1783
|
configuration_dict = configuration.to_dict()
|
|
1780
1784
|
|
|
1781
1785
|
payload["configuration"] = configuration_dict
|
|
@@ -1814,7 +1818,8 @@ class Application:
|
|
|
1814
1818
|
batch_experiment_id: Optional[str] = None,
|
|
1815
1819
|
external_result: Optional[Union[ExternalRunResult, dict[str, Any]]] = None,
|
|
1816
1820
|
json_configurations: Optional[dict[str, Any]] = None,
|
|
1817
|
-
|
|
1821
|
+
input_dir_path: Optional[str] = None,
|
|
1822
|
+
output_dir_path: Optional[str] = ".",
|
|
1818
1823
|
) -> RunResult:
|
|
1819
1824
|
"""
|
|
1820
1825
|
Submit an input to start a new run of the application and poll for the
|
|
@@ -1833,14 +1838,14 @@ class Application:
|
|
|
1833
1838
|
input data is extracted from the `.data` property.
|
|
1834
1839
|
|
|
1835
1840
|
If you want to work with `nextmv.InputFormat.CSV_ARCHIVE` or
|
|
1836
|
-
`nextmv.InputFormat.MULTI_FILE`, you should use the `
|
|
1841
|
+
`nextmv.InputFormat.MULTI_FILE`, you should use the `input_dir_path`
|
|
1837
1842
|
argument instead. This argument takes precedence over the `input`.
|
|
1838
|
-
If `
|
|
1843
|
+
If `input_dir_path` is specified, this function looks for files in that
|
|
1839
1844
|
directory and tars them, to later be uploaded using the
|
|
1840
|
-
`upload_large_input` method. If both the `
|
|
1845
|
+
`upload_large_input` method. If both the `input_dir_path` and `input`
|
|
1841
1846
|
arguments are provided, the `input` is ignored.
|
|
1842
1847
|
|
|
1843
|
-
When `
|
|
1848
|
+
When `input_dir_path` is specified, the `configuration` argument must
|
|
1844
1849
|
also be provided. More specifically, the
|
|
1845
1850
|
`RunConfiguration.format.format_input.input_type` parameter
|
|
1846
1851
|
dictates what kind of input is being submitted to the Nextmv Cloud.
|
|
@@ -1897,13 +1902,17 @@ class Application:
|
|
|
1897
1902
|
json_configurations: Optional[dict[str, Any]]
|
|
1898
1903
|
Optional configurations for JSON serialization. This is used to
|
|
1899
1904
|
customize the serialization before data is sent.
|
|
1900
|
-
|
|
1905
|
+
input_dir_path: Optional[str]
|
|
1901
1906
|
Path to a directory containing input files. If specified, the
|
|
1902
1907
|
function will package the files in the directory into a tar file
|
|
1903
1908
|
and upload it as a large input. This is useful for input formats
|
|
1904
1909
|
like `nextmv.InputFormat.CSV_ARCHIVE` or `nextmv.InputFormat.MULTI_FILE`.
|
|
1905
|
-
If both `input` and `
|
|
1910
|
+
If both `input` and `input_dir_path` are specified, the `input` is
|
|
1906
1911
|
ignored, and the files in the directory are used instead.
|
|
1912
|
+
output_dir_path : Optional[str], default="."
|
|
1913
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
1914
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
1915
|
+
will be created. Uses the current directory by default.
|
|
1907
1916
|
|
|
1908
1917
|
Returns
|
|
1909
1918
|
----------
|
|
@@ -1936,12 +1945,13 @@ class Application:
|
|
|
1936
1945
|
batch_experiment_id=batch_experiment_id,
|
|
1937
1946
|
external_result=external_result,
|
|
1938
1947
|
json_configurations=json_configurations,
|
|
1939
|
-
|
|
1948
|
+
input_dir_path=input_dir_path,
|
|
1940
1949
|
)
|
|
1941
1950
|
|
|
1942
1951
|
return self.run_result_with_polling(
|
|
1943
1952
|
run_id=run_id,
|
|
1944
1953
|
polling_options=polling_options,
|
|
1954
|
+
output_dir_path=output_dir_path,
|
|
1945
1955
|
)
|
|
1946
1956
|
|
|
1947
1957
|
def new_scenario_test(
|
|
@@ -2215,10 +2225,13 @@ class Application:
|
|
|
2215
2225
|
if exist_ok and self.version_exists(version_id=id):
|
|
2216
2226
|
return self.version(version_id=id)
|
|
2217
2227
|
|
|
2218
|
-
|
|
2228
|
+
if id is None:
|
|
2229
|
+
id = _safe_id(prefix="version")
|
|
2230
|
+
|
|
2231
|
+
payload = {
|
|
2232
|
+
"id": id,
|
|
2233
|
+
}
|
|
2219
2234
|
|
|
2220
|
-
if id is not None:
|
|
2221
|
-
payload["id"] = id
|
|
2222
2235
|
if name is not None:
|
|
2223
2236
|
payload["name"] = name
|
|
2224
2237
|
if description is not None:
|
|
@@ -2501,7 +2514,7 @@ class Application:
|
|
|
2501
2514
|
)
|
|
2502
2515
|
return RunLog.from_dict(response.json())
|
|
2503
2516
|
|
|
2504
|
-
def run_result(self, run_id: str) -> RunResult:
|
|
2517
|
+
def run_result(self, run_id: str, output_dir_path: Optional[str] = ".") -> RunResult:
|
|
2505
2518
|
"""
|
|
2506
2519
|
Get the result of a run.
|
|
2507
2520
|
|
|
@@ -2511,6 +2524,10 @@ class Application:
|
|
|
2511
2524
|
----------
|
|
2512
2525
|
run_id : str
|
|
2513
2526
|
ID of the run to get results for.
|
|
2527
|
+
output_dir_path : Optional[str], default="."
|
|
2528
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
2529
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
2530
|
+
will be created. Uses the current directory by default.
|
|
2514
2531
|
|
|
2515
2532
|
Returns
|
|
2516
2533
|
-------
|
|
@@ -2531,12 +2548,17 @@ class Application:
|
|
|
2531
2548
|
|
|
2532
2549
|
run_information = self.run_metadata(run_id=run_id)
|
|
2533
2550
|
|
|
2534
|
-
return self.__run_result(
|
|
2551
|
+
return self.__run_result(
|
|
2552
|
+
run_id=run_id,
|
|
2553
|
+
run_information=run_information,
|
|
2554
|
+
output_dir_path=output_dir_path,
|
|
2555
|
+
)
|
|
2535
2556
|
|
|
2536
2557
|
def run_result_with_polling(
|
|
2537
2558
|
self,
|
|
2538
2559
|
run_id: str,
|
|
2539
2560
|
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
2561
|
+
output_dir_path: Optional[str] = ".",
|
|
2540
2562
|
) -> RunResult:
|
|
2541
2563
|
"""
|
|
2542
2564
|
Get the result of a run with polling.
|
|
@@ -2551,6 +2573,10 @@ class Application:
|
|
|
2551
2573
|
ID of the run to retrieve the result for.
|
|
2552
2574
|
polling_options : PollingOptions, default=_DEFAULT_POLLING_OPTIONS
|
|
2553
2575
|
Options to use when polling for the run result.
|
|
2576
|
+
output_dir_path : Optional[str], default="."
|
|
2577
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
2578
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
2579
|
+
will be created. Uses the current directory by default.
|
|
2554
2580
|
|
|
2555
2581
|
Returns
|
|
2556
2582
|
-------
|
|
@@ -2592,7 +2618,11 @@ class Application:
|
|
|
2592
2618
|
|
|
2593
2619
|
run_information = poll(polling_options=polling_options, polling_func=polling_func)
|
|
2594
2620
|
|
|
2595
|
-
return self.__run_result(
|
|
2621
|
+
return self.__run_result(
|
|
2622
|
+
run_id=run_id,
|
|
2623
|
+
run_information=run_information,
|
|
2624
|
+
output_dir_path=output_dir_path,
|
|
2625
|
+
)
|
|
2596
2626
|
|
|
2597
2627
|
def scenario_test(self, scenario_test_id: str) -> BatchExperiment:
|
|
2598
2628
|
"""
|
|
@@ -2707,6 +2737,7 @@ class Application:
|
|
|
2707
2737
|
tracked_run: TrackedRun,
|
|
2708
2738
|
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
2709
2739
|
instance_id: Optional[str] = None,
|
|
2740
|
+
output_dir_path: Optional[str] = ".",
|
|
2710
2741
|
) -> RunResult:
|
|
2711
2742
|
"""
|
|
2712
2743
|
Track an external run and poll for the result. This is a convenience
|
|
@@ -2723,6 +2754,10 @@ class Application:
|
|
|
2723
2754
|
instance_id: Optional[str]
|
|
2724
2755
|
Optional instance ID if you want to associate your tracked run with
|
|
2725
2756
|
an instance.
|
|
2757
|
+
output_dir_path : Optional[str], default="."
|
|
2758
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
2759
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
2760
|
+
will be created. Uses the current directory by default.
|
|
2726
2761
|
|
|
2727
2762
|
Returns
|
|
2728
2763
|
-------
|
|
@@ -2747,12 +2782,13 @@ class Application:
|
|
|
2747
2782
|
return self.run_result_with_polling(
|
|
2748
2783
|
run_id=run_id,
|
|
2749
2784
|
polling_options=polling_options,
|
|
2785
|
+
output_dir_path=output_dir_path,
|
|
2750
2786
|
)
|
|
2751
2787
|
|
|
2752
2788
|
def update_instance(
|
|
2753
2789
|
self,
|
|
2754
2790
|
id: str,
|
|
2755
|
-
name: str,
|
|
2791
|
+
name: Optional[str] = None,
|
|
2756
2792
|
version_id: Optional[str] = None,
|
|
2757
2793
|
description: Optional[str] = None,
|
|
2758
2794
|
configuration: Optional[InstanceConfiguration] = None,
|
|
@@ -2764,14 +2800,14 @@ class Application:
|
|
|
2764
2800
|
----------
|
|
2765
2801
|
id : str
|
|
2766
2802
|
ID of the instance to update.
|
|
2767
|
-
name : str
|
|
2768
|
-
|
|
2803
|
+
name : Optional[str], default=None
|
|
2804
|
+
Optional name of the instance.
|
|
2769
2805
|
version_id : Optional[str], default=None
|
|
2770
|
-
ID of the version to associate the instance with.
|
|
2806
|
+
Optional ID of the version to associate the instance with.
|
|
2771
2807
|
description : Optional[str], default=None
|
|
2772
|
-
|
|
2808
|
+
Optional description of the instance.
|
|
2773
2809
|
configuration : Optional[InstanceConfiguration], default=None
|
|
2774
|
-
|
|
2810
|
+
Optional configuration to use for the instance.
|
|
2775
2811
|
|
|
2776
2812
|
Returns
|
|
2777
2813
|
-------
|
|
@@ -2784,12 +2820,21 @@ class Application:
|
|
|
2784
2820
|
If the response status code is not 2xx.
|
|
2785
2821
|
"""
|
|
2786
2822
|
|
|
2787
|
-
|
|
2823
|
+
# Get the instance as it currently exsits.
|
|
2824
|
+
instance = self.instance(id)
|
|
2825
|
+
instance_dict = instance.to_dict()
|
|
2826
|
+
|
|
2827
|
+
payload = {
|
|
2828
|
+
"name": instance_dict["name"],
|
|
2829
|
+
"version_id": instance_dict["version_id"],
|
|
2830
|
+
"description": instance_dict["description"],
|
|
2831
|
+
"configuration": instance_dict["configuration"],
|
|
2832
|
+
}
|
|
2788
2833
|
|
|
2789
|
-
if version_id is not None:
|
|
2790
|
-
payload["version_id"] = version_id
|
|
2791
2834
|
if name is not None:
|
|
2792
2835
|
payload["name"] = name
|
|
2836
|
+
if version_id is not None:
|
|
2837
|
+
payload["version_id"] = version_id
|
|
2793
2838
|
if description is not None:
|
|
2794
2839
|
payload["description"] = description
|
|
2795
2840
|
if configuration is not None:
|
|
@@ -2806,8 +2851,8 @@ class Application:
|
|
|
2806
2851
|
def update_batch_experiment(
|
|
2807
2852
|
self,
|
|
2808
2853
|
batch_experiment_id: str,
|
|
2809
|
-
name: str,
|
|
2810
|
-
description: str,
|
|
2854
|
+
name: Optional[str] = None,
|
|
2855
|
+
description: Optional[str] = None,
|
|
2811
2856
|
) -> BatchExperimentInformation:
|
|
2812
2857
|
"""
|
|
2813
2858
|
Update a batch experiment.
|
|
@@ -2816,10 +2861,10 @@ class Application:
|
|
|
2816
2861
|
----------
|
|
2817
2862
|
batch_experiment_id : str
|
|
2818
2863
|
ID of the batch experiment to update.
|
|
2819
|
-
name : str
|
|
2820
|
-
|
|
2821
|
-
description : str
|
|
2822
|
-
|
|
2864
|
+
name : Optional[str], default=None
|
|
2865
|
+
Optional name of the batch experiment.
|
|
2866
|
+
description : Optional[str], default=None
|
|
2867
|
+
Optional description of the batch experiment.
|
|
2823
2868
|
|
|
2824
2869
|
Returns
|
|
2825
2870
|
-------
|
|
@@ -2832,10 +2877,13 @@ class Application:
|
|
|
2832
2877
|
If the response status code is not 2xx.
|
|
2833
2878
|
"""
|
|
2834
2879
|
|
|
2835
|
-
payload = {
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2880
|
+
payload = {}
|
|
2881
|
+
|
|
2882
|
+
if name is not None:
|
|
2883
|
+
payload["name"] = name
|
|
2884
|
+
if description is not None:
|
|
2885
|
+
payload["description"] = description
|
|
2886
|
+
|
|
2839
2887
|
response = self.client.request(
|
|
2840
2888
|
method="PATCH",
|
|
2841
2889
|
endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
|
|
@@ -2847,9 +2895,9 @@ class Application:
|
|
|
2847
2895
|
def update_managed_input(
|
|
2848
2896
|
self,
|
|
2849
2897
|
managed_input_id: str,
|
|
2850
|
-
name: str,
|
|
2851
|
-
description: str,
|
|
2852
|
-
) ->
|
|
2898
|
+
name: Optional[str] = None,
|
|
2899
|
+
description: Optional[str] = None,
|
|
2900
|
+
) -> ManagedInput:
|
|
2853
2901
|
"""
|
|
2854
2902
|
Update a managed input.
|
|
2855
2903
|
|
|
@@ -2857,15 +2905,15 @@ class Application:
|
|
|
2857
2905
|
----------
|
|
2858
2906
|
managed_input_id : str
|
|
2859
2907
|
ID of the managed input to update.
|
|
2860
|
-
name : str
|
|
2861
|
-
|
|
2862
|
-
description : str
|
|
2863
|
-
|
|
2908
|
+
name : Optional[str], default=None
|
|
2909
|
+
Optional new name for the managed input.
|
|
2910
|
+
description : Optional[str], default=None
|
|
2911
|
+
Optional new description for the managed input.
|
|
2864
2912
|
|
|
2865
2913
|
Returns
|
|
2866
2914
|
-------
|
|
2867
|
-
|
|
2868
|
-
|
|
2915
|
+
ManagedInput
|
|
2916
|
+
The updated managed input.
|
|
2869
2917
|
|
|
2870
2918
|
Raises
|
|
2871
2919
|
------
|
|
@@ -2873,21 +2921,32 @@ class Application:
|
|
|
2873
2921
|
If the response status code is not 2xx.
|
|
2874
2922
|
"""
|
|
2875
2923
|
|
|
2924
|
+
managed_input = self.managed_input(managed_input_id)
|
|
2925
|
+
managed_input_dict = managed_input.to_dict()
|
|
2926
|
+
|
|
2876
2927
|
payload = {
|
|
2877
|
-
"name": name,
|
|
2878
|
-
"description": description,
|
|
2928
|
+
"name": managed_input_dict["name"],
|
|
2929
|
+
"description": managed_input_dict["description"],
|
|
2879
2930
|
}
|
|
2880
|
-
|
|
2931
|
+
|
|
2932
|
+
if name is not None:
|
|
2933
|
+
payload["name"] = name
|
|
2934
|
+
if description is not None:
|
|
2935
|
+
payload["description"] = description
|
|
2936
|
+
|
|
2937
|
+
response = self.client.request(
|
|
2881
2938
|
method="PUT",
|
|
2882
2939
|
endpoint=f"{self.endpoint}/inputs/{managed_input_id}",
|
|
2883
2940
|
payload=payload,
|
|
2884
2941
|
)
|
|
2885
2942
|
|
|
2943
|
+
return ManagedInput.from_dict(response.json())
|
|
2944
|
+
|
|
2886
2945
|
def update_scenario_test(
|
|
2887
2946
|
self,
|
|
2888
2947
|
scenario_test_id: str,
|
|
2889
|
-
name: str,
|
|
2890
|
-
description: str,
|
|
2948
|
+
name: Optional[str] = None,
|
|
2949
|
+
description: Optional[str] = None,
|
|
2891
2950
|
) -> BatchExperimentInformation:
|
|
2892
2951
|
"""
|
|
2893
2952
|
Update a scenario test.
|
|
@@ -2900,10 +2959,10 @@ class Application:
|
|
|
2900
2959
|
----------
|
|
2901
2960
|
scenario_test_id : str
|
|
2902
2961
|
ID of the scenario test to update.
|
|
2903
|
-
name : str
|
|
2904
|
-
|
|
2905
|
-
description : str
|
|
2906
|
-
|
|
2962
|
+
name : Optional[str], default=None
|
|
2963
|
+
Optional new name for the scenario test.
|
|
2964
|
+
description : Optional[str], default=None
|
|
2965
|
+
Optional new description for the scenario test.
|
|
2907
2966
|
|
|
2908
2967
|
Returns
|
|
2909
2968
|
-------
|
|
@@ -2935,9 +2994,9 @@ class Application:
|
|
|
2935
2994
|
def update_secrets_collection(
|
|
2936
2995
|
self,
|
|
2937
2996
|
secrets_collection_id: str,
|
|
2938
|
-
name: str,
|
|
2939
|
-
description: str,
|
|
2940
|
-
secrets: list[Secret],
|
|
2997
|
+
name: Optional[str] = None,
|
|
2998
|
+
description: Optional[str] = None,
|
|
2999
|
+
secrets: Optional[list[Secret]] = None,
|
|
2941
3000
|
) -> SecretsCollectionSummary:
|
|
2942
3001
|
"""
|
|
2943
3002
|
Update a secrets collection.
|
|
@@ -2950,13 +3009,13 @@ class Application:
|
|
|
2950
3009
|
----------
|
|
2951
3010
|
secrets_collection_id : str
|
|
2952
3011
|
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.
|
|
3012
|
+
name : Optional[str], default=None
|
|
3013
|
+
Optional new name for the secrets collection.
|
|
3014
|
+
description : Optional[str], default=None
|
|
3015
|
+
Optional new description for the secrets collection.
|
|
3016
|
+
secrets : Optional[list[Secret]], default=None
|
|
3017
|
+
Optional list of secrets to update. Each secret should be an
|
|
3018
|
+
instance of the Secret class containing a key and value.
|
|
2960
3019
|
|
|
2961
3020
|
Returns
|
|
2962
3021
|
-------
|
|
@@ -2988,14 +3047,22 @@ class Application:
|
|
|
2988
3047
|
'api-secrets'
|
|
2989
3048
|
"""
|
|
2990
3049
|
|
|
2991
|
-
|
|
2992
|
-
|
|
3050
|
+
collection = self.secrets_collection(secrets_collection_id)
|
|
3051
|
+
collection_dict = collection.to_dict()
|
|
2993
3052
|
|
|
2994
3053
|
payload = {
|
|
2995
|
-
"name": name,
|
|
2996
|
-
"description": description,
|
|
2997
|
-
"secrets": [
|
|
3054
|
+
"name": collection_dict["name"],
|
|
3055
|
+
"description": collection_dict["description"],
|
|
3056
|
+
"secrets": collection_dict["secrets"],
|
|
2998
3057
|
}
|
|
3058
|
+
|
|
3059
|
+
if name is not None:
|
|
3060
|
+
payload["name"] = name
|
|
3061
|
+
if description is not None:
|
|
3062
|
+
payload["description"] = description
|
|
3063
|
+
if secrets is not None and len(secrets) > 0:
|
|
3064
|
+
payload["secrets"] = [secret.to_dict() for secret in secrets]
|
|
3065
|
+
|
|
2999
3066
|
response = self.client.request(
|
|
3000
3067
|
method="PUT",
|
|
3001
3068
|
endpoint=f"{self.endpoint}/secrets/{secrets_collection_id}",
|
|
@@ -3229,6 +3296,7 @@ class Application:
|
|
|
3229
3296
|
self,
|
|
3230
3297
|
run_id: str,
|
|
3231
3298
|
run_information: RunInformation,
|
|
3299
|
+
output_dir_path: Optional[str] = ".",
|
|
3232
3300
|
) -> RunResult:
|
|
3233
3301
|
"""
|
|
3234
3302
|
Get the result of a run.
|
|
@@ -3245,6 +3313,10 @@ class Application:
|
|
|
3245
3313
|
ID of the run to retrieve the result for.
|
|
3246
3314
|
run_information : RunInformation
|
|
3247
3315
|
Information about the run, including metadata such as output size.
|
|
3316
|
+
output_dir_path : Optional[str], default="."
|
|
3317
|
+
Path to a directory where non-JSON output files will be saved. This is
|
|
3318
|
+
required if the output is non-JSON. If the directory does not exist, it
|
|
3319
|
+
will be created. Uses the current directory by default.
|
|
3248
3320
|
|
|
3249
3321
|
Returns
|
|
3250
3322
|
-------
|
|
@@ -3265,10 +3337,13 @@ class Application:
|
|
|
3265
3337
|
a download URL and fetch the output data separately.
|
|
3266
3338
|
"""
|
|
3267
3339
|
query_params = None
|
|
3268
|
-
|
|
3269
|
-
if
|
|
3340
|
+
use_presigned_url = False
|
|
3341
|
+
if (
|
|
3342
|
+
run_information.metadata.format.format_output.output_type != OutputFormat.JSON
|
|
3343
|
+
or run_information.metadata.output_size > _MAX_RUN_SIZE
|
|
3344
|
+
):
|
|
3270
3345
|
query_params = {"format": "url"}
|
|
3271
|
-
|
|
3346
|
+
use_presigned_url = True
|
|
3272
3347
|
|
|
3273
3348
|
response = self.client.request(
|
|
3274
3349
|
method="GET",
|
|
@@ -3278,7 +3353,7 @@ class Application:
|
|
|
3278
3353
|
result = RunResult.from_dict(response.json())
|
|
3279
3354
|
result.console_url = self.__console_url(result.id)
|
|
3280
3355
|
|
|
3281
|
-
if not
|
|
3356
|
+
if not use_presigned_url or result.metadata.status_v2 != StatusV2.succeeded:
|
|
3282
3357
|
return result
|
|
3283
3358
|
|
|
3284
3359
|
download_url = DownloadURL.from_dict(response.json()["output"])
|
|
@@ -3287,7 +3362,24 @@ class Application:
|
|
|
3287
3362
|
endpoint=download_url.url,
|
|
3288
3363
|
headers={"Content-Type": "application/json"},
|
|
3289
3364
|
)
|
|
3290
|
-
|
|
3365
|
+
|
|
3366
|
+
# See whether we can attach the output directly or need to save to the given
|
|
3367
|
+
# directory
|
|
3368
|
+
if run_information.metadata.format.format_output.output_type != OutputFormat.JSON:
|
|
3369
|
+
if not output_dir_path or output_dir_path == "":
|
|
3370
|
+
raise ValueError(
|
|
3371
|
+
"If the output format is not JSON, an output_dir_path must be provided.",
|
|
3372
|
+
)
|
|
3373
|
+
if not os.path.exists(output_dir_path):
|
|
3374
|
+
os.makedirs(output_dir_path, exist_ok=True)
|
|
3375
|
+
# Save .tar.gz file to a temp directory and extract contents to output_dir_path
|
|
3376
|
+
with tempfile.TemporaryDirectory() as tmpdirname:
|
|
3377
|
+
temp_tar_path = os.path.join(tmpdirname, f"{run_id}.tar.gz")
|
|
3378
|
+
with open(temp_tar_path, "wb") as f:
|
|
3379
|
+
f.write(download_response.content)
|
|
3380
|
+
shutil.unpack_archive(temp_tar_path, output_dir_path)
|
|
3381
|
+
else:
|
|
3382
|
+
result.output = download_response.json()
|
|
3291
3383
|
|
|
3292
3384
|
return result
|
|
3293
3385
|
|
|
@@ -3325,7 +3417,14 @@ class Application:
|
|
|
3325
3417
|
}
|
|
3326
3418
|
|
|
3327
3419
|
if manifest.configuration is not None and manifest.configuration.options is not None:
|
|
3328
|
-
|
|
3420
|
+
options = manifest.configuration.options.to_dict()
|
|
3421
|
+
if "format" in options and isinstance(options["format"], list):
|
|
3422
|
+
# the endpoint expects a dictionary with a template key having a list of strings
|
|
3423
|
+
# the app.yaml however defines format as a list of strings, so we need to convert it here
|
|
3424
|
+
options["format"] = {
|
|
3425
|
+
"template": options["format"],
|
|
3426
|
+
}
|
|
3427
|
+
activation_request["requirements"]["options"] = options
|
|
3329
3428
|
|
|
3330
3429
|
response = self.client.request(
|
|
3331
3430
|
method="PUT",
|
nextmv/cloud/manifest.py
CHANGED
|
@@ -347,6 +347,9 @@ class ManifestOptionUI(BaseModel):
|
|
|
347
347
|
A list of team roles to which this option will be hidden in the UI. For
|
|
348
348
|
example, if you want to hide an option from the "operator" role, you can
|
|
349
349
|
pass `hidden_from=["operator"]`.
|
|
350
|
+
display_name : str, optional
|
|
351
|
+
An optional display name for the option. This is useful for making
|
|
352
|
+
the option more user-friendly in the UI.
|
|
350
353
|
|
|
351
354
|
Examples
|
|
352
355
|
--------
|
|
@@ -360,6 +363,10 @@ class ManifestOptionUI(BaseModel):
|
|
|
360
363
|
"""The type of control to use for the option in the Nextmv Cloud UI."""
|
|
361
364
|
hidden_from: Optional[list[str]] = None
|
|
362
365
|
"""A list of team roles for which this option will be hidden in the UI."""
|
|
366
|
+
display_name: Optional[str] = None
|
|
367
|
+
"""An optional display name for the option. This is useful for making
|
|
368
|
+
the option more user-friendly in the UI.
|
|
369
|
+
"""
|
|
363
370
|
|
|
364
371
|
|
|
365
372
|
class ManifestOption(BaseModel):
|
|
@@ -484,8 +491,9 @@ class ManifestOption(BaseModel):
|
|
|
484
491
|
ui=ManifestOptionUI(
|
|
485
492
|
control_type=option.control_type,
|
|
486
493
|
hidden_from=option.hidden_from,
|
|
494
|
+
display_name=option.display_name,
|
|
487
495
|
)
|
|
488
|
-
if option.control_type or option.hidden_from
|
|
496
|
+
if option.control_type or option.hidden_from or option.display_name
|
|
489
497
|
else None,
|
|
490
498
|
)
|
|
491
499
|
|
|
@@ -535,6 +543,7 @@ class ManifestOption(BaseModel):
|
|
|
535
543
|
additional_attributes=self.additional_attributes,
|
|
536
544
|
control_type=self.ui.control_type if self.ui else None,
|
|
537
545
|
hidden_from=self.ui.hidden_from if self.ui else None,
|
|
546
|
+
display_name=self.ui.display_name if self.ui else None,
|
|
538
547
|
)
|
|
539
548
|
|
|
540
549
|
|
|
@@ -593,6 +602,10 @@ class ManifestOptions(BaseModel):
|
|
|
593
602
|
is a parameter that configures the decision model.
|
|
594
603
|
validation: Optional[ManifestValidation], default=None
|
|
595
604
|
Optional validation rules for all options.
|
|
605
|
+
format: Optional[list[str]], default=None
|
|
606
|
+
A list of strings that define how options are transformed into command
|
|
607
|
+
line arguments. Use `{{name}}` to refer to the option name and
|
|
608
|
+
`{{value}}` to refer to the option value.
|
|
596
609
|
|
|
597
610
|
|
|
598
611
|
Examples
|
|
@@ -621,9 +634,21 @@ class ManifestOptions(BaseModel):
|
|
|
621
634
|
|
|
622
635
|
An option is a parameter that configures the decision model.
|
|
623
636
|
"""
|
|
637
|
+
format: Optional[list[str]] = None
|
|
638
|
+
"""A list of strings that define how options are transformed into command line arguments.
|
|
639
|
+
|
|
640
|
+
Use `{{name}}` to refer to the option name and `{{value}}` to refer to the option value.
|
|
641
|
+
For example, `["-{{name}}", "{{value}}"]` will transform an option named `max_vehicles`
|
|
642
|
+
with a value of `10` into the command line argument `-max_vehicles 10`.
|
|
643
|
+
"""
|
|
624
644
|
|
|
625
645
|
@classmethod
|
|
626
|
-
def from_options(
|
|
646
|
+
def from_options(
|
|
647
|
+
cls,
|
|
648
|
+
options: Options,
|
|
649
|
+
validation: OptionsEnforcement = None,
|
|
650
|
+
format: Optional[list[str]] = None,
|
|
651
|
+
) -> "ManifestOptions":
|
|
627
652
|
"""
|
|
628
653
|
Create a `ManifestOptions` from a `nextmv.Options`.
|
|
629
654
|
|
|
@@ -634,6 +659,14 @@ class ManifestOptions(BaseModel):
|
|
|
634
659
|
validation : Optional[OptionsEnforcement], default=None
|
|
635
660
|
Optional validation rules for the options. If provided, it will be
|
|
636
661
|
used to set the `validation` attribute of the `ManifestOptions`.
|
|
662
|
+
format : Optional[list[str]], default=None
|
|
663
|
+
A list of strings that define how options are transformed into
|
|
664
|
+
command line arguments. Use `{{name}}` to refer to the option name
|
|
665
|
+
and `{{value}}` to refer to the option value.
|
|
666
|
+
|
|
667
|
+
For example, `["-{{name}}", "{{value}}"]` will transform an option
|
|
668
|
+
named `max_vehicles` with a value of `10` into the command line
|
|
669
|
+
argument `-max_vehicles 10`.
|
|
637
670
|
|
|
638
671
|
Returns
|
|
639
672
|
-------
|
|
@@ -655,6 +688,7 @@ class ManifestOptions(BaseModel):
|
|
|
655
688
|
strict=validation.strict if validation else False,
|
|
656
689
|
validation=ManifestValidation(enforce="all" if validation and validation.validation_enforce else "none"),
|
|
657
690
|
items=items,
|
|
691
|
+
format=format,
|
|
658
692
|
)
|
|
659
693
|
|
|
660
694
|
|
nextmv/cloud/run.py
CHANGED
|
@@ -14,6 +14,8 @@ RunLog
|
|
|
14
14
|
Log of a run.
|
|
15
15
|
FormatInput
|
|
16
16
|
Input format for a run configuration.
|
|
17
|
+
FormatOutput
|
|
18
|
+
Output format for a run configuration.
|
|
17
19
|
Format
|
|
18
20
|
Format for a run configuration.
|
|
19
21
|
RunType
|
|
@@ -110,6 +112,83 @@ def run_duration(start: Union[datetime, float], end: Union[datetime, float]) ->
|
|
|
110
112
|
raise TypeError("Start and end must be either datetime or float.")
|
|
111
113
|
|
|
112
114
|
|
|
115
|
+
class FormatInput(BaseModel):
|
|
116
|
+
"""
|
|
117
|
+
Input format for a run configuration.
|
|
118
|
+
|
|
119
|
+
You can import the `FormatInput` class directly from `cloud`:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from nextmv.cloud import FormatInput
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
input_type : InputFormat, optional
|
|
128
|
+
Type of the input format. Defaults to `InputFormat.JSON`.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
input_type: InputFormat = Field(
|
|
132
|
+
serialization_alias="type",
|
|
133
|
+
validation_alias=AliasChoices("type", "input_type"),
|
|
134
|
+
default=InputFormat.JSON,
|
|
135
|
+
)
|
|
136
|
+
"""Type of the input format."""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class FormatOutput(BaseModel):
|
|
140
|
+
"""
|
|
141
|
+
Output format for a run configuration.
|
|
142
|
+
|
|
143
|
+
You can import the `FormatOutput` class directly from `cloud`:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from nextmv.cloud import FormatOutput
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
output_type : OutputFormat, optional
|
|
152
|
+
Type of the output format. Defaults to `OutputFormat.JSON`.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
output_type: OutputFormat = Field(
|
|
156
|
+
serialization_alias="type",
|
|
157
|
+
validation_alias=AliasChoices("type", "output_type"),
|
|
158
|
+
default=OutputFormat.JSON,
|
|
159
|
+
)
|
|
160
|
+
"""Type of the output format."""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class Format(BaseModel):
|
|
164
|
+
"""
|
|
165
|
+
Format for a run configuration.
|
|
166
|
+
|
|
167
|
+
You can import the `Format` class directly from `cloud`:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from nextmv.cloud import Format
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
format_input : FormatInput
|
|
176
|
+
Input format for the run configuration.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
format_input: FormatInput = Field(
|
|
180
|
+
serialization_alias="input",
|
|
181
|
+
validation_alias=AliasChoices("input", "format_input"),
|
|
182
|
+
)
|
|
183
|
+
"""Input format for the run configuration."""
|
|
184
|
+
format_output: Optional[FormatOutput] = Field(
|
|
185
|
+
serialization_alias="output",
|
|
186
|
+
validation_alias=AliasChoices("output", "format_output"),
|
|
187
|
+
default=None,
|
|
188
|
+
)
|
|
189
|
+
"""Output format for the run configuration."""
|
|
190
|
+
|
|
191
|
+
|
|
113
192
|
class Metadata(BaseModel):
|
|
114
193
|
"""
|
|
115
194
|
Metadata of a run, whether it was successful or not.
|
|
@@ -160,6 +239,8 @@ class Metadata(BaseModel):
|
|
|
160
239
|
"""Size of the input in bytes."""
|
|
161
240
|
output_size: float
|
|
162
241
|
"""Size of the output in bytes."""
|
|
242
|
+
format: Format
|
|
243
|
+
"""Format of the input and output of the run."""
|
|
163
244
|
status: Status
|
|
164
245
|
"""Deprecated: use status_v2."""
|
|
165
246
|
status_v2: StatusV2
|
|
@@ -279,53 +360,6 @@ class RunLog(BaseModel):
|
|
|
279
360
|
"""Log of the run."""
|
|
280
361
|
|
|
281
362
|
|
|
282
|
-
class FormatInput(BaseModel):
|
|
283
|
-
"""
|
|
284
|
-
Input format for a run configuration.
|
|
285
|
-
|
|
286
|
-
You can import the `FormatInput` class directly from `cloud`:
|
|
287
|
-
|
|
288
|
-
```python
|
|
289
|
-
from nextmv.cloud import FormatInput
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
Parameters
|
|
293
|
-
----------
|
|
294
|
-
input_type : InputFormat, optional
|
|
295
|
-
Type of the input format. Defaults to `InputFormat.JSON`.
|
|
296
|
-
"""
|
|
297
|
-
|
|
298
|
-
input_type: InputFormat = Field(
|
|
299
|
-
serialization_alias="type",
|
|
300
|
-
validation_alias=AliasChoices("type", "input_type"),
|
|
301
|
-
default=InputFormat.JSON,
|
|
302
|
-
)
|
|
303
|
-
"""Type of the input format."""
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
class Format(BaseModel):
|
|
307
|
-
"""
|
|
308
|
-
Format for a run configuration.
|
|
309
|
-
|
|
310
|
-
You can import the `Format` class directly from `cloud`:
|
|
311
|
-
|
|
312
|
-
```python
|
|
313
|
-
from nextmv.cloud import Format
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
Parameters
|
|
317
|
-
----------
|
|
318
|
-
format_input : FormatInput
|
|
319
|
-
Input format for the run configuration.
|
|
320
|
-
"""
|
|
321
|
-
|
|
322
|
-
format_input: FormatInput = Field(
|
|
323
|
-
serialization_alias="input",
|
|
324
|
-
validation_alias=AliasChoices("input", "format_input"),
|
|
325
|
-
)
|
|
326
|
-
"""Input format for the run configuration."""
|
|
327
|
-
|
|
328
|
-
|
|
329
363
|
class RunType(str, Enum):
|
|
330
364
|
"""
|
|
331
365
|
The actual type of the run.
|
|
@@ -491,7 +525,10 @@ class RunConfiguration(BaseModel):
|
|
|
491
525
|
if self.format is not None:
|
|
492
526
|
return
|
|
493
527
|
|
|
494
|
-
self.format = Format(
|
|
528
|
+
self.format = Format(
|
|
529
|
+
format_input=FormatInput(input_type=InputFormat.JSON),
|
|
530
|
+
format_output=FormatOutput(output_type=OutputFormat.JSON),
|
|
531
|
+
)
|
|
495
532
|
|
|
496
533
|
if isinstance(input, dict):
|
|
497
534
|
self.format.format_input.input_type = InputFormat.JSON
|
|
@@ -504,6 +541,19 @@ class RunConfiguration(BaseModel):
|
|
|
504
541
|
elif isinstance(input, Input):
|
|
505
542
|
self.format.format_input.input_type = input.input_format
|
|
506
543
|
|
|
544
|
+
# As input and output are symmetric, we set the output according to the input
|
|
545
|
+
# format.
|
|
546
|
+
if self.format.format_input.input_type == InputFormat.JSON:
|
|
547
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
|
|
548
|
+
elif self.format.format_input.input_type == InputFormat.TEXT: # Text still maps to json
|
|
549
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
|
|
550
|
+
elif self.format.format_input.input_type == InputFormat.CSV_ARCHIVE:
|
|
551
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.CSV_ARCHIVE)
|
|
552
|
+
elif self.format.format_input.input_type == InputFormat.MULTI_FILE:
|
|
553
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.MULTI_FILE)
|
|
554
|
+
else:
|
|
555
|
+
self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
|
|
556
|
+
|
|
507
557
|
|
|
508
558
|
class ExternalRunResult(BaseModel):
|
|
509
559
|
"""
|
nextmv/cloud/safe.py
CHANGED
|
@@ -81,3 +81,33 @@ def _name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
|
|
|
81
81
|
safe_name = _start_case(safe_id)
|
|
82
82
|
|
|
83
83
|
return safe_name, safe_id
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _safe_id(prefix: str) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Generate a safe ID from a prefix.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
prefix : str
|
|
93
|
+
Prefix to use for the ID.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
str
|
|
98
|
+
A safe ID.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
random_slug = _nanoid(8)
|
|
102
|
+
# Space available for user text once prefix, random slug and separator "-"
|
|
103
|
+
# are accounted for
|
|
104
|
+
safe_id_max = (
|
|
105
|
+
ENTITY_ID_CHAR_COUNT_MAX - INDEX_TAG_CHAR_COUNT - (len(random_slug) + 1) # +1 for the hyphen before the slug
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if len(prefix) > safe_id_max:
|
|
109
|
+
return prefix[: safe_id_max - 1] + f"-{random_slug}"
|
|
110
|
+
|
|
111
|
+
safe_id = f"{prefix}-{random_slug}"
|
|
112
|
+
|
|
113
|
+
return safe_id
|
nextmv/model.py
CHANGED
nextmv/options.py
CHANGED
|
@@ -227,6 +227,9 @@ class Option:
|
|
|
227
227
|
A list of team roles to which this option will be hidden in the UI. For
|
|
228
228
|
example, if you want to hide an option from the "operator" role, you can
|
|
229
229
|
pass `hidden_from=["operator"]`.
|
|
230
|
+
display_name : str, optional
|
|
231
|
+
An optional display name for the option. This is useful for making
|
|
232
|
+
the option more user-friendly in the UI.
|
|
230
233
|
|
|
231
234
|
Examples
|
|
232
235
|
--------
|
|
@@ -284,6 +287,11 @@ class Option:
|
|
|
284
287
|
example, if you want to hide an option from the "operator" role, you can
|
|
285
288
|
pass `hidden_from=["operator"]`.
|
|
286
289
|
"""
|
|
290
|
+
display_name: Optional[str] = None
|
|
291
|
+
"""
|
|
292
|
+
An optional display name for the option. This is useful for making
|
|
293
|
+
the option more user-friendly in the UI.
|
|
294
|
+
"""
|
|
287
295
|
|
|
288
296
|
@classmethod
|
|
289
297
|
def from_dict(cls, data: dict[str, Any]) -> "Option":
|
|
@@ -324,6 +332,7 @@ class Option:
|
|
|
324
332
|
additional_attributes=data.get("additional_attributes"),
|
|
325
333
|
control_type=data.get("control_type"),
|
|
326
334
|
hidden_from=data.get("hidden_from"),
|
|
335
|
+
display_name=data.get("display_name"),
|
|
327
336
|
)
|
|
328
337
|
|
|
329
338
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -355,6 +364,7 @@ class Option:
|
|
|
355
364
|
"additional_attributes": self.additional_attributes,
|
|
356
365
|
"control_type": self.control_type,
|
|
357
366
|
"hidden_from": self.hidden_from,
|
|
367
|
+
"display_name": self.display_name,
|
|
358
368
|
}
|
|
359
369
|
|
|
360
370
|
|
|
@@ -980,6 +990,9 @@ class Options:
|
|
|
980
990
|
if isinstance(option, Option) and option.hidden_from:
|
|
981
991
|
description += f" (hidden from: {', '.join(option.hidden_from)})"
|
|
982
992
|
|
|
993
|
+
if isinstance(option, Option) and option.display_name is not None:
|
|
994
|
+
description += f" (display name: {option.display_name})"
|
|
995
|
+
|
|
983
996
|
if option.description is not None and option.description != "":
|
|
984
997
|
description += f": {option.description}"
|
|
985
998
|
|
|
@@ -1051,6 +1064,7 @@ class Options:
|
|
|
1051
1064
|
else:
|
|
1052
1065
|
raise TypeError(f"expected an <Option> (or deprecated <Parameter>) object, but got {type(option)}")
|
|
1053
1066
|
|
|
1067
|
+
|
|
1054
1068
|
class OptionsEnforcement:
|
|
1055
1069
|
"""
|
|
1056
1070
|
OptionsEnforcement is a class that provides rules for how the options
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
nextmv/__about__.py,sha256=
|
|
1
|
+
nextmv/__about__.py,sha256=fH2N4VgXGHNLupMl8aEtSxm93zKJU7HumfTMlmPMKeE,30
|
|
2
2
|
nextmv/__entrypoint__.py,sha256=dA0iwwHtrq6Z9w9FxmxKLoBGLyhe7jWtUAU-Y3PEgHg,1094
|
|
3
3
|
nextmv/__init__.py,sha256=FsF0pEkOSBuPY5EKu7NsBxro7jswGmOmaw61kZEudXY,1930
|
|
4
4
|
nextmv/_serialization.py,sha256=JlSl6BL0M2Esf7F89GsGIZ__Pp8RnFRNM0UxYhuuYU4,2853
|
|
@@ -6,21 +6,21 @@ nextmv/base_model.py,sha256=qmJ4AsYr9Yv01HQX_BERrn3229gyoZrYyP9tcyqNfeU,2311
|
|
|
6
6
|
nextmv/deprecated.py,sha256=kEVfyQ-nT0v2ePXTNldjQG9uH5IlfQVy3L4tztIxwmU,1638
|
|
7
7
|
nextmv/input.py,sha256=iTMIdhSi4H-Xot44CYaUH110WDcpWDsJ5JXxSMGIZaY,40030
|
|
8
8
|
nextmv/logger.py,sha256=kNIbu46MisrzYe4T0hNMpWfRTKKacDVvbtQcNys_c_E,2513
|
|
9
|
-
nextmv/model.py,sha256=
|
|
10
|
-
nextmv/options.py,sha256
|
|
9
|
+
nextmv/model.py,sha256=vI3pSV3iTwjRPflar7nAg-6h98XRUyi9II5O2J06-Kc,15018
|
|
10
|
+
nextmv/options.py,sha256=yPJu5lYMbV6YioMwAXv7ctpZUggLXKlZc9CqIbUFvE4,37895
|
|
11
11
|
nextmv/output.py,sha256=vcBqtP1hJLYyUvfk_vl1Ejbk3q5KxOmlTz4Lnqqnyd0,54141
|
|
12
|
-
nextmv/cloud/__init__.py,sha256=
|
|
12
|
+
nextmv/cloud/__init__.py,sha256=cGRkIcn1evTdgw7lEH2k4MPTZnq37XIF3ovbuDjUWGI,3914
|
|
13
13
|
nextmv/cloud/acceptance_test.py,sha256=Bcfdmh2fkPeBx8FDCngeUo2fjV_LhsUdygnzDQCDbYY,26898
|
|
14
14
|
nextmv/cloud/account.py,sha256=eukiYQha4U2fkIjg4SgdoawKE1kU5G7GPyDJVrn8hHA,6064
|
|
15
|
-
nextmv/cloud/application.py,sha256=
|
|
15
|
+
nextmv/cloud/application.py,sha256=P5LYXNYlaJYTZiCbJxxG3pvT6KLHS8ZK8HO1R46Lju4,130127
|
|
16
16
|
nextmv/cloud/batch_experiment.py,sha256=Rmcwe1uAVz2kRrAMQqLYC5d__L_IqPXbF__RE3uQTAY,8196
|
|
17
17
|
nextmv/cloud/client.py,sha256=E0DiUb377jvEnpXlRnfT1PGCI0Jm0lTUoX5VqeU91lk,18165
|
|
18
18
|
nextmv/cloud/input_set.py,sha256=2dqmf5z-rZjTKwtBRvnUdfPfKv28It5uTCX0C70uP4Y,4242
|
|
19
19
|
nextmv/cloud/instance.py,sha256=SS4tbp0LQMWDaeYpwcNxJei82oi_Hozv1t5i3QGjASY,4024
|
|
20
|
-
nextmv/cloud/manifest.py,sha256=
|
|
20
|
+
nextmv/cloud/manifest.py,sha256=5FqXtT1q7qmyQxqB5rG33y97WFZ6erL1f9kRel575RQ,37456
|
|
21
21
|
nextmv/cloud/package.py,sha256=f0OjdlIOsI2LpmgSxdFf6YaA8Ucs9yAm_3bO0Cp8LH4,13027
|
|
22
|
-
nextmv/cloud/run.py,sha256
|
|
23
|
-
nextmv/cloud/safe.py,sha256=
|
|
22
|
+
nextmv/cloud/run.py,sha256=-I1S5fLIFLH1P9xViv5bOS2LbO9-w6tnB8cBGU0_yTo,22743
|
|
23
|
+
nextmv/cloud/safe.py,sha256=hWh_quD2KSazTV5SOAddOsFLsY4oWBzajOUcCeHOjc8,3180
|
|
24
24
|
nextmv/cloud/scenario.py,sha256=JRFTDiFBcrgud6wE2qDHUu5oO-Ur3zbPYhhB6ONCxTo,14263
|
|
25
25
|
nextmv/cloud/secrets.py,sha256=fA5cX0jfTsPVZWV7433wzETGlXpWRLHGswuObx9e6FQ,6820
|
|
26
26
|
nextmv/cloud/status.py,sha256=blvykRCTCTBkaqH88j4dzdQLhU2v1Ig62-_va98zw20,2789
|
|
@@ -31,7 +31,7 @@ nextmv/default_app/requirements.txt,sha256=wRE_HkYYWzCGnYZ2NuatHXul4gCHvU3iUAdsx
|
|
|
31
31
|
nextmv/default_app/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
32
|
nextmv/default_app/src/main.py,sha256=HlO8UwZbZYiAQyrZDSR_T4NBcwP1gIjdTdwkJP_dn8I,847
|
|
33
33
|
nextmv/default_app/src/visuals.py,sha256=WYK_YBnLmYo3TpVev1CpoNCuW5R7hk9QIkeCmvMn1Fs,1014
|
|
34
|
-
nextmv-0.29.
|
|
35
|
-
nextmv-0.29.
|
|
36
|
-
nextmv-0.29.
|
|
37
|
-
nextmv-0.29.
|
|
34
|
+
nextmv-0.29.5.dev0.dist-info/METADATA,sha256=JOL30ADklPJ0SrmbxPzQvq1J8x15ETOJJU1H5J6amHg,15831
|
|
35
|
+
nextmv-0.29.5.dev0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
36
|
+
nextmv-0.29.5.dev0.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
|
|
37
|
+
nextmv-0.29.5.dev0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|