dbos 1.13.0a5__tar.gz → 1.13.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.
- {dbos-1.13.0a5 → dbos-1.13.0a6}/PKG-INFO +1 -1
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_app_db.py +0 -2
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_client.py +27 -13
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_dbos_config.py +107 -42
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_sys_db.py +0 -1
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_sys_db_sqlite.py +0 -3
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/cli/cli.py +200 -81
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/dbos-config.schema.json +4 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/pyproject.toml +1 -1
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/conftest.py +35 -15
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/queuedworkflow.py +2 -1
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_client.py +2 -2
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_config.py +115 -10
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_docker_secrets.py +5 -5
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_package.py +11 -7
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_queue.py +2 -1
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_schema_migration.py +80 -2
- dbos-1.13.0a5/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -67
- dbos-1.13.0a5/tests/test_migrate.py +0 -79
- {dbos-1.13.0a5 → dbos-1.13.0a6}/LICENSE +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/README.md +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/__init__.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/__main__.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_admin_server.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/env.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/script.py.mako +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/01ce9f07bd10_streaming.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/471b60d64126_dbos_migrations.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_alembic_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_classproperty.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_context.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_core.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_croniter.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_dbos.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_debug.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_error.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_event_loop.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_fastapi.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_flask.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_kafka.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_kafka_message.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_logger.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_migration.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_outcome.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_queue.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_recovery.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_registrations.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_roles.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_scheduler.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_serialization.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_sys_db_postgres.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_tracer.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_utils.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/_workflow_commands.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/cli/_github_init.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/cli/_template_init.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/cli/migration.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/dbos/py.typed +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/__init__.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/atexit_no_launch.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/classdefs.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/client_collateral.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/client_worker.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/more_classdefs.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_admin_server.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_async.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_async_workflow_management.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_classdecorators.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_cli.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_concurrency.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_croniter.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_dbos.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_debug.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_failures.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_fastapi.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_flask.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_kafka.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_outcome.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_scheduler.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_singleton.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_spans.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_streaming.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_workflow_introspection.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/tests/test_workflow_management.py +0 -0
- {dbos-1.13.0a5 → dbos-1.13.0a6}/version/__init__.py +0 -0
|
@@ -383,7 +383,6 @@ class SQLiteApplicationDatabase(ApplicationDatabase):
|
|
|
383
383
|
).fetchone()
|
|
384
384
|
|
|
385
385
|
if result is None:
|
|
386
|
-
# Create the table with proper SQLite syntax
|
|
387
386
|
conn.execute(
|
|
388
387
|
sa.text(
|
|
389
388
|
f"""
|
|
@@ -402,7 +401,6 @@ class SQLiteApplicationDatabase(ApplicationDatabase):
|
|
|
402
401
|
"""
|
|
403
402
|
)
|
|
404
403
|
)
|
|
405
|
-
# Create the index
|
|
406
404
|
conn.execute(
|
|
407
405
|
sa.text(
|
|
408
406
|
"CREATE INDEX transaction_outputs_created_at_index ON transaction_outputs (created_at)"
|
|
@@ -25,7 +25,11 @@ else:
|
|
|
25
25
|
|
|
26
26
|
from dbos import _serialization
|
|
27
27
|
from dbos._dbos import WorkflowHandle, WorkflowHandleAsync
|
|
28
|
-
from dbos._dbos_config import
|
|
28
|
+
from dbos._dbos_config import (
|
|
29
|
+
get_application_database_url,
|
|
30
|
+
get_system_database_url,
|
|
31
|
+
is_valid_database_url,
|
|
32
|
+
)
|
|
29
33
|
from dbos._error import DBOSException, DBOSNonExistentWorkflowError
|
|
30
34
|
from dbos._registrations import DEFAULT_MAX_RECOVERY_ATTEMPTS
|
|
31
35
|
from dbos._serialization import WorkflowInputs
|
|
@@ -113,21 +117,32 @@ class WorkflowHandleClientAsyncPolling(Generic[R]):
|
|
|
113
117
|
class DBOSClient:
|
|
114
118
|
def __init__(
|
|
115
119
|
self,
|
|
116
|
-
database_url: str,
|
|
120
|
+
database_url: Optional[str] = None, # DEPRECATED
|
|
117
121
|
*,
|
|
118
122
|
system_database_url: Optional[str] = None,
|
|
119
|
-
|
|
123
|
+
application_database_url: Optional[str] = None,
|
|
124
|
+
system_database: Optional[str] = None, # DEPRECATED
|
|
120
125
|
):
|
|
121
|
-
|
|
126
|
+
application_database_url = get_application_database_url(
|
|
127
|
+
{
|
|
128
|
+
"system_database_url": system_database_url,
|
|
129
|
+
"database_url": (
|
|
130
|
+
database_url if database_url else application_database_url
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
system_database_url = get_system_database_url(
|
|
135
|
+
{
|
|
136
|
+
"system_database_url": system_database_url,
|
|
137
|
+
"database_url": application_database_url,
|
|
138
|
+
"database": {"sys_db_name": system_database},
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
assert is_valid_database_url(system_database_url)
|
|
142
|
+
assert is_valid_database_url(application_database_url)
|
|
122
143
|
# We only create database connections but do not run migrations
|
|
123
144
|
self._sys_db = SystemDatabase.create(
|
|
124
|
-
system_database_url=
|
|
125
|
-
{
|
|
126
|
-
"system_database_url": system_database_url,
|
|
127
|
-
"database_url": database_url,
|
|
128
|
-
"database": {"sys_db_name": system_database},
|
|
129
|
-
}
|
|
130
|
-
),
|
|
145
|
+
system_database_url=system_database_url,
|
|
131
146
|
engine_kwargs={
|
|
132
147
|
"pool_timeout": 30,
|
|
133
148
|
"max_overflow": 0,
|
|
@@ -136,14 +151,13 @@ class DBOSClient:
|
|
|
136
151
|
)
|
|
137
152
|
self._sys_db.check_connection()
|
|
138
153
|
self._app_db = ApplicationDatabase.create(
|
|
139
|
-
database_url=
|
|
154
|
+
database_url=application_database_url,
|
|
140
155
|
engine_kwargs={
|
|
141
156
|
"pool_timeout": 30,
|
|
142
157
|
"max_overflow": 0,
|
|
143
158
|
"pool_size": 2,
|
|
144
159
|
},
|
|
145
160
|
)
|
|
146
|
-
self._db_url = database_url
|
|
147
161
|
|
|
148
162
|
def destroy(self) -> None:
|
|
149
163
|
self._sys_db.destroy()
|
|
@@ -22,9 +22,10 @@ class DBOSConfig(TypedDict, total=False):
|
|
|
22
22
|
|
|
23
23
|
Attributes:
|
|
24
24
|
name (str): Application name
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
system_database_url (str): Connection string for the DBOS system database. Defaults to sqlite:///{name} if not provided.
|
|
26
|
+
application_database_url (str): Connection string for the DBOS application database, in which DBOS @Transaction functions run. Optional. Should be the same type of database (SQLite or Postgres) as the system database.
|
|
27
|
+
database_url (str): (DEPRECATED) Database connection string
|
|
28
|
+
sys_db_name (str): (DEPRECATED) System database name
|
|
28
29
|
sys_db_pool_size (int): System database pool size
|
|
29
30
|
db_engine_kwargs (Dict[str, Any]): SQLAlchemy engine kwargs (See https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine)
|
|
30
31
|
log_level (str): Log level
|
|
@@ -39,8 +40,9 @@ class DBOSConfig(TypedDict, total=False):
|
|
|
39
40
|
"""
|
|
40
41
|
|
|
41
42
|
name: str
|
|
42
|
-
database_url: Optional[str]
|
|
43
43
|
system_database_url: Optional[str]
|
|
44
|
+
application_database_url: Optional[str]
|
|
45
|
+
database_url: Optional[str]
|
|
44
46
|
sys_db_name: Optional[str]
|
|
45
47
|
sys_db_pool_size: Optional[int]
|
|
46
48
|
db_engine_kwargs: Optional[Dict[str, Any]]
|
|
@@ -107,12 +109,12 @@ class ConfigFile(TypedDict, total=False):
|
|
|
107
109
|
|
|
108
110
|
Attributes:
|
|
109
111
|
name (str): Application name
|
|
110
|
-
runtimeConfig (RuntimeConfig): Configuration for
|
|
112
|
+
runtimeConfig (RuntimeConfig): Configuration for DBOS Cloud
|
|
111
113
|
database (DatabaseConfig): Configure pool sizes, migrate commands
|
|
112
|
-
database_url (str):
|
|
114
|
+
database_url (str): Application database URL
|
|
115
|
+
system_database_url (str): System database URL
|
|
113
116
|
telemetry (TelemetryConfig): Configuration for tracing / logging
|
|
114
|
-
env (Dict[str,str]): Environment
|
|
115
|
-
application (Dict[str, Any]): Application-specific configuration section
|
|
117
|
+
env (Dict[str,str]): Environment variables
|
|
116
118
|
|
|
117
119
|
"""
|
|
118
120
|
|
|
@@ -144,8 +146,12 @@ def translate_dbos_config_to_config_file(config: DBOSConfig) -> ConfigFile:
|
|
|
144
146
|
if db_config:
|
|
145
147
|
translated_config["database"] = db_config
|
|
146
148
|
|
|
149
|
+
# Use application_database_url instead of the deprecated database_url if provided
|
|
147
150
|
if "database_url" in config:
|
|
148
151
|
translated_config["database_url"] = config.get("database_url")
|
|
152
|
+
elif "application_database_url" in config:
|
|
153
|
+
translated_config["database_url"] = config.get("application_database_url")
|
|
154
|
+
|
|
149
155
|
if "system_database_url" in config:
|
|
150
156
|
translated_config["system_database_url"] = config.get("system_database_url")
|
|
151
157
|
|
|
@@ -233,7 +239,6 @@ def _substitute_env_vars(content: str, silent: bool = False) -> str:
|
|
|
233
239
|
def load_config(
|
|
234
240
|
config_file_path: str = DBOS_CONFIG_PATH,
|
|
235
241
|
*,
|
|
236
|
-
run_process_config: bool = True,
|
|
237
242
|
silent: bool = False,
|
|
238
243
|
) -> ConfigFile:
|
|
239
244
|
"""
|
|
@@ -283,8 +288,6 @@ def load_config(
|
|
|
283
288
|
]
|
|
284
289
|
|
|
285
290
|
data = cast(ConfigFile, data)
|
|
286
|
-
if run_process_config:
|
|
287
|
-
data = process_config(data=data, silent=silent)
|
|
288
291
|
return data # type: ignore
|
|
289
292
|
|
|
290
293
|
|
|
@@ -333,25 +336,16 @@ def process_config(
|
|
|
333
336
|
|
|
334
337
|
# Ensure database dict exists
|
|
335
338
|
data.setdefault("database", {})
|
|
336
|
-
|
|
337
|
-
# Database URL resolution
|
|
338
339
|
connect_timeout = None
|
|
339
|
-
|
|
340
|
+
|
|
341
|
+
# Process the application database URL, if provided
|
|
342
|
+
if data.get("database_url"):
|
|
340
343
|
# Parse the db string and check required fields
|
|
341
344
|
assert data["database_url"] is not None
|
|
342
345
|
assert is_valid_database_url(data["database_url"])
|
|
343
346
|
|
|
344
347
|
url = make_url(data["database_url"])
|
|
345
348
|
|
|
346
|
-
if data["database_url"].startswith("sqlite"):
|
|
347
|
-
data["system_database_url"] = data["database_url"]
|
|
348
|
-
else:
|
|
349
|
-
assert url.database is not None
|
|
350
|
-
if not data["database"].get("sys_db_name"):
|
|
351
|
-
data["database"]["sys_db_name"] = (
|
|
352
|
-
url.database + SystemSchema.sysdb_suffix
|
|
353
|
-
)
|
|
354
|
-
|
|
355
349
|
# Gather connect_timeout from the URL if provided. It should be used in engine kwargs if not provided there (instead of our default)
|
|
356
350
|
connect_timeout_str = url.query.get("connect_timeout")
|
|
357
351
|
if connect_timeout_str is not None:
|
|
@@ -376,18 +370,80 @@ def process_config(
|
|
|
376
370
|
host=os.getenv("DBOS_DBHOST", url.host),
|
|
377
371
|
port=port,
|
|
378
372
|
).render_as_string(hide_password=False)
|
|
379
|
-
|
|
373
|
+
|
|
374
|
+
# Process the system database URL, if provided
|
|
375
|
+
if data.get("system_database_url"):
|
|
376
|
+
# Parse the db string and check required fields
|
|
377
|
+
assert data["system_database_url"]
|
|
378
|
+
assert is_valid_database_url(data["system_database_url"])
|
|
379
|
+
|
|
380
|
+
url = make_url(data["system_database_url"])
|
|
381
|
+
|
|
382
|
+
# Gather connect_timeout from the URL if provided. It should be used in engine kwargs if not provided there (instead of our default). This overrides a timeout from the application database, if any.
|
|
383
|
+
connect_timeout_str = url.query.get("connect_timeout")
|
|
384
|
+
if connect_timeout_str is not None:
|
|
385
|
+
assert isinstance(
|
|
386
|
+
connect_timeout_str, str
|
|
387
|
+
), "connect_timeout must be a string and defined once in the URL"
|
|
388
|
+
if connect_timeout_str.isdigit():
|
|
389
|
+
connect_timeout = int(connect_timeout_str)
|
|
390
|
+
|
|
391
|
+
# In debug mode perform env vars overrides
|
|
392
|
+
if isDebugMode:
|
|
393
|
+
# Override the username, password, host, and port
|
|
394
|
+
port_str = os.getenv("DBOS_DBPORT")
|
|
395
|
+
port = (
|
|
396
|
+
int(port_str)
|
|
397
|
+
if port_str is not None and port_str.isdigit()
|
|
398
|
+
else url.port
|
|
399
|
+
)
|
|
400
|
+
data["system_database_url"] = url.set(
|
|
401
|
+
username=os.getenv("DBOS_DBUSER", url.username),
|
|
402
|
+
password=os.getenv("DBOS_DBPASSWORD", url.password),
|
|
403
|
+
host=os.getenv("DBOS_DBHOST", url.host),
|
|
404
|
+
port=port,
|
|
405
|
+
).render_as_string(hide_password=False)
|
|
406
|
+
|
|
407
|
+
# If an application database URL is provided but not the system database URL,
|
|
408
|
+
# construct the system database URL.
|
|
409
|
+
if data.get("database_url") and not data.get("system_database_url"):
|
|
410
|
+
assert data["database_url"]
|
|
411
|
+
if data["database_url"].startswith("sqlite"):
|
|
412
|
+
data["system_database_url"] = data["database_url"]
|
|
413
|
+
else:
|
|
414
|
+
url = make_url(data["database_url"])
|
|
415
|
+
assert url.database
|
|
416
|
+
if data["database"].get("sys_db_name"):
|
|
417
|
+
url = url.set(database=data["database"]["sys_db_name"])
|
|
418
|
+
else:
|
|
419
|
+
url = url.set(database=f"{url.database}{SystemSchema.sysdb_suffix}")
|
|
420
|
+
data["system_database_url"] = url.render_as_string(hide_password=False)
|
|
421
|
+
|
|
422
|
+
# If a system database URL is provided but not an application database URL, set the
|
|
423
|
+
# application database URL to the system database URL.
|
|
424
|
+
if data.get("system_database_url") and not data.get("database_url"):
|
|
425
|
+
assert data["system_database_url"]
|
|
426
|
+
data["database_url"] = data["system_database_url"]
|
|
427
|
+
|
|
428
|
+
# If neither URL is provided, use a default SQLite database URL.
|
|
429
|
+
if not data.get("database_url") and not data.get("system_database_url"):
|
|
380
430
|
_app_db_name = _app_name_to_db_name(data["name"])
|
|
381
|
-
data["
|
|
382
|
-
|
|
431
|
+
data["system_database_url"] = data["database_url"] = (
|
|
432
|
+
f"sqlite:///{_app_db_name}.sqlite"
|
|
433
|
+
)
|
|
383
434
|
|
|
384
435
|
configure_db_engine_parameters(data["database"], connect_timeout=connect_timeout)
|
|
385
436
|
|
|
386
|
-
# Pretty-print where we've loaded database connection information from, respecting the log level
|
|
387
437
|
assert data["database_url"] is not None
|
|
438
|
+
assert data["system_database_url"] is not None
|
|
439
|
+
# Pretty-print connection information, respecting log level
|
|
388
440
|
if not silent and logs["logLevel"] == "INFO" or logs["logLevel"] == "DEBUG":
|
|
389
|
-
|
|
390
|
-
|
|
441
|
+
printable_sys_db_url = make_url(data["system_database_url"]).render_as_string(
|
|
442
|
+
hide_password=True
|
|
443
|
+
)
|
|
444
|
+
print(
|
|
445
|
+
f"[bold blue]DBOS system database URL: {printable_sys_db_url}[/bold blue]"
|
|
446
|
+
)
|
|
391
447
|
if data["database_url"].startswith("sqlite"):
|
|
392
448
|
print(
|
|
393
449
|
f"[bold blue]Using SQLite as a system database. The SQLite system database is for development and testing. PostgreSQL is recommended for production use.[/bold blue]"
|
|
@@ -474,36 +530,34 @@ def _app_name_to_db_name(app_name: str) -> str:
|
|
|
474
530
|
|
|
475
531
|
def overwrite_config(provided_config: ConfigFile) -> ConfigFile:
|
|
476
532
|
# Load the DBOS configuration file and force the use of:
|
|
477
|
-
# 1. The database url provided by DBOS_DATABASE_URL
|
|
533
|
+
# 1. The application and system database url provided by DBOS_DATABASE_URL and DBOS_SYSTEM_DATABASE_URL
|
|
478
534
|
# 2. OTLP traces endpoints (add the config data to the provided config)
|
|
479
535
|
# 3. Use the application name from the file. This is a defensive measure to ensure the application name is whatever it was registered with in the cloud
|
|
480
536
|
# 4. Remove admin_port is provided in code
|
|
481
537
|
# 5. Remove env vars if provided in code
|
|
482
538
|
# Optimistically assume that expected fields in config_from_file are present
|
|
483
539
|
|
|
484
|
-
config_from_file = load_config(
|
|
540
|
+
config_from_file = load_config()
|
|
485
541
|
# Be defensive
|
|
486
542
|
if config_from_file is None:
|
|
487
543
|
return provided_config
|
|
488
544
|
|
|
489
|
-
#
|
|
545
|
+
# Set the application name to the cloud app name
|
|
490
546
|
provided_config["name"] = config_from_file["name"]
|
|
491
547
|
|
|
492
|
-
#
|
|
493
|
-
if "database" not in provided_config:
|
|
494
|
-
provided_config["database"] = {}
|
|
495
|
-
provided_config["database"]["sys_db_name"] = config_from_file["database"][
|
|
496
|
-
"sys_db_name"
|
|
497
|
-
]
|
|
498
|
-
|
|
548
|
+
# Use the DBOS Cloud application and system database URLs
|
|
499
549
|
db_url = os.environ.get("DBOS_DATABASE_URL")
|
|
500
550
|
if db_url is None:
|
|
501
551
|
raise DBOSInitializationError(
|
|
502
552
|
"DBOS_DATABASE_URL environment variable is not set. This is required to connect to the database."
|
|
503
553
|
)
|
|
504
554
|
provided_config["database_url"] = db_url
|
|
505
|
-
|
|
506
|
-
|
|
555
|
+
system_db_url = os.environ.get("DBOS_SYSTEM_DATABASE_URL")
|
|
556
|
+
if system_db_url is None:
|
|
557
|
+
raise DBOSInitializationError(
|
|
558
|
+
"DBOS_SYSTEM_DATABASE_URL environment variable is not set. This is required to connect to the database."
|
|
559
|
+
)
|
|
560
|
+
provided_config["system_database_url"] = system_db_url
|
|
507
561
|
|
|
508
562
|
# Telemetry config
|
|
509
563
|
if "telemetry" not in provided_config or provided_config["telemetry"] is None:
|
|
@@ -563,7 +617,7 @@ def get_system_database_url(config: ConfigFile) -> str:
|
|
|
563
617
|
if config["database_url"].startswith("sqlite"):
|
|
564
618
|
return config["database_url"]
|
|
565
619
|
app_db_url = make_url(config["database_url"])
|
|
566
|
-
if config["database"].get("sys_db_name") is not None:
|
|
620
|
+
if config.get("database") and config["database"].get("sys_db_name") is not None:
|
|
567
621
|
sys_db_name = config["database"]["sys_db_name"]
|
|
568
622
|
else:
|
|
569
623
|
assert app_db_url.database is not None
|
|
@@ -571,3 +625,14 @@ def get_system_database_url(config: ConfigFile) -> str:
|
|
|
571
625
|
return app_db_url.set(database=sys_db_name).render_as_string(
|
|
572
626
|
hide_password=False
|
|
573
627
|
)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def get_application_database_url(config: ConfigFile) -> str:
|
|
631
|
+
# For backwards compatibility, the application database URL is "database_url"
|
|
632
|
+
if config.get("database_url"):
|
|
633
|
+
assert config["database_url"]
|
|
634
|
+
return config["database_url"]
|
|
635
|
+
else:
|
|
636
|
+
# If the application database URL is not specified, set it to the system database URL
|
|
637
|
+
assert config["system_database_url"]
|
|
638
|
+
return config["system_database_url"]
|
|
@@ -1422,7 +1422,6 @@ class SystemDatabase(ABC):
|
|
|
1422
1422
|
debug_mode=debug_mode,
|
|
1423
1423
|
)
|
|
1424
1424
|
else:
|
|
1425
|
-
# Default to PostgreSQL for postgresql://, postgres://, or other URLs
|
|
1426
1425
|
from ._sys_db_postgres import PostgresSystemDatabase
|
|
1427
1426
|
|
|
1428
1427
|
return PostgresSystemDatabase(
|
|
@@ -92,18 +92,15 @@ class SQLiteSystemDatabase(SystemDatabase):
|
|
|
92
92
|
last_applied = i
|
|
93
93
|
|
|
94
94
|
def _cleanup_connections(self) -> None:
|
|
95
|
-
"""Clean up SQLite-specific connections."""
|
|
96
95
|
# SQLite doesn't require special connection cleanup
|
|
97
96
|
pass
|
|
98
97
|
|
|
99
98
|
def _is_unique_constraint_violation(self, dbapi_error: DBAPIError) -> bool:
|
|
100
99
|
"""Check if the error is a unique constraint violation in SQLite."""
|
|
101
|
-
# SQLite UNIQUE constraint error
|
|
102
100
|
return "UNIQUE constraint failed" in str(dbapi_error.orig)
|
|
103
101
|
|
|
104
102
|
def _is_foreign_key_violation(self, dbapi_error: DBAPIError) -> bool:
|
|
105
103
|
"""Check if the error is a foreign key violation in SQLite."""
|
|
106
|
-
# SQLite FOREIGN KEY constraint error
|
|
107
104
|
return "FOREIGN KEY constraint failed" in str(dbapi_error.orig)
|
|
108
105
|
|
|
109
106
|
@staticmethod
|