dstack 0.19.26__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.

Files changed (93) hide show
  1. dstack/_internal/cli/commands/__init__.py +11 -8
  2. dstack/_internal/cli/commands/apply.py +6 -3
  3. dstack/_internal/cli/commands/completion.py +3 -1
  4. dstack/_internal/cli/commands/config.py +1 -0
  5. dstack/_internal/cli/commands/init.py +4 -4
  6. dstack/_internal/cli/commands/offer.py +1 -1
  7. dstack/_internal/cli/commands/project.py +1 -0
  8. dstack/_internal/cli/commands/server.py +2 -2
  9. dstack/_internal/cli/main.py +1 -1
  10. dstack/_internal/cli/services/configurators/base.py +2 -4
  11. dstack/_internal/cli/services/configurators/fleet.py +4 -5
  12. dstack/_internal/cli/services/configurators/gateway.py +3 -5
  13. dstack/_internal/cli/services/configurators/run.py +165 -43
  14. dstack/_internal/cli/services/configurators/volume.py +3 -5
  15. dstack/_internal/cli/services/repos.py +1 -18
  16. dstack/_internal/core/backends/amddevcloud/__init__.py +1 -0
  17. dstack/_internal/core/backends/amddevcloud/backend.py +16 -0
  18. dstack/_internal/core/backends/amddevcloud/compute.py +5 -0
  19. dstack/_internal/core/backends/amddevcloud/configurator.py +29 -0
  20. dstack/_internal/core/backends/aws/compute.py +6 -1
  21. dstack/_internal/core/backends/base/compute.py +33 -5
  22. dstack/_internal/core/backends/base/offers.py +2 -0
  23. dstack/_internal/core/backends/configurators.py +15 -0
  24. dstack/_internal/core/backends/digitalocean/__init__.py +1 -0
  25. dstack/_internal/core/backends/digitalocean/backend.py +16 -0
  26. dstack/_internal/core/backends/digitalocean/compute.py +5 -0
  27. dstack/_internal/core/backends/digitalocean/configurator.py +31 -0
  28. dstack/_internal/core/backends/digitalocean_base/__init__.py +1 -0
  29. dstack/_internal/core/backends/digitalocean_base/api_client.py +104 -0
  30. dstack/_internal/core/backends/digitalocean_base/backend.py +5 -0
  31. dstack/_internal/core/backends/digitalocean_base/compute.py +173 -0
  32. dstack/_internal/core/backends/digitalocean_base/configurator.py +57 -0
  33. dstack/_internal/core/backends/digitalocean_base/models.py +43 -0
  34. dstack/_internal/core/backends/gcp/compute.py +32 -8
  35. dstack/_internal/core/backends/hotaisle/api_client.py +25 -33
  36. dstack/_internal/core/backends/hotaisle/compute.py +1 -6
  37. dstack/_internal/core/backends/models.py +7 -0
  38. dstack/_internal/core/backends/nebius/compute.py +0 -7
  39. dstack/_internal/core/backends/oci/compute.py +4 -5
  40. dstack/_internal/core/backends/vultr/compute.py +1 -5
  41. dstack/_internal/core/compatibility/fleets.py +5 -0
  42. dstack/_internal/core/compatibility/runs.py +10 -1
  43. dstack/_internal/core/models/backends/base.py +5 -1
  44. dstack/_internal/core/models/common.py +67 -43
  45. dstack/_internal/core/models/configurations.py +109 -69
  46. dstack/_internal/core/models/files.py +1 -1
  47. dstack/_internal/core/models/fleets.py +115 -25
  48. dstack/_internal/core/models/instances.py +5 -5
  49. dstack/_internal/core/models/profiles.py +66 -47
  50. dstack/_internal/core/models/repos/remote.py +21 -16
  51. dstack/_internal/core/models/resources.py +69 -65
  52. dstack/_internal/core/models/runs.py +41 -14
  53. dstack/_internal/core/services/repos.py +85 -80
  54. dstack/_internal/server/app.py +5 -0
  55. dstack/_internal/server/background/tasks/process_fleets.py +117 -13
  56. dstack/_internal/server/background/tasks/process_instances.py +12 -71
  57. dstack/_internal/server/background/tasks/process_running_jobs.py +2 -0
  58. dstack/_internal/server/background/tasks/process_runs.py +2 -0
  59. dstack/_internal/server/background/tasks/process_submitted_jobs.py +48 -16
  60. dstack/_internal/server/migrations/versions/2498ab323443_add_fleetmodel_consolidation_attempt_.py +44 -0
  61. dstack/_internal/server/models.py +11 -7
  62. dstack/_internal/server/schemas/gateways.py +10 -9
  63. dstack/_internal/server/schemas/runner.py +1 -0
  64. dstack/_internal/server/services/backends/handlers.py +2 -0
  65. dstack/_internal/server/services/docker.py +8 -7
  66. dstack/_internal/server/services/fleets.py +23 -25
  67. dstack/_internal/server/services/instances.py +3 -3
  68. dstack/_internal/server/services/jobs/configurators/base.py +46 -6
  69. dstack/_internal/server/services/jobs/configurators/dev.py +4 -4
  70. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +3 -5
  71. dstack/_internal/server/services/jobs/configurators/extensions/vscode.py +4 -6
  72. dstack/_internal/server/services/jobs/configurators/service.py +0 -3
  73. dstack/_internal/server/services/jobs/configurators/task.py +0 -3
  74. dstack/_internal/server/services/projects.py +52 -1
  75. dstack/_internal/server/services/runs.py +16 -0
  76. dstack/_internal/server/settings.py +46 -0
  77. dstack/_internal/server/statics/index.html +1 -1
  78. dstack/_internal/server/statics/{main-aec4762350e34d6fbff9.css → main-5e0d56245c4bd241ec27.css} +1 -1
  79. dstack/_internal/server/statics/{main-d151b300fcac3933213d.js → main-a2a16772fbf11a14d191.js} +1215 -998
  80. dstack/_internal/server/statics/{main-d151b300fcac3933213d.js.map → main-a2a16772fbf11a14d191.js.map} +1 -1
  81. dstack/_internal/server/testing/common.py +6 -3
  82. dstack/_internal/utils/env.py +85 -11
  83. dstack/_internal/utils/path.py +8 -1
  84. dstack/_internal/utils/ssh.py +7 -0
  85. dstack/api/_public/repos.py +41 -6
  86. dstack/api/_public/runs.py +14 -1
  87. dstack/version.py +1 -1
  88. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/METADATA +2 -2
  89. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/RECORD +92 -78
  90. dstack/_internal/server/statics/static/media/github.1f7102513534c83a9d8d735d2b8c12a2.svg +0 -3
  91. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/WHEEL +0 -0
  92. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/entry_points.txt +0 -0
  93. {dstack-0.19.26.dist-info → dstack-0.19.28.dist-info}/licenses/LICENSE.md +0 -0
