nextmv 0.31.0__tar.gz → 0.33.0.dev0__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.
Files changed (89) hide show
  1. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/PKG-INFO +1 -1
  2. nextmv-0.33.0.dev0/nextmv/__about__.py +1 -0
  3. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/__init__.py +4 -9
  4. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/__init__.py +7 -0
  5. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/acceptance_test.py +60 -6
  6. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/application.py +274 -49
  7. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/batch_experiment.py +7 -1
  8. nextmv-0.33.0.dev0/nextmv/cloud/ensemble.py +248 -0
  9. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/default_app/README.md +17 -2
  10. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/default_app/app.yaml +1 -2
  11. nextmv-0.33.0.dev0/nextmv/default_app/input.json +5 -0
  12. nextmv-0.33.0.dev0/nextmv/default_app/main.py +37 -0
  13. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/input.py +2 -8
  14. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/local/application.py +250 -224
  15. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/local/executor.py +9 -13
  16. nextmv-0.33.0.dev0/nextmv/local/local.py +97 -0
  17. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/local/runner.py +3 -41
  18. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/output.py +6 -26
  19. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/run.py +476 -108
  20. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/local/test_application.py +2 -1
  21. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/local/test_executor.py +1 -5
  22. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/local/test_runner.py +3 -2
  23. nextmv-0.33.0.dev0/tests/test_run.py +208 -0
  24. nextmv-0.31.0/nextmv/__about__.py +0 -1
  25. nextmv-0.31.0/tests/test_run.py +0 -18
  26. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/.gitignore +0 -0
  27. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/LICENSE +0 -0
  28. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/README.md +0 -0
  29. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/__entrypoint__.py +0 -0
  30. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/_serialization.py +0 -0
  31. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/base_model.py +0 -0
  32. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/account.py +0 -0
  33. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/client.py +0 -0
  34. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/input_set.py +0 -0
  35. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/instance.py +0 -0
  36. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/package.py +0 -0
  37. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/scenario.py +0 -0
  38. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/secrets.py +0 -0
  39. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/url.py +0 -0
  40. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/cloud/version.py +0 -0
  41. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/default_app/.gitignore +0 -0
  42. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/default_app/requirements.txt +0 -0
  43. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/default_app/src/__init__.py +0 -0
  44. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/default_app/src/main.py +0 -0
  45. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/default_app/src/visuals.py +0 -0
  46. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/deprecated.py +0 -0
  47. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/local/__init__.py +0 -0
  48. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/local/geojson_handler.py +0 -0
  49. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/local/plotly_handler.py +0 -0
  50. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/logger.py +0 -0
  51. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/manifest.py +0 -0
  52. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/model.py +0 -0
  53. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/options.py +0 -0
  54. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/polling.py +0 -0
  55. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/safe.py +0 -0
  56. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/nextmv/status.py +0 -0
  57. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/pyproject.toml +0 -0
  58. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/__init__.py +0 -0
  59. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/cloud/__init__.py +0 -0
  60. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/cloud/app.yaml +0 -0
  61. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/cloud/test_client.py +0 -0
  62. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/cloud/test_package.py +0 -0
  63. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/cloud/test_scenario.py +0 -0
  64. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/local/__init__.py +0 -0
  65. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/__init__.py +0 -0
  66. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options1.py +0 -0
  67. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options2.py +0 -0
  68. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options3.py +0 -0
  69. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options4.py +0 -0
  70. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options5.py +0 -0
  71. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options6.py +0 -0
  72. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options7.py +0 -0
  73. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/scripts/options_deprecated.py +0 -0
  74. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_base_model.py +0 -0
  75. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_entrypoint/__init__.py +0 -0
  76. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_entrypoint/test_entrypoint.py +0 -0
  77. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_input.py +0 -0
  78. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_inputs/test_data.csv +0 -0
  79. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_inputs/test_data.json +0 -0
  80. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_inputs/test_data.txt +0 -0
  81. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_logger.py +0 -0
  82. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_manifest.py +0 -0
  83. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_model.py +0 -0
  84. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_options.py +0 -0
  85. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_output.py +0 -0
  86. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_polling.py +0 -0
  87. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_safe.py +0 -0
  88. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_serialization.py +0 -0
  89. {nextmv-0.31.0 → nextmv-0.33.0.dev0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 0.31.0
3
+ Version: 0.33.0.dev0
4
4
  Summary: The all-purpose Python SDK for Nextmv
5
5
  Project-URL: Homepage, https://www.nextmv.io
6
6
  Project-URL: Documentation, https://nextmv-py.docs.nextmv.io/en/latest/nextmv/
@@ -0,0 +1 @@
1
+ __version__ = "v0.33.0.dev0"
@@ -3,8 +3,6 @@
3
3
  from .__about__ import __version__
4
4
  from .base_model import BaseModel as BaseModel
5
5
  from .base_model import from_dict as from_dict
6
- from .input import DEFAULT_INPUT_JSON_FILE as DEFAULT_INPUT_JSON_FILE
7
- from .input import INPUTS_KEY as INPUTS_KEY
8
6
  from .input import DataFile as DataFile
9
7
  from .input import Input as Input
10
8
  from .input import InputFormat as InputFormat
@@ -31,13 +29,6 @@ from .model import ModelConfiguration as ModelConfiguration
31
29
  from .options import Option as Option
32
30
  from .options import Options as Options
33
31
  from .options import Parameter as Parameter
34
- from .output import ASSETS_KEY as ASSETS_KEY
35
- from .output import DEFAULT_OUTPUT_JSON_FILE as DEFAULT_OUTPUT_JSON_FILE
36
- from .output import LOGS_FILE as LOGS_FILE
37
- from .output import LOGS_KEY as LOGS_KEY
38
- from .output import OUTPUTS_KEY as OUTPUTS_KEY
39
- from .output import SOLUTIONS_KEY as SOLUTIONS_KEY
40
- from .output import STATISTICS_KEY as STATISTICS_KEY
41
32
  from .output import Asset as Asset
42
33
  from .output import DataPoint as DataPoint
43
34
  from .output import LocalOutputWriter as LocalOutputWriter
@@ -66,13 +57,17 @@ from .run import Format as Format
66
57
  from .run import FormatInput as FormatInput
67
58
  from .run import FormatOutput as FormatOutput
68
59
  from .run import Metadata as Metadata
60
+ from .run import OptionsSummaryItem as OptionsSummaryItem
61
+ from .run import Run as Run
69
62
  from .run import RunConfiguration as RunConfiguration
70
63
  from .run import RunInformation as RunInformation
64
+ from .run import RunInfoStatistics as RunInfoStatistics
71
65
  from .run import RunLog as RunLog
72
66
  from .run import RunQueuing as RunQueuing
73
67
  from .run import RunResult as RunResult
74
68
  from .run import RunType as RunType
75
69
  from .run import RunTypeConfiguration as RunTypeConfiguration
70
+ from .run import StatisticsIndicator as StatisticsIndicator
76
71
  from .run import TrackedRun as TrackedRun
77
72
  from .run import TrackedRunStatus as TrackedRunStatus
78
73
  from .run import run_duration as run_duration
@@ -47,6 +47,7 @@ from .acceptance_test import MetricParams as MetricParams
47
47
  from .acceptance_test import MetricResult as MetricResult
48
48
  from .acceptance_test import MetricStatistics as MetricStatistics
49
49
  from .acceptance_test import MetricTolerance as MetricTolerance
50
+ from .acceptance_test import MetricToleranceType as MetricToleranceType
50
51
  from .acceptance_test import MetricType as MetricType
51
52
  from .acceptance_test import ResultStatistics as ResultStatistics
52
53
  from .acceptance_test import StatisticType as StatisticType
@@ -63,6 +64,12 @@ from .batch_experiment import BatchExperimentRun as BatchExperimentRun
63
64
  from .batch_experiment import ExperimentStatus as ExperimentStatus
64
65
  from .client import Client as Client
65
66
  from .client import get_size as get_size
67
+ from .ensemble import EnsembleDefinition as EnsembleDefinition
68
+ from .ensemble import EvaluationRule as EvaluationRule
69
+ from .ensemble import RuleObjective as RuleObjective
70
+ from .ensemble import RuleTolerance as RuleTolerance
71
+ from .ensemble import RuleToleranceType as RuleToleranceType
72
+ from .ensemble import RunGroup as RunGroup
66
73
  from .input_set import InputSet as InputSet
67
74
  from .input_set import ManagedInput as ManagedInput
68
75
  from .instance import Instance as Instance
@@ -47,6 +47,7 @@ from typing import Optional
47
47
 
48
48
  from nextmv.base_model import BaseModel
49
49
  from nextmv.cloud.batch_experiment import ExperimentStatus
50
+ from nextmv.deprecated import deprecated
50
51
 
51
52
 
52
53
  class MetricType(str, Enum):
@@ -214,6 +215,9 @@ class Comparison(str, Enum):
214
215
 
215
216
  class ToleranceType(str, Enum):
216
217
  """
218
+ !!! warning
219
+ `ToleranceType` is deprecated, use `MetricToleranceType` instead.
220
+
217
221
  Type of tolerance used for a metric.
218
222
 
219
223
  You can import the `ToleranceType` class directly from `cloud`:
@@ -242,6 +246,57 @@ class ToleranceType(str, Enum):
242
246
  <ToleranceType.absolute: 'absolute'>
243
247
  """
244
248
 
249
+ undefined = ""
250
+ """ToleranceType is deprecated, please use MetricToleranceType instead.
251
+ Undefined tolerance type."""
252
+ absolute = "absolute"
253
+ """ToleranceType is deprecated, please use MetricToleranceType instead.
254
+ Absolute tolerance type."""
255
+ relative = "relative"
256
+ """ToleranceType is deprecated, please use MetricToleranceType instead.
257
+ Relative tolerance type."""
258
+
259
+ def __new__(cls, value: str):
260
+ """Create a new ToleranceType instance and emit deprecation warning."""
261
+ deprecated(
262
+ "ToleranceType",
263
+ "ToleranceType is deprecated and will be removed in a future version. "
264
+ "Please use MetricToleranceType instead",
265
+ )
266
+ obj = str.__new__(cls, value)
267
+ obj._value_ = value
268
+ return obj
269
+
270
+ class MetricToleranceType(str, Enum):
271
+ """
272
+ Type of tolerance used for a metric.
273
+
274
+ You can import the `MetricToleranceType` class directly from `cloud`:
275
+
276
+ ```python
277
+ from nextmv.cloud import MetricToleranceType
278
+ ```
279
+
280
+ This enumeration defines the different types of tolerances that can be used
281
+ when comparing metrics in acceptance tests.
282
+
283
+ Attributes
284
+ ----------
285
+ undefined : str
286
+ Undefined tolerance type (empty string).
287
+ absolute : str
288
+ Absolute tolerance type, using a fixed value.
289
+ relative : str
290
+ Relative tolerance type, using a percentage.
291
+
292
+ Examples
293
+ --------
294
+ >>> from nextmv.cloud import MetricToleranceType
295
+ >>> tol_type = MetricToleranceType.absolute
296
+ >>> tol_type
297
+ <MetricToleranceType.absolute: 'absolute'>
298
+ """
299
+
245
300
  undefined = ""
246
301
  """Undefined tolerance type."""
247
302
  absolute = "absolute"
@@ -249,7 +304,6 @@ class ToleranceType(str, Enum):
249
304
  relative = "relative"
250
305
  """Relative tolerance type."""
251
306
 
252
-
253
307
  class MetricTolerance(BaseModel):
254
308
  """
255
309
  Tolerance used for a metric in an acceptance test.
@@ -265,22 +319,22 @@ class MetricTolerance(BaseModel):
265
319
 
266
320
  Attributes
267
321
  ----------
268
- type : ToleranceType
322
+ type : MetricToleranceType
269
323
  Type of tolerance (absolute or relative).
270
324
  value : float
271
325
  Value of the tolerance.
272
326
 
273
327
  Examples
274
328
  --------
275
- >>> from nextmv.cloud import MetricTolerance, ToleranceType
276
- >>> tolerance = MetricTolerance(type=ToleranceType.absolute, value=0.1)
329
+ >>> from nextmv.cloud import MetricTolerance, MetricToleranceType
330
+ >>> tolerance = MetricTolerance(type=MetricToleranceType.absolute, value=0.1)
277
331
  >>> tolerance.type
278
- <ToleranceType.absolute: 'absolute'>
332
+ <MetricToleranceType.absolute: 'absolute'>
279
333
  >>> tolerance.value
280
334
  0.1
281
335
  """
282
336
 
283
- type: ToleranceType
337
+ type: MetricToleranceType
284
338
  """Type of tolerance."""
285
339
  value: float
286
340
  """Value of the tolerance."""
@@ -46,6 +46,11 @@ from nextmv.cloud.batch_experiment import (
46
46
  to_runs,
47
47
  )
48
48
  from nextmv.cloud.client import Client, get_size
49
+ from nextmv.cloud.ensemble import (
50
+ EnsembleDefinition,
51
+ EvaluationRule,
52
+ RunGroup,
53
+ )
49
54
  from nextmv.cloud.input_set import InputSet, ManagedInput
50
55
  from nextmv.cloud.instance import Instance, InstanceConfiguration
51
56
  from nextmv.cloud.scenario import Scenario, ScenarioInputType, _option_sets, _scenarios_by_id
@@ -64,6 +69,7 @@ from nextmv.run import (
64
69
  Format,
65
70
  FormatInput,
66
71
  FormatOutput,
72
+ Run,
67
73
  RunConfiguration,
68
74
  RunInformation,
69
75
  RunLog,
@@ -128,6 +134,8 @@ class Application:
128
134
  """Base endpoint for the application."""
129
135
  experiments_endpoint: str = "{base}/experiments"
130
136
  """Base endpoint for the experiments in the application."""
137
+ ensembles_endpoint: str = "{base}/ensembles"
138
+ """Base endpoint for managing the ensemble definitions in the application"""
131
139
 
132
140
  def __post_init__(self):
133
141
  """Initialize the endpoint and experiments_endpoint attributes.
@@ -137,6 +145,7 @@ class Application:
137
145
  """
138
146
  self.endpoint = self.endpoint.format(id=self.id)
139
147
  self.experiments_endpoint = self.experiments_endpoint.format(base=self.endpoint)
148
+ self.ensembles_endpoint = self.ensembles_endpoint.format(base=self.endpoint)
140
149
 
141
150
  @classmethod
142
151
  def new(
@@ -289,7 +298,8 @@ class Application:
289
298
 
290
299
  def batch_experiment(self, batch_id: str) -> BatchExperiment:
291
300
  """
292
- Get a batch experiment.
301
+ Get a batch experiment. This method also returns the runs of the batch
302
+ experiment under the `.runs` attribute.
293
303
 
294
304
  Parameters
295
305
  ----------
@@ -318,7 +328,17 @@ class Application:
318
328
  endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
319
329
  )
320
330
 
321
- return BatchExperiment.from_dict(response.json())
331
+ exp = BatchExperiment.from_dict(response.json())
332
+
333
+ runs_response = self.client.request(
334
+ method="GET",
335
+ endpoint=f"{self.experiments_endpoint}/batch/{batch_id}/runs",
336
+ )
337
+
338
+ runs = [Run.from_dict(run) for run in runs_response.json().get("runs", [])]
339
+ exp.runs = runs
340
+
341
+ return exp
322
342
 
323
343
  def batch_experiment_metadata(self, batch_id: str) -> BatchExperimentMetadata:
324
344
  """
@@ -503,6 +523,30 @@ class Application:
503
523
  endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
504
524
  )
505
525
 
526
+ def delete_ensemble_definition(self, ensemble_definition_id: str) -> None:
527
+ """
528
+ Delete an ensemble definition.
529
+
530
+ Parameters
531
+ ----------
532
+ ensemble_definition_id : str
533
+ ID of the ensemble definition to delete.
534
+
535
+ Raises
536
+ ------
537
+ requests.HTTPError
538
+ If the response status code is not 2xx.
539
+
540
+ Examples
541
+ --------
542
+ >>> app.delete_ensemble_definition("development-ensemble-definition")
543
+ """
544
+
545
+ _ = self.client.request(
546
+ method="DELETE",
547
+ endpoint=f"{self.ensembles_endpoint}/{ensemble_definition_id}",
548
+ )
549
+
506
550
  def delete_scenario_test(self, scenario_test_id: str) -> None:
507
551
  """
508
552
  Delete a scenario test.
@@ -551,6 +595,39 @@ class Application:
551
595
  endpoint=f"{self.endpoint}/secrets/{secrets_collection_id}",
552
596
  )
553
597
 
598
+ def ensemble_definition(self, ensemble_definition_id: str) -> EnsembleDefinition:
599
+ """
600
+ Get an ensemble definition.
601
+
602
+ Parameters
603
+ ----------
604
+ ensemble_definition_id : str
605
+ ID of the ensemble definition to retrieve.
606
+
607
+ Returns
608
+ -------
609
+ EnsembleDefintion
610
+ The requested ensemble definition details.
611
+
612
+ Raises
613
+ ------
614
+ requests.HTTPError
615
+ If the response status code is not 2xx.
616
+
617
+ Examples
618
+ --------
619
+ >>> ensemble_definition = app.ensemble_definition("instance-123")
620
+ >>> print(ensemble_definition.name)
621
+ 'Production Ensemble Definition'
622
+ """
623
+
624
+ response = self.client.request(
625
+ method="GET",
626
+ endpoint=f"{self.ensembles_endpoint}/{ensemble_definition_id}",
627
+ )
628
+
629
+ return EnsembleDefinition.from_dict(response.json())
630
+
554
631
  @staticmethod
555
632
  def exists(client: Client, id: str) -> bool:
556
633
  """
@@ -736,6 +813,36 @@ class Application:
736
813
 
737
814
  return [BatchExperimentMetadata.from_dict(batch_experiment) for batch_experiment in response.json()]
738
815
 
816
+ def list_ensemble_definitions(self) -> list[EnsembleDefinition]:
817
+ """
818
+ List all ensemble_definitions.
819
+
820
+ Returns
821
+ -------
822
+ list[EnsembleDefinition]
823
+ List of all ensemble definitions associated with this application.
824
+
825
+ Raises
826
+ ------
827
+ requests.HTTPError
828
+ If the response status code is not 2xx.
829
+
830
+ Examples
831
+ --------
832
+ >>> ensemble_definitions = app.list_ensemble_definitions()
833
+ >>> for ensemble_definition in ensemble_definitions:
834
+ ... print(ensemble_definition.name)
835
+ 'Development Ensemble Definition'
836
+ 'Production Ensemble Definition'
837
+ """
838
+
839
+ response = self.client.request(
840
+ method="GET",
841
+ endpoint=f"{self.ensembles_endpoint}",
842
+ )
843
+
844
+ return [EnsembleDefinition.from_dict(ensemble_definition) for ensemble_definition in response.json()["items"]]
845
+
739
846
  def list_input_sets(self) -> list[InputSet]:
740
847
  """
741
848
  List all input sets.
@@ -818,6 +925,28 @@ class Application:
818
925
 
819
926
  return [ManagedInput.from_dict(managed_input) for managed_input in response.json()]
820
927
 
928
+ def list_runs(self) -> list[Run]:
929
+ """
930
+ List all runs.
931
+
932
+ Returns
933
+ -------
934
+ list[Run]
935
+ List of runs.
936
+
937
+ Raises
938
+ ------
939
+ requests.HTTPError
940
+ If the response status code is not 2xx.
941
+ """
942
+
943
+ response = self.client.request(
944
+ method="GET",
945
+ endpoint=f"{self.endpoint}/runs",
946
+ )
947
+
948
+ return [Run.from_dict(run) for run in response.json().get("runs", [])]
949
+
821
950
  def list_scenario_tests(self) -> list[BatchExperimentMetadata]:
822
951
  """
823
952
  List all batch scenario tests. Scenario tests are based on the batch
@@ -1280,6 +1409,53 @@ class Application:
1280
1409
 
1281
1410
  return self.batch_experiment_with_polling(batch_id=batch_id, polling_options=polling_options)
1282
1411
 
1412
+ def new_ensemble_defintion(
1413
+ self,
1414
+ id: str,
1415
+ run_groups: list[RunGroup],
1416
+ rules: list[EvaluationRule],
1417
+ name: Optional[str] = None,
1418
+ description: Optional[str] = None,
1419
+ ) -> EnsembleDefinition:
1420
+ """
1421
+ Create a new ensemble definition.
1422
+
1423
+ Parameters
1424
+ ----------
1425
+ id: str
1426
+ ID of the ensemble defintion.
1427
+ run_groups: list[RunGroup]
1428
+ Information to facilitate the execution of child runs.
1429
+ rules: list[EvaluationRule]
1430
+ Information to facilitate the selection of
1431
+ a result for the ensemble run from child runs.
1432
+ name: Optional[str]
1433
+ Name of the ensemble definition.
1434
+ description: Optional[str]
1435
+ Description of the ensemble definition.
1436
+ """
1437
+
1438
+ if name is None:
1439
+ name = id
1440
+ if description is None:
1441
+ description = name
1442
+
1443
+ payload = {
1444
+ "id": id,
1445
+ "run_groups": [run_group.to_dict() for run_group in run_groups],
1446
+ "rules": [rule.to_dict() for rule in rules],
1447
+ "name": name,
1448
+ "description": description,
1449
+ }
1450
+
1451
+ response = self.client.request(
1452
+ method="POST",
1453
+ endpoint=f"{self.ensembles_endpoint}",
1454
+ payload=payload,
1455
+ )
1456
+
1457
+ return EnsembleDefinition.from_dict(response.json())
1458
+
1283
1459
  def new_input_set(
1284
1460
  self,
1285
1461
  id: str,
@@ -2202,13 +2378,14 @@ class Application:
2202
2378
 
2203
2379
  if id is None:
2204
2380
  id = safe_id(prefix="version")
2381
+ if name is None:
2382
+ name = id
2205
2383
 
2206
2384
  payload = {
2207
2385
  "id": id,
2386
+ "name": name,
2208
2387
  }
2209
2388
 
2210
- if name is not None:
2211
- payload["name"] = name
2212
2389
  if description is not None:
2213
2390
  payload["description"] = description
2214
2391
 
@@ -2759,7 +2936,7 @@ class Application:
2759
2936
  Examples
2760
2937
  --------
2761
2938
  >>> from nextmv.cloud import Application
2762
- >>> from nextmv.cloud.run import TrackedRun
2939
+ >>> from nextmv import TrackedRun
2763
2940
  >>> app = Application(id="app_123")
2764
2941
  >>> tracked_run = TrackedRun(input={"data": [...]}, output={"solution": [...]})
2765
2942
  >>> run_id = app.track_run(tracked_run)
@@ -2900,6 +3077,98 @@ class Application:
2900
3077
  output_dir_path=output_dir_path,
2901
3078
  )
2902
3079
 
3080
+ def update_batch_experiment(
3081
+ self,
3082
+ batch_experiment_id: str,
3083
+ name: Optional[str] = None,
3084
+ description: Optional[str] = None,
3085
+ ) -> BatchExperimentInformation:
3086
+ """
3087
+ Update a batch experiment.
3088
+
3089
+ Parameters
3090
+ ----------
3091
+ batch_experiment_id : str
3092
+ ID of the batch experiment to update.
3093
+ name : Optional[str], default=None
3094
+ Optional name of the batch experiment.
3095
+ description : Optional[str], default=None
3096
+ Optional description of the batch experiment.
3097
+
3098
+ Returns
3099
+ -------
3100
+ BatchExperimentInformation
3101
+ The information with the updated batch experiment.
3102
+
3103
+ Raises
3104
+ ------
3105
+ requests.HTTPError
3106
+ If the response status code is not 2xx.
3107
+ """
3108
+
3109
+ payload = {}
3110
+
3111
+ if name is not None:
3112
+ payload["name"] = name
3113
+ if description is not None:
3114
+ payload["description"] = description
3115
+
3116
+ response = self.client.request(
3117
+ method="PATCH",
3118
+ endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
3119
+ payload=payload,
3120
+ )
3121
+
3122
+ return BatchExperimentInformation.from_dict(response.json())
3123
+
3124
+ def update_ensemble_definition(
3125
+ self,
3126
+ id: str,
3127
+ name: Optional[str] = None,
3128
+ description: Optional[str] = None,
3129
+ ) -> EnsembleDefinition:
3130
+ """
3131
+ Update an ensemble definition.
3132
+
3133
+ Parameters
3134
+ ----------
3135
+ id : str
3136
+ ID of the ensemble definition to update.
3137
+ name : Optional[str], default=None
3138
+ Optional name of the ensemble definition.
3139
+ description : Optional[str], default=None
3140
+ Optional description of the ensemble definition.
3141
+
3142
+ Returns
3143
+ -------
3144
+ EnsembleDefinition
3145
+ The updated ensemble definition.
3146
+
3147
+ Raises
3148
+ ------
3149
+ ValueError
3150
+ If neither name nor description is updated
3151
+ requests.HTTPError
3152
+ If the response status code is not 2xx.
3153
+ """
3154
+
3155
+ payload = {}
3156
+
3157
+ if name is None and description is None:
3158
+ raise ValueError("Must define at least one value among name and description to modify")
3159
+ if name is not None:
3160
+ payload["name"] = name
3161
+ if description is not None:
3162
+ payload["description"] = description
3163
+
3164
+ response = self.client.request(
3165
+ method="PATCH",
3166
+ endpoint=f"{self.ensembles_endpoint}/{id}",
3167
+ payload=payload,
3168
+ )
3169
+
3170
+ return EnsembleDefinition.from_dict(response.json())
3171
+
2903
3172
  def update_instance(
2904
3173
  self,
2905
3174
  id: str,
@@ -2963,50 +3232,6 @@ class Application:
2963
3232
 
2964
3233
  return Instance.from_dict(response.json())
2965
3234
 
2966
- def update_batch_experiment(
2967
- self,
2968
- batch_experiment_id: str,
2969
- name: Optional[str] = None,
2970
- description: Optional[str] = None,
2971
- ) -> BatchExperimentInformation:
2972
- """
2973
- Update a batch experiment.
2974
-
2975
- Parameters
2976
- ----------
2977
- batch_experiment_id : str
2978
- ID of the batch experiment to update.
2979
- name : Optional[str], default=None
2980
- Optional name of the batch experiment.
2981
- description : Optional[str], default=None
2982
- Optional description of the batch experiment.
2983
-
2984
- Returns
2985
- -------
2986
- BatchExperimentInformation
2987
- The information with the updated batch experiment.
2988
-
2989
- Raises
2990
- ------
2991
- requests.HTTPError
2992
- If the response status code is not 2xx.
2993
- """
2994
-
2995
- payload = {}
2996
-
2997
- if name is not None:
2998
- payload["name"] = name
2999
- if description is not None:
3000
- payload["description"] = description
3001
-
3002
- response = self.client.request(
3003
- method="PATCH",
3004
- endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
3005
- payload=payload,
3006
- )
3007
-
3008
- return BatchExperimentInformation.from_dict(response.json())
3009
-
3010
3235
  def update_managed_input(
3011
3236
  self,
3012
3237
  managed_input_id: str,
@@ -21,6 +21,7 @@ from typing import Any, Optional
21
21
 
22
22
  from nextmv.base_model import BaseModel
23
23
  from nextmv.cloud.input_set import InputSet
24
+ from nextmv.run import Run
24
25
 
25
26
 
26
27
  class ExperimentStatus(str, Enum):
@@ -191,7 +192,10 @@ class BatchExperiment(BatchExperimentInformation):
191
192
  instance_ids : list[str]
192
193
  List of instance IDs used for the experiment.
193
194
  grouped_distributional_summaries : list[dict[str, Any]], optional
194
- Grouped distributional summaries of the batch experiment. Defaults to None.
195
+ Grouped distributional summaries of the batch experiment. Defaults to
196
+ None.
197
+ runs : list[Run], optional
198
+ List of runs in the batch experiment. Defaults to None.
195
199
  """
196
200
 
197
201
  input_set_id: str
@@ -200,6 +204,8 @@ class BatchExperiment(BatchExperimentInformation):
200
204
  """List of instance IDs used for the experiment."""
201
205
  grouped_distributional_summaries: Optional[list[dict[str, Any]]] = None
202
206
  """Grouped distributional summaries of the batch experiment."""
207
+ runs: Optional[list[Run]] = None
208
+ """List of runs in the batch experiment."""
203
209
 
204
210
 
205
211
  class BatchExperimentRun(BaseModel):