mostlyright-data 0.9.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.
Files changed (314) hide show
  1. mostlyright/data_harness/__init__.py +158 -0
  2. mostlyright/data_harness/acquisition/__init__.py +55 -0
  3. mostlyright/data_harness/acquisition/http.py +2773 -0
  4. mostlyright/data_harness/acquisition/parsing.py +809 -0
  5. mostlyright/data_harness/acquisition/ranges.py +495 -0
  6. mostlyright/data_harness/acquisition/result_download.py +360 -0
  7. mostlyright/data_harness/acquisition/retention_admission.py +248 -0
  8. mostlyright/data_harness/acquisition/sandbox.py +4888 -0
  9. mostlyright/data_harness/acquisition/url_policy.py +530 -0
  10. mostlyright/data_harness/agent_runtime.py +2743 -0
  11. mostlyright/data_harness/assets/logo-ink.svg +31 -0
  12. mostlyright/data_harness/backends/__init__.py +28 -0
  13. mostlyright/data_harness/backends/pandas_backend.py +350 -0
  14. mostlyright/data_harness/backends/polars_backend.py +366 -0
  15. mostlyright/data_harness/backends/protocol.py +124 -0
  16. mostlyright/data_harness/backends/reference.py +83 -0
  17. mostlyright/data_harness/backends/registry.py +55 -0
  18. mostlyright/data_harness/backends/restrictions.py +126 -0
  19. mostlyright/data_harness/canonical.py +333 -0
  20. mostlyright/data_harness/catalog_job.py +625 -0
  21. mostlyright/data_harness/cli.py +5398 -0
  22. mostlyright/data_harness/contracts.py +53 -0
  23. mostlyright/data_harness/coordinator.py +1307 -0
  24. mostlyright/data_harness/deploy.py +924 -0
  25. mostlyright/data_harness/deploy_target.py +312 -0
  26. mostlyright/data_harness/deployment_evidence.py +1067 -0
  27. mostlyright/data_harness/event_presentation.py +576 -0
  28. mostlyright/data_harness/events.py +2152 -0
  29. mostlyright/data_harness/fast_delimited.py +239 -0
  30. mostlyright/data_harness/fleet.py +237 -0
  31. mostlyright/data_harness/formats.py +236 -0
  32. mostlyright/data_harness/governors.py +1163 -0
  33. mostlyright/data_harness/hosted_bootstrap.py +972 -0
  34. mostlyright/data_harness/hosted_crawler.py +1115 -0
  35. mostlyright/data_harness/hosted_crawler_container_smoke.py +351 -0
  36. mostlyright/data_harness/hosted_crawler_fetch.py +423 -0
  37. mostlyright/data_harness/hosted_crawler_job.py +1277 -0
  38. mostlyright/data_harness/hosted_crawler_protocol.py +676 -0
  39. mostlyright/data_harness/hosted_dataset.py +1500 -0
  40. mostlyright/data_harness/hosted_deploy.py +3037 -0
  41. mostlyright/data_harness/hosted_handoff.py +62 -0
  42. mostlyright/data_harness/hosted_ingestion_contract.py +504 -0
  43. mostlyright/data_harness/hosted_ingestion_job.py +356 -0
  44. mostlyright/data_harness/hosted_ingestion_job_smoke.py +40 -0
  45. mostlyright/data_harness/hosted_session_container_smoke.py +194 -0
  46. mostlyright/data_harness/hosted_session_worker.py +3554 -0
  47. mostlyright/data_harness/hosted_session_worker_job_smoke.py +46 -0
  48. mostlyright/data_harness/hosted_worker.py +6784 -0
  49. mostlyright/data_harness/ingestion/__init__.py +56 -0
  50. mostlyright/data_harness/ingestion/contracts.py +461 -0
  51. mostlyright/data_harness/ingestion/faults.py +42 -0
  52. mostlyright/data_harness/ingestion/gcs_store.py +1162 -0
  53. mostlyright/data_harness/ingestion/spool.py +130 -0
  54. mostlyright/data_harness/ingestion/store.py +885 -0
  55. mostlyright/data_harness/key_seam.py +434 -0
  56. mostlyright/data_harness/linux_process_boundary.py +262 -0
  57. mostlyright/data_harness/local_contracts.py +2880 -0
  58. mostlyright/data_harness/local_search/__init__.py +5 -0
  59. mostlyright/data_harness/local_search/build_index.py +1087 -0
  60. mostlyright/data_harness/local_search/contracts.py +920 -0
  61. mostlyright/data_harness/local_search/query_trace.py +266 -0
  62. mostlyright/data_harness/local_search/retrieval.py +700 -0
  63. mostlyright/data_harness/local_search/sealed.py +474 -0
  64. mostlyright/data_harness/local_search/service.py +784 -0
  65. mostlyright/data_harness/nbrender/CONTRACT.md +212 -0
  66. mostlyright/data_harness/nbrender/__init__.py +12 -0
  67. mostlyright/data_harness/nbrender/chrome.py +359 -0
  68. mostlyright/data_harness/nbrender/code_body.py +266 -0
  69. mostlyright/data_harness/nbrender/document.py +407 -0
  70. mostlyright/data_harness/nbrender/frame.py +275 -0
  71. mostlyright/data_harness/nbrender/interactive.py +337 -0
  72. mostlyright/data_harness/nbrender/markdown_body.py +477 -0
  73. mostlyright/data_harness/nbrender/mr_components.py +134 -0
  74. mostlyright/data_harness/nbrender/outputs_data.py +595 -0
  75. mostlyright/data_harness/nbrender/outputs_rich.py +906 -0
  76. mostlyright/data_harness/nbrender/outputs_source.py +260 -0
  77. mostlyright/data_harness/nbrender/outputs_stage.py +176 -0
  78. mostlyright/data_harness/nbrender/outputs_text.py +400 -0
  79. mostlyright/data_harness/nbrender/parse.py +394 -0
  80. mostlyright/data_harness/nbrender/status.py +40 -0
  81. mostlyright/data_harness/nbrender/tokens.py +1295 -0
  82. mostlyright/data_harness/notebook.py +1710 -0
  83. mostlyright/data_harness/offline.py +2049 -0
  84. mostlyright/data_harness/operation_registry.py +1007 -0
  85. mostlyright/data_harness/operator_setup.py +239 -0
  86. mostlyright/data_harness/pipeline.py +6428 -0
  87. mostlyright/data_harness/plan_graph.py +2026 -0
  88. mostlyright/data_harness/preparation/__init__.py +104 -0
  89. mostlyright/data_harness/preparation/contracts.py +1017 -0
  90. mostlyright/data_harness/preparation/engine.py +221 -0
  91. mostlyright/data_harness/preparation/errors.py +14 -0
  92. mostlyright/data_harness/preparation/gates.py +751 -0
  93. mostlyright/data_harness/preparation/joins.py +574 -0
  94. mostlyright/data_harness/preparation/profile.py +384 -0
  95. mostlyright/data_harness/preparation/table.py +217 -0
  96. mostlyright/data_harness/preparation/transforms.py +568 -0
  97. mostlyright/data_harness/progress_events.py +534 -0
  98. mostlyright/data_harness/readers/__init__.py +46 -0
  99. mostlyright/data_harness/readers/containers.py +963 -0
  100. mostlyright/data_harness/readers/contracts.py +542 -0
  101. mostlyright/data_harness/readers/delimited.py +257 -0
  102. mostlyright/data_harness/readers/grib2/__init__.py +33 -0
  103. mostlyright/data_harness/readers/grib2/admission.py +722 -0
  104. mostlyright/data_harness/readers/grib2/decode.py +1009 -0
  105. mostlyright/data_harness/readers/grib2/geometry.py +1133 -0
  106. mostlyright/data_harness/readers/grib2/portable_math.py +501 -0
  107. mostlyright/data_harness/readers/json_tabular.py +485 -0
  108. mostlyright/data_harness/readers/registry.py +514 -0
  109. mostlyright/data_harness/readers/samples/README.md +110 -0
  110. mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/cities.csv.gz +0 -0
  111. mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/expected.json +24 -0
  112. mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/cities.csv.gz +0 -0
  113. mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/expected.json +24 -0
  114. mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/cities.tar +0 -0
  115. mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/expected.json +24 -0
  116. mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/cities.tar +0 -0
  117. mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/expected.json +24 -0
  118. mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/cities.zip +0 -0
  119. mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/expected.json +25 -0
  120. mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
  121. mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/expected.json +25 -0
  122. mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
  123. mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/expected.json +25 -0
  124. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/cities.csv +3 -0
  125. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/expected.json +23 -0
  126. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/cities.tsv +5 -0
  127. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/expected.json +25 -0
  128. mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/expected.json +30 -0
  129. mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/observations.csv +5 -0
  130. mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/expected.json +44 -0
  131. mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/stations.json +1 -0
  132. mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/expected.json +48 -0
  133. mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/observations.ndjson +4 -0
  134. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/cities.xlsx +0 -0
  135. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/expected.json +24 -0
  136. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/expected.json +27 -0
  137. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
  138. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/expected.json +27 -0
  139. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
  140. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/README.md +20 -0
  141. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/expected.json +55 -0
  142. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/gfs-2m-temperature.grib2 +0 -0
  143. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/expected.json +54 -0
  144. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
  145. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/expected.json +54 -0
  146. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/hrrr-categorical-rain.grib2 +0 -0
  147. mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/expected.json +54 -0
  148. mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
  149. mostlyright/data_harness/readers/samples.py +582 -0
  150. mostlyright/data_harness/readers/spreadsheet.py +803 -0
  151. mostlyright/data_harness/readers/tabular.py +510 -0
  152. mostlyright/data_harness/recipe.py +5321 -0
  153. mostlyright/data_harness/repair/__init__.py +78 -0
  154. mostlyright/data_harness/repair/adapters.py +274 -0
  155. mostlyright/data_harness/repair/contracts.py +872 -0
  156. mostlyright/data_harness/repair/coordinator.py +1099 -0
  157. mostlyright/data_harness/repair/errors.py +16 -0
  158. mostlyright/data_harness/review.py +2533 -0
  159. mostlyright/data_harness/rowset.py +283 -0
  160. mostlyright/data_harness/serving.py +1975 -0
  161. mostlyright/data_harness/serving_edge.py +590 -0
  162. mostlyright/data_harness/serving_http.py +1031 -0
  163. mostlyright/data_harness/session_probes.py +759 -0
  164. mostlyright/data_harness/signing.py +101 -0
  165. mostlyright/data_harness/source_discovery.py +898 -0
  166. mostlyright/data_harness/sources/__init__.py +209 -0
  167. mostlyright/data_harness/sources/_adapter_steps.py +213 -0
  168. mostlyright/data_harness/sources/adapters.py +1214 -0
  169. mostlyright/data_harness/sources/cadence.py +1428 -0
  170. mostlyright/data_harness/sources/cadence_emission.py +453 -0
  171. mostlyright/data_harness/sources/cadence_history.py +546 -0
  172. mostlyright/data_harness/sources/catalog/__init__.py +17 -0
  173. mostlyright/data_harness/sources/catalog/admission.py +477 -0
  174. mostlyright/data_harness/sources/catalog/authoring.py +1701 -0
  175. mostlyright/data_harness/sources/catalog/authoring_policy.py +701 -0
  176. mostlyright/data_harness/sources/catalog/authoring_shards.py +1217 -0
  177. mostlyright/data_harness/sources/catalog/bounded_io.py +231 -0
  178. mostlyright/data_harness/sources/catalog/channel.py +523 -0
  179. mostlyright/data_harness/sources/catalog/channel_client.py +296 -0
  180. mostlyright/data_harness/sources/catalog/contracts.py +825 -0
  181. mostlyright/data_harness/sources/catalog/coverage.py +137 -0
  182. mostlyright/data_harness/sources/catalog/delta.py +1340 -0
  183. mostlyright/data_harness/sources/catalog/embedding.py +532 -0
  184. mostlyright/data_harness/sources/catalog/entry_v2.py +1182 -0
  185. mostlyright/data_harness/sources/catalog/fill.py +3889 -0
  186. mostlyright/data_harness/sources/catalog/fill_partitions.py +459 -0
  187. mostlyright/data_harness/sources/catalog/fill_staging.py +1105 -0
  188. mostlyright/data_harness/sources/catalog/gating.py +374 -0
  189. mostlyright/data_harness/sources/catalog/generation_receipt.py +1607 -0
  190. mostlyright/data_harness/sources/catalog/harvest/__init__.py +7 -0
  191. mostlyright/data_harness/sources/catalog/harvest/ckan.py +384 -0
  192. mostlyright/data_harness/sources/catalog/harvest/datagov_v4.py +798 -0
  193. mostlyright/data_harness/sources/catalog/harvest/protocol.py +964 -0
  194. mostlyright/data_harness/sources/catalog/harvest/sdmx.py +445 -0
  195. mostlyright/data_harness/sources/catalog/harvest/stac.py +384 -0
  196. mostlyright/data_harness/sources/catalog/health.py +447 -0
  197. mostlyright/data_harness/sources/catalog/hosted_catalog.py +105 -0
  198. mostlyright/data_harness/sources/catalog/identity_history.py +1549 -0
  199. mostlyright/data_harness/sources/catalog/neural.py +1618 -0
  200. mostlyright/data_harness/sources/catalog/packed_catalog.py +2345 -0
  201. mostlyright/data_harness/sources/catalog/packed_retrieval.py +1517 -0
  202. mostlyright/data_harness/sources/catalog/packed_writer.py +2802 -0
  203. mostlyright/data_harness/sources/catalog/query_trace.py +1037 -0
  204. mostlyright/data_harness/sources/catalog/recommend.py +171 -0
  205. mostlyright/data_harness/sources/catalog/retrieval.py +230 -0
  206. mostlyright/data_harness/sources/catalog/retrieval_manifest.py +995 -0
  207. mostlyright/data_harness/sources/catalog/rights_decisions.py +254 -0
  208. mostlyright/data_harness/sources/catalog/sealed.py +560 -0
  209. mostlyright/data_harness/sources/catalog/search.py +230 -0
  210. mostlyright/data_harness/sources/catalog/streaming_delta.py +1097 -0
  211. mostlyright/data_harness/sources/catalog/update.py +891 -0
  212. mostlyright/data_harness/sources/collections.py +815 -0
  213. mostlyright/data_harness/sources/contracts.py +2223 -0
  214. mostlyright/data_harness/sources/deletion.py +761 -0
  215. mostlyright/data_harness/sources/fitness.py +162 -0
  216. mostlyright/data_harness/sources/governance.py +163 -0
  217. mostlyright/data_harness/sources/hosted.py +173 -0
  218. mostlyright/data_harness/sources/integration.py +218 -0
  219. mostlyright/data_harness/sources/range_reader.py +418 -0
  220. mostlyright/data_harness/sources/registry.py +514 -0
  221. mostlyright/data_harness/sources/rights_rule.py +59 -0
  222. mostlyright/data_harness/sources/source_cadence_vectors.v1.json +1 -0
  223. mostlyright/data_harness/sources/sports.py +521 -0
  224. mostlyright/data_harness/sources/stream.py +524 -0
  225. mostlyright/data_harness/sources/stream_connector.py +418 -0
  226. mostlyright/data_harness/sources/stream_recorder.py +1404 -0
  227. mostlyright/data_harness/studio_boundary.py +2019 -0
  228. mostlyright/data_harness/thin/__init__.py +37 -0
  229. mostlyright/data_harness/thin/acquire.py +1137 -0
  230. mostlyright/data_harness/thin/acquire_cancel.py +579 -0
  231. mostlyright/data_harness/thin/approvals.py +617 -0
  232. mostlyright/data_harness/thin/commands.py +406 -0
  233. mostlyright/data_harness/thin/download.py +194 -0
  234. mostlyright/data_harness/thin/narrative.py +589 -0
  235. mostlyright/data_harness/thin/parity.py +1070 -0
  236. mostlyright/data_harness/thin/propose.py +2759 -0
  237. mostlyright/data_harness/thin/research.py +1663 -0
  238. mostlyright/data_harness/thin/router.py +924 -0
  239. mostlyright/data_harness/thin/runs.py +519 -0
  240. mostlyright/data_harness/thin/session.py +281 -0
  241. mostlyright/data_harness/thin/stream.py +501 -0
  242. mostlyright/data_harness/thin/transport.py +187 -0
  243. mostlyright/data_harness/thin/vocabulary.py +368 -0
  244. mostlyright/data_harness/thin/workers.py +164 -0
  245. mostlyright/data_harness/ucum/TABLE-PIN.json +40 -0
  246. mostlyright/data_harness/ucum/ucum-subset.v1.json +632 -0
  247. mostlyright/data_harness/unit_flow.py +927 -0
  248. mostlyright/data_harness/units.py +572 -0
  249. mostlyright/data_harness/ux/__init__.py +9 -0
  250. mostlyright/data_harness/ux/approve.py +485 -0
  251. mostlyright/data_harness/ux/author_yaml.py +597 -0
  252. mostlyright/data_harness/ux/cloud_auth.py +447 -0
  253. mostlyright/data_harness/ux/commands/__init__.py +260 -0
  254. mostlyright/data_harness/ux/commands/approve.py +136 -0
  255. mostlyright/data_harness/ux/commands/auth.py +744 -0
  256. mostlyright/data_harness/ux/commands/author.py +79 -0
  257. mostlyright/data_harness/ux/commands/catalog_author.py +403 -0
  258. mostlyright/data_harness/ux/commands/catalog_fill.py +523 -0
  259. mostlyright/data_harness/ux/commands/catalog_harvest.py +545 -0
  260. mostlyright/data_harness/ux/commands/catalog_publish.py +1838 -0
  261. mostlyright/data_harness/ux/commands/catalog_search.py +71 -0
  262. mostlyright/data_harness/ux/commands/catalog_update.py +437 -0
  263. mostlyright/data_harness/ux/commands/deploy.py +134 -0
  264. mostlyright/data_harness/ux/commands/deploy_dataset.py +98 -0
  265. mostlyright/data_harness/ux/commands/deploy_plan.py +105 -0
  266. mostlyright/data_harness/ux/commands/deploy_status.py +104 -0
  267. mostlyright/data_harness/ux/commands/diff.py +74 -0
  268. mostlyright/data_harness/ux/commands/index.py +84 -0
  269. mostlyright/data_harness/ux/commands/inventory.py +47 -0
  270. mostlyright/data_harness/ux/commands/list_builds.py +143 -0
  271. mostlyright/data_harness/ux/commands/login.py +63 -0
  272. mostlyright/data_harness/ux/commands/peek.py +236 -0
  273. mostlyright/data_harness/ux/commands/plan_check.py +90 -0
  274. mostlyright/data_harness/ux/commands/preflight.py +97 -0
  275. mostlyright/data_harness/ux/commands/record.py +107 -0
  276. mostlyright/data_harness/ux/commands/review_setup.py +47 -0
  277. mostlyright/data_harness/ux/commands/search.py +440 -0
  278. mostlyright/data_harness/ux/commands/show.py +61 -0
  279. mostlyright/data_harness/ux/commands/whoami.py +37 -0
  280. mostlyright/data_harness/ux/credential_native.py +551 -0
  281. mostlyright/data_harness/ux/credential_store.py +1055 -0
  282. mostlyright/data_harness/ux/credentials.py +631 -0
  283. mostlyright/data_harness/ux/diffing.py +444 -0
  284. mostlyright/data_harness/ux/headline.py +671 -0
  285. mostlyright/data_harness/ux/hosted_acquisition.py +974 -0
  286. mostlyright/data_harness/ux/hosted_run_status.py +619 -0
  287. mostlyright/data_harness/ux/inventory.py +427 -0
  288. mostlyright/data_harness/ux/local_review.py +375 -0
  289. mostlyright/data_harness/ux/login.py +691 -0
  290. mostlyright/data_harness/ux/path_kind.py +147 -0
  291. mostlyright/data_harness/ux/peek.py +1000 -0
  292. mostlyright/data_harness/ux/plain_file.py +178 -0
  293. mostlyright/data_harness/ux/plan_check.py +311 -0
  294. mostlyright/data_harness/ux/preflight.py +918 -0
  295. mostlyright/data_harness/ux/readers.py +1124 -0
  296. mostlyright/data_harness/ux/remediation.py +2195 -0
  297. mostlyright/data_harness/ux/render.py +657 -0
  298. mostlyright/data_harness/ux/workload.py +1077 -0
  299. mostlyright/data_harness/viewer.py +3713 -0
  300. mostlyright/data_harness/visual_run/__init__.py +83 -0
  301. mostlyright/data_harness/visual_run/authoring.py +235 -0
  302. mostlyright/data_harness/visual_run/contracts.py +673 -0
  303. mostlyright/data_harness/visual_run/materialize.py +486 -0
  304. mostlyright/data_harness/visual_run/observations.py +874 -0
  305. mostlyright/data_harness/visual_run/query.py +259 -0
  306. mostlyright/data_harness/visual_run/reducer.py +280 -0
  307. mostlyright/data_harness/visual_run/sdk.py +892 -0
  308. mostlyright/data_harness/visual_run/store.py +584 -0
  309. mostlyright/data_harness/visual_run/transport.py +239 -0
  310. mostlyright/data_harness/watch.py +2999 -0
  311. mostlyright_data-0.9.0.dist-info/METADATA +607 -0
  312. mostlyright_data-0.9.0.dist-info/RECORD +314 -0
  313. mostlyright_data-0.9.0.dist-info/WHEEL +4 -0
  314. mostlyright_data-0.9.0.dist-info/entry_points.txt +12 -0
