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.
- mostlyright/data_harness/__init__.py +158 -0
- mostlyright/data_harness/acquisition/__init__.py +55 -0
- mostlyright/data_harness/acquisition/http.py +2773 -0
- mostlyright/data_harness/acquisition/parsing.py +809 -0
- mostlyright/data_harness/acquisition/ranges.py +495 -0
- mostlyright/data_harness/acquisition/result_download.py +360 -0
- mostlyright/data_harness/acquisition/retention_admission.py +248 -0
- mostlyright/data_harness/acquisition/sandbox.py +4888 -0
- mostlyright/data_harness/acquisition/url_policy.py +530 -0
- mostlyright/data_harness/agent_runtime.py +2743 -0
- mostlyright/data_harness/assets/logo-ink.svg +31 -0
- mostlyright/data_harness/backends/__init__.py +28 -0
- mostlyright/data_harness/backends/pandas_backend.py +350 -0
- mostlyright/data_harness/backends/polars_backend.py +366 -0
- mostlyright/data_harness/backends/protocol.py +124 -0
- mostlyright/data_harness/backends/reference.py +83 -0
- mostlyright/data_harness/backends/registry.py +55 -0
- mostlyright/data_harness/backends/restrictions.py +126 -0
- mostlyright/data_harness/canonical.py +333 -0
- mostlyright/data_harness/catalog_job.py +625 -0
- mostlyright/data_harness/cli.py +5398 -0
- mostlyright/data_harness/contracts.py +53 -0
- mostlyright/data_harness/coordinator.py +1307 -0
- mostlyright/data_harness/deploy.py +924 -0
- mostlyright/data_harness/deploy_target.py +312 -0
- mostlyright/data_harness/deployment_evidence.py +1067 -0
- mostlyright/data_harness/event_presentation.py +576 -0
- mostlyright/data_harness/events.py +2152 -0
- mostlyright/data_harness/fast_delimited.py +239 -0
- mostlyright/data_harness/fleet.py +237 -0
- mostlyright/data_harness/formats.py +236 -0
- mostlyright/data_harness/governors.py +1163 -0
- mostlyright/data_harness/hosted_bootstrap.py +972 -0
- mostlyright/data_harness/hosted_crawler.py +1115 -0
- mostlyright/data_harness/hosted_crawler_container_smoke.py +351 -0
- mostlyright/data_harness/hosted_crawler_fetch.py +423 -0
- mostlyright/data_harness/hosted_crawler_job.py +1277 -0
- mostlyright/data_harness/hosted_crawler_protocol.py +676 -0
- mostlyright/data_harness/hosted_dataset.py +1500 -0
- mostlyright/data_harness/hosted_deploy.py +3037 -0
- mostlyright/data_harness/hosted_handoff.py +62 -0
- mostlyright/data_harness/hosted_ingestion_contract.py +504 -0
- mostlyright/data_harness/hosted_ingestion_job.py +356 -0
- mostlyright/data_harness/hosted_ingestion_job_smoke.py +40 -0
- mostlyright/data_harness/hosted_session_container_smoke.py +194 -0
- mostlyright/data_harness/hosted_session_worker.py +3554 -0
- mostlyright/data_harness/hosted_session_worker_job_smoke.py +46 -0
- mostlyright/data_harness/hosted_worker.py +6784 -0
- mostlyright/data_harness/ingestion/__init__.py +56 -0
- mostlyright/data_harness/ingestion/contracts.py +461 -0
- mostlyright/data_harness/ingestion/faults.py +42 -0
- mostlyright/data_harness/ingestion/gcs_store.py +1162 -0
- mostlyright/data_harness/ingestion/spool.py +130 -0
- mostlyright/data_harness/ingestion/store.py +885 -0
- mostlyright/data_harness/key_seam.py +434 -0
- mostlyright/data_harness/linux_process_boundary.py +262 -0
- mostlyright/data_harness/local_contracts.py +2880 -0
- mostlyright/data_harness/local_search/__init__.py +5 -0
- mostlyright/data_harness/local_search/build_index.py +1087 -0
- mostlyright/data_harness/local_search/contracts.py +920 -0
- mostlyright/data_harness/local_search/query_trace.py +266 -0
- mostlyright/data_harness/local_search/retrieval.py +700 -0
- mostlyright/data_harness/local_search/sealed.py +474 -0
- mostlyright/data_harness/local_search/service.py +784 -0
- mostlyright/data_harness/nbrender/CONTRACT.md +212 -0
- mostlyright/data_harness/nbrender/__init__.py +12 -0
- mostlyright/data_harness/nbrender/chrome.py +359 -0
- mostlyright/data_harness/nbrender/code_body.py +266 -0
- mostlyright/data_harness/nbrender/document.py +407 -0
- mostlyright/data_harness/nbrender/frame.py +275 -0
- mostlyright/data_harness/nbrender/interactive.py +337 -0
- mostlyright/data_harness/nbrender/markdown_body.py +477 -0
- mostlyright/data_harness/nbrender/mr_components.py +134 -0
- mostlyright/data_harness/nbrender/outputs_data.py +595 -0
- mostlyright/data_harness/nbrender/outputs_rich.py +906 -0
- mostlyright/data_harness/nbrender/outputs_source.py +260 -0
- mostlyright/data_harness/nbrender/outputs_stage.py +176 -0
- mostlyright/data_harness/nbrender/outputs_text.py +400 -0
- mostlyright/data_harness/nbrender/parse.py +394 -0
- mostlyright/data_harness/nbrender/status.py +40 -0
- mostlyright/data_harness/nbrender/tokens.py +1295 -0
- mostlyright/data_harness/notebook.py +1710 -0
- mostlyright/data_harness/offline.py +2049 -0
- mostlyright/data_harness/operation_registry.py +1007 -0
- mostlyright/data_harness/operator_setup.py +239 -0
- mostlyright/data_harness/pipeline.py +6428 -0
- mostlyright/data_harness/plan_graph.py +2026 -0
- mostlyright/data_harness/preparation/__init__.py +104 -0
- mostlyright/data_harness/preparation/contracts.py +1017 -0
- mostlyright/data_harness/preparation/engine.py +221 -0
- mostlyright/data_harness/preparation/errors.py +14 -0
- mostlyright/data_harness/preparation/gates.py +751 -0
- mostlyright/data_harness/preparation/joins.py +574 -0
- mostlyright/data_harness/preparation/profile.py +384 -0
- mostlyright/data_harness/preparation/table.py +217 -0
- mostlyright/data_harness/preparation/transforms.py +568 -0
- mostlyright/data_harness/progress_events.py +534 -0
- mostlyright/data_harness/readers/__init__.py +46 -0
- mostlyright/data_harness/readers/containers.py +963 -0
- mostlyright/data_harness/readers/contracts.py +542 -0
- mostlyright/data_harness/readers/delimited.py +257 -0
- mostlyright/data_harness/readers/grib2/__init__.py +33 -0
- mostlyright/data_harness/readers/grib2/admission.py +722 -0
- mostlyright/data_harness/readers/grib2/decode.py +1009 -0
- mostlyright/data_harness/readers/grib2/geometry.py +1133 -0
- mostlyright/data_harness/readers/grib2/portable_math.py +501 -0
- mostlyright/data_harness/readers/json_tabular.py +485 -0
- mostlyright/data_harness/readers/registry.py +514 -0
- mostlyright/data_harness/readers/samples/README.md +110 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/cities.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/cities.csv +3 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/expected.json +23 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/cities.tsv +5 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/expected.json +30 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/observations.csv +5 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/expected.json +44 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/stations.json +1 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/expected.json +48 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/observations.ndjson +4 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/cities.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/expected.json +24 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/README.md +20 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/expected.json +55 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/gfs-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/hrrr-categorical-rain.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples.py +582 -0
- mostlyright/data_harness/readers/spreadsheet.py +803 -0
- mostlyright/data_harness/readers/tabular.py +510 -0
- mostlyright/data_harness/recipe.py +5321 -0
- mostlyright/data_harness/repair/__init__.py +78 -0
- mostlyright/data_harness/repair/adapters.py +274 -0
- mostlyright/data_harness/repair/contracts.py +872 -0
- mostlyright/data_harness/repair/coordinator.py +1099 -0
- mostlyright/data_harness/repair/errors.py +16 -0
- mostlyright/data_harness/review.py +2533 -0
- mostlyright/data_harness/rowset.py +283 -0
- mostlyright/data_harness/serving.py +1975 -0
- mostlyright/data_harness/serving_edge.py +590 -0
- mostlyright/data_harness/serving_http.py +1031 -0
- mostlyright/data_harness/session_probes.py +759 -0
- mostlyright/data_harness/signing.py +101 -0
- mostlyright/data_harness/source_discovery.py +898 -0
- mostlyright/data_harness/sources/__init__.py +209 -0
- mostlyright/data_harness/sources/_adapter_steps.py +213 -0
- mostlyright/data_harness/sources/adapters.py +1214 -0
- mostlyright/data_harness/sources/cadence.py +1428 -0
- mostlyright/data_harness/sources/cadence_emission.py +453 -0
- mostlyright/data_harness/sources/cadence_history.py +546 -0
- mostlyright/data_harness/sources/catalog/__init__.py +17 -0
- mostlyright/data_harness/sources/catalog/admission.py +477 -0
- mostlyright/data_harness/sources/catalog/authoring.py +1701 -0
- mostlyright/data_harness/sources/catalog/authoring_policy.py +701 -0
- mostlyright/data_harness/sources/catalog/authoring_shards.py +1217 -0
- mostlyright/data_harness/sources/catalog/bounded_io.py +231 -0
- mostlyright/data_harness/sources/catalog/channel.py +523 -0
- mostlyright/data_harness/sources/catalog/channel_client.py +296 -0
- mostlyright/data_harness/sources/catalog/contracts.py +825 -0
- mostlyright/data_harness/sources/catalog/coverage.py +137 -0
- mostlyright/data_harness/sources/catalog/delta.py +1340 -0
- mostlyright/data_harness/sources/catalog/embedding.py +532 -0
- mostlyright/data_harness/sources/catalog/entry_v2.py +1182 -0
- mostlyright/data_harness/sources/catalog/fill.py +3889 -0
- mostlyright/data_harness/sources/catalog/fill_partitions.py +459 -0
- mostlyright/data_harness/sources/catalog/fill_staging.py +1105 -0
- mostlyright/data_harness/sources/catalog/gating.py +374 -0
- mostlyright/data_harness/sources/catalog/generation_receipt.py +1607 -0
- mostlyright/data_harness/sources/catalog/harvest/__init__.py +7 -0
- mostlyright/data_harness/sources/catalog/harvest/ckan.py +384 -0
- mostlyright/data_harness/sources/catalog/harvest/datagov_v4.py +798 -0
- mostlyright/data_harness/sources/catalog/harvest/protocol.py +964 -0
- mostlyright/data_harness/sources/catalog/harvest/sdmx.py +445 -0
- mostlyright/data_harness/sources/catalog/harvest/stac.py +384 -0
- mostlyright/data_harness/sources/catalog/health.py +447 -0
- mostlyright/data_harness/sources/catalog/hosted_catalog.py +105 -0
- mostlyright/data_harness/sources/catalog/identity_history.py +1549 -0
- mostlyright/data_harness/sources/catalog/neural.py +1618 -0
- mostlyright/data_harness/sources/catalog/packed_catalog.py +2345 -0
- mostlyright/data_harness/sources/catalog/packed_retrieval.py +1517 -0
- mostlyright/data_harness/sources/catalog/packed_writer.py +2802 -0
- mostlyright/data_harness/sources/catalog/query_trace.py +1037 -0
- mostlyright/data_harness/sources/catalog/recommend.py +171 -0
- mostlyright/data_harness/sources/catalog/retrieval.py +230 -0
- mostlyright/data_harness/sources/catalog/retrieval_manifest.py +995 -0
- mostlyright/data_harness/sources/catalog/rights_decisions.py +254 -0
- mostlyright/data_harness/sources/catalog/sealed.py +560 -0
- mostlyright/data_harness/sources/catalog/search.py +230 -0
- mostlyright/data_harness/sources/catalog/streaming_delta.py +1097 -0
- mostlyright/data_harness/sources/catalog/update.py +891 -0
- mostlyright/data_harness/sources/collections.py +815 -0
- mostlyright/data_harness/sources/contracts.py +2223 -0
- mostlyright/data_harness/sources/deletion.py +761 -0
- mostlyright/data_harness/sources/fitness.py +162 -0
- mostlyright/data_harness/sources/governance.py +163 -0
- mostlyright/data_harness/sources/hosted.py +173 -0
- mostlyright/data_harness/sources/integration.py +218 -0
- mostlyright/data_harness/sources/range_reader.py +418 -0
- mostlyright/data_harness/sources/registry.py +514 -0
- mostlyright/data_harness/sources/rights_rule.py +59 -0
- mostlyright/data_harness/sources/source_cadence_vectors.v1.json +1 -0
- mostlyright/data_harness/sources/sports.py +521 -0
- mostlyright/data_harness/sources/stream.py +524 -0
- mostlyright/data_harness/sources/stream_connector.py +418 -0
- mostlyright/data_harness/sources/stream_recorder.py +1404 -0
- mostlyright/data_harness/studio_boundary.py +2019 -0
- mostlyright/data_harness/thin/__init__.py +37 -0
- mostlyright/data_harness/thin/acquire.py +1137 -0
- mostlyright/data_harness/thin/acquire_cancel.py +579 -0
- mostlyright/data_harness/thin/approvals.py +617 -0
- mostlyright/data_harness/thin/commands.py +406 -0
- mostlyright/data_harness/thin/download.py +194 -0
- mostlyright/data_harness/thin/narrative.py +589 -0
- mostlyright/data_harness/thin/parity.py +1070 -0
- mostlyright/data_harness/thin/propose.py +2759 -0
- mostlyright/data_harness/thin/research.py +1663 -0
- mostlyright/data_harness/thin/router.py +924 -0
- mostlyright/data_harness/thin/runs.py +519 -0
- mostlyright/data_harness/thin/session.py +281 -0
- mostlyright/data_harness/thin/stream.py +501 -0
- mostlyright/data_harness/thin/transport.py +187 -0
- mostlyright/data_harness/thin/vocabulary.py +368 -0
- mostlyright/data_harness/thin/workers.py +164 -0
- mostlyright/data_harness/ucum/TABLE-PIN.json +40 -0
- mostlyright/data_harness/ucum/ucum-subset.v1.json +632 -0
- mostlyright/data_harness/unit_flow.py +927 -0
- mostlyright/data_harness/units.py +572 -0
- mostlyright/data_harness/ux/__init__.py +9 -0
- mostlyright/data_harness/ux/approve.py +485 -0
- mostlyright/data_harness/ux/author_yaml.py +597 -0
- mostlyright/data_harness/ux/cloud_auth.py +447 -0
- mostlyright/data_harness/ux/commands/__init__.py +260 -0
- mostlyright/data_harness/ux/commands/approve.py +136 -0
- mostlyright/data_harness/ux/commands/auth.py +744 -0
- mostlyright/data_harness/ux/commands/author.py +79 -0
- mostlyright/data_harness/ux/commands/catalog_author.py +403 -0
- mostlyright/data_harness/ux/commands/catalog_fill.py +523 -0
- mostlyright/data_harness/ux/commands/catalog_harvest.py +545 -0
- mostlyright/data_harness/ux/commands/catalog_publish.py +1838 -0
- mostlyright/data_harness/ux/commands/catalog_search.py +71 -0
- mostlyright/data_harness/ux/commands/catalog_update.py +437 -0
- mostlyright/data_harness/ux/commands/deploy.py +134 -0
- mostlyright/data_harness/ux/commands/deploy_dataset.py +98 -0
- mostlyright/data_harness/ux/commands/deploy_plan.py +105 -0
- mostlyright/data_harness/ux/commands/deploy_status.py +104 -0
- mostlyright/data_harness/ux/commands/diff.py +74 -0
- mostlyright/data_harness/ux/commands/index.py +84 -0
- mostlyright/data_harness/ux/commands/inventory.py +47 -0
- mostlyright/data_harness/ux/commands/list_builds.py +143 -0
- mostlyright/data_harness/ux/commands/login.py +63 -0
- mostlyright/data_harness/ux/commands/peek.py +236 -0
- mostlyright/data_harness/ux/commands/plan_check.py +90 -0
- mostlyright/data_harness/ux/commands/preflight.py +97 -0
- mostlyright/data_harness/ux/commands/record.py +107 -0
- mostlyright/data_harness/ux/commands/review_setup.py +47 -0
- mostlyright/data_harness/ux/commands/search.py +440 -0
- mostlyright/data_harness/ux/commands/show.py +61 -0
- mostlyright/data_harness/ux/commands/whoami.py +37 -0
- mostlyright/data_harness/ux/credential_native.py +551 -0
- mostlyright/data_harness/ux/credential_store.py +1055 -0
- mostlyright/data_harness/ux/credentials.py +631 -0
- mostlyright/data_harness/ux/diffing.py +444 -0
- mostlyright/data_harness/ux/headline.py +671 -0
- mostlyright/data_harness/ux/hosted_acquisition.py +974 -0
- mostlyright/data_harness/ux/hosted_run_status.py +619 -0
- mostlyright/data_harness/ux/inventory.py +427 -0
- mostlyright/data_harness/ux/local_review.py +375 -0
- mostlyright/data_harness/ux/login.py +691 -0
- mostlyright/data_harness/ux/path_kind.py +147 -0
- mostlyright/data_harness/ux/peek.py +1000 -0
- mostlyright/data_harness/ux/plain_file.py +178 -0
- mostlyright/data_harness/ux/plan_check.py +311 -0
- mostlyright/data_harness/ux/preflight.py +918 -0
- mostlyright/data_harness/ux/readers.py +1124 -0
- mostlyright/data_harness/ux/remediation.py +2195 -0
- mostlyright/data_harness/ux/render.py +657 -0
- mostlyright/data_harness/ux/workload.py +1077 -0
- mostlyright/data_harness/viewer.py +3713 -0
- mostlyright/data_harness/visual_run/__init__.py +83 -0
- mostlyright/data_harness/visual_run/authoring.py +235 -0
- mostlyright/data_harness/visual_run/contracts.py +673 -0
- mostlyright/data_harness/visual_run/materialize.py +486 -0
- mostlyright/data_harness/visual_run/observations.py +874 -0
- mostlyright/data_harness/visual_run/query.py +259 -0
- mostlyright/data_harness/visual_run/reducer.py +280 -0
- mostlyright/data_harness/visual_run/sdk.py +892 -0
- mostlyright/data_harness/visual_run/store.py +584 -0
- mostlyright/data_harness/visual_run/transport.py +239 -0
- mostlyright/data_harness/watch.py +2999 -0
- mostlyright_data-0.9.0.dist-info/METADATA +607 -0
- mostlyright_data-0.9.0.dist-info/RECORD +314 -0
- mostlyright_data-0.9.0.dist-info/WHEEL +4 -0
- mostlyright_data-0.9.0.dist-info/entry_points.txt +12 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""The ONE cross-runtime value domain for a cell of a sealed dataset.
|
|
2
|
+
|
|
3
|
+
Two callers need to turn sealed Parquet bytes into a canonical JSON payload: the hosted
|
|
4
|
+
worker's table preview and the serving surface. They face the same two hard facts.
|
|
5
|
+
``canonical.canonical_json_bytes`` refuses every float, so a numeric cell has to reach the
|
|
6
|
+
encoder as text; and ``canonical.MAX_CANONICAL_MEMBERS`` is exactly the pipeline's
|
|
7
|
+
``MAX_ROWS``, so a whole-dataset payload cannot be canonicalized at all and has to be fitted
|
|
8
|
+
to a byte budget.
|
|
9
|
+
|
|
10
|
+
Both problems are solved here once. A second renderer would be a second value domain, and
|
|
11
|
+
two restatements of a value domain stay mutually consistent right up until one of them is
|
|
12
|
+
edited -- which is the failure ``docs/CONTRACTS.md`` records for restated format tables: the
|
|
13
|
+
copy keeps agreeing with itself while the real gate rots.
|
|
14
|
+
|
|
15
|
+
This module is deliberately a LEAF. Its only in-package import is ``canonical``; the bounds
|
|
16
|
+
it enforces arrive as arguments rather than being read from ``pipeline``. A caller that must
|
|
17
|
+
prove a small call graph -- that no request path can reach an origin fetch or an unsealed
|
|
18
|
+
draft -- can then start from a small named set instead of from the pipeline's whole import
|
|
19
|
+
closure.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import math
|
|
25
|
+
import re
|
|
26
|
+
from collections.abc import Callable, Sequence
|
|
27
|
+
|
|
28
|
+
import pyarrow as pa
|
|
29
|
+
import pyarrow.parquet as pq
|
|
30
|
+
|
|
31
|
+
from mostlyright.data_harness.canonical import CanonicalJSONError
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"COLUMN_NAME",
|
|
35
|
+
"RowsetError",
|
|
36
|
+
"fit_canonical_payload",
|
|
37
|
+
"inspect_sealed_parquet",
|
|
38
|
+
"open_sealed_parquet",
|
|
39
|
+
"render_cell",
|
|
40
|
+
"render_filtered_window",
|
|
41
|
+
"render_rows",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# The one refusal code this module raises. A caller that needs a different error identity
|
|
45
|
+
# translates at its own boundary rather than teaching this module about that identity.
|
|
46
|
+
ROWS_INVALID = "DATASET_ROWS_INVALID"
|
|
47
|
+
|
|
48
|
+
# The frozen column-name shape a sealed dataset may present to either reader.
|
|
49
|
+
COLUMN_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_.:-]{0,127}$")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RowsetError(RuntimeError):
|
|
53
|
+
"""A deterministic rowset gate refused a sealed dataset read.
|
|
54
|
+
|
|
55
|
+
Shaped like ``pipeline.BuildError``: a typed refusal carrying a greppable ``code``,
|
|
56
|
+
whose ``str()`` is exactly the message, so a caller can re-raise it under its own error
|
|
57
|
+
type without the message text changing.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, code: str, message: str) -> None:
|
|
61
|
+
super().__init__(message)
|
|
62
|
+
self.code = code
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def render_cell(scalar: pa.Scalar) -> bool | str | None:
|
|
66
|
+
"""Render one Arrow scalar into the cross-runtime value domain.
|
|
67
|
+
|
|
68
|
+
The domain is exactly ``bool | str | None``. Every number leaves as text because the
|
|
69
|
+
canonical encoder refuses non-integer JSON numbers outright, and rendering an integer as
|
|
70
|
+
text too keeps one rule for all numerics instead of a width-dependent one.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
if not scalar.is_valid:
|
|
74
|
+
return None
|
|
75
|
+
value = scalar.as_py()
|
|
76
|
+
data_type = scalar.type
|
|
77
|
+
if pa.types.is_boolean(data_type):
|
|
78
|
+
if type(value) is not bool: # pragma: no cover - Arrow type invariant
|
|
79
|
+
raise RowsetError(ROWS_INVALID, "boolean scalar is invalid")
|
|
80
|
+
return value
|
|
81
|
+
if pa.types.is_string(data_type) or pa.types.is_large_string(data_type):
|
|
82
|
+
if not isinstance(value, str): # pragma: no cover - Arrow type invariant
|
|
83
|
+
raise RowsetError(ROWS_INVALID, "string scalar is invalid")
|
|
84
|
+
return value
|
|
85
|
+
if pa.types.is_integer(data_type):
|
|
86
|
+
return str(value)
|
|
87
|
+
if pa.types.is_floating(data_type):
|
|
88
|
+
if not isinstance(value, float) or not math.isfinite(value):
|
|
89
|
+
raise RowsetError(
|
|
90
|
+
ROWS_INVALID,
|
|
91
|
+
"floating preview values must be finite",
|
|
92
|
+
)
|
|
93
|
+
return format(value, ".17g")
|
|
94
|
+
if pa.types.is_decimal(data_type):
|
|
95
|
+
return format(value, "f")
|
|
96
|
+
if pa.types.is_date(data_type):
|
|
97
|
+
return value.isoformat()
|
|
98
|
+
if pa.types.is_timestamp(data_type):
|
|
99
|
+
rendered = value.isoformat(timespec="microseconds")
|
|
100
|
+
return rendered.removesuffix("+00:00") + "Z" if rendered.endswith("+00:00") else rendered
|
|
101
|
+
raise RowsetError(
|
|
102
|
+
ROWS_INVALID,
|
|
103
|
+
f"unsupported preview column type: {data_type}",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def render_rows(
|
|
108
|
+
source: pa.RecordBatch | pa.Table,
|
|
109
|
+
columns: Sequence[str],
|
|
110
|
+
) -> list[dict[str, bool | str | None]]:
|
|
111
|
+
"""Render every row of one batch or table as an ordered mapping of rendered cells.
|
|
112
|
+
|
|
113
|
+
Columns are addressed positionally, in the order the caller read them off the sealed
|
|
114
|
+
schema, so a duplicate or reordered name in the mapping cannot silently pick a different
|
|
115
|
+
Arrow column than the one the guards inspected.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
column: render_cell(source.column(column_index)[row_index])
|
|
121
|
+
for column_index, column in enumerate(columns)
|
|
122
|
+
}
|
|
123
|
+
for row_index in range(source.num_rows)
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def render_filtered_window(
|
|
128
|
+
parquet: pq.ParquetFile,
|
|
129
|
+
columns: Sequence[str],
|
|
130
|
+
*,
|
|
131
|
+
select: Callable[[pa.RecordBatch], pa.RecordBatch | pa.Table],
|
|
132
|
+
offset: int,
|
|
133
|
+
limit: int,
|
|
134
|
+
batch_size: int = 1_024,
|
|
135
|
+
exact_match_count: int | None = None,
|
|
136
|
+
) -> tuple[int, list[dict[str, bool | str | None]]]:
|
|
137
|
+
"""Retain one bounded window and count only when no exact metadata count exists.
|
|
138
|
+
|
|
139
|
+
Sealed file order remains the order: batches are visited in Parquet order and rows are
|
|
140
|
+
appended in their order inside each batch. Unfiltered serving supplies the exact Parquet
|
|
141
|
+
metadata count, so scanning stops as soon as the requested window is full. A filtered read has
|
|
142
|
+
no trustworthy metadata count and continues only to keep ``matched_row_count`` exact. Live
|
|
143
|
+
Arrow state is one fixed-size batch plus at most ``limit`` rendered rows, never the table.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
if exact_match_count is not None and (
|
|
147
|
+
type(exact_match_count) is not int or exact_match_count < 0
|
|
148
|
+
):
|
|
149
|
+
raise RowsetError(ROWS_INVALID, "exact match count is invalid")
|
|
150
|
+
matched_row_count = 0
|
|
151
|
+
rendered: list[dict[str, bool | str | None]] = []
|
|
152
|
+
for row_group in range(parquet.metadata.num_row_groups):
|
|
153
|
+
row_group_rows = parquet.metadata.row_group(row_group).num_rows
|
|
154
|
+
if exact_match_count is not None and matched_row_count + row_group_rows <= offset:
|
|
155
|
+
matched_row_count += row_group_rows
|
|
156
|
+
continue
|
|
157
|
+
for batch in parquet.iter_batches(batch_size=batch_size, row_groups=[row_group]):
|
|
158
|
+
selected = select(batch)
|
|
159
|
+
selected_count = selected.num_rows
|
|
160
|
+
start = max(offset - matched_row_count, 0)
|
|
161
|
+
remaining = limit - len(rendered)
|
|
162
|
+
if remaining > 0 and start < selected_count:
|
|
163
|
+
retained = selected.slice(start, min(remaining, selected_count - start))
|
|
164
|
+
rendered.extend(render_rows(retained, columns))
|
|
165
|
+
matched_row_count += selected_count
|
|
166
|
+
if exact_match_count is not None and len(rendered) == limit:
|
|
167
|
+
return exact_match_count, rendered
|
|
168
|
+
return exact_match_count if exact_match_count is not None else matched_row_count, rendered
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def inspect_sealed_parquet(
|
|
172
|
+
parquet: pq.ParquetFile,
|
|
173
|
+
*,
|
|
174
|
+
max_columns: int,
|
|
175
|
+
max_rows: int,
|
|
176
|
+
max_uncompressed_bytes: int,
|
|
177
|
+
) -> tuple[list[str], int]:
|
|
178
|
+
"""Validate one already-open sealed Parquet file entirely from its metadata."""
|
|
179
|
+
|
|
180
|
+
columns = list(parquet.schema_arrow.names)
|
|
181
|
+
total_row_count = parquet.metadata.num_rows
|
|
182
|
+
if (
|
|
183
|
+
not 1 <= len(columns) <= max_columns
|
|
184
|
+
or len(columns) != len(set(columns))
|
|
185
|
+
or any(not COLUMN_NAME.fullmatch(column) for column in columns)
|
|
186
|
+
):
|
|
187
|
+
raise RowsetError(
|
|
188
|
+
ROWS_INVALID,
|
|
189
|
+
"preview columns violate the frozen table-preview contract",
|
|
190
|
+
)
|
|
191
|
+
if type(total_row_count) is not int or not 0 <= total_row_count <= max_rows:
|
|
192
|
+
raise RowsetError(
|
|
193
|
+
ROWS_INVALID,
|
|
194
|
+
"preview row count exceeds the candidate row budget",
|
|
195
|
+
)
|
|
196
|
+
declared_uncompressed_bytes = sum(
|
|
197
|
+
parquet.metadata.row_group(index).total_byte_size
|
|
198
|
+
for index in range(parquet.metadata.num_row_groups)
|
|
199
|
+
)
|
|
200
|
+
if (
|
|
201
|
+
type(declared_uncompressed_bytes) is not int
|
|
202
|
+
or not 0 <= declared_uncompressed_bytes <= max_uncompressed_bytes
|
|
203
|
+
):
|
|
204
|
+
raise RowsetError(
|
|
205
|
+
ROWS_INVALID,
|
|
206
|
+
"preview Parquet exceeds the candidate decompression budget",
|
|
207
|
+
)
|
|
208
|
+
return columns, total_row_count
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def open_sealed_parquet(
|
|
212
|
+
parquet_raw: bytes,
|
|
213
|
+
*,
|
|
214
|
+
max_columns: int,
|
|
215
|
+
max_rows: int,
|
|
216
|
+
max_uncompressed_bytes: int,
|
|
217
|
+
) -> tuple[pq.ParquetFile, list[str], int]:
|
|
218
|
+
"""Open sealed Parquet bytes and refuse anything outside the declared bounds.
|
|
219
|
+
|
|
220
|
+
Every guard reads only Parquet metadata, so an oversized or malformed file is refused
|
|
221
|
+
before a single row group is decompressed. The bounds arrive as arguments: this module
|
|
222
|
+
does not decide what a dataset's limits are, it only enforces the ones it is handed.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
parquet = pq.ParquetFile(pa.BufferReader(parquet_raw))
|
|
227
|
+
columns, total_row_count = inspect_sealed_parquet(
|
|
228
|
+
parquet,
|
|
229
|
+
max_columns=max_columns,
|
|
230
|
+
max_rows=max_rows,
|
|
231
|
+
max_uncompressed_bytes=max_uncompressed_bytes,
|
|
232
|
+
)
|
|
233
|
+
except RowsetError:
|
|
234
|
+
raise
|
|
235
|
+
except Exception as error:
|
|
236
|
+
raise RowsetError(
|
|
237
|
+
ROWS_INVALID,
|
|
238
|
+
f"sealed Parquet preview derivation failed: {type(error).__name__}",
|
|
239
|
+
) from error
|
|
240
|
+
return parquet, columns, total_row_count
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def fit_canonical_payload(
|
|
244
|
+
encode: Callable[[int], bytes],
|
|
245
|
+
row_count: int,
|
|
246
|
+
*,
|
|
247
|
+
max_bytes: int,
|
|
248
|
+
) -> bytes:
|
|
249
|
+
"""Return the encoding of the longest row prefix that fits ``max_bytes``.
|
|
250
|
+
|
|
251
|
+
The loop searches rather than estimates because the canonical encoding of a row is not a
|
|
252
|
+
function of its Python size: escaping, key ordering and the shared envelope all move the
|
|
253
|
+
encoded length, and ``MAX_CANONICAL_MEMBERS`` bounds a member COUNT rather than a byte
|
|
254
|
+
count. The only honest bound on a candidate prefix is the length of that prefix once
|
|
255
|
+
encoded, so the prefix length is what gets bisected. A ``CanonicalJSONError`` from the
|
|
256
|
+
encoder is read as "this prefix is too large" and shrinks the search, which is what keeps
|
|
257
|
+
the member ceiling a bound on the payload rather than an error propagated to the caller.
|
|
258
|
+
|
|
259
|
+
A payload is always shrunk by whole rows from the end. A cell value is never truncated,
|
|
260
|
+
so every row that is served is the row that was sealed.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
low = 0
|
|
264
|
+
high = row_count
|
|
265
|
+
selected = b""
|
|
266
|
+
while low <= high:
|
|
267
|
+
middle = (low + high) // 2
|
|
268
|
+
try:
|
|
269
|
+
encoded = encode(middle)
|
|
270
|
+
except CanonicalJSONError:
|
|
271
|
+
high = middle - 1
|
|
272
|
+
continue
|
|
273
|
+
if len(encoded) <= max_bytes:
|
|
274
|
+
selected = encoded
|
|
275
|
+
low = middle + 1
|
|
276
|
+
else:
|
|
277
|
+
high = middle - 1
|
|
278
|
+
if not selected:
|
|
279
|
+
raise RowsetError(
|
|
280
|
+
ROWS_INVALID,
|
|
281
|
+
"preview metadata alone exceeds the artifact byte budget",
|
|
282
|
+
)
|
|
283
|
+
return selected
|