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,178 @@
1
+ """One rule for reading a file somebody named on the command line, in one place.
2
+
3
+ Every command that takes a path from a person has the same three questions to answer before it
4
+ reads a byte: is there anything there, is what is there a plain file, and is it small enough. The
5
+ obvious way to answer them -- ``path.stat()``, then ``path.is_file()``, then ``path.read_bytes()``
6
+ -- gets all three wrong at once. ``st_size`` is 0 for a FIFO, a pipe and a character device, so a
7
+ cap taken off the path governs nothing; the stat follows links, so a link to ``/dev/zero`` reads
8
+ the same way; and the file the size was measured on is not the file the read then opens, so a
9
+ declared bound is a bound on a moment that has passed.
10
+
11
+ So the rule here is the open-then-fstat rule the sealed tree opens everything with: open once, ask
12
+ the descriptor what it is, and read that same descriptor under a bound. ``O_NONBLOCK`` is part of
13
+ the rule rather than a flourish -- opening a FIFO for reading blocks until somebody opens the other
14
+ end, so without it the refusal below could never be reached and the command would hang instead of
15
+ answering. On a plain file the flag does nothing at all.
16
+
17
+ **Why the refusal is untyped here.** :class:`PlainFileRefusal` carries a reason and the path and no
18
+ wording, because the wording belongs to the command the person ran: ``author`` says a Recipe is
19
+ written in a file, ``peek`` says there is nothing there to look at, and neither sentence would read
20
+ right on the other surface. What the callers share is the rule, and sharing the rule is the whole
21
+ point: one implementation keeps every command on the same file-type and byte-boundary contract.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import os
27
+ import stat
28
+ from pathlib import Path
29
+
30
+ # Why a named file was not read. Four reasons, so a caller answers four sentences rather than
31
+ # branching on an errno.
32
+ MISSING = "missing"
33
+ NOT_PLAIN = "not a plain file"
34
+ TOO_LARGE = "too large"
35
+
36
+ # ``os.open`` on a link that leads nowhere raises ``FileNotFoundError``, but the named entry still
37
+ # exists and needs link-specific repair. It is a distinct *reason* rather than a shade of missing
38
+ # because absence and a dangling link name different fixes.
39
+ DANGLING = "a link that leads nowhere"
40
+
41
+ # The non-blocking open is the FIFO part of the rule, and it is a POSIX part: Windows has no
42
+ # ``O_NONBLOCK`` and no FIFO a plain ``open`` could hang on, so there the flag is simply absent.
43
+ # Looked up once here rather than at the call, so the thin client (ADR 0021) answers ``whoami``
44
+ # on Windows instead of failing before it reads a byte -- the any-OS smoke matrix found this.
45
+ _NONBLOCKING = getattr(os, "O_NONBLOCK", 0)
46
+
47
+ # ⚠ THE OTHER HALF OF THAT SENTENCE, AND THE HALF THE MATRIX CANNOT SEE. ``O_BINARY`` exists only
48
+ # on Windows, where ``os.open`` otherwise leaves the C runtime free to translate CRLF on the way
49
+ # through -- so the bytes this rule returned would not be the bytes on disk. Every caller reads
50
+ # something whose EXACT bytes are the point: a canonical-JSON credential that is parsed back, a
51
+ # Recipe that is about to be digested. A silently rewritten byte is not a crash the smoke job
52
+ # could catch; it is a digest that no longer matches, on one operating system only.
53
+ _BINARY = getattr(os, "O_BINARY", 0)
54
+
55
+
56
+ class PlainFileRefusal(Exception):
57
+ """A named file this rule will not read, with the reason and the path but no wording.
58
+
59
+ Every caller translates this into its own vocabulary and its own error class. Nothing catches
60
+ it at the command line, so a caller that forgets to translate fails loudly in its own tests
61
+ rather than reaching a person as an untyped traceback.
62
+
63
+ ``mode`` is the ``st_mode`` the descriptor reported, for :data:`NOT_PLAIN`; the ``st_mode`` the
64
+ one ``lstat`` reported, for :data:`DANGLING`; and ``None`` for the other two reasons. It is not
65
+ a reason of its own and it is not a case added to the rule: it is the fact this rule already
66
+ looked at to decide, handed back rather than thrown away, so a caller whose vocabulary
67
+ distinguishes a folder from a pipe can say which it found. A caller whose vocabulary does not
68
+ -- ``peek`` answers all four with one sentence -- ignores it.
69
+ """
70
+
71
+ def __init__(self, reason: str, path: Path, *, mode: int | None = None) -> None:
72
+ self.reason = reason
73
+ self.path = path
74
+ self.mode = mode
75
+ super().__init__(f"{path}: {reason}")
76
+
77
+
78
+ def open_plain_file(path: Path | str) -> tuple[int, int]:
79
+ """A descriptor on a plain file and the size that descriptor reports.
80
+
81
+ The caller owns the descriptor and must close it. Only :data:`MISSING`, :data:`DANGLING` and
82
+ :data:`NOT_PLAIN` are answered here; every other ``OSError`` -- a permission refusal, a
83
+ component of the path that is not a folder -- is left to travel, because those are the command
84
+ line's own boundary to answer and inventing a sentence for them here would be a second answer
85
+ to a question that already has one.
86
+ """
87
+
88
+ target = Path(path)
89
+ try:
90
+ descriptor = os.open(target, os.O_RDONLY | _NONBLOCKING | _BINARY)
91
+ except FileNotFoundError:
92
+ # One `lstat` before the answer, because `ENOENT` is two findings and only one of them is
93
+ # "nothing is there". The open resolved the name and found nothing at the end of it; this
94
+ # asks about the name itself, without following it, and an entry that answers is an entry
95
+ # that exists. Only a look that also fails is `MISSING`.
96
+ try:
97
+ entry = os.lstat(target)
98
+ except (FileNotFoundError, NotADirectoryError):
99
+ raise PlainFileRefusal(MISSING, target) from None
100
+ except OSError as refused_look:
101
+ # The name resolved to nothing and the look itself was refused, so this rule has no
102
+ # finding of its own; the operating system's refusal is the answer, and the command
103
+ # line's boundary already has a sentence for it. `MISSING` here would be a guess.
104
+ raise refused_look from None
105
+ raise PlainFileRefusal(DANGLING, target, mode=entry.st_mode) from None
106
+ except OSError as open_error:
107
+ # Some kernels refuse to open a Unix-domain socket even with ``O_NONBLOCK``. The
108
+ # descriptor-first rule therefore cannot reach ``fstat`` for that path kind. A
109
+ # best-effort fallback classification is safe here because it is used only to refuse a
110
+ # non-regular target; it never licenses a file for reading. If the path vanished, is a
111
+ # regular file, or cannot be inspected, preserve the original system refusal.
112
+ try:
113
+ refused = target.stat()
114
+ except OSError:
115
+ raise open_error from None
116
+ if not stat.S_ISREG(refused.st_mode):
117
+ raise PlainFileRefusal(NOT_PLAIN, target, mode=refused.st_mode) from None
118
+ raise
119
+ try:
120
+ opened = os.fstat(descriptor)
121
+ except BaseException:
122
+ os.close(descriptor)
123
+ raise
124
+ if not stat.S_ISREG(opened.st_mode):
125
+ os.close(descriptor)
126
+ raise PlainFileRefusal(NOT_PLAIN, target, mode=opened.st_mode)
127
+ return descriptor, opened.st_size
128
+
129
+
130
+ def read_bounded(descriptor: int, *, max_bytes: int) -> bytes:
131
+ """Everything the descriptor holds, up to one byte past the cap and never further.
132
+
133
+ The cap governs the read as well as the size check above it. A file that grows between the
134
+ ``fstat`` and the read is the case the size check alone cannot see, and it is the case a
135
+ declared bound exists for.
136
+ """
137
+
138
+ chunks: list[bytes] = []
139
+ remaining = max_bytes + 1
140
+ while remaining > 0:
141
+ chunk = os.read(descriptor, min(remaining, 1 << 16))
142
+ if not chunk:
143
+ break
144
+ chunks.append(chunk)
145
+ remaining -= len(chunk)
146
+ return b"".join(chunks)
147
+
148
+
149
+ def read_plain_file(path: Path | str, *, max_bytes: int) -> bytes:
150
+ """The whole rule in one call: open it, check it, read it under the bound.
151
+
152
+ Both refusals a size can produce are :data:`TOO_LARGE`: the one the descriptor reports before
153
+ the read, and the one the read itself finds when the file grew in between.
154
+ """
155
+
156
+ target = Path(path)
157
+ descriptor, size = open_plain_file(target)
158
+ try:
159
+ if size > max_bytes:
160
+ raise PlainFileRefusal(TOO_LARGE, target)
161
+ raw = read_bounded(descriptor, max_bytes=max_bytes)
162
+ finally:
163
+ os.close(descriptor)
164
+ if len(raw) > max_bytes:
165
+ raise PlainFileRefusal(TOO_LARGE, target)
166
+ return raw
167
+
168
+
169
+ __all__ = [
170
+ "DANGLING",
171
+ "MISSING",
172
+ "NOT_PLAIN",
173
+ "TOO_LARGE",
174
+ "PlainFileRefusal",
175
+ "open_plain_file",
176
+ "read_bounded",
177
+ "read_plain_file",
178
+ ]
@@ -0,0 +1,311 @@
1
+ """Try a plan by building it for real into a folder that is then thrown away.
2
+
3
+ The command exercises the real build path without retaining an immutable output directory. It is
4
+ the safe way to test a plan before choosing the path for a durable Build.
5
+
6
+ **There is one build path, and this is it.** :func:`pipeline.build_candidate` is called unmodified,
7
+ with the plan value exactly as it was handed in. Nothing here re-implements a lighter check, skips a
8
+ gate, or relaxes a limit: a check that is easier to pass than execution is invalid
9
+ you wanted to know. A recording-wrapper test asserts that call happens exactly once, so this
10
+ paragraph is machine-checked rather than believed.
11
+
12
+ **Nobody ran this build, so its fingerprint is nobody's.** A build's own fingerprint binds who ran
13
+ it and under which attempt; those two values are synthesised here (:data:`CHECK_PRINCIPAL`,
14
+ :data:`CHECK_ATTEMPT`) because a dry run has no real runner. The fingerprint that comes out is
15
+ therefore not the one the real build will have, and reporting it would send somebody chasing a
16
+ mismatch that was never a mismatch. :class:`PlanCheck` has no field for it -- absent, not empty --
17
+ and every run says so in plain words. The *data* fingerprint is a different
18
+ matter: it depends on the plan and the sources and nothing else, so it is reported, and a test
19
+ measures it against a real build of the same plan rather than against a written-down constant.
20
+
21
+ **The folder goes away, whatever happens.** The cleanup runs in a ``finally``, so an interrupt takes
22
+ the folder with it. It also has to survive a recorded quirk of this repository: the folder a build
23
+ seals is mode 0555 on purpose, so removing it means restoring owner write permission from the bottom
24
+ up first. A cleanup that fails is reported as a fact rather than raised, because a folder left
25
+ behind is worth knowing about but is not a reason to call a build that worked a failure.
26
+
27
+ **Two things about the folder are checked before anything is built into it.** It must be private to
28
+ its owner, and it must not sit inside the folder the sources are read from -- the second because a
29
+ build refuses to read from the folder it writes to, and meeting that refusal through a path you
30
+ never chose would be a puzzle rather than an answer. Both are refusals that name themselves, and
31
+ both clean up the folder they refused.
32
+
33
+ One more thing, recorded because it costs an afternoon otherwise: the system temporary folder is
34
+ often reached through a link, and sealing refuses an output whose parent chain contains one. The
35
+ folder is therefore resolved to its real path before anything is built into it.
36
+
37
+ Nothing here prints. :meth:`PlanCheck.to_dict` returns the one payload both renderings are built
38
+ from, so the same facts can be shown in a browser later without re-deriving them.
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import os
44
+ import shutil
45
+ import stat
46
+ import tempfile
47
+ from dataclasses import dataclass
48
+ from pathlib import Path
49
+ from typing import Any
50
+
51
+ from mostlyright.data_harness import pipeline
52
+ from mostlyright.data_harness.local_contracts import (
53
+ GraphTablePlan,
54
+ TablePlan,
55
+ parse_fresh_plan_document,
56
+ refuse_inconsistent_units,
57
+ refuse_retired_operations,
58
+ )
59
+ from mostlyright.data_harness.pipeline import REFERENCE_BACKEND_NAME, BuildError
60
+
61
+ # Calling the command's check builder rather than restating its wording keeps one
62
+ # check reading the same in `mr-data plan-check` as it does in `mr-data review --local`. Two
63
+ # spellings of one check are two things a person has to learn.
64
+ from mostlyright.data_harness.ux.local_review import CHECKS_KEY, LocalCheck, _quality_checks
65
+
66
+ # The one status this command reports. A plan that does not build raises instead, so this word is
67
+ # never on a failure.
68
+ PLAN_CHECK_STATUS = "plan_checked"
69
+
70
+ # The synthetic identity every dry run builds under. Marked as a check rather than borrowed from
71
+ # the caller: these two values are the reason the resulting build fingerprint is not the real
72
+ # build's, and a value that came from the operator would blur that distinction.
73
+ CHECK_PRINCIPAL = "plan-check"
74
+ CHECK_ATTEMPT = "throwaway"
75
+
76
+ # The name the folder is made under, so a leftover one can be recognised for what it is.
77
+ TEMP_PREFIX = "mr-data-plan-check-"
78
+
79
+ # What `mkdtemp` promises. Asserted rather than assumed: a whole build lands inside it.
80
+ PRIVATE_MODE = 0o700
81
+
82
+ # The two refusals this module makes on its own. They are spelled out again at the raise sites
83
+ # below, because the gate that proves every code names its fix reads code literals out of the
84
+ # source; a test asserts the raised identifier is the one named here, so the pair cannot drift.
85
+ TEMP_OVERLAP_CODE = "PLAN_CHECK_TEMP_OVERLAP"
86
+ TEMP_NOT_PRIVATE_CODE = "PLAN_CHECK_TEMP_NOT_PRIVATE"
87
+
88
+ # Where the closing sentence sits in the payload. It is a key a person reads, and it renders last.
89
+ UNPREDICTED_KEY = "what this does not tell you"
90
+
91
+ # Why no build fingerprint is shown. Said in the same breath as the data fingerprint that is
92
+ # shown, because the distinction between the two is the whole of what this command has to teach.
93
+ WHY_NO_BUILD_FINGERPRINT = (
94
+ "The data fingerprint above is the one your real build will produce. The build's own "
95
+ "fingerprint is not shown, because it depends on who runs the build and under which attempt, "
96
+ "and a fingerprint that will not match is worse than none."
97
+ )
98
+
99
+ _DELETED = "This was a real build, into a folder that has now been deleted."
100
+ _KEPT = (
101
+ "This was a real build, into the folder named above, which was left in place as you asked. "
102
+ "Run chmod -R u+w on it before removing it: the folder a build seals is read-only on purpose."
103
+ )
104
+ _NOT_REMOVED = (
105
+ "This was a real build, into the folder named above, which could not be removed afterwards. "
106
+ "Run chmod -R u+w on it before removing it: the folder a build seals is read-only on purpose."
107
+ )
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class PlanCheck:
112
+ """What a real build of one plan produced, from a folder that no longer exists.
113
+
114
+ ``built`` is a property rather than a field because a plan that does not build raises: this
115
+ object exists only when the build happened, so there is no state in which it could be false.
116
+ There is deliberately no field for the build's own fingerprint because the synthetic runner
117
+ identity guarantees that it will differ from the durable Build's fingerprint.
118
+ """
119
+
120
+ row_count: int
121
+ columns: tuple[str, ...]
122
+ table_sha256: str
123
+ quality: tuple[LocalCheck, ...]
124
+ engine: str
125
+ temp_root: Path
126
+ cleanup_failed: bool = False
127
+ kept: bool = False
128
+
129
+ @property
130
+ def built(self) -> bool:
131
+ return True
132
+
133
+ @property
134
+ def removed(self) -> bool:
135
+ """Whether the throwaway folder is gone. False when it was kept, or would not go."""
136
+
137
+ return not self.kept and not self.cleanup_failed
138
+
139
+ def to_dict(self) -> dict[str, Any]:
140
+ """The one payload both renderings are built from."""
141
+
142
+ width = len(str(max(len(self.quality), 1)))
143
+ payload: dict[str, Any] = {
144
+ "status": PLAN_CHECK_STATUS,
145
+ "built": self.built,
146
+ "table_sha256": self.table_sha256,
147
+ "row_count": self.row_count,
148
+ "columns": list(self.columns),
149
+ "engine": self.engine,
150
+ "throwaway_folder_removed": self.removed,
151
+ }
152
+ if not self.removed:
153
+ # Named only when there is something to go and look at, and named before the sentence
154
+ # that tells you what to do with it.
155
+ payload["throwaway_folder"] = str(self.temp_root)
156
+ payload[CHECKS_KEY] = {
157
+ f"check {index:0{width}d}": check.line
158
+ for index, check in enumerate(self.quality, start=1)
159
+ }
160
+ payload[UNPREDICTED_KEY] = f"{self._folder_sentence()} {WHY_NO_BUILD_FINGERPRINT}"
161
+ return payload
162
+
163
+ def _folder_sentence(self) -> str:
164
+ if self.removed:
165
+ return _DELETED
166
+ return _KEPT if self.kept else _NOT_REMOVED
167
+
168
+
169
+ def check_plan(
170
+ plan_value: Any,
171
+ *,
172
+ input_root: Path | str,
173
+ engine: str = REFERENCE_BACKEND_NAME,
174
+ keep: bool = False,
175
+ ) -> PlanCheck:
176
+ """Build ``plan_value`` for real into a throwaway folder and report what came out.
177
+
178
+ ``keep`` leaves the folder in place for someone looking at a failure; every other path removes
179
+ it, including the one an interrupt takes. A refusal from the build is raised exactly as the
180
+ real build raises it, so a plan that would fail fails here in the same words.
181
+ """
182
+
183
+ # The same gate `build` applies to the same bytes: a dry run of a plan nobody could build is
184
+ # a dry run that lies about what the real build would do. The isinstance short-circuit mirrors
185
+ # `build_candidate`, which this function hands the value straight to: it accepts an
186
+ # already-parsed plan, and so must the exported reuse point in front of it.
187
+ if isinstance(plan_value, (TablePlan, GraphTablePlan)):
188
+ refuse_retired_operations(plan_value)
189
+ refuse_inconsistent_units(plan_value)
190
+ else:
191
+ parse_fresh_plan_document(plan_value)
192
+ root = Path(os.path.realpath(input_root))
193
+ # Resolved to its real path: the system temporary folder is often reached through a link, and
194
+ # sealing refuses an output whose parent chain contains one.
195
+ temp_root = Path(os.path.realpath(tempfile.mkdtemp(prefix=TEMP_PREFIX)))
196
+ try:
197
+ _require_private(temp_root)
198
+ _refuse_overlap(temp_root, root)
199
+ except BaseException:
200
+ # A folder nobody asked for and nothing was built into is never left behind, whatever the
201
+ # caller asked to keep.
202
+ _remove_tree(temp_root)
203
+ raise
204
+
205
+ cleanup_failed = False
206
+ try:
207
+ try:
208
+ # The real build, unmodified, on the plan value exactly as it was given. Called
209
+ # through the module so a test can record the call without a second build path
210
+ # existing for it to record.
211
+ result = pipeline.build_candidate(
212
+ plan_value,
213
+ input_root=root,
214
+ output_dir=temp_root / "check",
215
+ producer_principal=CHECK_PRINCIPAL,
216
+ producer_attempt=CHECK_ATTEMPT,
217
+ engine=engine,
218
+ )
219
+ # One reading of the sealed tree, for the facts a plan's author asked about: what the
220
+ # columns came out as, and which checks it passed.
221
+ inspection = pipeline.inspect_candidate(result.run_dir)
222
+ except BuildError as error:
223
+ raise _named_if_kept(error, temp_root, keep=keep) from error
224
+ finally:
225
+ if not keep:
226
+ cleanup_failed = not _remove_tree(temp_root)
227
+
228
+ return PlanCheck(
229
+ row_count=inspection.row_count,
230
+ columns=tuple(inspection.columns),
231
+ table_sha256=inspection.table_sha256,
232
+ quality=tuple(_quality_checks(inspection.quality)),
233
+ engine=engine,
234
+ temp_root=temp_root,
235
+ cleanup_failed=cleanup_failed,
236
+ kept=keep,
237
+ )
238
+
239
+
240
+ def _named_if_kept(error: BuildError, temp_root: Path, *, keep: bool) -> BuildError:
241
+ """The build's own refusal, saying where the folder was left when one was left.
242
+
243
+ The kind, the identifier and the severity are the real build's; only the message gains the
244
+ place to go and look. A refusal that keeps evidence and does not say where it put it is a
245
+ refusal somebody has to guess at.
246
+ """
247
+
248
+ if not keep:
249
+ return error
250
+ return BuildError(
251
+ error.finding_id,
252
+ f"{error}; the folder it was building into was kept at {temp_root}",
253
+ severity=error.severity,
254
+ )
255
+
256
+
257
+ def _require_private(temp_root: Path) -> None:
258
+ """Refuse a folder other people can read. A whole build is about to land inside it."""
259
+
260
+ mode = stat.S_IMODE(temp_root.stat().st_mode)
261
+ if mode != PRIVATE_MODE:
262
+ raise BuildError(
263
+ "PLAN_CHECK_TEMP_NOT_PRIVATE",
264
+ f"the temporary folder {temp_root} is not private to its owner",
265
+ )
266
+
267
+
268
+ def _refuse_overlap(temp_root: Path, root: Path) -> None:
269
+ """Refuse a temporary folder inside the input folder, in words about the folder that moved.
270
+
271
+ A build refuses to read from the folder it writes to. That refusal is right, but meeting it
272
+ through a path the caller never chose is a puzzle, so the overlap is named here instead.
273
+ """
274
+
275
+ if temp_root == root or root in temp_root.parents or temp_root in root.parents:
276
+ raise BuildError(
277
+ "PLAN_CHECK_TEMP_OVERLAP",
278
+ f"the temporary folder {temp_root} sits inside the input folder {root}",
279
+ )
280
+
281
+
282
+ def _remove_tree(temp_root: Path) -> bool:
283
+ """Remove the throwaway folder, restoring write permission first. True when it is gone.
284
+
285
+ A sealed build is mode 0555 on purpose, so a plain removal fails with a permission error part
286
+ way through and leaves the rest behind. Owner write is added from the bottom up -- the
287
+ directories are what the removal needs, and a directory has to be walked before it is fixed.
288
+ """
289
+
290
+ try:
291
+ for directory, _dirnames, _filenames in os.walk(temp_root, topdown=False):
292
+ os.chmod(directory, PRIVATE_MODE)
293
+ shutil.rmtree(temp_root)
294
+ except OSError:
295
+ return False
296
+ return True
297
+
298
+
299
+ __all__ = [
300
+ "CHECK_ATTEMPT",
301
+ "CHECK_PRINCIPAL",
302
+ "PLAN_CHECK_STATUS",
303
+ "PRIVATE_MODE",
304
+ "TEMP_NOT_PRIVATE_CODE",
305
+ "TEMP_OVERLAP_CODE",
306
+ "TEMP_PREFIX",
307
+ "UNPREDICTED_KEY",
308
+ "WHY_NO_BUILD_FINGERPRINT",
309
+ "PlanCheck",
310
+ "check_plan",
311
+ ]