nextmv 0.32.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 (88) hide show
  1. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/PKG-INFO +1 -1
  2. nextmv-0.33.0.dev0/nextmv/__about__.py +1 -0
  3. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/__init__.py +7 -0
  4. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/acceptance_test.py +60 -6
  5. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/application.py +237 -46
  6. nextmv-0.33.0.dev0/nextmv/cloud/ensemble.py +248 -0
  7. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/local/executor.py +2 -1
  8. nextmv-0.32.0/nextmv/__about__.py +0 -1
  9. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/.gitignore +0 -0
  10. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/LICENSE +0 -0
  11. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/README.md +0 -0
  12. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/__entrypoint__.py +0 -0
  13. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/__init__.py +0 -0
  14. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/_serialization.py +0 -0
  15. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/base_model.py +0 -0
  16. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/account.py +0 -0
  17. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/batch_experiment.py +0 -0
  18. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/client.py +0 -0
  19. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/input_set.py +0 -0
  20. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/instance.py +0 -0
  21. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/package.py +0 -0
  22. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/scenario.py +0 -0
  23. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/secrets.py +0 -0
  24. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/url.py +0 -0
  25. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/cloud/version.py +0 -0
  26. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/.gitignore +0 -0
  27. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/README.md +0 -0
  28. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/app.yaml +0 -0
  29. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/input.json +0 -0
  30. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/main.py +0 -0
  31. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/requirements.txt +0 -0
  32. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/src/__init__.py +0 -0
  33. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/src/main.py +0 -0
  34. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/default_app/src/visuals.py +0 -0
  35. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/deprecated.py +0 -0
  36. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/input.py +0 -0
  37. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/local/__init__.py +0 -0
  38. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/local/application.py +0 -0
  39. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/local/geojson_handler.py +0 -0
  40. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/local/local.py +0 -0
  41. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/local/plotly_handler.py +0 -0
  42. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/local/runner.py +0 -0
  43. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/logger.py +0 -0
  44. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/manifest.py +0 -0
  45. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/model.py +0 -0
  46. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/options.py +0 -0
  47. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/output.py +0 -0
  48. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/polling.py +0 -0
  49. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/run.py +0 -0
  50. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/safe.py +0 -0
  51. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/nextmv/status.py +0 -0
  52. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/pyproject.toml +0 -0
  53. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/__init__.py +0 -0
  54. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/cloud/__init__.py +0 -0
  55. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/cloud/app.yaml +0 -0
  56. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/cloud/test_client.py +0 -0
  57. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/cloud/test_package.py +0 -0
  58. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/cloud/test_scenario.py +0 -0
  59. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/local/__init__.py +0 -0
  60. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/local/test_application.py +0 -0
  61. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/local/test_executor.py +0 -0
  62. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/local/test_runner.py +0 -0
  63. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/__init__.py +0 -0
  64. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options1.py +0 -0
  65. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options2.py +0 -0
  66. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options3.py +0 -0
  67. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options4.py +0 -0
  68. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options5.py +0 -0
  69. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options6.py +0 -0
  70. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options7.py +0 -0
  71. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/scripts/options_deprecated.py +0 -0
  72. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_base_model.py +0 -0
  73. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_entrypoint/__init__.py +0 -0
  74. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_entrypoint/test_entrypoint.py +0 -0
  75. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_input.py +0 -0
  76. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_inputs/test_data.csv +0 -0
  77. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_inputs/test_data.json +0 -0
  78. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_inputs/test_data.txt +0 -0
  79. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_logger.py +0 -0
  80. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_manifest.py +0 -0
  81. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_model.py +0 -0
  82. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_options.py +0 -0
  83. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_output.py +0 -0
  84. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_polling.py +0 -0
  85. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_run.py +0 -0
  86. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_safe.py +0 -0
  87. {nextmv-0.32.0 → nextmv-0.33.0.dev0}/tests/test_serialization.py +0 -0
  88. {nextmv-0.32.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.32.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"
@@ -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
@@ -129,6 +134,8 @@ class Application:
129
134
  """Base endpoint for the application."""
130
135
  experiments_endpoint: str = "{base}/experiments"
131
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"""
132
139
 
133
140
  def __post_init__(self):
134
141
  """Initialize the endpoint and experiments_endpoint attributes.
@@ -138,6 +145,7 @@ class Application:
138
145
  """
139
146
  self.endpoint = self.endpoint.format(id=self.id)
140
147
  self.experiments_endpoint = self.experiments_endpoint.format(base=self.endpoint)
148
+ self.ensembles_endpoint = self.ensembles_endpoint.format(base=self.endpoint)
141
149
 
142
150
  @classmethod
143
151
  def new(
@@ -515,6 +523,30 @@ class Application:
515
523
  endpoint=f"{self.experiments_endpoint}/batch/{batch_id}",
516
524
  )
517
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
+
518
550
  def delete_scenario_test(self, scenario_test_id: str) -> None:
519
551
  """
520
552
  Delete a scenario test.
@@ -563,6 +595,39 @@ class Application:
563
595
  endpoint=f"{self.endpoint}/secrets/{secrets_collection_id}",
564
596
  )
