dstack 0.18.44__py3-none-any.whl → 0.19.0__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 (267) hide show
  1. dstack/_internal/cli/commands/gateway.py +15 -3
  2. dstack/_internal/cli/commands/logs.py +0 -22
  3. dstack/_internal/cli/commands/stats.py +8 -17
  4. dstack/_internal/cli/main.py +1 -5
  5. dstack/_internal/cli/services/configurators/fleet.py +4 -39
  6. dstack/_internal/cli/services/configurators/run.py +22 -21
  7. dstack/_internal/cli/services/profile.py +34 -83
  8. dstack/_internal/cli/utils/gateway.py +1 -1
  9. dstack/_internal/core/backends/__init__.py +56 -39
  10. dstack/_internal/core/backends/aws/__init__.py +0 -25
  11. dstack/_internal/core/backends/aws/auth.py +1 -10
  12. dstack/_internal/core/backends/aws/backend.py +26 -0
  13. dstack/_internal/core/backends/aws/compute.py +20 -45
  14. dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
  15. dstack/_internal/core/backends/aws/models.py +135 -0
  16. dstack/_internal/core/backends/aws/resources.py +1 -1
  17. dstack/_internal/core/backends/azure/__init__.py +0 -20
  18. dstack/_internal/core/backends/azure/auth.py +2 -11
  19. dstack/_internal/core/backends/azure/backend.py +21 -0
  20. dstack/_internal/core/backends/azure/compute.py +13 -27
  21. dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
  22. dstack/_internal/core/backends/azure/models.py +89 -0
  23. dstack/_internal/core/backends/base/__init__.py +0 -12
  24. dstack/_internal/core/backends/base/backend.py +18 -0
  25. dstack/_internal/core/backends/base/compute.py +153 -33
  26. dstack/_internal/core/backends/base/configurator.py +105 -0
  27. dstack/_internal/core/backends/base/models.py +14 -0
  28. dstack/_internal/core/backends/configurators.py +138 -0
  29. dstack/_internal/core/backends/cudo/__init__.py +0 -15
  30. dstack/_internal/core/backends/cudo/backend.py +16 -0
  31. dstack/_internal/core/backends/cudo/compute.py +8 -26
  32. dstack/_internal/core/backends/cudo/configurator.py +72 -0
  33. dstack/_internal/core/backends/cudo/models.py +37 -0
  34. dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
  35. dstack/_internal/core/backends/datacrunch/backend.py +16 -0
  36. dstack/_internal/core/backends/datacrunch/compute.py +8 -25
  37. dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
  38. dstack/_internal/core/backends/datacrunch/models.py +38 -0
  39. dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
  40. dstack/_internal/core/backends/gcp/__init__.py +0 -16
  41. dstack/_internal/core/backends/gcp/auth.py +2 -11
  42. dstack/_internal/core/backends/gcp/backend.py +17 -0
  43. dstack/_internal/core/backends/gcp/compute.py +13 -43
  44. dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
  45. dstack/_internal/core/backends/gcp/models.py +125 -0
  46. dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
  47. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  48. dstack/_internal/core/backends/kubernetes/compute.py +16 -5
  49. dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
  50. dstack/_internal/core/backends/kubernetes/models.py +72 -0
  51. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
  52. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  53. dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
  54. dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
  55. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  56. dstack/_internal/core/backends/local/__init__.py +0 -13
  57. dstack/_internal/core/backends/local/backend.py +14 -0
  58. dstack/_internal/core/backends/local/compute.py +16 -2
  59. dstack/_internal/core/backends/models.py +128 -0
  60. dstack/_internal/core/backends/oci/__init__.py +0 -15
  61. dstack/_internal/core/backends/oci/auth.py +1 -5
  62. dstack/_internal/core/backends/oci/backend.py +16 -0
  63. dstack/_internal/core/backends/oci/compute.py +9 -23
  64. dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
  65. dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
  66. dstack/_internal/core/backends/oci/region.py +1 -1
  67. dstack/_internal/core/backends/runpod/__init__.py +0 -15
  68. dstack/_internal/core/backends/runpod/backend.py +16 -0
  69. dstack/_internal/core/backends/runpod/compute.py +7 -3
  70. dstack/_internal/core/backends/runpod/configurator.py +59 -0
  71. dstack/_internal/core/backends/runpod/models.py +54 -0
  72. dstack/_internal/core/backends/template/__init__.py +0 -0
  73. dstack/_internal/core/backends/tensordock/__init__.py +0 -15
  74. dstack/_internal/core/backends/tensordock/backend.py +16 -0
  75. dstack/_internal/core/backends/tensordock/compute.py +8 -27
  76. dstack/_internal/core/backends/tensordock/configurator.py +68 -0
  77. dstack/_internal/core/backends/tensordock/models.py +38 -0
  78. dstack/_internal/core/backends/vastai/__init__.py +0 -15
  79. dstack/_internal/core/backends/vastai/backend.py +16 -0
  80. dstack/_internal/core/backends/vastai/compute.py +2 -2
  81. dstack/_internal/core/backends/vastai/configurator.py +66 -0
  82. dstack/_internal/core/backends/vastai/models.py +37 -0
  83. dstack/_internal/core/backends/vultr/__init__.py +0 -15
  84. dstack/_internal/core/backends/vultr/backend.py +16 -0
  85. dstack/_internal/core/backends/vultr/compute.py +10 -24
  86. dstack/_internal/core/backends/vultr/configurator.py +64 -0
  87. dstack/_internal/core/backends/vultr/models.py +34 -0
  88. dstack/_internal/core/models/backends/__init__.py +0 -184
  89. dstack/_internal/core/models/backends/base.py +0 -19
  90. dstack/_internal/core/models/configurations.py +20 -15
  91. dstack/_internal/core/models/envs.py +4 -3
  92. dstack/_internal/core/models/fleets.py +17 -22
  93. dstack/_internal/core/models/gateways.py +3 -3
  94. dstack/_internal/core/models/instances.py +24 -0
  95. dstack/_internal/core/models/profiles.py +41 -46
  96. dstack/_internal/core/models/projects.py +1 -1
  97. dstack/_internal/core/models/repos/base.py +0 -5
  98. dstack/_internal/core/models/repos/local.py +3 -3
  99. dstack/_internal/core/models/repos/remote.py +26 -12
  100. dstack/_internal/core/models/repos/virtual.py +1 -1
  101. dstack/_internal/core/models/resources.py +45 -76
  102. dstack/_internal/core/models/runs.py +17 -19
  103. dstack/_internal/core/models/volumes.py +1 -3
  104. dstack/_internal/core/services/profiles.py +7 -16
  105. dstack/_internal/core/services/repos.py +0 -4
  106. dstack/_internal/server/app.py +0 -3
  107. dstack/_internal/server/background/tasks/process_gateways.py +4 -8
  108. dstack/_internal/server/background/tasks/process_instances.py +14 -9
  109. dstack/_internal/server/background/tasks/process_metrics.py +1 -1
  110. dstack/_internal/server/background/tasks/process_placement_groups.py +4 -1
  111. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +1 -1
  112. dstack/_internal/server/background/tasks/process_running_jobs.py +14 -5
  113. dstack/_internal/server/background/tasks/process_submitted_jobs.py +16 -37
  114. dstack/_internal/server/background/tasks/process_volumes.py +5 -2
  115. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  116. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  117. dstack/_internal/server/models.py +48 -9
  118. dstack/_internal/server/routers/backends.py +14 -23
  119. dstack/_internal/server/routers/instances.py +3 -4
  120. dstack/_internal/server/routers/metrics.py +10 -8
  121. dstack/_internal/server/routers/prometheus.py +1 -1
  122. dstack/_internal/server/routers/repos.py +1 -2
  123. dstack/_internal/server/routers/runs.py +13 -59
  124. dstack/_internal/server/schemas/gateways.py +14 -23
  125. dstack/_internal/server/schemas/projects.py +7 -2
  126. dstack/_internal/server/schemas/repos.py +2 -38
  127. dstack/_internal/server/schemas/runner.py +1 -0
  128. dstack/_internal/server/schemas/runs.py +1 -24
  129. dstack/_internal/server/services/backends/__init__.py +85 -158
  130. dstack/_internal/server/services/config.py +52 -576
  131. dstack/_internal/server/services/fleets.py +8 -103
  132. dstack/_internal/server/services/gateways/__init__.py +12 -4
  133. dstack/_internal/server/services/{pools.py → instances.py} +22 -329
  134. dstack/_internal/server/services/jobs/__init__.py +9 -6
  135. dstack/_internal/server/services/jobs/configurators/base.py +16 -0
  136. dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
  137. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  138. dstack/_internal/server/services/metrics.py +39 -13
  139. dstack/_internal/server/services/offers.py +1 -1
  140. dstack/_internal/server/services/projects.py +23 -14
  141. dstack/_internal/server/services/prometheus.py +176 -18
  142. dstack/_internal/server/services/runs.py +24 -16
  143. dstack/_internal/server/services/volumes.py +8 -4
  144. dstack/_internal/server/statics/index.html +1 -1
  145. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js → main-4a0fe83e84574654e397.js} +18 -14
  146. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js.map → main-4a0fe83e84574654e397.js.map} +1 -1
  147. dstack/_internal/server/testing/common.py +58 -32
  148. dstack/_internal/utils/json_schema.py +6 -0
  149. dstack/_internal/utils/ssh.py +2 -1
  150. dstack/api/__init__.py +4 -0
  151. dstack/api/_public/__init__.py +16 -20
  152. dstack/api/_public/backends.py +1 -1
  153. dstack/api/_public/repos.py +36 -36
  154. dstack/api/_public/runs.py +167 -83
  155. dstack/api/server/__init__.py +11 -13
  156. dstack/api/server/_backends.py +12 -16
  157. dstack/api/server/_fleets.py +15 -57
  158. dstack/api/server/_gateways.py +3 -14
  159. dstack/api/server/_repos.py +1 -4
  160. dstack/api/server/_runs.py +21 -100
  161. dstack/api/server/_volumes.py +10 -5
  162. dstack/version.py +1 -1
  163. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/METADATA +1 -1
  164. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/RECORD +218 -204
  165. tests/_internal/cli/services/configurators/test_profile.py +6 -6
  166. tests/_internal/core/backends/aws/test_configurator.py +35 -0
  167. tests/_internal/core/backends/aws/test_resources.py +1 -1
  168. tests/_internal/core/backends/azure/test_configurator.py +61 -0
  169. tests/_internal/core/backends/cudo/__init__.py +0 -0
  170. tests/_internal/core/backends/cudo/test_configurator.py +37 -0
  171. tests/_internal/core/backends/datacrunch/__init__.py +0 -0
  172. tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
  173. tests/_internal/core/backends/gcp/test_configurator.py +42 -0
  174. tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
  175. tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
  176. tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
  177. tests/_internal/core/backends/oci/test_configurator.py +55 -0
  178. tests/_internal/core/backends/runpod/__init__.py +0 -0
  179. tests/_internal/core/backends/runpod/test_configurator.py +33 -0
  180. tests/_internal/core/backends/tensordock/__init__.py +0 -0
  181. tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
  182. tests/_internal/core/backends/vastai/__init__.py +0 -0
  183. tests/_internal/core/backends/vastai/test_configurator.py +33 -0
  184. tests/_internal/core/backends/vultr/__init__.py +0 -0
  185. tests/_internal/core/backends/vultr/test_configurator.py +33 -0
  186. tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
  187. tests/_internal/server/background/tasks/test_process_instances.py +49 -48
  188. tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
  189. tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
  190. tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +0 -3
  191. tests/_internal/server/background/tasks/test_process_running_jobs.py +0 -21
  192. tests/_internal/server/background/tasks/test_process_runs.py +8 -22
  193. tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
  194. tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
  195. tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
  196. tests/_internal/server/routers/test_backends.py +6 -764
  197. tests/_internal/server/routers/test_fleets.py +0 -26
  198. tests/_internal/server/routers/test_gateways.py +27 -3
  199. tests/_internal/server/routers/test_instances.py +0 -10
  200. tests/_internal/server/routers/test_metrics.py +27 -0
  201. tests/_internal/server/routers/test_projects.py +56 -0
  202. tests/_internal/server/routers/test_prometheus.py +116 -27
  203. tests/_internal/server/routers/test_repos.py +0 -15
  204. tests/_internal/server/routers/test_runs.py +4 -219
  205. tests/_internal/server/routers/test_volumes.py +2 -3
  206. tests/_internal/server/services/backends/__init__.py +0 -0
  207. tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
  208. tests/_internal/server/services/test_config.py +7 -4
  209. tests/_internal/server/services/test_fleets.py +1 -4
  210. tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
  211. tests/_internal/server/services/test_metrics.py +9 -5
  212. tests/_internal/server/services/test_repos.py +1 -14
  213. tests/_internal/server/services/test_runs.py +0 -4
  214. dstack/_internal/cli/commands/pool.py +0 -581
  215. dstack/_internal/cli/commands/run.py +0 -75
  216. dstack/_internal/core/backends/aws/config.py +0 -18
  217. dstack/_internal/core/backends/azure/config.py +0 -12
  218. dstack/_internal/core/backends/base/config.py +0 -5
  219. dstack/_internal/core/backends/cudo/config.py +0 -9
  220. dstack/_internal/core/backends/datacrunch/config.py +0 -9
  221. dstack/_internal/core/backends/gcp/config.py +0 -22
  222. dstack/_internal/core/backends/kubernetes/config.py +0 -6
  223. dstack/_internal/core/backends/lambdalabs/config.py +0 -9
  224. dstack/_internal/core/backends/nebius/__init__.py +0 -15
  225. dstack/_internal/core/backends/nebius/api_client.py +0 -319
  226. dstack/_internal/core/backends/nebius/compute.py +0 -220
  227. dstack/_internal/core/backends/nebius/config.py +0 -6
  228. dstack/_internal/core/backends/nebius/types.py +0 -37
  229. dstack/_internal/core/backends/oci/config.py +0 -6
  230. dstack/_internal/core/backends/runpod/config.py +0 -17
  231. dstack/_internal/core/backends/tensordock/config.py +0 -9
  232. dstack/_internal/core/backends/vastai/config.py +0 -6
  233. dstack/_internal/core/backends/vultr/config.py +0 -9
  234. dstack/_internal/core/models/backends/aws.py +0 -86
  235. dstack/_internal/core/models/backends/azure.py +0 -68
  236. dstack/_internal/core/models/backends/cudo.py +0 -43
  237. dstack/_internal/core/models/backends/datacrunch.py +0 -44
  238. dstack/_internal/core/models/backends/gcp.py +0 -67
  239. dstack/_internal/core/models/backends/kubernetes.py +0 -40
  240. dstack/_internal/core/models/backends/lambdalabs.py +0 -43
  241. dstack/_internal/core/models/backends/nebius.py +0 -54
  242. dstack/_internal/core/models/backends/runpod.py +0 -42
  243. dstack/_internal/core/models/backends/tensordock.py +0 -44
  244. dstack/_internal/core/models/backends/vastai.py +0 -43
  245. dstack/_internal/core/models/backends/vultr.py +0 -40
  246. dstack/_internal/core/models/pools.py +0 -43
  247. dstack/_internal/server/routers/pools.py +0 -142
  248. dstack/_internal/server/schemas/pools.py +0 -38
  249. dstack/_internal/server/services/backends/configurators/base.py +0 -72
  250. dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
  251. dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
  252. dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
  253. dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
  254. dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
  255. dstack/_internal/server/services/backends/configurators/runpod.py +0 -67
  256. dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
  257. dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
  258. dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
  259. dstack/api/_public/pools.py +0 -41
  260. dstack/api/_public/resources.py +0 -105
  261. dstack/api/server/_pools.py +0 -63
  262. tests/_internal/server/routers/test_pools.py +0 -612
  263. /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
  264. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/LICENSE.md +0 -0
  265. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/WHEEL +0 -0
  266. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/entry_points.txt +0 -0
  267. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/top_level.txt +0 -0
