dstack 0.19.25__py3-none-any.whl → 0.19.26__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.

Files changed (128) hide show
  1. dstack/_internal/cli/commands/__init__.py +2 -2
  2. dstack/_internal/cli/commands/apply.py +3 -61
  3. dstack/_internal/cli/commands/attach.py +1 -1
  4. dstack/_internal/cli/commands/completion.py +1 -1
  5. dstack/_internal/cli/commands/delete.py +2 -2
  6. dstack/_internal/cli/commands/fleet.py +1 -1
  7. dstack/_internal/cli/commands/gateway.py +2 -2
  8. dstack/_internal/cli/commands/init.py +56 -24
  9. dstack/_internal/cli/commands/logs.py +1 -1
  10. dstack/_internal/cli/commands/metrics.py +1 -1
  11. dstack/_internal/cli/commands/offer.py +45 -7
  12. dstack/_internal/cli/commands/project.py +2 -2
  13. dstack/_internal/cli/commands/secrets.py +2 -2
  14. dstack/_internal/cli/commands/server.py +1 -1
  15. dstack/_internal/cli/commands/stop.py +1 -1
  16. dstack/_internal/cli/commands/volume.py +1 -1
  17. dstack/_internal/cli/main.py +2 -2
  18. dstack/_internal/cli/services/completion.py +2 -2
  19. dstack/_internal/cli/services/configurators/__init__.py +6 -2
  20. dstack/_internal/cli/services/configurators/base.py +6 -7
  21. dstack/_internal/cli/services/configurators/fleet.py +1 -3
  22. dstack/_internal/cli/services/configurators/gateway.py +2 -4
  23. dstack/_internal/cli/services/configurators/run.py +195 -55
  24. dstack/_internal/cli/services/configurators/volume.py +2 -4
  25. dstack/_internal/cli/services/profile.py +1 -1
  26. dstack/_internal/cli/services/repos.py +51 -47
  27. dstack/_internal/core/backends/aws/configurator.py +11 -7
  28. dstack/_internal/core/backends/azure/configurator.py +11 -7
  29. dstack/_internal/core/backends/base/configurator.py +25 -13
  30. dstack/_internal/core/backends/cloudrift/configurator.py +13 -7
  31. dstack/_internal/core/backends/cudo/configurator.py +11 -7
  32. dstack/_internal/core/backends/datacrunch/compute.py +5 -1
  33. dstack/_internal/core/backends/datacrunch/configurator.py +13 -7
  34. dstack/_internal/core/backends/gcp/configurator.py +11 -7
  35. dstack/_internal/core/backends/hotaisle/configurator.py +13 -7
  36. dstack/_internal/core/backends/kubernetes/configurator.py +13 -7
  37. dstack/_internal/core/backends/lambdalabs/configurator.py +11 -7
  38. dstack/_internal/core/backends/nebius/compute.py +1 -1
  39. dstack/_internal/core/backends/nebius/configurator.py +11 -7
  40. dstack/_internal/core/backends/nebius/resources.py +21 -11
  41. dstack/_internal/core/backends/oci/configurator.py +11 -7
  42. dstack/_internal/core/backends/runpod/configurator.py +11 -7
  43. dstack/_internal/core/backends/template/configurator.py.jinja +11 -7
  44. dstack/_internal/core/backends/tensordock/configurator.py +13 -7
  45. dstack/_internal/core/backends/vastai/configurator.py +11 -7
  46. dstack/_internal/core/backends/vultr/configurator.py +11 -4
  47. dstack/_internal/core/compatibility/gpus.py +13 -0
  48. dstack/_internal/core/compatibility/runs.py +1 -0
  49. dstack/_internal/core/models/common.py +3 -3
  50. dstack/_internal/core/models/configurations.py +172 -27
  51. dstack/_internal/core/models/files.py +1 -1
  52. dstack/_internal/core/models/fleets.py +5 -1
  53. dstack/_internal/core/models/profiles.py +41 -11
  54. dstack/_internal/core/models/resources.py +46 -42
  55. dstack/_internal/core/models/runs.py +4 -0
  56. dstack/_internal/core/services/configs/__init__.py +2 -2
  57. dstack/_internal/core/services/profiles.py +2 -2
  58. dstack/_internal/core/services/repos.py +5 -3
  59. dstack/_internal/core/services/ssh/ports.py +1 -1
  60. dstack/_internal/proxy/lib/deps.py +6 -2
  61. dstack/_internal/server/app.py +22 -17
  62. dstack/_internal/server/background/tasks/process_gateways.py +4 -1
  63. dstack/_internal/server/background/tasks/process_instances.py +10 -2
  64. dstack/_internal/server/background/tasks/process_probes.py +1 -1
  65. dstack/_internal/server/background/tasks/process_running_jobs.py +10 -4
  66. dstack/_internal/server/background/tasks/process_runs.py +1 -1
  67. dstack/_internal/server/background/tasks/process_submitted_jobs.py +54 -43
  68. dstack/_internal/server/background/tasks/process_terminating_jobs.py +2 -2
  69. dstack/_internal/server/background/tasks/process_volumes.py +1 -1
  70. dstack/_internal/server/db.py +8 -4
  71. dstack/_internal/server/models.py +1 -0
  72. dstack/_internal/server/routers/gpus.py +1 -6
  73. dstack/_internal/server/schemas/runner.py +10 -0
  74. dstack/_internal/server/services/backends/__init__.py +14 -8
  75. dstack/_internal/server/services/backends/handlers.py +6 -1
  76. dstack/_internal/server/services/docker.py +5 -5
  77. dstack/_internal/server/services/fleets.py +14 -13
  78. dstack/_internal/server/services/gateways/__init__.py +2 -0
  79. dstack/_internal/server/services/gateways/client.py +5 -2
  80. dstack/_internal/server/services/gateways/connection.py +1 -1
  81. dstack/_internal/server/services/gpus.py +50 -49
  82. dstack/_internal/server/services/instances.py +41 -1
  83. dstack/_internal/server/services/jobs/__init__.py +15 -4
  84. dstack/_internal/server/services/jobs/configurators/base.py +7 -11
  85. dstack/_internal/server/services/jobs/configurators/dev.py +5 -0
  86. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +3 -3
  87. dstack/_internal/server/services/jobs/configurators/extensions/vscode.py +3 -3
  88. dstack/_internal/server/services/jobs/configurators/service.py +1 -0
  89. dstack/_internal/server/services/jobs/configurators/task.py +3 -0
  90. dstack/_internal/server/services/locking.py +5 -5
  91. dstack/_internal/server/services/logging.py +10 -2
  92. dstack/_internal/server/services/logs/__init__.py +8 -6
  93. dstack/_internal/server/services/logs/aws.py +330 -327
  94. dstack/_internal/server/services/logs/filelog.py +7 -6
  95. dstack/_internal/server/services/logs/gcp.py +141 -139
  96. dstack/_internal/server/services/plugins.py +1 -1
  97. dstack/_internal/server/services/projects.py +2 -5
  98. dstack/_internal/server/services/proxy/repo.py +5 -1
  99. dstack/_internal/server/services/requirements/__init__.py +0 -0
  100. dstack/_internal/server/services/requirements/combine.py +259 -0
  101. dstack/_internal/server/services/runner/client.py +7 -0
  102. dstack/_internal/server/services/runs.py +1 -1
  103. dstack/_internal/server/services/services/__init__.py +8 -2
  104. dstack/_internal/server/services/services/autoscalers.py +2 -0
  105. dstack/_internal/server/services/ssh.py +2 -1
  106. dstack/_internal/server/services/storage/__init__.py +5 -6
  107. dstack/_internal/server/services/storage/gcs.py +49 -49
  108. dstack/_internal/server/services/storage/s3.py +52 -52
  109. dstack/_internal/server/statics/index.html +1 -1
  110. dstack/_internal/server/testing/common.py +1 -1
  111. dstack/_internal/server/utils/logging.py +3 -3
  112. dstack/_internal/server/utils/provisioning.py +3 -3
  113. dstack/_internal/utils/json_schema.py +3 -1
  114. dstack/_internal/utils/typing.py +14 -0
  115. dstack/api/_public/repos.py +21 -2
  116. dstack/api/_public/runs.py +5 -7
  117. dstack/api/server/__init__.py +17 -19
  118. dstack/api/server/_gpus.py +2 -1
  119. dstack/api/server/_group.py +4 -3
  120. dstack/api/server/_repos.py +20 -3
  121. dstack/plugins/builtin/rest_plugin/_plugin.py +1 -0
  122. dstack/version.py +1 -1
  123. {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/METADATA +1 -1
  124. {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/RECORD +127 -124
  125. dstack/api/huggingface/__init__.py +0 -73
  126. {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/WHEEL +0 -0
  127. {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/entry_points.txt +0 -0
  128. {dstack-0.19.25.dist-info → dstack-0.19.26.dist-info}/licenses/LICENSE.md +0 -0
@@ -54,7 +54,7 @@ class BaseCommand(ABC):
54
54
 
55
55
 
56
56
  class APIBaseCommand(BaseCommand):
57
- api: Client = None
57
+ api: Client
58
58
 
59
59
  def _register(self):
60
60
  self._parser.add_argument(
@@ -62,7 +62,7 @@ class APIBaseCommand(BaseCommand):
62
62
  help="The name of the project. Defaults to [code]$DSTACK_PROJECT[/]",
63
63
  metavar="NAME",
64
64
  default=os.getenv("DSTACK_PROJECT"),
65
- ).completer = ProjectNameCompleter()
65
+ ).completer = ProjectNameCompleter() # type: ignore[attr-defined]
66
66
 
67
67
  def _command(self, args: argparse.Namespace):
68
68
  configure_logging()
@@ -1,7 +1,6 @@
1
1
  import argparse
2
- from pathlib import Path
3
2
 
4
- from argcomplete import FilesCompleter
3
+ from argcomplete import FilesCompleter # type: ignore[attr-defined]
5
4
 
6
5
  from dstack._internal.cli.commands import APIBaseCommand
7
6
  from dstack._internal.cli.services.configurators import (
@@ -9,12 +8,7 @@ from dstack._internal.cli.services.configurators import (
9
8
  get_apply_configurator_class,
10
9
  load_apply_configuration,
11
10
  )
12
- from dstack._internal.cli.services.repos import (
13
- init_default_virtual_repo,
14
- init_repo,
15
- register_init_repo_args,
16
- )
17
- from dstack._internal.cli.utils.common import console, warn
11
+ from dstack._internal.cli.utils.common import console
18
12
  from dstack._internal.core.errors import CLIError
19
13
  from dstack._internal.core.models.configurations import ApplyConfigurationType
20
14
 
@@ -48,7 +42,7 @@ class ApplyCommand(APIBaseCommand):
48
42
  " Defaults to [code]$PWD/.dstack.yml[/]"
49
43
  ),
50
44
  dest="configuration_file",
51
- ).completer = FilesCompleter(allowednames=["*.yml", "*.yaml"])
45
+ ).completer = FilesCompleter(allowednames=["*.yml", "*.yaml"]) # type: ignore[attr-defined]
52
46
  self._parser.add_argument(
53
47
  "-y",
54
48
  "--yes",
@@ -66,37 +60,6 @@ class ApplyCommand(APIBaseCommand):
66
60
  help="Exit immediately after submitting configuration",
67
61
  action="store_true",
68
62
  )
69
- self._parser.add_argument(
70
- "--ssh-identity",
71
- metavar="SSH_PRIVATE_KEY",
72
- help="The private SSH key path for SSH tunneling",
73
- type=Path,
74
- dest="ssh_identity_file",
75
- )
76
- repo_group = self._parser.add_argument_group("Repo Options")
77
- repo_group.add_argument(
78
- "-P",
79
- "--repo",
80
- help=("The repo to use for the run. Can be a local path or a Git repo URL."),
81
- dest="repo",
82
- )
83
- repo_group.add_argument(
84
- "--repo-branch",
85
- help="The repo branch to use for the run",
86
- dest="repo_branch",
87
- )
88
- repo_group.add_argument(
89
- "--repo-hash",
90
- help="The hash of the repo commit to use for the run",
91
- dest="repo_hash",
92
- )
93
- repo_group.add_argument(
94
- "--no-repo",
95
- help="Do not use any repo for the run",
96
- dest="no_repo",
97
- action="store_true",
98
- )
99
- register_init_repo_args(repo_group)
100
63
 
101
64
  def _command(self, args: argparse.Namespace):
102
65
  try:
@@ -117,26 +80,6 @@ class ApplyCommand(APIBaseCommand):
117
80
  super()._command(args)
118
81
  if not args.yes and args.configuration_file == APPLY_STDIN_NAME:
119
82
  raise CLIError("Cannot read configuration from stdin if -y/--yes is not specified")
120
- if args.repo and args.no_repo:
121
- raise CLIError("Either --repo or --no-repo can be specified")
122
- if args.local:
123
- warn(
124
- "Local repos are deprecated since 0.19.25 and will be removed soon."
125
- " Consider using `files` instead: https://dstack.ai/docs/concepts/tasks/#files"
126
- )
127
- repo = None
128
- if args.repo:
129
- repo = init_repo(
130
- api=self.api,
131
- repo_path=args.repo,
132
- repo_branch=args.repo_branch,
133
- repo_hash=args.repo_hash,
134
- local=args.local,
135
- git_identity_file=args.git_identity_file,
136
- oauth_token=args.gh_token,
137
- )
138
- elif args.no_repo:
139
- repo = init_default_virtual_repo(api=self.api)
140
83
  configuration_path, configuration = load_apply_configuration(args.configuration_file)
141
84
  configurator_class = get_apply_configurator_class(configuration.type)
142
85
  configurator = configurator_class(api_client=self.api)
@@ -148,7 +91,6 @@ class ApplyCommand(APIBaseCommand):
148
91
  command_args=args,
149
92
  configurator_args=known,
150
93
  unknown_args=unknown,
151
- repo=repo,
152
94
  )
