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,542 @@
1
+ """The contract every Reader family implements, and the typed failures it may raise.
2
+
3
+ A Reader family is the code that turns fetched bytes into rows. This module states the
4
+ shape of that hand-off and nothing else: it decodes nothing, registers nothing, and reaches
5
+ no filesystem. Four things live here.
6
+
7
+ ``ReaderError``
8
+ The one failure type, carrying a stable machine-readable code, the subject that failed,
9
+ and a human detail. The codes are a closed table, ``READER_ERROR_CODES``, validated at
10
+ construction so a typo becomes a failure instead of an unmapped error string. The table is
11
+ the interface consumed by error remediation, so codes are added here and nowhere else.
12
+
13
+ ``ReaderPin``
14
+ The three fields a recipe seals to say which code decoded its bytes and with which
15
+ settings. The pin is all-present or all-absent, never half-stated, and its
16
+ ``options_digest`` is computed exactly the way the recipe computes its own
17
+ ``decode_options_digest``: sha256 over ``canonical_json_bytes`` of the options mapping.
18
+ It is never computed a second way.
19
+
20
+ ``ReaderBudgets``
21
+ The resource ceilings a family decodes under. ``narrowed_by`` takes a per-field minimum
22
+ against a caps mapping, so a recipe may narrow a budget and may never widen one. The
23
+ caps mapping is accepted as a plain mapping rather than the recipe's own typed object,
24
+ because importing ``recipe`` here would close an import cycle.
25
+
26
+ ``ReaderResult`` and ``ReaderFamily``
27
+ What a decode returns and what a family must offer. A decode receives exact bytes and
28
+ returns exact bytes and touches no filesystem: no temporary directory, no extraction, no
29
+ spill.
30
+
31
+ Decode options reach this module already screened for secrets and already bounded by the
32
+ recipe layer. This module states that dependency rather than duplicating the screen; a
33
+ second, differently-worded screen would be a second declaration of the same rule.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import re
39
+ from collections.abc import Mapping
40
+ from dataclasses import dataclass, fields
41
+ from types import MappingProxyType
42
+ from typing import Any, Protocol
43
+
44
+ from mostlyright.data_harness.canonical import (
45
+ CanonicalJSONError,
46
+ canonical_json_bytes,
47
+ sha256_bytes,
48
+ )
49
+ from mostlyright.data_harness.formats import (
50
+ DATA_FORMATS,
51
+ FORMAT_MEDIA_TYPES,
52
+ READER_CONTRACT_VERSION,
53
+ )
54
+
55
+ __all__ = [
56
+ "DECODE_FLAGS",
57
+ "READER_CONTRACT_VERSION",
58
+ "READER_ERROR_CODES",
59
+ "ReaderBudgets",
60
+ "ReaderError",
61
+ "ReaderFamily",
62
+ "ReaderPin",
63
+ "ReaderResult",
64
+ "identifier",
65
+ "semver",
66
+ ]
67
+
68
+ # Error remediation consumes this closed table. Add new codes here, not at individual raise sites.
69
+ READER_ERROR_CODES: frozenset[str] = frozenset(
70
+ {
71
+ # Resolution and contract shape.
72
+ "READER_FAMILY_UNKNOWN",
73
+ "READER_DEPENDENCY_UNAVAILABLE",
74
+ "READER_CONTRACT",
75
+ "READER_OPTIONS",
76
+ "READER_ADMISSION",
77
+ "READER_BUDGET",
78
+ # Container members.
79
+ "READER_MEMBER_MISSING",
80
+ "READER_MEMBER_AMBIGUOUS",
81
+ "READER_MEMBER_TYPE",
82
+ "READER_NESTED_CONTAINER",
83
+ # Spreadsheet coordinates.
84
+ "READER_SHEET_MISSING",
85
+ "READER_RANGE",
86
+ # Weather-model admission, refused before decode.
87
+ "READER_GRID_UNKNOWN",
88
+ "READER_PACKING_UNKNOWN",
89
+ # Bytes a family admitted and then could not decode: truncated, corrupted in transit, or
90
+ # damaged at the publisher. It is separate from the admission codes on purpose, because
91
+ # the fix is different in kind: an admission refusal is repaired by editing the recipe or
92
+ # by widening a reviewed allowlist, and this one is repaired by fetching the file again.
93
+ # A family whose decoder reports its own failures translates them into this code rather
94
+ # than letting the decoder's exception type escape, since an untyped failure carries no
95
+ # code for the remediation map to bind to.
96
+ "READER_DECODE",
97
+ # Output and the pre-refresh warm-up check.
98
+ "READER_OUTPUT",
99
+ "READER_WARMUP",
100
+ }
101
+ )
102
+
103
+ # The closed vocabulary of facts a decode may report about itself. A flag is a fact the
104
+ # receipt carries forward, not a warning: "this spreadsheet held formulas, and the values
105
+ # read were the ones the authoring application had cached" is a provenance statement a
106
+ # reader of the dataset is owed. Flags are sorted and de-duplicated on the result, so two
107
+ # runs of the same decode produce the same tuple and the same receipt bytes.
108
+ #
109
+ # ``contains_masked_points`` is the weather family's: a message may carry a bitmap marking points
110
+ # that hold no value, and a point the recipe named which the message marked absent becomes an
111
+ # empty cell. Without the flag that empty cell is ambiguous -- it reads the same as a Reader that
112
+ # had a problem -- and with it the receipt states that the publisher marked the point absent.
113
+ DECODE_FLAGS: frozenset[str] = frozenset(
114
+ {"contains_formula_cells", "contains_cached_values", "contains_masked_points"}
115
+ )
116
+
117
+ # Identity and version rules, spelled the way the recipe spells them. They are duplicated
118
+ # rather than imported because importing ``recipe`` here would close an import cycle; a test
119
+ # asserts the two patterns letter for letter so the duplication cannot drift.
120
+ _IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$")
121
+ _SEMVER = re.compile(r"^[1-9][0-9]*\.[0-9]+\.[0-9]+$")
122
+
123
+ # The same ceiling the recipe applies to a sealed decode-options object.
124
+ _MAX_DECODE_OPTIONS_BYTES = 256 * 1024
125
+
126
+ # A sealed snapshot filename is a bare name in a coordinator-owned directory: no separator,
127
+ # no parent reference, no leading dot.
128
+ _FILENAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,254}$")
129
+
130
+ # Nesting depth is fixed rather than tunable. A budget that could ask for depth 2 would be
131
+ # the archive-in-archive bug class re-entering by configuration, so the only admissible
132
+ # value is 1 and any other value is refused where the budget is built.
133
+ _FIXED_NESTING_DEPTH = 1
134
+
135
+
136
+ # The attributes the interpreter owns rather than this class. Sealing them seals nothing: an
137
+ # exception whose traceback cannot be set is not immutable, it is broken. ``contextlib`` and
138
+ # the exception-chaining machinery assign ``__traceback__`` at Python level, so a Reader
139
+ # refusal crossing a context manager would surface as ``AttributeError: cannot set
140
+ # '__traceback__'`` instead of as its own code -- the loudest failure replaced by a confusing
141
+ # one. What must not be rewritable is the code, the subject, and the detail, and those stay
142
+ # sealed exactly as they were.
143
+ _INTERPRETER_OWNED_ATTRIBUTES = frozenset(
144
+ {"__traceback__", "__cause__", "__context__", "__suppress_context__", "__notes__"}
145
+ )
146
+
147
+
148
+ class ReaderError(ValueError):
149
+ """One stable, fail-closed Reader failure: a code, the subject, and a detail.
150
+
151
+ The three-part shape mirrors the source-contract error the acquisition layer already
152
+ raises, so a caller handling one handles the other the same way. Instances are
153
+ immutable once constructed: an error that a handler could rewrite on its way up is an
154
+ error whose code no longer means anything.
155
+ """
156
+
157
+ def __init__(self, code: str, subject: str, detail: str) -> None:
158
+ if not isinstance(code, str) or code not in READER_ERROR_CODES:
159
+ raise ValueError(
160
+ f"unknown Reader error code {code!r}; add it to READER_ERROR_CODES so "
161
+ "error remediation can handle it"
162
+ )
163
+ super().__init__(f"{subject}: {detail} [{code}]")
164
+ self.__dict__["code"] = code
165
+ self.__dict__["subject"] = subject
166
+ self.__dict__["detail"] = detail
167
+ self.__dict__["_sealed"] = True
168
+
169
+ def __setattr__(self, name: str, value: Any) -> None:
170
+ if self.__dict__.get("_sealed") and name not in _INTERPRETER_OWNED_ATTRIBUTES:
171
+ raise AttributeError(f"ReaderError is immutable; cannot set {name!r}")
172
+ super().__setattr__(name, value)
173
+
174
+ def __delattr__(self, name: str) -> None:
175
+ raise AttributeError(f"ReaderError is immutable; cannot delete {name!r}")
176
+
177
+
178
+ def identifier(value: Any, subject: str) -> str:
179
+ """Admit a canonical identifier, on the recipe's own rule.
180
+
181
+ Public because the registry validates a family's declared identity against exactly
182
+ this rule. One rule, one implementation, so a family that could be registered can
183
+ always be pinned and a family that can be pinned can always be registered.
184
+ """
185
+
186
+ if not isinstance(value, str) or _IDENTIFIER.fullmatch(value) is None:
187
+ raise ReaderError("READER_CONTRACT", subject, "must be a canonical identifier")
188
+ return value
189
+
190
+
191
+ def semver(value: Any, subject: str) -> str:
192
+ """Admit a canonical positive semver, on the recipe's own rule."""
193
+
194
+ if not isinstance(value, str) or _SEMVER.fullmatch(value) is None:
195
+ raise ReaderError("READER_CONTRACT", subject, "must be a canonical positive semver")
196
+ return value
197
+
198
+
199
+ def _strict_json_object(value: Any, subject: str) -> dict[str, Any]:
200
+ """Admit a bounded, canonicalisable JSON object on the recipe's own terms."""
201
+
202
+ if not isinstance(value, Mapping):
203
+ raise ReaderError("READER_OPTIONS", subject, "must be an object")
204
+ try:
205
+ raw = canonical_json_bytes(dict(value))
206
+ except CanonicalJSONError as error:
207
+ raise ReaderError(
208
+ "READER_OPTIONS",
209
+ subject,
210
+ "contains values that cannot be sealed canonically",
211
+ ) from error
212
+ if len(raw) > _MAX_DECODE_OPTIONS_BYTES:
213
+ raise ReaderError("READER_OPTIONS", subject, "exceeds 256 KiB")
214
+ return dict(value)
215
+
216
+
217
+ @dataclass(frozen=True)
218
+ class ReaderPin:
219
+ """The exact decoder a recipe sealed, and the exact settings it ran with.
220
+
221
+ ``family_id`` and ``family_version`` resolve one entry of the Toolbox on an exact key.
222
+ ``decode_options`` are the family's own settings, sealed verbatim, so replaying the
223
+ recipe replays the decode. Every knob is inside ``options_digest``: changing one
224
+ changes the recipe digest and invalidates an approval granted against the old settings.
225
+ """
226
+
227
+ family_id: str
228
+ family_version: str
229
+ decode_options: Mapping[str, Any]
230
+
231
+ def __post_init__(self) -> None:
232
+ identifier(self.family_id, "reader.pin.family_id")
233
+ semver(self.family_version, "reader.pin.family_version")
234
+ object.__setattr__(
235
+ self,
236
+ "decode_options",
237
+ MappingProxyType(_strict_json_object(self.decode_options, "reader.pin.decode_options")),
238
+ )
239
+
240
+ @classmethod
241
+ def from_recipe_fields(
242
+ cls,
243
+ family_id: str | None,
244
+ family_version: str | None,
245
+ decode_options: Mapping[str, Any] | None,
246
+ ) -> ReaderPin | None:
247
+ """Build the pin from a recipe source's three fields, or None when none are stated.
248
+
249
+ All three present or all three absent is the only admissible shape. A source that
250
+ names a decoder without pinning its settings, or pins settings without naming a
251
+ decoder, could not be replayed from its own record. Absent means today's direct
252
+ fetch, where no Reader family decoded anything. This is the one construction path,
253
+ so no caller has to re-derive the rule.
254
+ """
255
+
256
+ present = tuple(item is not None for item in (family_id, family_version, decode_options))
257
+ if not any(present):
258
+ return None
259
+ if not all(present):
260
+ raise ReaderError(
261
+ "READER_CONTRACT",
262
+ "reader.pin",
263
+ "family_id, family_version, and decode_options are all present or all absent",
264
+ )
265
+ assert family_id is not None and family_version is not None
266
+ assert decode_options is not None
267
+ return cls(family_id, family_version, decode_options)
268
+
269
+ @property
270
+ def options_digest(self) -> str:
271
+ """Digest over the exact decode settings, computed the recipe's one way."""
272
+
273
+ return sha256_bytes(canonical_json_bytes(dict(self.decode_options)))
274
+
275
+ @property
276
+ def coordinate(self) -> str:
277
+ """The ``id@version`` coordinate, for a refusal message a person can act on."""
278
+
279
+ return f"{self.family_id}@{self.family_version}"
280
+
281
+
282
+ @dataclass(frozen=True)
283
+ class ReaderBudgets:
284
+ """The ceilings one decode runs under. A recipe may narrow them; never widen them.
285
+
286
+ The defaults are the family-level defaults that apply when a recipe states no caps.
287
+ ``narrowed_by`` is per-field minimum only, which is what makes a stated cap unable to
288
+ buy more room than the family already allows.
289
+ """
290
+
291
+ max_input_bytes: int = 16 * 1024 * 1024
292
+ max_output_bytes: int = 16 * 1024 * 1024
293
+ max_uncompressed_bytes: int = 128 * 1024 * 1024
294
+ max_expansion_ratio: int = 100
295
+ max_declared_cells: int = 10_000_000
296
+ max_container_members: int = 1_024
297
+ max_nesting_depth: int = _FIXED_NESTING_DEPTH
298
+ max_rows: int = 1_000_000
299
+ max_columns: int = 1_024
300
+
301
+ def __post_init__(self) -> None:
302
+ for item in fields(self):
303
+ value = getattr(self, item.name)
304
+ if type(value) is not int or value < 1:
305
+ raise ReaderError(
306
+ "READER_BUDGET",
307
+ f"reader.budgets.{item.name}",
308
+ "must be a positive integer",
309
+ )
310
+ if self.max_nesting_depth != _FIXED_NESTING_DEPTH:
311
+ raise ReaderError(
312
+ "READER_BUDGET",
313
+ "reader.budgets.max_nesting_depth",
314
+ "must be exactly 1: nesting depth is a security invariant, not a budget",
315
+ )
316
+
317
+ def narrowed_by(self, caps: Mapping[str, Any] | None) -> ReaderBudgets:
318
+ """Return budgets no wider than these on any field.
319
+
320
+ A cap naming a field this contract does not have is refused by name rather than
321
+ ignored, because a silently ignored cap is a budget a recipe believes it set.
322
+ """
323
+
324
+ if caps is None:
325
+ return self
326
+ if not isinstance(caps, Mapping):
327
+ raise ReaderError("READER_BUDGET", "reader.budgets.caps", "must be a mapping or absent")
328
+ known = {item.name for item in fields(self)}
329
+ unknown = sorted(str(key) for key in caps if key not in known)
330
+ if unknown:
331
+ raise ReaderError(
332
+ "READER_BUDGET",
333
+ "reader.budgets.caps",
334
+ f"names no such budget: {', '.join(unknown)}",
335
+ )
336
+ narrowed: dict[str, int] = {}
337
+ for name in known:
338
+ current = getattr(self, name)
339
+ if name not in caps:
340
+ narrowed[name] = current
341
+ continue
342
+ requested = caps[name]
343
+ if type(requested) is not int or requested < 1:
344
+ raise ReaderError(
345
+ "READER_BUDGET",
346
+ f"reader.budgets.caps.{name}",
347
+ "must be a positive integer",
348
+ )
349
+ narrowed[name] = min(current, requested)
350
+ return ReaderBudgets(**narrowed)
351
+
352
+
353
+ def bulk_default_budgets() -> ReaderBudgets:
354
+ """Family defaults for the bulk-capable text and container families.
355
+
356
+ Sized for EPA-scale public sources under the raised hosted Courier caps: a 256 MiB
357
+ fetched archive whose decode output may reach the 4 GiB normalized budget. The
358
+ uncompressed ceiling rises with the output budget because a container's declared
359
+ member size is checked against it before extraction. Spreadsheet and GRIB families
360
+ keep their own, tighter certified defaults.
361
+ """
362
+
363
+ return ReaderBudgets(
364
+ max_input_bytes=268_435_456,
365
+ max_output_bytes=4_294_967_296,
366
+ max_uncompressed_bytes=4_294_967_296,
367
+ max_declared_cells=400_000_000,
368
+ max_rows=10_000_000,
369
+ )
370
+
371
+
372
+ @dataclass(frozen=True)
373
+ class ReaderResult:
374
+ """What one decode produced: exact output bytes and the facts about producing them.
375
+
376
+ ``content`` is already the canonical output encoding. The Reader owns the byte
377
+ stability of the sealed artifact, so the bytes here are the bytes that get sealed.
378
+ """
379
+
380
+ content: bytes
381
+ data_format: str
382
+ media_type: str
383
+ filename: str
384
+ row_count: int
385
+ column_names: tuple[str, ...]
386
+ declared_cell_count: int
387
+ flags: tuple[str, ...] = ()
388
+
389
+ def __post_init__(self) -> None:
390
+ if not isinstance(self.content, (bytes, bytearray)) or len(self.content) == 0:
391
+ raise ReaderError(
392
+ "READER_OUTPUT",
393
+ "reader.result.content",
394
+ "must be nonempty exact bytes",
395
+ )
396
+ object.__setattr__(self, "content", bytes(self.content))
397
+ if self.data_format not in DATA_FORMATS:
398
+ raise ReaderError(
399
+ "READER_OUTPUT",
400
+ "reader.result.data_format",
401
+ "is not a format the table admits",
402
+ )
403
+ if self.media_type not in FORMAT_MEDIA_TYPES[self.data_format]:
404
+ raise ReaderError(
405
+ "READER_OUTPUT",
406
+ "reader.result.media_type",
407
+ "is not a media type the table maps to this format",
408
+ )
409
+ if not isinstance(self.filename, str) or _FILENAME.fullmatch(self.filename) is None:
410
+ raise ReaderError(
411
+ "READER_OUTPUT",
412
+ "reader.result.filename",
413
+ "must be a confined bare filename",
414
+ )
415
+ if type(self.row_count) is not int or self.row_count < 0:
416
+ raise ReaderError(
417
+ "READER_OUTPUT",
418
+ "reader.result.row_count",
419
+ "must be a non-negative integer",
420
+ )
421
+ if not isinstance(self.column_names, tuple) or not self.column_names:
422
+ raise ReaderError(
423
+ "READER_OUTPUT",
424
+ "reader.result.column_names",
425
+ "must be a nonempty tuple",
426
+ )
427
+ for index, name in enumerate(self.column_names):
428
+ if not isinstance(name, str) or not name or "\x00" in name:
429
+ raise ReaderError(
430
+ "READER_OUTPUT",
431
+ f"reader.result.column_names[{index}]",
432
+ "must be nonempty text without a NUL byte",
433
+ )
434
+ if len(set(self.column_names)) != len(self.column_names):
435
+ raise ReaderError(
436
+ "READER_OUTPUT",
437
+ "reader.result.column_names",
438
+ "must not repeat a column name",
439
+ )
440
+ if type(
441
+ self.declared_cell_count
442
+ ) is not int or self.declared_cell_count < self.row_count * len(self.column_names):
443
+ raise ReaderError(
444
+ "READER_OUTPUT",
445
+ "reader.result.declared_cell_count",
446
+ "must be an integer no smaller than the emitted table geometry",
447
+ )
448
+ if not isinstance(self.flags, tuple):
449
+ raise ReaderError("READER_OUTPUT", "reader.result.flags", "must be a tuple")
450
+ unknown = sorted(str(flag) for flag in self.flags if flag not in DECODE_FLAGS)
451
+ if unknown:
452
+ raise ReaderError(
453
+ "READER_OUTPUT",
454
+ "reader.result.flags",
455
+ f"names no such decode flag: {', '.join(unknown)}",
456
+ )
457
+ object.__setattr__(self, "flags", tuple(sorted(set(self.flags))))
458
+
459
+ def validate_for(self, output_format: str, budgets: ReaderBudgets) -> None:
460
+ """Bind the result to the family that produced it and the budgets it ran under.
461
+
462
+ Kept separate from construction because a result does not know which family built
463
+ it, and a family's declared output format is the thing being checked.
464
+ """
465
+
466
+ if self.data_format != output_format:
467
+ raise ReaderError(
468
+ "READER_OUTPUT",
469
+ "reader.result.data_format",
470
+ "differs from the format the family declares it emits",
471
+ )
472
+ if len(self.content) > budgets.max_output_bytes:
473
+ raise ReaderError(
474
+ "READER_BUDGET",
475
+ "reader.result.content",
476
+ "exceeds the output byte budget",
477
+ )
478
+ if self.row_count > budgets.max_rows:
479
+ raise ReaderError("READER_BUDGET", "reader.result.row_count", "exceeds the row budget")
480
+ if len(self.column_names) > budgets.max_columns:
481
+ raise ReaderError(
482
+ "READER_BUDGET",
483
+ "reader.result.column_names",
484
+ "exceeds the column budget",
485
+ )
486
+ if self.declared_cell_count > budgets.max_declared_cells:
487
+ raise ReaderError(
488
+ "READER_BUDGET",
489
+ "reader.result.declared_cell_count",
490
+ "exceeds the declared-cell budget",
491
+ )
492
+
493
+
494
+ class ReaderFamily(Protocol):
495
+ """One certified decoder, resolved by exact identity from the closed Toolbox.
496
+
497
+ A family is deliberately not runtime-checkable. Membership of the Toolbox is decided
498
+ by the registry at registration time against this contract's requirements, not by an
499
+ isinstance test at a call site, so a partially-shaped object cannot slip through by
500
+ happening to answer the attributes an isinstance check samples.
501
+ """
502
+
503
+ @property
504
+ def family_id(self) -> str:
505
+ """The identifier half of the exact resolution key."""
506
+
507
+ @property
508
+ def family_version(self) -> str:
509
+ """The version half of the exact resolution key. Never resolved approximately."""
510
+
511
+ @property
512
+ def contract_version(self) -> str:
513
+ """The Reader-contract label this family was built against."""
514
+
515
+ @property
516
+ def output_format(self) -> str:
517
+ """The one format this family emits, which the result must match."""
518
+
519
+ @property
520
+ def accepted_media_types(self) -> tuple[str, ...]:
521
+ """The media types this family admits as input, sorted and de-duplicated."""
522
+
523
+ @property
524
+ def default_budgets(self) -> ReaderBudgets:
525
+ """The ceilings that apply when a recipe states no caps."""
526
+
527
+ def validate_options(self, options: Mapping[str, Any]) -> Mapping[str, Any]:
528
+ """Admit this family's own decode settings, refusing an unknown key by name."""
529
+
530
+ def decode(
531
+ self,
532
+ content: bytes,
533
+ pin: ReaderPin,
534
+ budgets: ReaderBudgets,
535
+ ) -> ReaderResult:
536
+ """Turn exact input bytes into exact output bytes, touching no filesystem.
537
+
538
+ The decode receives the bytes it is given and returns the bytes it produced. It
539
+ creates no temporary directory, extracts no archive member to disk, and spills
540
+ nothing: a container is streamed in memory or it is refused. That is the whole
541
+ security argument for admitting containers at all, and it admits no exception.
542
+ """