dstack 0.19.27__py3-none-any.whl → 0.19.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dstack might be problematic. Click here for more details.

Files changed (44) 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 +2 -2
  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 +51 -27
  14. dstack/_internal/cli/services/configurators/volume.py +3 -5
  15. dstack/_internal/core/compatibility/runs.py +2 -0
  16. dstack/_internal/core/models/common.py +67 -43
  17. dstack/_internal/core/models/configurations.py +88 -62
  18. dstack/_internal/core/models/fleets.py +41 -24
  19. dstack/_internal/core/models/instances.py +5 -5
  20. dstack/_internal/core/models/profiles.py +66 -47
  21. dstack/_internal/core/models/repos/remote.py +21 -16
  22. dstack/_internal/core/models/resources.py +69 -65
  23. dstack/_internal/core/models/runs.py +17 -9
  24. dstack/_internal/server/app.py +5 -0
  25. dstack/_internal/server/background/tasks/process_fleets.py +8 -0
  26. dstack/_internal/server/background/tasks/process_submitted_jobs.py +32 -12
  27. dstack/_internal/server/models.py +6 -5
  28. dstack/_internal/server/schemas/gateways.py +10 -9
  29. dstack/_internal/server/services/backends/handlers.py +2 -0
  30. dstack/_internal/server/services/docker.py +8 -7
  31. dstack/_internal/server/services/projects.py +52 -1
  32. dstack/_internal/server/settings.py +46 -0
  33. dstack/_internal/server/statics/index.html +1 -1
  34. dstack/_internal/server/statics/{main-56191c63d516fd0041c4.css → main-5e0d56245c4bd241ec27.css} +1 -1
  35. dstack/_internal/server/statics/{main-4eecc75fbe64067eb1bc.js → main-a2a16772fbf11a14d191.js} +70 -100
  36. dstack/_internal/server/statics/{main-4eecc75fbe64067eb1bc.js.map → main-a2a16772fbf11a14d191.js.map} +1 -1
  37. dstack/_internal/utils/env.py +85 -11
  38. dstack/version.py +1 -1
  39. {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/METADATA +1 -1
  40. {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/RECORD +43 -44
  41. dstack/_internal/server/statics/static/media/github.1f7102513534c83a9d8d735d2b8c12a2.svg +0 -3
  42. {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/WHEEL +0 -0
  43. {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/entry_points.txt +0 -0
  44. {dstack-0.19.27.dist-info → dstack-0.19.28.dist-info}/licenses/LICENSE.md +0 -0
@@ -10,12 +10,23 @@ from pydantic import Field, ValidationError, conint, constr, root_validator, val
10
10
  from typing_extensions import Self
11
11
 
12
12
  from dstack._internal.core.errors import ConfigurationError
13
- from dstack._internal.core.models.common import CoreModel, Duration, RegistryAuth
13
+ from dstack._internal.core.models.common import (
14
+ CoreConfig,
15
+ CoreModel,
16
+ Duration,
17
+ RegistryAuth,
18
+ generate_dual_core_model,
19
+ )
14
20
  from dstack._internal.core.models.envs import Env
15
21
  from dstack._internal.core.models.files import FilePathMapping
16
22
  from dstack._internal.core.models.fleets import FleetConfiguration
17
23
  from dstack._internal.core.models.gateways import GatewayConfiguration
18
- from dstack._internal.core.models.profiles import ProfileParams, parse_duration, parse_off_duration
24
+ from dstack._internal.core.models.profiles import (
25
+ ProfileParams,
26
+ ProfileParamsConfig,
27
+ parse_duration,
28
+ parse_off_duration,
29
+ )
19
30
  from dstack._internal.core.models.resources import Range, ResourcesSpec
20
31
  from dstack._internal.core.models.services import AnyModel, OpenAIChatModel
21
32
  from dstack._internal.core.models.unix import UnixUser
@@ -276,7 +287,20 @@ class HTTPHeaderSpec(CoreModel):
276
287
  ]
277
288
 
278
289
 
279
- class ProbeConfig(CoreModel):
290
+ class ProbeConfigConfig(CoreConfig):
291
+ @staticmethod
292
+ def schema_extra(schema: Dict[str, Any]):
293
+ add_extra_schema_types(
294
+ schema["properties"]["timeout"],
295
+ extra_types=[{"type": "string"}],
296
+ )
297
+ add_extra_schema_types(
298
+ schema["properties"]["interval"],
299
+ extra_types=[{"type": "string"}],
300
+ )
301
+
302
+
303
+ class ProbeConfig(generate_dual_core_model(ProbeConfigConfig)):
280
304
  type: Literal["http"] # expect other probe types in the future, namely `exec`
281
305
  url: Annotated[
282
306
  Optional[str], Field(description=f"The URL to request. Defaults to `{DEFAULT_PROBE_URL}`")
@@ -331,18 +355,6 @@ class ProbeConfig(CoreModel):
331
355
  ),
332
356
  ] = None
333
357
 
334
- class Config(CoreModel.Config):
335
- @staticmethod
336
- def schema_extra(schema: Dict[str, Any]):
337
- add_extra_schema_types(
338
- schema["properties"]["timeout"],
339
- extra_types=[{"type": "string"}],
340
- )
341
- add_extra_schema_types(
342
- schema["properties"]["interval"],
343
- extra_types=[{"type": "string"}],
344
- )
345
-
346
358
  @validator("timeout", pre=True)
347
359
  def parse_timeout(cls, v: Optional[Union[int, str]]) -> Optional[int]:
348
360
  if v is None:
@@ -381,6 +393,19 @@ class ProbeConfig(CoreModel):
381
393
  return values
382
394
 
383
395
 
396
+ class BaseRunConfigurationConfig(CoreConfig):
397
+ @staticmethod
398
+ def schema_extra(schema: Dict[str, Any]):
399
+ add_extra_schema_types(
400
+ schema["properties"]["volumes"]["items"],
401
+ extra_types=[{"type": "string"}],
402
+ )
403
+ add_extra_schema_types(
404
+ schema["properties"]["files"]["items"],
405
+ extra_types=[{"type": "string"}],
406
+ )
407
+
408
+
384
409
  class BaseRunConfiguration(CoreModel):
385
410
  type: Literal["none"]
386
411
  name: Annotated[
@@ -484,18 +509,6 @@ class BaseRunConfiguration(CoreModel):
484
509
  # deprecated since 0.18.31; task, service -- no effect; dev-environment -- executed right before `init`
485
510
  setup: CommandsList = []
486
511
 
487
- class Config(CoreModel.Config):
488
- @staticmethod
489
- def schema_extra(schema: Dict[str, Any]):
490
- add_extra_schema_types(
491
- schema["properties"]["volumes"]["items"],
492
- extra_types=[{"type": "string"}],
493
- )
494
- add_extra_schema_types(
495
- schema["properties"]["files"]["items"],
496
- extra_types=[{"type": "string"}],
497
- )
498
-
499
512
  @validator("python", pre=True, always=True)
500
513
  def convert_python(cls, v, values) -> Optional[PythonVersion]:
501
514
  if v is not None and values.get("image"):
@@ -621,20 +634,25 @@ class DevEnvironmentConfigurationParams(CoreModel):
621
634
  return None
622
635
 
623
636
 
637
+ class DevEnvironmentConfigurationConfig(
638
+ ProfileParamsConfig,
639
+ BaseRunConfigurationConfig,
640
+ ):
641
+ @staticmethod
642
+ def schema_extra(schema: Dict[str, Any]):
643
+ ProfileParamsConfig.schema_extra(schema)
644
+ BaseRunConfigurationConfig.schema_extra(schema)
645
+
646
+
624
647
  class DevEnvironmentConfiguration(
625
648
  ProfileParams,
626
649
  BaseRunConfiguration,
627
650
  ConfigurationWithPortsParams,
628
651
  DevEnvironmentConfigurationParams,
652
+ generate_dual_core_model(DevEnvironmentConfigurationConfig),
629
653
  ):
630
654
  type: Literal["dev-environment"] = "dev-environment"
631
655
 
632
- class Config(ProfileParams.Config, BaseRunConfiguration.Config):
633
- @staticmethod
634
- def schema_extra(schema: Dict[str, Any]):
635
- ProfileParams.Config.schema_extra(schema)
636
- BaseRunConfiguration.Config.schema_extra(schema)
637
-
638
656
  @validator("entrypoint")
639
657
  def validate_entrypoint(cls, v: Optional[str]) -> Optional[str]:
640
658
  if v is not None:
@@ -646,20 +664,38 @@ class TaskConfigurationParams(CoreModel):
646
664
  nodes: Annotated[int, Field(description="Number of nodes", ge=1)] = 1
647
665
 
648
666
 
667
+ class TaskConfigurationConfig(
668
+ ProfileParamsConfig,
669
+ BaseRunConfigurationConfig,
670
+ ):
671
+ @staticmethod
672
+ def schema_extra(schema: Dict[str, Any]):
673
+ ProfileParamsConfig.schema_extra(schema)
674
+ BaseRunConfigurationConfig.schema_extra(schema)
675
+
676
+
649
677
  class TaskConfiguration(
650
678
  ProfileParams,
651
679
  BaseRunConfiguration,
652
680
  ConfigurationWithCommandsParams,
653
681
  ConfigurationWithPortsParams,
654
682
  TaskConfigurationParams,
683
+ generate_dual_core_model(TaskConfigurationConfig),
655
684
  ):
656
685
  type: Literal["task"] = "task"
657
686
 
658
- class Config(ProfileParams.Config, BaseRunConfiguration.Config):
659
- @staticmethod
660
- def schema_extra(schema: Dict[str, Any]):
661
- ProfileParams.Config.schema_extra(schema)
662
- BaseRunConfiguration.Config.schema_extra(schema)
687
+
688
+ class ServiceConfigurationParamsConfig(CoreConfig):
689
+ @staticmethod
690
+ def schema_extra(schema: Dict[str, Any]):
691
+ add_extra_schema_types(
692
+ schema["properties"]["replicas"],
693
+ extra_types=[{"type": "integer"}, {"type": "string"}],
694
+ )
695
+ add_extra_schema_types(
696
+ schema["properties"]["model"],
697
+ extra_types=[{"type": "string"}],
698
+ )
663
699
 
664
700
 
665
701
  class ServiceConfigurationParams(CoreModel):
@@ -719,18 +755,6 @@ class ServiceConfigurationParams(CoreModel):
719
755
  Field(description="List of probes used to determine job health"),
720
756
  ] = []
721
757
 
722
- class Config(CoreModel.Config):
723
- @staticmethod
724
- def schema_extra(schema: Dict[str, Any]):
725
- add_extra_schema_types(
726
- schema["properties"]["replicas"],
727
- extra_types=[{"type": "integer"}, {"type": "string"}],
728
- )
729
- add_extra_schema_types(
730
- schema["properties"]["model"],
731
- extra_types=[{"type": "string"}],
732
- )
733
-
734
758
  @validator("port")
735
759
  def convert_port(cls, v) -> PortMapping:
736
760
  if isinstance(v, int):
@@ -797,25 +821,27 @@ class ServiceConfigurationParams(CoreModel):
797
821
  return v
798
822
 
799
823
 
824
+ class ServiceConfigurationConfig(
825
+ ProfileParamsConfig,
826
+ BaseRunConfigurationConfig,
827
+ ServiceConfigurationParamsConfig,
828
+ ):
829
+ @staticmethod
830
+ def schema_extra(schema: Dict[str, Any]):
831
+ ProfileParamsConfig.schema_extra(schema)
832
+ BaseRunConfigurationConfig.schema_extra(schema)
833
+ ServiceConfigurationParamsConfig.schema_extra(schema)
834
+
835
+
800
836
  class ServiceConfiguration(
801
837
  ProfileParams,
802
838
  BaseRunConfiguration,
803
839
  ConfigurationWithCommandsParams,
804
840
  ServiceConfigurationParams,
841
+ generate_dual_core_model(ServiceConfigurationConfig),
805
842
  ):
806
843
  type: Literal["service"] = "service"
807
844
 
808
- class Config(
809
- ProfileParams.Config,
810
- BaseRunConfiguration.Config,
811
- ServiceConfigurationParams.Config,
812
- ):
813
- @staticmethod
814
- def schema_extra(schema: Dict[str, Any]):
815
- ProfileParams.Config.schema_extra(schema)
816
- BaseRunConfiguration.Config.schema_extra(schema)
817
- ServiceConfigurationParams.Config.schema_extra(schema)
818
-
819
845
 
820
846
  AnyRunConfiguration = Union[DevEnvironmentConfiguration, TaskConfiguration, ServiceConfiguration]
821
847
 
@@ -876,7 +902,7 @@ class DstackConfiguration(CoreModel):
876
902
  Field(discriminator="type"),
877
903
  ]
878
904
 
879
- class Config(CoreModel.Config):
905
+ class Config(CoreConfig):
880
906
  json_loads = orjson.loads
881
907
  json_dumps = pydantic_orjson_dumps_with_indent
882
908
 
@@ -2,13 +2,18 @@ import ipaddress
2
2
  import uuid
3
3
  from datetime import datetime
4
4
  from enum import Enum
5
- from typing import Any, Dict, List, Optional, Type, Union
5
+ from typing import Any, Dict, List, Optional, Union
6
6
 
7
7
  from pydantic import Field, root_validator, validator
8
8
  from typing_extensions import Annotated, Literal
9
9
 
10
10
  from dstack._internal.core.models.backends.base import BackendType
11
- from dstack._internal.core.models.common import ApplyAction, CoreModel
11
+ from dstack._internal.core.models.common import (
12
+ ApplyAction,
13
+ CoreConfig,
14
+ CoreModel,
15
+ generate_dual_core_model,
16
+ )
12
17
  from dstack._internal.core.models.envs import Env
13
18
  from dstack._internal.core.models.instances import Instance, InstanceOfferWithAvailability, SSHKey
14
19
  from dstack._internal.core.models.profiles import (
@@ -202,6 +207,21 @@ class FleetNodesSpec(CoreModel):
202
207
  return values
203
208
 
204
209
 
210
+ class InstanceGroupParamsConfig(CoreConfig):
211
+ @staticmethod
212
+ def schema_extra(schema: Dict[str, Any]):
213
+ del schema["properties"]["termination_policy"]
214
+ del schema["properties"]["termination_idle_time"]
215
+ add_extra_schema_types(
216
+ schema["properties"]["nodes"],
217
+ extra_types=[{"type": "integer"}, {"type": "string"}],
218
+ )
219
+ add_extra_schema_types(
220
+ schema["properties"]["idle_duration"],
221
+ extra_types=[{"type": "string"}],
222
+ )
223
+
224
+
205
225
  class InstanceGroupParams(CoreModel):
206
226
  env: Annotated[
207
227
  Env,
@@ -297,20 +317,6 @@ class InstanceGroupParams(CoreModel):
297
317
  termination_policy: Annotated[Optional[TerminationPolicy], Field(exclude=True)] = None
298
318
  termination_idle_time: Annotated[Optional[Union[str, int]], Field(exclude=True)] = None
299
319
 
300
- class Config(CoreModel.Config):
301
- @staticmethod
302
- def schema_extra(schema: Dict[str, Any], model: Type):
303
- del schema["properties"]["termination_policy"]
304
- del schema["properties"]["termination_idle_time"]
305
- add_extra_schema_types(
306
- schema["properties"]["nodes"],
307
- extra_types=[{"type": "integer"}, {"type": "string"}],
308
- )
309
- add_extra_schema_types(
310
- schema["properties"]["idle_duration"],
311
- extra_types=[{"type": "string"}],
312
- )
313
-
314
320
  @validator("nodes", pre=True)
315
321
  def parse_nodes(cls, v: Optional[Union[dict, str]]) -> Optional[dict]:
316
322
  if isinstance(v, str) and ".." in v:
@@ -331,7 +337,17 @@ class FleetProps(CoreModel):
331
337
  name: Annotated[Optional[str], Field(description="The fleet name")] = None
332
338
 
333
339
 
334
- class FleetConfiguration(InstanceGroupParams, FleetProps):
340
+ class FleetConfigurationConfig(InstanceGroupParamsConfig):
341
+ @staticmethod
342
+ def schema_extra(schema: Dict[str, Any]):
343
+ InstanceGroupParamsConfig.schema_extra(schema)
344
+
345
+
346
+ class FleetConfiguration(
347
+ InstanceGroupParams,
348
+ FleetProps,
349
+ generate_dual_core_model(FleetConfigurationConfig),
350
+ ):
335
351
  tags: Annotated[
336
352
  Optional[Dict[str, str]],
337
353
  Field(
@@ -346,7 +362,14 @@ class FleetConfiguration(InstanceGroupParams, FleetProps):
346
362
  _validate_tags = validator("tags", pre=True, allow_reuse=True)(tags_validator)
347
363
 
348
364
 
349
- class FleetSpec(CoreModel):
365
+ class FleetSpecConfig(CoreConfig):
366
+ @staticmethod
367
+ def schema_extra(schema: Dict[str, Any]):
368
+ prop = schema.get("properties", {})
369
+ prop.pop("merged_profile", None)
370
+
371
+
372
+ class FleetSpec(generate_dual_core_model(FleetSpecConfig)):
350
373
  configuration: FleetConfiguration
351
374
  configuration_path: Optional[str] = None
352
375
  profile: Profile
@@ -356,12 +379,6 @@ class FleetSpec(CoreModel):
356
379
  # TODO: make merged_profile a computed field after migrating to pydanticV2
357
380
  merged_profile: Annotated[Profile, Field(exclude=True)] = None
358
381
 
359
- class Config(CoreModel.Config):
360
- @staticmethod
361
- def schema_extra(schema: Dict[str, Any], model: Type) -> None:
362
- prop = schema.get("properties", {})
363
- prop.pop("merged_profile", None)
364
-
365
382
  @root_validator
366
383
  def _merged_profile(cls, values) -> Dict:
367
384
  try:
@@ -7,7 +7,10 @@ import gpuhunt
7
7
  from pydantic import root_validator
8
8
 
9
9
  from dstack._internal.core.models.backends.base import BackendType
10
- from dstack._internal.core.models.common import CoreModel
10
+ from dstack._internal.core.models.common import (
11
+ CoreModel,
12
+ FrozenCoreModel,
13
+ )
11
14
  from dstack._internal.core.models.envs import Env
12
15
  from dstack._internal.core.models.health import HealthStatus
13
16
  from dstack._internal.core.models.volumes import Volume
@@ -117,14 +120,11 @@ class InstanceType(CoreModel):
117
120
  resources: Resources
118
121
 
119
122
 
120
- class SSHConnectionParams(CoreModel):
123
+ class SSHConnectionParams(FrozenCoreModel):
121
124
  hostname: str
122
125
  username: str
123
126
  port: int
124
127
 
125
- class Config(CoreModel.Config):
126
- frozen = True
127
-
128
128
 
129
129
  class SSHKey(CoreModel):
130
130
  public: str
@@ -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