@@ -16,9 +16,7 @@ import dstack.api as api
16
16
  from dstack._internal.core.consts import DSTACK_RUNNER_HTTP_PORT, DSTACK_RUNNER_SSH_PORT
17
17
  from dstack._internal.core.errors import ClientError, ConfigurationError, ResourceNotExistsError
18
18
  from dstack._internal.core.models.backends.base import BackendType
19
- from dstack._internal.core.models.common import ApplyAction
20
19
  from dstack._internal.core.models.configurations import AnyRunConfiguration, PortMapping
21
- from dstack._internal.core.models.pools import Instance
22
20
  from dstack._internal.core.models.profiles import (
23
21
  CreationPolicy,
24
22
  Profile,
@@ -28,12 +26,11 @@ from dstack._internal.core.models.profiles import (
28
26
  UtilizationPolicy,
29
27
  )
30
28
  from dstack._internal.core.models.repos.base import Repo
29
+ from dstack._internal.core.models.repos.virtual import VirtualRepo
31
30
  from dstack._internal.core.models.resources import ResourcesSpec
32
31
  from dstack._internal.core.models.runs import (
33
32
  Job,
34
33
  JobSpec,
35
- PoolInstanceOffers,
36
- Requirements,
37
34
  RunPlan,
38
35
  RunSpec,
39
36
  RunStatus,
@@ -191,14 +188,14 @@ class Run(ABC):
191
188
  job_num: int = 0,
192
189
  ) -> Iterable[bytes]:
193
190
  """
194
- Iterate through run's log messages
191
+ Iterate through run's log messages.
195
192
 
196
193
  Args:
197
- start_time: minimal log timestamp
198
- diagnose: return runner logs if `True`
194
+ start_time: Minimal log timestamp.
195
+ diagnose: Return runner logs if `True`.
199
196
 
200
197
  Yields:
201
- log messages
198
+ Log messages.
202
199
  """
203
200
  if diagnose is False and self._ssh_attach is not None:
204
201
  yield from self._attached_logs()
@@ -228,17 +225,17 @@ class Run(ABC):
228
225
 
229
226
  def refresh(self):
230
227
  """
231
- Get up-to-date run info
228
+ Get up-to-date run info.
232
229
  """
233
230
  self._run = self._api_client.runs.get(self._project, self._run.run_spec.run_name)
234
231
  logger.debug("Refreshed run %s: %s", self.name, self.status)
235
232
 
236
233
  def stop(self, abort: bool = False):
237
234
  """
238
- Terminate the instance and detach
235
+ Terminate the instance and detach.
239
236
 
240
237
  Args:
241
- abort: gracefully stop the run if `False`
238
+ abort: Gracefully stop the run if `False`.
242
239
  """
243
240
  self._api_client.runs.stop(self._project, [self.name], abort)
244
241
  logger.debug("%s run %s", "Aborted" if abort else "Stopped", self.name)
@@ -256,7 +253,7 @@ class Run(ABC):
256
253
  Establish an SSH tunnel to the instance and update SSH config
257
254
 
258
255
  Args:
259
- ssh_identity_file: SSH keypair to access instances
256
+ ssh_identity_file: SSH keypair to access instances.
260
257
 
261
258
  Raises:
262
259
  dstack.api.PortUsedError: If ports are in use or the run is attached by another process.
@@ -393,7 +390,7 @@ class ServiceModel:
393
390
 
394
391
  class RunCollection:
395
392
  """
396
- Operations with runs
393
+ Operations with runs.
397
394
  """
398
395
 
399
396
  def __init__(
@@ -406,6 +403,122 @@ class RunCollection:
406
403
  self._project = project
407
404
  self._client = client
408
405
 
406
+ def get_run_plan(
407
+ self,
408
+ configuration: AnyRunConfiguration,
409
+ repo: Optional[Repo] = None,
410
+ profile: Optional[Profile] = None,
411
+ configuration_path: Optional[str] = None,
412
+ ) -> RunPlan:
413
+ """
414
+ Get a run plan.
415
+ Use this method to see the run plan before applying the cofiguration.
416
+
417
+ Args:
418
+ configuration (Union[Task, Service, DevEnvironment]): The run configuration.
419
+ repo (Union[LocalRepo, RemoteRepo, VirtualRepo, None]):
420
+ The repo to use for the run. Pass `None` if repo is not needed.
421
+ profile: The profile to use for the run.
422
+ configuration_path: The path to the configuration file. Omit if the configuration is not loaded from a file.
423
+
424
+ Returns:
425
+ Run plan.
426
+ """
427
+ if repo is None:
428
+ repo = VirtualRepo()
429
+
430
+ run_spec = RunSpec(
431
+ run_name=configuration.name,
432
+ repo_id=repo.repo_id,
433
+ repo_data=repo.run_repo_data,
434
+ repo_code_hash=None, # `apply_plan` will fill it
435
+ working_dir=configuration.working_dir,
436
+ configuration_path=configuration_path,
437
+ configuration=configuration,
438
+ profile=profile,
439
+ ssh_key_pub=Path(self._client.ssh_identity_file + ".pub").read_text().strip(),
440
+ )
441
+ logger.debug("Getting run plan")
442
+ run_plan = self._api_client.runs.get_plan(self._project, run_spec)
443
+ return run_plan
444
+
445
+ def apply_plan(
446
+ self,
447
+ run_plan: RunPlan,
448
+ repo: Optional[Repo] = None,
449
+ reserve_ports: bool = True,
450
+ ) -> Run:
451
+ """
452
+ Apply the run plan.
453
+ Use this method to apply run plans returned by `get_run_plan`.
454
+
455
+ Args:
456
+ run_plan: The result of `get_run_plan` call.
457
+ repo (Union[LocalRepo, RemoteRepo, VirtualRepo, None]):
458
+ The repo to use for the run. Should be the same repo that is passed to `get_run_plan`.
459
+ reserve_ports: Reserve local ports before applying. Use if you'll attach to the run.
460
+
461
+ Returns:
462
+ Submitted run.
463
+ """
464
+ ports_lock = None
465
+ if reserve_ports:
466
+ # TODO handle multiple jobs
467
+ ports_lock = _reserve_ports(run_plan.job_plans[0].job_spec)
468
+
469
+ if repo is None:
470
+ repo = VirtualRepo()
471
+ else:
472
+ # Do not upload the diff without a repo (a default virtual repo)
473
+ # since upload_code() requires a repo to be initialized.
474
+ with tempfile.TemporaryFile("w+b") as fp:
475
+ run_plan.run_spec.repo_code_hash = repo.write_code_file(fp)
476
+ fp.seek(0)
477
+ self._api_client.repos.upload_code(
478
+ project_name=self._project,
479
+ repo_id=repo.repo_id,
480
+ code_hash=run_plan.run_spec.repo_code_hash,
481
+ fp=fp,
482
+ )
483
+ run = self._api_client.runs.apply_plan(self._project, run_plan)
484
+ return self._model_to_submitted_run(run, ports_lock)
485
+
486
+ def apply_configuration(
487
+ self,
488
+ configuration: AnyRunConfiguration,
489
+ repo: Optional[Repo] = None,
490
+ profile: Optional[Profile] = None,
491
+ configuration_path: Optional[str] = None,
492
+ reserve_ports: bool = True,
493
+ ) -> Run:
494
+ """
495
+ Apply the run configuration.
496
+ Use this method to apply configurations without getting a run plan first.
497
+
498
+ Args:
499
+ configuration (Union[Task, Service, DevEnvironment]): The run configuration.
500
+ repo (Union[LocalRepo, RemoteRepo, VirtualRepo, None]):
501
+ The repo to use for the run. Pass `None` if repo is not needed.
502
+ profile: The profile to use for the run.
503
+ configuration_path: The path to the configuration file. Omit if the configuration is not loaded from a file.
504
+ reserve_ports: Reserve local ports before applying. Use if you'll attach to the run.
505
+
506
+ Returns:
507
+ Submitted run.
508
+ """
509
+ run_plan = self.get_run_plan(
510
+ configuration=configuration,
511
+ repo=repo,
512
+ profile=profile,
513
+ configuration_path=configuration_path,
514
+ )
515
+ run = self.apply_plan(
516
+ run_plan=run_plan,
517
+ repo=repo,
518
+ reserve_ports=reserve_ports,
519
+ )
520
+ return run
521
+
409
522
  def submit(
410
523
  self,
411
524
  configuration: AnyRunConfiguration,
@@ -423,31 +536,30 @@ class RunCollection:
423
536
  run_name: Optional[str] = None,
424
537
  reserve_ports: bool = True,
425
538
  ) -> Run:
426
- """
427
- Submit a run
539
+ # """
540
+ # Submit a run
428
541
 
429
- Args:
430
- configuration (Union[Task, Service]): A run configuration.
431
- configuration_path: The path to the configuration file, relative to the root directory of the repo.
432
- repo (Union[LocalRepo, RemoteRepo, VirtualRepo]): A repo to mount to the run.
433
- backends: A list of allowed backend for provisioning.
434
- regions: A list of cloud regions for provisioning.
435
- resources: The requirements to run the configuration. Overrides the configuration's resources.
436
- spot_policy: A spot policy for provisioning.
437
- retry_policy (RetryPolicy): A retry policy.
438
- max_duration: The max instance running duration in seconds.
439
- max_price: The max instance price in dollars per hour for provisioning.
440
- working_dir: A working directory relative to the repo root directory
441
- run_name: A desired name of the run. Must be unique in the project. If not specified, a random name is assigned.
442
- reserve_ports: Whether local ports should be reserved in advance.
542
+ # Args:
543
+ # configuration (Union[Task, Service]): A run configuration.
544
+ # configuration_path: The path to the configuration file, relative to the root directory of the repo.
545
+ # repo (Union[LocalRepo, RemoteRepo, VirtualRepo]): A repo to mount to the run.
546
+ # backends: A list of allowed backend for provisioning.
547
+ # regions: A list of cloud regions for provisioning.
548
+ # resources: The requirements to run the configuration. Overrides the configuration's resources.
549
+ # spot_policy: A spot policy for provisioning.
550
+ # retry_policy (RetryPolicy): A retry policy.
551
+ # max_duration: The max instance running duration in seconds.
552
+ # max_price: The max instance price in dollars per hour for provisioning.
553
+ # working_dir: A working directory relative to the repo root directory
554
+ # run_name: A desired name of the run. Must be unique in the project. If not specified, a random name is assigned.
555
+ # reserve_ports: Whether local ports should be reserved in advance.
443
556
 
444
- Returns:
445
- submitted run
446
- """
557
+ # Returns:
558
+ # Submitted run.
559
+ # """
560
+ logger.warning("The submit() method is deprecated in favor of apply_configuration().")
447
561
  if repo is None:
448
- repo = configuration.get_repo()
449
- if repo is None:
450
- raise ConfigurationError("Repo is required for this type of configuration")
562
+ repo = VirtualRepo()
451
563
  # TODO: Add Git credentials to RemoteRepo and if they are set, pass them here to RepoCollection.init
452
564
  self._client.repos.init(repo)
453
565
 
@@ -468,18 +580,14 @@ class RunCollection:
468
580
  )
469
581
  return self.exec_plan(run_plan, repo, reserve_ports=reserve_ports)
470
582
 
471
- def get_offers(self, profile: Profile, requirements: Requirements) -> PoolInstanceOffers:
472
- return self._api_client.runs.get_offers(self._project, profile, requirements)
473
-
474
- def create_instance(self, profile: Profile, requirements: Requirements) -> Instance:
475
- return self._api_client.runs.create_instance(self._project, profile, requirements)
476
-
477
- # TODO: [Andrey] I guess we need to drop profile-related fields (currently retry is not reflected there)
583
+ # Deprecated in favor of get_run_plan()
478
584
  def get_plan(
479
585
  self,
480
586
  configuration: AnyRunConfiguration,
481
587
  repo: Optional[Repo] = None,
482
588
  configuration_path: Optional[str] = None,
589
+ # Unused profile args are deprecated and removed but
590
+ # kept for signature backward compatibility.
483
591
  backends: Optional[List[BackendType]] = None,
484
592
  regions: Optional[List[str]] = None,
485
593
  instance_types: Optional[List[str]] = None,
@@ -506,11 +614,9 @@ class RunCollection:
506
614
  # Returns:
507
615
  # run plan
508
616
  # """
509
-
617
+ logger.warning("The get_plan() method is deprecated in favor of get_run_plan().")
510
618
  if repo is None:
511
- repo = configuration.get_repo()
512
- if repo is None:
513
- raise ConfigurationError("Repo is required for this type of configuration")
619
+ repo = VirtualRepo()
514
620
 
515
621
  if working_dir is None:
516
622
  working_dir = "."
@@ -536,17 +642,12 @@ class RunCollection:
536
642
  reservation=reservation,
537
643
  spot_policy=spot_policy,
538
644
  retry=None,
539
- retry_policy=retry_policy,
540
645
  utilization_policy=utilization_policy,
541
646
  max_duration=max_duration,
542
647
  stop_duration=stop_duration,
543
648
  max_price=max_price,
544
- pool_name=pool_name,
545
- instance_name=instance_name,
546
649
  creation_policy=creation_policy,
547
650
  idle_duration=idle_duration,
548
- termination_policy=termination_policy,
549
- termination_idle_time=termination_policy_idle,
550
651
  )
551
652
  run_spec = RunSpec(
552
653
  run_name=run_name,
@@ -577,45 +678,28 @@ class RunCollection:
577
678
  reserve_ports: bool = True,
578
679
  ) -> Run:
579
680
  # """
580
- # Execute run plan
581
- #
681
+ # Execute the run plan.
682
+
582
683
  # Args:
583
- # run_plan: result of `get_plan` call
584
- # repo: repo to use for the run
585
- # reserve_ports: reserve local ports before submit
586
- #
684
+ # run_plan: Result of `get_run_plan` call.
685
+ # repo: Repo to use for the run.
686
+ # reserve_ports: Reserve local ports before submit.
687
+
587
688
  # Returns:
588
- # submitted run
689
+ # Submitted run.
589
690
  # """
590
- ports_lock = None
591
- if reserve_ports:
592
- # TODO handle multiple jobs
593
- ports_lock = _reserve_ports(run_plan.job_plans[0].job_spec)
594
-
595
- with tempfile.TemporaryFile("w+b") as fp:
596
- run_plan.run_spec.repo_code_hash = repo.write_code_file(fp)
597
- fp.seek(0)
598
- self._api_client.repos.upload_code(
599
- self._project, repo.repo_id, run_plan.run_spec.repo_code_hash, fp
600
- )
601
- # Calling submit when action is CREATE since apply_plan is not backward-compatible.
602
- # Otherwise, apply_plan can replace submit, i.e. it creates the run if it does not exist.
603
- # TODO: Remove in 0.19
604
- if run_plan.action == ApplyAction.UPDATE:
605
- run = self._api_client.runs.apply_plan(self._project, run_plan)
606
- else:
607
- run = self._api_client.runs.submit(self._project, run_plan.run_spec)
608
- return self._model_to_submitted_run(run, ports_lock)
691
+ logger.warning("The exec_plan() method is deprecated in favor of apply_plan().")
692
+ return self.apply_plan(run_plan=run_plan, repo=repo, reserve_ports=reserve_ports)
609
693
 
610
694
  def list(self, all: bool = False) -> List[Run]:
611
695
  """
612
- List runs
696
+ List runs.
613
697
 
614
698
  Args:
615
- all: show all runs (active and finished) if `True`
699
+ all: Show all runs (active and finished) if `True`.
616
700
 
617
701
  Returns:
618
- list of runs
702
+ List of runs.
619
703
  """
620
704
  # Return only one page of latest runs (<=100). Returning all the pages may be costly.
621
705
  # TODO: Consider introducing `since` filter with a reasonable default.
@@ -634,13 +718,13 @@ class RunCollection:
634
718
 
635
719
  def get(self, run_name: str) -> Optional[Run]:
636
720
  """
637
- Get run by run name
721
+ Get run by run name.
638
722
 
639
723
  Args:
640
- run_name: run name
724
+ run_name: Run name.
641
725
 
642
726
  Returns:
643
- The run or `None` if not found
727
+ The run or `None` if not found.
644
728
  """
645
729
  try:
646
730
  run = self._api_client.runs.get(self._project, run_name)
@@ -13,7 +13,6 @@ from dstack.api.server._fleets import FleetsAPIClient
13
13
  from dstack.api.server._gateways import GatewaysAPIClient
14
14
  from dstack.api.server._logs import LogsAPIClient
15
15
  from dstack.api.server._metrics import MetricsAPIClient
16
- from dstack.api.server._pools import PoolAPIClient
17
16
  from dstack.api.server._projects import ProjectsAPIClient
18
17
  from dstack.api.server._repos import ReposAPIClient
19
18
  from dstack.api.server._runs import RunsAPIClient
@@ -30,23 +29,26 @@ _RETRY_INTERVAL = 1
30
29
 
31
30
  class APIClient:
32
31
  """
33
- Low-level API client for interacting with dstack server. Implements all API endpoints
32
+ Low-level API client for interacting with the `dstack` server.
33
+ Supports all HTTP API endpoints.
34
34
 
35
35
  Attributes:
36
36
  users: operations with users
37
37
  projects: operations with projects
38
38
  backends: operations with backends
39
+ fleets: operations with fleets
39
40
  runs: operations with runs
41
+ metrics: operations with metrics
40
42
  logs: operations with logs
41
43
  gateways: operations with gateways
42
- pools: operations with pools
44
+ volumes: operations with volumes
43
45
  """
44
46
 
45
47
  def __init__(self, base_url: str, token: str):
46
48
  """
47
49
  Args:
48
- base_url: API endpoints prefix, e.g. `http://127.0.0.1:3000/`
49
- token: API token
50
+ base_url: The API endpoints prefix, e.g. `http://127.0.0.1:3000/`.
51
+ token: The API token.
50
52
  """
51
53
  self._base_url = base_url.rstrip("/")
52
54
  self._token = token
@@ -72,6 +74,10 @@ class APIClient:
72
74
  def backends(self) -> BackendsAPIClient:
73
75
  return BackendsAPIClient(self._request)
74
76
 
77
+ @property
78
+ def fleets(self) -> FleetsAPIClient:
79
+ return FleetsAPIClient(self._request)
80
+
75
81
  @property
76
82
  def repos(self) -> ReposAPIClient:
77
83
  return ReposAPIClient(self._request)
@@ -96,14 +102,6 @@ class APIClient:
96
102
  def gateways(self) -> GatewaysAPIClient:
97
103
  return GatewaysAPIClient(self._request)
98
104
 
99
- @property
100
- def pool(self) -> PoolAPIClient:
101
- return PoolAPIClient(self._request)
102
-
103
- @property
104
- def fleets(self) -> FleetsAPIClient:
105
- return FleetsAPIClient(self._request)
106
-
107
105
  @property
108
106
  def volumes(self) -> VolumesAPIClient:
109
107
  return VolumesAPIClient(self._request)
@@ -2,10 +2,8 @@ from typing import List
2
2
 
3
3
  from pydantic import parse_obj_as
4
4
 
5
- from dstack._internal.core.models.backends import (
6
- AnyConfigInfoWithCreds,
7
- AnyConfigInfoWithCredsPartial,
8
- AnyConfigValues,
5
+ from dstack._internal.core.backends.models import (
6
+ AnyBackendConfigWithCreds,
9
7
  )
10
8
  from dstack._internal.core.models.backends.base import BackendType
11
9
  from dstack._internal.server.schemas.backends import DeleteBackendsRequest
@@ -17,26 +15,24 @@ class BackendsAPIClient(APIClientGroup):
17
15
  resp = self._request("/api/backends/list_types")
18
16
  return parse_obj_as(List[BackendType], resp.json())
19
17
 
20
- def config_values(self, config: AnyConfigInfoWithCredsPartial) -> AnyConfigValues:
21
- resp = self._request("/api/backends/config_values", body=config.json())
22
- return parse_obj_as(AnyConfigValues, resp.json())
23
-
24
18
  def create(
25
- self, project_name: str, config: AnyConfigInfoWithCreds
26
- ) -> AnyConfigInfoWithCredsPartial:
19
+ self, project_name: str, config: AnyBackendConfigWithCreds
20
+ ) -> AnyBackendConfigWithCreds:
27
21
  resp = self._request(f"/api/project/{project_name}/backends/create", body=config.json())
28
- return parse_obj_as(AnyConfigInfoWithCredsPartial, resp.json())
22
+ return parse_obj_as(AnyBackendConfigWithCreds, resp.json())
29
23
 
30
24
  def update(
31
- self, project_name: str, config: AnyConfigInfoWithCreds
32
- ) -> AnyConfigInfoWithCredsPartial:
25
+ self, project_name: str, config: AnyBackendConfigWithCreds
26
+ ) -> AnyBackendConfigWithCreds:
33
27
  resp = self._request(f"/api/project/{project_name}/backends/update", body=config.json())
