dbos 0.23.0a2__tar.gz → 0.23.0a5__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-0.23.0a2 → dbos-0.23.0a5}/PKG-INFO +1 -1
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_core.py +2 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_recovery.py +1 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_sys_db.py +40 -82
- dbos-0.23.0a5/dbos/_workflow_commands.py +148 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/cli/cli.py +41 -20
- {dbos-0.23.0a2 → dbos-0.23.0a5}/pyproject.toml +1 -1
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/conftest.py +8 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_admin_server.py +53 -46
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_dbos.py +7 -5
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_fastapi.py +2 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_flask.py +2 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_scheduler.py +2 -0
- dbos-0.23.0a5/tests/test_workflow_cmds.py +299 -0
- dbos-0.23.0a2/dbos/_workflow_commands.py +0 -183
- dbos-0.23.0a2/tests/test_workflow_cmds.py +0 -289
- {dbos-0.23.0a2 → dbos-0.23.0a5}/LICENSE +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/README.md +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/__init__.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_admin_server.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_app_db.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_classproperty.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_cloudutils/authentication.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_cloudutils/cloudutils.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_cloudutils/databases.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_context.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_croniter.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_db_wizard.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_dbos.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_dbos_config.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_error.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_fastapi.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_flask.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_kafka.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_kafka_message.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_logger.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/env.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/script.py.mako +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_outcome.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_queue.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_registrations.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_request.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_roles.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_scheduler.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_schemas/__init__.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_schemas/application_database.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_schemas/system_database.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_serialization.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_tracer.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/_utils.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/cli/_github_init.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/cli/_template_init.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/dbos-config.schema.json +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/dbos/py.typed +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/__init__.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/atexit_no_ctor.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/atexit_no_launch.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/classdefs.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/more_classdefs.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/queuedworkflow.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_async.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_classdecorators.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_concurrency.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_config.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_croniter.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_failures.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_fastapi_roles.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_kafka.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_outcome.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_package.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_queue.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_schema_migration.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_singleton.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_spans.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_sqlalchemy.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/tests/test_workflow_cancel.py +0 -0
- {dbos-0.23.0a2 → dbos-0.23.0a5}/version/__init__.py +0 -0
|
@@ -43,6 +43,7 @@ def startup_recovery_thread(
|
|
|
43
43
|
def recover_pending_workflows(
|
|
44
44
|
dbos: "DBOS", executor_ids: List[str] = ["local"]
|
|
45
45
|
) -> List["WorkflowHandle[Any]"]:
|
|
46
|
+
"""Attempt to recover pending workflows for a list of specific executors and return workflow handles for them."""
|
|
46
47
|
workflow_handles: List["WorkflowHandle[Any]"] = []
|
|
47
48
|
for executor_id in executor_ids:
|
|
48
49
|
dbos.logger.debug(f"Recovering pending workflows for executor: {executor_id}")
|
|
@@ -68,17 +68,19 @@ class WorkflowStatusInternal(TypedDict):
|
|
|
68
68
|
name: str
|
|
69
69
|
class_name: Optional[str]
|
|
70
70
|
config_name: Optional[str]
|
|
71
|
+
authenticated_user: Optional[str]
|
|
72
|
+
assumed_role: Optional[str]
|
|
73
|
+
authenticated_roles: Optional[str] # JSON list of roles
|
|
71
74
|
output: Optional[str] # JSON (jsonpickle)
|
|
75
|
+
request: Optional[str] # JSON (jsonpickle)
|
|
72
76
|
error: Optional[str] # JSON (jsonpickle)
|
|
77
|
+
created_at: Optional[int] # Unix epoch timestamp in ms
|
|
78
|
+
updated_at: Optional[int] # Unix epoch timestamp in ms
|
|
79
|
+
queue_name: Optional[str]
|
|
73
80
|
executor_id: Optional[str]
|
|
74
81
|
app_version: Optional[str]
|
|
75
82
|
app_id: Optional[str]
|
|
76
|
-
request: Optional[str] # JSON (jsonpickle)
|
|
77
83
|
recovery_attempts: Optional[int]
|
|
78
|
-
authenticated_user: Optional[str]
|
|
79
|
-
assumed_role: Optional[str]
|
|
80
|
-
authenticated_roles: Optional[str] # JSON list of roles.
|
|
81
|
-
queue_name: Optional[str]
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
class RecordedResult(TypedDict):
|
|
@@ -104,19 +106,12 @@ class GetWorkflowsInput:
|
|
|
104
106
|
Structure for argument to `get_workflows` function.
|
|
105
107
|
|
|
106
108
|
This specifies the search criteria for workflow retrieval by `get_workflows`.
|
|
107
|
-
|
|
108
|
-
Attributes:
|
|
109
|
-
name(str): The name of the workflow function
|
|
110
|
-
authenticated_user(str): The name of the user who invoked the function
|
|
111
|
-
start_time(str): Beginning of search range for time of invocation, in ISO 8601 format
|
|
112
|
-
end_time(str): End of search range for time of invocation, in ISO 8601 format
|
|
113
|
-
status(str): Current status of the workflow invocation (see `WorkflowStatusString`)
|
|
114
|
-
application_version(str): Application version that invoked the workflow
|
|
115
|
-
limit(int): Limit on number of returned records
|
|
116
|
-
|
|
117
109
|
"""
|
|
118
110
|
|
|
119
111
|
def __init__(self) -> None:
|
|
112
|
+
self.workflow_ids: Optional[List[str]] = (
|
|
113
|
+
None # Search only in these workflow IDs
|
|
114
|
+
)
|
|
120
115
|
self.name: Optional[str] = None # The name of the workflow function
|
|
121
116
|
self.authenticated_user: Optional[str] = None # The user who ran the workflow.
|
|
122
117
|
self.start_time: Optional[str] = None # Timestamp in ISO 8601 format
|
|
@@ -128,14 +123,18 @@ class GetWorkflowsInput:
|
|
|
128
123
|
self.limit: Optional[int] = (
|
|
129
124
|
None # Return up to this many workflows IDs. IDs are ordered by workflow creation time.
|
|
130
125
|
)
|
|
126
|
+
self.offset: Optional[int] = (
|
|
127
|
+
None # Offset into the matching records for pagination
|
|
128
|
+
)
|
|
131
129
|
|
|
132
130
|
|
|
133
131
|
class GetQueuedWorkflowsInput(TypedDict):
|
|
134
|
-
queue_name: Optional[str]
|
|
135
|
-
status: Optional[str]
|
|
132
|
+
queue_name: Optional[str] # Get workflows belonging to this queue
|
|
133
|
+
status: Optional[str] # Get workflows with this status
|
|
136
134
|
start_time: Optional[str] # Timestamp in ISO 8601 format
|
|
137
135
|
end_time: Optional[str] # Timestamp in ISO 8601 format
|
|
138
136
|
limit: Optional[int] # Return up to this many workflows IDs.
|
|
137
|
+
offset: Optional[int] # Offset into the matching records for pagination
|
|
139
138
|
name: Optional[str] # The name of the workflow function
|
|
140
139
|
|
|
141
140
|
|
|
@@ -305,6 +304,7 @@ class SystemDatabase:
|
|
|
305
304
|
.on_conflict_do_update(
|
|
306
305
|
index_elements=["workflow_uuid"],
|
|
307
306
|
set_=dict(
|
|
307
|
+
executor_id=status["executor_id"],
|
|
308
308
|
recovery_attempts=(
|
|
309
309
|
SystemSchema.workflow_status.c.recovery_attempts + 1
|
|
310
310
|
),
|
|
@@ -488,27 +488,33 @@ class SystemDatabase:
|
|
|
488
488
|
SystemSchema.workflow_status.c.assumed_role,
|
|
489
489
|
SystemSchema.workflow_status.c.queue_name,
|
|
490
490
|
SystemSchema.workflow_status.c.executor_id,
|
|
491
|
+
SystemSchema.workflow_status.c.created_at,
|
|
492
|
+
SystemSchema.workflow_status.c.updated_at,
|
|
493
|
+
SystemSchema.workflow_status.c.application_version,
|
|
494
|
+
SystemSchema.workflow_status.c.application_id,
|
|
491
495
|
).where(SystemSchema.workflow_status.c.workflow_uuid == workflow_uuid)
|
|
492
496
|
).fetchone()
|
|
493
497
|
if row is None:
|
|
494
498
|
return None
|
|
495
499
|
status: WorkflowStatusInternal = {
|
|
496
500
|
"workflow_uuid": workflow_uuid,
|
|
497
|
-
"status": row[0],
|
|
498
|
-
"name": row[1],
|
|
499
|
-
"class_name": row[5],
|
|
500
|
-
"config_name": row[4],
|
|
501
501
|
"output": None,
|
|
502
502
|
"error": None,
|
|
503
|
-
"
|
|
504
|
-
"
|
|
505
|
-
"executor_id": row[10],
|
|
503
|
+
"status": row[0],
|
|
504
|
+
"name": row[1],
|
|
506
505
|
"request": row[2],
|
|
507
506
|
"recovery_attempts": row[3],
|
|
507
|
+
"config_name": row[4],
|
|
508
|
+
"class_name": row[5],
|
|
508
509
|
"authenticated_user": row[6],
|
|
509
510
|
"authenticated_roles": row[7],
|
|
510
511
|
"assumed_role": row[8],
|
|
511
512
|
"queue_name": row[9],
|
|
513
|
+
"executor_id": row[10],
|
|
514
|
+
"created_at": row[11],
|
|
515
|
+
"updated_at": row[12],
|
|
516
|
+
"app_version": row[13],
|
|
517
|
+
"app_id": row[14],
|
|
512
518
|
}
|
|
513
519
|
return status
|
|
514
520
|
|
|
@@ -537,47 +543,6 @@ class SystemDatabase:
|
|
|
537
543
|
)
|
|
538
544
|
return stat
|
|
539
545
|
|
|
540
|
-
def get_workflow_status_w_outputs(
|
|
541
|
-
self, workflow_uuid: str
|
|
542
|
-
) -> Optional[WorkflowStatusInternal]:
|
|
543
|
-
with self.engine.begin() as c:
|
|
544
|
-
row = c.execute(
|
|
545
|
-
sa.select(
|
|
546
|
-
SystemSchema.workflow_status.c.status,
|
|
547
|
-
SystemSchema.workflow_status.c.name,
|
|
548
|
-
SystemSchema.workflow_status.c.request,
|
|
549
|
-
SystemSchema.workflow_status.c.output,
|
|
550
|
-
SystemSchema.workflow_status.c.error,
|
|
551
|
-
SystemSchema.workflow_status.c.config_name,
|
|
552
|
-
SystemSchema.workflow_status.c.class_name,
|
|
553
|
-
SystemSchema.workflow_status.c.authenticated_user,
|
|
554
|
-
SystemSchema.workflow_status.c.authenticated_roles,
|
|
555
|
-
SystemSchema.workflow_status.c.assumed_role,
|
|
556
|
-
SystemSchema.workflow_status.c.queue_name,
|
|
557
|
-
).where(SystemSchema.workflow_status.c.workflow_uuid == workflow_uuid)
|
|
558
|
-
).fetchone()
|
|
559
|
-
if row is None:
|
|
560
|
-
return None
|
|
561
|
-
status: WorkflowStatusInternal = {
|
|
562
|
-
"workflow_uuid": workflow_uuid,
|
|
563
|
-
"status": row[0],
|
|
564
|
-
"name": row[1],
|
|
565
|
-
"config_name": row[5],
|
|
566
|
-
"class_name": row[6],
|
|
567
|
-
"output": row[3],
|
|
568
|
-
"error": row[4],
|
|
569
|
-
"app_id": None,
|
|
570
|
-
"app_version": None,
|
|
571
|
-
"executor_id": None,
|
|
572
|
-
"request": row[2],
|
|
573
|
-
"recovery_attempts": None,
|
|
574
|
-
"authenticated_user": row[7],
|
|
575
|
-
"authenticated_roles": row[8],
|
|
576
|
-
"assumed_role": row[9],
|
|
577
|
-
"queue_name": row[10],
|
|
578
|
-
}
|
|
579
|
-
return status
|
|
580
|
-
|
|
581
546
|
def await_workflow_result_internal(self, workflow_uuid: str) -> dict[str, Any]:
|
|
582
547
|
polling_interval_secs: float = 1.000
|
|
583
548
|
|
|
@@ -624,21 +589,6 @@ class SystemDatabase:
|
|
|
624
589
|
raise _serialization.deserialize_exception(stat["error"])
|
|
625
590
|
return None
|
|
626
591
|
|
|
627
|
-
def get_workflow_info(
|
|
628
|
-
self, workflow_uuid: str, get_request: bool
|
|
629
|
-
) -> Optional[WorkflowInformation]:
|
|
630
|
-
stat = self.get_workflow_status_w_outputs(workflow_uuid)
|
|
631
|
-
if stat is None:
|
|
632
|
-
return None
|
|
633
|
-
info = cast(WorkflowInformation, stat)
|
|
634
|
-
input = self.get_workflow_inputs(workflow_uuid)
|
|
635
|
-
if input is not None:
|
|
636
|
-
info["input"] = input
|
|
637
|
-
if not get_request:
|
|
638
|
-
info.pop("request", None)
|
|
639
|
-
|
|
640
|
-
return info
|
|
641
|
-
|
|
642
592
|
def update_workflow_inputs(
|
|
643
593
|
self, workflow_uuid: str, inputs: str, conn: Optional[sa.Connection] = None
|
|
644
594
|
) -> None:
|
|
@@ -714,14 +664,20 @@ class SystemDatabase:
|
|
|
714
664
|
SystemSchema.workflow_status.c.application_version
|
|
715
665
|
== input.application_version
|
|
716
666
|
)
|
|
667
|
+
if input.workflow_ids:
|
|
668
|
+
query = query.where(
|
|
669
|
+
SystemSchema.workflow_status.c.workflow_uuid.in_(input.workflow_ids)
|
|
670
|
+
)
|
|
717
671
|
if input.limit:
|
|
718
672
|
query = query.limit(input.limit)
|
|
673
|
+
if input.offset:
|
|
674
|
+
query = query.offset(input.offset)
|
|
719
675
|
|
|
720
676
|
with self.engine.begin() as c:
|
|
721
677
|
rows = c.execute(query)
|
|
722
|
-
|
|
678
|
+
workflow_ids = [row[0] for row in rows]
|
|
723
679
|
|
|
724
|
-
return GetWorkflowsOutput(
|
|
680
|
+
return GetWorkflowsOutput(workflow_ids)
|
|
725
681
|
|
|
726
682
|
def get_queued_workflows(
|
|
727
683
|
self, input: GetQueuedWorkflowsInput
|
|
@@ -762,6 +718,8 @@ class SystemDatabase:
|
|
|
762
718
|
)
|
|
763
719
|
if input.get("limit"):
|
|
764
720
|
query = query.limit(input["limit"])
|
|
721
|
+
if input.get("offset"):
|
|
722
|
+
query = query.offset(input["offset"])
|
|
765
723
|
|
|
766
724
|
with self.engine.begin() as c:
|
|
767
725
|
rows = c.execute(query)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from typing import List, Optional, cast
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from . import _serialization
|
|
6
|
+
from ._dbos_config import ConfigFile
|
|
7
|
+
from ._logger import dbos_logger
|
|
8
|
+
from ._sys_db import (
|
|
9
|
+
GetQueuedWorkflowsInput,
|
|
10
|
+
GetWorkflowsInput,
|
|
11
|
+
GetWorkflowsOutput,
|
|
12
|
+
SystemDatabase,
|
|
13
|
+
WorkflowStatuses,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WorkflowInformation:
|
|
18
|
+
workflow_id: str
|
|
19
|
+
status: WorkflowStatuses
|
|
20
|
+
workflow_name: str
|
|
21
|
+
workflow_class_name: Optional[str]
|
|
22
|
+
workflow_config_name: Optional[str]
|
|
23
|
+
authenticated_user: Optional[str]
|
|
24
|
+
assumed_role: Optional[str]
|
|
25
|
+
authenticated_roles: Optional[str] # JSON list of roles.
|
|
26
|
+
input: Optional[_serialization.WorkflowInputs] # JSON (jsonpickle)
|
|
27
|
+
output: Optional[str] = None # JSON (jsonpickle)
|
|
28
|
+
request: Optional[str] # JSON (jsonpickle)
|
|
29
|
+
error: Optional[str] = None # JSON (jsonpickle)
|
|
30
|
+
created_at: Optional[int] # Unix epoch timestamp in ms
|
|
31
|
+
updated_at: Optional[int] # Unix epoch timestamp in ms
|
|
32
|
+
queue_name: Optional[str]
|
|
33
|
+
executor_id: Optional[str]
|
|
34
|
+
app_version: Optional[str]
|
|
35
|
+
app_id: Optional[str]
|
|
36
|
+
recovery_attempts: Optional[int]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def list_workflows(
|
|
40
|
+
sys_db: SystemDatabase,
|
|
41
|
+
*,
|
|
42
|
+
workflow_ids: Optional[List[str]] = None,
|
|
43
|
+
user: Optional[str] = None,
|
|
44
|
+
start_time: Optional[str] = None,
|
|
45
|
+
end_time: Optional[str] = None,
|
|
46
|
+
status: Optional[str] = None,
|
|
47
|
+
request: bool = False,
|
|
48
|
+
app_version: Optional[str] = None,
|
|
49
|
+
name: Optional[str] = None,
|
|
50
|
+
limit: Optional[int] = None,
|
|
51
|
+
offset: Optional[int] = None,
|
|
52
|
+
) -> List[WorkflowInformation]:
|
|
53
|
+
input = GetWorkflowsInput()
|
|
54
|
+
input.workflow_ids = workflow_ids
|
|
55
|
+
input.authenticated_user = user
|
|
56
|
+
input.start_time = start_time
|
|
57
|
+
input.end_time = end_time
|
|
58
|
+
if status is not None:
|
|
59
|
+
input.status = cast(WorkflowStatuses, status)
|
|
60
|
+
input.application_version = app_version
|
|
61
|
+
input.limit = limit
|
|
62
|
+
input.name = name
|
|
63
|
+
input.offset = offset
|
|
64
|
+
|
|
65
|
+
output: GetWorkflowsOutput = sys_db.get_workflows(input)
|
|
66
|
+
infos: List[WorkflowInformation] = []
|
|
67
|
+
for workflow_id in output.workflow_uuids:
|
|
68
|
+
info = get_workflow(sys_db, workflow_id, request) # Call the method for each ID
|
|
69
|
+
if info is not None:
|
|
70
|
+
infos.append(info)
|
|
71
|
+
return infos
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def list_queued_workflows(
|
|
75
|
+
sys_db: SystemDatabase,
|
|
76
|
+
*,
|
|
77
|
+
limit: Optional[int] = None,
|
|
78
|
+
start_time: Optional[str] = None,
|
|
79
|
+
end_time: Optional[str] = None,
|
|
80
|
+
queue_name: Optional[str] = None,
|
|
81
|
+
status: Optional[str] = None,
|
|
82
|
+
name: Optional[str] = None,
|
|
83
|
+
request: bool = False,
|
|
84
|
+
offset: Optional[int] = None,
|
|
85
|
+
) -> List[WorkflowInformation]:
|
|
86
|
+
input: GetQueuedWorkflowsInput = {
|
|
87
|
+
"queue_name": queue_name,
|
|
88
|
+
"start_time": start_time,
|
|
89
|
+
"end_time": end_time,
|
|
90
|
+
"status": status,
|
|
91
|
+
"limit": limit,
|
|
92
|
+
"name": name,
|
|
93
|
+
"offset": offset,
|
|
94
|
+
}
|
|
95
|
+
output: GetWorkflowsOutput = sys_db.get_queued_workflows(input)
|
|
96
|
+
infos: List[WorkflowInformation] = []
|
|
97
|
+
for workflow_id in output.workflow_uuids:
|
|
98
|
+
info = get_workflow(sys_db, workflow_id, request) # Call the method for each ID
|
|
99
|
+
if info is not None:
|
|
100
|
+
infos.append(info)
|
|
101
|
+
return infos
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_workflow(
|
|
105
|
+
sys_db: SystemDatabase, workflowUUID: str, getRequest: bool
|
|
106
|
+
) -> Optional[WorkflowInformation]:
|
|
107
|
+
|
|
108
|
+
info = sys_db.get_workflow_status(workflowUUID)
|
|
109
|
+
if info is None:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
winfo = WorkflowInformation()
|
|
113
|
+
|
|
114
|
+
winfo.workflow_id = workflowUUID
|
|
115
|
+
winfo.status = info["status"]
|
|
116
|
+
winfo.workflow_name = info["name"]
|
|
117
|
+
winfo.workflow_class_name = info["class_name"]
|
|
118
|
+
winfo.workflow_config_name = info["config_name"]
|
|
119
|
+
winfo.authenticated_user = info["authenticated_user"]
|
|
120
|
+
winfo.assumed_role = info["assumed_role"]
|
|
121
|
+
winfo.authenticated_roles = info["authenticated_roles"]
|
|
122
|
+
winfo.request = info["request"]
|
|
123
|
+
winfo.created_at = info["created_at"]
|
|
124
|
+
winfo.updated_at = info["updated_at"]
|
|
125
|
+
winfo.queue_name = info["queue_name"]
|
|
126
|
+
winfo.executor_id = info["executor_id"]
|
|
127
|
+
winfo.app_version = info["app_version"]
|
|
128
|
+
winfo.app_id = info["app_id"]
|
|
129
|
+
winfo.recovery_attempts = info["recovery_attempts"]
|
|
130
|
+
|
|
131
|
+
# no input field
|
|
132
|
+
input_data = sys_db.get_workflow_inputs(workflowUUID)
|
|
133
|
+
if input_data is not None:
|
|
134
|
+
winfo.input = input_data
|
|
135
|
+
|
|
136
|
+
if info.get("status") == "SUCCESS":
|
|
137
|
+
result = sys_db.await_workflow_result(workflowUUID)
|
|
138
|
+
winfo.output = result
|
|
139
|
+
elif info.get("status") == "ERROR":
|
|
140
|
+
try:
|
|
141
|
+
sys_db.await_workflow_result(workflowUUID)
|
|
142
|
+
except Exception as e:
|
|
143
|
+
winfo.error = str(e)
|
|
144
|
+
|
|
145
|
+
if not getRequest:
|
|
146
|
+
winfo.request = None
|
|
147
|
+
|
|
148
|
+
return winfo
|
|
@@ -19,12 +19,7 @@ from .. import load_config
|
|
|
19
19
|
from .._app_db import ApplicationDatabase
|
|
20
20
|
from .._dbos_config import _is_valid_app_name
|
|
21
21
|
from .._sys_db import SystemDatabase, reset_system_database
|
|
22
|
-
from .._workflow_commands import
|
|
23
|
-
cancel_workflow,
|
|
24
|
-
get_workflow,
|
|
25
|
-
list_queued_workflows,
|
|
26
|
-
list_workflows,
|
|
27
|
-
)
|
|
22
|
+
from .._workflow_commands import get_workflow, list_queued_workflows, list_workflows
|
|
28
23
|
from ..cli._github_init import create_template_from_github
|
|
29
24
|
from ._template_init import copy_template, get_project_name, get_templates_directory
|
|
30
25
|
|
|
@@ -290,25 +285,37 @@ def list(
|
|
|
290
285
|
request: Annotated[
|
|
291
286
|
bool,
|
|
292
287
|
typer.Option("--request", help="Retrieve workflow request information"),
|
|
293
|
-
] =
|
|
288
|
+
] = False,
|
|
294
289
|
) -> None:
|
|
295
290
|
config = load_config(silent=True)
|
|
291
|
+
sys_db = SystemDatabase(config)
|
|
296
292
|
workflows = list_workflows(
|
|
297
|
-
|
|
293
|
+
sys_db,
|
|
294
|
+
limit=limit,
|
|
295
|
+
user=user,
|
|
296
|
+
start_time=starttime,
|
|
297
|
+
end_time=endtime,
|
|
298
|
+
status=status,
|
|
299
|
+
request=request,
|
|
300
|
+
app_version=appversion,
|
|
301
|
+
name=name,
|
|
298
302
|
)
|
|
299
303
|
print(jsonpickle.encode(workflows, unpicklable=False))
|
|
300
304
|
|
|
301
305
|
|
|
302
306
|
@workflow.command(help="Retrieve the status of a workflow")
|
|
303
307
|
def get(
|
|
304
|
-
|
|
308
|
+
workflow_id: Annotated[str, typer.Argument()],
|
|
305
309
|
request: Annotated[
|
|
306
310
|
bool,
|
|
307
311
|
typer.Option("--request", help="Retrieve workflow request information"),
|
|
308
|
-
] =
|
|
312
|
+
] = False,
|
|
309
313
|
) -> None:
|
|
310
314
|
config = load_config(silent=True)
|
|
311
|
-
|
|
315
|
+
sys_db = SystemDatabase(config)
|
|
316
|
+
print(
|
|
317
|
+
jsonpickle.encode(get_workflow(sys_db, workflow_id, request), unpicklable=False)
|
|
318
|
+
)
|
|
312
319
|
|
|
313
320
|
|
|
314
321
|
@workflow.command(
|
|
@@ -316,10 +323,23 @@ def get(
|
|
|
316
323
|
)
|
|
317
324
|
def cancel(
|
|
318
325
|
uuid: Annotated[str, typer.Argument()],
|
|
326
|
+
host: Annotated[
|
|
327
|
+
typing.Optional[str],
|
|
328
|
+
typer.Option("--host", "-H", help="Specify the admin host"),
|
|
329
|
+
] = "localhost",
|
|
330
|
+
port: Annotated[
|
|
331
|
+
typing.Optional[int],
|
|
332
|
+
typer.Option("--port", "-p", help="Specify the admin port"),
|
|
333
|
+
] = 3001,
|
|
319
334
|
) -> None:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
335
|
+
response = requests.post(
|
|
336
|
+
f"http://{host}:{port}/workflows/{uuid}/cancel", json=[], timeout=5
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if response.status_code == 204:
|
|
340
|
+
print(f"Workflow {uuid} has been cancelled")
|
|
341
|
+
else:
|
|
342
|
+
print(f"Failed to cancel workflow {uuid}. Status code: {response.status_code}")
|
|
323
343
|
|
|
324
344
|
|
|
325
345
|
@workflow.command(help="Resume a workflow that has been cancelled")
|
|
@@ -327,7 +347,7 @@ def resume(
|
|
|
327
347
|
uuid: Annotated[str, typer.Argument()],
|
|
328
348
|
host: Annotated[
|
|
329
349
|
typing.Optional[str],
|
|
330
|
-
typer.Option("--host", "-
|
|
350
|
+
typer.Option("--host", "-H", help="Specify the admin host"),
|
|
331
351
|
] = "localhost",
|
|
332
352
|
port: Annotated[
|
|
333
353
|
typing.Optional[int],
|
|
@@ -338,7 +358,7 @@ def resume(
|
|
|
338
358
|
f"http://{host}:{port}/workflows/{uuid}/resume", json=[], timeout=5
|
|
339
359
|
)
|
|
340
360
|
|
|
341
|
-
if response.status_code ==
|
|
361
|
+
if response.status_code == 204:
|
|
342
362
|
print(f"Workflow {uuid} has been resumed")
|
|
343
363
|
else:
|
|
344
364
|
print(f"Failed to resume workflow {uuid}. Status code: {response.status_code}")
|
|
@@ -349,7 +369,7 @@ def restart(
|
|
|
349
369
|
uuid: Annotated[str, typer.Argument()],
|
|
350
370
|
host: Annotated[
|
|
351
371
|
typing.Optional[str],
|
|
352
|
-
typer.Option("--host", "-
|
|
372
|
+
typer.Option("--host", "-H", help="Specify the admin host"),
|
|
353
373
|
] = "localhost",
|
|
354
374
|
port: Annotated[
|
|
355
375
|
typing.Optional[int],
|
|
@@ -360,7 +380,7 @@ def restart(
|
|
|
360
380
|
f"http://{host}:{port}/workflows/{uuid}/restart", json=[], timeout=5
|
|
361
381
|
)
|
|
362
382
|
|
|
363
|
-
if response.status_code ==
|
|
383
|
+
if response.status_code == 204:
|
|
364
384
|
print(f"Workflow {uuid} has been restarted")
|
|
365
385
|
else:
|
|
366
386
|
print(f"Failed to resume workflow {uuid}. Status code: {response.status_code}")
|
|
@@ -415,11 +435,12 @@ def list_queue(
|
|
|
415
435
|
request: Annotated[
|
|
416
436
|
bool,
|
|
417
437
|
typer.Option("--request", help="Retrieve workflow request information"),
|
|
418
|
-
] =
|
|
438
|
+
] = False,
|
|
419
439
|
) -> None:
|
|
420
440
|
config = load_config(silent=True)
|
|
441
|
+
sys_db = SystemDatabase(config)
|
|
421
442
|
workflows = list_queued_workflows(
|
|
422
|
-
|
|
443
|
+
sys_db=sys_db,
|
|
423
444
|
limit=limit,
|
|
424
445
|
start_time=start_time,
|
|
425
446
|
end_time=end_time,
|
|
@@ -11,6 +11,7 @@ from flask import Flask
|
|
|
11
11
|
|
|
12
12
|
from dbos import DBOS, ConfigFile
|
|
13
13
|
from dbos._schemas.system_database import SystemSchema
|
|
14
|
+
from dbos._sys_db import SystemDatabase
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@pytest.fixture(scope="session")
|
|
@@ -45,6 +46,13 @@ def config() -> ConfigFile:
|
|
|
45
46
|
return default_config()
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
@pytest.fixture()
|
|
50
|
+
def sys_db(config: ConfigFile) -> Generator[SystemDatabase, Any, None]:
|
|
51
|
+
sys_db = SystemDatabase(config)
|
|
52
|
+
yield sys_db
|
|
53
|
+
sys_db.destroy()
|
|
54
|
+
|
|
55
|
+
|
|
48
56
|
@pytest.fixture(scope="session")
|
|
49
57
|
def postgres_db_engine() -> sa.Engine:
|
|
50
58
|
cfg = default_config()
|