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,31 @@
|
|
|
1
|
+
<!-- Vendored from mostly-right-landing/src/assets/logo-ink.svg on 2026-08-11.
|
|
2
|
+
Facet and wordmark classes expose the canonical Logo.astro motion without a runtime dependency. -->
|
|
3
|
+
<svg class="mr-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 332.3 49.3" width="332.3" height="49.3" role="img" aria-label="Mostly Right">
|
|
4
|
+
<title>Mostly Right</title>
|
|
5
|
+
<g class="mr-logo-mark" transform="translate(0 4.45) scale(0.841458)">
|
|
6
|
+
<path class="mr-logo-facet mr-logo-facet--1" fill="#1A1813" d="M40,24h-24v16h8c1.5-5,5.1-8,8-8s6.5,3,8,8h8v-8c-2.9,0-6.5-3-8-8Z"></path>
|
|
7
|
+
<path class="mr-logo-facet mr-logo-facet--2" fill="#F15B22" d="M40,40h-16c-1.5,5-5.1,8-8,8h32c-2.9,0-6.5-3-8-8Z"></path>
|
|
8
|
+
<path class="mr-logo-facet mr-logo-facet--3" fill="#FFC613" d="M32,0c2.9,0,6.5,3,8,8h8V0h-16Z"></path>
|
|
9
|
+
<path class="mr-logo-facet mr-logo-facet--4" fill="#2B5FE3" d="M40,8h-24v8h16c2.9,0,6.5,3,8,8h8v-8c-2.9,0-6.5-3-8-8Z"></path>
|
|
10
|
+
<path class="mr-logo-facet mr-logo-facet--5" fill="#F15B22" d="M0,16c2.9,0,6.5-3,8-8H0v8Z"></path>
|
|
11
|
+
<path class="mr-logo-facet mr-logo-facet--6" fill="#1A1813" d="M8,8h8V0c-2.9,0-6.5,3-8,8Z"></path>
|
|
12
|
+
<path class="mr-logo-facet mr-logo-facet--7" fill="#2B5FE3" d="M0,32c2.9,0,6.5-3,8-8H0v8Z"></path>
|
|
13
|
+
<path class="mr-logo-facet mr-logo-facet--8" fill="#FFC613" d="M8,24h8v-8c-2.9,0-6.5,3-8,8Z"></path>
|
|
14
|
+
<rect class="mr-logo-facet mr-logo-facet--9" fill="#FFC613" x="0" y="40" width="16" height="8"></rect>
|
|
15
|
+
</g>
|
|
16
|
+
<g class="mr-logo-word" fill="#1A1813" transform="translate(-6.86 0)">
|
|
17
|
+
<path d="M101.06,24.06v12.85h-7.58v-12.64c0-3.69-1.84-4.86-3.79-4.86-2.35,0-4.1,1.64-4.1,5.84v11.67h-7.58v-12.49c0-3.69-1.84-5.02-3.74-5.02-2.05,0-4.15,1.02-4.15,5.02v12.49h-7.58V13h7.58v3.79c.92-3.02,4.2-4.4,6.45-4.4,3.53,0,6.14,1.69,7.58,4.56,1.64-3.17,5.32-4.56,7.83-4.56,5.99,0,9.06,4.35,9.06,11.67Z"></path>
|
|
18
|
+
<path d="M105.72,24.98c0-7.58,6.14-12.59,12.95-12.59s13,5.02,13,12.59-6.19,12.54-13,12.54-12.95-5.02-12.95-12.54ZM123.94,24.98c0-3.28-2.35-5.58-5.27-5.58s-5.27,2.3-5.27,5.58,2.35,5.53,5.27,5.53,5.27-2.25,5.27-5.53Z"></path>
|
|
19
|
+
<path d="M135.25,29.18h7.17c0,1.18.87,2.56,3.02,2.56,1.59,0,2.92-.82,2.92-1.95,0-.77-.56-1.13-2.35-1.54l-3.12-.82c-7.01-1.89-7.22-5.89-7.22-7.88,0-3.53,3.99-7.17,9.78-7.17,4.51,0,9.98,2.1,9.88,8.09h-7.17c0-1.54-1.08-2.3-2.56-2.3-1.28,0-2.35.61-2.35,1.64,0,.92.82,1.43,2.05,1.74l4.15,1.13c5.99,1.64,6.5,5.43,6.5,7.06,0,5.27-5.48,7.78-10.29,7.78-5.32,0-10.39-2.92-10.39-8.34Z"></path>
|
|
20
|
+
<path d="M175.8,13v5.73h-5.02v18.17h-7.58v-18.17h-4.35v-5.73h4.35V4.45h7.58v8.55h5.02Z"></path>
|
|
21
|
+
<path d="M179.94,36.91V0h7.58v36.91h-7.58Z"></path>
|
|
22
|
+
<path d="M208.97,13h8.14l-14.74,35.68h-8.09l5.89-14.64-9.42-21.04h8.4l4.97,12.39,4.86-12.39Z"></path>
|
|
23
|
+
</g>
|
|
24
|
+
<g class="mr-logo-word" fill="#1A1813" transform="translate(-10.87 0)">
|
|
25
|
+
<path d="M249.61,13.11l-1.79,7.22c-1.48-.87-2.66-.92-3.43-.92-2.97,0-5.43,2.51-5.43,9.57v7.94h-7.58V13h7.47v5.07c1.38-3.94,4.25-5.68,7.17-5.68,1.13,0,2.35.1,3.58.72Z"></path>
|
|
26
|
+
<path d="M252.02,5.02c0-2.51,2.15-4.4,4.71-4.4s4.61,1.89,4.61,4.4-2.05,4.4-4.61,4.4-4.71-2-4.71-4.4ZM252.94,36.91V13h7.58v23.91h-7.58Z"></path>
|
|
27
|
+
<path d="M284.52,13h7.53v21.04c0,10.14-5.32,15.26-13.98,15.26-5.43,0-10.03-3.17-11.93-7.68l6.25-2.51c.87,1.95,3.17,3.53,5.68,3.53,3.94,0,6.45-2.2,6.45-7.68v-.72c-1.79,2.25-4.97,3.28-7.42,3.28-5.94,0-11.57-4.86-11.57-12.59s5.63-12.54,11.57-12.54c2.46,0,5.63.97,7.42,3.23v-2.61ZM284.21,24.93c0-3.07-2.61-5.53-5.48-5.53s-5.53,2.51-5.53,5.53,2.56,5.58,5.53,5.58,5.48-2.51,5.48-5.58Z"></path>
|
|
28
|
+
<path d="M322.3,24.06v12.85h-7.58v-12.39c0-3.63-2.25-5.12-4.15-5.12-2.05,0-4.61,1.13-4.61,5.12v12.39h-7.58V0h7.58v16.84c.87-3.07,4.71-4.45,6.86-4.45,6.35,0,9.47,4.35,9.47,11.67Z"></path>
|
|
29
|
+
<path d="M343.08,13v5.73h-5.02v18.17h-7.58v-18.17h-4.35v-5.73h4.35V4.45h7.58v8.55h5.02Z"></path>
|
|
30
|
+
</g>
|
|
31
|
+
</svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""The ``backends`` package: named backend protocol, reference backend, registry.
|
|
2
|
+
|
|
3
|
+
This is the engine seam of the candidate build path. ``pipeline.build_candidate``
|
|
4
|
+
resolves its engine by name through ``get_backend`` and runs the inner
|
|
5
|
+
``clean -> join -> select`` row transforms on the returned backend; ``reference`` is the
|
|
6
|
+
default and the byte-parity authority every other engine must reproduce exactly.
|
|
7
|
+
|
|
8
|
+
This seam belongs to ``pipeline`` and ``local_contracts.TablePlan``. It is unrelated to
|
|
9
|
+
the ``preparation`` package, whose ``execute_preparation`` is a separate stack with its own
|
|
10
|
+
plan type and no backend dispatch.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from mostlyright.data_harness.backends.pandas_backend import PandasBackend
|
|
16
|
+
from mostlyright.data_harness.backends.polars_backend import PolarsBackend
|
|
17
|
+
from mostlyright.data_harness.backends.protocol import Backend
|
|
18
|
+
from mostlyright.data_harness.backends.reference import ReferenceBackend
|
|
19
|
+
from mostlyright.data_harness.backends.registry import get_backend, registered_backends
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Backend",
|
|
23
|
+
"PandasBackend",
|
|
24
|
+
"PolarsBackend",
|
|
25
|
+
"ReferenceBackend",
|
|
26
|
+
"get_backend",
|
|
27
|
+
"registered_backends",
|
|
28
|
+
]
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""The pandas backend: ``PandasBackend`` over Arrow-backed pandas DataFrames.
|
|
2
|
+
|
|
3
|
+
``PandasBackend`` re-owns the narrow ``clean -> join -> select`` triple behind the
|
|
4
|
+
``Backend`` protocol; every other candidate byte is still produced by the shared layer (the
|
|
5
|
+
one pyarrow sink, the canonical schema, ``_coerce_native_rows``, the evidence serializer).
|
|
6
|
+
Because the shared layer downstream is unchanged, the backend's job is to reproduce the
|
|
7
|
+
reference compute byte-identically while genuinely exercising a pandas object.
|
|
8
|
+
|
|
9
|
+
Design (the load-bearing dtype strategy):
|
|
10
|
+
|
|
11
|
+
- The row-shape transforms (``clean``/``join``/``select``) are computed **value-wise** in
|
|
12
|
+
plain Python, mirroring ``pipeline._apply_cleaning`` / ``_join`` / ``_select`` exactly so
|
|
13
|
+
unicode-whitespace, empty-vs-null, ordering, and the typed ``BuildError`` refusals match
|
|
14
|
+
the reference bit-for-bit. Every value-level cast routes through the shared
|
|
15
|
+
``pipeline._cast`` — never an engine-native parser, which is what keeps cast results and
|
|
16
|
+
cast refusals identical across engines.
|
|
17
|
+
- The op's output is then materialized as an **Arrow-backed** pandas DataFrame — one
|
|
18
|
+
explicit ``pd.ArrowDtype`` per column drawn from the closed ``pipeline._ARROW_BY_LOGICAL``
|
|
19
|
+
vocabulary the sink uses, constructed from the already-typed native values (never
|
|
20
|
+
inferred, never a whole-frame ``pa.Table`` pandas conversion into the sink, never a native
|
|
21
|
+
cast token). Arrow-backed columns
|
|
22
|
+
carry true Arrow nulls (never ``np.nan``/``NaT``) and keep an ``int64`` column ``int64``
|
|
23
|
+
under nulls (no int->float promotion).
|
|
24
|
+
- The frame is converted back to an ordered native ``list[dict]`` at the boundary
|
|
25
|
+
(``_rows_from_frame``): the index is dropped (positional order), and each column is read
|
|
26
|
+
through pyarrow ``to_pylist()`` so Arrow nulls become Python ``None`` and every scalar is
|
|
27
|
+
a native Python builtin (``int``/``float``/``str``/``datetime.date``) — no numpy scalar,
|
|
28
|
+
no ``pd.NA``. ``pipeline._coerce_native_rows`` remains the shared backstop.
|
|
29
|
+
|
|
30
|
+
``import pandas`` / ``import pyarrow`` are **function-local** (mirroring ``reference.py``'s
|
|
31
|
+
lazy-engine-import ethos), so ``import backends`` and constructing ``PandasBackend()`` at
|
|
32
|
+
registry load never import pandas. The module is named ``pandas_backend`` (not ``pandas``)
|
|
33
|
+
to avoid the ``import pandas`` self-shadow footgun.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
from mostlyright.data_harness.local_contracts import CleaningStep, TablePlan
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PandasBackend:
|
|
44
|
+
"""pandas engine for the ``clean -> join -> select`` triple over Arrow-backed frames."""
|
|
45
|
+
|
|
46
|
+
# A plain literal (not a lazy ``pipeline`` import), so constructing the backend at
|
|
47
|
+
# ``registry`` module load never imports ``pipeline`` or pandas. The seam resolves the
|
|
48
|
+
# backend by this exact name; any drift fails closed at ``get_backend`` (BACKEND_UNKNOWN).
|
|
49
|
+
name: str = "pandas"
|
|
50
|
+
|
|
51
|
+
def versions(self) -> dict[str, str]:
|
|
52
|
+
# Provenance only (engine-runtime.v2 ``backend_versions``). The pandas import is
|
|
53
|
+
# function-local (only reached during a build, when pandas is already dispatched), so
|
|
54
|
+
# ``import backends`` / constructing the backend stay pandas-free. The version is read
|
|
55
|
+
# from the module attribute ``pd.__version__`` rather than the stdlib metadata-version
|
|
56
|
+
# helper: that helper's module name is one of the dynamic-import tokens banned under
|
|
57
|
+
# ``backends/`` by ``test_backend_registry.py``. For a pinned release the two
|
|
58
|
+
# spellings are identical.
|
|
59
|
+
import pandas as pd
|
|
60
|
+
|
|
61
|
+
return {"pandas": str(pd.__version__)}
|
|
62
|
+
|
|
63
|
+
def materialize_graph_rows(
|
|
64
|
+
self,
|
|
65
|
+
columns: tuple[str, ...],
|
|
66
|
+
rows: tuple[tuple[Any, ...], ...],
|
|
67
|
+
) -> tuple[tuple[Any, ...], ...]:
|
|
68
|
+
"""Exercise an object-typed pandas frame without allowing dtype inference to coerce."""
|
|
69
|
+
|
|
70
|
+
import pandas as pd
|
|
71
|
+
|
|
72
|
+
if any(len(row) != len(columns) for row in rows):
|
|
73
|
+
raise ValueError("graph row width differs from its columns")
|
|
74
|
+
frame = pd.DataFrame(
|
|
75
|
+
{
|
|
76
|
+
column: pd.Series([row[index] for row in rows], dtype="object")
|
|
77
|
+
for index, column in enumerate(columns)
|
|
78
|
+
},
|
|
79
|
+
columns=list(columns),
|
|
80
|
+
index=range(len(rows)),
|
|
81
|
+
)
|
|
82
|
+
return tuple(tuple(row) for row in frame.itertuples(index=False, name=None))
|
|
83
|
+
|
|
84
|
+
def clean(
|
|
85
|
+
self,
|
|
86
|
+
rows: list[dict[str, Any]],
|
|
87
|
+
columns: list[str],
|
|
88
|
+
lineage: dict[str, dict[str, Any]],
|
|
89
|
+
step: CleaningStep,
|
|
90
|
+
) -> tuple[list[dict[str, Any]], list[str], dict[str, dict[str, Any]]]:
|
|
91
|
+
# Value-wise reproduction of ``pipeline._apply_cleaning`` (the DataFrame is a shaping
|
|
92
|
+
# container, not a transform engine): shared ``_require_columns`` refusals, Python
|
|
93
|
+
# shared ASCII-only trim, ``== ""`` -> None, positional
|
|
94
|
+
# rename with ``RENAME_COLLISION``, and every cast through the shared ``_cast``.
|
|
95
|
+
from mostlyright.data_harness import pipeline
|
|
96
|
+
|
|
97
|
+
output_rows = [dict(row) for row in rows]
|
|
98
|
+
output_columns = list(columns)
|
|
99
|
+
output_lineage = {
|
|
100
|
+
column: {**entry, "operations": list(entry["operations"])}
|
|
101
|
+
for column, entry in lineage.items()
|
|
102
|
+
}
|
|
103
|
+
if step.operation in {"trim", "empty_to_null"}:
|
|
104
|
+
target_columns = tuple(step.columns)
|
|
105
|
+
pipeline._require_columns(output_columns, target_columns, step.operation)
|
|
106
|
+
for row in output_rows:
|
|
107
|
+
for column in target_columns:
|
|
108
|
+
value = row[column]
|
|
109
|
+
if step.operation == "trim":
|
|
110
|
+
if value is not None and not isinstance(value, str):
|
|
111
|
+
raise pipeline.BuildError(
|
|
112
|
+
"CLEAN_TYPE", f"trim requires strings in {column!r}"
|
|
113
|
+
)
|
|
114
|
+
row[column] = None if value is None else pipeline._trim_ascii(value)
|
|
115
|
+
elif value == "":
|
|
116
|
+
row[column] = None
|
|
117
|
+
for column in target_columns:
|
|
118
|
+
output_lineage[column]["operations"].append(step.operation)
|
|
119
|
+
elif step.operation == "rename":
|
|
120
|
+
mapping = dict(step.columns)
|
|
121
|
+
pipeline._require_columns(output_columns, tuple(mapping), "rename")
|
|
122
|
+
final_columns = [mapping.get(column, column) for column in output_columns]
|
|
123
|
+
if len(final_columns) != len(set(final_columns)):
|
|
124
|
+
raise pipeline.BuildError("RENAME_COLLISION", "rename creates a column collision")
|
|
125
|
+
output_rows = [
|
|
126
|
+
{mapping.get(column, column): row[column] for column in output_columns}
|
|
127
|
+
for row in output_rows
|
|
128
|
+
]
|
|
129
|
+
new_lineage: dict[str, dict[str, Any]] = {}
|
|
130
|
+
for column in output_columns:
|
|
131
|
+
target = mapping.get(column, column)
|
|
132
|
+
entry = output_lineage[column]
|
|
133
|
+
if target != column:
|
|
134
|
+
entry["operations"].append(f"rename:{column}->{target}")
|
|
135
|
+
new_lineage[target] = entry
|
|
136
|
+
output_columns = final_columns
|
|
137
|
+
output_lineage = new_lineage
|
|
138
|
+
elif step.operation == "cast":
|
|
139
|
+
mapping = dict(step.columns)
|
|
140
|
+
pipeline._require_columns(output_columns, tuple(mapping), "cast")
|
|
141
|
+
for row_index, row in enumerate(output_rows, start=1):
|
|
142
|
+
for column, target in mapping.items():
|
|
143
|
+
row[column] = pipeline._cast(row[column], target, column, row_index)
|
|
144
|
+
for column, target in mapping.items():
|
|
145
|
+
output_lineage[column]["operations"].append(f"cast:{target}")
|
|
146
|
+
else: # pragma: no cover - contract parser makes this unreachable
|
|
147
|
+
raise pipeline.ContractError(
|
|
148
|
+
"plan.cleaning.operation",
|
|
149
|
+
"CLEAN_OPERATION_UNSUPPORTED",
|
|
150
|
+
f"unsupported operation: {step.operation}",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
shaped = _rows_from_frame(
|
|
154
|
+
_frame_from_rows(
|
|
155
|
+
output_rows, output_columns, _logical_by_column(output_lineage, output_columns)
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
return shaped, output_columns, output_lineage
|
|
159
|
+
|
|
160
|
+
def join(
|
|
161
|
+
self,
|
|
162
|
+
plan: TablePlan,
|
|
163
|
+
rows_by_source: dict[str, list[dict[str, Any]]],
|
|
164
|
+
columns_by_source: dict[str, list[str]],
|
|
165
|
+
lineage_by_source: dict[str, dict[str, dict[str, Any]]],
|
|
166
|
+
) -> tuple[
|
|
167
|
+
list[dict[str, Any]],
|
|
168
|
+
list[str],
|
|
169
|
+
dict[str, dict[str, Any]],
|
|
170
|
+
dict[str, Any],
|
|
171
|
+
]:
|
|
172
|
+
# Value-wise reproduction of ``pipeline._join``. Left-source order is imposed
|
|
173
|
+
# positionally (iterate the left rows; look up the right by an equality index) — never
|
|
174
|
+
# ``DataFrame.merge``'s incidental ordering. Same guards and typed refusals; output
|
|
175
|
+
# columns ``[*left_columns, *right_nonkeys]``; merged lineage; and a ``report``
|
|
176
|
+
# byte-identical to the reference one. The report counts stay native ``int`` because
|
|
177
|
+
# they are pure-Python ``len(...)``; a numpy scalar there would change the evidence
|
|
178
|
+
# bytes.
|
|
179
|
+
from mostlyright.data_harness import pipeline
|
|
180
|
+
|
|
181
|
+
spec = plan.join
|
|
182
|
+
left_rows = rows_by_source[spec.left]
|
|
183
|
+
right_rows = rows_by_source[spec.right]
|
|
184
|
+
left_columns = columns_by_source[spec.left]
|
|
185
|
+
right_columns = columns_by_source[spec.right]
|
|
186
|
+
pipeline._require_columns(left_columns, spec.on, "join.left keys")
|
|
187
|
+
pipeline._require_columns(right_columns, spec.on, "join.right keys")
|
|
188
|
+
for key in spec.on:
|
|
189
|
+
left_types = {type(row[key]) for row in left_rows if row[key] is not None}
|
|
190
|
+
right_types = {type(row[key]) for row in right_rows if row[key] is not None}
|
|
191
|
+
if any(row[key] is None for row in left_rows + right_rows):
|
|
192
|
+
raise pipeline.BuildError("JOIN_NULL_KEY", f"join key {key!r} contains null")
|
|
193
|
+
if len(left_types) != 1 or left_types != right_types:
|
|
194
|
+
raise pipeline.BuildError(
|
|
195
|
+
"JOIN_KEY_TYPE", f"join key {key!r} types do not match exactly"
|
|
196
|
+
)
|
|
197
|
+
right_nonkeys = [column for column in right_columns if column not in spec.on]
|
|
198
|
+
collisions = set(left_columns) & set(right_nonkeys)
|
|
199
|
+
if collisions:
|
|
200
|
+
raise pipeline.BuildError(
|
|
201
|
+
"JOIN_COLUMN_COLLISION", f"join column collision: {sorted(collisions)}"
|
|
202
|
+
)
|
|
203
|
+
right_index: dict[tuple[Any, ...], dict[str, Any]] = {}
|
|
204
|
+
for row in right_rows:
|
|
205
|
+
key = tuple(row[column] for column in spec.on)
|
|
206
|
+
if key in right_index:
|
|
207
|
+
raise pipeline.BuildError("JOIN_CARDINALITY", "right join keys are not unique")
|
|
208
|
+
right_index[key] = row
|
|
209
|
+
if spec.cardinality == "one_to_one":
|
|
210
|
+
left_keys = [tuple(row[column] for column in spec.on) for row in left_rows]
|
|
211
|
+
if len(left_keys) != len(set(left_keys)):
|
|
212
|
+
raise pipeline.BuildError("JOIN_CARDINALITY", "left join keys are not unique")
|
|
213
|
+
output: list[dict[str, Any]] = []
|
|
214
|
+
unmatched = 0
|
|
215
|
+
for left_row in left_rows:
|
|
216
|
+
key = tuple(left_row[column] for column in spec.on)
|
|
217
|
+
right_row = right_index.get(key)
|
|
218
|
+
if right_row is None:
|
|
219
|
+
unmatched += 1
|
|
220
|
+
merged = {**left_row, **{column: None for column in right_nonkeys}}
|
|
221
|
+
else:
|
|
222
|
+
merged = {**left_row, **{column: right_row[column] for column in right_nonkeys}}
|
|
223
|
+
output.append(merged)
|
|
224
|
+
multiplier = len(output) / len(left_rows)
|
|
225
|
+
if unmatched:
|
|
226
|
+
raise pipeline.BuildError("JOIN_UNMATCHED", f"left join has {unmatched} unmatched rows")
|
|
227
|
+
if multiplier != 1.0:
|
|
228
|
+
raise pipeline.BuildError("JOIN_MULTIPLIER", f"join row multiplier is {multiplier}")
|
|
229
|
+
columns = [*left_columns, *right_nonkeys]
|
|
230
|
+
lineage = {
|
|
231
|
+
**lineage_by_source[spec.left],
|
|
232
|
+
**{column: lineage_by_source[spec.right][column] for column in right_nonkeys},
|
|
233
|
+
}
|
|
234
|
+
report = {
|
|
235
|
+
"schema_version": "join-evidence.v1",
|
|
236
|
+
"left_source": spec.left,
|
|
237
|
+
"right_source": spec.right,
|
|
238
|
+
"keys": list(spec.on),
|
|
239
|
+
"cardinality": spec.cardinality,
|
|
240
|
+
"left_rows": len(left_rows),
|
|
241
|
+
"right_rows": len(right_rows),
|
|
242
|
+
"output_rows": len(output),
|
|
243
|
+
"matched_left_rows": len(output) - unmatched,
|
|
244
|
+
"unmatched_left_rows": unmatched,
|
|
245
|
+
"row_multiplier": multiplier,
|
|
246
|
+
"stable_order": "left_source_order",
|
|
247
|
+
}
|
|
248
|
+
shaped = _rows_from_frame(
|
|
249
|
+
_frame_from_rows(output, columns, _logical_by_column(lineage, columns))
|
|
250
|
+
)
|
|
251
|
+
return shaped, columns, lineage, report
|
|
252
|
+
|
|
253
|
+
def select(
|
|
254
|
+
self,
|
|
255
|
+
plan: TablePlan,
|
|
256
|
+
rows: list[dict[str, Any]],
|
|
257
|
+
columns: list[str],
|
|
258
|
+
lineage: dict[str, dict[str, Any]],
|
|
259
|
+
) -> tuple[list[dict[str, Any]], dict[str, dict[str, Any]]]:
|
|
260
|
+
# Value-wise reproduction of ``pipeline._select``: project/order columns to
|
|
261
|
+
# ``plan.select`` (shared ``_require_columns`` for the missing-column refusal);
|
|
262
|
+
# ``selected_lineage`` in ``plan.select`` order.
|
|
263
|
+
from mostlyright.data_harness import pipeline
|
|
264
|
+
|
|
265
|
+
pipeline._require_columns(columns, plan.select, "select")
|
|
266
|
+
selected_rows = [{column: row[column] for column in plan.select} for row in rows]
|
|
267
|
+
selected_lineage = {column: lineage[column] for column in plan.select}
|
|
268
|
+
shaped = _rows_from_frame(
|
|
269
|
+
_frame_from_rows(
|
|
270
|
+
selected_rows,
|
|
271
|
+
list(plan.select),
|
|
272
|
+
_logical_by_column(selected_lineage, plan.select),
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
return shaped, selected_lineage
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _logical_by_column(lineage: dict[str, dict[str, Any]], columns: Any) -> dict[str, str]:
|
|
279
|
+
"""Map each column to its closed logical type via the shared ``_logical_type_for``.
|
|
280
|
+
|
|
281
|
+
The logical type is drawn only from the column's lineage ``operations`` (the last
|
|
282
|
+
``cast:<target>``, else ``"string"``) — never from an engine scalar's class name — so
|
|
283
|
+
the Arrow-backed dtype the frame is built with matches the sink's imposed schema.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
from mostlyright.data_harness import pipeline
|
|
287
|
+
|
|
288
|
+
return {column: pipeline._logical_type_for(lineage[column]) for column in columns}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _arrow_dtype_for(logical: str) -> Any:
|
|
292
|
+
"""Return the ``pd.ArrowDtype`` for one authoritative local logical type.
|
|
293
|
+
|
|
294
|
+
Uses the same closed ``pipeline._ARROW_BY_LOGICAL`` map the shared sink imposes, so the
|
|
295
|
+
frame's per-column dtype is the canonical one and can never be inferred/lenient.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
import pandas as pd
|
|
299
|
+
|
|
300
|
+
from mostlyright.data_harness import pipeline
|
|
301
|
+
|
|
302
|
+
return pd.ArrowDtype(pipeline._ARROW_BY_LOGICAL[logical])
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _frame_from_rows(
|
|
306
|
+
rows: list[dict[str, Any]], columns: Any, logical_by_column: dict[str, str]
|
|
307
|
+
) -> Any:
|
|
308
|
+
"""Build an Arrow-backed pandas DataFrame with an explicit per-column dtype.
|
|
309
|
+
|
|
310
|
+
Each column is constructed with ``pd.array(values, dtype=pd.ArrowDtype(pa_type))`` from
|
|
311
|
+
the already-typed native values — never inferred, never a whole-frame conversion into the
|
|
312
|
+
sink. Arrow-backed columns carry true Arrow nulls (never ``np.nan``) and keep ``int64``
|
|
313
|
+
as ``int64`` under
|
|
314
|
+
nulls (no float promotion). The explicit ``index`` preserves the row count even for a
|
|
315
|
+
zero-column frame (an unreachable but defensively-handled shape).
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
import pandas as pd
|
|
319
|
+
|
|
320
|
+
column_order = list(columns)
|
|
321
|
+
data = {
|
|
322
|
+
column: pd.array(
|
|
323
|
+
[row[column] for row in rows],
|
|
324
|
+
dtype=_arrow_dtype_for(logical_by_column[column]),
|
|
325
|
+
)
|
|
326
|
+
for column in column_order
|
|
327
|
+
}
|
|
328
|
+
return pd.DataFrame(data, columns=column_order, index=range(len(rows)))
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _rows_from_frame(frame: Any) -> list[dict[str, Any]]:
|
|
332
|
+
"""Convert an Arrow-backed DataFrame back to an ordered native ``list[dict]``.
|
|
333
|
+
|
|
334
|
+
The index is dropped (positional order); each column is read through pyarrow
|
|
335
|
+
``to_pylist()`` so an Arrow null becomes Python ``None`` and every scalar is a native
|
|
336
|
+
Python builtin (``bool``/``int``/``float``/``str``/``datetime.date``/UTC
|
|
337
|
+
``datetime.datetime``) — no numpy scalar, no ``pd.NA``. Column (key) order follows the
|
|
338
|
+
frame's column order. ``pipeline``'s
|
|
339
|
+
``_coerce_native_rows`` remains the shared backstop downstream.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
column_order = list(frame.columns)
|
|
343
|
+
native_by_column = {
|
|
344
|
+
column: frame[column].array.__arrow_array__().to_pylist() for column in column_order
|
|
345
|
+
}
|
|
346
|
+
row_count = len(frame)
|
|
347
|
+
return [
|
|
348
|
+
{column: native_by_column[column][index] for column in column_order}
|
|
349
|
+
for index in range(row_count)
|
|
350
|
+
]
|