onec-interactive-runtime-core 0.1.19__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 (87) hide show
  1. onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
  2. onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
  3. onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
  4. onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
  5. onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
  6. onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
  7. onec_runtime/AGENTS.md +7 -0
  8. onec_runtime/__init__.py +3 -0
  9. onec_runtime/artifacts.py +59 -0
  10. onec_runtime/bootstrap.py +310 -0
  11. onec_runtime/breakpoint_workspace.py +249 -0
  12. onec_runtime/bsl/__init__.py +139 -0
  13. onec_runtime/bsl/diagnostics.py +1046 -0
  14. onec_runtime/bsl/full_ast_worker_projection.py +522 -0
  15. onec_runtime/bsl/generated_semantic_parser.py +6322 -0
  16. onec_runtime/bsl/lexer.py +164 -0
  17. onec_runtime/bsl/module_catalog.py +420 -0
  18. onec_runtime/bsl/module_delta.py +1079 -0
  19. onec_runtime/bsl/module_universe.py +1877 -0
  20. onec_runtime/bsl/notebook_cells.py +232 -0
  21. onec_runtime/bsl/notebook_method_globals.py +74 -0
  22. onec_runtime/bsl/notebook_methods.py +175 -0
  23. onec_runtime/bsl/parser_artifact_identity.py +119 -0
  24. onec_runtime/bsl/parser_target.py +388 -0
  25. onec_runtime/bsl/preprocessor.py +16 -0
  26. onec_runtime/bsl/semantic_lowering.py +1539 -0
  27. onec_runtime/bsl/source_maps.py +2411 -0
  28. onec_runtime/bsl/worker_dependency_resolver.py +289 -0
  29. onec_runtime/bsl/worker_preprocessor.py +247 -0
  30. onec_runtime/bsl/worker_projection_model.py +137 -0
  31. onec_runtime/bsl/worker_reload_source_map.py +1697 -0
  32. onec_runtime/capture.py +113 -0
  33. onec_runtime/capture_source.py +453 -0
  34. onec_runtime/compact_table.py +376 -0
  35. onec_runtime/compact_table_backend.py +500 -0
  36. onec_runtime/config.py +198 -0
  37. onec_runtime/configurator_agent.py +520 -0
  38. onec_runtime/controller_worker.py +400 -0
  39. onec_runtime/credentials.py +18 -0
  40. onec_runtime/epf_container.py +196 -0
  41. onec_runtime/errors.py +205 -0
  42. onec_runtime/experiment.py +130 -0
  43. onec_runtime/extension_bundle.py +944 -0
  44. onec_runtime/extension_lifecycle.py +402 -0
  45. onec_runtime/extension_state.py +222 -0
  46. onec_runtime/fault_injection.py +30 -0
  47. onec_runtime/kernel.py +179 -0
  48. onec_runtime/lease.py +123 -0
  49. onec_runtime/observation.py +287 -0
  50. onec_runtime/performance_profile.py +245 -0
  51. onec_runtime/privacy.py +263 -0
  52. onec_runtime/processes.py +265 -0
  53. onec_runtime/prototype_runtime.py +2644 -0
  54. onec_runtime/rdbg/__init__.py +1 -0
  55. onec_runtime/rdbg/models.py +130 -0
  56. onec_runtime/rdbg/reconnect.py +59 -0
  57. onec_runtime/rdbg/session.py +913 -0
  58. onec_runtime/rdbg/transport.py +138 -0
  59. onec_runtime/rdbg/xml_codec.py +980 -0
  60. onec_runtime/recovery.py +106 -0
  61. onec_runtime/recovery_journal.py +79 -0
  62. onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
  63. onec_runtime/resources/extension/extension-manifest.json +108 -0
  64. onec_runtime/runtime_api.py +5272 -0
  65. onec_runtime/runtime_contracts.py +196 -0
  66. onec_runtime/server_worker.py +2034 -0
  67. onec_runtime/session.py +2123 -0
  68. onec_runtime/startup_diagnostics.py +45 -0
  69. onec_runtime/stop_routing.py +65 -0
  70. onec_runtime/supervised_processes.py +232 -0
  71. onec_runtime/supervised_runtime.py +512 -0
  72. onec_runtime/supervisor.py +363 -0
  73. onec_runtime/supervisor_model.py +148 -0
  74. onec_runtime/supervisor_protocol.py +77 -0
  75. onec_runtime/table_materialization.py +471 -0
  76. onec_runtime/table_transfer_backend.py +203 -0
  77. onec_runtime/table_value.py +311 -0
  78. onec_runtime/toolchain.py +730 -0
  79. onec_runtime/value_materialization.py +432 -0
  80. onec_runtime/value_transfer_backend.py +199 -0
  81. onec_runtime/worker_breakpoints.py +1431 -0
  82. onec_runtime/worker_epf.py +313 -0
  83. onec_runtime/worker_epf_template.py +53 -0
  84. onec_runtime/worker_stage_protocol.py +516 -0
  85. onec_runtime/worker_universe.py +3772 -0
  86. onec_runtime_build/__init__.py +1 -0
  87. onec_runtime_build/parser_target_development.py +116 -0
