nextmv 1.0.0.dev9__py3-none-any.whl → 1.1.0.dev0__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/__about__.py +1 -1
- nextmv/cli/CONTRIBUTING.md +31 -11
- nextmv/cli/cloud/__init__.py +2 -0
- nextmv/cli/cloud/acceptance/delete.py +1 -4
- nextmv/cli/cloud/account/__init__.py +5 -3
- nextmv/cli/cloud/account/create.py +4 -1
- nextmv/cli/cloud/account/delete.py +1 -1
- nextmv/cli/cloud/app/delete.py +1 -1
- nextmv/cli/cloud/app/push.py +23 -42
- nextmv/cli/cloud/batch/delete.py +1 -4
- nextmv/cli/cloud/ensemble/delete.py +1 -4
- nextmv/cli/cloud/input_set/__init__.py +2 -0
- nextmv/cli/cloud/input_set/delete.py +64 -0
- nextmv/cli/cloud/instance/delete.py +1 -1
- nextmv/cli/cloud/managed_input/delete.py +1 -1
- nextmv/cli/cloud/run/create.py +4 -9
- nextmv/cli/cloud/scenario/delete.py +1 -4
- nextmv/cli/cloud/secrets/delete.py +1 -4
- nextmv/cli/cloud/shadow/delete.py +1 -4
- nextmv/cli/cloud/shadow/stop.py +14 -2
- nextmv/cli/cloud/sso/__init__.py +32 -0
- nextmv/cli/cloud/sso/create.py +97 -0
- nextmv/cli/cloud/sso/delete.py +58 -0
- nextmv/cli/cloud/sso/disable.py +56 -0
- nextmv/cli/cloud/sso/enable.py +56 -0
- nextmv/cli/cloud/sso/get.py +61 -0
- nextmv/cli/cloud/sso/update.py +78 -0
- nextmv/cli/cloud/switchback/delete.py +1 -4
- nextmv/cli/cloud/switchback/stop.py +14 -2
- nextmv/cli/cloud/version/delete.py +1 -1
- nextmv/cli/community/__init__.py +1 -1
- nextmv/cli/community/clone.py +11 -198
- nextmv/cli/community/list.py +51 -116
- nextmv/cli/configuration/config.py +27 -0
- nextmv/cli/configuration/create.py +4 -4
- nextmv/cli/configuration/delete.py +1 -1
- nextmv/cli/main.py +2 -3
- nextmv/cli/message.py +71 -54
- nextmv/cloud/__init__.py +5 -0
- nextmv/cloud/account.py +12 -10
- nextmv/cloud/application/__init__.py +12 -204
- nextmv/cloud/application/_acceptance.py +13 -8
- nextmv/cloud/application/_input_set.py +42 -6
- nextmv/cloud/application/_run.py +2 -2
- nextmv/cloud/application/_shadow.py +9 -3
- nextmv/cloud/application/_switchback.py +11 -2
- nextmv/cloud/batch_experiment.py +3 -1
- nextmv/cloud/community.py +446 -0
- nextmv/cloud/integration.py +7 -4
- nextmv/cloud/shadow.py +25 -0
- nextmv/cloud/sso.py +248 -0
- nextmv/cloud/switchback.py +2 -0
- nextmv/model.py +40 -4
- nextmv/options.py +1 -1
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/METADATA +3 -1
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/RECORD +59 -49
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/entry_points.txt +0 -0
- {nextmv-1.0.0.dev9.dist-info → nextmv-1.1.0.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/cli/message.py
CHANGED
|
@@ -11,106 +11,107 @@ import rich
|
|
|
11
11
|
import typer
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def message(msg: str, emoji: str | None = None) -> None:
|
|
15
15
|
"""
|
|
16
|
-
Pretty-print
|
|
17
|
-
|
|
16
|
+
Pretty-print a message. Your message should end with a period. The use of
|
|
17
|
+
emojis is encouraged to give context to the message. An emoji should be a
|
|
18
|
+
string as specified in:
|
|
19
|
+
https://rich.readthedocs.io/en/latest/markup.html#emoji.
|
|
18
20
|
|
|
19
21
|
Parameters
|
|
20
22
|
----------
|
|
21
23
|
msg : str
|
|
22
|
-
The
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
The message to display.
|
|
25
|
+
emoji : str | None
|
|
26
|
+
An optional emoji to prefix the message. If None, no emoji is used. The
|
|
27
|
+
emoji should be a string as specified in:
|
|
28
|
+
https://rich.readthedocs.io/en/latest/markup.html#emoji. For example:
|
|
29
|
+
`:hourglass_flowing_sand:`.
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
|
-
msg = msg
|
|
31
|
-
if
|
|
32
|
-
msg
|
|
33
|
-
|
|
34
|
-
rich.print(f":x: [red]Error:[/red] {msg}", file=sys.stderr)
|
|
32
|
+
msg = _format(msg)
|
|
33
|
+
if emoji:
|
|
34
|
+
rich.print(f"{emoji} {msg}", file=sys.stderr)
|
|
35
|
+
return
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
rich.print(msg, file=sys.stderr)
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def
|
|
40
|
+
def info(msg: str) -> None:
|
|
40
41
|
"""
|
|
41
|
-
Pretty-print
|
|
42
|
+
Pretty-print an informational message. Your message should end with a
|
|
43
|
+
period.
|
|
42
44
|
|
|
43
45
|
Parameters
|
|
44
46
|
----------
|
|
45
47
|
msg : str
|
|
46
|
-
The
|
|
48
|
+
The informational message to display.
|
|
47
49
|
"""
|
|
48
50
|
|
|
49
|
-
msg =
|
|
50
|
-
if not msg.endswith("."):
|
|
51
|
-
msg += "."
|
|
51
|
+
message(msg, emoji=":bulb:")
|
|
52
52
|
|
|
53
|
-
rich.print(f":white_check_mark: {msg}", file=sys.stderr)
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
def warning(msg: str) -> None:
|
|
54
|
+
def in_progress(msg: str) -> None:
|
|
57
55
|
"""
|
|
58
|
-
Pretty-print
|
|
56
|
+
Pretty-print an in-progress message with an hourglass emoji. Your message
|
|
57
|
+
should end with a period.
|
|
59
58
|
|
|
60
59
|
Parameters
|
|
61
60
|
----------
|
|
62
61
|
msg : str
|
|
63
|
-
The
|
|
62
|
+
The in-progress message to display.
|
|
64
63
|
"""
|
|
65
64
|
|
|
66
|
-
msg =
|
|
67
|
-
if not msg.endswith("."):
|
|
68
|
-
msg += "."
|
|
69
|
-
|
|
70
|
-
rich.print(f":construction: [yellow] Warning:[/yellow] {msg}", file=sys.stderr)
|
|
65
|
+
message(msg, emoji=":hourglass_flowing_sand:")
|
|
71
66
|
|
|
72
67
|
|
|
73
|
-
def
|
|
68
|
+
def success(msg: str) -> None:
|
|
74
69
|
"""
|
|
75
|
-
Pretty-print
|
|
76
|
-
period. The use of emojis is encouraged to give context to the message. An
|
|
77
|
-
emoji should be a string as specified in:
|
|
78
|
-
https://rich.readthedocs.io/en/latest/markup.html#emoji.
|
|
70
|
+
Pretty-print a success message. Your message should end with a period.
|
|
79
71
|
|
|
80
72
|
Parameters
|
|
81
73
|
----------
|
|
82
74
|
msg : str
|
|
83
|
-
The
|
|
84
|
-
emoji : str | None
|
|
85
|
-
An optional emoji to prefix the message. If None, no emoji is used. The
|
|
86
|
-
emoji should be a string as specified in:
|
|
87
|
-
https://rich.readthedocs.io/en/latest/markup.html#emoji. For example:
|
|
88
|
-
`:hourglass_flowing_sand:`.
|
|
75
|
+
The success message to display.
|
|
89
76
|
"""
|
|
90
77
|
|
|
91
|
-
msg =
|
|
92
|
-
if not msg.endswith("."):
|
|
93
|
-
msg += "."
|
|
78
|
+
message(msg, emoji=":white_check_mark:")
|
|
94
79
|
|
|
95
|
-
if emoji:
|
|
96
|
-
rich.print(f"{emoji} {msg}", file=sys.stderr)
|
|
97
|
-
return
|
|
98
80
|
|
|
99
|
-
|
|
81
|
+
def warning(msg: str) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Pretty-print a warning message. Your message should end with a period.
|
|
100
84
|
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
msg : str
|
|
88
|
+
The warning message to display.
|
|
89
|
+
"""
|
|
101
90
|
|
|
102
|
-
|
|
91
|
+
msg = _format(msg)
|
|
92
|
+
rich.print(f":construction: [yellow] Warning:[/yellow] {msg}", file=sys.stderr)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def error(msg: str) -> None:
|
|
103
96
|
"""
|
|
104
|
-
Pretty-print an
|
|
105
|
-
|
|
97
|
+
Pretty-print an error message and exit with code 1. Your message should end
|
|
98
|
+
with a period.
|
|
106
99
|
|
|
107
100
|
Parameters
|
|
108
101
|
----------
|
|
109
102
|
msg : str
|
|
110
|
-
The
|
|
103
|
+
The error message to display.
|
|
104
|
+
|
|
105
|
+
Raises
|
|
106
|
+
------
|
|
107
|
+
typer.Exit
|
|
108
|
+
Exits the program with code 1.
|
|
111
109
|
"""
|
|
112
110
|
|
|
113
|
-
|
|
111
|
+
msg = _format(msg)
|
|
112
|
+
rich.print(f":x: [red]Error:[/red] {msg}", file=sys.stderr)
|
|
113
|
+
|
|
114
|
+
raise typer.Exit(code=1)
|
|
114
115
|
|
|
115
116
|
|
|
116
117
|
def print_json(data: dict[str, Any] | list[dict[str, Any]]) -> None:
|
|
@@ -151,3 +152,19 @@ def enum_values(enum_class: Enum) -> str:
|
|
|
151
152
|
return " and ".join(values)
|
|
152
153
|
|
|
153
154
|
return ", ".join(values[:-1]) + ", and " + values[-1]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _format(msg: str) -> str:
|
|
158
|
+
"""
|
|
159
|
+
Format a message to ensure it ends with a period.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
msg : str
|
|
164
|
+
The message to format.
|
|
165
|
+
"""
|
|
166
|
+
msg = msg.rstrip("\n")
|
|
167
|
+
if not msg.endswith("."):
|
|
168
|
+
msg += "."
|
|
169
|
+
|
|
170
|
+
return msg
|
nextmv/cloud/__init__.py
CHANGED
|
@@ -30,6 +30,9 @@ from .batch_experiment import BatchExperimentRun as BatchExperimentRun
|
|
|
30
30
|
from .batch_experiment import ExperimentStatus as ExperimentStatus
|
|
31
31
|
from .client import Client as Client
|
|
32
32
|
from .client import get_size as get_size
|
|
33
|
+
from .community import CommunityApp as CommunityApp
|
|
34
|
+
from .community import clone_community_app as clone_community_app
|
|
35
|
+
from .community import list_community_apps as list_community_apps
|
|
33
36
|
from .ensemble import EnsembleDefinition as EnsembleDefinition
|
|
34
37
|
from .ensemble import EvaluationRule as EvaluationRule
|
|
35
38
|
from .ensemble import RuleObjective as RuleObjective
|
|
@@ -55,8 +58,10 @@ from .secrets import SecretType as SecretType
|
|
|
55
58
|
from .shadow import ShadowTest as ShadowTest
|
|
56
59
|
from .shadow import ShadowTestMetadata as ShadowTestMetadata
|
|
57
60
|
from .shadow import StartEvents as StartEvents
|
|
61
|
+
from .shadow import StopIntent as StopIntent
|
|
58
62
|
from .shadow import TerminationEvents as TerminationEvents
|
|
59
63
|
from .shadow import TestComparison as TestComparison
|
|
64
|
+
from .sso import SSOConfiguration as SSOConfiguration
|
|
60
65
|
from .switchback import SwitchbackPlan as SwitchbackPlan
|
|
61
66
|
from .switchback import SwitchbackPlanUnit as SwitchbackPlanUnit
|
|
62
67
|
from .switchback import SwitchbackTest as SwitchbackTest
|
nextmv/cloud/account.py
CHANGED
|
@@ -188,30 +188,32 @@ class Account(BaseModel):
|
|
|
188
188
|
|
|
189
189
|
You can import the `Account` class directly from `cloud`:
|
|
190
190
|
|
|
191
|
-
```python
|
|
192
|
-
from nextmv.cloud import Account
|
|
193
|
-
```
|
|
191
|
+
```python from nextmv.cloud import Account ```
|
|
194
192
|
|
|
195
193
|
This class provides access to account-level operations in the Nextmv Cloud,
|
|
196
194
|
such as retrieving the queue of runs.
|
|
197
195
|
|
|
198
|
-
Note: It is recommended to use `Account.get()` or `Account.new()`
|
|
199
|
-
|
|
196
|
+
Note: It is recommended to use `Account.get()` or `Account.new()` instead
|
|
197
|
+
of direct initialization to ensure proper setup.
|
|
200
198
|
|
|
201
|
-
|
|
199
|
+
Attributes
|
|
202
200
|
----------
|
|
203
|
-
client : Client
|
|
204
|
-
Client to use for interacting with the Nextmv Cloud API.
|
|
205
201
|
account_id : str, optional
|
|
206
202
|
ID of the account (organization).
|
|
207
203
|
name : str, optional
|
|
208
204
|
Name of the account (organization).
|
|
209
205
|
members : list[AccountMember], optional
|
|
210
206
|
List of members in the account (organization).
|
|
207
|
+
client : Client
|
|
208
|
+
Client to use for interacting with the Nextmv Cloud API. This is an
|
|
209
|
+
SDK-specific attribute and it is not part of the API representation of
|
|
210
|
+
an account.
|
|
211
211
|
account_endpoint : str, default="v1/account"
|
|
212
|
-
Base endpoint for the account
|
|
212
|
+
Base endpoint for the account. This is an SDK-specific attribute and it
|
|
213
|
+
is not part of the API representation of an account.
|
|
213
214
|
organization_endpoint : str, default="v1/organization/{organization_id}"
|
|
214
|
-
Base endpoint for organization operations
|
|
215
|
+
Base endpoint for organization operations. This is an SDK-specific
|
|
216
|
+
attribute and it is not part of the API representation of an account.
|
|
215
217
|
|
|
216
218
|
Examples
|
|
217
219
|
--------
|
|
@@ -21,7 +21,7 @@ list_application
|
|
|
21
21
|
import json
|
|
22
22
|
import shutil
|
|
23
23
|
import sys
|
|
24
|
-
from datetime import datetime
|
|
24
|
+
from datetime import datetime
|
|
25
25
|
from enum import Enum
|
|
26
26
|
from typing import Any
|
|
27
27
|
|
|
@@ -46,9 +46,7 @@ from nextmv.cloud.application._switchback import ApplicationSwitchbackMixin
|
|
|
46
46
|
from nextmv.cloud.application._utils import _is_not_exist_error
|
|
47
47
|
from nextmv.cloud.application._version import ApplicationVersionMixin
|
|
48
48
|
from nextmv.cloud.client import Client
|
|
49
|
-
from nextmv.cloud.instance import Instance
|
|
50
49
|
from nextmv.cloud.url import UploadURL
|
|
51
|
-
from nextmv.cloud.version import Version
|
|
52
50
|
from nextmv.logger import log
|
|
53
51
|
from nextmv.manifest import Manifest
|
|
54
52
|
from nextmv.model import Model, ModelConfiguration
|
|
@@ -123,10 +121,8 @@ class Application(
|
|
|
123
121
|
Note: It is recommended to use `Application.get()` or `Application.new()`
|
|
124
122
|
instead of direct initialization to ensure proper setup.
|
|
125
123
|
|
|
126
|
-
|
|
124
|
+
Attributes
|
|
127
125
|
----------
|
|
128
|
-
client : Client
|
|
129
|
-
Client to use for interacting with the Nextmv Cloud API.
|
|
130
126
|
id : str
|
|
131
127
|
ID of the application.
|
|
132
128
|
name : str, optional
|
|
@@ -147,12 +143,19 @@ class Application(
|
|
|
147
143
|
Creation timestamp of the application.
|
|
148
144
|
updated_at : datetime, optional
|
|
149
145
|
Last update timestamp of the application.
|
|
146
|
+
client : Client
|
|
147
|
+
Client to use for interacting with the Nextmv Cloud API. This is an
|
|
148
|
+
SDK-specific attribute and it is not part of the API representation of
|
|
149
|
+
an application.
|
|
150
150
|
endpoint : str, default="v1/applications/{id}"
|
|
151
|
-
Base endpoint for the application
|
|
151
|
+
Base endpoint for the application. This is an SDK-specific attribute
|
|
152
|
+
and it is not part of the API representation of an application.
|
|
152
153
|
experiments_endpoint : str, default="{base}/experiments"
|
|
153
|
-
Base endpoint for experiments
|
|
154
|
+
Base endpoint for experiments. This is an SDK-specific attribute and it
|
|
155
|
+
is not part of the API representation of an application.
|
|
154
156
|
ensembles_endpoint : str, default="{base}/ensembles"
|
|
155
|
-
Base endpoint for ensembles
|
|
157
|
+
Base endpoint for ensembles. This is an SDK-specific attribute and it
|
|
158
|
+
is not part of the API representation of an application.
|
|
156
159
|
|
|
157
160
|
Examples
|
|
158
161
|
--------
|
|
@@ -407,14 +410,6 @@ class Application(
|
|
|
407
410
|
model: Model | None = None,
|
|
408
411
|
model_configuration: ModelConfiguration | None = None,
|
|
409
412
|
rich_print: bool = False,
|
|
410
|
-
auto_create: bool = False,
|
|
411
|
-
version_id: str | None = None,
|
|
412
|
-
version_name: str | None = None,
|
|
413
|
-
version_description: str | None = None,
|
|
414
|
-
instance_id: str | None = None,
|
|
415
|
-
instance_name: str | None = None,
|
|
416
|
-
instance_description: str | None = None,
|
|
417
|
-
update_default_instance: bool = False,
|
|
418
413
|
) -> None:
|
|
419
414
|
"""
|
|
420
415
|
Push an app to Nextmv Cloud.
|
|
@@ -432,17 +427,6 @@ class Application(
|
|
|
432
427
|
`nextmv.Model`. The model is encoded, some dependencies and
|
|
433
428
|
accompanying files are packaged, and the app is pushed to Nextmv Cloud.
|
|
434
429
|
|
|
435
|
-
By default, this function only pushes the app. If you want to
|
|
436
|
-
automatically create a new version and instance after pushing, set
|
|
437
|
-
`auto_create=True`. The `version_id`, `version_name`, and
|
|
438
|
-
`version_description` arguments allow you to customize the new version
|
|
439
|
-
that is created. If not specified, defaults will be generated (random
|
|
440
|
-
ID, timestamped name/description). Similarly, `instance_id`,
|
|
441
|
-
`instance_name`, and `instance_description` can be used to customize
|
|
442
|
-
the new instance. If `update_default_instance` is True, the
|
|
443
|
-
application's default instance will be updated to the newly created
|
|
444
|
-
instance.
|
|
445
|
-
|
|
446
430
|
Parameters
|
|
447
431
|
----------
|
|
448
432
|
manifest : Optional[Manifest], default=None
|
|
@@ -461,30 +445,6 @@ class Application(
|
|
|
461
445
|
with `model`.
|
|
462
446
|
rich_print : bool, default=False
|
|
463
447
|
Whether to use rich printing when verbose output is enabled.
|
|
464
|
-
auto_create : bool, default=False
|
|
465
|
-
If True, automatically create a new version and instance after
|
|
466
|
-
pushing the app.
|
|
467
|
-
version_id : Optional[str], default=None
|
|
468
|
-
ID of the version to create after pushing the app. If None, a unique
|
|
469
|
-
ID will be generated.
|
|
470
|
-
version_name : Optional[str], default=None
|
|
471
|
-
Name of the version to create after pushing the app. If None, a
|
|
472
|
-
name will be generated.
|
|
473
|
-
version_description : Optional[str], default=None
|
|
474
|
-
Description of the version to create after pushing the app. If None, a
|
|
475
|
-
generic description with a timestamp will be generated.
|
|
476
|
-
instance_id : Optional[str], default=None
|
|
477
|
-
ID of the instance to create after pushing the app. If None, a unique
|
|
478
|
-
ID will be generated.
|
|
479
|
-
instance_name : Optional[str], default=None
|
|
480
|
-
Name of the instance to create after pushing the app. If None, a
|
|
481
|
-
name will be generated.
|
|
482
|
-
instance_description : Optional[str], default=None
|
|
483
|
-
Description of the instance to create after pushing the app. If None,
|
|
484
|
-
a generic description with a timestamp will be generated.
|
|
485
|
-
update_default_instance : bool, default=False
|
|
486
|
-
If True, update the application's default instance to the newly
|
|
487
|
-
created instance.
|
|
488
448
|
|
|
489
449
|
Raises
|
|
490
450
|
------
|
|
@@ -588,44 +548,6 @@ class Application(
|
|
|
588
548
|
except OSError as e:
|
|
589
549
|
raise Exception(f"error deleting output directory: {e}") from e
|
|
590
550
|
|
|
591
|
-
if not auto_create:
|
|
592
|
-
return
|
|
593
|
-
|
|
594
|
-
if verbose:
|
|
595
|
-
if rich_print:
|
|
596
|
-
rich.print(
|
|
597
|
-
f":hourglass_flowing_sand: Push completed for Nextmv application [magenta]{self.id}[/magenta], "
|
|
598
|
-
"creating a new version and instance...",
|
|
599
|
-
file=sys.stderr,
|
|
600
|
-
)
|
|
601
|
-
else:
|
|
602
|
-
log("⌛️ Push completed for the Nextmv application, creating a new version and instance...")
|
|
603
|
-
|
|
604
|
-
now = datetime.now(timezone.utc)
|
|
605
|
-
version = self.__version_on_push(
|
|
606
|
-
now=now,
|
|
607
|
-
version_id=version_id,
|
|
608
|
-
version_name=version_name,
|
|
609
|
-
version_description=version_description,
|
|
610
|
-
verbose=verbose,
|
|
611
|
-
rich_print=rich_print,
|
|
612
|
-
)
|
|
613
|
-
instance = self.__instance_on_push(
|
|
614
|
-
now=now,
|
|
615
|
-
version_id=version.id,
|
|
616
|
-
instance_id=instance_id,
|
|
617
|
-
instance_name=instance_name,
|
|
618
|
-
instance_description=instance_description,
|
|
619
|
-
verbose=verbose,
|
|
620
|
-
rich_print=rich_print,
|
|
621
|
-
)
|
|
622
|
-
self.__update_on_push(
|
|
623
|
-
instance=instance,
|
|
624
|
-
update_default_instance=update_default_instance,
|
|
625
|
-
verbose=verbose,
|
|
626
|
-
rich_print=rich_print,
|
|
627
|
-
)
|
|
628
|
-
|
|
629
551
|
def update(
|
|
630
552
|
self,
|
|
631
553
|
name: str | None = None,
|
|
@@ -941,120 +863,6 @@ class Application(
|
|
|
941
863
|
log(f'💥️ Successfully pushed to application: "{self.id}".')
|
|
942
864
|
log(json.dumps(data, indent=2))
|
|
943
865
|
|
|
944
|
-
def __version_on_push(
|
|
945
|
-
self,
|
|
946
|
-
now: datetime,
|
|
947
|
-
version_id: str | None = None,
|
|
948
|
-
version_name: str | None = None,
|
|
949
|
-
version_description: str | None = None,
|
|
950
|
-
verbose: bool = False,
|
|
951
|
-
rich_print: bool = False,
|
|
952
|
-
) -> Version:
|
|
953
|
-
if version_description is None or version_description == "":
|
|
954
|
-
version_description = f"Version created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
|
|
955
|
-
|
|
956
|
-
version = self.new_version(
|
|
957
|
-
id=version_id,
|
|
958
|
-
name=version_name,
|
|
959
|
-
description=version_description,
|
|
960
|
-
)
|
|
961
|
-
version_dict = version.to_dict()
|
|
962
|
-
|
|
963
|
-
if not verbose:
|
|
964
|
-
return version
|
|
965
|
-
|
|
966
|
-
if rich_print:
|
|
967
|
-
rich.print(
|
|
968
|
-
f":white_check_mark: Automatically created new version [magenta]{version.id}[/magenta].",
|
|
969
|
-
file=sys.stderr,
|
|
970
|
-
)
|
|
971
|
-
rich.print_json(data=version_dict)
|
|
972
|
-
|
|
973
|
-
return version
|
|
974
|
-
|
|
975
|
-
log(f'✅ Automatically created new version "{version.id}".')
|
|
976
|
-
log(json.dumps(version_dict, indent=2))
|
|
977
|
-
|
|
978
|
-
return version
|
|
979
|
-
|
|
980
|
-
def __instance_on_push(
|
|
981
|
-
self,
|
|
982
|
-
now: datetime,
|
|
983
|
-
version_id: str,
|
|
984
|
-
instance_id: str | None = None,
|
|
985
|
-
instance_name: str | None = None,
|
|
986
|
-
instance_description: str | None = None,
|
|
987
|
-
verbose: bool = False,
|
|
988
|
-
rich_print: bool = False,
|
|
989
|
-
) -> Instance:
|
|
990
|
-
if instance_description is None or instance_description == "":
|
|
991
|
-
instance_description = f"Instance created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
|
|
992
|
-
|
|
993
|
-
instance = self.new_instance(
|
|
994
|
-
version_id=version_id,
|
|
995
|
-
id=instance_id,
|
|
996
|
-
name=instance_name,
|
|
997
|
-
description=instance_description,
|
|
998
|
-
)
|
|
999
|
-
instance_dict = instance.to_dict()
|
|
1000
|
-
|
|
1001
|
-
if not verbose:
|
|
1002
|
-
return instance
|
|
1003
|
-
|
|
1004
|
-
if rich_print:
|
|
1005
|
-
rich.print(
|
|
1006
|
-
f":white_check_mark: Automatically created new instance [magenta]{instance.id}[/magenta] "
|
|
1007
|
-
f"using version [magenta]{version_id}[/magenta].",
|
|
1008
|
-
file=sys.stderr,
|
|
1009
|
-
)
|
|
1010
|
-
rich.print_json(data=instance_dict)
|
|
1011
|
-
|
|
1012
|
-
return instance
|
|
1013
|
-
|
|
1014
|
-
log(f'✅ Automatically created new instance "{instance.id}" using version "{version_id}".')
|
|
1015
|
-
log(json.dumps(instance_dict, indent=2))
|
|
1016
|
-
|
|
1017
|
-
return instance
|
|
1018
|
-
|
|
1019
|
-
def __update_on_push(
|
|
1020
|
-
self,
|
|
1021
|
-
instance: Instance,
|
|
1022
|
-
update_default_instance: bool = False,
|
|
1023
|
-
verbose: bool = False,
|
|
1024
|
-
rich_print: bool = False,
|
|
1025
|
-
) -> None:
|
|
1026
|
-
if not update_default_instance:
|
|
1027
|
-
return
|
|
1028
|
-
|
|
1029
|
-
if verbose:
|
|
1030
|
-
if rich_print:
|
|
1031
|
-
rich.print(
|
|
1032
|
-
f":hourglass_flowing_sand: Updating default instance for app [magenta]{self.id}[/magenta]...",
|
|
1033
|
-
file=sys.stderr,
|
|
1034
|
-
)
|
|
1035
|
-
else:
|
|
1036
|
-
log("⌛️ Updating default instance for the Nextmv application...")
|
|
1037
|
-
|
|
1038
|
-
updated_app = self.update(default_instance_id=instance.id)
|
|
1039
|
-
if not verbose:
|
|
1040
|
-
return
|
|
1041
|
-
|
|
1042
|
-
if rich_print:
|
|
1043
|
-
rich.print(
|
|
1044
|
-
f":white_check_mark: Updated default instance to "
|
|
1045
|
-
f"[magenta]{updated_app.default_instance_id}[/magenta] for application "
|
|
1046
|
-
f"[magenta]{self.id}[/magenta].",
|
|
1047
|
-
file=sys.stderr,
|
|
1048
|
-
)
|
|
1049
|
-
rich.print_json(data=updated_app.to_dict())
|
|
1050
|
-
|
|
1051
|
-
return
|
|
1052
|
-
|
|
1053
|
-
log(
|
|
1054
|
-
f'✅ Updated default instance to "{updated_app.default_instance_id}" for application "{self.id}".',
|
|
1055
|
-
)
|
|
1056
|
-
log(json.dumps(updated_app.to_dict(), indent=2))
|
|
1057
|
-
|
|
1058
866
|
|
|
1059
867
|
def list_applications(client: Client) -> list[Application]:
|
|
1060
868
|
"""
|
|
@@ -9,6 +9,7 @@ import requests
|
|
|
9
9
|
from nextmv.cloud.acceptance_test import AcceptanceTest, Metric
|
|
10
10
|
from nextmv.cloud.batch_experiment import BatchExperimentRun, ExperimentStatus
|
|
11
11
|
from nextmv.polling import DEFAULT_POLLING_OPTIONS, PollingOptions, poll
|
|
12
|
+
from nextmv.safe import safe_id
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from . import Application
|
|
@@ -163,8 +164,8 @@ class ApplicationAcceptanceMixin:
|
|
|
163
164
|
self: "Application",
|
|
164
165
|
candidate_instance_id: str,
|
|
165
166
|
baseline_instance_id: str,
|
|
166
|
-
id: str,
|
|
167
167
|
metrics: list[Metric | dict[str, Any]],
|
|
168
|
+
id: str | None = None,
|
|
168
169
|
name: str | None = None,
|
|
169
170
|
input_set_id: str | None = None,
|
|
170
171
|
description: str | None = None,
|
|
@@ -185,8 +186,8 @@ class ApplicationAcceptanceMixin:
|
|
|
185
186
|
ID of the candidate instance.
|
|
186
187
|
baseline_instance_id : str
|
|
187
188
|
ID of the baseline instance.
|
|
188
|
-
id : str
|
|
189
|
-
ID of the acceptance test.
|
|
189
|
+
id : str | None, default=None
|
|
190
|
+
ID of the acceptance test. Will be generated if not provided.
|
|
190
191
|
metrics : list[Union[Metric, dict[str, Any]]]
|
|
191
192
|
List of metrics to use for the acceptance test.
|
|
192
193
|
name : Optional[str], default=None
|
|
@@ -210,6 +211,11 @@ class ApplicationAcceptanceMixin:
|
|
|
210
211
|
If the batch experiment ID does not match the acceptance test ID.
|
|
211
212
|
"""
|
|
212
213
|
|
|
214
|
+
# Generate ID if not provided
|
|
215
|
+
if id is None or id == "":
|
|
216
|
+
id = safe_id("acceptance")
|
|
217
|
+
|
|
218
|
+
# Use ID as name if name not provided
|
|
213
219
|
if name is None or name == "":
|
|
214
220
|
name = id
|
|
215
221
|
|
|
@@ -265,11 +271,10 @@ class ApplicationAcceptanceMixin:
|
|
|
265
271
|
"metrics": payload_metrics,
|
|
266
272
|
"experiment_id": batch_experiment_id,
|
|
267
273
|
"name": name,
|
|
274
|
+
"id": id,
|
|
268
275
|
}
|
|
269
276
|
if description is not None:
|
|
270
277
|
payload["description"] = description
|
|
271
|
-
if id is not None:
|
|
272
|
-
payload["id"] = id
|
|
273
278
|
|
|
274
279
|
response = self.client.request(
|
|
275
280
|
method="POST",
|
|
@@ -283,8 +288,8 @@ class ApplicationAcceptanceMixin:
|
|
|
283
288
|
self: "Application",
|
|
284
289
|
candidate_instance_id: str,
|
|
285
290
|
baseline_instance_id: str,
|
|
286
|
-
id: str,
|
|
287
291
|
metrics: list[Metric | dict[str, Any]],
|
|
292
|
+
id: str | None = None,
|
|
288
293
|
name: str | None = None,
|
|
289
294
|
input_set_id: str | None = None,
|
|
290
295
|
description: str | None = None,
|
|
@@ -302,8 +307,8 @@ class ApplicationAcceptanceMixin:
|
|
|
302
307
|
ID of the candidate instance.
|
|
303
308
|
baseline_instance_id : str
|
|
304
309
|
ID of the baseline instance.
|
|
305
|
-
id : str
|
|
306
|
-
ID of the acceptance test.
|
|
310
|
+
id : str | None, default=None
|
|
311
|
+
ID of the acceptance test. Will be generated if not provided.
|
|
307
312
|
metrics : list[Union[Metric, dict[str, Any]]]
|
|
308
313
|
List of metrics to use for the acceptance test.
|
|
309
314
|
name : Optional[str], default=None
|
|
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from nextmv.cloud.input_set import InputSet, ManagedInput
|
|
9
|
+
from nextmv.safe import safe_id
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from . import Application
|
|
@@ -16,6 +17,32 @@ class ApplicationInputSetMixin:
|
|
|
16
17
|
Mixin class for managing app input sets within an application.
|
|
17
18
|
"""
|
|
18
19
|
|
|
20
|
+
def delete_input_set(self: "Application", input_set_id: str) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Delete an input set.
|
|
23
|
+
|
|
24
|
+
Deletes an input set along with all the associated information.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
input_set_id : str
|
|
29
|
+
ID of the input set to delete.
|
|
30
|
+
|
|
31
|
+
Raises
|
|
32
|
+
------
|
|
33
|
+
requests.HTTPError
|
|
34
|
+
If the response status code is not 2xx.
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
>>> app.delete_input_set("input-set-123")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_ = self.client.request(
|
|
42
|
+
method="DELETE",
|
|
43
|
+
endpoint=f"{self.experiments_endpoint}/inputsets/{input_set_id}",
|
|
44
|
+
)
|
|
45
|
+
|
|
19
46
|
def input_set(self: "Application", input_set_id: str) -> InputSet:
|
|
20
47
|
"""
|
|
21
48
|
Get an input set.
|
|
@@ -81,8 +108,8 @@ class ApplicationInputSetMixin:
|
|
|
81
108
|
|
|
82
109
|
def new_input_set(
|
|
83
110
|
self: "Application",
|
|
84
|
-
id: str,
|
|
85
|
-
name: str,
|
|
111
|
+
id: str | None = None,
|
|
112
|
+
name: str | None = None,
|
|
86
113
|
description: str | None = None,
|
|
87
114
|
end_time: datetime | None = None,
|
|
88
115
|
instance_id: str | None = None,
|
|
@@ -107,10 +134,11 @@ class ApplicationInputSetMixin:
|
|
|
107
134
|
|
|
108
135
|
Parameters
|
|
109
136
|
----------
|
|
110
|
-
id: str
|
|
111
|
-
ID of the input set
|
|
112
|
-
name: str
|
|
113
|
-
Name of the input set.
|
|
137
|
+
id: str | None = None
|
|
138
|
+
ID of the input set, will be generated if not provided.
|
|
139
|
+
name: str | None = None
|
|
140
|
+
Name of the input set. If not provided, the ID will be used as
|
|
141
|
+
the name.
|
|
114
142
|
description: Optional[str]
|
|
115
143
|
Optional description of the input set.
|
|
116
144
|
end_time: Optional[datetime]
|
|
@@ -145,6 +173,14 @@ class ApplicationInputSetMixin:
|
|
|
145
173
|
If the response status code is not 2xx.
|
|
146
174
|
"""
|
|
147
175
|
|
|
176
|
+
# Generate ID if not provided
|
|
177
|
+
if id is None or id == "":
|
|
178
|
+
id = safe_id("input-set")
|
|
179
|
+
|
|
180
|
+
# Use ID as name if name not provided
|
|
181
|
+
if name is None or name == "":
|
|
182
|
+
name = id
|
|
183
|
+
|
|
148
184
|
payload = {
|
|
149
185
|
"id": id,
|
|
150
186
|
"name": name,
|