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,510 @@
1
+ """The one canonical output encoder, the pinned float rule, and the shared delimited reader.
2
+
3
+ Every Reader family emits through this module. The sealed artifact is a Reader's output,
4
+ so byte-stability is a Reader-contract obligation rather than a per-family accident: two
5
+ runs of the same decode on two hosts produce the same bytes, therefore the same digest,
6
+ therefore the same receipt.
7
+
8
+ The float rule, pinned
9
+ ----------------------
10
+ This is the contract other families are held to, so it is written as prose a reviewer can
11
+ check rather than left to whatever a formatter happens to do.
12
+
13
+ * Values are IEEE-754 binary64 end to end. Nothing is widened, narrowed, or rounded on the
14
+ way to text, and no fused multiply-add or extended-precision accumulation is permitted in
15
+ a path that produces a value rendered here.
16
+ * The text form is the shortest decimal string that parses back to the identical bit
17
+ pattern. Shortest round-trip, not fixed precision: a formatter that emits more digits is
18
+ as wrong as one that emits fewer, because the extra digits are not information and they
19
+ move the digest.
20
+ * No locale, no thousands separator, and no exponent normalisation beyond the shortest form
21
+ itself. The decimal point is a full stop and nothing else.
22
+ * Negative zero renders as ``-0.0`` and is never folded into ``0.0``. They are two values,
23
+ they compare equal, and a fold would make two different inputs seal to one artifact.
24
+ * A non-finite value is never rendered. It is a missing cell instead. This is what keeps
25
+ the output inside the boundary the sandbox IPC enforces, which refuses non-finite numbers
26
+ outright: a missing value has already become an empty cell before anything crosses it.
27
+
28
+ The missing-value rule
29
+ ----------------------
30
+ An absent value and a non-finite value are both the empty cell. The empty cell is the only
31
+ representation of "no value here": there is no sentinel, no ``NULL`` text, and no
32
+ distinction between an absent value and an empty string once the bytes are sealed.
33
+
34
+ One decoder, one delimiter parameter
35
+ ------------------------------------
36
+ ``decode_delimited`` takes its delimiter as an argument, which is the whole of
37
+ tab-separated support and the whole of decoding a container's member. A separate
38
+ tab-separated reader would be a second implementation of one thing, so there is not one.
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import csv
44
+ import io
45
+ import itertools
46
+ import math
47
+ import re
48
+ from collections.abc import Iterable, Iterator, Mapping, Sequence
49
+ from typing import Any
50
+
51
+ from mostlyright.data_harness.fast_delimited import certified_rows, certifies
52
+ from mostlyright.data_harness.readers.contracts import ReaderBudgets, ReaderError
53
+
54
+ __all__ = [
55
+ "FALLBACK_STEM",
56
+ "check_declared_size",
57
+ "decode_delimited",
58
+ "decode_delimited_stream",
59
+ "encode_canonical_csv",
60
+ "format_float",
61
+ "sealed_filename",
62
+ ]
63
+
64
+ # A column name may not carry a control character. The rule is the parser's own, so a table
65
+ # a Reader produces is a table the parser would have admitted.
66
+ _SAFE_COLUMN = re.compile(r"^[^\x00-\x1f\x7f]{1,256}$")
67
+
68
+ # The quoting settings. Fixed, not configurable: a configurable quote character would be a
69
+ # knob that moves the sealed bytes without moving the recipe digest.
70
+ _QUOTE_CHARACTER = '"'
71
+
72
+ # The characters a derived sealed filename may carry, from the Reader contract's own rule.
73
+ _FILENAME_SAFE = frozenset("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-")
74
+ _FILENAME_STEM_BYTES = 200
75
+
76
+ # The stem used when a family has no caller-supplied name to derive one from, and when a
77
+ # supplied name leaves nothing usable behind. Public because a family with no name of its own
78
+ # -- a gzip stream carries none -- passes it in deliberately rather than inventing a second one.
79
+ FALLBACK_STEM = "decoded"
80
+
81
+
82
+ def sealed_filename(stem_source: str, *, suffix: str) -> str:
83
+ """Derive the sealed artifact's bare filename from whatever the source called the thing.
84
+
85
+ Derived rather than taken. An archive member name, a worksheet title -- both are text a
86
+ stranger chose, and the sealed filename has to satisfy the Reader contract's confined
87
+ bare-name rule. Anything outside that rule becomes an underscore, and a name left with
88
+ nothing usable becomes a fixed stem instead of an empty one.
89
+
90
+ One implementation, shared by every family. A second copy would let two families disagree
91
+ about what a confined name is, and the disagreement would surface as a ``READER_OUTPUT``
92
+ refusal from the result contract rather than as anything a reader of the code could see.
93
+ """
94
+
95
+ base = stem_source.replace("\\", "/").rsplit("/", 1)[-1]
96
+ stem = base[: base.rindex(".")] if "." in base[1:] else base
97
+ kept = "".join(character if character in _FILENAME_SAFE else "_" for character in stem)
98
+ kept = kept.lstrip("_.-")[:_FILENAME_STEM_BYTES]
99
+ return (kept or FALLBACK_STEM) + suffix
100
+
101
+
102
+ def format_float(value: float) -> str:
103
+ """Render one binary64 value as the shortest decimal text that round-trips.
104
+
105
+ See the float rule in the module docstring; this function is that rule's one
106
+ implementation. A non-finite value is refused rather than rendered, because the caller
107
+ that has one is meant to emit a missing cell instead.
108
+ """
109
+
110
+ if not isinstance(value, float) or isinstance(value, bool):
111
+ raise ReaderError(
112
+ "READER_OUTPUT",
113
+ "reader.output.value",
114
+ "the float rule applies to binary64 values only",
115
+ )
116
+ if not math.isfinite(value):
117
+ raise ReaderError(
118
+ "READER_OUTPUT",
119
+ "reader.output.value",
120
+ "a non-finite value is never rendered; emit a missing cell instead",
121
+ )
122
+ # ``repr`` is Python's shortest-round-trip binary64 formatter. It is named here rather
123
+ # than reimplemented because a hand-rolled shortest-form search is exactly the code that
124
+ # would drift from the rule the docstring pins.
125
+ return repr(value)
126
+
127
+
128
+ def _render_cell(value: Any, subject: str) -> str:
129
+ """Render one value as canonical cell text, or as the empty cell when it is missing."""
130
+
131
+ if value is None:
132
+ return ""
133
+ if isinstance(value, str):
134
+ if "\x00" in value:
135
+ raise ReaderError("READER_OUTPUT", subject, "a cell may not carry a NUL byte")
136
+ return value
137
+ if isinstance(value, bool):
138
+ # Pinned lower-case, so a spreadsheet's boolean seals the same way on every host.
139
+ return "true" if value else "false"
140
+ if isinstance(value, int):
141
+ return str(value)
142
+ if isinstance(value, float):
143
+ return "" if not math.isfinite(value) else format_float(value)
144
+ raise ReaderError(
145
+ "READER_OUTPUT",
146
+ subject,
147
+ f"no pinned rendering exists for a {type(value).__name__} cell",
148
+ )
149
+
150
+
151
+ def _validate_columns(
152
+ columns: Sequence[str], budgets: ReaderBudgets, subject: str
153
+ ) -> tuple[str, ...]:
154
+ if isinstance(columns, str) or not isinstance(columns, Sequence) or not columns:
155
+ raise ReaderError("READER_OUTPUT", subject, "must be a nonempty sequence of column names")
156
+ if len(columns) > budgets.max_columns:
157
+ raise ReaderError("READER_BUDGET", subject, "exceeds the column budget")
158
+ if any(not isinstance(name, str) or _SAFE_COLUMN.fullmatch(name) is None for name in columns):
159
+ raise ReaderError(
160
+ "READER_OUTPUT",
161
+ subject,
162
+ "column names must be bounded text without control characters",
163
+ )
164
+ if len(set(columns)) != len(columns):
165
+ raise ReaderError("READER_OUTPUT", subject, "column names must be unique")
166
+ return tuple(columns)
167
+
168
+
169
+ def check_declared_size(
170
+ *,
171
+ row_count: int,
172
+ column_count: int,
173
+ budgets: ReaderBudgets,
174
+ subject: str,
175
+ ) -> None:
176
+ """Refuse a declared geometry that is over budget, before anything is decoded.
177
+
178
+ A spreadsheet states its used range and a weather-model message states its grid, so the
179
+ size of the result is knowable before the first cell is read. A family calls this with
180
+ the declared numbers in hand, which is what turns a declared-size bomb into a refusal
181
+ instead of an allocation.
182
+ """
183
+
184
+ for name, value in (("row_count", row_count), ("column_count", column_count)):
185
+ if type(value) is not int or value < 0:
186
+ raise ReaderError(
187
+ "READER_BUDGET",
188
+ f"{subject}.{name}",
189
+ "a declared size must be a non-negative integer",
190
+ )
191
+ if column_count > budgets.max_columns:
192
+ raise ReaderError("READER_BUDGET", subject, "the declared column count is over budget")
193
+ if row_count > budgets.max_rows:
194
+ raise ReaderError("READER_BUDGET", subject, "the declared row count is over budget")
195
+ if row_count * column_count > budgets.max_declared_cells:
196
+ raise ReaderError("READER_BUDGET", subject, "the declared cell count is over budget")
197
+
198
+
199
+ def encode_canonical_csv(
200
+ columns: Sequence[str],
201
+ rows: Iterable[Sequence[Any] | Mapping[str, Any]],
202
+ *,
203
+ budgets: ReaderBudgets | None = None,
204
+ ) -> bytes:
205
+ """Encode a table as the canonical sealed bytes: utf-8, ``\\n``, no byte-order mark.
206
+
207
+ Column order is exactly the stated order and never inferred from a row. A row may be a
208
+ sequence matching that order exactly, or a mapping keyed by column name -- a mapping
209
+ naming a column the header does not state is refused rather than dropped, and a column
210
+ the mapping omits is the empty cell.
211
+
212
+ Budgets are enforced while reading the rows, so an over-budget input is refused before
213
+ it is materialised rather than after.
214
+ """
215
+
216
+ limits = budgets if budgets is not None else ReaderBudgets()
217
+ header = _validate_columns(columns, limits, "reader.output.columns")
218
+ # One scratch buffer holds one encoded line at a time, and the table accumulates as
219
+ # utf-8 bytes. The whole output therefore exists once, not once as text and once as
220
+ # bytes -- a table near the output budget would otherwise cost twice its own size at
221
+ # the encode boundary. Encoding line by line is byte-identical to encoding the joined
222
+ # text, because utf-8 carries no state between code points.
223
+ scratch = io.StringIO(newline="")
224
+ writer = csv.DictWriter(
225
+ scratch,
226
+ fieldnames=list(header),
227
+ lineterminator="\n",
228
+ extrasaction="raise",
229
+ restval="",
230
+ quotechar=_QUOTE_CHARACTER,
231
+ doublequote=True,
232
+ )
233
+ encoded = bytearray()
234
+ encoded_size = 0
235
+ encode_error: UnicodeEncodeError | None = None
236
+
237
+ def flush_scratch() -> None:
238
+ # The output-byte budget stays an end-of-input refusal, exactly where it has always
239
+ # been: rows keep streaming so an admission refusal later in the input still wins,
240
+ # but the buffer stops growing, so an over-budget input cannot cost its own size in
241
+ # memory before it is refused. An unencodable cell -- a lone surrogate -- is held
242
+ # to the same order: the joined-text form could not crash on it until the loop had
243
+ # finished, so the first such error is recorded here and raised after the loop,
244
+ # before the budget comparison, exactly where the one-shot encode used to raise.
245
+ nonlocal encoded_size, encode_error
246
+ piece_text = scratch.getvalue()
247
+ scratch.seek(0)
248
+ scratch.truncate(0)
249
+ if encode_error is not None:
250
+ return
251
+ try:
252
+ piece = piece_text.encode("utf-8")
253
+ except UnicodeEncodeError as error:
254
+ encode_error = error
255
+ return
256
+ encoded_size += len(piece)
257
+ if encoded_size <= limits.max_output_bytes:
258
+ encoded.extend(piece)
259
+
260
+ writer.writeheader()
261
+ flush_scratch()
262
+
263
+ width = len(header)
264
+ written = 0
265
+ for row in rows:
266
+ if written >= limits.max_rows:
267
+ raise ReaderError("READER_BUDGET", "reader.output.rows", "exceeds the row budget")
268
+ if (written + 1) * width > limits.max_declared_cells:
269
+ raise ReaderError("READER_BUDGET", "reader.output.rows", "exceeds the cell budget")
270
+ subject = f"reader.output.rows[{written}]"
271
+ if isinstance(row, Mapping):
272
+ rendered = {key: _render_cell(value, subject) for key, value in row.items()}
273
+ elif isinstance(row, Sequence) and not isinstance(row, (str, bytes, bytearray)):
274
+ if len(row) != width:
275
+ raise ReaderError("READER_OUTPUT", subject, "row width differs from the header")
276
+ rendered = {
277
+ name: _render_cell(value, subject) for name, value in zip(header, row, strict=True)
278
+ }
279
+ else:
280
+ raise ReaderError("READER_OUTPUT", subject, "a row must be a sequence or a mapping")
281
+ try:
282
+ writer.writerow(rendered)
283
+ except ValueError as error:
284
+ raise ReaderError(
285
+ "READER_OUTPUT",
286
+ subject,
287
+ "row names a column the header does not state",
288
+ ) from error
289
+ flush_scratch()
290
+ written += 1
291
+
292
+ if encode_error is not None:
293
+ raise encode_error
294
+ if encoded_size > limits.max_output_bytes:
295
+ raise ReaderError(
296
+ "READER_BUDGET",
297
+ "reader.output.content",
298
+ "exceeds the output byte budget",
299
+ )
300
+ return bytes(encoded)
301
+
302
+
303
+ def decode_delimited(
304
+ content: bytes,
305
+ *,
306
+ delimiter: str,
307
+ header: bool = True,
308
+ budgets: ReaderBudgets | None = None,
309
+ ) -> tuple[tuple[str, ...], tuple[tuple[str, ...], ...]]:
310
+ """Decode delimited text into a header and its rows, refusing what the parser refuses.
311
+
312
+ This is ``decode_delimited_stream`` drained into tuples, for the callers whose input is
313
+ small and whose next step needs the whole table. The admissions, budgets, and refusal
314
+ order are the stream's own -- there is one delimited decoder, not a strict one and a
315
+ lenient one.
316
+ """
317
+
318
+ columns, rows = decode_delimited_stream(
319
+ content, delimiter=delimiter, header=header, budgets=budgets
320
+ )
321
+ return columns, tuple(rows)
322
+
323
+
324
+ def decode_delimited_stream(
325
+ content: bytes,
326
+ *,
327
+ delimiter: str,
328
+ header: bool = True,
329
+ budgets: ReaderBudgets | None = None,
330
+ ) -> tuple[tuple[str, ...], Iterator[tuple[str, ...]]]:
331
+ """Decode delimited text into a header and an iterator of admitted rows.
332
+
333
+ The refusals are the ones the acquisition parser already makes, for the same reasons: a
334
+ NUL byte, a repeated column name, and a row whose width differs from its header are all
335
+ refused rather than repaired. Text that is not strict UTF-8 is refused too; one leading
336
+ byte-order mark is removed first, because a mark is an encoding artefact of the file and
337
+ not the first character of the first column's name.
338
+
339
+ When the recipe says there is no header, columns are named by position rather than
340
+ guessed from the first row, so the first row stays data.
341
+
342
+ The header and every prologue refusal are settled before this function returns; each row
343
+ is admitted as it is pulled. Streaming is what keeps a year-scale table from existing
344
+ as millions of live tuples at once: the caller that feeds rows straight into the
345
+ canonical encoder holds one row at a time, and the budgets refuse an over-budget input
346
+ at the same row they always have.
347
+
348
+ Which tokenizer splits the rows is chosen by the input rather than by an option.
349
+ ``fast_delimited`` states a closed subset of inputs for which splitting on the line
350
+ terminator and then on the delimiter is the same tokenizing the stdlib parser performs,
351
+ and inside that subset it does the splitting without decoding the whole input into one
352
+ string first. Everything outside the subset -- a quote, a carriage return, a NUL byte,
353
+ a blank line, text that is not strict UTF-8 -- reaches the stdlib parser below and is
354
+ admitted or refused by it exactly as it always was. The one refusal that belongs to the
355
+ tokenizing rather than to this function, a field longer than ``csv.field_size_limit()``,
356
+ the splitter raises itself as the same ``csv.Error``, so it arrives here as the malformed
357
+ refusal it has always arrived as.
358
+ """
359
+
360
+ limits = budgets if budgets is not None else ReaderBudgets()
361
+ if (
362
+ not isinstance(delimiter, str)
363
+ or len(delimiter) != 1
364
+ or delimiter == _QUOTE_CHARACTER
365
+ or delimiter in "\r\n"
366
+ ):
367
+ raise ReaderError(
368
+ "READER_OPTIONS",
369
+ "reader.decode.delimiter",
370
+ "must be exactly one character that is neither the quote nor a line terminator",
371
+ )
372
+ if not isinstance(content, (bytes, bytearray)):
373
+ raise ReaderError("READER_ADMISSION", "reader.decode.content", "must be exact bytes")
374
+ # Checked before decoding, so an over-budget input is refused without being materialised
375
+ # as text first.
376
+ if len(content) > limits.max_input_bytes:
377
+ raise ReaderError(
378
+ "READER_BUDGET",
379
+ "reader.decode.content",
380
+ "exceeds the input byte budget",
381
+ )
382
+ raw = bytes(content).removeprefix(b"\xef\xbb\xbf")
383
+ # The certified splitter is the same tokenizing for the inputs it admits, so which one ran
384
+ # is not observable in a row, a refusal, or a sealed byte -- only in what the decode cost.
385
+ # An input it declines never reaches it: the stdlib reader below runs from the first byte,
386
+ # including for text that is not strict UTF-8, which the subset excludes rather than
387
+ # refuses so that the refusal keeps its one home.
388
+ certified = certifies(raw, delimiter=delimiter)
389
+ reader: Iterator[list[str]]
390
+ if certified:
391
+ reader = certified_rows(raw, delimiter=delimiter)
392
+ else:
393
+ try:
394
+ text = raw.decode("utf-8", errors="strict")
395
+ except UnicodeDecodeError:
396
+ raise ReaderError(
397
+ "READER_ADMISSION",
398
+ "reader.decode.content",
399
+ "source text must be strict UTF-8",
400
+ ) from None
401
+
402
+ reader = csv.reader(
403
+ io.StringIO(text, newline=""),
404
+ delimiter=delimiter,
405
+ quotechar=_QUOTE_CHARACTER,
406
+ doublequote=True,
407
+ strict=True,
408
+ )
409
+ pending: list[list[str]] = []
410
+ try:
411
+ if header:
412
+ first = next(reader, None)
413
+ if first is None:
414
+ raise ReaderError(
415
+ "READER_ADMISSION",
416
+ "reader.decode.header",
417
+ "delimited text has no header row",
418
+ )
419
+ columns = _decoded_columns(first, limits)
420
+ else:
421
+ first = next(reader, None)
422
+ if first is None:
423
+ raise ReaderError(
424
+ "READER_ADMISSION",
425
+ "reader.decode.content",
426
+ "delimited text has no rows",
427
+ )
428
+ positional = [f"column_{index + 1}" for index in range(len(first))]
429
+ columns = _decoded_columns(positional, limits)
430
+ pending.append(first)
431
+ except csv.Error:
432
+ raise ReaderError(
433
+ "READER_ADMISSION",
434
+ "reader.decode.content",
435
+ "delimited text is malformed",
436
+ ) from None
437
+ return columns, _admitted_rows(
438
+ reader, pending, len(columns), limits, scan_for_nul=not certified
439
+ )
440
+
441
+
442
+ def _admitted_rows(
443
+ reader: Iterator[list[str]],
444
+ pending: list[list[str]],
445
+ width: int,
446
+ limits: ReaderBudgets,
447
+ *,
448
+ scan_for_nul: bool = True,
449
+ ) -> Iterator[tuple[str, ...]]:
450
+ """Yield each admitted row, refusing exactly what the drained decoder refused.
451
+
452
+ ``scan_for_nul`` is not a policy switch and never widens what is admitted: it is off only
453
+ when the whole input has already been searched for a NUL byte and found not to carry one,
454
+ which is one of the conditions the certified subset is defined by. A cell of an input
455
+ with no NUL byte in it cannot contain a NUL byte, so the scan below would spend a pass
456
+ over every cell to reach the answer that pass already has.
457
+ """
458
+
459
+ admitted = 0
460
+ try:
461
+ # Chained rather than unpacked: unpacking would read the whole input into a list
462
+ # before the first budget check, which is the allocation the budgets exist to
463
+ # prevent.
464
+ for raw_row in itertools.chain(pending, reader):
465
+ if admitted >= limits.max_rows:
466
+ raise ReaderError("READER_BUDGET", "reader.decode.rows", "exceeds the row budget")
467
+ if (admitted + 1) * width > limits.max_declared_cells:
468
+ raise ReaderError("READER_BUDGET", "reader.decode.rows", "exceeds the cell budget")
469
+ if len(raw_row) != width:
470
+ raise ReaderError(
471
+ "READER_ADMISSION",
472
+ f"reader.decode.rows[{admitted}]",
473
+ "row width differs from its header",
474
+ )
475
+ if scan_for_nul:
476
+ for cell in raw_row:
477
+ if "\x00" in cell:
478
+ raise ReaderError(
479
+ "READER_ADMISSION",
480
+ f"reader.decode.rows[{admitted}]",
481
+ "delimited text contains a NUL byte",
482
+ )
483
+ yield tuple(raw_row)
484
+ admitted += 1
485
+ except csv.Error:
486
+ raise ReaderError(
487
+ "READER_ADMISSION",
488
+ "reader.decode.content",
489
+ "delimited text is malformed",
490
+ ) from None
491
+
492
+
493
+ def _decoded_columns(values: Sequence[str], budgets: ReaderBudgets) -> tuple[str, ...]:
494
+ if not values:
495
+ raise ReaderError(
496
+ "READER_ADMISSION",
497
+ "reader.decode.header",
498
+ "table must have a nonempty column set",
499
+ )
500
+ if len(values) > budgets.max_columns:
501
+ raise ReaderError("READER_BUDGET", "reader.decode.header", "exceeds the column budget")
502
+ if any(_SAFE_COLUMN.fullmatch(name) is None for name in values):
503
+ raise ReaderError(
504
+ "READER_ADMISSION",
505
+ "reader.decode.header",
506
+ "column names must be bounded text without control characters",
507
+ )
508
+ if len(set(values)) != len(values):
509
+ raise ReaderError("READER_ADMISSION", "reader.decode.header", "column names must be unique")
510
+ return tuple(values)