dstack 0.19.25rc1__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 -58
  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 +6 -3
  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.25rc1.dist-info → dstack-0.19.26.dist-info}/METADATA +1 -1
  124. {dstack-0.19.25rc1.dist-info → dstack-0.19.26.dist-info}/RECORD +127 -124
  125. dstack/api/huggingface/__init__.py +0 -73
  126. {dstack-0.19.25rc1.dist-info → dstack-0.19.26.dist-info}/WHEEL +0 -0
  127. {dstack-0.19.25rc1.dist-info → dstack-0.19.26.dist-info}/entry_points.txt +0 -0
  128. {dstack-0.19.25rc1.dist-info → dstack-0.19.26.dist-info}/licenses/LICENSE.md +0 -0
@@ -26,6 +26,7 @@ from dstack._internal.server.schemas.runner import (
26
26
  ShimVolumeInfo,
27
27
  SubmitBody,
28
28
  TaskInfoResponse,
29
+ TaskListResponse,
29
30
  TaskSubmitRequest,
30
31
  TaskTerminateRequest,
31
32
  )
@@ -245,6 +246,12 @@ class ShimClient:
245
246
  self._raise_for_status(resp)
246
247
  return self._response(InstanceHealthResponse, resp)
247
248
 
249
+ def list_tasks(self) -> TaskListResponse:
250
+ if not self.is_api_v2_supported():
251
+ raise ShimAPIVersionError()
252
+ resp = self._request("GET", "/api/tasks", raise_for_status=True)
253
+ return self._response(TaskListResponse, resp)
254
+
248
255
  def get_task(self, task_id: "_TaskID") -> TaskInfoResponse:
249
256
  if not self.is_api_v2_supported():
250
257
  raise ShimAPIVersionError()
