nextmv 1.0.0.dev2__py3-none-any.whl → 1.0.0.dev4__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 +81 -29
- nextmv/cli/cloud/__init__.py +2 -0
- nextmv/cli/cloud/acceptance/create.py +20 -22
- nextmv/cli/cloud/acceptance/delete.py +7 -8
- nextmv/cli/cloud/acceptance/get.py +9 -10
- nextmv/cli/cloud/acceptance/list.py +3 -3
- nextmv/cli/cloud/acceptance/update.py +6 -6
- nextmv/cli/cloud/account/__init__.py +3 -3
- nextmv/cli/cloud/account/create.py +11 -11
- nextmv/cli/cloud/account/delete.py +6 -7
- nextmv/cli/cloud/account/get.py +3 -3
- nextmv/cli/cloud/account/update.py +5 -5
- nextmv/cli/cloud/app/create.py +25 -26
- nextmv/cli/cloud/app/delete.py +5 -6
- nextmv/cli/cloud/app/exists.py +2 -2
- nextmv/cli/cloud/app/get.py +2 -2
- nextmv/cli/cloud/app/list.py +3 -3
- nextmv/cli/cloud/app/push.py +269 -45
- nextmv/cli/cloud/app/update.py +12 -12
- nextmv/cli/cloud/batch/create.py +26 -28
- nextmv/cli/cloud/batch/delete.py +5 -6
- nextmv/cli/cloud/batch/get.py +8 -8
- nextmv/cli/cloud/batch/list.py +3 -3
- nextmv/cli/cloud/batch/metadata.py +4 -4
- nextmv/cli/cloud/batch/update.py +6 -6
- nextmv/cli/cloud/data/__init__.py +1 -1
- nextmv/cli/cloud/data/upload.py +15 -15
- nextmv/cli/cloud/ensemble/__init__.py +2 -0
- nextmv/cli/cloud/ensemble/create.py +21 -22
- nextmv/cli/cloud/ensemble/delete.py +5 -6
- nextmv/cli/cloud/ensemble/get.py +4 -4
- nextmv/cli/cloud/ensemble/list.py +63 -0
- nextmv/cli/cloud/ensemble/update.py +9 -9
- nextmv/cli/cloud/input_set/create.py +20 -22
- nextmv/cli/cloud/input_set/get.py +3 -3
- nextmv/cli/cloud/input_set/list.py +3 -3
- nextmv/cli/cloud/input_set/update.py +24 -24
- nextmv/cli/cloud/instance/create.py +14 -15
- nextmv/cli/cloud/instance/delete.py +5 -6
- nextmv/cli/cloud/instance/exists.py +2 -2
- nextmv/cli/cloud/instance/get.py +2 -2
- nextmv/cli/cloud/instance/list.py +3 -3
- nextmv/cli/cloud/instance/update.py +14 -14
- nextmv/cli/cloud/managed_input/create.py +14 -16
- nextmv/cli/cloud/managed_input/delete.py +6 -7
- nextmv/cli/cloud/managed_input/get.py +3 -3
- nextmv/cli/cloud/managed_input/list.py +3 -3
- nextmv/cli/cloud/managed_input/update.py +9 -9
- nextmv/cli/cloud/run/cancel.py +2 -2
- nextmv/cli/cloud/run/create.py +32 -33
- nextmv/cli/cloud/run/get.py +8 -8
- nextmv/cli/cloud/run/input.py +4 -4
- nextmv/cli/cloud/run/list.py +6 -6
- nextmv/cli/cloud/run/logs.py +9 -10
- nextmv/cli/cloud/run/metadata.py +4 -4
- nextmv/cli/cloud/run/track.py +32 -33
- nextmv/cli/cloud/scenario/create.py +21 -21
- nextmv/cli/cloud/scenario/delete.py +5 -6
- nextmv/cli/cloud/scenario/get.py +8 -8
- nextmv/cli/cloud/scenario/list.py +3 -3
- nextmv/cli/cloud/scenario/metadata.py +4 -4
- nextmv/cli/cloud/scenario/update.py +6 -6
- nextmv/cli/cloud/secrets/create.py +17 -17
- nextmv/cli/cloud/secrets/delete.py +5 -6
- nextmv/cli/cloud/secrets/get.py +4 -4
- nextmv/cli/cloud/secrets/list.py +3 -3
- nextmv/cli/cloud/secrets/update.py +17 -20
- nextmv/cli/cloud/shadow/__init__.py +1 -1
- nextmv/cli/cloud/shadow/create.py +32 -32
- nextmv/cli/cloud/shadow/delete.py +5 -6
- nextmv/cli/cloud/shadow/get.py +2 -2
- nextmv/cli/cloud/shadow/list.py +3 -3
- nextmv/cli/cloud/shadow/metadata.py +4 -4
- nextmv/cli/cloud/shadow/start.py +3 -3
- nextmv/cli/cloud/shadow/stop.py +8 -10
- nextmv/cli/cloud/shadow/update.py +7 -6
- nextmv/cli/cloud/switchback/__init__.py +33 -0
- nextmv/cli/cloud/switchback/create.py +151 -0
- nextmv/cli/cloud/switchback/delete.py +67 -0
- nextmv/cli/cloud/switchback/get.py +62 -0
- nextmv/cli/cloud/switchback/list.py +63 -0
- nextmv/cli/cloud/switchback/metadata.py +68 -0
- nextmv/cli/cloud/switchback/start.py +43 -0
- nextmv/cli/cloud/switchback/stop.py +41 -0
- nextmv/cli/cloud/switchback/update.py +96 -0
- nextmv/cli/cloud/upload/create.py +2 -2
- nextmv/cli/cloud/version/create.py +9 -10
- nextmv/cli/cloud/version/delete.py +5 -6
- nextmv/cli/cloud/version/exists.py +2 -2
- nextmv/cli/cloud/version/get.py +2 -2
- nextmv/cli/cloud/version/list.py +3 -3
- nextmv/cli/cloud/version/update.py +8 -8
- nextmv/cli/community/clone.py +12 -10
- nextmv/cli/community/list.py +9 -9
- nextmv/cli/configuration/config.py +43 -10
- nextmv/cli/configuration/create.py +3 -3
- nextmv/cli/configuration/delete.py +7 -7
- nextmv/cli/configuration/list.py +3 -3
- nextmv/cli/confirm.py +32 -0
- nextmv/cli/main.py +27 -36
- nextmv/cli/message.py +2 -2
- nextmv/cli/options.py +14 -0
- nextmv/cli/version.py +1 -1
- nextmv/cloud/__init__.py +5 -0
- nextmv/cloud/application/__init__.py +192 -54
- nextmv/cloud/application/_batch_scenario.py +2 -2
- nextmv/cloud/application/_instance.py +2 -2
- nextmv/cloud/application/_managed_input.py +1 -1
- nextmv/cloud/application/_shadow.py +1 -1
- nextmv/cloud/application/_switchback.py +323 -0
- nextmv/cloud/application/_version.py +3 -2
- nextmv/cloud/shadow.py +43 -4
- nextmv/cloud/switchback.py +226 -0
- {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/METADATA +1 -1
- nextmv-1.0.0.dev4.dist-info/RECORD +183 -0
- nextmv-1.0.0.dev2.dist-info/RECORD +0 -170
- {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/WHEEL +0 -0
- {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/entry_points.txt +0 -0
- {nextmv-1.0.0.dev2.dist-info → nextmv-1.0.0.dev4.dist-info}/licenses/LICENSE +0 -0
nextmv/cli/main.py
CHANGED
|
@@ -13,22 +13,25 @@ about the features used here. An example of Rich markup can be found in the
|
|
|
13
13
|
epilog of the Typer application defined below.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import os
|
|
17
16
|
import sys
|
|
18
17
|
from typing import Annotated
|
|
19
18
|
|
|
20
19
|
import rich
|
|
21
20
|
import typer
|
|
22
|
-
from
|
|
21
|
+
from typer import rich_utils
|
|
23
22
|
|
|
24
23
|
from nextmv.cli.cloud import app as cloud_app
|
|
25
24
|
from nextmv.cli.community import app as community_app
|
|
26
25
|
from nextmv.cli.configuration import app as configuration_app
|
|
27
26
|
from nextmv.cli.configuration.config import CONFIG_DIR, GO_CLI_PATH, load_config
|
|
27
|
+
from nextmv.cli.confirm import get_confirmation
|
|
28
28
|
from nextmv.cli.message import error, info, success, warning
|
|
29
29
|
from nextmv.cli.version import app as version_app
|
|
30
30
|
from nextmv.cli.version import version_callback
|
|
31
31
|
|
|
32
|
+
# Disable dim text for the extended help of commands.
|
|
33
|
+
rich_utils.STYLE_HELPTEXT = ""
|
|
34
|
+
|
|
32
35
|
# Main CLI application.
|
|
33
36
|
app = typer.Typer(
|
|
34
37
|
help="The Nextmv Command Line Interface (CLI).",
|
|
@@ -66,6 +69,15 @@ def callback(
|
|
|
66
69
|
environment.
|
|
67
70
|
"""
|
|
68
71
|
|
|
72
|
+
# Skip checks for help commands.
|
|
73
|
+
if "--help" in sys.argv or "-h" in sys.argv:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Skip checks for certain commands.
|
|
77
|
+
ignored_commands = {"configuration", "version"}
|
|
78
|
+
if ctx.invoked_subcommand in ignored_commands:
|
|
79
|
+
return
|
|
80
|
+
|
|
69
81
|
handle_go_cli()
|
|
70
82
|
handle_config_existence(ctx)
|
|
71
83
|
|
|
@@ -80,19 +92,21 @@ def handle_go_cli() -> None:
|
|
|
80
92
|
|
|
81
93
|
exists = go_cli_exists()
|
|
82
94
|
if exists:
|
|
83
|
-
delete =
|
|
95
|
+
delete = get_confirmation(
|
|
84
96
|
"Do you want to delete the [italic red]deprecated[/italic red] Nextmv CLI "
|
|
85
|
-
f"at [magenta]{GO_CLI_PATH}[/magenta] now?"
|
|
86
|
-
default=False,
|
|
97
|
+
f"at [magenta]{GO_CLI_PATH}[/magenta] now?"
|
|
87
98
|
)
|
|
88
99
|
if delete:
|
|
89
100
|
remove_go_cli()
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
info(
|
|
104
|
+
msg="You can delete the [italic red]deprecated[/italic red] Nextmv CLI later by removing "
|
|
105
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. "
|
|
106
|
+
"Make sure you also clean up your [code]PATH[/code], "
|
|
107
|
+
f"by removing references to [magenta]{CONFIG_DIR}[/magenta] from it.",
|
|
108
|
+
emoji=":bulb:",
|
|
109
|
+
)
|
|
96
110
|
|
|
97
111
|
|
|
98
112
|
def handle_config_existence(ctx: typer.Context) -> None:
|
|
@@ -105,10 +119,6 @@ def handle_config_existence(ctx: typer.Context) -> None:
|
|
|
105
119
|
The Typer context object.
|
|
106
120
|
"""
|
|
107
121
|
|
|
108
|
-
ignored_commands = {"configuration", "version"}
|
|
109
|
-
if ctx.invoked_subcommand in ignored_commands:
|
|
110
|
-
return
|
|
111
|
-
|
|
112
122
|
config = load_config()
|
|
113
123
|
if config == {}:
|
|
114
124
|
error("No configuration found. Please run [code]nextmv configuration create[/code].")
|
|
@@ -130,11 +140,9 @@ def go_cli_exists() -> bool:
|
|
|
130
140
|
if exists:
|
|
131
141
|
warning(
|
|
132
142
|
"A [italic red]deprecated[/italic red] Nextmv CLI is installed at "
|
|
133
|
-
f"[magenta]{GO_CLI_PATH}[/magenta]. You
|
|
143
|
+
f"[magenta]{GO_CLI_PATH}[/magenta]. You should delete it to avoid conflicts."
|
|
134
144
|
)
|
|
135
145
|
|
|
136
|
-
check_config_in_path()
|
|
137
|
-
|
|
138
146
|
return exists
|
|
139
147
|
|
|
140
148
|
|
|
@@ -145,24 +153,7 @@ def remove_go_cli() -> None:
|
|
|
145
153
|
|
|
146
154
|
if GO_CLI_PATH.exists():
|
|
147
155
|
GO_CLI_PATH.unlink()
|
|
148
|
-
success(f"Deleted deprecated [magenta]{GO_CLI_PATH}[/magenta].")
|
|
149
|
-
|
|
150
|
-
check_config_in_path()
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def check_config_in_path() -> None:
|
|
154
|
-
"""
|
|
155
|
-
Check if the configuration directory is in the PATH and notify the user.
|
|
156
|
-
"""
|
|
157
|
-
|
|
158
|
-
path_dirs = os.environ.get("PATH", "").split(os.pathsep)
|
|
159
|
-
config_dir_str = str(CONFIG_DIR)
|
|
160
|
-
|
|
161
|
-
if config_dir_str in path_dirs:
|
|
162
|
-
warning(
|
|
163
|
-
f"[magenta]{CONFIG_DIR}[/magenta] was found in your [code]PATH[/code]. "
|
|
164
|
-
f"You should remove any entries related to [magenta]{CONFIG_DIR}[/magenta] from your [code]PATH[/code]."
|
|
165
|
-
)
|
|
156
|
+
success(f"Deleted [italic red]deprecated[/italic red] [magenta]{GO_CLI_PATH}[/magenta].")
|
|
166
157
|
|
|
167
158
|
|
|
168
159
|
def main() -> None:
|
nextmv/cli/message.py
CHANGED
|
@@ -31,7 +31,7 @@ def error(msg: str) -> None:
|
|
|
31
31
|
if not msg.endswith("."):
|
|
32
32
|
msg += "."
|
|
33
33
|
|
|
34
|
-
rich.print(f"[red]Error:[/red] {msg}", file=sys.stderr)
|
|
34
|
+
rich.print(f":x: [red]Error:[/red] {msg}", file=sys.stderr)
|
|
35
35
|
|
|
36
36
|
raise typer.Exit(code=1)
|
|
37
37
|
|
|
@@ -67,7 +67,7 @@ def warning(msg: str) -> None:
|
|
|
67
67
|
if not msg.endswith("."):
|
|
68
68
|
msg += "."
|
|
69
69
|
|
|
70
|
-
rich.print(f":construction: {msg}", file=sys.stderr)
|
|
70
|
+
rich.print(f":construction: [yellow] Warning:[/yellow] {msg}", file=sys.stderr)
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def info(msg: str, emoji: str | None = None) -> None:
|
nextmv/cli/options.py
CHANGED
|
@@ -204,3 +204,17 @@ ShadowTestIDOption = Annotated[
|
|
|
204
204
|
metavar="SHADOW_TEST_ID",
|
|
205
205
|
),
|
|
206
206
|
]
|
|
207
|
+
|
|
208
|
+
# switchback_test_id option - can be used in any command that requires a switchback test ID.
|
|
209
|
+
# Define it as follows in commands or callbacks, as necessary:
|
|
210
|
+
# switchback_test_id: SwitchbackTestIDOption
|
|
211
|
+
SwitchbackTestIDOption = Annotated[
|
|
212
|
+
str,
|
|
213
|
+
typer.Option(
|
|
214
|
+
"--switchback-test-id",
|
|
215
|
+
"-s",
|
|
216
|
+
help="The Nextmv Cloud switchback test ID to use for this action.",
|
|
217
|
+
envvar="NEXTMV_SWITCHBACK_TEST_ID",
|
|
218
|
+
metavar="SWITCHBACK_TEST_ID",
|
|
219
|
+
),
|
|
220
|
+
]
|
nextmv/cli/version.py
CHANGED
nextmv/cloud/__init__.py
CHANGED
|
@@ -95,6 +95,11 @@ from .shadow import ShadowTestMetadata as ShadowTestMetadata
|
|
|
95
95
|
from .shadow import StartEvents as StartEvents
|
|
96
96
|
from .shadow import TerminationEvents as TerminationEvents
|
|
97
97
|
from .shadow import TestComparison as TestComparison
|
|
98
|
+
from .switchback import SwitchbackPlan as SwitchbackPlan
|
|
99
|
+
from .switchback import SwitchbackPlanUnit as SwitchbackPlanUnit
|
|
100
|
+
from .switchback import SwitchbackTest as SwitchbackTest
|
|
101
|
+
from .switchback import SwitchbackTestMetadata as SwitchbackTestMetadata
|
|
102
|
+
from .switchback import TestComparisonSingle as TestComparisonSingle
|
|
98
103
|
from .url import DownloadURL as DownloadURL
|
|
99
104
|
from .url import UploadURL as UploadURL
|
|
100
105
|
from .version import Version as Version
|
|
@@ -42,10 +42,13 @@ from nextmv.cloud.application._managed_input import ApplicationManagedInputMixin
|
|
|
42
42
|
from nextmv.cloud.application._run import ApplicationRunMixin
|
|
43
43
|
from nextmv.cloud.application._secrets import ApplicationSecretsMixin
|
|
44
44
|
from nextmv.cloud.application._shadow import ApplicationShadowMixin
|
|
45
|
+
from nextmv.cloud.application._switchback import ApplicationSwitchbackMixin
|
|
45
46
|
from nextmv.cloud.application._utils import _is_not_exist_error
|
|
46
47
|
from nextmv.cloud.application._version import ApplicationVersionMixin
|
|
47
48
|
from nextmv.cloud.client import Client
|
|
49
|
+
from nextmv.cloud.instance import Instance
|
|
48
50
|
from nextmv.cloud.url import UploadURL
|
|
51
|
+
from nextmv.cloud.version import Version
|
|
49
52
|
from nextmv.logger import log
|
|
50
53
|
from nextmv.manifest import Manifest
|
|
51
54
|
from nextmv.model import Model, ModelConfiguration
|
|
@@ -102,6 +105,7 @@ class Application(
|
|
|
102
105
|
ApplicationInputSetMixin,
|
|
103
106
|
ApplicationManagedInputMixin,
|
|
104
107
|
ApplicationShadowMixin,
|
|
108
|
+
ApplicationSwitchbackMixin,
|
|
105
109
|
):
|
|
106
110
|
"""
|
|
107
111
|
A published decision model that can be executed.
|
|
@@ -248,7 +252,7 @@ class Application(
|
|
|
248
252
|
def new(
|
|
249
253
|
cls,
|
|
250
254
|
client: Client,
|
|
251
|
-
name: str,
|
|
255
|
+
name: str | None = None,
|
|
252
256
|
id: str | None = None,
|
|
253
257
|
description: str | None = None,
|
|
254
258
|
is_workflow: bool | None = None,
|
|
@@ -266,13 +270,13 @@ class Application(
|
|
|
266
270
|
----------
|
|
267
271
|
client : Client
|
|
268
272
|
Client to use for interacting with the Nextmv Cloud API.
|
|
269
|
-
name : str
|
|
270
|
-
Name of the application.
|
|
271
|
-
id : str
|
|
273
|
+
name : str | None = None
|
|
274
|
+
Name of the application. Uses the ID as the name if not provided.
|
|
275
|
+
id : str | None = None
|
|
272
276
|
ID of the application. Will be generated if not provided.
|
|
273
|
-
description : str
|
|
277
|
+
description : str | None = None
|
|
274
278
|
Description of the application.
|
|
275
|
-
is_workflow : bool
|
|
279
|
+
is_workflow : bool | None = None
|
|
276
280
|
Whether the application is a Decision Workflow.
|
|
277
281
|
exist_ok : bool, default=False
|
|
278
282
|
If True and an application with the same ID already exists,
|
|
@@ -294,7 +298,10 @@ class Application(
|
|
|
294
298
|
>>> app = Application.new(client=client, name="My New App", id="my-app")
|
|
295
299
|
"""
|
|
296
300
|
|
|
297
|
-
if id is None:
|
|
301
|
+
if exist_ok and (id is None or id == ""):
|
|
302
|
+
raise ValueError("If exist_ok is True, id must be provided")
|
|
303
|
+
|
|
304
|
+
if id is None or id == "":
|
|
298
305
|
id = safe_id("app")
|
|
299
306
|
|
|
300
307
|
if exist_ok and cls.exists(client=client, id=id):
|
|
@@ -305,6 +312,9 @@ class Application(
|
|
|
305
312
|
|
|
306
313
|
return cls.from_dict({"client": client} | response.json())
|
|
307
314
|
|
|
315
|
+
if name is None or name == "":
|
|
316
|
+
name = id
|
|
317
|
+
|
|
308
318
|
payload = {
|
|
309
319
|
"name": name,
|
|
310
320
|
"id": id,
|
|
@@ -397,10 +407,14 @@ class Application(
|
|
|
397
407
|
model: Model | None = None,
|
|
398
408
|
model_configuration: ModelConfiguration | None = None,
|
|
399
409
|
rich_print: bool = False,
|
|
400
|
-
|
|
410
|
+
auto_create: bool = False,
|
|
401
411
|
version_id: str | None = None,
|
|
402
412
|
version_name: str | None = None,
|
|
403
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,
|
|
404
418
|
) -> None:
|
|
405
419
|
"""
|
|
406
420
|
Push an app to Nextmv Cloud.
|
|
@@ -418,14 +432,16 @@ class Application(
|
|
|
418
432
|
`nextmv.Model`. The model is encoded, some dependencies and
|
|
419
433
|
accompanying files are packaged, and the app is pushed to Nextmv Cloud.
|
|
420
434
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
that is created. If
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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.
|
|
429
445
|
|
|
430
446
|
Parameters
|
|
431
447
|
----------
|
|
@@ -445,21 +461,30 @@ class Application(
|
|
|
445
461
|
with `model`.
|
|
446
462
|
rich_print : bool, default=False
|
|
447
463
|
Whether to use rich printing when verbose output is enabled.
|
|
448
|
-
|
|
449
|
-
If True,
|
|
464
|
+
auto_create : bool, default=False
|
|
465
|
+
If True, automatically create a new version and instance after
|
|
466
|
+
pushing the app.
|
|
450
467
|
version_id : Optional[str], default=None
|
|
451
468
|
ID of the version to create after pushing the app. If None, a unique
|
|
452
469
|
ID will be generated.
|
|
453
470
|
version_name : Optional[str], default=None
|
|
454
|
-
Name of the version to create after pushing the app. If None, a
|
|
455
|
-
|
|
471
|
+
Name of the version to create after pushing the app. If None, a
|
|
472
|
+
name will be generated.
|
|
456
473
|
version_description : Optional[str], default=None
|
|
457
474
|
Description of the version to create after pushing the app. If None, a
|
|
458
475
|
generic description with a timestamp will be generated.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
None
|
|
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.
|
|
463
488
|
|
|
464
489
|
Raises
|
|
465
490
|
------
|
|
@@ -563,44 +588,43 @@ class Application(
|
|
|
563
588
|
except OSError as e:
|
|
564
589
|
raise Exception(f"error deleting output directory: {e}") from e
|
|
565
590
|
|
|
566
|
-
if
|
|
567
|
-
if verbose:
|
|
568
|
-
if rich_print:
|
|
569
|
-
rich.print(
|
|
570
|
-
f":white_check_mark: Push completed for Nextmv application [magenta]{self.id}[/magenta] "
|
|
571
|
-
"without creating a new version.",
|
|
572
|
-
file=sys.stderr,
|
|
573
|
-
)
|
|
574
|
-
else:
|
|
575
|
-
log("✅ Push completed without creating a new version for Nextmv application.")
|
|
576
|
-
|
|
591
|
+
if not auto_create:
|
|
577
592
|
return
|
|
578
593
|
|
|
579
|
-
now = datetime.now(timezone.utc)
|
|
580
|
-
if version_id is None:
|
|
581
|
-
version_id = safe_id(prefix="version") + f"-{now.strftime('%Y%m%d-%H%M%S')}"
|
|
582
|
-
if version_name is None:
|
|
583
|
-
version_name = f"Version {version_id}"
|
|
584
|
-
if version_description is None:
|
|
585
|
-
version_description = f"Version created automatically from push at {now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
|
|
586
|
-
|
|
587
|
-
version = self.new_version(
|
|
588
|
-
id=version_id,
|
|
589
|
-
name=version_name,
|
|
590
|
-
description=version_description,
|
|
591
|
-
)
|
|
592
|
-
version_dict = version.to_dict()
|
|
593
|
-
|
|
594
594
|
if verbose:
|
|
595
595
|
if rich_print:
|
|
596
596
|
rich.print(
|
|
597
|
-
f":
|
|
597
|
+
f":hourglass_flowing_sand: Push completed for Nextmv application [magenta]{self.id}[/magenta], "
|
|
598
|
+
"creating a new version and instance...",
|
|
598
599
|
file=sys.stderr,
|
|
599
600
|
)
|
|
600
|
-
rich.print_json(data=version_dict)
|
|
601
601
|
else:
|
|
602
|
-
log(
|
|
603
|
-
|
|
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
|
+
)
|
|
604
628
|
|
|
605
629
|
def update(
|
|
606
630
|
self,
|
|
@@ -917,6 +941,120 @@ class Application(
|
|
|
917
941
|
log(f'💥️ Successfully pushed to application: "{self.id}".')
|
|
918
942
|
log(json.dumps(data, indent=2))
|
|
919
943
|
|
|
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
|
+
|
|
920
1058
|
|
|
921
1059
|
def list_applications(client: Client) -> list[Application]:
|
|
922
1060
|
"""
|
|
@@ -304,7 +304,7 @@ class ApplicationBatchMixin:
|
|
|
304
304
|
"""
|
|
305
305
|
|
|
306
306
|
# Generate ID if not provided
|
|
307
|
-
if id is None:
|
|
307
|
+
if id is None or id == "":
|
|
308
308
|
id = safe_id("batch")
|
|
309
309
|
|
|
310
310
|
# Use ID as name if name not provided
|
|
@@ -481,7 +481,7 @@ class ApplicationBatchMixin:
|
|
|
481
481
|
raise ValueError("At least one scenario must be provided")
|
|
482
482
|
|
|
483
483
|
# Generate ID if not provided
|
|
484
|
-
if id is None:
|
|
484
|
+
if id is None or id == "":
|
|
485
485
|
id = safe_id("scenario")
|
|
486
486
|
|
|
487
487
|
# Use ID as name if name not provided
|
|
@@ -194,13 +194,13 @@ class ApplicationInstanceMixin:
|
|
|
194
194
|
'Production Instance'
|
|
195
195
|
"""
|
|
196
196
|
|
|
197
|
-
if exist_ok and id is None:
|
|
197
|
+
if exist_ok and (id is None or id == ""):
|
|
198
198
|
raise ValueError("If exist_ok is True, id must be provided")
|
|
199
199
|
|
|
200
200
|
if exist_ok and self.instance_exists(instance_id=id):
|
|
201
201
|
return self.instance(instance_id=id)
|
|
202
202
|
|
|
203
|
-
if id is None:
|
|
203
|
+
if id is None or id == "":
|
|
204
204
|
id = safe_id(prefix="instance")
|
|
205
205
|
if name is None:
|
|
206
206
|
name = id
|
|
@@ -147,7 +147,7 @@ class ApplicationManagedInputMixin:
|
|
|
147
147
|
if upload_id is None and run_id is None:
|
|
148
148
|
raise ValueError("Either upload_id or run_id must be specified")
|
|
149
149
|
|
|
150
|
-
if id is None:
|
|
150
|
+
if id is None or id == "":
|
|
151
151
|
id = safe_id(prefix="managed-input")
|
|
152
152
|
if name is None:
|
|
153
153
|
name = id
|