smartcomment 0.1.0__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.
- smartcomment/__init__.py +49 -0
- smartcomment/analysis/__init__.py +1 -0
- smartcomment/analysis/visualization/__init__.py +43 -0
- smartcomment/analysis/visualization/_utils.py +103 -0
- smartcomment/analysis/visualization/graphviz.py +201 -0
- smartcomment/analysis/visualization/pyvis.py +155 -0
- smartcomment/api/__init__.py +18 -0
- smartcomment/api/_helpers.py +440 -0
- smartcomment/api/_mutation.py +318 -0
- smartcomment/api/public.py +971 -0
- smartcomment/debugging.py +69 -0
- smartcomment/drivers/__init__.py +12 -0
- smartcomment/drivers/base.py +309 -0
- smartcomment/drivers/in_memory.py +323 -0
- smartcomment/identity/__init__.py +6 -0
- smartcomment/identity/registry.py +214 -0
- smartcomment/logging.py +80 -0
- smartcomment/runtime/__init__.py +44 -0
- smartcomment/runtime/context.py +630 -0
- smartcomment/runtime/errors.py +117 -0
- smartcomment/runtime/graph.py +520 -0
- smartcomment/runtime/network.py +1841 -0
- smartcomment/runtime/operation.py +274 -0
- smartcomment/runtime/session.py +133 -0
- smartcomment/runtime/variable.py +237 -0
- smartcomment/schema/__init__.py +1 -0
- smartcomment/schema/base.py +134 -0
- smartcomment/schema/operation.py +96 -0
- smartcomment/schema/session.py +42 -0
- smartcomment/schema/variable.py +126 -0
- smartcomment-0.1.0.dist-info/METADATA +29 -0
- smartcomment-0.1.0.dist-info/RECORD +34 -0
- smartcomment-0.1.0.dist-info/WHEEL +5 -0
- smartcomment-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
"""The tracing context and context manager functions for smartcomment."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import inspect
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
from pydantic import JsonValue, PrivateAttr
|
|
8
|
+
from .network import ExecNetwork, none_session_id
|
|
9
|
+
from .operation import RuntimeOp
|
|
10
|
+
from .session import RuntimeSession
|
|
11
|
+
from .variable import RuntimeVariable
|
|
12
|
+
from ..schema.base import BaseMetadataModel
|
|
13
|
+
from ..schema.operation import OpRecord
|
|
14
|
+
from ..schema.session import Session
|
|
15
|
+
from ..logging import logger
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
Iterator,
|
|
19
|
+
Iterable,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Module-level context variables (one per hierarchy level).
|
|
24
|
+
_GRAPH: ContextVar[ExecNetwork | None] = ContextVar("_GRAPH", default=None)
|
|
25
|
+
_SESSION: ContextVar[Session | None] = ContextVar("_SESSION", default=None)
|
|
26
|
+
_OP: ContextVar[OpRecord | None] = ContextVar("_OP", default=None)
|
|
27
|
+
|
|
28
|
+
# Global toggle for tracing.
|
|
29
|
+
_TRACING_ENABLED: bool = True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TracingContext(BaseMetadataModel):
|
|
33
|
+
"""Per-scope context that tracks runtime variables and propagates metadata.
|
|
34
|
+
|
|
35
|
+
A tracing context is stored in the context variable and
|
|
36
|
+
provides read-access to the current graph, session, and operation
|
|
37
|
+
via properties that delegate to the module-level context variables.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_runtime_variables: dict[str, RuntimeVariable[Any]] = PrivateAttr(default_factory=dict)
|
|
41
|
+
_full_node_id_to_names: dict[str, set[str]] = PrivateAttr(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
def register_variable(
|
|
44
|
+
self,
|
|
45
|
+
name: str,
|
|
46
|
+
rv: RuntimeVariable[Any],
|
|
47
|
+
overwrite: bool = False,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Register a runtime variable by user-chosen name.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
name (`str`):
|
|
53
|
+
Semantic name for the variable (e.g. a local variable name).
|
|
54
|
+
rv (`RuntimeVariable[Any]`):
|
|
55
|
+
The runtime handle.
|
|
56
|
+
overwrite (`bool`, defaults to `False`):
|
|
57
|
+
If it is not enabled, an error will be raised when the given name
|
|
58
|
+
is already registered. Otherwise, it will overwrite the existing variable.
|
|
59
|
+
"""
|
|
60
|
+
if name in self._runtime_variables:
|
|
61
|
+
if not overwrite:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Variable name '{name}' is already registered in this context. "
|
|
64
|
+
f"Use `overwrite=True` to replace it."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
logger.debug(
|
|
68
|
+
"A variable with name '{name}' is already registered in this context. "
|
|
69
|
+
"It will be overwritten.",
|
|
70
|
+
name=name,
|
|
71
|
+
)
|
|
72
|
+
# Detach the name from the previous runtime variable's alias set.
|
|
73
|
+
# This keeps the reverse index consistent when the old and new
|
|
74
|
+
# handles point to different full node identifiers.
|
|
75
|
+
old_rv = self._runtime_variables[name]
|
|
76
|
+
old_names = self._full_node_id_to_names[old_rv.full_node_id]
|
|
77
|
+
old_names.discard(name)
|
|
78
|
+
if not old_names:
|
|
79
|
+
self._full_node_id_to_names.pop(old_rv.full_node_id)
|
|
80
|
+
|
|
81
|
+
self._runtime_variables[name] = rv
|
|
82
|
+
self._full_node_id_to_names.setdefault(rv.full_node_id, set()).add(name)
|
|
83
|
+
|
|
84
|
+
def get_variable(self, name: str) -> RuntimeVariable[Any]:
|
|
85
|
+
"""Look up a runtime variable by its registered name.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
name (`str`):
|
|
89
|
+
The name used during registration.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
`RuntimeVariable`:
|
|
93
|
+
The runtime handle.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
`NameError`:
|
|
97
|
+
If the variable with the given name has not been declared in
|
|
98
|
+
this tracing context.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
return self._runtime_variables[name]
|
|
102
|
+
except KeyError:
|
|
103
|
+
raise NameError(
|
|
104
|
+
f"Variable name '{name}' is not declared in this tracing context."
|
|
105
|
+
) from None
|
|
106
|
+
|
|
107
|
+
def get_variable_by_full_node_id(self, full_node_id: str) -> RuntimeVariable[Any]:
|
|
108
|
+
"""Look up a runtime variable by its full (namespaced) node identifier.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
full_node_id (`str`):
|
|
112
|
+
The full node identifier of the variable.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
`RuntimeVariable`:
|
|
116
|
+
The runtime handle.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
`NameError`:
|
|
120
|
+
If no variable with the given full node identifier is found.
|
|
121
|
+
"""
|
|
122
|
+
names = self._full_node_id_to_names.get(full_node_id)
|
|
123
|
+
if names is None:
|
|
124
|
+
raise NameError(
|
|
125
|
+
f"No variable with full node identifier '{full_node_id}' is "
|
|
126
|
+
"registered in this tracing context."
|
|
127
|
+
)
|
|
128
|
+
return self._runtime_variables[next(iter(names))]
|
|
129
|
+
|
|
130
|
+
def get_names_by_full_node_id(self, full_node_id: str) -> set[str]:
|
|
131
|
+
"""Return all names registered for the given full node identifier.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
full_node_id (`str`):
|
|
135
|
+
The full node identifier of the variable.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
`set[str]`:
|
|
139
|
+
A fresh copy of the set of registered names. The returned set
|
|
140
|
+
is empty when no variable with this identifier is registered.
|
|
141
|
+
"""
|
|
142
|
+
names = self._full_node_id_to_names.get(full_node_id)
|
|
143
|
+
if names is None:
|
|
144
|
+
return set()
|
|
145
|
+
return set(names)
|
|
146
|
+
|
|
147
|
+
def remove_variable(self, name: str) -> RuntimeVariable[Any]:
|
|
148
|
+
"""Remove a runtime variable by its registered name.
|
|
149
|
+
|
|
150
|
+
The variable is removed from the tracing context only. It is not
|
|
151
|
+
deleted from the underlying execution graph or driver.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
name (`str`):
|
|
155
|
+
The name used during registration.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
`RuntimeVariable[Any]`:
|
|
159
|
+
The removed runtime variable handle.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
`NameError`:
|
|
163
|
+
If no variable with the given name is registered in this
|
|
164
|
+
tracing context.
|
|
165
|
+
"""
|
|
166
|
+
try:
|
|
167
|
+
rv = self._runtime_variables.pop(name)
|
|
168
|
+
except KeyError:
|
|
169
|
+
raise NameError(
|
|
170
|
+
f"Variable name '{name}' is not declared in this tracing context."
|
|
171
|
+
) from None
|
|
172
|
+
|
|
173
|
+
names = self._full_node_id_to_names.get(rv.full_node_id)
|
|
174
|
+
if names is not None:
|
|
175
|
+
names.discard(name)
|
|
176
|
+
if not names:
|
|
177
|
+
self._full_node_id_to_names.pop(rv.full_node_id)
|
|
178
|
+
return rv
|
|
179
|
+
|
|
180
|
+
def query_variables(
|
|
181
|
+
self,
|
|
182
|
+
*,
|
|
183
|
+
name_pattern: str | None = None,
|
|
184
|
+
category: str | Iterable[str] | None = None,
|
|
185
|
+
class_name: str | None = None,
|
|
186
|
+
) -> list[RuntimeVariable[Any]]:
|
|
187
|
+
"""Query registered runtime variables by public traits.
|
|
188
|
+
|
|
189
|
+
All supplied filters are combined with logical AND. When no filters
|
|
190
|
+
are given, all registered variables are returned.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
name_pattern (`str | None`, optional):
|
|
194
|
+
Regular expression matched against the registered variable
|
|
195
|
+
name. Plain keywords are treated as literal substrings.
|
|
196
|
+
category (`str | Iterable[str] | None`, optional):
|
|
197
|
+
A single category string or a set of category strings.
|
|
198
|
+
A variable matches if its category is contained in the set.
|
|
199
|
+
class_name (`str | None`, optional):
|
|
200
|
+
Exact class name to match.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
`list[RuntimeVariable[Any]]`:
|
|
204
|
+
Variables satisfying all filters, ordered by registration
|
|
205
|
+
insertion order.
|
|
206
|
+
"""
|
|
207
|
+
compiled = re.compile(name_pattern) if name_pattern is not None else None
|
|
208
|
+
cat_set = None
|
|
209
|
+
if isinstance(category, str):
|
|
210
|
+
cat_set = {category}
|
|
211
|
+
elif category is not None:
|
|
212
|
+
cat_set = set(category)
|
|
213
|
+
|
|
214
|
+
results = []
|
|
215
|
+
for registered_name, rv in self._runtime_variables.items():
|
|
216
|
+
if compiled is not None and not compiled.search(registered_name):
|
|
217
|
+
continue
|
|
218
|
+
if cat_set is not None and rv.category not in cat_set:
|
|
219
|
+
continue
|
|
220
|
+
if class_name is not None and rv.class_name != class_name:
|
|
221
|
+
continue
|
|
222
|
+
results.append(rv)
|
|
223
|
+
return results
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def graph(self) -> ExecNetwork | None:
|
|
227
|
+
"""Get the execution graph from the current context."""
|
|
228
|
+
return _GRAPH.get()
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def session(self) -> Session | None:
|
|
232
|
+
"""Get the current session from the current context."""
|
|
233
|
+
return _SESSION.get()
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def op(self) -> OpRecord | None:
|
|
237
|
+
"""Get the current operation from the current context."""
|
|
238
|
+
return _OP.get()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# The current tracing context.
|
|
242
|
+
_TRACING_CTX: ContextVar[TracingContext | None] = ContextVar("_TRACING_CTX", default=None)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def enable_tracing() -> None:
|
|
246
|
+
"""Enable the global tracing toggle."""
|
|
247
|
+
global _TRACING_ENABLED
|
|
248
|
+
_TRACING_ENABLED = True
|
|
249
|
+
|
|
250
|
+
logger.info("Tracing is enabled.")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def disable_tracing() -> None:
|
|
254
|
+
"""Disable the global tracing toggle.
|
|
255
|
+
All `comment_*` calls become no-ops and pure comments."""
|
|
256
|
+
global _TRACING_ENABLED
|
|
257
|
+
_TRACING_ENABLED = False
|
|
258
|
+
|
|
259
|
+
logger.info("Tracing is disabled.")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def is_tracing_enabled() -> bool:
|
|
263
|
+
"""Return whether tracing is currently enabled."""
|
|
264
|
+
return _TRACING_ENABLED
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def current_graph() -> ExecNetwork | None:
|
|
268
|
+
"""Return the active execution graph, if any."""
|
|
269
|
+
return _GRAPH.get()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def current_session() -> Session | None:
|
|
273
|
+
"""Return the active session, if any."""
|
|
274
|
+
return _SESSION.get()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def current_op() -> OpRecord | None:
|
|
278
|
+
"""Return the active operation record, if any."""
|
|
279
|
+
return _OP.get()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def current_context() -> TracingContext | None:
|
|
283
|
+
"""Return the active tracing context, if any."""
|
|
284
|
+
return _TRACING_CTX.get()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@contextmanager
|
|
288
|
+
def comment_graph(
|
|
289
|
+
*,
|
|
290
|
+
graph_id: str | None = None,
|
|
291
|
+
user_id: str | None = None,
|
|
292
|
+
project_id: str | None = None,
|
|
293
|
+
storage_path: str | None = None,
|
|
294
|
+
driver_type: str | None = None,
|
|
295
|
+
graph: ExecNetwork | None = None,
|
|
296
|
+
metadata: dict[str, JsonValue] | None = None,
|
|
297
|
+
strict: bool | None = None,
|
|
298
|
+
) -> Iterator[ExecNetwork | None]:
|
|
299
|
+
"""Create or adopt an execution network and set it as the active graph.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
graph_id (`str | None`, optional):
|
|
303
|
+
Explicit graph identifier. It is automatically generated if omitted.
|
|
304
|
+
user_id (`str | None`, optional):
|
|
305
|
+
User identifier that owns the graph.
|
|
306
|
+
project_id (`str | None`, optional):
|
|
307
|
+
Project identifier that owns the graph.
|
|
308
|
+
storage_path (`str | None`, optional):
|
|
309
|
+
On-disk path for persistent storage.
|
|
310
|
+
driver_type (`str | None`, optional):
|
|
311
|
+
Registered driver backend name. It defaults to ``"in_memory"``
|
|
312
|
+
when creating a new graph.
|
|
313
|
+
graph (`ExecNetwork | None`, optional):
|
|
314
|
+
An existing graph to continue.
|
|
315
|
+
metadata (`dict[str, JsonValue] | None`, optional):
|
|
316
|
+
Extra metadata merged into the graph.
|
|
317
|
+
strict (`bool | None`, optional):
|
|
318
|
+
Enable or disable strict consistency checking for this
|
|
319
|
+
graph. When it is enabled, encountering a changed value for
|
|
320
|
+
an existing identity without an explicit ``comment_mutation`` raises
|
|
321
|
+
a trace consistency error. It defaults to ``False``. When an existing
|
|
322
|
+
graph is passed in, this parameter overrides its current setting
|
|
323
|
+
only if explicitly provided.
|
|
324
|
+
|
|
325
|
+
Yields:
|
|
326
|
+
`ExecNetwork | None`:
|
|
327
|
+
The active graph if tracing is enabled.
|
|
328
|
+
"""
|
|
329
|
+
if not _TRACING_ENABLED:
|
|
330
|
+
yield None
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
created = False
|
|
334
|
+
if graph is None:
|
|
335
|
+
kwargs = {}
|
|
336
|
+
if graph_id is not None:
|
|
337
|
+
kwargs["graph_id"] = graph_id
|
|
338
|
+
if user_id is not None:
|
|
339
|
+
kwargs["user_id"] = user_id
|
|
340
|
+
if project_id is not None:
|
|
341
|
+
kwargs["project_id"] = project_id
|
|
342
|
+
if storage_path is not None:
|
|
343
|
+
kwargs["storage_path"] = storage_path
|
|
344
|
+
if driver_type is not None:
|
|
345
|
+
kwargs["driver_type"] = driver_type
|
|
346
|
+
if strict is not None:
|
|
347
|
+
kwargs["strict"] = strict
|
|
348
|
+
graph = ExecNetwork(**kwargs)
|
|
349
|
+
created = True
|
|
350
|
+
elif strict is not None:
|
|
351
|
+
graph.strict = strict
|
|
352
|
+
|
|
353
|
+
if metadata:
|
|
354
|
+
graph.update_metadata(metadata)
|
|
355
|
+
|
|
356
|
+
# Set up tracing context and inherit propagated attributes.
|
|
357
|
+
ctx = TracingContext()
|
|
358
|
+
parent_ctx = _TRACING_CTX.get()
|
|
359
|
+
if parent_ctx is not None:
|
|
360
|
+
ctx._runtime_variables = parent_ctx._runtime_variables.copy()
|
|
361
|
+
ctx._full_node_id_to_names = {
|
|
362
|
+
fid: names.copy()
|
|
363
|
+
for fid, names in parent_ctx._full_node_id_to_names.items()
|
|
364
|
+
}
|
|
365
|
+
ctx.update_metadata(parent_ctx.metadata)
|
|
366
|
+
# Inject propagated attributes onto the graph schema object.
|
|
367
|
+
if parent_ctx.metadata:
|
|
368
|
+
graph.update_metadata(parent_ctx.metadata)
|
|
369
|
+
|
|
370
|
+
if created:
|
|
371
|
+
logger.info(
|
|
372
|
+
"An execution graph (graph_id={}, driver_type={}, strict={}) is created.",
|
|
373
|
+
graph.graph_id,
|
|
374
|
+
graph.driver_type,
|
|
375
|
+
graph.strict,
|
|
376
|
+
)
|
|
377
|
+
else:
|
|
378
|
+
logger.debug(
|
|
379
|
+
"An existing execution graph (graph_id={}) is adopted.",
|
|
380
|
+
graph.graph_id,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
token_graph = _GRAPH.set(graph)
|
|
384
|
+
token_ctx = _TRACING_CTX.set(ctx)
|
|
385
|
+
try:
|
|
386
|
+
yield graph
|
|
387
|
+
finally:
|
|
388
|
+
_GRAPH.reset(token_graph)
|
|
389
|
+
_TRACING_CTX.reset(token_ctx)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@contextmanager
|
|
393
|
+
def comment_session(
|
|
394
|
+
*,
|
|
395
|
+
session_id: str | None = None,
|
|
396
|
+
session_name: str | None = None,
|
|
397
|
+
comment: str | None = None,
|
|
398
|
+
category: str = "session",
|
|
399
|
+
metadata: dict[str, JsonValue] | None = None,
|
|
400
|
+
) -> Iterator[RuntimeSession | None]:
|
|
401
|
+
"""Create a session and set it as the active session.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
session_id (`str | None`, optional):
|
|
405
|
+
Explicit session identifier.
|
|
406
|
+
session_name (`str | None`, optional):
|
|
407
|
+
Human-readable session name.
|
|
408
|
+
comment (`str | None`, optional):
|
|
409
|
+
Session description.
|
|
410
|
+
category (`str`, defaults to `"session"`):
|
|
411
|
+
Session category.
|
|
412
|
+
metadata (`dict[str, JsonValue] | None`, optional):
|
|
413
|
+
Extra metadata for the session schema object.
|
|
414
|
+
|
|
415
|
+
Yields:
|
|
416
|
+
`RuntimeSession | None`:
|
|
417
|
+
A read-only session handle if tracing is enabled.
|
|
418
|
+
|
|
419
|
+
Raises:
|
|
420
|
+
`RuntimeError`:
|
|
421
|
+
If no graph context is active and tracing is enabled.
|
|
422
|
+
`ValueError`:
|
|
423
|
+
If provided session identifier is the reserved NONE sentinel session identifier.
|
|
424
|
+
"""
|
|
425
|
+
if not _TRACING_ENABLED:
|
|
426
|
+
yield None
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
graph = _GRAPH.get()
|
|
430
|
+
if graph is None:
|
|
431
|
+
raise RuntimeError(
|
|
432
|
+
"`comment_session` requires an active graph context. "
|
|
433
|
+
"Wrap your code in `with comment_graph() as graph:`."
|
|
434
|
+
)
|
|
435
|
+
none_sid = none_session_id()
|
|
436
|
+
if session_id == none_sid:
|
|
437
|
+
raise ValueError(
|
|
438
|
+
f"The session identifier '{none_sid}' is reserved for the built-in "
|
|
439
|
+
"NONE sentinel."
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
frame = inspect.stack()[2]
|
|
443
|
+
kwargs = {
|
|
444
|
+
"graph_id": graph.graph_id,
|
|
445
|
+
"user_id": graph.user_id,
|
|
446
|
+
"project_id": graph.project_id,
|
|
447
|
+
"category": category,
|
|
448
|
+
"filename": frame.filename,
|
|
449
|
+
"lineno": frame.lineno,
|
|
450
|
+
}
|
|
451
|
+
if session_id is not None:
|
|
452
|
+
kwargs["session_id"] = session_id
|
|
453
|
+
if session_name is not None:
|
|
454
|
+
kwargs["session_name"] = session_name
|
|
455
|
+
if comment is not None:
|
|
456
|
+
kwargs["comment"] = comment
|
|
457
|
+
|
|
458
|
+
session = Session(**kwargs)
|
|
459
|
+
if metadata:
|
|
460
|
+
session.update_metadata(metadata)
|
|
461
|
+
|
|
462
|
+
# Inject propagated attributes onto the session schema.
|
|
463
|
+
parent_ctx = _TRACING_CTX.get()
|
|
464
|
+
assert parent_ctx is not None, (
|
|
465
|
+
"The tracing context must be active when `comment_session` is called."
|
|
466
|
+
)
|
|
467
|
+
propagated = parent_ctx.metadata
|
|
468
|
+
if propagated:
|
|
469
|
+
session.update_metadata(propagated)
|
|
470
|
+
|
|
471
|
+
# Persist the session in the graph driver.
|
|
472
|
+
graph._driver.add_session(session)
|
|
473
|
+
|
|
474
|
+
# Set up tracing context and inherit propagated attributes.
|
|
475
|
+
ctx = TracingContext()
|
|
476
|
+
ctx._runtime_variables = parent_ctx._runtime_variables.copy()
|
|
477
|
+
ctx._full_node_id_to_names = {
|
|
478
|
+
fid: names.copy()
|
|
479
|
+
for fid, names in parent_ctx._full_node_id_to_names.items()
|
|
480
|
+
}
|
|
481
|
+
ctx.update_metadata(parent_ctx.metadata)
|
|
482
|
+
|
|
483
|
+
token_session = _SESSION.set(session)
|
|
484
|
+
token_ctx = _TRACING_CTX.set(ctx)
|
|
485
|
+
try:
|
|
486
|
+
yield RuntimeSession(session=session)
|
|
487
|
+
finally:
|
|
488
|
+
_SESSION.reset(token_session)
|
|
489
|
+
_TRACING_CTX.reset(token_ctx)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@contextmanager
|
|
493
|
+
def comment_op_scope(
|
|
494
|
+
*,
|
|
495
|
+
op_name: str | None = None,
|
|
496
|
+
comment: str | None = None,
|
|
497
|
+
category: str = "operation",
|
|
498
|
+
metadata: dict[str, JsonValue] | None = None,
|
|
499
|
+
) -> Iterator[RuntimeOp | None]:
|
|
500
|
+
"""Create an operation record scope and set it as the active operation.
|
|
501
|
+
|
|
502
|
+
If no session is active, an anonymous session is created automatically
|
|
503
|
+
for the duration of this scope.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
op_name (`str | None`, optional):
|
|
507
|
+
Operation name.
|
|
508
|
+
comment (`str | None`, optional):
|
|
509
|
+
Operation description.
|
|
510
|
+
category (`str`, defaults to `"operation"`):
|
|
511
|
+
Operation category.
|
|
512
|
+
metadata (`dict[str, JsonValue] | None`, optional):
|
|
513
|
+
Extra metadata.
|
|
514
|
+
|
|
515
|
+
Yields:
|
|
516
|
+
`RuntimeOp | None`:
|
|
517
|
+
A read-only operation handle if tracing is enabled.
|
|
518
|
+
|
|
519
|
+
Raises:
|
|
520
|
+
`RuntimeError`:
|
|
521
|
+
If no graph context is active and tracing is enabled.
|
|
522
|
+
"""
|
|
523
|
+
if not _TRACING_ENABLED:
|
|
524
|
+
yield None
|
|
525
|
+
return
|
|
526
|
+
|
|
527
|
+
graph = _GRAPH.get()
|
|
528
|
+
if graph is None:
|
|
529
|
+
raise RuntimeError(
|
|
530
|
+
"`comment_op_scope` requires an active graph context. "
|
|
531
|
+
"Wrap your code in `with comment_graph() as graph:`."
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
tracing_ctx = _TRACING_CTX.get()
|
|
535
|
+
assert tracing_ctx is not None, (
|
|
536
|
+
"The tracing context must be active when `comment_op_scope` is called."
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
session = _SESSION.get()
|
|
540
|
+
auto_session_token = None
|
|
541
|
+
if session is None:
|
|
542
|
+
frame_session = inspect.stack()[2]
|
|
543
|
+
session = Session(
|
|
544
|
+
graph_id=graph.graph_id,
|
|
545
|
+
user_id=graph.user_id,
|
|
546
|
+
project_id=graph.project_id,
|
|
547
|
+
category="session",
|
|
548
|
+
filename=frame_session.filename,
|
|
549
|
+
lineno=frame_session.lineno,
|
|
550
|
+
)
|
|
551
|
+
if tracing_ctx.metadata:
|
|
552
|
+
session.update_metadata(tracing_ctx.metadata)
|
|
553
|
+
graph._driver.add_session(session)
|
|
554
|
+
auto_session_token = _SESSION.set(session)
|
|
555
|
+
|
|
556
|
+
frame = inspect.stack()[2]
|
|
557
|
+
op = OpRecord(
|
|
558
|
+
graph_id=graph.graph_id,
|
|
559
|
+
session_id=session.session_id,
|
|
560
|
+
user_id=graph.user_id,
|
|
561
|
+
project_id=graph.project_id,
|
|
562
|
+
op_name=op_name,
|
|
563
|
+
comment=comment,
|
|
564
|
+
category=category,
|
|
565
|
+
filename=frame.filename,
|
|
566
|
+
lineno=frame.lineno,
|
|
567
|
+
)
|
|
568
|
+
if metadata:
|
|
569
|
+
op.update_metadata(metadata)
|
|
570
|
+
|
|
571
|
+
propagated = tracing_ctx.metadata
|
|
572
|
+
if propagated:
|
|
573
|
+
op.update_metadata(propagated)
|
|
574
|
+
|
|
575
|
+
# Persist the operation in the graph driver.
|
|
576
|
+
graph._driver.add_operation(op)
|
|
577
|
+
|
|
578
|
+
token_op = _OP.set(op)
|
|
579
|
+
try:
|
|
580
|
+
yield RuntimeOp(op=op)
|
|
581
|
+
finally:
|
|
582
|
+
_OP.reset(token_op)
|
|
583
|
+
if auto_session_token is not None:
|
|
584
|
+
_SESSION.reset(auto_session_token)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
@contextmanager
|
|
588
|
+
def propagate_attributes(
|
|
589
|
+
**attrs: Any,
|
|
590
|
+
) -> Iterator[None]:
|
|
591
|
+
"""Add key-value attributes to the tracing context for propagation.
|
|
592
|
+
|
|
593
|
+
Attributes set here flow through the context inheritance chain and
|
|
594
|
+
are automatically injected into every execution network, session,
|
|
595
|
+
operation record, and variable created in nested scopes.
|
|
596
|
+
|
|
597
|
+
Example::
|
|
598
|
+
|
|
599
|
+
with propagate_attributes(run_id="exp-1", model="gpt-4"):
|
|
600
|
+
with comment_graph() as graph:
|
|
601
|
+
# `graph.metadata` contains `run_id` and `model`.
|
|
602
|
+
with comment_session(session_name="s1"):
|
|
603
|
+
rv = comment_variable("hello", id_strategy="content")
|
|
604
|
+
# `rv.metadata` also contains `run_id` and `model`.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
**attrs (`Any`):
|
|
608
|
+
Arbitrary key-value pairs to propagate.
|
|
609
|
+
"""
|
|
610
|
+
if not _TRACING_ENABLED:
|
|
611
|
+
yield
|
|
612
|
+
return
|
|
613
|
+
|
|
614
|
+
parent_ctx = _TRACING_CTX.get()
|
|
615
|
+
|
|
616
|
+
ctx = TracingContext()
|
|
617
|
+
if parent_ctx is not None:
|
|
618
|
+
ctx._runtime_variables = parent_ctx._runtime_variables.copy()
|
|
619
|
+
ctx._full_node_id_to_names = {
|
|
620
|
+
fid: names.copy()
|
|
621
|
+
for fid, names in parent_ctx._full_node_id_to_names.items()
|
|
622
|
+
}
|
|
623
|
+
ctx.update_metadata(parent_ctx.metadata)
|
|
624
|
+
ctx.update_metadata(attrs)
|
|
625
|
+
|
|
626
|
+
token = _TRACING_CTX.set(ctx)
|
|
627
|
+
try:
|
|
628
|
+
yield
|
|
629
|
+
finally:
|
|
630
|
+
_TRACING_CTX.reset(token)
|