@@ -6,7 +6,12 @@ from pydantic import Field, root_validator, validator
6
6
  from typing_extensions import Annotated, Literal
7
7
 
8
8
  from dstack._internal.core.models.backends.base import BackendType
9
- from dstack._internal.core.models.common import CoreModel, Duration
9
+ from dstack._internal.core.models.common import (
10
+ CoreConfig,
11
+ CoreModel,
12
+ Duration,
13
+ generate_dual_core_model,
14
+ )
10
15
  from dstack._internal.utils.common import list_enum_values_for_annotation
11
16
  from dstack._internal.utils.cron import validate_cron
12
17
  from dstack._internal.utils.json_schema import add_extra_schema_types
@@ -112,7 +117,16 @@ class RetryEvent(str, Enum):
112
117
  ERROR = "error"
113
118
 
114
119
 
115
- class ProfileRetry(CoreModel):
120
+ class ProfileRetryConfig(CoreConfig):
121
+ @staticmethod
122
+ def schema_extra(schema: Dict[str, Any]):
123
+ add_extra_schema_types(
124
+ schema["properties"]["duration"],
125
+ extra_types=[{"type": "string"}],
126
+ )
127
+
128
+
129
+ class ProfileRetry(generate_dual_core_model(ProfileRetryConfig)):
116
130
  on_events: Annotated[
117
131
  Optional[List[RetryEvent]],
118
132
  Field(
@@ -128,14 +142,6 @@ class ProfileRetry(CoreModel):
128
142
  Field(description="The maximum period of retrying the run, e.g., `4h` or `1d`"),
129
143
  ] = None
130
144
 
131
- class Config(CoreModel.Config):
132
- @staticmethod
133
- def schema_extra(schema: Dict[str, Any]):
134
- add_extra_schema_types(
135
- schema["properties"]["duration"],
136
- extra_types=[{"type": "string"}],
137
- )
138
-
139
145
  _validate_duration = validator("duration", pre=True, allow_reuse=True)(parse_duration)
140
146
 
141
147
  @root_validator
@@ -146,7 +152,16 @@ class ProfileRetry(CoreModel):
146
152
  return values
147
153
 
148
154
 
149
- class UtilizationPolicy(CoreModel):
155
+ class UtilizationPolicyConfig(CoreConfig):
156
+ @staticmethod
157
+ def schema_extra(schema: Dict[str, Any]):
158
+ add_extra_schema_types(
159
+ schema["properties"]["time_window"],
160
+ extra_types=[{"type": "string"}],
161
+ )
162
+
163
+
164
+ class UtilizationPolicy(generate_dual_core_model(UtilizationPolicyConfig)):
150
165
  _min_time_window = "5m"
151
166
 
152
167
  min_gpu_utilization: Annotated[
@@ -171,14 +186,6 @@ class UtilizationPolicy(CoreModel):
171
186
  ),
172
187
  ]