@@ -529,7 +529,7 @@ async def submit_run(
529
529
  initial_status = RunStatus.PENDING
530
530
  initial_replicas = 0
531
531
  elif run_spec.configuration.type == "service":
532
- initial_replicas = run_spec.configuration.replicas.min
532
+ initial_replicas = run_spec.configuration.replicas.min or 0
533
533
 
534
534
  run_model = RunModel(
535
535
  id=uuid.uuid4(),
@@ -5,7 +5,6 @@ Application logic related to `type: service` runs.
5
5
  import uuid
6
6
  from datetime import datetime
7
7
  from typing import Optional
8
- from urllib.parse import urlparse
9
8
 
10
9
  import httpx
11
10
  from sqlalchemy import select
@@ -73,6 +72,8 @@ async def register_service(session: AsyncSession, run_model: RunModel, run_spec:
73
72
  async def _register_service_in_gateway(
74
73
  session: AsyncSession, run_model: RunModel, run_spec: RunSpec, gateway: GatewayModel
75
74
  ) -> ServiceSpec:
75
+ assert run_spec.configuration.type == "service"
76
+
76
77
  if gateway.gateway_compute is None:
77
78
  raise ServerClientError("Gateway has no instance associated with it")
78
79
 
@@ -100,6 +101,9 @@ async def _register_service_in_gateway(
100
101
  model_url=f"{gateway_protocol}://gateway.{wildcard_domain}",
101
102
  )
102
103
 
104
+ domain = service_spec.get_domain()
105
+ assert domain is not None
106
+
103
107
  conn = await get_or_add_gateway_connection(session, gateway.id)
104
108
  try:
105
109
  logger.debug("%s: registering service as %s", fmt(run_model), service_spec.url)
@@ -107,7 +111,7 @@ async def _register_service_in_gateway(
107
111
  await client.register_service(
108
112
  project=run_model.project.name,
109
113
  run_name=run_model.run_name,
110
- domain=urlparse(service_spec.url).hostname,
114
+ domain=domain,
111
115
  service_https=service_https,
112
116
  gateway_https=gateway_https,
113
117
  auth=run_spec.configuration.auth,
@@ -127,6 +131,7 @@ async def _register_service_in_gateway(
127
131
 
128
132
 
129
133
  def _register_service_in_server(run_model: RunModel, run_spec: RunSpec) -> ServiceSpec:
134
+ assert run_spec.configuration.type == "service"
130
135
  if run_spec.configuration.https != SERVICE_HTTPS_DEFAULT:
131
136
  # Note: if the user sets `https: <default-value>`, it will be ignored silently
132
137
  # TODO: in 0.19, make `https` Optional to be able to tell if it was set or omitted
@@ -270,6 +275,7 @@ async def unregister_replica(session: AsyncSession, job_model: JobModel):
270
275
 
271
276
 
272
277
  def _get_service_https(run_spec: RunSpec, configuration: GatewayConfiguration) -> bool:
278
+ assert run_spec.configuration.type == "service"
273
279
  if not run_spec.configuration.https:
274
280
  return False
275
281
  if configuration.certificate is not None and configuration.certificate.type == "acm":
@@ -120,6 +120,8 @@ class RPSAutoscaler(BaseServiceScaler):
120
120
 
121
121
 
122
122
  def get_service_scaler(conf: ServiceConfiguration) -> BaseServiceScaler:
123
+ assert conf.replicas.min is not None
124
+ assert conf.replicas.max is not None
123
125
  if conf.scaling is None:
124
126
  return ManualScaler(
125
127
  min_replicas=conf.replicas.min,
@@ -20,10 +20,11 @@ def container_ssh_tunnel(
20
20
  """
21
21
  Build SSHTunnel for connecting to the container running the specified job.
22
22
  """
23
-
24
23
  jpd: JobProvisioningData = JobProvisioningData.__response__.parse_raw(
25
24
  job.job_provisioning_data
26
25
  )
26
+ assert jpd.hostname is not None
27
+ assert jpd.ssh_port is not None
27
28
  if not jpd.dockerized:
28
29
  ssh_destination = f"{jpd.username}@{jpd.hostname}"
29
30
  ssh_port = jpd.ssh_port
@@ -1,9 +1,8 @@
1
1
  from typing import Optional
2
2
 
3
3
  from dstack._internal.server import settings
4
+ from dstack._internal.server.services.storage import gcs, s3
4
5
  from dstack._internal.server.services.storage.base import BaseStorage
5
- from dstack._internal.server.services.storage.gcs import GCS_AVAILABLE, GCSStorage
6
- from dstack._internal.server.services.storage.s3 import BOTO_AVAILABLE, S3Storage
7
6
 
8
7
  _default_storage = None
9
8
 
@@ -20,16 +19,16 @@ def init_default_storage():
20
19
  )
21
20
 
22
21
  if settings.SERVER_S3_BUCKET:
23
- if not BOTO_AVAILABLE:
22
+ if not s3.BOTO_AVAILABLE:
24
23
  raise ValueError("AWS dependencies are not installed")
25
- _default_storage = S3Storage(
24
+ _default_storage = s3.S3Storage(
26
25
  bucket=settings.SERVER_S3_BUCKET,
27
26
  region=settings.SERVER_S3_BUCKET_REGION,
28
27
  )
29
28
  elif settings.SERVER_GCS_BUCKET:
30
- if not GCS_AVAILABLE:
29
+ if not gcs.GCS_AVAILABLE:
31
30
  raise ValueError("GCS dependencies are not installed")
32
- _default_storage = GCSStorage(
31
+ _default_storage = gcs.GCSStorage(
33
32
  bucket=settings.SERVER_GCS_BUCKET,
34
33
  )
35
34
 
@@ -8,59 +8,59 @@ try:
8
8
  from google.cloud.exceptions import NotFound
9
9
  except ImportError:
10
10
  GCS_AVAILABLE = False
11
+ else:
11
12
 
13
+ class GCSStorage(BaseStorage):
14
+ def __init__(
15
+ self,
16
+ bucket: str,
17
+ ):
18
+ self._client = storage.Client()
19
+ self._bucket = self._client.bucket(bucket)
12
20
 
13
- class GCSStorage(BaseStorage):
14
- def __init__(
15
- self,
16
- bucket: str,
17
- ):
18
- self._client = storage.Client()
19
- self._bucket = self._client.bucket(bucket)
21
+ def upload_code(
22
+ self,
23
+ project_id: str,
24
+ repo_id: str,
25
+ code_hash: str,
26
+ blob: bytes,
27
+ ):
28
+ key = self._get_code_key(project_id, repo_id, code_hash)
29
+ self._upload(key, blob)
20
30
 
21
- def upload_code(
22
- self,
23
- project_id: str,
24
- repo_id: str,
25
- code_hash: str,
26
- blob: bytes,
27
- ):
28
- key = self._get_code_key(project_id, repo_id, code_hash)
29
- self._upload(key, blob)
31
+ def get_code(
32
+ self,
33
+ project_id: str,
34
+ repo_id: str,
35
+ code_hash: str,
36
+ ) -> Optional[bytes]:
37
+ key = self._get_code_key(project_id, repo_id, code_hash)
38
+ return self._get(key)
30
39
 
31
- def get_code(
32
- self,
33
- project_id: str,
34
- repo_id: str,
35
- code_hash: str,
36
- ) -> Optional[bytes]:
37
- key = self._get_code_key(project_id, repo_id, code_hash)
38
- return self._get(key)
40
+ def upload_archive(
41
+ self,
42
+ user_id: str,
43
+ archive_hash: str,
44
+ blob: bytes,
45
+ ):
46
+ key = self._get_archive_key(user_id, archive_hash)
47
+ self._upload(key, blob)
39
48
 
40
- def upload_archive(
41
- self,
42
- user_id: str,
43
- archive_hash: str,
44
- blob: bytes,
45
- ):
46
- key = self._get_archive_key(user_id, archive_hash)
47
- self._upload(key, blob)
49
+ def get_archive(
50
+ self,
51
+ user_id: str,
52
+ archive_hash: str,
53
+ ) -> Optional[bytes]:
54
+ key = self._get_archive_key(user_id, archive_hash)
55
+ return self._get(key)
48
56
 
49
- def get_archive(
50
- self,
51
- user_id: str,
52
- archive_hash: str,
53
- ) -> Optional[bytes]:
54
- key = self._get_archive_key(user_id, archive_hash)
55
- return self._get(key)
57
+ def _upload(self, key: str, blob: bytes):
58
+ blob_obj = self._bucket.blob(key)
59
+ blob_obj.upload_from_string(blob)
56
60
 
57
- def _upload(self, key: str, blob: bytes):
58
- blob_obj = self._bucket.blob(key)
59
- blob_obj.upload_from_string(blob)
60
-
61
- def _get(self, key: str) -> Optional[bytes]:
62
- try:
63
- blob = self._bucket.blob(key)
64
- except NotFound:
65
- return None
66
- return blob.download_as_bytes()
61
+ def _get(self, key: str) -> Optional[bytes]:
62
+ try:
63
+ blob = self._bucket.blob(key)
64
+ except NotFound:
65
+ return None
66
+ return blob.download_as_bytes()
@@ -8,62 +8,62 @@ try:
8
8
  from boto3 import Session
9
9
  except ImportError:
10
10
  BOTO_AVAILABLE = False
11
+ else:
11
12
 
13
+ class S3Storage(BaseStorage):
14
+ def __init__(
15
+ self,
16
+ bucket: str,
17
+ region: Optional[str] = None,
18
+ ):
19
+ self._session = Session()
20
+ self._client = self._session.client("s3", region_name=region)
21
+ self.bucket = bucket
12
22
 
13
- class S3Storage(BaseStorage):
14
- def __init__(
15
- self,
16
- bucket: str,
17
- region: Optional[str] = None,
18
- ):
19
- self._session = Session()
20
- self._client = self._session.client("s3", region_name=region)
21
- self.bucket = bucket
23
+ def upload_code(
24
+ self,
25
+ project_id: str,
26
+ repo_id: str,
27
+ code_hash: str,
28
+ blob: bytes,
29
+ ):
30
+ key = self._get_code_key(project_id, repo_id, code_hash)
31
+ self._upload(key, blob)
22
32
 
23
- def upload_code(
24
- self,
25
- project_id: str,
26
- repo_id: str,
27
- code_hash: str,
28
- blob: bytes,
29
- ):
30
- key = self._get_code_key(project_id, repo_id, code_hash)
31
- self._upload(key, blob)
33
+ def get_code(
34
+ self,
35
+ project_id: str,
36
+ repo_id: str,
37
+ code_hash: str,
38
+ ) -> Optional[bytes]:
39
+ key = self._get_code_key(project_id, repo_id, code_hash)
40
+ return self._get(key)
32
41
 
33
- def get_code(
34
- self,
35
- project_id: str,
36
- repo_id: str,
37
- code_hash: str,
38
- ) -> Optional[bytes]:
39
- key = self._get_code_key(project_id, repo_id, code_hash)
40
- return self._get(key)
42
+ def upload_archive(
43
+ self,
44
+ user_id: str,
45
+ archive_hash: str,
46
+ blob: bytes,
47
+ ):
48
+ key = self._get_archive_key(user_id, archive_hash)
49
+ self._upload(key, blob)
41
50
 
42
- def upload_archive(
43
- self,
44
- user_id: str,
45
- archive_hash: str,
46
- blob: bytes,
47
- ):
48
- key = self._get_archive_key(user_id, archive_hash)
49
- self._upload(key, blob)
51
+ def get_archive(
52
+ self,
53
+ user_id: str,
54
+ archive_hash: str,
55
+ ) -> Optional[bytes]:
56
+ key = self._get_archive_key(user_id, archive_hash)
57
+ return self._get(key)
50
58
 
51
- def get_archive(
52
- self,
53
- user_id: str,
54
- archive_hash: str,
55
- ) -> Optional[bytes]:
56
- key = self._get_archive_key(user_id, archive_hash)
57
- return self._get(key)
59
+ def _upload(self, key: str, blob: bytes):
60
+ self._client.put_object(Bucket=self.bucket, Key=key, Body=blob)
58
61
 
59
- def _upload(self, key: str, blob: bytes):
60
- self._client.put_object(Bucket=self.bucket, Key=key, Body=blob)
61
-
62
- def _get(self, key: str) -> Optional[bytes]:
63
- try:
64
- response = self._client.get_object(Bucket=self.bucket, Key=key)
65
- except botocore.exceptions.ClientError as e:
66
- if e.response["Error"]["Code"] == "NoSuchKey":
67
- return None
68
- raise e
69
- return response["Body"].read()
62
+ def _get(self, key: str) -> Optional[bytes]:
63
+ try:
64
+ response = self._client.get_object(Bucket=self.bucket, Key=key)
65
+ except botocore.exceptions.ClientError as e:
66
+ if e.response["Error"]["Code"] == "NoSuchKey":
67
+ return None
68
+ raise e
69
+ return response["Body"].read()
@@ -1,3 +1,3 @@
1
1
  <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>dstack</title><meta name="description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
2
2
  "/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"><meta name="og:title" content="dstack"><meta name="og:type" content="article"><meta name="og:image" content="/splash_thumbnail.png"><meta name="og:description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
3
- "><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-d151b300fcac3933213d.js"></script><link href="/main-aec4762350e34d6fbff9.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="1c028b8e-609e-40e3-b792-f3939a461850" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true" async></script></body></html>
3
+ "><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-d151b300fcac3933213d.js"></script><link href="/main-aec4762350e34d6fbff9.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script async src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="11a9339d-20ce-4ddb-9ba3-1b6e29afe8eb" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true"></script></body></html>
@@ -573,7 +573,7 @@ def get_fleet_spec(conf: Optional[FleetConfiguration] = None) -> FleetSpec:
573
573
  return FleetSpec(
574
574
  configuration=conf,
575
575
  configuration_path="fleet.dstack.yml",
576
- profile=Profile(name=""),
576
+ profile=Profile(),
577
577
  )
578
578
 
579
579
 
@@ -31,15 +31,15 @@ def configure_logging():
31
31
  rename_fields={"name": "logger", "asctime": "timestamp", "levelname": "level"},
32
32
  ),
33
33
  }
34
- handlers = {
34
+ handlers: dict[str, logging.Handler] = {
35
35
  "rich": DstackRichHandler(console=console),
36
36
  "standard": logging.StreamHandler(stream=sys.stdout),
37
37
  "json": logging.StreamHandler(stream=sys.stdout),
38
38
  }
39
39
  if settings.LOG_FORMAT not in formatters:
40
40
  raise ValueError(f"Invalid settings.LOG_FORMAT: {settings.LOG_FORMAT}")
41
- formatter = formatters.get(settings.LOG_FORMAT)
42
- handler = handlers.get(settings.LOG_FORMAT)
41
+ formatter = formatters[settings.LOG_FORMAT]
42
+ handler = handlers[settings.LOG_FORMAT]
43
43
  handler.setFormatter(formatter)
44
44
  handler.addFilter(AsyncioCancelledErrorFilter())
45
45
  root_logger = logging.getLogger(None)
@@ -312,10 +312,10 @@ def get_paramiko_connection(
312
312
  with proxy_ctx as proxy_client, paramiko.SSHClient() as client:
313
313
  proxy_channel: Optional[paramiko.Channel] = None
314
314
  if proxy_client is not None:
315
+ transport = proxy_client.get_transport()
316
+ assert transport is not None
315
317
  try:
316
- proxy_channel = proxy_client.get_transport().open_channel(
317
- "direct-tcpip", (host, port), ("", 0)
318
- )
318
+ proxy_channel = transport.open_channel("direct-tcpip", (host, port), ("", 0))
319
319
  except (paramiko.SSHException, OSError) as e:
320
320
  raise ProvisioningError(f"Proxy channel failed: {e}") from e
321
321
  client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
@@ -3,7 +3,9 @@ def add_extra_schema_types(schema_property: dict, extra_types: list[dict]):
3
3
  refs = [schema_property.pop("allOf")[0]]
4
4
  elif "anyOf" in schema_property:
5
5
  refs = schema_property.pop("anyOf")
6
- else:
6
+ elif "type" in schema_property:
7
7
  refs = [{"type": schema_property.pop("type")}]
8
+ else:
9
+ refs = [{"$ref": schema_property.pop("$ref")}]
8
10
  refs.extend(extra_types)
9
11
  schema_property["anyOf"] = refs
@@ -0,0 +1,14 @@
1
+ from typing import Any, Protocol, TypeVar, Union
2
+
3
+ _T_contra = TypeVar("_T_contra", contravariant=True)
4
+
5
+
6
+ class SupportsDunderLT(Protocol[_T_contra]):
7
+ def __lt__(self, other: _T_contra, /) -> bool: ...
8
+
9
+
10
+ class SupportsDunderGT(Protocol[_T_contra]):
11
+ def __gt__(self, other: _T_contra, /) -> bool: ...
12
+
13
+
14
+ SupportsRichComparison = Union[SupportsDunderLT[Any], SupportsDunderGT[Any]]
@@ -68,6 +68,7 @@ class RepoCollection:
68
68
  """
69
69
  creds = None
70
70
  if isinstance(repo, RemoteRepo):
71
+ assert repo.repo_url is not None
71
72
  try:
72
73
  creds = get_local_repo_credentials(
73
74
  repo_url=repo.repo_url,
@@ -140,22 +141,40 @@ class RepoCollection:
140
141
  def is_initialized(
141
142
  self,
142
143
  repo: Repo,
144
+ by_user: bool = False,
143
145
  ) -> bool:
144
146
  """
145
- Checks if the remote repo is initialized in the project
147
+ Checks if the repo is initialized in the project
146
148
 
147
149
  Args:
148
150
  repo: The repo to check.
151
+ by_user: Require the remote repo to be initialized by the user, that is, to have
152
+ the user's credentials. Ignored for other repo types.
149
153
 
150
154
  Returns:
151
155
  Whether the repo is initialized or not.
152
156
  """
157
+ if isinstance(repo, RemoteRepo) and by_user:
158
+ return self._is_initialized_by_user(repo)
153
159
  try:
154
- self._api_client.repos.get(self._project, repo.repo_id, include_creds=False)
160
+ self._api_client.repos.get(self._project, repo.repo_id)
155
161
  return True
156
162
  except ResourceNotExistsError:
157
163
  return False
158
164
 
165
+ def _is_initialized_by_user(self, repo: RemoteRepo) -> bool:
166
+ try:
167
+ repo_head = self._api_client.repos.get_with_creds(self._project, repo.repo_id)
168
+ except ResourceNotExistsError:
169
+ return False
170
+ # This works because:
171
+ # - RepoCollection.init() always submits RemoteRepoCreds for remote repos, even if
172
+ # the repo is public
173
+ # - Server returns creds only if there is RepoCredsModel for the user (or legacy
174
+ # shared creds in RepoModel)
175
+ # TODO: add an API method with the same logic returning a bool value?
176
+ return repo_head.repo_creds is not None
177
+
159
178
 
160
179
  def get_ssh_keypair(key_path: Optional[PathLike], dstack_key_path: Path) -> str:
161
180
  """Returns a path to the private key"""
@@ -23,7 +23,7 @@ from dstack._internal.core.models.configurations import (
23
23
  PortMapping,
24
24
  ServiceConfiguration,
25
25
  )
26
- from dstack._internal.core.models.files import FileArchiveMapping, FilePathMapping
26
+ from dstack._internal.core.models.files import FileArchiveMapping
27
27
  from dstack._internal.core.models.profiles import (
28
28
  CreationPolicy,
29
29
  Profile,
@@ -436,7 +436,7 @@ class RunCollection:
436
436
  ) -> RunPlan:
437
437
  """
438
438
  Get a run plan.
439
- Use this method to see the run plan before applying the cofiguration.
439
+ Use this method to see the run plan before applying the configuration.
440
440
 
441
441
  Args:
442
442
  configuration (Union[Task, Service, DevEnvironment]): The run configuration.
@@ -499,7 +499,6 @@ class RunCollection:
499
499
 
500
500
  self._validate_configuration_files(configuration, run_spec.configuration_path)
501
501
  for file_mapping in configuration.files:
502
- assert isinstance(file_mapping, FilePathMapping)
503
502
  with tempfile.TemporaryFile("w+b") as fp:
504
503
  try:
505
504
  archive_hash = create_file_archive(file_mapping.local_path, fp)
@@ -691,11 +690,11 @@ class RunCollection:
691
690
  spot_policy=spot_policy,
692
691
  retry=None,
693
692
  utilization_policy=utilization_policy,
694
- max_duration=max_duration,
695
- stop_duration=stop_duration,
693
+ max_duration=max_duration, # type: ignore[assignment]
694
+ stop_duration=stop_duration, # type: ignore[assignment]
696
695
  max_price=max_price,
697
696
  creation_policy=creation_policy,
698
- idle_duration=idle_duration,
697
+ idle_duration=idle_duration, # type: ignore[assignment]
699
698
  )
700
699
  run_spec = RunSpec(
701
700
  run_name=run_name,
@@ -812,7 +811,6 @@ class RunCollection:
812
811
  if configuration_path is not None:
813
812
  base_dir = Path(configuration_path).expanduser().resolve().parent
814
813
  for file_mapping in configuration.files:
815
- assert isinstance(file_mapping, FilePathMapping)
816
814
  path = Path(file_mapping.local_path).expanduser()
817
815
  if not path.is_absolute():
818
816
  if base_dir is None: