qplotter 1.6.0b3__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.
Files changed (119) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +63 -0
  4. qplot/_metadata.py +12 -0
  5. qplot/_repair.py +14 -0
  6. qplot/_shutdown_supervisor.py +4049 -0
  7. qplot/_version.py +12 -0
  8. qplot/_windows_shutdown_job.py +1166 -0
  9. qplot/configuration/__init__.py +3 -0
  10. qplot/configuration/config.py +573 -0
  11. qplot/configuration/config_schema.json +319 -0
  12. qplot/configuration/scripts.py +255 -0
  13. qplot/configuration/themes/__init__.py +13 -0
  14. qplot/configuration/themes/_base.py +579 -0
  15. qplot/configuration/themes/blank.py +17 -0
  16. qplot/configuration/themes/dark.py +54 -0
  17. qplot/configuration/themes/light.py +54 -0
  18. qplot/datahandling/LoadFromDB.py +224 -0
  19. qplot/datahandling/__init__.py +47 -0
  20. qplot/datahandling/_trusted_live_helper.py +812 -0
  21. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  22. qplot/datahandling/database.py +2330 -0
  23. qplot/datahandling/dimensions.py +43 -0
  24. qplot/datahandling/file_identity.py +461 -0
  25. qplot/datahandling/parameter_data.py +149 -0
  26. qplot/datahandling/plot_progress.py +17 -0
  27. qplot/datahandling/qcodes_cache.py +277 -0
  28. qplot/datahandling/qcodes_compat.py +14 -0
  29. qplot/datahandling/readSQL.py +2442 -0
  30. qplot/datahandling/readonly.py +2561 -0
  31. qplot/datahandling/source_activity.py +21 -0
  32. qplot/datahandling/trusted_array_preview.py +127 -0
  33. qplot/datahandling/trusted_derived_cache.py +1605 -0
  34. qplot/datahandling/trusted_derived_rendering.py +868 -0
  35. qplot/datahandling/trusted_heatmap.py +241 -0
  36. qplot/datahandling/trusted_live.py +2808 -0
  37. qplot/datahandling/trusted_live_queries.py +5907 -0
  38. qplot/datahandling/trusted_live_service.py +1894 -0
  39. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  40. qplot/datahandling/trusted_plot.py +283 -0
  41. qplot/datahandling/trusted_presentation.py +1065 -0
  42. qplot/datahandling/trusted_snapshot.py +1132 -0
  43. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  44. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  45. qplot/diagnostics.py +241 -0
  46. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  47. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  48. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  49. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  50. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  51. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  52. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  53. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  54. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  55. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  56. qplot/testdata.py +1733 -0
  57. qplot/tools/__init__.py +13 -0
  58. qplot/tools/finite_means.py +114 -0
  59. qplot/tools/general.py +134 -0
  60. qplot/tools/heatmap_geometry.py +381 -0
  61. qplot/tools/operation_registry.py +179 -0
  62. qplot/tools/plot_tools.py +625 -0
  63. qplot/tools/sample_statistics.py +56 -0
  64. qplot/tools/worker.py +2787 -0
  65. qplot/windows/__init__.py +9 -0
  66. qplot/windows/_colorbar.py +422 -0
  67. qplot/windows/_commands.py +523 -0
  68. qplot/windows/_config_persistence.py +123 -0
  69. qplot/windows/_database_actions.py +4904 -0
  70. qplot/windows/_dataset_handle.py +221 -0
  71. qplot/windows/_dragdrop.py +71 -0
  72. qplot/windows/_export_paths.py +1167 -0
  73. qplot/windows/_help.py +169 -0
  74. qplot/windows/_native_averaging.py +173 -0
  75. qplot/windows/_native_transforms.py +389 -0
  76. qplot/windows/_plot1d_snap.py +608 -0
  77. qplot/windows/_plot1d_traces.py +2183 -0
  78. qplot/windows/_plot2d_colorbar.py +1126 -0
  79. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  80. qplot/windows/_plot2d_layers.py +2197 -0
  81. qplot/windows/_plot2d_sweeps.py +712 -0
  82. qplot/windows/_plotWin.py +1993 -0
  83. qplot/windows/_plot_actions.py +2695 -0
  84. qplot/windows/_plot_appearance.py +276 -0
  85. qplot/windows/_plot_axis_scaling.py +1485 -0
  86. qplot/windows/_plot_export.py +1445 -0
  87. qplot/windows/_plot_feedback.py +110 -0
  88. qplot/windows/_plot_marquee.py +656 -0
  89. qplot/windows/_plot_refresh.py +942 -0
  90. qplot/windows/_plot_state.py +239 -0
  91. qplot/windows/_plot_transform_labels.py +60 -0
  92. qplot/windows/_preferences.py +635 -0
  93. qplot/windows/_refresh_interval.py +70 -0
  94. qplot/windows/_run_controls.py +828 -0
  95. qplot/windows/_shortcuts.py +47 -0
  96. qplot/windows/_subplots/__init__.py +8 -0
  97. qplot/windows/_subplots/subplot1d.py +531 -0
  98. qplot/windows/_subplots/subplot2d.py +918 -0
  99. qplot/windows/_svg_export.py +209 -0
  100. qplot/windows/_trusted_derived_qt.py +1759 -0
  101. qplot/windows/_widgets/__init__.py +23 -0
  102. qplot/windows/_widgets/_run_formatting.py +387 -0
  103. qplot/windows/_widgets/details_tables.py +1119 -0
  104. qplot/windows/_widgets/dropbox.py +290 -0
  105. qplot/windows/_widgets/operations.py +460 -0
  106. qplot/windows/_widgets/preview.py +2997 -0
  107. qplot/windows/_widgets/run_list_items.py +488 -0
  108. qplot/windows/_widgets/toolbar.py +111 -0
  109. qplot/windows/_widgets/treeWidgets.py +2572 -0
  110. qplot/windows/_window_controls.py +413 -0
  111. qplot/windows/main.py +1635 -0
  112. qplot/windows/plot1d.py +311 -0
  113. qplot/windows/plot2d.py +1901 -0
  114. qplotter-1.6.0b3.dist-info/METADATA +318 -0
  115. qplotter-1.6.0b3.dist-info/RECORD +119 -0
  116. qplotter-1.6.0b3.dist-info/WHEEL +5 -0
  117. qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
  118. qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
  119. qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1065 @@
