dbos 1.13.0a5__tar.gz → 1.13.0a7__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 (117) hide show
  1. {dbos-1.13.0a5 → dbos-1.13.0a7}/PKG-INFO +1 -1
  2. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_app_db.py +0 -2
  3. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_client.py +27 -13
  4. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_dbos_config.py +107 -42
  5. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_sys_db.py +0 -1
  6. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_sys_db_sqlite.py +0 -3
  7. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/cli/cli.py +202 -83
  8. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/dbos-config.schema.json +4 -0
  9. {dbos-1.13.0a5 → dbos-1.13.0a7}/pyproject.toml +1 -1
  10. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/conftest.py +35 -15
  11. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/queuedworkflow.py +2 -1
  12. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_client.py +2 -2
  13. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_config.py +115 -10
  14. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_docker_secrets.py +5 -5
  15. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_package.py +11 -7
  16. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_queue.py +2 -1
  17. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_schema_migration.py +80 -2
  18. dbos-1.13.0a5/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -67
  19. dbos-1.13.0a5/tests/test_migrate.py +0 -79
  20. {dbos-1.13.0a5 → dbos-1.13.0a7}/LICENSE +0 -0
  21. {dbos-1.13.0a5 → dbos-1.13.0a7}/README.md +0 -0
  22. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/__init__.py +0 -0
  23. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/__main__.py +0 -0
  24. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_admin_server.py +0 -0
  25. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/env.py +0 -0
  26. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/script.py.mako +0 -0
  27. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/01ce9f07bd10_streaming.py +0 -0
  28. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  29. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
  30. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/471b60d64126_dbos_migrations.py +0 -0
  31. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  32. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  33. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
  34. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
  35. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
  36. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  37. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  38. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  39. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
  40. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  41. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_alembic_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
  42. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_classproperty.py +0 -0
  43. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_conductor/conductor.py +0 -0
  44. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_conductor/protocol.py +0 -0
  45. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_context.py +0 -0
  46. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_core.py +0 -0
  47. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_croniter.py +0 -0
  48. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_dbos.py +0 -0
  49. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_debug.py +0 -0
  50. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_docker_pg_helper.py +0 -0
  51. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_error.py +0 -0
  52. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_event_loop.py +0 -0
  53. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_fastapi.py +0 -0
  54. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_flask.py +0 -0
  55. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_kafka.py +0 -0
  56. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_kafka_message.py +0 -0
  57. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_logger.py +0 -0
  58. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_migration.py +0 -0
  59. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_outcome.py +0 -0
  60. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_queue.py +0 -0
  61. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_recovery.py +0 -0
  62. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_registrations.py +0 -0
  63. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_roles.py +0 -0
  64. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_scheduler.py +0 -0
  65. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_schemas/__init__.py +0 -0
  66. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_schemas/application_database.py +0 -0
  67. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_schemas/system_database.py +0 -0
  68. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_serialization.py +0 -0
  69. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_sys_db_postgres.py +0 -0
  70. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/README.md +0 -0
  71. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  72. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
  73. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  74. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  75. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  76. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  77. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  78. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  79. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_tracer.py +0 -0
  80. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_utils.py +0 -0
  81. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/_workflow_commands.py +0 -0
  82. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/cli/_github_init.py +0 -0
  83. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/cli/_template_init.py +0 -0
  84. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/cli/migration.py +0 -0
  85. {dbos-1.13.0a5 → dbos-1.13.0a7}/dbos/py.typed +0 -0
  86. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/__init__.py +0 -0
  87. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/atexit_no_ctor.py +0 -0
  88. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/atexit_no_launch.py +0 -0
  89. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/classdefs.py +0 -0
  90. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/client_collateral.py +0 -0
  91. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/client_worker.py +0 -0
  92. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/dupname_classdefs1.py +0 -0
  93. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/dupname_classdefsa.py +0 -0
  94. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/more_classdefs.py +0 -0
  95. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_admin_server.py +0 -0
  96. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_async.py +0 -0
  97. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_async_workflow_management.py +0 -0
  98. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_classdecorators.py +0 -0
  99. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_cli.py +0 -0
  100. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_concurrency.py +0 -0
  101. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_croniter.py +0 -0
  102. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_dbos.py +0 -0
  103. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_debug.py +0 -0
  104. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_failures.py +0 -0
  105. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_fastapi.py +0 -0
  106. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_fastapi_roles.py +0 -0
  107. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_flask.py +0 -0
  108. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_kafka.py +0 -0
  109. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_outcome.py +0 -0
  110. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_scheduler.py +0 -0
  111. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_singleton.py +0 -0
  112. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_spans.py +0 -0
  113. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_sqlalchemy.py +0 -0
  114. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_streaming.py +0 -0
  115. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_workflow_introspection.py +0 -0
  116. {dbos-1.13.0a5 → dbos-1.13.0a7}/tests/test_workflow_management.py +0 -0
  117. {dbos-1.13.0a5 → dbos-1.13.0a7}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.13.0a5
3
+ Version: 1.13.0a7
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -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 get_system_database_url, is_valid_database_url
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
- system_database: Optional[str] = None,
123
+ application_database_url: Optional[str] = None,
124
+ system_database: Optional[str] = None, # DEPRECATED
120
125
  ):
121
- assert is_valid_database_url(database_url)
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=get_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=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
- database_url (str): Database connection string
26
- system_database_url (str): Connection string for the system database (if different from the application database)
27
- sys_db_name (str): System database name (deprecated)
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 request serving
112
+ runtimeConfig (RuntimeConfig): Configuration for DBOS Cloud
111
113
  database (DatabaseConfig): Configure pool sizes, migrate commands
112
- database_url (str): Database connection string
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 varialbes
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
- if data.get("database_url") is not None and data["database_url"] != "":
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
- else:
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["database_url"] = f"sqlite:///{_app_db_name}.sqlite"
382
- data["system_database_url"] = data["database_url"]
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
- log_url = make_url(data["database_url"]).render_as_string(hide_password=True)
390
- print(f"[bold blue]Using database connection string: {log_url}[/bold blue]")
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(run_process_config=False)
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
- # Name
545
+ # Set the application name to the cloud app name
490
546
  provided_config["name"] = config_from_file["name"]
491
547
 
492
- # Database config. Expects DBOS_DATABASE_URL to be set
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
- if "system_database_url" in provided_config:
506
- del provided_config["system_database_url"]
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