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,281 @@
1
+ """The device credential this machine stores, exchanged for a short-lived Studio token.
2
+
3
+ The flow is the one ADR 0015 settled and ``mr-data login`` already writes half of: a durable
4
+ ``mr_cli_`` device key lives at ``~/.mostlyright/credentials`` at mode 0600, and every
5
+ cloud-touching command trades it at Cloud's ``/api/cli/studio-token`` for a token that expires in
6
+ minutes and names the workspace it is good for. The device key never reaches Studio and the
7
+ Studio token is never written to disk.
8
+
9
+ ⚠ THE 503 RETRY IS NOT OPTIONAL POLISH. Studio scales to zero, and Cloud provisions this
10
+ workspace's tenancy on every exchange, so the request that WAKES Studio is the request that has to
11
+ wait for it. Cloud distinguishes the two 503 shapes by stamping ``retry-after`` on the one worth
12
+ retrying and nothing on the one no amount of waiting can fix, so the ABSENCE of the header is the
13
+ signal to stop. This mirrors ``hosted_deploy.exchange_studio_token``, which learned it from eight
14
+ recorded production failures on 2026-08-20.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import re
20
+ import time
21
+ from collections.abc import Callable, Mapping
22
+ from dataclasses import dataclass
23
+ from datetime import UTC, datetime
24
+ from typing import Any
25
+ from urllib.parse import quote, urlencode
26
+ from uuid import UUID
27
+
28
+ from mostlyright.data_harness.canonical import CanonicalJSONError, parse_json
29
+ from mostlyright.data_harness.thin.transport import (
30
+ MAX_JSON_RESPONSE_BYTES,
31
+ ThinLaneError,
32
+ Transport,
33
+ UrlLibTransport,
34
+ service_origin,
35
+ )
36
+ from mostlyright.data_harness.ux.credentials import ResolvedCloudCredentials
37
+
38
+ #: Cloud's token-exchange route. Held identical to ``hosted_deploy.CLOUD_TOKEN_PATH`` by a test.
39
+ CLOUD_TOKEN_PATH = "/api/cli/studio-token"
40
+
41
+ #: The Cloud page a person opens about one run. ADR 0021 makes the live run view the place a
42
+ #: hosted submission points at, and ``mostlyright-cloud`` serves it at this route
43
+ #: (``dashboard/app/dashboard/runs/[runId]/page.tsx``). The origin is never hardcoded beyond the
44
+ #: configured Cloud URL, which ``MOSTLYRIGHT_CLOUD_URL`` overrides and the stored credential
45
+ #: carries.
46
+ CLOUD_RUN_DASHBOARD_PATH = "/dashboard/runs/{run_id}"
47
+
48
+ #: The durable Cloud home of one Dataset. Research starts here before a session exists, then the
49
+ #: same page follows the exact session run through :func:`dataset_research_dashboard_url`.
50
+ CLOUD_DATASET_DASHBOARD_PATH = "/dashboard/datasets/{dataset_id}"
51
+
52
+ #: The Cloud page a person opens to settle one approval. ``mostlyright-cloud`` serves it at
53
+ #: ``dashboard/app/dashboard/approvals/[approvalRequestId]/page.tsx`` -- the sibling of the run
54
+ #: page above, and the address ``mr-data propose`` prints for the human who must grant the Table
55
+ #: recipe approval it opened. It is where a person goes; the actual step-up or editor confirmation
56
+ #: is what ``mr-data recipe-approve`` records once they have.
57
+ CLOUD_APPROVAL_DASHBOARD_PATH = "/dashboard/approvals/{approval_request_id}"
58
+
59
+ MAX_TOKEN_RESPONSE_BYTES = 32 * 1024
60
+ TOKEN_EXCHANGE_RETRY_ATTEMPTS = 2
61
+ TOKEN_EXCHANGE_MAX_RETRY_SECONDS = 30.0
62
+
63
+ _CLI_KEY = re.compile(r"^mr_cli_[A-Za-z0-9_-]{64}$")
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class StudioSession:
68
+ """One short-lived Studio authority: where to call, as whom, and until when."""
69
+
70
+ studio_base_url: str
71
+ workspace_id: UUID
72
+ token: str
73
+ expires_at: datetime
74
+ cloud_url: str
75
+
76
+ def __repr__(self) -> str:
77
+ return (
78
+ "StudioSession("
79
+ f"studio_base_url={self.studio_base_url!r}, workspace_id={self.workspace_id!r}, "
80
+ f"token='<redacted>', expires_at={self.expires_at!r}, cloud_url={self.cloud_url!r})"
81
+ )
82
+
83
+ def authorization(self) -> dict[str, str]:
84
+ """The bearer header every Studio call in this lane carries, and nothing else."""
85
+
86
+ return {"Authorization": f"Bearer {self.token}"}
87
+
88
+ def to_receipt(self) -> dict[str, Any]:
89
+ """Everything about this session except the token."""
90
+
91
+ return {
92
+ "studio_base_url": self.studio_base_url,
93
+ "workspace_id": str(self.workspace_id),
94
+ "expires_at": self.expires_at.isoformat().replace("+00:00", "Z"),
95
+ }
96
+
97
+
98
+ def run_dashboard_url(cloud_url: str, run_id: str) -> str:
99
+ """The Cloud live-run page for one run, from one validated origin."""
100
+
101
+ origin = service_origin(cloud_url, "Cloud URL", allow_loopback_http=False)
102
+ return origin + CLOUD_RUN_DASHBOARD_PATH.format(run_id=run_id)
103
+
104
+
105
+ def dataset_dashboard_url(cloud_url: str, dataset_id: str) -> str:
106
+ """The Cloud detail page for one Dataset, with its identifier held to one path segment."""
107
+
108
+ origin = service_origin(cloud_url, "Cloud URL", allow_loopback_http=False)
109
+ encoded_dataset_id = quote(str(dataset_id), safe="")
110
+ return origin + CLOUD_DATASET_DASHBOARD_PATH.format(dataset_id=encoded_dataset_id)
111
+
112
+
113
+ def dataset_research_dashboard_url(cloud_url: str, dataset_id: str, run_id: str) -> str:
114
+ """The exact research run embedded in one Dataset detail page.
115
+
116
+ Path and query coordinates are encoded independently so neither can introduce a sibling path,
117
+ another query member, or a fragment. The fragment is the fixed UI section identifier.
118
+ """
119
+
120
+ base = dataset_dashboard_url(cloud_url, dataset_id)
121
+ query = urlencode({"run": str(run_id)}, quote_via=quote, safe="")
122
+ return f"{base}?{query}#research"
123
+
124
+
125
+ def approval_dashboard_url(cloud_url: str, approval_request_id: str) -> str:
126
+ """The Cloud approvals page for one approval request, from the same validated origin.
127
+
128
+ The sibling of :func:`run_dashboard_url`: ``mr-data propose`` opens a Table recipe approval
129
+ and hands a person this address to settle it, the way ``mr-data approve`` prints the human
130
+ action Studio names. Both go through :func:`service_origin`, so the origin is checked once and
131
+ the two surfaces cannot drift into two spellings of one Cloud URL.
132
+ """
133
+
134
+ origin = service_origin(cloud_url, "Cloud URL", allow_loopback_http=False)
135
+ return origin + CLOUD_APPROVAL_DASHBOARD_PATH.format(approval_request_id=approval_request_id)
136
+
137
+
138
+ def require_device_key(credentials: ResolvedCloudCredentials) -> None:
139
+ """Refuse a credential that is not an ``mr_cli_`` device key.
140
+
141
+ A live data key authenticates too, and it has read-only scope, so accepting one here would
142
+ trade a clear refusal now for an opaque 403 from Studio three calls later.
143
+ """
144
+
145
+ if _CLI_KEY.fullmatch(credentials.raw_key) is None:
146
+ raise ThinLaneError(
147
+ "THIN_AUTHENTICATION_FAILED",
148
+ "the hosted lane requires an mr_cli_ device credential; run mr-data login",
149
+ )
150
+
151
+
152
+ def _text(value: Mapping[str, Any], field: str) -> str:
153
+ selected = value.get(field)
154
+ if not isinstance(selected, str) or not selected:
155
+ raise ThinLaneError("THIN_RESPONSE_INVALID", f"the response omitted {field}")
156
+ return selected
157
+
158
+
159
+ def _uuid(value: str, field: str) -> UUID:
160
+ try:
161
+ return UUID(value)
162
+ except ValueError as error:
163
+ raise ThinLaneError("THIN_RESPONSE_INVALID", f"{field} is not a UUID") from error
164
+
165
+
166
+ def _timestamp(value: str, field: str) -> datetime:
167
+ try:
168
+ parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
169
+ except ValueError as error:
170
+ raise ThinLaneError("THIN_RESPONSE_INVALID", f"{field} is not a timestamp") from error
171
+ if parsed.tzinfo is None or parsed.utcoffset() != UTC.utcoffset(parsed):
172
+ raise ThinLaneError("THIN_RESPONSE_INVALID", f"{field} is not UTC")
173
+ return parsed
174
+
175
+
176
+ def _transient_retry_seconds(headers: Mapping[str, str]) -> float | None:
177
+ """Seconds Cloud asked this command to wait, or ``None`` when it did not ask at all.
178
+
179
+ ⚠ The absence of ``retry-after`` is the signal, not a missing detail to paper over: Cloud's
180
+ cold-start 503 stamps it and its "Studio is not configured here" 503 does not. A malformed or
181
+ out-of-range value fails toward refusing now rather than sleeping on a number we do not trust.
182
+ """
183
+
184
+ raw = headers.get("retry-after", "")
185
+ if not raw.isdecimal():
186
+ return None
187
+ seconds = int(raw)
188
+ if not 1 <= seconds <= 3600:
189
+ return None
190
+ return float(min(seconds, TOKEN_EXCHANGE_MAX_RETRY_SECONDS))
191
+
192
+
193
+ def open_studio_session(
194
+ credentials: ResolvedCloudCredentials,
195
+ *,
196
+ transport: Transport | None = None,
197
+ now: Callable[[], datetime] = lambda: datetime.now(UTC),
198
+ sleep: Callable[[float], None] = time.sleep,
199
+ ) -> StudioSession:
200
+ """Exchange the stored device key for a short-lived Studio token."""
201
+
202
+ require_device_key(credentials)
203
+ cloud_url = service_origin(credentials.cloud_url, "Cloud URL", allow_loopback_http=False)
204
+ selected = transport or UrlLibTransport()
205
+ attempts_remaining = TOKEN_EXCHANGE_RETRY_ATTEMPTS
206
+ while True:
207
+ try:
208
+ status, raw, headers = selected.request(
209
+ "POST",
210
+ f"{cloud_url}{CLOUD_TOKEN_PATH}",
211
+ {"Accept": "application/json", "x-api-key": credentials.raw_key},
212
+ b"",
213
+ MAX_TOKEN_RESPONSE_BYTES,
214
+ )
215
+ except ThinLaneError:
216
+ raise
217
+ except Exception as error:
218
+ raise ThinLaneError(
219
+ "THIN_TOKEN_EXCHANGE_FAILED", "the Cloud token exchange could not be reached"
220
+ ) from error
221
+ if status != 503 or attempts_remaining == 0:
222
+ break
223
+ delay = _transient_retry_seconds(headers)
224
+ if delay is None:
225
+ break
226
+ attempts_remaining -= 1
227
+ sleep(delay)
228
+ if status == 401:
229
+ raise ThinLaneError(
230
+ "THIN_AUTHENTICATION_FAILED", "the stored device credential was rejected"
231
+ )
232
+ if status == 402:
233
+ raise ThinLaneError(
234
+ "THIN_SUBSCRIPTION_REQUIRED", "this workspace has no hosted execution access"
235
+ )
236
+ if status == 429:
237
+ raise ThinLaneError("THIN_RATE_LIMITED", "Cloud asked this command to retry later")
238
+ if status == 503:
239
+ raise ThinLaneError(
240
+ "THIN_STUDIO_UNAVAILABLE",
241
+ "Studio is not configured, or stayed unavailable across every attempt",
242
+ )
243
+ try:
244
+ parsed = parse_json(raw)
245
+ except CanonicalJSONError:
246
+ parsed = None
247
+ if status != 200 or not isinstance(parsed, dict):
248
+ raise ThinLaneError(
249
+ "THIN_TOKEN_EXCHANGE_FAILED", f"the Cloud token exchange failed (HTTP {status})"
250
+ )
251
+ expires_at = _timestamp(_text(parsed, "expires_at"), "expires_at")
252
+ if expires_at <= now():
253
+ raise ThinLaneError("THIN_TOKEN_EXCHANGE_FAILED", "Cloud returned an expired Studio token")
254
+ return StudioSession(
255
+ studio_base_url=service_origin(
256
+ _text(parsed, "studio_base_url"), "Studio base URL", allow_loopback_http=True
257
+ ),
258
+ workspace_id=_uuid(_text(parsed, "workspace_id"), "workspace_id"),
259
+ token=_text(parsed, "token"),
260
+ expires_at=expires_at,
261
+ cloud_url=cloud_url,
262
+ )
263
+
264
+
265
+ __all__ = [
266
+ "CLOUD_APPROVAL_DASHBOARD_PATH",
267
+ "CLOUD_DATASET_DASHBOARD_PATH",
268
+ "CLOUD_RUN_DASHBOARD_PATH",
269
+ "CLOUD_TOKEN_PATH",
270
+ "MAX_JSON_RESPONSE_BYTES",
271
+ "MAX_TOKEN_RESPONSE_BYTES",
272
+ "TOKEN_EXCHANGE_MAX_RETRY_SECONDS",
273
+ "TOKEN_EXCHANGE_RETRY_ATTEMPTS",
274
+ "StudioSession",
275
+ "approval_dashboard_url",
276
+ "dataset_dashboard_url",
277
+ "dataset_research_dashboard_url",
278
+ "open_studio_session",
279
+ "require_device_key",
280
+ "run_dashboard_url",
281
+ ]