173
188
 
174
- class Config(CoreModel.Config):
175
- @staticmethod
176
- def schema_extra(schema: Dict[str, Any]):
177
- add_extra_schema_types(
178
- schema["properties"]["time_window"],
179
- extra_types=[{"type": "string"}],
180
- )
181
-
182
189
  @validator("time_window", pre=True)
183
190
  def validate_time_window(cls, v: Union[int, str]) -> int:
184
191
  v = parse_duration(v)
@@ -219,6 +226,28 @@ class Schedule(CoreModel):
219
226
  return self.cron
220
227
 
221
228
 
229
+ class ProfileParamsConfig(CoreConfig):
230
+ @staticmethod
231
+ def schema_extra(schema: Dict[str, Any]):
232
+ del schema["properties"]["pool_name"]
233
+ del schema["properties"]["instance_name"]
234
+ del schema["properties"]["retry_policy"]
235
+ del schema["properties"]["termination_policy"]
236
+ del schema["properties"]["termination_idle_time"]
237
+ add_extra_schema_types(
238
+ schema["properties"]["max_duration"],
239
+ extra_types=[{"type": "boolean"}, {"type": "string"}],
240
+ )
241
+ add_extra_schema_types(
242
+ schema["properties"]["stop_duration"],
243
+ extra_types=[{"type": "boolean"}, {"type": "string"}],
244
+ )
245
+ add_extra_schema_types(
246
+ schema["properties"]["idle_duration"],
247
+ extra_types=[{"type": "string"}],
248
+ )
249
+
250
+
222
251
  class ProfileParams(CoreModel):
