nextmv 0.30.0__py3-none-any.whl → 0.32.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/run.py DELETED
@@ -1,755 +0,0 @@
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
- FormatOutput
18
- Output format for a run configuration.
19
- Format
20
- Format for a run configuration.
21
- RunType
22
- The actual type of the run.
23
- RunTypeConfiguration
24
- Defines the configuration for the type of the run that is being executed
25
- on an application.
26
- RunQueuing
27
- RunQueuing configuration for a run.
28
- RunConfiguration
29
- Configuration for an app run.
30
- ExternalRunResult
31
- Result of a run used to configure a new application run as an
32
- external one.
33
- TrackedRunStatus
34
- The status of a tracked run.
35
- TrackedRun
36
- An external run that is tracked in the Nextmv platform.
37
-
38
- Functions
39
- ---------
40
- run_duration(start, end)
41
- Calculate the duration of a run in milliseconds.
42
- """
43
-
44
- from dataclasses import dataclass
45
- from datetime import datetime
46
- from enum import Enum
47
- from typing import Any, Optional, Union
48
-
49
- from pydantic import AliasChoices, Field
50
-
51
- from nextmv._serialization import serialize_json
52
- from nextmv.base_model import BaseModel
53
- from nextmv.cloud.status import Status, StatusV2
54
- from nextmv.input import Input, InputFormat
55
- from nextmv.output import Output, OutputFormat
56
-
57
-
58
- def run_duration(start: Union[datetime, float], end: Union[datetime, float]) -> int:
59
- """
60
- Calculate the duration of a run in milliseconds.
61
-
62
- You can import the `run_duration` function directly from `cloud`:
63
-
64
- ```python
65
- from nextmv.cloud import run_duration
66
- ```
67
-
68
- Parameters
69
- ----------
70
- start : datetime or float
71
- The start time of the run. Can be a datetime object or a float
72
- representing the start time in seconds since the epoch.
73
- end : datetime or float
74
- The end time of the run. Can be a datetime object or a float
75
- representing the end time in seconds since the epoch.
76
-
77
- Returns
78
- -------
79
- int
80
- The duration of the run in milliseconds.
81
-
82
- Raises
83
- ------
84
- ValueError
85
- If the start time is after the end time.
86
- TypeError
87
- If start and end are not both datetime objects or both float numbers.
88
-
89
- Examples
90
- --------
91
- >>> from datetime import datetime, timedelta
92
- >>> start_dt = datetime(2023, 1, 1, 12, 0, 0)
93
- >>> end_dt = datetime(2023, 1, 1, 12, 0, 1)
94
- >>> run_duration(start_dt, end_dt)
95
- 1000
96
-
97
- >>> start_float = 1672574400.0 # Corresponds to 2023-01-01 12:00:00
98
- >>> end_float = 1672574401.0 # Corresponds to 2023-01-01 12:00:01
99
- >>> run_duration(start_float, end_float)
100
- 1000
101
- """
102
- if isinstance(start, float) and isinstance(end, float):
103
- if start > end:
104
- raise ValueError("Start time must be before end time.")
105
- return int(round((end - start) * 1000))
106
-
107
- if isinstance(start, datetime) and isinstance(end, datetime):
108
- if start > end:
109
- raise ValueError("Start time must be before end time.")
110
- return int(round((end - start).total_seconds() * 1000))
111
-
112
- raise TypeError("Start and end must be either datetime or float.")
113
-
114
-
115
- class FormatInput(BaseModel):
116
- """
117
- Input format for a run configuration.
118
-
119
- You can import the `FormatInput` class directly from `cloud`:
120
-
121
- ```python
122
- from nextmv.cloud import FormatInput
123
- ```
124
-
125
- Parameters
126
- ----------
127
- input_type : InputFormat, optional
128
- Type of the input format. Defaults to `InputFormat.JSON`.
129
- """
130
-
131
- input_type: InputFormat = Field(
132
- serialization_alias="type",
133
- validation_alias=AliasChoices("type", "input_type"),
134
- default=InputFormat.JSON,
135
- )
136
- """Type of the input format."""
137
-
138
-
139
- class FormatOutput(BaseModel):
140
- """
141
- Output format for a run configuration.
142
-
143
- You can import the `FormatOutput` class directly from `cloud`:
144
-
145
- ```python
146
- from nextmv.cloud import FormatOutput
147
- ```
148
-
149
- Parameters
150
- ----------
151
- output_type : OutputFormat, optional
152
- Type of the output format. Defaults to `OutputFormat.JSON`.
153
- """
154
-
155
- output_type: OutputFormat = Field(
156
- serialization_alias="type",
157
- validation_alias=AliasChoices("type", "output_type"),
158
- default=OutputFormat.JSON,
159
- )
160
- """Type of the output format."""
161
-
162
-
163
- class Format(BaseModel):
164
- """
165
- Format for a run configuration.
166
-
167
- You can import the `Format` class directly from `cloud`:
168
-
169
- ```python
170
- from nextmv.cloud import Format
171
- ```
172
-
173
- Parameters
174
- ----------
175
- format_input : FormatInput
176
- Input format for the run configuration.
177
- """
178
-
179
- format_input: FormatInput = Field(
180
- serialization_alias="input",
181
- validation_alias=AliasChoices("input", "format_input"),
182
- )
183
- """Input format for the run configuration."""
184
- format_output: Optional[FormatOutput] = Field(
185
- serialization_alias="output",
186
- validation_alias=AliasChoices("output", "format_output"),
187
- default=None,
188
- )
189
- """Output format for the run configuration."""
190
-
191
-
192
- class Metadata(BaseModel):
193
- """
194
- Metadata of a run, whether it was successful or not.
195
-
196
- You can import the `Metadata` class directly from `cloud`:
197
-
198
- ```python
199
- from nextmv.cloud import Metadata
200
- ```
201
-
202
- Parameters
203
- ----------
204
- application_id : str
205
- ID of the application where the run was submitted to.
206
- application_instance_id : str
207
- ID of the instance where the run was submitted to.
208
- application_version_id : str
209
- ID of the version of the application where the run was submitted to.
210
- created_at : datetime
211
- Date and time when the run was created.
212
- duration : float
213
- Duration of the run in milliseconds.
214
- error : str
215
- Error message if the run failed.
216
- input_size : float
217
- Size of the input in bytes.
218
- output_size : float
219
- Size of the output in bytes.
220
- status : Status
221
- Deprecated: use status_v2.
222
- status_v2 : StatusV2
223
- Status of the run.
224
- """
225
-
226
- application_id: str
227
- """ID of the application where the run was submitted to."""
228
- application_instance_id: str
229
- """ID of the instance where the run was submitted to."""
230
- application_version_id: str
231
- """ID of the version of the application where the run was submitted to."""
232
- created_at: datetime
233
- """Date and time when the run was created."""
234
- duration: float
235
- """Duration of the run in milliseconds."""
236
- error: str
237
- """Error message if the run failed."""
238
- input_size: float
239
- """Size of the input in bytes."""
240
- output_size: float
241
- """Size of the output in bytes."""
242
- format: Format
243
- """Format of the input and output of the run."""
244
- status: Status
245
- """Deprecated: use status_v2."""
246
- status_v2: StatusV2
247
- """Status of the run."""
248
-
249
-
250
- class RunInformation(BaseModel):
251
- """
252
- Information of a run.
253
-
254
- You can import the `RunInformation` class directly from `cloud`:
255
-
256
- ```python
257
- from nextmv.cloud import RunInformation
258
- ```
259
-
260
- Parameters
261
- ----------
262
- description : str
263
- Description of the run.
264
- id : str
265
- ID of the run.
266
- metadata : Metadata
267
- Metadata of the run.
268
- name : str
269
- Name of the run.
270
- user_email : str
271
- Email of the user who submitted the run.
272
- console_url : str, optional
273
- URL to the run in the Nextmv console. Defaults to "".
274
- """
275
-
276
- description: str
277
- """Description of the run."""
278
- id: str
279
- """ID of the run."""
280
- metadata: Metadata
281
- """Metadata of the run."""
282
- name: str
283
- """Name of the run."""
284
- user_email: str
285
- """Email of the user who submitted the run."""
286
- console_url: str = Field(default="")
287
-
288
-
289
- class ErrorLog(BaseModel):
290
- """
291
- Error log of a run, when it was not successful.
292
-
293
- You can import the `ErrorLog` class directly from `cloud`:
294
-
295
- ```python
296
- from nextmv.cloud import ErrorLog
297
- ```
298
-
299
- Parameters
300
- ----------
301
- error : str, optional
302
- Error message. Defaults to None.
303
- stdout : str, optional
304
- Standard output. Defaults to None.
305
- stderr : str, optional
306
- Standard error. Defaults to None.
307
- """
308
-
309
- error: Optional[str] = None
310
- """Error message."""
311
- stdout: Optional[str] = None
312
- """Standard output."""
313
- stderr: Optional[str] = None
314
- """Standard error."""
315
-
316
-
317
- class RunResult(RunInformation):
318
- """
319
- Result of a run, whether it was successful or not.
320
-
321
- You can import the `RunResult` class directly from `cloud`:
322
-
323
- ```python
324
- from nextmv.cloud import RunResult
325
- ```
326
-
327
- Parameters
328
- ----------
329
- error_log : ErrorLog, optional
330
- Error log of the run. Only available if the run failed. Defaults to
331
- None.
332
- output : dict[str, Any], optional
333
- Output of the run. Only available if the run succeeded. Defaults to
334
- None.
335
- """
336
-
337
- error_log: Optional[ErrorLog] = None
338
- """Error log of the run. Only available if the run failed."""
339
- output: Optional[dict[str, Any]] = None
340
- """Output of the run. Only available if the run succeeded."""
341
-
342
-
343
- class RunLog(BaseModel):
344
- """
345
- Log of a run.
346
-
347
- You can import the `RunLog` class directly from `cloud`:
348
-
349
- ```python
350
- from nextmv.cloud import RunLog
351
- ```
352
-
353
- Parameters
354
- ----------
355
- log : str
356
- Log of the run.
357
- """
358
-
359
- log: str
360
- """Log of the run."""
361
-
362
-
363
- class RunType(str, Enum):
364
- """
365
- The actual type of the run.
366
-
367
- You can import the `RunType` class directly from `cloud`:
368
-
369
- ```python
370
- from nextmv.cloud import RunType
371
- ```
372
-
373
- Parameters
374
- ----------
375
- STANDARD : str
376
- Standard run type.
377
- EXTERNAL : str
378
- External run type.
379
- ENSEMBLE : str
380
- Ensemble run type.
381
- """
382
-
383
- STANDARD = "standard"
384
- """Standard run type."""
385
- EXTERNAL = "external"
386
- """External run type."""
387
- ENSEMBLE = "ensemble"
388
- """Ensemble run type."""
389
-
390
-
391
- class RunTypeConfiguration(BaseModel):
392
- """
393
- Defines the configuration for the type of the run that is being executed
394
- on an application.
395
-
396
- You can import the `RunTypeConfiguration` class directly from `cloud`:
397
-
398
- ```python
399
- from nextmv.cloud import RunTypeConfiguration
400
- ```
401
-
402
- Parameters
403
- ----------
404
- run_type : RunType
405
- Type of the run.
406
- definition_id : str, optional
407
- ID of the definition for the run type. Defaults to None.
408
- reference_id : str, optional
409
- ID of the reference for the run type. Defaults to None.
410
- """
411
-
412
- run_type: RunType = Field(
413
- serialization_alias="type",
414
- validation_alias=AliasChoices("type", "run_type"),
415
- )
416
- """Type of the run."""
417
- definition_id: Optional[str] = None
418
- """ID of the definition for the run type."""
419
- reference_id: Optional[str] = None
420
- """ID of the reference for the run type."""
421
-
422
-
423
- class RunQueuing(BaseModel):
424
- """
425
- RunQueuing configuration for a run.
426
-
427
- You can import the `RunQueuing` class directly from `cloud`:
428
-
429
- ```python
430
- from nextmv.cloud import RunQueuing
431
- ```
432
-
433
- Parameters
434
- ----------
435
- priority : int, optional
436
- Priority of the run in the queue. 1 is the highest priority, 9 is the
437
- lowest priority. Defaults to None.
438
- disabled : bool, optional
439
- Whether the run should be queued, or not. If True, the run will not be
440
- queued. If False, the run will be queued. Defaults to None.
441
- """
442
-
443
- priority: Optional[int] = None
444
- """
445
- Priority of the run in the queue. 1 is the highest priority, 9 is the
446
- lowest priority.
447
- """
448
- disabled: Optional[bool] = None
449
- """
450
- Whether the run should be queued, or not. If True, the run will not be
451
- queued. If False, the run will be queued.
452
- """
453
-
454
- def __post_init_post_parse__(self):
455
- """
456
- Validations done after parsing the model.
457
-
458
- Raises
459
- ------
460
- ValueError
461
- If priority is not between 1 and 9, or if disabled is not a
462
- boolean value.
463
- """
464
-
465
- if self.priority is not None and (self.priority < 1 or self.priority > 9):
466
- raise ValueError("Priority must be between 1 and 9.")
467
-
468
- if self.disabled is not None and self.disabled not in {True, False}:
469
- raise ValueError("Disabled must be a boolean value.")
470
-
471
-
472
- class RunConfiguration(BaseModel):
473
- """
474
- Configuration for an app run.
475
-
476
- You can import the `RunConfiguration` class directly from `cloud`:
477
-
478
- ```python
479
- from nextmv.cloud import RunConfiguration
480
- ```
481
-
482
- Parameters
483
- ----------
484
- execution_class : str, optional
485
- Execution class for the instance. Defaults to None.
486
- format : Format, optional
487
- Format for the run configuration. Defaults to None.
488
- run_type : RunTypeConfiguration, optional
489
- Run type configuration for the run. Defaults to None.
490
- secrets_collection_id : str, optional
491
- ID of the secrets collection to use for the run. Defaults to None.
492
- queuing : RunQueuing, optional
493
- Queuing configuration for the run. Defaults to None.
494
- """
495
-
496
- execution_class: Optional[str] = None
497
- """Execution class for the instance."""
498
- format: Optional[Format] = None
499
- """Format for the run configuration."""
500
- run_type: Optional[RunTypeConfiguration] = None
501
- """Run type configuration for the run."""
502
- secrets_collection_id: Optional[str] = None
503
- """ID of the secrets collection to use for the run."""
504
- queuing: Optional[RunQueuing] = None
505
- """Queuing configuration for the run."""
506
-
507
- def resolve(
508
- self,
509
- input: Union[Input, dict[str, Any], BaseModel, str],
510
- dir_path: Optional[str] = None,
511
- ) -> None:
512
- """
513
- Resolves the run configuration by modifying or setting the `format`,
514
- based on the type of input that is provided.
515
-
516
- Parameters
517
- ----------
518
- input : Input or dict[str, Any] or BaseModel or str, optional
519
- The input to use for resolving the run configuration.
520
- dir_path : str, optional
521
- The directory path where inputs can be loaded from.
522
- """
523
-
524
- # If the value is set by the user, do not change it.
525
- if self.format is not None:
526
- return
527
-
528
- self.format = Format(
529
- format_input=FormatInput(input_type=InputFormat.JSON),
530
- format_output=FormatOutput(output_type=OutputFormat.JSON),
531
- )
532
-
533
- if isinstance(input, dict):
534
- self.format.format_input.input_type = InputFormat.JSON
535
- elif isinstance(input, str):
536
- self.format.format_input.input_type = InputFormat.TEXT
537
- elif dir_path is not None and dir_path != "":
538
- # Kinda hard to detect if we should be working with CSV_ARCHIVE or
539
- # MULTI_FILE, so we default to MULTI_FILE.
540
- self.format.format_input.input_type = InputFormat.MULTI_FILE
541
- elif isinstance(input, Input):
542
- self.format.format_input.input_type = input.input_format
543
-
544
- # As input and output are symmetric, we set the output according to the input
545
- # format.
546
- if self.format.format_input.input_type == InputFormat.JSON:
547
- self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
548
- elif self.format.format_input.input_type == InputFormat.TEXT: # Text still maps to json
549
- self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
550
- elif self.format.format_input.input_type == InputFormat.CSV_ARCHIVE:
551
- self.format.format_output = FormatOutput(output_type=OutputFormat.CSV_ARCHIVE)
552
- elif self.format.format_input.input_type == InputFormat.MULTI_FILE:
553
- self.format.format_output = FormatOutput(output_type=OutputFormat.MULTI_FILE)
554
- else:
555
- self.format.format_output = FormatOutput(output_type=OutputFormat.JSON)
556
-
557
-
558
- class ExternalRunResult(BaseModel):
559
- """
560
- Result of a run used to configure a new application run as an
561
- external one.
562
-
563
- You can import the `ExternalRunResult` class directly from `cloud`:
564
-
565
- ```python
566
- from nextmv.cloud import ExternalRunResult
567
- ```
568
-
569
- Parameters
570
- ----------
571
- output_upload_id : str, optional
572
- ID of the output upload. Defaults to None.
573
- error_upload_id : str, optional
574
- ID of the error upload. Defaults to None.
575
- status : str, optional
576
- Status of the run. Must be "succeeded" or "failed". Defaults to None.
577
- error_message : str, optional
578
- Error message of the run. Defaults to None.
579
- execution_duration : int, optional
580
- Duration of the run, in milliseconds. Defaults to None.
581
- """
582
-
583
- output_upload_id: Optional[str] = None
584
- """ID of the output upload."""
585
- error_upload_id: Optional[str] = None
586
- """ID of the error upload."""
587
- status: Optional[str] = None
588
- """Status of the run."""
589
- error_message: Optional[str] = None
590
- """Error message of the run."""
591
- execution_duration: Optional[int] = None
592
- """Duration of the run, in milliseconds."""
593
-
594
- def __post_init_post_parse__(self):
595
- """
596
- Validations done after parsing the model.
597
-
598
- Raises
599
- ------
600
- ValueError
601
- If the status value is not "succeeded" or "failed".
602
- """
603
-
604
- valid_statuses = {"succeeded", "failed"}
605
- if self.status is not None and self.status not in valid_statuses:
606
- raise ValueError("Invalid status value, must be one of: " + ", ".join(valid_statuses))
607
-
608
-
609
- class TrackedRunStatus(str, Enum):
610
- """
611
- The status of a tracked run.
612
-
613
- You can import the `TrackedRunStatus` class directly from `cloud`:
614
-
615
- ```python
616
- from nextmv.cloud import TrackedRunStatus
617
- ```
618
-
619
- Parameters
620
- ----------
621
- SUCCEEDED : str
622
- The run succeeded.
623
- FAILED : str
624
- The run failed.
625
- """
626
-
627
- SUCCEEDED = "succeeded"
628
- """The run succeeded."""
629
- FAILED = "failed"
630
- """The run failed."""
631
-
632
-
633
- @dataclass
634
- class TrackedRun:
635
- """
636
- An external run that is tracked in the Nextmv platform.
637
-
638
- You can import the `TrackedRun` class directly from `cloud`:
639
-
640
- ```python
641
- from nextmv.cloud import TrackedRun
642
- ```
643
-
644
- Parameters
645
- ----------
646
- input : Input or dict[str, Any] or str
647
- The input of the run being tracked. Please note that if the input
648
- format is JSON, then the input data must be JSON serializable. This
649
- field is required.
650
- output : Output or dict[str, Any] or str
651
- The output of the run being tracked. Please note that if the output
652
- format is JSON, then the output data must be JSON serializable. This
653
- field is required. Only JSON output_format is supported.
654
- status : TrackedRunStatus
655
- The status of the run being tracked. This field is required.
656
- duration : int, optional
657
- The duration of the run being tracked, in milliseconds. This field is
658
- optional. Defaults to None.
659
- error : str, optional
660
- An error message if the run failed. You should only specify this if the
661
- run failed (the `status` is `TrackedRunStatus.FAILED`), otherwise an
662
- exception will be raised. This field is optional. Defaults to None.
663
- logs : list[str], optional
664
- The logs of the run being tracked. Each element of the list is a line in
665
- the log. This field is optional. Defaults to None.
666
-
667
- Raises
668
- ------
669
- ValueError
670
- If the status value is invalid, if an error message is provided for a
671
- successful run, or if input/output formats are not JSON or
672
- input/output dicts are not JSON serializable.
673
- """
674
-
675
- input: Union[Input, dict[str, Any], str]
676
- """The input of the run being tracked."""
677
- output: Union[Output, dict[str, Any], str]
678
- """The output of the run being tracked. Only JSON output_format is supported."""
679
- status: TrackedRunStatus
680
- """The status of the run being tracked"""
681
-
682
- duration: Optional[int] = None
683
- """The duration of the run being tracked, in milliseconds."""
684
- error: Optional[str] = None
685
- """An error message if the run failed. You should only specify this if the
686
- run failed, otherwise an exception will be raised."""
687
- logs: Optional[list[str]] = None
688
- """The logs of the run being tracked. Each element of the list is a line in
689
- the log."""
690
-
691
- def __post_init__(self): # noqa: C901
692
- """
693
- Validations done after parsing the model.
694
-
695
- Raises
696
- ------
697
- ValueError
698
- If the status value is invalid, if an error message is provided for
699
- a successful run, or if input/output formats are not JSON or
700
- input/output dicts are not JSON serializable.
701
- """
702
-
703
- valid_statuses = {TrackedRunStatus.SUCCEEDED, TrackedRunStatus.FAILED}
704
- if self.status not in valid_statuses:
705
- raise ValueError("Invalid status value, must be one of: " + ", ".join(valid_statuses))
706
-
707
- if self.error is not None and self.error != "" and self.status != TrackedRunStatus.FAILED:
708
- raise ValueError("Error message must be empty if the run succeeded.")
709
-
710
- if isinstance(self.input, Input):
711
- if self.input.input_format != InputFormat.JSON:
712
- raise ValueError("Input.input_format must be JSON.")
713
- elif isinstance(self.input, dict):
714
- try:
715
- _ = serialize_json(self.input)
716
- except (TypeError, OverflowError) as e:
717
- raise ValueError("Input is dict[str, Any] but it is not JSON serializable") from e
718
-
719
- if isinstance(self.output, Output):
720
- if self.output.output_format != OutputFormat.JSON:
721
- raise ValueError("Output.output_format must be JSON.")
722
- elif isinstance(self.output, dict):
723
- try:
724
- _ = serialize_json(self.output)
725
- except (TypeError, OverflowError) as e:
726
- raise ValueError("Output is dict[str, Any] but it is not JSON serializable") from e
727
-
728
- def logs_text(self) -> str:
729
- """
730
- Returns the logs as a single string.
731
-
732
- Each log entry is separated by a newline character.
733
-
734
- Returns
735
- -------
736
- str
737
- The logs as a single string. If no logs are present, an empty
738
- string is returned.
739
-
740
- Raises
741
- ------
742
- TypeError
743
- If `self.logs` is not a string or a list of strings.
744
- """
745
-
746
- if self.logs is None:
747
- return ""
748
-
749
- if isinstance(self.logs, str):
750
- return self.logs
751
-
752
- if isinstance(self.logs, list):
753
- return "\\n".join(self.logs)
754
-
755
- raise TypeError("Logs must be a string or a list of strings.")