vellum-ai 0.14.3__py3-none-any.whl → 0.14.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/resources/document_indexes/client.py +4 -4
- vellum/client/resources/documents/client.py +0 -2
- vellum/client/resources/folder_entities/client.py +4 -8
- vellum/client/resources/test_suite_runs/client.py +0 -2
- vellum/client/types/deployment_read.py +5 -5
- vellum/client/types/deployment_release_tag_read.py +2 -2
- vellum/client/types/document_document_to_document_index.py +5 -5
- vellum/client/types/document_index_read.py +5 -5
- vellum/client/types/document_read.py +1 -1
- vellum/client/types/enriched_normalized_completion.py +3 -3
- vellum/client/types/generate_options_request.py +2 -2
- vellum/client/types/slim_deployment_read.py +5 -5
- vellum/client/types/slim_document.py +3 -3
- vellum/client/types/slim_document_document_to_document_index.py +5 -5
- vellum/client/types/slim_workflow_deployment.py +5 -5
- vellum/client/types/test_suite_run_read.py +5 -5
- vellum/client/types/workflow_deployment_read.py +5 -5
- vellum/client/types/workflow_release_tag_read.py +2 -2
- vellum/workflows/constants.py +9 -0
- vellum/workflows/context.py +8 -3
- vellum/workflows/nodes/core/map_node/node.py +1 -1
- vellum/workflows/nodes/core/retry_node/node.py +4 -3
- vellum/workflows/nodes/core/try_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -0
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +81 -1
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +44 -20
- vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +17 -10
- vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +1 -0
- vellum/workflows/tests/test_undefined.py +12 -0
- vellum/workflows/workflows/base.py +76 -0
- vellum/workflows/workflows/tests/test_base_workflow.py +135 -0
- vellum/workflows/workflows/tests/test_context.py +60 -0
- {vellum_ai-0.14.3.dist-info → vellum_ai-0.14.5.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.3.dist-info → vellum_ai-0.14.5.dist-info}/RECORD +47 -44
- vellum_ee/workflows/display/nodes/__init__.py +4 -0
- vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +39 -0
- vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/retry_node.py +36 -4
- vellum_ee/workflows/display/nodes/vellum/try_node.py +43 -29
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +25 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +14 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +19 -1
- {vellum_ai-0.14.3.dist-info → vellum_ai-0.14.5.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.3.dist-info → vellum_ai-0.14.5.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.3.dist-info → vellum_ai-0.14.5.dist-info}/entry_points.txt +0 -0
@@ -67,6 +67,49 @@ def _clean_for_dict_wrapper(obj):
|
|
67
67
|
return obj
|
68
68
|
|
69
69
|
|
70
|
+
def _get_type_name(obj: Any) -> str:
|
71
|
+
if isinstance(obj, type):
|
72
|
+
return obj.__name__
|
73
|
+
|
74
|
+
if get_origin(obj) is Union:
|
75
|
+
children = [_get_type_name(child) for child in get_args(obj)]
|
76
|
+
return " | ".join(children)
|
77
|
+
|
78
|
+
return str(obj)
|
79
|
+
|
80
|
+
|
81
|
+
def _cast_to_output_type(result: Any, output_type: Any) -> Any:
|
82
|
+
is_valid_output_type = isinstance(output_type, type)
|
83
|
+
if get_origin(output_type) is Union:
|
84
|
+
allowed_types = get_args(output_type)
|
85
|
+
for allowed_type in allowed_types:
|
86
|
+
try:
|
87
|
+
return _cast_to_output_type(result, allowed_type)
|
88
|
+
except NodeException:
|
89
|
+
continue
|
90
|
+
elif get_origin(output_type) is list:
|
91
|
+
allowed_item_type = get_args(output_type)[0]
|
92
|
+
if isinstance(result, list):
|
93
|
+
return [_cast_to_output_type(item, allowed_item_type) for item in result]
|
94
|
+
elif is_valid_output_type and issubclass(output_type, BaseModel) and not isinstance(result, output_type):
|
95
|
+
try:
|
96
|
+
return output_type.model_validate(result)
|
97
|
+
except ValidationError as e:
|
98
|
+
raise NodeException(
|
99
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
100
|
+
message=re.sub(r"\s+For further information visit [^\s]+", "", str(e)),
|
101
|
+
) from e
|
102
|
+
elif is_valid_output_type and isinstance(result, output_type):
|
103
|
+
return result
|
104
|
+
|
105
|
+
output_type_name = _get_type_name(output_type)
|
106
|
+
result_type_name = _get_type_name(type(result))
|
107
|
+
raise NodeException(
|
108
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
109
|
+
message=f"Expected an output of type '{output_type_name}', but received '{result_type_name}'",
|
110
|
+
)
|
111
|
+
|
112
|
+
|
70
113
|
def run_code_inline(
|
71
114
|
code: str,
|
72
115
|
inputs: EntityInputsInterface,
|
@@ -112,25 +155,6 @@ __arg__out = main({", ".join(run_args)})
|
|
112
155
|
result = exec_globals["__arg__out"]
|
113
156
|
|
114
157
|
if output_type != Any:
|
115
|
-
|
116
|
-
allowed_types = get_args(output_type)
|
117
|
-
if not isinstance(result, allowed_types):
|
118
|
-
raise NodeException(
|
119
|
-
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
120
|
-
message=f"Expected output to be in types {allowed_types}, but received '{type(result).__name__}'",
|
121
|
-
)
|
122
|
-
elif issubclass(output_type, BaseModel) and not isinstance(result, output_type):
|
123
|
-
try:
|
124
|
-
result = output_type.model_validate(result)
|
125
|
-
except ValidationError as e:
|
126
|
-
raise NodeException(
|
127
|
-
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
128
|
-
message=re.sub(r"\s+For further information visit [^\s]+", "", str(e)),
|
129
|
-
) from e
|
130
|
-
elif not isinstance(result, output_type):
|
131
|
-
raise NodeException(
|
132
|
-
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
133
|
-
message=f"Expected an output of type '{output_type.__name__}', but received '{type(result).__name__}'",
|
134
|
-
)
|
158
|
+
result = _cast_to_output_type(result, output_type)
|
135
159
|
|
136
160
|
return logs, result
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import json
|
1
2
|
from typing import Iterator
|
2
3
|
|
3
4
|
from vellum.workflows.errors import WorkflowErrorCode
|
@@ -44,13 +45,19 @@ class PromptDeploymentNode(BasePromptDeploymentNode[StateType]):
|
|
44
45
|
code=WorkflowErrorCode.INTERNAL_ERROR,
|
45
46
|
)
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
48
|
+
string_outputs = []
|
49
|
+
for output in outputs:
|
50
|
+
if output.value is None:
|
51
|
+
continue
|
52
|
+
|
53
|
+
if output.type == "STRING":
|
54
|
+
string_outputs.append(output.value)
|
55
|
+
elif output.type == "JSON":
|
56
|
+
string_outputs.append(json.dumps(output.value, indent=4))
|
57
|
+
elif output.type == "FUNCTION_CALL":
|
58
|
+
string_outputs.append(output.value.model_dump_json(indent=4))
|
59
|
+
else:
|
60
|
+
string_outputs.append(output.value.message)
|
61
|
+
|
62
|
+
value = "\n".join(string_outputs)
|
63
|
+
yield BaseOutput(name="text", value=value)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from vellum.workflows.constants import undefined
|
4
|
+
|
5
|
+
|
6
|
+
def test_undefined__ensure_sensible_error_messages():
|
7
|
+
# WHEN we invoke an invalid operation on `undefined`
|
8
|
+
with pytest.raises(Exception) as e:
|
9
|
+
len(undefined)
|
10
|
+
|
11
|
+
# THEN we get a sensible error message
|
12
|
+
assert str(e.value) == "object of type 'undefined' has no len()"
|
@@ -100,6 +100,30 @@ class _BaseWorkflowMeta(type):
|
|
100
100
|
new_dct,
|
101
101
|
)
|
102
102
|
|
103
|
+
def collect_nodes(graph_item: Union[GraphAttribute, Set[GraphAttribute]]) -> Set[Type[BaseNode]]:
|
104
|
+
nodes: Set[Type[BaseNode]] = set()
|
105
|
+
if isinstance(graph_item, Graph):
|
106
|
+
nodes.update(node for node in graph_item.nodes)
|
107
|
+
elif isinstance(graph_item, set):
|
108
|
+
for item in graph_item:
|
109
|
+
if isinstance(item, Graph):
|
110
|
+
nodes.update(node for node in item.nodes)
|
111
|
+
elif inspect.isclass(item) and issubclass(item, BaseNode):
|
112
|
+
nodes.add(item)
|
113
|
+
elif issubclass(graph_item, BaseNode):
|
114
|
+
nodes.add(graph_item)
|
115
|
+
else:
|
116
|
+
raise ValueError(f"Unexpected graph type: {graph_item.__class__}")
|
117
|
+
return nodes
|
118
|
+
|
119
|
+
graph_nodes = collect_nodes(dct.get("graph", set()))
|
120
|
+
unused_nodes = collect_nodes(dct.get("unused_graphs", set()))
|
121
|
+
|
122
|
+
overlap = graph_nodes & unused_nodes
|
123
|
+
if overlap:
|
124
|
+
node_names = [node.__name__ for node in overlap]
|
125
|
+
raise ValueError(f"Node(s) {', '.join(node_names)} cannot appear in both graph and unused_graphs")
|
126
|
+
|
103
127
|
cls = super().__new__(mcs, name, bases, dct)
|
104
128
|
workflow_class = cast(Type["BaseWorkflow"], cls)
|
105
129
|
workflow_class.__id__ = uuid4_from_hash(workflow_class.__qualname__)
|
@@ -112,6 +136,7 @@ GraphAttribute = Union[Type[BaseNode], Graph, Set[Type[BaseNode]], Set[Graph]]
|
|
112
136
|
class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
113
137
|
__id__: UUID = uuid4_from_hash(__qualname__)
|
114
138
|
graph: ClassVar[GraphAttribute]
|
139
|
+
unused_graphs: ClassVar[Set[GraphAttribute]] # nodes or graphs that are defined but not used in the graph
|
115
140
|
emitters: List[BaseWorkflowEmitter]
|
116
141
|
resolvers: List[BaseWorkflowResolver]
|
117
142
|
|
@@ -196,6 +221,57 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
196
221
|
nodes.add(node)
|
197
222
|
yield node
|
198
223
|
|
224
|
+
@classmethod
|
225
|
+
def get_unused_subgraphs(cls) -> List[Graph]:
|
226
|
+
"""
|
227
|
+
Returns a list of subgraphs that are defined but not used in the graph
|
228
|
+
"""
|
229
|
+
if not hasattr(cls, "unused_graphs"):
|
230
|
+
return []
|
231
|
+
|
232
|
+
graphs = []
|
233
|
+
for item in cls.unused_graphs:
|
234
|
+
if isinstance(item, Graph):
|
235
|
+
graphs.append(item)
|
236
|
+
elif isinstance(item, set):
|
237
|
+
for subitem in item:
|
238
|
+
if isinstance(subitem, Graph):
|
239
|
+
graphs.append(subitem)
|
240
|
+
elif issubclass(subitem, BaseNode):
|
241
|
+
graphs.append(Graph.from_node(subitem))
|
242
|
+
elif issubclass(item, BaseNode):
|
243
|
+
graphs.append(Graph.from_node(item))
|
244
|
+
return graphs
|
245
|
+
|
246
|
+
@classmethod
|
247
|
+
def get_unused_nodes(cls) -> Iterator[Type[BaseNode]]:
|
248
|
+
"""
|
249
|
+
Returns an iterator over the nodes that are defined but not used in the graph.
|
250
|
+
"""
|
251
|
+
if not hasattr(cls, "unused_graphs"):
|
252
|
+
yield from ()
|
253
|
+
else:
|
254
|
+
nodes = set()
|
255
|
+
subgraphs = cls.get_unused_subgraphs()
|
256
|
+
for subgraph in subgraphs:
|
257
|
+
for node in subgraph.nodes:
|
258
|
+
if node not in nodes:
|
259
|
+
nodes.add(node)
|
260
|
+
yield node
|
261
|
+
|
262
|
+
@classmethod
|
263
|
+
def get_unused_edges(cls) -> Iterator[Edge]:
|
264
|
+
"""
|
265
|
+
Returns an iterator over edges that are defined but not used in the graph.
|
266
|
+
"""
|
267
|
+
edges = set()
|
268
|
+
subgraphs = cls.get_unused_subgraphs()
|
269
|
+
for subgraph in subgraphs:
|
270
|
+
for edge in subgraph.edges:
|
271
|
+
if edge not in edges:
|
272
|
+
edges.add(edge)
|
273
|
+
yield edge
|
274
|
+
|
199
275
|
@classmethod
|
200
276
|
def get_entrypoints(cls) -> Iterable[Type[BaseNode]]:
|
201
277
|
return iter({e for g in cls.get_subgraphs() for e in g.entrypoints})
|
@@ -1,3 +1,6 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from vellum.workflows.edges.edge import Edge
|
1
4
|
from vellum.workflows.inputs.base import BaseInputs
|
2
5
|
from vellum.workflows.nodes.bases.base import BaseNode
|
3
6
|
from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
|
@@ -78,3 +81,135 @@ def test_subworkflow__inherit_base_outputs():
|
|
78
81
|
# TEST that the outputs are correct
|
79
82
|
assert terminal_event.name == "workflow.execution.fulfilled", terminal_event
|
80
83
|
assert terminal_event.outputs == {"output": "bar"}
|
84
|
+
|
85
|
+
|
86
|
+
def test_workflow__nodes_not_in_graph():
|
87
|
+
class NodeA(BaseNode):
|
88
|
+
pass
|
89
|
+
|
90
|
+
class NodeB(BaseNode):
|
91
|
+
pass
|
92
|
+
|
93
|
+
class NodeC(BaseNode):
|
94
|
+
pass
|
95
|
+
|
96
|
+
# WHEN we create a workflow with multiple unused nodes
|
97
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
98
|
+
graph = NodeA
|
99
|
+
unused_graphs = {NodeB, NodeC}
|
100
|
+
|
101
|
+
# TEST that all nodes from unused_graphs are collected
|
102
|
+
unused_graphs = set(TestWorkflow.get_unused_nodes())
|
103
|
+
assert unused_graphs == {NodeB, NodeC}
|
104
|
+
|
105
|
+
|
106
|
+
def test_workflow__unused_graphs():
|
107
|
+
class NodeA(BaseNode):
|
108
|
+
pass
|
109
|
+
|
110
|
+
class NodeB(BaseNode):
|
111
|
+
pass
|
112
|
+
|
113
|
+
class NodeC(BaseNode):
|
114
|
+
pass
|
115
|
+
|
116
|
+
class NodeD(BaseNode):
|
117
|
+
pass
|
118
|
+
|
119
|
+
class NodeE(BaseNode):
|
120
|
+
pass
|
121
|
+
|
122
|
+
class NodeF(BaseNode):
|
123
|
+
pass
|
124
|
+
|
125
|
+
# WHEN we create a workflow with unused nodes in a graph
|
126
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
127
|
+
graph = NodeA
|
128
|
+
unused_graphs = {NodeB >> {NodeC >> NodeD}, NodeE, NodeF}
|
129
|
+
|
130
|
+
# TEST that all nodes from unused_graphs are collected
|
131
|
+
unused_graphs = set(TestWorkflow.get_unused_nodes())
|
132
|
+
assert unused_graphs == {NodeB, NodeC, NodeD, NodeE, NodeF}
|
133
|
+
|
134
|
+
|
135
|
+
def test_workflow__no_unused_nodes():
|
136
|
+
class NodeA(BaseNode):
|
137
|
+
pass
|
138
|
+
|
139
|
+
class NodeB(BaseNode):
|
140
|
+
pass
|
141
|
+
|
142
|
+
# WHEN we create a workflow with no unused nodes
|
143
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
144
|
+
graph = NodeA >> NodeB
|
145
|
+
|
146
|
+
# TEST that nodes not in the graph are empty
|
147
|
+
nodes = set(TestWorkflow.get_unused_nodes())
|
148
|
+
assert nodes == set()
|
149
|
+
|
150
|
+
|
151
|
+
def test_workflow__node_in_both_graph_and_unused():
|
152
|
+
class NodeA(BaseNode):
|
153
|
+
pass
|
154
|
+
|
155
|
+
class NodeB(BaseNode):
|
156
|
+
pass
|
157
|
+
|
158
|
+
class NodeC(BaseNode):
|
159
|
+
pass
|
160
|
+
|
161
|
+
# WHEN we try to create a workflow where NodeA appears in both graph and unused
|
162
|
+
with pytest.raises(ValueError) as exc_info:
|
163
|
+
|
164
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
165
|
+
graph = NodeA >> NodeB
|
166
|
+
unused_graphs = {NodeA >> NodeC}
|
167
|
+
|
168
|
+
# THEN it should raise an error
|
169
|
+
assert "Node(s) NodeA cannot appear in both graph and unused_graphs" in str(exc_info.value)
|
170
|
+
|
171
|
+
|
172
|
+
def test_workflow__get_unused_edges():
|
173
|
+
"""
|
174
|
+
Test that get_unused_edges correctly identifies edges that are defined but not used in the workflow graph.
|
175
|
+
"""
|
176
|
+
|
177
|
+
class NodeA(BaseNode):
|
178
|
+
pass
|
179
|
+
|
180
|
+
class NodeB(BaseNode):
|
181
|
+
pass
|
182
|
+
|
183
|
+
class NodeC(BaseNode):
|
184
|
+
pass
|
185
|
+
|
186
|
+
class NodeD(BaseNode):
|
187
|
+
pass
|
188
|
+
|
189
|
+
class NodeE(BaseNode):
|
190
|
+
pass
|
191
|
+
|
192
|
+
class NodeF(BaseNode):
|
193
|
+
pass
|
194
|
+
|
195
|
+
class NodeG(BaseNode):
|
196
|
+
pass
|
197
|
+
|
198
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
199
|
+
graph = NodeA >> NodeB
|
200
|
+
unused_graphs = {NodeC >> {NodeD >> NodeE, NodeF} >> NodeG}
|
201
|
+
|
202
|
+
edge_c_to_d = Edge(from_port=NodeC.Ports.default, to_node=NodeD)
|
203
|
+
edge_c_to_f = Edge(from_port=NodeC.Ports.default, to_node=NodeF)
|
204
|
+
edge_d_to_e = Edge(from_port=NodeD.Ports.default, to_node=NodeE)
|
205
|
+
edge_e_to_g = Edge(from_port=NodeE.Ports.default, to_node=NodeG)
|
206
|
+
edge_f_to_g = Edge(from_port=NodeF.Ports.default, to_node=NodeG)
|
207
|
+
|
208
|
+
# Collect unused edges
|
209
|
+
unused_edges = set(TestWorkflow.get_unused_edges())
|
210
|
+
|
211
|
+
# Expected unused edges
|
212
|
+
expected_unused_edges = {edge_c_to_d, edge_c_to_f, edge_d_to_e, edge_e_to_g, edge_f_to_g}
|
213
|
+
|
214
|
+
# TEST that unused edges are correctly identified
|
215
|
+
assert unused_edges == expected_unused_edges, f"Expected {expected_unused_edges}, but got {unused_edges}"
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from uuid import UUID, uuid4
|
2
|
+
|
3
|
+
from vellum.workflows import BaseWorkflow
|
4
|
+
from vellum.workflows.context import execution_context, get_execution_context
|
5
|
+
from vellum.workflows.events.types import NodeParentContext, WorkflowParentContext
|
6
|
+
from vellum.workflows.inputs import BaseInputs
|
7
|
+
from vellum.workflows.nodes import BaseNode
|
8
|
+
from vellum.workflows.references import VellumSecretReference
|
9
|
+
from vellum.workflows.state import BaseState
|
10
|
+
|
11
|
+
|
12
|
+
class MockInputs(BaseInputs):
|
13
|
+
foo: str
|
14
|
+
|
15
|
+
|
16
|
+
class MockNode(BaseNode):
|
17
|
+
node_foo = MockInputs.foo
|
18
|
+
node_secret = VellumSecretReference("secret")
|
19
|
+
|
20
|
+
class Outputs(BaseNode.Outputs):
|
21
|
+
example: str
|
22
|
+
|
23
|
+
|
24
|
+
class MockWorkflow(BaseWorkflow[MockInputs, BaseState]):
|
25
|
+
graph = MockNode
|
26
|
+
|
27
|
+
|
28
|
+
def test_context_trace_and_parent():
|
29
|
+
trace_id = uuid4()
|
30
|
+
parent_context = NodeParentContext(
|
31
|
+
node_definition=MockNode,
|
32
|
+
span_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
|
33
|
+
parent=WorkflowParentContext(
|
34
|
+
workflow_definition=MockWorkflow,
|
35
|
+
span_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
|
36
|
+
),
|
37
|
+
)
|
38
|
+
second_parent_context = WorkflowParentContext(
|
39
|
+
workflow_definition=MockWorkflow, span_id=uuid4(), parent=parent_context
|
40
|
+
)
|
41
|
+
# When using execution context , if we set trace id within
|
42
|
+
with execution_context(parent_context=parent_context, trace_id=trace_id):
|
43
|
+
test = get_execution_context()
|
44
|
+
assert test.trace_id == trace_id
|
45
|
+
assert test.parent_context == parent_context
|
46
|
+
with execution_context(parent_context=second_parent_context):
|
47
|
+
test1 = get_execution_context()
|
48
|
+
assert test1.trace_id == trace_id
|
49
|
+
assert test1.parent_context == second_parent_context
|
50
|
+
# then we can assume trace id will not change
|
51
|
+
with execution_context(trace_id=uuid4()):
|
52
|
+
test3 = get_execution_context()
|
53
|
+
assert test3.trace_id == trace_id
|
54
|
+
with execution_context(parent_context=parent_context, trace_id=uuid4()):
|
55
|
+
test3 = get_execution_context()
|
56
|
+
assert test3.trace_id == trace_id
|
57
|
+
# and if we have a new context, the trace will differ
|
58
|
+
with execution_context(parent_context=parent_context, trace_id=uuid4()):
|
59
|
+
test = get_execution_context()
|
60
|
+
assert test.trace_id != trace_id
|