dbos 0.18.0a1__py3-none-any.whl → 0.19.0a4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dbos might be problematic. Click here for more details.

dbos/_core.py CHANGED
@@ -84,7 +84,7 @@ if TYPE_CHECKING:
84
84
  IsolationLevel,
85
85
  )
86
86
 
87
- from sqlalchemy.exc import DBAPIError
87
+ from sqlalchemy.exc import DBAPIError, InvalidRequestError
88
88
 
89
89
  P = ParamSpec("P") # A generic type for workflow parameters
90
90
  R = TypeVar("R", covariant=True) # A generic type for workflow return values
@@ -180,11 +180,12 @@ def _init_workflow(
180
180
  if class_name is not None:
181
181
  inputs = {"args": inputs["args"][1:], "kwargs": inputs["kwargs"]}
182
182
 
183
+ wf_status = status["status"]
183
184
  if temp_wf_type != "transaction" or queue is not None:
184
185
  # Synchronously record the status and inputs for workflows and single-step workflows
185
186
  # We also have to do this for single-step workflows because of the foreign key constraint on the operation outputs table
186
187
  # TODO: Make this transactional (and with the queue step below)
187
- dbos._sys_db.update_workflow_status(
188
+ wf_status = dbos._sys_db.update_workflow_status(
188
189
  status, False, ctx.in_recovery, max_recovery_attempts=max_recovery_attempts
189
190
  )
190
191
  dbos._sys_db.update_workflow_inputs(wfid, _serialization.serialize_args(inputs))
@@ -192,9 +193,10 @@ def _init_workflow(
192
193
  # Buffer the inputs for single-transaction workflows, but don't buffer the status
193
194
  dbos._sys_db.buffer_workflow_inputs(wfid, _serialization.serialize_args(inputs))
194
195
 
195
- if queue is not None:
196
+ if queue is not None and wf_status == WorkflowStatusString.ENQUEUED.value:
196
197
  dbos._sys_db.enqueue(wfid, queue)
197
198
 
199
+ status["status"] = wf_status
198
200
  return status
199
201
 
200
202
 
@@ -413,7 +415,13 @@ def start_workflow(
413
415
  max_recovery_attempts=fi.max_recovery_attempts,
414
416
  )
415
417
 
416
- if not execute_workflow:
418
+ wf_status = status["status"]
419
+
420
+ if (
421
+ not execute_workflow
422
+ or wf_status == WorkflowStatusString.ERROR.value
423
+ or wf_status == WorkflowStatusString.SUCCESS.value
424
+ ):
417
425
  return WorkflowHandlePolling(new_wf_id, dbos)
418
426
 
419
427
  if fself is not None:
@@ -545,6 +553,7 @@ def decorate_transaction(
545
553
  max_retry_wait_seconds = 2.0
546
554
  while True:
547
555
  has_recorded_error = False
556
+ txn_error: Optional[Exception] = None
548
557
  try:
549
558
  with session.begin():
550
559
  # This must be the first statement in the transaction!
@@ -608,15 +617,24 @@ def decorate_transaction(
608
617
  max_retry_wait_seconds,
609
618
  )
610
619
  continue
620
+ txn_error = dbapi_error
621
+ raise
622
+ except InvalidRequestError as invalid_request_error:
623
+ dbos.logger.error(
624
+ f"InvalidRequestError in transaction {func.__qualname__} \033[1m Hint: Do not call commit() or rollback() within a DBOS transaction.\033[0m"
625
+ )
626
+ txn_error = invalid_request_error
611
627
  raise
612
628
  except Exception as error:
629
+ txn_error = error
630
+ raise
631
+ finally:
613
632
  # Don't record the error if it was already recorded
614
- if not has_recorded_error:
633
+ if txn_error and not has_recorded_error:
615
634
  txn_output["error"] = (
616
- _serialization.serialize_exception(error)
635
+ _serialization.serialize_exception(txn_error)
617
636
  )
618
637
  dbos._app_db.record_transaction_error(txn_output)
619
- raise
620
638
  return output
621
639
 
622
640
  if inspect.iscoroutinefunction(func):
dbos/_db_wizard.py CHANGED
@@ -1,5 +1,7 @@
1
+ import json
2
+ import os
1
3
  import time
2
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING, Optional, TypedDict
3
5
 
4
6
  import docker # type: ignore
5
7
  import typer
@@ -15,16 +17,29 @@ from ._cloudutils.databases import choose_database, get_user_db_credentials
15
17
  from ._error import DBOSInitializationError
16
18
  from ._logger import dbos_logger
17
19
 
20
+ DB_CONNECTION_PATH = os.path.join(".dbos", "db_connection")
18
21
 
19
- def db_connect(config: "ConfigFile", config_file_path: str) -> "ConfigFile":
22
+
23
+ class DatabaseConnection(TypedDict):
24
+ hostname: Optional[str]
25
+ port: Optional[int]
26
+ username: Optional[str]
27
+ password: Optional[str]
28
+ local_suffix: Optional[bool]
29
+
30
+
31
+ def db_wizard(config: "ConfigFile", config_file_path: str) -> "ConfigFile":
20
32
  # 1. Check the connectivity to the database. Return if successful. If cannot connect, continue to the following steps.
21
33
  db_connection_error = _check_db_connectivity(config)
22
34
  if db_connection_error is None:
23
35
  return config
24
36
 
25
37
  # 2. If the error is due to password authentication or the configuration is non-default, surface the error and exit.
26
- if "password authentication failed" in str(db_connection_error) or "28P01" in str(
27
- db_connection_error
38
+ error_str = str(db_connection_error)
39
+ if (
40
+ "password authentication failed" in error_str
41
+ or "28P01" in error_str
42
+ or "no password supplied" in error_str
28
43
  ):
29
44
  raise DBOSInitializationError(
30
45
  f"Could not connect to Postgres: password authentication failed: {db_connection_error}"
@@ -79,17 +94,20 @@ def db_connect(config: "ConfigFile", config_file_path: str) -> "ConfigFile":
79
94
  f"Could not connect to the database. Exception: {db_connection_error}"
80
95
  )
81
96
 
82
- # 6. Save the config to the config file and return the updated config.
83
- # TODO: make the config file prettier
84
- with open(config_file_path, "w") as file:
85
- file.write(yaml.dump(config))
86
-
97
+ # 6. Save the config to the database connection file
98
+ updated_connection = DatabaseConnection(
99
+ hostname=config["database"]["hostname"],
100
+ port=config["database"]["port"],
101
+ username=config["database"]["username"],
102
+ password=config["database"]["password"],
103
+ local_suffix=config["database"]["local_suffix"],
104
+ )
105
+ save_db_connection(updated_connection)
87
106
  return config
88
107
 
89
108
 
90
109
  def _start_docker_postgres(config: "ConfigFile") -> bool:
91
110
  print("Starting a Postgres Docker container...")
92
- config["database"]["password"] = "dbos"
93
111
  client = docker.from_env()
94
112
  pg_data = "/var/lib/postgresql/data"
95
113
  container_name = "dbos-db"
@@ -119,7 +137,7 @@ def _start_docker_postgres(config: "ConfigFile") -> bool:
119
137
  continue
120
138
  print("[green]Postgres Docker container started successfully![/green]")
121
139
  break
122
- except Exception as e:
140
+ except:
123
141
  attempts -= 1
124
142
  time.sleep(1)
125
143
 
@@ -148,7 +166,7 @@ def _check_db_connectivity(config: "ConfigFile") -> Optional[Exception]:
148
166
  host=config["database"]["hostname"],
149
167
  port=config["database"]["port"],
150
168
  database="postgres",
151
- query={"connect_timeout": "2"},
169
+ query={"connect_timeout": "1"},
152
170
  )
153
171
  postgres_db_engine = create_engine(postgres_db_url)
154
172
  try:
@@ -165,3 +183,26 @@ def _check_db_connectivity(config: "ConfigFile") -> Optional[Exception]:
165
183
  postgres_db_engine.dispose()
166
184
 
167
185
  return None
186
+
187
+
188
+ def load_db_connection() -> DatabaseConnection:
189
+ try:
190
+ with open(DB_CONNECTION_PATH, "r") as f:
191
+ data = json.load(f)
192
+ return DatabaseConnection(
193
+ hostname=data.get("hostname", None),
194
+ port=data.get("port", None),
195
+ username=data.get("username", None),
196
+ password=data.get("password", None),
197
+ local_suffix=data.get("local_suffix", None),
198
+ )
199
+ except:
200
+ return DatabaseConnection(
201
+ hostname=None, port=None, username=None, password=None, local_suffix=None
202
+ )
203
+
204
+
205
+ def save_db_connection(connection: DatabaseConnection) -> None:
206
+ os.makedirs(".dbos", exist_ok=True)
207
+ with open(DB_CONNECTION_PATH, "w") as f:
208
+ json.dump(connection, f)
dbos/_dbos.py CHANGED
@@ -83,7 +83,7 @@ from ._context import (
83
83
  )
84
84
  from ._dbos_config import ConfigFile, load_config, set_env_vars
85
85
  from ._error import DBOSException, DBOSNonExistentWorkflowError
86
- from ._logger import add_otlp_to_all_loggers, dbos_logger, init_logger
86
+ from ._logger import add_otlp_to_all_loggers, dbos_logger
87
87
  from ._sys_db import SystemDatabase
88
88
 
89
89
  # Most DBOS functions are just any callable F, so decorators / wrappers work on F
dbos/_dbos_config.py CHANGED
@@ -6,12 +6,15 @@ from typing import Any, Dict, List, Optional, TypedDict, cast
6
6
 
7
7
  import yaml
8
8
  from jsonschema import ValidationError, validate
9
+ from rich import print
9
10
  from sqlalchemy import URL
10
11
 
11
- from ._db_wizard import db_connect
12
+ from ._db_wizard import db_wizard, load_db_connection
12
13
  from ._error import DBOSInitializationError
13
14
  from ._logger import config_logger, dbos_logger, init_logger
14
15
 
16
+ DBOS_CONFIG_PATH = "dbos-config.yaml"
17
+
15
18
 
16
19
  class RuntimeConfig(TypedDict, total=False):
17
20
  start: List[str]
@@ -23,7 +26,7 @@ class DatabaseConfig(TypedDict, total=False):
23
26
  hostname: str
24
27
  port: int
25
28
  username: str
26
- password: Optional[str]
29
+ password: str
27
30
  connectionTimeoutMillis: Optional[int]
28
31
  app_db_name: str
29
32
  sys_db_name: Optional[str]
@@ -93,7 +96,7 @@ def _substitute_env_vars(content: str) -> str:
93
96
  return re.sub(regex, replace_func, content)
94
97
 
95
98
 
96
- def get_dbos_database_url(config_file_path: str = "dbos-config.yaml") -> str:
99
+ def get_dbos_database_url(config_file_path: str = DBOS_CONFIG_PATH) -> str:
97
100
  """
98
101
  Retrieve application database URL from configuration `.yaml` file.
99
102
 
@@ -119,7 +122,9 @@ def get_dbos_database_url(config_file_path: str = "dbos-config.yaml") -> str:
119
122
  return db_url.render_as_string(hide_password=False)
120
123
 
121
124
 
122
- def load_config(config_file_path: str = "dbos-config.yaml") -> ConfigFile:
125
+ def load_config(
126
+ config_file_path: str = DBOS_CONFIG_PATH, *, use_db_wizard: bool = True
127
+ ) -> ConfigFile:
123
128
  """
124
129
  Load the DBOS `ConfigFile` from the specified path (typically `dbos-config.yaml`).
125
130
 
@@ -151,6 +156,9 @@ def load_config(config_file_path: str = "dbos-config.yaml") -> ConfigFile:
151
156
  except ValidationError as e:
152
157
  raise DBOSInitializationError(f"Validation error: {e}")
153
158
 
159
+ if "database" not in data:
160
+ data["database"] = {}
161
+
154
162
  if "name" not in data:
155
163
  raise DBOSInitializationError(
156
164
  f"dbos-config.yaml must specify an application name"
@@ -169,8 +177,6 @@ def load_config(config_file_path: str = "dbos-config.yaml") -> ConfigFile:
169
177
  if "runtimeConfig" not in data or "start" not in data["runtimeConfig"]:
170
178
  raise DBOSInitializationError(f"dbos-config.yaml must specify a start command")
171
179
 
172
- data = cast(ConfigFile, data)
173
-
174
180
  if not _is_valid_app_name(data["name"]):
175
181
  raise DBOSInitializationError(
176
182
  f'Invalid app name {data["name"]}. App names must be between 3 and 30 characters long and contain only lowercase letters, numbers, dashes, and underscores.'
@@ -179,10 +185,49 @@ def load_config(config_file_path: str = "dbos-config.yaml") -> ConfigFile:
179
185
  if "app_db_name" not in data["database"]:
180
186
  data["database"]["app_db_name"] = _app_name_to_db_name(data["name"])
181
187
 
188
+ # Load the DB connection file. Use its values for missing fields from dbos-config.yaml. Use defaults otherwise.
189
+ data = cast(ConfigFile, data)
190
+ db_connection = load_db_connection()
191
+ if data["database"].get("hostname"):
192
+ print(
193
+ "[bold blue]Loading database connection parameters from dbos-config.yaml[/bold blue]"
194
+ )
195
+ elif db_connection.get("hostname"):
196
+ print(
197
+ "[bold blue]Loading database connection parameters from .dbos/db_connection[/bold blue]"
198
+ )
199
+ else:
200
+ print(
201
+ "[bold blue]Using default database connection parameters (localhost)[/bold blue]"
202
+ )
203
+
204
+ data["database"]["hostname"] = (
205
+ data["database"].get("hostname") or db_connection.get("hostname") or "localhost"
206
+ )
207
+ data["database"]["port"] = (
208
+ data["database"].get("port") or db_connection.get("port") or 5432
209
+ )
210
+ data["database"]["username"] = (
211
+ data["database"].get("username") or db_connection.get("username") or "postgres"
212
+ )
213
+ data["database"]["password"] = (
214
+ data["database"].get("password")
215
+ or db_connection.get("password")
216
+ or os.environ.get("PGPASSWORD")
217
+ or "dbos"
218
+ )
219
+ data["database"]["local_suffix"] = (
220
+ data["database"].get("local_suffix")
221
+ or db_connection.get("local_suffix")
222
+ or False
223
+ )
224
+
225
+ # Configure the DBOS logger
182
226
  config_logger(data)
183
227
 
184
228
  # Check the connectivity to the database and make sure it's properly configured
185
- data = db_connect(data, config_file_path)
229
+ if use_db_wizard:
230
+ data = db_wizard(data, config_file_path)
186
231
 
187
232
  if "local_suffix" in data["database"] and data["database"]["local_suffix"]:
188
233
  data["database"]["app_db_name"] = f"{data['database']['app_db_name']}_local"
@@ -0,0 +1,34 @@
1
+ """workflow_queues_executor_id
2
+
3
+ Revision ID: 04ca4f231047
4
+ Revises: d76646551a6c
5
+ Create Date: 2025-01-15 15:05:08.043190
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "04ca4f231047"
16
+ down_revision: Union[str, None] = "d76646551a6c"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ op.add_column(
23
+ "workflow_queue",
24
+ sa.Column(
25
+ "executor_id",
26
+ sa.Text(),
27
+ nullable=True,
28
+ ),
29
+ schema="dbos",
30
+ )
31
+
32
+
33
+ def downgrade() -> None:
34
+ op.drop_column("workflow_queue", "executor_id", schema="dbos")
dbos/_queue.py CHANGED
@@ -2,6 +2,9 @@ import threading
2
2
  import traceback
3
3
  from typing import TYPE_CHECKING, Optional, TypedDict
4
4
 
5
+ from psycopg import errors
6
+ from sqlalchemy.exc import OperationalError
7
+
5
8
  from ._core import P, R, execute_workflow_by_id, start_workflow
6
9
 
7
10
  if TYPE_CHECKING:
@@ -33,9 +36,19 @@ class Queue:
33
36
  name: str,
34
37
  concurrency: Optional[int] = None,
35
38
  limiter: Optional[QueueRateLimit] = None,
39
+ worker_concurrency: Optional[int] = None,
36
40
  ) -> None:
41
+ if (
42
+ worker_concurrency is not None
43
+ and concurrency is not None
44
+ and worker_concurrency > concurrency
45
+ ):
46
+ raise ValueError(
47
+ "worker_concurrency must be less than or equal to concurrency"
48
+ )
37
49
  self.name = name
38
50
  self.concurrency = concurrency
51
+ self.worker_concurrency = worker_concurrency
39
52
  self.limiter = limiter
40
53
  from ._dbos import _get_or_create_dbos_registry
41
54
 
@@ -60,6 +73,12 @@ def queue_thread(stop_event: threading.Event, dbos: "DBOS") -> None:
60
73
  wf_ids = dbos._sys_db.start_queued_workflows(queue, dbos._executor_id)
61
74
  for id in wf_ids:
62
75
  execute_workflow_by_id(dbos, id)
76
+ except OperationalError as e:
77
+ # Ignore serialization error
78
+ if not isinstance(e.orig, errors.SerializationFailure):
79
+ dbos.logger.warning(
80
+ f"Exception encountered in queue thread: {traceback.format_exc()}"
81
+ )
63
82
  except Exception:
64
83
  dbos.logger.warning(
65
84
  f"Exception encountered in queue thread: {traceback.format_exc()}"
@@ -154,6 +154,7 @@ class SystemSchema:
154
154
  nullable=False,
155
155
  primary_key=True,
156
156
  ),
157
+ Column("executor_id", Text),
157
158
  Column("queue_name", Text, nullable=False),
158
159
  Column(
159
160
  "created_at_epoch_ms",
dbos/_sys_db.py CHANGED
@@ -13,7 +13,6 @@ from typing import (
13
13
  Optional,
14
14
  Sequence,
15
15
  Set,
16
- Tuple,
17
16
  TypedDict,
18
17
  cast,
19
18
  )
@@ -23,12 +22,14 @@ import sqlalchemy as sa
23
22
  import sqlalchemy.dialects.postgresql as pg
24
23
  from alembic import command
25
24
  from alembic.config import Config
25
+ from sqlalchemy import or_
26
26
  from sqlalchemy.exc import DBAPIError
27
27
 
28
28
  from . import _serialization
29
29
  from ._dbos_config import ConfigFile
30
30
  from ._error import (
31
31
  DBOSDeadLetterQueueError,
32
+ DBOSException,
32
33
  DBOSNonExistentWorkflowError,
33
34
  DBOSWorkflowConflictIDError,
34
35
  )
@@ -249,7 +250,9 @@ class SystemDatabase:
249
250
  *,
250
251
  conn: Optional[sa.Connection] = None,
251
252
  max_recovery_attempts: int = DEFAULT_MAX_RECOVERY_ATTEMPTS,
252
- ) -> None:
253
+ ) -> WorkflowStatuses:
254
+ wf_status: WorkflowStatuses = status["status"]
255
+
253
256
  cmd = pg.insert(SystemSchema.workflow_status).values(
254
257
  workflow_uuid=status["workflow_uuid"],
255
258
  status=status["status"],
@@ -286,17 +289,19 @@ class SystemDatabase:
286
289
  )
287
290
  else:
288
291
  cmd = cmd.on_conflict_do_nothing()
289
- cmd = cmd.returning(SystemSchema.workflow_status.c.recovery_attempts) # type: ignore
292
+ cmd = cmd.returning(SystemSchema.workflow_status.c.recovery_attempts, SystemSchema.workflow_status.c.status) # type: ignore
290
293
 
291
294
  if conn is not None:
292
295
  results = conn.execute(cmd)
293
296
  else:
294
297
  with self.engine.begin() as c:
295
298
  results = c.execute(cmd)
299
+
296
300
  if in_recovery:
297
301
  row = results.fetchone()
298
302
  if row is not None:
299
303
  recovery_attempts: int = row[0]
304
+ wf_status = row[1]
300
305
  if recovery_attempts > max_recovery_attempts:
301
306
  with self.engine.begin() as c:
302
307
  c.execute(
@@ -328,6 +333,8 @@ class SystemDatabase:
328
333
  if status["workflow_uuid"] in self._temp_txn_wf_ids:
329
334
  self._exported_temp_txn_wf_status.add(status["workflow_uuid"])
330
335
 
336
+ return wf_status
337
+
331
338
  def set_workflow_status(
332
339
  self,
333
340
  workflow_uuid: str,
@@ -405,7 +412,10 @@ class SystemDatabase:
405
412
  res["output"]
406
413
  )
407
414
  return resstat
408
- return None
415
+ else:
416
+ raise DBOSException(
417
+ "Workflow status record not found. This should not happen! \033[1m Hint: Check if your workflow is deterministic.\033[0m"
418
+ )
409
419
  stat = self.get_workflow_status(workflow_uuid)
410
420
  self.record_operation_result(
411
421
  {
@@ -1130,27 +1140,38 @@ class SystemDatabase:
1130
1140
  if num_recent_queries >= queue.limiter["limit"]:
1131
1141
  return []
1132
1142
 
1133
- # Select not-yet-completed functions in the queue ordered by the
1134
- # time at which they were enqueued.
1135
- # If there is a concurrency limit N, select only the N most recent
1143
+ # Dequeue functions eligible for this worker and ordered by the time at which they were enqueued.
1144
+ # If there is a global or local concurrency limit N, select only the N oldest enqueued
1136
1145
  # functions, else select all of them.
1137
1146
  query = (
1138
1147
  sa.select(
1139
1148
  SystemSchema.workflow_queue.c.workflow_uuid,
1140
1149
  SystemSchema.workflow_queue.c.started_at_epoch_ms,
1150
+ SystemSchema.workflow_queue.c.executor_id,
1141
1151
  )
1142
1152
  .where(SystemSchema.workflow_queue.c.queue_name == queue.name)
1143
1153
  .where(SystemSchema.workflow_queue.c.completed_at_epoch_ms == None)
1154
+ .where(
1155
+ # Only select functions that have not been started yet or have been started by this worker
1156
+ or_(
1157
+ SystemSchema.workflow_queue.c.executor_id == None,
1158
+ SystemSchema.workflow_queue.c.executor_id == executor_id,
1159
+ )
1160
+ )
1144
1161
  .order_by(SystemSchema.workflow_queue.c.created_at_epoch_ms.asc())
1145
1162
  )
1146
- if queue.concurrency is not None:
1163
+ # Set a dequeue limit if necessary
1164
+ if queue.worker_concurrency is not None:
1165
+ query = query.limit(queue.worker_concurrency)
1166
+ elif queue.concurrency is not None:
1147
1167
  query = query.limit(queue.concurrency)
1148
1168
 
1149
- # From the functions retrieved, get the workflow IDs of the functions
1150
- # that have not yet been started so we can start them.
1151
1169
  rows = c.execute(query).fetchall()
1170
+
1171
+ # Now, get the workflow IDs of functions that have not yet been started
1152
1172
  dequeued_ids: List[str] = [row[0] for row in rows if row[1] is None]
1153
1173
  ret_ids: list[str] = []
1174
+ dbos_logger.debug(f"[{queue.name}] dequeueing {len(dequeued_ids)} task(s)")
1154
1175
  for id in dequeued_ids:
1155
1176
 
1156
1177
  # If we have a limiter, stop starting functions when the number
@@ -1173,11 +1194,11 @@ class SystemDatabase:
1173
1194
  )
1174
1195
  )
1175
1196
 
1176
- # Then give it a start time
1197
+ # Then give it a start time and assign the executor ID
1177
1198
  c.execute(
1178
1199
  SystemSchema.workflow_queue.update()
1179
1200
  .where(SystemSchema.workflow_queue.c.workflow_uuid == id)
1180
- .values(started_at_epoch_ms=start_time_ms)
1201
+ .values(started_at_epoch_ms=start_time_ms, executor_id=executor_id)
1181
1202
  )
1182
1203
  ret_ids.append(id)
1183
1204
 
@@ -9,10 +9,6 @@ runtimeConfig:
9
9
  start:
10
10
  - "fastapi run ${package_name}/main.py"
11
11
  database:
12
- hostname: localhost
13
- port: 5432
14
- username: postgres
15
- password: ${PGPASSWORD}
16
12
  migrate:
17
13
  - ${migration_command}
18
14
  telemetry:
@@ -81,13 +81,7 @@
81
81
  "type": "array",
82
82
  "description": "Specify a list of user DB rollback commands to run"
83
83
  }
84
- },
85
- "required": [
86
- "hostname",
87
- "port",
88
- "username",
89
- "password"
90
- ]
84
+ }
91
85
  },
92
86
  "telemetry": {
93
87
  "type": "object",
@@ -181,9 +175,6 @@
181
175
  "type": "string",
182
176
  "deprecated": true
183
177
  }
184
- },
185
- "required": [
186
- "database"
187
- ]
178
+ }
188
179
  }
189
180
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.18.0a1
3
+ Version: 0.19.0a4
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
- dbos-0.18.0a1.dist-info/METADATA,sha256=8i67YGwId1DCG0l6uylCoyiSjCz2881pFqnDxh-2JbI,5144
2
- dbos-0.18.0a1.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- dbos-0.18.0a1.dist-info/entry_points.txt,sha256=z6GcVANQV7Uw_82H9Ob2axJX6V3imftyZsljdh-M1HU,54
4
- dbos-0.18.0a1.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
1
+ dbos-0.19.0a4.dist-info/METADATA,sha256=NOwdv7iSopa_WvJkvQ3-AJ-peRmVMFW3E5D7SfxERqI,5144
2
+ dbos-0.19.0a4.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ dbos-0.19.0a4.dist-info/entry_points.txt,sha256=z6GcVANQV7Uw_82H9Ob2axJX6V3imftyZsljdh-M1HU,54
4
+ dbos-0.19.0a4.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
5
5
  dbos/__init__.py,sha256=CxRHBHEthPL4PZoLbZhp3rdm44-KkRTT2-7DkK9d4QQ,724
6
6
  dbos/_admin_server.py,sha256=DOgzVp9kmwiebQqmJB1LcrZnGTxSMbZiGXdenc1wZDg,3163
7
7
  dbos/_app_db.py,sha256=_tv2vmPjjiaikwgxH3mqxgJ4nUUcG2-0uMXKWCqVu1c,5509
@@ -10,11 +10,11 @@ dbos/_cloudutils/authentication.py,sha256=V0fCWQN9stCkhbuuxgPTGpvuQcDqfU3KAxPAh0
10
10
  dbos/_cloudutils/cloudutils.py,sha256=5e3CW1deSW-dI5G3QN0XbiVsBhyqT8wu7fuV2f8wtGU,7688
11
11
  dbos/_cloudutils/databases.py,sha256=x4187Djsyoa-QaG3Kog8JT2_GERsnqa93LIVanmVUmg,8393
12
12
  dbos/_context.py,sha256=KV3fd3-Rv6EWrYDUdHARxltSlNZGNtQtNSqeQ-gkXE8,18049
13
- dbos/_core.py,sha256=NWJFQX5bECBvKlYH9pVmNJgmqFGYPnkHnOGjOlOQ3Ag,33504
13
+ dbos/_core.py,sha256=dbG8573iSzB_WITWOh6yOV-w32BM8UbJcOB4Fr0e-lw,34456
14
14
  dbos/_croniter.py,sha256=hbhgfsHBqclUS8VeLnJ9PSE9Z54z6mi4nnrr1aUXn0k,47561
15
- dbos/_db_wizard.py,sha256=o0OdGEsOjtL-1G6HgbXSigBYSaEN25tfSLrgBw0uh6o,6273
16
- dbos/_dbos.py,sha256=z12yGw2QHx7BLdxvoI2zJiwDTSes1r48E7qZ7uOn0mw,34723
17
- dbos/_dbos_config.py,sha256=Hs9L-PJhxeGa2R3nDX75ZFOGLRxA3AiXVf7UoemN9lM,6643
15
+ dbos/_db_wizard.py,sha256=xgKLna0_6Xi50F3o8msRosXba8NScHlpJR5ICVCkHDQ,7534
16
+ dbos/_dbos.py,sha256=LWFa48CPt7bsNAnMZrNDzHHTFCyMrY-nKbMZwCG_dqY,34710
17
+ dbos/_dbos_config.py,sha256=h_q1gzudhsAMVkGMD0qQ6kLic6YhdJgzm50YFSIx9Bo,8196
18
18
  dbos/_error.py,sha256=UETk8CoZL-TO2Utn1-E7OSWelhShWmKM-fOlODMR9PE,3893
19
19
  dbos/_fastapi.py,sha256=iyefCZq-ZDKRUjN_rgYQmFmyvWf4gPrSlC6CLbfq4a8,3419
20
20
  dbos/_flask.py,sha256=z1cijbTi5Dpq6kqikPCx1LcR2YHHv2oc41NehOWjw74,2431
@@ -23,6 +23,7 @@ dbos/_kafka_message.py,sha256=NYvOXNG3Qn7bghn1pv3fg4Pbs86ILZGcK4IB-MLUNu0,409
23
23
  dbos/_logger.py,sha256=iYwbA7DLyXalWa2Yu07HO6Xm301nRuenMU64GgwUMkU,3576
24
24
  dbos/_migrations/env.py,sha256=38SIGVbmn_VV2x2u1aHLcPOoWgZ84eCymf3g_NljmbU,1626
25
25
  dbos/_migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
26
+ dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py,sha256=ICLPl8CN9tQXMsLDsAj8z1TsL831-Z3F8jSBvrR-wyw,736
26
27
  dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py,sha256=ZBYrtTdxy64HxIAlOes89fVIk2P1gNaJack7wuC_epg,873
27
28
  dbos/_migrations/versions/5c361fc04708_added_system_tables.py,sha256=QMgFMb0aLgC25YicsvPSr6AHRCA6Zd66hyaRUhwKzrQ,6404
28
29
  dbos/_migrations/versions/a3b18ad34abe_added_triggers.py,sha256=Rv0ZsZYZ_WdgGEULYsPfnp4YzaO5L198gDTgYY39AVA,2022
@@ -30,7 +31,7 @@ dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py,sha256=8PyFi8rd6CN-m
30
31
  dbos/_migrations/versions/d76646551a6c_workflow_queue.py,sha256=G942nophZ2uC2vc4hGBC02Ptng1715roTjY3xiyzZU4,729
31
32
  dbos/_migrations/versions/eab0cc1d9a14_job_queue.py,sha256=uvhFOtqbBreCePhAxZfIT0qCAI7BiZTou9wt6QnbY7c,1412
32
33
  dbos/_outcome.py,sha256=FDMgWVjZ06vm9xO-38H17mTqBImUYQxgKs_bDCSIAhE,6648
33
- dbos/_queue.py,sha256=5NZ6RfKQd8LQD8EeUXgrwu86r0AadKEqPIMmL_1ORuw,1956
34
+ dbos/_queue.py,sha256=VSaF-BTv2tm-44O_690omo0pE31NQAhOT3ARL4VLRzY,2723
34
35
  dbos/_recovery.py,sha256=jbzGYxICA2drzyzlBSy2UiXhKV_16tBVacKQdTkqf-w,2008
35
36
  dbos/_registrations.py,sha256=mei6q6_3R5uei8i_Wo_TqGZs85s10shOekDX41sFYD0,6642
36
37
  dbos/_request.py,sha256=cX1B3Atlh160phgS35gF1VEEV4pD126c9F3BDgBmxZU,929
@@ -38,22 +39,22 @@ dbos/_roles.py,sha256=iOsgmIAf1XVzxs3gYWdGRe1B880YfOw5fpU7Jwx8_A8,2271
38
39
  dbos/_scheduler.py,sha256=0I3e8Y-OIBG3wiUCIskShd-Sk_eUFCFyRB5u4L7IHXI,1940
39
40
  dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
41
  dbos/_schemas/application_database.py,sha256=KeyoPrF7hy_ODXV7QNike_VFSD74QBRfQ76D7QyE9HI,966
41
- dbos/_schemas/system_database.py,sha256=7iw7eHJzEvkatHMOaHORoSvtfisF73wW5j8hRt_Ph14,5126
42
+ dbos/_schemas/system_database.py,sha256=rwp4EvCSaXcUoMaRczZCvETCxGp72k3-hvLyGUDkih0,5163
42
43
  dbos/_serialization.py,sha256=YCYv0qKAwAZ1djZisBC7khvKqG-5OcIv9t9EC5PFIog,1743
43
- dbos/_sys_db.py,sha256=uZKeCnGc2MgvEd0ID3nReBBZj21HzClP56TFkXTvIZE,49028
44
+ dbos/_sys_db.py,sha256=2W3ta0Q-isESMjyGbXCPfaoll-vyPQg1innBEeNfg2c,50088
44
45
  dbos/_templates/hello/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
45
46
  dbos/_templates/hello/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
47
  dbos/_templates/hello/__package/main.py,sha256=eI0SS9Nwj-fldtiuSzIlIG6dC91GXXwdRsoHxv6S_WI,2719
47
48
  dbos/_templates/hello/__package/schema.py,sha256=7Z27JGC8yy7Z44cbVXIREYxtUhU4JVkLCp5Q7UahVQ0,260
48
49
  dbos/_templates/hello/alembic.ini,sha256=VKBn4Gy8mMuCdY7Hip1jmo3wEUJ1VG1aW7EqY0_n-as,3695
49
- dbos/_templates/hello/dbos-config.yaml.dbos,sha256=7yu1q8FAgOZnwJtU-e_5qgV-wkHRn6cqo-GEmk9rK8U,577
50
+ dbos/_templates/hello/dbos-config.yaml.dbos,sha256=OMlcpdYUJKjyAme7phOz3pbn9upcIRjm42iwEThWUEQ,495
50
51
  dbos/_templates/hello/migrations/env.py.dbos,sha256=GUV6sjkDzf9Vl6wkGEd0RSkK-ftRfV6EUwSQdd0qFXg,2392
51
52
  dbos/_templates/hello/migrations/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
52
53
  dbos/_templates/hello/migrations/versions/2024_07_31_180642_init.py,sha256=U5thFWGqNN4QLrNXT7wUUqftIFDNE5eSdqD8JNW1mec,942
53
54
  dbos/_templates/hello/start_postgres_docker.py,sha256=lQVLlYO5YkhGPEgPqwGc7Y8uDKse9HsWv5fynJEFJHM,1681
54
55
  dbos/_tracer.py,sha256=rvBY1RQU6DO7rL7EnaJJxGcmd4tP_PpGqUEE6imZnhY,2518
55
56
  dbos/cli.py,sha256=em1uAxrp5yyg53V7ZpmHFtqD6OJp2cMJkG9vGJPoFTA,10904
56
- dbos/dbos-config.schema.json,sha256=00lliu7hGT6NAIZt8UNAn8mEhQ71RGw6Q2CI3nWxULA,5899
57
+ dbos/dbos-config.schema.json,sha256=X5TpXNcARGceX0zQs0fVgtZW_Xj9uBbY5afPt9Rz9yk,5741
57
58
  dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
58
59
  version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
59
- dbos-0.18.0a1.dist-info/RECORD,,
60
+ dbos-0.19.0a4.dist-info/RECORD,,