565
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
+
566
631
  @staticmethod
567
632
  def exists(client: Client, id: str) -> bool:
568
633
  """
@@ -748,6 +813,36 @@ class Application:
748
813
 
749
814
  return [BatchExperimentMetadata.from_dict(batch_experiment) for batch_experiment in response.json()]
750
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
+
751
846
  def list_input_sets(self) -> list[InputSet]:
752
847
  """
753
848
  List all input sets.
@@ -1314,6 +1409,53 @@ class Application:
1314
1409
 
1315
1410
  return self.batch_experiment_with_polling(batch_id=batch_id, polling_options=polling_options)
1316
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
+
1317
1459
  def new_input_set(
1318
1460
  self,
1319
1461
  id: str,
@@ -2236,13 +2378,14 @@ class Application:
2236
2378
 
2237
2379
  if id is None:
2238
2380
  id = safe_id(prefix="version")
2381
+ if name is None:
2382
+ name = id
2239
2383
 
2240
2384
  payload = {
2241
2385
  "id": id,
2386
+ "name": name,
2242
2387
  }
2243
2388
 
2244
- if name is not None:
2245
- payload["name"] = name
2246
2389
  if description is not None:
2247
2390
  payload["description"] = description
2248
2391
 
@@ -2934,6 +3077,98 @@ class Application:
2934
3077
  output_dir_path=output_dir_path,
2935
3078
  )
