nextmv 0.31.0__py3-none-any.whl → 0.33.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.
@@ -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):
@@ -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."""
nextmv/cloud/package.py CHANGED
@@ -222,7 +222,7 @@ def __handle_python(
222
222
  __install_dependencies(manifest, app_dir, temp_dir)
223
223
 
224
224
 
225
- def __install_dependencies(
225
+ def __install_dependencies( # noqa: C901 # complexity
226
226
  manifest: Manifest,
227
227
  app_dir: str,
228
228
  temp_dir: str,
@@ -253,31 +253,58 @@ def __install_dependencies(
253
253
  if not os.path.isfile(os.path.join(app_dir, pip_requirements)):
254
254
  raise FileNotFoundError(f"pip requirements file '{pip_requirements}' not found in '{app_dir}'")
255
255
 
256
+ platform_filter = []
257
+ if not manifest.python.arch or manifest.python.arch == "arm64":
258
+ platform_filter.extend(
259
+ [
260
+ "--platform=manylinux2014_aarch64",
261
+ "--platform=manylinux_2_17_aarch64",
262
+ "--platform=manylinux_2_24_aarch64",
263
+ "--platform=manylinux_2_28_aarch64",
264
+ "--platform=linux_aarch64",
265
+ ]
266
+ )
267
+ elif manifest.python.arch == "amd64":
268
+ platform_filter.extend(
269
+ [
270
+ "--platform=manylinux2014_x86_64",
271
+ "--platform=manylinux_2_17_x86_64",
272
+ "--platform=manylinux_2_24_x86_64",
273
+ "--platform=manylinux_2_28_x86_64",
274
+ "--platform=linux_x86_64",
275
+ ]
276
+ )
277
+ else:
278
+ raise Exception(f"unknown architecture '{manifest.python.arch}' specified in manifest")
279
+
280
+ version_filter = ["--python-version=3.11"]
281
+ if manifest.python.version:
282
+ __confirm_python_bundling_version(manifest.python.version)
283
+ version_filter = [f"--python-version={manifest.python.version}"]
284
+
256
285
  py_cmd = __get_python_command()
257
286
  dep_dir = os.path.join(".nextmv", "python", "deps")
258
- command = [
259
- py_cmd,
260
- "-m",
261
- "pip",
262
- "install",
263
- "-r",
264
- pip_requirements,
265
- "--platform=manylinux2014_aarch64",
266
- "--platform=manylinux_2_17_aarch64",
267
- "--platform=manylinux_2_24_aarch64",
268
- "--platform=manylinux_2_28_aarch64",
269
- "--platform=linux_aarch64",
270
- "--only-binary=:all:",
271
- "--python-version=3.11",
272
- "--implementation=cp",
273
- "--upgrade",
274
- "--no-warn-conflicts",
275
- "--target",
276
- os.path.join(temp_dir, dep_dir),
277
- "--no-user", # We explicitly avoid user mode (mainly to fix issues with Windows store Python installations)
278
- "--no-input",
279
- "--quiet",
280
- ]
287
+ command = (
288
+ [
289
+ py_cmd,
290
+ "-m",
291
+ "pip",
292
+ "install",
293
+ "-r",
294
+ pip_requirements,
295
+ "--only-binary=:all:",
296
+ "--implementation=cp",
297
+ "--upgrade",
298
+ "--no-warn-conflicts",
299
+ "--target",
300
+ os.path.join(temp_dir, dep_dir),
301
+ "--no-user", # We explicitly avoid user mode (mainly to fix issues with Windows store Python installations)
302
+ "--no-input",
303
+ "--quiet",
304
+ ]
305
+ + platform_filter
306
+ + version_filter
307
+ )
281
308
  result = subprocess.run(
282
309
  command,
283
310
  cwd=app_dir,
@@ -381,6 +408,17 @@ def __confirm_python_version(output: str) -> None:
381
408
  raise Exception("python version 3.9 or higher is required")
382
409
 
383
410
 
411
+ def __confirm_python_bundling_version(version: str) -> None:
412
+ # Only accept versions in the form "major.minor" where both are integers
413
+ re_version = re.compile(r"^(\d+)\.(\d+)$")
414
+ match = re_version.fullmatch(version)
415
+ if match:
416
+ major, minor = int(match.group(1)), int(match.group(2))
417
+ if major == 3 and minor >= 9:
418
+ return
419
+ raise Exception(f"python version 3.9 or higher is required for bundling, got {version}")
420
+
421
+
384
422
  def __compress_tar(source: str, target: str) -> tuple[str, int]:
385
423
  """Compress the source directory into a tar.gz file in the target"""
386
424
 
@@ -4,6 +4,7 @@ This is the basic structure of a Nextmv application.
4
4
 
5
5
  ```text
6
6
  ├── app.yaml
7
+ ├── main.py
7
8
  ├── README.md
8
9
  ├── requirements.txt
9
10
  └── src
@@ -11,7 +12,21 @@ This is the basic structure of a Nextmv application.
11
12
 
12
13
  * `app.yaml`: App manifest, containing the configuration to run the app
13
14
  remotely on Nextmv Cloud.
15
+ * `main.py`: Entry point for the app.
14
16
  * `README.md`: Description of the app.
15
17
  * `requirements.txt`: Python dependencies for the app.
16
- * `src/`: Source code for the app. The `main.py` file is the entry point for
17
- the app.
18
+ * `src/`: Source code for the app.
19
+
20
+ A sample input file is also provided as `input.json`.
21
+
22
+ 1. Install packages.
23
+
24
+ ```bash
25
+ pip3 install -r requirements.txt
26
+ ```
27
+
28
+ 2. Run the app.
29
+
30
+ ```bash
31
+ cat input.json | python3 main.py
32
+ ```
@@ -9,5 +9,4 @@ python:
9
9
  # (e.g.: configs/*.json) is supported.
10
10
  files:
11
11
  - src/
12
-
13
- entrypoint: src/main.py
12
+ - main.py
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "Patches",
3
+ "radius": 6378,
4
+ "distance": 147.6
5
+ }
@@ -0,0 +1,37 @@
1
+ from src.visuals import create_visuals
2
+
3
+ import nextmv
4
+
5
+ # Read the input from stdin.
6
+ input = nextmv.load()
7
+ name = input.data["name"]
8
+
9
+ options = nextmv.Options(
10
+ nextmv.Option("details", bool, True, "Print details to logs. Default true.", False),
11
+ )
12
+
13
+ ##### Insert model here
14
+
15
+ # Print logs that render in the run view in Nextmv Console.
16
+ message = f"Hello, {name}"
17
+ nextmv.log(message)
18
+
19
+ if options.details:
20
+ detail = f"You are {input.data['distance']} million km from the sun"
21
+ nextmv.log(detail)
22
+
23
+ assets = create_visuals(name, input.data["radius"], input.data["distance"])
24
+
25
+ # Write output and statistics.
26
+ output = nextmv.Output(
27
+ options=options,
28
+ solution={"message": message},
29
+ statistics=nextmv.Statistics(
30
+ result=nextmv.ResultStatistics(
31
+ value=1.23,
32
+ custom={"message": message},
33
+ ),
34
+ ),
35
+ assets=assets,
36
+ )
37
+ nextmv.write(output)
nextmv/input.py CHANGED
@@ -21,12 +21,10 @@ Functions
21
21
  load
22
22
  Load input data using a specified loader.
23
23
 
24
- Constants
25
- ---------
24
+ Attributes
25
+ ----------
26
26
  INPUTS_KEY : str
27
27
  Key used for identifying inputs in the run.
28
- DEFAULT_INPUT_JSON_FILE : str
29
- Constant for the default input JSON file name.
30
28
  """
31
29
 
32
30
  import copy
@@ -47,10 +45,6 @@ INPUTS_KEY = "inputs"
47
45
  """
48
46
  Inputs key constant used for identifying inputs in the run.
49
47
  """
50
- DEFAULT_INPUT_JSON_FILE = "input.json"
51
- """
52
- Constant for the default input JSON file name.
53
- """
54
48
 
55
49
 
56
50
  class InputFormat(str, Enum):