dbos 1.12.0a1__tar.gz → 1.12.0a3__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.
- {dbos-1.12.0a1 → dbos-1.12.0a3}/PKG-INFO +1 -1
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_core.py +5 -17
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_fastapi.py +2 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/cli/cli.py +35 -6
- {dbos-1.12.0a1 → dbos-1.12.0a3}/pyproject.toml +1 -1
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_async.py +0 -24
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_classdecorators.py +1 -1
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_concurrency.py +0 -13
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_dbos.py +30 -22
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_failures.py +6 -9
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_package.py +100 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_queue.py +2 -1
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_spans.py +17 -33
- {dbos-1.12.0a1 → dbos-1.12.0a3}/LICENSE +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/README.md +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/__init__.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/__main__.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_admin_server.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_app_db.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_classproperty.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_client.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_context.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_croniter.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_dbos.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_dbos_config.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_debug.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_error.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_event_loop.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_flask.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_kafka.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_kafka_message.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_logger.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/env.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/script.py.mako +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/01ce9f07bd10_streaming.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_outcome.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_queue.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_recovery.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_registrations.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_roles.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_scheduler.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_serialization.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_sys_db.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_tracer.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_utils.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_workflow_commands.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/cli/_github_init.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/cli/_template_init.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/cli/migration.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/py.typed +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/__init__.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/atexit_no_launch.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/classdefs.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/client_collateral.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/client_worker.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/conftest.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/more_classdefs.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/queuedworkflow.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_admin_server.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_async_workflow_management.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_cli.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_client.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_config.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_croniter.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_debug.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_docker_secrets.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_fastapi.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_flask.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_kafka.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_migrate.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_outcome.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_scheduler.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_schema_migration.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_singleton.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_streaming.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_workflow_introspection.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/tests/test_workflow_management.py +0 -0
- {dbos-1.12.0a1 → dbos-1.12.0a3}/version/__init__.py +0 -0
|
@@ -1156,27 +1156,15 @@ def decorate_step(
|
|
|
1156
1156
|
|
|
1157
1157
|
@wraps(func)
|
|
1158
1158
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
1159
|
-
|
|
1160
|
-
#
|
|
1161
|
-
# No DBOS, just call the original function directly
|
|
1162
|
-
# In a step already, just call the original function directly.
|
|
1163
|
-
# In a workflow (that is not in a step already)
|
|
1164
|
-
# Not in a workflow (we will start the single op workflow)
|
|
1165
|
-
if not dbosreg.dbos or not dbosreg.dbos._launched:
|
|
1166
|
-
# Call the original function directly
|
|
1167
|
-
return func(*args, **kwargs)
|
|
1159
|
+
# If the step is called from a workflow, run it as a step.
|
|
1160
|
+
# Otherwise, run it as a normal function.
|
|
1168
1161
|
ctx = get_local_dbos_context()
|
|
1169
|
-
if ctx and ctx.
|
|
1170
|
-
|
|
1171
|
-
return func(*args, **kwargs)
|
|
1172
|
-
if ctx and ctx.is_within_workflow():
|
|
1173
|
-
assert ctx.is_workflow(), "Steps must be called from within workflows"
|
|
1162
|
+
if ctx and ctx.is_workflow():
|
|
1163
|
+
rr: Optional[str] = check_required_roles(func, fi)
|
|
1174
1164
|
with DBOSAssumeRole(rr):
|
|
1175
1165
|
return invoke_step(*args, **kwargs)
|
|
1176
1166
|
else:
|
|
1177
|
-
|
|
1178
|
-
assert tempwf
|
|
1179
|
-
return tempwf(*args, **kwargs)
|
|
1167
|
+
return func(*args, **kwargs)
|
|
1180
1168
|
|
|
1181
1169
|
wrapper = (
|
|
1182
1170
|
_mark_coroutine(wrapper) if inspect.iscoroutinefunction(func) else wrapper # type: ignore
|
|
@@ -83,4 +83,6 @@ def setup_fastapi_middleware(app: FastAPI, dbos: DBOS) -> None:
|
|
|
83
83
|
response = await call_next(request)
|
|
84
84
|
else:
|
|
85
85
|
response = await call_next(request)
|
|
86
|
+
if hasattr(response, "status_code"):
|
|
87
|
+
DBOS.span.set_attribute("responseCode", response.status_code)
|
|
86
88
|
return response
|
|
@@ -14,6 +14,7 @@ from rich import print as richprint
|
|
|
14
14
|
from rich.prompt import IntPrompt
|
|
15
15
|
from typing_extensions import Annotated, List
|
|
16
16
|
|
|
17
|
+
from dbos._context import SetWorkflowID
|
|
17
18
|
from dbos._debug import debug_workflow, parse_start_command
|
|
18
19
|
from dbos.cli.migration import grant_dbos_schema_permissions, migrate_dbos_databases
|
|
19
20
|
|
|
@@ -567,7 +568,9 @@ def resume(
|
|
|
567
568
|
start_client(db_url=db_url).resume_workflow(workflow_id=workflow_id)
|
|
568
569
|
|
|
569
570
|
|
|
570
|
-
@workflow.command(
|
|
571
|
+
@workflow.command(
|
|
572
|
+
help="[DEPRECATED - Use fork instead] Restart a workflow from the beginning with a new id"
|
|
573
|
+
)
|
|
571
574
|
def restart(
|
|
572
575
|
workflow_id: Annotated[str, typer.Argument()],
|
|
573
576
|
db_url: Annotated[
|
|
@@ -600,6 +603,22 @@ def fork(
|
|
|
600
603
|
help="Restart from this step",
|
|
601
604
|
),
|
|
602
605
|
] = 1,
|
|
606
|
+
forked_workflow_id: Annotated[
|
|
607
|
+
typing.Optional[str],
|
|
608
|
+
typer.Option(
|
|
609
|
+
"--forked-workflow-id",
|
|
610
|
+
"-f",
|
|
611
|
+
help="Custom ID for the forked workflow",
|
|
612
|
+
),
|
|
613
|
+
] = None,
|
|
614
|
+
application_version: Annotated[
|
|
615
|
+
typing.Optional[str],
|
|
616
|
+
typer.Option(
|
|
617
|
+
"--application-version",
|
|
618
|
+
"-v",
|
|
619
|
+
help="Custom application version for the forked workflow",
|
|
620
|
+
),
|
|
621
|
+
] = None,
|
|
603
622
|
db_url: Annotated[
|
|
604
623
|
typing.Optional[str],
|
|
605
624
|
typer.Option(
|
|
@@ -609,11 +628,21 @@ def fork(
|
|
|
609
628
|
),
|
|
610
629
|
] = None,
|
|
611
630
|
) -> None:
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
631
|
+
client = start_client(db_url=db_url)
|
|
632
|
+
|
|
633
|
+
if forked_workflow_id is not None:
|
|
634
|
+
with SetWorkflowID(forked_workflow_id):
|
|
635
|
+
status = client.fork_workflow(
|
|
636
|
+
workflow_id=workflow_id,
|
|
637
|
+
start_step=step,
|
|
638
|
+
application_version=application_version,
|
|
639
|
+
).get_status()
|
|
640
|
+
else:
|
|
641
|
+
status = client.fork_workflow(
|
|
642
|
+
workflow_id=workflow_id,
|
|
643
|
+
start_step=step,
|
|
644
|
+
application_version=application_version,
|
|
645
|
+
).get_status()
|
|
617
646
|
print(jsonpickle.encode(status, unpicklable=False))
|
|
618
647
|
|
|
619
648
|
|
|
@@ -329,30 +329,6 @@ def test_async_tx_raises(config: ConfigFile) -> None:
|
|
|
329
329
|
DBOS.destroy(destroy_registry=True)
|
|
330
330
|
|
|
331
331
|
|
|
332
|
-
@pytest.mark.asyncio
|
|
333
|
-
async def test_async_step_temp(dbos: DBOS) -> None:
|
|
334
|
-
step_counter: int = 0
|
|
335
|
-
|
|
336
|
-
@DBOS.step()
|
|
337
|
-
async def test_step(var: str) -> str:
|
|
338
|
-
await asyncio.sleep(0.1)
|
|
339
|
-
nonlocal step_counter
|
|
340
|
-
step_counter += 1
|
|
341
|
-
DBOS.logger.info("I'm test_step")
|
|
342
|
-
return var + f"step{step_counter}"
|
|
343
|
-
|
|
344
|
-
wfuuid = f"test_async_step_temp-{time.time_ns()}"
|
|
345
|
-
with SetWorkflowID(wfuuid):
|
|
346
|
-
result = await test_step("alice")
|
|
347
|
-
assert result == "alicestep1"
|
|
348
|
-
|
|
349
|
-
with SetWorkflowID(wfuuid):
|
|
350
|
-
result = await test_step("alice")
|
|
351
|
-
assert result == "alicestep1"
|
|
352
|
-
|
|
353
|
-
assert step_counter == 1
|
|
354
|
-
|
|
355
|
-
|
|
356
332
|
@pytest.mark.asyncio
|
|
357
333
|
async def test_start_workflow_async(dbos: DBOS) -> None:
|
|
358
334
|
wf_counter: int = 0
|
|
@@ -574,7 +574,7 @@ def test_step_recovery(dbos: DBOS) -> None:
|
|
|
574
574
|
def call_step() -> None:
|
|
575
575
|
with SetWorkflowID(wfid):
|
|
576
576
|
nonlocal return_value
|
|
577
|
-
return_value = inst.step
|
|
577
|
+
return_value = DBOS.start_workflow(inst.step, input).get_result()
|
|
578
578
|
|
|
579
579
|
thread = threading.Thread(target=call_step)
|
|
580
580
|
thread.start()
|
|
@@ -59,10 +59,6 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
|
|
|
59
59
|
res = test_step()
|
|
60
60
|
return res
|
|
61
61
|
|
|
62
|
-
def test_comm_thread(id: str) -> str:
|
|
63
|
-
with SetWorkflowID(id):
|
|
64
|
-
return test_step()
|
|
65
|
-
|
|
66
62
|
# Need to set isolation level to a lower one, otherwise it gets serialization error instead (we already handle it correctly by automatic retries).
|
|
67
63
|
@DBOS.transaction(isolation_level="REPEATABLE READ")
|
|
68
64
|
def test_transaction() -> str:
|
|
@@ -97,15 +93,6 @@ def test_concurrent_conflict_uuid(dbos: DBOS) -> None:
|
|
|
97
93
|
assert wf_handle1.get_result() == wfuuid
|
|
98
94
|
assert wf_handle2.get_result() == wfuuid
|
|
99
95
|
|
|
100
|
-
# Make sure temp workflows can handle conflicts as well.
|
|
101
|
-
wfuuid = str(uuid.uuid4())
|
|
102
|
-
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
103
|
-
future1 = executor.submit(test_comm_thread, wfuuid)
|
|
104
|
-
future2 = executor.submit(test_comm_thread, wfuuid)
|
|
105
|
-
|
|
106
|
-
assert future1.result() == wfuuid
|
|
107
|
-
assert future2.result() == wfuuid
|
|
108
|
-
|
|
109
96
|
# Make sure temp transactions can handle conflicts as well.
|
|
110
97
|
wfuuid = str(uuid.uuid4())
|
|
111
98
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
@@ -307,16 +307,12 @@ def test_temp_workflow(dbos: DBOS) -> None:
|
|
|
307
307
|
assert res == "var"
|
|
308
308
|
|
|
309
309
|
wfs = dbos._sys_db.get_workflows(gwi)
|
|
310
|
-
assert len(wfs) ==
|
|
310
|
+
assert len(wfs) == 1
|
|
311
311
|
|
|
312
312
|
wfi1 = dbos._sys_db.get_workflow_status(wfs[0].workflow_id)
|
|
313
313
|
assert wfi1
|
|
314
314
|
assert wfi1["name"].startswith("<temp>")
|
|
315
315
|
|
|
316
|
-
wfi2 = dbos._sys_db.get_workflow_status(wfs[1].workflow_id)
|
|
317
|
-
assert wfi2
|
|
318
|
-
assert wfi2["name"].startswith("<temp>")
|
|
319
|
-
|
|
320
316
|
assert txn_counter == 1
|
|
321
317
|
assert step_counter == 1
|
|
322
318
|
|
|
@@ -350,7 +346,7 @@ def test_temp_workflow_errors(dbos: DBOS) -> None:
|
|
|
350
346
|
def test_retried_step(var: str) -> str:
|
|
351
347
|
nonlocal retried_step_counter
|
|
352
348
|
retried_step_counter += 1
|
|
353
|
-
raise
|
|
349
|
+
raise ValueError(var)
|
|
354
350
|
|
|
355
351
|
with pytest.raises(Exception) as exc_info:
|
|
356
352
|
test_transaction("tval")
|
|
@@ -360,12 +356,12 @@ def test_temp_workflow_errors(dbos: DBOS) -> None:
|
|
|
360
356
|
test_step("cval")
|
|
361
357
|
assert "cval" == str(exc_info.value)
|
|
362
358
|
|
|
363
|
-
with pytest.raises(
|
|
359
|
+
with pytest.raises(ValueError) as exc_info:
|
|
364
360
|
test_retried_step("rval")
|
|
365
361
|
|
|
366
362
|
assert txn_counter == 1
|
|
367
363
|
assert step_counter == 1
|
|
368
|
-
assert retried_step_counter ==
|
|
364
|
+
assert retried_step_counter == 1
|
|
369
365
|
|
|
370
366
|
|
|
371
367
|
def test_recovery_workflow(dbos: DBOS) -> None:
|
|
@@ -1102,9 +1098,6 @@ def test_nonserializable_values(dbos: DBOS) -> None:
|
|
|
1102
1098
|
with pytest.raises(Exception) as exc_info:
|
|
1103
1099
|
test_ns_transaction("h")
|
|
1104
1100
|
assert "data item should not be a function" in str(exc_info.value)
|
|
1105
|
-
with pytest.raises(Exception) as exc_info:
|
|
1106
|
-
test_ns_step("f")
|
|
1107
|
-
assert "data item should not be a function" in str(exc_info.value)
|
|
1108
1101
|
with pytest.raises(Exception) as exc_info:
|
|
1109
1102
|
test_ns_wf("g")
|
|
1110
1103
|
assert "data item should not be a function" in str(exc_info.value)
|
|
@@ -1645,22 +1638,14 @@ def test_custom_names(dbos: DBOS) -> None:
|
|
|
1645
1638
|
async def test_step_without_dbos(dbos: DBOS, config: DBOSConfig) -> None:
|
|
1646
1639
|
DBOS.destroy(destroy_registry=True)
|
|
1647
1640
|
|
|
1648
|
-
is_dbos_active = False
|
|
1649
|
-
|
|
1650
1641
|
@DBOS.step()
|
|
1651
1642
|
def step(x: int) -> int:
|
|
1652
|
-
|
|
1653
|
-
assert DBOS.workflow_id is not None
|
|
1654
|
-
else:
|
|
1655
|
-
assert DBOS.workflow_id is None
|
|
1643
|
+
assert DBOS.workflow_id is None
|
|
1656
1644
|
return x
|
|
1657
1645
|
|
|
1658
1646
|
@DBOS.step()
|
|
1659
1647
|
async def async_step(x: int) -> int:
|
|
1660
|
-
|
|
1661
|
-
assert DBOS.workflow_id is not None
|
|
1662
|
-
else:
|
|
1663
|
-
assert DBOS.workflow_id is None
|
|
1648
|
+
assert DBOS.workflow_id is None
|
|
1664
1649
|
return x
|
|
1665
1650
|
|
|
1666
1651
|
assert step(5) == 5
|
|
@@ -1672,7 +1657,30 @@ async def test_step_without_dbos(dbos: DBOS, config: DBOSConfig) -> None:
|
|
|
1672
1657
|
assert await async_step(5) == 5
|
|
1673
1658
|
|
|
1674
1659
|
DBOS.launch()
|
|
1675
|
-
is_dbos_active = True
|
|
1676
1660
|
|
|
1677
1661
|
assert step(5) == 5
|
|
1678
1662
|
assert await async_step(5) == 5
|
|
1663
|
+
|
|
1664
|
+
assert len(DBOS.list_workflows()) == 0
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def test_nested_steps(dbos: DBOS) -> None:
|
|
1668
|
+
|
|
1669
|
+
@DBOS.step()
|
|
1670
|
+
def outer_step() -> str:
|
|
1671
|
+
return inner_step()
|
|
1672
|
+
|
|
1673
|
+
@DBOS.step()
|
|
1674
|
+
def inner_step() -> str:
|
|
1675
|
+
id = DBOS.workflow_id
|
|
1676
|
+
assert id is not None
|
|
1677
|
+
return id
|
|
1678
|
+
|
|
1679
|
+
@DBOS.workflow()
|
|
1680
|
+
def workflow() -> str:
|
|
1681
|
+
return outer_step()
|
|
1682
|
+
|
|
1683
|
+
id = workflow()
|
|
1684
|
+
steps = DBOS.list_workflow_steps(id)
|
|
1685
|
+
assert len(steps) == 1
|
|
1686
|
+
assert steps[0]["function_name"] == outer_step.__qualname__
|
|
@@ -333,15 +333,8 @@ def test_step_retries(dbos: DBOS) -> None:
|
|
|
333
333
|
error_message = f"Step {failing_step.__qualname__} has exceeded its maximum of {max_attempts} retries"
|
|
334
334
|
|
|
335
335
|
# Test calling the step directly
|
|
336
|
-
with pytest.raises(
|
|
336
|
+
with pytest.raises(Exception) as excinfo:
|
|
337
337
|
failing_step()
|
|
338
|
-
assert error_message in str(excinfo.value)
|
|
339
|
-
assert step_counter == max_attempts
|
|
340
|
-
assert len(excinfo.value.errors) == max_attempts
|
|
341
|
-
for error in excinfo.value.errors:
|
|
342
|
-
assert isinstance(error, Exception)
|
|
343
|
-
assert error
|
|
344
|
-
assert "fail" in str(error)
|
|
345
338
|
|
|
346
339
|
# Test calling the workflow
|
|
347
340
|
step_counter = 0
|
|
@@ -349,6 +342,11 @@ def test_step_retries(dbos: DBOS) -> None:
|
|
|
349
342
|
failing_workflow()
|
|
350
343
|
assert error_message in str(excinfo.value)
|
|
351
344
|
assert step_counter == max_attempts
|
|
345
|
+
assert len(excinfo.value.errors) == max_attempts
|
|
346
|
+
for error in excinfo.value.errors:
|
|
347
|
+
assert isinstance(error, Exception)
|
|
348
|
+
assert error
|
|
349
|
+
assert "fail" in str(error)
|
|
352
350
|
|
|
353
351
|
# Test enqueueing the step
|
|
354
352
|
step_counter = 0
|
|
@@ -399,7 +397,6 @@ def test_step_status(dbos: DBOS) -> None:
|
|
|
399
397
|
|
|
400
398
|
assert failing_workflow() == None
|
|
401
399
|
step_counter = 0
|
|
402
|
-
assert failing_step() == None
|
|
403
400
|
|
|
404
401
|
|
|
405
402
|
def test_recovery_during_retries(dbos: DBOS) -> None:
|
|
@@ -275,3 +275,103 @@ def test_workflow_commands(postgres_db_engine: sa.Engine) -> None:
|
|
|
275
275
|
assert isinstance(fork_wf_data, dict)
|
|
276
276
|
assert fork_wf_data["workflow_id"] != wf_id
|
|
277
277
|
assert fork_wf_data["status"] == "ENQUEUED"
|
|
278
|
+
|
|
279
|
+
# fork the workflow with custom forked workflow ID
|
|
280
|
+
custom_fork_id = "custom-fork-id-12345"
|
|
281
|
+
output = subprocess.check_output(
|
|
282
|
+
[
|
|
283
|
+
"dbos",
|
|
284
|
+
"workflow",
|
|
285
|
+
"fork",
|
|
286
|
+
wf_id,
|
|
287
|
+
"--step",
|
|
288
|
+
"3",
|
|
289
|
+
"--forked-workflow-id",
|
|
290
|
+
custom_fork_id,
|
|
291
|
+
],
|
|
292
|
+
cwd=temp_path,
|
|
293
|
+
env=env,
|
|
294
|
+
)
|
|
295
|
+
custom_fork_data = json.loads(output)
|
|
296
|
+
assert isinstance(custom_fork_data, dict)
|
|
297
|
+
assert custom_fork_data["workflow_id"] == custom_fork_id
|
|
298
|
+
assert custom_fork_data["status"] == "ENQUEUED"
|
|
299
|
+
|
|
300
|
+
# verify the forked workflow data with get command
|
|
301
|
+
output = subprocess.check_output(
|
|
302
|
+
["dbos", "workflow", "get", custom_fork_id, "--db-url", db_url],
|
|
303
|
+
cwd=temp_path,
|
|
304
|
+
)
|
|
305
|
+
custom_fork_get_data = json.loads(output)
|
|
306
|
+
assert isinstance(custom_fork_get_data, dict)
|
|
307
|
+
assert custom_fork_get_data["workflow_id"] == custom_fork_id
|
|
308
|
+
|
|
309
|
+
# fork the workflow with custom application version
|
|
310
|
+
output = subprocess.check_output(
|
|
311
|
+
[
|
|
312
|
+
"dbos",
|
|
313
|
+
"workflow",
|
|
314
|
+
"fork",
|
|
315
|
+
wf_id,
|
|
316
|
+
"--step",
|
|
317
|
+
"2",
|
|
318
|
+
"--application-version",
|
|
319
|
+
"test-version",
|
|
320
|
+
],
|
|
321
|
+
cwd=temp_path,
|
|
322
|
+
env=env,
|
|
323
|
+
)
|
|
324
|
+
version_fork_data = json.loads(output)
|
|
325
|
+
assert isinstance(version_fork_data, dict)
|
|
326
|
+
assert version_fork_data["workflow_id"] != wf_id
|
|
327
|
+
assert version_fork_data["status"] == "ENQUEUED"
|
|
328
|
+
|
|
329
|
+
# verify the forked workflow data with get command and check application version
|
|
330
|
+
output = subprocess.check_output(
|
|
331
|
+
[
|
|
332
|
+
"dbos",
|
|
333
|
+
"workflow",
|
|
334
|
+
"get",
|
|
335
|
+
version_fork_data["workflow_id"],
|
|
336
|
+
"--db-url",
|
|
337
|
+
db_url,
|
|
338
|
+
],
|
|
339
|
+
cwd=temp_path,
|
|
340
|
+
)
|
|
341
|
+
version_fork_get_data = json.loads(output)
|
|
342
|
+
assert isinstance(version_fork_get_data, dict)
|
|
343
|
+
assert version_fork_get_data["workflow_id"] == version_fork_data["workflow_id"]
|
|
344
|
+
assert version_fork_get_data["app_version"] == "test-version"
|
|
345
|
+
|
|
346
|
+
# fork the workflow with both custom ID and application version
|
|
347
|
+
custom_fork_id2 = "custom-fork-with-version-67890"
|
|
348
|
+
output = subprocess.check_output(
|
|
349
|
+
[
|
|
350
|
+
"dbos",
|
|
351
|
+
"workflow",
|
|
352
|
+
"fork",
|
|
353
|
+
wf_id,
|
|
354
|
+
"--step",
|
|
355
|
+
"4",
|
|
356
|
+
"--forked-workflow-id",
|
|
357
|
+
custom_fork_id2,
|
|
358
|
+
"--application-version",
|
|
359
|
+
"v2.0.0",
|
|
360
|
+
],
|
|
361
|
+
cwd=temp_path,
|
|
362
|
+
env=env,
|
|
363
|
+
)
|
|
364
|
+
combined_fork_data = json.loads(output)
|
|
365
|
+
assert isinstance(combined_fork_data, dict)
|
|
366
|
+
assert combined_fork_data["workflow_id"] == custom_fork_id2
|
|
367
|
+
assert combined_fork_data["status"] == "ENQUEUED"
|
|
368
|
+
|
|
369
|
+
# verify the forked workflow data with get command and check both ID and application version
|
|
370
|
+
output = subprocess.check_output(
|
|
371
|
+
["dbos", "workflow", "get", custom_fork_id2, "--db-url", db_url],
|
|
372
|
+
cwd=temp_path,
|
|
373
|
+
)
|
|
374
|
+
combined_fork_get_data = json.loads(output)
|
|
375
|
+
assert isinstance(combined_fork_get_data, dict)
|
|
376
|
+
assert combined_fork_get_data["workflow_id"] == custom_fork_id2
|
|
377
|
+
assert combined_fork_get_data["app_version"] == "v2.0.0"
|
|
@@ -181,7 +181,8 @@ def test_queue_step(dbos: DBOS) -> None:
|
|
|
181
181
|
handle = queue.enqueue(test_step, "abc")
|
|
182
182
|
assert handle.get_result() == "abc1"
|
|
183
183
|
with SetWorkflowID(wfid):
|
|
184
|
-
|
|
184
|
+
handle = queue.enqueue(test_step, "abc")
|
|
185
|
+
assert handle.get_result() == "abc1"
|
|
185
186
|
assert step_counter == 1
|
|
186
187
|
|
|
187
188
|
|
|
@@ -53,11 +53,10 @@ def test_spans(config: DBOSConfig) -> None:
|
|
|
53
53
|
dbos_logger.addHandler(LoggingHandler(logger_provider=log_provider))
|
|
54
54
|
|
|
55
55
|
test_workflow()
|
|
56
|
-
test_step()
|
|
57
56
|
|
|
58
57
|
log_processor.force_flush(timeout_millis=5000)
|
|
59
58
|
logs = log_exporter.get_finished_logs()
|
|
60
|
-
assert len(logs) ==
|
|
59
|
+
assert len(logs) == 2
|
|
61
60
|
for log in logs:
|
|
62
61
|
assert log.log_record.attributes is not None
|
|
63
62
|
assert (
|
|
@@ -78,7 +77,7 @@ def test_spans(config: DBOSConfig) -> None:
|
|
|
78
77
|
|
|
79
78
|
spans = exporter.get_finished_spans()
|
|
80
79
|
|
|
81
|
-
assert len(spans) ==
|
|
80
|
+
assert len(spans) == 3
|
|
82
81
|
|
|
83
82
|
for span in spans:
|
|
84
83
|
assert span.attributes is not None
|
|
@@ -92,26 +91,19 @@ def test_spans(config: DBOSConfig) -> None:
|
|
|
92
91
|
assert spans[0].name == test_step.__qualname__
|
|
93
92
|
assert spans[1].name == "a new span"
|
|
94
93
|
assert spans[2].name == test_workflow.__qualname__
|
|
95
|
-
assert spans[3].name == test_step.__qualname__
|
|
96
|
-
assert spans[4].name == f"<temp>.{test_step.__qualname__}"
|
|
97
94
|
|
|
98
95
|
assert spans[0].parent.span_id == spans[2].context.span_id # type: ignore
|
|
99
96
|
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
100
97
|
assert spans[2].parent == None
|
|
101
|
-
assert spans[3].parent.span_id == spans[4].context.span_id # type: ignore
|
|
102
|
-
assert spans[4].parent == None
|
|
103
98
|
|
|
104
99
|
# Span ID and trace ID should match the log record
|
|
105
100
|
# For pyright
|
|
106
101
|
assert spans[0].context is not None
|
|
107
102
|
assert spans[2].context is not None
|
|
108
|
-
assert spans[3].context is not None
|
|
109
103
|
assert logs[0].log_record.span_id == spans[0].context.span_id
|
|
110
104
|
assert logs[0].log_record.trace_id == spans[0].context.trace_id
|
|
111
105
|
assert logs[1].log_record.span_id == spans[2].context.span_id
|
|
112
106
|
assert logs[1].log_record.trace_id == spans[2].context.trace_id
|
|
113
|
-
assert logs[2].log_record.span_id == spans[3].context.span_id
|
|
114
|
-
assert logs[2].log_record.trace_id == spans[3].context.trace_id
|
|
115
107
|
|
|
116
108
|
|
|
117
109
|
@pytest.mark.asyncio
|
|
@@ -147,11 +139,10 @@ async def test_spans_async(dbos: DBOS) -> None:
|
|
|
147
139
|
dbos_logger.addHandler(LoggingHandler(logger_provider=log_provider))
|
|
148
140
|
|
|
149
141
|
await test_workflow()
|
|
150
|
-
await test_step()
|
|
151
142
|
|
|
152
143
|
log_processor.force_flush(timeout_millis=5000)
|
|
153
144
|
logs = log_exporter.get_finished_logs()
|
|
154
|
-
assert len(logs) ==
|
|
145
|
+
assert len(logs) == 2
|
|
155
146
|
for log in logs:
|
|
156
147
|
assert log.log_record.attributes is not None
|
|
157
148
|
assert (
|
|
@@ -171,7 +162,7 @@ async def test_spans_async(dbos: DBOS) -> None:
|
|
|
171
162
|
|
|
172
163
|
spans = exporter.get_finished_spans()
|
|
173
164
|
|
|
174
|
-
assert len(spans) ==
|
|
165
|
+
assert len(spans) == 3
|
|
175
166
|
|
|
176
167
|
for span in spans:
|
|
177
168
|
assert span.attributes is not None
|
|
@@ -184,34 +175,27 @@ async def test_spans_async(dbos: DBOS) -> None:
|
|
|
184
175
|
assert spans[0].name == test_step.__qualname__
|
|
185
176
|
assert spans[1].name == "a new span"
|
|
186
177
|
assert spans[2].name == test_workflow.__qualname__
|
|
187
|
-
assert spans[3].name == test_step.__qualname__
|
|
188
|
-
assert spans[4].name == f"<temp>.{test_step.__qualname__}"
|
|
189
178
|
|
|
190
179
|
assert spans[0].parent.span_id == spans[2].context.span_id # type: ignore
|
|
191
180
|
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
192
181
|
assert spans[2].parent == None
|
|
193
|
-
assert spans[3].parent.span_id == spans[4].context.span_id # type: ignore
|
|
194
|
-
assert spans[4].parent == None
|
|
195
182
|
|
|
196
183
|
# Span ID and trace ID should match the log record
|
|
197
184
|
assert spans[0].context is not None
|
|
198
185
|
assert spans[2].context is not None
|
|
199
|
-
assert spans[3].context is not None
|
|
200
186
|
assert logs[0].log_record.span_id == spans[0].context.span_id
|
|
201
187
|
assert logs[0].log_record.trace_id == spans[0].context.trace_id
|
|
202
188
|
assert logs[1].log_record.span_id == spans[2].context.span_id
|
|
203
189
|
assert logs[1].log_record.trace_id == spans[2].context.trace_id
|
|
204
|
-
assert logs[2].log_record.span_id == spans[3].context.span_id
|
|
205
|
-
assert logs[2].log_record.trace_id == spans[3].context.trace_id
|
|
206
190
|
|
|
207
191
|
|
|
208
|
-
def
|
|
192
|
+
def test_wf_fastapi(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
209
193
|
dbos, app = dbos_fastapi
|
|
210
194
|
|
|
211
|
-
@app.get("/
|
|
212
|
-
@DBOS.
|
|
213
|
-
def
|
|
214
|
-
dbos.logger.info("This is a
|
|
195
|
+
@app.get("/wf")
|
|
196
|
+
@DBOS.workflow()
|
|
197
|
+
def test_workflow_endpoint() -> str:
|
|
198
|
+
dbos.logger.info("This is a test_workflow_endpoint")
|
|
215
199
|
return "test"
|
|
216
200
|
|
|
217
201
|
exporter = InMemorySpanExporter()
|
|
@@ -229,7 +213,7 @@ def test_temp_wf_fastapi(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
|
229
213
|
dbos_logger.addHandler(LoggingHandler(logger_provider=log_provider))
|
|
230
214
|
|
|
231
215
|
client = TestClient(app)
|
|
232
|
-
response = client.get("/
|
|
216
|
+
response = client.get("/wf")
|
|
233
217
|
assert response.status_code == 200
|
|
234
218
|
assert response.text == '"test"'
|
|
235
219
|
|
|
@@ -242,14 +226,14 @@ def test_temp_wf_fastapi(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
|
242
226
|
)
|
|
243
227
|
assert logs[0].log_record.span_id is not None and logs[0].log_record.span_id > 0
|
|
244
228
|
assert logs[0].log_record.trace_id is not None and logs[0].log_record.trace_id > 0
|
|
245
|
-
assert logs[0].log_record.body == "This is a
|
|
229
|
+
assert logs[0].log_record.body == "This is a test_workflow_endpoint"
|
|
246
230
|
assert logs[0].log_record.attributes["traceId"] == format_trace_id(
|
|
247
231
|
logs[0].log_record.trace_id
|
|
248
232
|
)
|
|
249
233
|
|
|
250
234
|
spans = exporter.get_finished_spans()
|
|
251
235
|
|
|
252
|
-
assert len(spans) ==
|
|
236
|
+
assert len(spans) == 2
|
|
253
237
|
|
|
254
238
|
for span in spans:
|
|
255
239
|
assert span.attributes is not None
|
|
@@ -258,13 +242,13 @@ def test_temp_wf_fastapi(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
|
258
242
|
assert span.context.span_id > 0
|
|
259
243
|
assert span.context.trace_id > 0
|
|
260
244
|
|
|
261
|
-
assert spans[0].name ==
|
|
262
|
-
assert spans[1].name ==
|
|
263
|
-
assert spans[
|
|
245
|
+
assert spans[0].name == test_workflow_endpoint.__qualname__
|
|
246
|
+
assert spans[1].name == "/wf"
|
|
247
|
+
assert spans[1].attributes is not None
|
|
248
|
+
assert spans[1].attributes["responseCode"] == 200
|
|
264
249
|
|
|
265
250
|
assert spans[0].parent.span_id == spans[1].context.span_id # type: ignore
|
|
266
|
-
assert spans[1].parent
|
|
267
|
-
assert spans[2].parent == None
|
|
251
|
+
assert spans[1].parent == None
|
|
268
252
|
|
|
269
253
|
# Span ID and trace ID should match the log record
|
|
270
254
|
assert spans[0].context is not None
|
|
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
|
{dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py
RENAMED
|
File without changes
|
{dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/66478e1b95e5_consolidate_queues.py
RENAMED
|
File without changes
|
|
File without changes
|
{dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dbos-1.12.0a1 → dbos-1.12.0a3}/dbos/_migrations/versions/d994145b47b6_consolidate_inputs.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
|