223
252
  backends: Annotated[
224
253
  Optional[List[BackendType]],
@@ -358,27 +387,6 @@ class ProfileParams(CoreModel):
358
387
  termination_policy: Annotated[Optional[TerminationPolicy], Field(exclude=True)] = None
359
388
  termination_idle_time: Annotated[Optional[Union[str, int]], Field(exclude=True)] = None
360
389
 
361
- class Config(CoreModel.Config):
362
- @staticmethod
363
- def schema_extra(schema: Dict[str, Any]) -> None:
364
- del schema["properties"]["pool_name"]
365
- del schema["properties"]["instance_name"]
366
- del schema["properties"]["retry_policy"]
367
- del schema["properties"]["termination_policy"]
368
- del schema["properties"]["termination_idle_time"]
369
- add_extra_schema_types(
370
- schema["properties"]["max_duration"],
371
- extra_types=[{"type": "boolean"}, {"type": "string"}],
372
- )
373
- add_extra_schema_types(
374
- schema["properties"]["stop_duration"],
375
- extra_types=[{"type": "boolean"}, {"type": "string"}],
376
- )
377
- add_extra_schema_types(
378
- schema["properties"]["idle_duration"],
379
- extra_types=[{"type": "string"}],
380
- )
381
-
382
390
  _validate_max_duration = validator("max_duration", pre=True, allow_reuse=True)(
383
391
  parse_max_duration
384
392
  )
@@ -403,17 +411,28 @@ class ProfileProps(CoreModel):
403
411
  ] = False
404
412
 
405
413
 
406
- class Profile(ProfileProps, ProfileParams):
414
+ class ProfileConfig(ProfileParamsConfig):
415
+ @staticmethod
416
+ def schema_extra(schema: Dict[str, Any]):
417
+ ProfileParamsConfig.schema_extra(schema)
418
+
419
+
420
+ class Profile(
421
+ ProfileProps,
422
+ ProfileParams,
423
+ generate_dual_core_model(ProfileConfig),
424
+ ):
407
425
  pass
408
426
 
409
427
 
410
- class ProfilesConfig(CoreModel):
411
- profiles: List[Profile]
428
+ class ProfilesConfigConfig(CoreConfig):
429
+ json_loads = orjson.loads
430
+ json_dumps = pydantic_orjson_dumps_with_indent
431
+ schema_extra = {"$schema": "http://json-schema.org/draft-07/schema#"}
412
432
 
413
- class Config(CoreModel.Config):
414
- json_loads = orjson.loads
415
- json_dumps = pydantic_orjson_dumps_with_indent
416
- schema_extra = {"$schema": "http://json-schema.org/draft-07/schema#"}
433
+
434
+ class ProfilesConfig(generate_dual_core_model(ProfilesConfigConfig)):
435
+ profiles: List[Profile]
417
436
 
418
437
  def default(self) -> Optional[Profile]:
419
438
  for p in self.profiles:
@@ -11,7 +11,7 @@ from pydantic import Field
11
11
  from typing_extensions import Literal
12
12
 
13
13
  from dstack._internal.core.errors import DstackError
14
- from dstack._internal.core.models.common import CoreModel
14
+ from dstack._internal.core.models.common import CoreConfig, generate_dual_core_model
15
15
  from dstack._internal.core.models.repos.base import BaseRepoInfo, Repo
16
16
  from dstack._internal.utils.hash import get_sha256, slugify
17
17
  from dstack._internal.utils.path import PathLike
@@ -24,21 +24,33 @@ class RepoError(DstackError):
24
24
  pass
25
25
 
26
26
 
27
- class RemoteRepoCreds(CoreModel):
27
+ class RemoteRepoCredsConfig(CoreConfig):
28
+ @staticmethod
29
+ def schema_extra(schema: Dict[str, Any]):
30
+ del schema["properties"]["protocol"]
31
+
32
+
33
+ class RemoteRepoCreds(generate_dual_core_model(RemoteRepoCredsConfig)):
28
34
  clone_url: str
29
- private_key: Optional[str]
30
- oauth_token: Optional[str]
35
+ private_key: Optional[str] = None
36
+ oauth_token: Optional[str] = None
31
37
 
32
38
  # TODO: remove in 0.20. Left for compatibility with CLI <=0.18.44
33
39
  protocol: Annotated[Optional[str], Field(exclude=True)] = None
34
40
 
35
- class Config(CoreModel.Config):
36
- @staticmethod
37
- def schema_extra(schema: Dict[str, Any]) -> None:
38
- del schema["properties"]["protocol"]
39
41
 
