dbos 1.13.0a3__py3-none-any.whl → 1.13.0a5__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/_sys_db_sqlite.py ADDED
@@ -0,0 +1,185 @@
1
+ import os
2
+ import time
3
+ from typing import Any, Dict, Optional, Tuple
4
+
5
+ import sqlalchemy as sa
6
+ from sqlalchemy.exc import DBAPIError
7
+
8
+ from dbos._migration import sqlite_migrations
9
+ from dbos._schemas.system_database import SystemSchema
10
+
11
+ from ._logger import dbos_logger
12
+ from ._sys_db import SystemDatabase
13
+
14
+
15
+ class SQLiteSystemDatabase(SystemDatabase):
16
+ """SQLite-specific implementation of SystemDatabase."""
17
+
18
+ def _create_engine(
19
+ self, system_database_url: str, engine_kwargs: Dict[str, Any]
20
+ ) -> sa.Engine:
21
+ """Create a SQLite engine."""
22
+ # TODO: Make the schema dynamic so this isn't needed
23
+ SystemSchema.workflow_status.schema = None
24
+ SystemSchema.operation_outputs.schema = None
25
+ SystemSchema.notifications.schema = None
26
+ SystemSchema.workflow_events.schema = None
27
+ SystemSchema.streams.schema = None
28
+ return sa.create_engine(system_database_url)
29
+
30
+ def run_migrations(self) -> None:
31
+ """Run SQLite-specific migrations."""
32
+ if self._debug_mode:
33
+ dbos_logger.warning("System database migrations are skipped in debug mode.")
34
+ return
35
+
36
+ with self.engine.begin() as conn:
37
+ # Enable foreign keys for SQLite
38
+ conn.execute(sa.text("PRAGMA foreign_keys = ON"))
39
+
40
+ # Check if migrations table exists
41
+ result = conn.execute(
42
+ sa.text(
43
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='dbos_migrations'"
44
+ )
45
+ ).fetchone()
46
+
47
+ if result is None:
48
+ # Create migrations table
49
+ conn.execute(
50
+ sa.text(
51
+ "CREATE TABLE dbos_migrations (version INTEGER NOT NULL PRIMARY KEY)"
52
+ )
53
+ )
54
+ last_applied = 0
55
+ else:
56
+ # Get current migration version
57
+ version_result = conn.execute(
58
+ sa.text("SELECT version FROM dbos_migrations")
59
+ ).fetchone()
60
+ last_applied = version_result[0] if version_result else 0
61
+
62
+ # Apply migrations starting from the next version
63
+ for i, migration_sql in enumerate(sqlite_migrations, 1):
64
+ if i <= last_applied:
65
+ continue
66
+
67
+ # Execute the migration
68
+ dbos_logger.info(
69
+ f"Applying DBOS SQLite system database schema migration {i}"
70
+ )
71
+
72
+ # SQLite only allows one statement at a time, so split by semicolon
73
+ statements = [
74
+ stmt.strip() for stmt in migration_sql.split(";") if stmt.strip()
75
+ ]
76
+ for statement in statements:
77
+ conn.execute(sa.text(statement))
78
+
79
+ # Update the single row with the new version
80
+ if last_applied == 0:
81
+ conn.execute(
82
+ sa.text(
83
+ "INSERT INTO dbos_migrations (version) VALUES (:version)"
84
+ ),
85
+ {"version": i},
86
+ )
87
+ else:
88
+ conn.execute(
89
+ sa.text("UPDATE dbos_migrations SET version = :version"),
90
+ {"version": i},
91
+ )
92
+ last_applied = i
93
+
94
+ def _cleanup_connections(self) -> None:
95
+ """Clean up SQLite-specific connections."""
96
+ # SQLite doesn't require special connection cleanup
97
+ pass
98
+
99
+ def _is_unique_constraint_violation(self, dbapi_error: DBAPIError) -> bool:
100
+ """Check if the error is a unique constraint violation in SQLite."""
101
+ # SQLite UNIQUE constraint error
102
+ return "UNIQUE constraint failed" in str(dbapi_error.orig)
103
+
104
+ def _is_foreign_key_violation(self, dbapi_error: DBAPIError) -> bool:
105
+ """Check if the error is a foreign key violation in SQLite."""
106
+ # SQLite FOREIGN KEY constraint error
107
+ return "FOREIGN KEY constraint failed" in str(dbapi_error.orig)
108
+
109
+ @staticmethod
110
+ def _reset_system_database(database_url: str) -> None:
111
+ """Reset the SQLite system database by deleting the database file."""
112
+
113
+ # Parse the SQLite database URL to get the file path
114
+ url = sa.make_url(database_url)
115
+ db_path = url.database
116
+
117
+ if db_path is None:
118
+ raise ValueError(f"System database path not found in URL {url}")
119
+
120
+ try:
121
+ if os.path.exists(db_path):
122
+ os.remove(db_path)
123
+ dbos_logger.info(f"Deleted SQLite database file: {db_path}")
124
+ else:
125
+ dbos_logger.info(f"SQLite database file does not exist: {db_path}")
126
+ except OSError as e:
127
+ dbos_logger.error(
128
+ f"Error deleting SQLite database file {db_path}: {str(e)}"
129
+ )
130
+ raise e
131
+
132
+ def _notification_listener(self) -> None:
133
+ """Poll for notifications and workflow events in SQLite."""
134
+
135
+ def split_payload(payload: str) -> Tuple[str, Optional[str]]:
136
+ """Split payload into components (first::second format)."""
137
+ if "::" in payload:
138
+ parts = payload.split("::", 1)
139
+ return parts[0], parts[1]
140
+ return payload, None
141
+
142
+ def signal_condition(condition_map: Any, payload: str) -> None:
143
+ """Signal a condition variable if it exists."""
144
+ condition = condition_map.get(payload)
145
+ if condition:
146
+ condition.acquire()
147
+ condition.notify_all()
148
+ condition.release()
149
+ dbos_logger.debug(f"Signaled condition for {payload}")
150
+
151
+ while self._run_background_processes:
152
+ try:
153
+ # Poll every second
154
+ time.sleep(1)
155
+
156
+ # Check all payloads in the notifications_map
157
+ for payload in list(self.notifications_map._dict.keys()):
158
+ dest_uuid, topic = split_payload(payload)
159
+ with self.engine.begin() as conn:
160
+ result = conn.execute(
161
+ sa.text(
162
+ "SELECT 1 FROM notifications WHERE destination_uuid = :dest_uuid AND topic = :topic LIMIT 1"
163
+ ),
164
+ {"dest_uuid": dest_uuid, "topic": topic},
165
+ )
166
+ if result.fetchone():
167
+ signal_condition(self.notifications_map, payload)
168
+
169
+ # Check all payloads in the workflow_events_map
170
+ for payload in list(self.workflow_events_map._dict.keys()):
171
+ workflow_uuid, key = split_payload(payload)
172
+ with self.engine.begin() as conn:
173
+ result = conn.execute(
174
+ sa.text(
175
+ "SELECT 1 FROM workflow_events WHERE workflow_uuid = :workflow_uuid AND key = :key LIMIT 1"
176
+ ),
177
+ {"workflow_uuid": workflow_uuid, "key": key},
178
+ )
179
+ if result.fetchone():
180
+ signal_condition(self.workflow_events_map, payload)
181
+
182
+ except Exception as e:
183
+ if self._run_background_processes:
184
+ dbos_logger.warning(f"SQLite notification poller error: {e}")
185
+ time.sleep(1)
dbos/_utils.py CHANGED
@@ -20,7 +20,9 @@ class GlobalParams:
20
20
  dbos_version = "unknown"