2936
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
+
2937
3172
  def update_instance(
2938
3173
  self,
2939
3174
  id: str,
@@ -2997,50 +3232,6 @@ class Application:
2997
3232
 
2998
3233
  return Instance.from_dict(response.json())
2999
3234
 
3000
- def update_batch_experiment(
3001
- self,
3002
- batch_experiment_id: str,
3003
- name: Optional[str] = None,
3004
- description: Optional[str] = None,
3005
- ) -> BatchExperimentInformation:
3006
- """
3007
- Update a batch experiment.
3008
-
3009
- Parameters
3010
- ----------
3011
- batch_experiment_id : str
3012
- ID of the batch experiment to update.
3013
- name : Optional[str], default=None
3014
- Optional name of the batch experiment.
3015
- description : Optional[str], default=None
3016
- Optional description of the batch experiment.
3017
-
3018
- Returns
3019
- -------
3020
- BatchExperimentInformation
3021
- The information with the updated batch experiment.
3022
-
3023
- Raises
3024
- ------
3025
- requests.HTTPError
3026
- If the response status code is not 2xx.
3027
- """
3028
-
3029
- payload = {}
3030
-
3031
- if name is not None:
3032
- payload["name"] = name
3033
- if description is not None:
3034
- payload["description"] = description
3035
-
3036
- response = self.client.request(
3037
- method="PATCH",
3038
- endpoint=f"{self.experiments_endpoint}/batch/{batch_experiment_id}",
3039
- payload=payload,
3040
- )
3041
-
3042
- return BatchExperimentInformation.from_dict(response.json())
3043
-
3044
3235
  def update_managed_input(
3045
3236
  self,
3046
3237
  managed_input_id: str,
@@ -0,0 +1,248 @@
1
+ """
2
+ Classes for working with Nextmv Cloud Ensemble Runs.
3
+
4
+ This module provides classes for interacting with ensemble runs in Nextmv Cloud.
5
+ It details the core data structures for ensemble definitions.
6
+
7
+ Classes
8
+ -------
9
+ RunGroup
10
+ A structure to group execution of child runs for an ensemble run.
11
+ RuleObjective
12
+ An enum that specifies the supported evaluation rule objectives.
13
+ ToleranceType
14
+ An enum that specifies the supported tolerance types for evaluation rules.
15
+ RuleTolerance
16
+ A structure for defining tolerance thresholds for an evaluation rule
17
+ EvaluationRule
18
+ A structure to evaluate run results for an ensemble run.
19
+ EnsembleDefinition
20
+ Representation of a Nextmv Cloud Ensemble Definition for an application.
21
+ """
22
+
23
+ from datetime import datetime
24
+ from enum import Enum
25
+ from typing import Optional
26
+
27
+ from nextmv.base_model import BaseModel
28
+
29
+
30
+ class RunGroup(BaseModel):
31
+ """A structure to group child runs for an ensemble run.
32
+
33
+ You can import the `RunGroup` class directly from `cloud`:
34
+
35
+ ```python
36
+ from nextmv.cloud import RunGroup
37
+ ```
38
+
39
+ This class represents a grouping of child runs that share a configuration
40
+ for ensemble run executions.
41
+
42
+ Parameters
43
+ ----------
44
+ id : str
45
+ The unique identifier of the run group.
46
+ instance_id : str
47
+ ID of the app instance that this run group executes on.
48
+ options : dict, optional
49
+ Runtime options/parameters for the application.
50
+ repetitions : int, optional
51
+ The number of times the run is to be repeated on the instance and with
52
+ the options defined in the run group
53
+ """
54
+
55
+ id: str
56
+ """The unique identifier of the run group."""
57
+ instance_id: str
58
+ """ID of the app instance that this run group executes on."""
59
+ options: Optional[dict] = None
60
+ """Runtime options/parameters for the application."""
61
+ repetitions: Optional[int] = None
62
+ """The number of times the run is to be repeated on the instance and with
63
+ the options defined in the run group"""
64
+
65
+
66
+ class RuleObjective(str, Enum):
67
+ """The value of this data determines how a value of a run is optimized to
68
+ determined which ensemble child run is the "best" for a given metric and
69
+ rule, as well as which other ones are within tolerance of that run for the
70
+ purposes of selecting a result for the ensemble run from among the child runs.
71
+
72
+ You can import the `RuleObjective` class directly from `cloud`:
73
+
74
+ ```python
75
+ from nextmv.cloud import RuleObjective
76
+ ```
77
+
78
+ This enum specifies the supported evaluation rule objectives.
79
+
80
+ Attributes
81
+ ----------
82
+ MAXIMIZE : str
83
+ Maximize the value of the evaluated metric.
84
+ MINIMIZE : str
85
+ Minimize the value of the evaluated metric.
86
+ """
87
+
88
+ MAXIMIZE = "maximize"
89
+ """Maximize the value of the evaluated metric."""
90
+ MINIMIZE = "minimize"
91
+ """Minimize the value of the evaluated metric."""
92
+
93
+
94
+ class RuleToleranceType(str, Enum):
95
+ """The type of comparison used to determine if a run metric is within
96
+ tolerance of a the "best" run for that rule and metric
97
+
98
+ You can import the `RuleToleranceType` class directly from `cloud`:
99
+
100
+ ```python
101
+ from nextmv.cloud import RuleToleranceType
102
+ ```
103
+
104
+ This enum specifies the supported tolerance types.
105
+
106
+ Attributes
107
+ ----------
108
+ ABSOLUTE : str
109
+ Uses the absolute difference between the value of the "best" run and
110
+ the run being evaluated for tolerance
111
+ RELATIVE : str
112
+ Uses the the percentage of the "best" run by which the run being
113
+ evaluted for tolerance differs. A value of `1` is 100%.
114
+ """
115
+
116
+ ABSOLUTE = "absolute"
117
+ """Uses the absolute difference between the value of the "best" run and
118
+ the run being evaluated for tolerance"""
119
+ RELATIVE = "relative"
120
+ """Uses the the percentage of the "best" run by which the run being
121
+ evaluted for tolerance differs. A value of `1` is 100%."""
122
+
123
+
124
+ class RuleTolerance(BaseModel):
125
+ """A structure used to determine if a run is within tolerance of of the best
126
+ run (as determined by the objective of the `EvaluationRule` it is defined on).
127
+
128
+ You can import the `RuleTolerance` class directly from `cloud`:
129
+
130
+ ```python
131
+ from nextmv.cloud import RuleTolerance
132
+ ```
133
+
134
+ This class represents the tolerance on a particular evaluation rule by
135
+ which a child run may be selected as the result of an ensemble run.
136
+
137
+ value : float
138
+ The value within which runs can deviate from the "best" run
139
+ for that metric to be considered within tolerance of it.
140
+ type : ToleranceType
141
+ The method by which runs are determined to be within tolerance.
142
+ """
143
+
144
+ value: float
145
+ """The value within which runs can deviate from the "best" run
146
+ for that metric to be considered within tolerance of it."""
147
+ type: RuleToleranceType
148
+ """The method by which runs are determined to be within tolerance."""
149
+
150
+
151
+ class EvaluationRule(BaseModel):
152
+ """A structure to evaluate run results for an ensemble run.
153
+
154
+ You can import the `EvaluationRule` class directly from `cloud`:
155
+
156
+ ```python
157
+ from nextmv.cloud import EvaluationRule
158
+ ```
159
+
160
+ This class represents a rule by which the child runs for an ensemble run
161
+ will be evaluated for the purpose of selecting an optimal result for the
162
+ ensemble run.
163
+
164
+ Parameters
165
+ ----------
166
+ id : str
167
+ The unique identifier of the evaluation rule.
168
+ statistics_path : str
169
+ The path within the statistics of a run output (conforming to Nextmv
170
+ statistics convention and flattened to a string starting with `$` and
171
+ delimited by `.` e.g. `$.result.value`.)
172
+ objective : RuleObjective
173
+ The objective by which runs are optimized for this rule
174
+ tolerance : RuleTolerance
175
+ The tolerance by which runs can be accepted as a potential result
176
+ for an evaluation rule
177
+ index : int, optional
178
+ The index (non-negative integer) of the evalutation rule. Lower indicies
179
+ are evaluated first.
180
+ """
181
+
182
+ id: str
183
+ """The unique identifier of the evaluation rule."""
184
+ statistics_path: str
185
+ """The path within the statistics of a run output (conforming to Nextmv
186
+ statistics convention and flattened to a string starting with `$` and
187
+ delimited by `.` e.g. `$.result.value`.)"""
188
+ objective: RuleObjective
189
+ """The objective by which runs are optimized for this rule"""
190
+ tolerance: RuleTolerance
191
+ """The tolerance by which runs can be accepted as a potential result
192
+ for an evaluation rule"""
193
+ index: int
194
+ """The index (non-negative integer) of the evalutation rule. Lower indicies
195
+ are evaluated first."""
196
+
197
+
198
+ class EnsembleDefinition(BaseModel):
199
+ """An ensemble definition for an application.
200
+
201
+ You can import the `EnsembleDefinition` class directly from `cloud`:
202
+
203
+ ```python
204
+ from nextmv.cloud import EnsembleDefinition
205
+ ```
206
+
207
+ A Nextmv Cloud ensemble definition represents a structure by which an
208
+ application can coordinate and execute, and determine the optimal result of
209
+ an ensemble run.
210
+
211
+ Parameters
212
+ ----------
213
+ id : str
214
+ The unique identifier of the ensemble definition.
215
+ application_id : str
216
+ ID of the application that this ensemble definition belongs to.
217
+ name : str
218
+ Human-readable name of the ensemble definition.
219
+ description : str
220
+ Detailed description of the ensemble definition.
221
+ run_groups : list[RunGroup], optional
222
+ The run groups that structure the execution of an ensemble run
223
+ rules : list[EvaluationRule], optional
224
+ The rules by which ensemble child runs are evaluated
225
+ to find an optimal result.
226
+ created_at : datetime
227
+ Timestamp when the ensemble definition was created.
228
+ updated_at : datetime
229
+ Timestamp when the ensemble definition was last updated.
230
+ """
231
+
232
+ id: str
233
+ """The unique identifier of the ensemble definition."""
234
+ application_id: str
235
+ """ID of the application that this ensemble definition belongs to."""
236
+ name: str = ""
237
+ """Human-readable name of the ensemble definition."""
238
+ description: str = ""
239
+ """Detailed description of the ensemble definition."""
240
+ run_groups: list[RunGroup]
241
+ """The run groups that structure the execution of an ensemble run"""
242
+ rules: list[EvaluationRule]
243
+ """The rules by which ensemble child runs are evaluated
244
+ to find an optimal result."""
245
+ created_at: datetime
246
+ """Timestamp when the ensemble definition was created."""
247
+ updated_at: datetime
248
+ """Timestamp when the ensemble definition was last updated."""
@@ -32,6 +32,7 @@ import json
32
32
  import os
33
33
  import shutil
34
34
  import subprocess
35
+ import sys
35
36
  import tempfile
36
37
  from datetime import datetime, timezone
37
38
  from typing import Any, Optional, Union
@@ -143,7 +144,7 @@ def execute_run(
143
144
  # supporting a Python-first experience, so we are not summoning
144
145
  # applications that are not Python-based.
145
146
  entrypoint = os.path.join(temp_src, manifest.entrypoint)
146
- args = ["python", entrypoint] + options_args(options)
147
+ args = [sys.executable, entrypoint] + options_args(options)
147
148
 
148
149
  result = subprocess.run(
149
150
  args,
@@ -1 +0,0 @@
1
- __version__ = "v0.32.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes