dbos 1.7.0a5__py3-none-any.whl → 1.8.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 dbos might be problematic. Click here for more details.

dbos/_admin_server.py CHANGED
@@ -343,6 +343,8 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
343
343
  offset=filters.get("offset"),
344
344
  sort_desc=filters.get("sort_desc", False),
345
345
  workflow_id_prefix=filters.get("workflow_id_prefix"),
346
+ load_input=filters.get("load_input", False),
347
+ load_output=filters.get("load_output", False),
346
348
  )
347
349
  workflows_output = [
348
350
  conductor_protocol.WorkflowsOutput.from_workflow_information(i)
@@ -367,6 +369,7 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
367
369
  offset=filters.get("offset"),
368
370
  queue_name=filters.get("queue_name"),
369
371
  sort_desc=filters.get("sort_desc", False),
372
+ load_input=filters.get("load_input", False),
370
373
  )
371
374
  workflows_output = [
372
375
  conductor_protocol.WorkflowsOutput.from_workflow_information(i)
dbos/_client.py CHANGED
@@ -13,7 +13,7 @@ else:
13
13
 
14
14
  from dbos import _serialization
15
15
  from dbos._dbos import WorkflowHandle, WorkflowHandleAsync
16
- from dbos._dbos_config import is_valid_database_url
16
+ from dbos._dbos_config import get_system_database_url, is_valid_database_url
17
17
  from dbos._error import DBOSException, DBOSNonExistentWorkflowError
18
18
  from dbos._registrations import DEFAULT_MAX_RECOVERY_ATTEMPTS
19
19
  from dbos._serialization import WorkflowInputs
@@ -97,17 +97,28 @@ class WorkflowHandleClientAsyncPolling(Generic[R]):
97
97
 
98
98
 
99
99
  class DBOSClient:
100
- def __init__(self, database_url: str, *, system_database: Optional[str] = None):
100
+ def __init__(
101
+ self,
102
+ database_url: str,
103
+ *,
104
+ system_database_url: Optional[str] = None,
105
+ system_database: Optional[str] = None,
106
+ ):
101
107
  assert is_valid_database_url(database_url)
102
108
  # We only create database connections but do not run migrations
103
109
  self._sys_db = SystemDatabase(
104
- database_url=database_url,
110
+ system_database_url=get_system_database_url(
111
+ {
112
+ "system_database_url": system_database_url,
113
+ "database_url": database_url,
114
+ "database": {"sys_db_name": system_database},
115
+ }
116
+ ),
105
117
  engine_kwargs={
106
118
  "pool_timeout": 30,
107
119
  "max_overflow": 0,
108
120
  "pool_size": 2,
109
121
  },
110
- sys_db_name=system_database,
111
122
  )
112
123
  self._sys_db.check_connection()
113
124
  self._app_db = ApplicationDatabase(
@@ -294,6 +305,8 @@ class DBOSClient:
294
305
  offset: Optional[int] = None,
295
306
  sort_desc: bool = False,
296
307
  workflow_id_prefix: Optional[str] = None,
308
+ load_input: bool = True,
309
+ load_output: bool = True,
297
310
  ) -> List[WorkflowStatus]:
298
311
  return list_workflows(
299
312
  self._sys_db,
@@ -308,6 +321,8 @@ class DBOSClient:
308
321
  offset=offset,
309
322
  sort_desc=sort_desc,
310
323
  workflow_id_prefix=workflow_id_prefix,
324
+ load_input=load_input,
325
+ load_output=load_output,
311
326
  )
312
327
 
313
328
  async def list_workflows_async(
@@ -324,6 +339,8 @@ class DBOSClient:
324
339
  offset: Optional[int] = None,
325
340
  sort_desc: bool = False,
326
341
  workflow_id_prefix: Optional[str] = None,
342
+ load_input: bool = True,
343
+ load_output: bool = True,
327
344
  ) -> List[WorkflowStatus]:
328
345
  return await asyncio.to_thread(
329
346
  self.list_workflows,
@@ -338,6 +355,8 @@ class DBOSClient:
338
355
  offset=offset,
339
356
  sort_desc=sort_desc,
340
357
  workflow_id_prefix=workflow_id_prefix,
358
+ load_input=load_input,
359
+ load_output=load_output,
341
360
  )
342
361
 
343
362
  def list_queued_workflows(
@@ -351,6 +370,7 @@ class DBOSClient:
351
370
  limit: Optional[int] = None,
352
371
  offset: Optional[int] = None,
353
372
  sort_desc: bool = False,
373
+ load_input: bool = True,
354
374
  ) -> List[WorkflowStatus]:
355
375
  return list_queued_workflows(
356
376
  self._sys_db,
@@ -362,6 +382,7 @@ class DBOSClient:
362
382
  limit=limit,
363
383
  offset=offset,
364
384
  sort_desc=sort_desc,
385
+ load_input=load_input,
365
386
  )
366
387
 
367
388
  async def list_queued_workflows_async(
@@ -375,6 +396,7 @@ class DBOSClient:
375
396
  limit: Optional[int] = None,
376
397
  offset: Optional[int] = None,
377
398
  sort_desc: bool = False,
399
+ load_input: bool = True,
378
400
  ) -> List[WorkflowStatus]:
379
401
  return await asyncio.to_thread(
380
402
  self.list_queued_workflows,
@@ -386,6 +408,7 @@ class DBOSClient:
386
408
  limit=limit,
387
409
  offset=offset,
388
410
  sort_desc=sort_desc,
411
+ load_input=load_input,
389
412
  )
390
413
 
391
414
  def list_workflow_steps(self, workflow_id: str) -> List[StepInfo]:
@@ -223,6 +223,8 @@ class ConductorWebsocket(threading.Thread):
223
223
  body = list_workflows_message.body
224
224
  infos = []
225
225
  try:
226
+ load_input = body.get("load_input", False)
227
+ load_output = body.get("load_output", False)
226
228
  infos = list_workflows(
227
229
  self.dbos._sys_db,
228
230
  workflow_ids=body["workflow_uuids"],
@@ -235,6 +237,8 @@ class ConductorWebsocket(threading.Thread):
235
237
  limit=body["limit"],
236
238
  offset=body["offset"],
237
239
  sort_desc=body["sort_desc"],
240
+ load_input=load_input,
241
+ load_output=load_output,
238
242
  )
239
243
  except Exception as e:
240
244
  error_message = f"Exception encountered when listing workflows: {traceback.format_exc()}"
@@ -257,6 +261,7 @@ class ConductorWebsocket(threading.Thread):
257
261
  q_body = list_queued_workflows_message.body
258
262
  infos = []
259
263
  try:
264
+ q_load_input = q_body.get("load_input", False)
260
265
  infos = list_queued_workflows(
261
266
  self.dbos._sys_db,
262
267
  start_time=q_body["start_time"],
@@ -267,6 +272,7 @@ class ConductorWebsocket(threading.Thread):
267
272
  offset=q_body["offset"],
268
273
  queue_name=q_body["queue_name"],
269
274
  sort_desc=q_body["sort_desc"],
275
+ load_input=q_load_input,
270
276
  )
271
277
  except Exception as e:
272
278
  error_message = f"Exception encountered when listing queued workflows: {traceback.format_exc()}"
@@ -110,7 +110,7 @@ class RestartResponse(BaseMessage):
110
110
  error_message: Optional[str] = None
111
111
 
112
112
 
113
- class ListWorkflowsBody(TypedDict):
113
+ class ListWorkflowsBody(TypedDict, total=False):
114
114
  workflow_uuids: List[str]
115
115
  workflow_name: Optional[str]
116
116
  authenticated_user: Optional[str]
@@ -121,6 +121,8 @@ class ListWorkflowsBody(TypedDict):
121
121
  limit: Optional[int]
122
122
  offset: Optional[int]
123
123
  sort_desc: bool
124
+ load_input: bool
125
+ load_output: bool
124
126
 
125
127
 
126
128
  @dataclass
@@ -209,7 +211,7 @@ class ListWorkflowsResponse(BaseMessage):
209
211
  error_message: Optional[str] = None
210
212
 
211
213
 
212
- class ListQueuedWorkflowsBody(TypedDict):
214
+ class ListQueuedWorkflowsBody(TypedDict, total=False):
213
215
  workflow_name: Optional[str]
214
216
  start_time: Optional[str]
215
217
  end_time: Optional[str]
@@ -218,6 +220,7 @@ class ListQueuedWorkflowsBody(TypedDict):
218
220
  limit: Optional[int]
219
221
  offset: Optional[int]
220
222
  sort_desc: bool
223
+ load_input: bool
221
224
 
222
225
 
223
226
  @dataclass
dbos/_context.py CHANGED
@@ -10,7 +10,7 @@ from enum import Enum
10
10
  from types import TracebackType
11
11
  from typing import List, Literal, Optional, Type, TypedDict
12
12
 
13
- from opentelemetry.trace import Span, Status, StatusCode
13
+ from opentelemetry.trace import Span, Status, StatusCode, use_span
14
14
  from sqlalchemy.orm import Session
15
15
 
16
16
  from dbos._utils import GlobalParams
@@ -68,6 +68,20 @@ class StepStatus:
68
68
  max_attempts: Optional[int]
69
69
 
70
70
 
71
+ @dataclass
72
+ class ContextSpan:
73
+ """
74
+ A span that is used to track the context of a workflow or step execution.
75
+
76
+ Attributes:
77
+ span: The OpenTelemetry span object.
78
+ context_manager: The context manager that is used to manage the span's lifecycle.
79
+ """
80
+
81
+ span: Span
82
+ context_manager: AbstractContextManager[Span]
83
+
84
+
71
85
  class DBOSContext:
72
86
  def __init__(self) -> None:
73
87
  self.executor_id = GlobalParams.executor_id
@@ -86,7 +100,7 @@ class DBOSContext:
86
100
  self.curr_step_function_id: int = -1
87
101
  self.curr_tx_function_id: int = -1
88
102
  self.sql_session: Optional[Session] = None
89
- self.spans: list[Span] = []
103
+ self.context_spans: list[ContextSpan] = []
90
104
 
91
105
  self.authenticated_user: Optional[str] = None
92
106
  self.authenticated_roles: Optional[List[str]] = None
@@ -202,8 +216,8 @@ class DBOSContext:
202
216
  self._end_span(exc_value)
203
217
 
204
218
  def get_current_span(self) -> Optional[Span]:
205
- if len(self.spans):
206
- return self.spans[-1]
219
+ if len(self.context_spans) > 0:
220
+ return self.context_spans[-1].span
207
221
  return None
208
222
 
209
223
  def _start_span(self, attributes: TracedAttributes) -> None:
@@ -218,27 +232,38 @@ class DBOSContext:
218
232
  )
