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,384 @@
1
+ """Parse one STAC ``/collections`` response into public-source records.
2
+
3
+ Records declare JSON because it is the supported STAC response encoding. Asset encodings remain
4
+ in descriptive text. Missing optional fields are accepted. Text and identifiers are bounded by
5
+ the shared harvest protocol; unusable records are skipped.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from mostlyright.data_harness.canonical import CanonicalJSONError, parse_foreign_json
13
+ from mostlyright.data_harness.sources.catalog.harvest.ckan import MAX_HARVEST_RECORDS
14
+ from mostlyright.data_harness.sources.catalog.harvest.protocol import (
15
+ MAX_RECORD_PUBLISHER,
16
+ MAX_RECORD_TEXT,
17
+ MAX_RECORD_TITLE,
18
+ CatalogHarvestError,
19
+ HarvestCursor,
20
+ HarvestedRecord,
21
+ HarvesterDescriptor,
22
+ HarvestPage,
23
+ HarvestResponseEvidence,
24
+ bounded_record_text,
25
+ carries_a_zone_offset,
26
+ declared_timestamp,
27
+ harvest_limits,
28
+ usable_https_uri,
29
+ usable_license_id,
30
+ usable_record_id,
31
+ )
32
+ from mostlyright.data_harness.sources.contracts import EvidenceReference
33
+
34
+ STAC_LIMITS = harvest_limits("application/json", max_response_bytes=2 * 1024 * 1024)
35
+
36
+ SUPPORTED_STAC_MAJOR = "1."
37
+
38
+ ATTRIBUTION = "attribution"
39
+
40
+ # SPDX identifiers, which is a different vocabulary from CKAN's licence register -- hence a second
41
+ # table rather than a shared one. The mapping idiom is the same; the identifiers genuinely differ.
42
+ STAC_LICENCE_RIGHTS: dict[str, tuple[str, tuple[str, ...], str | None]] = {
43
+ "cc0-1.0": ("approved", ("analysis", "publication"), None),
44
+ "public-domain": ("approved", ("analysis", "publication"), None),
45
+ "pddl-1.0": ("approved", ("analysis", "publication"), None),
46
+ "cc-by-4.0": ("conditional", ("analysis", "publication"), ATTRIBUTION),
47
+ "cc-by-sa-4.0": ("conditional", ("analysis", "publication"), ATTRIBUTION),
48
+ "odc-by-1.0": ("conditional", ("analysis", "publication"), ATTRIBUTION),
49
+ }
50
+
51
+ UNCLEAR_LICENCE_IDS = frozenset({"", "proprietary", "various", "other", "unknown"})
52
+
53
+
54
+ def stac_licence_rights(license_id: str | None) -> tuple[str, tuple[str, ...], str | None]:
55
+ """Map a STAC SPDX licence identifier onto a rights status, allowed uses and an obligation."""
56
+
57
+ if license_id is None:
58
+ return ("unclear", (), None)
59
+ normalized = license_id.strip().lower()
60
+ if normalized in UNCLEAR_LICENCE_IDS:
61
+ return ("unclear", (), None)
62
+ return STAC_LICENCE_RIGHTS.get(normalized, ("unclear", (), None))
63
+
64
+
65
+ class StacHarvester:
66
+ """Read a STAC ``/collections`` document. Takes bytes; never fetches."""
67
+
68
+ _DESCRIPTOR = HarvesterDescriptor(
69
+ protocol="stac",
70
+ harvester_id="stac.collections",
71
+ harvester_version="1.0.0",
72
+ )
73
+
74
+ @property
75
+ def descriptor(self) -> HarvesterDescriptor:
76
+ return self._DESCRIPTOR
77
+
78
+ def parse(
79
+ self,
80
+ payload: bytes,
81
+ *,
82
+ uri: str,
83
+ observed_at: str,
84
+ evidence: EvidenceReference,
85
+ ) -> tuple[HarvestedRecord, ...]:
86
+ page = self.parse_page(
87
+ payload,
88
+ uri=uri,
89
+ observed_at=observed_at,
90
+ evidence=evidence,
91
+ response_evidence=None,
92
+ cursor=None,
93
+ )
94
+ if not page.records:
95
+ raise CatalogHarvestError(
96
+ "HARVEST_SHAPE", "stac.collections", "no collection carried a usable identity"
97
+ )
98
+ return page.records
99
+
100
+ def parse_page(
101
+ self,
102
+ payload: bytes,
103
+ *,
104
+ uri: str,
105
+ observed_at: str,
106
+ evidence: EvidenceReference,
107
+ response_evidence: HarvestResponseEvidence | None,
108
+ cursor: HarvestCursor | None,
109
+ ) -> HarvestPage:
110
+ if cursor is not None and (cursor.protocol != "stac" or cursor.value != uri):
111
+ raise CatalogHarvestError("HARVEST_CURSOR", "stac.cursor", "cursor mismatch")
112
+ document = _document(payload)
113
+ collections = document.get("collections")
114
+ if not isinstance(collections, list):
115
+ raise CatalogHarvestError(
116
+ "HARVEST_SHAPE",
117
+ "stac.collections",
118
+ "a STAC collections response must carry a collections array",
119
+ )
120
+ if len(collections) > MAX_HARVEST_RECORDS:
121
+ raise CatalogHarvestError(
122
+ "HARVEST_RECORD_LIMIT",
123
+ "stac.collections",
124
+ f"a single response may not carry more than {MAX_HARVEST_RECORDS} collections",
125
+ )
126
+ skipped: list[str] = []
127
+ records: list[HarvestedRecord] = []
128
+ for position, collection in enumerate(collections):
129
+ if not isinstance(collection, dict):
130
+ raise CatalogHarvestError(
131
+ "HARVEST_SHAPE",
132
+ f"stac.collections[{position}]",
133
+ "each collection must be an object",
134
+ )
135
+ _require_supported_version(collection, document, position)
136
+ record = _record(
137
+ collection,
138
+ uri=uri,
139
+ observed_at=observed_at,
140
+ evidence=evidence,
141
+ skipped=skipped,
142
+ )
143
+ if record is not None:
144
+ records.append(record)
145
+ next_uri = _next_link(document)
146
+ provider_count = _matched_count(document)
147
+ if not collections and next_uri is not None:
148
+ raise CatalogHarvestError(
149
+ "HARVEST_EMPTY_PAGE", "stac.collections", "an empty page may not continue"
150
+ )
151
+ return HarvestPage(
152
+ protocol="stac",
153
+ records=tuple(records),
154
+ next_cursor=(
155
+ HarvestCursor(protocol="stac", kind="next", value=next_uri)
156
+ if next_uri is not None
157
+ else None
158
+ ),
159
+ provider_count=provider_count,
160
+ count_basis="reported_total" if provider_count is not None else "not_reported",
161
+ skipped_record_ids=tuple(sorted(skipped)),
162
+ evidence=evidence,
163
+ response_evidence=response_evidence,
164
+ )
165
+
166
+
167
+ def _next_link(document: dict[str, Any]) -> str | None:
168
+ links = document.get("links")
169
+ if links is None:
170
+ return None
171
+ if not isinstance(links, list):
172
+ raise CatalogHarvestError("HARVEST_SHAPE", "stac.links", "links must be an array")
173
+ found: list[str] = []
174
+ for position, link in enumerate(links):
175
+ if not isinstance(link, dict):
176
+ raise CatalogHarvestError(
177
+ "HARVEST_SHAPE", f"stac.links[{position}]", "each link must be an object"
178
+ )
179
+ if link.get("rel") != "next":
180
+ continue
181
+ href = usable_https_uri(link.get("href"))
182
+ if href is None:
183
+ raise CatalogHarvestError(
184
+ "HARVEST_CURSOR", f"stac.links[{position}].href", "next must be bounded HTTPS"
185
+ )
186
+ found.append(href)
187
+ if len(found) > 1:
188
+ raise CatalogHarvestError("HARVEST_CURSOR", "stac.links", "at most one next link is valid")
189
+ return found[0] if found else None
190
+
191
+
192
+ def _matched_count(document: dict[str, Any]) -> int | None:
193
+ value = document.get("numberMatched")
194
+ context = document.get("context")
195
+ if value is None and isinstance(context, dict):
196
+ value = context.get("matched")
197
+ if value is None:
198
+ return None
199
+ if type(value) is not int or value < 0:
200
+ raise CatalogHarvestError("HARVEST_COUNT", "stac.matched", "must be nonnegative integer")
201
+ return value
202
+
203
+
204
+ def _document(payload: bytes) -> dict[str, Any]:
205
+ # Fraction-tolerant on purpose. A STAC Collection is required by its own specification to carry
206
+ # `extent.spatial.bbox`, and every real bbox is fractional; so are `gsd`, `center_wavelength`,
207
+ # `proj:transform` and most `summaries` values. Parsing these with the integer-only canonical
208
+ # parser refuses every genuine /collections response. `parse_foreign_json` keeps the rest of the
209
+ # hardening -- duplicate keys, byte-order marks, strict UTF-8, NaN/Infinity, integer range --
210
+ # and no float reaches a `HarvestedRecord`: every field below is read through `_string`.
211
+ try:
212
+ document = parse_foreign_json(payload)
213
+ except CanonicalJSONError as error:
214
+ raise CatalogHarvestError(
215
+ "HARVEST_SHAPE",
216
+ "stac.response",
217
+ f"the response is not strict JSON: {error.detail}",
218
+ ) from None
219
+ if not isinstance(document, dict):
220
+ raise CatalogHarvestError(
221
+ "HARVEST_SHAPE",
222
+ "stac.response",
223
+ "a STAC response must be a JSON object",
224
+ )
225
+ return document
226
+
227
+
228
+ def _require_supported_version(
229
+ collection: dict[str, Any],
230
+ document: dict[str, Any],
231
+ position: int,
232
+ ) -> None:
233
+ declared = collection.get("stac_version") or document.get("stac_version")
234
+ if not isinstance(declared, str) or not declared.startswith(SUPPORTED_STAC_MAJOR):
235
+ raise CatalogHarvestError(
236
+ "HARVEST_STAC_VERSION",
237
+ f"stac.collections[{position}].stac_version",
238
+ f"this harvester reads STAC {SUPPORTED_STAC_MAJOR}x, found {declared!r}",
239
+ )
240
+
241
+
242
+ def _record(
243
+ collection: dict[str, Any],
244
+ *,
245
+ uri: str,
246
+ observed_at: str,
247
+ evidence: EvidenceReference,
248
+ skipped: list[str],
249
+ ) -> HarvestedRecord | None:
250
+ collection_id = usable_record_id(collection.get("id"))
251
+ if collection_id is None:
252
+ skipped.append("<collection without a usable id>")
253
+ return None
254
+ landing = usable_https_uri(_self_link(collection)) or usable_https_uri(
255
+ _collection_uri(uri, collection_id)
256
+ )
257
+ if landing is None:
258
+ skipped.append(collection_id)
259
+ return None
260
+ encodings = _asset_encodings(collection)
261
+ description = _string(collection.get("description")) or "No description published."
262
+ if encodings:
263
+ description = (
264
+ f"{description} Published asset encodings: {', '.join(encodings)}. "
265
+ "The harness reads this catalogue as JSON metadata; those encodings need a Reader."
266
+ )
267
+ title = _string(collection.get("title"))
268
+ return HarvestedRecord(
269
+ protocol="stac",
270
+ record_id=collection_id,
271
+ title=bounded_record_text(title, maximum=MAX_RECORD_TITLE) if title else collection_id,
272
+ description=bounded_record_text(description, maximum=MAX_RECORD_TEXT),
273
+ publisher=bounded_record_text(_publisher(collection), maximum=MAX_RECORD_PUBLISHER),
274
+ landing_uri=landing,
275
+ data_formats=("json",),
276
+ evidence=evidence,
277
+ license_id=usable_license_id(collection.get("license")),
278
+ license_uri=None,
279
+ declared_updated_at=_temporal_end(collection),
280
+ )
281
+
282
+
283
+ def _publisher(collection: dict[str, Any]) -> str:
284
+ providers = collection.get("providers")
285
+ if isinstance(providers, list):
286
+ for provider in providers:
287
+ if isinstance(provider, dict):
288
+ name = _string(provider.get("name"))
289
+ if name is not None:
290
+ return name
291
+ return "Unnamed publisher"
292
+
293
+
294
+ def _self_link(collection: dict[str, Any]) -> str | None:
295
+ links = collection.get("links")
296
+ if not isinstance(links, list):
297
+ return None
298
+ for link in links:
299
+ if not isinstance(link, dict) or link.get("rel") != "self":
300
+ continue
301
+ href = _string(link.get("href"))
302
+ if href is not None and href.startswith("https://"):
303
+ return href
304
+ return None
305
+
306
+
307
+ def _collection_uri(uri: str, collection_id: str) -> str | None:
308
+ if not uri.startswith("https://"):
309
+ return None
310
+ base = uri.split("?", 1)[0].rstrip("/")
311
+ return f"{base}/{collection_id}"
312
+
313
+
314
+ def _asset_encodings(collection: dict[str, Any]) -> tuple[str, ...]:
315
+ """The media types this collection's item assets really publish, in declared order."""
316
+
317
+ assets = collection.get("item_assets")
318
+ if not isinstance(assets, dict):
319
+ return ()
320
+ encodings: list[str] = []
321
+ for value in assets.values():
322
+ if not isinstance(value, dict):
323
+ continue
324
+ media_type = _string(value.get("type"))
325
+ if media_type is not None and media_type not in encodings:
326
+ encodings.append(media_type)
327
+ return tuple(encodings[:16])
328
+
329
+
330
+ def _temporal_end(collection: dict[str, Any]) -> str | None:
331
+ """The end of the first temporal interval, when the publisher declares a concrete one.
332
+
333
+ An open-ended interval leaves this ``None``. Reading it as "now" would invent a fact the
334
+ catalogue did not state, and cadence is one of the things the facts gate decides on.
335
+ """
336
+
337
+ extent = collection.get("extent")
338
+ if not isinstance(extent, dict):
339
+ return None
340
+ temporal = extent.get("temporal")
341
+ if not isinstance(temporal, dict):
342
+ return None
343
+ interval = temporal.get("interval")
344
+ if not isinstance(interval, list) or not interval:
345
+ return None
346
+ first = interval[0]
347
+ if not isinstance(first, list) or len(first) < 2:
348
+ return None
349
+ end = _string(first[1])
350
+ if end is None:
351
+ return None
352
+ return _stac_timestamp(end)
353
+
354
+
355
+ def _stac_timestamp(value: str) -> str | None:
356
+ """Read a temporal-interval bound as a canonical UTC timestamp, or not at all.
357
+
358
+ STAC requires UTC, but the interval is publisher text and a real instance spells it however it
359
+ likes. The normalised result goes through ``declared_timestamp`` and becomes an undeclared
360
+ update time when it is not canonical -- without that, one collection's zone-bearing or
361
+ malformed bound refused every other collection in the same response. See that function.
362
+
363
+ The offset check runs before the fractional second is cut, because the offset rides on the
364
+ fraction and was being cut away with it -- see ``carries_a_zone_offset``.
365
+ """
366
+
367
+ if carries_a_zone_offset(value.strip()):
368
+ return None
369
+ text = value.strip()
370
+ if text.endswith("Z"):
371
+ text = text[:-1]
372
+ if "." in text:
373
+ head, _, fraction = text.partition(".")
374
+ text = f"{head}.{fraction[:6]}" if fraction[:6].isdigit() else head
375
+ if len(text) < 19:
376
+ return None
377
+ return declared_timestamp(f"{text}Z")
378
+
379
+
380
+ def _string(value: Any) -> str | None:
381
+ if not isinstance(value, str):
382
+ return None
383
+ text = value.strip()
384
+ return text or None