dbos 1.8.0a5__tar.gz → 1.8.0a8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dbos-1.8.0a5 → dbos-1.8.0a8}/PKG-INFO +1 -1
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_dbos.py +0 -28
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_error.py +5 -5
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_sys_db.py +12 -7
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/cli/cli.py +2 -2
- {dbos-1.8.0a5 → dbos-1.8.0a8}/pyproject.toml +1 -1
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_admin_server.py +1 -1
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_async.py +6 -2
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_classdecorators.py +27 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_failures.py +7 -4
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_queue.py +1 -1
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_scheduler.py +3 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_singleton.py +0 -24
- {dbos-1.8.0a5 → dbos-1.8.0a8}/LICENSE +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/README.md +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/__init__.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/__main__.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_admin_server.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_app_db.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_classproperty.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_client.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_context.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_core.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_croniter.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_dbos_config.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_debug.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_event_loop.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_fastapi.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_flask.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_kafka.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_kafka_message.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_logger.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/env.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/script.py.mako +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_outcome.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_queue.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_recovery.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_registrations.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_roles.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_scheduler.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_serialization.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_tracer.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_utils.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_workflow_commands.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/cli/_github_init.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/cli/_template_init.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/py.typed +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/__init__.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/atexit_no_launch.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/classdefs.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/client_collateral.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/client_worker.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/conftest.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/more_classdefs.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/queuedworkflow.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_async_workflow_management.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_cli.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_client.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_concurrency.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_config.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_croniter.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_dbos.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_debug.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_docker_secrets.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_fastapi.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_flask.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_kafka.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_outcome.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_package.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_schema_migration.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_spans.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_workflow_introspection.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/tests/test_workflow_management.py +0 -0
- {dbos-1.8.0a5 → dbos-1.8.0a8}/version/__init__.py +0 -0
|
@@ -1376,31 +1376,3 @@ class DBOSConfiguredInstance:
|
|
|
1376
1376
|
def __init__(self, config_name: str) -> None:
|
|
1377
1377
|
self.config_name = config_name
|
|
1378
1378
|
DBOS.register_instance(self)
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
# Apps that import DBOS probably don't exit. If they do, let's see if
|
|
1382
|
-
# it looks like startup was abandoned or a call was forgotten...
|
|
1383
|
-
def _dbos_exit_hook() -> None:
|
|
1384
|
-
if _dbos_global_registry is None:
|
|
1385
|
-
# Probably used as or for a support module
|
|
1386
|
-
return
|
|
1387
|
-
if _dbos_global_instance is None:
|
|
1388
|
-
print("DBOS exiting; functions were registered but DBOS() was not called")
|
|
1389
|
-
dbos_logger.warning(
|
|
1390
|
-
"DBOS exiting; functions were registered but DBOS() was not called"
|
|
1391
|
-
)
|
|
1392
|
-
return
|
|
1393
|
-
if not _dbos_global_instance._launched:
|
|
1394
|
-
if _dbos_global_instance.fastapi is not None:
|
|
1395
|
-
# FastAPI lifespan middleware will call launch/destroy, so we can ignore this.
|
|
1396
|
-
# This is likely to happen during fastapi dev runs, where the reloader loads the module multiple times.
|
|
1397
|
-
return
|
|
1398
|
-
print("DBOS exiting; DBOS exists but launch() was not called")
|
|
1399
|
-
dbos_logger.warning("DBOS exiting; DBOS exists but launch() was not called")
|
|
1400
|
-
return
|
|
1401
|
-
# If we get here, we're exiting normally
|
|
1402
|
-
_dbos_global_instance.destroy()
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
# Register the exit hook
|
|
1406
|
-
atexit.register(_dbos_exit_hook)
|
|
@@ -55,7 +55,7 @@ class DBOSErrorCode(Enum):
|
|
|
55
55
|
InitializationError = 3
|
|
56
56
|
WorkflowFunctionNotFound = 4
|
|
57
57
|
NonExistentWorkflowError = 5
|
|
58
|
-
|
|
58
|
+
MaxRecoveryAttemptsExceeded = 6
|
|
59
59
|
MaxStepRetriesExceeded = 7
|
|
60
60
|
NotAuthorized = 8
|
|
61
61
|
ConflictingWorkflowError = 9
|
|
@@ -121,13 +121,13 @@ class DBOSNonExistentWorkflowError(DBOSException):
|
|
|
121
121
|
)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
class
|
|
125
|
-
"""Exception raised when a workflow
|
|
124
|
+
class MaxRecoveryAttemptsExceededError(DBOSException):
|
|
125
|
+
"""Exception raised when a workflow exceeds its max recovery attempts."""
|
|
126
126
|
|
|
127
127
|
def __init__(self, wf_id: str, max_retries: int):
|
|
128
128
|
super().__init__(
|
|
129
|
-
f"Workflow {wf_id} has
|
|
130
|
-
dbos_error_code=DBOSErrorCode.
|
|
129
|
+
f"Workflow {wf_id} has exceeded its maximum of {max_retries} execution or recovery attempts. Further attempts to execute or recover it will fail. See documentation for details: https://docs.dbos.dev/python/reference/decorators",
|
|
130
|
+
dbos_error_code=DBOSErrorCode.MaxRecoveryAttemptsExceeded.value,
|
|
131
131
|
)
|
|
132
132
|
|
|
133
133
|
|
|
@@ -37,12 +37,12 @@ from ._context import get_local_dbos_context
|
|
|
37
37
|
from ._error import (
|
|
38
38
|
DBOSAwaitedWorkflowCancelledError,
|
|
39
39
|
DBOSConflictingWorkflowError,
|
|
40
|
-
DBOSDeadLetterQueueError,
|
|
41
40
|
DBOSNonExistentWorkflowError,
|
|
42
41
|
DBOSQueueDeduplicatedError,
|
|
43
42
|
DBOSUnexpectedStepError,
|
|
44
43
|
DBOSWorkflowCancelledError,
|
|
45
44
|
DBOSWorkflowConflictIDError,
|
|
45
|
+
MaxRecoveryAttemptsExceededError,
|
|
46
46
|
)
|
|
47
47
|
from ._logger import dbos_logger
|
|
48
48
|
from ._schemas.system_database import SystemSchema
|
|
@@ -57,20 +57,25 @@ class WorkflowStatusString(Enum):
|
|
|
57
57
|
PENDING = "PENDING"
|
|
58
58
|
SUCCESS = "SUCCESS"
|
|
59
59
|
ERROR = "ERROR"
|
|
60
|
-
|
|
60
|
+
MAX_RECOVERY_ATTEMPTS_EXCEEDED = "MAX_RECOVERY_ATTEMPTS_EXCEEDED"
|
|
61
61
|
CANCELLED = "CANCELLED"
|
|
62
62
|
ENQUEUED = "ENQUEUED"
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
WorkflowStatuses = Literal[
|
|
66
|
-
"PENDING",
|
|
66
|
+
"PENDING",
|
|
67
|
+
"SUCCESS",
|
|
68
|
+
"ERROR",
|
|
69
|
+
"MAX_RECOVERY_ATTEMPTS_EXCEEDED",
|
|
70
|
+
"CANCELLED",
|
|
71
|
+
"ENQUEUED",
|
|
67
72
|
]
|
|
68
73
|
|
|
69
74
|
|
|
70
75
|
class WorkflowStatus:
|
|
71
76
|
# The workflow ID
|
|
72
77
|
workflow_id: str
|
|
73
|
-
# The workflow status. Must be one of ENQUEUED, PENDING, SUCCESS, ERROR, CANCELLED, or
|
|
78
|
+
# The workflow status. Must be one of ENQUEUED, PENDING, SUCCESS, ERROR, CANCELLED, or MAX_RECOVERY_ATTEMPTS_EXCEEDED
|
|
74
79
|
status: str
|
|
75
80
|
# The name of the workflow function
|
|
76
81
|
name: str
|
|
@@ -515,7 +520,7 @@ class SystemDatabase:
|
|
|
515
520
|
raise DBOSConflictingWorkflowError(status["workflow_uuid"], err_msg)
|
|
516
521
|
|
|
517
522
|
# Every time we start executing a workflow (and thus attempt to insert its status), we increment `recovery_attempts` by 1.
|
|
518
|
-
# When this number becomes equal to `maxRetries + 1`, we mark the workflow as `
|
|
523
|
+
# When this number becomes equal to `maxRetries + 1`, we mark the workflow as `MAX_RECOVERY_ATTEMPTS_EXCEEDED`.
|
|
519
524
|
if (
|
|
520
525
|
(wf_status != "SUCCESS" and wf_status != "ERROR")
|
|
521
526
|
and max_recovery_attempts is not None
|
|
@@ -532,7 +537,7 @@ class SystemDatabase:
|
|
|
532
537
|
== WorkflowStatusString.PENDING.value
|
|
533
538
|
)
|
|
534
539
|
.values(
|
|
535
|
-
status=WorkflowStatusString.
|
|
540
|
+
status=WorkflowStatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED.value,
|
|
536
541
|
deduplication_id=None,
|
|
537
542
|
started_at_epoch_ms=None,
|
|
538
543
|
queue_name=None,
|
|
@@ -541,7 +546,7 @@ class SystemDatabase:
|
|
|
541
546
|
conn.execute(dlq_cmd)
|
|
542
547
|
# Need to commit here because we're throwing an exception
|
|
543
548
|
conn.commit()
|
|
544
|
-
raise
|
|
549
|
+
raise MaxRecoveryAttemptsExceededError(
|
|
545
550
|
status["workflow_uuid"], max_recovery_attempts
|
|
546
551
|
)
|
|
547
552
|
|
|
@@ -450,7 +450,7 @@ def list(
|
|
|
450
450
|
typer.Option(
|
|
451
451
|
"--status",
|
|
452
452
|
"-S",
|
|
453
|
-
help="Retrieve workflows with this status (PENDING, SUCCESS, ERROR,
|
|
453
|
+
help="Retrieve workflows with this status (PENDING, SUCCESS, ERROR, ENQUEUED, CANCELLED, or MAX_RECOVERY_ATTEMPTS_EXCEEDED)",
|
|
454
454
|
),
|
|
455
455
|
] = None,
|
|
456
456
|
appversion: Annotated[
|
|
@@ -657,7 +657,7 @@ def list_queue(
|
|
|
657
657
|
typer.Option(
|
|
658
658
|
"--status",
|
|
659
659
|
"-S",
|
|
660
|
-
help="Retrieve functions with this status (PENDING, SUCCESS, ERROR,
|
|
660
|
+
help="Retrieve functions with this status (PENDING, SUCCESS, ERROR, ENQUEUED, CANCELLED, or MAX_RECOVERY_ATTEMPTS_EXCEEDED)",
|
|
661
661
|
),
|
|
662
662
|
] = None,
|
|
663
663
|
queue_name: Annotated[
|
|
@@ -104,7 +104,7 @@ def test_deactivate(dbos: DBOS, config: DBOSConfig) -> None:
|
|
|
104
104
|
assert event.is_set()
|
|
105
105
|
# Verify the scheduled workflow does not run anymore
|
|
106
106
|
time.sleep(5)
|
|
107
|
-
assert wf_counter <= val +
|
|
107
|
+
assert wf_counter <= val + 2
|
|
108
108
|
# Enqueue a workflow, verify it still runs
|
|
109
109
|
assert queue.enqueue(regular_workflow).get_result() == 5
|
|
110
110
|
|
|
@@ -32,12 +32,12 @@ async def test_async_workflow(dbos: DBOS) -> None:
|
|
|
32
32
|
nonlocal wf_counter
|
|
33
33
|
wf_counter += 1
|
|
34
34
|
res1 = test_transaction(var1)
|
|
35
|
-
res2 = test_step(var2)
|
|
35
|
+
res2 = await test_step(var2)
|
|
36
36
|
DBOS.logger.info("I'm test_workflow")
|
|
37
37
|
return res1 + res2
|
|
38
38
|
|
|
39
39
|
@DBOS.step()
|
|
40
|
-
def test_step(var: str) -> str:
|
|
40
|
+
async def test_step(var: str) -> str:
|
|
41
41
|
nonlocal step_counter
|
|
42
42
|
step_counter += 1
|
|
43
43
|
DBOS.logger.info("I'm test_step")
|
|
@@ -73,6 +73,10 @@ async def test_async_workflow(dbos: DBOS) -> None:
|
|
|
73
73
|
sync_handle = DBOS.start_workflow(test_workflow, "alice", "bob")
|
|
74
74
|
assert sync_handle.get_result() == "alicetxn31bobstep3" # type: ignore
|
|
75
75
|
|
|
76
|
+
# Test DBOS.start_workflow_async on steps
|
|
77
|
+
handle = await DBOS.start_workflow_async(test_step, "alice")
|
|
78
|
+
assert (await handle.get_result()) == "alicestep4"
|
|
79
|
+
|
|
76
80
|
|
|
77
81
|
@pytest.mark.asyncio
|
|
78
82
|
async def test_async_step(dbos: DBOS) -> None:
|
|
@@ -10,6 +10,7 @@ from dbos import DBOS, DBOSConfiguredInstance, Queue, SetWorkflowID
|
|
|
10
10
|
|
|
11
11
|
# Private API used because this is a test
|
|
12
12
|
from dbos._context import DBOSContextEnsure, assert_current_dbos_context
|
|
13
|
+
from dbos._dbos_config import DBOSConfig
|
|
13
14
|
from tests.conftest import queue_entries_are_cleaned_up
|
|
14
15
|
|
|
15
16
|
|
|
@@ -884,3 +885,29 @@ def test_mixed_methods(dbos: DBOS) -> None:
|
|
|
884
885
|
status = handle.get_status()
|
|
885
886
|
assert status.class_name == None
|
|
886
887
|
assert status.config_name == None
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def test_class_step_without_dbos(dbos: DBOS, config: DBOSConfig) -> None:
|
|
891
|
+
DBOS.destroy(destroy_registry=True)
|
|
892
|
+
|
|
893
|
+
@DBOS.dbos_class()
|
|
894
|
+
class TestClass(DBOSConfiguredInstance):
|
|
895
|
+
def __init__(self, x: int) -> None:
|
|
896
|
+
self.x = x
|
|
897
|
+
super().__init__("test")
|
|
898
|
+
|
|
899
|
+
@DBOS.step()
|
|
900
|
+
def step(self, x: int) -> int:
|
|
901
|
+
return self.x + x
|
|
902
|
+
|
|
903
|
+
input = 5
|
|
904
|
+
inst = TestClass(input)
|
|
905
|
+
assert inst.step(input) == input + input
|
|
906
|
+
|
|
907
|
+
DBOS(config=config)
|
|
908
|
+
|
|
909
|
+
assert inst.step(input) == input + input
|
|
910
|
+
|
|
911
|
+
DBOS.launch()
|
|
912
|
+
|
|
913
|
+
assert inst.step(input) == input + input
|
|
@@ -10,11 +10,11 @@ from sqlalchemy.exc import InvalidRequestError, OperationalError
|
|
|
10
10
|
from dbos import DBOS, Queue, SetWorkflowID
|
|
11
11
|
from dbos._error import (
|
|
12
12
|
DBOSAwaitedWorkflowCancelledError,
|
|
13
|
-
DBOSDeadLetterQueueError,
|
|
14
13
|
DBOSMaxStepRetriesExceeded,
|
|
15
14
|
DBOSNotAuthorizedError,
|
|
16
15
|
DBOSQueueDeduplicatedError,
|
|
17
16
|
DBOSUnexpectedStepError,
|
|
17
|
+
MaxRecoveryAttemptsExceededError,
|
|
18
18
|
)
|
|
19
19
|
from dbos._registrations import DEFAULT_MAX_RECOVERY_ATTEMPTS
|
|
20
20
|
from dbos._serialization import (
|
|
@@ -179,12 +179,15 @@ def test_dead_letter_queue(dbos: DBOS) -> None:
|
|
|
179
179
|
# and puts the workflow in the DLQ status.
|
|
180
180
|
with pytest.raises(Exception) as exc_info:
|
|
181
181
|
DBOS._recover_pending_workflows()
|
|
182
|
-
assert exc_info.errisinstance(
|
|
183
|
-
assert
|
|
182
|
+
assert exc_info.errisinstance(MaxRecoveryAttemptsExceededError)
|
|
183
|
+
assert (
|
|
184
|
+
handle.get_status().status
|
|
185
|
+
== WorkflowStatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED.value
|
|
186
|
+
)
|
|
184
187
|
with pytest.raises(Exception) as exc_info:
|
|
185
188
|
with SetWorkflowID(wfid):
|
|
186
189
|
dead_letter_workflow()
|
|
187
|
-
assert exc_info.errisinstance(
|
|
190
|
+
assert exc_info.errisinstance(MaxRecoveryAttemptsExceededError)
|
|
188
191
|
|
|
189
192
|
# Resume the workflow. Verify it can recover again without error.
|
|
190
193
|
resumed_handle = dbos.resume_workflow(wfid)
|
|
@@ -1041,7 +1041,7 @@ def test_dlq_enqueued_workflows(dbos: DBOS) -> None:
|
|
|
1041
1041
|
time.sleep(2)
|
|
1042
1042
|
assert (
|
|
1043
1043
|
blocked_handle.get_status().status
|
|
1044
|
-
== WorkflowStatusString.
|
|
1044
|
+
== WorkflowStatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED.value
|
|
1045
1045
|
)
|
|
1046
1046
|
with dbos._sys_db.engine.begin() as c:
|
|
1047
1047
|
query = sa.select(SystemSchema.workflow_status.c.recovery_attempts).where(
|
|
@@ -219,6 +219,9 @@ def test_scheduler_oaoo(dbos: DBOS) -> None:
|
|
|
219
219
|
for evt in dbos.poller_stop_events:
|
|
220
220
|
evt.set()
|
|
221
221
|
|
|
222
|
+
# Wait for workflows to finish
|
|
223
|
+
time.sleep(2)
|
|
224
|
+
|
|
222
225
|
dbos._sys_db.update_workflow_outcome(workflow_id, "PENDING")
|
|
223
226
|
|
|
224
227
|
workflow_handles = DBOS._recover_pending_workflows()
|
|
@@ -129,27 +129,3 @@ def test_dbos_singleton_negative(cleanup_test_databases: None) -> None:
|
|
|
129
129
|
assert "launch" in str(exc_info.value)
|
|
130
130
|
|
|
131
131
|
DBOS.destroy()
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def test_dbos_atexit_no_dbos(cleanup_test_databases: None) -> None:
|
|
135
|
-
# Run the .py as a separate process
|
|
136
|
-
result = subprocess.run(
|
|
137
|
-
[sys.executable, path.join("tests", "atexit_no_ctor.py")],
|
|
138
|
-
capture_output=True,
|
|
139
|
-
text=True,
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
# Assert that the output contains the warning message
|
|
143
|
-
assert "DBOS exiting; functions were registered" in result.stdout
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def test_dbos_atexit_no_launch(cleanup_test_databases: None) -> None:
|
|
147
|
-
# Run the .py as a separate process
|
|
148
|
-
result = subprocess.run(
|
|
149
|
-
[sys.executable, path.join("tests", "atexit_no_launch.py")],
|
|
150
|
-
capture_output=True,
|
|
151
|
-
text=True,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
# Assert that the output contains the warning message
|
|
155
|
-
assert "DBOS exists but launch() was not called" in result.stdout
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dbos-1.8.0a5 → dbos-1.8.0a8}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|