21
21
 
22
22
 
23
- def retriable_postgres_exception(e: DBAPIError) -> bool:
23
+ def retriable_postgres_exception(e: Exception) -> bool:
24
+ if not isinstance(e, DBAPIError):
25
+ return False
24
26
  if e.connection_invalidated:
25
27
  return True
26
28
  if isinstance(e.orig, psycopg.OperationalError):
@@ -48,3 +50,10 @@ def retriable_postgres_exception(e: DBAPIError) -> bool:
48
50
  return False
49
51
  else:
50
52
  return False
53
+
54
+
55
+ def retriable_sqlite_exception(e: Exception) -> bool:
56
+ if "database is locked" in str(e):
57
+ return True
58
+ else:
59
+ return False
dbos/cli/cli.py CHANGED
@@ -29,7 +29,7 @@ from .._dbos_config import (
29
29
  )
30
30
  from .._docker_pg_helper import start_docker_pg, stop_docker_pg
31
31
  from .._schemas.system_database import SystemSchema
32
- from .._sys_db import SystemDatabase, reset_system_database
32
+ from .._sys_db import SystemDatabase
33
33
  from .._utils import GlobalParams
34
34
  from ..cli._github_init import create_template_from_github
35
35
  from ._template_init import copy_template, get_project_name, get_templates_directory
