nextmv 0.23.0__py3-none-any.whl → 0.24.0__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/__entrypoint__.py +7 -10
- nextmv/__init__.py +3 -0
- nextmv/cloud/__init__.py +7 -1
- nextmv/cloud/application.py +541 -47
- nextmv/cloud/batch_experiment.py +58 -22
- nextmv/cloud/client.py +2 -0
- nextmv/cloud/input_set.py +26 -0
- nextmv/cloud/manifest.py +157 -8
- nextmv/cloud/run.py +8 -7
- nextmv/cloud/safe.py +83 -0
- nextmv/cloud/scenario.py +229 -0
- nextmv/deprecated.py +13 -0
- nextmv/input.py +74 -0
- nextmv/options.py +293 -78
- nextmv/output.py +64 -7
- {nextmv-0.23.0.dist-info → nextmv-0.24.0.dist-info}/METADATA +1 -1
- nextmv-0.24.0.dist-info/RECORD +30 -0
- nextmv-0.23.0.dist-info/RECORD +0 -27
- {nextmv-0.23.0.dist-info → nextmv-0.24.0.dist-info}/WHEEL +0 -0
- {nextmv-0.23.0.dist-info → nextmv-0.24.0.dist-info}/licenses/LICENSE +0 -0
nextmv/cloud/application.py
CHANGED
|
@@ -16,10 +16,21 @@ from nextmv.cloud import package
|
|
|
16
16
|
from nextmv.cloud.acceptance_test import AcceptanceTest, ExperimentStatus, Metric
|
|
17
17
|
from nextmv.cloud.batch_experiment import BatchExperiment, BatchExperimentMetadata, BatchExperimentRun
|
|
18
18
|
from nextmv.cloud.client import Client, get_size
|
|
19
|
-
from nextmv.cloud.input_set import InputSet
|
|
19
|
+
from nextmv.cloud.input_set import InputSet, ManagedInput
|
|
20
20
|
from nextmv.cloud.instance import Instance, InstanceConfiguration
|
|
21
21
|
from nextmv.cloud.manifest import Manifest
|
|
22
|
-
from nextmv.cloud.run import
|
|
22
|
+
from nextmv.cloud.run import (
|
|
23
|
+
ExternalRunResult,
|
|
24
|
+
Format,
|
|
25
|
+
FormatInput,
|
|
26
|
+
RunConfiguration,
|
|
27
|
+
RunInformation,
|
|
28
|
+
RunLog,
|
|
29
|
+
RunResult,
|
|
30
|
+
TrackedRun,
|
|
31
|
+
)
|
|
32
|
+
from nextmv.cloud.safe import name_and_id
|
|
33
|
+
from nextmv.cloud.scenario import Scenario, ScenarioInputType, _option_sets, _scenarios_by_id
|
|
23
34
|
from nextmv.cloud.secrets import Secret, SecretsCollection, SecretsCollectionSummary
|
|
24
35
|
from nextmv.cloud.status import StatusV2
|
|
25
36
|
from nextmv.cloud.version import Version
|
|
@@ -230,11 +241,15 @@ class Application:
|
|
|
230
241
|
Deletes a batch experiment, along with all the associated information,
|
|
231
242
|
such as its runs.
|
|
232
243
|
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
Parameters
|
|
245
|
+
----------
|
|
246
|
+
batch_id: str
|
|
247
|
+
ID of the batch experiment.
|
|
235
248
|
|
|
236
|
-
Raises
|
|
237
|
-
|
|
249
|
+
Raises
|
|
250
|
+
------
|
|
251
|
+
requests.HTTPError
|
|
252
|
+
If the response status code is not 2xx.
|
|
238
253
|
"""
|
|
239
254
|
|
|
240
255
|
_ = self.client.request(
|
|
@@ -242,6 +257,24 @@ class Application:
|
|
|
242
257
|
endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
|
|
243
258
|
)
|
|
244
259
|
|
|
260
|
+
def delete_scenario_test(self, scenario_test_id: str) -> None:
|
|
261
|
+
"""
|
|
262
|
+
Deletes a scenario test. Scenario tests are based on the batch
|
|
263
|
+
experiments API, so this function summons `delete_batch_experiment`.
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
scenario_test_id: str
|
|
268
|
+
ID of the scenario test.
|
|
269
|
+
|
|
270
|
+
Raises
|
|
271
|
+
------
|
|
272
|
+
requests.HTTPError
|
|
273
|
+
If the response status code is not 2xx.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
self.delete_batch_experiment(batch_id=scenario_test_id)
|
|
277
|
+
|
|
245
278
|
def delete_secrets_collection(self, secrets_collection_id: str) -> None:
|
|
246
279
|
"""
|
|
247
280
|
Deletes a secrets collection.
|
|
@@ -359,16 +392,21 @@ class Application:
|
|
|
359
392
|
"""
|
|
360
393
|
List all batch experiments.
|
|
361
394
|
|
|
362
|
-
Returns
|
|
395
|
+
Returns
|
|
396
|
+
-------
|
|
397
|
+
list[BatchExperimentMetadata]
|
|
363
398
|
List of batch experiments.
|
|
364
399
|
|
|
365
|
-
Raises
|
|
366
|
-
|
|
400
|
+
Raises
|
|
401
|
+
------
|
|
402
|
+
requests.HTTPError
|
|
403
|
+
If the response status code is not 2xx.
|
|
367
404
|
"""
|
|
368
405
|
|
|
369
406
|
response = self.client.request(
|
|
370
407
|
method="GET",
|
|
371
408
|
endpoint=f"{self.experiments_endpoint}/batch",
|
|
409
|
+
query_params={"type": "batch"},
|
|
372
410
|
)
|
|
373
411
|
|
|
374
412
|
return [BatchExperimentMetadata.from_dict(batch_experiment) for batch_experiment in response.json()]
|
|
@@ -409,6 +447,53 @@ class Application:
|
|
|
409
447
|
|
|
410
448
|
return [Instance.from_dict(instance) for instance in response.json()]
|
|
411
449
|
|
|
450
|
+
def list_managed_inputs(self) -> list[ManagedInput]:
|
|
451
|
+
"""
|
|
452
|
+
List all managed inputs.
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
list[ManagedInput]
|
|
457
|
+
List of managed inputs.
|
|
458
|
+
|
|
459
|
+
Raises
|
|
460
|
+
------
|
|
461
|
+
requests.HTTPError
|
|
462
|
+
If the response status code is not 2xx.
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
response = self.client.request(
|
|
466
|
+
method="GET",
|
|
467
|
+
endpoint=f"{self.endpoint}/inputs",
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
return [ManagedInput.from_dict(managed_input) for managed_input in response.json()]
|
|
471
|
+
|
|
472
|
+
def list_scenario_tests(self) -> list[BatchExperimentMetadata]:
|
|
473
|
+
"""
|
|
474
|
+
List all batch scenario tests. Scenario tests are based on the batch
|
|
475
|
+
experiments API, so this function returns the same information as
|
|
476
|
+
`list_batch_experiments`, albeit using a different query parameter.
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
list[BatchExperimentMetadata]
|
|
481
|
+
List of scenario tests.
|
|
482
|
+
|
|
483
|
+
Raises
|
|
484
|
+
------
|
|
485
|
+
requests.HTTPError
|
|
486
|
+
If the response status code is not 2xx.
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
response = self.client.request(
|
|
490
|
+
method="GET",
|
|
491
|
+
endpoint=f"{self.experiments_endpoint}/batch",
|
|
492
|
+
query_params={"type": "scenario"},
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
return [BatchExperimentMetadata.from_dict(batch_experiment) for batch_experiment in response.json()]
|
|
496
|
+
|
|
412
497
|
def list_secrets_collections(self) -> list[SecretsCollectionSummary]:
|
|
413
498
|
"""
|
|
414
499
|
List all secrets collections.
|
|
@@ -445,6 +530,33 @@ class Application:
|
|
|
445
530
|
|
|
446
531
|
return [Version.from_dict(version) for version in response.json()]
|
|
447
532
|
|
|
533
|
+
def managed_input(self, managed_input_id: str) -> ManagedInput:
|
|
534
|
+
"""
|
|
535
|
+
Get a managed input.
|
|
536
|
+
|
|
537
|
+
Parameters
|
|
538
|
+
----------
|
|
539
|
+
managed_input_id: str
|
|
540
|
+
ID of the managed input.
|
|
541
|
+
|
|
542
|
+
Returns
|
|
543
|
+
-------
|
|
544
|
+
ManagedInput
|
|
545
|
+
The managed input.
|
|
546
|
+
|
|
547
|
+
Raises
|
|
548
|
+
------
|
|
549
|
+
requests.HTTPError
|
|
550
|
+
If the response status code is not 2xx.
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
response = self.client.request(
|
|
554
|
+
method="GET",
|
|
555
|
+
endpoint=f"{self.endpoint}/inputs/{managed_input_id}",
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
return ManagedInput.from_dict(response.json())
|
|
559
|
+
|
|
448
560
|
@classmethod
|
|
449
561
|
def new(
|
|
450
562
|
cls,
|
|
@@ -640,37 +752,58 @@ class Application:
|
|
|
640
752
|
def new_batch_experiment(
|
|
641
753
|
self,
|
|
642
754
|
name: str,
|
|
643
|
-
input_set_id: str,
|
|
644
|
-
instance_ids: list[str] = None,
|
|
755
|
+
input_set_id: Optional[str] = None,
|
|
756
|
+
instance_ids: Optional[list[str]] = None,
|
|
645
757
|
description: Optional[str] = None,
|
|
646
758
|
id: Optional[str] = None,
|
|
647
759
|
option_sets: Optional[dict[str, dict[str, str]]] = None,
|
|
648
760
|
runs: Optional[list[Union[BatchExperimentRun, dict[str, Any]]]] = None,
|
|
761
|
+
type: Optional[str] = "batch",
|
|
649
762
|
) -> str:
|
|
650
763
|
"""
|
|
651
764
|
Create a new batch experiment.
|
|
652
765
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
766
|
+
Parameters
|
|
767
|
+
----------
|
|
768
|
+
name: str
|
|
769
|
+
Name of the batch experiment.
|
|
770
|
+
input_set_id: str
|
|
771
|
+
ID of the input set to use for the batch experiment.
|
|
772
|
+
instance_ids: list[str]
|
|
773
|
+
List of instance IDs to use for the batch experiment.
|
|
774
|
+
description: Optional[str]
|
|
775
|
+
Optional description of the batch experiment.
|
|
776
|
+
id: Optional[str]
|
|
777
|
+
ID of the batch experiment. Will be generated if not provided.
|
|
778
|
+
option_sets: Optional[dict[str, dict[str, str]]]
|
|
779
|
+
Option sets to use for the batch experiment. This is a dictionary
|
|
780
|
+
where the keys are option set IDs and the values are dictionaries
|
|
781
|
+
with the actual options.
|
|
782
|
+
runs: Optional[list[BatchExperimentRun]]
|
|
783
|
+
List of runs to use for the batch experiment.
|
|
784
|
+
type: Optional[str]
|
|
785
|
+
Type of the batch experiment. This is used to determine the
|
|
786
|
+
experiment type. The default value is "batch". If you want to
|
|
787
|
+
create a scenario test, set this to "scenario".
|
|
661
788
|
|
|
662
|
-
Returns
|
|
789
|
+
Returns
|
|
790
|
+
-------
|
|
791
|
+
str
|
|
663
792
|
ID of the batch experiment.
|
|
664
793
|
|
|
665
|
-
Raises
|
|
666
|
-
|
|
794
|
+
Raises
|
|
795
|
+
------
|
|
796
|
+
requests.HTTPError
|
|
797
|
+
If the response status code is not 2xx.
|
|
667
798
|
"""
|
|
668
799
|
|
|
669
800
|
payload = {
|
|
670
801
|
"name": name,
|
|
671
|
-
"input_set_id": input_set_id,
|
|
672
|
-
"instance_ids": instance_ids,
|
|
673
802
|
}
|
|
803
|
+
if input_set_id is not None:
|
|
804
|
+
payload["input_set_id"] = input_set_id
|
|
805
|
+
if instance_ids is not None:
|
|
806
|
+
payload["instance_ids"] = instance_ids
|
|
674
807
|
if description is not None:
|
|
675
808
|
payload["description"] = description
|
|
676
809
|
if id is not None:
|
|
@@ -682,6 +815,8 @@ class Application:
|
|
|
682
815
|
for i, run in enumerate(runs):
|
|
683
816
|
payload_runs[i] = run.to_dict() if isinstance(run, BatchExperimentRun) else run
|
|
684
817
|
payload["runs"] = payload_runs
|
|
818
|
+
if type is not None:
|
|
819
|
+
payload["type"] = type
|
|
685
820
|
|
|
686
821
|
response = self.client.request(
|
|
687
822
|
method="POST",
|
|
@@ -701,26 +836,61 @@ class Application:
|
|
|
701
836
|
maximum_runs: Optional[int] = None,
|
|
702
837
|
run_ids: Optional[list[str]] = None,
|
|
703
838
|
start_time: Optional[datetime] = None,
|
|
839
|
+
inputs: Optional[list[ManagedInput]] = None,
|
|
704
840
|
) -> InputSet:
|
|
705
841
|
"""
|
|
706
|
-
Create a new input set.
|
|
842
|
+
Create a new input set. You can create an input set from three
|
|
843
|
+
different methodologies:
|
|
707
844
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
start_time: Start time of the runs to construct the input set.
|
|
845
|
+
1. Using `instance_id`, `start_time`, `end_time` and `maximum_runs`.
|
|
846
|
+
Instance runs will be obtained from the application matching the
|
|
847
|
+
criteria of dates and maximum number of runs.
|
|
848
|
+
2. Using `run_ids`. The input set will be created using the list of
|
|
849
|
+
runs specified by the user.
|
|
850
|
+
3. Using `inputs`. The input set will be created using the list of
|
|
851
|
+
inputs specified by the user. This is useful for creating an input
|
|
852
|
+
set from a list of inputs that are already available in the
|
|
853
|
+
application.
|
|
718
854
|
|
|
719
|
-
|
|
720
|
-
|
|
855
|
+
Parameters
|
|
856
|
+
----------
|
|
857
|
+
id: str
|
|
858
|
+
ID of the input set
|
|
859
|
+
name: str
|
|
860
|
+
Name of the input set.
|
|
861
|
+
description: Optional[str]
|
|
862
|
+
Optional description of the input set.
|
|
863
|
+
end_time: Optional[datetime]
|
|
864
|
+
End time of the input set. This is used to filter the runs
|
|
865
|
+
associated with the input set.
|
|
866
|
+
instance_id: Optional[str]
|
|
867
|
+
ID of the instance to use for the input set. This is used to
|
|
868
|
+
filter the runs associated with the input set. If not provided,
|
|
869
|
+
the application’s `default_instance_id` is used.
|
|
870
|
+
maximum_runs: Optional[int]
|
|
871
|
+
Maximum number of runs to use for the input set. This is used to
|
|
872
|
+
filter the runs associated with the input set. If not provided,
|
|
873
|
+
all runs are used.
|
|
874
|
+
run_ids: Optional[list[str]]
|
|
875
|
+
List of run IDs to use for the input set.
|
|
876
|
+
start_time: Optional[datetime]
|
|
877
|
+
Start time of the input set. This is used to filter the runs
|
|
878
|
+
associated with the input set.
|
|
879
|
+
inputs: Optional[list[ExperimentInput]]
|
|
880
|
+
List of inputs to use for the input set. This is used to create
|
|
881
|
+
the input set from a list of inputs that are already available in
|
|
882
|
+
the application.
|
|
721
883
|
|
|
722
|
-
|
|
723
|
-
|
|
884
|
+
|
|
885
|
+
Returns
|
|
886
|
+
-------
|
|
887
|
+
InputSet
|
|
888
|
+
The new input set.
|
|
889
|
+
|
|
890
|
+
Raises
|
|
891
|
+
------
|
|
892
|
+
requests.HTTPError
|
|
893
|
+
If the response status code is not 2xx.
|
|
724
894
|
"""
|
|
725
895
|
|
|
726
896
|
payload = {
|
|
@@ -739,6 +909,8 @@ class Application:
|
|
|
739
909
|
payload["run_ids"] = run_ids
|
|
740
910
|
if start_time is not None:
|
|
741
911
|
payload["start_time"] = start_time.isoformat()
|
|
912
|
+
if inputs is not None:
|
|
913
|
+
payload["inputs"] = [input.to_dict() for input in inputs]
|
|
742
914
|
|
|
743
915
|
response = self.client.request(
|
|
744
916
|
method="POST",
|
|
@@ -794,6 +966,84 @@ class Application:
|
|
|
794
966
|
|
|
795
967
|
return Instance.from_dict(response.json())
|
|
796
968
|
|
|
969
|
+
def new_managed_input(
|
|
970
|
+
self,
|
|
971
|
+
id: str,
|
|
972
|
+
name: str,
|
|
973
|
+
description: Optional[str] = None,
|
|
974
|
+
upload_id: Optional[str] = None,
|
|
975
|
+
run_id: Optional[str] = None,
|
|
976
|
+
format: Optional[Union[Format, dict[str, any]]] = None,
|
|
977
|
+
) -> ManagedInput:
|
|
978
|
+
"""
|
|
979
|
+
Create a new managed input. There are two methods for creating a
|
|
980
|
+
managed input:
|
|
981
|
+
|
|
982
|
+
1. Specifying the `upload_id` parameter. You may use the `upload_url`
|
|
983
|
+
method to obtain the upload ID and the `upload_large_input` method
|
|
984
|
+
to upload the data to it.
|
|
985
|
+
2. Specifying the `run_id` parameter. The managed input will be
|
|
986
|
+
created from the run specified by the `run_id` parameter.
|
|
987
|
+
|
|
988
|
+
Either the `upload_id` or the `run_id` parameter must be specified.
|
|
989
|
+
|
|
990
|
+
Parameters
|
|
991
|
+
----------
|
|
992
|
+
id: str
|
|
993
|
+
ID of the managed input.
|
|
994
|
+
name: str
|
|
995
|
+
Name of the managed input.
|
|
996
|
+
description: Optional[str]
|
|
997
|
+
Optional description of the managed input.
|
|
998
|
+
upload_id: Optional[str]
|
|
999
|
+
ID of the upload to use for the managed input.
|
|
1000
|
+
run_id: Optional[str]
|
|
1001
|
+
ID of the run to use for the managed input.
|
|
1002
|
+
format: Optional[Format]
|
|
1003
|
+
Format of the managed input. Default will be formatted as `JSON`.
|
|
1004
|
+
|
|
1005
|
+
Returns
|
|
1006
|
+
-------
|
|
1007
|
+
ManagedInput
|
|
1008
|
+
The new managed input.
|
|
1009
|
+
|
|
1010
|
+
Raises
|
|
1011
|
+
------
|
|
1012
|
+
requests.HTTPError
|
|
1013
|
+
If the response status code is not 2xx.
|
|
1014
|
+
ValueError
|
|
1015
|
+
If neither the `upload_id` nor the `run_id` parameter is
|
|
1016
|
+
specified.
|
|
1017
|
+
"""
|
|
1018
|
+
|
|
1019
|
+
if upload_id is None and run_id is None:
|
|
1020
|
+
raise ValueError("Either upload_id or run_id must be specified")
|
|
1021
|
+
|
|
1022
|
+
payload = {
|
|
1023
|
+
"id": id,
|
|
1024
|
+
"name": name,
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if description is not None:
|
|
1028
|
+
payload["description"] = description
|
|
1029
|
+
if upload_id is not None:
|
|
1030
|
+
payload["upload_id"] = upload_id
|
|
1031
|
+
if run_id is not None:
|
|
1032
|
+
payload["run_id"] = run_id
|
|
1033
|
+
|
|
1034
|
+
if format is not None:
|
|
1035
|
+
payload["format"] = format.to_dict() if isinstance(format, Format) else format
|
|
1036
|
+
else:
|
|
1037
|
+
payload["format"] = Format(format_input=FormatInput(input_type=InputFormat.JSON)).to_dict()
|
|
1038
|
+
|
|
1039
|
+
response = self.client.request(
|
|
1040
|
+
method="POST",
|
|
1041
|
+
endpoint=f"{self.endpoint}/inputs",
|
|
1042
|
+
payload=payload,
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
return ManagedInput.from_dict(response.json())
|
|
1046
|
+
|
|
797
1047
|
def new_run( # noqa: C901 # Refactor this function at some point.
|
|
798
1048
|
self,
|
|
799
1049
|
input: Union[Input, dict[str, Any], BaseModel, str] = None,
|
|
@@ -893,11 +1143,11 @@ class Application:
|
|
|
893
1143
|
|
|
894
1144
|
options_dict = {}
|
|
895
1145
|
if isinstance(input, Input) and input.options is not None:
|
|
896
|
-
options_dict = input.options.
|
|
1146
|
+
options_dict = input.options.to_dict_cloud()
|
|
897
1147
|
|
|
898
1148
|
if options is not None:
|
|
899
1149
|
if isinstance(options, Options):
|
|
900
|
-
options_dict = options.
|
|
1150
|
+
options_dict = options.to_dict_cloud()
|
|
901
1151
|
elif isinstance(options, dict):
|
|
902
1152
|
for k, v in options.items():
|
|
903
1153
|
if isinstance(v, str):
|
|
@@ -1048,6 +1298,127 @@ class Application:
|
|
|
1048
1298
|
polling_options=polling_options,
|
|
1049
1299
|
)
|
|
1050
1300
|
|
|
1301
|
+
def new_scenario_test(
|
|
1302
|
+
self,
|
|
1303
|
+
id: str,
|
|
1304
|
+
name: str,
|
|
1305
|
+
scenarios: list[Scenario],
|
|
1306
|
+
description: Optional[str] = None,
|
|
1307
|
+
repetitions: Optional[int] = 0,
|
|
1308
|
+
) -> str:
|
|
1309
|
+
"""
|
|
1310
|
+
Create a new scenario test. The test is based on `scenarios` and you
|
|
1311
|
+
may specify `repetitions` to run the test multiple times. 0 repetitions
|
|
1312
|
+
means that the tests will be executed once. 1 repetition means that the
|
|
1313
|
+
test will be repeated once, i.e.: it will be executed twice. 2
|
|
1314
|
+
repetitions equals 3 executions, so on, and so forth.
|
|
1315
|
+
|
|
1316
|
+
For each scenario, consider the `scenario_input` and `configuration`.
|
|
1317
|
+
The `scenario_input.scenario_input_type` allows you to specify the data
|
|
1318
|
+
that will be used for that scenario.
|
|
1319
|
+
|
|
1320
|
+
- `ScenarioInputType.INPUT_SET`: the data should be taken from an
|
|
1321
|
+
existing input set.
|
|
1322
|
+
- `ScenarioInputType.INPUT`: the data should be taken from a list of
|
|
1323
|
+
existing inputs. When using this type, an input set will be created
|
|
1324
|
+
from this set of managed inputs.
|
|
1325
|
+
- `ScenarioInputType.New`: a new set of data will be uploaded as a set
|
|
1326
|
+
of managed inputs. A new input set will be created from this set of
|
|
1327
|
+
managed inputs.
|
|
1328
|
+
|
|
1329
|
+
On the other hand, the `configuration` allows you to specify multiple
|
|
1330
|
+
option variations for the scenario. Please see the
|
|
1331
|
+
`ScenarioConfiguration` class for more information.
|
|
1332
|
+
|
|
1333
|
+
The scenario tests uses the batch experiments API under the hood.
|
|
1334
|
+
|
|
1335
|
+
Parameters
|
|
1336
|
+
----------
|
|
1337
|
+
id: str
|
|
1338
|
+
ID of the scenario test.
|
|
1339
|
+
name: str
|
|
1340
|
+
Name of the scenario test.
|
|
1341
|
+
scenarios: list[Scenario]
|
|
1342
|
+
List of scenarios to use for the scenario test. At least one
|
|
1343
|
+
scenario should be provided.
|
|
1344
|
+
description: Optional[str]
|
|
1345
|
+
Optional description of the scenario test.
|
|
1346
|
+
repetitions: Optional[int]
|
|
1347
|
+
Number of repetitions to use for the scenario test. 0
|
|
1348
|
+
repetitions means that the tests will be executed once. 1
|
|
1349
|
+
repetition means that the test will be repeated once, i.e.: it
|
|
1350
|
+
will be executed twice. 2 repetitions equals 3 executions, so on,
|
|
1351
|
+
and so forth.
|
|
1352
|
+
|
|
1353
|
+
Returns
|
|
1354
|
+
-------
|
|
1355
|
+
str
|
|
1356
|
+
ID of the scenario test.
|
|
1357
|
+
|
|
1358
|
+
Raises
|
|
1359
|
+
------
|
|
1360
|
+
requests.HTTPError
|
|
1361
|
+
If the response status code is not 2xx.
|
|
1362
|
+
ValueError
|
|
1363
|
+
If no scenarios are provided.
|
|
1364
|
+
"""
|
|
1365
|
+
|
|
1366
|
+
if len(scenarios) < 1:
|
|
1367
|
+
raise ValueError("At least one scenario must be provided")
|
|
1368
|
+
|
|
1369
|
+
scenarios_by_id = _scenarios_by_id(scenarios)
|
|
1370
|
+
|
|
1371
|
+
# Save all the information needed by scenario.
|
|
1372
|
+
input_sets = {}
|
|
1373
|
+
instances = {}
|
|
1374
|
+
for scenario_id, scenario in scenarios_by_id.items():
|
|
1375
|
+
instance = self.instance(instance_id=scenario.instance_id)
|
|
1376
|
+
|
|
1377
|
+
# Each scenario is associated to an input set, so we must either
|
|
1378
|
+
# get it or create it.
|
|
1379
|
+
input_set = self.__input_set_for_scenario(scenario, scenario_id)
|
|
1380
|
+
|
|
1381
|
+
instances[scenario_id] = instance
|
|
1382
|
+
input_sets[scenario_id] = input_set
|
|
1383
|
+
|
|
1384
|
+
# Calculate the combinations of all the option sets across scenarios.
|
|
1385
|
+
opt_sets_by_scenario = _option_sets(scenarios)
|
|
1386
|
+
|
|
1387
|
+
# The scenario tests results in multiple individual runs.
|
|
1388
|
+
runs = []
|
|
1389
|
+
run_counter = 0
|
|
1390
|
+
opt_sets = {}
|
|
1391
|
+
for scenario_id, scenario_opt_sets in opt_sets_by_scenario.items():
|
|
1392
|
+
opt_sets = {**opt_sets, **scenario_opt_sets}
|
|
1393
|
+
input_set = input_sets[scenario_id]
|
|
1394
|
+
scenario = scenarios_by_id[scenario_id]
|
|
1395
|
+
|
|
1396
|
+
for set_key in scenario_opt_sets.keys():
|
|
1397
|
+
inputs = input_set.input_ids if len(input_set.input_ids) > 0 else input_set.inputs
|
|
1398
|
+
for input in inputs:
|
|
1399
|
+
input_id = input.id if isinstance(input, ManagedInput) else input
|
|
1400
|
+
for repetition in range(repetitions + 1):
|
|
1401
|
+
run_counter += 1
|
|
1402
|
+
run = BatchExperimentRun(
|
|
1403
|
+
input_id=input_id,
|
|
1404
|
+
input_set_id=input_set.id,
|
|
1405
|
+
instance_id=scenario.instance_id,
|
|
1406
|
+
option_set=set_key,
|
|
1407
|
+
scenario_id=scenario_id,
|
|
1408
|
+
repetition=repetition,
|
|
1409
|
+
run_number=f"{run_counter}",
|
|
1410
|
+
)
|
|
1411
|
+
runs.append(run)
|
|
1412
|
+
|
|
1413
|
+
return self.new_batch_experiment(
|
|
1414
|
+
id=id,
|
|
1415
|
+
name=name,
|
|
1416
|
+
description=description,
|
|
1417
|
+
type="scenario",
|
|
1418
|
+
option_sets=opt_sets,
|
|
1419
|
+
runs=runs,
|
|
1420
|
+
)
|
|
1421
|
+
|
|
1051
1422
|
def new_secrets_collection(
|
|
1052
1423
|
self,
|
|
1053
1424
|
secrets: list[Secret],
|
|
@@ -1201,12 +1572,12 @@ class Application:
|
|
|
1201
1572
|
|
|
1202
1573
|
|
|
1203
1574
|
# Define the options that the model needs.
|
|
1204
|
-
|
|
1575
|
+
opt = []
|
|
1205
1576
|
default_options = nextroute.Options()
|
|
1206
1577
|
for name, default_value in default_options.to_dict().items():
|
|
1207
|
-
|
|
1578
|
+
opt.append(nextmv.Option(name.lower(), type(default_value), default_value, name, False))
|
|
1208
1579
|
|
|
1209
|
-
options = nextmv.Options(*
|
|
1580
|
+
options = nextmv.Options(*opt)
|
|
1210
1581
|
|
|
1211
1582
|
# Instantiate the model and model configuration.
|
|
1212
1583
|
model = DecisionModel()
|
|
@@ -1326,7 +1697,10 @@ class Application:
|
|
|
1326
1697
|
endpoint=f"{self.endpoint}/runs/{run_id}/metadata",
|
|
1327
1698
|
)
|
|
1328
1699
|
|
|
1329
|
-
|
|
1700
|
+
info = RunInformation.from_dict(response.json())
|
|
1701
|
+
info.console_url = self.__console_url(info.id)
|
|
1702
|
+
|
|
1703
|
+
return info
|
|
1330
1704
|
|
|
1331
1705
|
def run_logs(self, run_id: str) -> RunLog:
|
|
1332
1706
|
"""
|
|
@@ -1401,6 +1775,30 @@ class Application:
|
|
|
1401
1775
|
|
|
1402
1776
|
return self.__run_result(run_id=run_id, run_information=run_information)
|
|
1403
1777
|
|
|
1778
|
+
def scenario_test(self, scenario_test_id: str) -> BatchExperiment:
|
|
1779
|
+
"""
|
|
1780
|
+
Get the scenario test. Scenario tests are based on batch experiments,
|
|
1781
|
+
so this function will return the corresponding batch experiment
|
|
1782
|
+
associated to the scenario test.
|
|
1783
|
+
|
|
1784
|
+
Parameters
|
|
1785
|
+
----------
|
|
1786
|
+
scenario_test_id : str
|
|
1787
|
+
ID of the scenario test.
|
|
1788
|
+
|
|
1789
|
+
Returns
|
|
1790
|
+
-------
|
|
1791
|
+
BatchExperiment
|
|
1792
|
+
The scenario test.
|
|
1793
|
+
|
|
1794
|
+
Raises
|
|
1795
|
+
------
|
|
1796
|
+
requests.HTTPError
|
|
1797
|
+
If the response status code is not 2xx.
|
|
1798
|
+
"""
|
|
1799
|
+
|
|
1800
|
+
return self.batch_experiment(batch_id=scenario_test_id)
|
|
1801
|
+
|
|
1404
1802
|
def track_run(self, tracked_run: TrackedRun) -> str:
|
|
1405
1803
|
"""
|
|
1406
1804
|
Track an external run.
|
|
@@ -1431,7 +1829,7 @@ class Application:
|
|
|
1431
1829
|
|
|
1432
1830
|
upload_input = tracked_run.input
|
|
1433
1831
|
if isinstance(tracked_run.input, Input):
|
|
1434
|
-
upload_input = tracked_run.input.
|
|
1832
|
+
upload_input = tracked_run.input.data
|
|
1435
1833
|
|
|
1436
1834
|
self.upload_large_input(input=upload_input, upload_url=url_input)
|
|
1437
1835
|
|
|
@@ -1546,6 +1944,45 @@ class Application:
|
|
|
1546
1944
|
|
|
1547
1945
|
return Instance.from_dict(response.json())
|
|
1548
1946
|
|
|
1947
|
+
def update_managed_input(
|
|
1948
|
+
self,
|
|
1949
|
+
managed_input_id: str,
|
|
1950
|
+
name: str,
|
|
1951
|
+
description: str,
|
|
1952
|
+
) -> None:
|
|
1953
|
+
"""
|
|
1954
|
+
Update a managed input.
|
|
1955
|
+
|
|
1956
|
+
Parameters
|
|
1957
|
+
----------
|
|
1958
|
+
managed_input_id : str
|
|
1959
|
+
ID of the managed input to update.
|
|
1960
|
+
name : str
|
|
1961
|
+
Name of the managed input.
|
|
1962
|
+
description : str
|
|
1963
|
+
Description of the managed input.
|
|
1964
|
+
|
|
1965
|
+
Returns
|
|
1966
|
+
-------
|
|
1967
|
+
None
|
|
1968
|
+
No return value.
|
|
1969
|
+
|
|
1970
|
+
Raises
|
|
1971
|
+
------
|
|
1972
|
+
requests.HTTPError
|
|
1973
|
+
If the response status code is not 2xx.
|
|
1974
|
+
"""
|
|
1975
|
+
|
|
1976
|
+
payload = {
|
|
1977
|
+
"name": name,
|
|
1978
|
+
"description": description,
|
|
1979
|
+
}
|
|
1980
|
+
_ = self.client.request(
|
|
1981
|
+
method="PUT",
|
|
1982
|
+
endpoint=f"{self.endpoint}/inputs/{managed_input_id}",
|
|
1983
|
+
payload=payload,
|
|
1984
|
+
)
|
|
1985
|
+
|
|
1549
1986
|
def update_secrets_collection(
|
|
1550
1987
|
self,
|
|
1551
1988
|
secrets_collection_id: str,
|
|
@@ -1605,7 +2042,7 @@ class Application:
|
|
|
1605
2042
|
if isinstance(input, dict):
|
|
1606
2043
|
input = json.dumps(input)
|
|
1607
2044
|
|
|
1608
|
-
|
|
2045
|
+
self.client.upload_to_presigned_url(
|
|
1609
2046
|
url=upload_url.upload_url,
|
|
1610
2047
|
data=input,
|
|
1611
2048
|
)
|
|
@@ -1702,6 +2139,8 @@ class Application:
|
|
|
1702
2139
|
query_params=query_params,
|
|
1703
2140
|
)
|
|
1704
2141
|
result = RunResult.from_dict(response.json())
|
|
2142
|
+
result.console_url = self.__console_url(result.id)
|
|
2143
|
+
|
|
1705
2144
|
if not large_output:
|
|
1706
2145
|
return result
|
|
1707
2146
|
|
|
@@ -1766,6 +2205,61 @@ class Application:
|
|
|
1766
2205
|
)
|
|
1767
2206
|
)
|
|
1768
2207
|
|
|
2208
|
+
def __console_url(self, run_id: str) -> str:
|
|
2209
|
+
"""Auxiliary method to get the console URL for a run."""
|
|
2210
|
+
|
|
2211
|
+
return f"{self.client.console_url}/app/{self.id}/run/{run_id}?view=details"
|
|
2212
|
+
|
|
2213
|
+
def __input_set_for_scenario(self, scenario: Scenario, scenario_id: str) -> InputSet:
|
|
2214
|
+
# If working with an input set, there is no need to create one.
|
|
2215
|
+
if scenario.scenario_input.scenario_input_type == ScenarioInputType.INPUT_SET:
|
|
2216
|
+
input_set = self.input_set(input_set_id=scenario.scenario_input.scenario_input_data)
|
|
2217
|
+
return input_set
|
|
2218
|
+
|
|
2219
|
+
# If working with a list of managed inputs, we need to create an
|
|
2220
|
+
# input set.
|
|
2221
|
+
if scenario.scenario_input.scenario_input_type == ScenarioInputType.INPUT:
|
|
2222
|
+
name, id = name_and_id(prefix="inpset", entity_id=scenario_id)
|
|
2223
|
+
input_set = self.new_input_set(
|
|
2224
|
+
id=id,
|
|
2225
|
+
name=name,
|
|
2226
|
+
description=f"Automatically created from scenario test: {id}",
|
|
2227
|
+
maximum_runs=20,
|
|
2228
|
+
inputs=[
|
|
2229
|
+
ManagedInput.from_dict(data={"id": input_id})
|
|
2230
|
+
for input_id in scenario.scenario_input.scenario_input_data
|
|
2231
|
+
],
|
|
2232
|
+
)
|
|
2233
|
+
return input_set
|
|
2234
|
+
|
|
2235
|
+
# If working with new data, we need to create managed inputs, and then,
|
|
2236
|
+
# an input set.
|
|
2237
|
+
if scenario.scenario_input.scenario_input_type == ScenarioInputType.NEW:
|
|
2238
|
+
managed_inputs = []
|
|
2239
|
+
for data in scenario.scenario_input.scenario_input_data:
|
|
2240
|
+
upload_url = self.upload_url()
|
|
2241
|
+
self.upload_large_input(input=data, upload_url=upload_url)
|
|
2242
|
+
name, id = name_and_id(prefix="man-input", entity_id=scenario_id)
|
|
2243
|
+
managed_input = self.new_managed_input(
|
|
2244
|
+
id=id,
|
|
2245
|
+
name=name,
|
|
2246
|
+
description=f"Automatically created from scenario test: {id}",
|
|
2247
|
+
upload_id=upload_url.upload_id,
|
|
2248
|
+
)
|
|
2249
|
+
managed_inputs.append(managed_input)
|
|
2250
|
+
|
|
2251
|
+
name, id = name_and_id(prefix="inpset", entity_id=scenario_id)
|
|
2252
|
+
input_set = self.new_input_set(
|
|
2253
|
+
id=id,
|
|
2254
|
+
name=name,
|
|
2255
|
+
description=f"Automatically created from scenario test: {id}",
|
|
2256
|
+
maximum_runs=20,
|
|
2257
|
+
inputs=managed_inputs,
|
|
2258
|
+
)
|
|
2259
|
+
return input_set
|
|
2260
|
+
|
|
2261
|
+
raise ValueError(f"Unknown scenario input type: {scenario.scenario_input.scenario_input_type}")
|
|
2262
|
+
|
|
1769
2263
|
|
|
1770
2264
|
def poll(polling_options: PollingOptions, polling_func: Callable[[], tuple[any, bool]]) -> any:
|
|
1771
2265
|
"""
|