153
95
  except KeyboardInterrupt:
154
96
  console.print("\nOperation interrupted by user. Exiting...")
@@ -61,7 +61,7 @@ class AttachCommand(APIBaseCommand):
61
61
  type=int,
62
62
  default=0,
63
63
  )
64
- self._parser.add_argument("run_name").completer = RunNameCompleter()
64
+ self._parser.add_argument("run_name").completer = RunNameCompleter() # type: ignore[attr-defined]
65
65
 
66
66
  def _command(self, args: argparse.Namespace):
67
67
  super()._command(args)
@@ -17,4 +17,4 @@ class CompletionCommand(BaseCommand):
17
17
 
18
18
  def _command(self, args):
19
19
  super()._command(args)
20
- print(argcomplete.shellcode(["dstack"], shell=args.shell))
20
+ print(argcomplete.shellcode(["dstack"], shell=args.shell)) # type: ignore[attr-defined]
@@ -1,7 +1,7 @@
1
1
  import argparse
2
2
  from pathlib import Path
3
3
 
4
- from argcomplete import FilesCompleter
4
+ from argcomplete import FilesCompleter # type: ignore[attr-defined]
5
5
 
6
6
  from dstack._internal.cli.commands import APIBaseCommand
7
7
  from dstack._internal.cli.services.configurators import (
@@ -24,7 +24,7 @@ class DeleteCommand(APIBaseCommand):
24
24
  metavar="FILE",
25
25
  help="The path to the configuration file. Defaults to [code]$PWD/.dstack.yml[/]",
26
26
  dest="configuration_file",
27
- ).completer = FilesCompleter(allowednames=["*.yml", "*.yaml"])
27
+ ).completer = FilesCompleter(allowednames=["*.yml", "*.yaml"]) # type: ignore[attr-defined]
28
28
  self._parser.add_argument(
29
29
  "-y",
30
30
  "--yes",
@@ -48,7 +48,7 @@ class FleetCommand(APIBaseCommand):
48
48
  delete_parser.add_argument(
49
49
  "name",
50
50
  help="The name of the fleet",
51
- ).completer = FleetNameCompleter()
51
+ ).completer = FleetNameCompleter() # type: ignore[attr-defined]
52
52
  delete_parser.add_argument(
53
53
  "-i",
54
54
  "--instance",
@@ -68,7 +68,7 @@ class GatewayCommand(APIBaseCommand):
68
68
  delete_parser.set_defaults(subfunc=self._delete)
69
69
  delete_parser.add_argument(
70
70
  "name", help="The name of the gateway"
71
- ).completer = GatewayNameCompleter()
71
+ ).completer = GatewayNameCompleter() # type: ignore[attr-defined]
72
72
  delete_parser.add_argument(
73
73
  "-y", "--yes", action="store_true", help="Don't ask for confirmation"
74
74
  )
