dbos 2.1.0a2__py3-none-any.whl → 2.2.0a2__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.
dbos/__init__.py CHANGED
@@ -12,6 +12,7 @@ from ._dbos_config import DBOSConfig
12
12
  from ._debouncer import Debouncer, DebouncerClient
13
13
  from ._kafka_message import KafkaMessage
14
14
  from ._queue import Queue
15
+ from ._serialization import Serializer
15
16
  from ._sys_db import GetWorkflowsInput, WorkflowStatus, WorkflowStatusString
16
17
 
17
18
  __all__ = [
@@ -35,4 +36,5 @@ __all__ = [
35
36
  "Queue",
36
37
  "Debouncer",
37
38
  "DebouncerClient",
39
+ "Serializer",
38
40
  ]
dbos/_app_db.py CHANGED
@@ -8,8 +8,8 @@ from sqlalchemy.exc import DBAPIError
8
8
  from sqlalchemy.orm import Session, sessionmaker
9
9
 
10
10
  from dbos._migration import get_sqlite_timestamp_expr
11
+ from dbos._serialization import Serializer
11
12
 
12
- from . import _serialization
13
13
  from ._error import DBOSUnexpectedStepError, DBOSWorkflowConflictIDError
14
14
  from ._logger import dbos_logger
15
15
  from ._schemas.application_database import ApplicationSchema
@@ -34,17 +34,52 @@ class RecordedResult(TypedDict):
34
34
 
35
35
  class ApplicationDatabase(ABC):
36
36
 
37
+ @staticmethod
38
+ def create(
39
+ database_url: str,
40
+ engine_kwargs: Dict[str, Any],
41
+ schema: Optional[str],
42
+ serializer: Serializer,
43
+ debug_mode: bool = False,
44
+ ) -> "ApplicationDatabase":
45
+ """Factory method to create the appropriate ApplicationDatabase implementation based on URL."""
46
+ if database_url.startswith("sqlite"):
47
+ return SQLiteApplicationDatabase(
48
+ database_url=database_url,
49
+ engine_kwargs=engine_kwargs,
50
+ schema=schema,
51
+ serializer=serializer,
52
+ debug_mode=debug_mode,
53
+ )
54
+ else:
55
+ # Default to PostgreSQL for postgresql://, postgres://, or other URLs
56
+ return PostgresApplicationDatabase(
57
+ database_url=database_url,
58
+ engine_kwargs=engine_kwargs,
59
+ schema=schema,
60
+ serializer=serializer,
61
+ debug_mode=debug_mode,
62
+ )
63
+
37
64
  def __init__(
38
65
  self,
39
66
  *,
40
67
  database_url: str,
41
68
  engine_kwargs: Dict[str, Any],
69
+ serializer: Serializer,
70
+ schema: Optional[str],
42
71
  debug_mode: bool = False,
43
72
  ):
73
+ if database_url.startswith("sqlite"):
74
+ self.schema = None
75
+ else:
76
+ self.schema = schema if schema else "dbos"
77
+ ApplicationSchema.transaction_outputs.schema = schema
44
78
  self.engine = self._create_engine(database_url, engine_kwargs)
45
79
  self._engine_kwargs = engine_kwargs
46
80
  self.sessionmaker = sessionmaker(bind=self.engine)
47
81
  self.debug_mode = debug_mode
82
+ self.serializer = serializer
48
83
 
49
84
  @abstractmethod
50
85
  def _create_engine(
@@ -156,10 +191,12 @@ class ApplicationDatabase(ABC):
156
191
  function_id=row[0],
157
192
  function_name=row[1],
158
193
  output=(
159
- _serialization.deserialize(row[2]) if row[2] is not None else row[2]
194
+ self.serializer.deserialize(row[2])
195
+ if row[2] is not None
196
+ else row[2]
160
197
  ),
161
198
  error=(
162
- _serialization.deserialize_exception(row[3])
199
+ self.serializer.deserialize(row[3])
163
200
  if row[3] is not None
164
201
  else row[3]
165
202
  ),
@@ -237,52 +274,10 @@ class ApplicationDatabase(ABC):
237
274
  """Check if the error is a serialization/concurrency error."""
238
275
  pass
239
276
 
240
- @staticmethod
241
- def create(
242
- database_url: str,
243
- engine_kwargs: Dict[str, Any],
244
- schema: Optional[str],
245
- debug_mode: bool = False,
246
- ) -> "ApplicationDatabase":
247
- """Factory method to create the appropriate ApplicationDatabase implementation based on URL."""
248
- if database_url.startswith("sqlite"):
249
- return SQLiteApplicationDatabase(
250
- database_url=database_url,
251
- engine_kwargs=engine_kwargs,
252
- debug_mode=debug_mode,
253
- )
254
- else:
255
- # Default to PostgreSQL for postgresql://, postgres://, or other URLs
256
- return PostgresApplicationDatabase(
257
- database_url=database_url,
258
- engine_kwargs=engine_kwargs,
259
- debug_mode=debug_mode,
260
- schema=schema,
261
- )
262
-
263
277
 
264
278
  class PostgresApplicationDatabase(ApplicationDatabase):
265
279
  """PostgreSQL-specific implementation of ApplicationDatabase."""
266
280
 
267
- def __init__(
268
- self,
269
- *,
270
- database_url: str,
271
- engine_kwargs: Dict[str, Any],
272
- schema: Optional[str],
273
- debug_mode: bool = False,
274
- ):
275
- super().__init__(
276
- database_url=database_url,
277
- engine_kwargs=engine_kwargs,
278
- debug_mode=debug_mode,
279
- )
280
- if schema is None:
281
- self.schema = "dbos"
282
- else:
283
- self.schema = schema
284
- ApplicationSchema.transaction_outputs.schema = schema
285
-
286
281
  def _create_engine(
287
282
  self, database_url: str, engine_kwargs: Dict[str, Any]
288
283
  ) -> sa.Engine:
dbos/_client.py CHANGED
@@ -16,7 +16,6 @@ from typing import (
16
16
 
17
17
  import sqlalchemy as sa
18
18
 
19
- from dbos import _serialization
20
19
  from dbos._app_db import ApplicationDatabase
21
20
  from dbos._context import MaxPriority, MinPriority
22
21
  from dbos._sys_db import SystemDatabase
@@ -27,7 +26,7 @@ if TYPE_CHECKING:
27
26
  from dbos._dbos_config import get_system_database_url, is_valid_database_url
28
27
  from dbos._error import DBOSException, DBOSNonExistentWorkflowError
29
28
  from dbos._registrations import DEFAULT_MAX_RECOVERY_ATTEMPTS
30
- from dbos._serialization import WorkflowInputs
29
+ from dbos._serialization import DefaultSerializer, Serializer, WorkflowInputs
31
30
  from dbos._sys_db import (
32
31
  EnqueueOptionsInternal,
33
32
  StepInfo,
@@ -127,7 +126,9 @@ class DBOSClient:
127
126
  system_database_engine: Optional[sa.Engine] = None,
128
127
  application_database_url: Optional[str] = None,
129
128
  dbos_system_schema: Optional[str] = "dbos",
129
+ serializer: Serializer = DefaultSerializer(),
130
130
  ):
131
+ self._serializer = serializer
131
132
  application_database_url = (
132
133
  database_url if database_url else application_database_url
133
134
  )
@@ -150,6 +151,7 @@ class DBOSClient:
150
151
  },
151
152
  engine=system_database_engine,
152
153
  schema=dbos_system_schema,
154
+ serializer=serializer,
153
155
  )
154
156
  self._sys_db.check_connection()
155
157
  if application_database_url:
@@ -161,6 +163,7 @@ class DBOSClient:
161
163
  "pool_size": 2,
162
164
  },
