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,523 @@
|
|
|
1
|
+
"""``mr-data catalog-fill``: bounded staging followed by one complete publication.
|
|
2
|
+
|
|
3
|
+
This is the small-catalog command: one invocation stages a whole public catalog and publishes one
|
|
4
|
+
successor from it, with the authored entries handed in as one document.
|
|
5
|
+
|
|
6
|
+
A keyed provider-scale catalog is not filled this way and this command refuses to try. That work
|
|
7
|
+
is three separate commands with three separate receipts -- ``catalog-harvest``, then
|
|
8
|
+
``catalog-author``, then ``catalog-publish`` -- because a sweep of several hundred thousand
|
|
9
|
+
records is interrupted and resumed rather than run once, and because the key that reaches it
|
|
10
|
+
belongs to exactly one of those three.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import stat
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
from urllib.parse import urlsplit
|
|
23
|
+
|
|
24
|
+
from mostlyright.data_harness.acquisition.http import (
|
|
25
|
+
PinnedHttpsRetriever,
|
|
26
|
+
RetrievalLimits,
|
|
27
|
+
StdlibPinnedTransport,
|
|
28
|
+
)
|
|
29
|
+
from mostlyright.data_harness.acquisition.url_policy import EgressPolicy, SystemResolver
|
|
30
|
+
from mostlyright.data_harness.canonical import parse_canonical_json, sha256_bytes
|
|
31
|
+
from mostlyright.data_harness.sources.catalog.contracts import (
|
|
32
|
+
CatalogEntry,
|
|
33
|
+
catalog_entry_from_dict,
|
|
34
|
+
)
|
|
35
|
+
from mostlyright.data_harness.sources.catalog.delta import (
|
|
36
|
+
checkpoint_catalog_delta_classification,
|
|
37
|
+
classify_catalog_delta,
|
|
38
|
+
publish_retrieval_catalog_successor,
|
|
39
|
+
)
|
|
40
|
+
from mostlyright.data_harness.sources.catalog.embedding import LexicalHashingBackend
|
|
41
|
+
from mostlyright.data_harness.sources.catalog.fill import (
|
|
42
|
+
CatalogFillConfig,
|
|
43
|
+
CatalogFillLimits,
|
|
44
|
+
run_catalog_fill,
|
|
45
|
+
)
|
|
46
|
+
from mostlyright.data_harness.sources.catalog.retrieval_manifest import load_retrieval_catalog
|
|
47
|
+
from mostlyright.data_harness.sources.catalog.rights_decisions import load_rights_decisions
|
|
48
|
+
from mostlyright.data_harness.sources.contracts import SourceContractError
|
|
49
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
50
|
+
|
|
51
|
+
_AUTHORING_SCHEMA = "mr-data-catalog-fill-authoring.v1"
|
|
52
|
+
_MAX_AUTHORING_BYTES = 64 * 1024 * 1024
|
|
53
|
+
_DIGEST = re.compile(r"^[0-9a-f]{64}$")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class _AuthoringBundle:
|
|
58
|
+
candidates_by_record_id: dict[str, CatalogEntry]
|
|
59
|
+
predecessor_record_ids: dict[str, str]
|
|
60
|
+
predecessor_chains: dict[str, tuple[CatalogEntry, ...]]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--protocol",
|
|
66
|
+
choices=("ckan", "stac", "sdmx"),
|
|
67
|
+
required=True,
|
|
68
|
+
help=(
|
|
69
|
+
"the closed public catalog protocol; a keyed provider is harvested, authored and "
|
|
70
|
+
"published as three separate commands instead"
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument("--endpoint", required=True, help="credential-free public HTTPS endpoint")
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--output",
|
|
76
|
+
dest="output_root",
|
|
77
|
+
type=Path,
|
|
78
|
+
required=True,
|
|
79
|
+
help="the explicit public catalog output root",
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--predecessor",
|
|
83
|
+
dest="predecessor_root",
|
|
84
|
+
type=Path,
|
|
85
|
+
help="the exact predecessor catalog root, when updating",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--predecessor-sha256", help="the exact predecessor retrieval-manifest fingerprint"
|
|
89
|
+
)
|
|
90
|
+
parser.add_argument(
|
|
91
|
+
"--staging",
|
|
92
|
+
dest="staging_root",
|
|
93
|
+
type=Path,
|
|
94
|
+
required=True,
|
|
95
|
+
help="the private resumable staging root",
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--checkpoint",
|
|
99
|
+
"--checkpoint-sha256",
|
|
100
|
+
dest="checkpoint_sha256",
|
|
101
|
+
help="the exact retained checkpoint fingerprint required for resume",
|
|
102
|
+
)
|
|
103
|
+
parser.add_argument(
|
|
104
|
+
"--observed-at", required=True, help="the canonical UTC observation timestamp"
|
|
105
|
+
)
|
|
106
|
+
parser.add_argument(
|
|
107
|
+
"--page-size", type=int, required=True, help="records requested per provider page"
|
|
108
|
+
)
|
|
109
|
+
parser.add_argument(
|
|
110
|
+
"--provider-count-reconciliation",
|
|
111
|
+
choices=("required-when-reported",),
|
|
112
|
+
required=True,
|
|
113
|
+
help="require equality whenever the provider reports a total",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"--max-convergence-passes",
|
|
117
|
+
type=int,
|
|
118
|
+
required=True,
|
|
119
|
+
help="maximum complete passes allowed to reach convergence",
|
|
120
|
+
)
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
"--max-requests", type=int, required=True, help="maximum aggregate network requests"
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--max-responses", type=int, required=True, help="maximum aggregate network responses"
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--max-pages", type=int, required=True, help="maximum aggregate provider pages"
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--max-records", type=int, required=True, help="maximum aggregate provider records"
|
|
132
|
+
)
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"--max-response-bytes",
|
|
135
|
+
type=int,
|
|
136
|
+
required=True,
|
|
137
|
+
help="maximum bytes in one response",
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"--max-aggregate-bytes",
|
|
141
|
+
type=int,
|
|
142
|
+
required=True,
|
|
143
|
+
help="maximum bytes across all responses",
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"--max-wall-time-ns",
|
|
147
|
+
type=int,
|
|
148
|
+
required=True,
|
|
149
|
+
help="maximum cumulative retrieval time in nanoseconds",
|
|
150
|
+
)
|
|
151
|
+
parser.add_argument(
|
|
152
|
+
"--max-pages-this-invocation",
|
|
153
|
+
type=int,
|
|
154
|
+
help="stop after this many newly fetched pages and return an incomplete checkpoint",
|
|
155
|
+
)
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
"--authoring",
|
|
158
|
+
type=Path,
|
|
159
|
+
required=True,
|
|
160
|
+
help="bounded canonical current-entry and predecessor mapping document",
|
|
161
|
+
)
|
|
162
|
+
parser.add_argument(
|
|
163
|
+
"--authoring-sha256", required=True, help="the exact authoring-document fingerprint"
|
|
164
|
+
)
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"--rights-decisions",
|
|
167
|
+
type=Path,
|
|
168
|
+
help="bounded canonical human rights-decision document for queued records",
|
|
169
|
+
)
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
"--rights-decisions-sha256",
|
|
172
|
+
help="the exact rights-decision-document fingerprint",
|
|
173
|
+
)
|
|
174
|
+
parser.add_argument(
|
|
175
|
+
"--neural-mode",
|
|
176
|
+
choices=("lexical-only", "sidecar"),
|
|
177
|
+
required=True,
|
|
178
|
+
help="the explicit representation mode to publish",
|
|
179
|
+
)
|
|
180
|
+
parser.add_argument(
|
|
181
|
+
"--neural-model-root",
|
|
182
|
+
type=Path,
|
|
183
|
+
help="the explicit governed sidecar root for neural mode",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
188
|
+
_validate_coordinate_pairs(args)
|
|
189
|
+
fill_result = _run_fill(args)
|
|
190
|
+
base = _fill_payload(args, fill_result)
|
|
191
|
+
if fill_result.status != "complete":
|
|
192
|
+
return {
|
|
193
|
+
**base,
|
|
194
|
+
"status": "catalog_fill_incomplete",
|
|
195
|
+
"manifest_sha256": None,
|
|
196
|
+
"publication_sha256": None,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# No caller-authored facts are read before the deterministic fill proves a complete,
|
|
200
|
+
# reconciled generation. In particular, a pilot or budget stop cannot reach publication.
|
|
201
|
+
bundle = _load_authoring_bundle(args.authoring, args.authoring_sha256)
|
|
202
|
+
rights_decisions = None
|
|
203
|
+
if args.rights_decisions is not None:
|
|
204
|
+
rights_decisions = load_rights_decisions(
|
|
205
|
+
args.rights_decisions, args.rights_decisions_sha256
|
|
206
|
+
)
|
|
207
|
+
lexical_backend, neural_backend, fallback_reason = _publication_backends(args)
|
|
208
|
+
try:
|
|
209
|
+
predecessor, predecessor_directory = _load_predecessor(args, neural_backend)
|
|
210
|
+
classification = classify_catalog_delta(
|
|
211
|
+
predecessor=predecessor,
|
|
212
|
+
predecessor_record_ids=bundle.predecessor_record_ids,
|
|
213
|
+
candidates_by_record_id=bundle.candidates_by_record_id,
|
|
214
|
+
fill_result=fill_result,
|
|
215
|
+
staging_root=args.staging_root,
|
|
216
|
+
predecessor_chains=bundle.predecessor_chains,
|
|
217
|
+
rights_decisions=rights_decisions,
|
|
218
|
+
rights_decisions_sha256=(
|
|
219
|
+
args.rights_decisions_sha256 if rights_decisions is not None else None
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
classification_checkpoint = checkpoint_catalog_delta_classification(
|
|
223
|
+
classification,
|
|
224
|
+
staging_root=args.staging_root,
|
|
225
|
+
lexical_backend=lexical_backend,
|
|
226
|
+
neural_backend=neural_backend,
|
|
227
|
+
)
|
|
228
|
+
# This is the only public-writer call in the command. It happens after complete fill,
|
|
229
|
+
# strict authoring capture, predecessor verification, classification, and checkpointing.
|
|
230
|
+
publication = publish_retrieval_catalog_successor(
|
|
231
|
+
classification,
|
|
232
|
+
classification_checkpoint_sha256=classification_checkpoint,
|
|
233
|
+
staging_root=args.staging_root,
|
|
234
|
+
lexical_backend=lexical_backend,
|
|
235
|
+
neural_backend=neural_backend,
|
|
236
|
+
directory=args.output_root,
|
|
237
|
+
predecessor_directory=predecessor_directory,
|
|
238
|
+
egress_policy=_publication_citation_policy(args.endpoint, rights_decisions),
|
|
239
|
+
fallback_reason=fallback_reason,
|
|
240
|
+
)
|
|
241
|
+
finally:
|
|
242
|
+
close = getattr(neural_backend, "close", None)
|
|
243
|
+
if callable(close):
|
|
244
|
+
close()
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
**base,
|
|
248
|
+
"status": "catalog_published",
|
|
249
|
+
"rights_decisions_sha256": classification.rights_decisions_sha256,
|
|
250
|
+
"classification_sha256": classification.digest,
|
|
251
|
+
"classification_checkpoint_sha256": classification_checkpoint,
|
|
252
|
+
"manifest_sha256": publication.manifest_sha256,
|
|
253
|
+
"publication_sha256": publication.digest,
|
|
254
|
+
"counts": {
|
|
255
|
+
"unchanged": publication.unchanged,
|
|
256
|
+
"changed": publication.changed,
|
|
257
|
+
"new": publication.new,
|
|
258
|
+
"absent": publication.absent,
|
|
259
|
+
"flagged": publication.flagged,
|
|
260
|
+
},
|
|
261
|
+
"representations": {
|
|
262
|
+
"lexical": {
|
|
263
|
+
"reused_vector_sha256s": list(publication.lexical_reused_vector_sha256s),
|
|
264
|
+
"created_vector_sha256s": list(publication.lexical_created_vector_sha256s),
|
|
265
|
+
"encoder_invocations": publication.lexical_encoder_invocations,
|
|
266
|
+
},
|
|
267
|
+
"neural": {
|
|
268
|
+
"reused_vector_sha256s": list(publication.neural_reused_vector_sha256s),
|
|
269
|
+
"created_vector_sha256s": list(publication.neural_created_vector_sha256s),
|
|
270
|
+
"encoder_invocations": publication.neural_encoder_invocations,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
"fallback_reason": publication.fallback_reason,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _run_fill(args: argparse.Namespace):
|
|
278
|
+
limits = CatalogFillLimits(
|
|
279
|
+
max_requests=args.max_requests,
|
|
280
|
+
max_pages=args.max_pages,
|
|
281
|
+
max_records=args.max_records,
|
|
282
|
+
max_response_bytes=args.max_response_bytes,
|
|
283
|
+
max_aggregate_bytes=args.max_aggregate_bytes,
|
|
284
|
+
max_responses=args.max_responses,
|
|
285
|
+
max_wall_time_ns=args.max_wall_time_ns,
|
|
286
|
+
)
|
|
287
|
+
config = CatalogFillConfig(
|
|
288
|
+
protocol=args.protocol,
|
|
289
|
+
endpoint=args.endpoint,
|
|
290
|
+
observed_at=args.observed_at,
|
|
291
|
+
page_size=args.page_size,
|
|
292
|
+
max_convergence_passes=args.max_convergence_passes,
|
|
293
|
+
limits=limits,
|
|
294
|
+
predecessor_sha256=args.predecessor_sha256,
|
|
295
|
+
)
|
|
296
|
+
retriever = PinnedHttpsRetriever(
|
|
297
|
+
resolver=SystemResolver(),
|
|
298
|
+
egress_policy=_endpoint_policy(args.endpoint),
|
|
299
|
+
transport=StdlibPinnedTransport(),
|
|
300
|
+
limits=RetrievalLimits(
|
|
301
|
+
max_response_bytes=min(args.max_response_bytes, 1 << 34),
|
|
302
|
+
max_aggregate_response_bytes=min(args.max_aggregate_bytes, 1 << 40),
|
|
303
|
+
max_redirects=5,
|
|
304
|
+
max_requests=16,
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
return run_catalog_fill(
|
|
308
|
+
config=config,
|
|
309
|
+
staging_root=args.staging_root,
|
|
310
|
+
retriever=retriever,
|
|
311
|
+
expected_checkpoint_digest=args.checkpoint_sha256,
|
|
312
|
+
max_pages_this_invocation=args.max_pages_this_invocation,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _fill_payload(args: argparse.Namespace, result) -> dict[str, Any]:
|
|
317
|
+
receipt = result.receipt
|
|
318
|
+
return {
|
|
319
|
+
"schema_version": "mr-data-catalog-fill.v1",
|
|
320
|
+
"reason_code": result.reason_code,
|
|
321
|
+
"fill_receipt_sha256": receipt.digest,
|
|
322
|
+
"fill_receipt": receipt.to_dict(),
|
|
323
|
+
"flagged_queue_sha256": receipt.flagged_queue_sha256,
|
|
324
|
+
"staging_sha256": result.eligible_staging_sha256,
|
|
325
|
+
"checkpoint_sha256": result.checkpoint_sha256,
|
|
326
|
+
"provider_count_reconciliation": args.provider_count_reconciliation,
|
|
327
|
+
"convergence_passes": receipt.convergence_passes,
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _validate_coordinate_pairs(args: argparse.Namespace) -> None:
|
|
332
|
+
predecessor_pair = (args.predecessor_root is None, args.predecessor_sha256 is None)
|
|
333
|
+
if predecessor_pair not in {(True, True), (False, False)}:
|
|
334
|
+
raise SourceContractError(
|
|
335
|
+
"CATALOG_PREDECESSOR_COORDINATE",
|
|
336
|
+
"predecessor",
|
|
337
|
+
"predecessor root and exact digest must be supplied together",
|
|
338
|
+
)
|
|
339
|
+
decisions_pair = (args.rights_decisions is None, args.rights_decisions_sha256 is None)
|
|
340
|
+
if decisions_pair not in {(True, True), (False, False)}:
|
|
341
|
+
raise SourceContractError(
|
|
342
|
+
"RIGHTS_DECISIONS_COORDINATE",
|
|
343
|
+
"rights_decisions",
|
|
344
|
+
"rights decisions and their exact digest must be supplied together",
|
|
345
|
+
)
|
|
346
|
+
if args.neural_mode == "sidecar" and args.neural_model_root is None:
|
|
347
|
+
raise SourceContractError(
|
|
348
|
+
"NEURAL_MODEL_ROOT_REQUIRED",
|
|
349
|
+
"neural_model_root",
|
|
350
|
+
"sidecar mode requires one explicit local model root",
|
|
351
|
+
)
|
|
352
|
+
if args.neural_mode == "lexical-only" and args.neural_model_root is not None:
|
|
353
|
+
raise SourceContractError(
|
|
354
|
+
"NEURAL_MODEL_ROOT_UNEXPECTED",
|
|
355
|
+
"neural_model_root",
|
|
356
|
+
"lexical-only mode accepts no model root",
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _endpoint_policy(endpoint: str) -> EgressPolicy:
|
|
361
|
+
hostname = urlsplit(endpoint).hostname
|
|
362
|
+
if hostname is None:
|
|
363
|
+
raise SourceContractError("FILL_ENDPOINT", "endpoint", "must name one public host")
|
|
364
|
+
return EgressPolicy(allowed_hostnames=(hostname.lower(),))
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _publication_citation_policy(endpoint: str, rights_decisions) -> EgressPolicy:
|
|
368
|
+
"""The hosts a sealed entry may cite: the provider, plus what a person's decision cites.
|
|
369
|
+
|
|
370
|
+
Machine-derived citations all name the provider endpoint, so the fetch policy and the
|
|
371
|
+
citation policy used to be the same object. A human rights decision breaks that identity
|
|
372
|
+
by construction: the person looked at a licence page, and licence pages live on the
|
|
373
|
+
publisher's own site, not on its data API. The widening is bounded and receipted -- only
|
|
374
|
+
hosts named as evidence inside the digest-verified decision document join the allowlist,
|
|
375
|
+
and the document's digest rides the published classification. Network egress is untouched:
|
|
376
|
+
this policy governs which citations a sealed entry may carry, and the fetch retriever
|
|
377
|
+
keeps the endpoint-only policy.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
endpoint_policy = _endpoint_policy(endpoint)
|
|
381
|
+
if not rights_decisions:
|
|
382
|
+
return endpoint_policy
|
|
383
|
+
hosts = set(endpoint_policy.allowed_hostnames)
|
|
384
|
+
for decision in rights_decisions.values():
|
|
385
|
+
for reference in decision.rights.evidence:
|
|
386
|
+
hostname = urlsplit(reference.uri).hostname
|
|
387
|
+
if hostname is not None:
|
|
388
|
+
hosts.add(hostname.lower())
|
|
389
|
+
return EgressPolicy(allowed_hostnames=tuple(sorted(hosts)))
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _publication_backends(args: argparse.Namespace):
|
|
393
|
+
lexical = LexicalHashingBackend()
|
|
394
|
+
if args.neural_mode == "lexical-only":
|
|
395
|
+
return lexical, None, "NEURAL_NOT_REQUESTED"
|
|
396
|
+
from mostlyright.data_harness.sources.catalog.neural import resolve_local_neural_backend
|
|
397
|
+
|
|
398
|
+
return lexical, resolve_local_neural_backend(args.neural_model_root), None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _load_predecessor(args: argparse.Namespace, neural_backend):
|
|
402
|
+
if args.predecessor_root is None:
|
|
403
|
+
return (), None
|
|
404
|
+
loaded = load_retrieval_catalog(
|
|
405
|
+
args.predecessor_root,
|
|
406
|
+
expected_manifest_sha256=args.predecessor_sha256,
|
|
407
|
+
neural_backend=neural_backend,
|
|
408
|
+
)
|
|
409
|
+
return loaded.lexical.entries, args.predecessor_root
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _load_authoring_bundle(path: Path, expected_sha256: str) -> _AuthoringBundle:
|
|
413
|
+
if not isinstance(expected_sha256, str) or _DIGEST.fullmatch(expected_sha256) is None:
|
|
414
|
+
raise SourceContractError(
|
|
415
|
+
"CATALOG_AUTHORING_DIGEST", "authoring_sha256", "must be a lowercase SHA-256"
|
|
416
|
+
)
|
|
417
|
+
raw = _read_bounded_regular(path)
|
|
418
|
+
if sha256_bytes(raw) != expected_sha256:
|
|
419
|
+
raise SourceContractError(
|
|
420
|
+
"CATALOG_AUTHORING_DIGEST",
|
|
421
|
+
"authoring",
|
|
422
|
+
"authoring bytes differ from the required digest",
|
|
423
|
+
)
|
|
424
|
+
value = parse_canonical_json(raw)
|
|
425
|
+
expected = {
|
|
426
|
+
"schema_version",
|
|
427
|
+
"candidates_by_record_id",
|
|
428
|
+
"predecessor_record_ids",
|
|
429
|
+
"predecessor_chains",
|
|
430
|
+
}
|
|
431
|
+
if (
|
|
432
|
+
not isinstance(value, dict)
|
|
433
|
+
or set(value) != expected
|
|
434
|
+
or value["schema_version"] != _AUTHORING_SCHEMA
|
|
435
|
+
):
|
|
436
|
+
raise SourceContractError(
|
|
437
|
+
"CATALOG_AUTHORING_SCHEMA", "authoring", "authoring fields or version differ"
|
|
438
|
+
)
|
|
439
|
+
candidates_raw = _mapping(value["candidates_by_record_id"], "candidates_by_record_id")
|
|
440
|
+
predecessor_ids_raw = _mapping(value["predecessor_record_ids"], "predecessor_record_ids")
|
|
441
|
+
chains_raw = _mapping(value["predecessor_chains"], "predecessor_chains")
|
|
442
|
+
all_keys = (*candidates_raw, *predecessor_ids_raw, *chains_raw)
|
|
443
|
+
if any(not isinstance(key, str) for key in all_keys):
|
|
444
|
+
raise SourceContractError(
|
|
445
|
+
"CATALOG_AUTHORING_SCHEMA", "authoring", "mapping keys must be strings"
|
|
446
|
+
)
|
|
447
|
+
candidates = {
|
|
448
|
+
key: catalog_entry_from_dict(candidate) for key, candidate in candidates_raw.items()
|
|
449
|
+
}
|
|
450
|
+
predecessor_ids: dict[str, str] = {}
|
|
451
|
+
for key, entry_id in predecessor_ids_raw.items():
|
|
452
|
+
if not isinstance(entry_id, str):
|
|
453
|
+
raise SourceContractError(
|
|
454
|
+
"CATALOG_AUTHORING_SCHEMA",
|
|
455
|
+
"predecessor_record_ids",
|
|
456
|
+
"entry identities must be strings",
|
|
457
|
+
)
|
|
458
|
+
predecessor_ids[key] = entry_id
|
|
459
|
+
chains: dict[str, tuple[CatalogEntry, ...]] = {}
|
|
460
|
+
for entry_id, raw_chain in chains_raw.items():
|
|
461
|
+
if not isinstance(raw_chain, list):
|
|
462
|
+
raise SourceContractError(
|
|
463
|
+
"CATALOG_AUTHORING_SCHEMA", "predecessor_chains", "chains must be arrays"
|
|
464
|
+
)
|
|
465
|
+
chains[entry_id] = tuple(catalog_entry_from_dict(item) for item in raw_chain)
|
|
466
|
+
return _AuthoringBundle(candidates, predecessor_ids, chains)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _read_bounded_regular(path: Path) -> bytes:
|
|
470
|
+
flags = os.O_RDONLY | os.O_NONBLOCK | getattr(os, "O_NOFOLLOW", 0)
|
|
471
|
+
try:
|
|
472
|
+
descriptor = os.open(path, flags)
|
|
473
|
+
except OSError as error:
|
|
474
|
+
raise SourceContractError(
|
|
475
|
+
"CATALOG_AUTHORING_UNREADABLE", "authoring", "cannot open the authoring document"
|
|
476
|
+
) from error
|
|
477
|
+
try:
|
|
478
|
+
opened = os.fstat(descriptor)
|
|
479
|
+
if not stat.S_ISREG(opened.st_mode) or opened.st_nlink != 1:
|
|
480
|
+
raise SourceContractError(
|
|
481
|
+
"CATALOG_AUTHORING_FILE",
|
|
482
|
+
"authoring",
|
|
483
|
+
"must be a single-link regular file",
|
|
484
|
+
)
|
|
485
|
+
if opened.st_size > _MAX_AUTHORING_BYTES:
|
|
486
|
+
raise SourceContractError(
|
|
487
|
+
"CATALOG_AUTHORING_LIMIT", "authoring", "exceeds the authoring byte limit"
|
|
488
|
+
)
|
|
489
|
+
chunks: list[bytes] = []
|
|
490
|
+
remaining = _MAX_AUTHORING_BYTES + 1
|
|
491
|
+
while remaining:
|
|
492
|
+
chunk = os.read(descriptor, min(remaining, 1 << 16))
|
|
493
|
+
if not chunk:
|
|
494
|
+
break
|
|
495
|
+
chunks.append(chunk)
|
|
496
|
+
remaining -= len(chunk)
|
|
497
|
+
finally:
|
|
498
|
+
os.close(descriptor)
|
|
499
|
+
raw = b"".join(chunks)
|
|
500
|
+
if len(raw) > _MAX_AUTHORING_BYTES:
|
|
501
|
+
raise SourceContractError(
|
|
502
|
+
"CATALOG_AUTHORING_LIMIT", "authoring", "exceeds the authoring byte limit"
|
|
503
|
+
)
|
|
504
|
+
return raw
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _mapping(value: Any, path: str) -> dict[str, Any]:
|
|
508
|
+
if not isinstance(value, dict):
|
|
509
|
+
raise SourceContractError("CATALOG_AUTHORING_SCHEMA", path, "must be an object")
|
|
510
|
+
return value
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _exit_code(payload: dict[str, Any]) -> int:
|
|
514
|
+
return 0 if payload.get("status") == "catalog_published" else 1
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
SPEC = CommandSpec(
|
|
518
|
+
name="catalog-fill",
|
|
519
|
+
help="stage, reconcile, and publish one bounded public catalog successor",
|
|
520
|
+
add_arguments=add_arguments,
|
|
521
|
+
run=run,
|
|
522
|
+
exit_code=_exit_code,
|
|
523
|
+
)
|