@@ -79,7 +79,7 @@ class GatewayCommand(APIBaseCommand):
79
79
  update_parser.set_defaults(subfunc=self._update)
80
80
  update_parser.add_argument(
81
81
  "name", help="The name of the gateway"
82
- ).completer = GatewayNameCompleter()
82
+ ).completer = GatewayNameCompleter() # type: ignore[attr-defined]
83
83
  update_parser.add_argument(
84
84
  "--set-default", action="store_true", help="Set it the default gateway for the project"
85
85
  )
@@ -1,12 +1,17 @@
1
1
  import argparse
2
2
  import os
3
3
  from pathlib import Path
4
+ from typing import Optional
4
5
 
5
6
  from dstack._internal.cli.commands import BaseCommand
6
- from dstack._internal.cli.services.repos import init_repo, register_init_repo_args
7
+ from dstack._internal.cli.services.repos import (
8
+ get_repo_from_dir,
9
+ get_repo_from_url,
10
+ is_git_repo_url,
11
+ register_init_repo_args,
12
+ )
7
13
  from dstack._internal.cli.utils.common import configure_logging, confirm_ask, console, warn
8
14
  from dstack._internal.core.errors import ConfigurationError
9
- from dstack._internal.core.models.repos.base import RepoType
10
15
  from dstack._internal.core.services.configs import ConfigManager