@@ -0,0 +1,62 @@
1
+ """The record one hosted Table handoff leaves behind, and the one way to read it.
2
+
3
+ The record answers a question a folder full of verified bytes cannot: which released version, which
4
+ hosted run, and which fingerprints these local bytes came from. Nothing is admitted anywhere on the
5
+ strength of it -- the viewer verifies the Build and re-derives the notebook for itself, exactly as
6
+ it does for a Build made here -- so this carries no authority and is never consulted by a decision.
7
+ It exists so that what is already admitted can be attributed.
8
+
9
+ It lives in its own module because two very different callers need it: the lane that writes it,
10
+ which reaches Studio, and the viewer, which reaches nothing. A viewer that had to import the
11
+ downloading lane to read one small local file would be importing a Studio client to render a
12
+ notebook.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import stat
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from mostlyright.data_harness import canonical
23
+
24
+ HANDOFF_RECEIPT_SCHEMA = "mostlyright-hosted-table-handoff-receipt.v2"
25
+ HANDOFF_RELATIVE_PATH = Path("evidence") / "hosted-table" / "handoff.json"
26
+ MAX_HANDOFF_RECEIPT_BYTES = 64 * 1024
27
+
28
+
29
+ def read_handoff_receipt(
30
+ run_dir: Path, *, max_bytes: int = MAX_HANDOFF_RECEIPT_BYTES
31
+ ) -> dict[str, Any] | None:
32
+ """Return the handoff record under ``run_dir``, or nothing, without following a link."""
33
+
34
+ path = Path(run_dir) / HANDOFF_RELATIVE_PATH
35
+ try:
36
+ descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
37
+ except OSError:
38
+ return None
39
+ try:
40
+ info = os.fstat(descriptor)
41
+ if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size > max_bytes:
42
+ return None
43
+ raw = os.read(descriptor, max_bytes)
44
+ except OSError:
45
+ return None
46
+ finally:
47
+ os.close(descriptor)
48
+ try:
49
+ value = canonical.parse_canonical_json(raw)
50
+ except canonical.CanonicalJSONError:
51
+ return None
52
+ if not isinstance(value, dict) or value.get("schema_version") != HANDOFF_RECEIPT_SCHEMA:
53
+ return None
54
+ return value
55
+
56
+
57
+ __all__ = [
58
+ "HANDOFF_RECEIPT_SCHEMA",
59
+ "HANDOFF_RELATIVE_PATH",
60
+ "MAX_HANDOFF_RECEIPT_BYTES",
61
+ "read_handoff_receipt",
62
+ ]
@@ -0,0 +1,504 @@
1
+ """Frozen, capability-free wire contracts for one hosted ingestion delivery.
2
+
3
+ This module is intentionally independent from the generated Studio client. It is the narrow
4
+ contract a Cloud Run Job receives *after* redeeming its opaque dispatch id. The document carries
5
+ only identities, immutable source pins, GCS object coordinates and a deadline; it never carries a
6
+ Studio bearer credential, signed URL, bucket authority, or source credential. Studio owns the
7
+ matching schema and translates its typed ``sha256:...`` values at its one adapter boundary; the
8
+ Harness contracts deliberately use the existing bare 64-hex ingestion digests.
9
+
10
+ The persisted delivery envelope binds a mutable, delivery-scoped manifest to the exact execution
11
+ and attempt which may advance it. Chunks deliberately remain content-addressed under a shared,
12
+ reviewed prefix, but two deliveries can never CAS the same manifest object.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from dataclasses import dataclass
19
+ from datetime import UTC, datetime
20
+ from typing import Any, Self
21
+ from uuid import UUID
22
+
23
+ from mostlyright.data_harness.canonical import canonical_sha256
24
+ from mostlyright.data_harness.ingestion.contracts import (
25
+ IngestionCorrupt,
26
+ IngestionError,
27
+ IngestionManifestV1,
28
+ SourceVersionPinV1,
29
+ )
30
+
31
+ INGESTION_MANIFEST_REF_SCHEMA = "mostlyright-ingestion-manifest-ref.v1"
32
+ INGESTION_DELIVERY_BINDING_SCHEMA = "mostlyright-ingestion-delivery-binding.v1"
33
+ INGESTION_DELIVERY_MANIFEST_SCHEMA = "mostlyright-ingestion-delivery-manifest.v1"
34
+ HOSTED_INGESTION_DISPATCH_SCHEMA = "mostlyright-ingestion-dispatch.v1"
35
+ PUBLIC_OFFSET_CONTINUATION_SCHEMA = "mostlyright-ingestion-public-offset.v1"
36
+
37
+ MAX_MANIFEST_REF_BYTES = 16 * 1024 * 1024
38
+ MAX_PREFIX_LENGTH = 512
39
+ MAX_OBJECT_NAME_LENGTH = 1024
40
+ MAX_SEGMENT_ID_LENGTH = 128
41
+ MAX_DELIVERY_DEADLINE_SECONDS = 15 * 60
42
+
43
+ _DIGEST = re.compile(r"[0-9a-f]{64}\Z")
44
+ _GENERATION = re.compile(r"[1-9][0-9]*\Z")
45
+ _PREFIX_SEGMENT = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
46
+ _SEGMENT_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
47
+ _RFC3339 = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,6})?Z\Z")
48
+
49
+
50
+ def _error(code: str, path: str, detail: str) -> IngestionError:
51
+ return IngestionError(code, path, detail)
52
+
53
+
54
+ def _require_exact(value: object, keys: frozenset[str], path: str) -> dict[str, Any]:
55
+ if not isinstance(value, dict) or set(value) != keys:
56
+ raise _error("INGESTION_DELIVERY_DOCUMENT", path, "has unsupported or missing members")
57
+ return value
58
+
59
+
60
+ def _require_digest(value: object, path: str) -> str:
61
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
62
+ raise _error("INGESTION_DIGEST", path, "must be one lowercase SHA-256 digest")
63
+ return value
64
+
65
+
66
+ def _require_uuid4(value: object, path: str) -> str:
67
+ if not isinstance(value, str):
68
+ raise _error("INGESTION_DELIVERY_ID", path, "must be one canonical UUID4")
69
+ try:
70
+ parsed = UUID(value)
71
+ except ValueError as error:
72
+ raise _error("INGESTION_DELIVERY_ID", path, "must be one canonical UUID4") from error
73
+ if parsed.version != 4 or str(parsed) != value:
74
+ raise _error("INGESTION_DELIVERY_ID", path, "must be one canonical UUID4")
75
+ return value
76
+
77
+
78
+ def _require_positive_int(value: object, path: str, *, maximum: int = (1 << 63) - 1) -> int:
79
+ if isinstance(value, bool) or not isinstance(value, int) or not 0 < value <= maximum:
80
+ raise _error("INGESTION_DELIVERY_NUMBER", path, "must be one bounded positive integer")
81
+ return value
82
+
83
+
84
+ def _require_nonnegative_int(value: object, path: str, *, maximum: int = (1 << 63) - 1) -> int:
85
+ if isinstance(value, bool) or not isinstance(value, int) or not 0 <= value <= maximum:
86
+ raise _error("INGESTION_DELIVERY_NUMBER", path, "must be one bounded non-negative integer")
87
+ return value
88
+
89
+
90
+ def _require_prefix(value: object, path: str) -> str:
91
+ if not isinstance(value, str) or not value or len(value) > MAX_PREFIX_LENGTH:
92
+ raise _error("INGESTION_DELIVERY_PREFIX", path, "must be one bounded object prefix")
93
+ if any(_PREFIX_SEGMENT.fullmatch(part) is None for part in value.split("/")):
94
+ raise _error("INGESTION_DELIVERY_PREFIX", path, "contains an unsafe object-prefix segment")
95
+ return value
96
+
97
+
98
+ def _require_object_name(value: object, path: str) -> str:
99
+ """Validate a GCS object name without imposing the shorter prefix limit on its suffix."""
100
+
101
+ if not isinstance(value, str) or not value or len(value) > MAX_OBJECT_NAME_LENGTH:
102
+ raise _error("INGESTION_DELIVERY_OBJECT", path, "must be one bounded object name")
103
+ if any(_PREFIX_SEGMENT.fullmatch(part) is None for part in value.split("/")):
104
+ raise _error("INGESTION_DELIVERY_OBJECT", path, "contains an unsafe object-name segment")
105
+ return value
106
+
107
+
108
+ def _require_delivery_prefix(value: object, path: str, delivery_id: str) -> str:
109
+ """A mutable manifest prefix must name precisely the delivery that may advance it."""
110
+
111
+ prefix = _require_prefix(value, path)
112
+ if prefix.rsplit("/", 1)[-1] != delivery_id:
113
+ raise _error(
114
+ "INGESTION_DELIVERY_PREFIX",
115
+ path,
116
+ "must end in this delivery's canonical identifier",
117
+ )
118
+ return prefix
119
+
120
+
121
+ def _require_run_id(value: object, path: str) -> str:
122
+ if not isinstance(value, str) or _SEGMENT_ID.fullmatch(value) is None:
123
+ raise _error("INGESTION_RUN_ID", path, "must be one safe bounded run id")
124
+ return value
125
+
126
+
127
+ def _require_segment_id(value: object, path: str) -> str:
128
+ if not isinstance(value, str) or _SEGMENT_ID.fullmatch(value) is None:
129
+ raise _error("INGESTION_SEGMENT_ID", path, "must be one safe bounded segment id")
130
+ return value
131
+
132
+
133
+ def _require_deadline(value: object, path: str) -> str:
134
+ if not isinstance(value, str) or _RFC3339.fullmatch(value) is None:
135
+ raise _error("INGESTION_DELIVERY_DEADLINE", path, "must be one UTC RFC3339 timestamp")
136
+ try:
137
+ datetime.fromisoformat(value.removesuffix("Z") + "+00:00")
138
+ except ValueError as error:
139
+ raise _error("INGESTION_DELIVERY_DEADLINE", path, "is not a real UTC timestamp") from error
140
+ return value
141
+
142
+
143
+ def parse_delivery_deadline(value: str) -> datetime:
144
+ """Return a UTC deadline after the frozen wire syntax has been validated."""
145
+
146
+ _require_deadline(value, "delivery.deadline")
147
+ return datetime.fromisoformat(value.removesuffix("Z") + "+00:00").astimezone(UTC)
148
+
149
+
150
+ @dataclass(frozen=True)
151
+ class IngestionManifestReferenceV1:
152
+ """An exact GCS generation and raw-document digest, never a bucket authority."""
153
+
154
+ object_name: str
155
+ generation: str
156
+ manifest_sha256: str
157
+ byte_length: int
158
+ schema_version: str = INGESTION_MANIFEST_REF_SCHEMA
159
+
160
+ def __post_init__(self) -> None:
161
+ if self.schema_version != INGESTION_MANIFEST_REF_SCHEMA:
162
+ raise _error(
163
+ "INGESTION_MANIFEST_REF_SCHEMA", "manifest_ref.schema_version", "is unsupported"
164
+ )
165
+ _require_object_name(self.object_name, "manifest_ref.object_name")
166
+ if _GENERATION.fullmatch(self.generation) is None:
167
+ raise _error(
168
+ "INGESTION_MANIFEST_REF_GENERATION", "manifest_ref.generation", "must be positive"
169
+ )
170
+ _require_digest(self.manifest_sha256, "manifest_ref.manifest_sha256")
171
+ if (
172
+ isinstance(self.byte_length, bool)
173
+ or not isinstance(self.byte_length, int)
174
+ or not 0 < self.byte_length <= MAX_MANIFEST_REF_BYTES
175
+ ):
176
+ raise _error(
177
+ "INGESTION_MANIFEST_REF_LENGTH",
178
+ "manifest_ref.byte_length",
179
+ "is outside the supported range",
180
+ )
181
+
182
+ def to_dict(self) -> dict[str, str | int]:
183
+ return {
184
+ "schema_version": self.schema_version,
185
+ "object_name": self.object_name,
186
+ "generation": self.generation,
187
+ "manifest_sha256": self.manifest_sha256,
188
+ "byte_length": self.byte_length,
189
+ }
190
+
191
+ @classmethod
192
+ def from_dict(cls, value: object) -> Self:
193
+ document = _require_exact(
194
+ value,
195
+ frozenset(
196
+ {"schema_version", "object_name", "generation", "manifest_sha256", "byte_length"}
197
+ ),
198
+ "manifest_ref",
199
+ )
200
+ return cls(
201
+ schema_version=document["schema_version"],
202
+ object_name=document["object_name"],
203
+ generation=document["generation"],
204
+ manifest_sha256=document["manifest_sha256"],
205
+ byte_length=document["byte_length"],
206
+ )
207
+
208
+ def require_for_prefix(self, *, prefix: str, run_id: str) -> None:
209
+ """Reject a ref outside the exact reviewed delivery namespace and run coordinate."""
210
+
211
+ checked_prefix = _require_prefix(prefix, "manifest_prefix")
212
+ checked_run_id = _require_run_id(run_id, "run_id")
213
+ expected = f"{checked_prefix}/runs/{checked_run_id}.json"
214
+ if self.object_name != expected:
215
+ raise _error(
216
+ "INGESTION_MANIFEST_REF_PATH",
217
+ "manifest_ref.object_name",
218
+ "does not name this delivery's manifest object",
219
+ )
220
+
221
+
222
+ @dataclass(frozen=True)
223
+ class PublicOffsetContinuationV1:
224
+ """The only first-slice continuation: a deterministic offset in immutable public bytes."""
225
+
226
+ offset: int
227
+ schema_version: str = PUBLIC_OFFSET_CONTINUATION_SCHEMA
228
+
229
+ def __post_init__(self) -> None:
230
+ if self.schema_version != PUBLIC_OFFSET_CONTINUATION_SCHEMA:
231
+ raise _error(
232
+ "INGESTION_CONTINUATION_SCHEMA", "continuation.schema_version", "is unsupported"
233
+ )
234
+ _require_nonnegative_int(self.offset, "continuation.offset")
235
+
236
+ def to_dict(self) -> dict[str, str | int]:
237
+ return {"schema_version": self.schema_version, "offset": self.offset}
238
+
239
+ @classmethod
240
+ def from_dict(cls, value: object) -> Self:
241
+ document = _require_exact(value, frozenset({"schema_version", "offset"}), "continuation")
242
+ return cls(schema_version=document["schema_version"], offset=document["offset"])
243
+
244
+
245
+ @dataclass(frozen=True)
246
+ class IngestionDeliveryBindingV1:
247
+ """Identity/fence context sealed around every delivery-scoped manifest document."""
248
+
249
+ delivery_id: str
250
+ execution_id: str
251
+ run_id: str
252
+ segment_id: str
253
+ source_pin_sha256: str
254
+ attempt_id: str
255
+ attempt_generation: int
256
+ attempt_fence: int
257
+ checkpoint_epoch: int
258
+ checkpoint_sequence: int
259
+ parent_checkpoint_digest: str | None
260
+ deadline: str
261
+ schema_version: str = INGESTION_DELIVERY_BINDING_SCHEMA
262
+
263
+ def __post_init__(self) -> None:
264
+ if self.schema_version != INGESTION_DELIVERY_BINDING_SCHEMA:
265
+ raise _error("INGESTION_DELIVERY_SCHEMA", "delivery.schema_version", "is unsupported")
266
+ _require_uuid4(self.delivery_id, "delivery.delivery_id")
267
+ _require_uuid4(self.execution_id, "delivery.execution_id")
268
+ _require_run_id(self.run_id, "delivery.run_id")
269
+ _require_segment_id(self.segment_id, "delivery.segment_id")
270
+ _require_digest(self.source_pin_sha256, "delivery.source_pin_sha256")
271
+ _require_uuid4(self.attempt_id, "delivery.attempt_id")
272
+ _require_positive_int(self.attempt_generation, "delivery.attempt_generation")
273
+ _require_positive_int(self.attempt_fence, "delivery.attempt_fence")
274
+ _require_nonnegative_int(self.checkpoint_epoch, "delivery.checkpoint_epoch")
275
+ _require_nonnegative_int(self.checkpoint_sequence, "delivery.checkpoint_sequence")
276
+ if self.parent_checkpoint_digest is not None:
277
+ _require_digest(self.parent_checkpoint_digest, "delivery.parent_checkpoint_digest")
278
+ _require_deadline(self.deadline, "delivery.deadline")
279
+
280
+ @property
281
+ def deadline_at(self) -> datetime:
282
+ return parse_delivery_deadline(self.deadline)
283
+
284
+ @property
285
+ def digest(self) -> str:
286
+ return canonical_sha256(self.to_dict())
287
+
288
+ def to_dict(self) -> dict[str, str | int | None]:
289
+ return {
290
+ "schema_version": self.schema_version,
291
+ "delivery_id": self.delivery_id,
292
+ "execution_id": self.execution_id,
293
+ "run_id": self.run_id,
294
+ "segment_id": self.segment_id,
295
+ "source_pin_sha256": self.source_pin_sha256,
296
+ "attempt_id": self.attempt_id,
297
+ "attempt_generation": self.attempt_generation,
298
+ "attempt_fence": self.attempt_fence,
299
+ "checkpoint_epoch": self.checkpoint_epoch,
300
+ "checkpoint_sequence": self.checkpoint_sequence,
301
+ "parent_checkpoint_digest": self.parent_checkpoint_digest,
302
+ "deadline": self.deadline,
303
+ }
304
+
305
+ @classmethod
306
+ def from_dict(cls, value: object) -> Self:
307
+ document = _require_exact(
308
+ value,
309
+ frozenset(
310
+ {
311
+ "schema_version",
312
+ "delivery_id",
313
+ "execution_id",
314
+ "run_id",
315
+ "segment_id",
316
+ "source_pin_sha256",
317
+ "attempt_id",
318
+ "attempt_generation",
319
+ "attempt_fence",
320
+ "checkpoint_epoch",
321
+ "checkpoint_sequence",
322
+ "parent_checkpoint_digest",
323
+ "deadline",
324
+ }
325
+ ),
326
+ "delivery",
327
+ )
328
+ return cls(**document)
329
+
330
+ def require_manifest(self, manifest: IngestionManifestV1) -> None:
331
+ if manifest.run_id != self.run_id:
332
+ raise IngestionCorrupt(
333
+ "INGESTION_DELIVERY_RUN", "ingestion_manifest.run_id", "does not match delivery"
334
+ )
335
+ if manifest.source_pin.digest != self.source_pin_sha256:
336
+ raise IngestionCorrupt(
337
+ "INGESTION_DELIVERY_SOURCE",
338
+ "ingestion_manifest.source_pin",
339
+ "does not match delivery",
340
+ )
341
+
342
+
343
+ @dataclass(frozen=True)
344
+ class IngestionDeliveryManifestV1:
345
+ """The only persisted mutable document in a delivery-specific manifest namespace."""
346
+
347
+ delivery: IngestionDeliveryBindingV1
348
+ manifest: IngestionManifestV1
349
+ schema_version: str = INGESTION_DELIVERY_MANIFEST_SCHEMA
350
+
351
+ def __post_init__(self) -> None:
352
+ if self.schema_version != INGESTION_DELIVERY_MANIFEST_SCHEMA:
353
+ raise _error(
354
+ "INGESTION_DELIVERY_MANIFEST_SCHEMA",
355
+ "delivery_manifest.schema_version",
356
+ "is unsupported",
357
+ )
358
+ if not isinstance(self.delivery, IngestionDeliveryBindingV1):
359
+ raise _error("INGESTION_DELIVERY_BINDING", "delivery_manifest.delivery", "is invalid")
360
+ if not isinstance(self.manifest, IngestionManifestV1):
361
+ raise _error("INGESTION_MANIFEST", "delivery_manifest.manifest", "is invalid")
362
+ self.delivery.require_manifest(self.manifest)
363
+
364
+ def to_dict(self) -> dict[str, object]:
365
+ return {
366
+ "schema_version": self.schema_version,
367
+ "delivery": self.delivery.to_dict(),
368
+ "manifest": self.manifest.to_dict(),
369
+ }
370
+
371
+ @classmethod
372
+ def from_dict(cls, value: object) -> Self:
373
+ document = _require_exact(
374
+ value, frozenset({"schema_version", "delivery", "manifest"}), "delivery_manifest"
375
+ )
376
+ return cls(
377
+ schema_version=document["schema_version"],
378
+ delivery=IngestionDeliveryBindingV1.from_dict(document["delivery"]),
379
+ manifest=IngestionManifestV1.from_dict(document["manifest"]),
380
+ )
381
+
382
+
383
+ @dataclass(frozen=True)
384
+ class PriorManifestV1:
385
+ """The predecessor's namespace plus an exact document reference, or nothing at all."""
386
+
387
+ prefix: str
388
+ manifest_ref: IngestionManifestReferenceV1
389
+
390
+ def __post_init__(self) -> None:
391
+ _require_prefix(self.prefix, "prior_manifest.prefix")
392
+ if not isinstance(self.manifest_ref, IngestionManifestReferenceV1):
393
+ raise _error("INGESTION_MANIFEST_REF", "prior_manifest.manifest_ref", "is invalid")
394
+
395
+ def to_dict(self) -> dict[str, object]:
396
+ return {"prefix": self.prefix, "manifest_ref": self.manifest_ref.to_dict()}
397
+
398
+ @classmethod
399
+ def from_dict(cls, value: object) -> Self:
400
+ document = _require_exact(value, frozenset({"prefix", "manifest_ref"}), "prior_manifest")
401
+ return cls(
402
+ prefix=document["prefix"],
403
+ manifest_ref=IngestionManifestReferenceV1.from_dict(document["manifest_ref"]),
404
+ )
405
+
406
+
407
+ @dataclass(frozen=True)
408
+ class HostedIngestionDispatchV1:
409
+ """One authenticated dispatch exchange result with an entirely closed schema."""
410
+
411
+ delivery: IngestionDeliveryBindingV1
412
+ source_pin: SourceVersionPinV1
413
+ continuation: PublicOffsetContinuationV1
414
+ manifest_prefix: str
415
+ chunk_prefix: str
416
+ prior_manifest: PriorManifestV1 | None
417
+ schema_version: str = HOSTED_INGESTION_DISPATCH_SCHEMA
418
+
419
+ def __post_init__(self) -> None:
420
+ if self.schema_version != HOSTED_INGESTION_DISPATCH_SCHEMA:
421
+ raise _error("INGESTION_DISPATCH_SCHEMA", "dispatch.schema_version", "is unsupported")
422
+ if not isinstance(self.delivery, IngestionDeliveryBindingV1):
423
+ raise _error("INGESTION_DELIVERY_BINDING", "dispatch.delivery", "is invalid")
424
+ if not isinstance(self.source_pin, SourceVersionPinV1):
425
+ raise _error("INGESTION_SOURCE_PIN", "dispatch.source_pin", "is invalid")
426
+ if self.source_pin.digest != self.delivery.source_pin_sha256:
427
+ raise _error(
428
+ "INGESTION_DELIVERY_SOURCE", "dispatch.source_pin", "does not match delivery"
429
+ )
430
+ if not isinstance(self.continuation, PublicOffsetContinuationV1):
431
+ raise _error("INGESTION_CONTINUATION", "dispatch.continuation", "is invalid")
432
+ _require_prefix(self.manifest_prefix, "dispatch.manifest_prefix")
433
+ _require_delivery_prefix(
434
+ self.manifest_prefix, "dispatch.manifest_prefix", self.delivery.delivery_id
435
+ )
436
+ _require_prefix(self.chunk_prefix, "dispatch.chunk_prefix")
437
+ if self.prior_manifest is not None and not isinstance(self.prior_manifest, PriorManifestV1):
438
+ raise _error("INGESTION_PRIOR_MANIFEST", "dispatch.prior_manifest", "is invalid")
439
+ if self.prior_manifest is not None:
440
+ self.prior_manifest.manifest_ref.require_for_prefix(
441
+ prefix=self.prior_manifest.prefix, run_id=self.delivery.run_id
442
+ )
443
+
444
+ def to_dict(self) -> dict[str, object]:
445
+ return {
446
+ "schema_version": self.schema_version,
447
+ "delivery": self.delivery.to_dict(),
448
+ "source_pin": self.source_pin.to_dict(),
449
+ "continuation": self.continuation.to_dict(),
450
+ "manifest_prefix": self.manifest_prefix,
451
+ "chunk_prefix": self.chunk_prefix,
452
+ "prior_manifest": None
453
+ if self.prior_manifest is None
454
+ else self.prior_manifest.to_dict(),
455
+ }
456
+
457
+ @classmethod
458
+ def from_dict(cls, value: object) -> Self:
459
+ document = _require_exact(
460
+ value,
461
+ frozenset(
462
+ {
463
+ "schema_version",
464
+ "delivery",
465
+ "source_pin",
466
+ "continuation",
467
+ "manifest_prefix",
468
+ "chunk_prefix",
469
+ "prior_manifest",
470
+ }
471
+ ),
472
+ "dispatch",
473
+ )
474
+ prior = document["prior_manifest"]
475
+ if prior is not None and not isinstance(prior, dict):
476
+ raise _error(
477
+ "INGESTION_PRIOR_MANIFEST", "dispatch.prior_manifest", "must be null or object"
478
+ )
479
+ return cls(
480
+ schema_version=document["schema_version"],
481
+ delivery=IngestionDeliveryBindingV1.from_dict(document["delivery"]),
482
+ source_pin=SourceVersionPinV1.from_dict(document["source_pin"]),
483
+ continuation=PublicOffsetContinuationV1.from_dict(document["continuation"]),
484
+ manifest_prefix=document["manifest_prefix"],
485
+ chunk_prefix=document["chunk_prefix"],
486
+ prior_manifest=None if prior is None else PriorManifestV1.from_dict(prior),
487
+ )
488
+
489
+
490
+ __all__ = [
491
+ "HOSTED_INGESTION_DISPATCH_SCHEMA",
492
+ "INGESTION_DELIVERY_BINDING_SCHEMA",
493
+ "INGESTION_DELIVERY_MANIFEST_SCHEMA",
494
+ "INGESTION_MANIFEST_REF_SCHEMA",
495
+ "MAX_DELIVERY_DEADLINE_SECONDS",
496
+ "PUBLIC_OFFSET_CONTINUATION_SCHEMA",
497
+ "HostedIngestionDispatchV1",
498
+ "IngestionDeliveryBindingV1",
499
+ "IngestionDeliveryManifestV1",
500
+ "IngestionManifestReferenceV1",
501
+ "PriorManifestV1",
502
+ "PublicOffsetContinuationV1",
503
+ "parse_delivery_deadline",
504
+ ]