34
- return parse_obj_as(AnyConfigInfoWithCredsPartial, resp.json())
28
+ return parse_obj_as(AnyBackendConfigWithCreds, resp.json())
35
29
 
36
30
  def delete(self, project_name: str, backends_names: List[BackendType]):
37
31
  body = DeleteBackendsRequest(backends_names=backends_names)
38
32
  self._request(f"/api/project/{project_name}/backends/delete", body=body.json())
39
33
 
40
- def config_info(self, project_name: str, backend_name: BackendType) -> AnyConfigInfoWithCreds:
34
+ def config_info(
35
+ self, project_name: str, backend_name: BackendType
36
+ ) -> AnyBackendConfigWithCreds:
41
37
  resp = self._request(f"/api/project/{project_name}/backends/{backend_name}/config_info")
42
- return parse_obj_as(AnyConfigInfoWithCreds, resp.json())
38
+ return parse_obj_as(AnyBackendConfigWithCreds, resp.json())
@@ -1,4 +1,4 @@
1
- from typing import List, Optional, Union
1
+ from typing import Any, Dict, List, Optional
2
2
 
3
3
  from pydantic import parse_obj_as
4
4
 
@@ -22,7 +22,7 @@ class FleetsAPIClient(APIClientGroup):
22
22
  body = GetFleetRequest(name=name)
