dbos 1.15.0a5__py3-none-any.whl → 1.15.0a7__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/_client.py CHANGED
@@ -14,6 +14,8 @@ from typing import (
14
14
  Union,
15
15
  )
16
16
 
17
+ import sqlalchemy as sa
18
+
17
19
  from dbos import _serialization
18
20
  from dbos._app_db import ApplicationDatabase
19
21
  from dbos._context import MaxPriority, MinPriority
@@ -122,6 +124,7 @@ class DBOSClient:
122
124
  database_url: Optional[str] = None, # DEPRECATED
123
125
  *,
124
126
  system_database_url: Optional[str] = None,
127
+ system_database_engine: Optional[sa.Engine] = None,
125
128
  application_database_url: Optional[str] = None,
126
129
  dbos_system_schema: Optional[str] = "dbos",
127
130
  ):
@@ -145,6 +148,7 @@ class DBOSClient:
145
148
  "max_overflow": 0,
146
149
  "pool_size": 2,
147
150
  },
151
+ engine=system_database_engine,
148
152
  schema=dbos_system_schema,
149
153
  )
150
154
  self._sys_db.check_connection()
dbos/_dbos.py CHANGED
@@ -335,6 +335,8 @@ class DBOS:
335
335
  self._background_threads: List[threading.Thread] = []
336
336
  self.conductor_url: Optional[str] = conductor_url
337
337
  self.conductor_key: Optional[str] = conductor_key
338
+ if config.get("conductor_key"):
339
+ self.conductor_key = config.get("conductor_key")
338
340
  self.conductor_websocket: Optional[ConductorWebsocket] = None
339
341
  self._background_event_loop: BackgroundEventLoop = BackgroundEventLoop()
340
342
  self._active_workflows_set: set[str] = set()
@@ -449,6 +451,7 @@ class DBOS:
449
451
  self._sys_db_field = SystemDatabase.create(
450
452
  system_database_url=get_system_database_url(self._config),
451
453
  engine_kwargs=self._config["database"]["sys_db_engine_kwargs"],
454
+ engine=self._config["system_database_engine"],
452
455
  debug_mode=debug_mode,
453
456
  schema=schema,
454
457
  )
dbos/_dbos_config.py CHANGED
@@ -3,6 +3,7 @@ import re
3
3
  from importlib import resources
4
4
  from typing import Any, Dict, List, Optional, TypedDict, cast
5
5
 
6
+ import sqlalchemy as sa
6
7
  import yaml
7
8
  from sqlalchemy import make_url
8
9
 
@@ -34,6 +35,8 @@ class DBOSConfig(TypedDict, total=False):
34
35
  executor_id (str): Executor ID, used to identify the application instance in distributed environments
35
36
  dbos_system_schema (str): Schema name for DBOS system tables. Defaults to "dbos".
36
37
  enable_otlp (bool): If True, enable built-in DBOS OTLP tracing and logging.
38
+ system_database_engine (sa.Engine): A custom system database engine. If provided, DBOS will not create an engine but use this instead.
39
+ conductor_key (str): An API key for DBOS Conductor. Pass this in to connect your process to Conductor.
37
40
  """
38
41
 
39
42
  name: str
@@ -52,6 +55,8 @@ class DBOSConfig(TypedDict, total=False):
52
55
  executor_id: Optional[str]
53
56
  dbos_system_schema: Optional[str]
54
57
  enable_otlp: Optional[bool]
58
+ system_database_engine: Optional[sa.Engine]
59
+ conductor_key: Optional[str]
55
60
 
56
61
 
57
62
  class RuntimeConfig(TypedDict, total=False):
@@ -97,20 +102,7 @@ class TelemetryConfig(TypedDict, total=False):
97
102
  class ConfigFile(TypedDict, total=False):
98
103
  """
99
104
  Data structure containing the DBOS Configuration.