@@ -0,0 +1,500 @@
1
+ from __future__ import annotations
2
+
3
+ from base64 import b64decode
4
+ import binascii
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from hashlib import sha256
8
+ import re
9
+ from uuid import uuid4
10
+
11
+ import pandas as pd
12
+
13
+ from onec_runtime.compact_table import decode_compact_table_payload
14
+ from onec_runtime.errors import ProtocolError
15
+ from onec_runtime.experiment import bsl_string_literal
16
+ from onec_runtime.performance_profile import PhaseRecorder
17
+ from onec_runtime.rdbg.models import CollectionRow
18
+ from onec_runtime.table_materialization import ReferenceMode, ReferencePolicy
19
+
20
+
21
+ _HANDLE = re.compile(
22
+ r"Контекст\.[^\W\d]\w*(?:\.[^\W\d]\w*)*\Z",
23
+ re.UNICODE,
24
+ )
25
+ _CONTEXT_KEY = re.compile(r"__onec_compact_table_[0-9a-f]{32}\Z")
26
+ _HASH = re.compile(r"[0-9a-f]{64}\Z")
27
+ _IDENTIFIER = re.compile(r"[^\W\d]\w*\Z", re.UNICODE)
28
+ _SCALAR_KINDS = frozenset(
29
+ {"string", "nullable_string", "boolean", "integer", "number", "datetime", "uuid"}
30
+ )
31
+
32
+
33
+ @dataclass(frozen=True, slots=True)
34
+ class CompactColumn:
35
+ name: str
36
+ kind: str
37
+ is_reference: bool
38
+
39
+
40
+ def _compact_cell_kind(type_name: str) -> tuple[str, bool] | None:
41
+ if type_name in {"Неопределено", "Null"}:
42
+ return None
43
+ if type_name.startswith("ПеречислениеСсылка."):
44
+ return "nullable_string", False
45
+ if "Ссылка." in type_name:
46
+ return "reference", True
47
+ kinds = {
48
+ "Строка": "nullable_string",
49
+ "Булево": "boolean",
50
+ "Число": "number",
51
+ "Дата": "datetime",
52
+ "УникальныйИдентификатор": "uuid",
53
+ }
54
+ kind = kinds.get(type_name)
55
+ if kind is None:
56
+ raise ProtocolError("unsupported compact table column type")
57
+ return kind, False
58
+
59
+
60
+ def infer_compact_columns(
61
+ rows: tuple[CollectionRow, ...],
62
+ ) -> tuple[CompactColumn, ...] | None:
63
+ if not rows:
64
+ return None
65
+ names = tuple(cell.name for cell in rows[0].cells)
66
+ if not names or len(set(names)) != len(names):
67
+ raise ProtocolError("compact table frame schema is invalid")
68
+ observed: list[tuple[str, bool] | None] = [None] * len(names)
69
+ for row in rows:
70
+ if tuple(cell.name for cell in row.cells) != names:
71
+ raise ProtocolError("compact table frame schema changed")
72
+ for ordinal, cell in enumerate(row.cells):
73
+ candidate = _compact_cell_kind(cell.type_name)
74
+ if candidate is None:
75
+ continue
76
+ if observed[ordinal] is None:
77
+ observed[ordinal] = candidate
78
+ elif observed[ordinal] != candidate:
79
+ raise ProtocolError("compact table frame column type changed")
80
+ if any(item is None for item in observed):
81
+ return None
82
+ return tuple(
83
+ CompactColumn(name, item[0], item[1])
84
+ for name, item in zip(names, observed, strict=True)
85
+ if item is not None
86
+ )
87
+
88
+
89
+ def infer_declared_compact_columns(
90
+ rows: tuple[CollectionRow, ...],
91
+ ) -> tuple[CompactColumn, ...] | None:
92
+ if not rows:
93
+ return None
94
+ columns: list[CompactColumn] = []
95
+ for row in rows:
96
+ cells = {cell.name: cell for cell in row.cells}
97
+ if set(cells) != {"Имя", "Вид", "Ссылка"}:
98
+ raise ProtocolError("declared compact table schema is invalid")
99
+ name = cells["Имя"].value_string
100
+ kind = cells["Вид"].value_string
101
+ is_reference = cells["Ссылка"].value_boolean
102
+ if not name or is_reference is None:
103
+ raise ProtocolError("declared compact table column is invalid")
104
+ if not kind:
105
+ return None
106
+ if kind not in _SCALAR_KINDS | {"reference"}:
107
+ raise ProtocolError("declared compact table kind is invalid")
108
+ if (kind == "reference") != is_reference:
109
+ raise ProtocolError("declared compact table reference marker is invalid")
110
+ columns.append(CompactColumn(name, kind, is_reference))
111
+ if len({column.name for column in columns}) != len(columns):
112
+ raise ProtocolError("declared compact table column names are invalid")
113
+ return tuple(columns)
114
+
115
+
116
+ def _reference_mode(value: str | ReferenceMode) -> ReferenceMode:
117
+ try:
118
+ return ReferenceMode(value)
119
+ except ValueError as error:
120
+ raise ProtocolError("unknown table reference mode") from error
121
+
122
+
123
+ def build_compact_transfer_instruction(
124
+ handle: str,
125
+ policy: ReferencePolicy,
126
+ context_key: str,
127
+ *,
128
+ runtime_generation: int,
129
+ context_generation: int,
130
+ columns: tuple[CompactColumn, ...] | None = None,
131
+ max_rows: int | None = None,
132
+ max_payload_bytes: int | None = None,
133
+ ) -> str:
134
+ if not _HANDLE.fullmatch(handle):
135
+ raise ProtocolError(
136
+ "table handle must be one direct or dotted persistent Context path"
137
+ )
138
+ if not _CONTEXT_KEY.fullmatch(context_key):
139
+ raise ProtocolError("compact table context key is invalid")
140
+ if runtime_generation <= 0 or context_generation <= 0:
141
+ raise ProtocolError("table transfer generations must be positive")
142
+ bounded_rows = _optional_budget(max_rows, "table row budget")
143
+ bounded_bytes = _optional_budget(max_payload_bytes, "table byte budget")
144
+ default = _reference_mode(policy.refs)
145
+ overrides = dict(policy.ref_columns or {})
146
+ if columns is not None:
147
+ return _build_specialized_compact_transfer_instruction(
148
+ handle,
149
+ policy,
150
+ context_key,
151
+ runtime_generation=runtime_generation,
152
+ context_generation=context_generation,
153
+ columns=columns,
154
+ max_rows=bounded_rows,
155
+ max_payload_bytes=bounded_bytes,
156
+ )
157
+ lines = ["РежимыСсылокМатериализации = Новый Соответствие;"]
158
+ for column in sorted(overrides):
159
+ if not isinstance(column, str) or not column:
160
+ raise ProtocolError("table reference column name is invalid")
161
+ mode = _reference_mode(overrides[column])
162
+ lines.append(
163
+ "РежимыСсылокМатериализации.Вставить("
164
+ f"{bsl_string_literal(column)}, {bsl_string_literal(mode.value)});"
165
+ )
166
+ lines.extend(
167
+ [
168
+ "КомпактнаяМатериализация = "
169
+ "RuntimeTableTransferServer.СериализоватьКомпактнуюТаблицу("
170
+ f"{handle}, {bsl_string_literal(default.value)}, "
171
+ "РежимыСсылокМатериализации, "
172
+ f"{bounded_rows}, {bounded_bytes});",
173
+ f"Контекст.Вставить({bsl_string_literal(context_key)}, "
174
+ "КомпактнаяМатериализация.Base64);",
175
+ "Результат = "
176
+ f"Формат({runtime_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
177
+ f"Формат({context_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
178
+ "Формат(КомпактнаяМатериализация.Размер, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
179
+ "КомпактнаяМатериализация.Хеш + \"|\" + "
180
+ "Формат(СтрДлина(КомпактнаяМатериализация.Base64), "
181
+ "\"ЧГ=0; ЧДЦ=0\");",
182
+ ]
183
+ )
184
+ return "\n".join(lines)
185
+
186
+
187
+ def _optional_budget(value: int | None, label: str) -> int:
188
+ if value is None:
189
+ return 0
190
+ if type(value) is not int or value <= 0:
191
+ raise ProtocolError(f"{label} must be positive")
192
+ return value
193
+
194
+
195
+ def _table_field(row_name: str, column_name: str) -> str:
196
+ if _IDENTIFIER.fullmatch(column_name):
197
+ return f"{row_name}.{column_name}"
198
+ return f"{row_name}[{bsl_string_literal(column_name)}]"
199
+
200
+
201
+ def _metadata_lines(
202
+ context_key: str,
203
+ *,
204
+ runtime_generation: int,
205
+ context_generation: int,
206
+ ) -> list[str]:
207
+ return [
208
+ f"Контекст.Вставить({bsl_string_literal(context_key)}, "
209
+ "КомпактнаяМатериализация.Base64);",
210
+ "Результат = "
211
+ f"Формат({runtime_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
212
+ f"Формат({context_generation}, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
213
+ "Формат(КомпактнаяМатериализация.Размер, \"ЧГ=0; ЧДЦ=0\") + \"|\" + "
214
+ "КомпактнаяМатериализация.Хеш + \"|\" + "
215
+ "Формат(СтрДлина(КомпактнаяМатериализация.Base64), "
216
+ "\"ЧГ=0; ЧДЦ=0\");",
217
+ ]
218
+
219
+
220
+ def _build_specialized_compact_transfer_instruction(
221
+ handle: str,
222
+ policy: ReferencePolicy,
223
+ context_key: str,
224
+ *,
225
+ runtime_generation: int,
226
+ context_generation: int,
227
+ columns: tuple[CompactColumn, ...],
228
+ max_rows: int,
229
+ max_payload_bytes: int,
230
+ ) -> str:
231
+ if not columns:
232
+ raise ProtocolError("compact table schema has no columns")
233
+ names = [column.name for column in columns]
234
+ if any(not name for name in names) or len(set(names)) != len(names):
235
+ raise ProtocolError("compact table column names are invalid")
236
+ default = _reference_mode(policy.refs)
237
+ overrides = dict(policy.ref_columns or {})
238
+ references = {column.name for column in columns if column.is_reference}
239
+ unknown = set(overrides) - references
240
+ if unknown:
241
+ raise ProtocolError("unknown reference column: " + ", ".join(sorted(unknown)))
242
+
243
+ effective_kinds: list[str] = []
244
+ reference_modes: dict[str, ReferenceMode] = {}
245
+ for column in columns:
246
+ if column.is_reference:
247
+ mode = _reference_mode(overrides.get(column.name, default))
248
+ effective_kinds.append(mode.value)
249
+ reference_modes[column.name] = mode
250
+ else:
251
+ if column.kind not in _SCALAR_KINDS:
252
+ raise ProtocolError(
253
+ f"unsupported compact scalar kind for column {column.name}"
254
+ )
255
+ effective_kinds.append(column.kind)
256
+
257
+ lines = [
258
+ "СтрокиJSONL = Новый Массив;",
259
+ "КолонкиJSONL = Новый Массив;",
260
+ f"МаксимумСтрокМатериализации = {max_rows};",
261
+ f"МаксимумБайтМатериализации = {max_payload_bytes};",
262
+ ]
263
+ lines.extend(
264
+ f"КолонкиJSONL.Добавить({bsl_string_literal(name)});" for name in names
265
+ )
266
+ lines.append("ВидыJSONL = Новый Массив;")
267
+ lines.extend(
268
+ f"ВидыJSONL.Добавить({bsl_string_literal(kind)});"
269
+ for kind in effective_kinds
270
+ )
271
+ lines.append("РежимыСсылокJSONL = Новый Структура;")
272
+ lines.extend(
273
+ "РежимыСсылокJSONL.Вставить("
274
+ f"{bsl_string_literal(name)}, {bsl_string_literal(mode.value)});"
275
+ for name, mode in reference_modes.items()
276
+ )
277
+ lines.extend(
278
+ [
279
+ "СхемаJSONL = Новый Структура;",
280
+ 'СхемаJSONL.Вставить("version", 1);',
281
+ 'СхемаJSONL.Вставить("columns", КолонкиJSONL);',
282
+ 'СхемаJSONL.Вставить("kinds", ВидыJSONL);',
283
+ 'СхемаJSONL.Вставить("reference_modes", РежимыСсылокJSONL);',
284
+ "ЗаписьСхемыJSONL = Новый ЗаписьJSON;",
285
+ "ЗаписьСхемыJSONL.УстановитьСтроку("
286
+ "Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));",
287
+ "ЗаписатьJSON(ЗаписьСхемыJSONL, СхемаJSONL);",
288
+ "СтрокиJSONL.Добавить(ЗаписьСхемыJSONL.Закрыть());",
289
+ "РазмерJSONL = ПолучитьДвоичныеДанныеИзСтроки(СтрокиJSONL[0] + "
290
+ "Символы.ПС, КодировкаТекста.UTF8, Ложь).Размер();",
291
+ "КоличествоСтрокJSONL = 0;",
292
+ "Если МаксимумБайтМатериализации > 0 И "
293
+ "РазмерJSONL > МаксимумБайтМатериализации Тогда",
294
+ ' ВызватьИсключение "Превышен лимит байтов компактной таблицы";',
295
+ "КонецЕсли;",
296
+ "ТаблицаМатериализации = RuntimeTableTransferServer."
297
+ f"ПодготовитьТабличноеЗначение({handle}, "
298
+ "МаксимумСтрокМатериализации);",
299
+ "Для Каждого СтрокаМатериализации Из ТаблицаМатериализации Цикл",
300
+ " Если МаксимумСтрокМатериализации > 0 И "
301
+ "КоличествоСтрокJSONL >= МаксимумСтрокМатериализации Тогда",
302
+ ' ВызватьИсключение "Превышен лимит строк компактной таблицы";',
303
+ " КонецЕсли;",
304
+ " ЗначенияСтроки = Новый Массив;",
305
+ ]
306
+ )
307
+ for ordinal, column in enumerate(columns):
308
+ value = _table_field("СтрокаМатериализации", column.name)
309
+ if not column.is_reference:
310
+ encoded = (
311
+ f"XMLСтрока({value})"
312
+ if column.kind == "datetime"
313
+ else (
314
+ f"?({value} = Неопределено Или {value} = NULL, "
315
+ f"Неопределено, Строка({value}))"
316
+ )
317
+ if column.kind == "nullable_string"
318
+ else f"Строка({value})"
319
+ if column.kind == "uuid"
320
+ else value
321
+ )
322
+ lines.append(f" ЗначенияСтроки.Добавить({encoded});")
323
+ continue
324
+ mode = reference_modes[column.name]
325
+ if mode is ReferenceMode.PRESENTATION:
326
+ encoded = f"?(ЗначениеЗаполнено({value}), Строка({value}), Неопределено)"
327
+ lines.append(f" ЗначенияСтроки.Добавить({encoded});")
328
+ elif mode is ReferenceMode.UUID:
329
+ encoded = (
330
+ f"?(ЗначениеЗаполнено({value}), "
331
+ f"Строка({value}.УникальныйИдентификатор()), Неопределено)"
332
+ )
333
+ lines.append(f" ЗначенияСтроки.Добавить({encoded});")
334
+ else:
335
+ temporary = f"ОбеФормыСсылки{ordinal}"
336
+ lines.extend(
337
+ [
338
+ f" Если ЗначениеЗаполнено({value}) Тогда",
339
+ f" {temporary} = Новый Массив;",
340
+ f" {temporary}.Добавить(Строка({value}));",
341
+ f" {temporary}.Добавить(Строка("
342
+ f"{value}.УникальныйИдентификатор()));",
343
+ " Иначе",
344
+ f" {temporary} = Неопределено;",
345
+ " КонецЕсли;",
346
+ f" ЗначенияСтроки.Добавить({temporary});",
347
+ ]
348
+ )
349
+ lines.extend(
350
+ [
351
+ " ЗаписьСтрокиJSONL = Новый ЗаписьJSON;",
352
+ " ЗаписьСтрокиJSONL.УстановитьСтроку("
353
+ "Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));",
354
+ " ЗаписатьJSON(ЗаписьСтрокиJSONL, ЗначенияСтроки);",
355
+ " СтрокаJSONL = ЗаписьСтрокиJSONL.Закрыть();",
356
+ " РазмерСтрокиJSONL = ПолучитьДвоичныеДанныеИзСтроки("
357
+ "СтрокаJSONL + Символы.ПС, КодировкаТекста.UTF8, Ложь).Размер();",
358
+ " Если МаксимумБайтМатериализации > 0 И "
359
+ "РазмерJSONL + РазмерСтрокиJSONL > МаксимумБайтМатериализации Тогда",
360
+ ' ВызватьИсключение "Превышен лимит байтов компактной таблицы";',
361
+ " КонецЕсли;",
362
+ " СтрокиJSONL.Добавить(СтрокаJSONL);",
363
+ " РазмерJSONL = РазмерJSONL + РазмерСтрокиJSONL;",
364
+ " КоличествоСтрокJSONL = КоличествоСтрокJSONL + 1;",
365
+ "КонецЦикла;",
366
+ "КомпактнаяМатериализация = RuntimeTableTransferServer."
367
+ "ЗавершитьКомпактнуюМатериализацию(СтрокиJSONL);",
368
+ ]
369
+ )
370
+ lines.extend(
371
+ _metadata_lines(
372
+ context_key,
373
+ runtime_generation=runtime_generation,
374
+ context_generation=context_generation,
375
+ )
376
+ )
377
+ return "\n".join(lines)
378
+
379
+
380
+ class CompactRuntimeTableTransfer:
381
+ def __init__(
382
+ self,
383
+ instruction_executor: Callable[[str], object],
384
+ context_reader: Callable[[str, int], str],
385
+ *,
386
+ runtime_generation: Callable[[], int],
387
+ context_generation: int,
388
+ context_cleaner: Callable[[str], None] | None = None,
389
+ schema_reader: Callable[[str], tuple[CompactColumn, ...] | None] | None = None,
390
+ max_text_size: int = 100_000_000,
391
+ max_payload_bytes: int = 75_000_000,
392
+ max_rows: int | None = None,
393
+ key_factory: Callable[[], str] | None = None,
394
+ profiler: PhaseRecorder | None = None,
395
+ ) -> None:
396
+ self._execute = instruction_executor
397
+ self._read = context_reader
398
+ self._runtime_generation = runtime_generation
399
+ self._context_generation = context_generation
400
+ self._clean = context_cleaner
401
+ self._schema_reader = schema_reader
402
+ self._expected_runtime_generation = runtime_generation()
403
+ self._max_text_size = max_text_size
404
+ if max_payload_bytes <= 0:
405
+ raise ValueError("max payload bytes must be positive")
406
+ self._max_payload_bytes = max_payload_bytes
407
+ self._max_rows = _optional_budget(max_rows, "table row budget")
408
+ self._key_factory = key_factory or (
409
+ lambda: "__onec_compact_table_" + uuid4().hex
410
+ )
411
+ self._profiler = profiler
412
+
413
+ def _profile(self, phase: str, operation, **metadata): # type: ignore[no-untyped-def]
414
+ if self._profiler is None:
415
+ return operation()
416
+ return self._profiler.measure(phase, operation, **metadata)
417
+
418
+ def to_df(self, handle: str, policy: ReferencePolicy) -> pd.DataFrame:
419
+ payload = self.payload(handle, policy)
420
+ return self._profile(
421
+ "table.build_dataframe",
422
+ lambda: decode_compact_table_payload(payload, policy),
423
+ input_bytes=len(payload),
424
+ item_count=lambda frame: len(frame.index),
425
+ )
426
+
427
+ def payload(self, handle: str, policy: ReferencePolicy) -> bytes:
428
+ generation = self._runtime_generation()
429
+ if generation != self._expected_runtime_generation:
430
+ raise ProtocolError("table materializer runtime generation is stale")
431
+ key = self._key_factory()
432
+ columns = self._profile(
433
+ "table.schema_read",
434
+ lambda: self._schema_reader(handle) if self._schema_reader else None,
435
+ )
436
+ source = build_compact_transfer_instruction(
437
+ handle,
438
+ policy,
439
+ key,
440
+ runtime_generation=generation,
441
+ context_generation=self._context_generation,
442
+ columns=columns,
443
+ max_rows=self._max_rows or None,
444
+ max_payload_bytes=self._max_payload_bytes,
445
+ )
446
+ metadata = self._profile(
447
+ "table.prepare_jsonl",
448
+ lambda: self._execute(source),
449
+ input_bytes=len(source.encode("utf-8")),
450
+ )
451
+ if not isinstance(metadata, str):
452
+ if self._clean is not None:
453
+ self._clean(key)
454
+ raise ProtocolError("compact table metadata is not a string")
455
+ fields = metadata.split("|")
456
+ if len(fields) != 5:
457
+ if self._clean is not None:
458
+ self._clean(key)
459
+ raise ProtocolError("compact table metadata field count is invalid")
460
+ try:
461
+ observed_runtime = int(fields[0])
462
+ observed_context = int(fields[1])
463
+ byte_count = int(fields[2])
464
+ base64_count = int(fields[4])
465
+ except ValueError as error:
466
+ if self._clean is not None:
467
+ self._clean(key)
468
+ raise ProtocolError("compact table metadata number is invalid") from error
469
+ payload_hash = fields[3]
470
+ if (
471
+ observed_runtime != generation
472
+ or observed_context != self._context_generation
473
+ or byte_count <= 0
474
+ or byte_count > self._max_payload_bytes
475
+ or base64_count <= 0
476
+ or base64_count > self._max_text_size
477
+ or not _HASH.fullmatch(payload_hash)
478
+ ):
479
+ if self._clean is not None:
480
+ self._clean(key)
481
+ raise ProtocolError("compact table metadata is invalid")
482
+ content = self._profile(
483
+ "table.transfer_base64",
484
+ lambda: self._read(key, self._max_text_size),
485
+ output_bytes=lambda value: len(value.encode("ascii")),
486
+ )
487
+ if len(content) != base64_count:
488
+ raise ProtocolError("compact table metadata is invalid")
489
+ try:
490
+ payload = self._profile(
491
+ "table.decode_base64",
492
+ lambda: b64decode("".join(content.split()), validate=True),
493
+ input_bytes=len(content.encode("ascii")),
494
+ output_bytes=len,
495
+ )
496
+ except (ValueError, binascii.Error) as error:
497
+ raise ProtocolError("compact table Base64 payload is invalid") from error
498
+ if len(payload) != byte_count or sha256(payload).hexdigest() != payload_hash:
499
+ raise ProtocolError("compact table payload integrity check failed")
500
+ return payload