219
233
  attributes["authenticatedUserAssumedRole"] = self.assumed_role
220
234
  span = dbos_tracer.start_span(
221
- attributes, parent=self.spans[-1] if len(self.spans) > 0 else None
235
+ attributes,
236
+ parent=self.context_spans[-1].span if len(self.context_spans) > 0 else None,
237
+ )
238
+ # Activate the current span
239
+ cm = use_span(
240
+ span,
241
+ end_on_exit=False,
242
+ record_exception=False,
243
+ set_status_on_exception=False,
222
244
  )
223
- self.spans.append(span)
245
+ self.context_spans.append(ContextSpan(span, cm))
246
+ cm.__enter__()
224
247
 
225
248
  def _end_span(self, exc_value: Optional[BaseException]) -> None:
249
+ context_span = self.context_spans.pop()
226
250
  if exc_value is None:
227
- self.spans[-1].set_status(Status(StatusCode.OK))
251
+ context_span.span.set_status(Status(StatusCode.OK))
228
252
  else:
229
- self.spans[-1].set_status(
253
+ context_span.span.set_status(
230
254
  Status(StatusCode.ERROR, description=str(exc_value))
231
255
  )
232
- dbos_tracer.end_span(self.spans.pop())
256
+ dbos_tracer.end_span(context_span.span)
257
+ context_span.context_manager.__exit__(None, None, None)
233
258
 
234
259
  def set_authentication(
235
260
  self, user: Optional[str], roles: Optional[List[str]]
236
261
  ) -> None:
237
262
  self.authenticated_user = user
238
263
  self.authenticated_roles = roles
239
- if user is not None and len(self.spans) > 0:
240
- self.spans[-1].set_attribute("authenticatedUser", user)
241
- self.spans[-1].set_attribute(
264
+ if user is not None and len(self.context_spans) > 0:
265
+ self.context_spans[-1].span.set_attribute("authenticatedUser", user)
266
+ self.context_spans[-1].span.set_attribute(
242
267
  "authenticatedUserRoles", json.dumps(roles) if roles is not None else ""
243
268
  )
244
269
 
dbos/_dbos.py CHANGED
@@ -91,6 +91,7 @@ from ._context import (
91
91
  from ._dbos_config import (
92
92
  ConfigFile,
93
93
  DBOSConfig,
94
+ get_system_database_url,
94
95
  overwrite_config,
95
96
  process_config,
96
97
  translate_dbos_config_to_config_file,
@@ -424,9 +425,8 @@ class DBOS:
424
425
  assert self._config["database_url"] is not None
425
426
  assert self._config["database"]["sys_db_engine_kwargs"] is not None
426
427
  self._sys_db_field = SystemDatabase(
427
- database_url=self._config["database_url"],
428
+ system_database_url=get_system_database_url(self._config),
428
429
  engine_kwargs=self._config["database"]["sys_db_engine_kwargs"],
429
- sys_db_name=self._config["database"]["sys_db_name"],
430
430
  debug_mode=debug_mode,
431
431
  )
432
432
  assert self._config["database"]["db_engine_kwargs"] is not None
@@ -966,6 +966,12 @@ class DBOS:
966
966
  fn, "DBOS.cancelWorkflow"
967
967
  )
968
968
 
969
+ @classmethod
970
+ async def cancel_workflow_async(cls, workflow_id: str) -> None:
971
+ """Cancel a workflow by ID."""
972
+ await cls._configure_asyncio_thread_pool()
973
+ await asyncio.to_thread(cls.cancel_workflow, workflow_id)
974
+
969
975
  @classmethod
970
976
  async def _configure_asyncio_thread_pool(cls) -> None:
971
977
  """
@@ -987,11 +993,23 @@ class DBOS:
987
993
  _get_dbos_instance()._sys_db.call_function_as_step(fn, "DBOS.resumeWorkflow")
988
994
  return cls.retrieve_workflow(workflow_id)
989
995
 
996
+ @classmethod
997
+ async def resume_workflow_async(cls, workflow_id: str) -> WorkflowHandleAsync[Any]:
998
+ """Resume a workflow by ID."""
999
+ await cls._configure_asyncio_thread_pool()
1000
+ await asyncio.to_thread(cls.resume_workflow, workflow_id)
1001
+ return await cls.retrieve_workflow_async(workflow_id)
1002
+
990
1003
  @classmethod
991
1004
  def restart_workflow(cls, workflow_id: str) -> WorkflowHandle[Any]:
992
1005
  """Restart a workflow with a new workflow ID"""
993
1006
  return cls.fork_workflow(workflow_id, 1)
994
1007
 
1008
+ @classmethod
1009
+ async def restart_workflow_async(cls, workflow_id: str) -> WorkflowHandleAsync[Any]:
1010
+ """Restart a workflow with a new workflow ID"""
1011
+ return await cls.fork_workflow_async(workflow_id, 1)
1012
+
995
1013
  @classmethod
996
1014
  def fork_workflow(
997
1015
  cls,
@@ -1017,6 +1035,23 @@ class DBOS:
1017
1035
  )
1018
1036
  return cls.retrieve_workflow(new_id)
1019
1037
 
1038
+ @classmethod
1039
+ async def fork_workflow_async(
1040
+ cls,
1041
+ workflow_id: str,
1042
+ start_step: int,
1043
+ *,
1044
+ application_version: Optional[str] = None,
1045
+ ) -> WorkflowHandleAsync[Any]:
1046
+ """Restart a workflow with a new workflow ID from a specific step"""
1047
+ await cls._configure_asyncio_thread_pool()
1048
+ new_id = await asyncio.to_thread(
1049
+ lambda: cls.fork_workflow(
1050
+ workflow_id, start_step, application_version=application_version
1051
+ ).get_workflow_id()
1052
+ )
1053
+ return await cls.retrieve_workflow_async(new_id)
1054
+
1020
1055
  @classmethod
1021
1056
  def list_workflows(
1022
1057
  cls,
@@ -1032,6 +1067,8 @@ class DBOS:
1032
1067
  offset: Optional[int] = None,
1033
1068
  sort_desc: bool = False,
1034
1069
  workflow_id_prefix: Optional[str] = None,
1070
+ load_input: bool = True,
1071
+ load_output: bool = True,
1035
1072
  ) -> List[WorkflowStatus]:
1036
1073
  def fn() -> List[WorkflowStatus]:
1037
1074
  return list_workflows(
@@ -1047,12 +1084,50 @@ class DBOS:
1047
1084
  offset=offset,
1048
1085
  sort_desc=sort_desc,
1049
1086
  workflow_id_prefix=workflow_id_prefix,
1087
+ load_input=load_input,
1088
+ load_output=load_output,
1050
1089
  )
1051
1090
 
1052
1091
  return _get_dbos_instance()._sys_db.call_function_as_step(
1053
1092
  fn, "DBOS.listWorkflows"
1054
1093
  )
1055
1094
 
1095
+ @classmethod
1096
+ async def list_workflows_async(
1097
+ cls,
1098
+ *,
1099
+ workflow_ids: Optional[List[str]] = None,
1100
+ status: Optional[Union[str, List[str]]] = None,
1101
+ start_time: Optional[str] = None,
1102
+ end_time: Optional[str] = None,
1103
+ name: Optional[str] = None,
1104
+ app_version: Optional[str] = None,
1105
+ user: Optional[str] = None,
1106
+ limit: Optional[int] = None,
1107
+ offset: Optional[int] = None,
1108
+ sort_desc: bool = False,
1109
+ workflow_id_prefix: Optional[str] = None,
1110
+ load_input: bool = True,
1111
+ load_output: bool = True,
1112
+ ) -> List[WorkflowStatus]:
1113
+ await cls._configure_asyncio_thread_pool()
1114
+ return await asyncio.to_thread(
1115
+ cls.list_workflows,
1116
+ workflow_ids=workflow_ids,
1117
+ status=status,
1118
+ start_time=start_time,
1119
+ end_time=end_time,
1120
+ name=name,
1121
+ app_version=app_version,
1122
+ user=user,
1123
+ limit=limit,
1124
+ offset=offset,
1125
+ sort_desc=sort_desc,
1126
+ workflow_id_prefix=workflow_id_prefix,
1127
+ load_input=load_input,
1128
+ load_output=load_output,
1129
+ )
1130
+
1056
1131
  @classmethod
1057
1132
  def list_queued_workflows(
1058
1133
  cls,
@@ -1065,6 +1140,7 @@ class DBOS:
1065
1140
  limit: Optional[int] = None,
1066
1141
  offset: Optional[int] = None,
1067
1142
  sort_desc: bool = False,
1143
+ load_input: bool = True,
1068
1144
  ) -> List[WorkflowStatus]:
1069
1145
  def fn() -> List[WorkflowStatus]:
1070
1146
  return list_queued_workflows(
@@ -1077,12 +1153,41 @@ class DBOS:
1077
1153
  limit=limit,
1078
1154
  offset=offset,
1079
1155
  sort_desc=sort_desc,
1156
+ load_input=load_input,
1080
1157
  )
1081
1158
 
1082
1159
  return _get_dbos_instance()._sys_db.call_function_as_step(
1083
1160
  fn, "DBOS.listQueuedWorkflows"
1084
1161
  )
1085
1162
 
1163
+ @classmethod
1164
+ async def list_queued_workflows_async(
1165
+ cls,
1166
+ *,
1167
+ queue_name: Optional[str] = None,
1168
+ status: Optional[Union[str, List[str]]] = None,
1169
+ start_time: Optional[str] = None,
1170
+ end_time: Optional[str] = None,
1171
+ name: Optional[str] = None,
1172
+ limit: Optional[int] = None,
1173
+ offset: Optional[int] = None,
1174
+ sort_desc: bool = False,
1175
+ load_input: bool = True,
1176
+ ) -> List[WorkflowStatus]:
1177
+ await cls._configure_asyncio_thread_pool()
1178
+ return await asyncio.to_thread(
1179
+ cls.list_queued_workflows,
1180
+ queue_name=queue_name,
1181
+ status=status,
1182
+ start_time=start_time,
1183
+ end_time=end_time,
1184
+ name=name,
1185
+ limit=limit,
1186
+ offset=offset,
1187
+ sort_desc=sort_desc,
1188
+ load_input=load_input,
1189
+ )
1190
+
1086
1191
  @classmethod
1087
1192
  def list_workflow_steps(cls, workflow_id: str) -> List[StepInfo]:
1088
1193
  def fn() -> List[StepInfo]:
@@ -1094,6 +1199,11 @@ class DBOS:
1094
1199
  fn, "DBOS.listWorkflowSteps"
1095
1200
  )
1096
1201
 
1202
+ @classmethod
1203
+ async def list_workflow_steps_async(cls, workflow_id: str) -> List[StepInfo]:
1204
+ await cls._configure_asyncio_thread_pool()
1205
+ return await asyncio.to_thread(cls.list_workflow_steps, workflow_id)
1206
+
1097
1207
  @classproperty
1098
1208
  def logger(cls) -> Logger:
1099
1209
  """Return the DBOS `Logger` for the current context."""
@@ -1266,31 +1376,3 @@ class DBOSConfiguredInstance:
1266
1376
  def __init__(self, config_name: str) -> None:
1267
1377
  self.config_name = config_name
1268
1378
  DBOS.register_instance(self)
1269
-
1270
-
1271
- # Apps that import DBOS probably don't exit. If they do, let's see if
1272
- # it looks like startup was abandoned or a call was forgotten...
1273
- def _dbos_exit_hook() -> None:
1274
- if _dbos_global_registry is None:
1275
- # Probably used as or for a support module
1276
- return
1277
- if _dbos_global_instance is None:
1278
- print("DBOS exiting; functions were registered but DBOS() was not called")
1279
- dbos_logger.warning(
1280
- "DBOS exiting; functions were registered but DBOS() was not called"
1281
- )
1282
- return
1283
- if not _dbos_global_instance._launched:
1284
- if _dbos_global_instance.fastapi is not None:
1285
- # FastAPI lifespan middleware will call launch/destroy, so we can ignore this.
1286
- # This is likely to happen during fastapi dev runs, where the reloader loads the module multiple times.
1287
- return
1288
- print("DBOS exiting; DBOS exists but launch() was not called")
1289
- dbos_logger.warning("DBOS exiting; DBOS exists but launch() was not called")
1290
- return
1291
- # If we get here, we're exiting normally
1292
- _dbos_global_instance.destroy()
1293
-
1294
-
1295
- # Register the exit hook
1296
- atexit.register(_dbos_exit_hook)
dbos/_dbos_config.py CHANGED
@@ -23,7 +23,8 @@ class DBOSConfig(TypedDict, total=False):
23
23
  Attributes:
24
24
  name (str): Application name
25
25
  database_url (str): Database connection string
26
- sys_db_name (str): System database name
26
+ system_database_url (str): Connection string for the system database (if different from the application database)
27
+ sys_db_name (str): System database name (deprecated)
27
28
  sys_db_pool_size (int): System database pool size
28
29
  db_engine_kwargs (Dict[str, Any]): SQLAlchemy engine kwargs (See https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine)
29
30
  log_level (str): Log level
@@ -36,6 +37,7 @@ class DBOSConfig(TypedDict, total=False):
36
37
 
37
38
  name: str
38
39
  database_url: Optional[str]
40
+ system_database_url: Optional[str]
39
41
  sys_db_name: Optional[str]
40
42
  sys_db_pool_size: Optional[int]
41
43
  db_engine_kwargs: Optional[Dict[str, Any]]
@@ -111,6 +113,7 @@ class ConfigFile(TypedDict, total=False):
111
113
  runtimeConfig: RuntimeConfig
112
114
  database: DatabaseConfig
113
115
  database_url: Optional[str]
116
+ system_database_url: Optional[str]
114
117
  telemetry: Optional[TelemetryConfig]
115
118
  env: Dict[str, str]
116
119
 
@@ -136,6 +139,8 @@ def translate_dbos_config_to_config_file(config: DBOSConfig) -> ConfigFile:
136
139
 
137
140
  if "database_url" in config:
138
141
  translated_config["database_url"] = config.get("database_url")
142
+ if "system_database_url" in config:
143
+ translated_config["system_database_url"] = config.get("system_database_url")
139
144
 
140
145
  # Runtime config
141
146
  translated_config["runtimeConfig"] = {"run_admin_server": True}
@@ -488,6 +493,8 @@ def overwrite_config(provided_config: ConfigFile) -> ConfigFile:
488
493
  "DBOS_DATABASE_URL environment variable is not set. This is required to connect to the database."
489
494
  )
490
495
  provided_config["database_url"] = db_url
496
+ if "system_database_url" in provided_config:
497
+ del provided_config["system_database_url"]
491
498
 
492
499
  # Telemetry config
493
500
  if "telemetry" not in provided_config or provided_config["telemetry"] is None:
@@ -537,3 +544,19 @@ def overwrite_config(provided_config: ConfigFile) -> ConfigFile:
537
544
  del provided_config["env"]
538
545
 
539
546
  return provided_config
547
+
548
+
549
+ def get_system_database_url(config: ConfigFile) -> str:
550
+ if "system_database_url" in config and config["system_database_url"] is not None:
551
+ return config["system_database_url"]
552
+ else:
553
+ assert config["database_url"] is not None
554
+ app_db_url = make_url(config["database_url"])
555
+ if config["database"].get("sys_db_name") is not None:
556
+ sys_db_name = config["database"]["sys_db_name"]
557
+ else:
558
+ assert app_db_url.database is not None
559
+ sys_db_name = app_db_url.database + SystemSchema.sysdb_suffix
560
+ return app_db_url.set(database=sys_db_name).render_as_string(
561
+ hide_password=False
562
+ )
dbos/_error.py CHANGED
@@ -55,7 +55,7 @@ class DBOSErrorCode(Enum):
55
55
  InitializationError = 3
56
56
  WorkflowFunctionNotFound = 4
57
57
  NonExistentWorkflowError = 5
58
- DeadLetterQueueError = 6
58
+ MaxRecoveryAttemptsExceeded = 6
59
59
  MaxStepRetriesExceeded = 7
60
60
  NotAuthorized = 8
61
61
  ConflictingWorkflowError = 9
@@ -121,13 +121,13 @@ class DBOSNonExistentWorkflowError(DBOSException):
121
121
  )
122
122
 
123
123
 
124
- class DBOSDeadLetterQueueError(DBOSException):
125
- """Exception raised when a workflow database record does not exist for a given ID."""
124
+ class MaxRecoveryAttemptsExceededError(DBOSException):
125
+ """Exception raised when a workflow exceeds its max recovery attempts."""
126
126
 
127
127
  def __init__(self, wf_id: str, max_retries: int):
128
128
  super().__init__(
129
- f"Workflow {wf_id} has been moved to the dead-letter queue after exceeding the maximum of ${max_retries} retries",
130
- dbos_error_code=DBOSErrorCode.DeadLetterQueueError.value,
129
+ f"Workflow {wf_id} has exceeded its maximum of {max_retries} execution or recovery attempts. Further attempts to execute or recover it will fail. See documentation for details: https://docs.dbos.dev/python/reference/decorators",
130
+ dbos_error_code=DBOSErrorCode.MaxRecoveryAttemptsExceeded.value,
131
131
  )
132
132
 
133
133
 
dbos/_sys_db.py CHANGED
@@ -37,12 +37,12 @@ from ._context import get_local_dbos_context
37
37
  from ._error import (
38
38
  DBOSAwaitedWorkflowCancelledError,
39
39
  DBOSConflictingWorkflowError,
40
- DBOSDeadLetterQueueError,
41
40
  DBOSNonExistentWorkflowError,
42
41
  DBOSQueueDeduplicatedError,
43
42
  DBOSUnexpectedStepError,
44
43
  DBOSWorkflowCancelledError,
45
44
  DBOSWorkflowConflictIDError,
45
+ MaxRecoveryAttemptsExceededError,
46
46
  )
47
47
  from ._logger import dbos_logger
48
48
  from ._schemas.system_database import SystemSchema
@@ -57,20 +57,25 @@ class WorkflowStatusString(Enum):
57
57
  PENDING = "PENDING"
58
58
  SUCCESS = "SUCCESS"
59
59
  ERROR = "ERROR"
60
- RETRIES_EXCEEDED = "RETRIES_EXCEEDED"
60
+ MAX_RECOVERY_ATTEMPTS_EXCEEDED = "MAX_RECOVERY_ATTEMPTS_EXCEEDED"
61
61
  CANCELLED = "CANCELLED"
62
62
  ENQUEUED = "ENQUEUED"
63
63
 
64
64
 
65
65
  WorkflowStatuses = Literal[
66
- "PENDING", "SUCCESS", "ERROR", "RETRIES_EXCEEDED", "CANCELLED", "ENQUEUED"
66
+ "PENDING",
67
+ "SUCCESS",
68
+ "ERROR",
69
+ "MAX_RECOVERY_ATTEMPTS_EXCEEDED",
70
+ "CANCELLED",
71
+ "ENQUEUED",
67
72
  ]
68
73
 
69
74
 
70
75
  class WorkflowStatus:
71
76
  # The workflow ID
72
77
  workflow_id: str
73
- # The workflow status. Must be one of ENQUEUED, PENDING, SUCCESS, ERROR, CANCELLED, or RETRIES_EXCEEDED
78
+ # The workflow status. Must be one of ENQUEUED, PENDING, SUCCESS, ERROR, CANCELLED, or MAX_RECOVERY_ATTEMPTS_EXCEEDED
74
79
  status: str
75
80
  # The name of the workflow function
76
81
  name: str
@@ -331,22 +336,15 @@ class SystemDatabase:
331
336
  def __init__(
332
337
  self,
333
338
  *,
334
- database_url: str,
339
+ system_database_url: str,
335
340
  engine_kwargs: Dict[str, Any],
336
- sys_db_name: Optional[str] = None,
337
341
  debug_mode: bool = False,
338
342
  ):
339
343
  # Set driver
340
- system_db_url = sa.make_url(database_url).set(drivername="postgresql+psycopg")
341
- # Resolve system database name
342
- sysdb_name = sys_db_name
343
- if not sysdb_name:
344
- assert system_db_url.database is not None
345
- sysdb_name = system_db_url.database + SystemSchema.sysdb_suffix
346
- system_db_url = system_db_url.set(database=sysdb_name)
344
+ url = sa.make_url(system_database_url).set(drivername="postgresql+psycopg")
347
345
 
348
346
  self.engine = sa.create_engine(
349
- system_db_url,
347
+ url,
350
348
  **engine_kwargs,
351
349
  )
352
350
  self._engine_kwargs = engine_kwargs
@@ -437,7 +435,14 @@ class SystemDatabase:
437
435
 
438
436
  # Values to update when a row already exists for this workflow
439
437
  update_values: dict[str, Any] = {
440
- "recovery_attempts": SystemSchema.workflow_status.c.recovery_attempts + 1,
438
+ "recovery_attempts": sa.case(
439
+ (
440
+ SystemSchema.workflow_status.c.status
441
+ != WorkflowStatusString.ENQUEUED.value,
442
+ SystemSchema.workflow_status.c.recovery_attempts + 1,
443
+ ),
444
+ else_=SystemSchema.workflow_status.c.recovery_attempts,
445
+ ),
441
446
  "updated_at": func.extract("epoch", func.now()) * 1000,
442
447
  }
443
448
  # Don't update an existing executor ID when enqueueing a workflow.
@@ -515,7 +520,7 @@ class SystemDatabase:
515
520
  raise DBOSConflictingWorkflowError(status["workflow_uuid"], err_msg)
516
521
 
517
522
  # Every time we start executing a workflow (and thus attempt to insert its status), we increment `recovery_attempts` by 1.
518
- # When this number becomes equal to `maxRetries + 1`, we mark the workflow as `RETRIES_EXCEEDED`.
523
+ # When this number becomes equal to `maxRetries + 1`, we mark the workflow as `MAX_RECOVERY_ATTEMPTS_EXCEEDED`.
519
524
  if (
520
525
  (wf_status != "SUCCESS" and wf_status != "ERROR")
521
526
  and max_recovery_attempts is not None
@@ -532,7 +537,7 @@ class SystemDatabase:
532
537
  == WorkflowStatusString.PENDING.value
533
538
  )
534
539
  .values(
535
- status=WorkflowStatusString.RETRIES_EXCEEDED.value,
540
+ status=WorkflowStatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED.value,
536
541
  deduplication_id=None,
537
542
  started_at_epoch_ms=None,
538
543
  queue_name=None,
@@ -541,7 +546,7 @@ class SystemDatabase:
541
546
  conn.execute(dlq_cmd)
542
547
  # Need to commit here because we're throwing an exception
543
548
  conn.commit()
544
- raise DBOSDeadLetterQueueError(
549
+ raise MaxRecoveryAttemptsExceededError(
545
550
  status["workflow_uuid"], max_recovery_attempts
546
551
  )
547
552
 
@@ -788,11 +793,17 @@ class SystemDatabase:
788
793
  pass # CB: I guess we're assuming the WF will show up eventually.
789
794
  time.sleep(1)
790
795
 
791
- def get_workflows(self, input: GetWorkflowsInput) -> List[WorkflowStatus]:
796
+ def get_workflows(
797
+ self,
798
+ input: GetWorkflowsInput,
799
+ *,
800
+ load_input: bool = True,
801
+ load_output: bool = True,
802
+ ) -> List[WorkflowStatus]:
792
803
  """
793
804
  Retrieve a list of workflows result and inputs based on the input criteria. The result is a list of external-facing workflow status objects.
794
805
  """
795
- query = sa.select(
806
+ load_columns = [
796
807
  SystemSchema.workflow_status.c.workflow_uuid,
797
808
  SystemSchema.workflow_status.c.status,
798
809
  SystemSchema.workflow_status.c.name,
@@ -808,12 +819,16 @@ class SystemDatabase:
808
819
  SystemSchema.workflow_status.c.updated_at,
809
820
  SystemSchema.workflow_status.c.application_version,
810
821
  SystemSchema.workflow_status.c.application_id,
811
- SystemSchema.workflow_status.c.inputs,
812
- SystemSchema.workflow_status.c.output,
813
- SystemSchema.workflow_status.c.error,
814
822
  SystemSchema.workflow_status.c.workflow_deadline_epoch_ms,
815
823
  SystemSchema.workflow_status.c.workflow_timeout_ms,
816
- )
824
+ ]
825
+ if load_input:
826
+ load_columns.append(SystemSchema.workflow_status.c.inputs)
827
+ if load_output:
828
+ load_columns.append(SystemSchema.workflow_status.c.output)
829
+ load_columns.append(SystemSchema.workflow_status.c.error)
830
+
831
+ query = sa.select(*load_columns)
817
832
  if input.sort_desc:
818
833
  query = query.order_by(SystemSchema.workflow_status.c.created_at.desc())
819
834
  else:
@@ -880,29 +895,35 @@ class SystemDatabase:
880
895
  info.updated_at = row[12]
881
896
  info.app_version = row[13]
882
897
  info.app_id = row[14]
898
+ info.workflow_deadline_epoch_ms = row[15]
899
+ info.workflow_timeout_ms = row[16]
883
900
 
901
+ raw_input = row[17] if load_input else None
902
+ raw_output = row[18] if load_output else None
903
+ raw_error = row[19] if load_output else None
884
904
  inputs, output, exception = _serialization.safe_deserialize(
885
905
  info.workflow_id,
886
- serialized_input=row[15],
887
- serialized_output=row[16],
888
- serialized_exception=row[17],
906
+ serialized_input=raw_input,
907
+ serialized_output=raw_output,
908
+ serialized_exception=raw_error,
889
909
  )
890
910
  info.input = inputs
891
911
  info.output = output
892
912
  info.error = exception
893
- info.workflow_deadline_epoch_ms = row[18]
894
- info.workflow_timeout_ms = row[19]
895
913
 
896
914
  infos.append(info)
897
915
  return infos
898
916
 
899
917
  def get_queued_workflows(
900
- self, input: GetQueuedWorkflowsInput
918
+ self,
919
+ input: GetQueuedWorkflowsInput,
920
+ *,
921
+ load_input: bool = True,
901
922
  ) -> List[WorkflowStatus]:
902
923
  """
903
924
  Retrieve a list of queued workflows result and inputs based on the input criteria. The result is a list of external-facing workflow status objects.
904
925
  """
905
- query = sa.select(
926
+ load_columns = [
906
927
  SystemSchema.workflow_status.c.workflow_uuid,
907
928
  SystemSchema.workflow_status.c.status,
908
929
  SystemSchema.workflow_status.c.name,
@@ -918,12 +939,13 @@ class SystemDatabase:
918
939
  SystemSchema.workflow_status.c.updated_at,
919
940
  SystemSchema.workflow_status.c.application_version,
920
941
  SystemSchema.workflow_status.c.application_id,
921
- SystemSchema.workflow_status.c.inputs,
922
- SystemSchema.workflow_status.c.output,
923
- SystemSchema.workflow_status.c.error,
924
942
  SystemSchema.workflow_status.c.workflow_deadline_epoch_ms,
925
943
  SystemSchema.workflow_status.c.workflow_timeout_ms,
926
- ).where(
944
+ ]
945
+ if load_input:
946
+ load_columns.append(SystemSchema.workflow_status.c.inputs)
947
+
948
+ query = sa.select(*load_columns).where(
927
949
  sa.and_(
928
950
  SystemSchema.workflow_status.c.queue_name.isnot(None),
929
951
  SystemSchema.workflow_status.c.status.in_(["ENQUEUED", "PENDING"]),
@@ -984,18 +1006,21 @@ class SystemDatabase:
984
1006
  info.updated_at = row[12]
985
1007
  info.app_version = row[13]
986
1008
  info.app_id = row[14]
1009
+ info.workflow_deadline_epoch_ms = row[15]
1010
+ info.workflow_timeout_ms = row[16]
1011
+
1012
+ raw_input = row[17] if load_input else None
987
1013
 
1014
+ # Error and Output are not loaded because they should always be None for queued workflows.
988
1015
  inputs, output, exception = _serialization.safe_deserialize(
989
1016
  info.workflow_id,
990
- serialized_input=row[15],
991
- serialized_output=row[16],
992
- serialized_exception=row[17],
1017
+ serialized_input=raw_input,
1018
+ serialized_output=None,
1019
+ serialized_exception=None,
993
1020
  )
994
1021
  info.input = inputs
995
1022
  info.output = output
996
1023
  info.error = exception
997
- info.workflow_deadline_epoch_ms = row[18]
998
- info.workflow_timeout_ms = row[19]
999
1024
 
1000
1025
  infos.append(info)
1001
1026
 
@@ -33,6 +33,8 @@ def list_workflows(
33
33
  offset: Optional[int] = None,
34
34
  sort_desc: bool = False,
35
35
  workflow_id_prefix: Optional[str] = None,
36
+ load_input: bool = True,
37
+ load_output: bool = True,
36
38
  ) -> List[WorkflowStatus]:
37
39
  input = GetWorkflowsInput()
38
40
  input.workflow_ids = workflow_ids
@@ -47,7 +49,9 @@ def list_workflows(
47
49
  input.sort_desc = sort_desc
48
50
  input.workflow_id_prefix = workflow_id_prefix
49
51
 
50
- infos: List[WorkflowStatus] = sys_db.get_workflows(input)
52
+ infos: List[WorkflowStatus] = sys_db.get_workflows(
53
+ input, load_input=load_input, load_output=load_output
54
+ )
51
55
 
52
56
  return infos
53
57
 
@@ -63,6 +67,7 @@ def list_queued_workflows(
63
67
  limit: Optional[int] = None,
64
68
  offset: Optional[int] = None,
65
69
  sort_desc: bool = False,
70
+ load_input: bool = True,
66
71
  ) -> List[WorkflowStatus]:
67
72
  input: GetQueuedWorkflowsInput = {
68
73
  "queue_name": queue_name,
@@ -75,7 +80,9 @@ def list_queued_workflows(
75
80
  "sort_desc": sort_desc,
76
81
  }
77
82
 
78
- infos: List[WorkflowStatus] = sys_db.get_queued_workflows(input)
83
+ infos: List[WorkflowStatus] = sys_db.get_queued_workflows(
84
+ input, load_input=load_input
85
+ )
79
86
  return infos
80
87
 
81
88
 
dbos/cli/cli.py CHANGED
@@ -21,6 +21,7 @@ from .._client import DBOSClient
21
21
  from .._dbos_config import (
22
22
  _app_name_to_db_name,
23
23
  _is_valid_app_name,
24
+ get_system_database_url,
24
25
  is_valid_database_url,
25
26
  load_config,
26
27
  )
@@ -294,13 +295,12 @@ def migrate(
294
295
  sys_db = None
295
296
  try:
296
297
  sys_db = SystemDatabase(
297
- database_url=connection_string,
298
+ system_database_url=get_system_database_url(config),
298
299
  engine_kwargs={
299
300
  "pool_timeout": 30,
300
301
  "max_overflow": 0,
301
302
  "pool_size": 2,
302
303
  },
303
- sys_db_name=sys_db_name,
304
304
  )
305
305
  app_db = ApplicationDatabase(
306
306
  database_url=connection_string,
@@ -450,7 +450,7 @@ def list(
450
450
  typer.Option(
451
451
  "--status",
452
452
  "-S",
453
- help="Retrieve workflows with this status (PENDING, SUCCESS, ERROR, RETRIES_EXCEEDED, ENQUEUED, or CANCELLED)",
453
+ help="Retrieve workflows with this status (PENDING, SUCCESS, ERROR, ENQUEUED, CANCELLED, or MAX_RECOVERY_ATTEMPTS_EXCEEDED)",
454
454
  ),
455
455
  ] = None,
456
456
  appversion: Annotated[
@@ -657,7 +657,7 @@ def list_queue(
657
657
  typer.Option(
658
658
  "--status",
659
659
  "-S",
660
- help="Retrieve functions with this status (PENDING, SUCCESS, ERROR, RETRIES_EXCEEDED, ENQUEUED, or CANCELLED)",
660
+ help="Retrieve functions with this status (PENDING, SUCCESS, ERROR, ENQUEUED, CANCELLED, or MAX_RECOVERY_ATTEMPTS_EXCEEDED)",
661
661
  ),
662
662
  ] = None,
663
663
  queue_name: Annotated[
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.7.0a5
3
+ Version: 1.8.0
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,23 +1,23 @@
1
- dbos-1.7.0a5.dist-info/METADATA,sha256=MpwGnaijkOfUai8Dqdrk2NS_wemMnKYf9N1YItrPMpo,13267
2
- dbos-1.7.0a5.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- dbos-1.7.0a5.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-1.7.0a5.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
1
+ dbos-1.8.0.dist-info/METADATA,sha256=DRFRURSy4U5kkdo7hqLRdpzPwVuRqoXCEt2Qou2EWqI,13265
2
+ dbos-1.8.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ dbos-1.8.0.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-1.8.0.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
5
  dbos/__init__.py,sha256=NssPCubaBxdiKarOWa-wViz1hdJSkmBGcpLX_gQ4NeA,891
6
6
  dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
7
- dbos/_admin_server.py,sha256=S2hFr3m5R3WkbOp3Yz9lWt5iLBfWPnvhYwYLokVax0A,16094
7
+ dbos/_admin_server.py,sha256=e8ELhcDWqR3_PNobnNgUvLGh5lzZq0yFSF6dvtzoQRI,16267
8
8
  dbos/_app_db.py,sha256=htblDPfqrpb_uZoFcvaud7cgQ-PDyn6Bn-cBidxdCTA,10603
9
9
  dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
10
- dbos/_client.py,sha256=DeiJHo5fTedWsipr7qlQQIcDmVAPjzzX94X01121oQM,14780
11
- dbos/_conductor/conductor.py,sha256=y_T-8kEHwKWt6W8LtcFMctB_6EvYFWsuGLxiFuuKKBU,23702
12
- dbos/_conductor/protocol.py,sha256=DOTprPSd7oHDcvwWSyZpnlPds_JfILtcKzHZa-qBsF4,7330
13
- dbos/_context.py,sha256=zhje6jObpBcRALYfHyyIEumHtk_enl_PxLl01j4oDME,24897
10
+ dbos/_client.py,sha256=6ReC_VF1JhscxtMjmjwJH_X7HV0L9Zoj_OiYhs4el40,15517
11
+ dbos/_conductor/conductor.py,sha256=3E_hL3c9g9yWqKZkvI6KA0-ZzPMPRo06TOzT1esMiek,24114
12
+ dbos/_conductor/protocol.py,sha256=q3rgLxINFtWFigdOONc-4gX4vn66UmMlJQD6Kj8LnL4,7420
13
+ dbos/_context.py,sha256=0vFtLAk3WF5BQYIYNFImDRBppKO2CTKOSy51zQC-Cu8,25723
14
14
  dbos/_core.py,sha256=kRY2PXVryfpwjbOCmgzPA_-qNsFmRMLi-CxYCnyp1V8,49495
15
15
  dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
16
- dbos/_dbos.py,sha256=BprKIGPT-QDeoxtKM6kjRUK9dyF8sPCFfHIyIt0u7CE,48142
17
- dbos/_dbos_config.py,sha256=JUG4V1rrP0p1AYESgih4ea80qOH_13UsgoIIm8X84pw,20562
16
+ dbos/_dbos.py,sha256=ByU8F1ueT2WTjT9SW518FfRL300kpK_6y8n7WWSwtOQ,50869
17
+ dbos/_dbos_config.py,sha256=TWIbGCWl_8o3l0Y2IzrL1q9mTYl_vVeZDjYJMDXCPVU,21676
18
18
  dbos/_debug.py,sha256=99j2SChWmCPAlZoDmjsJGe77tpU2LEa8E2TtLAnnh7o,1831
19
19
  dbos/_docker_pg_helper.py,sha256=tLJXWqZ4S-ExcaPnxg_i6cVxL6ZxrYlZjaGsklY-s2I,6115
20
- dbos/_error.py,sha256=nS7KuXJHhuNXZRErxdEUGT38Hb0VPyxNwSyADiVpHcE,8581
20
+ dbos/_error.py,sha256=2_2ve3qN0BLhFWMmd3aD9At54tIMCeh8TqHtVcEEVQo,8705
21
21
  dbos/_event_loop.py,sha256=cvaFN9-II3MsHEOq8QoICc_8qSKrjikMlLfuhC3Y8Dk,2923
22
22
  dbos/_fastapi.py,sha256=T7YlVY77ASqyTqq0aAPclZ9YzlXdGTT0lEYSwSgt1EE,3151
23
23
  dbos/_flask.py,sha256=Npnakt-a3W5OykONFRkDRnumaDhTQmA0NPdUCGRYKXE,1652
@@ -49,7 +49,7 @@ dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  dbos/_schemas/application_database.py,sha256=SypAS9l9EsaBHFn9FR8jmnqt01M74d9AF1AMa4m2hhI,1040
50
50
  dbos/_schemas/system_database.py,sha256=rbFKggONdvvbb45InvGz0TM6a7c-Ux9dcaL-h_7Z7pU,4438
51
51
  dbos/_serialization.py,sha256=bWuwhXSQcGmiazvhJHA5gwhrRWxtmFmcCFQSDJnqqkU,3666
52
- dbos/_sys_db.py,sha256=PaWa5Y8ublSMqPQXCHvYqln01cGf2LtPdXaLEHJq500,80653
52
+ dbos/_sys_db.py,sha256=0GAWwxxelTxukYHQIwLr9JOlwy0vENkxbyGcfnv8_Ko,81321
53
53
  dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
54
54
  dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  dbos/_templates/dbos-db-starter/__package/main.py.dbos,sha256=aQnBPSSQpkB8ERfhf7gB7P9tsU6OPKhZscfeh0yiaD8,2702
@@ -62,11 +62,11 @@ dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py,sh
62
62
  dbos/_templates/dbos-db-starter/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
63
63
  dbos/_tracer.py,sha256=RnlcaOJEx_58hr2J9L9g6E7gjAHAeEtEGugJZmCwNfQ,2963
64
64
  dbos/_utils.py,sha256=uywq1QrjMwy17btjxW4bES49povlQwYwYbvKwMT6C2U,1575
65
- dbos/_workflow_commands.py,sha256=4QCs7ziQ9T457tqfaNFwiXd6mDisr-ZK__skz1Uteyg,4648
65
+ dbos/_workflow_commands.py,sha256=EmmAaQfRWeOZm_WPTznuU-O3he3jiSzzT9VpYrhxugE,4835
66
66
  dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
67
67
  dbos/cli/_template_init.py,sha256=7JBcpMqP1r2mfCnvWatu33z8ctEGHJarlZYKgB83cXE,2972
68
- dbos/cli/cli.py,sha256=IcfaX4rrSrk6f24S2jrlR33snYMyNyEIx_lNQtuVr2E,22081
68
+ dbos/cli/cli.py,sha256=TwiaWr5zZrXOyhgv7OgkWzVA3y8znCj2L6i0sb91--0,22122
69
69
  dbos/dbos-config.schema.json,sha256=CjaspeYmOkx6Ip_pcxtmfXJTn_YGdSx_0pcPBF7KZmo,6060
70
70
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
71
71
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
72
- dbos-1.7.0a5.dist-info/RECORD,,
72
+ dbos-1.8.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.4.4)
2
+ Generator: pdm-backend (2.4.5)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any