dbos 0.23.0a8__py3-none-any.whl → 0.23.0a10__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/__main__.py +26 -0
- dbos/_app_db.py +29 -24
- dbos/_core.py +45 -25
- dbos/_dbos.py +15 -5
- dbos/_dbos_config.py +45 -11
- dbos/_debug.py +45 -0
- dbos/_recovery.py +12 -6
- dbos/_sys_db.py +130 -51
- dbos/cli/cli.py +18 -0
- {dbos-0.23.0a8.dist-info → dbos-0.23.0a10.dist-info}/METADATA +1 -1
- {dbos-0.23.0a8.dist-info → dbos-0.23.0a10.dist-info}/RECORD +14 -12
- {dbos-0.23.0a8.dist-info → dbos-0.23.0a10.dist-info}/WHEEL +0 -0
- {dbos-0.23.0a8.dist-info → dbos-0.23.0a10.dist-info}/entry_points.txt +0 -0
- {dbos-0.23.0a8.dist-info → dbos-0.23.0a10.dist-info}/licenses/LICENSE +0 -0
dbos/__main__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
from typing import NoReturn, Optional, Union
|
|
4
|
+
|
|
5
|
+
from dbos.cli.cli import app
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> NoReturn:
|
|
9
|
+
# Modify sys.argv[0] to remove script or executable extensions
|
|
10
|
+
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
|
|
11
|
+
|
|
12
|
+
retval: Optional[Union[str, int]] = 1
|
|
13
|
+
try:
|
|
14
|
+
app()
|
|
15
|
+
retval = None
|
|
16
|
+
except SystemExit as e:
|
|
17
|
+
retval = e.code
|
|
18
|
+
except Exception as e:
|
|
19
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
20
|
+
retval = 1
|
|
21
|
+
finally:
|
|
22
|
+
sys.exit(retval)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
main()
|
dbos/_app_db.py
CHANGED
|
@@ -27,29 +27,30 @@ class RecordedResult(TypedDict):
|
|
|
27
27
|
|
|
28
28
|
class ApplicationDatabase:
|
|
29
29
|
|
|
30
|
-
def __init__(self, config: ConfigFile):
|
|
30
|
+
def __init__(self, config: ConfigFile, *, debug_mode: bool = False):
|
|
31
31
|
self.config = config
|
|
32
32
|
|
|
33
33
|
app_db_name = config["database"]["app_db_name"]
|
|
34
34
|
|
|
35
35
|
# If the application database does not already exist, create it
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
if not debug_mode:
|
|
37
|
+
postgres_db_url = sa.URL.create(
|
|
38
|
+
"postgresql+psycopg",
|
|
39
|
+
username=config["database"]["username"],
|
|
40
|
+
password=config["database"]["password"],
|
|
41
|
+
host=config["database"]["hostname"],
|
|
42
|
+
port=config["database"]["port"],
|
|
43
|
+
database="postgres",
|
|
44
|
+
)
|
|
45
|
+
postgres_db_engine = sa.create_engine(postgres_db_url)
|
|
46
|
+
with postgres_db_engine.connect() as conn:
|
|
47
|
+
conn.execution_options(isolation_level="AUTOCOMMIT")
|
|
48
|
+
if not conn.execute(
|
|
49
|
+
sa.text("SELECT 1 FROM pg_database WHERE datname=:db_name"),
|
|
50
|
+
parameters={"db_name": app_db_name},
|
|
51
|
+
).scalar():
|
|
52
|
+
conn.execute(sa.text(f"CREATE DATABASE {app_db_name}"))
|
|
53
|
+
postgres_db_engine.dispose()
|
|
53
54
|
|
|
54
55
|
# Create a connection pool for the application database
|
|
55
56
|
app_db_url = sa.URL.create(
|
|
@@ -64,14 +65,16 @@ class ApplicationDatabase:
|
|
|
64
65
|
app_db_url, pool_size=20, max_overflow=5, pool_timeout=30
|
|
65
66
|
)
|
|
66
67
|
self.sessionmaker = sessionmaker(bind=self.engine)
|
|
68
|
+
self.debug_mode = debug_mode
|
|
67
69
|
|
|
68
70
|
# Create the dbos schema and transaction_outputs table in the application database
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if not debug_mode:
|
|
72
|
+
with self.engine.begin() as conn:
|
|
73
|
+
schema_creation_query = sa.text(
|
|
74
|
+
f"CREATE SCHEMA IF NOT EXISTS {ApplicationSchema.schema}"
|
|
75
|
+
)
|
|
76
|
+
conn.execute(schema_creation_query)
|
|
77
|
+
ApplicationSchema.metadata_obj.create_all(self.engine)
|
|
75
78
|
|
|
76
79
|
def destroy(self) -> None:
|
|
77
80
|
self.engine.dispose()
|
|
@@ -100,6 +103,8 @@ class ApplicationDatabase:
|
|
|
100
103
|
raise
|
|
101
104
|
|
|
102
105
|
def record_transaction_error(self, output: TransactionResultInternal) -> None:
|
|
106
|
+
if self.debug_mode:
|
|
107
|
+
raise Exception("called record_transaction_error in debug mode")
|
|
103
108
|
try:
|
|
104
109
|
with self.engine.begin() as conn:
|
|
105
110
|
conn.execute(
|
dbos/_core.py
CHANGED
|
@@ -186,21 +186,31 @@ def _init_workflow(
|
|
|
186
186
|
inputs = {"args": inputs["args"][1:], "kwargs": inputs["kwargs"]}
|
|
187
187
|
|
|
188
188
|
wf_status = status["status"]
|
|
189
|
-
if
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
wf_status =
|
|
194
|
-
status, max_recovery_attempts=max_recovery_attempts
|
|
195
|
-
)
|
|
196
|
-
# TODO: Modify the inputs if they were changed by `update_workflow_inputs`
|
|
197
|
-
dbos._sys_db.update_workflow_inputs(wfid, _serialization.serialize_args(inputs))
|
|
189
|
+
if dbos.debug_mode:
|
|
190
|
+
get_status_result = dbos._sys_db.get_workflow_status(wfid)
|
|
191
|
+
if get_status_result is None:
|
|
192
|
+
raise DBOSNonExistentWorkflowError(wfid)
|
|
193
|
+
wf_status = get_status_result["status"]
|
|
198
194
|
else:
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
if temp_wf_type != "transaction" or queue is not None:
|
|
196
|
+
# Synchronously record the status and inputs for workflows and single-step workflows
|
|
197
|
+
# We also have to do this for single-step workflows because of the foreign key constraint on the operation outputs table
|
|
198
|
+
# TODO: Make this transactional (and with the queue step below)
|
|
199
|
+
wf_status = dbos._sys_db.insert_workflow_status(
|
|
200
|
+
status, max_recovery_attempts=max_recovery_attempts
|
|
201
|
+
)
|
|
202
|
+
# TODO: Modify the inputs if they were changed by `update_workflow_inputs`
|
|
203
|
+
dbos._sys_db.update_workflow_inputs(
|
|
204
|
+
wfid, _serialization.serialize_args(inputs)
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
# Buffer the inputs for single-transaction workflows, but don't buffer the status
|
|
208
|
+
dbos._sys_db.buffer_workflow_inputs(
|
|
209
|
+
wfid, _serialization.serialize_args(inputs)
|
|
210
|
+
)
|
|
201
211
|
|
|
202
|
-
|
|
203
|
-
|
|
212
|
+
if queue is not None and wf_status == WorkflowStatusString.ENQUEUED.value:
|
|
213
|
+
dbos._sys_db.enqueue(wfid, queue)
|
|
204
214
|
|
|
205
215
|
status["status"] = wf_status
|
|
206
216
|
return status
|
|
@@ -215,10 +225,11 @@ def _get_wf_invoke_func(
|
|
|
215
225
|
output = func()
|
|
216
226
|
status["status"] = "SUCCESS"
|
|
217
227
|
status["output"] = _serialization.serialize(output)
|
|
218
|
-
if
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
if not dbos.debug_mode:
|
|
229
|
+
if status["queue_name"] is not None:
|
|
230
|
+
queue = dbos._registry.queue_info_map[status["queue_name"]]
|
|
231
|
+
dbos._sys_db.remove_from_queue(status["workflow_uuid"], queue)
|
|
232
|
+
dbos._sys_db.buffer_workflow_status(status)
|
|
222
233
|
return output
|
|
223
234
|
except DBOSWorkflowConflictIDError:
|
|
224
235
|
# Retrieve the workflow handle and wait for the result.
|
|
@@ -233,10 +244,11 @@ def _get_wf_invoke_func(
|
|
|
233
244
|
except Exception as error:
|
|
234
245
|
status["status"] = "ERROR"
|
|
235
246
|
status["error"] = _serialization.serialize_exception(error)
|
|
236
|
-
if
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
247
|
+
if not dbos.debug_mode:
|
|
248
|
+
if status["queue_name"] is not None:
|
|
249
|
+
queue = dbos._registry.queue_info_map[status["queue_name"]]
|
|
250
|
+
dbos._sys_db.remove_from_queue(status["workflow_uuid"], queue)
|
|
251
|
+
dbos._sys_db.update_workflow_status(status)
|
|
240
252
|
raise
|
|
241
253
|
|
|
242
254
|
return persist
|
|
@@ -422,10 +434,12 @@ def start_workflow(
|
|
|
422
434
|
|
|
423
435
|
wf_status = status["status"]
|
|
424
436
|
|
|
425
|
-
if (
|
|
426
|
-
not
|
|
427
|
-
|
|
428
|
-
|
|
437
|
+
if not execute_workflow or (
|
|
438
|
+
not dbos.debug_mode
|
|
439
|
+
and (
|
|
440
|
+
wf_status == WorkflowStatusString.ERROR.value
|
|
441
|
+
or wf_status == WorkflowStatusString.SUCCESS.value
|
|
442
|
+
)
|
|
429
443
|
):
|
|
430
444
|
dbos.logger.debug(
|
|
431
445
|
f"Workflow {new_wf_id} already completed with status {wf_status}. Directly returning a workflow handle."
|
|
@@ -597,6 +611,10 @@ def decorate_transaction(
|
|
|
597
611
|
ctx.function_id,
|
|
598
612
|
)
|
|
599
613
|
)
|
|
614
|
+
if dbos.debug_mode and recorded_output is None:
|
|
615
|
+
raise DBOSException(
|
|
616
|
+
"Transaction output not found in debug mode"
|
|
617
|
+
)
|
|
600
618
|
if recorded_output:
|
|
601
619
|
dbos.logger.debug(
|
|
602
620
|
f"Replaying transaction, id: {ctx.function_id}, name: {attributes['name']}"
|
|
@@ -780,6 +798,8 @@ def decorate_step(
|
|
|
780
798
|
recorded_output = dbos._sys_db.check_operation_execution(
|
|
781
799
|
ctx.workflow_id, ctx.function_id
|
|
782
800
|
)
|
|
801
|
+
if dbos.debug_mode and recorded_output is None:
|
|
802
|
+
raise DBOSException("Step output not found in debug mode")
|
|
783
803
|
if recorded_output:
|
|
784
804
|
dbos.logger.debug(
|
|
785
805
|
f"Replaying step, id: {ctx.function_id}, name: {attributes['name']}"
|
dbos/_dbos.py
CHANGED
|
@@ -313,6 +313,7 @@ class DBOS:
|
|
|
313
313
|
dbos_logger.info("Initializing DBOS")
|
|
314
314
|
self.config: ConfigFile = config
|
|
315
315
|
self._launched: bool = False
|
|
316
|
+
self._debug_mode: bool = False
|
|
316
317
|
self._sys_db_field: Optional[SystemDatabase] = None
|
|
317
318
|
self._app_db_field: Optional[ApplicationDatabase] = None
|
|
318
319
|
self._registry: DBOSRegistry = _get_or_create_dbos_registry()
|
|
@@ -380,23 +381,32 @@ class DBOS:
|
|
|
380
381
|
rv: AdminServer = self._admin_server_field
|
|
381
382
|
return rv
|
|
382
383
|
|
|
384
|
+
@property
|
|
385
|
+
def debug_mode(self) -> bool:
|
|
386
|
+
return self._debug_mode
|
|
387
|
+
|
|
383
388
|
@classmethod
|
|
384
|
-
def launch(cls) -> None:
|
|
389
|
+
def launch(cls, *, debug_mode: bool = False) -> None:
|
|
385
390
|
if _dbos_global_instance is not None:
|
|
386
|
-
_dbos_global_instance._launch()
|
|
391
|
+
_dbos_global_instance._launch(debug_mode=debug_mode)
|
|
387
392
|
|
|
388
|
-
def _launch(self) -> None:
|
|
393
|
+
def _launch(self, *, debug_mode: bool = False) -> None:
|
|
389
394
|
try:
|
|
390
395
|
if self._launched:
|
|
391
396
|
dbos_logger.warning(f"DBOS was already launched")
|
|
392
397
|
return
|
|
393
398
|
self._launched = True
|
|
399
|
+
self._debug_mode = debug_mode
|
|
394
400
|
if GlobalParams.app_version == "":
|
|
395
401
|
GlobalParams.app_version = self._registry.compute_app_version()
|
|
396
402
|
dbos_logger.info(f"Application version: {GlobalParams.app_version}")
|
|
397
403
|
self._executor_field = ThreadPoolExecutor(max_workers=64)
|
|
398
|
-
self._sys_db_field = SystemDatabase(self.config)
|
|
399
|
-
self._app_db_field = ApplicationDatabase(self.config)
|
|
404
|
+
self._sys_db_field = SystemDatabase(self.config, debug_mode=debug_mode)
|
|
405
|
+
self._app_db_field = ApplicationDatabase(self.config, debug_mode=debug_mode)
|
|
406
|
+
|
|
407
|
+
if debug_mode:
|
|
408
|
+
return
|
|
409
|
+
|
|
400
410
|
admin_port = self.config["runtimeConfig"].get("admin_port")
|
|
401
411
|
if admin_port is None:
|
|
402
412
|
admin_port = 3001
|
dbos/_dbos_config.py
CHANGED
|
@@ -192,7 +192,11 @@ def load_config(
|
|
|
192
192
|
data = cast(ConfigFile, data)
|
|
193
193
|
db_connection = load_db_connection()
|
|
194
194
|
if not silent:
|
|
195
|
-
if
|
|
195
|
+
if os.getenv("DBOS_DBHOST"):
|
|
196
|
+
print(
|
|
197
|
+
"[bold blue]Loading database connection parameters from debug environment variables[/bold blue]"
|
|
198
|
+
)
|
|
199
|
+
elif data["database"].get("hostname"):
|
|
196
200
|
print(
|
|
197
201
|
"[bold blue]Loading database connection parameters from dbos-config.yaml[/bold blue]"
|
|
198
202
|
)
|
|
@@ -205,32 +209,62 @@ def load_config(
|
|
|
205
209
|
"[bold blue]Using default database connection parameters (localhost)[/bold blue]"
|
|
206
210
|
)
|
|
207
211
|
|
|
212
|
+
dbos_dbport: Optional[int] = None
|
|
213
|
+
dbport_env = os.getenv("DBOS_DBPORT")
|
|
214
|
+
if dbport_env:
|
|
215
|
+
try:
|
|
216
|
+
dbos_dbport = int(dbport_env)
|
|
217
|
+
except ValueError:
|
|
218
|
+
pass
|
|
219
|
+
dbos_dblocalsuffix: Optional[bool] = None
|
|
220
|
+
dblocalsuffix_env = os.getenv("DBOS_DBLOCALSUFFIX")
|
|
221
|
+
if dblocalsuffix_env:
|
|
222
|
+
try:
|
|
223
|
+
dbos_dblocalsuffix = dblocalsuffix_env.casefold() == "true".casefold()
|
|
224
|
+
except ValueError:
|
|
225
|
+
pass
|
|
226
|
+
|
|
208
227
|
data["database"]["hostname"] = (
|
|
209
|
-
|
|
228
|
+
os.getenv("DBOS_DBHOST")
|
|
229
|
+
or data["database"].get("hostname")
|
|
230
|
+
or db_connection.get("hostname")
|
|
231
|
+
or "localhost"
|
|
210
232
|
)
|
|
233
|
+
|
|
211
234
|
data["database"]["port"] = (
|
|
212
|
-
data["database"].get("port") or db_connection.get("port") or 5432
|
|
235
|
+
dbos_dbport or data["database"].get("port") or db_connection.get("port") or 5432
|
|
213
236
|
)
|
|
214
237
|
data["database"]["username"] = (
|
|
215
|
-
|
|
238
|
+
os.getenv("DBOS_DBUSER")
|
|
239
|
+
or data["database"].get("username")
|
|
240
|
+
or db_connection.get("username")
|
|
241
|
+
or "postgres"
|
|
216
242
|
)
|
|
217
243
|
data["database"]["password"] = (
|
|
218
|
-
|
|
244
|
+
os.getenv("DBOS_DBPASSWORD")
|
|
245
|
+
or data["database"].get("password")
|
|
219
246
|
or db_connection.get("password")
|
|
220
247
|
or os.environ.get("PGPASSWORD")
|
|
221
248
|
or "dbos"
|
|
222
249
|
)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
250
|
+
|
|
251
|
+
local_suffix = False
|
|
252
|
+
dbcon_local_suffix = db_connection.get("local_suffix")
|
|
253
|
+
if dbcon_local_suffix is not None:
|
|
254
|
+
local_suffix = dbcon_local_suffix
|
|
255
|
+
if data["database"].get("local_suffix") is not None:
|
|
256
|
+
local_suffix = data["database"].get("local_suffix")
|
|
257
|
+
if dbos_dblocalsuffix is not None:
|
|
258
|
+
local_suffix = dbos_dblocalsuffix
|
|
259
|
+
data["database"]["local_suffix"] = local_suffix
|
|
228
260
|
|
|
229
261
|
# Configure the DBOS logger
|
|
230
262
|
config_logger(data)
|
|
231
263
|
|
|
232
264
|
# Check the connectivity to the database and make sure it's properly configured
|
|
233
|
-
if
|
|
265
|
+
# Note, never use db wizard if the DBOS is running in debug mode (i.e. DBOS_DEBUG_WORKFLOW_ID env var is set)
|
|
266
|
+
debugWorkflowId = os.getenv("DBOS_DEBUG_WORKFLOW_ID")
|
|
267
|
+
if use_db_wizard and debugWorkflowId is None:
|
|
234
268
|
data = db_wizard(data, config_file_path)
|
|
235
269
|
|
|
236
270
|
if "local_suffix" in data["database"] and data["database"]["local_suffix"]:
|
dbos/_debug.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import runpy
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from dbos import DBOS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PythonModule:
|
|
10
|
+
def __init__(self, module_name: str):
|
|
11
|
+
self.module_name = module_name
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def debug_workflow(workflow_id: str, entrypoint: Union[str, PythonModule]) -> None:
|
|
15
|
+
# include the current directory (represented by empty string) in the search path
|
|
16
|
+
# if it not already included
|
|
17
|
+
if "" not in sys.path:
|
|
18
|
+
sys.path.insert(0, "")
|
|
19
|
+
if isinstance(entrypoint, str):
|
|
20
|
+
runpy.run_path(entrypoint)
|
|
21
|
+
elif isinstance(entrypoint, PythonModule):
|
|
22
|
+
runpy.run_module(entrypoint.module_name)
|
|
23
|
+
else:
|
|
24
|
+
raise ValueError("Invalid entrypoint type. Must be a string or PythonModule.")
|
|
25
|
+
|
|
26
|
+
DBOS.logger.info(f"Debugging workflow {workflow_id}...")
|
|
27
|
+
DBOS.launch(debug_mode=True)
|
|
28
|
+
handle = DBOS.execute_workflow_id(workflow_id)
|
|
29
|
+
handle.get_result()
|
|
30
|
+
DBOS.logger.info("Workflow Debugging complete. Exiting process.")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_start_command(command: str) -> Union[str, PythonModule]:
|
|
34
|
+
match = re.match(r"fastapi\s+run\s+(\.?[\w/]+\.py)", command)
|
|
35
|
+
if match:
|
|
36
|
+
return match.group(1)
|
|
37
|
+
match = re.match(r"python3?\s+(\.?[\w/]+\.py)", command)
|
|
38
|
+
if match:
|
|
39
|
+
return match.group(1)
|
|
40
|
+
match = re.match(r"python3?\s+-m\s+([\w\.]+)", command)
|
|
41
|
+
if match:
|
|
42
|
+
return PythonModule(match.group(1))
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"Invalid command format. Must be 'fastapi run <script>' or 'python <script>' or 'python -m <module>'"
|
|
45
|
+
)
|
dbos/_recovery.py
CHANGED
|
@@ -27,8 +27,9 @@ def startup_recovery_thread(
|
|
|
27
27
|
pending_workflow.queue_name
|
|
28
28
|
and pending_workflow.queue_name != "_dbos_internal_queue"
|
|
29
29
|
):
|
|
30
|
-
dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
|
|
31
|
-
|
|
30
|
+
cleared = dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
|
|
31
|
+
if cleared:
|
|
32
|
+
continue
|
|
32
33
|
execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
|
|
33
34
|
pending_workflows.remove(pending_workflow)
|
|
34
35
|
except DBOSWorkflowFunctionNotFoundError:
|
|
@@ -56,10 +57,15 @@ def recover_pending_workflows(
|
|
|
56
57
|
and pending_workflow.queue_name != "_dbos_internal_queue"
|
|
57
58
|
):
|
|
58
59
|
try:
|
|
59
|
-
dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
cleared = dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
|
|
61
|
+
if cleared:
|
|
62
|
+
workflow_handles.append(
|
|
63
|
+
dbos.retrieve_workflow(pending_workflow.workflow_uuid)
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
workflow_handles.append(
|
|
67
|
+
execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
|
|
68
|
+
)
|
|
63
69
|
except Exception as e:
|
|
64
70
|
dbos.logger.error(e)
|
|
65
71
|
else:
|
dbos/_sys_db.py
CHANGED
|
@@ -156,7 +156,7 @@ _buffer_flush_interval_secs = 1.0
|
|
|
156
156
|
|
|
157
157
|
class SystemDatabase:
|
|
158
158
|
|
|
159
|
-
def __init__(self, config: ConfigFile):
|
|
159
|
+
def __init__(self, config: ConfigFile, *, debug_mode: bool = False):
|
|
160
160
|
self.config = config
|
|
161
161
|
|
|
162
162
|
sysdb_name = (
|
|
@@ -165,26 +165,27 @@ class SystemDatabase:
|
|
|
165
165
|
else config["database"]["app_db_name"] + SystemSchema.sysdb_suffix
|
|
166
166
|
)
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
168
|
+
if not debug_mode:
|
|
169
|
+
# If the system database does not already exist, create it
|
|
170
|
+
postgres_db_url = sa.URL.create(
|
|
171
|
+
"postgresql+psycopg",
|
|
172
|
+
username=config["database"]["username"],
|
|
173
|
+
password=config["database"]["password"],
|
|
174
|
+
host=config["database"]["hostname"],
|
|
175
|
+
port=config["database"]["port"],
|
|
176
|
+
database="postgres",
|
|
177
|
+
# fills the "application_name" column in pg_stat_activity
|
|
178
|
+
query={"application_name": f"dbos_transact_{GlobalParams.executor_id}"},
|
|
179
|
+
)
|
|
180
|
+
engine = sa.create_engine(postgres_db_url)
|
|
181
|
+
with engine.connect() as conn:
|
|
182
|
+
conn.execution_options(isolation_level="AUTOCOMMIT")
|
|
183
|
+
if not conn.execute(
|
|
184
|
+
sa.text("SELECT 1 FROM pg_database WHERE datname=:db_name"),
|
|
185
|
+
parameters={"db_name": sysdb_name},
|
|
186
|
+
).scalar():
|
|
187
|
+
conn.execute(sa.text(f"CREATE DATABASE {sysdb_name}"))
|
|
188
|
+
engine.dispose()
|
|
188
189
|
|
|
189
190
|
system_db_url = sa.URL.create(
|
|
190
191
|
"postgresql+psycopg",
|
|
@@ -203,25 +204,41 @@ class SystemDatabase:
|
|
|
203
204
|
)
|
|
204
205
|
|
|
205
206
|
# Run a schema migration for the system database
|
|
206
|
-
|
|
207
|
-
os.path.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
alembic_cfg.set_main_option("sqlalchemy.url", escaped_conn_string)
|
|
219
|
-
try:
|
|
220
|
-
command.upgrade(alembic_cfg, "head")
|
|
221
|
-
except Exception as e:
|
|
222
|
-
dbos_logger.warning(
|
|
223
|
-
f"Exception during system database construction. This is most likely because the system database was configured using a later version of DBOS: {e}"
|
|
207
|
+
if not debug_mode:
|
|
208
|
+
migration_dir = os.path.join(
|
|
209
|
+
os.path.dirname(os.path.realpath(__file__)), "_migrations"
|
|
210
|
+
)
|
|
211
|
+
alembic_cfg = Config()
|
|
212
|
+
alembic_cfg.set_main_option("script_location", migration_dir)
|
|
213
|
+
logging.getLogger("alembic").setLevel(logging.WARNING)
|
|
214
|
+
# Alembic requires the % in URL-escaped parameters to itself be escaped to %%.
|
|
215
|
+
escaped_conn_string = re.sub(
|
|
216
|
+
r"%(?=[0-9A-Fa-f]{2})",
|
|
217
|
+
"%%",
|
|
218
|
+
self.engine.url.render_as_string(hide_password=False),
|
|
224
219
|
)
|
|
220
|
+
alembic_cfg.set_main_option("sqlalchemy.url", escaped_conn_string)
|
|
221
|
+
try:
|
|
222
|
+
command.upgrade(alembic_cfg, "head")
|
|
223
|
+
except Exception as e:
|
|
224
|
+
dbos_logger.warning(
|
|
225
|
+
f"Exception during system database construction. This is most likely because the system database was configured using a later version of DBOS: {e}"
|
|
226
|
+
)
|
|
227
|
+
alembic_cfg = Config()
|
|
228
|
+
alembic_cfg.set_main_option("script_location", migration_dir)
|
|
229
|
+
# Alembic requires the % in URL-escaped parameters to itself be escaped to %%.
|
|
230
|
+
escaped_conn_string = re.sub(
|
|
231
|
+
r"%(?=[0-9A-Fa-f]{2})",
|
|
232
|
+
"%%",
|
|
233
|
+
self.engine.url.render_as_string(hide_password=False),
|
|
234
|
+
)
|
|
235
|
+
alembic_cfg.set_main_option("sqlalchemy.url", escaped_conn_string)
|
|
236
|
+
try:
|
|
237
|
+
command.upgrade(alembic_cfg, "head")
|
|
238
|
+
except Exception as e:
|
|
239
|
+
dbos_logger.warning(
|
|
240
|
+
f"Exception during system database construction. This is most likely because the system database was configured using a later version of DBOS: {e}"
|
|
241
|
+
)
|
|
225
242
|
|
|
226
243
|
self.notification_conn: Optional[psycopg.connection.Connection] = None
|
|
227
244
|
self.notifications_map: Dict[str, threading.Condition] = {}
|
|
@@ -237,6 +254,7 @@ class SystemDatabase:
|
|
|
237
254
|
|
|
238
255
|
# Now we can run background processes
|
|
239
256
|
self._run_background_processes = True
|
|
257
|
+
self._debug_mode = debug_mode
|
|
240
258
|
|
|
241
259
|
# Destroy the pool when finished
|
|
242
260
|
def destroy(self) -> None:
|
|
@@ -258,6 +276,8 @@ class SystemDatabase:
|
|
|
258
276
|
*,
|
|
259
277
|
max_recovery_attempts: int = DEFAULT_MAX_RECOVERY_ATTEMPTS,
|
|
260
278
|
) -> WorkflowStatuses:
|
|
279
|
+
if self._debug_mode:
|
|
280
|
+
raise Exception("called insert_workflow_status in debug mode")
|
|
261
281
|
wf_status: WorkflowStatuses = status["status"]
|
|
262
282
|
|
|
263
283
|
cmd = (
|
|
@@ -357,6 +377,8 @@ class SystemDatabase:
|
|
|
357
377
|
*,
|
|
358
378
|
conn: Optional[sa.Connection] = None,
|
|
359
379
|
) -> None:
|
|
380
|
+
if self._debug_mode:
|
|
381
|
+
raise Exception("called update_workflow_status in debug mode")
|
|
360
382
|
wf_status: WorkflowStatuses = status["status"]
|
|
361
383
|
|
|
362
384
|
cmd = (
|
|
@@ -406,6 +428,8 @@ class SystemDatabase:
|
|
|
406
428
|
self,
|
|
407
429
|
workflow_id: str,
|
|
408
430
|
) -> None:
|
|
431
|
+
if self._debug_mode:
|
|
432
|
+
raise Exception("called cancel_workflow in debug mode")
|
|
409
433
|
with self.engine.begin() as c:
|
|
410
434
|
# Remove the workflow from the queues table so it does not block the table
|
|
411
435
|
c.execute(
|
|
@@ -426,6 +450,8 @@ class SystemDatabase:
|
|
|
426
450
|
self,
|
|
427
451
|
workflow_id: str,
|
|
428
452
|
) -> None:
|
|
453
|
+
if self._debug_mode:
|
|
454
|
+
raise Exception("called resume_workflow in debug mode")
|
|
429
455
|
with self.engine.begin() as c:
|
|
430
456
|
# Check the status of the workflow. If it is complete, do nothing.
|
|
431
457
|
row = c.execute(
|
|
@@ -573,6 +599,9 @@ class SystemDatabase:
|
|
|
573
599
|
def update_workflow_inputs(
|
|
574
600
|
self, workflow_uuid: str, inputs: str, conn: Optional[sa.Connection] = None
|
|
575
601
|
) -> None:
|
|
602
|
+
if self._debug_mode:
|
|
603
|
+
raise Exception("called update_workflow_inputs in debug mode")
|
|
604
|
+
|
|
576
605
|
cmd = (
|
|
577
606
|
pg.insert(SystemSchema.workflow_inputs)
|
|
578
607
|
.values(
|
|
@@ -737,6 +766,8 @@ class SystemDatabase:
|
|
|
737
766
|
def record_operation_result(
|
|
738
767
|
self, result: OperationResultInternal, conn: Optional[sa.Connection] = None
|
|
739
768
|
) -> None:
|
|
769
|
+
if self._debug_mode:
|
|
770
|
+
raise Exception("called record_operation_result in debug mode")
|
|
740
771
|
error = result["error"]
|
|
741
772
|
output = result["output"]
|
|
742
773
|
assert error is None or output is None, "Only one of error or output can be set"
|
|
@@ -796,6 +827,11 @@ class SystemDatabase:
|
|
|
796
827
|
recorded_output = self.check_operation_execution(
|
|
797
828
|
workflow_uuid, function_id, conn=c
|
|
798
829
|
)
|
|
830
|
+
if self._debug_mode and recorded_output is None:
|
|
831
|
+
raise Exception(
|
|
832
|
+
"called send in debug mode without a previous execution"
|
|
833
|
+
)
|
|
834
|
+
|
|
799
835
|
if recorded_output is not None:
|
|
800
836
|
dbos_logger.debug(
|
|
801
837
|
f"Replaying send, id: {function_id}, destination_uuid: {destination_uuid}, topic: {topic}"
|
|
@@ -839,6 +875,8 @@ class SystemDatabase:
|
|
|
839
875
|
|
|
840
876
|
# First, check for previous executions.
|
|
841
877
|
recorded_output = self.check_operation_execution(workflow_uuid, function_id)
|
|
878
|
+
if self._debug_mode and recorded_output is None:
|
|
879
|
+
raise Exception("called recv in debug mode without a previous execution")
|
|
842
880
|
if recorded_output is not None:
|
|
843
881
|
dbos_logger.debug(f"Replaying recv, id: {function_id}, topic: {topic}")
|
|
844
882
|
if recorded_output["output"] is not None:
|
|
@@ -988,6 +1026,9 @@ class SystemDatabase:
|
|
|
988
1026
|
) -> float:
|
|
989
1027
|
recorded_output = self.check_operation_execution(workflow_uuid, function_id)
|
|
990
1028
|
end_time: float
|
|
1029
|
+
if self._debug_mode and recorded_output is None:
|
|
1030
|
+
raise Exception("called sleep in debug mode without a previous execution")
|
|
1031
|
+
|
|
991
1032
|
if recorded_output is not None:
|
|
992
1033
|
dbos_logger.debug(f"Replaying sleep, id: {function_id}, seconds: {seconds}")
|
|
993
1034
|
assert recorded_output["output"] is not None, "no recorded end time"
|
|
@@ -1022,6 +1063,10 @@ class SystemDatabase:
|
|
|
1022
1063
|
recorded_output = self.check_operation_execution(
|
|
1023
1064
|
workflow_uuid, function_id, conn=c
|
|
1024
1065
|
)
|
|
1066
|
+
if self._debug_mode and recorded_output is None:
|
|
1067
|
+
raise Exception(
|
|
1068
|
+
"called set_event in debug mode without a previous execution"
|
|
1069
|
+
)
|
|
1025
1070
|
if recorded_output is not None:
|
|
1026
1071
|
dbos_logger.debug(f"Replaying set_event, id: {function_id}, key: {key}")
|
|
1027
1072
|
return # Already sent before
|
|
@@ -1066,6 +1111,10 @@ class SystemDatabase:
|
|
|
1066
1111
|
recorded_output = self.check_operation_execution(
|
|
1067
1112
|
caller_ctx["workflow_uuid"], caller_ctx["function_id"]
|
|
1068
1113
|
)
|
|
1114
|
+
if self._debug_mode and recorded_output is None:
|
|
1115
|
+
raise Exception(
|
|
1116
|
+
"called get_event in debug mode without a previous execution"
|
|
1117
|
+
)
|
|
1069
1118
|
if recorded_output is not None:
|
|
1070
1119
|
dbos_logger.debug(
|
|
1071
1120
|
f"Replaying get_event, id: {caller_ctx['function_id']}, key: {key}"
|
|
@@ -1128,6 +1177,9 @@ class SystemDatabase:
|
|
|
1128
1177
|
return value
|
|
1129
1178
|
|
|
1130
1179
|
def _flush_workflow_status_buffer(self) -> None:
|
|
1180
|
+
if self._debug_mode:
|
|
1181
|
+
raise Exception("called _flush_workflow_status_buffer in debug mode")
|
|
1182
|
+
|
|
1131
1183
|
"""Export the workflow status buffer to the database, up to the batch size."""
|
|
1132
1184
|
if len(self._workflow_status_buffer) == 0:
|
|
1133
1185
|
return
|
|
@@ -1158,6 +1210,9 @@ class SystemDatabase:
|
|
|
1158
1210
|
break
|
|
1159
1211
|
|
|
1160
1212
|
def _flush_workflow_inputs_buffer(self) -> None:
|
|
1213
|
+
if self._debug_mode:
|
|
1214
|
+
raise Exception("called _flush_workflow_inputs_buffer in debug mode")
|
|
1215
|
+
|
|
1161
1216
|
"""Export the workflow inputs buffer to the database, up to the batch size."""
|
|
1162
1217
|
if len(self._workflow_inputs_buffer) == 0:
|
|
1163
1218
|
return
|
|
@@ -1222,6 +1277,8 @@ class SystemDatabase:
|
|
|
1222
1277
|
)
|
|
1223
1278
|
|
|
1224
1279
|
def enqueue(self, workflow_id: str, queue_name: str) -> None:
|
|
1280
|
+
if self._debug_mode:
|
|
1281
|
+
raise Exception("called enqueue in debug mode")
|
|
1225
1282
|
with self.engine.begin() as c:
|
|
1226
1283
|
c.execute(
|
|
1227
1284
|
pg.insert(SystemSchema.workflow_queue)
|
|
@@ -1233,6 +1290,9 @@ class SystemDatabase:
|
|
|
1233
1290
|
)
|
|
1234
1291
|
|
|
1235
1292
|
def start_queued_workflows(self, queue: "Queue", executor_id: str) -> List[str]:
|
|
1293
|
+
if self._debug_mode:
|
|
1294
|
+
return []
|
|
1295
|
+
|
|
1236
1296
|
start_time_ms = int(time.time() * 1000)
|
|
1237
1297
|
if queue.limiter is not None:
|
|
1238
1298
|
limiter_period_ms = int(queue.limiter["period"] * 1000)
|
|
@@ -1383,6 +1443,9 @@ class SystemDatabase:
|
|
|
1383
1443
|
return ret_ids
|
|
1384
1444
|
|
|
1385
1445
|
def remove_from_queue(self, workflow_id: str, queue: "Queue") -> None:
|
|
1446
|
+
if self._debug_mode:
|
|
1447
|
+
raise Exception("called remove_from_queue in debug mode")
|
|
1448
|
+
|
|
1386
1449
|
with self.engine.begin() as c:
|
|
1387
1450
|
if queue.limiter is None:
|
|
1388
1451
|
c.execute(
|
|
@@ -1397,19 +1460,35 @@ class SystemDatabase:
|
|
|
1397
1460
|
.values(completed_at_epoch_ms=int(time.time() * 1000))
|
|
1398
1461
|
)
|
|
1399
1462
|
|
|
1400
|
-
def clear_queue_assignment(self, workflow_id: str) -> None:
|
|
1401
|
-
with self.engine.begin() as c:
|
|
1402
|
-
c.execute(
|
|
1403
|
-
sa.update(SystemSchema.workflow_queue)
|
|
1404
|
-
.where(SystemSchema.workflow_queue.c.workflow_uuid == workflow_id)
|
|
1405
|
-
.values(executor_id=None, started_at_epoch_ms=None)
|
|
1406
|
-
)
|
|
1407
|
-
c.execute(
|
|
1408
|
-
sa.update(SystemSchema.workflow_status)
|
|
1409
|
-
.where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
|
|
1410
|
-
.values(executor_id=None, status=WorkflowStatusString.ENQUEUED.value)
|
|
1411
|
-
)
|
|
1412
1463
|
|
|
1464
|
+
def clear_queue_assignment(self, workflow_id: str) -> bool:
|
|
1465
|
+
if self._debug_mode:
|
|
1466
|
+
raise Exception("called clear_queue_assignment in debug mode")
|
|
1467
|
+
|
|
1468
|
+
with self.engine.connect() as conn:
|
|
1469
|
+
with conn.begin() as transaction:
|
|
1470
|
+
res = conn.execute(
|
|
1471
|
+
sa.update(SystemSchema.workflow_queue)
|
|
1472
|
+
.where(SystemSchema.workflow_queue.c.workflow_uuid == workflow_id)
|
|
1473
|
+
.values(executor_id=None, started_at_epoch_ms=None)
|
|
1474
|
+
)
|
|
1475
|
+
|
|
1476
|
+
# If no rows were affected, the workflow is not anymore in the queue
|
|
1477
|
+
if res.rowcount == 0:
|
|
1478
|
+
transaction.rollback()
|
|
1479
|
+
return False
|
|
1480
|
+
|
|
1481
|
+
res = conn.execute(
|
|
1482
|
+
sa.update(SystemSchema.workflow_status)
|
|
1483
|
+
.where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
|
|
1484
|
+
.values(executor_id=None, status=WorkflowStatusString.ENQUEUED.value)
|
|
1485
|
+
)
|
|
1486
|
+
if res.rowcount == 0:
|
|
1487
|
+
# This should never happen
|
|
1488
|
+
raise Exception(
|
|
1489
|
+
f"UNREACHABLE: Workflow {workflow_id} is found in the workflow_queue table but not found in the workflow_status table"
|
|
1490
|
+
)
|
|
1491
|
+
return True
|
|
1413
1492
|
|
|
1414
1493
|
def reset_system_database(config: ConfigFile) -> None:
|
|
1415
1494
|
sysdb_name = (
|
dbos/cli/cli.py
CHANGED
|
@@ -15,6 +15,8 @@ from rich import print
|
|
|
15
15
|
from rich.prompt import IntPrompt
|
|
16
16
|
from typing_extensions import Annotated
|
|
17
17
|
|
|
18
|
+
from dbos._debug import debug_workflow, parse_start_command
|
|
19
|
+
|
|
18
20
|
from .. import load_config
|
|
19
21
|
from .._app_db import ApplicationDatabase
|
|
20
22
|
from .._dbos_config import _is_valid_app_name
|
|
@@ -232,6 +234,22 @@ def reset(
|
|
|
232
234
|
return
|
|
233
235
|
|
|
234
236
|
|
|
237
|
+
@app.command(help="Replay Debug a DBOS workflow")
|
|
238
|
+
def debug(
|
|
239
|
+
workflow_id: Annotated[str, typer.Argument(help="Workflow ID to debug")],
|
|
240
|
+
) -> None:
|
|
241
|
+
config = load_config(silent=True, use_db_wizard=False)
|
|
242
|
+
start = config["runtimeConfig"]["start"]
|
|
243
|
+
if not start:
|
|
244
|
+
typer.echo("No start commands found in 'dbos-config.yaml'")
|
|
245
|
+
raise typer.Exit(code=1)
|
|
246
|
+
if len(start) > 1:
|
|
247
|
+
typer.echo("Multiple start commands found in 'dbos-config.yaml'")
|
|
248
|
+
raise typer.Exit(code=1)
|
|
249
|
+
entrypoint = parse_start_command(start[0])
|
|
250
|
+
debug_workflow(workflow_id, entrypoint)
|
|
251
|
+
|
|
252
|
+
|
|
235
253
|
@workflow.command(help="List workflows for your application")
|
|
236
254
|
def list(
|
|
237
255
|
limit: Annotated[
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
dbos-0.23.
|
|
2
|
-
dbos-0.23.
|
|
3
|
-
dbos-0.23.
|
|
4
|
-
dbos-0.23.
|
|
1
|
+
dbos-0.23.0a10.dist-info/METADATA,sha256=ij2aQb4i-QIMqQA7NwPXx8R-pVqhJoR8d1f9jkTghy0,5524
|
|
2
|
+
dbos-0.23.0a10.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
|
|
3
|
+
dbos-0.23.0a10.dist-info/entry_points.txt,sha256=_QOQ3tVfEjtjBlr1jS4sHqHya9lI2aIEIWkz8dqYp14,58
|
|
4
|
+
dbos-0.23.0a10.dist-info/licenses/LICENSE,sha256=VGZit_a5-kdw9WT6fY5jxAWVwGQzgLFyPWrcVVUhVNU,1067
|
|
5
5
|
dbos/__init__.py,sha256=CxRHBHEthPL4PZoLbZhp3rdm44-KkRTT2-7DkK9d4QQ,724
|
|
6
|
+
dbos/__main__.py,sha256=P7jAr-7L9XE5mrsQ7i4b-bLr2ap1tCQfhMByLCRWDj0,568
|
|
6
7
|
dbos/_admin_server.py,sha256=YiVn5lywz2Vg8_juyNHOYl0HVEy48--7b4phwK7r92o,5732
|
|
7
|
-
dbos/_app_db.py,sha256=
|
|
8
|
+
dbos/_app_db.py,sha256=QFL1ceCugJFj_LBvK_G_0tt5jjyTM-4KnqmhbuC1ggg,5826
|
|
8
9
|
dbos/_classproperty.py,sha256=f0X-_BySzn3yFDRKB2JpCbLYQ9tLwt1XftfshvY7CBs,626
|
|
9
10
|
dbos/_cloudutils/authentication.py,sha256=V0fCWQN9stCkhbuuxgPTGpvuQcDqfU3KAxPAh01vKW4,5007
|
|
10
11
|
dbos/_cloudutils/cloudutils.py,sha256=YC7jGsIopT0KveLsqbRpQk2KlRBk-nIRC_UCgep4f3o,7797
|
|
11
12
|
dbos/_cloudutils/databases.py,sha256=_shqaqSvhY4n2ScgQ8IP5PDZvzvcx3YBKV8fj-cxhSY,8543
|
|
12
13
|
dbos/_context.py,sha256=Ue5qu3rzLfRmPkz-UUZi9ZS8iXpapRN0NTM4mbA2QmQ,17738
|
|
13
|
-
dbos/_core.py,sha256=
|
|
14
|
+
dbos/_core.py,sha256=UQb068FT59Op-F5RmtxreSeSQ1_wljOso0dQCUOPrC4,37528
|
|
14
15
|
dbos/_croniter.py,sha256=XHAyUyibs_59sJQfSNWkP7rqQY6_XrlfuuCxk4jYqek,47559
|
|
15
16
|
dbos/_db_wizard.py,sha256=6tfJaCRa1NtkUdNW75a2yvi_mEgnPJ9C1HP2zPG1hCU,8067
|
|
16
|
-
dbos/_dbos.py,sha256=
|
|
17
|
-
dbos/_dbos_config.py,sha256=
|
|
17
|
+
dbos/_dbos.py,sha256=jNh1R4b13a4g1VXeSPaC2d3J2JXiF6YNpCBek9hkcbM,39393
|
|
18
|
+
dbos/_dbos_config.py,sha256=_VETbEsMZ66563A8sX05B_coKz2BrILbIm9H5BmnPmk,9572
|
|
19
|
+
dbos/_debug.py,sha256=wcvjM2k4BrK7mlYjImUZXNBUB00fPGjQrNimZXlj76c,1491
|
|
18
20
|
dbos/_error.py,sha256=xqB7b7g5AF_OwOvqLKLXL1xldn2gAtORix2ZC2B8zK0,5089
|
|
19
21
|
dbos/_fastapi.py,sha256=ke03vqsSYDnO6XeOtOVFXj0-f-v1MGsOxa9McaROvNc,3616
|
|
20
22
|
dbos/_flask.py,sha256=DZKUZR5-xOzPI7tYZ53r2PvvHVoAb8SYwLzMVFsVfjI,2608
|
|
@@ -32,7 +34,7 @@ dbos/_migrations/versions/d76646551a6c_workflow_queue.py,sha256=G942nophZ2uC2vc4
|
|
|
32
34
|
dbos/_migrations/versions/eab0cc1d9a14_job_queue.py,sha256=uvhFOtqbBreCePhAxZfIT0qCAI7BiZTou9wt6QnbY7c,1412
|
|
33
35
|
dbos/_outcome.py,sha256=FDMgWVjZ06vm9xO-38H17mTqBImUYQxgKs_bDCSIAhE,6648
|
|
34
36
|
dbos/_queue.py,sha256=I2gBc7zQ4G0vyDDBnKwIFzxtqfD7DxHO2IZ41brFSOM,2927
|
|
35
|
-
dbos/_recovery.py,sha256=
|
|
37
|
+
dbos/_recovery.py,sha256=kaljwWky6Y73QnpMlY_cMSwiWf9wcyYigiUX7m3e4Jg,3137
|
|
36
38
|
dbos/_registrations.py,sha256=_zy6k944Ll8QwqU12Kr3OP23ukVtm8axPNN1TS_kJRc,6717
|
|
37
39
|
dbos/_request.py,sha256=cX1B3Atlh160phgS35gF1VEEV4pD126c9F3BDgBmxZU,929
|
|
38
40
|
dbos/_roles.py,sha256=iOsgmIAf1XVzxs3gYWdGRe1B880YfOw5fpU7Jwx8_A8,2271
|
|
@@ -41,7 +43,7 @@ dbos/_schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
41
43
|
dbos/_schemas/application_database.py,sha256=KeyoPrF7hy_ODXV7QNike_VFSD74QBRfQ76D7QyE9HI,966
|
|
42
44
|
dbos/_schemas/system_database.py,sha256=rwp4EvCSaXcUoMaRczZCvETCxGp72k3-hvLyGUDkih0,5163
|
|
43
45
|
dbos/_serialization.py,sha256=YCYv0qKAwAZ1djZisBC7khvKqG-5OcIv9t9EC5PFIog,1743
|
|
44
|
-
dbos/_sys_db.py,sha256=
|
|
46
|
+
dbos/_sys_db.py,sha256=lpgmCpuGfMbv18q7qjrDUw0vqjXn8aId4E3b64lSEp0,64443
|
|
45
47
|
dbos/_templates/dbos-db-starter/README.md,sha256=GhxhBj42wjTt1fWEtwNriHbJuKb66Vzu89G4pxNHw2g,930
|
|
46
48
|
dbos/_templates/dbos-db-starter/__package/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
49
|
dbos/_templates/dbos-db-starter/__package/main.py,sha256=eI0SS9Nwj-fldtiuSzIlIG6dC91GXXwdRsoHxv6S_WI,2719
|
|
@@ -57,8 +59,8 @@ dbos/_utils.py,sha256=wjOJzxN66IzL9p4dwcEmQACRQah_V09G6mJI2exQfOM,155
|
|
|
57
59
|
dbos/_workflow_commands.py,sha256=Z1PwprvR_A8PXV2FNhcMrvV8B4NlDI9dc5naMeeNKGw,4774
|
|
58
60
|
dbos/cli/_github_init.py,sha256=Y_bDF9gfO2jB1id4FV5h1oIxEJRWyqVjhb7bNEa5nQ0,3224
|
|
59
61
|
dbos/cli/_template_init.py,sha256=AfuMaO8bmr9WsPNHr6j2cp7kjVVZDUpH7KpbTg0hhFs,2722
|
|
60
|
-
dbos/cli/cli.py,sha256=
|
|
62
|
+
dbos/cli/cli.py,sha256=ohrXoRshwxOwN-gFag_RW2yoNjwcCVv6nTYQdaArwek,15506
|
|
61
63
|
dbos/dbos-config.schema.json,sha256=X5TpXNcARGceX0zQs0fVgtZW_Xj9uBbY5afPt9Rz9yk,5741
|
|
62
64
|
dbos/py.typed,sha256=QfzXT1Ktfk3Rj84akygc7_42z0lRpCq0Ilh8OXI6Zas,44
|
|
63
65
|
version/__init__.py,sha256=L4sNxecRuqdtSFdpUGX3TtBi9KL3k7YsZVIvv-fv9-A,1678
|
|
64
|
-
dbos-0.23.
|
|
66
|
+
dbos-0.23.0a10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|