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,597 @@
1
+ """A bounded, fail-closed reader for the YAML subset a person writes a Recipe in.
2
+
3
+ Nobody hand-maintains ten kilobytes of JSON twice. YAML is the authoring surface; JSON stays the
4
+ contract. This module reads exactly enough YAML for that job and refuses everything else **by
5
+ name**, because a reader that guesses at a Recipe is worse than one that stops: whatever comes out
6
+ of here is what `mr-data approve` binds a fingerprint to.
7
+
8
+ What it reads:
9
+
10
+ * names with values under them, at any consistent indentation
11
+ * lists, including lists of names-with-values and names with lists under them
12
+ * plain values -- text, whole numbers, decimals, ``true``, ``false``, ``null`` and ``~``
13
+ * text in double quotes (with ``\\"``, ``\\\\``, ``\\n`` and ``\\t``) or single quotes (with ``''``)
14
+ * ``#`` notes, at the end of a line or on a line of their own
15
+ * one optional ``---`` before the first line of content
16
+ * ``[]`` and ``{}`` for a list or a group with nothing in it -- the only bracketed forms accepted
17
+
18
+ Everything else raises :class:`AuthorError` naming the construct and the line it is on: anchors,
19
+ aliases, merge keys, tags, bracketed lists and groups with contents in them, a second document,
20
+ tabs used for indentation, block text markers, an indentation step that never opened, a repeated
21
+ name, and anything the line scan does not positively recognise.
22
+
23
+ Three properties this file is responsible for:
24
+
25
+ * **No third-party loader exists here.** Nothing can call one by mistake or construct an object
26
+ from a tag, and this boundary adds no parser dependency.
27
+ * **No expansion vector.** Anchors and aliases are refused outright, so the repeated-expansion
28
+ denial of service has nothing to work with. Depth and input size are capped besides.
29
+ * **When in doubt, refuse.** Every accepted form is written down below. A line that matches none of
30
+ them is ``AUTHOR_YAML_UNSUPPORTED`` with its own text quoted back, never a best-effort guess.
31
+
32
+ The value this returns is still only a value. :func:`mostlyright.data_harness.recipe
33
+ .parse_frozen_recipe` is the second gate, and the plain-language summary `mr-data approve` prints
34
+ is the third.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import re
40
+ from dataclasses import dataclass
41
+ from pathlib import Path
42
+ from typing import Any
43
+
44
+ from mostlyright.data_harness.recipe import RecipeError
45
+ from mostlyright.data_harness.ux.path_kind import UNKNOWN_KIND, name_of_kind, presence_at
46
+ from mostlyright.data_harness.ux.plain_file import (
47
+ DANGLING,
48
+ MISSING,
49
+ NOT_PLAIN,
50
+ PlainFileRefusal,
51
+ read_plain_file,
52
+ )
53
+
54
+ # One megabyte of hand-written YAML is far more Recipe than anyone writes; above it the input is
55
+ # refused before a single byte is decoded.
56
+ MAX_INPUT_BYTES = 1 << 20
57
+
58
+ # The checked-in example Recipe nests six deep. Thirty-two leaves room for anything a person means
59
+ # and stops a file whose only purpose is to nest.
60
+ MAX_DEPTH = 32
61
+
62
+ _DOCUMENT_MARKER = "---"
63
+ _DOCUMENT_END = "..."
64
+
65
+ _ESCAPES = {'"': '"', "\\": "\\", "n": "\n", "t": "\t"}
66
+
67
+ _INTEGER = re.compile(r"[-+]?[0-9]+")
68
+ _DECIMAL = re.compile(
69
+ r"(?:[-+]?(?:[0-9]+\.[0-9]*|\.[0-9]+)(?:[eE][-+]?[0-9]+)?)|(?:[-+]?[0-9]+[eE][-+]?[0-9]+)"
70
+ )
71
+
72
+ # Spellings that are almost certainly meant as one of the three constants. Reading them as text
73
+ # would be a silent surprise, so they are refused and the writer is told which spelling to use.
74
+ # The lower-case words `on`, `off`, `yes`, `no` and `none` are ordinary text here and stay text --
75
+ # the checked-in example Recipe uses `none` as a value and `on` as a name.
76
+ _AMBIGUOUS = frozenset({"True", "TRUE", "False", "FALSE", "Null", "NULL", "None"})
77
+
78
+ _SEQUENCE = "sequence"
79
+ _MAPPING = "mapping"
80
+ _VALUE = "value"
81
+
82
+ _PLAIN_FORMS = (
83
+ "write text, a whole number, a decimal, true, false, or null, or put the text in quotes"
84
+ )
85
+
86
+
87
+ class AuthorError(RecipeError):
88
+ """One YAML line the authoring reader will not read, named and located.
89
+
90
+ It rides on :class:`RecipeError` on purpose: the command line already catches that class, so
91
+ an authoring refusal reaches a person through the same path a Recipe refusal does without a
92
+ second command-boundary translation.
93
+ """
94
+
95
+ def __init__(self, code: str, line: int, detail: str) -> None:
96
+ self.line = line
97
+ super().__init__(code, f"line {line}" if line else "recipe", detail)
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class _Token:
102
+ """One thing found on one line: a list dash, a name, or a value standing alone.
103
+
104
+ ``indent`` is the column the thing starts at, so ``- name: value`` yields a dash at the line's
105
+ own indentation and a name two columns further in. That is what lets one rule -- deeper column
106
+ means underneath -- build the whole tree.
107
+ """
108
+
109
+ kind: str
110
+ indent: int
111
+ line: int
112
+ key: str | None = None
113
+ text: str | None = None
114
+
115
+
116
+ def read_author_source(path: Path) -> bytes:
117
+ """Read one authoring file, refusing an oversized one before any of it is read.
118
+
119
+ The rule itself -- open once, ask the descriptor what it is, read that descriptor under the
120
+ bound -- lives in :mod:`ux.plain_file`, because ``peek`` needs the identical rule and the
121
+ second copy of it is where this defect came back. What stays here is the wording: a Recipe is
122
+ written in a file, and the three sentences below say so in the words this command uses.
123
+ """
124
+
125
+ try:
126
+ return read_plain_file(path, max_bytes=MAX_INPUT_BYTES)
127
+ except PlainFileRefusal as refusal:
128
+ # Raised here rather than returned from a helper: the sweep that proves every typed code
129
+ # names its fix reads `raise` statements out of the live source, so a code that reaches a
130
+ # person only through a returned object is a code that sweep cannot see.
131
+ if refusal.reason == DANGLING:
132
+ # A dangling link is present even though its target is absent, so missing-path advice
133
+ # would misstate the observation. The noun comes from the mode already held by the
134
+ # rule, not a second filesystem lookup.
135
+ raise AuthorError(
136
+ "AUTHOR_YAML_DANGLING",
137
+ 0,
138
+ f"there is {name_of_kind(refusal.mode) or UNKNOWN_KIND} at {refusal.path}, "
139
+ "and it leads "
140
+ "nowhere; repoint it at your Recipe, or remove it and write one there",
141
+ ) from None
142
+ if refusal.reason == MISSING:
143
+ raise AuthorError(
144
+ "AUTHOR_YAML_MISSING",
145
+ 0,
146
+ f"there is {presence_at(path) or UNKNOWN_KIND} at {path}; check the spelling and "
147
+ "the folder you are in",
148
+ ) from None
149
+ if refusal.reason == NOT_PLAIN:
150
+ raise AuthorError(
151
+ "AUTHOR_YAML_TARGET",
152
+ 0,
153
+ f"{path} is not a plain file; a Recipe is written in a file, not in a folder, a "
154
+ "pipe, or a device",
155
+ ) from None
156
+ raise AuthorError(
157
+ "AUTHOR_YAML_SIZE",
158
+ 0,
159
+ f"{path} is larger than {MAX_INPUT_BYTES} bytes; write the Recipe in a smaller file",
160
+ ) from None
161
+
162
+
163
+ def parse_author_yaml(raw: bytes | str) -> Any:
164
+ """Read one authoring file and return the plain value it describes.
165
+
166
+ Returns ``None`` for a file holding nothing but notes; naming what a Recipe is missing is
167
+ ``parse_frozen_recipe``'s job, not this one's.
168
+ """
169
+
170
+ tokens = _tokenise(_decode(raw))
171
+ if not tokens:
172
+ return None
173
+ first = tokens[0]
174
+ if first.indent != 0:
175
+ raise AuthorError(
176
+ "AUTHOR_YAML_INDENT",
177
+ first.line,
178
+ "the first line of content is indented; start it at the left margin",
179
+ )
180
+ if first.kind == _VALUE:
181
+ raise AuthorError(
182
+ "AUTHOR_YAML_UNSUPPORTED",
183
+ first.line,
184
+ f"a Recipe is a set of names with values under them; write name: value, not "
185
+ f"{_quote_back(first.text)}",
186
+ )
187
+ value, index = _build(tokens, 0, 0, 1)
188
+ if index < len(tokens):
189
+ raise AuthorError(
190
+ "AUTHOR_YAML_INDENT",
191
+ tokens[index].line,
192
+ "this line does not line up with anything above it; check its indentation",
193
+ )
194
+ return value
195
+
196
+
197
+ # ------------------------------------------------------------------------------------------------
198
+ # Bytes to lines
199
+ # ------------------------------------------------------------------------------------------------
200
+
201
+
202
+ def _decode(raw: bytes | str) -> str:
203
+ if isinstance(raw, str):
204
+ data = raw.encode("utf-8", errors="surrogatepass")
205
+ else:
206
+ data = raw
207
+ if len(data) > MAX_INPUT_BYTES:
208
+ raise AuthorError(
209
+ "AUTHOR_YAML_SIZE",
210
+ 0,
211
+ f"the Recipe file is larger than {MAX_INPUT_BYTES} bytes; write a smaller one",
212
+ )
213
+ if isinstance(raw, str):
214
+ return raw
215
+ try:
216
+ return data.decode("utf-8")
217
+ except UnicodeDecodeError as exc:
218
+ raise AuthorError(
219
+ "AUTHOR_YAML_ENCODING",
220
+ 0,
221
+ "the Recipe file is not plain UTF-8 text; save it as UTF-8 and try again",
222
+ ) from exc
223
+
224
+
225
+ def _tokenise(text: str) -> list[_Token]:
226
+ """Turn every line of content into the things standing on it, left to right."""
227
+
228
+ tokens: list[_Token] = []
229
+ marker_seen = False
230
+ content_seen = False
231
+ for number, source in enumerate(text.split("\n"), start=1):
232
+ line = source[:-1] if source.endswith("\r") else source
233
+ body_start = len(line) - len(line.lstrip(" \t"))
234
+ if "\t" in line[:body_start]:
235
+ raise AuthorError(
236
+ "AUTHOR_YAML_TAB",
237
+ number,
238
+ "this line is indented with a tab; use spaces",
239
+ )
240
+ stripped = line[body_start:].rstrip()
241
+ if not stripped or stripped.startswith("#"):
242
+ continue
243
+ if stripped == _DOCUMENT_MARKER:
244
+ if content_seen or marker_seen:
245
+ raise AuthorError(
246
+ "AUTHOR_YAML_MULTIDOC",
247
+ number,
248
+ "one Recipe per file; --- may appear once, before the first line of content",
249
+ )
250
+ marker_seen = True
251
+ continue
252
+ if stripped == _DOCUMENT_END or stripped.startswith(f"{_DOCUMENT_END} "):
253
+ raise AuthorError(
254
+ "AUTHOR_YAML_MULTIDOC",
255
+ number,
256
+ "one Recipe per file; write no ... end marker",
257
+ )
258
+ content_seen = True
259
+ _tokenise_line(tokens, line, body_start, number)
260
+ return tokens
261
+
262
+
263
+ def _tokenise_line(tokens: list[_Token], line: str, indent: int, number: int) -> None:
264
+ column = indent
265
+ rest = line[indent:].rstrip()
266
+ while rest.startswith("-") and (len(rest) == 1 or rest[1] in " \t"):
267
+ if len(rest) > 1 and rest[1] == "\t":
268
+ raise AuthorError(
269
+ "AUTHOR_YAML_TAB",
270
+ number,
271
+ "there is a tab after this list dash; use spaces",
272
+ )
273
+ tokens.append(_Token(_SEQUENCE, column, number))
274
+ step = 1
275
+ while step < len(rest) and rest[step] == " ":
276
+ step += 1
277
+ column += step
278
+ rest = rest[step:]
279
+ if not rest:
280
+ return
281
+ if not rest or rest.startswith("#"):
282
+ return
283
+ if rest.startswith("\t"):
284
+ raise AuthorError("AUTHOR_YAML_TAB", number, "there is a tab here; use spaces")
285
+ if rest == "?" or rest.startswith("? "):
286
+ # The marker for a name written on its own line, with its value on the next. Caught here
287
+ # rather than left to the build so the refusal names the line the writer typed it on.
288
+ raise AuthorError(
289
+ "AUTHOR_YAML_UNSUPPORTED",
290
+ number,
291
+ "? starts a name written on a line of its own, which is not read here; write "
292
+ "name: value on one line",
293
+ )
294
+ key, value = _split_name(rest, number)
295
+ if key is None:
296
+ tokens.append(_Token(_VALUE, column, number, text=rest))
297
+ return
298
+ _check_name(key, number)
299
+ tokens.append(_Token(_MAPPING, column, number, key=key, text=value))
300
+
301
+
302
+ def _split_name(rest: str, number: int) -> tuple[str | None, str | None]:
303
+ """Split ``name: value`` at the colon that ends the name, or report there is no name here.
304
+
305
+ The colon that ends a name is the first one followed by a space or by the end of the line, so
306
+ a value holding a time or an address keeps its own colons.
307
+ """
308
+
309
+ if rest[0] in "\"'":
310
+ name, end = _read_quoted(rest, number)
311
+ tail = rest[end:]
312
+ if tail.startswith(":") and (len(tail) == 1 or tail[1] == " "):
313
+ return name, _inline_value(tail[1:])
314
+ return None, None
315
+ for index, character in enumerate(rest):
316
+ if character == ":" and (index + 1 == len(rest) or rest[index + 1] == " "):
317
+ return rest[:index].rstrip(), _inline_value(rest[index + 1 :])
318
+ return None, None
319
+
320
+
321
+ def _inline_value(text: str) -> str | None:
322
+ """What follows the colon, or nothing when only a note follows it."""
323
+
324
+ value = text.strip()
325
+ if not value or value.startswith("#"):
326
+ return None
327
+ return value
328
+
329
+
330
+ def _check_name(key: str, number: int) -> None:
331
+ if not key:
332
+ raise AuthorError("AUTHOR_YAML_UNSUPPORTED", number, "this line has a colon but no name")
333
+ if key == "<<":
334
+ raise AuthorError(
335
+ "AUTHOR_YAML_MERGE_KEY",
336
+ number,
337
+ "<< copies one group of names into another, which is not read here; write the names "
338
+ "out in full",
339
+ )
340
+ _refuse_construct(key[0], key, number)
341
+
342
+
343
+ # ------------------------------------------------------------------------------------------------
344
+ # Values
345
+ # ------------------------------------------------------------------------------------------------
346
+
347
+
348
+ def _scalar(text: str | None, number: int) -> Any:
349
+ """One plain value, or a typed refusal naming what was written instead."""
350
+
351
+ if not text:
352
+ return None
353
+ if text[0] in "\"'":
354
+ value, end = _read_quoted(text, number)
355
+ _require_note_only(text[end:], number)
356
+ return value
357
+ bare = _strip_note(text)
358
+ if not bare:
359
+ return None
360
+ if bare == "[]":
361
+ return []
362
+ if bare == "{}":
363
+ return {}
364
+ _refuse_construct(bare[0], bare, number)
365
+ if bare in ("null", "~"):
366
+ return None
367
+ if bare == "true":
368
+ return True
369
+ if bare == "false":
370
+ return False
371
+ if bare in _AMBIGUOUS:
372
+ raise AuthorError(
373
+ "AUTHOR_YAML_AMBIGUOUS",
374
+ number,
375
+ f"{bare} could be a value or the word itself; write true, false or null in lower "
376
+ f"case, or put {bare} in quotes to mean the word",
377
+ )
378
+ if _INTEGER.fullmatch(bare):
379
+ return _whole_number(bare, number)
380
+ if _DECIMAL.fullmatch(bare):
381
+ return float(bare)
382
+ return bare
383
+
384
+
385
+ def _whole_number(bare: str, number: int) -> int:
386
+ """One whole number, or the same refusal by name that every other unreadable value gets.
387
+
388
+ The only conversion in this reader that the interpreter can refuse: CPython will not turn a
389
+ decimal string longer than ``sys.get_int_max_str_digits()`` digits into an int, and says so
390
+ with a bare ``ValueError``. That class is not an ``AuthorError``, is not caught at the command
391
+ line, and reached the terminal as a Python traceback with no code and no fix -- out of the one
392
+ module whose whole contract is refusing by name with the line it is on.
393
+ """
394
+
395
+ try:
396
+ return int(bare)
397
+ except ValueError as exc:
398
+ raise AuthorError(
399
+ "AUTHOR_YAML_NUMBER",
400
+ number,
401
+ f"a whole number written with {len(bare.lstrip('+-'))} digits is longer than this "
402
+ "reader converts; put it in quotes to keep it as text, or write a shorter number",
403
+ ) from exc
404
+
405
+
406
+ def _refuse_construct(head: str, written: str, number: int) -> None:
407
+ """Refuse, by name, every construct this reader does not read."""
408
+
409
+ if head == "&":
410
+ raise AuthorError(
411
+ "AUTHOR_YAML_ANCHOR",
412
+ number,
413
+ f"& marks a spot to copy from later, which is not read here; write the value out in "
414
+ f"full instead of {_quote_back(written)}",
415
+ )
416
+ if head == "*":
417
+ raise AuthorError(
418
+ "AUTHOR_YAML_ALIAS",
419
+ number,
420
+ f"* copies a value marked earlier, which is not read here; write the value out in "
421
+ f"full instead of {_quote_back(written)}",
422
+ )
423
+ if head == "!":
424
+ raise AuthorError(
425
+ "AUTHOR_YAML_TAG",
426
+ number,
427
+ f"! names a type to build, which is never read here; write the plain value instead of "
428
+ f"{_quote_back(written)}",
429
+ )
430
+ if head in "{[":
431
+ raise AuthorError(
432
+ "AUTHOR_YAML_FLOW",
433
+ number,
434
+ f"a list or group written inside brackets is not read here; write one entry per line "
435
+ f"under its name instead of {_quote_back(written)}",
436
+ )
437
+ if head in "|>":
438
+ raise AuthorError(
439
+ "AUTHOR_YAML_BLOCK_SCALAR",
440
+ number,
441
+ f"{head} starts text carried over several lines, which is not read here; write the "
442
+ f"text on one line in quotes, using \\n where you want a line break, instead of "
443
+ f"{_quote_back(written)}",
444
+ )
445
+ if head in "?%@`":
446
+ raise AuthorError(
447
+ "AUTHOR_YAML_UNSUPPORTED",
448
+ number,
449
+ f"{_quote_back(written)} starts with {head}, which is not read here; {_PLAIN_FORMS}",
450
+ )
451
+
452
+
453
+ def _read_quoted(text: str, number: int) -> tuple[str, int]:
454
+ """Read one quoted value, returning it and the column just past its closing quote."""
455
+
456
+ quote = text[0]
457
+ out: list[str] = []
458
+ index = 1
459
+ while index < len(text):
460
+ character = text[index]
461
+ if quote == '"' and character == "\\":
462
+ if index + 1 >= len(text):
463
+ break
464
+ following = text[index + 1]
465
+ mapped = _ESCAPES.get(following)
466
+ if mapped is None:
467
+ raise AuthorError(
468
+ "AUTHOR_YAML_QUOTE",
469
+ number,
470
+ f"\\{following} is not one of the four escapes read here; "
471
+ f'write \\" \\\\ \\n or \\t',
472
+ )
473
+ out.append(mapped)
474
+ index += 2
475
+ continue
476
+ if character == quote:
477
+ if quote == "'" and index + 1 < len(text) and text[index + 1] == "'":
478
+ out.append("'")
479
+ index += 2
480
+ continue
481
+ return "".join(out), index + 1
482
+ out.append(character)
483
+ index += 1
484
+ raise AuthorError(
485
+ "AUTHOR_YAML_QUOTE",
486
+ number,
487
+ f"this {quote} is never closed; close it on the same line",
488
+ )
489
+
490
+
491
+ def _require_note_only(tail: str, number: int) -> None:
492
+ remainder = tail.strip()
493
+ if remainder and not remainder.startswith("#"):
494
+ raise AuthorError(
495
+ "AUTHOR_YAML_UNSUPPORTED",
496
+ number,
497
+ f"{_quote_back(remainder)} follows the closing quote; put the whole value inside the "
498
+ f"quotes, or start a note with #",
499
+ )
500
+
501
+
502
+ def _strip_note(text: str) -> str:
503
+ """Drop an end-of-line note. A # with no space before it is part of the word."""
504
+
505
+ for index in range(1, len(text)):
506
+ if text[index] == "#" and text[index - 1] in " \t":
507
+ return text[:index].rstrip()
508
+ return text
509
+
510
+
511
+ def _quote_back(written: str | None) -> str:
512
+ return repr(written if written is not None else "")
513
+
514
+
515
+ # ------------------------------------------------------------------------------------------------
516
+ # Lines to a value
517
+ # ------------------------------------------------------------------------------------------------
518
+
519
+
520
+ def _build(tokens: list[_Token], index: int, column: int, depth: int) -> tuple[Any, int]:
521
+ if depth > MAX_DEPTH:
522
+ raise AuthorError(
523
+ "AUTHOR_YAML_DEPTH",
524
+ tokens[index].line,
525
+ f"this is nested more than {MAX_DEPTH} deep; a Recipe never needs that",
526
+ )
527
+ token = tokens[index]
528
+ if token.kind == _SEQUENCE:
529
+ return _build_sequence(tokens, index, column, depth)
530
+ if token.kind == _MAPPING:
531
+ return _build_mapping(tokens, index, column, depth)
532
+ return _scalar(token.text, token.line), index + 1
533
+
534
+
535
+ def _build_mapping(
536
+ tokens: list[_Token], index: int, column: int, depth: int
537
+ ) -> tuple[dict[str, Any], int]:
538
+ result: dict[str, Any] = {}
539
+ while index < len(tokens):
540
+ token = tokens[index]
541
+ if token.indent < column:
542
+ break
543
+ if token.indent > column:
544
+ raise AuthorError(
545
+ "AUTHOR_YAML_INDENT",
546
+ token.line,
547
+ "this line is indented further in than the line above it opened; line it up with "
548
+ "the names it belongs to",
549
+ )
550
+ if token.kind != _MAPPING:
551
+ raise AuthorError(
552
+ "AUTHOR_YAML_INDENT",
553
+ token.line,
554
+ "a list entry stands where a name was expected; give the list a name, or indent "
555
+ "it under the one it belongs to",
556
+ )
557
+ name = token.key or ""
558
+ if name in result:
559
+ raise AuthorError(
560
+ "AUTHOR_YAML_DUPLICATE_KEY",
561
+ token.line,
562
+ f"{name} is named twice in the same group; every name is written once",
563
+ )
564
+ index += 1
565
+ if token.text is not None:
566
+ result[name] = _scalar(token.text, token.line)
567
+ continue
568
+ result[name], index = _under(tokens, index, column, depth)
569
+ return result, index
570
+
571
+
572
+ def _under(tokens: list[_Token], index: int, column: int, depth: int) -> tuple[Any, int]:
573
+ """What sits under a name that carries no value of its own."""
574
+
575
+ if index < len(tokens) and tokens[index].indent > column:
576
+ return _build(tokens, index, tokens[index].indent, depth + 1)
577
+ # A list may sit flush with the name it belongs to; that is the form most people type, and it
578
+ # means what the indented form means.
579
+ if index < len(tokens) and tokens[index].indent == column and tokens[index].kind == _SEQUENCE:
580
+ return _build_sequence(tokens, index, column, depth + 1)
581
+ return None, index
582
+
583
+
584
+ def _build_sequence(
585
+ tokens: list[_Token], index: int, column: int, depth: int
586
+ ) -> tuple[list[Any], int]:
587
+ items: list[Any] = []
588
+ while (
589
+ index < len(tokens) and tokens[index].kind == _SEQUENCE and tokens[index].indent == column
590
+ ):
591
+ index += 1
592
+ if index < len(tokens) and tokens[index].indent > column:
593
+ item, index = _build(tokens, index, tokens[index].indent, depth + 1)
594
+ else:
595
+ item = None
596
+ items.append(item)
597
+ return items, index