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.
@@ -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
+ )