dbos 1.8.0a1__tar.gz → 1.8.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.8.0a1 → dbos-1.8.0a3}/PKG-INFO +1 -1
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_context.py +37 -12
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_error.py +1 -1
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_sys_db.py +8 -1
- {dbos-1.8.0a1 → dbos-1.8.0a3}/pyproject.toml +1 -1
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_queue.py +7 -0
- dbos-1.8.0a3/tests/test_spans.py +272 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_workflow_management.py +1 -1
- dbos-1.8.0a1/tests/test_spans.py +0 -147
- {dbos-1.8.0a1 → dbos-1.8.0a3}/LICENSE +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/README.md +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/__init__.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/__main__.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_admin_server.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_app_db.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_classproperty.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_client.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_core.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_croniter.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_dbos.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_dbos_config.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_debug.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_event_loop.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_fastapi.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_flask.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_kafka.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_kafka_message.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_logger.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/env.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/script.py.mako +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_outcome.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_queue.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_recovery.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_registrations.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_roles.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_scheduler.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_serialization.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_tracer.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_utils.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/_workflow_commands.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/cli/_github_init.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/cli/_template_init.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/cli/cli.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/dbos/py.typed +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/__init__.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/atexit_no_launch.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/classdefs.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/client_collateral.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/client_worker.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/conftest.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/more_classdefs.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/queuedworkflow.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_admin_server.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_async.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_classdecorators.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_cli.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_client.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_concurrency.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_config.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_croniter.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_dbos.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_debug.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_docker_secrets.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_failures.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_fastapi.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_flask.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_kafka.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_outcome.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_package.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_scheduler.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_schema_migration.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_singleton.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/tests/test_workflow_introspection.py +0 -0
- {dbos-1.8.0a1 → dbos-1.8.0a3}/version/__init__.py +0 -0
|
@@ -10,7 +10,7 @@ from enum import Enum
|
|
|
10
10
|
from types import TracebackType
|
|
11
11
|
from typing import List, Literal, Optional, Type, TypedDict
|
|
12
12
|
|
|
13
|
-
from opentelemetry.trace import Span, Status, StatusCode
|
|
13
|
+
from opentelemetry.trace import Span, Status, StatusCode, use_span
|
|
14
14
|
from sqlalchemy.orm import Session
|
|
15
15
|
|
|
16
16
|
from dbos._utils import GlobalParams
|
|
@@ -68,6 +68,20 @@ class StepStatus:
|
|
|
68
68
|
max_attempts: Optional[int]
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
@dataclass
|
|
72
|
+
class ContextSpan:
|
|
73
|
+
"""
|
|
74
|
+
A span that is used to track the context of a workflow or step execution.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
span: The OpenTelemetry span object.
|
|
78
|
+
context_manager: The context manager that is used to manage the span's lifecycle.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
span: Span
|
|
82
|
+
context_manager: AbstractContextManager[Span]
|
|
83
|
+
|
|
84
|
+
|
|
71
85
|
class DBOSContext:
|
|
72
86
|
def __init__(self) -> None:
|
|
73
87
|
self.executor_id = GlobalParams.executor_id
|
|
@@ -86,7 +100,7 @@ class DBOSContext:
|
|
|
86
100
|
self.curr_step_function_id: int = -1
|
|
87
101
|
self.curr_tx_function_id: int = -1
|
|
88
102
|
self.sql_session: Optional[Session] = None
|
|
89
|
-
self.
|
|
103
|
+
self.context_spans: list[ContextSpan] = []
|
|
90
104
|
|
|
91
105
|
self.authenticated_user: Optional[str] = None
|
|
92
106
|
self.authenticated_roles: Optional[List[str]] = None
|
|
@@ -202,8 +216,8 @@ class DBOSContext:
|
|
|
202
216
|
self._end_span(exc_value)
|
|
203
217
|
|
|
204
218
|
def get_current_span(self) -> Optional[Span]:
|
|
205
|
-
if len(self.
|
|
206
|
-
return self.
|
|
219
|
+
if len(self.context_spans) > 0:
|
|
220
|
+
return self.context_spans[-1].span
|
|
207
221
|
return None
|
|
208
222
|
|
|
209
223
|
def _start_span(self, attributes: TracedAttributes) -> None:
|
|
@@ -218,27 +232,38 @@ class DBOSContext:
|
|
|
218
232
|
)
|
|
219
233
|
attributes["authenticatedUserAssumedRole"] = self.assumed_role
|
|
220
234
|
span = dbos_tracer.start_span(
|
|
221
|
-
attributes,
|
|
235
|
+
attributes,
|
|
236
|
+
parent=self.context_spans[-1].span if len(self.context_spans) > 0 else None,
|
|
237
|
+
)
|
|
238
|
+
# Activate the current span
|
|
239
|
+
cm = use_span(
|
|
240
|
+
span,
|
|
241
|
+
end_on_exit=False,
|
|
242
|
+
record_exception=False,
|
|
243
|
+
set_status_on_exception=False,
|
|
222
244
|
)
|
|
223
|
-
self.
|
|
245
|
+
self.context_spans.append(ContextSpan(span, cm))
|
|
246
|
+
cm.__enter__()
|
|
224
247
|
|
|
225
248
|
def _end_span(self, exc_value: Optional[BaseException]) -> None:
|
|
249
|
+
context_span = self.context_spans.pop()
|
|
226
250
|
if exc_value is None:
|
|
227
|
-
|
|
251
|
+
context_span.span.set_status(Status(StatusCode.OK))
|
|
228
252
|
else:
|
|
229
|
-
|
|
253
|
+
context_span.span.set_status(
|
|
230
254
|
Status(StatusCode.ERROR, description=str(exc_value))
|
|
231
255
|
)
|
|
232
|
-
dbos_tracer.end_span(
|
|
256
|
+
dbos_tracer.end_span(context_span.span)
|
|
257
|
+
context_span.context_manager.__exit__(None, None, None)
|
|
233
258
|
|
|
234
259
|
def set_authentication(
|
|
235
260
|
self, user: Optional[str], roles: Optional[List[str]]
|
|
236
261
|
) -> None:
|
|
237
262
|
self.authenticated_user = user
|
|
238
263
|
self.authenticated_roles = roles
|
|
239
|
-
if user is not None and len(self.
|
|
240
|
-
self.
|
|
241
|
-
self.
|
|
264
|
+
if user is not None and len(self.context_spans) > 0:
|
|
265
|
+
self.context_spans[-1].span.set_attribute("authenticatedUser", user)
|
|
266
|
+
self.context_spans[-1].span.set_attribute(
|
|
242
267
|
"authenticatedUserRoles", json.dumps(roles) if roles is not None else ""
|
|
243
268
|
)
|
|
244
269
|
|
|
@@ -126,7 +126,7 @@ class DBOSDeadLetterQueueError(DBOSException):
|
|
|
126
126
|
|
|
127
127
|
def __init__(self, wf_id: str, max_retries: int):
|
|
128
128
|
super().__init__(
|
|
129
|
-
f"Workflow {wf_id} has been moved to the dead-letter queue after exceeding the maximum of
|
|
129
|
+
f"Workflow {wf_id} has been moved to the dead-letter queue after exceeding the maximum of {max_retries} retries",
|
|
130
130
|
dbos_error_code=DBOSErrorCode.DeadLetterQueueError.value,
|
|
131
131
|
)
|
|
132
132
|
|
|
@@ -437,7 +437,14 @@ class SystemDatabase:
|
|
|
437
437
|
|
|
438
438
|
# Values to update when a row already exists for this workflow
|
|
439
439
|
update_values: dict[str, Any] = {
|
|
440
|
-
"recovery_attempts":
|
|
440
|
+
"recovery_attempts": sa.case(
|
|
441
|
+
(
|
|
442
|
+
SystemSchema.workflow_status.c.status
|
|
443
|
+
!= WorkflowStatusString.ENQUEUED.value,
|
|
444
|
+
SystemSchema.workflow_status.c.recovery_attempts + 1,
|
|
445
|
+
),
|
|
446
|
+
else_=SystemSchema.workflow_status.c.recovery_attempts,
|
|
447
|
+
),
|
|
441
448
|
"updated_at": func.extract("epoch", func.now()) * 1000,
|
|
442
449
|
}
|
|
443
450
|
# Don't update an existing executor ID when enqueueing a workflow.
|
|
@@ -1016,6 +1016,13 @@ def test_dlq_enqueued_workflows(dbos: DBOS) -> None:
|
|
|
1016
1016
|
blocked_handle = queue.enqueue(blocked_workflow)
|
|
1017
1017
|
regular_handle = queue.enqueue(regular_workflow)
|
|
1018
1018
|
|
|
1019
|
+
# Enqueue the blocked workflow repeatedly, verify recovery attempts is not increased
|
|
1020
|
+
for _ in range(max_recovery_attempts):
|
|
1021
|
+
with SetWorkflowID(blocked_handle.workflow_id):
|
|
1022
|
+
queue.enqueue(blocked_workflow)
|
|
1023
|
+
recovery_attempts = blocked_handle.get_status().recovery_attempts
|
|
1024
|
+
assert recovery_attempts is not None and recovery_attempts <= 1
|
|
1025
|
+
|
|
1019
1026
|
# Verify that the blocked workflow starts and is PENDING while the regular workflow remains ENQUEUED.
|
|
1020
1027
|
start_event.wait()
|
|
1021
1028
|
assert blocked_handle.get_status().status == WorkflowStatusString.PENDING.value
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
from typing import Tuple
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from fastapi import FastAPI
|
|
5
|
+
from fastapi.testclient import TestClient
|
|
6
|
+
from opentelemetry._logs import set_logger_provider
|
|
7
|
+
from opentelemetry.sdk import trace as tracesdk
|
|
8
|
+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
9
|
+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, InMemoryLogExporter
|
|
10
|
+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
11
|
+
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
|
12
|
+
from opentelemetry.trace.span import format_trace_id
|
|
13
|
+
|
|
14
|
+
from dbos import DBOS, DBOSConfig
|
|
15
|
+
from dbos._logger import dbos_logger
|
|
16
|
+
from dbos._tracer import dbos_tracer
|
|
17
|
+
from dbos._utils import GlobalParams
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_spans(config: DBOSConfig) -> None:
|
|
21
|
+
DBOS.destroy(destroy_registry=True)
|
|
22
|
+
config["otlp_attributes"] = {"foo": "bar"}
|
|
23
|
+
DBOS(config=config)
|
|
24
|
+
DBOS.launch()
|
|
25
|
+
|
|
26
|
+
@DBOS.workflow()
|
|
27
|
+
def test_workflow() -> None:
|
|
28
|
+
test_step()
|
|
29
|
+
current_span = DBOS.span
|
|
30
|
+
subspan = DBOS.tracer.start_span({"name": "a new span"}, parent=current_span)
|
|
31
|
+
# Note: DBOS.tracer.start_span() does not set the new span as the current span. So this log is still attached to the workflow span.
|
|
32
|
+
DBOS.logger.info("This is a test_workflow")
|
|
33
|
+
subspan.add_event("greeting_event", {"name": "a new event"})
|
|
34
|
+
DBOS.tracer.end_span(subspan)
|
|
35
|
+
|
|
36
|
+
@DBOS.step()
|
|
37
|
+
def test_step() -> None:
|
|
38
|
+
DBOS.logger.info("This is a test_step")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
exporter = InMemorySpanExporter()
|
|
42
|
+
span_processor = SimpleSpanProcessor(exporter)
|
|
43
|
+
provider = tracesdk.TracerProvider()
|
|
44
|
+
provider.add_span_processor(span_processor)
|
|
45
|
+
dbos_tracer.set_provider(provider)
|
|
46
|
+
|
|
47
|
+
# Set up in-memory log exporter
|
|
48
|
+
log_exporter = InMemoryLogExporter() # type: ignore
|
|
49
|
+
log_processor = BatchLogRecordProcessor(log_exporter)
|
|
50
|
+
log_provider = LoggerProvider()
|
|
51
|
+
log_provider.add_log_record_processor(log_processor)
|
|
52
|
+
set_logger_provider(log_provider)
|
|
53
|
+
dbos_logger.addHandler(LoggingHandler(logger_provider=log_provider))
|
|
54
|
+
|
|
55
|
+
test_workflow()
|
|
56
|
+
test_step()
|
|
57
|
+
|
|
58
|
+
log_processor.force_flush(timeout_millis=5000)
|
|
59
|
+
logs = log_exporter.get_finished_logs()
|
|
60
|
+
assert len(logs) == 3
|
|
61
|
+
for log in logs:
|
|
62
|
+
assert log.log_record.attributes is not None
|
|
63
|
+
assert (
|
|
64
|
+
log.log_record.attributes["applicationVersion"] == GlobalParams.app_version
|
|
65
|
+
)
|
|
66
|
+
assert log.log_record.attributes["executorID"] == GlobalParams.executor_id
|
|
67
|
+
assert log.log_record.attributes["foo"] == "bar"
|
|
68
|
+
# Make sure the log record has a span_id and trace_id
|
|
69
|
+
assert log.log_record.span_id is not None and log.log_record.span_id > 0
|
|
70
|
+
assert log.log_record.trace_id is not None and log.log_record.trace_id > 0
|
|
71
|
+
assert (
|
|
72
|
+
log.log_record.body == "This is a test_step"
|
|
73
|
+
or log.log_record.body == "This is a test_workflow"
|
|
74
|
+
)
|
|
75
|
+
assert log.log_record.attributes["traceId"] == format_trace_id(
|
|
76
|
+
log.log_record.trace_id
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
spans = exporter.get_finished_spans()
|
|
80
|
+
|
|
81
|
+
assert len(spans) == 5
|
|
82
|
+
|
|
83
|
+
for span in spans:
|
|
84
|
+
assert span.attributes is not None
|
|
85
|
+
assert span.attributes["applicationVersion"] == GlobalParams.app_version
|
|
86
|
+
assert span.attributes["executorID"] == GlobalParams.executor_id
|
|
87
|
+
assert span.context is not None
|
|
88
|
+
assert span.attributes["foo"] == "bar"
|
|
89
|
+
assert span.context.span_id > 0
|
|
90
|
+
assert span.context.trace_id > 0
|
|
91
|
+
|
|
92
|
+
assert spans[0].name == test_step.__qualname__
|
|
93
|
+
assert spans[1].name == "a new span"
|
|
94
|
+
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
|
+
|
|
98
|
+
assert spans[0].parent.span_id == spans[2].context.span_id # type: ignore
|
|
99
|
+
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
100
|
+
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
|
+
|
|
104
|
+
# Span ID and trace ID should match the log record
|
|
105
|
+
# For pyright
|
|
106
|
+
assert spans[0].context is not None
|
|
107
|
+
assert spans[2].context is not None
|
|
108
|
+
assert spans[3].context is not None
|
|
109
|
+
assert logs[0].log_record.span_id == spans[0].context.span_id
|
|
110
|
+
assert logs[0].log_record.trace_id == spans[0].context.trace_id
|
|
111
|
+
assert logs[1].log_record.span_id == spans[2].context.span_id
|
|
112
|
+
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
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.asyncio
|
|
118
|
+
async def test_spans_async(dbos: DBOS) -> None:
|
|
119
|
+
|
|
120
|
+
@DBOS.workflow()
|
|
121
|
+
async def test_workflow() -> None:
|
|
122
|
+
await test_step()
|
|
123
|
+
current_span = DBOS.span
|
|
124
|
+
subspan = DBOS.tracer.start_span({"name": "a new span"}, parent=current_span)
|
|
125
|
+
# Note: DBOS.tracer.start_span() does not set the new span as the current span. So this log is still attached to the workflow span.
|
|
126
|
+
DBOS.logger.info("This is a test_workflow")
|
|
127
|
+
subspan.add_event("greeting_event", {"name": "a new event"})
|
|
128
|
+
DBOS.tracer.end_span(subspan)
|
|
129
|
+
|
|
130
|
+
@DBOS.step()
|
|
131
|
+
async def test_step() -> None:
|
|
132
|
+
DBOS.logger.info("This is a test_step")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
exporter = InMemorySpanExporter()
|
|
136
|
+
span_processor = SimpleSpanProcessor(exporter)
|
|
137
|
+
provider = tracesdk.TracerProvider()
|
|
138
|
+
provider.add_span_processor(span_processor)
|
|
139
|
+
dbos_tracer.set_provider(provider)
|
|
140
|
+
|
|
141
|
+
# Set up in-memory log exporter
|
|
142
|
+
log_exporter = InMemoryLogExporter() # type: ignore
|
|
143
|
+
log_processor = BatchLogRecordProcessor(log_exporter)
|
|
144
|
+
log_provider = LoggerProvider()
|
|
145
|
+
log_provider.add_log_record_processor(log_processor)
|
|
146
|
+
set_logger_provider(log_provider)
|
|
147
|
+
dbos_logger.addHandler(LoggingHandler(logger_provider=log_provider))
|
|
148
|
+
|
|
149
|
+
await test_workflow()
|
|
150
|
+
await test_step()
|
|
151
|
+
|
|
152
|
+
log_processor.force_flush(timeout_millis=5000)
|
|
153
|
+
logs = log_exporter.get_finished_logs()
|
|
154
|
+
assert len(logs) == 3
|
|
155
|
+
for log in logs:
|
|
156
|
+
assert log.log_record.attributes is not None
|
|
157
|
+
assert (
|
|
158
|
+
log.log_record.attributes["applicationVersion"] == GlobalParams.app_version
|
|
159
|
+
)
|
|
160
|
+
assert log.log_record.attributes["executorID"] == GlobalParams.executor_id
|
|
161
|
+
# Make sure the log record has a span_id and trace_id
|
|
162
|
+
assert log.log_record.span_id is not None and log.log_record.span_id > 0
|
|
163
|
+
assert log.log_record.trace_id is not None and log.log_record.trace_id > 0
|
|
164
|
+
assert (
|
|
165
|
+
log.log_record.body == "This is a test_step"
|
|
166
|
+
or log.log_record.body == "This is a test_workflow"
|
|
167
|
+
)
|
|
168
|
+
assert log.log_record.attributes["traceId"] == format_trace_id(
|
|
169
|
+
log.log_record.trace_id
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
spans = exporter.get_finished_spans()
|
|
173
|
+
|
|
174
|
+
assert len(spans) == 5
|
|
175
|
+
|
|
176
|
+
for span in spans:
|
|
177
|
+
assert span.attributes is not None
|
|
178
|
+
assert span.attributes["applicationVersion"] == GlobalParams.app_version
|
|
179
|
+
assert span.attributes["executorID"] == GlobalParams.executor_id
|
|
180
|
+
assert span.context is not None
|
|
181
|
+
assert span.context.span_id > 0
|
|
182
|
+
assert span.context.trace_id > 0
|
|
183
|
+
|
|
184
|
+
assert spans[0].name == test_step.__qualname__
|
|
185
|
+
assert spans[1].name == "a new span"
|
|
186
|
+
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
|
+
|
|
190
|
+
assert spans[0].parent.span_id == spans[2].context.span_id # type: ignore
|
|
191
|
+
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
192
|
+
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
|
+
|
|
196
|
+
# Span ID and trace ID should match the log record
|
|
197
|
+
assert spans[0].context is not None
|
|
198
|
+
assert spans[2].context is not None
|
|
199
|
+
assert spans[3].context is not None
|
|
200
|
+
assert logs[0].log_record.span_id == spans[0].context.span_id
|
|
201
|
+
assert logs[0].log_record.trace_id == spans[0].context.trace_id
|
|
202
|
+
assert logs[1].log_record.span_id == spans[2].context.span_id
|
|
203
|
+
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
|
+
|
|
207
|
+
|
|
208
|
+
def test_temp_wf_fastapi(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
209
|
+
dbos, app = dbos_fastapi
|
|
210
|
+
|
|
211
|
+
@app.get("/step")
|
|
212
|
+
@DBOS.step()
|
|
213
|
+
def test_step_endpoint() -> str:
|
|
214
|
+
dbos.logger.info("This is a test_step_endpoint")
|
|
215
|
+
return "test"
|
|
216
|
+
|
|
217
|
+
exporter = InMemorySpanExporter()
|
|
218
|
+
span_processor = SimpleSpanProcessor(exporter)
|
|
219
|
+
provider = tracesdk.TracerProvider()
|
|
220
|
+
provider.add_span_processor(span_processor)
|
|
221
|
+
dbos_tracer.set_provider(provider)
|
|
222
|
+
|
|
223
|
+
# Set up in-memory log exporter
|
|
224
|
+
log_exporter = InMemoryLogExporter() # type: ignore
|
|
225
|
+
log_processor = BatchLogRecordProcessor(log_exporter)
|
|
226
|
+
log_provider = LoggerProvider()
|
|
227
|
+
log_provider.add_log_record_processor(log_processor)
|
|
228
|
+
set_logger_provider(log_provider)
|
|
229
|
+
dbos_logger.addHandler(LoggingHandler(logger_provider=log_provider))
|
|
230
|
+
|
|
231
|
+
client = TestClient(app)
|
|
232
|
+
response = client.get("/step")
|
|
233
|
+
assert response.status_code == 200
|
|
234
|
+
assert response.text == '"test"'
|
|
235
|
+
|
|
236
|
+
log_processor.force_flush(timeout_millis=5000)
|
|
237
|
+
logs = log_exporter.get_finished_logs()
|
|
238
|
+
assert len(logs) == 1
|
|
239
|
+
assert logs[0].log_record.attributes is not None
|
|
240
|
+
assert (
|
|
241
|
+
logs[0].log_record.attributes["applicationVersion"] == GlobalParams.app_version
|
|
242
|
+
)
|
|
243
|
+
assert logs[0].log_record.span_id is not None and logs[0].log_record.span_id > 0
|
|
244
|
+
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 test_step_endpoint"
|
|
246
|
+
assert logs[0].log_record.attributes["traceId"] == format_trace_id(
|
|
247
|
+
logs[0].log_record.trace_id
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
spans = exporter.get_finished_spans()
|
|
251
|
+
|
|
252
|
+
assert len(spans) == 3
|
|
253
|
+
|
|
254
|
+
for span in spans:
|
|
255
|
+
assert span.attributes is not None
|
|
256
|
+
assert span.attributes["applicationVersion"] == GlobalParams.app_version
|
|
257
|
+
assert span.context is not None
|
|
258
|
+
assert span.context.span_id > 0
|
|
259
|
+
assert span.context.trace_id > 0
|
|
260
|
+
|
|
261
|
+
assert spans[0].name == test_step_endpoint.__qualname__
|
|
262
|
+
assert spans[1].name == f"<temp>.{test_step_endpoint.__qualname__}"
|
|
263
|
+
assert spans[2].name == "/step"
|
|
264
|
+
|
|
265
|
+
assert spans[0].parent.span_id == spans[1].context.span_id # type: ignore
|
|
266
|
+
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
267
|
+
assert spans[2].parent == None
|
|
268
|
+
|
|
269
|
+
# Span ID and trace ID should match the log record
|
|
270
|
+
assert spans[0].context is not None
|
|
271
|
+
assert logs[0].log_record.span_id == spans[0].context.span_id
|
|
272
|
+
assert logs[0].log_record.trace_id == spans[0].context.trace_id
|
dbos-1.8.0a1/tests/test_spans.py
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
from typing import Tuple
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from fastapi import FastAPI
|
|
5
|
-
from fastapi.testclient import TestClient
|
|
6
|
-
from opentelemetry.sdk import trace as tracesdk
|
|
7
|
-
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
8
|
-
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
|
9
|
-
|
|
10
|
-
from dbos import DBOS, DBOSConfig
|
|
11
|
-
from dbos._tracer import dbos_tracer
|
|
12
|
-
from dbos._utils import GlobalParams
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_spans(config: DBOSConfig) -> None:
|
|
16
|
-
DBOS.destroy(destroy_registry=True)
|
|
17
|
-
config["otlp_attributes"] = {"foo": "bar"}
|
|
18
|
-
DBOS(config=config)
|
|
19
|
-
DBOS.launch()
|
|
20
|
-
|
|
21
|
-
@DBOS.workflow()
|
|
22
|
-
def test_workflow() -> None:
|
|
23
|
-
test_step()
|
|
24
|
-
current_span = DBOS.span
|
|
25
|
-
subspan = DBOS.tracer.start_span({"name": "a new span"}, parent=current_span)
|
|
26
|
-
subspan.add_event("greeting_event", {"name": "a new event"})
|
|
27
|
-
DBOS.tracer.end_span(subspan)
|
|
28
|
-
|
|
29
|
-
@DBOS.step()
|
|
30
|
-
def test_step() -> None:
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
exporter = InMemorySpanExporter()
|
|
34
|
-
span_processor = SimpleSpanProcessor(exporter)
|
|
35
|
-
provider = tracesdk.TracerProvider()
|
|
36
|
-
provider.add_span_processor(span_processor)
|
|
37
|
-
dbos_tracer.set_provider(provider)
|
|
38
|
-
|
|
39
|
-
test_workflow()
|
|
40
|
-
test_step()
|
|
41
|
-
|
|
42
|
-
spans = exporter.get_finished_spans()
|
|
43
|
-
|
|
44
|
-
assert len(spans) == 5
|
|
45
|
-
|
|
46
|
-
for span in spans:
|
|
47
|
-
assert span.attributes is not None
|
|
48
|
-
assert span.attributes["applicationVersion"] == GlobalParams.app_version
|
|
49
|
-
assert span.attributes["executorID"] == GlobalParams.executor_id
|
|
50
|
-
assert span.context is not None
|
|
51
|
-
assert span.attributes["foo"] == "bar"
|
|
52
|
-
|
|
53
|
-
assert spans[0].name == test_step.__qualname__
|
|
54
|
-
assert spans[1].name == "a new span"
|
|
55
|
-
assert spans[2].name == test_workflow.__qualname__
|
|
56
|
-
assert spans[3].name == test_step.__qualname__
|
|
57
|
-
assert spans[4].name == f"<temp>.{test_step.__qualname__}"
|
|
58
|
-
|
|
59
|
-
assert spans[0].parent.span_id == spans[2].context.span_id # type: ignore
|
|
60
|
-
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
61
|
-
assert spans[2].parent == None
|
|
62
|
-
assert spans[3].parent.span_id == spans[4].context.span_id # type: ignore
|
|
63
|
-
assert spans[4].parent == None
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@pytest.mark.asyncio
|
|
67
|
-
async def test_spans_async(dbos: DBOS) -> None:
|
|
68
|
-
|
|
69
|
-
@DBOS.workflow()
|
|
70
|
-
async def test_workflow() -> None:
|
|
71
|
-
await test_step()
|
|
72
|
-
current_span = DBOS.span
|
|
73
|
-
subspan = DBOS.tracer.start_span({"name": "a new span"}, parent=current_span)
|
|
74
|
-
subspan.add_event("greeting_event", {"name": "a new event"})
|
|
75
|
-
DBOS.tracer.end_span(subspan)
|
|
76
|
-
|
|
77
|
-
@DBOS.step()
|
|
78
|
-
async def test_step() -> None:
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
exporter = InMemorySpanExporter()
|
|
82
|
-
span_processor = SimpleSpanProcessor(exporter)
|
|
83
|
-
provider = tracesdk.TracerProvider()
|
|
84
|
-
provider.add_span_processor(span_processor)
|
|
85
|
-
dbos_tracer.set_provider(provider)
|
|
86
|
-
|
|
87
|
-
await test_workflow()
|
|
88
|
-
await test_step()
|
|
89
|
-
|
|
90
|
-
spans = exporter.get_finished_spans()
|
|
91
|
-
|
|
92
|
-
assert len(spans) == 5
|
|
93
|
-
|
|
94
|
-
for span in spans:
|
|
95
|
-
assert span.attributes is not None
|
|
96
|
-
assert span.attributes["applicationVersion"] == GlobalParams.app_version
|
|
97
|
-
assert span.attributes["executorID"] == GlobalParams.executor_id
|
|
98
|
-
assert span.context is not None
|
|
99
|
-
|
|
100
|
-
assert spans[0].name == test_step.__qualname__
|
|
101
|
-
assert spans[1].name == "a new span"
|
|
102
|
-
assert spans[2].name == test_workflow.__qualname__
|
|
103
|
-
assert spans[3].name == test_step.__qualname__
|
|
104
|
-
assert spans[4].name == f"<temp>.{test_step.__qualname__}"
|
|
105
|
-
|
|
106
|
-
assert spans[0].parent.span_id == spans[2].context.span_id # type: ignore
|
|
107
|
-
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
108
|
-
assert spans[2].parent == None
|
|
109
|
-
assert spans[3].parent.span_id == spans[4].context.span_id # type: ignore
|
|
110
|
-
assert spans[4].parent == None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def test_temp_wf_fastapi(dbos_fastapi: Tuple[DBOS, FastAPI]) -> None:
|
|
114
|
-
dbos, app = dbos_fastapi
|
|
115
|
-
|
|
116
|
-
@app.get("/step")
|
|
117
|
-
@DBOS.step()
|
|
118
|
-
def test_step_endpoint() -> str:
|
|
119
|
-
return "test"
|
|
120
|
-
|
|
121
|
-
exporter = InMemorySpanExporter()
|
|
122
|
-
span_processor = SimpleSpanProcessor(exporter)
|
|
123
|
-
provider = tracesdk.TracerProvider()
|
|
124
|
-
provider.add_span_processor(span_processor)
|
|
125
|
-
dbos_tracer.set_provider(provider)
|
|
126
|
-
|
|
127
|
-
client = TestClient(app)
|
|
128
|
-
response = client.get("/step")
|
|
129
|
-
assert response.status_code == 200
|
|
130
|
-
assert response.text == '"test"'
|
|
131
|
-
|
|
132
|
-
spans = exporter.get_finished_spans()
|
|
133
|
-
|
|
134
|
-
assert len(spans) == 3
|
|
135
|
-
|
|
136
|
-
for span in spans:
|
|
137
|
-
assert span.attributes is not None
|
|
138
|
-
assert span.attributes["applicationVersion"] == GlobalParams.app_version
|
|
139
|
-
assert span.context is not None
|
|
140
|
-
|
|
141
|
-
assert spans[0].name == test_step_endpoint.__qualname__
|
|
142
|
-
assert spans[1].name == f"<temp>.{test_step_endpoint.__qualname__}"
|
|
143
|
-
assert spans[2].name == "/step"
|
|
144
|
-
|
|
145
|
-
assert spans[0].parent.span_id == spans[1].context.span_id # type: ignore
|
|
146
|
-
assert spans[1].parent.span_id == spans[2].context.span_id # type: ignore
|
|
147
|
-
assert spans[2].parent == 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
|
{dbos-1.8.0a1 → dbos-1.8.0a3}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|