dstack 0.19.23__py3-none-any.whl → 0.19.24__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.

@@ -165,6 +165,9 @@ class InstanceAvailability(Enum):
165
165
  AVAILABLE = "available"
166
166
  NOT_AVAILABLE = "not_available"
167
167
  NO_QUOTA = "no_quota"
168
+ NO_BALANCE = (
169
+ "no_balance" # Introduced in 0.19.24, may be used after a short compatibility period
170
+ )
168
171
  IDLE = "idle"
169
172
  BUSY = "busy"
170
173
 
@@ -350,15 +350,17 @@ class JobSubmission(CoreModel):
350
350
  deployment_num: int = 0 # default for compatibility with pre-0.19.14 servers
351
351
  submitted_at: datetime
352
352
  last_processed_at: datetime
353
- finished_at: Optional[datetime]
354
- inactivity_secs: Optional[int]
353
+ finished_at: Optional[datetime] = None
354
+ inactivity_secs: Optional[int] = None
355
355
  status: JobStatus
356
356
  status_message: str = "" # default for backward compatibility
357
- termination_reason: Optional[JobTerminationReason]
358
- termination_reason_message: Optional[str]
359
- exit_status: Optional[int]
360
- job_provisioning_data: Optional[JobProvisioningData]
361
- job_runtime_data: Optional[JobRuntimeData]
357
+ # termination_reason stores JobTerminationReason.
358
+ # str allows adding new enum members without breaking compatibility with old clients.
359
+ termination_reason: Optional[str] = None
360
+ termination_reason_message: Optional[str] = None
361
+ exit_status: Optional[int] = None
362
+ job_provisioning_data: Optional[JobProvisioningData] = None
363
+ job_runtime_data: Optional[JobRuntimeData] = None
362
364
  error: Optional[str] = None
363
365
  probes: list[Probe] = []
364
366
 
@@ -508,7 +510,9 @@ class Run(CoreModel):
508
510
  last_processed_at: datetime
509
511
  status: RunStatus
510
512
  status_message: str = "" # default for backward compatibility
511
- termination_reason: Optional[RunTerminationReason] = None
513
+ # termination_reason stores RunTerminationReason.
514
+ # str allows adding new enum members without breaking compatibility with old clients.
515
+ termination_reason: Optional[str] = None
512
516
  run_spec: RunSpec
513
517
  jobs: List[Job]
514
518
  latest_job_submission: Optional[JobSubmission] = None
@@ -455,8 +455,7 @@ async def _wait_for_instance_provisioning_data(job_model: JobModel):
455
455
 
456
456
  if job_model.instance.status == InstanceStatus.TERMINATED:
457
457
  job_model.status = JobStatus.TERMINATING
458
- # TODO use WAITING_INSTANCE_LIMIT_EXCEEDED after 0.19.x
459
- job_model.termination_reason = JobTerminationReason.FAILED_TO_START_DUE_TO_NO_CAPACITY
458
+ job_model.termination_reason = JobTerminationReason.WAITING_INSTANCE_LIMIT_EXCEEDED
460
459
  return
461
460
 
462
461
  job_model.job_provisioning_data = job_model.instance.job_provisioning_data