23
23
  resp = self._request(
24
24
  f"/api/project/{project_name}/fleets/get",
25
- body=body.json(exclude={"id"}), # `id` is not supported in pre-0.18.36 servers
25
+ body=body.json(),
26
26
  )
27
27
  return parse_obj_as(Fleet.__response__, resp.json())
28
28
 
@@ -55,62 +55,20 @@ class FleetsAPIClient(APIClientGroup):
55
55
  self._request(f"/api/project/{project_name}/fleets/delete_instances", body=body.json())
56
56
 
57
57
 
58
- _ExcludeDict = dict[str, Union[bool, set[str], "_ExcludeDict"]]
59
-
60
-
61
- def _get_fleet_spec_excludes(fleet_spec: FleetSpec) -> Optional[_ExcludeDict]:
62
- spec_excludes: _ExcludeDict = {}
63
- configuration_excludes: _ExcludeDict = {}
58
+ def _get_fleet_spec_excludes(fleet_spec: FleetSpec) -> Optional[Dict]:
59
+ """
60
+ Returns `fleet_spec` exclude mapping to exclude certain fields from the request.
61
+ Use this method to exclude new fields when they are not set to keep
62
+ clients backward-compatibility with older servers.
63
+ """
64
+ spec_excludes: Dict[str, Any] = {}
65
+ configuration_excludes: Dict[str, Any] = {}
64
66
  profile_excludes: set[str] = set()
