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,444 @@
1
+ """Comparing two builds: what is different between the one on the left and the one on the right.
2
+
3
+ ``recipe-verify`` can already compare a new version against the version before it. This module
4
+ answers the other question, the one a person actually has when a refresh looks wrong: here are two
5
+ builds, what is different about them?
6
+
7
+ Three rules shape everything below.
8
+
9
+ **Both sides are verified before anything is compared.** Each side goes through
10
+ :func:`recipe.version_summary`, which calls ``pipeline.verify_candidate`` and then reads the
11
+ profile and lineage evidence off the verified snapshot. A comparison of unverified bytes would be
12
+ worth nothing, because the bytes could say anything.
13
+
14
+ **There is one drift vocabulary, not two.** Every change this module reports names the reviewed
15
+ drift code that governs the same condition, in :data:`RELATED_DRIFT_CODES`: a column set that moved
16
+ is ``RECIPE_SCHEMA_DRIFT``, a type that moved is ``RECIPE_TYPE_DRIFT``, a row count that moved is
17
+ ``RECIPE_ROW_COUNT_DRIFT``. Those codes are read from ``recipe._drift_report`` rather than invented
18
+ here. Two conditions have no reviewed code, because a comparison of two arbitrary builds can meet
19
+ cases a successor-against-predecessor check never does, and both are additions made here:
20
+
21
+ * ``data_changed`` -- the shape is untouched and the bytes are not. The drift report has no such
22
+ code: it recomputes a version against its own predecessor under a recipe, where a change of data
23
+ with no change of shape is the ordinary case rather than a finding.
24
+ * ``sources_changed`` -- even the data fingerprint matches, and only the recorded lineage differs.
25
+ The drift report folds lineage into ``EXACT_CANDIDATE_REPLAY`` for one build; it never compares
26
+ the lineage of two.
27
+
28
+ Naming the reviewed code is a pointer, not a claim: it says which alert would look at this
29
+ condition when a Recipe and its refresh bounds are in play. This module raises no alert.
30
+
31
+ **A comparison describes; it never judges.** No severity, no eligibility, no opinion about whether
32
+ a difference is acceptable -- ``recipe-verify`` owns that judgement and a read-only command that
33
+ implied it would be putting a release opinion where nobody asked for one. That is also why the
34
+ ``DriftFinding`` shape is borrowed here without its ``severity`` field.
35
+
36
+ The change list is the shortest true explanation rather than a restatement of every field: a
37
+ removed column already explains the data fingerprint that moved with it, so ``data_changed`` is
38
+ reported only when the shape is identical, and ``sources_changed`` only when even the data matches.
39
+ Every fingerprint on both sides is carried in the payload regardless, so nothing is hidden.
40
+
41
+ Nothing here prints. Every function returns facts or text, so the same comparison can be rendered
42
+ into a browser page later without re-deriving it.
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ from dataclasses import dataclass
48
+ from pathlib import Path
49
+ from typing import Any
50
+
51
+ from mostlyright.data_harness.pipeline import verify_candidate
52
+ from mostlyright.data_harness.recipe import VersionSummary, version_summary
53
+ from mostlyright.data_harness.ux.readers import require_a_build
54
+
55
+ # Every change this module can report, in the order a reader meets them: what the columns did,
56
+ # then what the rows did, then what the bytes did.
57
+ CHANGE_CODES: tuple[str, ...] = (
58
+ "column_added",
59
+ "column_removed",
60
+ "column_type_changed",
61
+ "row_count_changed",
62
+ "data_changed",
63
+ "sources_changed",
64
+ )
65
+
66
+ # The reviewed drift code that governs the same condition, or ``None`` where the drift review has
67
+ # no equivalent. Both ``None`` entries are argued for in the module docstring above.
68
+ RELATED_DRIFT_CODES: dict[str, str | None] = {
69
+ "column_added": "RECIPE_SCHEMA_DRIFT",
70
+ "column_removed": "RECIPE_SCHEMA_DRIFT",
71
+ "column_type_changed": "RECIPE_TYPE_DRIFT",
72
+ "row_count_changed": "RECIPE_ROW_COUNT_DRIFT",
73
+ "data_changed": None,
74
+ "sources_changed": None,
75
+ }
76
+
77
+ # The changes that are about the columns themselves, for `--columns-only`.
78
+ COLUMN_CHANGE_CODES: frozenset[str] = frozenset(
79
+ {"column_added", "column_removed", "column_type_changed"}
80
+ )
81
+
82
+ _STATUS_BY_RESULT = {"identical": "builds_identical", "differs": "builds_differ"}
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class BuildChange:
87
+ """One difference between two builds, named at the granularity a person can act on.
88
+
89
+ A user chasing a broken refresh needs the column's name, not the sentence "the schema
90
+ changed", so every column-level difference is its own change carrying its own name.
91
+ """
92
+
93
+ code: str
94
+ summary: str
95
+ column: str | None = None
96
+ was: Any = None
97
+ now: Any = None
98
+ difference: str | None = None
99
+
100
+ def __post_init__(self) -> None:
101
+ if self.code not in RELATED_DRIFT_CODES:
102
+ raise ValueError(f"unknown change code: {self.code!r}")
103
+
104
+ @property
105
+ def related_drift_code(self) -> str | None:
106
+ return RELATED_DRIFT_CODES[self.code]
107
+
108
+ @property
109
+ def is_column_change(self) -> bool:
110
+ return self.code in COLUMN_CHANGE_CODES
111
+
112
+ def to_dict(self) -> dict[str, Any]:
113
+ """The one record both renderings are built from.
114
+
115
+ The column name is a value and never a key (29-04's rule): a column called ``max_temp_c``
116
+ used as a key would be shown to a person as ``Max temp c``, a name its data does not have.
117
+ """
118
+
119
+ payload: dict[str, Any] = {"what": self.code}
120
+ if self.column is not None:
121
+ payload["column"] = self.column
122
+ if self.was is not None:
123
+ # `from` and `to` rather than the field names: read in order, a person meets them as
124
+ # "from this, to that", and that is also the order the plain rendering sorts them in.
125
+ payload["from"] = self.was
126
+ if self.now is not None:
127
+ payload["to"] = self.now
128
+ if self.difference is not None:
129
+ payload["difference"] = self.difference
130
+ payload["related drift alert"] = self.related_drift_code
131
+ return payload
132
+
133
+
134
+ @dataclass(frozen=True)
135
+ class ComparedBuild:
136
+ """One side of a comparison: what it is, who made it, and the verified shape of its data."""
137
+
138
+ run_dir: Path
139
+ manifest_version: str
140
+ candidate_digest: str
141
+ producer_principal: str
142
+ summary: VersionSummary
143
+
144
+ @property
145
+ def table_sha256(self) -> str:
146
+ return self.summary.table_sha256
147
+
148
+ @property
149
+ def row_count(self) -> int:
150
+ return self.summary.row_count
151
+
152
+ @property
153
+ def columns(self) -> tuple[str, ...]:
154
+ return self.summary.columns
155
+
156
+ @property
157
+ def column_types(self) -> dict[str, str]:
158
+ return dict(self.summary.column_types)
159
+
160
+ @property
161
+ def lineage_digest(self) -> str:
162
+ return self.summary.lineage_digest
163
+
164
+ def to_dict(self) -> dict[str, Any]:
165
+ return {
166
+ "run_dir": str(self.run_dir),
167
+ "manifest_version": self.manifest_version,
168
+ "candidate_digest": self.candidate_digest,
169
+ "table_sha256": self.table_sha256,
170
+ "lineage_digest": self.lineage_digest,
171
+ "built_by": self.producer_principal,
172
+ "row_count": self.row_count,
173
+ "columns": list(self.columns),
174
+ }
175
+
176
+
177
+ @dataclass(frozen=True)
178
+ class BuildComparison:
179
+ """Two verified builds and every difference between them."""
180
+
181
+ left: ComparedBuild
182
+ right: ComparedBuild
183
+ changes: tuple[BuildChange, ...]
184
+
185
+ @property
186
+ def status(self) -> str:
187
+ return "identical" if not self.changes else "differs"
188
+
189
+ @property
190
+ def same_build(self) -> bool:
191
+ """Whether both sides are the one build.
192
+
193
+ Kept apart from :attr:`status` on purpose. Two builds can hold the same data in the same
194
+ shape and still be two builds -- made by different people, under different Receipt
195
+ versions -- and answering "is the data the same?" with "these are the same build" would be
196
+ a different, false statement.
197
+ """
198
+
199
+ return self.left.candidate_digest == self.right.candidate_digest
200
+
201
+ @property
202
+ def column_changes(self) -> tuple[BuildChange, ...]:
203
+ return tuple(change for change in self.changes if change.is_column_change)
204
+
205
+ def to_dict(self, *, columns_only: bool = False) -> dict[str, Any]:
206
+ shown = self.column_changes if columns_only else self.changes
207
+ hidden = len(self.changes) - len(shown)
208
+ payload: dict[str, Any] = {
209
+ "status": _STATUS_BY_RESULT[self.status],
210
+ "summary": plain_comparison(self, columns_only=columns_only),
211
+ # The full count, always. A narrowed view narrows what is listed, never what is true.
212
+ "change count": len(self.changes),
213
+ "changes": _numbered(shown),
214
+ "first build": self.left.to_dict(),
215
+ "second build": self.right.to_dict(),
216
+ }
217
+ if hidden:
218
+ # A caller reading the record rather than the sentences still has to be able to tell
219
+ # that the list in front of it is not the whole list. The prose caveat is inside
220
+ # `summary`; this is the same fact in one field, so nobody has to parse prose for it.
221
+ payload["columns only"] = True
222
+ return payload
223
+
224
+
225
+ def compare_builds(left: Path | str, right: Path | str) -> BuildComparison:
226
+ """Compare two sealed builds, verifying both before comparing either.
227
+
228
+ ``left`` is read as the earlier of the two and ``right`` as the later, so "added" means it is
229
+ in the second and not the first. Neither has to be the other's successor, and the two may
230
+ carry different Receipt versions.
231
+ """
232
+
233
+ first = _side(left)
234
+ second = _side(right)
235
+ return BuildComparison(
236
+ left=first, right=second, changes=_changes(first.summary, second.summary)
237
+ )
238
+
239
+
240
+ def plain_comparison(comparison: BuildComparison, *, columns_only: bool = False) -> str:
241
+ """The plain-language answer, written from the same changes the record carries.
242
+
243
+ A comparison with nothing to report still says a sentence. An empty screen is not an answer.
244
+ """
245
+
246
+ if not comparison.changes:
247
+ return "\n".join(_nothing_changed_lines(comparison))
248
+ shown = comparison.column_changes if columns_only else comparison.changes
249
+ hidden = len(comparison.changes) - len(shown)
250
+ lines = [
251
+ f"Going from the first build to the second, {_things(len(comparison.changes))} changed:"
252
+ ]
253
+ if shown:
254
+ lines.extend(f" {change.summary}" for change in shown)
255
+ else:
256
+ lines.append(" no column changed.")
257
+ if hidden:
258
+ lines.append(f" {_left_out_sentence(hidden)}")
259
+ return "\n".join(lines)
260
+
261
+
262
+ # ------------------------------------------------------------------------------------------------
263
+ # Reading one side
264
+ # ------------------------------------------------------------------------------------------------
265
+
266
+
267
+ def _side(run_dir: Path | str) -> ComparedBuild:
268
+ """Verify one side and read back everything the comparison needs from it.
269
+
270
+ ``version_summary`` verifies the sealed tree and returns the shape of the data; it does not
271
+ carry the Receipt version or who made the build, and both belong in the answer, so
272
+ ``verify_candidate`` is called for those. A folder with no build in it is named as such first,
273
+ because the verified reader's refusal for a missing tree reads like a break-in rather than the
274
+ typo it usually is.
275
+ """
276
+
277
+ target = Path(run_dir)
278
+ require_a_build(target)
279
+ verified = verify_candidate(target)
280
+ return ComparedBuild(
281
+ run_dir=target,
282
+ manifest_version=verified.manifest_version,
283
+ candidate_digest=verified.candidate_digest,
284
+ producer_principal=verified.producer_principal,
285
+ summary=version_summary(target),
286
+ )
287
+
288
+
289
+ # ------------------------------------------------------------------------------------------------
290
+ # The comparison itself
291
+ # ------------------------------------------------------------------------------------------------
292
+
293
+
294
+ def _changes(before: VersionSummary, after: VersionSummary) -> tuple[BuildChange, ...]:
295
+ """Every difference between two verified summaries, most specific explanation first.
296
+
297
+ The shape is compared field by field over ``to_drift_dict``. Only when nothing about the shape
298
+ moved does the data fingerprint get its own change, and only when even that matches does the
299
+ recorded lineage get one: a removed column already accounts for the fingerprint that moved with
300
+ it, and saying so twice would read as two problems where there is one.
301
+ """
302
+
303
+ changes = [
304
+ *_column_changes(before, after),
305
+ *_type_changes(before, after),
306
+ *_row_count_changes(before, after),
307
+ ]
308
+ if changes:
309
+ return tuple(changes)
310
+ if before.table_sha256 != after.table_sha256:
311
+ return (
312
+ BuildChange(
313
+ code="data_changed",
314
+ summary=(
315
+ "the data changed but its shape did not: the same columns, the same types "
316
+ "and the same number of rows, over different data"
317
+ ),
318
+ was=before.table_sha256,
319
+ now=after.table_sha256,
320
+ ),
321
+ )
322
+ if before.lineage_digest != after.lineage_digest:
323
+ return (
324
+ BuildChange(
325
+ code="sources_changed",
326
+ summary=("the data is the same, but where it came from is recorded differently"),
327
+ was=before.lineage_digest,
328
+ now=after.lineage_digest,
329
+ ),
330
+ )
331
+ return ()
332
+
333
+
334
+ def _column_changes(before: VersionSummary, after: VersionSummary) -> list[BuildChange]:
335
+ gone = sorted(set(before.columns) - set(after.columns))
336
+ arrived = sorted(set(after.columns) - set(before.columns))
337
+ return [
338
+ *(
339
+ BuildChange(
340
+ code="column_removed",
341
+ summary=f"the column {name} was removed",
342
+ column=name,
343
+ was=dict(before.column_types).get(name),
344
+ )
345
+ for name in gone
346
+ ),
347
+ *(
348
+ BuildChange(
349
+ code="column_added",
350
+ summary=f"the column {name} was added",
351
+ column=name,
352
+ now=dict(after.column_types).get(name),
353
+ )
354
+ for name in arrived
355
+ ),
356
+ ]
357
+
358
+
359
+ def _type_changes(before: VersionSummary, after: VersionSummary) -> list[BuildChange]:
360
+ old = dict(before.column_types)
361
+ new = dict(after.column_types)
362
+ return [
363
+ BuildChange(
364
+ code="column_type_changed",
365
+ summary=f"the column {name} changed type from {old[name]} to {new[name]}",
366
+ column=name,
367
+ was=old[name],
368
+ now=new[name],
369
+ )
370
+ for name in sorted(set(old) & set(new))
371
+ if old[name] != new[name]
372
+ ]
373
+
374
+
375
+ def _row_count_changes(before: VersionSummary, after: VersionSummary) -> list[BuildChange]:
376
+ if before.row_count == after.row_count:
377
+ return []
378
+ difference = _signed(after.row_count - before.row_count)
379
+ return [
380
+ BuildChange(
381
+ code="row_count_changed",
382
+ summary=(
383
+ f"the row count went from {before.row_count} to {after.row_count} ({difference})"
384
+ ),
385
+ was=before.row_count,
386
+ now=after.row_count,
387
+ difference=difference,
388
+ )
389
+ ]
390
+
391
+
392
+ # ------------------------------------------------------------------------------------------------
393
+ # Saying it plainly
394
+ # ------------------------------------------------------------------------------------------------
395
+
396
+
397
+ def _nothing_changed_lines(comparison: BuildComparison) -> list[str]:
398
+ lines = ["Nothing about the data or its shape is different."]
399
+ if comparison.same_build:
400
+ lines.append("Both sides are the same build: one build fingerprint, read twice.")
401
+ return lines
402
+ lines.append("These are still two builds, not one: their build fingerprints differ.")
403
+ if comparison.left.manifest_version != comparison.right.manifest_version:
404
+ lines.append(
405
+ f"They were written under different Receipt versions: "
406
+ f"{comparison.left.manifest_version} and {comparison.right.manifest_version}."
407
+ )
408
+ return lines
409
+
410
+
411
+ def _left_out_sentence(hidden: int) -> str:
412
+ thing = "change is" if hidden == 1 else "changes are"
413
+ return (
414
+ f"only column changes were asked for, so {hidden} other {thing} not shown here; "
415
+ "run this again without --columns-only to see everything"
416
+ )
417
+
418
+
419
+ def _things(count: int) -> str:
420
+ return "1 thing" if count == 1 else f"{count} things"
421
+
422
+
423
+ def _signed(delta: int) -> str:
424
+ return f"+{delta}" if delta > 0 else str(delta)
425
+
426
+
427
+ def _numbered(changes: tuple[BuildChange, ...]) -> dict[str, Any]:
428
+ width = len(str(max(len(changes), 1)))
429
+ return {
430
+ f"change {index:0{width}d}": change.to_dict()
431
+ for index, change in enumerate(changes, start=1)
432
+ }
433
+
434
+
435
+ __all__ = [
436
+ "CHANGE_CODES",
437
+ "COLUMN_CHANGE_CODES",
438
+ "RELATED_DRIFT_CODES",
439
+ "BuildChange",
440
+ "BuildComparison",
441
+ "ComparedBuild",
442
+ "compare_builds",
443
+ "plain_comparison",
444
+ ]