nextmv 0.27.0__py3-none-any.whl → 0.28.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/cloud/package.py CHANGED
@@ -10,7 +10,7 @@ import tarfile
10
10
  import tempfile
11
11
  from typing import Optional
12
12
 
13
- from nextmv.cloud.manifest import FILE_NAME, Manifest, ManifestBuild, ManifestType
13
+ from nextmv.cloud.manifest import MANIFEST_FILE_NAME, Manifest, ManifestBuild, ManifestType
14
14
  from nextmv.logger import log
15
15
  from nextmv.model import Model, ModelConfiguration, _cleanup_python_model
16
16
 
@@ -55,7 +55,7 @@ def _package(
55
55
  raise Exception(f"error copying asset files {file['absolute_path']}: {e}") from e
56
56
 
57
57
  if verbose:
58
- log(f'📋 Copied files listed in "{FILE_NAME}" manifest.')
58
+ log(f'📋 Copied files listed in "{MANIFEST_FILE_NAME}" manifest.')
59
59
 
60
60
  if manifest.type == ManifestType.PYTHON:
61
61
  _cleanup_python_model(app_dir, model_configuration, verbose)
nextmv/cloud/run.py CHANGED
@@ -1,4 +1,43 @@
1
- """This module contains definitions for an app run."""
1
+ """This module contains definitions for an app run.
2
+
3
+ Classes
4
+ -------
5
+ Metadata
6
+ Metadata of a run, whether it was successful or not.
7
+ RunInformation
8
+ Information of a run.
9
+ ErrorLog
10
+ Error log of a run, when it was not successful.
11
+ RunResult
12
+ Result of a run, whether it was successful or not.
13
+ RunLog
14
+ Log of a run.
15
+ FormatInput
16
+ Input format for a run configuration.
17
+ Format
18
+ Format for a run configuration.
19
+ RunType
20
+ The actual type of the run.
21
+ RunTypeConfiguration
22
+ Defines the configuration for the type of the run that is being executed
23
+ on an application.
24
+ RunQueuing
25
+ RunQueuing configuration for a run.
26
+ RunConfiguration
27
+ Configuration for an app run.
28
+ ExternalRunResult
29
+ Result of a run used to configure a new application run as an
30
+ external one.
31
+ TrackedRunStatus
32
+ The status of a tracked run.
33
+ TrackedRun
34
+ An external run that is tracked in the Nextmv platform.
35
+
36
+ Functions
37
+ ---------
38
+ run_duration(start, end)
39
+ Calculate the duration of a run in milliseconds.
40
+ """
2
41
 
3
42
  import json
4
43
  from dataclasses import dataclass
@@ -14,19 +53,22 @@ from nextmv.input import Input, InputFormat
14
53
  from nextmv.output import Output, OutputFormat
15
54
 
16
55
 
17
- def run_duration(
18
- start: Union[datetime, float],
19
- end: Union[datetime, float],
20
- ) -> int:
56
+ def run_duration(start: Union[datetime, float], end: Union[datetime, float]) -> int:
21
57
  """
22
58
  Calculate the duration of a run in milliseconds.
23
59
 
60
+ You can import the `run_duration` function directly from `cloud`:
61
+
62
+ ```python
63
+ from nextmv.cloud import run_duration
64
+ ```
65
+
24
66
  Parameters
25
67
  ----------
26
- start : Union[datetime, float]
68
+ start : datetime or float
27
69
  The start time of the run. Can be a datetime object or a float
28
70
  representing the start time in seconds since the epoch.
29
- end : Union[datetime, float]
71
+ end : datetime or float
30
72
  The end time of the run. Can be a datetime object or a float
31
73
  representing the end time in seconds since the epoch.
32
74
 
@@ -34,20 +76,73 @@ def run_duration(
34
76
  -------
35
77
  int
36
78
  The duration of the run in milliseconds.
79
+
80
+ Raises
81
+ ------
82
+ ValueError
83
+ If the start time is after the end time.
84
+ TypeError
85
+ If start and end are not both datetime objects or both float numbers.
86
+
87
+ Examples
88
+ --------
89
+ >>> from datetime import datetime, timedelta
90
+ >>> start_dt = datetime(2023, 1, 1, 12, 0, 0)
91
+ >>> end_dt = datetime(2023, 1, 1, 12, 0, 1)
92
+ >>> run_duration(start_dt, end_dt)
93
+ 1000
94
+
95
+ >>> start_float = 1672574400.0 # Corresponds to 2023-01-01 12:00:00
96
+ >>> end_float = 1672574401.0 # Corresponds to 2023-01-01 12:00:01
97
+ >>> run_duration(start_float, end_float)
98
+ 1000
37
99
  """
38
100
  if isinstance(start, float) and isinstance(end, float):
39
101
  if start > end:
40
102
  raise ValueError("Start time must be before end time.")
41
103
  return int(round((end - start) * 1000))
104
+
42
105
  if isinstance(start, datetime) and isinstance(end, datetime):
43
106
  if start > end:
44
107
  raise ValueError("Start time must be before end time.")
45
108
  return int(round((end - start).total_seconds() * 1000))
109
+
46
110
  raise TypeError("Start and end must be either datetime or float.")
47
111
 
48
112
 
49
113
  class Metadata(BaseModel):
50
- """Metadata of a run, whether it was successful or not."""
114
+ """
115
+ Metadata of a run, whether it was successful or not.
116
+
117
+ You can import the `Metadata` class directly from `cloud`:
118
+
119
+ ```python
120
+ from nextmv.cloud import Metadata
121
+ ```
122
+
123
+ Parameters
124
+ ----------
125
+ application_id : str
126
+ ID of the application where the run was submitted to.
127
+ application_instance_id : str
128
+ ID of the instance where the run was submitted to.
129
+ application_version_id : str
130
+ ID of the version of the application where the run was submitted to.
131
+ created_at : datetime
132
+ Date and time when the run was created.
133
+ duration : float
134
+ Duration of the run in milliseconds.
135
+ error : str
136
+ Error message if the run failed.
137
+ input_size : float
138
+ Size of the input in bytes.
139
+ output_size : float
140
+ Size of the output in bytes.
141
+ status : Status
142
+ Deprecated: use status_v2.
143
+ status_v2 : StatusV2
144
+ Status of the run.
145
+ """
51
146
 
52
147
  application_id: str
53
148
  """ID of the application where the run was submitted to."""
@@ -72,7 +167,30 @@ class Metadata(BaseModel):
72
167
 
73
168
 
74
169
  class RunInformation(BaseModel):
75
- """Information of a run."""
170
+ """
171
+ Information of a run.
172
+
173
+ You can import the `RunInformation` class directly from `cloud`:
174
+
175
+ ```python
176
+ from nextmv.cloud import RunInformation
177
+ ```
178
+
179
+ Parameters
180
+ ----------
181
+ description : str
182
+ Description of the run.
183
+ id : str
184
+ ID of the run.
185
+ metadata : Metadata
186
+ Metadata of the run.
187
+ name : str
188
+ Name of the run.
189
+ user_email : str
190
+ Email of the user who submitted the run.
191
+ console_url : str, optional
192
+ URL to the run in the Nextmv console. Defaults to "".
193
+ """
76
194
 
77
195
  description: str
78
196
  """Description of the run."""
@@ -88,7 +206,24 @@ class RunInformation(BaseModel):
88
206
 
89
207
 
90
208
  class ErrorLog(BaseModel):
91
- """Error log of a run, when it was not successful."""
209
+ """
210
+ Error log of a run, when it was not successful.
211
+
212
+ You can import the `ErrorLog` class directly from `cloud`:
213
+
214
+ ```python
215
+ from nextmv.cloud import ErrorLog
216
+ ```
217
+
218
+ Parameters
219
+ ----------
220
+ error : str, optional
221
+ Error message. Defaults to None.
222
+ stdout : str, optional
223
+ Standard output. Defaults to None.
224
+ stderr : str, optional
225
+ Standard error. Defaults to None.
226
+ """
92
227
 
93
228
  error: Optional[str] = None
94
229
  """Error message."""
@@ -99,7 +234,24 @@ class ErrorLog(BaseModel):
99
234
 
100
235
 
101
236
  class RunResult(RunInformation):
102
- """Result of a run, whether it was successful or not."""
237
+ """
238
+ Result of a run, whether it was successful or not.
239
+
240
+ You can import the `RunResult` class directly from `cloud`:
241
+
242
+ ```python
243
+ from nextmv.cloud import RunResult
244
+ ```
245
+
246
+ Parameters
247
+ ----------
248
+ error_log : ErrorLog, optional
249
+ Error log of the run. Only available if the run failed. Defaults to
250
+ None.
251
+ output : dict[str, Any], optional
252
+ Output of the run. Only available if the run succeeded. Defaults to
253
+ None.
254
+ """
103
255
 
104
256
  error_log: Optional[ErrorLog] = None
105
257
  """Error log of the run. Only available if the run failed."""
@@ -108,14 +260,40 @@ class RunResult(RunInformation):
108
260
 
109
261
 
110
262
  class RunLog(BaseModel):
111
- """Log of a run."""
263
+ """
264
+ Log of a run.
265
+
266
+ You can import the `RunLog` class directly from `cloud`:
267
+
268
+ ```python
269
+ from nextmv.cloud import RunLog
270
+ ```
271
+
272
+ Parameters
273
+ ----------
274
+ log : str
275
+ Log of the run.
276
+ """
112
277
 
113
278
  log: str
114
279
  """Log of the run."""
115
280
 
116
281
 
117
282
  class FormatInput(BaseModel):
118
- """Input format for a run configuration."""
283
+ """
284
+ Input format for a run configuration.
285
+
286
+ You can import the `FormatInput` class directly from `cloud`:
287
+
288
+ ```python
289
+ from nextmv.cloud import FormatInput
290
+ ```
291
+
292
+ Parameters
293
+ ----------
294
+ input_type : InputFormat, optional
295
+ Type of the input format. Defaults to `InputFormat.JSON`.
296
+ """
119
297
 
120
298
  input_type: InputFormat = Field(
121
299
  serialization_alias="type",
@@ -126,7 +304,20 @@ class FormatInput(BaseModel):
126
304
 
127
305
 
128
306
  class Format(BaseModel):
129
- """Format for a run configuration."""
307
+ """
308
+ Format for a run configuration.
309
+
310
+ You can import the `Format` class directly from `cloud`:
311
+
312
+ ```python
313
+ from nextmv.cloud import Format
314
+ ```
315
+
316
+ Parameters
317
+ ----------
318
+ format_input : FormatInput
319
+ Input format for the run configuration.
320
+ """
130
321
 
131
322
  format_input: FormatInput = Field(
132
323
  serialization_alias="input",
@@ -136,7 +327,24 @@ class Format(BaseModel):
136
327
 
137
328
 
138
329
  class RunType(str, Enum):
139
- """The actual type of the run."""
330
+ """
331
+ The actual type of the run.
332
+
333
+ You can import the `RunType` class directly from `cloud`:
334
+
335
+ ```python
336
+ from nextmv.cloud import RunType
337
+ ```
338
+
339
+ Parameters
340
+ ----------
341
+ STANDARD : str
342
+ Standard run type.
343
+ EXTERNAL : str
344
+ External run type.
345
+ ENSEMBLE : str
346
+ Ensemble run type.
347
+ """
140
348
 
141
349
  STANDARD = "standard"
142
350
  """Standard run type."""
@@ -147,8 +355,25 @@ class RunType(str, Enum):
147
355
 
148
356
 
149
357
  class RunTypeConfiguration(BaseModel):
150
- """Defines the configuration for the type of the run that is being executed
151
- on an application."""
358
+ """
359
+ Defines the configuration for the type of the run that is being executed
360
+ on an application.
361
+
362
+ You can import the `RunTypeConfiguration` class directly from `cloud`:
363
+
364
+ ```python
365
+ from nextmv.cloud import RunTypeConfiguration
366
+ ```
367
+
368
+ Parameters
369
+ ----------
370
+ run_type : RunType
371
+ Type of the run.
372
+ definition_id : str, optional
373
+ ID of the definition for the run type. Defaults to None.
374
+ reference_id : str, optional
375
+ ID of the reference for the run type. Defaults to None.
376
+ """
152
377
 
153
378
  run_type: RunType = Field(
154
379
  serialization_alias="type",
@@ -162,7 +387,24 @@ class RunTypeConfiguration(BaseModel):
162
387
 
163
388
 
164
389
  class RunQueuing(BaseModel):
165
- """RunQueuing configuration for a run."""
390
+ """
391
+ RunQueuing configuration for a run.
392
+
393
+ You can import the `RunQueuing` class directly from `cloud`:
394
+
395
+ ```python
396
+ from nextmv.cloud import RunQueuing
397
+ ```
398
+
399
+ Parameters
400
+ ----------
401
+ priority : int, optional
402
+ Priority of the run in the queue. 1 is the highest priority, 9 is the
403
+ lowest priority. Defaults to None.
404
+ disabled : bool, optional
405
+ Whether the run should be queued, or not. If True, the run will not be
406
+ queued. If False, the run will be queued. Defaults to None.
407
+ """
166
408
 
167
409
  priority: Optional[int] = None
168
410
  """
@@ -176,7 +418,15 @@ class RunQueuing(BaseModel):
176
418
  """
177
419
 
178
420
  def __post_init_post_parse__(self):
179
- """Validations done after parsing the model."""
421
+ """
422
+ Validations done after parsing the model.
423
+
424
+ Raises
425
+ ------
426
+ ValueError
427
+ If priority is not between 1 and 9, or if disabled is not a
428
+ boolean value.
429
+ """
180
430
 
181
431
  if self.priority is not None and (self.priority < 1 or self.priority > 9):
182
432
  raise ValueError("Priority must be between 1 and 9.")
@@ -186,7 +436,28 @@ class RunQueuing(BaseModel):
186
436
 
187
437
 
188
438
  class RunConfiguration(BaseModel):
189
- """Configuration for an app run."""
439
+ """
440
+ Configuration for an app run.
441
+
442
+ You can import the `RunConfiguration` class directly from `cloud`:
443
+
444
+ ```python
445
+ from nextmv.cloud import RunConfiguration
446
+ ```
447
+
448
+ Parameters
449
+ ----------
450
+ execution_class : str, optional
451
+ Execution class for the instance. Defaults to None.
452
+ format : Format, optional
453
+ Format for the run configuration. Defaults to None.
454
+ run_type : RunTypeConfiguration, optional
455
+ Run type configuration for the run. Defaults to None.
456
+ secrets_collection_id : str, optional
457
+ ID of the secrets collection to use for the run. Defaults to None.
458
+ queuing : RunQueuing, optional
459
+ Queuing configuration for the run. Defaults to None.
460
+ """
190
461
 
191
462
  execution_class: Optional[str] = None
192
463
  """Execution class for the instance."""
@@ -201,8 +472,29 @@ class RunConfiguration(BaseModel):
201
472
 
202
473
 
203
474
  class ExternalRunResult(BaseModel):
204
- """Result of a run used to configure a new application run as an
205
- external one."""
475
+ """
476
+ Result of a run used to configure a new application run as an
477
+ external one.
478
+
479
+ You can import the `ExternalRunResult` class directly from `cloud`:
480
+
481
+ ```python
482
+ from nextmv.cloud import ExternalRunResult
483
+ ```
484
+
485
+ Parameters
486
+ ----------
487
+ output_upload_id : str, optional
488
+ ID of the output upload. Defaults to None.
489
+ error_upload_id : str, optional
490
+ ID of the error upload. Defaults to None.
491
+ status : str, optional
492
+ Status of the run. Must be "succeeded" or "failed". Defaults to None.
493
+ error_message : str, optional
494
+ Error message of the run. Defaults to None.
495
+ execution_duration : int, optional
496
+ Duration of the run, in milliseconds. Defaults to None.
497
+ """
206
498
 
207
499
  output_upload_id: Optional[str] = None
208
500
  """ID of the output upload."""
@@ -216,7 +508,14 @@ class ExternalRunResult(BaseModel):
216
508
  """Duration of the run, in milliseconds."""
217
509
 
218
510
  def __post_init_post_parse__(self):
219
- """Validations done after parsing the model."""
511
+ """
512
+ Validations done after parsing the model.
513
+
514
+ Raises
515
+ ------
516
+ ValueError
517
+ If the status value is not "succeeded" or "failed".
518
+ """
220
519
 
221
520
  valid_statuses = {"succeeded", "failed"}
222
521
  if self.status is not None and self.status not in valid_statuses:
@@ -227,7 +526,13 @@ class TrackedRunStatus(str, Enum):
227
526
  """
228
527
  The status of a tracked run.
229
528
 
230
- Attributes
529
+ You can import the `TrackedRunStatus` class directly from `cloud`:
530
+
531
+ ```python
532
+ from nextmv.cloud import TrackedRunStatus
533
+ ```
534
+
535
+ Parameters
231
536
  ----------
232
537
  SUCCEEDED : str
233
538
  The run succeeded.
@@ -246,28 +551,41 @@ class TrackedRun:
246
551
  """
247
552
  An external run that is tracked in the Nextmv platform.
248
553
 
249
- Attributes
554
+ You can import the `TrackedRun` class directly from `cloud`:
555
+
556
+ ```python
557
+ from nextmv.cloud import TrackedRun
558
+ ```
559
+
560
+ Parameters
250
561
  ----------
251
- input : Union[Input, dict[str, Any], str]
562
+ input : Input or dict[str, Any] or str
252
563
  The input of the run being tracked. Please note that if the input
253
564
  format is JSON, then the input data must be JSON serializable. This
254
565
  field is required.
255
- output : Union[Output, dict[str, Any], str]
566
+ output : Output or dict[str, Any] or str
256
567
  The output of the run being tracked. Please note that if the output
257
568
  format is JSON, then the output data must be JSON serializable. This
258
- field is required.
569
+ field is required. Only JSON output_format is supported.
259
570
  status : TrackedRunStatus
260
571
  The status of the run being tracked. This field is required.
261
- duration : Optional[int]
262
- The duration of the run being tracked, in seconds. This field is
263
- optional.
264
- error : Optional[str]
572
+ duration : int, optional
573
+ The duration of the run being tracked, in milliseconds. This field is
574
+ optional. Defaults to None.
575
+ error : str, optional
265
576
  An error message if the run failed. You should only specify this if the
266
577
  run failed (the `status` is `TrackedRunStatus.FAILED`), otherwise an
267
- exception will be raised. This field is optional.
268
- logs : Optional[list[str]]
578
+ exception will be raised. This field is optional. Defaults to None.
579
+ logs : list[str], optional
269
580
  The logs of the run being tracked. Each element of the list is a line in
270
- the log. This field is optional.
581
+ the log. This field is optional. Defaults to None.
582
+
583
+ Raises
584
+ ------
585
+ ValueError
586
+ If the status value is invalid, if an error message is provided for a
587
+ successful run, or if input/output formats are not JSON or
588
+ input/output dicts are not JSON serializable.
271
589
  """
272
590
 
273
591
  input: Union[Input, dict[str, Any], str]
@@ -287,7 +605,16 @@ class TrackedRun:
287
605
  the log."""
288
606
 
289
607
  def __post_init__(self): # noqa: C901
290
- """Validations done after parsing the model."""
608
+ """
609
+ Validations done after parsing the model.
610
+
611
+ Raises
612
+ ------
613
+ ValueError
614
+ If the status value is invalid, if an error message is provided for
615
+ a successful run, or if input/output formats are not JSON or
616
+ input/output dicts are not JSON serializable.
617
+ """
291
618
 
292
619
  valid_statuses = {TrackedRunStatus.SUCCEEDED, TrackedRunStatus.FAILED}
293
620
  if self.status not in valid_statuses:
@@ -318,14 +645,18 @@ class TrackedRun:
318
645
  """
319
646
  Returns the logs as a single string.
320
647
 
321
- Parameters
322
- ----------
323
- None
648
+ Each log entry is separated by a newline character.
324
649
 
325
650
  Returns
326
651
  -------
327
652
  str
328
- The logs as a single string.
653
+ The logs as a single string. If no logs are present, an empty
654
+ string is returned.
655
+
656
+ Raises
657
+ ------
658
+ TypeError
659
+ If `self.logs` is not a string or a list of strings.
329
660
  """
330
661
 
331
662
  if self.logs is None:
@@ -335,6 +666,6 @@ class TrackedRun:
335
666
  return self.logs
336
667
 
337
668
  if isinstance(self.logs, list):
338
- return "\n".join(self.logs)
669
+ return "\\n".join(self.logs)
339
670
 
340
671
  raise TypeError("Logs must be a string or a list of strings.")
nextmv/cloud/safe.py CHANGED
@@ -11,27 +11,27 @@ INDEX_TAG_CHAR_COUNT: int = 3 # room reserved for “-001”, “-xyz”, etc.
11
11
  RE_NON_ALNUM = re.compile(r"[^A-Za-z0-9]+")
12
12
 
13
13
 
14
- def kebab_case(value: str) -> str:
14
+ def _kebab_case(value: str) -> str:
15
15
  """Convert arbitrary text to `kebab-case` (lower-case, hyphen-separated)."""
16
16
 
17
17
  cleaned = RE_NON_ALNUM.sub(" ", value).strip()
18
18
  return "-".join(word.lower() for word in cleaned.split())
19
19
 
20
20
 
21
- def start_case(value: str) -> str:
21
+ def _start_case(value: str) -> str:
22
22
  """Convert `kebab-case` (or any hyphen/underscore string) to `Start Case`."""
23
23
 
24
24
  cleaned = re.sub(r"[-_]+", " ", value)
25
25
  return " ".join(word.capitalize() for word in cleaned.split())
26
26
 
27
27
 
28
- def nanoid(size: int = 8, alphabet: str = string.ascii_lowercase + string.digits) -> str:
28
+ def _nanoid(size: int = 8, alphabet: str = string.ascii_lowercase + string.digits) -> str:
29
29
  """Simple nanoid clone using the std-lib `secrets` module."""
30
30
 
31
31
  return "".join(secrets.choice(alphabet) for _ in range(size))
32
32
 
33
33
 
34
- def name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
34
+ def _name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
35
35
  """
36
36
  Generate a safe ID and human-readable name from a prefix and user-supplied
37
37
  identifier.
@@ -53,8 +53,8 @@ def name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
53
53
  if not prefix or not entity_id:
54
54
  return "", ""
55
55
 
56
- safe_user_defined_id = kebab_case(entity_id)
57
- random_slug = nanoid(8)
56
+ safe_user_defined_id = _kebab_case(entity_id)
57
+ random_slug = _nanoid(8)
58
58
 
59
59
  # Space available for user text once prefix, random slug and separator "-"
60
60
  # are accounted for
@@ -78,6 +78,6 @@ def name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
78
78
  safe_id_parts.append(safe_slug)
79
79
 
80
80
  safe_id = "-".join(filter(None, safe_id_parts)) + f"-{random_slug}"
81
- safe_name = start_case(safe_id)
81
+ safe_name = _start_case(safe_id)
82
82
 
83
83
  return safe_name, safe_id