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,312 @@
1
+ """Go-live target resolution: explicit-only, read-and-refuse.
2
+
3
+ A deploy target says where a live dataset would be published: which cloud project, which region,
4
+ which Studio service account and worker job, and which cloud endpoint verifies API keys. Getting
5
+ that answer wrong spends real money in a real project, so this module is deliberately unable to
6
+ guess it.
7
+
8
+ Two rules hold everywhere in this module:
9
+
10
+ * Nothing is inferred. Every field comes from a mapping a human named explicitly. The module
11
+ never consults ambient ``gcloud`` configuration, never reads an environment variable for target
12
+ identity, and never shells out.
13
+ * Nothing is created. The module never creates a project, never enables billing, and never
14
+ touches IAM. It reads a descriptor and either resolves it or refuses it.
15
+
16
+ A refusal is a typed, quiet outcome that names its reasons, not a crash.
17
+
18
+ Why the corroboration step exists
19
+ ---------------------------------
20
+
21
+ The rule for this project is that live staging happens only against an unambiguously verified
22
+ target, and that ambiguity means skip with a reason. On the machine this was written for, the
23
+ target is ambiguous, measured rather than assumed:
24
+
25
+ * ``gcloud config`` reports ``core/project=mostly-light``.
26
+ * ``gcloud config`` reports ``billing/quota_project=mostlyright-backend``.
27
+ * ``gcloud projects list`` returns PERMISSION_DENIED, so no third signal can break the tie.
28
+ * The active account is a personal one, with an inactive company account also credentialed.
29
+
30
+ Two config keys naming two different projects is exactly the case where a helpful default would
31
+ be a costly one. So :func:`preflight` compares the explicit descriptor against a corroborating
32
+ signal the caller supplies, and returns a refusal naming both values when they disagree. It never
33
+ picks a winner.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import re
39
+ from collections.abc import Mapping
40
+ from dataclasses import dataclass
41
+ from typing import Any
42
+ from urllib.parse import urlsplit
43
+
44
+ REQUIRED_FIELDS: tuple[str, ...] = (
45
+ "gcp_project_id",
46
+ "region",
47
+ "studio_service_account",
48
+ "studio_worker_job",
49
+ "cloud_verify_url",
50
+ )
51
+
52
+ CORROBORATION_FIELDS: tuple[str, ...] = ("source", "gcp_project_id", "unreadable_reason")
53
+
54
+ MAX_FIELD_LENGTH = 2048
55
+
56
+ # GCP's documented project-id shape: 6 to 30 characters, a lowercase letter first, then lowercase
57
+ # letters, digits, or hyphens, and never a trailing hyphen.
58
+ _PROJECT_ID = re.compile(r"^[a-z][a-z0-9-]{4,28}[a-z0-9]$")
59
+
60
+ # Cloud Run region shape, for example us-central1, europe-west4, asia-northeast1.
61
+ _REGION = re.compile(r"^[a-z]{2,}-[a-z]{2,}[1-9][0-9]?$")
62
+
63
+
64
+ class DeployTargetError(RuntimeError):
65
+ """A typed go-live target failure."""
66
+
67
+
68
+ class TargetAmbiguityError(DeployTargetError, ValueError):
69
+ """The target could not be established beyond doubt, so it is refused."""
70
+
71
+
72
+ def _https_url(value: str, label: str) -> str:
73
+ """Return ``value`` when it is a plain absolute https URL, otherwise refuse it.
74
+
75
+ This mirrors ``hosted_bootstrap._https_url`` (hosted_bootstrap.py:147) instead of importing
76
+ it. Importing that module pulls the entire hosted-worker stack (pyarrow, pandas, numpy,
77
+ cryptography) into a module whose whole job is to refuse quickly, and it raises
78
+ ``HostedWorkerError`` with a bootstrap-specific code that means nothing here. The URL policy
79
+ itself must stay identical to the bootstrap one: https only, an absolute URL, no user
80
+ information, no fragment, no trailing slash.
81
+ """
82
+
83
+ if not isinstance(value, str) or not 1 <= len(value) <= MAX_FIELD_LENGTH:
84
+ raise TargetAmbiguityError(f"{label} is not a usable URL")
85
+ if value.endswith("/"):
86
+ raise TargetAmbiguityError(f"{label} must not end with a slash")
87
+ parsed = urlsplit(value)
88
+ if (
89
+ parsed.scheme != "https"
90
+ or not parsed.hostname
91
+ or parsed.username is not None
92
+ or parsed.password is not None
93
+ or parsed.fragment
94
+ ):
95
+ raise TargetAmbiguityError(
96
+ f"{label} must be an absolute https URL without user information or fragment"
97
+ )
98
+ return value
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class DeployTarget:
103
+ """One fully named go-live target; callers may narrow a target but never widen it.
104
+
105
+ Every field was named explicitly by a human. No field was inferred from ambient state, and no
106
+ field may be filled in later from a default, because a partially known target is exactly the
107
+ condition this module exists to refuse.
108
+ """
109
+
110
+ gcp_project_id: str
111
+ region: str
112
+ studio_service_account: str
113
+ studio_worker_job: str
114
+ cloud_verify_url: str
115
+
116
+ def __post_init__(self) -> None:
117
+ for name in REQUIRED_FIELDS:
118
+ value = getattr(self, name)
119
+ if type(value) is not str:
120
+ raise TargetAmbiguityError(f"{name} must be text that names one value")
121
+ if not value or value != value.strip():
122
+ raise TargetAmbiguityError(f"{name} is missing")
123
+ if len(value) > MAX_FIELD_LENGTH:
124
+ raise TargetAmbiguityError(f"{name} is longer than {MAX_FIELD_LENGTH} characters")
125
+ if not _PROJECT_ID.fullmatch(self.gcp_project_id):
126
+ raise TargetAmbiguityError(
127
+ "gcp_project_id is not a valid cloud project id: it must be 6 to 30 characters, "
128
+ "start with a lowercase letter, use only lowercase letters, digits, or hyphens, "
129
+ "and not end with a hyphen"
130
+ )
131
+ if not _REGION.fullmatch(self.region):
132
+ raise TargetAmbiguityError(
133
+ "region is not a valid cloud region, for example us-central1"
134
+ )
135
+ _https_url(self.cloud_verify_url, "cloud_verify_url")
136
+
137
+ def to_dict(self) -> dict[str, Any]:
138
+ """Return a plain JSON-safe mapping of the named target."""
139
+
140
+ return {name: getattr(self, name) for name in REQUIRED_FIELDS}
141
+
142
+
143
+ def resolve_deploy_target(descriptor: Mapping[str, object]) -> DeployTarget:
144
+ """Resolve an explicit descriptor into a :class:`DeployTarget`, or refuse it by name.
145
+
146
+ Load-bearing rule: this function reads only the mapping it is handed. It never consults
147
+ ambient ``gcloud`` configuration, never reads an environment variable for target identity,
148
+ never shells out, and never creates a project, enables billing, or touches IAM. It is
149
+ read-and-refuse only.
150
+
151
+ Unknown keys are refused rather than ignored, because a typo in a target descriptor is the
152
+ cheapest way to deploy into the wrong project.
153
+
154
+ Raises:
155
+ TargetAmbiguityError: when any field is absent, blank, malformed, or unrecognized.
156
+ """
157
+
158
+ if not isinstance(descriptor, Mapping):
159
+ raise TargetAmbiguityError(
160
+ "the deployment descriptor must be a mapping of field names to named values"
161
+ )
162
+
163
+ unknown = sorted(str(key) for key in descriptor if key not in REQUIRED_FIELDS)
164
+ if unknown:
165
+ raise TargetAmbiguityError(
166
+ "the deployment descriptor names fields this resolver does not know: "
167
+ + ", ".join(unknown)
168
+ )
169
+
170
+ values: dict[str, str] = {}
171
+ for name in REQUIRED_FIELDS:
172
+ if name not in descriptor:
173
+ raise TargetAmbiguityError(f"the deployment descriptor does not name {name}")
174
+ raw = descriptor[name]
175
+ if type(raw) is not str:
176
+ raise TargetAmbiguityError(f"{name} must be text that names one value")
177
+ value = raw.strip()
178
+ if not value:
179
+ raise TargetAmbiguityError(f"the deployment descriptor does not name {name}")
180
+ values[name] = value
181
+
182
+ return DeployTarget(**values)
183
+
184
+
185
+ @dataclass(frozen=True)
186
+ class DeployPreflight:
187
+ """The verdict on whether a go-live target is known beyond doubt.
188
+
189
+ Invariant: ``ok`` is true if and only if ``refusals`` is empty and ``target`` is not None.
190
+ Only two shapes exist. An ok verdict carries a resolved target and no reasons; a refusal
191
+ carries at least one named reason and no target at all. A refused verdict never hands back a
192
+ target, because a target in hand is the thing a caller acts on.
193
+ """
194
+
195
+ ok: bool
196
+ refusals: tuple[str, ...]
197
+ target: DeployTarget | None
198
+
199
+ def __post_init__(self) -> None:
200
+ if type(self.ok) is not bool:
201
+ raise DeployTargetError("ok must be a boolean verdict")
202
+ if type(self.refusals) is not tuple or any(
203
+ type(item) is not str or not item.strip() for item in self.refusals
204
+ ):
205
+ raise DeployTargetError("refusals must be a tuple of non-empty reasons")
206
+ if self.target is not None and not isinstance(self.target, DeployTarget):
207
+ raise DeployTargetError("target must be a resolved DeployTarget or None")
208
+ clean = not self.refusals and self.target is not None
209
+ if self.ok is not clean:
210
+ raise DeployTargetError(
211
+ "a preflight is ok only when there are no refusals and a target was resolved"
212
+ )
213
+ if not self.ok and (not self.refusals or self.target is not None):
214
+ raise DeployTargetError(
215
+ "a refused preflight must name at least one reason and carry no target"
216
+ )
217
+
218
+ def to_dict(self) -> dict[str, Any]:
219
+ """Return a plain JSON-safe mapping of the verdict.
220
+
221
+ The verdict carries the named target values and the refusal reasons only. This module
222
+ accepts no credential of any kind, so a verdict cannot leak one.
223
+ """
224
+
225
+ return {
226
+ "ok": self.ok,
227
+ "refusals": list(self.refusals),
228
+ "target": None if self.target is None else self.target.to_dict(),
229
+ }
230
+
231
+
232
+ def _corroborating_project(corroboration: Mapping[str, object]) -> tuple[str | None, str, str]:
233
+ """Return the corroborated project id (or None), its source, and why it was unreadable."""
234
+
235
+ if not isinstance(corroboration, Mapping):
236
+ raise TargetAmbiguityError(
237
+ "corroboration must be a mapping describing what an independent signal reports"
238
+ )
239
+ unknown = sorted(str(key) for key in corroboration if key not in CORROBORATION_FIELDS)
240
+ if unknown:
241
+ raise TargetAmbiguityError(
242
+ "corroboration names fields this resolver does not know: " + ", ".join(unknown)
243
+ )
244
+ for name in ("source", "unreadable_reason"):
245
+ value = corroboration.get(name)
246
+ if value is not None and type(value) is not str:
247
+ raise TargetAmbiguityError(f"corroboration {name} must be text")
248
+ project = corroboration.get("gcp_project_id")
249
+ if project is not None and type(project) is not str:
250
+ raise TargetAmbiguityError("corroboration gcp_project_id must be text or absent")
251
+
252
+ source = str(corroboration.get("source") or "").strip() or "no corroborating signal"
253
+ reason = str(corroboration.get("unreadable_reason") or "").strip() or "nothing readable"
254
+ named = None if project is None else project.strip() or None
255
+ return named, source, reason
256
+
257
+
258
+ def preflight(
259
+ descriptor: Mapping[str, object],
260
+ *,
261
+ corroboration: Mapping[str, object],
262
+ ) -> DeployPreflight:
263
+ """Judge whether a go-live target is known beyond doubt, and refuse quietly when it is not.
264
+
265
+ ``corroboration`` describes what an independent signal reports about the target: a
266
+ ``gcp_project_id`` read from a deployment config, plus an optional ``source`` naming where
267
+ it came
268
+ from and an optional ``unreadable_reason`` when no signal could be read at all.
269
+
270
+ The caller supplies that signal. This module refuses to be the thing that learns the answer,
271
+ because a component that can discover a target can also silently pick the wrong one. It runs
272
+ no ``gcloud`` command, creates no project, enables no billing, and touches no IAM.
273
+
274
+ An ambiguous input is never an exception; it is a verdict with named reasons. Only a
275
+ structurally invalid call raises.
276
+
277
+ Raises:
278
+ TargetAmbiguityError: when ``descriptor`` or ``corroboration`` is not a well-formed
279
+ mapping of the fields this resolver knows.
280
+ """
281
+
282
+ if not isinstance(descriptor, Mapping):
283
+ raise TargetAmbiguityError(
284
+ "the deployment descriptor must be a mapping of field names to named values"
285
+ )
286
+ corroborated, source, reason = _corroborating_project(corroboration)
287
+
288
+ refusals: list[str] = []
289
+ target: DeployTarget | None
290
+ try:
291
+ target = resolve_deploy_target(descriptor)
292
+ except TargetAmbiguityError as error:
293
+ target = None
294
+ refusals.append(str(error))
295
+
296
+ if corroborated is None:
297
+ refusals.append(
298
+ f"the target project could not be corroborated: {source} reported {reason}; "
299
+ "go-live is refused until an independent signal names the target project"
300
+ )
301
+ elif target is not None and corroborated != target.gcp_project_id:
302
+ refusals.append(
303
+ "target project is ambiguous: the deployment descriptor names "
304
+ f"{target.gcp_project_id}, "
305
+ f"the corroborating signal ({source}) names {corroborated}; "
306
+ "name one explicitly before going live"
307
+ )
308
+
309
+ if refusals:
310
+ # A refused verdict never carries a target: the caller must have nothing to act on.
311
+ return DeployPreflight(ok=False, refusals=tuple(refusals), target=None)
312
+ return DeployPreflight(ok=True, refusals=(), target=target)