dstack 0.18.43__py3-none-any.whl → 0.19.0rc1__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.
Files changed (278) 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 -20
  7. dstack/_internal/cli/services/profile.py +34 -83
  8. dstack/_internal/cli/utils/gateway.py +1 -1
  9. dstack/_internal/cli/utils/run.py +11 -0
  10. dstack/_internal/core/backends/__init__.py +56 -39
  11. dstack/_internal/core/backends/aws/__init__.py +0 -25
  12. dstack/_internal/core/backends/aws/auth.py +1 -10
  13. dstack/_internal/core/backends/aws/backend.py +26 -0
  14. dstack/_internal/core/backends/aws/compute.py +21 -45
  15. dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
  16. dstack/_internal/core/backends/aws/models.py +135 -0
  17. dstack/_internal/core/backends/aws/resources.py +1 -1
  18. dstack/_internal/core/backends/azure/__init__.py +0 -20
  19. dstack/_internal/core/backends/azure/auth.py +2 -11
  20. dstack/_internal/core/backends/azure/backend.py +21 -0
  21. dstack/_internal/core/backends/azure/compute.py +14 -28
  22. dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
  23. dstack/_internal/core/backends/azure/models.py +89 -0
  24. dstack/_internal/core/backends/base/__init__.py +0 -12
  25. dstack/_internal/core/backends/base/backend.py +18 -0
  26. dstack/_internal/core/backends/base/compute.py +153 -33
  27. dstack/_internal/core/backends/base/configurator.py +105 -0
  28. dstack/_internal/core/backends/base/models.py +14 -0
  29. dstack/_internal/core/backends/configurators.py +138 -0
  30. dstack/_internal/core/backends/cudo/__init__.py +0 -15
  31. dstack/_internal/core/backends/cudo/backend.py +16 -0
  32. dstack/_internal/core/backends/cudo/compute.py +8 -26
  33. dstack/_internal/core/backends/cudo/configurator.py +72 -0
  34. dstack/_internal/core/backends/cudo/models.py +37 -0
  35. dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
  36. dstack/_internal/core/backends/datacrunch/backend.py +16 -0
  37. dstack/_internal/core/backends/datacrunch/compute.py +8 -25
  38. dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
  39. dstack/_internal/core/backends/datacrunch/models.py +38 -0
  40. dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
  41. dstack/_internal/core/backends/gcp/__init__.py +0 -16
  42. dstack/_internal/core/backends/gcp/auth.py +2 -11
  43. dstack/_internal/core/backends/gcp/backend.py +17 -0
  44. dstack/_internal/core/backends/gcp/compute.py +14 -44
  45. dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
  46. dstack/_internal/core/backends/gcp/models.py +125 -0
  47. dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
  48. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  49. dstack/_internal/core/backends/kubernetes/compute.py +16 -5
  50. dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
  51. dstack/_internal/core/backends/kubernetes/models.py +72 -0
  52. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
  53. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  54. dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
  55. dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
  56. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  57. dstack/_internal/core/backends/local/__init__.py +0 -13
  58. dstack/_internal/core/backends/local/backend.py +14 -0
  59. dstack/_internal/core/backends/local/compute.py +16 -2
  60. dstack/_internal/core/backends/models.py +128 -0
  61. dstack/_internal/core/backends/oci/__init__.py +0 -15
  62. dstack/_internal/core/backends/oci/auth.py +1 -5
  63. dstack/_internal/core/backends/oci/backend.py +16 -0
  64. dstack/_internal/core/backends/oci/compute.py +9 -23
  65. dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
  66. dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
  67. dstack/_internal/core/backends/oci/region.py +1 -1
  68. dstack/_internal/core/backends/runpod/__init__.py +0 -15
  69. dstack/_internal/core/backends/runpod/backend.py +16 -0
  70. dstack/_internal/core/backends/runpod/compute.py +28 -6
  71. dstack/_internal/core/backends/runpod/configurator.py +59 -0
  72. dstack/_internal/core/backends/runpod/models.py +54 -0
  73. dstack/_internal/core/backends/template/__init__.py +0 -0
  74. dstack/_internal/core/backends/tensordock/__init__.py +0 -15
  75. dstack/_internal/core/backends/tensordock/backend.py +16 -0
  76. dstack/_internal/core/backends/tensordock/compute.py +8 -27
  77. dstack/_internal/core/backends/tensordock/configurator.py +68 -0
  78. dstack/_internal/core/backends/tensordock/models.py +38 -0
  79. dstack/_internal/core/backends/vastai/__init__.py +0 -15
  80. dstack/_internal/core/backends/vastai/backend.py +16 -0
  81. dstack/_internal/core/backends/vastai/compute.py +2 -2
  82. dstack/_internal/core/backends/vastai/configurator.py +66 -0
  83. dstack/_internal/core/backends/vastai/models.py +37 -0
  84. dstack/_internal/core/backends/vultr/__init__.py +0 -15
  85. dstack/_internal/core/backends/vultr/backend.py +16 -0
  86. dstack/_internal/core/backends/vultr/compute.py +10 -24
  87. dstack/_internal/core/backends/vultr/configurator.py +64 -0
  88. dstack/_internal/core/backends/vultr/models.py +34 -0
  89. dstack/_internal/core/models/backends/__init__.py +0 -184
  90. dstack/_internal/core/models/backends/base.py +0 -19
  91. dstack/_internal/core/models/configurations.py +22 -16
  92. dstack/_internal/core/models/envs.py +4 -3
  93. dstack/_internal/core/models/fleets.py +17 -22
  94. dstack/_internal/core/models/gateways.py +3 -3
  95. dstack/_internal/core/models/instances.py +24 -0
  96. dstack/_internal/core/models/profiles.py +85 -45
  97. dstack/_internal/core/models/projects.py +1 -1
  98. dstack/_internal/core/models/repos/base.py +0 -5
  99. dstack/_internal/core/models/repos/local.py +3 -3
  100. dstack/_internal/core/models/repos/remote.py +26 -12
  101. dstack/_internal/core/models/repos/virtual.py +1 -1
  102. dstack/_internal/core/models/resources.py +45 -76
  103. dstack/_internal/core/models/runs.py +21 -19
  104. dstack/_internal/core/models/volumes.py +1 -3
  105. dstack/_internal/core/services/profiles.py +7 -16
  106. dstack/_internal/core/services/repos.py +0 -4
  107. dstack/_internal/server/app.py +11 -4
  108. dstack/_internal/server/background/__init__.py +10 -0
  109. dstack/_internal/server/background/tasks/process_gateways.py +4 -8
  110. dstack/_internal/server/background/tasks/process_instances.py +14 -9
  111. dstack/_internal/server/background/tasks/process_metrics.py +1 -1
  112. dstack/_internal/server/background/tasks/process_placement_groups.py +5 -1
  113. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +135 -0
  114. dstack/_internal/server/background/tasks/process_running_jobs.py +80 -24
  115. dstack/_internal/server/background/tasks/process_runs.py +1 -0
  116. dstack/_internal/server/background/tasks/process_submitted_jobs.py +20 -38
  117. dstack/_internal/server/background/tasks/process_volumes.py +5 -2
  118. dstack/_internal/server/migrations/versions/60e444118b6d_add_jobprometheusmetrics.py +40 -0
  119. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  120. dstack/_internal/server/migrations/versions/98d1b92988bc_add_jobterminationreason_terminated_due_.py +140 -0
  121. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  122. dstack/_internal/server/models.py +59 -9
  123. dstack/_internal/server/routers/backends.py +14 -23
  124. dstack/_internal/server/routers/instances.py +3 -4
  125. dstack/_internal/server/routers/metrics.py +31 -10
  126. dstack/_internal/server/routers/prometheus.py +36 -0
  127. dstack/_internal/server/routers/repos.py +1 -2
  128. dstack/_internal/server/routers/runs.py +13 -59
  129. dstack/_internal/server/schemas/gateways.py +14 -23
  130. dstack/_internal/server/schemas/projects.py +7 -2
  131. dstack/_internal/server/schemas/repos.py +2 -38
  132. dstack/_internal/server/schemas/runner.py +1 -0
  133. dstack/_internal/server/schemas/runs.py +1 -24
  134. dstack/_internal/server/security/permissions.py +1 -1
  135. dstack/_internal/server/services/backends/__init__.py +85 -158
  136. dstack/_internal/server/services/config.py +53 -567
  137. dstack/_internal/server/services/fleets.py +9 -103
  138. dstack/_internal/server/services/gateways/__init__.py +13 -4
  139. dstack/_internal/server/services/{pools.py → instances.py} +22 -329
  140. dstack/_internal/server/services/jobs/__init__.py +9 -6
  141. dstack/_internal/server/services/jobs/configurators/base.py +25 -1
  142. dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
  143. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  144. dstack/_internal/server/services/metrics.py +131 -72
  145. dstack/_internal/server/services/offers.py +1 -1
  146. dstack/_internal/server/services/projects.py +23 -14
  147. dstack/_internal/server/services/prometheus.py +245 -0
  148. dstack/_internal/server/services/runner/client.py +14 -3
  149. dstack/_internal/server/services/runs.py +67 -31
  150. dstack/_internal/server/services/volumes.py +9 -4
  151. dstack/_internal/server/settings.py +3 -0
  152. dstack/_internal/server/statics/index.html +1 -1
  153. dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js → main-4fd5a4770eff59325ee3.js} +68 -15
  154. dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js.map → main-4fd5a4770eff59325ee3.js.map} +1 -1
  155. dstack/_internal/server/statics/{main-7510e71dfa9749a4e70e.css → main-da9f8c06a69c20dac23e.css} +1 -1
  156. dstack/_internal/server/statics/static/media/entraID.d65d1f3e9486a8e56d24fc07b3230885.svg +9 -0
  157. dstack/_internal/server/testing/common.py +75 -32
  158. dstack/_internal/utils/json_schema.py +6 -0
  159. dstack/_internal/utils/ssh.py +2 -1
  160. dstack/api/__init__.py +4 -0
  161. dstack/api/_public/__init__.py +16 -20
  162. dstack/api/_public/backends.py +1 -1
  163. dstack/api/_public/repos.py +36 -36
  164. dstack/api/_public/runs.py +170 -83
  165. dstack/api/server/__init__.py +11 -13
  166. dstack/api/server/_backends.py +12 -16
  167. dstack/api/server/_fleets.py +15 -55
  168. dstack/api/server/_gateways.py +3 -14
  169. dstack/api/server/_repos.py +1 -4
  170. dstack/api/server/_runs.py +21 -96
  171. dstack/api/server/_volumes.py +10 -5
  172. dstack/api/utils.py +3 -0
  173. dstack/version.py +1 -1
  174. {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/METADATA +10 -1
  175. {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/RECORD +229 -206
  176. tests/_internal/cli/services/configurators/test_profile.py +6 -6
  177. tests/_internal/core/backends/aws/test_configurator.py +35 -0
  178. tests/_internal/core/backends/aws/test_resources.py +1 -1
  179. tests/_internal/core/backends/azure/test_configurator.py +61 -0
  180. tests/_internal/core/backends/cudo/__init__.py +0 -0
  181. tests/_internal/core/backends/cudo/test_configurator.py +37 -0
  182. tests/_internal/core/backends/datacrunch/__init__.py +0 -0
  183. tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
  184. tests/_internal/core/backends/gcp/test_configurator.py +42 -0
  185. tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
  186. tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
  187. tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
  188. tests/_internal/core/backends/oci/test_configurator.py +55 -0
  189. tests/_internal/core/backends/runpod/__init__.py +0 -0
  190. tests/_internal/core/backends/runpod/test_configurator.py +33 -0
  191. tests/_internal/core/backends/tensordock/__init__.py +0 -0
  192. tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
  193. tests/_internal/core/backends/vastai/__init__.py +0 -0
  194. tests/_internal/core/backends/vastai/test_configurator.py +33 -0
  195. tests/_internal/core/backends/vultr/__init__.py +0 -0
  196. tests/_internal/core/backends/vultr/test_configurator.py +33 -0
  197. tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
  198. tests/_internal/server/background/tasks/test_process_instances.py +49 -48
  199. tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
  200. tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
  201. tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +186 -0
  202. tests/_internal/server/background/tasks/test_process_running_jobs.py +123 -19
  203. tests/_internal/server/background/tasks/test_process_runs.py +8 -22
  204. tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
  205. tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
  206. tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
  207. tests/_internal/server/routers/test_backends.py +6 -764
  208. tests/_internal/server/routers/test_fleets.py +2 -26
  209. tests/_internal/server/routers/test_gateways.py +27 -3
  210. tests/_internal/server/routers/test_instances.py +0 -10
  211. tests/_internal/server/routers/test_metrics.py +42 -0
  212. tests/_internal/server/routers/test_projects.py +56 -0
  213. tests/_internal/server/routers/test_prometheus.py +333 -0
  214. tests/_internal/server/routers/test_repos.py +0 -15
  215. tests/_internal/server/routers/test_runs.py +83 -275
  216. tests/_internal/server/routers/test_volumes.py +2 -3
  217. tests/_internal/server/services/backends/__init__.py +0 -0
  218. tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
  219. tests/_internal/server/services/test_config.py +7 -4
  220. tests/_internal/server/services/test_fleets.py +1 -4
  221. tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
  222. tests/_internal/server/services/test_metrics.py +167 -0
  223. tests/_internal/server/services/test_repos.py +1 -14
  224. tests/_internal/server/services/test_runs.py +0 -4
  225. dstack/_internal/cli/commands/pool.py +0 -581
  226. dstack/_internal/cli/commands/run.py +0 -75
  227. dstack/_internal/core/backends/aws/config.py +0 -18
  228. dstack/_internal/core/backends/azure/config.py +0 -12
  229. dstack/_internal/core/backends/base/config.py +0 -5
  230. dstack/_internal/core/backends/cudo/config.py +0 -9
  231. dstack/_internal/core/backends/datacrunch/config.py +0 -9
  232. dstack/_internal/core/backends/gcp/config.py +0 -22
  233. dstack/_internal/core/backends/kubernetes/config.py +0 -6
  234. dstack/_internal/core/backends/lambdalabs/config.py +0 -9
  235. dstack/_internal/core/backends/nebius/__init__.py +0 -15
  236. dstack/_internal/core/backends/nebius/api_client.py +0 -319
  237. dstack/_internal/core/backends/nebius/compute.py +0 -220
  238. dstack/_internal/core/backends/nebius/config.py +0 -6
  239. dstack/_internal/core/backends/nebius/types.py +0 -37
  240. dstack/_internal/core/backends/oci/config.py +0 -6
  241. dstack/_internal/core/backends/runpod/config.py +0 -9
  242. dstack/_internal/core/backends/tensordock/config.py +0 -9
  243. dstack/_internal/core/backends/vastai/config.py +0 -6
  244. dstack/_internal/core/backends/vultr/config.py +0 -9
  245. dstack/_internal/core/models/backends/aws.py +0 -86
  246. dstack/_internal/core/models/backends/azure.py +0 -68
  247. dstack/_internal/core/models/backends/cudo.py +0 -43
  248. dstack/_internal/core/models/backends/datacrunch.py +0 -44
  249. dstack/_internal/core/models/backends/gcp.py +0 -67
  250. dstack/_internal/core/models/backends/kubernetes.py +0 -40
  251. dstack/_internal/core/models/backends/lambdalabs.py +0 -43
  252. dstack/_internal/core/models/backends/nebius.py +0 -54
  253. dstack/_internal/core/models/backends/runpod.py +0 -40
  254. dstack/_internal/core/models/backends/tensordock.py +0 -44
  255. dstack/_internal/core/models/backends/vastai.py +0 -43
  256. dstack/_internal/core/models/backends/vultr.py +0 -40
  257. dstack/_internal/core/models/pools.py +0 -43
  258. dstack/_internal/server/routers/pools.py +0 -142
  259. dstack/_internal/server/schemas/pools.py +0 -38
  260. dstack/_internal/server/services/backends/configurators/base.py +0 -72
  261. dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
  262. dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
  263. dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
  264. dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
  265. dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
  266. dstack/_internal/server/services/backends/configurators/runpod.py +0 -97
  267. dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
  268. dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
  269. dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
  270. dstack/api/_public/pools.py +0 -41
  271. dstack/api/_public/resources.py +0 -105
  272. dstack/api/server/_pools.py +0 -63
  273. tests/_internal/server/routers/test_pools.py +0 -612
  274. /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
  275. {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/LICENSE.md +0 -0
  276. {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/WHEEL +0 -0
  277. {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/entry_points.txt +0 -0
  278. {dstack-0.18.43.dist-info → dstack-0.19.0rc1.dist-info}/top_level.txt +0 -0
@@ -16,23 +16,21 @@ 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,
25
23
  ProfileRetryPolicy,
26
24
  SpotPolicy,
27
25
  TerminationPolicy,
26
+ UtilizationPolicy,
28
27
  )
29
28
  from dstack._internal.core.models.repos.base import Repo
29
+ from dstack._internal.core.models.repos.virtual import VirtualRepo
30
30
  from dstack._internal.core.models.resources import ResourcesSpec
31
31
  from dstack._internal.core.models.runs import (
32
32
  Job,
33
33
  JobSpec,
34
- PoolInstanceOffers,
35
- Requirements,
36
34
  RunPlan,
37
35
  RunSpec,
38
36
  RunStatus,
@@ -190,14 +188,14 @@ class Run(ABC):
190
188
  job_num: int = 0,
191
189
  ) -> Iterable[bytes]:
192
190
  """
193
- Iterate through run's log messages
191
+ Iterate through run's log messages.
194
192
 
195
193
  Args:
196
- start_time: minimal log timestamp
197
- diagnose: return runner logs if `True`
194
+ start_time: Minimal log timestamp.
195
+ diagnose: Return runner logs if `True`.
198
196
 
199
197
  Yields:
200
- log messages
198
+ Log messages.
201
199
  """
202
200
  if diagnose is False and self._ssh_attach is not None:
203
201
  yield from self._attached_logs()
@@ -227,17 +225,17 @@ class Run(ABC):
227
225
 
228
226
  def refresh(self):
229
227
  """
230
- Get up-to-date run info
228
+ Get up-to-date run info.
231
229
  """
232
230
  self._run = self._api_client.runs.get(self._project, self._run.run_spec.run_name)
233
231
  logger.debug("Refreshed run %s: %s", self.name, self.status)
234
232
 
235
233
  def stop(self, abort: bool = False):
236
234
  """
237
- Terminate the instance and detach
235
+ Terminate the instance and detach.
238
236
 
239
237
  Args:
240
- abort: gracefully stop the run if `False`
238
+ abort: Gracefully stop the run if `False`.
241
239
  """
242
240
  self._api_client.runs.stop(self._project, [self.name], abort)
243
241
  logger.debug("%s run %s", "Aborted" if abort else "Stopped", self.name)
@@ -255,7 +253,7 @@ class Run(ABC):
255
253
  Establish an SSH tunnel to the instance and update SSH config
256
254
 
257
255
  Args:
258
- ssh_identity_file: SSH keypair to access instances
256
+ ssh_identity_file: SSH keypair to access instances.
259
257
 
260
258
  Raises:
261
259
  dstack.api.PortUsedError: If ports are in use or the run is attached by another process.
@@ -392,7 +390,7 @@ class ServiceModel:
392
390
 
393
391
  class RunCollection:
394
392
  """
395
- Operations with runs
393
+ Operations with runs.
396
394
  """
397
395
 
398
396
  def __init__(
@@ -405,6 +403,122 @@ class RunCollection:
405
403
  self._project = project
406
404
  self._client = client
407
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
+
408
522
  def submit(
409
523
  self,
410
524
  configuration: AnyRunConfiguration,
@@ -422,31 +536,30 @@ class RunCollection:
422
536
  run_name: Optional[str] = None,
423
537
  reserve_ports: bool = True,
424
538
  ) -> Run:
425
- """
426
- Submit a run
539
+ # """
540
+ # Submit a run
427
541
 
428
- Args:
429
- configuration (Union[Task, Service]): A run configuration.
430
- configuration_path: The path to the configuration file, relative to the root directory of the repo.
431
- repo (Union[LocalRepo, RemoteRepo, VirtualRepo]): A repo to mount to the run.
432
- backends: A list of allowed backend for provisioning.
433
- regions: A list of cloud regions for provisioning.
434
- resources: The requirements to run the configuration. Overrides the configuration's resources.
435
- spot_policy: A spot policy for provisioning.
436
- retry_policy (RetryPolicy): A retry policy.
437
- max_duration: The max instance running duration in seconds.
438
- max_price: The max instance price in dollars per hour for provisioning.
439
- working_dir: A working directory relative to the repo root directory
440
- run_name: A desired name of the run. Must be unique in the project. If not specified, a random name is assigned.
441
- 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.
442
556
 
443
- Returns:
444
- submitted run
445
- """
557
+ # Returns:
558
+ # Submitted run.
559
+ # """
560
+ logger.warning("The submit() method is deprecated in favor of apply_configuration().")
446
561
  if repo is None:
447
- repo = configuration.get_repo()
448
- if repo is None:
449
- raise ConfigurationError("Repo is required for this type of configuration")
562
+ repo = VirtualRepo()
450
563
  # TODO: Add Git credentials to RemoteRepo and if they are set, pass them here to RepoCollection.init
451
564
  self._client.repos.init(repo)
452
565
 
@@ -467,24 +580,21 @@ class RunCollection:
467
580
  )
468
581
  return self.exec_plan(run_plan, repo, reserve_ports=reserve_ports)
469
582
 
470
- def get_offers(self, profile: Profile, requirements: Requirements) -> PoolInstanceOffers:
471
- return self._api_client.runs.get_offers(self._project, profile, requirements)
472
-
473
- def create_instance(self, profile: Profile, requirements: Requirements) -> Instance:
474
- return self._api_client.runs.create_instance(self._project, profile, requirements)
475
-
476
- # 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()
477
584
  def get_plan(
478
585
  self,
479
586
  configuration: AnyRunConfiguration,
480
587
  repo: Optional[Repo] = None,
481
588
  configuration_path: Optional[str] = None,
589
+ # Unused profile args are deprecated and removed but
590
+ # kept for signature backward compatibility.
482
591
  backends: Optional[List[BackendType]] = None,
483
592
  regions: Optional[List[str]] = None,
484
593
  instance_types: Optional[List[str]] = None,
485
594
  resources: Optional[ResourcesSpec] = None,
486
595
  spot_policy: Optional[SpotPolicy] = None,
487
596
  retry_policy: Optional[ProfileRetryPolicy] = None,
597
+ utilization_policy: Optional[UtilizationPolicy] = None,
488
598
  max_duration: Optional[Union[int, str]] = None,
489
599
  max_price: Optional[float] = None,
490
600
  working_dir: Optional[str] = None,
@@ -504,11 +614,9 @@ class RunCollection:
504
614
  # Returns:
505
615
  # run plan
506
616
  # """
507
-
617
+ logger.warning("The get_plan() method is deprecated in favor of get_run_plan().")
508
618
  if repo is None:
509
- repo = configuration.get_repo()
510
- if repo is None:
511
- raise ConfigurationError("Repo is required for this type of configuration")
619
+ repo = VirtualRepo()
512
620
 
513
621
  if working_dir is None:
514
622
  working_dir = "."
@@ -534,16 +642,12 @@ class RunCollection:
534
642
  reservation=reservation,
535
643
  spot_policy=spot_policy,
536
644
  retry=None,
537
- retry_policy=retry_policy,
645
+ utilization_policy=utilization_policy,
538
646
  max_duration=max_duration,
539
647
  stop_duration=stop_duration,
540
648
  max_price=max_price,
541
- pool_name=pool_name,
542
- instance_name=instance_name,
543
649
  creation_policy=creation_policy,
544
650
  idle_duration=idle_duration,
545
- termination_policy=termination_policy,
546
- termination_idle_time=termination_policy_idle,
547
651
  )
548
652
  run_spec = RunSpec(
549
653
  run_name=run_name,
@@ -574,45 +678,28 @@ class RunCollection:
574
678
  reserve_ports: bool = True,
575
679
  ) -> Run:
576
680
  # """
577
- # Execute run plan
578
- #
681
+ # Execute the run plan.
682
+
579
683
  # Args:
580
- # run_plan: result of `get_plan` call
581
- # repo: repo to use for the run
582
- # reserve_ports: reserve local ports before submit
583
- #
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
+
584
688
  # Returns:
585
- # submitted run
689
+ # Submitted run.
586
690
  # """
587
- ports_lock = None
588
- if reserve_ports:
589
- # TODO handle multiple jobs
590
- ports_lock = _reserve_ports(run_plan.job_plans[0].job_spec)
591
-
592
- with tempfile.TemporaryFile("w+b") as fp:
593
- run_plan.run_spec.repo_code_hash = repo.write_code_file(fp)
594
- fp.seek(0)
595
- self._api_client.repos.upload_code(
596
- self._project, repo.repo_id, run_plan.run_spec.repo_code_hash, fp
597
- )
598
- # Calling submit when action is CREATE since apply_plan is not backward-compatible.
599
- # Otherwise, apply_plan can replace submit, i.e. it creates the run if it does not exist.
600
- # TODO: Remove in 0.19
601
- if run_plan.action == ApplyAction.UPDATE:
602
- run = self._api_client.runs.apply_plan(self._project, run_plan)
603
- else:
604
- run = self._api_client.runs.submit(self._project, run_plan.run_spec)
605
- 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)
606
693
 
607
694
  def list(self, all: bool = False) -> List[Run]:
608
695
  """
609
- List runs
696
+ List runs.
610
697
 
611
698
  Args:
612
- all: show all runs (active and finished) if `True`
699
+ all: Show all runs (active and finished) if `True`.
613
700
 
614
701
  Returns:
615
- list of runs
702
+ List of runs.
616
703
  """
617
704
  # Return only one page of latest runs (<=100). Returning all the pages may be costly.
618
705
  # TODO: Consider introducing `since` filter with a reasonable default.
@@ -631,13 +718,13 @@ class RunCollection:
631
718
 
632
719
  def get(self, run_name: str) -> Optional[Run]:
633
720
  """
634
- Get run by run name
721
+ Get run by run name.
635
722
 
636
723
  Args:
637
- run_name: run name
724
+ run_name: Run name.
638
725
 
639
726
  Returns:
640
- The run or `None` if not found
727
+ The run or `None` if not found.
641
728
  """
642
729
  try:
643
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,60 +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
-
108
- if ssh_hosts_excludes:
109
- ssh_config_excludes["hosts"] = {"__all__": ssh_hosts_excludes}
110
- if ssh_config_excludes:
111
- 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")
112
72
  if configuration_excludes:
113
73
  spec_excludes["configuration"] = configuration_excludes
114
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