dstack 0.19.27__py3-none-any.whl → 0.19.28__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.
Potentially problematic release.
This version of dstack might be problematic. Click here for more details.
- dstack/_internal/cli/commands/__init__.py +11 -8
- dstack/_internal/cli/commands/apply.py +6 -3
- dstack/_internal/cli/commands/completion.py +3 -1
- dstack/_internal/cli/commands/config.py +1 -0
- dstack/_internal/cli/commands/init.py +2 -2
- dstack/_internal/cli/commands/offer.py +1 -1
- dstack/_internal/cli/commands/project.py +1 -0
- dstack/_internal/cli/commands/server.py +2 -2
- dstack/_internal/cli/main.py +1 -1
- dstack/_internal/cli/services/configurators/base.py +2 -4
- dstack/_internal/cli/services/configurators/fleet.py +4 -5
- dstack/_internal/cli/services/configurators/gateway.py +3 -5
- dstack/_internal/cli/services/configurators/run.py +51 -27
- dstack/_internal/cli/services/configurators/volume.py +3 -5
- dstack/_internal/core/compatibility/runs.py +2 -0
- dstack/_internal/core/models/common.py +67 -43
- dstack/_internal/core/models/configurations.py +88 -62
- dstack/_internal/core/models/fleets.py +41 -24
- dstack/_internal/core/models/instances.py +5 -5
- dstack/_internal/core/models/profiles.py +66 -47
- dstack/_internal/core/models/repos/remote.py +21 -16
- dstack/_internal/core/models/resources.py +69 -65
- dstack/_internal/core/models/runs.py +17 -9
- dstack/_internal/server/app.py +5 -0
- dstack/_internal/server/background/tasks/process_fleets.py +8 -0
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +32 -12
- dstack/_internal/server/models.py +6 -5
- dstack/_internal/server/schemas/gateways.py +10 -9
- dstack/_internal/server/services/backends/handlers.py +2 -0
- dstack/_internal/server/services/docker.py +8 -7
- dstack/_internal/server/services/projects.py +52 -1
- dstack/_internal/server/settings.py +46 -0
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-56191c63d516fd0041c4.css → main-5e0d56245c4bd241ec27.css} +1 -1
- dstack/_internal/server/statics/{main-4eecc75fbe64067eb1bc.js → main-a2a16772fbf11a14d191.js} +70 -100
- dstack/_internal/server/statics/{main-4eecc75fbe64067eb1bc.js.map → main-a2a16772fbf11a14d191.js.map} +1 -1
- dstack/_internal/utils/env.py +85 -11
- dstack/version.py +1 -1
- {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/METADATA +1 -1
- {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/RECORD +43 -44
- dstack/_internal/server/statics/static/media/github.1f7102513534c83a9d8d735d2b8c12a2.svg +0 -3
- {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/WHEEL +0 -0
- {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import os
|
|
3
|
+
import shlex
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import
|
|
5
|
+
from typing import ClassVar, Optional
|
|
5
6
|
|
|
6
7
|
from rich_argparse import RichHelpFormatter
|
|
7
8
|
|
|
8
9
|
from dstack._internal.cli.services.completion import ProjectNameCompleter
|
|
9
|
-
from dstack._internal.
|
|
10
|
+
from dstack._internal.core.errors import CLIError
|
|
10
11
|
from dstack.api import Client
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class BaseCommand(ABC):
|
|
14
|
-
NAME: str = "name the command"
|
|
15
|
-
DESCRIPTION: str = "describe the command"
|
|
16
|
-
DEFAULT_HELP: bool = True
|
|
17
|
-
ALIASES: Optional[
|
|
15
|
+
NAME: ClassVar[str] = "name the command"
|
|
16
|
+
DESCRIPTION: ClassVar[str] = "describe the command"
|
|
17
|
+
DEFAULT_HELP: ClassVar[bool] = True
|
|
18
|
+
ALIASES: ClassVar[Optional[list[str]]] = None
|
|
19
|
+
ACCEPT_EXTRA_ARGS: ClassVar[bool] = False
|
|
18
20
|
|
|
19
21
|
def __init__(self, parser: argparse.ArgumentParser):
|
|
20
22
|
self._parser = parser
|
|
@@ -50,7 +52,8 @@ class BaseCommand(ABC):
|
|
|
50
52
|
|
|
51
53
|
@abstractmethod
|
|
52
54
|
def _command(self, args: argparse.Namespace):
|
|
53
|
-
|
|
55
|
+
if not self.ACCEPT_EXTRA_ARGS and args.extra_args:
|
|
56
|
+
raise CLIError(f"Unrecognized arguments: {shlex.join(args.extra_args)}")
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
class APIBaseCommand(BaseCommand):
|
|
@@ -65,5 +68,5 @@ class APIBaseCommand(BaseCommand):
|
|
|
65
68
|
).completer = ProjectNameCompleter() # type: ignore[attr-defined]
|
|
66
69
|
|
|
67
70
|
def _command(self, args: argparse.Namespace):
|
|
68
|
-
|
|
71
|
+
super()._command(args)
|
|
69
72
|
self.api = Client.from_config(project_name=args.project)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import shlex
|
|
2
3
|
|
|
3
4
|
from argcomplete import FilesCompleter # type: ignore[attr-defined]
|
|
4
5
|
|
|
@@ -19,6 +20,7 @@ class ApplyCommand(APIBaseCommand):
|
|
|
19
20
|
NAME = "apply"
|
|
20
21
|
DESCRIPTION = "Apply a configuration"
|
|
21
22
|
DEFAULT_HELP = False
|
|
23
|
+
ACCEPT_EXTRA_ARGS = True
|
|
22
24
|
|
|
23
25
|
def _register(self):
|
|
24
26
|
super()._register()
|
|
@@ -84,13 +86,14 @@ class ApplyCommand(APIBaseCommand):
|
|
|
84
86
|
configurator_class = get_apply_configurator_class(configuration.type)
|
|
85
87
|
configurator = configurator_class(api_client=self.api)
|
|
86
88
|
configurator_parser = configurator.get_parser()
|
|
87
|
-
|
|
89
|
+
configurator_args, unknown_args = configurator_parser.parse_known_args(args.extra_args)
|
|
90
|
+
if unknown_args:
|
|
91
|
+
raise CLIError(f"Unrecognized arguments: {shlex.join(unknown_args)}")
|
|
88
92
|
configurator.apply_configuration(
|
|
89
93
|
conf=configuration,
|
|
90
94
|
configuration_path=configuration_path,
|
|
91
95
|
command_args=args,
|
|
92
|
-
configurator_args=
|
|
93
|
-
unknown_args=unknown,
|
|
96
|
+
configurator_args=configurator_args,
|
|
94
97
|
)
|
|
95
98
|
except KeyboardInterrupt:
|
|
96
99
|
console.print("\nOperation interrupted by user. Exiting...")
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
1
3
|
import argcomplete
|
|
2
4
|
|
|
3
5
|
from dstack._internal.cli.commands import BaseCommand
|
|
@@ -15,6 +17,6 @@ class CompletionCommand(BaseCommand):
|
|
|
15
17
|
choices=["bash", "zsh"],
|
|
16
18
|
)
|
|
17
19
|
|
|
18
|
-
def _command(self, args):
|
|
20
|
+
def _command(self, args: argparse.Namespace):
|
|
19
21
|
super()._command(args)
|
|
20
22
|
print(argcomplete.shellcode(["dstack"], shell=args.shell)) # type: ignore[attr-defined]
|
|
@@ -9,7 +9,7 @@ from dstack._internal.cli.services.repos import (
|
|
|
9
9
|
is_git_repo_url,
|
|
10
10
|
register_init_repo_args,
|
|
11
11
|
)
|
|
12
|
-
from dstack._internal.cli.utils.common import
|
|
12
|
+
from dstack._internal.cli.utils.common import confirm_ask, console, warn
|
|
13
13
|
from dstack._internal.core.errors import ConfigurationError
|
|
14
14
|
from dstack._internal.core.models.repos.remote import RemoteRepo
|
|
15
15
|
from dstack._internal.core.services.configs import ConfigManager
|
|
@@ -52,7 +52,7 @@ class InitCommand(BaseCommand):
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
def _command(self, args: argparse.Namespace):
|
|
55
|
-
|
|
55
|
+
super()._command(args)
|
|
56
56
|
|
|
57
57
|
repo_path: Optional[Path] = None
|
|
58
58
|
repo_url: Optional[str] = None
|
|
@@ -99,7 +99,7 @@ class OfferCommand(APIBaseCommand):
|
|
|
99
99
|
conf = TaskConfiguration(commands=[":"])
|
|
100
100
|
|
|
101
101
|
configurator = OfferConfigurator(api_client=self.api)
|
|
102
|
-
configurator.apply_args(conf, args
|
|
102
|
+
configurator.apply_args(conf, args)
|
|
103
103
|
profile = load_profile(Path.cwd(), profile_name=args.profile)
|
|
104
104
|
|
|
105
105
|
run_spec = RunSpec(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import argparse
|
|
1
2
|
import os
|
|
2
|
-
from argparse import Namespace
|
|
3
3
|
|
|
4
4
|
from dstack._internal import settings
|
|
5
5
|
from dstack._internal.cli.commands import BaseCommand
|
|
@@ -53,7 +53,7 @@ class ServerCommand(BaseCommand):
|
|
|
53
53
|
)
|
|
54
54
|
self._parser.add_argument("--token", type=str, help="The admin user token")
|
|
55
55
|
|
|
56
|
-
def _command(self, args: Namespace):
|
|
56
|
+
def _command(self, args: argparse.Namespace):
|
|
57
57
|
super()._command(args)
|
|
58
58
|
|
|
59
59
|
if not UVICORN_INSTALLED:
|
dstack/_internal/cli/main.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import os
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import Generic, List, TypeVar, Union, cast
|
|
4
|
+
from typing import ClassVar, Generic, List, TypeVar, Union, cast
|
|
5
5
|
|
|
6
6
|
from dstack._internal.cli.services.args import env_var
|
|
7
7
|
from dstack._internal.core.errors import ConfigurationError
|
|
@@ -18,7 +18,7 @@ ApplyConfigurationT = TypeVar("ApplyConfigurationT", bound=AnyApplyConfiguration
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class BaseApplyConfigurator(ABC, Generic[ApplyConfigurationT]):
|
|
21
|
-
TYPE: ApplyConfigurationType
|
|
21
|
+
TYPE: ClassVar[ApplyConfigurationType]
|
|
22
22
|
|
|
23
23
|
def __init__(self, api_client: Client):
|
|
24
24
|
self.api = api_client
|
|
@@ -30,7 +30,6 @@ class BaseApplyConfigurator(ABC, Generic[ApplyConfigurationT]):
|
|
|
30
30
|
configuration_path: str,
|
|
31
31
|
command_args: argparse.Namespace,
|
|
32
32
|
configurator_args: argparse.Namespace,
|
|
33
|
-
unknown_args: List[str],
|
|
34
33
|
):
|
|
35
34
|
"""
|
|
36
35
|
Implements `dstack apply` for a given configuration type.
|
|
@@ -40,7 +39,6 @@ class BaseApplyConfigurator(ABC, Generic[ApplyConfigurationT]):
|
|
|
40
39
|
configuration_path: The path to the configuration file.
|
|
41
40
|
command_args: The args parsed by `dstack apply`.
|
|
42
41
|
configurator_args: The known args parsed by `cls.get_parser()`.
|
|
43
|
-
unknown_args: The unknown args after parsing by `cls.get_parser()`.
|
|
44
42
|
"""
|
|
45
43
|
pass
|
|
46
44
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import time
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from rich.table import Table
|
|
7
7
|
|
|
@@ -46,7 +46,7 @@ logger = get_logger(__name__)
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class FleetConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator[FleetConfiguration]):
|
|
49
|
-
TYPE
|
|
49
|
+
TYPE = ApplyConfigurationType.FLEET
|
|
50
50
|
|
|
51
51
|
def apply_configuration(
|
|
52
52
|
self,
|
|
@@ -54,9 +54,8 @@ class FleetConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator[Fle
|
|
|
54
54
|
configuration_path: str,
|
|
55
55
|
command_args: argparse.Namespace,
|
|
56
56
|
configurator_args: argparse.Namespace,
|
|
57
|
-
unknown_args: List[str],
|
|
58
57
|
):
|
|
59
|
-
self.apply_args(conf, configurator_args
|
|
58
|
+
self.apply_args(conf, configurator_args)
|
|
60
59
|
profile = load_profile(Path.cwd(), None)
|
|
61
60
|
spec = FleetSpec(
|
|
62
61
|
configuration=conf,
|
|
@@ -309,7 +308,7 @@ class FleetConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator[Fle
|
|
|
309
308
|
)
|
|
310
309
|
cls.register_env_args(configuration_group)
|
|
311
310
|
|
|
312
|
-
def apply_args(self, conf: FleetConfiguration, args: argparse.Namespace
|
|
311
|
+
def apply_args(self, conf: FleetConfiguration, args: argparse.Namespace):
|
|
313
312
|
if args.name:
|
|
314
313
|
conf.name = args.name
|
|
315
314
|
self.apply_env_vars(conf.env, args)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import time
|
|
3
|
-
from typing import List
|
|
4
3
|
|
|
5
4
|
from rich.table import Table
|
|
6
5
|
|
|
@@ -27,7 +26,7 @@ from dstack.api._public import Client
|
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class GatewayConfigurator(BaseApplyConfigurator[GatewayConfiguration]):
|
|
30
|
-
TYPE
|
|
29
|
+
TYPE = ApplyConfigurationType.GATEWAY
|
|
31
30
|
|
|
32
31
|
def apply_configuration(
|
|
33
32
|
self,
|
|
@@ -35,9 +34,8 @@ class GatewayConfigurator(BaseApplyConfigurator[GatewayConfiguration]):
|
|
|
35
34
|
configuration_path: str,
|
|
36
35
|
command_args: argparse.Namespace,
|
|
37
36
|
configurator_args: argparse.Namespace,
|
|
38
|
-
unknown_args: List[str],
|
|
39
37
|
):
|
|
40
|
-
self.apply_args(conf, configurator_args
|
|
38
|
+
self.apply_args(conf, configurator_args)
|
|
41
39
|
spec = GatewaySpec(
|
|
42
40
|
configuration=conf,
|
|
43
41
|
configuration_path=configuration_path,
|
|
@@ -179,7 +177,7 @@ class GatewayConfigurator(BaseApplyConfigurator[GatewayConfiguration]):
|
|
|
179
177
|
help="The gateway name",
|
|
180
178
|
)
|
|
181
179
|
|
|
182
|
-
def apply_args(self, conf: GatewayConfiguration, args: argparse.Namespace
|
|
180
|
+
def apply_args(self, conf: GatewayConfiguration, args: argparse.Namespace):
|
|
183
181
|
if args.name:
|
|
184
182
|
conf.name = args.name
|
|
185
183
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import shlex
|
|
2
3
|
import subprocess
|
|
3
4
|
import sys
|
|
4
5
|
import time
|
|
@@ -35,6 +36,7 @@ from dstack._internal.core.models.configurations import (
|
|
|
35
36
|
LEGACY_REPO_DIR,
|
|
36
37
|
AnyRunConfiguration,
|
|
37
38
|
ApplyConfigurationType,
|
|
39
|
+
ConfigurationWithCommandsParams,
|
|
38
40
|
ConfigurationWithPortsParams,
|
|
39
41
|
DevEnvironmentConfiguration,
|
|
40
42
|
PortMapping,
|
|
@@ -80,20 +82,17 @@ class BaseRunConfigurator(
|
|
|
80
82
|
ApplyEnvVarsConfiguratorMixin,
|
|
81
83
|
BaseApplyConfigurator[RunConfigurationT],
|
|
82
84
|
):
|
|
83
|
-
TYPE: ApplyConfigurationType
|
|
84
|
-
|
|
85
85
|
def apply_configuration(
|
|
86
86
|
self,
|
|
87
87
|
conf: RunConfigurationT,
|
|
88
88
|
configuration_path: str,
|
|
89
89
|
command_args: argparse.Namespace,
|
|
90
90
|
configurator_args: argparse.Namespace,
|
|
91
|
-
unknown_args: List[str],
|
|
92
91
|
):
|
|
93
92
|
if configurator_args.repo and configurator_args.no_repo:
|
|
94
93
|
raise CLIError("Either --repo or --no-repo can be specified")
|
|
95
94
|
|
|
96
|
-
self.apply_args(conf, configurator_args
|
|
95
|
+
self.apply_args(conf, configurator_args)
|
|
97
96
|
self.validate_gpu_vendor_and_image(conf)
|
|
98
97
|
self.validate_cpu_arch_and_image(conf)
|
|
99
98
|
|
|
@@ -395,7 +394,7 @@ class BaseRunConfigurator(
|
|
|
395
394
|
)
|
|
396
395
|
register_init_repo_args(repo_group)
|
|
397
396
|
|
|
398
|
-
def apply_args(self, conf: RunConfigurationT, args: argparse.Namespace
|
|
397
|
+
def apply_args(self, conf: RunConfigurationT, args: argparse.Namespace):
|
|
399
398
|
apply_profile_args(args, conf)
|
|
400
399
|
if args.run_name:
|
|
401
400
|
conf.name = args.run_name
|
|
@@ -408,16 +407,6 @@ class BaseRunConfigurator(
|
|
|
408
407
|
|
|
409
408
|
self.apply_env_vars(conf.env, args)
|
|
410
409
|
self.interpolate_env(conf)
|
|
411
|
-
self.interpolate_run_args(conf.setup, unknown)
|
|
412
|
-
|
|
413
|
-
def interpolate_run_args(self, value: List[str], unknown):
|
|
414
|
-
run_args = " ".join(unknown)
|
|
415
|
-
interpolator = VariablesInterpolator({"run": {"args": run_args}}, skip=["secrets"])
|
|
416
|
-
try:
|
|
417
|
-
for i in range(len(value)):
|
|
418
|
-
value[i] = interpolator.interpolate_or_error(value[i])
|
|
419
|
-
except InterpolatorError as e:
|
|
420
|
-
raise ConfigurationError(e.args[0])
|
|
421
410
|
|
|
422
411
|
def interpolate_env(self, conf: RunConfigurationT):
|
|
423
412
|
env_dict = conf.env.as_dict()
|
|
@@ -701,18 +690,50 @@ class RunWithPortsConfiguratorMixin:
|
|
|
701
690
|
conf.ports = list(_merge_ports(conf.ports, args.ports).values())
|
|
702
691
|
|
|
703
692
|
|
|
704
|
-
class
|
|
693
|
+
class RunWithCommandsConfiguratorMixin:
|
|
694
|
+
@classmethod
|
|
695
|
+
def register_commands_args(cls, parser: argparse.ArgumentParser):
|
|
696
|
+
parser.add_argument(
|
|
697
|
+
"run_args",
|
|
698
|
+
help=(
|
|
699
|
+
"Run arguments. Available in the configuration [code]commands[/code] as"
|
|
700
|
+
" [code]${{ run.args }}[/code]."
|
|
701
|
+
" Use [code]--[/code] to separate run options from [code]dstack[/code] options"
|
|
702
|
+
),
|
|
703
|
+
nargs="*",
|
|
704
|
+
metavar="RUN_ARGS",
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
def apply_commands_args(
|
|
708
|
+
self,
|
|
709
|
+
conf: ConfigurationWithCommandsParams,
|
|
710
|
+
args: argparse.Namespace,
|
|
711
|
+
):
|
|
712
|
+
commands = conf.commands
|
|
713
|
+
run_args = shlex.join(args.run_args)
|
|
714
|
+
interpolator = VariablesInterpolator({"run": {"args": run_args}}, skip=["secrets"])
|
|
715
|
+
try:
|
|
716
|
+
for i, command in enumerate(commands):
|
|
717
|
+
commands[i] = interpolator.interpolate_or_error(command)
|
|
718
|
+
except InterpolatorError as e:
|
|
719
|
+
raise ConfigurationError(e.args[0])
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
class TaskConfigurator(
|
|
723
|
+
RunWithPortsConfiguratorMixin, RunWithCommandsConfiguratorMixin, BaseRunConfigurator
|
|
724
|
+
):
|
|
705
725
|
TYPE = ApplyConfigurationType.TASK
|
|
706
726
|
|
|
707
727
|
@classmethod
|
|
708
728
|
def register_args(cls, parser: argparse.ArgumentParser):
|
|
709
729
|
super().register_args(parser)
|
|
710
730
|
cls.register_ports_args(parser)
|
|
731
|
+
cls.register_commands_args(parser)
|
|
711
732
|
|
|
712
|
-
def apply_args(self, conf: TaskConfiguration, args: argparse.Namespace
|
|
713
|
-
super().apply_args(conf, args
|
|
733
|
+
def apply_args(self, conf: TaskConfiguration, args: argparse.Namespace):
|
|
734
|
+
super().apply_args(conf, args)
|
|
714
735
|
self.apply_ports_args(conf, args)
|
|
715
|
-
self.
|
|
736
|
+
self.apply_commands_args(conf, args)
|
|
716
737
|
|
|
717
738
|
|
|
718
739
|
class DevEnvironmentConfigurator(RunWithPortsConfiguratorMixin, BaseRunConfigurator):
|
|
@@ -723,10 +744,8 @@ class DevEnvironmentConfigurator(RunWithPortsConfiguratorMixin, BaseRunConfigura
|
|
|
723
744
|
super().register_args(parser)
|
|
724
745
|
cls.register_ports_args(parser)
|
|
725
746
|
|
|
726
|
-
def apply_args(
|
|
727
|
-
|
|
728
|
-
):
|
|
729
|
-
super().apply_args(conf, args, unknown)
|
|
747
|
+
def apply_args(self, conf: DevEnvironmentConfiguration, args: argparse.Namespace):
|
|
748
|
+
super().apply_args(conf, args)
|
|
730
749
|
self.apply_ports_args(conf, args)
|
|
731
750
|
if conf.ide == "vscode" and conf.version is None:
|
|
732
751
|
conf.version = _detect_vscode_version()
|
|
@@ -746,12 +765,17 @@ class DevEnvironmentConfigurator(RunWithPortsConfiguratorMixin, BaseRunConfigura
|
|
|
746
765
|
)
|
|
747
766
|
|
|
748
767
|
|
|
749
|
-
class ServiceConfigurator(BaseRunConfigurator):
|
|
768
|
+
class ServiceConfigurator(RunWithCommandsConfiguratorMixin, BaseRunConfigurator):
|
|
750
769
|
TYPE = ApplyConfigurationType.SERVICE
|
|
751
770
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
771
|
+
@classmethod
|
|
772
|
+
def register_args(cls, parser: argparse.ArgumentParser):
|
|
773
|
+
super().register_args(parser)
|
|
774
|
+
cls.register_commands_args(parser)
|
|
775
|
+
|
|
776
|
+
def apply_args(self, conf: TaskConfiguration, args: argparse.Namespace):
|
|
777
|
+
super().apply_args(conf, args)
|
|
778
|
+
self.apply_commands_args(conf, args)
|
|
755
779
|
|
|
756
780
|
|
|
757
781
|
def _merge_ports(conf: List[PortMapping], args: List[PortMapping]) -> Dict[int, PortMapping]:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import time
|
|
3
|
-
from typing import List
|
|
4
3
|
|
|
5
4
|
from rich.table import Table
|
|
6
5
|
|
|
@@ -26,7 +25,7 @@ from dstack.api._public import Client
|
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
class VolumeConfigurator(BaseApplyConfigurator[VolumeConfiguration]):
|
|
29
|
-
TYPE
|
|
28
|
+
TYPE = ApplyConfigurationType.VOLUME
|
|
30
29
|
|
|
31
30
|
def apply_configuration(
|
|
32
31
|
self,
|
|
@@ -34,9 +33,8 @@ class VolumeConfigurator(BaseApplyConfigurator[VolumeConfiguration]):
|
|
|
34
33
|
configuration_path: str,
|
|
35
34
|
command_args: argparse.Namespace,
|
|
36
35
|
configurator_args: argparse.Namespace,
|
|
37
|
-
unknown_args: List[str],
|
|
38
36
|
):
|
|
39
|
-
self.apply_args(conf, configurator_args
|
|
37
|
+
self.apply_args(conf, configurator_args)
|
|
40
38
|
spec = VolumeSpec(
|
|
41
39
|
configuration=conf,
|
|
42
40
|
configuration_path=configuration_path,
|
|
@@ -167,7 +165,7 @@ class VolumeConfigurator(BaseApplyConfigurator[VolumeConfiguration]):
|
|
|
167
165
|
help="The volume name",
|
|
168
166
|
)
|
|
169
167
|
|
|
170
|
-
def apply_args(self, conf: VolumeConfiguration, args: argparse.Namespace
|
|
168
|
+
def apply_args(self, conf: VolumeConfiguration, args: argparse.Namespace):
|
|
171
169
|
if args.name:
|
|
172
170
|
conf.name = args.name
|
|
173
171
|
|
|
@@ -31,6 +31,8 @@ def get_apply_plan_excludes(plan: ApplyRunPlanInput) -> Optional[IncludeExcludeD
|
|
|
31
31
|
current_resource_excludes["status_message"] = True
|
|
32
32
|
if current_resource.deployment_num == 0:
|
|
33
33
|
current_resource_excludes["deployment_num"] = True
|
|
34
|
+
if current_resource.fleet is None:
|
|
35
|
+
current_resource_excludes["fleet"] = True
|
|
34
36
|
apply_plan_excludes["current_resource"] = current_resource_excludes
|
|
35
37
|
current_resource_excludes["run_spec"] = get_run_spec_excludes(current_resource.run_spec)
|
|
36
38
|
job_submissions_excludes: IncludeExcludeDictType = {}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Any, Callable, Optional, Union
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Union
|
|
4
4
|
|
|
5
5
|
import orjson
|
|
6
6
|
from pydantic import Field
|
|
7
|
-
from pydantic_duality import
|
|
7
|
+
from pydantic_duality import generate_dual_base_model
|
|
8
8
|
from typing_extensions import Annotated
|
|
9
9
|
|
|
10
10
|
from dstack._internal.utils.json_utils import pydantic_orjson_dumps
|
|
@@ -17,46 +17,73 @@ IncludeExcludeDictType = dict[
|
|
|
17
17
|
IncludeExcludeType = Union[IncludeExcludeSetType, IncludeExcludeDictType]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class CoreConfig:
|
|
21
|
+
json_loads = orjson.loads
|
|
22
|
+
json_dumps = pydantic_orjson_dumps
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# All dstack models inherit from pydantic-duality's DualBaseModel.
|
|
20
26
|
# DualBaseModel creates two classes for the model:
|
|
21
27
|
# one with extra = "forbid" (CoreModel/CoreModel.__request__),
|
|
22
28
|
# and another with extra = "ignore" (CoreModel.__response__).
|
|
23
|
-
# This allows to use the same model both for
|
|
24
|
-
# for
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
29
|
+
# This allows to use the same model both for strict parsing of the user input and
|
|
30
|
+
# for permissive parsing of the server responses.
|
|
31
|
+
#
|
|
32
|
+
# We define a func to generate CoreModel dynamically that can be used
|
|
33
|
+
# to define custom Config for both __request__ and __response__ models.
|
|
34
|
+
# Note: Defining config in the model class directly overrides
|
|
35
|
+
# pydantic-duality's base config, breaking __response__.
|
|
36
|
+
def generate_dual_core_model(
|
|
37
|
+
custom_config: Union[type, Mapping],
|
|
38
|
+
) -> "type[CoreModel]":
|
|
39
|
+
class CoreModel(generate_dual_base_model(custom_config)):
|
|
40
|
+
def json(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
include: Optional[IncludeExcludeType] = None,
|
|
44
|
+
exclude: Optional[IncludeExcludeType] = None,
|
|
45
|
+
by_alias: bool = False,
|
|
46
|
+
skip_defaults: Optional[bool] = None, # ignore as it's deprecated
|
|
47
|
+
exclude_unset: bool = False,
|
|
48
|
+
exclude_defaults: bool = False,
|
|
49
|
+
exclude_none: bool = False,
|
|
50
|
+
encoder: Optional[Callable[[Any], Any]] = None,
|
|
51
|
+
models_as_dict: bool = True, # does not seems to be needed by dstack or dependencies
|
|
52
|
+
**dumps_kwargs: Any,
|
|
53
|
+
) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Override `json()` method so that it calls `dict()`.
|
|
56
|
+
Allows changing how models are serialized by overriding `dict()` only.
|
|
57
|
+
By default, `json()` won't call `dict()`, so changes applied in `dict()` won't take place.
|
|
58
|
+
"""
|
|
59
|
+
data = self.dict(
|
|
60
|
+
by_alias=by_alias,
|
|
61
|
+
include=include,
|
|
62
|
+
exclude=exclude,
|
|
63
|
+
exclude_unset=exclude_unset,
|
|
64
|
+
exclude_defaults=exclude_defaults,
|
|
65
|
+
exclude_none=exclude_none,
|
|
66
|
+
)
|
|
67
|
+
if self.__custom_root_type__:
|
|
68
|
+
data = data["__root__"]
|
|
69
|
+
return self.__config__.json_dumps(data, default=encoder, **dumps_kwargs)
|
|
70
|
+
|
|
71
|
+
return CoreModel
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if TYPE_CHECKING:
|
|
75
|
+
|
|
76
|
+
class CoreModel(generate_dual_base_model(CoreConfig)):
|
|
77
|
+
pass
|
|
78
|
+
else:
|
|
79
|
+
CoreModel = generate_dual_core_model(CoreConfig)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class FrozenConfig(CoreConfig):
|
|
83
|
+
frozen = True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
FrozenCoreModel = generate_dual_core_model(FrozenConfig)
|
|
60
87
|
|
|
61
88
|
|
|
62
89
|
class Duration(int):
|
|
@@ -93,7 +120,7 @@ class Duration(int):
|
|
|
93
120
|
raise ValueError(f"Cannot parse the duration {v}")
|
|
94
121
|
|
|
95
122
|
|
|
96
|
-
class RegistryAuth(
|
|
123
|
+
class RegistryAuth(FrozenCoreModel):
|
|
97
124
|
"""
|
|
98
125
|
Credentials for pulling a private Docker image.
|
|
99
126
|
|
|
@@ -105,9 +132,6 @@ class RegistryAuth(CoreModel):
|
|
|
105
132
|
username: Annotated[str, Field(description="The username")]
|
|
106
133
|
password: Annotated[str, Field(description="The password or access token")]
|
|
107
134
|
|
|
108
|
-
class Config(CoreModel.Config):
|
|
109
|
-
frozen = True
|
|
110
|
-
|
|
111
135
|
|
|
112
136
|
class ApplyAction(str, Enum):
|
|
113
137
|
CREATE = "create" # resource is to be created or overridden
|