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.
- dbos-1.15.0a3/README.md → dbos-2.8.0a6/PKG-INFO +42 -0
- dbos-1.15.0a3/PKG-INFO → dbos-2.8.0a6/README.md +6 -19
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/__init__.py +4 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_admin_server.py +3 -1
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_app_db.py +62 -141
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_client.py +82 -46
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_conductor/conductor.py +65 -32
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_conductor/protocol.py +62 -1
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_context.py +10 -3
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_core.py +290 -167
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_dbos.py +177 -73
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_dbos_config.py +41 -99
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_debouncer.py +14 -9
- dbos-2.8.0a6/dbos/_debug_trigger.py +108 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_error.py +12 -4
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_fastapi.py +4 -4
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_flask.py +2 -3
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_kafka.py +6 -4
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_logger.py +34 -20
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_migration.py +134 -32
- dbos-2.8.0a6/dbos/_queue.py +258 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_recovery.py +1 -1
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_scheduler.py +29 -16
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_schemas/application_database.py +1 -1
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_schemas/system_database.py +29 -9
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_serialization.py +28 -36
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_sys_db.py +781 -359
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_sys_db_postgres.py +46 -55
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_sys_db_sqlite.py +1 -59
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_tracer.py +30 -18
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_utils.py +10 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_workflow_commands.py +25 -32
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/cli.py +9 -92
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/migration.py +33 -1
- dbos-2.8.0a6/dbos/dbos-config.schema.json +61 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/pyproject.toml +24 -4
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/conftest.py +44 -4
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_admin_server.py +80 -19
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_async.py +40 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_async_workflow_management.py +0 -19
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_classdecorators.py +51 -55
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_client.py +45 -10
- dbos-2.8.0a6/tests/test_cockroachdb.py +40 -0
- dbos-2.8.0a6/tests/test_concurrency.py +59 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_config.py +32 -117
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_dbos.py +550 -201
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_debouncer.py +3 -5
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_failures.py +74 -55
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_fastapi.py +2 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_fastapi_roles.py +6 -8
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_kafka.py +50 -17
- dbos-2.8.0a6/tests/test_metrics.py +52 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_package.py +0 -11
- dbos-2.8.0a6/tests/test_patch.py +313 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_queue.py +215 -53
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_scheduler.py +20 -7
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_schema_migration.py +97 -29
- dbos-2.8.0a6/tests/test_singleexec.py +324 -0
- dbos-2.8.0a6/tests/test_singleexec_async.py +314 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_spans.py +56 -72
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_streaming.py +1 -2
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_workflow_introspection.py +68 -7
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_workflow_management.py +112 -62
- dbos-1.15.0a3/dbos/__main__.py +0 -29
- dbos-1.15.0a3/dbos/_debug.py +0 -43
- dbos-1.15.0a3/dbos/_queue.py +0 -132
- dbos-1.15.0a3/dbos/dbos-config.schema.json +0 -182
- dbos-1.15.0a3/tests/test_concurrency.py +0 -170
- dbos-1.15.0a3/tests/test_debug.py +0 -140
- {dbos-1.15.0a3 → dbos-2.8.0a6}/LICENSE +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_classproperty.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_croniter.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_event_loop.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_kafka_message.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_outcome.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_registrations.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_roles.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/migrations/create_table.py.dbos +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/_github_init.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/cli/_template_init.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/dbos/py.typed +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/__init__.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/atexit_no_launch.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/classdefs.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/client_collateral.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/client_worker.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/more_classdefs.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/queuedworkflow.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/script_without_fastapi.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_cli.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_croniter.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_docker_secrets.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_flask.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_outcome.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_singleton.py +0 -0
- {dbos-1.15.0a3 → dbos-2.8.0a6}/tests/test_sqlalchemy.py +0 -0
- {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
|
+
[](https://github.com/dbos-inc/dbos-transact-py/actions/workflows/unit-test.yml)
|
|
41
|
+
[](https://pypi.python.org/pypi/dbos)
|
|
42
|
+
[](https://pypi.python.org/pypi/dbos)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
[](https://discord.com/invite/jsmC6pXGgX)
|
|
45
|
+
|
|
4
46
|
# DBOS Transact: Lightweight Durable Workflows
|
|
5
47
|
|
|
6
48
|
#### [Documentation](https://docs.dbos.dev/) • [Examples](https://docs.dbos.dev/examples) • [Github](https://github.com/dbos-inc) • [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
|
+
[](https://github.com/dbos-inc/dbos-transact-py/actions/workflows/unit-test.yml)
|
|
5
|
+
[](https://pypi.python.org/pypi/dbos)
|
|
6
|
+
[](https://pypi.python.org/pypi/dbos)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://discord.com/invite/jsmC6pXGgX)
|
|
9
|
+
|
|
23
10
|
# DBOS Transact: Lightweight Durable Workflows
|
|
24
11
|
|
|
25
12
|
#### [Documentation](https://docs.dbos.dev/) • [Examples](https://docs.dbos.dev/examples) • [Github](https://github.com/dbos-inc) • [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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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(
|