65
- ssh_config_excludes: _ExcludeDict = {}
66
- ssh_hosts_excludes: set[str] = set()
67
-
68
- # TODO: Can be removed in 0.19
69
- if fleet_spec.configuration_path is None:
70
- spec_excludes["configuration_path"] = True
71
- if fleet_spec.configuration.ssh_config is not None:
72
- if fleet_spec.configuration.ssh_config.proxy_jump is None:
73
- ssh_config_excludes["proxy_jump"] = True
74
- if all(
75
- isinstance(h, str) or h.proxy_jump is None
76
- for h in fleet_spec.configuration.ssh_config.hosts
77
- ):
78
- ssh_hosts_excludes.add("proxy_jump")
79
- if all(
80
- isinstance(h, str) or h.internal_ip is None
81
- for h in fleet_spec.configuration.ssh_config.hosts
82
- ):
83
- ssh_hosts_excludes.add("internal_ip")
84
- if all(
85
- isinstance(h, str) or h.blocks == 1 for h in fleet_spec.configuration.ssh_config.hosts
86
- ):
87
- ssh_hosts_excludes.add("blocks")
88
- # client >= 0.18.30 / server <= 0.18.29 compatibility tweak
89
- if fleet_spec.configuration.reservation is None:
90
- configuration_excludes["reservation"] = True
91
- if fleet_spec.profile is not None and fleet_spec.profile.reservation is None:
92
- profile_excludes.add("reservation")
93
- if fleet_spec.configuration.idle_duration is None:
94
- configuration_excludes["idle_duration"] = True
95
- if fleet_spec.profile is not None and fleet_spec.profile.idle_duration is None:
96
- profile_excludes.add("idle_duration")
97
- # client >= 0.18.38 / server <= 0.18.37 compatibility tweak
98
- if fleet_spec.profile is not None and fleet_spec.profile.stop_duration is None:
99
- profile_excludes.add("stop_duration")
100
- # client >= 0.18.41 / server <= 0.18.40 compatibility tweak
101
- if fleet_spec.configuration.availability_zones is None:
102
- configuration_excludes["availability_zones"] = True
103
- if fleet_spec.profile is not None and fleet_spec.profile.availability_zones is None:
104
- profile_excludes.add("availability_zones")
105
- if fleet_spec.configuration.blocks == 1:
106
- configuration_excludes["blocks"] = True
107
- if fleet_spec.profile is not None and fleet_spec.profile.utilization_policy is None:
108
- profile_excludes.add("utilization_policy")
109
-
110
- if ssh_hosts_excludes:
111
- ssh_config_excludes["hosts"] = {"__all__": ssh_hosts_excludes}
112
- if ssh_config_excludes:
113
- configuration_excludes["ssh_config"] = ssh_config_excludes
67
+ # Fields can be excluded like this:
68
+ # if fleet_spec.configuration.availability_zones is None:
69
+ # configuration_excludes["availability_zones"] = True
70
+ # if fleet_spec.profile is not None and fleet_spec.profile.availability_zones is None:
71
+ # profile_excludes.add("availability_zones")
114
72
  if configuration_excludes:
