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,257 @@
|
|
|
1
|
+
"""``delimited_text``: a tab-separated source, at the cost of one file and no table edit.
|
|
2
|
+
|
|
3
|
+
This family exists to make a point as much as to read a file, so the point is written here
|
|
4
|
+
where the next person to propose the alternative will find it.
|
|
5
|
+
|
|
6
|
+
A delimiter is a setting, not a wire format
|
|
7
|
+
-------------------------------------------
|
|
8
|
+
The sealed artifact is always canonical CSV -- ``formats.READER_OUTPUT_FORMATS`` has one
|
|
9
|
+
member and the reason is recorded there. So a tab-separated source is a Reader family whose
|
|
10
|
+
decode options name a delimiter, and the format tables do not move at all. Under the other
|
|
11
|
+
design, where the sealed artifact keeps the source's own encoding, ``tsv`` would have to
|
|
12
|
+
enter ``PARSER_FORMAT_ORDER`` -- which rotates the ``parse`` and ``retrieve_and_parse``
|
|
13
|
+
policy digests on both platform clauses, re-pins four literals, and widens the media-type
|
|
14
|
+
table, the suffix table, the snapshot-suffix pattern, the deletion regex, the plan-format
|
|
15
|
+
enum, and the parser dispatch. ``raw/{source_id}.csv`` would then be a lie. None of that
|
|
16
|
+
happens here, and ``tests/h3/test_readers_delimited.py`` asserts that none of it happened.
|
|
17
|
+
|
|
18
|
+
Thin on purpose
|
|
19
|
+
---------------
|
|
20
|
+
Admission is "are these bytes valid UTF-8 text within budget", the decode is
|
|
21
|
+
``tabular.decode_delimited``, and the emission is ``tabular.encode_canonical_csv``. The same
|
|
22
|
+
two functions decode a zip member, a gzip stream, and a tar member, which is what lets four
|
|
23
|
+
paths produce one answer. If this module ever grows a parsing rule of its own, that rule has
|
|
24
|
+
been duplicated out of ``tabular.py`` and the four paths have started to disagree.
|
|
25
|
+
|
|
26
|
+
Four decisions, each with its reason
|
|
27
|
+
------------------------------------
|
|
28
|
+
Every one of these is a refusal, and a refusal is only defensible if the reason is written
|
|
29
|
+
next to it.
|
|
30
|
+
|
|
31
|
+
* **The delimiter set is closed** to the single characters in ``ADMITTED_DELIMITERS``: tab
|
|
32
|
+
and comma today. Every additional delimiter is a new parser surface and a new class of
|
|
33
|
+
ambiguity -- a semicolon file in a locale that uses the comma as a decimal separator reads
|
|
34
|
+
as a different table under each -- and no supported source needs one. Widening the
|
|
35
|
+
set is a source edit that goes through review, exactly like the format table.
|
|
36
|
+
* **The encoding is strict UTF-8**, spelled one way. A byte sequence that is not valid UTF-8
|
|
37
|
+
is refused rather than replaced, because a replacement character in sealed data is silent
|
|
38
|
+
corruption: the artifact verifies clean forever and the value it carries is not the value
|
|
39
|
+
the publisher wrote. ``UTF-8`` and ``utf-8-sig`` are refused as spellings too, since two
|
|
40
|
+
spellings of one setting seal to two different options digests for one decode.
|
|
41
|
+
* **A leading byte-order mark is consumed**, by ``decode_delimited``, before the header is
|
|
42
|
+
read. A mark that survives into the first column name produces a dataset whose first
|
|
43
|
+
column silently fails every join, and "whatever the standard library happens to do" is not
|
|
44
|
+
a rule anyone can check. A mark anywhere else in the text is a character and stays.
|
|
45
|
+
* **A headerless source is refused.** ``header: false`` would need the recipe to supply the
|
|
46
|
+
column names, and this family's closed option set has no way to state them; naming columns
|
|
47
|
+
by position instead would put names into a dataset that nobody wrote down and no recipe
|
|
48
|
+
describes. So the option is admitted only as ``true``, and reading a headerless source is
|
|
49
|
+
a reviewed widening -- a ``columns`` option -- rather than something a decoder invents.
|
|
50
|
+
The shared helper still names columns positionally for the container families, which pass
|
|
51
|
+
``header`` through from their own settings; that is their decision to make, not this one's.
|
|
52
|
+
|
|
53
|
+
Import discipline: this module reads ``formats``, ``readers.contracts``, and
|
|
54
|
+
``readers.tabular``. It reaches no filesystem and imports nothing from ``acquisition``.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from __future__ import annotations
|
|
58
|
+
|
|
59
|
+
from collections.abc import Iterator, Mapping
|
|
60
|
+
from dataclasses import dataclass, field
|
|
61
|
+
from typing import Any
|
|
62
|
+
|
|
63
|
+
from mostlyright.data_harness.formats import (
|
|
64
|
+
FORMAT_MEDIA_TYPES,
|
|
65
|
+
FORMAT_SUFFIXES,
|
|
66
|
+
READER_CONTRACT_VERSION,
|
|
67
|
+
READER_OUTPUT_FORMATS,
|
|
68
|
+
)
|
|
69
|
+
from mostlyright.data_harness.readers.contracts import (
|
|
70
|
+
ReaderBudgets,
|
|
71
|
+
ReaderError,
|
|
72
|
+
ReaderPin,
|
|
73
|
+
ReaderResult,
|
|
74
|
+
bulk_default_budgets,
|
|
75
|
+
)
|
|
76
|
+
from mostlyright.data_harness.readers.tabular import (
|
|
77
|
+
FALLBACK_STEM,
|
|
78
|
+
decode_delimited_stream,
|
|
79
|
+
encode_canonical_csv,
|
|
80
|
+
sealed_filename,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
__all__ = ["ADMITTED_DELIMITERS", "ADMITTED_ENCODINGS", "DelimitedTextReader"]
|
|
84
|
+
|
|
85
|
+
# The one encoding a Reader may seal, unpacked rather than restated. A second member in the
|
|
86
|
+
# leaf's table makes this line raise at import, which is the loud failure that forces a family
|
|
87
|
+
# to choose its output encoding deliberately.
|
|
88
|
+
(_OUTPUT_FORMAT,) = READER_OUTPUT_FORMATS
|
|
89
|
+
_OUTPUT_MEDIA_TYPE = sorted(FORMAT_MEDIA_TYPES[_OUTPUT_FORMAT])[0]
|
|
90
|
+
_OUTPUT_SUFFIX = sorted(FORMAT_SUFFIXES[_OUTPUT_FORMAT])[0]
|
|
91
|
+
|
|
92
|
+
# The closed delimiter table. Two single characters, and the module docstring says why the
|
|
93
|
+
# set is closed rather than open. Adding one is a reviewed source edit.
|
|
94
|
+
ADMITTED_DELIMITERS: tuple[str, ...] = ("\t", ",")
|
|
95
|
+
|
|
96
|
+
# The closed encoding table, one spelling of one encoding. A tuple rather than a bare
|
|
97
|
+
# constant because the shape is the same shape the delimiter set has, and the next entry --
|
|
98
|
+
# if there is ever an honest one -- goes through the same review.
|
|
99
|
+
ADMITTED_ENCODINGS: tuple[str, ...] = ("utf-8",)
|
|
100
|
+
|
|
101
|
+
# The whole option vocabulary. ``delimiter`` is required because it is the whole of what
|
|
102
|
+
# this family decides; the other two have stated defaults, so an ordinary recipe names one
|
|
103
|
+
# setting and a strict recipe names three.
|
|
104
|
+
_REQUIRED_OPTION_KEYS = ("delimiter",)
|
|
105
|
+
_OPTIONAL_OPTION_KEYS = ("header", "encoding")
|
|
106
|
+
_OPTION_KEYS = (*_REQUIRED_OPTION_KEYS, *_OPTIONAL_OPTION_KEYS)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _validate_options(options: Any, *, subject: str) -> dict[str, Any]:
|
|
110
|
+
"""Admit this family's closed option set, refusing an unknown key by name.
|
|
111
|
+
|
|
112
|
+
Strict on purpose. The options digest is sealed into the recipe forever, so a key that
|
|
113
|
+
was accepted and ignored would be an unreviewable difference between two recipes that
|
|
114
|
+
look identical to everyone who reads them.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
if not isinstance(options, Mapping):
|
|
118
|
+
raise ReaderError("READER_OPTIONS", subject, "must be an object")
|
|
119
|
+
unknown = sorted(str(key) for key in options if key not in _OPTION_KEYS)
|
|
120
|
+
if unknown:
|
|
121
|
+
raise ReaderError(
|
|
122
|
+
"READER_OPTIONS",
|
|
123
|
+
subject,
|
|
124
|
+
f"names no such setting: {', '.join(unknown)}; this family takes "
|
|
125
|
+
f"{', '.join(sorted(_OPTION_KEYS))}",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
delimiter = options.get("delimiter")
|
|
129
|
+
if not isinstance(delimiter, str) or delimiter not in ADMITTED_DELIMITERS:
|
|
130
|
+
raise ReaderError(
|
|
131
|
+
"READER_OPTIONS",
|
|
132
|
+
f"{subject}.delimiter",
|
|
133
|
+
"must name one of the delimiters this family admits: a tab or a comma. The set "
|
|
134
|
+
"is closed because every additional delimiter is a new parser surface and a new "
|
|
135
|
+
"class of ambiguity; widening it is a reviewed source edit",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
encoding = options.get("encoding", ADMITTED_ENCODINGS[0])
|
|
139
|
+
if not isinstance(encoding, str) or encoding not in ADMITTED_ENCODINGS:
|
|
140
|
+
raise ReaderError(
|
|
141
|
+
"READER_OPTIONS",
|
|
142
|
+
f"{subject}.encoding",
|
|
143
|
+
f"must be exactly {ADMITTED_ENCODINGS[0]!r}. Text that is not valid UTF-8 is "
|
|
144
|
+
"refused rather than decoded with replacement characters, because a replacement "
|
|
145
|
+
"character in sealed data is corruption that verifies clean forever",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
header = options.get("header", True)
|
|
149
|
+
if not isinstance(header, bool):
|
|
150
|
+
raise ReaderError("READER_OPTIONS", f"{subject}.header", "must be true or false")
|
|
151
|
+
if not header:
|
|
152
|
+
raise ReaderError(
|
|
153
|
+
"READER_OPTIONS",
|
|
154
|
+
f"{subject}.header",
|
|
155
|
+
"must be true: a headerless source needs the recipe to supply its column names, "
|
|
156
|
+
"this family's settings have no way to state them, and naming columns by position "
|
|
157
|
+
"would put names into the dataset that nobody wrote down. Add a header row to the "
|
|
158
|
+
"source, or widen this family with a reviewed columns option",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return {"delimiter": delimiter, "header": header, "encoding": encoding}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass(frozen=True)
|
|
165
|
+
class DelimitedTextReader:
|
|
166
|
+
"""``delimited_text``: delimited text read from bytes, sealed as canonical CSV.
|
|
167
|
+
|
|
168
|
+
The order of the work is options, decode, emit -- three steps, because the admission this
|
|
169
|
+
family performs *is* the decode: "is this valid UTF-8 text, within budget, whose rows are
|
|
170
|
+
the width its header states" is exactly what ``decode_delimited`` answers, and asking it
|
|
171
|
+
twice in two places is how two answers come to disagree.
|
|
172
|
+
|
|
173
|
+
``accepted_media_types`` admits the two media types a delimited source arrives under.
|
|
174
|
+
That is an input-side declaration and it is not a format table: what may be *sealed* is
|
|
175
|
+
still ``csv`` alone, which is why ``text/tab-separated-values`` appears here and in no
|
|
176
|
+
table in ``formats``.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
family_id: str = "delimited_text"
|
|
180
|
+
family_version: str = "1.0.0"
|
|
181
|
+
contract_version: str = READER_CONTRACT_VERSION
|
|
182
|
+
output_format: str = _OUTPUT_FORMAT
|
|
183
|
+
accepted_media_types: tuple[str, ...] = ("text/csv", "text/tab-separated-values")
|
|
184
|
+
default_budgets: ReaderBudgets = field(default_factory=bulk_default_budgets)
|
|
185
|
+
|
|
186
|
+
def validate_options(self, options: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
187
|
+
return _validate_options(options, subject="reader.delimited_text.decode_options")
|
|
188
|
+
|
|
189
|
+
def decode(self, content: bytes, pin: ReaderPin, budgets: ReaderBudgets) -> ReaderResult:
|
|
190
|
+
options = self.validate_options(pin.decode_options)
|
|
191
|
+
# Parsed rows feed the canonical encoder one at a time rather than being drained
|
|
192
|
+
# into a table first. A year-scale source is millions of rows, and the table form
|
|
193
|
+
# would hold every cell as a live object at once -- the decode's memory ceiling is
|
|
194
|
+
# the row, not the file.
|
|
195
|
+
columns, rows = decode_delimited_stream(
|
|
196
|
+
content,
|
|
197
|
+
delimiter=str(options["delimiter"]),
|
|
198
|
+
header=bool(options["header"]),
|
|
199
|
+
budgets=budgets,
|
|
200
|
+
)
|
|
201
|
+
row_count = 0
|
|
202
|
+
|
|
203
|
+
def counted() -> Iterator[tuple[str, ...]]:
|
|
204
|
+
nonlocal row_count
|
|
205
|
+
for row in rows:
|
|
206
|
+
row_count += 1
|
|
207
|
+
yield row
|
|
208
|
+
|
|
209
|
+
payload = encode_canonical_csv(columns, counted(), budgets=budgets)
|
|
210
|
+
return ReaderResult(
|
|
211
|
+
content=payload,
|
|
212
|
+
data_format=_OUTPUT_FORMAT,
|
|
213
|
+
media_type=_OUTPUT_MEDIA_TYPE,
|
|
214
|
+
# Loose delimited text carries no name of its own: the bytes are the whole input,
|
|
215
|
+
# and the source's filename is not part of what was decoded. So the shared
|
|
216
|
+
# fallback stem is used deliberately, exactly as the gzip family uses it, rather
|
|
217
|
+
# than a name invented here or taken from somewhere the decode never looked.
|
|
218
|
+
filename=sealed_filename(FALLBACK_STEM, suffix=_OUTPUT_SUFFIX),
|
|
219
|
+
row_count=row_count,
|
|
220
|
+
column_names=columns,
|
|
221
|
+
declared_cell_count=row_count * len(columns),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass(frozen=True)
|
|
226
|
+
class DelimitedTextReaderV1_1(DelimitedTextReader):
|
|
227
|
+
"""``delimited_text@1.1.0`` admits ``text/plain`` as a label, and decodes nothing new.
|
|
228
|
+
|
|
229
|
+
A publisher that serves comma-separated bytes under ``text/plain`` is not describing a
|
|
230
|
+
different file; it is declining to describe the file at all. ``acquisition/http.py``
|
|
231
|
+
already writes that rule down for the byte-stream spelling -- a weak label "says only
|
|
232
|
+
that a server declined to say anything", and the control is the structural check behind
|
|
233
|
+
the sandbox boundary, never the header. Version ``1.0.0`` admitted the two labels that
|
|
234
|
+
name the format outright, which refused whole archives that were never unfit: Iowa
|
|
235
|
+
Environmental Mesonet serves its ASOS/METAR CSV under ``text/plain``, and so does a large
|
|
236
|
+
share of public agency endpoints.
|
|
237
|
+
|
|
238
|
+
This is an admission change and not a decode change. ``decode`` is inherited untouched,
|
|
239
|
+
so the bytes a recipe seals under this coordinate are the bytes ``1.0.0`` would have
|
|
240
|
+
sealed; what moves is only which responses reach the decoder. It is a new coordinate for
|
|
241
|
+
the reason ``archive.zip@1.1.0`` is one: ``1.0.0`` stays closed to the weak label, so an
|
|
242
|
+
already-approved recipe cannot silently begin admitting responses its review never saw.
|
|
243
|
+
|
|
244
|
+
The label is the only thing widened. ``text/html`` remains outside every allowlist, which
|
|
245
|
+
is what keeps the bot-wall refusal intact, and the shared decoder's own refusals -- strict
|
|
246
|
+
UTF-8, a NUL byte, a repeated column name, a row whose width differs from its header --
|
|
247
|
+
are unchanged and remain the whole admission. Archive and executable bytes arriving under
|
|
248
|
+
this label are refused as invalid UTF-8 rather than by prefix: the magic table is enforced
|
|
249
|
+
in ``acquisition/parsing`` and in the container families, and this family imports neither.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
family_version: str = "1.1.0"
|
|
253
|
+
accepted_media_types: tuple[str, ...] = (
|
|
254
|
+
"text/csv",
|
|
255
|
+
"text/plain",
|
|
256
|
+
"text/tab-separated-values",
|
|
257
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""The weather-model Reader separates admission from decoding.
|
|
2
|
+
|
|
3
|
+
A weather model publishes its output as GRIB2 messages, and a GRIB2 message states its
|
|
4
|
+
geometry and its packing method in its headers, before any value appears. That is the whole
|
|
5
|
+
reason this package is split the way it is. ``admission`` reads those headers, decides
|
|
6
|
+
whether this product is willing to open the message at all, and returns the offsets of the
|
|
7
|
+
data section without reading it. The decoder requires that admission result.
|
|
8
|
+
|
|
9
|
+
``MESSAGES_ROOT`` is the vendored evidence: three real messages, committed with their
|
|
10
|
+
provenance, from which every number in the allowlist was read. They are also the family's
|
|
11
|
+
known-good samples under ``readers/samples/weather.grib2/1.0.0``. Admission and sample checks read
|
|
12
|
+
the same files.
|
|
13
|
+
|
|
14
|
+
The path is spelled out rather than imported from ``readers.samples``, which would close an
|
|
15
|
+
import cycle: the sample loader reads the Toolbox, the Toolbox holds the weather family, and the
|
|
16
|
+
weather family is in this package.
|
|
17
|
+
|
|
18
|
+
Import discipline: this package reads ``readers.contracts`` and the standard library. Like
|
|
19
|
+
every module under ``readers/``, it must not import ``acquisition``, ``sources``, ``recipe``, or
|
|
20
|
+
``pipeline``; those layers import this one and a back-edge would close an import cycle.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import pathlib
|
|
26
|
+
|
|
27
|
+
__all__ = ["MESSAGES_ROOT"]
|
|
28
|
+
|
|
29
|
+
# The vendored messages ship inside the package because that is where they are needed: the
|
|
30
|
+
# warm-up check runs in production and its file must be installed with the code that opens it.
|
|
31
|
+
MESSAGES_ROOT = (
|
|
32
|
+
pathlib.Path(__file__).resolve().parent.parent / "samples" / "weather.grib2" / "1.0.0"
|
|
33
|
+
)
|