100
-
101
- This configuration data is typically loaded from `dbos-config.yaml`.
102
- See `https://docs.dbos.dev/python/reference/configuration#dbos-configuration-file`
103
-
104
- Attributes:
105
- name (str): Application name
106
- runtimeConfig (RuntimeConfig): Configuration for DBOS Cloud
107
- database (DatabaseConfig): Configure pool sizes, migrate commands
108
- database_url (str): Application database URL
109
- system_database_url (str): System database URL
110
- telemetry (TelemetryConfig): Configuration for tracing / logging
111
- env (Dict[str,str]): Environment variables
112
- dbos_system_schema (str): Schema name for DBOS system tables. Defaults to "dbos".
113
-
105
+ The DBOSConfig object is parsed into this.
114
106
  """
115
107
 
116
108
  name: str
@@ -120,6 +112,7 @@ class ConfigFile(TypedDict, total=False):
120
112
  system_database_url: Optional[str]
121
113
  telemetry: Optional[TelemetryConfig]
122
114
  env: Dict[str, str]
115
+ system_database_engine: Optional[sa.Engine]
123
116
  dbos_system_schema: Optional[str]
124
117
 
125
118
 
@@ -188,6 +181,8 @@ def translate_dbos_config_to_config_file(config: DBOSConfig) -> ConfigFile:
188
181
  if telemetry:
189
182
  translated_config["telemetry"] = telemetry
190
183
 
184
+ translated_config["system_database_engine"] = config.get("system_database_engine")
185
+
191
186
  return translated_config
192
187
 
193
188
 
dbos/_sys_db.py CHANGED
@@ -346,17 +346,32 @@ class SystemDatabase(ABC):
346
346
  *,
347
347
  system_database_url: str,
348
348
  engine_kwargs: Dict[str, Any],
349
+ engine: Optional[sa.Engine],
350
+ schema: Optional[str],
349
351
  debug_mode: bool = False,
350
352
  ):
351
353
  import sqlalchemy.dialects.postgresql as pg
352
354
  import sqlalchemy.dialects.sqlite as sq
353
355
 
354
356
  self.dialect = sq if system_database_url.startswith("sqlite") else pg
355
- self.engine = self._create_engine(system_database_url, engine_kwargs)
357
+
358
+ if system_database_url.startswith("sqlite"):
359
+ self.schema = None
360
+ else:
361
+ self.schema = schema if schema else "dbos"
362
+ SystemSchema.set_schema(self.schema)
363
+
364
+ if engine:
365
+ self.engine = engine
366
+ self.created_engine = False
367
+ else:
368
+ self.engine = self._create_engine(system_database_url, engine_kwargs)
369
+ self.created_engine = True
356
370
  self._engine_kwargs = engine_kwargs
357
371
 
358
372
  self.notifications_map = ThreadSafeConditionDict()
359
373
  self.workflow_events_map = ThreadSafeConditionDict()
374
+ self._listener_thread_lock = threading.Lock()
360
375
 
361
376
  # Now we can run background processes
362
377
  self._run_background_processes = True
@@ -1443,6 +1458,7 @@ class SystemDatabase(ABC):
1443
1458
  def create(
1444
1459
  system_database_url: str,
1445
1460
  engine_kwargs: Dict[str, Any],
1461
+ engine: Optional[sa.Engine],
1446
1462
  schema: Optional[str],
1447
1463
  debug_mode: bool = False,
1448
1464
  ) -> "SystemDatabase":
@@ -1453,6 +1469,8 @@ class SystemDatabase(ABC):
1453
1469
  return SQLiteSystemDatabase(
1454
1470
  system_database_url=system_database_url,
1455
1471
  engine_kwargs=engine_kwargs,
1472
+ engine=engine,
1473
+ schema=schema,
1456
1474
  debug_mode=debug_mode,
1457
1475
  )
1458
1476
  else:
@@ -1461,8 +1479,9 @@ class SystemDatabase(ABC):
1461
1479
  return PostgresSystemDatabase(
1462
1480
  system_database_url=system_database_url,
1463
1481
  engine_kwargs=engine_kwargs,
1464
- debug_mode=debug_mode,
1482
+ engine=engine,
1465
1483
  schema=schema,
1484
+ debug_mode=debug_mode,
1466
1485
  )
1467
1486
 
1468
1487
  @db_retry()
dbos/_sys_db_postgres.py CHANGED
@@ -1,12 +1,11 @@
1
1
  import time
2
- from typing import Any, Dict, Optional
2
+ from typing import Any, Dict, Optional, cast
3
3
 
4
4
  import psycopg
5
5
  import sqlalchemy as sa
6
6
  from sqlalchemy.exc import DBAPIError
7
7
 
8
8
  from dbos._migration import ensure_dbos_schema, run_dbos_migrations
9
- from dbos._schemas.system_database import SystemSchema
10
9
 
11
10
  from ._logger import dbos_logger
12
11
  from ._sys_db import SystemDatabase
@@ -15,25 +14,7 @@ from ._sys_db import SystemDatabase
15
14
  class PostgresSystemDatabase(SystemDatabase):
16
15
  """PostgreSQL-specific implementation of SystemDatabase."""
17
16
 
18
- def __init__(
19
- self,
20
- *,
21
- system_database_url: str,
22
- engine_kwargs: Dict[str, Any],
23
- schema: Optional[str],
24
- debug_mode: bool = False,
25
- ):
26
- super().__init__(
27
- system_database_url=system_database_url,
28
- engine_kwargs=engine_kwargs,
29
- debug_mode=debug_mode,
30
- )
31
- if schema is None:
32
- self.schema = "dbos"
33
- else:
34
- self.schema = schema
35
- SystemSchema.set_schema(self.schema)
36
- self.notification_conn: Optional[psycopg.connection.Connection] = None
17
+ notification_conn: Optional[sa.PoolProxiedConnection] = None
37
18
 
38
19
  def _create_engine(
39
20
  self, system_database_url: str, engine_kwargs: Dict[str, Any]
@@ -48,27 +29,35 @@ class PostgresSystemDatabase(SystemDatabase):
48
29
  return
49
30
  system_db_url = self.engine.url
50
31
  sysdb_name = system_db_url.database
51
- # If the system database does not already exist, create it
52
- engine = sa.create_engine(
53
- system_db_url.set(database="postgres"), **self._engine_kwargs
54
- )
55
- with engine.connect() as conn:
56
- conn.execution_options(isolation_level="AUTOCOMMIT")
57
- if not conn.execute(
58
- sa.text("SELECT 1 FROM pg_database WHERE datname=:db_name"),
59
- parameters={"db_name": sysdb_name},
60
- ).scalar():
61
- dbos_logger.info(f"Creating system database {sysdb_name}")
62
- conn.execute(sa.text(f"CREATE DATABASE {sysdb_name}"))
63
- engine.dispose()
32
+ # Unless we were provided an engine, if the system database does not already exist, create it
33
+ if self.created_engine:
34
+ engine = sa.create_engine(
35
+ system_db_url.set(database="postgres"), **self._engine_kwargs
36
+ )
37
+ with engine.connect() as conn:
38
+ conn.execution_options(isolation_level="AUTOCOMMIT")
39
+ if not conn.execute(
40
+ sa.text("SELECT 1 FROM pg_database WHERE datname=:db_name"),
41
+ parameters={"db_name": sysdb_name},
42
+ ).scalar():
43
+ dbos_logger.info(f"Creating system database {sysdb_name}")
44
+ conn.execute(sa.text(f"CREATE DATABASE {sysdb_name}"))
45
+ engine.dispose()
46
+ else:
47
+ # If we were provided an engine, validate it can connect
48
+ with self.engine.connect() as conn:
49
+ conn.execute(sa.text("SELECT 1"))
64
50
 
51
+ assert self.schema
65
52
  ensure_dbos_schema(self.engine, self.schema)
66
53
  run_dbos_migrations(self.engine, self.schema)
67
54
 
68
55
  def _cleanup_connections(self) -> None:
69
56
  """Clean up PostgreSQL-specific connections."""
70
- if self.notification_conn is not None:
71
- self.notification_conn.close()
57
+ with self._listener_thread_lock:
58
+ if self.notification_conn and self.notification_conn.dbapi_connection:
59
+ self.notification_conn.dbapi_connection.close()
60
+ self.notification_conn.invalidate()
72
61
 
73
62
  def _is_unique_constraint_violation(self, dbapi_error: DBAPIError) -> bool:
74
63
  """Check if the error is a unique constraint violation in PostgreSQL."""
@@ -111,20 +100,18 @@ class PostgresSystemDatabase(SystemDatabase):
111
100
  """Listen for PostgreSQL notifications using psycopg."""
112
101
  while self._run_background_processes:
113
102
  try:
114
- # since we're using the psycopg connection directly, we need a url without the "+psycopg" suffix
115
- url = sa.URL.create(
116
- "postgresql", **self.engine.url.translate_connect_args()
117
- )
118
- # Listen to notifications
119
- self.notification_conn = psycopg.connect(
120
- url.render_as_string(hide_password=False), autocommit=True
121
- )
122
-
123
- self.notification_conn.execute("LISTEN dbos_notifications_channel")
124
- self.notification_conn.execute("LISTEN dbos_workflow_events_channel")
125
-
103
+ with self._listener_thread_lock:
104
+ self.notification_conn = self.engine.raw_connection()
105
+ self.notification_conn.detach()
106
+ psycopg_conn = cast(
107
+ psycopg.connection.Connection, self.notification_conn
108
+ )
109
+ psycopg_conn.set_autocommit(True)
110
+
111
+ psycopg_conn.execute("LISTEN dbos_notifications_channel")
112
+ psycopg_conn.execute("LISTEN dbos_workflow_events_channel")
126
113
  while self._run_background_processes:
127
- gen = self.notification_conn.notifies()
114
+ gen = psycopg_conn.notifies()
128
115
  for notify in gen:
129
116
  channel = notify.channel
130
117
  dbos_logger.debug(
@@ -162,5 +149,4 @@ class PostgresSystemDatabase(SystemDatabase):
162
149
  time.sleep(1)
163
150
  # Then the loop will try to reconnect and restart the listener
164
151
  finally:
165
- if self.notification_conn is not None:
166
- self.notification_conn.close()
152
+ self._cleanup_connections()
dbos/_sys_db_sqlite.py CHANGED
@@ -6,7 +6,6 @@ import sqlalchemy as sa
6
6
  from sqlalchemy.exc import DBAPIError
7
7
 
8
8
  from dbos._migration import sqlite_migrations
9
- from dbos._schemas.system_database import SystemSchema
10
9
 
11
10
  from ._logger import dbos_logger
12
11
  from ._sys_db import SystemDatabase
@@ -19,7 +18,6 @@ class SQLiteSystemDatabase(SystemDatabase):
19
18
  self, system_database_url: str, engine_kwargs: Dict[str, Any]
20
19
  ) -> sa.Engine:
21
20
  """Create a SQLite engine."""
22
- SystemSchema.set_schema(None)
23
21
  return sa.create_engine(system_database_url)
24
22
 
25
23
  def run_migrations(self) -> None:
dbos/cli/migration.py CHANGED
@@ -20,6 +20,7 @@ def migrate_dbos_databases(
20
20
  "max_overflow": 0,
21
21
  "pool_size": 2,
22
22
  },
23
+ engine=None,
23
24
  schema=schema,
24
25
  )
25
26
  sys_db.run_migrations()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.15.0a5
3
+ Version: 1.15.0a7
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-1.15.0a5.dist-info/METADATA,sha256=ySISelF_uzHOl3jJk5bLWR6-58WI00Ys5TdK9cEhiFk,13021
2
- dbos-1.15.0a5.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- dbos-1.15.0a5.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-1.15.0a5.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
1
+ dbos-1.15.0a7.dist-info/METADATA,sha256=cVEV8R9_v6FXbU-t_Fs2e-6-M1yRTXJPbgyozjKOCxA,13021
2
+ dbos-1.15.0a7.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ dbos-1.15.0a7.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-1.15.0a7.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
5
  dbos/__init__.py,sha256=pT4BuNLDCrIQX27vQG8NlfxX6PZRU7r9miq4thJTszU,982
6
6
  dbos/__main__.py,sha256=G7Exn-MhGrVJVDbgNlpzhfh8WMX_72t3_oJaFT9Lmt8,653
7
7
  dbos/_admin_server.py,sha256=hubQJw5T8zGKCPNS6FQTXy8jQ8GTJxoYQaDTMlICl9k,16267
8
8
  dbos/_app_db.py,sha256=WJwUdKsTpSZPCIWVeSF5FQNf5y1PF_lJ96tiaCjvck8,16385
9
9
  dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
10
- dbos/_client.py,sha256=1M2PhMNodw7Cnfrs3D0_NbwY0VnDoA1OnAOkBnLzTGg,18805
10
+ dbos/_client.py,sha256=ppkO3bJ_qpBzWjYf9BkGfy8yF76e91UuhoLMtbIwXnU,18933
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
14
  dbos/_core.py,sha256=13DNN_fpSIs42NquV80XsHV7yKwY_adKP03h_xhXok4,50493
15
15
  dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
16
- dbos/_dbos.py,sha256=wT4POKmTKwPYrapMiV7EhfWicFdT9qVOiw3bujcE0Lg,57261
17
- dbos/_dbos_config.py,sha256=ha0Qo4kRCCi1dQZZzm1e24iMqR2Zty9LejcLxUbzlUg,25286
16
+ dbos/_dbos.py,sha256=MbcJRiPmjpJlIIlPBvEULAjMAcBZDzTX19tyT92-w_Q,57425
17
+ dbos/_dbos_config.py,sha256=4vgPyy4NiojAOiw3BnjWwiwT1_Ju3ZhXqJQOKDXhsE4,25148
18
18
  dbos/_debouncer.py,sha256=9-9dlXKLRHSUSylprCw18r-7MAdFwD8-w0KkY-bEG_I,15281
19
19
  dbos/_debug.py,sha256=0MfgNqutCUhI4PEmmra9x7f3DiFE_0nscfUCHdLimEY,1415
20
20
  dbos/_docker_pg_helper.py,sha256=xySum4hTA8TVMBODoG19u4cXQAB1vCock-jwM2pnmSI,7791
@@ -36,9 +36,9 @@ 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
38
  dbos/_serialization.py,sha256=GLgWLtHpvk7nSHyXukVQLE1ASNA3CJBtfF8w6iflBDw,3590
39
- dbos/_sys_db.py,sha256=Trlbf99KvH8YRthQ3diJQMSi0eDmky7X8dPG1wuv5VI,83098
40
- dbos/_sys_db_postgres.py,sha256=V3RGj2cXdCZ8xNxiMO-ECfKuHXtGZLVVNtOXtFSVHBw,7215
41
- dbos/_sys_db_sqlite.py,sha256=lEmJjyUHXo09q1sMpNpasurh4JyrH6DsmYzLjDC5k6Y,7091
39
+ dbos/_sys_db.py,sha256=rIbwxIc4uglpgsg5ZQRmfHv7YPfFQKUCDOW07pGrD8w,83699
40
+ dbos/_sys_db_postgres.py,sha256=GuyGVyZZD_Wl7LjRSkHnOuZ-hOROlO4Xs2UeDhKq10E,6963
41
+ dbos/_sys_db_sqlite.py,sha256=ifjKdy-Z9vlVIBf5L6XnSaNjiBdvqPE73asVHim4A5Q,6998
42
42
  dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
43
43
  dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
44
  dbos/_templates/dbos-db-starter/__package/main.py.dbos,sha256=aQnBPSSQpkB8ERfhf7gB7P9tsU6OPKhZscfeh0yiaD8,2702
@@ -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=1Y52EMc2rX7PJgJa3_KXV7oiQEO569k7aHXRofwYipo,3608
55
+ dbos/cli/migration.py,sha256=zJnDPUBnil5XZXFxc01EbZ0Radw_y8wtDGZExgelAxc,3633
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-1.15.0a5.dist-info/RECORD,,
59
+ dbos-1.15.0a7.dist-info/RECORD,,