dbos 1.15.0a3__tar.gz → 2.8.0a6__tar.gz

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.

Files changed (108) hide show
  1. dbos-1.15.0a3/README.md → dbos-2.8.0a6/PKG-INFO +42 -0
  2. dbos-1.15.0a3/PKG-INFO → dbos-2.8.0a6/README.md +6 -19
  3. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/__init__.py +4 -0
  4. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_admin_server.py +3 -1
  5. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_app_db.py +62 -141
  6. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_client.py +82 -46
  7. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_conductor/conductor.py +65 -32
  8. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_conductor/protocol.py +62 -1
  9. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_context.py +10 -3
  10. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_core.py +290 -167
  11. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_dbos.py +177 -73
  12. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_dbos_config.py +41 -99
  13. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_debouncer.py +14 -9
  14. dbos-2.8.0a6/dbos/_debug_trigger.py +108 -0
  15. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_error.py +12 -4
  16. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_fastapi.py +4 -4
  17. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_flask.py +2 -3
  18. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_kafka.py +6 -4
  19. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_logger.py +34 -20
  20. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_migration.py +134 -32
  21. dbos-2.8.0a6/dbos/_queue.py +258 -0
  22. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_recovery.py +1 -1
  23. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_scheduler.py +29 -16
  24. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_schemas/application_database.py +1 -1
  25. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_schemas/system_database.py +29 -9
  26. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_serialization.py +28 -36
  27. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_sys_db.py +781 -359
  28. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_sys_db_postgres.py +46 -55
  29. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_sys_db_sqlite.py +1 -59
  30. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_tracer.py +30 -18
  31. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_utils.py +10 -0
  32. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_workflow_commands.py +25 -32
  33. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/cli.py +9 -92
  34. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/migration.py +33 -1
  35. dbos-2.8.0a6/dbos/dbos-config.schema.json +61 -0
  36. {dbos-1.15.0a3 → dbos-2.8.0a6}/pyproject.toml +24 -4
  37. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/conftest.py +44 -4
  38. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_admin_server.py +80 -19
  39. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_async.py +40 -0
  40. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_async_workflow_management.py +0 -19
  41. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_classdecorators.py +51 -55
  42. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_client.py +45 -10
  43. dbos-2.8.0a6/tests/test_cockroachdb.py +40 -0
  44. dbos-2.8.0a6/tests/test_concurrency.py +59 -0
  45. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_config.py +32 -117
  46. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_dbos.py +550 -201
  47. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_debouncer.py +3 -5
  48. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_failures.py +74 -55
  49. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_fastapi.py +2 -0
  50. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_fastapi_roles.py +6 -8
  51. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_kafka.py +50 -17
  52. dbos-2.8.0a6/tests/test_metrics.py +52 -0
  53. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_package.py +0 -11
  54. dbos-2.8.0a6/tests/test_patch.py +313 -0
  55. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_queue.py +215 -53
  56. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_scheduler.py +20 -7
  57. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_schema_migration.py +97 -29
  58. dbos-2.8.0a6/tests/test_singleexec.py +324 -0
  59. dbos-2.8.0a6/tests/test_singleexec_async.py +314 -0
  60. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_spans.py +56 -72
  61. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_streaming.py +1 -2
  62. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_workflow_introspection.py +68 -7
  63. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_workflow_management.py +112 -62
  64. dbos-1.15.0a3/dbos/__main__.py +0 -29
  65. dbos-1.15.0a3/dbos/_debug.py +0 -43
  66. dbos-1.15.0a3/dbos/_queue.py +0 -132
  67. dbos-1.15.0a3/dbos/dbos-config.schema.json +0 -182
  68. dbos-1.15.0a3/tests/test_concurrency.py +0 -170
  69. dbos-1.15.0a3/tests/test_debug.py +0 -140
  70. {dbos-1.15.0a3 → dbos-2.8.0a6}/LICENSE +0 -0
  71. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_classproperty.py +0 -0
  72. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_croniter.py +0 -0
  73. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_docker_pg_helper.py +0 -0
  74. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_event_loop.py +0 -0
  75. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_kafka_message.py +0 -0
  76. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_outcome.py +0 -0
  77. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_registrations.py +0 -0
  78. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_roles.py +0 -0
  79. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_schemas/__init__.py +0 -0
  80. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/README.md +0 -0
  81. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  82. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
  83. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  84. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  85. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/migrations/create_table.py.dbos +0 -0
  86. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  87. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/_github_init.py +0 -0
  88. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/_template_init.py +0 -0
  89. {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/py.typed +0 -0
  90. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/__init__.py +0 -0
  91. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/atexit_no_ctor.py +0 -0
  92. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/atexit_no_launch.py +0 -0
  93. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/classdefs.py +0 -0
  94. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/client_collateral.py +0 -0
  95. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/client_worker.py +0 -0
  96. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/dupname_classdefs1.py +0 -0
  97. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/dupname_classdefsa.py +0 -0
  98. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/more_classdefs.py +0 -0
  99. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/queuedworkflow.py +0 -0
  100. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/script_without_fastapi.py +0 -0
  101. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_cli.py +0 -0
  102. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_croniter.py +0 -0
  103. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_docker_secrets.py +0 -0
  104. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_flask.py +0 -0
  105. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_outcome.py +0 -0
  106. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_singleton.py +0 -0
  107. {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_sqlalchemy.py +0 -0
  108. {dbos-1.15.0a3 → dbos-2.8.0a6}/version/__init__.py +0 -0
@@ -1,6 +1,48 @@
1
+ Metadata-Version: 2.1
2
+ Name: dbos
3
+ Version: 2.8.0a6
4
+ Summary: Ultra-lightweight durable execution in Python
5
+ Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
+ License: MIT
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Information Technology
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Internet
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Database
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Framework :: AsyncIO
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: pyyaml>=6.0.2
26
+ Requires-Dist: python-dateutil>=2.9.0.post0
27
+ Requires-Dist: psycopg[binary]>=3.1
28
+ Requires-Dist: websockets>=14.0
29
+ Requires-Dist: typer-slim>=0.17.4
30
+ Requires-Dist: sqlalchemy>=2.0.43
31
+ Provides-Extra: otel
32
+ Requires-Dist: opentelemetry-api>=1.37.0; extra == "otel"
33
+ Requires-Dist: opentelemetry-sdk>=1.37.0; extra == "otel"
34
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.37.0; extra == "otel"
35
+ Description-Content-Type: text/markdown
36
+
1
37
 
2
38
  <div align="center">
3
39
 
40
+ [![GitHub Actions](https://img.shields.io/github/actions/workflow/status/dbos-inc/dbos-transact-py/unit-test.yml?query=branch%3Amain)](https://github.com/dbos-inc/dbos-transact-py/actions/workflows/unit-test.yml)
41
+ [![PyPI release (latest SemVer)](https://img.shields.io/pypi/v/dbos.svg)](https://pypi.python.org/pypi/dbos)
42
+ [![Python Versions](https://img.shields.io/pypi/pyversions/dbos.svg)](https://pypi.python.org/pypi/dbos)
43
+ [![License (MIT)](https://img.shields.io/github/license/dbos-inc/dbos-transact-py.svg?v)](LICENSE)
44
+ [![Join Discord](https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white)](https://discord.com/invite/jsmC6pXGgX)
45
+
4
46
  # DBOS Transact: Lightweight Durable Workflows
5
47
 
6
48
  #### [Documentation](https://docs.dbos.dev/) &nbsp;&nbsp;•&nbsp;&nbsp; [Examples](https://docs.dbos.dev/examples) &nbsp;&nbsp;•&nbsp;&nbsp; [Github](https://github.com/dbos-inc) &nbsp;&nbsp;•&nbsp;&nbsp; [Discord](https://discord.com/invite/jsmC6pXGgX)
@@ -1,25 +1,12 @@
1
- Metadata-Version: 2.1
2
- Name: dbos
3
- Version: 1.15.0a3
4
- Summary: Ultra-lightweight durable execution in Python
5
- Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
- License: MIT
7
- Requires-Python: >=3.10
8
- Requires-Dist: pyyaml>=6.0.2
9
- Requires-Dist: python-dateutil>=2.9.0.post0
10
- Requires-Dist: psycopg[binary]>=3.1
11
- Requires-Dist: websockets>=14.0
12
- Requires-Dist: typer-slim>=0.17.4
13
- Requires-Dist: sqlalchemy>=2.0.43
14
- Provides-Extra: otel
15
- Requires-Dist: opentelemetry-api>=1.37.0; extra == "otel"
16
- Requires-Dist: opentelemetry-sdk>=1.37.0; extra == "otel"
17
- Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.37.0; extra == "otel"
18
- Description-Content-Type: text/markdown
19
-
20
1
 
21
2
  <div align="center">
22
3
 
4
+ [![GitHub Actions](https://img.shields.io/github/actions/workflow/status/dbos-inc/dbos-transact-py/unit-test.yml?query=branch%3Amain)](https://github.com/dbos-inc/dbos-transact-py/actions/workflows/unit-test.yml)
5
+ [![PyPI release (latest SemVer)](https://img.shields.io/pypi/v/dbos.svg)](https://pypi.python.org/pypi/dbos)
6
+ [![Python Versions](https://img.shields.io/pypi/pyversions/dbos.svg)](https://pypi.python.org/pypi/dbos)
7
+ [![License (MIT)](https://img.shields.io/github/license/dbos-inc/dbos-transact-py.svg?v)](LICENSE)
8
+ [![Join Discord](https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white)](https://discord.com/invite/jsmC6pXGgX)
9
+
23
10
  # DBOS Transact: Lightweight Durable Workflows
24
11
 
25
12
  #### [Documentation](https://docs.dbos.dev/) &nbsp;&nbsp;•&nbsp;&nbsp; [Examples](https://docs.dbos.dev/examples) &nbsp;&nbsp;•&nbsp;&nbsp; [Github](https://github.com/dbos-inc) &nbsp;&nbsp;•&nbsp;&nbsp; [Discord](https://discord.com/invite/jsmC6pXGgX)
@@ -12,7 +12,9 @@ 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
17
+ from .cli.migration import run_dbos_database_migrations
16
18
 
17
19
  __all__ = [
18
20
  "DBOSConfig",
@@ -35,4 +37,6 @@ __all__ = [
35
37
  "Queue",
36
38
  "Debouncer",
37
39
  "DebouncerClient",
40
+ "Serializer",
41
+ "run_dbos_database_migrations",
38
42
  ]
@@ -244,7 +244,7 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
244
244
  def _handle_restart(self, workflow_id: str) -> None:
245
245
  try:
246
246
  print(f"Restarting workflow {workflow_id}")
247
- handle = self.dbos.restart_workflow(workflow_id)
247
+ handle = self.dbos.fork_workflow(workflow_id, 1)
248
248
  response_body = json.dumps(
249
249
  {
250
250
  "workflow_id": handle.workflow_id,
@@ -338,6 +338,7 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
338
338
  end_time=filters.get("end_time"),
339
339
  status=filters.get("status"),
340
340
  app_version=filters.get("application_version"),
341
+ forked_from=filters.get("forked_from"),
341
342
  name=filters.get("workflow_name"),
342
343
  limit=filters.get("limit"),
343
344
  offset=filters.get("offset"),
@@ -364,6 +365,7 @@ class AdminRequestHandler(BaseHTTPRequestHandler):
364
365
  start_time=filters.get("start_time"),
365
366
  end_time=filters.get("end_time"),
366
367
  status=filters.get("status"),
368
+ forked_from=filters.get("forked_from"),
367
369
  name=filters.get("workflow_name"),
368
370
  limit=filters.get("limit"),
369
371
  offset=filters.get("offset"),
@@ -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,58 @@ 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
+ ) -> "ApplicationDatabase":
44
+ """Factory method to create the appropriate ApplicationDatabase implementation based on URL."""
45
+ if database_url.startswith("sqlite"):
46
+ return SQLiteApplicationDatabase(
47
+ database_url=database_url,
48
+ engine_kwargs=engine_kwargs,
49
+ schema=schema,
50
+ serializer=serializer,
51
+ )
52
+ else:
53
+ # Default to PostgreSQL for postgresql://, postgres://, or other URLs
54
+ return PostgresApplicationDatabase(
55
+ database_url=database_url,
56
+ engine_kwargs=engine_kwargs,
57
+ schema=schema,
58
+ serializer=serializer,
59
+ )
60
+
37
61
  def __init__(
38
62
  self,
39
63
  *,
40
64
  database_url: str,
41
65
  engine_kwargs: Dict[str, Any],
42
- debug_mode: bool = False,
66
+ serializer: Serializer,
67
+ schema: Optional[str],
43
68
  ):
69
+ # Log application database connection information
70
+ printable_url = sa.make_url(database_url).render_as_string(hide_password=True)
71
+ dbos_logger.info(
72
+ f"Initializing DBOS application database with URL: {printable_url}"
73
+ )
74
+ if not database_url.startswith("sqlite"):
75
+ dbos_logger.info(
76
+ f"DBOS application database engine parameters: {engine_kwargs}"
77
+ )
78
+
79
+ # Configure and initialize the application database
80
+ if database_url.startswith("sqlite"):
81
+ self.schema = None
82
+ else:
83
+ self.schema = schema if schema else "dbos"
84
+ ApplicationSchema.transaction_outputs.schema = schema
44
85
  self.engine = self._create_engine(database_url, engine_kwargs)
45
86
  self._engine_kwargs = engine_kwargs
46
87
  self.sessionmaker = sessionmaker(bind=self.engine)
47
- self.debug_mode = debug_mode
88
+ self.serializer = serializer
48
89
 
49
90
  @abstractmethod
50
91
  def _create_engine(
@@ -85,8 +126,6 @@ class ApplicationDatabase(ABC):
85
126
  raise
86
127
 
87
128
  def record_transaction_error(self, output: TransactionResultInternal) -> None:
88
- if self.debug_mode:
89
- raise Exception("called record_transaction_error in debug mode")
90
129
  try:
91
130
  with self.engine.begin() as conn:
92
131
  conn.execute(
@@ -138,77 +177,6 @@ class ApplicationDatabase(ABC):
138
177
  }
139
178
  return result
140
179
 
141
- def get_transactions(self, workflow_uuid: str) -> List[StepInfo]:
142
- with self.engine.begin() as conn:
143
- rows = conn.execute(
144
- sa.select(
145
- ApplicationSchema.transaction_outputs.c.function_id,
146
- ApplicationSchema.transaction_outputs.c.function_name,
147
- ApplicationSchema.transaction_outputs.c.output,
148
- ApplicationSchema.transaction_outputs.c.error,
149
- ).where(
150
- ApplicationSchema.transaction_outputs.c.workflow_uuid
151
- == workflow_uuid,
152
- )
153
- ).all()
154
- return [
155
- StepInfo(
156
- function_id=row[0],
157
- function_name=row[1],
158
- output=(
159
- _serialization.deserialize(row[2]) if row[2] is not None else row[2]
160
- ),
161
- error=(
162
- _serialization.deserialize_exception(row[3])
163
- if row[3] is not None
164
- else row[3]
165
- ),
166
- child_workflow_id=None,
167
- )
168
- for row in rows
169
- ]
170
-
171
- def clone_workflow_transactions(
172
- self, src_workflow_id: str, forked_workflow_id: str, start_step: int
173
- ) -> None:
174
- """
175
- Copies all steps from dbos.transctions_outputs where function_id < input function_id
176
- into a new workflow_uuid. Returns the new workflow_uuid.
177
- """
178
-
179
- with self.engine.begin() as conn:
180
-
181
- insert_stmt = sa.insert(ApplicationSchema.transaction_outputs).from_select(
182
- [
183
- "workflow_uuid",
184
- "function_id",
185
- "output",
186
- "error",
187
- "txn_id",
188
- "txn_snapshot",
189
- "executor_id",
190
- "function_name",
191
- ],
192
- sa.select(
193
- sa.literal(forked_workflow_id).label("workflow_uuid"),
194
- ApplicationSchema.transaction_outputs.c.function_id,
195
- ApplicationSchema.transaction_outputs.c.output,
196
- ApplicationSchema.transaction_outputs.c.error,
197
- ApplicationSchema.transaction_outputs.c.txn_id,
198
- ApplicationSchema.transaction_outputs.c.txn_snapshot,
199
- ApplicationSchema.transaction_outputs.c.executor_id,
200
- ApplicationSchema.transaction_outputs.c.function_name,
201
- ).where(
202
- (
203
- ApplicationSchema.transaction_outputs.c.workflow_uuid
204
- == src_workflow_id
205
- )
206
- & (ApplicationSchema.transaction_outputs.c.function_id < start_step)
207
- ),
208
- )
209
-
210
- conn.execute(insert_stmt)
211
-
212
180
  def garbage_collect(
213
181
  self, cutoff_epoch_timestamp_ms: int, pending_workflow_ids: list[str]
214
182
  ) -> None:
@@ -237,52 +205,10 @@ class ApplicationDatabase(ABC):
237
205
  """Check if the error is a serialization/concurrency error."""
238
206
  pass
239
207
 
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
208
 
264
209
  class PostgresApplicationDatabase(ApplicationDatabase):
265
210
  """PostgreSQL-specific implementation of ApplicationDatabase."""
266
211
 
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
212
  def _create_engine(
287
213
  self, database_url: str, engine_kwargs: Dict[str, Any]
288
214
  ) -> sa.Engine:
@@ -298,25 +224,26 @@ class PostgresApplicationDatabase(ApplicationDatabase):
298
224
  )
299
225
 
300
226
  def run_migrations(self) -> None:
301
- if self.debug_mode:
302
- dbos_logger.warning(
303
- "Application database migrations are skipped in debug mode."
304
- )
305
- return
306
227
  # Check if the database exists
307
228
  app_db_url = self.engine.url
308
- postgres_db_engine = sa.create_engine(
309
- app_db_url.set(database="postgres"),
310
- **self._engine_kwargs,
311
- )
312
- with postgres_db_engine.connect() as conn:
313
- conn.execution_options(isolation_level="AUTOCOMMIT")
314
- if not conn.execute(
315
- sa.text("SELECT 1 FROM pg_database WHERE datname=:db_name"),
316
- parameters={"db_name": app_db_url.database},
317
- ).scalar():
318
- conn.execute(sa.text(f"CREATE DATABASE {app_db_url.database}"))
319
- postgres_db_engine.dispose()
229
+ try:
230
+ postgres_db_engine = sa.create_engine(
231
+ app_db_url.set(database="postgres"),
232
+ **self._engine_kwargs,
233
+ )
234
+ with postgres_db_engine.connect() as conn:
235
+ conn.execution_options(isolation_level="AUTOCOMMIT")
236
+ if not conn.execute(
237
+ sa.text("SELECT 1 FROM pg_database WHERE datname=:db_name"),
238
+ parameters={"db_name": app_db_url.database},
239
+ ).scalar():
240
+ conn.execute(sa.text(f"CREATE DATABASE {app_db_url.database}"))
241
+ except Exception:
242
+ dbos_logger.warning(
243
+ f"Could not connect to postgres database to verify existence of {app_db_url.database}. Continuing..."
244
+ )
245
+ finally:
246
+ postgres_db_engine.dispose()
320
247
 
321
248
  # Create the dbos schema and transaction_outputs table in the application database
322
249
  with self.engine.begin() as conn:
@@ -380,12 +307,6 @@ class SQLiteApplicationDatabase(ApplicationDatabase):
380
307
  return sa.create_engine(database_url)
381
308
 
382
309
  def run_migrations(self) -> None:
383
- if self.debug_mode:
384
- dbos_logger.warning(
385
- "Application database migrations are skipped in debug mode."
386
- )
387
- return
388
-
389
310
  with self.engine.begin() as conn:
390
311
  # Check if table exists
391
312
  result = conn.execute(