11
16
  from dstack.api import Client
12
17
 
@@ -21,6 +26,15 @@ class InitCommand(BaseCommand):
21
26
  help="The name of the project",
22
27
  default=os.getenv("DSTACK_PROJECT"),
23
28
  )
29
+ self._parser.add_argument(
30
+ "-P",
31
+ "--repo",
32
+ help=(
33
+ "The repo to initialize. Can be a local path or a Git repo URL."
34
+ " Defaults to the current working directory."
35
+ ),
36
+ dest="repo",
37
+ )
24
38
  register_init_repo_args(self._parser)
25
39
  # Deprecated since 0.19.25, ignored
26
40
  self._parser.add_argument(
@@ -30,7 +44,7 @@ class InitCommand(BaseCommand):
30
44
  type=Path,
31
45
  dest="ssh_identity_file",
32
46
  )
33
- # A hidden mode for transitional period only, remove it with local repos
47
+ # A hidden mode for transitional period only, remove it with repos in `config.yml`
34
48
  self._parser.add_argument(
35
49
  "--remove",
36
50
  help=argparse.SUPPRESS,
@@ -39,44 +53,62 @@ class InitCommand(BaseCommand):
39
53
 
40
54
  def _command(self, args: argparse.Namespace):
41
55
  configure_logging()
56
+
57
+ repo_path: Optional[Path] = None
58
+ repo_url: Optional[str] = None
59
+ repo_arg: Optional[str] = args.repo
60
+ if repo_arg is not None:
61
+ if is_git_repo_url(repo_arg):
62
+ repo_url = repo_arg
63
+ else:
64
+ repo_path = Path(repo_arg).expanduser().resolve()
65
+ else:
66
+ repo_path = Path.cwd()
67
+
42
68
  if args.remove:
69
+ if repo_url is not None:
70
+ raise ConfigurationError(f"Local path expected, got URL: {repo_url}")
71
+ assert repo_path is not None
43
72
  config_manager = ConfigManager()
44
- repo_path = Path.cwd()
45
73
  repo_config = config_manager.get_repo_config(repo_path)
46
74
  if repo_config is None:
47
- raise ConfigurationError("The repo is not initialized, nothing to remove")
48
- if repo_config.repo_type != RepoType.LOCAL:
49
- raise ConfigurationError("`dstack init --remove` is for local repos only")
75
+ raise ConfigurationError("Repo record not found, nothing to remove")
50
76
  console.print(
51
- f"You are about to remove the local repo {repo_path}\n"
77
+ f"You are about to remove the repo {repo_path}\n"
52
78
  "Only the record about the repo will be removed,"
53
79
  " the repo files will remain intact\n"
54
80
  )
55
- if not confirm_ask("Remove the local repo?"):
81
+ if not confirm_ask("Remove the repo?"):
56
82
  return
57
83
  config_manager.delete_repo_config(repo_config.repo_id)
58
84
  config_manager.save()
59
- console.print("Local repo has been removed")
85
+ console.print("Repo has been removed")
60
86
  return
61
- api = Client.from_config(
62
- project_name=args.project, ssh_identity_file=args.ssh_identity_file
63
- )
64
- if args.local:
87
+
88
+ local: bool = args.local
89
+ if local:
65
90
  warn(
66
- "Local repos are deprecated since 0.19.25 and will be removed soon."
67
- " Consider using `files` instead: https://dstack.ai/docs/concepts/tasks/#files"
91
+ "Local repos are deprecated since 0.19.25 and will be removed soon. Consider"
92
+ " using [code]files[/code] instead: https://dstack.ai/docs/concepts/tasks/#files"
68
93
  )
69
94
  if args.ssh_identity_file:
70
95
  warn(
71
- "`--ssh-identity` in `dstack init` is deprecated and ignored since 0.19.25."
72
- " Use this option with `dstack apply` and `dstack attach` instead"
96
+ "[code]--ssh-identity[/code] in [code]dstack init[/code] is deprecated and ignored"
97
+ " since 0.19.25. Use this option with [code]dstack apply[/code]"
98
+ " and [code]dstack attach[/code] instead"
73
99
  )
74
- init_repo(
75
- api=api,
76
- repo_path=Path.cwd(),
77
- repo_branch=None,
78
- repo_hash=None,
79
- local=args.local,
100
+
101
+ if repo_url is not None:
102
+ # Dummy repo branch to avoid autodetection that fails on private repos.
103
+ # We don't need branch/hash for repo_id anyway.
104
+ repo = get_repo_from_url(repo_url, repo_branch="master")
105
+ elif repo_path is not None:
106
+ repo = get_repo_from_dir(repo_path, local=local)
107
+ else:
108
+ assert False, "should not reach here"
109
+ api = Client.from_config(project_name=args.project)
110
+ api.repos.init(
111
+ repo=repo,
80
112
  git_identity_file=args.git_identity_file,
81
113
  oauth_token=args.gh_token,
82
114
  )
@@ -30,7 +30,7 @@ class LogsCommand(APIBaseCommand):
30
30
  type=int,
31
31
  default=0,
32
32
  )
33
- self._parser.add_argument("run_name").completer = RunNameCompleter(all=True)
33
+ self._parser.add_argument("run_name").completer = RunNameCompleter(all=True) # type: ignore[attr-defined]
34
34
 
35
35
  def _command(self, args: argparse.Namespace):
36
36
  super()._command(args)
@@ -26,7 +26,7 @@ class MetricsCommand(APIBaseCommand):
26
26
 
27
27
  def _register(self):
28
28
  super()._register()
29
- self._parser.add_argument("run_name").completer = RunNameCompleter()
29
+ self._parser.add_argument("run_name").completer = RunNameCompleter() # type: ignore[attr-defined]
30
30
  self._parser.add_argument(
31
31
  "-w",
32
32
  "--watch",
@@ -3,7 +3,11 @@ from pathlib import Path
3
3
  from typing import List
4
4
 
5
5
  from dstack._internal.cli.commands import APIBaseCommand
6
- from dstack._internal.cli.services.configurators.run import BaseRunConfigurator
6
+ from dstack._internal.cli.services.args import cpu_spec, disk_spec, gpu_spec
7
+ from dstack._internal.cli.services.configurators.run import (
8
+ BaseRunConfigurator,
9
+ )
10
+ from dstack._internal.cli.services.profile import register_profile_args
7
11
  from dstack._internal.cli.utils.common import console
8
12
  from dstack._internal.cli.utils.gpu import print_gpu_json, print_gpu_table
9
13
  from dstack._internal.cli.utils.run import print_offers_json, print_run_plan
@@ -18,11 +22,8 @@ class OfferConfigurator(BaseRunConfigurator):
18
22
  TYPE = ApplyConfigurationType.TASK
19
23
 
20
24
  @classmethod
21
- def register_args(
22
- cls,
23
- parser: argparse.ArgumentParser,
24
- ):
25
- super().register_args(parser, default_max_offers=50)
25
+ def register_args(cls, parser: argparse.ArgumentParser):
26
+ configuration_group = parser.add_argument_group(f"{cls.TYPE.value} Options")
26
27
  parser.add_argument(
27
28
  "--group-by",
28
29
  action="append",
@@ -33,6 +34,43 @@ class OfferConfigurator(BaseRunConfigurator):
33
34
  "Can be repeated or comma-separated (e.g. [code]--group-by gpu,backend[/code])."
34
35
  ),
35
36
  )
37
+ configuration_group.add_argument(
38
+ "-n",
39
+ "--name",
40
+ dest="run_name",
41
+ help="The name of the run. If not specified, a random name is assigned",
42
+ )
43
+ configuration_group.add_argument(
44
+ "--max-offers",
45
+ help="Number of offers to show in the run plan",
46
+ type=int,
47
+ default=50,
48
+ )
49
+ cls.register_env_args(configuration_group)
50
+ configuration_group.add_argument(
51
+ "--cpu",
52
+ type=cpu_spec,
53
+ help="Request CPU for the run. "
54
+ "The format is [code]ARCH[/]:[code]COUNT[/] (all parts are optional)",
55
+ dest="cpu_spec",
56
+ metavar="SPEC",
57
+ )
58
+ configuration_group.add_argument(
59
+ "--gpu",
60
+ type=gpu_spec,
61
+ help="Request GPU for the run. "
62
+ "The format is [code]NAME[/]:[code]COUNT[/]:[code]MEMORY[/] (all parts are optional)",
63
+ dest="gpu_spec",
64
+ metavar="SPEC",
65
+ )
66
+ configuration_group.add_argument(
67
+ "--disk",
68
+ type=disk_spec,
69
+ help="Request the size range of disk for the run. Example [code]--disk 100GB..[/].",
70
+ metavar="RANGE",
71
+ dest="disk_spec",
72
+ )
73
+ register_profile_args(parser)
36
74
 
37
75
 
38
76
  class OfferCommand(APIBaseCommand):
@@ -117,7 +155,7 @@ class OfferCommand(APIBaseCommand):
117
155
 
118
156
  return processed
119
157
 
120
- def _list_gpus(self, args: List[str], run_spec: RunSpec) -> List[GpuGroup]:
158
+ def _list_gpus(self, args: argparse.Namespace, run_spec: RunSpec) -> List[GpuGroup]:
121
159
  group_by = [g for g in args.group_by if g != "gpu"] or None
122
160
  return self.api.client.gpus.list_gpus(
123
161
  self.api.project,
@@ -124,8 +124,8 @@ class ProjectCommand(BaseCommand):
124
124
  table.add_column("USER", style="grey58")
125
125
  table.add_column("DEFAULT", justify="center")
126
126
 
127
- for project_name in config_manager.list_projects():
128
- project_config = config_manager.get_project_config(project_name)
127
+ for project_config in config_manager.list_project_configs():
128
+ project_name = project_config.name
129
129
  is_default = project_name == default_project.name if default_project else False
130
130
 
131
131
  # Get username from API
@@ -29,7 +29,7 @@ class SecretCommand(APIBaseCommand):
29
29
  get_parser.add_argument(
30
30
  "name",
31
31
  help="The name of the secret",
32
- ).completer = SecretNameCompleter()
32
+ ).completer = SecretNameCompleter() # type: ignore[attr-defined]
33
33
  get_parser.set_defaults(subfunc=self._get)
34
34
 
35
35
  set_parser = subparsers.add_parser(
@@ -53,7 +53,7 @@ class SecretCommand(APIBaseCommand):
53
53
  delete_parser.add_argument(
54
54
  "name",
55
55
  help="The name of the secret",
56
- ).completer = SecretNameCompleter()
56
+ ).completer = SecretNameCompleter() # type: ignore[attr-defined]
57
57
  delete_parser.add_argument(
58
58
  "-y", "--yes", help="Don't ask for confirmation", action="store_true"
59
59
  )
@@ -74,7 +74,7 @@ class ServerCommand(BaseCommand):
74
74
  uvicorn_log_level = os.getenv("DSTACK_SERVER_UVICORN_LOG_LEVEL", "ERROR").lower()
75
75
  reload_disabled = os.getenv("DSTACK_SERVER_RELOAD_DISABLED") is not None
76
76
 
77
- uvicorn.run(
77
+ uvicorn.run( # type: ignore[unbound-variable]
78
78
  "dstack._internal.server.main:app",
79
79
  host=args.host,
80
80
  port=args.port,
@@ -14,7 +14,7 @@ class StopCommand(APIBaseCommand):
14
14
  super()._register()
15
15
  self._parser.add_argument("-x", "--abort", action="store_true")
16
16
  self._parser.add_argument("-y", "--yes", action="store_true")
17
- self._parser.add_argument("run_name").completer = RunNameCompleter()
17
+ self._parser.add_argument("run_name").completer = RunNameCompleter() # type: ignore[attr-defined]
18
18
 
19
19
  def _command(self, args: argparse.Namespace):
20
20
  super()._command(args)
@@ -48,7 +48,7 @@ class VolumeCommand(APIBaseCommand):
48
48
  delete_parser.add_argument(
49
49
  "name",
50
50
  help="The name of the volume",
51
- ).completer = VolumeNameCompleter()
51
+ ).completer = VolumeNameCompleter() # type: ignore[attr-defined]
52
52
  delete_parser.add_argument(
53
53
  "-y", "--yes", help="Don't ask for confirmation", action="store_true"
54
54
  )
@@ -41,8 +41,8 @@ def main():
41
41
 
42
42
  parser = argparse.ArgumentParser(
43
43
  description=(
44
- "Not sure where to start? Call [code]dstack init[/].\n"
45
- "Define a [code].dstack.yml[/] configuration file and run it via [code]dstack apply[/]\n"
44
+ "Not sure where to start?"
45
+ " Define a [code].dstack.yml[/] configuration file and run it via [code]dstack apply[/]\n"
46
46
  ),
47
47
  formatter_class=RichHelpFormatter,
48
48
  epilog=(
@@ -87,5 +87,5 @@ class ProjectNameCompleter(BaseCompleter):
87
87
 
88
88
  def __call__(self, prefix: str, parsed_args: argparse.Namespace, **kwargs) -> List[str]:
89
89
  argcomplete.debug(f"{self.__class__.__name__}: Listing projects from ConfigManager")
90
- projects = ConfigManager().list_projects()
91
- return [p for p in projects if p.startswith(prefix)]
90
+ projects = ConfigManager().list_project_configs()
91
+ return [p.name for p in projects if p.name.startswith(prefix)]
@@ -24,7 +24,9 @@ from dstack._internal.core.models.configurations import (
24
24
  APPLY_STDIN_NAME = "-"
25
25
 
26
26
 
27
- apply_configurators_mapping: Dict[ApplyConfigurationType, Type[BaseApplyConfigurator]] = {
27
+ apply_configurators_mapping: Dict[
28
+ ApplyConfigurationType, Type[BaseApplyConfigurator[AnyApplyConfiguration]]
29
+ ] = {
28
30
  cls.TYPE: cls
29
31
  for cls in [
30
32
  DevEnvironmentConfigurator,
@@ -47,7 +49,9 @@ run_configurators_mapping: Dict[ApplyConfigurationType, Type[BaseRunConfigurator
47
49
  }
48
50
 
49
51
 
50
- def get_apply_configurator_class(configurator_type: str) -> Type[BaseApplyConfigurator]:
52
+ def get_apply_configurator_class(
53
+ configurator_type: str,
54
+ ) -> Type[BaseApplyConfigurator[AnyApplyConfiguration]]:
51
55
  return apply_configurators_mapping[ApplyConfigurationType(configurator_type)]
52
56
 
53
57
 
@@ -1,7 +1,7 @@
1
1
  import argparse
2
2
  import os
3
3
  from abc import ABC, abstractmethod
4
- from typing import List, Optional, Union, cast
4
+ from typing import 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
@@ -10,13 +10,14 @@ from dstack._internal.core.models.configurations import (
10
10
  ApplyConfigurationType,
11
11
  )
12
12
  from dstack._internal.core.models.envs import Env, EnvSentinel, EnvVarTuple
13
- from dstack._internal.core.models.repos.base import Repo
14
13
  from dstack.api._public import Client
15
14
 
16
15
  ArgsParser = Union[argparse._ArgumentGroup, argparse.ArgumentParser]
17
16
 
17
+ ApplyConfigurationT = TypeVar("ApplyConfigurationT", bound=AnyApplyConfiguration)
18
18
 
19
- class BaseApplyConfigurator(ABC):
19
+
20
+ class BaseApplyConfigurator(ABC, Generic[ApplyConfigurationT]):
20
21
  TYPE: ApplyConfigurationType
21
22
 
22
23
  def __init__(self, api_client: Client):
@@ -25,12 +26,11 @@ class BaseApplyConfigurator(ABC):
25
26
  @abstractmethod
26
27
  def apply_configuration(
27
28
  self,
28
- conf: AnyApplyConfiguration,
29
+ conf: ApplyConfigurationT,
29
30
  configuration_path: str,
30
31
  command_args: argparse.Namespace,
31
32
  configurator_args: argparse.Namespace,
32
33
  unknown_args: List[str],
33
- repo: Optional[Repo] = None,
34
34
  ):
35
35
  """
36
36
  Implements `dstack apply` for a given configuration type.
@@ -41,14 +41,13 @@ class BaseApplyConfigurator(ABC):
41
41
  command_args: The args parsed by `dstack apply`.
42
42
  configurator_args: The known args parsed by `cls.get_parser()`.
43
43
  unknown_args: The unknown args after parsing by `cls.get_parser()`.
44
- repo: The repo to use with apply.
45
44
  """
46
45
  pass
47
46
 
48
47
  @abstractmethod
49
48
  def delete_configuration(
50
49
  self,
51
- conf: AnyApplyConfiguration,
50
+ conf: ApplyConfigurationT,
52
51
  configuration_path: str,
53
52
  command_args: argparse.Namespace,
54
53
  ):
@@ -35,7 +35,6 @@ from dstack._internal.core.models.fleets import (
35
35
  InstanceGroupPlacement,
36
36
  )
37
37
  from dstack._internal.core.models.instances import InstanceAvailability, InstanceStatus, SSHKey
38
- from dstack._internal.core.models.repos.base import Repo
39
38
  from dstack._internal.core.services.diff import diff_models
40
39
  from dstack._internal.utils.common import local_time
41
40
  from dstack._internal.utils.logging import get_logger
@@ -46,7 +45,7 @@ from dstack.api.utils import load_profile
46
45
  logger = get_logger(__name__)
47
46
 
48
47
 
49
- class FleetConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator):
48
+ class FleetConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator[FleetConfiguration]):
50
49
  TYPE: ApplyConfigurationType = ApplyConfigurationType.FLEET
51
50
 
52
51
  def apply_configuration(
@@ -56,7 +55,6 @@ class FleetConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator):
56
55
  command_args: argparse.Namespace,
57
56
  configurator_args: argparse.Namespace,
58
57
  unknown_args: List[str],
59
- repo: Optional[Repo] = None,
60
58
  ):
61
59
  self.apply_args(conf, configurator_args, unknown_args)
62
60
  profile = load_profile(Path.cwd(), None)
@@ -1,6 +1,6 @@
1
1
  import argparse
2
2
  import time
3
- from typing import List, Optional
3
+ from typing import List
4
4
 
5
5
  from rich.table import Table
6
6
 
@@ -21,13 +21,12 @@ from dstack._internal.core.models.gateways import (
21
21
  GatewaySpec,
22
22
  GatewayStatus,
23
23
  )
24
- from dstack._internal.core.models.repos.base import Repo
25
24
  from dstack._internal.core.services.diff import diff_models
26
25
  from dstack._internal.utils.common import local_time
27
26
  from dstack.api._public import Client
28
27
 
29
28
 
30
- class GatewayConfigurator(BaseApplyConfigurator):
29
+ class GatewayConfigurator(BaseApplyConfigurator[GatewayConfiguration]):
31
30
  TYPE: ApplyConfigurationType = ApplyConfigurationType.GATEWAY
32
31
 
33
32
  def apply_configuration(
@@ -37,7 +36,6 @@ class GatewayConfigurator(BaseApplyConfigurator):
37
36
  command_args: argparse.Namespace,
38
37
  configurator_args: argparse.Namespace,
39
38
  unknown_args: List[str],
40
- repo: Optional[Repo] = None,
41
39
  ):
42
40
  self.apply_args(conf, configurator_args, unknown_args)
43
41
  spec = GatewaySpec(