163
165
  schema=dbos_system_schema,
166
+ serializer=serializer,
164
167
  )
165
168
 
166
169
  def destroy(self) -> None:
@@ -217,7 +220,7 @@ class DBOSClient:
217
220
  if enqueue_options_internal["priority"] is not None
218
221
  else 0
219
222
  ),
220
- "inputs": _serialization.serialize_args(inputs),
223
+ "inputs": self._serializer.serialize(inputs),
221
224
  }
222
225
 
223
226
  self._sys_db.init_workflow(
@@ -282,7 +285,7 @@ class DBOSClient:
282
285
  "workflow_deadline_epoch_ms": None,
283
286
  "deduplication_id": None,
284
287
  "priority": 0,
285
- "inputs": _serialization.serialize_args({"args": (), "kwargs": {}}),
288
+ "inputs": self._serializer.serialize({"args": (), "kwargs": {}}),
286
289
  }
287
290
  with self._sys_db.engine.begin() as conn:
288
291
  self._sys_db._insert_workflow_status(
dbos/_core.py CHANGED
@@ -23,7 +23,6 @@ from typing import (
23
23
  from dbos._outcome import Immediate, NoResult, Outcome, Pending
24
24
  from dbos._utils import GlobalParams, retriable_postgres_exception
25
25
 
26
- from . import _serialization
27
26
  from ._app_db import ApplicationDatabase, TransactionResultInternal
28
27
  from ._context import (
29
28
  DBOSAssumeRole,
@@ -116,10 +115,10 @@ class WorkflowHandleFuture(Generic[R]):
116
115
  try:
117
116
  r = self.future.result()
118
117
  except Exception as e:
119
- serialized_e = _serialization.serialize_exception(e)
118
+ serialized_e = self.dbos._serializer.serialize(e)
120
119
  self.dbos._sys_db.record_get_result(self.workflow_id, None, serialized_e)
121
120
  raise
122
- serialized_r = _serialization.serialize(r)
121
+ serialized_r = self.dbos._serializer.serialize(r)
123
122
  self.dbos._sys_db.record_get_result(self.workflow_id, serialized_r, None)
124
123
  return r
125
124
 
@@ -143,10 +142,10 @@ class WorkflowHandlePolling(Generic[R]):
143
142
  try:
144
143
  r: R = self.dbos._sys_db.await_workflow_result(self.workflow_id)
145
144
  except Exception as e:
146
- serialized_e = _serialization.serialize_exception(e)
145
+ serialized_e = self.dbos._serializer.serialize(e)
147
146
  self.dbos._sys_db.record_get_result(self.workflow_id, None, serialized_e)
148
147
  raise
149
- serialized_r = _serialization.serialize(r)
148
+ serialized_r = self.dbos._serializer.serialize(r)
150
149
  self.dbos._sys_db.record_get_result(self.workflow_id, serialized_r, None)
151
150
  return r
152
151
 
@@ -171,7 +170,7 @@ class WorkflowHandleAsyncTask(Generic[R]):
171
170
  try:
172
171
  r = await self.task
173
172
  except Exception as e:
174
- serialized_e = _serialization.serialize_exception(e)
173
+ serialized_e = self.dbos._serializer.serialize(e)
175
174
  await asyncio.to_thread(
176
175
  self.dbos._sys_db.record_get_result,
177
176
  self.workflow_id,
@@ -179,7 +178,7 @@ class WorkflowHandleAsyncTask(Generic[R]):
179
178
  serialized_e,
180
179
  )
181
180
  raise
182
- serialized_r = _serialization.serialize(r)
181
+ serialized_r = self.dbos._serializer.serialize(r)
183
182
  await asyncio.to_thread(
184
183
  self.dbos._sys_db.record_get_result, self.workflow_id, serialized_r, None
185
184
  )
@@ -207,7 +206,7 @@ class WorkflowHandleAsyncPolling(Generic[R]):
207
206
  self.dbos._sys_db.await_workflow_result, self.workflow_id
208
207
  )
209
208
  except Exception as e:
210
- serialized_e = _serialization.serialize_exception(e)
209
+ serialized_e = self.dbos._serializer.serialize(e)
211
210
  await asyncio.to_thread(
212
211
  self.dbos._sys_db.record_get_result,
213
212
  self.workflow_id,
@@ -215,7 +214,7 @@ class WorkflowHandleAsyncPolling(Generic[R]):
215
214
  serialized_e,
216
215
  )
217
216
  raise
218
- serialized_r = _serialization.serialize(r)
217
+ serialized_r = self.dbos._serializer.serialize(r)
219
218
  await asyncio.to_thread(
220
219
  self.dbos._sys_db.record_get_result, self.workflow_id, serialized_r, None
221
220
  )
@@ -303,7 +302,7 @@ def _init_workflow(
303
302
  if enqueue_options is not None
304
303
  else 0
305
304
  ),
306
- "inputs": _serialization.serialize_args(inputs),
305
+ "inputs": dbos._serializer.serialize(inputs),
307
306
  }
308
307
 
309
308
  # Synchronously record the status and inputs for workflows
@@ -319,7 +318,7 @@ def _init_workflow(
319
318
  "function_id": ctx.parent_workflow_fid,
320
319
  "function_name": wf_name,
321
320
  "output": None,
322
- "error": _serialization.serialize_exception(e),
321
+ "error": dbos._serializer.serialize(e),
323
322
  }
324
323
  dbos._sys_db.record_operation_result(result)
325
324
  raise
@@ -378,7 +377,7 @@ def _get_wf_invoke_func(
378
377
  dbos._sys_db.update_workflow_outcome(
379
378
  status["workflow_uuid"],
380
379
  "SUCCESS",
381
- output=_serialization.serialize(output),
380
+ output=dbos._serializer.serialize(output),
382
381
  )
383
382
  return output
384
383
  except DBOSWorkflowConflictIDError:
@@ -392,7 +391,7 @@ def _get_wf_invoke_func(
392
391
  dbos._sys_db.update_workflow_outcome(
393
392
  status["workflow_uuid"],
394
393
  "ERROR",
395
- error=_serialization.serialize_exception(error),
394
+ error=dbos._serializer.serialize(error),
396
395
  )
397
396
  raise
398
397
  finally:
@@ -464,7 +463,7 @@ def execute_workflow_by_id(dbos: "DBOS", workflow_id: str) -> "WorkflowHandle[An
464
463
  status = dbos._sys_db.get_workflow_status(workflow_id)
465
464
  if not status:
466
465
  raise DBOSRecoveryError(workflow_id, "Workflow status not found")
467
- inputs = _serialization.deserialize_args(status["inputs"])
466
+ inputs: WorkflowInputs = dbos._serializer.deserialize(status["inputs"])
468
467
  wf_func = dbos._registry.workflow_info_map.get(status["name"], None)
469
468
  if not wf_func:
470
469
  raise DBOSWorkflowFunctionNotFoundError(
@@ -837,11 +836,11 @@ def workflow_wrapper(
837
836
  try:
838
837
  r = func()
839
838
  except Exception as e:
840
- serialized_e = _serialization.serialize_exception(e)
839
+ serialized_e = dbos._serializer.serialize(e)
841
840
  assert workflow_id is not None
842
841
  dbos._sys_db.record_get_result(workflow_id, None, serialized_e)
843
842
  raise
844
- serialized_r = _serialization.serialize(r)
843
+ serialized_r = dbos._serializer.serialize(r)
845
844
  assert workflow_id is not None
846
845
  dbos._sys_db.record_get_result(workflow_id, serialized_r, None)
847
846
  return r
@@ -948,15 +947,15 @@ def decorate_transaction(
948
947
  f"Replaying transaction, id: {ctx.function_id}, name: {attributes['name']}"
949
948
  )
950
949
  if recorded_output["error"]:
951
- deserialized_error = (
952
- _serialization.deserialize_exception(
950
+ deserialized_error: Exception = (
951
+ dbos._serializer.deserialize(
953
952
  recorded_output["error"]
954
953
  )
955
954
  )
956
955
  has_recorded_error = True
957
956
  raise deserialized_error
958
957
  elif recorded_output["output"]:
959
- return _serialization.deserialize(
958
+ return dbos._serializer.deserialize(
960
959
  recorded_output["output"]
961
960
  )
962
961
  else:
@@ -969,7 +968,9 @@ def decorate_transaction(
969
968
  )
970
969
 
971
970
  output = func(*args, **kwargs)
972
- txn_output["output"] = _serialization.serialize(output)
971
+ txn_output["output"] = dbos._serializer.serialize(
972
+ output
973
+ )
973
974
  assert (
974
975
  ctx.sql_session is not None
975
976
  ), "Cannot find a database connection"
@@ -1010,8 +1011,8 @@ def decorate_transaction(
1010
1011
  finally:
1011
1012
  # Don't record the error if it was already recorded
1012
1013
  if txn_error and not has_recorded_error:
1013
- txn_output["error"] = (
1014
- _serialization.serialize_exception(txn_error)
1014
+ txn_output["error"] = dbos._serializer.serialize(
1015
+ txn_error
1015
1016
  )
1016
1017
  dbos._app_db.record_transaction_error(txn_output)
1017
1018
  return output
@@ -1128,10 +1129,10 @@ def decorate_step(
1128
1129
  try:
1129
1130
  output = func()
1130
1131
  except Exception as error:
1131
- step_output["error"] = _serialization.serialize_exception(error)
1132
+ step_output["error"] = dbos._serializer.serialize(error)
1132
1133
  dbos._sys_db.record_operation_result(step_output)
1133
1134
  raise
1134
- step_output["output"] = _serialization.serialize(output)
1135
+ step_output["output"] = dbos._serializer.serialize(output)
1135
1136
  dbos._sys_db.record_operation_result(step_output)
1136
1137
  return output
1137
1138
 
@@ -1147,13 +1148,13 @@ def decorate_step(
1147
1148
  f"Replaying step, id: {ctx.function_id}, name: {attributes['name']}"
1148
1149
  )
1149
1150
  if recorded_output["error"] is not None:
1150
- deserialized_error = _serialization.deserialize_exception(
1151
+ deserialized_error: Exception = dbos._serializer.deserialize(
1151
1152
  recorded_output["error"]
1152
1153
  )
1153
1154
  raise deserialized_error
1154
1155
  elif recorded_output["output"] is not None:
1155
1156
  return cast(
1156
- R, _serialization.deserialize(recorded_output["output"])
1157
+ R, dbos._serializer.deserialize(recorded_output["output"])
1157
1158
  )
1158
1159
  else:
1159
1160
  raise Exception("Output and error are both None")
dbos/_dbos.py CHANGED
@@ -31,6 +31,7 @@ from typing import (
31
31
 
32
32
  from dbos._conductor.conductor import ConductorWebsocket
33
33
  from dbos._debouncer import debouncer_workflow
34
+ from dbos._serialization import DefaultSerializer, Serializer
34
35
  from dbos._sys_db import SystemDatabase, WorkflowStatus
35
36
  from dbos._utils import INTERNAL_QUEUE_NAME, GlobalParams
36
37
  from dbos._workflow_commands import fork_workflow, list_queued_workflows, list_workflows
@@ -341,6 +342,8 @@ class DBOS:
341
342
  self.conductor_websocket: Optional[ConductorWebsocket] = None
342
343
  self._background_event_loop: BackgroundEventLoop = BackgroundEventLoop()
343
344
  self._active_workflows_set: set[str] = set()
345
+ serializer = config.get("serializer")
346
+ self._serializer: Serializer = serializer if serializer else DefaultSerializer()
344
347
 
345
348
  # Globally set the application version and executor ID.
346
349
  # In DBOS Cloud, instead use the values supplied through environment variables.
@@ -449,28 +452,34 @@ class DBOS:
449
452
  assert self._config["database"]["sys_db_engine_kwargs"] is not None
450
453
  # Get the schema configuration, use "dbos" as default
451
454
  schema = self._config.get("dbos_system_schema", "dbos")
455
+ dbos_logger.debug("Creating system database")
452
456
  self._sys_db_field = SystemDatabase.create(
453
457
  system_database_url=get_system_database_url(self._config),
454
458
  engine_kwargs=self._config["database"]["sys_db_engine_kwargs"],
455
459
  engine=self._config["system_database_engine"],
456
460
  debug_mode=debug_mode,
457
461
  schema=schema,
462
+ serializer=self._serializer,
458
463
  )
459
464
  assert self._config["database"]["db_engine_kwargs"] is not None
460
465
  if self._config["database_url"]:
466
+ dbos_logger.debug("Creating application database")
461
467
  self._app_db_field = ApplicationDatabase.create(
462
468
  database_url=self._config["database_url"],
463
469
  engine_kwargs=self._config["database"]["db_engine_kwargs"],
464
470
  debug_mode=debug_mode,
465
471
  schema=schema,
472
+ serializer=self._serializer,
466
473
  )
467
474
 
468
475
  if debug_mode:
469
476
  return
470
477
 
471
478
  # Run migrations for the system and application databases
479
+ dbos_logger.debug("Running system database migrations")
472
480
  self._sys_db.run_migrations()
473
481
  if self._app_db:
482
+ dbos_logger.debug("Running application database migrations")
474
483
  self._app_db.run_migrations()
475
484
 
476
485
  admin_port = self._config.get("runtimeConfig", {}).get("admin_port")
@@ -481,10 +490,12 @@ class DBOS:
481
490
  )
482
491
  if run_admin_server:
483
492
  try:
493
+ dbos_logger.debug("Starting admin server")
484
494
  self._admin_server_field = AdminServer(dbos=self, port=admin_port)
485
495
  except Exception as e:
486
496
  dbos_logger.warning(f"Failed to start admin server: {e}")
487
497
 
498
+ dbos_logger.debug("Retrieving local pending workflows for recovery")
488
499
  workflow_ids = self._sys_db.get_pending_workflows(
489
500
  GlobalParams.executor_id, GlobalParams.app_version
490
501
  )
@@ -500,6 +511,7 @@ class DBOS:
500
511
  self._executor.submit(startup_recovery_thread, self, workflow_ids)
501
512
 
502
513
  # Listen to notifications
514
+ dbos_logger.debug("Starting notifications listener thread")
503
515
  notification_listener_thread = threading.Thread(
504
516
  target=self._sys_db._notification_listener,
505
517
  daemon=True,
@@ -511,6 +523,7 @@ class DBOS:
511
523
  self._registry.get_internal_queue()
512
524
 
513
525
  # Start the queue thread
526
+ dbos_logger.debug("Starting queue thread")
514
527
  evt = threading.Event()
515
528
  self.background_thread_stop_events.append(evt)
516
529
  bg_queue_thread = threading.Thread(
@@ -526,6 +539,7 @@ class DBOS:
526
539
  self.conductor_url = f"wss://{dbos_domain}/conductor/v1alpha1"
527
540
  evt = threading.Event()
528
541
  self.background_thread_stop_events.append(evt)
542
+ dbos_logger.debug("Starting Conductor thread")
529
543
  self.conductor_websocket = ConductorWebsocket(
530
544
  self,
531
545
  conductor_url=self.conductor_url,
@@ -536,6 +550,7 @@ class DBOS:
536
550
  self._background_threads.append(self.conductor_websocket)
537
551
 
538
552
  # Grab any pollers that were deferred and start them
553
+ dbos_logger.debug("Starting event receivers")
539
554
  for evt, func, args, kwargs in self._registry.pollers:
540
555
  self.poller_stop_events.append(evt)
541
556
  poller_thread = threading.Thread(
dbos/_dbos_config.py CHANGED
@@ -7,6 +7,8 @@ import sqlalchemy as sa
7
7
  import yaml
8
8
  from sqlalchemy import make_url
9
9
 
10
+ from dbos._serialization import Serializer
11
+
10
12
  from ._error import DBOSInitializationError
11
13
  from ._logger import dbos_logger
12
14
  from ._schemas.system_database import SystemSchema
@@ -37,6 +39,7 @@ class DBOSConfig(TypedDict, total=False):
37
39
  enable_otlp (bool): If True, enable built-in DBOS OTLP tracing and logging.
38
40
  system_database_engine (sa.Engine): A custom system database engine. If provided, DBOS will not create an engine but use this instead.
39
41
  conductor_key (str): An API key for DBOS Conductor. Pass this in to connect your process to Conductor.
42
+ serializer (Serializer): A custom serializer and deserializer DBOS uses when storing program data in the system database
40
43
  """
41
44
 
42
45
  name: str
@@ -57,6 +60,7 @@ class DBOSConfig(TypedDict, total=False):
57
60
  enable_otlp: Optional[bool]
58
61
  system_database_engine: Optional[sa.Engine]
59
62
  conductor_key: Optional[str]
63
+ serializer: Optional[Serializer]
60
64
 
61
65
 
62
66
  class RuntimeConfig(TypedDict, total=False):
@@ -67,16 +71,6 @@ class RuntimeConfig(TypedDict, total=False):
67
71
 
68
72
 
69
73
  class DatabaseConfig(TypedDict, total=False):
70
- """
71
- Internal data structure containing the DBOS database configuration.
72
- Attributes:
73
- sys_db_name (str): System database name
74
- sys_db_pool_size (int): System database pool size
75
- db_engine_kwargs (Dict[str, Any]): SQLAlchemy engine kwargs
76
- migrate (List[str]): Migration commands to run on startup
77
- dbos_system_schema (str): Schema name for DBOS system tables. Defaults to "dbos".
78
- """
79
-
80
74
  sys_db_pool_size: Optional[int]
81
75
  db_engine_kwargs: Optional[Dict[str, Any]]
82
76
  sys_db_engine_kwargs: Optional[Dict[str, Any]]
dbos/_scheduler.py CHANGED
@@ -1,3 +1,4 @@
1
+ import random
1
2
  import threading
2
3
  import traceback
3
4
  from datetime import datetime, timezone
@@ -15,28 +16,40 @@ from ._registrations import get_dbos_func_name
15
16
 
16
17
  ScheduledWorkflow = Callable[[datetime, datetime], None]
17
18
 
18
- scheduler_queue: Queue
19
-
20
19
 
21
20
  def scheduler_loop(
22
21
  func: ScheduledWorkflow, cron: str, stop_event: threading.Event
23
22
  ) -> None:
23
+ from dbos._dbos import _get_dbos_instance
24
+
25
+ dbos = _get_dbos_instance()
26
+ scheduler_queue = dbos._registry.get_internal_queue()
24
27
  try:
25
28
  iter = croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
26
- except Exception as e:
29
+ except Exception:
27
30
  dbos_logger.error(
28
31
  f'Cannot run scheduled function {get_dbos_func_name(func)}. Invalid crontab "{cron}"'
29
32
  )
33
+ raise
30
34
  while not stop_event.is_set():
31
- nextExecTime = iter.get_next(datetime)
32
- sleepTime = nextExecTime - datetime.now(timezone.utc)
33
- if stop_event.wait(timeout=sleepTime.total_seconds()):
35
+ next_exec_time = iter.get_next(datetime)
36
+ sleep_time = (next_exec_time - datetime.now(timezone.utc)).total_seconds()
37
+ sleep_time = max(0, sleep_time)
38
+ # To prevent a "thundering herd" problem in a distributed setting,
39
+ # apply jitter of up to 10% the sleep time, capped at 10 seconds
40
+ max_jitter = min(sleep_time / 10, 10)
41
+ jitter = random.uniform(0, max_jitter)
42
+ if stop_event.wait(timeout=sleep_time + jitter):
34
43
  return
35
44
  try:
36
- with SetWorkflowID(
37
- f"sched-{get_dbos_func_name(func)}-{nextExecTime.isoformat()}"
38
- ):
39
- scheduler_queue.enqueue(func, nextExecTime, datetime.now(timezone.utc))
45
+ workflowID = (
46
+ f"sched-{get_dbos_func_name(func)}-{next_exec_time.isoformat()}"
47
+ )
48
+ if not dbos._sys_db.get_workflow_status(workflowID):
49
+ with SetWorkflowID(workflowID):
50
+ scheduler_queue.enqueue(
51
+ func, next_exec_time, datetime.now(timezone.utc)
52
+ )
40
53
  except Exception:
41
54
  dbos_logger.warning(
42
55
  f"Exception encountered in scheduler thread: {traceback.format_exc()})"
@@ -49,13 +62,10 @@ def scheduled(
49
62
  def decorator(func: ScheduledWorkflow) -> ScheduledWorkflow:
50
63
  try:
51
64
  croniter(cron, datetime.now(timezone.utc), second_at_beginning=True)
52
- except Exception as e:
65
+ except Exception:
53
66
  raise ValueError(
54
67
  f'Invalid crontab "{cron}" for scheduled function function {get_dbos_func_name(func)}.'
55
68
  )
56
-
57
- global scheduler_queue
58
- scheduler_queue = dbosreg.get_internal_queue()
59
69
  stop_event = threading.Event()
60
70
  dbosreg.register_poller(stop_event, scheduler_loop, func, cron, stop_event)
61
71
  return func
dbos/_serialization.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import base64
2
2
  import pickle
3
- import types
3
+ from abc import ABC, abstractmethod
4
4
  from typing import Any, Dict, Optional, Tuple, TypedDict
5
5
 
6
6
  from ._logger import dbos_logger
@@ -11,47 +11,31 @@ class WorkflowInputs(TypedDict):
11
11
  kwargs: Dict[str, Any]
12
12
 
13
13
 
14
- def serialize(data: Any) -> str:
15
- pickled_data: bytes = pickle.dumps(data)
16
- encoded_data: str = base64.b64encode(pickled_data).decode("utf-8")
17
- return encoded_data
14
+ class Serializer(ABC):
18
15
 
16
+ @abstractmethod
17
+ def serialize(self, data: Any) -> str:
18
+ pass
19
19
 
20
- def serialize_args(data: WorkflowInputs) -> str:
21
- """Serialize args to a base64-encoded string using pickle."""
22
- pickled_data: bytes = pickle.dumps(data)
23
- encoded_data: str = base64.b64encode(pickled_data).decode("utf-8")
24
- return encoded_data
20
+ @abstractmethod
21
+ def deserialize(cls, serialized_data: str) -> Any:
22
+ pass
25
23
 
26
24
 
27
- def serialize_exception(data: Exception) -> str:
28
- """Serialize an Exception object to a base64-encoded string using pickle."""
29
- pickled_data: bytes = pickle.dumps(data)
30
- encoded_data: str = base64.b64encode(pickled_data).decode("utf-8")
31
- return encoded_data
25
+ class DefaultSerializer(Serializer):
32
26
 
27
+ def serialize(self, data: Any) -> str:
28
+ pickled_data: bytes = pickle.dumps(data)
29
+ encoded_data: str = base64.b64encode(pickled_data).decode("utf-8")
30
+ return encoded_data
33
31
 
34
- def deserialize(serialized_data: str) -> Any:
35
- """Deserialize a base64-encoded string back to a Python object using pickle."""
36
- pickled_data: bytes = base64.b64decode(serialized_data)
37
- return pickle.loads(pickled_data)
38
-
39
-
40
- def deserialize_args(serialized_data: str) -> WorkflowInputs:
41
- """Deserialize a base64-encoded string back to a Python object list using pickle."""
42
- pickled_data: bytes = base64.b64decode(serialized_data)
43
- args: WorkflowInputs = pickle.loads(pickled_data)
44
- return args
45
-
46
-
47
- def deserialize_exception(serialized_data: str) -> Exception:
48
- """Deserialize a base64-encoded string back to a Python Exception using pickle."""
49
- pickled_data: bytes = base64.b64decode(serialized_data)
50
- exc: Exception = pickle.loads(pickled_data)
51
- return exc
32
+ def deserialize(cls, serialized_data: str) -> Any:
33
+ pickled_data: bytes = base64.b64decode(serialized_data)
34
+ return pickle.loads(pickled_data)
52
35
 
53
36
 
54
37
  def safe_deserialize(
38
+ serializer: Serializer,
55
39
  workflow_id: str,
56
40
  *,
57
41
  serialized_input: Optional[str],
@@ -68,7 +52,9 @@ def safe_deserialize(
68
52
  input: Optional[WorkflowInputs]
69
53
  try:
70
54
  input = (
71
- deserialize_args(serialized_input) if serialized_input is not None else None
55
+ serializer.deserialize(serialized_input)
56
+ if serialized_input is not None
57
+ else None
72
58
  )
73
59
  except Exception as e:
74
60
  dbos_logger.warning(
@@ -78,7 +64,9 @@ def safe_deserialize(
78
64
  output: Optional[Any]
79
65
  try:
80
66
  output = (
81
- deserialize(serialized_output) if serialized_output is not None else None
67
+ serializer.deserialize(serialized_output)
68
+ if serialized_output is not None
69
+ else None
82
70
  )
83
71
  except Exception as e:
84
72
  dbos_logger.warning(
@@ -88,7 +76,7 @@ def safe_deserialize(
88
76
  exception: Optional[Exception]
89
77
  try:
90
78
  exception = (
91
- deserialize_exception(serialized_exception)
79
+ serializer.deserialize(serialized_exception)
92
80
  if serialized_exception is not None
93
81
  else None
94
82
  )
dbos/_sys_db.py CHANGED
@@ -30,7 +30,6 @@ from dbos._utils import (
30
30
  retriable_sqlite_exception,
31
31
  )
32
32
 
33
- from . import _serialization
34
33
  from ._context import get_local_dbos_context
35
34
  from ._error import (
36
35
  DBOSAwaitedWorkflowCancelledError,
@@ -44,6 +43,7 @@ from ._error import (
44
43
  )
45
44
  from ._logger import dbos_logger
46
45
  from ._schemas.system_database import SystemSchema
46
+ from ._serialization import Serializer, WorkflowInputs, safe_deserialize
47
47
 
48
48
  if TYPE_CHECKING:
49
49
  from ._queue import Queue
@@ -95,7 +95,7 @@ class WorkflowStatus:
95
95
  # All roles which the authenticated user could assume
96
96
  authenticated_roles: Optional[list[str]]
97
97
  # The deserialized workflow input object
98
- input: Optional[_serialization.WorkflowInputs]
98
+ input: Optional[WorkflowInputs]
99
99
  # The workflow's output, if any
100
100
  output: Optional[Any] = None
101
101
  # The error the workflow threw, if any
@@ -341,6 +341,39 @@ def db_retry(
341
341
 
342
342
  class SystemDatabase(ABC):
343
343
 
344
+ @staticmethod
345
+ def create(
346
+ system_database_url: str,
347
+ engine_kwargs: Dict[str, Any],
348
+ engine: Optional[sa.Engine],
349
+ schema: Optional[str],
350
+ serializer: Serializer,
351
+ debug_mode: bool = False,
352
+ ) -> "SystemDatabase":
353
+ """Factory method to create the appropriate SystemDatabase implementation based on URL."""
354
+ if system_database_url.startswith("sqlite"):
355
+ from ._sys_db_sqlite import SQLiteSystemDatabase
356
+
357
+ return SQLiteSystemDatabase(
358
+ system_database_url=system_database_url,
359
+ engine_kwargs=engine_kwargs,
360
+ engine=engine,
361
+ schema=schema,
362
+ serializer=serializer,
363
+ debug_mode=debug_mode,
364
+ )
365
+ else:
366
+ from ._sys_db_postgres import PostgresSystemDatabase
367
+
368
+ return PostgresSystemDatabase(
369
+ system_database_url=system_database_url,
370
+ engine_kwargs=engine_kwargs,
371
+ engine=engine,
372
+ schema=schema,
373
+ serializer=serializer,
374
+ debug_mode=debug_mode,
375
+ )
376
+
344
377
  def __init__(
345
378
  self,
346
379
  *,
@@ -348,6 +381,7 @@ class SystemDatabase(ABC):
348
381
  engine_kwargs: Dict[str, Any],
349
382
  engine: Optional[sa.Engine],
350
383
  schema: Optional[str],
384
+ serializer: Serializer,
351
385
  debug_mode: bool = False,
352
386
  ):
353
387
  import sqlalchemy.dialects.postgresql as pg
@@ -355,6 +389,8 @@ class SystemDatabase(ABC):
355
389
 
356
390
  self.dialect = sq if system_database_url.startswith("sqlite") else pg
357
391
 
392
+ self.serializer = serializer
393
+
358
394
  if system_database_url.startswith("sqlite"):
359
395
  self.schema = None
360
396
  else:
@@ -797,10 +833,11 @@ class SystemDatabase(ABC):
797
833
  status = row[0]
798
834
  if status == WorkflowStatusString.SUCCESS.value:
799
835
  output = row[1]
800
- return _serialization.deserialize(output)
836
+ return self.serializer.deserialize(output)
801
837
  elif status == WorkflowStatusString.ERROR.value:
802
838
  error = row[2]
803
- raise _serialization.deserialize_exception(error)
839
+ e: Exception = self.serializer.deserialize(error)
840
+ raise e
804
841
  elif status == WorkflowStatusString.CANCELLED.value:
805
842
  # Raise AwaitedWorkflowCancelledError here, not the cancellation exception
806
843
  # because the awaiting workflow is not being cancelled.
@@ -917,7 +954,8 @@ class SystemDatabase(ABC):
917
954
  raw_input = row[17] if load_input else None
918
955
  raw_output = row[18] if load_output else None
919
956
  raw_error = row[19] if load_output else None
920
- inputs, output, exception = _serialization.safe_deserialize(
957
+ inputs, output, exception = safe_deserialize(
958
+ self.serializer,
921
959
  info.workflow_id,
922
960
  serialized_input=raw_input,
923
961
  serialized_output=raw_output,
@@ -1028,7 +1066,8 @@ class SystemDatabase(ABC):
1028
1066
  raw_input = row[17] if load_input else None
1029
1067
 
1030
1068
  # Error and Output are not loaded because they should always be None for queued workflows.
1031
- inputs, output, exception = _serialization.safe_deserialize(
1069
+ inputs, output, exception = safe_deserialize(
1070
+ self.serializer,
1032
1071
  info.workflow_id,
1033
1072
  serialized_input=raw_input,
1034
1073
  serialized_output=None,
@@ -1079,7 +1118,8 @@ class SystemDatabase(ABC):
1079
1118
  ).fetchall()
1080
1119
  steps = []
1081
1120
  for row in rows:
1082
- _, output, exception = _serialization.safe_deserialize(
1121
+ _, output, exception = safe_deserialize(
1122
+ self.serializer,
1083
1123
  workflow_id,
1084
1124
  serialized_input=None,
1085
1125
  serialized_output=row[2],
@@ -1278,7 +1318,8 @@ class SystemDatabase(ABC):
1278
1318
  if row is None:
1279
1319
  return None
1280
1320
  elif row[1]:
1281
- raise _serialization.deserialize_exception(row[1])
1321
+ e: Exception = self.serializer.deserialize(row[1])
1322
+ raise e
1282
1323
  else:
1283
1324
  return str(row[0])
1284
1325
 
@@ -1317,7 +1358,7 @@ class SystemDatabase(ABC):
1317
1358
  sa.insert(SystemSchema.notifications).values(
1318
1359
  destination_uuid=destination_uuid,
1319
1360
  topic=topic,
1320
- message=_serialization.serialize(message),
1361
+ message=self.serializer.serialize(message),
1321
1362
  )
1322
1363
  )
1323
1364
  except DBAPIError as dbapi_error:
@@ -1354,7 +1395,7 @@ class SystemDatabase(ABC):
1354
1395
  if recorded_output is not None:
1355
1396
  dbos_logger.debug(f"Replaying recv, id: {function_id}, topic: {topic}")
1356
1397
  if recorded_output["output"] is not None:
1357
- return _serialization.deserialize(recorded_output["output"])
1398
+ return self.serializer.deserialize(recorded_output["output"])
1358
1399
  else:
1359
1400
  raise Exception("No output recorded in the last recv")
1360
1401
  else:
@@ -1421,13 +1462,13 @@ class SystemDatabase(ABC):
1421
1462
  rows = c.execute(delete_stmt).fetchall()
1422
1463
  message: Any = None
1423
1464
  if len(rows) > 0:
1424
- message = _serialization.deserialize(rows[0][0])
1465
+ message = self.serializer.deserialize(rows[0][0])
1425
1466
  self._record_operation_result_txn(
1426
1467
  {
1427
1468
  "workflow_uuid": workflow_uuid,
1428
1469
  "function_id": function_id,
1429
1470
  "function_name": function_name,
1430
- "output": _serialization.serialize(
1471
+ "output": self.serializer.serialize(
1431
1472
  message
1432
1473
  ), # None will be serialized to 'null'
1433
1474
  "error": None,
@@ -1453,36 +1494,6 @@ class SystemDatabase(ABC):
1453
1494
 
1454
1495
  PostgresSystemDatabase._reset_system_database(database_url)
1455
1496
 
1456
- @staticmethod
1457
- def create(
1458
- system_database_url: str,
1459
- engine_kwargs: Dict[str, Any],
1460
- engine: Optional[sa.Engine],
1461
- schema: Optional[str],
1462
- debug_mode: bool = False,
1463
- ) -> "SystemDatabase":
1464
- """Factory method to create the appropriate SystemDatabase implementation based on URL."""
1465
- if system_database_url.startswith("sqlite"):
1466
- from ._sys_db_sqlite import SQLiteSystemDatabase
1467
-
1468
- return SQLiteSystemDatabase(
1469
- system_database_url=system_database_url,
1470
- engine_kwargs=engine_kwargs,
1471
- engine=engine,
1472
- schema=schema,
1473
- debug_mode=debug_mode,
1474
- )
1475
- else:
1476
- from ._sys_db_postgres import PostgresSystemDatabase
1477
-
1478
- return PostgresSystemDatabase(
1479
- system_database_url=system_database_url,
1480
- engine_kwargs=engine_kwargs,
1481
- engine=engine,
1482
- schema=schema,
1483
- debug_mode=debug_mode,
1484
- )
1485
-
1486
1497
  @db_retry()
1487
1498
  def sleep(
1488
1499
  self,
@@ -1502,7 +1513,7 @@ class SystemDatabase(ABC):
1502
1513
  if recorded_output is not None:
1503
1514
  dbos_logger.debug(f"Replaying sleep, id: {function_id}, seconds: {seconds}")
1504
1515
  assert recorded_output["output"] is not None, "no recorded end time"
1505
- end_time = _serialization.deserialize(recorded_output["output"])
1516
+ end_time = self.serializer.deserialize(recorded_output["output"])
1506
1517
  else:
1507
1518
  dbos_logger.debug(f"Running sleep, id: {function_id}, seconds: {seconds}")
1508
1519
  end_time = time.time() + seconds
@@ -1512,7 +1523,7 @@ class SystemDatabase(ABC):
1512
1523
  "workflow_uuid": workflow_uuid,
1513
1524
  "function_id": function_id,
1514
1525
  "function_name": function_name,
1515
- "output": _serialization.serialize(end_time),
1526
+ "output": self.serializer.serialize(end_time),
1516
1527
  "error": None,
1517
1528
  }
1518
1529
  )
@@ -1550,11 +1561,11 @@ class SystemDatabase(ABC):
1550
1561
  .values(
1551
1562
  workflow_uuid=workflow_uuid,
1552
1563
  key=key,
1553
- value=_serialization.serialize(message),
1564
+ value=self.serializer.serialize(message),
1554
1565
  )
1555
1566
  .on_conflict_do_update(
1556
1567
  index_elements=["workflow_uuid", "key"],
1557
- set_={"value": _serialization.serialize(message)},
1568
+ set_={"value": self.serializer.serialize(message)},
1558
1569
  )
1559
1570
  )
1560
1571
  output: OperationResultInternal = {
@@ -1578,11 +1589,11 @@ class SystemDatabase(ABC):
1578
1589
  .values(
1579
1590
  workflow_uuid=workflow_uuid,
1580
1591
  key=key,
1581
- value=_serialization.serialize(message),
1592
+ value=self.serializer.serialize(message),
1582
1593
  )
1583
1594
  .on_conflict_do_update(
1584
1595
  index_elements=["workflow_uuid", "key"],
1585
- set_={"value": _serialization.serialize(message)},
1596
+ set_={"value": self.serializer.serialize(message)},
1586
1597
  )
1587
1598
  )
1588
1599
 
@@ -1607,7 +1618,7 @@ class SystemDatabase(ABC):
1607
1618
  events: Dict[str, Any] = {}
1608
1619
  for row in rows:
1609
1620
  key = row[0]
1610
- value = _serialization.deserialize(row[1])
1621
+ value = self.serializer.deserialize(row[1])
1611
1622
  events[key] = value
1612
1623
 
1613
1624
  return events
@@ -1641,7 +1652,7 @@ class SystemDatabase(ABC):
1641
1652
  f"Replaying get_event, id: {caller_ctx['function_id']}, key: {key}"
1642
1653
  )
1643
1654
  if recorded_output["output"] is not None:
1644
- return _serialization.deserialize(recorded_output["output"])
1655
+ return self.serializer.deserialize(recorded_output["output"])
1645
1656
  else:
1646
1657
  raise Exception("No output recorded in the last get_event")
1647
1658
  else:
@@ -1666,7 +1677,7 @@ class SystemDatabase(ABC):
1666
1677
 
1667
1678
  value: Any = None
1668
1679
  if len(init_recv) > 0:
1669
- value = _serialization.deserialize(init_recv[0][0])
1680
+ value = self.serializer.deserialize(init_recv[0][0])
1670
1681
  else:
1671
1682
  # Wait for the notification
1672
1683
  actual_timeout = timeout_seconds
@@ -1684,7 +1695,7 @@ class SystemDatabase(ABC):
1684
1695
  with self.engine.begin() as c:
1685
1696
  final_recv = c.execute(get_sql).fetchall()
1686
1697
  if len(final_recv) > 0:
1687
- value = _serialization.deserialize(final_recv[0][0])
1698
+ value = self.serializer.deserialize(final_recv[0][0])
1688
1699
  condition.release()
1689
1700
  self.workflow_events_map.pop(payload)
1690
1701
 
@@ -1695,7 +1706,7 @@ class SystemDatabase(ABC):
1695
1706
  "workflow_uuid": caller_ctx["workflow_uuid"],
1696
1707
  "function_id": caller_ctx["function_id"],
1697
1708
  "function_name": function_name,
1698
- "output": _serialization.serialize(
1709
+ "output": self.serializer.serialize(
1699
1710
  value
1700
1711
  ), # None will be serialized to 'null'
1701
1712
  "error": None,
@@ -1897,12 +1908,13 @@ class SystemDatabase(ABC):
1897
1908
  )
1898
1909
  if res is not None:
1899
1910
  if res["output"] is not None:
1900
- resstat: SystemDatabase.T = _serialization.deserialize(
1911
+ resstat: SystemDatabase.T = self.serializer.deserialize(
1901
1912
  res["output"]
1902
1913
  )
1903
1914
  return resstat
1904
1915
  elif res["error"] is not None:
1905
- raise _serialization.deserialize_exception(res["error"])
1916
+ e: Exception = self.serializer.deserialize(res["error"])
1917
+ raise e
1906
1918
  else:
1907
1919
  raise Exception(
1908
1920
  f"Recorded output and error are both None for {function_name}"
@@ -1914,7 +1926,7 @@ class SystemDatabase(ABC):
1914
1926
  "workflow_uuid": ctx.workflow_id,
1915
1927
  "function_id": ctx.function_id,
1916
1928
  "function_name": function_name,
1917
- "output": _serialization.serialize(result),
1929
+ "output": self.serializer.serialize(result),
1918
1930
  "error": None,
1919
1931
  }
1920
1932
  )
@@ -1968,7 +1980,7 @@ class SystemDatabase(ABC):
1968
1980
  )
1969
1981
 
1970
1982
  # Serialize the value before storing
1971
- serialized_value = _serialization.serialize(value)
1983
+ serialized_value = self.serializer.serialize(value)
1972
1984
 
1973
1985
  # Insert the new stream entry
1974
1986
  c.execute(
@@ -2023,7 +2035,7 @@ class SystemDatabase(ABC):
2023
2035
  )
2024
2036
 
2025
2037
  # Serialize the value before storing
2026
- serialized_value = _serialization.serialize(value)
2038
+ serialized_value = self.serializer.serialize(value)
2027
2039
 
2028
2040
  # Insert the new stream entry
2029
2041
  c.execute(
@@ -2068,7 +2080,7 @@ class SystemDatabase(ABC):
2068
2080
  )
2069
2081
 
2070
2082
  # Deserialize the value before returning
2071
- return _serialization.deserialize(result[0])
2083
+ return self.serializer.deserialize(result[0])
2072
2084
 
2073
2085
  def garbage_collect(
2074
2086
  self, cutoff_epoch_timestamp_ms: Optional[int], rows_threshold: Optional[int]
dbos/cli/migration.py CHANGED
@@ -4,6 +4,7 @@ import sqlalchemy as sa
4
4
  import typer
5
5
 
6
6
  from dbos._app_db import ApplicationDatabase
7
+ from dbos._serialization import DefaultSerializer
7
8
  from dbos._sys_db import SystemDatabase
8
9
 
9
10
 
@@ -22,6 +23,7 @@ def migrate_dbos_databases(
22
23
  },
23
24
  engine=None,
24
25
  schema=schema,
26
+ serializer=DefaultSerializer(),
25
27
  )
26
28
  sys_db.run_migrations()
27
29
  if app_database_url:
@@ -33,6 +35,7 @@ def migrate_dbos_databases(
33
35
  "pool_size": 2,
34
36
  },
35
37
  schema=schema,
38
+ serializer=DefaultSerializer(),
36
39
  )
37
40
  app_db.run_migrations()
38
41
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 2.1.0a2
3
+ Version: 2.2.0a2
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,20 +1,20 @@
1
- dbos-2.1.0a2.dist-info/METADATA,sha256=cjLi7rpM1wJUcKQxa6jFruCxYOhGVfEmGCogTXEa7HA,14532
2
- dbos-2.1.0a2.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- dbos-2.1.0a2.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-2.1.0a2.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
- dbos/__init__.py,sha256=pT4BuNLDCrIQX27vQG8NlfxX6PZRU7r9miq4thJTszU,982
1
+ dbos-2.2.0a2.dist-info/METADATA,sha256=N8ToPPsy7wCWFBPg5BbVEM_XYJ82Q5LFEFOu7WdlwIU,14532
2
+ dbos-2.2.0a2.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ dbos-2.2.0a2.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-2.2.0a2.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
+ dbos/__init__.py,sha256=M7FdFSBGhcvaLIXrNw_0eR68ijwMWV7_UEyimHMP_F4,1039
6
6
  dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
7
7
  dbos/_admin_server.py,sha256=hubQJw5T8zGKCPNS6FQTXy8jQ8GTJxoYQaDTMlICl9k,16267
8
- dbos/_app_db.py,sha256=WJwUdKsTpSZPCIWVeSF5FQNf5y1PF_lJ96tiaCjvck8,16385
8
+ dbos/_app_db.py,sha256=mvWQ66ebdbiD9fpGKHZBWNVEza6Ulo1D-3UoTB_LwRc,16378
9
9
  dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
10
- dbos/_client.py,sha256=ppkO3bJ_qpBzWjYf9BkGfy8yF76e91UuhoLMtbIwXnU,18933
10
+ dbos/_client.py,sha256=l15aNXTnNQrjmchBNvJypdwdc53dRlMQlUF40iOqDWo,19092
11
11
  dbos/_conductor/conductor.py,sha256=3E_hL3c9g9yWqKZkvI6KA0-ZzPMPRo06TOzT1esMiek,24114
12
12
  dbos/_conductor/protocol.py,sha256=q3rgLxINFtWFigdOONc-4gX4vn66UmMlJQD6Kj8LnL4,7420
13
13
  dbos/_context.py,sha256=cJDxVbswTLXKE5MV4Hmg6gpIX3Dd5mBTG-4lmofWP9E,27668
14
- dbos/_core.py,sha256=x0kGmYD2RAPsM6NCOW9b64FKo6xHaQEail8ld2nDWzc,50685
14
+ dbos/_core.py,sha256=ThzKR7LCqLlR4eNkoGKrssddM9iVJC0UfshmDbx1TEA,50728
15
15
  dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
16
- dbos/_dbos.py,sha256=lkOnSZrEPkqURBJatBhnslHO0OKgmrS8rqLowEn5Rr0,58441
17
- dbos/_dbos_config.py,sha256=4vgPyy4NiojAOiw3BnjWwiwT1_Ju3ZhXqJQOKDXhsE4,25148
16
+ dbos/_dbos.py,sha256=dr32Z_NT36JkUxWGyYVX7xkl3bYJmgsxVMOX8H9_mpM,59394
17
+ dbos/_dbos_config.py,sha256=NIMQfxkznoyscyeMFLrfrPAS1W_PHXXWrxqpvvrbp3E,24923
18
18
  dbos/_debouncer.py,sha256=qNjIVmWqTPp64M2cEbLnpgGmlKVdCaAKysD1BPJgWh4,15297
19
19
  dbos/_debug.py,sha256=0MfgNqutCUhI4PEmmra9x7f3DiFE_0nscfUCHdLimEY,1415
20
20
  dbos/_docker_pg_helper.py,sha256=xySum4hTA8TVMBODoG19u4cXQAB1vCock-jwM2pnmSI,7791
@@ -31,12 +31,12 @@ dbos/_queue.py,sha256=cgFFwVPUeQtrTgk7ivoTZb0v9ya8rZK4m7-G-h5gIb4,4846
31
31
  dbos/_recovery.py,sha256=K-wlFhdf4yGRm6cUzyhcTjQUS0xp2T5rdNMLiiBErYg,2882
32
32
  dbos/_registrations.py,sha256=bEOntObnWaBylnebr5ZpcX2hk7OVLDd1z4BvW4_y3zA,7380
33
33
  dbos/_roles.py,sha256=kCuhhg8XLtrHCgKgm44I0abIRTGHltf88OwjEKAUggk,2317
34
- dbos/_scheduler.py,sha256=CWeGVfl9h51VXfxt80y5Da_5pE8SPty_AYkfpJkkMxQ,2117
34
+ dbos/_scheduler.py,sha256=n96dNzKMr6-2RQvMxRI6BaoExHbLjw0Kr46j1P-DjP4,2620
35
35
  dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  dbos/_schemas/application_database.py,sha256=SypAS9l9EsaBHFn9FR8jmnqt01M74d9AF1AMa4m2hhI,1040
37
37
  dbos/_schemas/system_database.py,sha256=aEkjRQDh9xjdke0d9uFx_20-c9UjQtvuLtHZ24aOypA,5497
38
- dbos/_serialization.py,sha256=GLgWLtHpvk7nSHyXukVQLE1ASNA3CJBtfF8w6iflBDw,3590
39
- dbos/_sys_db.py,sha256=kvssT1I6-vW6eGKmbDqr4L-HO-2xbwkePO0TwVxRTM8,85203
38
+ dbos/_serialization.py,sha256=8TVXB1c2k3keodNcXszqmcOGTQz2r5UBSYtxn2OrYjI,2804
39
+ dbos/_sys_db.py,sha256=ON8P8DZOy9FZ-TFFcBKMhzTdBXbUhE2oEGKKofDJgwE,85571
40
40
  dbos/_sys_db_postgres.py,sha256=GuyGVyZZD_Wl7LjRSkHnOuZ-hOROlO4Xs2UeDhKq10E,6963
41
41
  dbos/_sys_db_sqlite.py,sha256=ifjKdy-Z9vlVIBf5L6XnSaNjiBdvqPE73asVHim4A5Q,6998
42
42
  dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
@@ -52,8 +52,8 @@ dbos/_workflow_commands.py,sha256=k-i1bCfNrux43BHLT8wQ-l-MVZX3D6LGZLH7-uuiDRo,49
52
52
  dbos/cli/_github_init.py,sha256=R_94Fnn40CAmPy-zM00lwHi0ndyfv57TmIooADjmag4,3378
53
53
  dbos/cli/_template_init.py,sha256=AltKk256VocgvxLpuTxpjJyACrdHFjbGoqYhHzeLae4,2649
54
54
  dbos/cli/cli.py,sha256=s-gGQvHvVPeQp68raQElGnbBlSCv69JZ3HNFj5Qt2bs,27686
55
- dbos/cli/migration.py,sha256=zJnDPUBnil5XZXFxc01EbZ0Radw_y8wtDGZExgelAxc,3633
55
+ dbos/cli/migration.py,sha256=I0_0ngWTuCPQf6Symbpd0lizaxWUKe3uTYEmuCmsrdU,3775
56
56
  dbos/dbos-config.schema.json,sha256=47wofTZ5jlFynec7bG0L369tAXbRQQ2euBxBXvg4m9c,1730
57
57
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
58
58
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
59
- dbos-2.1.0a2.dist-info/RECORD,,
59
+ dbos-2.2.0a2.dist-info/RECORD,,
File without changes