1
+ """Bounded selected-detail presentation models created before Qt publication."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from collections.abc import Mapping, Sequence
7
+ from dataclasses import dataclass, field
8
+ from typing import Literal, TypeAlias
9
+
10
+ TRUSTED_PRESENTATION_MAX_DEPTH = 16
11
+ TRUSTED_PRESENTATION_MAX_CONTAINER_ITEMS = 2_048
12
+ TRUSTED_PRESENTATION_MAX_RENDERED_NODES = 512
13
+ TRUSTED_PRESENTATION_MAX_RENDERED_TEXT_BYTES = 64 * 1024
14
+ TRUSTED_PRESENTATION_MAX_TOOLTIP_TEXT_BYTES = 64 * 1024
15
+ TRUSTED_PRESENTATION_MAX_KEY_BYTES = 256
16
+ TRUSTED_PRESENTATION_MAX_VALUE_BYTES = 256
17
+ TRUSTED_PRESENTATION_MAX_TOOLTIP_BYTES = 1_024
18
+ TRUSTED_PRESENTATION_MAX_RUN_FIELDS = 64
19
+ TRUSTED_PRESENTATION_MAX_METADATA_FIELDS = 64
20
+ TRUSTED_PRESENTATION_MAX_SEQUENCE_ITEMS = 32
21
+ TRUSTED_PRESENTATION_MAX_FIELD_VALUE_BYTES = 512
22
+ TRUSTED_PRESENTATION_MAX_COMPATIBILITY_TEXT_BYTES = 64 * 1024
23
+ TRUSTED_PRESENTATION_MAX_PARAMETERS = 256
24
+ TRUSTED_PRESENTATION_MAX_PARAMETER_TOTAL_TEXT_BYTES = 128 * 1024
25
+ TRUSTED_PRESENTATION_MAX_PARAMETER_TEXT_BYTES = 256
26
+ TRUSTED_PRESENTATION_MAX_PARAMETER_DEPENDENCIES = 32
27
+ TRUSTED_PRESENTATION_MAX_UNAVAILABLE_FIELDS = 256
28
+ TRUSTED_PRESENTATION_MAX_ERROR_BYTES = 1_024
29
+ TRUSTED_PRESENTATION_MAX_FULL_VALUE_TEXT_BYTES = 4 * 1024 * 1024
30
+ TRUSTED_PRESENTATION_MAX_PATH_BYTES = 1_024
31
+
32
+ _FINAL_ROW_COUNT = 2
33
+ _TEXT_RESERVE_BYTES = _FINAL_ROW_COUNT * (
34
+ TRUSTED_PRESENTATION_MAX_KEY_BYTES + TRUSTED_PRESENTATION_MAX_VALUE_BYTES
35
+ )
36
+ _TOOLTIP_RESERVE_BYTES = _FINAL_ROW_COUNT * TRUSTED_PRESENTATION_MAX_TOOLTIP_BYTES
37
+ _TRUNCATION_KEY = "[truncated]"
38
+ _DISPLAY_KEY = "[display]"
39
+ _VIEW_FULL_SUFFIX = " ... [view full]"
40
+ _VIEW_FULL_KEY_SUFFIX = " ... [view full key]"
41
+ _RUN_TRUNCATION_FIELD = (
42
+ "presentation_status",
43
+ "Some run fields were truncated; see Raw tab.",
44
+ )
45
+ _METADATA_TRUNCATION_VALUE = "Additional or oversized metadata is shown as truncated."
46
+
47
+ PresentationScalar: TypeAlias = None | bool | int | float | str
48
+ PresentationValue: TypeAlias = PresentationScalar | tuple[PresentationScalar, ...]
49
+ FrozenPresentationFields: TypeAlias = tuple[tuple[str, PresentationValue], ...]
50
+ PresentationStatus: TypeAlias = Literal["available", "empty", "truncated"]
51
+
52
+ _SELECTED_RUN_FIELDS = (
53
+ "run_id",
54
+ "exp_id",
55
+ "name",
56
+ "result_table_name",
57
+ "result_counter",
58
+ "run_timestamp",
59
+ "completed_timestamp",
60
+ "is_completed",
61
+ "guid",
62
+ "captured_run_id",
63
+ "captured_counter",
64
+ "database_modified_timestamp",
65
+ "expected_results",
66
+ "expected_results_source",
67
+ "measure_parameters",
68
+ "preview_dimensions",
69
+ "measurement_exception",
70
+ "parameters_truncated",
71
+ "point_shape",
72
+ "read_setpoint_count",
73
+ "result_count",
74
+ "setpoint_count",
75
+ "setpoint_count_source",
76
+ "setpoint_shape",
77
+ "setpoint_shape_source",
78
+ "storage_bytes",
79
+ "storage_bytes_estimated",
80
+ "sweep_parameters",
81
+ "exp_name",
82
+ "sample_name",
83
+ )
84
+
85
+
86
+ @dataclass(frozen=True, slots=True)
87
+ class TrustedPresentationNode:
88
+ """One bounded tree row with a separately bounded tooltip."""
89
+
90
+ key: str
91
+ value: str
92
+ tooltip: str
93
+ parent_index: int | None
94
+ path: str = ""
95
+ key_shortened: bool = False
96
+ value_shortened: bool = False
97
+ full_key_id: str | None = None
98
+ full_value_id: str | None = None
99
+ source_key_bytes: int = 0
100
+ source_value_bytes: int = 0
101
+
102
+
103
+ @dataclass(frozen=True, slots=True)
104
+ class TrustedPresentationFullValue:
105
+ """One exact scalar retained once for the current bounded detail."""
106
+
107
+ identifier: str
108
+ text: str = field(repr=False)
109
+ utf8_bytes: int
110
+
111
+
112
+ @dataclass(frozen=True, slots=True)
113
+ class TrustedPresentationView:
114
+ """Immutable flat tree safe for iterative construction by Qt."""
115
+
116
+ nodes: tuple[TrustedPresentationNode, ...]
117
+ status: PresentationStatus
118
+ message: str
119
+ inspected_items: int
120
+ rendered_text_bytes: int
121
+ tooltip_text_bytes: int
122
+ shortened_key_count: int = 0
123
+ shortened_value_count: int = 0
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class TrustedSelectedRunPresentation:
128
+ """All non-snapshot selected-detail values that may cross into Qt."""
129
+
130
+ run_fields: FrozenPresentationFields
131
+ metadata_fields: FrozenPresentationFields
132
+ metadata: TrustedPresentationView
133
+ raw: TrustedPresentationView
134
+ parameters_truncated: bool = False
135
+ full_values: tuple[TrustedPresentationFullValue, ...] = field(
136
+ default=(),
137
+ repr=False,
138
+ )
139
+
140
+
141
+ @dataclass(slots=True)
142
+ class _NodeBuilder:
143
+ key: str
144
+ value: str
145
+ tooltip: str
146
+ parent_index: int | None
147
+ path: str = ""
148
+ key_shortened: bool = False
149
+ value_shortened: bool = False
150
+ full_key_id: str | None = None
151
+ full_value_id: str | None = None
152
+ source_key_bytes: int = 0
153
+ source_value_bytes: int = 0
154
+
155
+
156
+ @dataclass(frozen=True, slots=True)
157
+ class _Task:
158
+ key: object
159
+ value: object
160
+ parent_index: int | None
161
+ depth: int
162
+ root: bool = False
163
+ path: tuple[str, ...] = ()
164
+
165
+
166
+ @dataclass(frozen=True, slots=True)
167
+ class _ExitContainer:
168
+ container_id: int
169
+
170
+
171
+ class _FullValueRegistry:
172
+ """Deduplicate exact selected-detail strings under one aggregate bound."""
173
+
174
+ def __init__(self) -> None:
175
+ self._values_by_identity: dict[int, TrustedPresentationFullValue] = {}
176
+ self._values: list[TrustedPresentationFullValue] = []
177
+ self.text_bytes = 0
178
+
179
+ def retain(self, text: str) -> TrustedPresentationFullValue | None:
180
+ retained = self._values_by_identity.get(id(text))
181
+ if retained is not None and retained.text is text:
182
+ return retained
183
+ encoded = text.encode("utf-8", errors="replace")
184
+ if (
185
+ len(encoded) > TRUSTED_PRESENTATION_MAX_FULL_VALUE_TEXT_BYTES
186
+ or self.text_bytes + len(encoded)
187
+ > TRUSTED_PRESENTATION_MAX_FULL_VALUE_TEXT_BYTES
188
+ ):
189
+ return None
190
+ identifier = f"value:{len(self._values)}"
191
+ retained = TrustedPresentationFullValue(identifier, text, len(encoded))
192
+ self._values_by_identity[id(text)] = retained
193
+ self._values.append(retained)
194
+ self.text_bytes += len(encoded)
195
+ return retained
196
+
197
+ def checkpoint(self) -> int:
198
+ return len(self._values)
199
+
200
+ def rollback(self, checkpoint: int) -> None:
201
+ while len(self._values) > checkpoint:
202
+ retained = self._values.pop()
203
+ if self._values_by_identity.get(id(retained.text)) is retained:
204
+ del self._values_by_identity[id(retained.text)]
205
+ self.text_bytes -= retained.utf8_bytes
206
+
207
+ def freeze(self) -> tuple[TrustedPresentationFullValue, ...]:
208
+ return tuple(self._values)
209
+
210
+
211
+ class _PresentationNormalizer:
212
+ def __init__(
213
+ self,
214
+ value: object,
215
+ *,
216
+ full_values: _FullValueRegistry | None = None,
217
+ path_prefix: str = "",
218
+ initial_reasons: Sequence[str] = (),
219
+ ) -> None:
220
+ self.value = value
221
+ self.full_values = full_values
222
+ self.path_prefix = _bounded_path_prefix(path_prefix)
223
+ self.nodes: list[_NodeBuilder] = []
224
+ self.inspected_items = 0
225
+ self.rendered_text_bytes = 0
226
+ self.tooltip_text_bytes = 0
227
+ self.reasons: list[str] = list(dict.fromkeys(initial_reasons))
228
+ self.shortened_key_count = 0
229
+ self.shortened_value_count = 0
230
+ self.ancestor_containers: set[int] = set()
231
+ self.halted = False
232
+
233
+ def normalize(self) -> TrustedPresentationView:
234
+ tasks: list[_Task | _ExitContainer] = [
235
+ _Task("Value", self.value, None, 0, True, ())
236
+ ]
237
+ while tasks and not self.halted:
238
+ if (
239
+ len(self.nodes)
240
+ >= TRUSTED_PRESENTATION_MAX_RENDERED_NODES - _FINAL_ROW_COUNT
241
+ ):
242
+ self._reason("The 512-node rendering limit was reached.")
243
+ break
244
+ task = tasks.pop()
245
+ if isinstance(task, _ExitContainer):
246
+ self.ancestor_containers.discard(task.container_id)
247
+ continue
248
+ if _is_container(task.value):
249
+ self._schedule_container(task, tasks)
250
+ else:
251
+ self._add_scalar(task)
252
+
253
+ if tasks:
254
+ self._reason("Additional values were omitted from the bounded view.")
255
+ if self.shortened_key_count or self.shortened_value_count:
256
+ self._append_display_summary()
257
+ if self.reasons:
258
+ self._append_marker()
259
+ status: PresentationStatus = "truncated"
260
+ message = " ".join(dict.fromkeys(self.reasons))
261
+ elif self.nodes:
262
+ status = "available"
263
+ if self.shortened_key_count or self.shortened_value_count:
264
+ message = self._display_summary_text()
265
+ else:
266
+ message = "Selected detail normalized within all presentation limits."
267
+ else:
268
+ self._add_node("No data", "", "", None)
269
+ status = "empty"
270
+ message = "No selected-detail values were available."
271
+ return TrustedPresentationView(
272
+ nodes=tuple(
273
+ TrustedPresentationNode(
274
+ node.key,
275
+ node.value,
276
+ node.tooltip,
277
+ node.parent_index,
278
+ node.path,
279
+ node.key_shortened,
280
+ node.value_shortened,
281
+ node.full_key_id,
282
+ node.full_value_id,
283
+ node.source_key_bytes,
284
+ node.source_value_bytes,
285
+ )
286
+ for node in self.nodes
287
+ ),
288
+ status=status,
289
+ message=message,
290
+ inspected_items=self.inspected_items,
291
+ rendered_text_bytes=self.rendered_text_bytes,
292
+ tooltip_text_bytes=self.tooltip_text_bytes,
293
+ shortened_key_count=self.shortened_key_count,
294
+ shortened_value_count=self.shortened_value_count,
295
+ )
296
+
297
+ def _schedule_container(
298
+ self,
299
+ task: _Task,
300
+ tasks: list[_Task | _ExitContainer],
301
+ ) -> None:
302
+ container_id = id(task.value)
303
+ if container_id in self.ancestor_containers:
304
+ self._add_node(
305
+ task.key,
306
+ "[cyclic container unavailable]",
307
+ "[cyclic container unavailable]",
308
+ task.parent_index,
309
+ path=task.path,
310
+ )
311
+ self._reason("A cyclic container was omitted.")
312
+ return
313
+ self.ancestor_containers.add(container_id)
314
+ tasks.append(_ExitContainer(container_id))
315
+
316
+ parent_index = task.parent_index
317
+ if not task.root:
318
+ parent_index = self._add_node(
319
+ task.key,
320
+ "",
321
+ "",
322
+ task.parent_index,
323
+ path=task.path,
324
+ )
325
+ if parent_index is None:
326
+ return
327
+ if task.depth >= TRUSTED_PRESENTATION_MAX_DEPTH:
328
+ self._reason("The 16-level nesting limit was reached.")
329
+ self._add_node(
330
+ _TRUNCATION_KEY,
331
+ "Nested values omitted.",
332
+ "Nested values omitted.",
333
+ parent_index,
334
+ path=(*task.path, _TRUNCATION_KEY),
335
+ )
336
+ return
337
+
338
+ children, has_more = self._bounded_children(task.value)
339
+ if has_more:
340
+ self._reason("The 2048-item container inspection limit was reached.")
341
+ for key, value in reversed(children):
342
+ tasks.append(
343
+ _Task(
344
+ key=key,
345
+ value=value,
346
+ parent_index=parent_index,
347
+ depth=task.depth + 1,
348
+ path=(*task.path, _path_segment(key)),
349
+ )
350
+ )
351
+
352
+ def _bounded_children(
353
+ self, value: object
354
+ ) -> tuple[list[tuple[object, object]], bool]:
355
+ remaining = TRUSTED_PRESENTATION_MAX_CONTAINER_ITEMS - self.inspected_items
356
+ if remaining <= 0:
357
+ return [], True
358
+ if isinstance(value, Mapping):
359
+ iterator = iter(value.items())
360
+ else:
361
+ iterator = enumerate(value) # type: ignore[arg-type]
362
+
363
+ children: list[tuple[object, object]] = []
364
+ for _index in range(remaining + 1):
365
+ try:
366
+ child = next(iterator)
367
+ except StopIteration:
368
+ return children, False
369
+ if len(children) >= remaining:
370
+ return children, True
371
+ children.append(child)
372
+ self.inspected_items += 1
373
+ return children, False
374
+
375
+ def _add_scalar(self, task: _Task) -> None:
376
+ text = _scalar_text(task.value)
377
+ node_key = "Value" if task.root else task.key
378
+ full_value = task.value if isinstance(task.value, str) else None
379
+ if isinstance(task.value, bytes) or not (
380
+ task.value is None or isinstance(task.value, (bool, int, float, str))
381
+ ):
382
+ self._reason("A binary or unsupported scalar value was unavailable.")
383
+ self._add_node(
384
+ node_key,
385
+ text,
386
+ text,
387
+ task.parent_index,
388
+ path=task.path or (_path_segment(node_key),),
389
+ full_value=full_value,
390
+ head_and_tail=_key_text(node_key).casefold()
391
+ in {"measurement_exception", "exception", "traceback"},
392
+ )
393
+
394
+ def _add_node(
395
+ self,
396
+ key: object,
397
+ value: str,
398
+ tooltip: str,
399
+ parent_index: int | None,
400
+ *,
401
+ path: tuple[str, ...] = (),
402
+ full_value: str | None = None,
403
+ head_and_tail: bool = False,
404
+ ) -> int | None:
405
+ if (
406
+ len(self.nodes)
407
+ >= TRUSTED_PRESENTATION_MAX_RENDERED_NODES - _FINAL_ROW_COUNT
408
+ ):
409
+ self._reason("The 512-node rendering limit was reached.")
410
+ self.halted = True
411
+ return None
412
+
413
+ source_key = _key_text(key)
414
+ source_value = full_value if full_value is not None else value
415
+ source_key_bytes = len(source_key.encode("utf-8", errors="replace"))
416
+ source_value_bytes = len(source_value.encode("utf-8", errors="replace"))
417
+ backing_checkpoint = (
418
+ self.full_values.checkpoint() if self.full_values is not None else 0
419
+ )
420
+ key_shortened = source_key_bytes > TRUSTED_PRESENTATION_MAX_KEY_BYTES
421
+ value_shortened = bool(
422
+ full_value is not None
423
+ and (
424
+ source_value != value
425
+ or len(value.encode("utf-8", errors="replace"))
426
+ > TRUSTED_PRESENTATION_MAX_VALUE_BYTES
427
+ )
428
+ )
429
+ full_key_id = None
430
+ full_value_id = None
431
+ if key_shortened:
432
+ retained_key = self._retain_full_text(source_key)
433
+ if retained_key is not None:
434
+ full_key_id = retained_key.identifier
435
+ else:
436
+ self._reason(
437
+ "A complete scalar key was omitted at the 4194304-byte "
438
+ "selected-detail backing limit."
439
+ )
440
+ if value_shortened:
441
+ retained_value = self._retain_full_text(source_value)
442
+ if retained_value is not None:
443
+ full_value_id = retained_value.identifier
444
+ else:
445
+ self._reason(
446
+ "A complete scalar value was omitted at the 4194304-byte "
447
+ "selected-detail backing limit."
448
+ )
449
+
450
+ if key_shortened:
451
+ key_text = _shortened_key_text(
452
+ source_key,
453
+ TRUSTED_PRESENTATION_MAX_KEY_BYTES,
454
+ available=full_key_id is not None,
455
+ )
456
+ else:
457
+ key_text = source_key
458
+ if value_shortened:
459
+ value_text = _shortened_value_text(
460
+ value,
461
+ TRUSTED_PRESENTATION_MAX_VALUE_BYTES,
462
+ head_and_tail=head_and_tail,
463
+ available=full_value_id is not None,
464
+ )
465
+ else:
466
+ value_text, _value_cell_truncated = _truncate_utf8(
467
+ value, TRUSTED_PRESENTATION_MAX_VALUE_BYTES
468
+ )
469
+ if key_shortened or value_shortened:
470
+ parts = []
471
+ actions = []
472
+ if key_shortened:
473
+ parts.append(f"key: {source_key_bytes} UTF-8 bytes")
474
+ if full_key_id is not None:
475
+ actions.append(
476
+ "Activate the marked key cell to view or explicitly copy "
477
+ "the complete key."
478
+ )
479
+ else:
480
+ actions.append(
481
+ "The complete key was not retained at the bounded detail limit."
482
+ )
483
+ if value_shortened:
484
+ parts.append(f"value: {source_value_bytes} UTF-8 bytes")
485
+ if full_value_id is not None:
486
+ actions.append(
487
+ "Activate the marked value cell to view or explicitly copy "
488
+ "the complete value."
489
+ )
490
+ else:
491
+ actions.append(
492
+ "The complete value was not retained at the bounded detail "
493
+ "limit."
494
+ )
495
+ tooltip_text, _tooltip_truncated = _truncate_utf8(
496
+ f"Shortened for display ({'; '.join(parts)}). {' '.join(actions)}",
497
+ TRUSTED_PRESENTATION_MAX_TOOLTIP_BYTES,
498
+ )
499
+ else:
500
+ tooltip_text, _tooltip_truncated = _truncate_utf8(
501
+ tooltip, TRUSTED_PRESENTATION_MAX_TOOLTIP_BYTES
502
+ )
503
+
504
+ rendered_remaining = (
505
+ TRUSTED_PRESENTATION_MAX_RENDERED_TEXT_BYTES
506
+ - _TEXT_RESERVE_BYTES
507
+ - self.rendered_text_bytes
508
+ )
509
+ tooltip_remaining = (
510
+ TRUSTED_PRESENTATION_MAX_TOOLTIP_TEXT_BYTES
511
+ - _TOOLTIP_RESERVE_BYTES
512
+ - self.tooltip_text_bytes
513
+ )
514
+ key_bytes = len(key_text.encode("utf-8"))
515
+ value_bytes = len(value_text.encode("utf-8"))
516
+ tooltip_bytes = len(tooltip_text.encode("utf-8"))
517
+ if (
518
+ rendered_remaining < key_bytes + value_bytes
519
+ or tooltip_remaining < tooltip_bytes
520
+ ):
521
+ if self.full_values is not None:
522
+ self.full_values.rollback(backing_checkpoint)
523
+ self._reason("The 65536-byte presentation text limit was reached.")
524
+ self.halted = True
525
+ return None
526
+
527
+ node_index = len(self.nodes)
528
+ self.nodes.append(
529
+ _NodeBuilder(
530
+ key_text,
531
+ value_text,
532
+ tooltip_text,
533
+ parent_index,
534
+ _bounded_path(self.path_prefix, path),
535
+ key_shortened,
536
+ value_shortened,
537
+ full_key_id,
538
+ full_value_id,
539
+ source_key_bytes,
540
+ source_value_bytes,
541
+ )
542
+ )
543
+ self.rendered_text_bytes += key_bytes + value_bytes
544
+ self.tooltip_text_bytes += tooltip_bytes
545
+ self.shortened_key_count += int(key_shortened)
546
+ self.shortened_value_count += int(value_shortened)
547
+ return node_index
548
+
549
+ def _retain_full_text(self, text: str) -> TrustedPresentationFullValue | None:
550
+ if self.full_values is None:
551
+ return None
552
+ return self.full_values.retain(text)
553
+
554
+ def _display_summary_text(self) -> str:
555
+ parts = []
556
+ if self.shortened_key_count:
557
+ noun = "key" if self.shortened_key_count == 1 else "keys"
558
+ parts.append(f"{self.shortened_key_count} {noun}")
559
+ if self.shortened_value_count:
560
+ noun = "value" if self.shortened_value_count == 1 else "values"
561
+ parts.append(f"{self.shortened_value_count} {noun}")
562
+ subject = " and ".join(parts)
563
+ return (
564
+ f"{subject} shortened for display. Marked rows identify the affected "
565
+ "text; activate rows labelled 'view full' to view or explicitly copy "
566
+ "the complete text."
567
+ )
568
+
569
+ def _append_display_summary(self) -> None:
570
+ if len(self.nodes) >= TRUSTED_PRESENTATION_MAX_RENDERED_NODES:
571
+ return
572
+ message, _ = _truncate_utf8(
573
+ self._display_summary_text(),
574
+ TRUSTED_PRESENTATION_MAX_VALUE_BYTES,
575
+ )
576
+ tooltip, _ = _truncate_utf8(message, _TOOLTIP_RESERVE_BYTES)
577
+ self.nodes.append(
578
+ _NodeBuilder(
579
+ _DISPLAY_KEY,
580
+ message,
581
+ tooltip,
582
+ None,
583
+ _bounded_path(self.path_prefix, (_DISPLAY_KEY,)),
584
+ )
585
+ )
586
+ self.rendered_text_bytes += len(_DISPLAY_KEY.encode("utf-8")) + len(
587
+ message.encode("utf-8")
588
+ )
589
+ self.tooltip_text_bytes += len(tooltip.encode("utf-8"))
590
+
591
+ def _append_marker(self) -> None:
592
+ if len(self.nodes) >= TRUSTED_PRESENTATION_MAX_RENDERED_NODES:
593
+ return
594
+ message = " ".join(dict.fromkeys(self.reasons))
595
+ message, _ = _truncate_utf8(
596
+ message,
597
+ TRUSTED_PRESENTATION_MAX_VALUE_BYTES,
598
+ )
599
+ tooltip, _ = _truncate_utf8(message, _TOOLTIP_RESERVE_BYTES)
600
+ self.nodes.append(
601
+ _NodeBuilder(
602
+ _TRUNCATION_KEY,
603
+ message,
604
+ tooltip,
605
+ None,
606
+ _bounded_path(self.path_prefix, (_TRUNCATION_KEY,)),
607
+ )
608
+ )
609
+ self.rendered_text_bytes += len(_TRUNCATION_KEY.encode("utf-8")) + len(
610
+ message.encode("utf-8")
611
+ )
612
+ self.tooltip_text_bytes += len(tooltip.encode("utf-8"))
613
+
614
+ def _reason(self, message: str) -> None:
615
+ if message not in self.reasons:
616
+ self.reasons.append(message)
617
+
618
+
619
+ def _is_container(value: object) -> bool:
620
+ return isinstance(value, (Mapping, list, tuple))
621
+
622
+
623
+ def _key_text(value: object) -> str:
624
+ if isinstance(value, str):
625
+ return value
626
+ if value is None or isinstance(value, (bool, int, float)):
627
+ return str(value)
628
+ return f"<{type(value).__name__} key>"
629
+
630
+
631
+ def _scalar_text(value: object) -> str:
632
+ if value is None:
633
+ return ""
634
+ if isinstance(value, bytes):
635
+ return f"[binary value omitted: {len(value)} bytes]"
636
+ if isinstance(value, str):
637
+ return value.replace("\r", " ").replace("\n", " ")
638
+ if isinstance(value, float):
639
+ return f"{value:.6g}"
640
+ if isinstance(value, (bool, int)):
641
+ return str(value)
642
+ return f"[{type(value).__name__} value unavailable]"
643
+
644
+
645
+ def _bounded_path_prefix(value: str) -> str:
646
+ if not value:
647
+ return ""
648
+ return _bounded_path("", (_path_segment(value),))
649
+
650
+
651
+ def _path_segment(value: object) -> str:
652
+ text = _key_text(value).replace("~", "~0").replace("/", "~1")
653
+ encoded = text.encode("utf-8", errors="replace")
654
+ if len(encoded) <= TRUSTED_PRESENTATION_MAX_KEY_BYTES:
655
+ return text
656
+ digest = hashlib.sha256(encoded).hexdigest()[:16]
657
+ prefix, _ = _truncate_utf8(
658
+ text,
659
+ TRUSTED_PRESENTATION_MAX_KEY_BYTES - len(digest) - 2,
660
+ )
661
+ return f"{prefix}~{digest}"
662
+
663
+
664
+ def _bounded_path(prefix: str, segments: Sequence[str]) -> str:
665
+ joined = "/".join((*((prefix.lstrip("/"),) if prefix else ()), *segments))
666
+ path = f"/{joined}" if joined else "/"
667
+ encoded = path.encode("utf-8", errors="replace")
668
+ if len(encoded) <= TRUSTED_PRESENTATION_MAX_PATH_BYTES:
669
+ return path
670
+ digest = hashlib.sha256(encoded).hexdigest()
671
+ retained, _ = _truncate_utf8(
672
+ path,
673
+ TRUSTED_PRESENTATION_MAX_PATH_BYTES - len(digest) - 2,
674
+ )
675
+ return f"{retained}~{digest}"
676
+
677
+
678
+ def _utf8_tail(text: str, limit: int) -> str:
679
+ if limit <= 0:
680
+ return ""
681
+ encoded = text.encode("utf-8", errors="replace")
682
+ if len(encoded) <= limit:
683
+ return text
684
+ tail = encoded[-limit:]
685
+ while tail:
686
+ try:
687
+ return tail.decode("utf-8")
688
+ except UnicodeDecodeError as error:
689
+ tail = tail[error.end :]
690
+ return ""
691
+
692
+
693
+ def _shortened_value_text(
694
+ text: str,
695
+ limit: int,
696
+ *,
697
+ head_and_tail: bool,
698
+ available: bool,
699
+ ) -> str:
700
+ suffix = _VIEW_FULL_SUFFIX if available else " ... [complete value unavailable]"
701
+ suffix_bytes = len(suffix.encode("utf-8"))
702
+ if suffix_bytes >= limit:
703
+ return _truncate_utf8(suffix, limit)[0]
704
+ body_limit = limit - suffix_bytes
705
+ if not head_and_tail:
706
+ prefix, _ = _truncate_utf8(text, body_limit)
707
+ return f"{prefix}{suffix}"
708
+
709
+ separator = " ... "
710
+ separator_bytes = len(separator.encode("utf-8"))
711
+ if separator_bytes >= body_limit:
712
+ prefix, _ = _truncate_utf8(text, body_limit)
713
+ return f"{prefix}{suffix}"
714
+ remaining = body_limit - separator_bytes
715
+ head_limit = max(1, remaining // 3)
716
+ tail_limit = remaining - head_limit
717
+ head, _ = _truncate_utf8(text, head_limit)
718
+ if head.endswith("..."):
719
+ head = head[:-3]
720
+ tail = _utf8_tail(text, tail_limit)
721
+ return f"{head}{separator}{tail}{suffix}"
722
+
723
+
724
+ def _shortened_key_text(text: str, limit: int, *, available: bool) -> str:
725
+ suffix = _VIEW_FULL_KEY_SUFFIX if available else " ... [complete key unavailable]"
726
+ suffix_bytes = len(suffix.encode("utf-8"))
727
+ if suffix_bytes >= limit:
728
+ return _truncate_utf8(suffix, limit)[0]
729
+ prefix, _ = _truncate_utf8(text, limit - suffix_bytes)
730
+ if prefix.endswith("..."):
731
+ prefix = prefix[:-3]
732
+ return f"{prefix}{suffix}"
733
+
734
+
735
+ def _truncate_utf8(text: str, limit: int) -> tuple[str, bool]:
736
+ encoded = text.encode("utf-8", errors="replace")
737
+ if len(encoded) <= limit:
738
+ return text, False
739
+ if limit <= 0:
740
+ return "", True
741
+ marker = b"..." if limit >= 3 else b""
742
+ prefix = encoded[: max(0, limit - len(marker))]
743
+ while prefix:
744
+ try:
745
+ decoded = prefix.decode("utf-8")
746
+ break
747
+ except UnicodeDecodeError as error:
748
+ prefix = prefix[: error.start]
749
+ else:
750
+ decoded = ""
751
+ return decoded + marker.decode("ascii"), True
752
+
753
+
754
+ def _bounded_field_value(value: object) -> tuple[PresentationValue, bool]:
755
+ if value is None or isinstance(value, (bool, int, float)):
756
+ return value, False
757
+ if isinstance(value, str):
758
+ return _truncate_utf8(value, TRUSTED_PRESENTATION_MAX_FIELD_VALUE_BYTES)
759
+ if isinstance(value, bytes):
760
+ return f"[binary value omitted: {len(value)} bytes]", True
761
+ if isinstance(value, (list, tuple)):
762
+ output: list[PresentationScalar] = []
763
+ truncated = len(value) > TRUSTED_PRESENTATION_MAX_SEQUENCE_ITEMS
764
+ for item in value[:TRUSTED_PRESENTATION_MAX_SEQUENCE_ITEMS]:
765
+ if item is None or isinstance(item, (bool, int, float)):
766
+ output.append(item)
767
+ elif isinstance(item, str):
768
+ bounded, was_truncated = _truncate_utf8(
769
+ item, TRUSTED_PRESENTATION_MAX_FIELD_VALUE_BYTES
770
+ )
771
+ output.append(bounded)
772
+ truncated = truncated or was_truncated
773
+ elif isinstance(item, bytes):
774
+ output.append(f"[binary value omitted: {len(item)} bytes]")
775
+ truncated = True
776
+ else:
777
+ output.append("[nested value omitted; see Raw tab]")
778
+ truncated = True
779
+ return tuple(output), truncated
780
+ return "[nested value omitted; see Raw tab]", True
781
+
782
+
783
+ def bounded_presentation_text(
784
+ value: object,
785
+ *,
786
+ limit: int = TRUSTED_PRESENTATION_MAX_PARAMETER_TEXT_BYTES,
787
+ ) -> tuple[str, bool]:
788
+ """Format one table identifier/label without retaining unbounded text."""
789
+
790
+ if isinstance(value, bytes):
791
+ return f"[binary value omitted: {len(value)} bytes]", True
792
+ if not isinstance(value, str):
793
+ value = "" if value is None else str(value)
794
+ return _truncate_utf8(value, limit)
795
+
796
+
797
+ def bounded_presentation_scalar(
798
+ value: object,
799
+ ) -> tuple[PresentationScalar, bool]:
800
+ """Bound one scalar used by a Qt cell and its tooltip."""
801
+
802
+ if value is None or isinstance(value, (bool, int, float)):
803
+ return value, False
804
+ if isinstance(value, bytes):
805
+ return f"[binary value omitted: {len(value)} bytes]", True
806
+ if isinstance(value, str):
807
+ return _truncate_utf8(value, TRUSTED_PRESENTATION_MAX_FIELD_VALUE_BYTES)
808
+ return f"[{type(value).__name__} value unavailable]", True
809
+
810
+
811
+ def bounded_presentation_names(
812
+ values: Sequence[object],
813
+ ) -> tuple[tuple[str, ...], bool]:
814
+ """Bound an identifier list retained by the GUI-delivered detail object."""
815
+
816
+ output: list[str] = []
817
+ truncated = len(values) > TRUSTED_PRESENTATION_MAX_UNAVAILABLE_FIELDS
818
+ for value in values[:TRUSTED_PRESENTATION_MAX_UNAVAILABLE_FIELDS]:
819
+ bounded, was_truncated = bounded_presentation_text(value)
820
+ output.append(bounded)
821
+ truncated = truncated or was_truncated
822
+ if truncated:
823
+ if len(output) >= TRUSTED_PRESENTATION_MAX_UNAVAILABLE_FIELDS:
824
+ output.pop()
825
+ output.append("[additional unavailable fields omitted]")
826
+ return tuple(dict.fromkeys(output)), truncated
827
+
828
+
829
+ def bounded_presentation_error(value: object) -> str:
830
+ """Return one bounded failure string safe for queued Qt publication."""
831
+
832
+ try:
833
+ message = str(value).strip()
834
+ except Exception:
835
+ message = f"{type(value).__name__} details are unavailable."
836
+ if not message:
837
+ message = "Run details are unavailable."
838
+ bounded, _truncated = _truncate_utf8(
839
+ message,
840
+ TRUSTED_PRESENTATION_MAX_ERROR_BYTES,
841
+ )
842
+ return bounded
843
+
844
+
845
+ def bounded_selected_run_fields(
846
+ fields: Mapping[str, object],
847
+ ) -> FrozenPresentationFields:
848
+ """Retain only bounded run fields needed by overview, actions, and rows."""
849
+
850
+ output: list[tuple[str, PresentationValue]] = []
851
+ truncated = False
852
+ retained_bytes = 0
853
+ for name in _SELECTED_RUN_FIELDS[:TRUSTED_PRESENTATION_MAX_RUN_FIELDS]:
854
+ if name not in fields:
855
+ continue
856
+ bounded, was_truncated = _bounded_field_value(fields[name])
857
+ field_bytes = len(name.encode("utf-8")) + _presentation_value_bytes(bounded)
858
+ if (
859
+ retained_bytes + field_bytes
860
+ > TRUSTED_PRESENTATION_MAX_COMPATIBILITY_TEXT_BYTES
861
+ ):
862
+ truncated = True
863
+ break
864
+ output.append((name, bounded))
865
+ retained_bytes += field_bytes
866
+ truncated = truncated or was_truncated
867
+ if truncated:
868
+ _make_room_for_field_marker(
869
+ output,
870
+ retained_bytes,
871
+ _RUN_TRUNCATION_FIELD,
872
+ max_items=TRUSTED_PRESENTATION_MAX_RUN_FIELDS,
873
+ )
874
+ output.append(_RUN_TRUNCATION_FIELD)
875
+ return tuple(output)
876
+
877
+
878
+ def bounded_metadata_fields(
879
+ fields: Mapping[str, object],
880
+ ) -> FrozenPresentationFields:
881
+ """Return a small compatibility mapping without retaining raw metadata."""
882
+
883
+ output: list[tuple[str, PresentationValue]] = []
884
+ truncated = len(fields) > TRUSTED_PRESENTATION_MAX_METADATA_FIELDS
885
+ seen_keys: set[str] = set()
886
+ retained_bytes = 0
887
+ for index, (name, value) in enumerate(fields.items()):
888
+ if index >= TRUSTED_PRESENTATION_MAX_METADATA_FIELDS:
889
+ break
890
+ bounded_name, key_truncated = _truncate_utf8(
891
+ _key_text(name), TRUSTED_PRESENTATION_MAX_KEY_BYTES
892
+ )
893
+ if bounded_name in seen_keys:
894
+ truncated = True
895
+ continue
896
+ seen_keys.add(bounded_name)
897
+ bounded_value, value_truncated = _bounded_field_value(value)
898
+ field_bytes = len(bounded_name.encode("utf-8")) + _presentation_value_bytes(
899
+ bounded_value
900
+ )
901
+ if (
902
+ retained_bytes + field_bytes
903
+ > TRUSTED_PRESENTATION_MAX_COMPATIBILITY_TEXT_BYTES
904
+ ):
905
+ truncated = True
906
+ break
907
+ output.append((bounded_name, bounded_value))
908
+ retained_bytes += field_bytes
909
+ truncated = truncated or key_truncated or value_truncated
910
+ if truncated:
911
+ marker_name = _TRUNCATION_KEY
912
+ while marker_name in seen_keys:
913
+ marker_name += "*"
914
+ marker = (marker_name, _METADATA_TRUNCATION_VALUE)
915
+ _make_room_for_field_marker(
916
+ output,
917
+ retained_bytes,
918
+ marker,
919
+ max_items=TRUSTED_PRESENTATION_MAX_METADATA_FIELDS,
920
+ )
921
+ output.append(marker)
922
+ return tuple(output)
923
+
924
+
925
+ def _make_room_for_field_marker(
926
+ output: list[tuple[str, PresentationValue]],
927
+ retained_bytes: int,
928
+ marker: tuple[str, PresentationValue],
929
+ *,
930
+ max_items: int,
931
+ ) -> None:
932
+ marker_bytes = _presentation_field_bytes(*marker)
933
+ while output and (
934
+ len(output) >= max_items
935
+ or retained_bytes + marker_bytes
936
+ > TRUSTED_PRESENTATION_MAX_COMPATIBILITY_TEXT_BYTES
937
+ ):
938
+ removed_name, removed_value = output.pop()
939
+ retained_bytes -= _presentation_field_bytes(removed_name, removed_value)
940
+
941
+
942
+ def _presentation_field_bytes(name: str, value: PresentationValue) -> int:
943
+ return len(name.encode("utf-8")) + _presentation_value_bytes(value)
944
+
945
+
946
+ def _presentation_value_bytes(value: PresentationValue) -> int:
947
+ if isinstance(value, tuple):
948
+ return sum(len(_scalar_text(item).encode("utf-8")) for item in value)
949
+ return len(_scalar_text(value).encode("utf-8"))
950
+
951
+
952
+ def normalize_presentation_tree(value: object) -> TrustedPresentationView:
953
+ """Flatten a nested value iteratively under all presentation budgets."""
954
+
955
+ return _PresentationNormalizer(value).normalize()
956
+
957
+
958
+ def build_selected_run_presentation(
959
+ *,
960
+ run_fields: Mapping[str, object],
961
+ metadata_fields: Mapping[str, object],
962
+ parameters: Sequence[Mapping[str, object]],
963
+ snapshot_summary: Mapping[str, object],
964
+ setpoint_summaries: Sequence[Mapping[str, object]],
965
+ unavailable_fields: Sequence[str],
966
+ parameters_truncated: bool = False,
967
+ ) -> TrustedSelectedRunPresentation:
968
+ """Build every non-snapshot tree/table input before crossing into Qt."""
969
+
970
+ raw_run = {name: value for name, value in run_fields.items() if name != "snapshot"}
971
+ raw_value = {
972
+ "Run": raw_run,
973
+ "Metadata": metadata_fields,
974
+ "Snapshot": snapshot_summary,
975
+ "Parameters": parameters,
976
+ "Setpoint summaries": setpoint_summaries,
977
+ "Unavailable fields": unavailable_fields,
978
+ }
979
+ if parameters_truncated:
980
+ raw_value["Parameters status"] = (
981
+ "Additional or oversized parameter details were omitted at "
982
+ "presentation limits."
983
+ )
984
+ full_values = _FullValueRegistry()
985
+ metadata = _PresentationNormalizer(
986
+ metadata_fields,
987
+ full_values=full_values,
988
+ path_prefix="Metadata",
989
+ ).normalize()
990
+ initial_reasons: list[str] = []
991
+ presentation_omissions = parameters_truncated or any(
992
+ name.endswith(".presentation") for name in unavailable_fields
993
+ )
994
+ upstream_unavailable = any(
995
+ not name.endswith(".presentation") for name in unavailable_fields
996
+ )
997
+ if presentation_omissions:
998
+ initial_reasons.append(
999
+ "One or more selected-run structures were omitted at presentation limits."
1000
+ )
1001
+ if upstream_unavailable:
1002
+ initial_reasons.append(
1003
+ "One or more selected-run fields were unavailable upstream; complete "
1004
+ "source data was omitted."
1005
+ )
1006
+ snapshot_status = snapshot_summary.get("Status")
1007
+ if isinstance(snapshot_status, str) and snapshot_status.casefold() in {
1008
+ "truncated",
1009
+ "malformed",
1010
+ "unavailable",
1011
+ }:
1012
+ initial_reasons.append(
1013
+ "Snapshot source data was truncated, malformed, or unavailable."
1014
+ )
1015
+ raw = _PresentationNormalizer(
1016
+ raw_value,
1017
+ full_values=full_values,
1018
+ path_prefix="Raw",
1019
+ initial_reasons=initial_reasons,
1020
+ ).normalize()
1021
+ return TrustedSelectedRunPresentation(
1022
+ run_fields=bounded_selected_run_fields(run_fields),
1023
+ metadata_fields=bounded_metadata_fields(metadata_fields),
1024
+ metadata=metadata,
1025
+ raw=raw,
1026
+ parameters_truncated=parameters_truncated,
1027
+ full_values=full_values.freeze(),
1028
+ )
1029
+
1030
+
1031
+ __all__ = [
1032
+ "TRUSTED_PRESENTATION_MAX_CONTAINER_ITEMS",
1033
+ "TRUSTED_PRESENTATION_MAX_COMPATIBILITY_TEXT_BYTES",
1034
+ "TRUSTED_PRESENTATION_MAX_DEPTH",
1035
+ "TRUSTED_PRESENTATION_MAX_ERROR_BYTES",
1036
+ "TRUSTED_PRESENTATION_MAX_FIELD_VALUE_BYTES",
1037
+ "TRUSTED_PRESENTATION_MAX_FULL_VALUE_TEXT_BYTES",
1038
+ "TRUSTED_PRESENTATION_MAX_KEY_BYTES",
1039
+ "TRUSTED_PRESENTATION_MAX_METADATA_FIELDS",
1040
+ "TRUSTED_PRESENTATION_MAX_PARAMETERS",
1041
+ "TRUSTED_PRESENTATION_MAX_PATH_BYTES",
1042
+ "TRUSTED_PRESENTATION_MAX_PARAMETER_TOTAL_TEXT_BYTES",
1043
+ "TRUSTED_PRESENTATION_MAX_PARAMETER_DEPENDENCIES",
1044
+ "TRUSTED_PRESENTATION_MAX_PARAMETER_TEXT_BYTES",
1045
+ "TRUSTED_PRESENTATION_MAX_RENDERED_NODES",
1046
+ "TRUSTED_PRESENTATION_MAX_RENDERED_TEXT_BYTES",
1047
+ "TRUSTED_PRESENTATION_MAX_RUN_FIELDS",
1048
+ "TRUSTED_PRESENTATION_MAX_SEQUENCE_ITEMS",
1049
+ "TRUSTED_PRESENTATION_MAX_TOOLTIP_BYTES",
1050
+ "TRUSTED_PRESENTATION_MAX_TOOLTIP_TEXT_BYTES",
1051
+ "TRUSTED_PRESENTATION_MAX_UNAVAILABLE_FIELDS",
1052
+ "TRUSTED_PRESENTATION_MAX_VALUE_BYTES",
1053
+ "TrustedPresentationNode",
1054
+ "TrustedPresentationFullValue",
1055
+ "TrustedPresentationView",
1056
+ "TrustedSelectedRunPresentation",
1057
+ "bounded_metadata_fields",
1058
+ "bounded_presentation_scalar",
1059
+ "bounded_presentation_names",
1060
+ "bounded_presentation_error",
1061
+ "bounded_presentation_text",
1062
+ "bounded_selected_run_fields",
1063
+ "build_selected_run_presentation",
1064
+ "normalize_presentation_tree",
1065
+ ]