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,318 @@
|
|
|
1
|
+
from pydantic import BaseModel, JsonValue
|
|
2
|
+
from ..schema.variable import Variable
|
|
3
|
+
from ..schema.operation import OpRecord, OpEdge
|
|
4
|
+
from ..runtime import (
|
|
5
|
+
current_context,
|
|
6
|
+
current_op,
|
|
7
|
+
is_tracing_enabled,
|
|
8
|
+
)
|
|
9
|
+
from ..runtime.variable import RuntimeVariable
|
|
10
|
+
from ._helpers import (
|
|
11
|
+
_resolve_call_context,
|
|
12
|
+
_ensure_variable,
|
|
13
|
+
_resolve_item,
|
|
14
|
+
)
|
|
15
|
+
from types import TracebackType
|
|
16
|
+
from typing import (
|
|
17
|
+
Any,
|
|
18
|
+
Callable,
|
|
19
|
+
Generic,
|
|
20
|
+
Self,
|
|
21
|
+
TypeVar,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _MutationScope(Generic[T]):
|
|
29
|
+
"""Context manager that records an in-place mutation as a new variable version.
|
|
30
|
+
|
|
31
|
+
Instances are created by :func:`comment_mutation` and should not be
|
|
32
|
+
instantiated directly. On entry, the current value of target is
|
|
33
|
+
snapshotted. On exit, the target is re-encoded and a new version node,
|
|
34
|
+
an operation record, and connecting edges are written to the execution graph.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
__slots__ = (
|
|
38
|
+
"_target", "_inputs",
|
|
39
|
+
"_comment", "_category", "_class_name", "_auto_class_name",
|
|
40
|
+
"_id_strategy", "_encoding_fn", "_decoding_fn", "_schema",
|
|
41
|
+
"_mutation_name", "_mutation_comment", "_mutation_category",
|
|
42
|
+
"_metadata", "_mutation_metadata", "_reuse_op",
|
|
43
|
+
"_result",
|
|
44
|
+
"_before_rv", "_graph", "_session_id", "_filename", "_lineno", "_input_rvs",
|
|
45
|
+
"_tracing_active",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
target: T,
|
|
52
|
+
inputs: list[Any] | None,
|
|
53
|
+
comment: str | None,
|
|
54
|
+
category: str,
|
|
55
|
+
class_name: str | None,
|
|
56
|
+
auto_class_name: bool,
|
|
57
|
+
id_strategy: Callable[[Any], str] | str | None,
|
|
58
|
+
encoding_fn: Callable[[Any], str] | None,
|
|
59
|
+
decoding_fn: Callable[[str], Any] | None,
|
|
60
|
+
schema: type[BaseModel] | None,
|
|
61
|
+
mutation_name: str | None,
|
|
62
|
+
mutation_comment: str | None,
|
|
63
|
+
mutation_category: str,
|
|
64
|
+
metadata: dict[str, JsonValue] | None,
|
|
65
|
+
mutation_metadata: dict[str, JsonValue] | None,
|
|
66
|
+
reuse_op: bool = False,
|
|
67
|
+
) -> None:
|
|
68
|
+
self._target = target
|
|
69
|
+
self._inputs = inputs
|
|
70
|
+
self._comment = comment
|
|
71
|
+
self._category = category
|
|
72
|
+
self._class_name = class_name
|
|
73
|
+
self._auto_class_name = auto_class_name
|
|
74
|
+
self._id_strategy = id_strategy
|
|
75
|
+
self._encoding_fn = encoding_fn
|
|
76
|
+
self._decoding_fn = decoding_fn
|
|
77
|
+
self._schema = schema
|
|
78
|
+
self._mutation_name = mutation_name
|
|
79
|
+
self._mutation_comment = mutation_comment
|
|
80
|
+
self._mutation_category = mutation_category
|
|
81
|
+
self._metadata = metadata
|
|
82
|
+
self._mutation_metadata = mutation_metadata
|
|
83
|
+
self._reuse_op = reuse_op
|
|
84
|
+
self._result = None
|
|
85
|
+
self._before_rv = None
|
|
86
|
+
self._graph = None
|
|
87
|
+
self._session_id = None
|
|
88
|
+
self._filename = None
|
|
89
|
+
self._lineno = None
|
|
90
|
+
self._input_rvs = []
|
|
91
|
+
|
|
92
|
+
# It denotes whether this scope is currently active.
|
|
93
|
+
self._tracing_active = False
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def target(self) -> T:
|
|
97
|
+
"""The mutable Python object being tracked."""
|
|
98
|
+
return self._target
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def result(self) -> RuntimeVariable[T] | None:
|
|
102
|
+
"""The runtime variable for the new version, available after exit.
|
|
103
|
+
|
|
104
|
+
It returns ``None`` when tracing is disabled or the `with` block
|
|
105
|
+
has not yet exited.
|
|
106
|
+
"""
|
|
107
|
+
return self._result
|
|
108
|
+
|
|
109
|
+
def __enter__(self) -> Self:
|
|
110
|
+
if not is_tracing_enabled():
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
# Mark this scope as active.
|
|
114
|
+
self._tracing_active = True
|
|
115
|
+
|
|
116
|
+
graph, session_id, filename, lineno = _resolve_call_context(
|
|
117
|
+
caller_name="comment_mutation",
|
|
118
|
+
)
|
|
119
|
+
self._graph = graph
|
|
120
|
+
self._session_id = session_id
|
|
121
|
+
self._filename = filename
|
|
122
|
+
self._lineno = lineno
|
|
123
|
+
|
|
124
|
+
# Get the runtime variable for the before-mutation state.
|
|
125
|
+
self._before_rv = _ensure_variable(
|
|
126
|
+
self._target,
|
|
127
|
+
id_strategy=self._id_strategy,
|
|
128
|
+
encoding_fn=self._encoding_fn,
|
|
129
|
+
decoding_fn=self._decoding_fn,
|
|
130
|
+
schema=self._schema,
|
|
131
|
+
comment=self._comment,
|
|
132
|
+
category=self._category,
|
|
133
|
+
class_name=self._class_name,
|
|
134
|
+
auto_class_name=self._auto_class_name,
|
|
135
|
+
metadata=self._metadata,
|
|
136
|
+
graph=graph,
|
|
137
|
+
session_id=session_id,
|
|
138
|
+
filename=filename,
|
|
139
|
+
lineno=lineno,
|
|
140
|
+
force_new_version=False,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if self._inputs:
|
|
144
|
+
resolve_kwargs = dict(
|
|
145
|
+
shared_id_strategy=self._id_strategy,
|
|
146
|
+
shared_encoding_fn=self._encoding_fn,
|
|
147
|
+
shared_decoding_fn=self._decoding_fn,
|
|
148
|
+
shared_schema=self._schema,
|
|
149
|
+
shared_auto_class_name=self._auto_class_name,
|
|
150
|
+
shared_category=self._category,
|
|
151
|
+
shared_metadata=self._metadata,
|
|
152
|
+
graph=graph,
|
|
153
|
+
session_id=session_id,
|
|
154
|
+
filename=filename,
|
|
155
|
+
lineno=lineno,
|
|
156
|
+
)
|
|
157
|
+
self._input_rvs = [
|
|
158
|
+
_resolve_item(item, **resolve_kwargs)
|
|
159
|
+
for item in self._inputs
|
|
160
|
+
]
|
|
161
|
+
target_fid = self._before_rv.full_node_id
|
|
162
|
+
for rv in self._input_rvs:
|
|
163
|
+
if rv.full_node_id == target_fid:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"An input variable with the full node identifier "
|
|
166
|
+
f"'{rv.full_node_id}' collides with the mutation "
|
|
167
|
+
"target. Self-referencing inputs are not allowed."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
# noinspection PyUnusedLocal
|
|
173
|
+
def __exit__(
|
|
174
|
+
self,
|
|
175
|
+
exc_type: type[BaseException] | None,
|
|
176
|
+
exc_val: BaseException | None,
|
|
177
|
+
exc_tb: TracebackType | None,
|
|
178
|
+
) -> bool:
|
|
179
|
+
# Return `False` to propagate any exception that occurred in the with-block.
|
|
180
|
+
if exc_type is not None or not self._tracing_active:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
graph = self._graph
|
|
184
|
+
session_id = self._session_id
|
|
185
|
+
filename = self._filename
|
|
186
|
+
lineno = self._lineno
|
|
187
|
+
before_rv = self._before_rv
|
|
188
|
+
|
|
189
|
+
# Re-encode the (now-mutated) target variable.
|
|
190
|
+
if self._schema is not None and self._encoding_fn is None:
|
|
191
|
+
after_value_str = self._target.model_dump_json()
|
|
192
|
+
elif self._encoding_fn is not None:
|
|
193
|
+
after_value_str = self._encoding_fn(self._target)
|
|
194
|
+
else:
|
|
195
|
+
after_value_str = repr(self._target)
|
|
196
|
+
|
|
197
|
+
new_var = Variable(
|
|
198
|
+
name=before_rv.name,
|
|
199
|
+
version=before_rv.version + 1,
|
|
200
|
+
value=after_value_str,
|
|
201
|
+
comment=before_rv.comment,
|
|
202
|
+
category=before_rv.category,
|
|
203
|
+
class_name=before_rv.class_name,
|
|
204
|
+
graph_id=graph.graph_id,
|
|
205
|
+
user_id=graph.user_id,
|
|
206
|
+
project_id=graph.project_id,
|
|
207
|
+
session_id=session_id,
|
|
208
|
+
filename=filename,
|
|
209
|
+
lineno=lineno,
|
|
210
|
+
)
|
|
211
|
+
if self._metadata:
|
|
212
|
+
new_var.update_metadata(self._metadata)
|
|
213
|
+
|
|
214
|
+
ctx = current_context()
|
|
215
|
+
|
|
216
|
+
assert ctx is not None, (
|
|
217
|
+
"The current tracing context is not active."
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
propagated = ctx.metadata
|
|
221
|
+
if propagated:
|
|
222
|
+
new_var.update_metadata(propagated)
|
|
223
|
+
|
|
224
|
+
graph._driver.add_node(new_var)
|
|
225
|
+
new_full_node_id = new_var.full_node_id
|
|
226
|
+
|
|
227
|
+
active_op = current_op() if self._reuse_op else None
|
|
228
|
+
if active_op is not None:
|
|
229
|
+
op = active_op
|
|
230
|
+
else:
|
|
231
|
+
mutation_name = self._mutation_name or f"mutation:{before_rv.full_name}"
|
|
232
|
+
op = OpRecord(
|
|
233
|
+
graph_id=graph.graph_id,
|
|
234
|
+
session_id=session_id,
|
|
235
|
+
user_id=graph.user_id,
|
|
236
|
+
project_id=graph.project_id,
|
|
237
|
+
op_name=mutation_name,
|
|
238
|
+
comment=self._mutation_comment,
|
|
239
|
+
category=self._mutation_category,
|
|
240
|
+
filename=filename,
|
|
241
|
+
lineno=lineno,
|
|
242
|
+
)
|
|
243
|
+
if self._mutation_metadata:
|
|
244
|
+
op.update_metadata(self._mutation_metadata)
|
|
245
|
+
if propagated:
|
|
246
|
+
op.update_metadata(propagated)
|
|
247
|
+
graph._driver.add_operation(op)
|
|
248
|
+
|
|
249
|
+
# Edges receive the operation metadata.
|
|
250
|
+
edge_metadata = {}
|
|
251
|
+
if self._mutation_metadata:
|
|
252
|
+
edge_metadata.update(self._mutation_metadata)
|
|
253
|
+
if propagated:
|
|
254
|
+
edge_metadata.update(propagated)
|
|
255
|
+
|
|
256
|
+
# Version edge generated by in-place mutation.
|
|
257
|
+
# If the user provide the comment on this in-place mutation
|
|
258
|
+
# it will be used for the version edge.
|
|
259
|
+
# Otherwise, the default comment `"Version lineage."` will be used.
|
|
260
|
+
edge_comment = self._mutation_comment or "Version lineage."
|
|
261
|
+
version_edge = OpEdge(
|
|
262
|
+
graph_id=graph.graph_id,
|
|
263
|
+
session_id=session_id,
|
|
264
|
+
user_id=graph.user_id,
|
|
265
|
+
project_id=graph.project_id,
|
|
266
|
+
op_id=op.op_id,
|
|
267
|
+
category=self._mutation_category,
|
|
268
|
+
source_full_node_id=before_rv.full_node_id,
|
|
269
|
+
target_full_node_id=new_full_node_id,
|
|
270
|
+
comment=edge_comment,
|
|
271
|
+
filename=filename,
|
|
272
|
+
lineno=lineno,
|
|
273
|
+
)
|
|
274
|
+
if edge_metadata:
|
|
275
|
+
version_edge.update_metadata(edge_metadata)
|
|
276
|
+
graph._driver.add_edge(version_edge)
|
|
277
|
+
|
|
278
|
+
# Input edges inherit the operation's category and comment.
|
|
279
|
+
for input_rv in self._input_rvs:
|
|
280
|
+
edge = OpEdge(
|
|
281
|
+
graph_id=graph.graph_id,
|
|
282
|
+
session_id=session_id,
|
|
283
|
+
user_id=graph.user_id,
|
|
284
|
+
project_id=graph.project_id,
|
|
285
|
+
op_id=op.op_id,
|
|
286
|
+
category=self._mutation_category,
|
|
287
|
+
source_full_node_id=input_rv.full_node_id,
|
|
288
|
+
target_full_node_id=new_full_node_id,
|
|
289
|
+
comment=self._mutation_comment,
|
|
290
|
+
filename=filename,
|
|
291
|
+
lineno=lineno,
|
|
292
|
+
)
|
|
293
|
+
if edge_metadata:
|
|
294
|
+
edge.update_metadata(edge_metadata)
|
|
295
|
+
graph._driver.add_edge(edge)
|
|
296
|
+
|
|
297
|
+
# Create the runtime variable for the new version.
|
|
298
|
+
# The user can access this runtime variable after the `with` block exits.
|
|
299
|
+
self._result = RuntimeVariable(
|
|
300
|
+
variable=new_var,
|
|
301
|
+
encoding_fn=self._encoding_fn,
|
|
302
|
+
decoding_fn=self._decoding_fn,
|
|
303
|
+
schema=self._schema,
|
|
304
|
+
)
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
def __repr__(self) -> str:
|
|
308
|
+
"""Get the string representation of the mutation scope."""
|
|
309
|
+
if self._result is None:
|
|
310
|
+
return (
|
|
311
|
+
f"{self.__class__.__name__}(target={self._target!r}, "
|
|
312
|
+
f"tracing_active={self._tracing_active})"
|
|
313
|
+
)
|
|
314
|
+
return (
|
|
315
|
+
f"{self.__class__.__name__}(target={self._target!r}, "
|
|
316
|
+
f"tracing_active={self._tracing_active}, "
|
|
317
|
+
f"result={self._result!r})"
|
|
318
|
+
)
|