42
+ class RemoteRepoInfoConfig(CoreConfig):
43
+ @staticmethod
44
+ def schema_extra(schema: Dict[str, Any]):
45
+ del schema["properties"]["repo_host_name"]
46
+ del schema["properties"]["repo_port"]
47
+ del schema["properties"]["repo_user_name"]
40
48
 
41
- class RemoteRepoInfo(BaseRepoInfo):
49
+
50
+ class RemoteRepoInfo(
51
+ BaseRepoInfo,
52
+ generate_dual_core_model(RemoteRepoInfoConfig),
53
+ ):
42
54
  repo_type: Literal["remote"] = "remote"
43
55
  repo_name: str
44
56
 
@@ -47,13 +59,6 @@ class RemoteRepoInfo(BaseRepoInfo):
47
59
  repo_port: Annotated[Optional[int], Field(exclude=True)] = None
48
60
  repo_user_name: Annotated[Optional[str], Field(exclude=True)] = None
49
61
 
50
- class Config(BaseRepoInfo.Config):
51
- @staticmethod
52
- def schema_extra(schema: Dict[str, Any]) -> None:
53
- del schema["properties"]["repo_host_name"]
54
- del schema["properties"]["repo_port"]
55
- del schema["properties"]["repo_user_name"]
56
-
57
62
 
58
63
  class RemoteRunRepoData(RemoteRepoInfo):
59
64
  repo_branch: Optional[str] = None
@@ -7,7 +7,7 @@ from pydantic import Field, parse_obj_as, root_validator, validator
7
7
  from pydantic.generics import GenericModel
8
8
  from typing_extensions import Annotated
9
9
 
10
- from dstack._internal.core.models.common import CoreModel
10
+ from dstack._internal.core.models.common import CoreConfig, CoreModel, generate_dual_core_model
11
11
  from dstack._internal.utils.common import pretty_resources
12
12
  from dstack._internal.utils.json_schema import add_extra_schema_types
13
13
  from dstack._internal.utils.logging import get_logger
@@ -129,21 +129,22 @@ DEFAULT_MEMORY_SIZE = Range[Memory](min=Memory.parse("8GB"))
129
129
  DEFAULT_GPU_COUNT = Range[int](min=1)
130
130
 
131
131
 
132
- class CPUSpec(CoreModel):
132
+ class CPUSpecConfig(CoreConfig):
133
+ @staticmethod
134
+ def schema_extra(schema: Dict[str, Any]):
135
+ add_extra_schema_types(
136
+ schema["properties"]["count"],
137
+ extra_types=[{"type": "integer"}, {"type": "string"}],
138
+ )
139
+
140
+
141
+ class CPUSpec(generate_dual_core_model(CPUSpecConfig)):
133
142
  arch: Annotated[
134
143
  Optional[gpuhunt.CPUArchitecture],
135
144
  Field(description="The CPU architecture, one of: `x86`, `arm`"),
136
145
  ] = None
137
146
  count: Annotated[Range[int], Field(description="The number of CPU cores")] = DEFAULT_CPU_COUNT
138
147
 
139
- class Config(CoreModel.Config):
140
- @staticmethod
141
- def schema_extra(schema: Dict[str, Any]):
142
- add_extra_schema_types(
143
- schema["properties"]["count"],
144
- extra_types=[{"type": "integer"}, {"type": "string"}],
145
- )
146
-
147
148
  @classmethod
148
149
  def __get_validators__(cls):
149
150
  yield cls.parse
@@ -190,7 +191,28 @@ class CPUSpec(CoreModel):
190
191
  return v
191
192
 
192
193
 
