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,568 @@
1
+ """Closed deterministic cleaning, normalization, resolution, and projection operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import re
7
+ from dataclasses import dataclass, replace
8
+ from datetime import date
9
+ from decimal import Decimal, InvalidOperation
10
+ from fractions import Fraction
11
+ from functools import cmp_to_key
12
+ from typing import Any
13
+
14
+ from mostlyright.data_harness.preparation.contracts import (
15
+ OPERATION_AUDIT_VERSION,
16
+ PHYSICAL_TYPES,
17
+ ColumnLineage,
18
+ OperationSpec,
19
+ parse_utc,
20
+ )
21
+ from mostlyright.data_harness.preparation.errors import PreparationError
22
+ from mostlyright.data_harness.preparation.profile import TableProfile, profile_table
23
+ from mostlyright.data_harness.preparation.table import TableState, validate_columns
24
+
25
+ _ASCII_TRIM = "\t\n\v\f\r "
26
+ _DIGEST = re.compile(r"\A[0-9a-f]{64}\Z")
27
+ _INT64 = re.compile(r"\A-?[0-9]+\Z")
28
+ _FLOAT64 = re.compile(r"\A-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE]-?[0-9]+)?\Z")
29
+ _UNIT_CONVERSIONS: dict[tuple[str, str], tuple[int, int, int, int]] = {
30
+ ("celsius", "fahrenheit"): (9, 5, 32, 1),
31
+ ("fahrenheit", "celsius"): (5, 9, -160, 9),
32
+ ("celsius", "kelvin"): (1, 1, 27315, 100),
33
+ ("kelvin", "celsius"): (1, 1, -27315, 100),
34
+ ("meter", "kilometer"): (1, 1000, 0, 1),
35
+ ("kilometer", "meter"): (1000, 1, 0, 1),
36
+ ("mile", "kilometer"): (25146, 15625, 0, 1),
37
+ ("kilometer", "mile"): (15625, 25146, 0, 1),
38
+ ("second", "minute"): (1, 60, 0, 1),
39
+ ("minute", "second"): (60, 1, 0, 1),
40
+ ("minute", "hour"): (1, 60, 0, 1),
41
+ ("hour", "minute"): (60, 1, 0, 1),
42
+ ("ratio", "percent"): (100, 1, 0, 1),
43
+ ("percent", "ratio"): (1, 100, 0, 1),
44
+ }
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class OperationAudit:
49
+ schema_version: str
50
+ operation_id: str
51
+ kind: str
52
+ parameters_digest: str
53
+ input_profile_digest: str
54
+ output_profile_digest: str
55
+ input_rows: int
56
+ output_rows: int
57
+ changed_cells: int
58
+ removed_rows: int
59
+
60
+ def __post_init__(self) -> None:
61
+ if self.schema_version != OPERATION_AUDIT_VERSION:
62
+ raise PreparationError("OPERATION_AUDIT_VERSION", "audit.schema_version", "unsupported")
63
+ for name in (
64
+ "parameters_digest",
65
+ "input_profile_digest",
66
+ "output_profile_digest",
67
+ ):
68
+ value = getattr(self, name)
69
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
70
+ raise PreparationError(
71
+ "OPERATION_AUDIT_DIGEST",
72
+ f"audit.{name}",
73
+ "must be a lowercase SHA-256 digest",
74
+ )
75
+ for name in (
76
+ "input_rows",
77
+ "output_rows",
78
+ "changed_cells",
79
+ "removed_rows",
80
+ ):
81
+ value = getattr(self, name)
82
+ if type(value) is not int or value < 0:
83
+ raise PreparationError(
84
+ "OPERATION_AUDIT_COUNT",
85
+ f"audit.{name}",
86
+ "must be nonnegative",
87
+ )
88
+ if self.removed_rows != self.input_rows - self.output_rows:
89
+ raise PreparationError(
90
+ "OPERATION_AUDIT_COUNT",
91
+ "audit.removed_rows",
92
+ "does not reconcile input and output rows",
93
+ )
94
+
95
+ def to_dict(self) -> dict[str, Any]:
96
+ return {
97
+ "schema_version": self.schema_version,
98
+ "operation_id": self.operation_id,
99
+ "kind": self.kind,
100
+ "parameters_digest": self.parameters_digest,
101
+ "input_profile_digest": self.input_profile_digest,
102
+ "output_profile_digest": self.output_profile_digest,
103
+ "input_rows": self.input_rows,
104
+ "output_rows": self.output_rows,
105
+ "changed_cells": self.changed_cells,
106
+ "removed_rows": self.removed_rows,
107
+ }
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class PreparedSource:
112
+ state: TableState
113
+ before_profile: TableProfile
114
+ after_profile: TableProfile
115
+ operation_audits: tuple[OperationAudit, ...]
116
+
117
+
118
+ def apply_operations(
119
+ state: TableState,
120
+ operations: tuple[OperationSpec, ...],
121
+ ) -> PreparedSource:
122
+ """Apply a fixed ordered operation list and bind every before/after profile."""
123
+
124
+ if not isinstance(operations, tuple):
125
+ raise PreparationError("OPERATION_COLLECTION", "operations", "must be an immutable tuple")
126
+ before = profile_table(state)
127
+ audits: list[OperationAudit] = []
128
+ current = state
129
+ for operation in operations:
130
+ if operation.source_id != state.table_id:
131
+ raise PreparationError(
132
+ "OPERATION_SOURCE",
133
+ operation.operation_id,
134
+ "operation source does not match the prepared source",
135
+ )
136
+ input_profile = profile_table(current)
137
+ output, changed = _apply(current, operation)
138
+ output_profile = profile_table(output)
139
+ audits.append(
140
+ OperationAudit(
141
+ schema_version=OPERATION_AUDIT_VERSION,
142
+ operation_id=operation.operation_id,
143
+ kind=operation.kind,
144
+ parameters_digest=operation.parameters_digest,
145
+ input_profile_digest=input_profile.digest,
146
+ output_profile_digest=output_profile.digest,
147
+ input_rows=len(current.rows),
148
+ output_rows=len(output.rows),
149
+ changed_cells=changed,
150
+ removed_rows=len(current.rows) - len(output.rows),
151
+ )
152
+ )
153
+ current = output
154
+ return PreparedSource(
155
+ state=current,
156
+ before_profile=before,
157
+ after_profile=profile_table(current),
158
+ operation_audits=tuple(audits),
159
+ )
160
+
161
+
162
+ def _apply(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
163
+ dispatch = {
164
+ "trim": _trim,
165
+ "case_normalize": _case_normalize,
166
+ "null_tokens": _null_tokens,
167
+ "strict_cast": _strict_cast,
168
+ "unit_convert": _unit_convert,
169
+ "entity_resolve": _entity_resolve,
170
+ "rename": _rename,
171
+ "project": _project,
172
+ "deduplicate": _deduplicate,
173
+ }
174
+ return dispatch[operation.kind](state, operation)
175
+
176
+
177
+ def _map_column(
178
+ state: TableState,
179
+ operation: OperationSpec,
180
+ transform: Any,
181
+ *,
182
+ field_transform: Any | None = None,
183
+ ) -> tuple[TableState, int]:
184
+ column = operation.parameter("column")
185
+ index = state.index(column)
186
+ changed = 0
187
+ rows: list[tuple[Any, ...]] = []
188
+ for row_index, row in enumerate(state.rows):
189
+ value = transform(row[index], row_index)
190
+ if _typed_key(value) != _typed_key(row[index]):
191
+ changed += 1
192
+ mutable = list(row)
193
+ mutable[index] = value
194
+ rows.append(tuple(mutable))
195
+ fields = state.fields
196
+ if field_transform is not None:
197
+ mutable_fields = list(fields)
198
+ mutable_fields[index] = field_transform(fields[index])
199
+ fields = tuple(mutable_fields)
200
+ lineage = _append_operation(state.lineage, (column,), operation.operation_id)
201
+ return state.replace(fields=fields, rows=tuple(rows), lineage=lineage), changed
202
+
203
+
204
+ def _trim(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
205
+ field = state.field(operation.parameter("column"))
206
+ if field.logical_type != "string":
207
+ raise PreparationError("OPERATION_TYPE", operation.operation_id, "trim requires string")
208
+ return _map_column(
209
+ state,
210
+ operation,
211
+ lambda value, _row: value.strip(_ASCII_TRIM) if value is not None else None,
212
+ )
213
+
214
+
215
+ def _case_normalize(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
216
+ field = state.field(operation.parameter("column"))
217
+ if field.logical_type != "string":
218
+ raise PreparationError(
219
+ "OPERATION_TYPE",
220
+ operation.operation_id,
221
+ "case normalization requires string",
222
+ )
223
+ mode = operation.parameter("mode")
224
+
225
+ def normalize(value: Any, _row: int) -> Any:
226
+ if value is None:
227
+ return None
228
+ if mode == "lower":
229
+ return value.lower()
230
+ if mode == "upper":
231
+ return value.upper()
232
+ return value.casefold()
233
+
234
+ return _map_column(state, operation, normalize)
235
+
236
+
237
+ def _null_tokens(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
238
+ field = state.field(operation.parameter("column"))
239
+ if field.logical_type != "string":
240
+ raise PreparationError(
241
+ "OPERATION_TYPE",
242
+ operation.operation_id,
243
+ "null tokens require string",
244
+ )
245
+ tokens = frozenset(operation.parameter("tokens"))
246
+ return _map_column(
247
+ state,
248
+ operation,
249
+ lambda value, _row: None if value in tokens else value,
250
+ )
251
+
252
+
253
+ def _strict_cast(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
254
+ column = operation.parameter("column")
255
+ target = operation.parameter("to_type")
256
+ field = state.field(column)
257
+ if target == field.logical_type:
258
+ raise PreparationError("OPERATION_CAST", operation.operation_id, "cast must change type")
259
+
260
+ def cast(value: Any, row_index: int) -> Any:
261
+ if value is None:
262
+ return None
263
+ path = f"{operation.operation_id}.row[{row_index}]"
264
+ if target == "string":
265
+ if type(value) is bool:
266
+ return "true" if value else "false"
267
+ if type(value) is int:
268
+ return str(value)
269
+ if type(value) is float and math.isfinite(value):
270
+ return format(value, ".17g")
271
+ if isinstance(value, str):
272
+ return value
273
+ if not isinstance(value, str):
274
+ raise PreparationError("CAST_VALUE", path, "cast input must be a canonical string")
275
+ if target == "int64":
276
+ if _INT64.fullmatch(value) is None:
277
+ raise PreparationError("CAST_INT64", path, "value is not strict int64 syntax")
278
+ parsed = int(value)
279
+ if not -(1 << 63) <= parsed <= (1 << 63) - 1:
280
+ raise PreparationError("CAST_INT64", path, "value is outside int64 range")
281
+ return parsed
282
+ if target == "float64":
283
+ if _FLOAT64.fullmatch(value) is None:
284
+ raise PreparationError(
285
+ "CAST_FLOAT64",
286
+ path,
287
+ "value is not strict finite float syntax",
288
+ )
289
+ parsed = float(value)
290
+ if not math.isfinite(parsed):
291
+ raise PreparationError("CAST_FLOAT64", path, "value is not finite")
292
+ return parsed
293
+ if target == "boolean":
294
+ if value not in {"false", "true"}:
295
+ raise PreparationError("CAST_BOOLEAN", path, "value must be true or false")
296
+ return value == "true"
297
+ if target == "date":
298
+ try:
299
+ parsed_date = date.fromisoformat(value)
300
+ except ValueError:
301
+ raise PreparationError("CAST_DATE", path, "value is not a real ISO date") from None
302
+ if parsed_date.isoformat() != value or len(value) != 10:
303
+ raise PreparationError("CAST_DATE", path, "value is not canonical ISO date")
304
+ return value
305
+ if target == "timestamp_utc":
306
+ parse_utc(value, path)
307
+ return value
308
+ raise PreparationError("CAST_VALUE", path, "cast conversion is unsupported")
309
+
310
+ return _map_column(
311
+ state,
312
+ operation,
313
+ cast,
314
+ field_transform=lambda current: replace(
315
+ current,
316
+ logical_type=target,
317
+ physical_type=PHYSICAL_TYPES[target],
318
+ ),
319
+ )
320
+
321
+
322
+ def _unit_convert(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
323
+ column = operation.parameter("column")
324
+ source = operation.parameter("from_unit")
325
+ target = operation.parameter("to_unit")
326
+ field = state.field(column)
327
+ if field.logical_type not in {"int64", "float64"} or field.semantic_type != "measure":
328
+ raise PreparationError(
329
+ "OPERATION_TYPE",
330
+ operation.operation_id,
331
+ "unit conversion requires a numeric measure",
332
+ )
333
+ if field.unit != source:
334
+ raise PreparationError(
335
+ "OPERATION_UNIT",
336
+ operation.operation_id,
337
+ "declared source unit differs",
338
+ )
339
+ try:
340
+ scale_numerator, scale_denominator, offset_numerator, offset_denominator = (
341
+ _UNIT_CONVERSIONS[(source, target)]
342
+ )
343
+ except KeyError:
344
+ raise PreparationError(
345
+ "OPERATION_UNIT",
346
+ operation.operation_id,
347
+ "conversion is not in the closed rational registry",
348
+ ) from None
349
+
350
+ def convert(value: Any, row_index: int) -> Any:
351
+ if value is None:
352
+ return None
353
+ try:
354
+ converted = Fraction(Decimal(str(value))) * Fraction(
355
+ scale_numerator, scale_denominator
356
+ ) + Fraction(offset_numerator, offset_denominator)
357
+ except (InvalidOperation, ValueError):
358
+ raise PreparationError(
359
+ "OPERATION_UNIT",
360
+ f"{operation.operation_id}.row[{row_index}]",
361
+ "numeric value cannot be converted",
362
+ ) from None
363
+ result = float(converted)
364
+ if not math.isfinite(result):
365
+ raise PreparationError(
366
+ "OPERATION_UNIT",
367
+ f"{operation.operation_id}.row[{row_index}]",
368
+ "conversion produced a non-finite value",
369
+ )
370
+ return result
371
+
372
+ return _map_column(
373
+ state,
374
+ operation,
375
+ convert,
376
+ field_transform=lambda current: replace(
377
+ current,
378
+ logical_type="float64",
379
+ physical_type=PHYSICAL_TYPES["float64"],
380
+ unit=target,
381
+ ),
382
+ )
383
+
384
+
385
+ def _entity_resolve(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
386
+ column = operation.parameter("column")
387
+ field = state.field(column)
388
+ if field.logical_type != "string" or field.semantic_type != "entity":
389
+ raise PreparationError(
390
+ "OPERATION_TYPE",
391
+ operation.operation_id,
392
+ "entity resolution requires a string entity field",
393
+ )
394
+ if field.entity != operation.parameter("entity_type"):
395
+ raise PreparationError(
396
+ "ENTITY_TYPE",
397
+ operation.operation_id,
398
+ "entity type differs from the field declaration",
399
+ )
400
+ aliases = dict(operation.parameter("aliases"))
401
+ canonical = frozenset(aliases.values())
402
+
403
+ def resolve(value: Any, row_index: int) -> Any:
404
+ if value is None or value in canonical:
405
+ return value
406
+ try:
407
+ return aliases[value]
408
+ except KeyError:
409
+ raise PreparationError(
410
+ "ENTITY_UNRESOLVED",
411
+ f"{operation.operation_id}.row[{row_index}]",
412
+ "entity value is neither canonical nor an allowlisted alias",
413
+ ) from None
414
+
415
+ return _map_column(state, operation, resolve)
416
+
417
+
418
+ def _rename(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
419
+ mappings = dict(operation.parameter("mappings"))
420
+ unknown = set(mappings) - set(state.columns)
421
+ if unknown:
422
+ raise PreparationError(
423
+ "OPERATION_RENAME",
424
+ operation.operation_id,
425
+ "references unknown columns",
426
+ )
427
+ output_names = tuple(mappings.get(name, name) for name in state.columns)
428
+ if len(set(output_names)) != len(output_names):
429
+ raise PreparationError(
430
+ "OPERATION_RENAME",
431
+ operation.operation_id,
432
+ "creates a name collision",
433
+ )
434
+ fields = tuple(
435
+ replace(
436
+ field,
437
+ name=mappings.get(field.name, field.name),
438
+ availability_column=(
439
+ mappings.get(field.availability_column, field.availability_column)
440
+ if field.availability_column is not None
441
+ else None
442
+ ),
443
+ )
444
+ for field in state.fields
445
+ )
446
+ lineage = tuple(
447
+ ColumnLineage(
448
+ output_column=mappings.get(item.output_column, item.output_column),
449
+ inputs=item.inputs,
450
+ operation_ids=(*item.operation_ids, operation.operation_id),
451
+ )
452
+ for item in state.lineage
453
+ )
454
+ return state.replace(fields=fields, lineage=lineage), len(state.rows) * len(mappings)
455
+
456
+
457
+ def _project(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
458
+ columns = operation.parameter("columns")
459
+ indexes = validate_columns(state, columns, operation.operation_id)
460
+ fields = tuple(state.fields[index] for index in indexes)
461
+ output_names = frozenset(columns)
462
+ for field in fields:
463
+ if field.availability_column is not None and field.availability_column not in output_names:
464
+ raise PreparationError(
465
+ "TEMPORAL_SEMANTICS",
466
+ operation.operation_id,
467
+ f"projection drops availability column for {field.name}",
468
+ )
469
+ rows = tuple(tuple(row[index] for index in indexes) for row in state.rows)
470
+ lineage_by_name = {item.output_column: item for item in state.lineage}
471
+ lineage = tuple(
472
+ ColumnLineage(
473
+ output_column=column,
474
+ inputs=lineage_by_name[column].inputs,
475
+ operation_ids=(*lineage_by_name[column].operation_ids, operation.operation_id),
476
+ )
477
+ for column in columns
478
+ )
479
+ removed_columns = len(state.columns) - len(columns)
480
+ return state.replace(fields=fields, rows=rows, lineage=lineage), len(rows) * removed_columns
481
+
482
+
483
+ def _deduplicate(state: TableState, operation: OperationSpec) -> tuple[TableState, int]:
484
+ key_indexes = validate_columns(state, operation.parameter("keys"), operation.operation_id)
485
+ authority = operation.parameter("authority")
486
+ authority_indexes = tuple((state.index(column), direction) for column, direction in authority)
487
+ groups: dict[tuple[Any, ...], list[tuple[int, tuple[Any, ...]]]] = {}
488
+ for ordinal, row in enumerate(state.rows):
489
+ key = tuple(row[index] for index in key_indexes)
490
+ if any(value is None for value in key):
491
+ raise PreparationError(
492
+ "DEDUPLICATE_NULL_KEY",
493
+ f"{operation.operation_id}.row[{ordinal}]",
494
+ "deduplication keys cannot be null",
495
+ )
496
+ if any(row[index] is None for index, _ in authority_indexes):
497
+ raise PreparationError(
498
+ "DEDUPLICATE_NULL_AUTHORITY",
499
+ f"{operation.operation_id}.row[{ordinal}]",
500
+ "authority values cannot be null",
501
+ )
502
+ groups.setdefault(key, []).append((ordinal, row))
503
+ selected: list[tuple[int, tuple[Any, ...]]] = []
504
+ for candidates in groups.values():
505
+ candidates.sort(
506
+ key=cmp_to_key(
507
+ lambda left, right: _compare_authority(
508
+ left,
509
+ right,
510
+ authority_indexes,
511
+ )
512
+ )
513
+ )
514
+ selected.append(candidates[0])
515
+ selected.sort(key=lambda item: item[0])
516
+ lineage = _append_operation(state.lineage, state.columns, operation.operation_id)
517
+ rows = tuple(row for _, row in selected)
518
+ return state.replace(rows=rows, lineage=lineage), len(state.rows) - len(rows)
519
+
520
+
521
+ def _compare_authority(
522
+ left: tuple[int, tuple[Any, ...]],
523
+ right: tuple[int, tuple[Any, ...]],
524
+ authority: tuple[tuple[int, str], ...],
525
+ ) -> int:
526
+ for index, direction in authority:
527
+ left_key = _sortable(left[1][index])
528
+ right_key = _sortable(right[1][index])
529
+ if left_key < right_key:
530
+ return -1 if direction == "ascending" else 1
531
+ if left_key > right_key:
532
+ return 1 if direction == "ascending" else -1
533
+ return (left[0] > right[0]) - (left[0] < right[0])
534
+
535
+
536
+ def _sortable(value: Any) -> tuple[int, Any]:
537
+ if type(value) is bool:
538
+ return (0, value)
539
+ if type(value) in {int, float}:
540
+ return (1, Decimal(str(value)))
541
+ return (2, value)
542
+
543
+
544
+ def _append_operation(
545
+ lineage: tuple[ColumnLineage, ...],
546
+ columns: tuple[str, ...],
547
+ operation_id: str,
548
+ ) -> tuple[ColumnLineage, ...]:
549
+ selected = frozenset(columns)
550
+ return tuple(
551
+ ColumnLineage(
552
+ output_column=item.output_column,
553
+ inputs=item.inputs,
554
+ operation_ids=(
555
+ (*item.operation_ids, operation_id)
556
+ if item.output_column in selected
557
+ else item.operation_ids
558
+ ),
559
+ )
560
+ for item in lineage
561
+ )
562
+
563
+
564
+ def _typed_key(value: Any) -> tuple[type[Any], Any]:
565
+ return (type(value), value)
566
+
567
+
568
+ __all__ = ["OperationAudit", "PreparedSource", "apply_operations"]