@@ -574,7 +574,7 @@ def _should_retry_job(run: Run, job: Job, job_model: JobModel) -> Optional[datet
574
574
 
575
575
  if (
576
576
  last_provisioned_submission.termination_reason is not None
577
- and last_provisioned_submission.termination_reason.to_retry_event()
577
+ and JobTerminationReason(last_provisioned_submission.termination_reason).to_retry_event()
578
578
  in job.job_spec.retry.on_events
579
579
  ):
580
580
  return common.get_current_datetime() - last_provisioned_submission.last_processed_at
@@ -0,0 +1,484 @@
1
+ """Store enums as strings
2
+
3
+ Revision ID: 74a1f55209bd
4
+ Revises: 728b1488b1b4
5
+ Create Date: 2025-08-06 13:49:28.785378
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "74a1f55209bd"
15
+ down_revision = "728b1488b1b4"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ with op.batch_alter_table("users", schema=None) as batch_op:
23
+ batch_op.alter_column(
24
+ "global_role",
25
+ existing_type=postgresql.ENUM("ADMIN", "USER", name="globalrole"),
26
+ type_=sa.String(length=100),
27
+ existing_nullable=False,
28
+ )
29
+
30
+ with op.batch_alter_table("members", schema=None) as batch_op:
31
+ batch_op.alter_column(
32
+ "project_role",
33
+ existing_type=postgresql.ENUM("ADMIN", "MANAGER", "USER", name="projectrole"),
34
+ type_=sa.String(length=100),
35
+ existing_nullable=False,
36
+ )
37
+
38
+ with op.batch_alter_table("repos", schema=None) as batch_op:
39
+ batch_op.alter_column(
40
+ "type",
41
+ existing_type=postgresql.ENUM("REMOTE", "LOCAL", "VIRTUAL", name="repotype"),
42
+ type_=sa.String(length=100),
43
+ existing_nullable=False,
44
+ )
45
+
46
+ with op.batch_alter_table("runs", schema=None) as batch_op:
47
+ batch_op.alter_column(
48
+ "status",
49
+ existing_type=postgresql.ENUM(
50
+ "PENDING",
51
+ "SUBMITTED",
52
+ "PROVISIONING",
53
+ "RUNNING",
54
+ "TERMINATING",
55
+ "TERMINATED",
56
+ "FAILED",
57
+ "DONE",
58
+ name="runstatus",
59
+ ),
60
+ type_=sa.String(length=100),
61
+ existing_nullable=False,
62
+ )
63
+ batch_op.alter_column(
64
+ "termination_reason",
65
+ existing_type=postgresql.ENUM(
66
+ "ALL_JOBS_DONE",
67
+ "JOB_FAILED",
68
+ "RETRY_LIMIT_EXCEEDED",
69
+ "STOPPED_BY_USER",
70
+ "ABORTED_BY_USER",
71
+ "SERVER_ERROR",
72
+ name="runterminationreason",
73
+ ),
74
+ type_=sa.String(length=100),
75
+ existing_nullable=True,
76
+ )
77
+
78
+ with op.batch_alter_table("jobs", schema=None) as batch_op:
79
+ batch_op.alter_column(
80
+ "status",
81
+ existing_type=postgresql.ENUM(
82
+ "SUBMITTED",
83
+ "PROVISIONING",
84
+ "PULLING",
85
+ "RUNNING",
86
+ "TERMINATING",
87
+ "TERMINATED",
88
+ "ABORTED",
89
+ "FAILED",
90
+ "DONE",
91
+ name="jobstatus",
92
+ ),
93
+ type_=sa.String(length=100),
94
+ existing_nullable=False,
95
+ )
96
+ batch_op.alter_column(
97
+ "termination_reason",
98
+ existing_type=postgresql.ENUM(
99
+ "FAILED_TO_START_DUE_TO_NO_CAPACITY",
100
+ "INTERRUPTED_BY_NO_CAPACITY",
101
+ "INSTANCE_UNREACHABLE",
102
+ "WAITING_INSTANCE_LIMIT_EXCEEDED",
103
+ "WAITING_RUNNER_LIMIT_EXCEEDED",
104
+ "TERMINATED_BY_USER",
105
+ "VOLUME_ERROR",
106
+ "GATEWAY_ERROR",
107
+ "SCALED_DOWN",
108
+ "DONE_BY_RUNNER",
109
+ "ABORTED_BY_USER",
110
+ "TERMINATED_BY_SERVER",
111
+ "INACTIVITY_DURATION_EXCEEDED",
112
+ "TERMINATED_DUE_TO_UTILIZATION_POLICY",
113
+ "CONTAINER_EXITED_WITH_ERROR",
114
+ "PORTS_BINDING_FAILED",
115
+ "CREATING_CONTAINER_ERROR",
116
+ "EXECUTOR_ERROR",
117
+ "MAX_DURATION_EXCEEDED",
118
+ name="jobterminationreason",
119
+ ),
120
+ type_=sa.String(length=100),
121
+ existing_nullable=True,
122
+ )
123
+
124
+ with op.batch_alter_table("fleets", schema=None) as batch_op:
125
+ batch_op.alter_column(
126
+ "status",
127
+ existing_type=postgresql.ENUM(
128
+ "SUBMITTED", "ACTIVE", "TERMINATING", "TERMINATED", "FAILED", name="fleetstatus"
129
+ ),
130
+ type_=sa.String(length=100),
131
+ existing_nullable=False,
132
+ )
133
+
134
+ with op.batch_alter_table("gateways", schema=None) as batch_op:
135
+ batch_op.alter_column(
136
+ "status",
137
+ existing_type=postgresql.ENUM(
138
+ "SUBMITTED", "PROVISIONING", "RUNNING", "FAILED", name="gatewaystatus"
139
+ ),
140
+ type_=sa.String(length=100),
141
+ existing_nullable=False,
142
+ )
143
+
144
+ with op.batch_alter_table("instances", schema=None) as batch_op:
145
+ batch_op.alter_column(
146
+ "status",
147
+ existing_type=postgresql.ENUM(
148
+ "PENDING",
149
+ "PROVISIONING",
150
+ "IDLE",
151
+ "BUSY",
152
+ "TERMINATING",
153
+ "TERMINATED",
154
+ name="instancestatus",
155
+ ),
156
+ type_=sa.String(length=100),
157
+ existing_nullable=False,
158
+ )
159
+
160
+ with op.batch_alter_table("volumes", schema=None) as batch_op:
161
+ batch_op.alter_column(
162
+ "status",
163
+ existing_type=postgresql.ENUM(
164
+ "SUBMITTED", "PROVISIONING", "ACTIVE", "FAILED", name="volumestatus"
165
+ ),
166
+ type_=sa.String(length=100),
167
+ existing_nullable=False,
168
+ )
169
+
170
+ sa.Enum("ADMIN", "USER", name="globalrole").drop(op.get_bind())
171
+ sa.Enum(
172
+ "ALL_JOBS_DONE",
173
+ "JOB_FAILED",
174
+ "RETRY_LIMIT_EXCEEDED",
175
+ "STOPPED_BY_USER",
176
+ "ABORTED_BY_USER",
177
+ "SERVER_ERROR",
178
+ name="runterminationreason",
179
+ ).drop(op.get_bind())
180
+ sa.Enum("SUBMITTED", "PROVISIONING", "RUNNING", "FAILED", name="gatewaystatus").drop(
181
+ op.get_bind()
182
+ )
183
+ sa.Enum("SUBMITTED", "PROVISIONING", "ACTIVE", "FAILED", name="volumestatus").drop(
184
+ op.get_bind()
185
+ )
186
+ sa.Enum(
187
+ "PENDING",
188
+ "SUBMITTED",
189
+ "PROVISIONING",
190
+ "RUNNING",
191
+ "TERMINATING",
192
+ "TERMINATED",
193
+ "FAILED",
194
+ "DONE",
195
+ name="runstatus",
196
+ ).drop(op.get_bind())
197
+ sa.Enum("REMOTE", "LOCAL", "VIRTUAL", name="repotype").drop(op.get_bind())
198
+ sa.Enum(
199
+ "SUBMITTED",
200
+ "PROVISIONING",
201
+ "PULLING",
202
+ "RUNNING",
203
+ "TERMINATING",
204
+ "TERMINATED",
205
+ "ABORTED",
206
+ "FAILED",
207
+ "DONE",
208
+ name="jobstatus",
209
+ ).drop(op.get_bind())
210
+ sa.Enum(
211
+ "PENDING",
212
+ "PROVISIONING",
213
+ "IDLE",
214
+ "BUSY",
215
+ "TERMINATING",
216
+ "TERMINATED",
217
+ name="instancestatus",
218
+ ).drop(op.get_bind())
219
+ sa.Enum("SUBMITTED", "ACTIVE", "TERMINATING", "TERMINATED", "FAILED", name="fleetstatus").drop(
220
+ op.get_bind()
221
+ )
222
+ sa.Enum("ADMIN", "MANAGER", "USER", name="projectrole").drop(op.get_bind())
223
+ sa.Enum(
224
+ "FAILED_TO_START_DUE_TO_NO_CAPACITY",
225
+ "INTERRUPTED_BY_NO_CAPACITY",
226
+ "INSTANCE_UNREACHABLE",
227
+ "WAITING_INSTANCE_LIMIT_EXCEEDED",
228
+ "WAITING_RUNNER_LIMIT_EXCEEDED",
229
+ "TERMINATED_BY_USER",
230
+ "VOLUME_ERROR",
231
+ "GATEWAY_ERROR",
232
+ "SCALED_DOWN",
233
+ "DONE_BY_RUNNER",
234
+ "ABORTED_BY_USER",
235
+ "TERMINATED_BY_SERVER",
236
+ "INACTIVITY_DURATION_EXCEEDED",
237
+ "TERMINATED_DUE_TO_UTILIZATION_POLICY",
238
+ "CONTAINER_EXITED_WITH_ERROR",
239
+ "PORTS_BINDING_FAILED",
240
+ "CREATING_CONTAINER_ERROR",
241
+ "EXECUTOR_ERROR",
242
+ "MAX_DURATION_EXCEEDED",
243
+ name="jobterminationreason",
244
+ ).drop(op.get_bind())
245
+ # ### end Alembic commands ###
246
+
247
+
248
+ def downgrade() -> None:
249
+ # ### commands auto generated by Alembic - please adjust! ###
250
+ sa.Enum(
251
+ "FAILED_TO_START_DUE_TO_NO_CAPACITY",
252
+ "INTERRUPTED_BY_NO_CAPACITY",
253
+ "INSTANCE_UNREACHABLE",
254
+ "WAITING_INSTANCE_LIMIT_EXCEEDED",
255
+ "WAITING_RUNNER_LIMIT_EXCEEDED",
256
+ "TERMINATED_BY_USER",
257
+ "VOLUME_ERROR",
258
+ "GATEWAY_ERROR",
259
+ "SCALED_DOWN",
260
+ "DONE_BY_RUNNER",
261
+ "ABORTED_BY_USER",
262
+ "TERMINATED_BY_SERVER",
263
+ "INACTIVITY_DURATION_EXCEEDED",
264
+ "TERMINATED_DUE_TO_UTILIZATION_POLICY",
265
+ "CONTAINER_EXITED_WITH_ERROR",
266
+ "PORTS_BINDING_FAILED",
267
+ "CREATING_CONTAINER_ERROR",
268
+ "EXECUTOR_ERROR",
269
+ "MAX_DURATION_EXCEEDED",
270
+ name="jobterminationreason",
271
+ ).create(op.get_bind())
272
+ sa.Enum("ADMIN", "MANAGER", "USER", name="projectrole").create(op.get_bind())
273
+ sa.Enum(
274
+ "SUBMITTED", "ACTIVE", "TERMINATING", "TERMINATED", "FAILED", name="fleetstatus"
275
+ ).create(op.get_bind())
276
+ sa.Enum(
277
+ "PENDING",
278
+ "PROVISIONING",
279
+ "IDLE",
280
+ "BUSY",
281
+ "TERMINATING",
282
+ "TERMINATED",
283
+ name="instancestatus",
284
+ ).create(op.get_bind())
285
+ sa.Enum(
286
+ "SUBMITTED",
287
+ "PROVISIONING",
288
+ "PULLING",
289
+ "RUNNING",
290
+ "TERMINATING",
291
+ "TERMINATED",
292
+ "ABORTED",
293
+ "FAILED",
294
+ "DONE",
295
+ name="jobstatus",
296
+ ).create(op.get_bind())
297
+ sa.Enum("REMOTE", "LOCAL", "VIRTUAL", name="repotype").create(op.get_bind())
298
+ sa.Enum(
299
+ "PENDING",
300
+ "SUBMITTED",
301
+ "PROVISIONING",
302
+ "RUNNING",
303
+ "TERMINATING",
304
+ "TERMINATED",
305
+ "FAILED",
306
+ "DONE",
307
+ name="runstatus",
308
+ ).create(op.get_bind())
309
+ sa.Enum("SUBMITTED", "PROVISIONING", "ACTIVE", "FAILED", name="volumestatus").create(
310
+ op.get_bind()
311
+ )
312
+ sa.Enum("SUBMITTED", "PROVISIONING", "RUNNING", "FAILED", name="gatewaystatus").create(
313
+ op.get_bind()
314
+ )
315
+ sa.Enum(
316
+ "ALL_JOBS_DONE",
317
+ "JOB_FAILED",
318
+ "RETRY_LIMIT_EXCEEDED",
319
+ "STOPPED_BY_USER",
320
+ "ABORTED_BY_USER",
321
+ "SERVER_ERROR",
322
+ name="runterminationreason",
323
+ ).create(op.get_bind())
324
+ sa.Enum("ADMIN", "USER", name="globalrole").create(op.get_bind())
325
+ with op.batch_alter_table("volumes", schema=None) as batch_op:
326
+ batch_op.alter_column(
327
+ "status",
328
+ existing_type=sa.String(length=100),
329
+ type_=postgresql.ENUM(
330
+ "SUBMITTED", "PROVISIONING", "ACTIVE", "FAILED", name="volumestatus"
331
+ ),
332
+ existing_nullable=False,
333
+ postgresql_using="status::VARCHAR::volumestatus",
334
+ )
335
+
336
+ with op.batch_alter_table("users", schema=None) as batch_op:
337
+ batch_op.alter_column(
338
+ "global_role",
339
+ existing_type=sa.String(length=100),
340
+ type_=postgresql.ENUM("ADMIN", "USER", name="globalrole"),
341
+ existing_nullable=False,
342
+ postgresql_using="global_role::VARCHAR::globalrole",
343
+ )
344
+
345
+ with op.batch_alter_table("runs", schema=None) as batch_op:
346
+ batch_op.alter_column(
347
+ "termination_reason",
348
+ existing_type=sa.String(length=100),
349
+ type_=postgresql.ENUM(
350
+ "ALL_JOBS_DONE",
351
+ "JOB_FAILED",
352
+ "RETRY_LIMIT_EXCEEDED",
353
+ "STOPPED_BY_USER",
354
+ "ABORTED_BY_USER",
355
+ "SERVER_ERROR",
356
+ name="runterminationreason",
357
+ ),
358
+ existing_nullable=True,
359
+ postgresql_using="termination_reason::VARCHAR::runterminationreason",
360
+ )
361
+ batch_op.alter_column(
362
+ "status",
363
+ existing_type=sa.String(length=100),
364
+ type_=postgresql.ENUM(
365
+ "PENDING",
366
+ "SUBMITTED",
367
+ "PROVISIONING",
368
+ "RUNNING",
369
+ "TERMINATING",
370
+ "TERMINATED",
371
+ "FAILED",
372
+ "DONE",
373
+ name="runstatus",
374
+ ),
375
+ existing_nullable=False,
376
+ postgresql_using="status::VARCHAR::runstatus",
377
+ )
378
+
379
+ with op.batch_alter_table("repos", schema=None) as batch_op:
380
+ batch_op.alter_column(
381
+ "type",
382
+ existing_type=sa.String(length=100),
383
+ type_=postgresql.ENUM("REMOTE", "LOCAL", "VIRTUAL", name="repotype"),
384
+ existing_nullable=False,
385
+ postgresql_using="type::VARCHAR::repotype",
386
+ )
387
+
388
+ with op.batch_alter_table("members", schema=None) as batch_op:
389
+ batch_op.alter_column(
390
+ "project_role",
391
+ existing_type=sa.String(length=100),
392
+ type_=postgresql.ENUM("ADMIN", "MANAGER", "USER", name="projectrole"),
393
+ existing_nullable=False,
394
+ postgresql_using="project_role::VARCHAR::projectrole",
395
+ )
396
+
397
+ with op.batch_alter_table("jobs", schema=None) as batch_op:
398
+ batch_op.alter_column(
399
+ "termination_reason",
400
+ existing_type=sa.String(length=100),
401
+ type_=postgresql.ENUM(
402
+ "FAILED_TO_START_DUE_TO_NO_CAPACITY",
403
+ "INTERRUPTED_BY_NO_CAPACITY",
404
+ "INSTANCE_UNREACHABLE",
405
+ "WAITING_INSTANCE_LIMIT_EXCEEDED",
406
+ "WAITING_RUNNER_LIMIT_EXCEEDED",
407
+ "TERMINATED_BY_USER",
408
+ "VOLUME_ERROR",
409
+ "GATEWAY_ERROR",
410
+ "SCALED_DOWN",
411
+ "DONE_BY_RUNNER",
412
+ "ABORTED_BY_USER",
413
+ "TERMINATED_BY_SERVER",
414
+ "INACTIVITY_DURATION_EXCEEDED",
415
+ "TERMINATED_DUE_TO_UTILIZATION_POLICY",
416
+ "CONTAINER_EXITED_WITH_ERROR",
417
+ "PORTS_BINDING_FAILED",
418
+ "CREATING_CONTAINER_ERROR",
419
+ "EXECUTOR_ERROR",
420
+ "MAX_DURATION_EXCEEDED",
421
+ name="jobterminationreason",
422
+ ),
423
+ existing_nullable=True,
424
+ postgresql_using="termination_reason::VARCHAR::jobterminationreason",
425
+ )
426
+ batch_op.alter_column(
427
+ "status",
428
+ existing_type=sa.String(length=100),
429
+ type_=postgresql.ENUM(
430
+ "SUBMITTED",
431
+ "PROVISIONING",
432
+ "PULLING",
433
+ "RUNNING",
434
+ "TERMINATING",
435
+ "TERMINATED",
436
+ "ABORTED",
437
+ "FAILED",
438
+ "DONE",
439
+ name="jobstatus",
440
+ ),
441
+ existing_nullable=False,
442
+ postgresql_using="status::VARCHAR::jobstatus",
443
+ )
444
+
445
+ with op.batch_alter_table("instances", schema=None) as batch_op:
446
+ batch_op.alter_column(
447
+ "status",
448
+ existing_type=sa.String(length=100),
449
+ type_=postgresql.ENUM(
450
+ "PENDING",
451
+ "PROVISIONING",
452
+ "IDLE",
453
+ "BUSY",
454
+ "TERMINATING",
455
+ "TERMINATED",
456
+ name="instancestatus",
457
+ ),
458
+ existing_nullable=False,
459
+ postgresql_using="status::VARCHAR::instancestatus",
460
+ )
461
+
462
+ with op.batch_alter_table("gateways", schema=None) as batch_op:
463
+ batch_op.alter_column(
464
+ "status",
465
+ existing_type=sa.String(length=100),
466
+ type_=postgresql.ENUM(
467
+ "SUBMITTED", "PROVISIONING", "RUNNING", "FAILED", name="gatewaystatus"
468
+ ),
469
+ existing_nullable=False,
470
+ postgresql_using="status::VARCHAR::gatewaystatus",
471
+ )
472
+
473
+ with op.batch_alter_table("fleets", schema=None) as batch_op:
474
+ batch_op.alter_column(
475
+ "status",
476
+ existing_type=sa.String(length=100),
477
+ type_=postgresql.ENUM(
478
+ "SUBMITTED", "ACTIVE", "TERMINATING", "TERMINATED", "FAILED", name="fleetstatus"
479
+ ),
480
+ existing_nullable=False,
481
+ postgresql_using="status::VARCHAR::fleetstatus",
482
+ )
483
+
484
+ # ### end Alembic commands ###
@@ -7,7 +7,6 @@ from sqlalchemy import (
7
7
  BigInteger,
8
8
  Boolean,
9
9
  DateTime,
10
- Enum,
11
10
  Float,
12
11
  ForeignKey,
13
12
  Index,
@@ -186,7 +185,7 @@ class UserModel(BaseModel):
186
185
  token: Mapped[DecryptedString] = mapped_column(EncryptedString(200), unique=True)
187
186
  # token_hash is needed for fast search by token when stored token is encrypted
188
187
  token_hash: Mapped[str] = mapped_column(String(2000), unique=True)
189
- global_role: Mapped[GlobalRole] = mapped_column(Enum(GlobalRole))
188
+ global_role: Mapped[GlobalRole] = mapped_column(EnumAsString(GlobalRole, 100))
190
189
  # deactivated users cannot access API
191
190
  active: Mapped[bool] = mapped_column(Boolean, default=True)
192
191
 
@@ -247,7 +246,7 @@ class MemberModel(BaseModel):
247
246
  project: Mapped["ProjectModel"] = relationship()
248
247
  user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
249
248
  user: Mapped[UserModel] = relationship(lazy="joined")
250
- project_role: Mapped[ProjectRole] = mapped_column(Enum(ProjectRole))
249
+ project_role: Mapped[ProjectRole] = mapped_column(EnumAsString(ProjectRole, 100))
251
250
  # member_num defines members ordering
252
251
  member_num: Mapped[Optional[int]] = mapped_column(Integer)
253
252
 
@@ -279,7 +278,7 @@ class RepoModel(BaseModel):
279
278
  project: Mapped["ProjectModel"] = relationship()
280
279
  # RepoModel.name stores repo_id
281
280
  name: Mapped[str] = mapped_column(String(100))
282
- type: Mapped[RepoType] = mapped_column(Enum(RepoType))
281
+ type: Mapped[RepoType] = mapped_column(EnumAsString(RepoType, 100))
283
282
 
284
283
  info: Mapped[str] = mapped_column(Text)
285
284
 
@@ -360,9 +359,9 @@ class RunModel(BaseModel):
360
359
  submitted_at: Mapped[datetime] = mapped_column(NaiveDateTime)
361
360
  last_processed_at: Mapped[datetime] = mapped_column(NaiveDateTime)
362
361
  next_triggered_at: Mapped[Optional[datetime]] = mapped_column(NaiveDateTime)
363
- status: Mapped[RunStatus] = mapped_column(Enum(RunStatus), index=True)
362
+ status: Mapped[RunStatus] = mapped_column(EnumAsString(RunStatus, 100), index=True)
364
363
  termination_reason: Mapped[Optional[RunTerminationReason]] = mapped_column(
365
- Enum(RunTerminationReason)
364
+ EnumAsString(RunTerminationReason, 100)
366
365
  )
367
366
  # resubmission_attempt counts consecutive transitions to pending without provisioning.
368
367
  # Can be used to choose retry delay depending on the attempt number.
@@ -401,9 +400,9 @@ class JobModel(BaseModel):
401
400
  submission_num: Mapped[int] = mapped_column(Integer)
402
401
  submitted_at: Mapped[datetime] = mapped_column(NaiveDateTime)
403
402
  last_processed_at: Mapped[datetime] = mapped_column(NaiveDateTime)
404
- status: Mapped[JobStatus] = mapped_column(Enum(JobStatus), index=True)
403
+ status: Mapped[JobStatus] = mapped_column(EnumAsString(JobStatus, 100), index=True)
405
404
  termination_reason: Mapped[Optional[JobTerminationReason]] = mapped_column(
406
- Enum(JobTerminationReason)
405
+ EnumAsString(JobTerminationReason, 100)
407
406
  )
408
407
  termination_reason_message: Mapped[Optional[str]] = mapped_column(Text)
409
408
  # `disconnected_at` stores the first time of connectivity issues with the instance.
@@ -446,7 +445,7 @@ class GatewayModel(BaseModel):
446
445
  # Use `get_gateway_configuration` to construct `configuration` for old gateways.
447
446
  configuration: Mapped[Optional[str]] = mapped_column(Text)
448
447
  created_at: Mapped[datetime] = mapped_column(NaiveDateTime, default=get_current_datetime)
449
- status: Mapped[GatewayStatus] = mapped_column(Enum(GatewayStatus))
448
+ status: Mapped[GatewayStatus] = mapped_column(EnumAsString(GatewayStatus, 100))
450
449
  status_message: Mapped[Optional[str]] = mapped_column(Text)
451
450
  last_processed_at: Mapped[datetime] = mapped_column(NaiveDateTime)
452
451
 
@@ -532,7 +531,7 @@ class FleetModel(BaseModel):
532
531
  deleted: Mapped[bool] = mapped_column(Boolean, default=False)
533
532
  deleted_at: Mapped[Optional[datetime]] = mapped_column(NaiveDateTime)
534
533
 
535
- status: Mapped[FleetStatus] = mapped_column(Enum(FleetStatus), index=True)
534
+ status: Mapped[FleetStatus] = mapped_column(EnumAsString(FleetStatus, 100), index=True)
536
535
  status_message: Mapped[Optional[str]] = mapped_column(Text)
537
536
 
538
537
  spec: Mapped[str] = mapped_column(Text)
@@ -571,7 +570,7 @@ class InstanceModel(BaseModel):
571
570
  fleet_id: Mapped[Optional[uuid.UUID]] = mapped_column(ForeignKey("fleets.id"))
572
571
  fleet: Mapped[Optional["FleetModel"]] = relationship(back_populates="instances")
573
572
 
574
- status: Mapped[InstanceStatus] = mapped_column(Enum(InstanceStatus), index=True)
573
+ status: Mapped[InstanceStatus] = mapped_column(EnumAsString(InstanceStatus, 100), index=True)
575
574
  unreachable: Mapped[bool] = mapped_column(Boolean)
576
575
 
577
576
  # VM
@@ -672,7 +671,7 @@ class VolumeModel(BaseModel):
672
671
  deleted: Mapped[bool] = mapped_column(Boolean, default=False)
673
672
  deleted_at: Mapped[Optional[datetime]] = mapped_column(NaiveDateTime)
674
673
 
675
- status: Mapped[VolumeStatus] = mapped_column(Enum(VolumeStatus), index=True)
674
+ status: Mapped[VolumeStatus] = mapped_column(EnumAsString(VolumeStatus, 100), index=True)
676
675
  status_message: Mapped[Optional[str]] = mapped_column(Text)
677
676
 
678
677
  configuration: Mapped[str] = mapped_column(Text)
@@ -152,7 +152,9 @@ def job_model_to_job_submission(
152
152
  inactivity_secs=job_model.inactivity_secs,
153
153
  status=job_model.status,
154
154
  status_message=status_message,
155
- termination_reason=job_model.termination_reason,
155
+ termination_reason=job_model.termination_reason.value
156
+ if job_model.termination_reason
157
+ else None,
156
158
  termination_reason_message=job_model.termination_reason_message,
157
159
  exit_status=job_model.exit_status,
158
160
  job_provisioning_data=job_provisioning_data,
@@ -129,7 +129,7 @@ async def create_repo(
129
129
  repo = RepoModel(
130
130
  project_id=project.id,
131
131
  name=repo_id,
132
- type=repo_info.repo_type,
132
+ type=RepoType(repo_info.repo_type),
133
133
  info=repo_info.json(),
134
134
  )
135
135
  try:
@@ -714,7 +714,9 @@ def run_model_to_run(
714
714
  last_processed_at=run_model.last_processed_at,
715
715
  status=run_model.status,
716
716
  status_message=status_message,
717
- termination_reason=run_model.termination_reason,
717
+ termination_reason=run_model.termination_reason.value
718
+ if run_model.termination_reason
719
+ else None,
718
720
  run_spec=run_spec,
719
721
  jobs=jobs,
720
722
  latest_job_submission=latest_job_submission,