@@ -371,22 +371,12 @@ def reset(
371
371
  typer.echo("Operation cancelled.")
372
372
  raise typer.Exit()
373
373
  try:
374
- # Make a SA url out of the user-provided URL and verify a database name is present
375
374
  database_url = _get_db_url(db_url)
376
- pg_db_url = sa.make_url(database_url)
377
- assert (
378
- pg_db_url.database is not None
379
- ), f"Database name is required in URL: {pg_db_url.render_as_string(hide_password=True)}"
380
- # Resolve system database name
381
- sysdb_name = (
382
- sys_db_name
383
- if sys_db_name
384
- else (pg_db_url.database + SystemSchema.sysdb_suffix)
375
+ system_database_url = get_system_database_url(
376
+ {"database_url": database_url, "database": {"sys_db_name": sys_db_name}}
385
377
  )
386
- reset_system_database(
387
- postgres_db_url=pg_db_url.set(database="postgres"), sysdb_name=sysdb_name
388
- )
389
- except sa.exc.SQLAlchemyError as e:
378
+ SystemDatabase.reset_system_database(system_database_url)
379
+ except Exception as e:
390
380
  typer.echo(f"Error resetting system database: {str(e)}")
391
381
  return
392
382
 
dbos/cli/migration.py CHANGED
@@ -9,7 +9,7 @@ def migrate_dbos_databases(app_database_url: str, system_database_url: str) -> N
9
9
  app_db = None
10
10
  sys_db = None
11
11
  try:
12
- sys_db = SystemDatabase(
12
+ sys_db = SystemDatabase.create(
13
13
  system_database_url=system_database_url,
14
14
  engine_kwargs={
15
15
  "pool_timeout": 30,
@@ -17,7 +17,7 @@ def migrate_dbos_databases(app_database_url: str, system_database_url: str) -> N
17
17
  "pool_size": 2,
18
18
  },
19
19
  )
20
- app_db = ApplicationDatabase(
20
+ app_db = ApplicationDatabase.create(
21
21
  database_url=app_database_url,
22
22
  engine_kwargs={
23
23
  "pool_timeout": 30,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.13.0a3
3
+ Version: 1.13.0a5
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
- dbos-1.13.0a3.dist-info/METADATA,sha256=EkcdC671U6Va-Oi2RP7Zs1v7Ag6ZaN99Gy4lil5JVss,13268
2
- dbos-1.13.0a3.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- dbos-1.13.0a3.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
- dbos-1.13.0a3.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
1
+ dbos-1.13.0a5.dist-info/METADATA,sha256=obXcr7i8Wu_nO0bUihx40pr-fopo32nU3rqFBBe3D0o,13268
2
+ dbos-1.13.0a5.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
+ dbos-1.13.0a5.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
4
+ dbos-1.13.0a5.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
7
  dbos/_admin_server.py,sha256=e8ELhcDWqR3_PNobnNgUvLGh5lzZq0yFSF6dvtzoQRI,16267
@@ -22,16 +22,16 @@ dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py,sha256=G942noph
22
22
  dbos/_alembic_migrations/versions/d994145b47b6_consolidate_inputs.py,sha256=_J0jP247fuo66fzOmLlKFO9FJ_CRBXlqa2lnLrcXugQ,672
23
23
  dbos/_alembic_migrations/versions/eab0cc1d9a14_job_queue.py,sha256=uvhFOtqbBreCePhAxZfIT0qCAI7BiZTou9wt6QnbY7c,1412
24
24
  dbos/_alembic_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py,sha256=m90Lc5YH0ZISSq1MyxND6oq3RZrZKrIqEsZtwJ1jWxA,1049
25
- dbos/_app_db.py,sha256=bUXQqzc0C9PHh4Zl2tHfBrQWNBURdI7F7XXjCpYirmw,10959
25
+ dbos/_app_db.py,sha256=e8mASnDX_tyE9DWf_yAZgIi7ZWeyVWWTrhCZK_Jy50g,16190
26
26
  dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
27
- dbos/_client.py,sha256=_wMe4qnRSwiRZo74xdqTBetbHlIVy3vQifdSd7os1ZY,18213
27
+ dbos/_client.py,sha256=hSSIqWZEbGVAyRy80rS4Sywaf_biQOF17ThW3Eyaaso,18267
28
28
  dbos/_conductor/conductor.py,sha256=3E_hL3c9g9yWqKZkvI6KA0-ZzPMPRo06TOzT1esMiek,24114
29
29
  dbos/_conductor/protocol.py,sha256=q3rgLxINFtWFigdOONc-4gX4vn66UmMlJQD6Kj8LnL4,7420
30
30
  dbos/_context.py,sha256=IMboNgbCqTxfIORqeifE3da-Ce5siMz7MYMLPk5M-AQ,26851
31
- dbos/_core.py,sha256=bEtIXf0OEzEwiS4sYLJR3vT-bmt5Hg1SBHUcCkaHmMQ,49005
31
+ dbos/_core.py,sha256=2IWEkewiYL3d3MrhMfqj0JDWNyNM54rpW7CRGjBTPfk,48788
32
32
  dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
33
- dbos/_dbos.py,sha256=kNTJLQn1uLnzbglyRZLSkJYOYhQseoVp84yaV1lAc00,58458
34
- dbos/_dbos_config.py,sha256=qduDBmrpISNRZcCO-NcARQMeO2AzExyC2h3HCd9lRqU,22128
33
+ dbos/_dbos.py,sha256=wKLN7W1ej6cyEOHnCtt3-awnU6SR6MQAlOvTTt5_N-E,58219
34
+ dbos/_dbos_config.py,sha256=jv-k2WyXgfoR3R0NaPxAFx5OzZYjr0rjH2npsbokGRM,22321
35
35
  dbos/_debug.py,sha256=99j2SChWmCPAlZoDmjsJGe77tpU2LEa8E2TtLAnnh7o,1831
36
36
  dbos/_docker_pg_helper.py,sha256=tLJXWqZ4S-ExcaPnxg_i6cVxL6ZxrYlZjaGsklY-s2I,6115
37
37
  dbos/_error.py,sha256=GwO0Ng4d4iB52brY09-Ss6Cz_V28Xc0D0cRCzZ6XmNM,8688
@@ -41,7 +41,7 @@ dbos/_flask.py,sha256=Npnakt-a3W5OykONFRkDRnumaDhTQmA0NPdUCGRYKXE,1652
41
41
  dbos/_kafka.py,sha256=Gm4fHWl7gYb-i5BMvwNwm5Km3z8zQpseqdMgqgFjlGI,4252
42
42
  dbos/_kafka_message.py,sha256=NYvOXNG3Qn7bghn1pv3fg4Pbs86ILZGcK4IB-MLUNu0,409
43
43
  dbos/_logger.py,sha256=vvBL4kzJWBPtfcHIuL0nJAq5cgpeo_D3IiIUDicWLOg,4732
44
- dbos/_migration.py,sha256=C2ODGlZKjnGKS3N8jlokzRDdQIHlQMPHnEtBsSXYX34,8016
44
+ dbos/_migration.py,sha256=wJrSTYerlkYDFYmkTqo4a7n4WaKmqXh8BdkUEzgSEQQ,10898
45
45
  dbos/_outcome.py,sha256=Kz3aL7517q9UEFTx3Cq9zzztjWyWVOx_08fZyHo9dvg,7035
46
46
  dbos/_queue.py,sha256=0kJTPwXy3nZ4Epzt-lHky9M9S4L31645drPGFR8fIJY,4854
47
47
  dbos/_recovery.py,sha256=K-wlFhdf4yGRm6cUzyhcTjQUS0xp2T5rdNMLiiBErYg,2882
@@ -52,7 +52,9 @@ dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  dbos/_schemas/application_database.py,sha256=SypAS9l9EsaBHFn9FR8jmnqt01M74d9AF1AMa4m2hhI,1040
53
53
  dbos/_schemas/system_database.py,sha256=-dAKk-_Y3vzbpLT4ei-sIrBQgFyQiwPj1enZb1TYc8I,4943
54
54
  dbos/_serialization.py,sha256=bWuwhXSQcGmiazvhJHA5gwhrRWxtmFmcCFQSDJnqqkU,3666
55
- dbos/_sys_db.py,sha256=YLoiAUiY1-kaGOVnJxoY_dVI2QeR50m_7ijlIdZYWFk,84768
55
+ dbos/_sys_db.py,sha256=zlAJ3YPLuzN8Ju32_lfhsq3-MxnH4V45Rf6X9Imxqf0,81838
56
+ dbos/_sys_db_postgres.py,sha256=WcG-f1CUzUNBGEOjqKEp6DDraN63jTnJ6CAfieCcxOs,7555
57
+ dbos/_sys_db_sqlite.py,sha256=58Fv7paGloCmpXdWZVCEpcZFvHWLuEkw4XBKCWprGsA,7500
56
58
  dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
57
59
  dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
60
  dbos/_templates/dbos-db-starter/__package/main.py.dbos,sha256=aQnBPSSQpkB8ERfhf7gB7P9tsU6OPKhZscfeh0yiaD8,2702
@@ -64,13 +66,13 @@ dbos/_templates/dbos-db-starter/migrations/script.py.mako,sha256=MEqL-2qATlST9TA
64
66
  dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py,sha256=MpS7LGaJS0CpvsjhfDkp9EJqvMvVCjRPfUp4c0aE2ys,941
65
67
  dbos/_templates/dbos-db-starter/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
66
68
  dbos/_tracer.py,sha256=8aOAVTBnj2q9DcOb5KJCfo56CVZ1ZvsWBscaNlIX-7k,3150
67
- dbos/_utils.py,sha256=uywq1QrjMwy17btjxW4bES49povlQwYwYbvKwMT6C2U,1575
69
+ dbos/_utils.py,sha256=ZdoM1MDbHnlJrh31zfhp3iX62bAxK1kyvMwXnltC_84,1779
68
70
  dbos/_workflow_commands.py,sha256=EmmAaQfRWeOZm_WPTznuU-O3he3jiSzzT9VpYrhxugE,4835
69
71
  dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
70
72
  dbos/cli/_template_init.py,sha256=7JBcpMqP1r2mfCnvWatu33z8ctEGHJarlZYKgB83cXE,2972
71
- dbos/cli/cli.py,sha256=btUbl0L_1cf46W8z0Hi6nPBCLaSqY9I4c1eZCG7obow,23128
72
- dbos/cli/migration.py,sha256=eI0sc0vYq2iUP3cBHPfTa6WHCyDBr8ld9nRxEZZzFrU,3316
73
+ dbos/cli/cli.py,sha256=basgruBuvOMIb5nrsa1cVKOOYvSx_Lqx3M51P0d6NEU,22698
74
+ dbos/cli/migration.py,sha256=5GiyagLZkyVvDz3StYxtFdkFoKFCmh6eSXjzsIGhZ_A,3330
73
75
  dbos/dbos-config.schema.json,sha256=CjaspeYmOkx6Ip_pcxtmfXJTn_YGdSx_0pcPBF7KZmo,6060
74
76
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
75
77
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
76
- dbos-1.13.0a3.dist-info/RECORD,,
78
+ dbos-1.13.0a5.dist-info/RECORD,,