193
- class GPUSpec(CoreModel):
194
+ class GPUSpecConfig(CoreConfig):
195
+ @staticmethod
196
+ def schema_extra(schema: Dict[str, Any]):
197
+ add_extra_schema_types(
198
+ schema["properties"]["count"],
199
+ extra_types=[{"type": "integer"}, {"type": "string"}],
200
+ )
201
+ add_extra_schema_types(
202
+ schema["properties"]["name"],
203
+ extra_types=[{"type": "string"}],
204
+ )
205
+ add_extra_schema_types(
206
+ schema["properties"]["memory"],
207
+ extra_types=[{"type": "integer"}, {"type": "string"}],
208
+ )
209
+ add_extra_schema_types(
210
+ schema["properties"]["total_memory"],
211
+ extra_types=[{"type": "integer"}, {"type": "string"}],
212
+ )
213
+
214
+
215
+ class GPUSpec(generate_dual_core_model(GPUSpecConfig)):
194
216
  vendor: Annotated[
195
217
  Optional[gpuhunt.AcceleratorVendor],
196
218
  Field(
@@ -218,26 +240,6 @@ class GPUSpec(CoreModel):
218
240
  Field(description="The minimum compute capability of the GPU (e.g., `7.5`)"),
219
241
  ] = None
220
242
 
221
- class Config(CoreModel.Config):
222
- @staticmethod
223
- def schema_extra(schema: Dict[str, Any]):
224
- add_extra_schema_types(
225
- schema["properties"]["count"],
226
- extra_types=[{"type": "integer"}, {"type": "string"}],
227
- )
228
- add_extra_schema_types(
229
- schema["properties"]["name"],
230
- extra_types=[{"type": "string"}],
231
- )
232
- add_extra_schema_types(
233
- schema["properties"]["memory"],
234
- extra_types=[{"type": "integer"}, {"type": "string"}],
235
- )
236
- add_extra_schema_types(
237
- schema["properties"]["total_memory"],
238
- extra_types=[{"type": "integer"}, {"type": "string"}],
239
- )
240
-
241
243
  @classmethod
242
244
  def __get_validators__(cls):
243
245
  yield cls.parse
@@ -317,16 +319,17 @@ class GPUSpec(CoreModel):
317
319
  return gpuhunt.AcceleratorVendor.cast(v)
318
320
 
319
321
 
320
- class DiskSpec(CoreModel):
321
- size: Annotated[Range[Memory], Field(description="Disk size")]
322
+ class DiskSpecConfig(CoreConfig):
323
+ @staticmethod
324
+ def schema_extra(schema: Dict[str, Any]):
325
+ add_extra_schema_types(
326
+ schema["properties"]["size"],
327
+ extra_types=[{"type": "integer"}, {"type": "string"}],
328
+ )
322
329
 
323
- class Config(CoreModel.Config):
324
- @staticmethod
325
- def schema_extra(schema: Dict[str, Any]):
326
- add_extra_schema_types(
327
- schema["properties"]["size"],
328
- extra_types=[{"type": "integer"}, {"type": "string"}],
329
- )
330
+
331
+ class DiskSpec(generate_dual_core_model(DiskSpecConfig)):
332
+ size: Annotated[Range[Memory], Field(description="Disk size")]
330
333
 
331
334
  @classmethod
332
335
  def __get_validators__(cls):
@@ -343,7 +346,32 @@ class DiskSpec(CoreModel):
343
346
  DEFAULT_DISK = DiskSpec(size=Range[Memory](min=Memory.parse("100GB"), max=None))
344
347
 
345
348
 
346
- class ResourcesSpec(CoreModel):
349
+ class ResourcesSpecConfig(CoreConfig):
350
+ @staticmethod
351
+ def schema_extra(schema: Dict[str, Any]):
352
+ add_extra_schema_types(
353
+ schema["properties"]["cpu"],
354
+ extra_types=[{"type": "integer"}, {"type": "string"}],
355
+ )
356
+ add_extra_schema_types(
357
+ schema["properties"]["memory"],
358
+ extra_types=[{"type": "integer"}, {"type": "string"}],
359
+ )
360
+ add_extra_schema_types(
361
+ schema["properties"]["shm_size"],
362
+ extra_types=[{"type": "integer"}, {"type": "string"}],
363
+ )
364
+ add_extra_schema_types(
365
+ schema["properties"]["gpu"],
366
+ extra_types=[{"type": "integer"}, {"type": "string"}],
367
+ )
368
+ add_extra_schema_types(
369
+ schema["properties"]["disk"],
370
+ extra_types=[{"type": "integer"}, {"type": "string"}],
371
+ )
372
+
373
+
374
+ class ResourcesSpec(generate_dual_core_model(ResourcesSpecConfig)):
347
375
  # TODO: Remove Range[int] in 0.20. Range[int] for backward compatibility only.
348
376
  cpu: Annotated[Union[CPUSpec, Range[int]], Field(description="The CPU requirements")] = (
349
377
  CPUSpec()
@@ -362,30 +390,6 @@ class ResourcesSpec(CoreModel):
362
390
  gpu: Annotated[Optional[GPUSpec], Field(description="The GPU requirements")] = None
363
391
  disk: Annotated[Optional[DiskSpec], Field(description="The disk resources")] = DEFAULT_DISK
364
392
 
365
- class Config(CoreModel.Config):
366
- @staticmethod
367
- def schema_extra(schema: Dict[str, Any]):
368
- add_extra_schema_types(
369
- schema["properties"]["cpu"],
370
- extra_types=[{"type": "integer"}, {"type": "string"}],
371
- )
372
- add_extra_schema_types(
373
- schema["properties"]["memory"],
374
- extra_types=[{"type": "integer"}, {"type": "string"}],
375
- )
376
- add_extra_schema_types(
377
- schema["properties"]["shm_size"],
378
- extra_types=[{"type": "integer"}, {"type": "string"}],
379
- )
380
- add_extra_schema_types(
381
- schema["properties"]["gpu"],
382
- extra_types=[{"type": "integer"}, {"type": "string"}],
383
- )
384
- add_extra_schema_types(
385
- schema["properties"]["disk"],
386
- extra_types=[{"type": "integer"}, {"type": "string"}],
387
- )
388
-
389
393
  def pretty_format(self) -> str:
390
394
  # TODO: Remove in 0.20. Use self.cpu directly
391
395
  cpu = parse_obj_as(CPUSpec, self.cpu)
@@ -1,16 +1,23 @@
1
1
  from datetime import datetime, timedelta
2
2
  from enum import Enum
3
- from typing import Any, Dict, List, Literal, Optional, Type
3
+ from typing import Any, Dict, List, Literal, Optional
4
4
  from urllib.parse import urlparse
5
5
 
6
6
  from pydantic import UUID4, Field, root_validator
7
7
  from typing_extensions import Annotated
8
8
 
9
9
  from dstack._internal.core.models.backends.base import BackendType
10
- from dstack._internal.core.models.common import ApplyAction, CoreModel, NetworkMode, RegistryAuth
10
+ from dstack._internal.core.models.common import (
11
+ ApplyAction,
12
+ CoreConfig,
13
+ CoreModel,
14
+ NetworkMode,
15
+ RegistryAuth,
16
+ generate_dual_core_model,
17
+ )
11
18
  from dstack._internal.core.models.configurations import (
12
19
  DEFAULT_PROBE_METHOD,
13
- DEFAULT_REPO_DIR,
20
+ LEGACY_REPO_DIR,
14
21
  AnyRunConfiguration,
15
22
  HTTPHeaderSpec,
16
23
  HTTPMethod,
@@ -259,6 +266,7 @@ class JobSpec(CoreModel):
259
266
  retry: Optional[Retry]
260
267
  volumes: Optional[List[MountPoint]] = None
261
268
  ssh_key: Optional[JobSSHKey] = None
269
+ # `working_dir` is always absolute (if not None) since 0.19.27
262
270
  working_dir: Optional[str]
263
271
  # `repo_data` is optional for client compatibility with pre-0.19.17 servers and for compatibility
264
272
  # with jobs submitted before 0.19.17. All new jobs are expected to have non-None `repo_data`.
@@ -268,6 +276,8 @@ class JobSpec(CoreModel):
268
276
  # submitted before 0.19.17. See `_get_repo_code_hash` on how to get the correct `repo_code_hash`
269
277
  # TODO: drop this comment when supporting jobs submitted before 0.19.17 is no longer relevant.
270
278
  repo_code_hash: Optional[str] = None
279
+ # `repo_dir` was added in 0.19.27. Default value is set for backward compatibility
280
+ repo_dir: str = LEGACY_REPO_DIR
271
281
  file_archives: list[FileArchiveMapping] = []
272
282
  # None for non-services and pre-0.19.19 services. See `get_service_port`
273
283
  service_port: Optional[int] = None
@@ -382,7 +392,14 @@ class Job(CoreModel):
382
392
  job_submissions: List[JobSubmission]
383
393
 
384
394
 
385
- class RunSpec(CoreModel):
395
+ class RunSpecConfig(CoreConfig):
396
+ @staticmethod
397
+ def schema_extra(schema: Dict[str, Any]):
398
+ prop = schema.get("properties", {})
399
+ prop.pop("merged_profile", None)
400
+
401
+
402
+ class RunSpec(generate_dual_core_model(RunSpecConfig)):
386
403
  # TODO: run_name, working_dir are redundant here since they already passed in configuration
387
404
  run_name: Annotated[
388
405
  Optional[str],
@@ -409,17 +426,27 @@ class RunSpec(CoreModel):
409
426
  Optional[str],
410
427
  Field(description="The hash of the repo diff. Can be omitted if there is no repo diff."),
411
428
  ] = None
429
+ repo_dir: Annotated[
430
+ Optional[str],
431
+ Field(
432
+ description=(
433
+ "The repo path inside the container. Relative paths are resolved"
434
+ f" relative to the working directory. Defaults to `{LEGACY_REPO_DIR}`."
435
+ )
436
+ ),
437
+ ] = None
412
438
  file_archives: Annotated[
413
439
  list[FileArchiveMapping],
414
- Field(description="The list of file archive ID to container path mappings"),
440
+ Field(description="The list of file archive ID to container path mappings."),
415
441
  ] = []
442
+ # Server uses configuration.working_dir instead of this field since 0.19.27, but
443
+ # the field still exists for compatibility with older servers
416
444
  working_dir: Annotated[
417
445
  Optional[str],
418
446
  Field(
419
447
  description=(
420
- "The path to the working directory inside the container."
421
- f" It's specified relative to the repository directory (`{DEFAULT_REPO_DIR}`) and should be inside it."
422
- ' Defaults to `"."`.'
448
+ "The absolute path to the working directory inside the container."
449
+ " Defaults to the default working directory from the `image`."
423
450
  )
424
451
  ),
425
452
  ] = None
@@ -445,12 +472,6 @@ class RunSpec(CoreModel):
445
472
  # TODO: make merged_profile a computed field after migrating to pydanticV2
446
473
  merged_profile: Annotated[Profile, Field(exclude=True)] = None
447
474
 
448
- class Config(CoreModel.Config):
449
- @staticmethod
450
- def schema_extra(schema: Dict[str, Any], model: Type) -> None:
451
- prop = schema.get("properties", {})
452
- prop.pop("merged_profile", None)
453
-
454
475
  @root_validator
455
476
  def _merged_profile(cls, values) -> Dict:
456
477
  if values.get("profile") is None:
@@ -506,10 +527,16 @@ class RunStatus(str, Enum):
506
527
  return self in self.finished_statuses()
507
528
 
508
529
 
530
+ class RunFleet(CoreModel):
531
+ id: UUID4
532
+ name: str
533
+
534
+
509
535
  class Run(CoreModel):
510
536
  id: UUID4
511
537
  project_name: str
512
538
  user: str
539
+ fleet: Optional[RunFleet] = None
513
540
  submitted_at: datetime
514
541
  last_processed_at: datetime
515
542
  status: RunStatus