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,31 @@
1
+ <!-- Vendored from mostly-right-landing/src/assets/logo-ink.svg on 2026-08-11.
2
+ Facet and wordmark classes expose the canonical Logo.astro motion without a runtime dependency. -->
3
+ <svg class="mr-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 332.3 49.3" width="332.3" height="49.3" role="img" aria-label="Mostly Right">
4
+ <title>Mostly Right</title>
5
+ <g class="mr-logo-mark" transform="translate(0 4.45) scale(0.841458)">
6
+ <path class="mr-logo-facet mr-logo-facet--1" fill="#1A1813" d="M40,24h-24v16h8c1.5-5,5.1-8,8-8s6.5,3,8,8h8v-8c-2.9,0-6.5-3-8-8Z"></path>
7
+ <path class="mr-logo-facet mr-logo-facet--2" fill="#F15B22" d="M40,40h-16c-1.5,5-5.1,8-8,8h32c-2.9,0-6.5-3-8-8Z"></path>
8
+ <path class="mr-logo-facet mr-logo-facet--3" fill="#FFC613" d="M32,0c2.9,0,6.5,3,8,8h8V0h-16Z"></path>
9
+ <path class="mr-logo-facet mr-logo-facet--4" fill="#2B5FE3" d="M40,8h-24v8h16c2.9,0,6.5,3,8,8h8v-8c-2.9,0-6.5-3-8-8Z"></path>
10
+ <path class="mr-logo-facet mr-logo-facet--5" fill="#F15B22" d="M0,16c2.9,0,6.5-3,8-8H0v8Z"></path>
11
+ <path class="mr-logo-facet mr-logo-facet--6" fill="#1A1813" d="M8,8h8V0c-2.9,0-6.5,3-8,8Z"></path>
12
+ <path class="mr-logo-facet mr-logo-facet--7" fill="#2B5FE3" d="M0,32c2.9,0,6.5-3,8-8H0v8Z"></path>
13
+ <path class="mr-logo-facet mr-logo-facet--8" fill="#FFC613" d="M8,24h8v-8c-2.9,0-6.5,3-8,8Z"></path>
14
+ <rect class="mr-logo-facet mr-logo-facet--9" fill="#FFC613" x="0" y="40" width="16" height="8"></rect>
15
+ </g>
16
+ <g class="mr-logo-word" fill="#1A1813" transform="translate(-6.86 0)">
17
+ <path d="M101.06,24.06v12.85h-7.58v-12.64c0-3.69-1.84-4.86-3.79-4.86-2.35,0-4.1,1.64-4.1,5.84v11.67h-7.58v-12.49c0-3.69-1.84-5.02-3.74-5.02-2.05,0-4.15,1.02-4.15,5.02v12.49h-7.58V13h7.58v3.79c.92-3.02,4.2-4.4,6.45-4.4,3.53,0,6.14,1.69,7.58,4.56,1.64-3.17,5.32-4.56,7.83-4.56,5.99,0,9.06,4.35,9.06,11.67Z"></path>
18
+ <path d="M105.72,24.98c0-7.58,6.14-12.59,12.95-12.59s13,5.02,13,12.59-6.19,12.54-13,12.54-12.95-5.02-12.95-12.54ZM123.94,24.98c0-3.28-2.35-5.58-5.27-5.58s-5.27,2.3-5.27,5.58,2.35,5.53,5.27,5.53,5.27-2.25,5.27-5.53Z"></path>
19
+ <path d="M135.25,29.18h7.17c0,1.18.87,2.56,3.02,2.56,1.59,0,2.92-.82,2.92-1.95,0-.77-.56-1.13-2.35-1.54l-3.12-.82c-7.01-1.89-7.22-5.89-7.22-7.88,0-3.53,3.99-7.17,9.78-7.17,4.51,0,9.98,2.1,9.88,8.09h-7.17c0-1.54-1.08-2.3-2.56-2.3-1.28,0-2.35.61-2.35,1.64,0,.92.82,1.43,2.05,1.74l4.15,1.13c5.99,1.64,6.5,5.43,6.5,7.06,0,5.27-5.48,7.78-10.29,7.78-5.32,0-10.39-2.92-10.39-8.34Z"></path>
20
+ <path d="M175.8,13v5.73h-5.02v18.17h-7.58v-18.17h-4.35v-5.73h4.35V4.45h7.58v8.55h5.02Z"></path>
21
+ <path d="M179.94,36.91V0h7.58v36.91h-7.58Z"></path>
22
+ <path d="M208.97,13h8.14l-14.74,35.68h-8.09l5.89-14.64-9.42-21.04h8.4l4.97,12.39,4.86-12.39Z"></path>
23
+ </g>
24
+ <g class="mr-logo-word" fill="#1A1813" transform="translate(-10.87 0)">
25
+ <path d="M249.61,13.11l-1.79,7.22c-1.48-.87-2.66-.92-3.43-.92-2.97,0-5.43,2.51-5.43,9.57v7.94h-7.58V13h7.47v5.07c1.38-3.94,4.25-5.68,7.17-5.68,1.13,0,2.35.1,3.58.72Z"></path>
26
+ <path d="M252.02,5.02c0-2.51,2.15-4.4,4.71-4.4s4.61,1.89,4.61,4.4-2.05,4.4-4.61,4.4-4.71-2-4.71-4.4ZM252.94,36.91V13h7.58v23.91h-7.58Z"></path>
27
+ <path d="M284.52,13h7.53v21.04c0,10.14-5.32,15.26-13.98,15.26-5.43,0-10.03-3.17-11.93-7.68l6.25-2.51c.87,1.95,3.17,3.53,5.68,3.53,3.94,0,6.45-2.2,6.45-7.68v-.72c-1.79,2.25-4.97,3.28-7.42,3.28-5.94,0-11.57-4.86-11.57-12.59s5.63-12.54,11.57-12.54c2.46,0,5.63.97,7.42,3.23v-2.61ZM284.21,24.93c0-3.07-2.61-5.53-5.48-5.53s-5.53,2.51-5.53,5.53,2.56,5.58,5.53,5.58,5.48-2.51,5.48-5.58Z"></path>
28
+ <path d="M322.3,24.06v12.85h-7.58v-12.39c0-3.63-2.25-5.12-4.15-5.12-2.05,0-4.61,1.13-4.61,5.12v12.39h-7.58V0h7.58v16.84c.87-3.07,4.71-4.45,6.86-4.45,6.35,0,9.47,4.35,9.47,11.67Z"></path>
29
+ <path d="M343.08,13v5.73h-5.02v18.17h-7.58v-18.17h-4.35v-5.73h4.35V4.45h7.58v8.55h5.02Z"></path>
30
+ </g>
31
+ </svg>
@@ -0,0 +1,28 @@
1
+ """The ``backends`` package: named backend protocol, reference backend, registry.
2
+
3
+ This is the engine seam of the candidate build path. ``pipeline.build_candidate``
4
+ resolves its engine by name through ``get_backend`` and runs the inner
5
+ ``clean -> join -> select`` row transforms on the returned backend; ``reference`` is the
6
+ default and the byte-parity authority every other engine must reproduce exactly.
7
+
8
+ This seam belongs to ``pipeline`` and ``local_contracts.TablePlan``. It is unrelated to
9
+ the ``preparation`` package, whose ``execute_preparation`` is a separate stack with its own
10
+ plan type and no backend dispatch.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from mostlyright.data_harness.backends.pandas_backend import PandasBackend
16
+ from mostlyright.data_harness.backends.polars_backend import PolarsBackend
17
+ from mostlyright.data_harness.backends.protocol import Backend
18
+ from mostlyright.data_harness.backends.reference import ReferenceBackend
19
+ from mostlyright.data_harness.backends.registry import get_backend, registered_backends
20
+
21
+ __all__ = [
22
+ "Backend",
23
+ "PandasBackend",
24
+ "PolarsBackend",
25
+ "ReferenceBackend",
26
+ "get_backend",
27
+ "registered_backends",
28
+ ]
@@ -0,0 +1,350 @@
1
+ """The pandas backend: ``PandasBackend`` over Arrow-backed pandas DataFrames.
2
+
3
+ ``PandasBackend`` re-owns the narrow ``clean -> join -> select`` triple behind the
4
+ ``Backend`` protocol; every other candidate byte is still produced by the shared layer (the
5
+ one pyarrow sink, the canonical schema, ``_coerce_native_rows``, the evidence serializer).
6
+ Because the shared layer downstream is unchanged, the backend's job is to reproduce the
7
+ reference compute byte-identically while genuinely exercising a pandas object.
8
+
9
+ Design (the load-bearing dtype strategy):
10
+
11
+ - The row-shape transforms (``clean``/``join``/``select``) are computed **value-wise** in
12
+ plain Python, mirroring ``pipeline._apply_cleaning`` / ``_join`` / ``_select`` exactly so
13
+ unicode-whitespace, empty-vs-null, ordering, and the typed ``BuildError`` refusals match
14
+ the reference bit-for-bit. Every value-level cast routes through the shared
15
+ ``pipeline._cast`` — never an engine-native parser, which is what keeps cast results and
16
+ cast refusals identical across engines.
17
+ - The op's output is then materialized as an **Arrow-backed** pandas DataFrame — one
18
+ explicit ``pd.ArrowDtype`` per column drawn from the closed ``pipeline._ARROW_BY_LOGICAL``
19
+ vocabulary the sink uses, constructed from the already-typed native values (never
20
+ inferred, never a whole-frame ``pa.Table`` pandas conversion into the sink, never a native
21
+ cast token). Arrow-backed columns
22
+ carry true Arrow nulls (never ``np.nan``/``NaT``) and keep an ``int64`` column ``int64``
23
+ under nulls (no int->float promotion).
24
+ - The frame is converted back to an ordered native ``list[dict]`` at the boundary
25
+ (``_rows_from_frame``): the index is dropped (positional order), and each column is read
26
+ through pyarrow ``to_pylist()`` so Arrow nulls become Python ``None`` and every scalar is
27
+ a native Python builtin (``int``/``float``/``str``/``datetime.date``) — no numpy scalar,
28
+ no ``pd.NA``. ``pipeline._coerce_native_rows`` remains the shared backstop.
29
+
30
+ ``import pandas`` / ``import pyarrow`` are **function-local** (mirroring ``reference.py``'s
31
+ lazy-engine-import ethos), so ``import backends`` and constructing ``PandasBackend()`` at
32
+ registry load never import pandas. The module is named ``pandas_backend`` (not ``pandas``)
33
+ to avoid the ``import pandas`` self-shadow footgun.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from typing import Any
39
+
40
+ from mostlyright.data_harness.local_contracts import CleaningStep, TablePlan
41
+
42
+
43
+ class PandasBackend:
44
+ """pandas engine for the ``clean -> join -> select`` triple over Arrow-backed frames."""
45
+
46
+ # A plain literal (not a lazy ``pipeline`` import), so constructing the backend at
47
+ # ``registry`` module load never imports ``pipeline`` or pandas. The seam resolves the
48
+ # backend by this exact name; any drift fails closed at ``get_backend`` (BACKEND_UNKNOWN).
49
+ name: str = "pandas"
50
+
51
+ def versions(self) -> dict[str, str]:
52
+ # Provenance only (engine-runtime.v2 ``backend_versions``). The pandas import is
53
+ # function-local (only reached during a build, when pandas is already dispatched), so
54
+ # ``import backends`` / constructing the backend stay pandas-free. The version is read
55
+ # from the module attribute ``pd.__version__`` rather than the stdlib metadata-version
56
+ # helper: that helper's module name is one of the dynamic-import tokens banned under
57
+ # ``backends/`` by ``test_backend_registry.py``. For a pinned release the two
58
+ # spellings are identical.
59
+ import pandas as pd
60
+
61
+ return {"pandas": str(pd.__version__)}
62
+
63
+ def materialize_graph_rows(
64
+ self,
65
+ columns: tuple[str, ...],
66
+ rows: tuple[tuple[Any, ...], ...],
67
+ ) -> tuple[tuple[Any, ...], ...]:
68
+ """Exercise an object-typed pandas frame without allowing dtype inference to coerce."""
69
+
70
+ import pandas as pd
71
+
72
+ if any(len(row) != len(columns) for row in rows):
73
+ raise ValueError("graph row width differs from its columns")
74
+ frame = pd.DataFrame(
75
+ {
76
+ column: pd.Series([row[index] for row in rows], dtype="object")
77
+ for index, column in enumerate(columns)
78
+ },
79
+ columns=list(columns),
80
+ index=range(len(rows)),
81
+ )
82
+ return tuple(tuple(row) for row in frame.itertuples(index=False, name=None))
83
+
84
+ def clean(
85
+ self,
86
+ rows: list[dict[str, Any]],
87
+ columns: list[str],
88
+ lineage: dict[str, dict[str, Any]],
89
+ step: CleaningStep,
90
+ ) -> tuple[list[dict[str, Any]], list[str], dict[str, dict[str, Any]]]:
91
+ # Value-wise reproduction of ``pipeline._apply_cleaning`` (the DataFrame is a shaping
92
+ # container, not a transform engine): shared ``_require_columns`` refusals, Python
93
+ # shared ASCII-only trim, ``== ""`` -> None, positional
94
+ # rename with ``RENAME_COLLISION``, and every cast through the shared ``_cast``.
95
+ from mostlyright.data_harness import pipeline
96
+
97
+ output_rows = [dict(row) for row in rows]
98
+ output_columns = list(columns)
99
+ output_lineage = {
100
+ column: {**entry, "operations": list(entry["operations"])}
101
+ for column, entry in lineage.items()
102
+ }
103
+ if step.operation in {"trim", "empty_to_null"}:
104
+ target_columns = tuple(step.columns)
105
+ pipeline._require_columns(output_columns, target_columns, step.operation)
106
+ for row in output_rows:
107
+ for column in target_columns:
108
+ value = row[column]
109
+ if step.operation == "trim":
110
+ if value is not None and not isinstance(value, str):
111
+ raise pipeline.BuildError(
112
+ "CLEAN_TYPE", f"trim requires strings in {column!r}"
113
+ )
114
+ row[column] = None if value is None else pipeline._trim_ascii(value)
115
+ elif value == "":
116
+ row[column] = None
117
+ for column in target_columns:
118
+ output_lineage[column]["operations"].append(step.operation)
119
+ elif step.operation == "rename":
120
+ mapping = dict(step.columns)
121
+ pipeline._require_columns(output_columns, tuple(mapping), "rename")
122
+ final_columns = [mapping.get(column, column) for column in output_columns]
123
+ if len(final_columns) != len(set(final_columns)):
124
+ raise pipeline.BuildError("RENAME_COLLISION", "rename creates a column collision")
125
+ output_rows = [
126
+ {mapping.get(column, column): row[column] for column in output_columns}
127
+ for row in output_rows
128
+ ]
129
+ new_lineage: dict[str, dict[str, Any]] = {}
130
+ for column in output_columns:
131
+ target = mapping.get(column, column)
132
+ entry = output_lineage[column]
133
+ if target != column:
134
+ entry["operations"].append(f"rename:{column}->{target}")
135
+ new_lineage[target] = entry
136
+ output_columns = final_columns
137
+ output_lineage = new_lineage
138
+ elif step.operation == "cast":
139
+ mapping = dict(step.columns)
140
+ pipeline._require_columns(output_columns, tuple(mapping), "cast")
141
+ for row_index, row in enumerate(output_rows, start=1):
142
+ for column, target in mapping.items():
143
+ row[column] = pipeline._cast(row[column], target, column, row_index)
144
+ for column, target in mapping.items():
145
+ output_lineage[column]["operations"].append(f"cast:{target}")
146
+ else: # pragma: no cover - contract parser makes this unreachable
147
+ raise pipeline.ContractError(
148
+ "plan.cleaning.operation",
149
+ "CLEAN_OPERATION_UNSUPPORTED",
150
+ f"unsupported operation: {step.operation}",
151
+ )
152
+
153
+ shaped = _rows_from_frame(
154
+ _frame_from_rows(
155
+ output_rows, output_columns, _logical_by_column(output_lineage, output_columns)
156
+ )
157
+ )
158
+ return shaped, output_columns, output_lineage
159
+
160
+ def join(
161
+ self,
162
+ plan: TablePlan,
163
+ rows_by_source: dict[str, list[dict[str, Any]]],
164
+ columns_by_source: dict[str, list[str]],
165
+ lineage_by_source: dict[str, dict[str, dict[str, Any]]],
166
+ ) -> tuple[
167
+ list[dict[str, Any]],
168
+ list[str],
169
+ dict[str, dict[str, Any]],
170
+ dict[str, Any],
171
+ ]:
172
+ # Value-wise reproduction of ``pipeline._join``. Left-source order is imposed
173
+ # positionally (iterate the left rows; look up the right by an equality index) — never
174
+ # ``DataFrame.merge``'s incidental ordering. Same guards and typed refusals; output
175
+ # columns ``[*left_columns, *right_nonkeys]``; merged lineage; and a ``report``
176
+ # byte-identical to the reference one. The report counts stay native ``int`` because
177
+ # they are pure-Python ``len(...)``; a numpy scalar there would change the evidence
178
+ # bytes.
179
+ from mostlyright.data_harness import pipeline
180
+
181
+ spec = plan.join
182
+ left_rows = rows_by_source[spec.left]
183
+ right_rows = rows_by_source[spec.right]
184
+ left_columns = columns_by_source[spec.left]
185
+ right_columns = columns_by_source[spec.right]
186
+ pipeline._require_columns(left_columns, spec.on, "join.left keys")
187
+ pipeline._require_columns(right_columns, spec.on, "join.right keys")
188
+ for key in spec.on:
189
+ left_types = {type(row[key]) for row in left_rows if row[key] is not None}
190
+ right_types = {type(row[key]) for row in right_rows if row[key] is not None}
191
+ if any(row[key] is None for row in left_rows + right_rows):
192
+ raise pipeline.BuildError("JOIN_NULL_KEY", f"join key {key!r} contains null")
193
+ if len(left_types) != 1 or left_types != right_types:
194
+ raise pipeline.BuildError(
195
+ "JOIN_KEY_TYPE", f"join key {key!r} types do not match exactly"
196
+ )
197
+ right_nonkeys = [column for column in right_columns if column not in spec.on]
198
+ collisions = set(left_columns) & set(right_nonkeys)
199
+ if collisions:
200
+ raise pipeline.BuildError(
201
+ "JOIN_COLUMN_COLLISION", f"join column collision: {sorted(collisions)}"
202
+ )
203
+ right_index: dict[tuple[Any, ...], dict[str, Any]] = {}
204
+ for row in right_rows:
205
+ key = tuple(row[column] for column in spec.on)
206
+ if key in right_index:
207
+ raise pipeline.BuildError("JOIN_CARDINALITY", "right join keys are not unique")
208
+ right_index[key] = row
209
+ if spec.cardinality == "one_to_one":
210
+ left_keys = [tuple(row[column] for column in spec.on) for row in left_rows]
211
+ if len(left_keys) != len(set(left_keys)):
212
+ raise pipeline.BuildError("JOIN_CARDINALITY", "left join keys are not unique")
213
+ output: list[dict[str, Any]] = []
214
+ unmatched = 0
215
+ for left_row in left_rows:
216
+ key = tuple(left_row[column] for column in spec.on)
217
+ right_row = right_index.get(key)
218
+ if right_row is None:
219
+ unmatched += 1
220
+ merged = {**left_row, **{column: None for column in right_nonkeys}}
221
+ else:
222
+ merged = {**left_row, **{column: right_row[column] for column in right_nonkeys}}
223
+ output.append(merged)
224
+ multiplier = len(output) / len(left_rows)
225
+ if unmatched:
226
+ raise pipeline.BuildError("JOIN_UNMATCHED", f"left join has {unmatched} unmatched rows")
227
+ if multiplier != 1.0:
228
+ raise pipeline.BuildError("JOIN_MULTIPLIER", f"join row multiplier is {multiplier}")
229
+ columns = [*left_columns, *right_nonkeys]
230
+ lineage = {
231
+ **lineage_by_source[spec.left],
232
+ **{column: lineage_by_source[spec.right][column] for column in right_nonkeys},
233
+ }
234
+ report = {
235
+ "schema_version": "join-evidence.v1",
236
+ "left_source": spec.left,
237
+ "right_source": spec.right,
238
+ "keys": list(spec.on),
239
+ "cardinality": spec.cardinality,
240
+ "left_rows": len(left_rows),
241
+ "right_rows": len(right_rows),
242
+ "output_rows": len(output),
243
+ "matched_left_rows": len(output) - unmatched,
244
+ "unmatched_left_rows": unmatched,
245
+ "row_multiplier": multiplier,
246
+ "stable_order": "left_source_order",
247
+ }
248
+ shaped = _rows_from_frame(
249
+ _frame_from_rows(output, columns, _logical_by_column(lineage, columns))
250
+ )
251
+ return shaped, columns, lineage, report
252
+
253
+ def select(
254
+ self,
255
+ plan: TablePlan,
256
+ rows: list[dict[str, Any]],
257
+ columns: list[str],
258
+ lineage: dict[str, dict[str, Any]],
259
+ ) -> tuple[list[dict[str, Any]], dict[str, dict[str, Any]]]:
260
+ # Value-wise reproduction of ``pipeline._select``: project/order columns to
261
+ # ``plan.select`` (shared ``_require_columns`` for the missing-column refusal);
262
+ # ``selected_lineage`` in ``plan.select`` order.
263
+ from mostlyright.data_harness import pipeline
264
+
265
+ pipeline._require_columns(columns, plan.select, "select")
266
+ selected_rows = [{column: row[column] for column in plan.select} for row in rows]
267
+ selected_lineage = {column: lineage[column] for column in plan.select}
268
+ shaped = _rows_from_frame(
269
+ _frame_from_rows(
270
+ selected_rows,
271
+ list(plan.select),
272
+ _logical_by_column(selected_lineage, plan.select),
273
+ )
274
+ )
275
+ return shaped, selected_lineage
276
+
277
+
278
+ def _logical_by_column(lineage: dict[str, dict[str, Any]], columns: Any) -> dict[str, str]:
279
+ """Map each column to its closed logical type via the shared ``_logical_type_for``.
280
+
281
+ The logical type is drawn only from the column's lineage ``operations`` (the last
282
+ ``cast:<target>``, else ``"string"``) — never from an engine scalar's class name — so
283
+ the Arrow-backed dtype the frame is built with matches the sink's imposed schema.
284
+ """
285
+
286
+ from mostlyright.data_harness import pipeline
287
+
288
+ return {column: pipeline._logical_type_for(lineage[column]) for column in columns}
289
+
290
+
291
+ def _arrow_dtype_for(logical: str) -> Any:
292
+ """Return the ``pd.ArrowDtype`` for one authoritative local logical type.
293
+
294
+ Uses the same closed ``pipeline._ARROW_BY_LOGICAL`` map the shared sink imposes, so the
295
+ frame's per-column dtype is the canonical one and can never be inferred/lenient.
296
+ """
297
+
298
+ import pandas as pd
299
+
300
+ from mostlyright.data_harness import pipeline
301
+
302
+ return pd.ArrowDtype(pipeline._ARROW_BY_LOGICAL[logical])
303
+
304
+
305
+ def _frame_from_rows(
306
+ rows: list[dict[str, Any]], columns: Any, logical_by_column: dict[str, str]
307
+ ) -> Any:
308
+ """Build an Arrow-backed pandas DataFrame with an explicit per-column dtype.
309
+
310
+ Each column is constructed with ``pd.array(values, dtype=pd.ArrowDtype(pa_type))`` from
311
+ the already-typed native values — never inferred, never a whole-frame conversion into the
312
+ sink. Arrow-backed columns carry true Arrow nulls (never ``np.nan``) and keep ``int64``
313
+ as ``int64`` under
314
+ nulls (no float promotion). The explicit ``index`` preserves the row count even for a
315
+ zero-column frame (an unreachable but defensively-handled shape).
316
+ """
317
+
318
+ import pandas as pd
319
+
320
+ column_order = list(columns)
321
+ data = {
322
+ column: pd.array(
323
+ [row[column] for row in rows],
324
+ dtype=_arrow_dtype_for(logical_by_column[column]),
325
+ )
326
+ for column in column_order
327
+ }
328
+ return pd.DataFrame(data, columns=column_order, index=range(len(rows)))
329
+
330
+
331
+ def _rows_from_frame(frame: Any) -> list[dict[str, Any]]:
332
+ """Convert an Arrow-backed DataFrame back to an ordered native ``list[dict]``.
333
+
334
+ The index is dropped (positional order); each column is read through pyarrow
335
+ ``to_pylist()`` so an Arrow null becomes Python ``None`` and every scalar is a native
336
+ Python builtin (``bool``/``int``/``float``/``str``/``datetime.date``/UTC
337
+ ``datetime.datetime``) — no numpy scalar, no ``pd.NA``. Column (key) order follows the
338
+ frame's column order. ``pipeline``'s
339
+ ``_coerce_native_rows`` remains the shared backstop downstream.
340
+ """
341
+
342
+ column_order = list(frame.columns)
343
+ native_by_column = {
344
+ column: frame[column].array.__arrow_array__().to_pylist() for column in column_order
345
+ }
346
+ row_count = len(frame)
347
+ return [
348
+ {column: native_by_column[column][index] for column in column_order}
349
+ for index in range(row_count)
350
+ ]