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
+ """Exact bounded before/after table profiles."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import re
7
+ from collections import Counter
8
+ from dataclasses import dataclass
9
+ from decimal import Decimal
10
+ from typing import Any
11
+
12
+ from mostlyright.data_harness.canonical import canonical_sha256
13
+ from mostlyright.data_harness.preparation.contracts import PROFILE_VERSION
14
+ from mostlyright.data_harness.preparation.errors import PreparationError
15
+ from mostlyright.data_harness.preparation.table import TableState, validate_columns
16
+
17
+ MAX_DISTRIBUTION_VALUES = 32
18
+ _DIGEST = re.compile(r"\A[0-9a-f]{64}\Z")
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ValueCount:
23
+ value: str
24
+ count: int
25
+
26
+ def __post_init__(self) -> None:
27
+ if not isinstance(self.value, str) or len(self.value) > 1_000_000:
28
+ raise PreparationError("PROFILE_VALUE", "profile.distribution.value", "is invalid")
29
+ if type(self.count) is not int or self.count < 1:
30
+ raise PreparationError(
31
+ "PROFILE_COUNT", "profile.distribution.count", "must be positive"
32
+ )
33
+
34
+ def to_dict(self) -> dict[str, Any]:
35
+ return {"value": self.value, "count": self.count}
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class FieldProfile:
40
+ name: str
41
+ logical_type: str
42
+ physical_type: str
43
+ null_count: int
44
+ non_null_count: int
45
+ distinct_count: int
46
+ minimum: str | None
47
+ maximum: str | None
48
+ minimum_utf8_bytes: int | None
49
+ maximum_utf8_bytes: int | None
50
+ quantiles: tuple[tuple[str, str], ...]
51
+ distribution: tuple[ValueCount, ...]
52
+ other_value_count: int
53
+ outlier_count: int
54
+ outlier_lower_bound: str | None
55
+ outlier_upper_bound: str | None
56
+
57
+ def __post_init__(self) -> None:
58
+ counts = (
59
+ self.null_count,
60
+ self.non_null_count,
61
+ self.distinct_count,
62
+ self.other_value_count,
63
+ self.outlier_count,
64
+ )
65
+ if any(type(value) is not int or value < 0 for value in counts):
66
+ raise PreparationError(
67
+ "PROFILE_COUNT", f"profile.fields.{self.name}", "has invalid counts"
68
+ )
69
+ if self.distinct_count > self.non_null_count:
70
+ raise PreparationError(
71
+ "PROFILE_COUNT",
72
+ f"profile.fields.{self.name}",
73
+ "distinct count exceeds non-null count",
74
+ )
75
+ if (self.minimum is None) != (self.maximum is None):
76
+ raise PreparationError(
77
+ "PROFILE_RANGE",
78
+ f"profile.fields.{self.name}",
79
+ "minimum and maximum must be paired",
80
+ )
81
+ if self.logical_type == "string":
82
+ if (self.minimum_utf8_bytes is None) != (self.maximum_utf8_bytes is None):
83
+ raise PreparationError(
84
+ "PROFILE_STRING_RANGE",
85
+ f"profile.fields.{self.name}",
86
+ "string byte bounds must be paired",
87
+ )
88
+ elif self.minimum_utf8_bytes is not None or self.maximum_utf8_bytes is not None:
89
+ raise PreparationError(
90
+ "PROFILE_STRING_RANGE",
91
+ f"profile.fields.{self.name}",
92
+ "non-string profile cannot carry string byte bounds",
93
+ )
94
+ if tuple(key for key, _ in self.quantiles) not in {
95
+ (),
96
+ ("0.25", "0.50", "0.75"),
97
+ }:
98
+ raise PreparationError(
99
+ "PROFILE_QUANTILES",
100
+ f"profile.fields.{self.name}",
101
+ "quantile set/order is not exact",
102
+ )
103
+ if len(self.distribution) > MAX_DISTRIBUTION_VALUES:
104
+ raise PreparationError(
105
+ "PROFILE_DISTRIBUTION",
106
+ f"profile.fields.{self.name}",
107
+ "distribution exceeds its bound",
108
+ )
109
+ distribution_values = tuple(item.value for item in self.distribution)
110
+ if len(set(distribution_values)) != len(distribution_values):
111
+ raise PreparationError(
112
+ "PROFILE_DISTRIBUTION",
113
+ f"profile.fields.{self.name}",
114
+ "distribution contains duplicate values",
115
+ )
116
+ if (self.outlier_lower_bound is None) != (self.outlier_upper_bound is None):
117
+ raise PreparationError(
118
+ "PROFILE_OUTLIER",
119
+ f"profile.fields.{self.name}",
120
+ "outlier bounds must be paired",
121
+ )
122
+
123
+ def to_dict(self) -> dict[str, Any]:
124
+ return {
125
+ "name": self.name,
126
+ "logical_type": self.logical_type,
127
+ "physical_type": self.physical_type,
128
+ "null_count": self.null_count,
129
+ "non_null_count": self.non_null_count,
130
+ "distinct_count": self.distinct_count,
131
+ "minimum": self.minimum,
132
+ "maximum": self.maximum,
133
+ "minimum_utf8_bytes": self.minimum_utf8_bytes,
134
+ "maximum_utf8_bytes": self.maximum_utf8_bytes,
135
+ "quantiles": [{"quantile": key, "value": value} for key, value in self.quantiles],
136
+ "distribution": [item.to_dict() for item in self.distribution],
137
+ "other_value_count": self.other_value_count,
138
+ "outlier_count": self.outlier_count,
139
+ "outlier_lower_bound": self.outlier_lower_bound,
140
+ "outlier_upper_bound": self.outlier_upper_bound,
141
+ }
142
+
143
+
144
+ @dataclass(frozen=True)
145
+ class TableProfile:
146
+ schema_version: str
147
+ table_id: str
148
+ schema_digest: str
149
+ row_count: int
150
+ column_count: int
151
+ duplicate_row_count: int
152
+ key_columns: tuple[str, ...]
153
+ duplicate_key_count: int
154
+ temporal_coverage: tuple[tuple[str, str | None, str | None], ...]
155
+ fields: tuple[FieldProfile, ...]
156
+
157
+ def __post_init__(self) -> None:
158
+ if self.schema_version != PROFILE_VERSION:
159
+ raise PreparationError("PROFILE_VERSION", "profile.schema_version", "is unsupported")
160
+ if not isinstance(self.table_id, str) or not self.table_id:
161
+ raise PreparationError("PROFILE_TABLE", "profile.table_id", "must be non-empty")
162
+ if not isinstance(self.schema_digest, str) or _DIGEST.fullmatch(self.schema_digest) is None:
163
+ raise PreparationError("PROFILE_DIGEST", "profile.schema_digest", "is invalid")
164
+ counts = (
165
+ self.row_count,
166
+ self.column_count,
167
+ self.duplicate_row_count,
168
+ self.duplicate_key_count,
169
+ )
170
+ if any(type(value) is not int or value < 0 for value in counts):
171
+ raise PreparationError("PROFILE_COUNT", "profile", "contains invalid counts")
172
+ if self.column_count != len(self.fields):
173
+ raise PreparationError(
174
+ "PROFILE_COUNT",
175
+ "profile.column_count",
176
+ "does not equal the field count",
177
+ )
178
+ if self.duplicate_row_count > self.row_count or self.duplicate_key_count > self.row_count:
179
+ raise PreparationError("PROFILE_COUNT", "profile", "duplicate count exceeds row count")
180
+ if any(item.null_count + item.non_null_count != self.row_count for item in self.fields):
181
+ raise PreparationError(
182
+ "PROFILE_COUNT",
183
+ "profile.fields",
184
+ "field counts do not reconcile the table row count",
185
+ )
186
+ field_names = tuple(item.name for item in self.fields)
187
+ if len(set(field_names)) != len(field_names):
188
+ raise PreparationError("PROFILE_FIELDS", "profile.fields", "contains duplicate names")
189
+ if any(column not in field_names for column in self.key_columns):
190
+ raise PreparationError(
191
+ "PROFILE_KEYS", "profile.key_columns", "references unknown fields"
192
+ )
193
+ temporal_names = tuple(item[0] for item in self.temporal_coverage)
194
+ if len(set(temporal_names)) != len(temporal_names) or any(
195
+ column not in field_names for column in temporal_names
196
+ ):
197
+ raise PreparationError(
198
+ "PROFILE_TEMPORAL",
199
+ "profile.temporal_coverage",
200
+ "is duplicate or references an unknown field",
201
+ )
202
+
203
+ @property
204
+ def digest(self) -> str:
205
+ return canonical_sha256(self.to_dict())
206
+
207
+ def to_dict(self) -> dict[str, Any]:
208
+ return {
209
+ "schema_version": self.schema_version,
210
+ "table_id": self.table_id,
211
+ "schema_digest": self.schema_digest,
212
+ "row_count": self.row_count,
213
+ "column_count": self.column_count,
214
+ "duplicate_row_count": self.duplicate_row_count,
215
+ "key_columns": list(self.key_columns),
216
+ "duplicate_key_count": self.duplicate_key_count,
217
+ "temporal_coverage": [
218
+ {"column": column, "minimum": minimum, "maximum": maximum}
219
+ for column, minimum, maximum in self.temporal_coverage
220
+ ],
221
+ "fields": [field.to_dict() for field in self.fields],
222
+ }
223
+
224
+
225
+ def profile_table(state: TableState, *, key_columns: tuple[str, ...] = ()) -> TableProfile:
226
+ """Calculate an exact deterministic bounded profile."""
227
+
228
+ key_indexes: tuple[int, ...] = ()
229
+ if key_columns:
230
+ key_indexes = validate_columns(state, key_columns, "profile.key_columns")
231
+ duplicate_rows = sum(
232
+ count - 1 for count in Counter(_row_key(row) for row in state.rows).values()
233
+ )
234
+ duplicate_keys = 0
235
+ if key_indexes:
236
+ duplicate_keys = sum(
237
+ count - 1
238
+ for count in Counter(
239
+ tuple(_value_key(row[index]) for index in key_indexes) for row in state.rows
240
+ ).values()
241
+ )
242
+ fields = tuple(
243
+ _profile_field(field.name, field.logical_type, field.physical_type, values)
244
+ for field, values in (
245
+ (field, tuple(row[index] for row in state.rows))
246
+ for index, field in enumerate(state.fields)
247
+ )
248
+ )
249
+ temporal = tuple(
250
+ (profile.name, profile.minimum, profile.maximum)
251
+ for field, profile in zip(state.fields, fields, strict=True)
252
+ if field.temporal_role != "none"
253
+ )
254
+ schema_digest = canonical_sha256([field.to_dict() for field in state.fields])
255
+ return TableProfile(
256
+ schema_version=PROFILE_VERSION,
257
+ table_id=state.table_id,
258
+ schema_digest=schema_digest,
259
+ row_count=len(state.rows),
260
+ column_count=len(state.fields),
261
+ duplicate_row_count=duplicate_rows,
262
+ key_columns=key_columns,
263
+ duplicate_key_count=duplicate_keys,
264
+ temporal_coverage=temporal,
265
+ fields=fields,
266
+ )
267
+
268
+
269
+ def _profile_field(
270
+ name: str,
271
+ logical_type: str,
272
+ physical_type: str,
273
+ values: tuple[Any, ...],
274
+ ) -> FieldProfile:
275
+ non_null = tuple(value for value in values if value is not None)
276
+ keyed = Counter(_value_key(value) for value in non_null)
277
+ distribution_items = sorted(
278
+ keyed.items(),
279
+ key=lambda item: (-item[1], item[0]),
280
+ )
281
+ distribution = tuple(
282
+ ValueCount(value=key, count=count)
283
+ for key, count in distribution_items[:MAX_DISTRIBUTION_VALUES]
284
+ )
285
+ other = sum(count for _, count in distribution_items[MAX_DISTRIBUTION_VALUES:])
286
+ ordered = sorted(non_null, key=_ordered_key)
287
+ minimum = _evidence_scalar(ordered[0]) if ordered else None
288
+ maximum = _evidence_scalar(ordered[-1]) if ordered else None
289
+ minimum_bytes: int | None = None
290
+ maximum_bytes: int | None = None
291
+ if logical_type == "string" and non_null:
292
+ sizes = tuple(len(value.encode("utf-8")) for value in non_null)
293
+ minimum_bytes = min(sizes)
294
+ maximum_bytes = max(sizes)
295
+ quantiles: tuple[tuple[str, str], ...] = ()
296
+ outlier_count = 0
297
+ lower: str | None = None
298
+ upper: str | None = None
299
+ if logical_type in {"int64", "float64"} and non_null:
300
+ numeric = sorted(_decimal(value) for value in non_null)
301
+ q1 = _nearest_rank(numeric, 1, 4)
302
+ q2 = _nearest_rank(numeric, 1, 2)
303
+ q3 = _nearest_rank(numeric, 3, 4)
304
+ quantiles = (
305
+ ("0.25", _decimal_text(q1)),
306
+ ("0.50", _decimal_text(q2)),
307
+ ("0.75", _decimal_text(q3)),
308
+ )
309
+ iqr = q3 - q1
310
+ lower_value = q1 - (iqr * Decimal(3) / Decimal(2))
311
+ upper_value = q3 + (iqr * Decimal(3) / Decimal(2))
312
+ lower = _decimal_text(lower_value)
313
+ upper = _decimal_text(upper_value)
314
+ outlier_count = sum(value < lower_value or value > upper_value for value in numeric)
315
+ return FieldProfile(
316
+ name=name,
317
+ logical_type=logical_type,
318
+ physical_type=physical_type,
319
+ null_count=len(values) - len(non_null),
320
+ non_null_count=len(non_null),
321
+ distinct_count=len(keyed),
322
+ minimum=minimum,
323
+ maximum=maximum,
324
+ minimum_utf8_bytes=minimum_bytes,
325
+ maximum_utf8_bytes=maximum_bytes,
326
+ quantiles=quantiles,
327
+ distribution=distribution,
328
+ other_value_count=other,
329
+ outlier_count=outlier_count,
330
+ outlier_lower_bound=lower,
331
+ outlier_upper_bound=upper,
332
+ )
333
+
334
+
335
+ def _nearest_rank(values: list[Decimal], numerator: int, denominator: int) -> Decimal:
336
+ index = ((len(values) - 1) * numerator) // denominator
337
+ return values[index]
338
+
339
+
340
+ def _decimal(value: Any) -> Decimal:
341
+ if type(value) is int:
342
+ return Decimal(value)
343
+ if type(value) is float and math.isfinite(value):
344
+ return Decimal(str(value))
345
+ raise PreparationError("PROFILE_NUMERIC", "profile", "encountered a non-finite numeric value")
346
+
347
+
348
+ def _decimal_text(value: Decimal) -> str:
349
+ normalized = value.normalize()
350
+ text = format(normalized, "f")
351
+ if "." in text:
352
+ text = text.rstrip("0").rstrip(".")
353
+ return "0" if text in {"", "-0"} else text
354
+
355
+
356
+ def _evidence_scalar(value: Any) -> str:
357
+ if value is None:
358
+ return "null"
359
+ if type(value) is bool:
360
+ return "true" if value else "false"
361
+ if type(value) is int:
362
+ return str(value)
363
+ if type(value) is float:
364
+ return _decimal_text(_decimal(value))
365
+ return value
366
+
367
+
368
+ def _value_key(value: Any) -> str:
369
+ return f"{type(value).__name__}:{_evidence_scalar(value)}"
370
+
371
+
372
+ def _ordered_key(value: Any) -> tuple[int, Any]:
373
+ if type(value) is bool:
374
+ return (0, value)
375
+ if type(value) in {int, float}:
376
+ return (1, _decimal(value))
377
+ return (2, value)
378
+
379
+
380
+ def _row_key(row: tuple[Any, ...]) -> tuple[str, ...]:
381
+ return tuple(_value_key(value) for value in row)
382
+
383
+
384
+ __all__ = ["FieldProfile", "TableProfile", "ValueCount", "profile_table"]
@@ -0,0 +1,217 @@
1
+ """Immutable in-memory tables at the acquisition-to-preparation boundary."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Iterable
7
+ from dataclasses import dataclass, replace
8
+ from datetime import date
9
+ from typing import Any
10
+
11
+ from mostlyright.data_harness.acquisition import ParsedTable
12
+ from mostlyright.data_harness.preparation.contracts import (
13
+ MAX_FIELDS,
14
+ MAX_ROWS,
15
+ ColumnLineage,
16
+ FieldSpec,
17
+ LineageInput,
18
+ parse_utc,
19
+ )
20
+ from mostlyright.data_harness.preparation.errors import PreparationError
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class TableState:
25
+ """An immutable typed table with complete source-to-current lineage."""
26
+
27
+ table_id: str
28
+ snapshot_digests: tuple[str, ...]
29
+ fields: tuple[FieldSpec, ...]
30
+ rows: tuple[tuple[Any, ...], ...]
31
+ lineage: tuple[ColumnLineage, ...]
32
+
33
+ def __post_init__(self) -> None:
34
+ if not isinstance(self.table_id, str) or not self.table_id:
35
+ raise PreparationError("TABLE_ID", "table.table_id", "must be non-empty")
36
+ if (
37
+ not isinstance(self.snapshot_digests, tuple)
38
+ or not self.snapshot_digests
39
+ or tuple(sorted(set(self.snapshot_digests))) != self.snapshot_digests
40
+ ):
41
+ raise PreparationError(
42
+ "TABLE_SNAPSHOTS",
43
+ "table.snapshot_digests",
44
+ "must be a non-empty sorted unique tuple",
45
+ )
46
+ if not isinstance(self.fields, tuple) or not 1 <= len(self.fields) <= MAX_FIELDS:
47
+ raise PreparationError("TABLE_FIELDS", "table.fields", "field count is outside bounds")
48
+ names = tuple(field.name for field in self.fields)
49
+ if len(set(names)) != len(names):
50
+ raise PreparationError("TABLE_FIELDS", "table.fields", "field names are not unique")
51
+ if not isinstance(self.rows, tuple) or len(self.rows) > MAX_ROWS:
52
+ raise PreparationError("TABLE_ROWS", "table.rows", "row count is outside bounds")
53
+ for row_index, row in enumerate(self.rows):
54
+ if not isinstance(row, tuple) or len(row) != len(self.fields):
55
+ raise PreparationError(
56
+ "TABLE_ROW_WIDTH",
57
+ f"table.rows[{row_index}]",
58
+ "row width differs from the schema",
59
+ )
60
+ for column_index, (field, value) in enumerate(zip(self.fields, row, strict=True)):
61
+ _validate_cell(value, field, f"table.rows[{row_index}][{column_index}]")
62
+ if not isinstance(self.lineage, tuple) or len(self.lineage) != len(self.fields):
63
+ raise PreparationError(
64
+ "TABLE_LINEAGE",
65
+ "table.lineage",
66
+ "must contain exactly one record per field",
67
+ )
68
+ if tuple(item.output_column for item in self.lineage) != names:
69
+ raise PreparationError(
70
+ "TABLE_LINEAGE",
71
+ "table.lineage",
72
+ "must be ordered exactly like the fields",
73
+ )
74
+ if any(
75
+ item.snapshot_digest not in self.snapshot_digests
76
+ for lineage in self.lineage
77
+ for item in lineage.inputs
78
+ ):
79
+ raise PreparationError(
80
+ "TABLE_LINEAGE",
81
+ "table.lineage",
82
+ "references an unbound snapshot digest",
83
+ )
84
+
85
+ @property
86
+ def columns(self) -> tuple[str, ...]:
87
+ return tuple(field.name for field in self.fields)
88
+
89
+ def index(self, column: str) -> int:
90
+ try:
91
+ return self.columns.index(column)
92
+ except ValueError:
93
+ raise PreparationError("TABLE_COLUMN", column, "column does not exist") from None
94
+
95
+ def field(self, column: str) -> FieldSpec:
96
+ return self.fields[self.index(column)]
97
+
98
+ def dictionaries(self) -> tuple[dict[str, Any], ...]:
99
+ return tuple(dict(zip(self.columns, row, strict=True)) for row in self.rows)
100
+
101
+ def replace(
102
+ self,
103
+ *,
104
+ table_id: str | None = None,
105
+ fields: tuple[FieldSpec, ...] | None = None,
106
+ rows: tuple[tuple[Any, ...], ...] | None = None,
107
+ lineage: tuple[ColumnLineage, ...] | None = None,
108
+ snapshot_digests: tuple[str, ...] | None = None,
109
+ ) -> TableState:
110
+ return replace(
111
+ self,
112
+ table_id=self.table_id if table_id is None else table_id,
113
+ fields=self.fields if fields is None else fields,
114
+ rows=self.rows if rows is None else rows,
115
+ lineage=self.lineage if lineage is None else lineage,
116
+ snapshot_digests=(
117
+ self.snapshot_digests if snapshot_digests is None else snapshot_digests
118
+ ),
119
+ )
120
+
121
+
122
+ def table_from_parsed(
123
+ *,
124
+ source_id: str,
125
+ parsed: ParsedTable,
126
+ fields: tuple[FieldSpec, ...],
127
+ ) -> TableState:
128
+ """Bind one parsed acquisition snapshot into a typed preparation table."""
129
+
130
+ if not isinstance(parsed, ParsedTable):
131
+ raise PreparationError(
132
+ "TABLE_PARSED", "parsed", "must be verified acquisition ParsedTable evidence"
133
+ )
134
+ if tuple(field.name for field in fields) != parsed.columns:
135
+ raise PreparationError(
136
+ "TABLE_SCHEMA",
137
+ "fields",
138
+ "declared fields do not exactly match parsed columns",
139
+ )
140
+ if any(field.source_id != source_id for field in fields):
141
+ raise PreparationError("TABLE_SOURCE", "fields", "field source IDs do not match")
142
+ lineage = tuple(
143
+ ColumnLineage(
144
+ output_column=field.name,
145
+ inputs=(
146
+ LineageInput(
147
+ source_id=source_id,
148
+ snapshot_digest=parsed.input_sha256,
149
+ column=field.name,
150
+ ),
151
+ ),
152
+ operation_ids=(),
153
+ )
154
+ for field in fields
155
+ )
156
+ return TableState(
157
+ table_id=source_id,
158
+ snapshot_digests=(parsed.input_sha256,),
159
+ fields=fields,
160
+ rows=parsed.rows,
161
+ lineage=lineage,
162
+ )
163
+
164
+
165
+ def validate_columns(state: TableState, columns: Iterable[str], path: str) -> tuple[int, ...]:
166
+ indexes: list[int] = []
167
+ seen: set[str] = set()
168
+ for column in columns:
169
+ if column in seen:
170
+ raise PreparationError("TABLE_COLUMN", path, "contains duplicate columns")
171
+ seen.add(column)
172
+ indexes.append(state.index(column))
173
+ if not indexes:
174
+ raise PreparationError("TABLE_COLUMN", path, "must contain at least one column")
175
+ return tuple(indexes)
176
+
177
+
178
+ def _validate_cell(value: Any, field: FieldSpec, path: str) -> None:
179
+ if value is None:
180
+ if not field.nullable:
181
+ raise PreparationError("TABLE_NULL", path, f"{field.name} is not nullable")
182
+ return
183
+ valid = False
184
+ if field.logical_type == "string":
185
+ valid = (
186
+ isinstance(value, str)
187
+ and len(value.encode("utf-8", errors="surrogatepass")) <= 1_048_576
188
+ and not any(0xD800 <= ord(character) <= 0xDFFF for character in value)
189
+ )
190
+ elif field.logical_type == "int64":
191
+ valid = type(value) is int and -(1 << 63) <= value <= (1 << 63) - 1
192
+ elif field.logical_type == "float64":
193
+ valid = type(value) in {int, float} and math.isfinite(float(value))
194
+ elif field.logical_type == "boolean":
195
+ valid = type(value) is bool
196
+ elif field.logical_type == "date":
197
+ if isinstance(value, str):
198
+ try:
199
+ valid = date.fromisoformat(value).isoformat() == value and len(value) == 10
200
+ except ValueError:
201
+ valid = False
202
+ elif field.logical_type == "timestamp_utc":
203
+ if isinstance(value, str):
204
+ try:
205
+ parse_utc(value, path)
206
+ valid = True
207
+ except PreparationError:
208
+ valid = False
209
+ if not valid:
210
+ raise PreparationError(
211
+ "TABLE_CELL_TYPE",
212
+ path,
213
+ f"value does not satisfy declared {field.logical_type}",
214
+ )
215
+
216
+
217
+ __all__ = ["TableState", "table_from_parsed", "validate_columns"]