115
73
  spec_excludes["configuration"] = configuration_excludes
116
74
  if profile_excludes:
@@ -1,8 +1,7 @@
1
- from typing import List, Optional
1
+ from typing import List
2
2
 
3
3
  from pydantic import parse_obj_as
4
4
 
5
- from dstack._internal.core.models.backends.base import BackendType
6
5
  from dstack._internal.core.models.gateways import Gateway, GatewayConfiguration
7
6
  from dstack._internal.server.schemas.gateways import (
8
7
  CreateGatewayRequest,
@@ -24,22 +23,12 @@ class GatewaysAPIClient(APIClientGroup):
24
23
  resp = self._request(f"/api/project/{project_name}/gateways/get", body=body.json())
25
24
  return parse_obj_as(Gateway.__response__, resp.json())
26
25
 
27
- # gateway_name, backend_type, region are left for backward-compatibility with 0.18.x
28
- # TODO: Remove in 0.19
29
26
  def create(
30
27
  self,
31
28
  project_name: str,
32
- gateway_name: Optional[str] = None,
33
- backend_type: Optional[BackendType] = None,
34
- region: Optional[str] = None,
35
- configuration: Optional[GatewayConfiguration] = None,
29
+ configuration: GatewayConfiguration,
36
30
  ) -> Gateway:
37
- body = CreateGatewayRequest(
38
- name=gateway_name,
39
- backend_type=backend_type,
40
- region=region,
41
- configuration=configuration,
42
- )
31
+ body = CreateGatewayRequest(configuration=configuration)
43
32
  resp = self._request(f"/api/project/{project_name}/gateways/create", body=body.json())
44
33
  return parse_obj_as(Gateway.__response__, resp.json())
45
34
 
@@ -6,7 +6,6 @@ from dstack._internal.core.models.repos import AnyRepoInfo, RemoteRepoCreds, Rep
6
6
  from dstack._internal.server.schemas.repos import (
7
7
  DeleteReposRequest,
8
8
  GetRepoRequest,
9
- RemoteRepoCredsDto,
10
9
  SaveRepoCredsRequest,
11
10
  )
12
11
  from dstack.api.server._group import APIClientGroup
@@ -32,9 +31,7 @@ class ReposAPIClient(APIClientGroup):
32
31
  body = SaveRepoCredsRequest(
33
32
  repo_id=repo_id,
34
33
  repo_info=repo_info,
35
- repo_creds=RemoteRepoCredsDto.from_remote_repo_creds(repo_creds)
36
- if repo_creds
37
- else None,
34
+ repo_creds=repo_creds,
38
35
  )
39
36
  self._request(f"/api/project/{project_name}/repos/init", body=body.json())
40
37