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,519 @@
1
+ """The Studio V3 run calls the thin lane makes: start one, read one, and fetch what it produced.
2
+
3
+ Written against the contract rather than against the generated client, and that is a decision
4
+ rather than an omission. The generated client is an optional extra (``mostlyright-studio-client-v3``
5
+ arrives with ``[hosted]``), it pulls ``httpx`` and its whole closure, and its own
6
+ ``stream_run_events`` buffers the entire Server-Sent-Events body into one ``str`` before returning
7
+ it -- unusable for a live stream by construction. A thin client that must run with no third-party
8
+ package at all therefore speaks the wire directly, and :data:`PINNED_CONTRACT_VERSION` plus the
9
+ route table below are what a reviewer diffs against ``contracts/openapi/studio-v3.yaml``.
10
+
11
+ Every route here is a READ or a start. Nothing in this module cancels, retries, approves or
12
+ releases: those are authority-bearing mutations whose refusals belong with the operator surfaces
13
+ that already own them. The three lanes that DO write --
14
+ :mod:`mostlyright.data_harness.thin.acquire`, which opens a hosted acquisition session,
15
+ :mod:`mostlyright.data_harness.thin.approvals`, which records an ask a human must still settle,
16
+ and :mod:`mostlyright.data_harness.thin.narrative`, which appends one unsealed cell of prose --
17
+ keep their own routes in their own modules and share only :class:`StudioApiClient`, so this
18
+ module's promise stays checkable by reading it.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import re
24
+ import secrets
25
+ from collections.abc import Mapping
26
+ from typing import Any
27
+ from urllib.parse import urlencode
28
+ from uuid import UUID
29
+
30
+ from mostlyright.data_harness.canonical import (
31
+ CanonicalJSONError,
32
+ canonical_json_bytes,
33
+ parse_foreign_json,
34
+ )
35
+ from mostlyright.data_harness.thin.session import StudioSession
36
+ from mostlyright.data_harness.thin.transport import (
37
+ MAX_JSON_RESPONSE_BYTES,
38
+ ThinLaneError,
39
+ Transport,
40
+ UrlLibTransport,
41
+ signed_transfer_url,
42
+ )
43
+
44
+ #: The contract version every payload below is written against. ``studio_boundary`` pins the
45
+ #: generated client to the same 3.0.0 and to one OpenAPI source digest; this constant is the thin
46
+ #: lane's half of that pin, and ``tests/test_thin_client.py`` holds the two equal.
47
+ PINNED_CONTRACT_VERSION = "3.0.0"
48
+
49
+ #: The routes. Kept as one table because it is the thing a reviewer checks against the contract.
50
+ START_RUN_PATH = "/v3/runs"
51
+ #: The same collection, read rather than added to. Written out rather than aliased to
52
+ #: :data:`START_RUN_PATH`, because the cross-repo gate checks each name against its own generated
53
+ #: operation and an alias would check one of them twice.
54
+ LIST_RUNS_PATH = "/v3/runs"
55
+ GET_RUN_PATH = "/v3/runs/{run_id}"
56
+ RUN_EVENTS_STREAM_PATH = "/v3/runs/{run_id}/events:stream"
57
+ RUN_EXECUTION_SUMMARY_PATH = "/v3/runs/{run_id}/execution-summary"
58
+ RUN_CANDIDATE_EVIDENCE_PATH = "/v3/runs/{run_id}/candidate-evidence"
59
+ RUN_AGENT_EVIDENCE_PATH = "/v3/runs/{run_id}/agent-evidence"
60
+ RUN_FINDINGS_PATH = "/v3/runs/{run_id}/findings"
61
+ #: Studio renders this itself, from the run's event log and from nothing else, once the run is
62
+ #: terminal. It is the whole reason the vendored client moved to 46e64f1; faa7dbb after it is what
63
+ #: made `acquire`, `approve` and `recipe-approve` writable, and fd14830 after that is what made
64
+ #: `note` able to put a sentence into the log this document is rendered from.
65
+ RUN_NOTEBOOK_PATH = "/v3/runs/{run_id}/notebook"
66
+ TABLE_VERSIONS_PATH = "/v3/tables/{table_id}/versions"
67
+ TABLE_VERSION_PATH = "/v3/tables/{table_id}/versions/{table_version_id}"
68
+ TABLE_VERSION_PREVIEW_PATH = "/v3/tables/{table_id}/versions/{table_version_id}/preview"
69
+ TABLE_VERSION_DOWNLOAD_PATH = "/v3/tables/{table_id}/versions/{table_version_id}/download"
70
+ ARTIFACT_DOWNLOAD_SESSION_PATH = "/v3/artifacts/{artifact_id}/download-session"
71
+ #: Two reads every lane that touches a recipe proposal shares. The proposal itself, for its status
72
+ #: and the ETag its approval is pinned to; and the Build an approval STARTED, which Studio #124
73
+ #: made possible: the dashboard's confirmation can start the first hosted Build in the same
74
+ #: transaction, after which the proposal is `activation_started`, `POST /v3/runs` refuses it, and
75
+ #: this projection is the only way to recover the run that already exists. It answers 404 while
76
+ #: the approval is undecided or unstarted, and that 404 is an ordinary answer here.
77
+ GET_RECIPE_PROPOSAL_PATH = "/v3/recipe-proposals/{recipe_proposal_id}"
78
+ TABLE_RECIPE_APPROVAL_START_PATH = "/v3/table-recipe-approvals/{approval_request_id}/start"
79
+
80
+ #: The Run statuses this contract admits (``run.schema.json#/properties/status``). Held equal to
81
+ #: the schema's own enum by a cross-repo gate, so a filter can never name a status no Run holds.
82
+ RUN_STATUSES = (
83
+ "queued",
84
+ "producer_retry_pending",
85
+ "running",
86
+ "awaiting_candidate_selection",
87
+ "verifying",
88
+ "verifier_retry_pending",
89
+ "releasable",
90
+ "released",
91
+ "repair_required",
92
+ "failed",
93
+ "cancelled",
94
+ "abandoned",
95
+ "human_direction_required",
96
+ )
97
+
98
+ #: Studio's own ceiling on one page of any V3 list (``components.parameters.Limit``). Asking for
99
+ #: more is a 422, so it is bounded here and the refusal is a sentence rather than a round trip.
100
+ MAX_LIST_LIMIT = 200
101
+
102
+ #: ``kind`` values a start-run request may name. The Run resource itself admits more (a successor
103
+ #: or a repair successor is created by Studio, never asked for from out here).
104
+ START_RUN_KINDS = ("initial_build", "incremental_refresh", "bounded_backfill")
105
+
106
+ #: The closed capacity vocabulary a start-run may name
107
+ #: (``common.schema.json#/$defs/resource_class``).
108
+ RESOURCE_CLASSES = ("standard", "large")
109
+
110
+ #: ``expected_recipe_digest`` is the one digest in this contract carried WITHOUT the ``sha256:``
111
+ #: prefix. Refusing a prefixed value here turns a silent 422 from Studio into a local sentence.
112
+ _RECIPE_DIGEST = re.compile(r"^[0-9a-f]{64}$")
113
+ _PREFIXED_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
114
+
115
+ #: ``common.schema.json#/$defs/idempotency_key``.
116
+ _IDEMPOTENCY_KEY = re.compile(r"^[A-Za-z0-9._:-]{16,128}$")
117
+
118
+
119
+ def new_idempotency_key(prefix: str = "mr-data") -> str:
120
+ """A fresh idempotency key for one submission.
121
+
122
+ Random rather than derived from the request body: two deliberate submissions of the same
123
+ recipe are two runs, and a content-derived key would silently return the first one's Run and
124
+ look like a success.
125
+ """
126
+
127
+ key = f"{prefix}-{secrets.token_hex(16)}"
128
+ if _IDEMPOTENCY_KEY.fullmatch(key) is None: # pragma: no cover - the format is fixed above
129
+ raise ThinLaneError("THIN_REQUEST_INVALID", "the generated idempotency key is malformed")
130
+ return key
131
+
132
+
133
+ def start_run_body(
134
+ *,
135
+ workspace_id: UUID,
136
+ expected_recipe_digest: str,
137
+ recipe_proposal_id: UUID,
138
+ resource_class: str | None = None,
139
+ ) -> dict[str, Any]:
140
+ """The ``initial_build`` start-run body, with the contract's conditional applied here.
141
+
142
+ ⚠ ``initial_build`` is exclusive. The contract requires ``recipe_proposal_id`` and FORBIDS
143
+ ``dataset_id``, ``table_id`` and ``requested_range``; the other two kinds invert that exactly.
144
+ This builder covers the initial build only, so the forbidden trio can never be added by
145
+ accident, and a caller that needs a refresh or a backfill gets a builder of its own rather
146
+ than a flag on this one.
147
+ """
148
+
149
+ if _PREFIXED_DIGEST.fullmatch(expected_recipe_digest):
150
+ raise ThinLaneError(
151
+ "THIN_REQUEST_INVALID",
152
+ "expected_recipe_digest is bare lowercase hex in this contract, without a sha256: "
153
+ "prefix",
154
+ )
155
+ if _RECIPE_DIGEST.fullmatch(expected_recipe_digest) is None:
156
+ raise ThinLaneError(
157
+ "THIN_REQUEST_INVALID", "expected_recipe_digest is not a 64-character hex digest"
158
+ )
159
+ if resource_class is not None and resource_class not in RESOURCE_CLASSES:
160
+ allowed = ", ".join(RESOURCE_CLASSES)
161
+ raise ThinLaneError("THIN_REQUEST_INVALID", f"resource_class must be one of: {allowed}")
162
+ body: dict[str, Any] = {
163
+ "workspace_id": str(workspace_id),
164
+ "expected_recipe_digest": expected_recipe_digest,
165
+ "kind": "initial_build",
166
+ "recipe_proposal_id": str(recipe_proposal_id),
167
+ }
168
+ if resource_class is not None:
169
+ body["resource_class"] = resource_class
170
+ return body
171
+
172
+
173
+ class StudioApiClient:
174
+ """One bearer-authenticated V3 call, and the refusal it becomes when it does not answer.
175
+
176
+ Extracted from :class:`StudioRunClient` rather than copied, because the two lanes that came
177
+ after it -- hosted acquisition and the approval ask -- speak the same wire under the same
178
+ hardening, and a second ``_call`` would be a second place for the byte ceiling, the JSON
179
+ hardening and the Studio-code translation to drift apart. Subclasses add routes; none of them
180
+ adds a transport.
181
+ """
182
+
183
+ def __init__(self, session: StudioSession, *, transport: Transport | None = None) -> None:
184
+ self._session = session
185
+ self._transport = transport or UrlLibTransport()
186
+
187
+ @property
188
+ def session(self) -> StudioSession:
189
+ return self._session
190
+
191
+ @property
192
+ def transport(self) -> Transport:
193
+ """The bounded transport this client speaks through.
194
+
195
+ Exposed so a signed download made ON BEHALF of a call this client made goes out over the
196
+ same substituted seam -- otherwise a test that fakes Studio would still reach the network
197
+ for the bytes Studio pointed it at.
198
+ """
199
+
200
+ return self._transport
201
+
202
+ def _call(
203
+ self,
204
+ method: str,
205
+ path: str,
206
+ *,
207
+ body: Mapping[str, Any] | None = None,
208
+ extra_headers: Mapping[str, str] | None = None,
209
+ query: Mapping[str, Any] | None = None,
210
+ expected: tuple[int, ...],
211
+ shape: type = dict,
212
+ response_headers: dict[str, str] | None = None,
213
+ ) -> Any:
214
+ headers = {"Accept": "application/json", **self._session.authorization()}
215
+ if extra_headers:
216
+ headers.update(extra_headers)
217
+ raw_body: bytes | None = None
218
+ if body is not None:
219
+ headers["Content-Type"] = "application/json"
220
+ raw_body = canonical_json_bytes(body)
221
+ url = f"{self._session.studio_base_url}{path}{_query_string(query)}"
222
+ try:
223
+ status, raw, answered_headers = self._transport.request(
224
+ method, url, headers, raw_body, MAX_JSON_RESPONSE_BYTES
225
+ )
226
+ if response_headers is not None:
227
+ response_headers.update(answered_headers)
228
+ except ThinLaneError:
229
+ raise
230
+ except Exception as error:
231
+ raise ThinLaneError(
232
+ "THIN_STUDIO_UNREACHABLE", f"Studio did not answer {path}"
233
+ ) from error
234
+ try:
235
+ # ⚠ `parse_foreign_json`, NOT `parse_json`. Every hardening stays -- duplicate keys,
236
+ # byte-order marks, non-strict UTF-8, NaN, out-of-range integers, depth and member
237
+ # ceilings are all refused identically -- and the single difference is that a finite
238
+ # fractional number is admissible. It has to be: a table preview's rows carry the
239
+ # table's own values, and a temperature column is not integers. `parse_json` refused
240
+ # the whole response for one `20.5`, which turned a perfectly good preview into
241
+ # "Studio did not answer with an object". Nothing read here can be sealed either way:
242
+ # `canonical_json_bytes` still refuses a float, so a value parsed here can be shown
243
+ # and can never reach a digest.
244
+ parsed = parse_foreign_json(raw)
245
+ except CanonicalJSONError:
246
+ parsed = None
247
+ if status not in expected:
248
+ raise _studio_refusal(status, parsed, path)
249
+ if not isinstance(parsed, shape):
250
+ wanted = "a list" if shape is list else "an object"
251
+ raise ThinLaneError("THIN_RESPONSE_INVALID", f"{path} did not answer with {wanted}")
252
+ return parsed
253
+
254
+ def _call_list(
255
+ self, path: str, *, query: Mapping[str, Any] | None = None
256
+ ) -> list[dict[str, Any]]:
257
+ """One V3 list route, which answers with a JSON array rather than with an object.
258
+
259
+ Separate from :meth:`_call` rather than folded into it with a flag: every list route in
260
+ this contract answers with a bare array and every single-resource route answers with an
261
+ object, so a reader of either method knows which shape it is looking at without checking a
262
+ parameter. Non-object members are dropped rather than refused -- a list is a page of
263
+ resources, and one malformed member is not a reason to refuse the page -- but the array
264
+ itself being absent is.
265
+ """
266
+
267
+ parsed = self._call("GET", path, query=query, expected=(200,), shape=list)
268
+ return [item for item in parsed if isinstance(item, dict)]
269
+
270
+ def recipe_proposal(
271
+ self, recipe_proposal_id: str, *, response_headers: dict[str, str] | None = None
272
+ ) -> dict[str, Any]:
273
+ """One recipe proposal as Studio holds it, with the ETag it answered under."""
274
+
275
+ return self._call(
276
+ "GET",
277
+ GET_RECIPE_PROPOSAL_PATH.format(recipe_proposal_id=recipe_proposal_id),
278
+ expected=(200,),
279
+ response_headers=response_headers,
280
+ )
281
+
282
+ def approval_start(self, approval_request_id: str) -> dict[str, Any] | None:
283
+ """The Build a Table recipe approval started, or ``None`` while it has started none.
284
+
285
+ 404 is the ordinary answer for an approval that is undecided, unstarted, or local-build,
286
+ and it is returned as ``None`` rather than raised: every caller asks this question to
287
+ decide between "print the build command" and "print the run that already exists".
288
+ """
289
+
290
+ try:
291
+ return self._call(
292
+ "GET",
293
+ TABLE_RECIPE_APPROVAL_START_PATH.format(approval_request_id=approval_request_id),
294
+ expected=(200,),
295
+ )
296
+ except ThinLaneError as refusal:
297
+ if refusal.code == "THIN_NOT_FOUND":
298
+ return None
299
+ raise
300
+
301
+
302
+ class StudioRunClient(StudioApiClient):
303
+ """Bearer-authenticated V3 reads and one start, over the thin transport."""
304
+
305
+ def start_initial_build(
306
+ self,
307
+ *,
308
+ expected_recipe_digest: str,
309
+ recipe_proposal_id: UUID,
310
+ resource_class: str | None = None,
311
+ idempotency_key: str | None = None,
312
+ ) -> dict[str, Any]:
313
+ """Queue one hosted initial build and return Studio's Run."""
314
+
315
+ return self._call(
316
+ "POST",
317
+ START_RUN_PATH,
318
+ body=start_run_body(
319
+ workspace_id=self._session.workspace_id,
320
+ expected_recipe_digest=expected_recipe_digest,
321
+ recipe_proposal_id=recipe_proposal_id,
322
+ resource_class=resource_class,
323
+ ),
324
+ extra_headers={"Idempotency-Key": idempotency_key or new_idempotency_key()},
325
+ expected=(202,),
326
+ )
327
+
328
+ def get_run(self, run_id: str) -> dict[str, Any]:
329
+ return self._call("GET", GET_RUN_PATH.format(run_id=run_id), expected=(200,))
330
+
331
+ def list_runs(
332
+ self,
333
+ *,
334
+ status: str | None = None,
335
+ limit: int | None = None,
336
+ dataset_id: str | None = None,
337
+ question_id: str | None = None,
338
+ created_after: str | None = None,
339
+ cursor: str | None = None,
340
+ ) -> list[dict[str, Any]]:
341
+ """One page of this workspace's runs, newest filters applied by Studio rather than here.
342
+
343
+ The workspace is the bearer token's, never a parameter: this contract scopes every list to
344
+ the authority that asked, and passing a workspace would be asking for one we might not
345
+ hold.
346
+ """
347
+
348
+ if status is not None and status not in RUN_STATUSES:
349
+ raise ThinLaneError(
350
+ "THIN_REQUEST_INVALID",
351
+ f"{status!r} is not a run state; this contract admits " + ", ".join(RUN_STATUSES),
352
+ )
353
+ if limit is not None and not 1 <= limit <= MAX_LIST_LIMIT:
354
+ raise ThinLaneError(
355
+ "THIN_REQUEST_INVALID", f"how many runs to list must be from 1 to {MAX_LIST_LIMIT}"
356
+ )
357
+ return self._call_list(
358
+ LIST_RUNS_PATH,
359
+ query={
360
+ "status": status,
361
+ "limit": limit,
362
+ "dataset_id": dataset_id,
363
+ "question_id": question_id,
364
+ "created_after": created_after,
365
+ "cursor": cursor,
366
+ },
367
+ )
368
+
369
+ def run_findings(self, run_id: str) -> list[dict[str, Any]]:
370
+ return self._call_list(RUN_FINDINGS_PATH.format(run_id=run_id))
371
+
372
+ def run_agent_evidence(self, run_id: str) -> list[dict[str, Any]]:
373
+ return self._call_list(RUN_AGENT_EVIDENCE_PATH.format(run_id=run_id))
374
+
375
+ def run_notebook(self, run_id: str) -> dict[str, Any]:
376
+ """The notebook Studio rendered for one terminal run, and a signed download per document.
377
+
378
+ 404 here is an ordinary answer rather than a fault: a run that has not finished has no
379
+ notebook, because the live view is the stream. The caller turns that into the sentence.
380
+ """
381
+
382
+ return self._call("GET", RUN_NOTEBOOK_PATH.format(run_id=run_id), expected=(200,))
383
+
384
+ def table_versions(self, table_id: str) -> list[dict[str, Any]]:
385
+ return self._call_list(TABLE_VERSIONS_PATH.format(table_id=table_id))
386
+
387
+ def table_version(self, table_id: str, table_version_id: str) -> dict[str, Any]:
388
+ return self._call(
389
+ "GET",
390
+ TABLE_VERSION_PATH.format(table_id=table_id, table_version_id=table_version_id),
391
+ expected=(200,),
392
+ )
393
+
394
+ def table_version_preview(self, table_id: str, table_version_id: str) -> dict[str, Any]:
395
+ """The bounded preview Studio publishes beside one released version.
396
+
397
+ Bounded by Studio, not by this client: the preview is an artifact the release wrote, so a
398
+ caller can narrow what it shows and can never widen it.
399
+ """
400
+
401
+ return self._call(
402
+ "GET",
403
+ TABLE_VERSION_PREVIEW_PATH.format(table_id=table_id, table_version_id=table_version_id),
404
+ expected=(200,),
405
+ )
406
+
407
+ def execution_summary(self, run_id: str) -> dict[str, Any]:
408
+ return self._call("GET", RUN_EXECUTION_SUMMARY_PATH.format(run_id=run_id), expected=(200,))
409
+
410
+ def candidate_evidence(self, run_id: str) -> dict[str, Any]:
411
+ return self._call("GET", RUN_CANDIDATE_EVIDENCE_PATH.format(run_id=run_id), expected=(200,))
412
+
413
+ def table_version_download(self, table_id: str, table_version_id: str) -> dict[str, Any]:
414
+ return self._call(
415
+ "GET",
416
+ TABLE_VERSION_DOWNLOAD_PATH.format(
417
+ table_id=table_id, table_version_id=table_version_id
418
+ ),
419
+ expected=(200,),
420
+ )
421
+
422
+ def artifact_download_session(self, artifact_id: str) -> dict[str, Any]:
423
+ """Ask Studio to sign one short-lived download of one artifact.
424
+
425
+ The session is created, never chosen: kind, purpose and classification come from sealed
426
+ server metadata, so this call carries no body at all beyond its idempotency key.
427
+ """
428
+
429
+ session = self._call(
430
+ "POST",
431
+ ARTIFACT_DOWNLOAD_SESSION_PATH.format(artifact_id=artifact_id),
432
+ body={},
433
+ extra_headers={"Idempotency-Key": new_idempotency_key("mr-data-dl")},
434
+ expected=(201,),
435
+ )
436
+ if session.get("direction") != "download" or session.get("method") != "GET":
437
+ raise ThinLaneError(
438
+ "THIN_RESPONSE_INVALID", "Studio signed a session that is not a download"
439
+ )
440
+ signed_transfer_url(session.get("signed_url", ""), "the signed download URL")
441
+ return session
442
+
443
+
444
+ def _query_string(query: Mapping[str, Any] | None) -> str:
445
+ """Render the filters one list route was given, and nothing for the ones it was not.
446
+
447
+ ``None`` means "not asked for" and is dropped rather than sent empty: this contract's list
448
+ parameters are all optional, and `?status=` is a different request from no `status` at all.
449
+ Sorted, so two identical requests produce one identical URL -- which is what makes a recorded
450
+ request in a test an assertion about the request rather than about dict ordering.
451
+ """
452
+
453
+ if not query:
454
+ return ""
455
+ selected = {key: value for key, value in sorted(query.items()) if value is not None}
456
+ if not selected:
457
+ return ""
458
+ return "?" + urlencode({key: str(value) for key, value in selected.items()})
459
+
460
+
461
+ def _studio_refusal(status: int, parsed: Any, path: str) -> ThinLaneError:
462
+ """Turn one ``application/problem+json`` body into a typed refusal.
463
+
464
+ Studio's error code is a closed vocabulary and is far more useful than the status, so it is
465
+ carried through verbatim as the refusal detail's first token. The message is Studio's own and
466
+ is bounded by its contract at 2000 characters; it is not reformatted here.
467
+ """
468
+
469
+ studio_code = parsed.get("code") if isinstance(parsed, dict) else None
470
+ if not isinstance(studio_code, str):
471
+ studio_code = None
472
+ if status in {401, 403}:
473
+ refusal = ThinLaneError(
474
+ "THIN_AUTHORIZATION_DENIED",
475
+ "the Studio token does not carry the authority this call needs",
476
+ )
477
+ elif status == 404:
478
+ refusal = ThinLaneError("THIN_NOT_FOUND", f"Studio has no such resource for {path}")
479
+ elif studio_code is not None:
480
+ message = parsed.get("message") if isinstance(parsed, dict) else None
481
+ detail = message if isinstance(message, str) and message else "Studio refused the call"
482
+ refusal = ThinLaneError(f"THIN_STUDIO_{studio_code}", detail)
483
+ else:
484
+ refusal = ThinLaneError("THIN_STUDIO_REFUSED", f"Studio answered {path} with HTTP {status}")
485
+ # The status and Studio's own code ride on the refusal as facts about the answer, beside the
486
+ # translated code a script branches on: a write lane deciding whether a refused request was
487
+ # applied needs to tell a validation 422 from a gateway 504 carrying the same translated code.
488
+ refusal.http_status = status # type: ignore[attr-defined]
489
+ refusal.studio_code = studio_code # type: ignore[attr-defined]
490
+ return refusal
491
+
492
+
493
+ __all__ = [
494
+ "ARTIFACT_DOWNLOAD_SESSION_PATH",
495
+ "GET_RECIPE_PROPOSAL_PATH",
496
+ "GET_RUN_PATH",
497
+ "LIST_RUNS_PATH",
498
+ "MAX_LIST_LIMIT",
499
+ "PINNED_CONTRACT_VERSION",
500
+ "RESOURCE_CLASSES",
501
+ "RUN_AGENT_EVIDENCE_PATH",
502
+ "RUN_CANDIDATE_EVIDENCE_PATH",
503
+ "RUN_EVENTS_STREAM_PATH",
504
+ "RUN_EXECUTION_SUMMARY_PATH",
505
+ "RUN_FINDINGS_PATH",
506
+ "RUN_NOTEBOOK_PATH",
507
+ "RUN_STATUSES",
508
+ "START_RUN_KINDS",
509
+ "START_RUN_PATH",
510
+ "TABLE_RECIPE_APPROVAL_START_PATH",
511
+ "TABLE_VERSIONS_PATH",
512
+ "TABLE_VERSION_DOWNLOAD_PATH",
513
+ "TABLE_VERSION_PATH",
514
+ "TABLE_VERSION_PREVIEW_PATH",
515
+ "StudioApiClient",
516
+ "StudioRunClient",
517
+ "new_idempotency_key",
518
+ "start_run_body",
519
+ ]