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,924 @@
|
|
|
1
|
+
"""``mr-data``'s front door: one binary, two install profiles, one command vocabulary.
|
|
2
|
+
|
|
3
|
+
WHY THIS MODULE EXISTS. ADR 0021 makes the client thin -- pure Python, no sandbox, no process
|
|
4
|
+
control -- and keeps the command names exactly as they are, so where a command runs stops being
|
|
5
|
+
user-visible variance. Those two requirements meet here. ``cli`` is the local lane and cannot be
|
|
6
|
+
imported at all without native wheels (it reaches ``hosted_worker``, which imports ``pyarrow`` and
|
|
7
|
+
``cryptography`` at module scope), so the entry point cannot BE ``cli``: it has to be something
|
|
8
|
+
that can decide, on a bare interpreter, whether the local lane is even installed.
|
|
9
|
+
|
|
10
|
+
THE DECISION, in one paragraph. With the ``[local]`` extra installed, every invocation is handed
|
|
11
|
+
to ``cli`` unchanged -- byte-identical help, byte-identical behaviour, no new code in the path --
|
|
12
|
+
unless the caller writes ``--hosted``, which selects the hosted lane for the commands that have
|
|
13
|
+
one. Without it, the hosted lane is the only lane there is: the hosted and portable commands run,
|
|
14
|
+
and every other name refuses by name and says WHY -- which of three different situations it is
|
|
15
|
+
in, and what would change it. What never happens is the third thing: a local command silently
|
|
16
|
+
doing nothing, or a hosted command silently running locally.
|
|
17
|
+
|
|
18
|
+
⚠ "INSTALLED" IS NOT THE ONLY QUESTION ANY MORE. The base wheel is `py3-none-any` and
|
|
19
|
+
installs on Windows, where the local lane cannot run no matter what is installed -- it needs
|
|
20
|
+
``fork`` and a kernel sandbox that does not exist there. So the routing decision asks the platform
|
|
21
|
+
first (:func:`local_lane_platform_supported`) and the closure second, and the refusal says which
|
|
22
|
+
of the two it was. That check is the run-time home of the rule ``scripts/hatch_build.py`` used to
|
|
23
|
+
enforce by refusing to build a wheel at all.
|
|
24
|
+
|
|
25
|
+
⚠ ``--hosted`` IS A LANE SELECTOR, NOT A NEW COMMAND. The vocabulary in :data:`VOCABULARY` is the
|
|
26
|
+
same fifty-eight names ``cli`` parses, and ``tests/test_thin_client.py`` asserts that set equality
|
|
27
|
+
against the live parser rather than trusting this file. A name added to one and not the other
|
|
28
|
+
fails the suite.
|
|
29
|
+
|
|
30
|
+
⚠ THE LANE TABLE IS NOT HERE. It is
|
|
31
|
+
:mod:`mostlyright.data_harness.thin.vocabulary`, which carries a written reason beside every
|
|
32
|
+
name. This module decides and refuses; that one says what the decision was and why, and is the
|
|
33
|
+
single source both the refusals and ``docs/HOSTED-PARITY.md`` read.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import argparse
|
|
39
|
+
import importlib
|
|
40
|
+
import json
|
|
41
|
+
import os
|
|
42
|
+
import sys
|
|
43
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
44
|
+
from typing import Any
|
|
45
|
+
|
|
46
|
+
from mostlyright.data_harness.thin.transport import ThinLaneError
|
|
47
|
+
from mostlyright.data_harness.thin.vocabulary import (
|
|
48
|
+
CLASSIFICATION,
|
|
49
|
+
HOSTED,
|
|
50
|
+
HOSTED_PENDING,
|
|
51
|
+
LANES,
|
|
52
|
+
LOCAL,
|
|
53
|
+
PORTABLE,
|
|
54
|
+
VOCABULARY,
|
|
55
|
+
WORKER,
|
|
56
|
+
names_in,
|
|
57
|
+
reason_for,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
#: The flag that asks for the hosted lane on a machine that also has the local one.
|
|
61
|
+
HOSTED_FLAG = "--hosted"
|
|
62
|
+
|
|
63
|
+
#: The flag that insists on the local lane, so a script can fail loudly on a thin install rather
|
|
64
|
+
#: than silently submitting work to the backend.
|
|
65
|
+
LOCAL_FLAG = "--local"
|
|
66
|
+
|
|
67
|
+
#: The third-party distributions the local lane cannot run without. Probed by name rather than
|
|
68
|
+
#: imported: ``find_spec`` answers on a bare interpreter in microseconds, and importing any of
|
|
69
|
+
#: them is precisely what the thin profile exists to avoid.
|
|
70
|
+
LOCAL_LANE_DISTRIBUTIONS = ("pyarrow", "polars", "cryptography")
|
|
71
|
+
|
|
72
|
+
#: Why an operating system can be unable to host the local lane at all, said once. The local lane
|
|
73
|
+
#: runs every acquisition and every Reader behind a kernel boundary it builds with POSIX process
|
|
74
|
+
#: control -- ``fork``, and then cgroups v2 or Seatbelt. Windows has none of the three, which is
|
|
75
|
+
#: the whole reason ADR 0021 stopped treating per-OS sandbox work as a product requirement.
|
|
76
|
+
LOCAL_LANE_PLATFORM_REASON = (
|
|
77
|
+
"the local execution lane needs POSIX process control and a kernel sandbox "
|
|
78
|
+
"(fork, and cgroups v2 or Seatbelt), which this operating system does not provide"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
#: Where the old wheel-tag fence went. ``scripts/hatch_build.py`` used to keep the local lane off
|
|
82
|
+
#: unsupported platforms by refusing to produce a wheel for them; the base wheel is now
|
|
83
|
+
#: `py3-none-any` and installs everywhere, so the rule has to be checked where the fact
|
|
84
|
+
#: is -- at run time, on the machine, before a local command is handed to ``cli``.
|
|
85
|
+
#:
|
|
86
|
+
#: ⚠ PROBED, NOT LISTED. An allow-list of OS/architecture tuples would refuse the local lane on
|
|
87
|
+
#: platforms it works on perfectly well today (linux/aarch64 among them, which the old build fence
|
|
88
|
+
#: excluded only because nobody minted wheels there). ``os.fork`` is the actual capability the
|
|
89
|
+
#: lane is built on, so it is the thing asked about.
|
|
90
|
+
LOCAL_LANE_UNSUPPORTED_PLATFORM_CODE = "THIN_LOCAL_LANE_UNSUPPORTED_PLATFORM"
|
|
91
|
+
|
|
92
|
+
#: What to install to get the local lane back. One string, so the refusals cannot disagree.
|
|
93
|
+
LOCAL_INSTALL_HINT = 'pip install "mostlyright-data[local]"'
|
|
94
|
+
|
|
95
|
+
#: The typed code each lane refuses with, and nothing else decides it. Three codes rather than
|
|
96
|
+
#: one, because the three answers are different answers: "this needs the other install", "this
|
|
97
|
+
#: should be hosted and is not yet, for the reason below", and "this is not a command you run".
|
|
98
|
+
REFUSAL_CODE = {
|
|
99
|
+
LOCAL: "THIN_COMMAND_IS_LOCAL",
|
|
100
|
+
HOSTED_PENDING: "THIN_COMMAND_HOSTED_PENDING",
|
|
101
|
+
WORKER: "THIN_COMMAND_IS_WORKER_INTERNAL",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#: The portable commands, and the ``ux.commands`` module each resolves to. Both are stdlib-only
|
|
105
|
+
#: today, so the thin profile runs the SAME implementation the local profile runs rather than a
|
|
106
|
+
#: second one written to look like it.
|
|
107
|
+
PORTABLE_MODULES = {"auth": "auth", "login": "login", "whoami": "whoami"}
|
|
108
|
+
|
|
109
|
+
#: One line per hosted command, for the help the thin profile prints. Short on purpose: the local
|
|
110
|
+
#: profile's ``--help`` is ``cli``'s and is not restated here, so these cannot drift out of sync
|
|
111
|
+
#: with prose they do not duplicate.
|
|
112
|
+
#
|
|
113
|
+
#: Written as pairs rather than as a literal mapping on purpose: a ``"status": "..."`` entry in a
|
|
114
|
+
#: dict literal is indistinguishable, to the status-vocabulary walker in ``tests/test_ux_render``,
|
|
115
|
+
#: from a payload declaring a status word -- and a help sentence is not a status.
|
|
116
|
+
HOSTED_SUMMARY = dict(
|
|
117
|
+
(
|
|
118
|
+
("build", "queue one hosted build and print where to watch it"),
|
|
119
|
+
("run", "queue one hosted build and print where to watch it"),
|
|
120
|
+
("watch", "stream one hosted run's live progress, resuming across stream cuts"),
|
|
121
|
+
("status", "report Studio's own reading of one hosted run, with its receipts"),
|
|
122
|
+
("deploy-dataset", "download one hosted run's dataset and verify its digest"),
|
|
123
|
+
("show", "show what one hosted run holds"),
|
|
124
|
+
("inspect", "show the sealed evidence for one hosted run"),
|
|
125
|
+
("verify", "re-check one hosted run against the evidence Studio sealed"),
|
|
126
|
+
("list", "list the hosted runs in this workspace"),
|
|
127
|
+
("fleet", "say which hosted runs are waiting for a person"),
|
|
128
|
+
("diff", "compare two hosted runs and say what changed"),
|
|
129
|
+
("note", "write one cell of the decision record into a hosted run's own log"),
|
|
130
|
+
("notebook", "bring back the notebook Studio rendered for one hosted run"),
|
|
131
|
+
("view", "open the notebook Studio rendered for one hosted run"),
|
|
132
|
+
("peek", "show the columns, types and first rows of one hosted table version"),
|
|
133
|
+
("deploy-status", "report what Studio says about the run one deployment queued"),
|
|
134
|
+
("acquire", "fetch one public address in Studio's clean room and print its bound Receipt"),
|
|
135
|
+
(
|
|
136
|
+
"acquire-cancel",
|
|
137
|
+
"cancel one active public acquisition and print Studio's cancellation receipt",
|
|
138
|
+
),
|
|
139
|
+
("approve", "ask for one approval, say where a person grants it, and read what they did"),
|
|
140
|
+
("recipe-approve", "the same ask, about one Table recipe approval"),
|
|
141
|
+
("propose", "author one hosted recipe proposal from a bundle and open its approval"),
|
|
142
|
+
("research-open", "open one hosted research session and watch its worker warm up"),
|
|
143
|
+
("research-probe", "ask a research session's worker one question and print its answer"),
|
|
144
|
+
("research-status", "report Studio's own reading of one research session, or one probe"),
|
|
145
|
+
("research-close", "close one research session; its worker exits"),
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def local_lane_platform_supported() -> bool:
|
|
151
|
+
"""Whether this operating system can host the local execution lane at all.
|
|
152
|
+
|
|
153
|
+
Distinct from :func:`local_lane_available`, and asked first, because the two answer different
|
|
154
|
+
questions and only one of them is fixed by installing something. On Windows the extra can be
|
|
155
|
+
installed -- pyarrow, polars and cryptography all publish Windows wheels -- and the lane still
|
|
156
|
+
cannot run, so "is it installed" is the wrong question to route on there.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
return hasattr(os, "fork")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def local_lane_available(
|
|
163
|
+
find_spec: Callable[[str], Any] | None = None,
|
|
164
|
+
) -> bool:
|
|
165
|
+
"""Whether this machine can run the local lane: the right OS, and the native closure."""
|
|
166
|
+
|
|
167
|
+
if not local_lane_platform_supported():
|
|
168
|
+
return False
|
|
169
|
+
resolve = find_spec or importlib.util.find_spec
|
|
170
|
+
for distribution in LOCAL_LANE_DISTRIBUTIONS:
|
|
171
|
+
try:
|
|
172
|
+
if resolve(distribution) is None:
|
|
173
|
+
return False
|
|
174
|
+
except (ImportError, ValueError): # pragma: no cover - a broken path entry
|
|
175
|
+
return False
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def selected_command(argv: Sequence[str]) -> str | None:
|
|
180
|
+
"""The first token that names a command, ignoring the flags that may precede it."""
|
|
181
|
+
|
|
182
|
+
for token in argv:
|
|
183
|
+
if token == "--":
|
|
184
|
+
return None
|
|
185
|
+
if not token.startswith("-"):
|
|
186
|
+
return token
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _lane_flags(argv: Sequence[str]) -> tuple[bool, bool]:
|
|
191
|
+
hosted = False
|
|
192
|
+
local = False
|
|
193
|
+
for token in argv:
|
|
194
|
+
if token == "--":
|
|
195
|
+
break
|
|
196
|
+
if token == HOSTED_FLAG:
|
|
197
|
+
hosted = True
|
|
198
|
+
elif token == LOCAL_FLAG:
|
|
199
|
+
local = True
|
|
200
|
+
return hosted, local
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _without_lane_flags(argv: Sequence[str]) -> list[str]:
|
|
204
|
+
return [token for token in argv if token not in {HOSTED_FLAG, LOCAL_FLAG}]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
#: The one sentence every translated positional gets. Written once so fifteen commands cannot
|
|
208
|
+
#: describe the same substitution fifteen slightly different ways.
|
|
209
|
+
_RUN_POSITIONAL = "the hosted run, by the identifier the submission printed"
|
|
210
|
+
|
|
211
|
+
#: The sentences the accepted-but-inert arguments carry in ``--help``, so somebody reads them
|
|
212
|
+
#: BEFORE running the command rather than in the receipt afterwards.
|
|
213
|
+
_NO_EFFECT_FOLDER = "no effect here; a hosted run has no folder on this computer"
|
|
214
|
+
_NO_EFFECT_SERVER = "no effect here; nothing is served from this computer"
|
|
215
|
+
_NO_EFFECT_REPLAY = (
|
|
216
|
+
"no effect here; the independent hosted check already ran and each row carries its verdict"
|
|
217
|
+
)
|
|
218
|
+
_NO_EFFECT_DECODED = "no effect here; the hosted preview arrives already decoded"
|
|
219
|
+
_NOTEBOOK_OUTPUT = "the folder to write into; an existing file is never overwritten"
|
|
220
|
+
|
|
221
|
+
#: The one translation `approve` makes, and the sentences its accepted-but-inert flags carry.
|
|
222
|
+
_APPROVAL_POSITIONAL = (
|
|
223
|
+
"the approval Studio holds, by its identifier; a hosted approval is a record on the backend "
|
|
224
|
+
"and not a Recipe file here"
|
|
225
|
+
)
|
|
226
|
+
_NO_EFFECT_APPROVAL_HUMAN = (
|
|
227
|
+
"no effect here; Studio records who decided and when, from the human session that decided"
|
|
228
|
+
)
|
|
229
|
+
_NO_EFFECT_APPROVAL_NAMED = "no effect here; the approval already has the identifier you named"
|
|
230
|
+
_NO_EFFECT_APPROVAL_POLICY = "no effect here; a hosted approval is decided under the workspace's"
|
|
231
|
+
_NO_EFFECT_APPROVAL_RECORD = (
|
|
232
|
+
"no effect here; the decision is Studio's immutable record and is read back from it"
|
|
233
|
+
)
|
|
234
|
+
_NO_EFFECT_STUDIO_CLEAN_ROOM = (
|
|
235
|
+
"no effect here; the clean room and the egress policy are Studio's, and the Receipt carries "
|
|
236
|
+
"their attestations"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _hosted_parser() -> argparse.ArgumentParser:
|
|
241
|
+
"""The hosted lane's own arguments, one subcommand per hosted name."""
|
|
242
|
+
|
|
243
|
+
parser = argparse.ArgumentParser(prog="mr-data", add_help=False)
|
|
244
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
245
|
+
|
|
246
|
+
def _add(name: str) -> argparse.ArgumentParser:
|
|
247
|
+
added = subparsers.add_parser(name, add_help=True, help=HOSTED_SUMMARY.get(name))
|
|
248
|
+
added.add_argument(
|
|
249
|
+
"--json",
|
|
250
|
+
action="store_true",
|
|
251
|
+
help="emit one machine-readable JSON object instead of human lines",
|
|
252
|
+
)
|
|
253
|
+
return added
|
|
254
|
+
|
|
255
|
+
for name in ("build", "run"):
|
|
256
|
+
submit = _add(name)
|
|
257
|
+
submit.add_argument(
|
|
258
|
+
"--recipe-proposal",
|
|
259
|
+
required=True,
|
|
260
|
+
help="the approved recipe proposal this build executes",
|
|
261
|
+
)
|
|
262
|
+
submit.add_argument(
|
|
263
|
+
"--recipe-digest",
|
|
264
|
+
required=True,
|
|
265
|
+
help="the recipe digest the proposal must still carry; bare lowercase hex",
|
|
266
|
+
)
|
|
267
|
+
submit.add_argument(
|
|
268
|
+
"--resource-class",
|
|
269
|
+
choices=("standard", "large"),
|
|
270
|
+
help="the capacity class to ask for; Studio refuses one it cannot satisfy",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
watch = _add("watch")
|
|
274
|
+
watch.add_argument("run_id", help="the run to follow")
|
|
275
|
+
watch.add_argument(
|
|
276
|
+
"--from-seq",
|
|
277
|
+
type=int,
|
|
278
|
+
default=0,
|
|
279
|
+
help="replay from after this log position; ignored once a cursor is known",
|
|
280
|
+
)
|
|
281
|
+
watch.add_argument(
|
|
282
|
+
"--last-event-id", help="resume from this cursor, as a previous watch reported it"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
status = _add("status")
|
|
286
|
+
status.add_argument("run_id", help="the run to report")
|
|
287
|
+
status.add_argument(
|
|
288
|
+
"--receipts",
|
|
289
|
+
action="store_true",
|
|
290
|
+
help="also read the execution summary and the sealed candidate evidence",
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
download = _add("deploy-dataset")
|
|
294
|
+
download.add_argument("run_id", help="the released run whose dataset to bring back")
|
|
295
|
+
download.add_argument("--output", help="the folder to write into; it is never overwritten")
|
|
296
|
+
download.add_argument(
|
|
297
|
+
"--kind",
|
|
298
|
+
action="append",
|
|
299
|
+
help="an artifact kind to fetch; repeatable, and defaults to the dataset itself",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# ------------------------------------------------------------------------------------
|
|
303
|
+
# The read, compare and view commands
|
|
304
|
+
# ------------------------------------------------------------------------------------
|
|
305
|
+
#
|
|
306
|
+
# Every argument the local command declares appears here under the SAME destination, so a
|
|
307
|
+
# script keeps working when the machine it runs on changes profile. Where the hosted lane
|
|
308
|
+
# cannot honour one it is still accepted, and `thin.parity._no_effect` reports it -- never
|
|
309
|
+
# `argparse` refusing it, which would be the breakage this phase exists to remove.
|
|
310
|
+
#
|
|
311
|
+
# The one translation, made in every command that needs it: the local positional names a
|
|
312
|
+
# folder and the hosted one names a run. It keeps the local destination (`run_dir`,
|
|
313
|
+
# `workspace`, `target`) so the two parsers stay comparable argument for argument.
|
|
314
|
+
|
|
315
|
+
show = _add("show")
|
|
316
|
+
show.add_argument("run_dir", help=_RUN_POSITIONAL)
|
|
317
|
+
show.add_argument(
|
|
318
|
+
"--card", action="store_true", help="print the plain-language summary and nothing else"
|
|
319
|
+
)
|
|
320
|
+
show.add_argument(
|
|
321
|
+
"--members", action="store_true", help="also list every artifact the run published"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
inspected = _add("inspect")
|
|
325
|
+
inspected.add_argument("workspace", help=_RUN_POSITIONAL)
|
|
326
|
+
|
|
327
|
+
verified = _add("verify")
|
|
328
|
+
verified.add_argument("run_dir", help=_RUN_POSITIONAL)
|
|
329
|
+
|
|
330
|
+
listed = _add("list")
|
|
331
|
+
listed.add_argument("root", nargs="?", default=None, help=_NO_EFFECT_FOLDER)
|
|
332
|
+
listed.add_argument("--verify", action="store_true", help=_NO_EFFECT_REPLAY)
|
|
333
|
+
listed.add_argument("--depth", type=int, default=None, help=_NO_EFFECT_FOLDER)
|
|
334
|
+
listed.add_argument(
|
|
335
|
+
"--status",
|
|
336
|
+
dest="run_status",
|
|
337
|
+
help="list only the runs Studio holds in this state",
|
|
338
|
+
)
|
|
339
|
+
listed.add_argument("--limit", type=int, help="how many runs to ask for, from 1 to 200")
|
|
340
|
+
|
|
341
|
+
scanned = _add("fleet")
|
|
342
|
+
scanned.add_argument("root", nargs="?", default=None, help=_NO_EFFECT_FOLDER)
|
|
343
|
+
scanned.add_argument(
|
|
344
|
+
"--html", action="store_true", help="no effect here; the live page is the dashboard"
|
|
345
|
+
)
|
|
346
|
+
scanned.add_argument("--limit", type=int, help="how many runs to ask for, from 1 to 200")
|
|
347
|
+
|
|
348
|
+
compared = _add("diff")
|
|
349
|
+
compared.add_argument("left", help="the first run")
|
|
350
|
+
compared.add_argument("right", help="the second run")
|
|
351
|
+
compared.add_argument(
|
|
352
|
+
"--columns-only",
|
|
353
|
+
action="store_true",
|
|
354
|
+
help="list only the changes to the columns; the count and the answer still cover them all",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# ------------------------------------------------------------------------------------
|
|
358
|
+
# The command that writes prose
|
|
359
|
+
# ------------------------------------------------------------------------------------
|
|
360
|
+
#
|
|
361
|
+
# `note` makes no translation at all, because there is nothing to translate: the local lane's
|
|
362
|
+
# decision record is a research notebook in a folder, this one is a cell in the run's log, and
|
|
363
|
+
# the two share a discipline rather than an argument surface. Every flag here therefore means
|
|
364
|
+
# exactly what it says, and `flags_without_effect` comes back empty on every invocation.
|
|
365
|
+
|
|
366
|
+
noted = _add("note")
|
|
367
|
+
noted.add_argument("--run", required=True, help=_RUN_POSITIONAL)
|
|
368
|
+
noted.add_argument(
|
|
369
|
+
"--list",
|
|
370
|
+
action="store_true",
|
|
371
|
+
help="list the cells this run holds instead of writing one, superseded ones flagged",
|
|
372
|
+
)
|
|
373
|
+
noted.add_argument(
|
|
374
|
+
"--heading",
|
|
375
|
+
help="the cell's one outcome-led line; it is what the notebook shows",
|
|
376
|
+
)
|
|
377
|
+
noted.add_argument(
|
|
378
|
+
"--cell-id",
|
|
379
|
+
dest="cell_id",
|
|
380
|
+
help=(
|
|
381
|
+
"a stable identifier for this cell; writing it again revises the cell in place. "
|
|
382
|
+
"Derived from the heading when left out"
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
noted.add_argument(
|
|
386
|
+
"--markdown-file",
|
|
387
|
+
dest="markdown_file",
|
|
388
|
+
metavar="FILE",
|
|
389
|
+
help="the cell body, as UTF-8 markdown; read from standard input when left out",
|
|
390
|
+
)
|
|
391
|
+
noted.add_argument(
|
|
392
|
+
"--checkpoint",
|
|
393
|
+
help=(
|
|
394
|
+
"the run-event this cell is about, as the sequence or the cursor mr-data watch "
|
|
395
|
+
"printed; the notebook orders cells by it"
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
noted.add_argument(
|
|
399
|
+
"--revise",
|
|
400
|
+
metavar="CELL_ID",
|
|
401
|
+
help="the differently named cell this one supersedes; it stays in the log and is flagged",
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
written = _add("notebook")
|
|
405
|
+
written.add_argument("run_dir", help=_RUN_POSITIONAL)
|
|
406
|
+
written.add_argument("--output", help=_NOTEBOOK_OUTPUT)
|
|
407
|
+
|
|
408
|
+
opened = _add("view")
|
|
409
|
+
opened.add_argument("run", help=_RUN_POSITIONAL)
|
|
410
|
+
opened.add_argument("--output", help=_NOTEBOOK_OUTPUT)
|
|
411
|
+
opened.add_argument("--open", action="store_true", help="open the document once it is here")
|
|
412
|
+
opened.add_argument("--workspace", help=_NO_EFFECT_FOLDER)
|
|
413
|
+
opened.add_argument("--run-dir", dest="run_dir", help=_NO_EFFECT_FOLDER)
|
|
414
|
+
opened.add_argument("--research-dir", dest="research_dir", help=_NO_EFFECT_FOLDER)
|
|
415
|
+
opened.add_argument("--host", help=_NO_EFFECT_SERVER)
|
|
416
|
+
opened.add_argument("--port", type=int, help=_NO_EFFECT_SERVER)
|
|
417
|
+
|
|
418
|
+
looked = _add("peek")
|
|
419
|
+
looked.add_argument("target", help=_RUN_POSITIONAL)
|
|
420
|
+
looked.add_argument(
|
|
421
|
+
"--rows", type=int, default=5, help="how many of the preview's rows to show"
|
|
422
|
+
)
|
|
423
|
+
looked.add_argument("--format", dest="data_format", help=_NO_EFFECT_DECODED)
|
|
424
|
+
looked.add_argument("--reader", help=_NO_EFFECT_DECODED)
|
|
425
|
+
looked.add_argument("--reader-options", dest="reader_options", help=_NO_EFFECT_DECODED)
|
|
426
|
+
looked.add_argument(
|
|
427
|
+
"--allow-redirect-host",
|
|
428
|
+
dest="allow_redirect_hosts",
|
|
429
|
+
action="append",
|
|
430
|
+
help=_NO_EFFECT_DECODED,
|
|
431
|
+
)
|
|
432
|
+
looked.add_argument(
|
|
433
|
+
"--json-records-pointer", dest="json_records_pointer", help=_NO_EFFECT_DECODED
|
|
434
|
+
)
|
|
435
|
+
looked.add_argument(
|
|
436
|
+
"--json-expand", dest="json_expand", action="append", help=_NO_EFFECT_DECODED
|
|
437
|
+
)
|
|
438
|
+
looked.add_argument(
|
|
439
|
+
"--json-column", dest="json_column", action="append", help=_NO_EFFECT_DECODED
|
|
440
|
+
)
|
|
441
|
+
looked.add_argument(
|
|
442
|
+
"--json-optional-column",
|
|
443
|
+
dest="json_optional_column",
|
|
444
|
+
action="append",
|
|
445
|
+
help=_NO_EFFECT_DECODED,
|
|
446
|
+
)
|
|
447
|
+
looked.add_argument(
|
|
448
|
+
"--json-document-format", dest="json_document_format", help=_NO_EFFECT_DECODED
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
deployment = _add("deploy-status")
|
|
452
|
+
deployment.add_argument("run_dir", help=_RUN_POSITIONAL)
|
|
453
|
+
deployment.add_argument(
|
|
454
|
+
"--watch-seconds",
|
|
455
|
+
dest="watch_seconds",
|
|
456
|
+
type=int,
|
|
457
|
+
help="no effect here; mr-data watch streams a run rather than asking again on a timer",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# ------------------------------------------------------------------------------------
|
|
461
|
+
# The two commands that write: one acquisition, and one ask a human must still settle
|
|
462
|
+
# ------------------------------------------------------------------------------------
|
|
463
|
+
#
|
|
464
|
+
# Same rule as above -- every argument the local command declares appears here under the SAME
|
|
465
|
+
# destination -- with the one translation each command needs. `acquire` needs none at all: the
|
|
466
|
+
# hosted route is the route a full install already takes when it is given neither attestation,
|
|
467
|
+
# so the document, the output directory and the allowlist mean exactly what they mean locally.
|
|
468
|
+
# `approve` needs one: `--recipe` names the approval Studio holds rather than a Recipe file,
|
|
469
|
+
# because a hosted approval is a record on the backend and there is no file here.
|
|
470
|
+
|
|
471
|
+
acquired = _add("acquire")
|
|
472
|
+
acquired.add_argument(
|
|
473
|
+
"--acquisition",
|
|
474
|
+
required=True,
|
|
475
|
+
help="acquisition document: the adapter, the request, its governance, its limits",
|
|
476
|
+
)
|
|
477
|
+
acquired.add_argument(
|
|
478
|
+
"--output", required=True, help="an existing directory; nothing is written outside it"
|
|
479
|
+
)
|
|
480
|
+
acquired.add_argument(
|
|
481
|
+
"--attempt-id",
|
|
482
|
+
dest="attempt_id",
|
|
483
|
+
required=True,
|
|
484
|
+
help="the attempt this acquisition is recorded under",
|
|
485
|
+
)
|
|
486
|
+
acquired.add_argument(
|
|
487
|
+
"--allow-host",
|
|
488
|
+
dest="allow_host",
|
|
489
|
+
action="append",
|
|
490
|
+
metavar="HOST",
|
|
491
|
+
help=(
|
|
492
|
+
"one exact hostname this acquisition may reach; repeatable; "
|
|
493
|
+
"there is no default and no environment override"
|
|
494
|
+
),
|
|
495
|
+
)
|
|
496
|
+
acquired.add_argument(
|
|
497
|
+
"--clean-room-attestation",
|
|
498
|
+
dest="clean_room_attestation",
|
|
499
|
+
metavar="DIGEST",
|
|
500
|
+
help=_NO_EFFECT_STUDIO_CLEAN_ROOM,
|
|
501
|
+
)
|
|
502
|
+
acquired.add_argument(
|
|
503
|
+
"--network-policy-attestation",
|
|
504
|
+
dest="network_policy_attestation",
|
|
505
|
+
metavar="DIGEST",
|
|
506
|
+
help=_NO_EFFECT_STUDIO_CLEAN_ROOM,
|
|
507
|
+
)
|
|
508
|
+
acquired.add_argument(
|
|
509
|
+
"--cadence-history",
|
|
510
|
+
dest="cadence_history",
|
|
511
|
+
metavar="FOLDER",
|
|
512
|
+
help="a probe run in Studio's clean room is recorded by Studio; this is refused by name",
|
|
513
|
+
)
|
|
514
|
+
acquired.add_argument(
|
|
515
|
+
"--cadence-authority",
|
|
516
|
+
dest="cadence_authority",
|
|
517
|
+
metavar="FILE",
|
|
518
|
+
help="the cadence authority document; refused with --cadence-history, for the same reason",
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
from mostlyright.data_harness.thin import acquire_cancel
|
|
522
|
+
|
|
523
|
+
acquire_cancel.declare_arguments(_add("acquire-cancel"))
|
|
524
|
+
|
|
525
|
+
for name in ("approve", "recipe-approve"):
|
|
526
|
+
asked = _add(name)
|
|
527
|
+
asked.add_argument("--recipe", required=True, help=_APPROVAL_POSITIONAL)
|
|
528
|
+
asked.add_argument(
|
|
529
|
+
"--decision",
|
|
530
|
+
choices=("approved", "rejected"),
|
|
531
|
+
default="approved",
|
|
532
|
+
help="the decision to ask a person for; approved when left out",
|
|
533
|
+
)
|
|
534
|
+
asked.add_argument(
|
|
535
|
+
"--reason", help="why you are asking, shown beside your principal in the queue"
|
|
536
|
+
)
|
|
537
|
+
asked.add_argument(
|
|
538
|
+
"--wait-seconds",
|
|
539
|
+
dest="wait_seconds",
|
|
540
|
+
type=int,
|
|
541
|
+
help="how long to wait for a person; 0 records the ask and returns",
|
|
542
|
+
)
|
|
543
|
+
asked.add_argument("--output", help=_NO_EFFECT_APPROVAL_RECORD)
|
|
544
|
+
asked.add_argument("--approval-id", dest="approval_id", help=_NO_EFFECT_APPROVAL_NAMED)
|
|
545
|
+
asked.add_argument("--approved-by", dest="approved_by", help=_NO_EFFECT_APPROVAL_HUMAN)
|
|
546
|
+
asked.add_argument("--approved-at", dest="approved_at", help=_NO_EFFECT_APPROVAL_HUMAN)
|
|
547
|
+
asked.add_argument(
|
|
548
|
+
"--approval-policy", dest="approval_policy", help=_NO_EFFECT_APPROVAL_POLICY
|
|
549
|
+
)
|
|
550
|
+
asked.add_argument(
|
|
551
|
+
"--approval-policy-digest",
|
|
552
|
+
dest="approval_policy_digest",
|
|
553
|
+
help=_NO_EFFECT_APPROVAL_POLICY,
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# ------------------------------------------------------------------------------------
|
|
557
|
+
# The authoring front door, and the research session it is explored through
|
|
558
|
+
# ------------------------------------------------------------------------------------
|
|
559
|
+
#
|
|
560
|
+
# No translation and no inert flag, because there is no local twin to translate from: `propose`
|
|
561
|
+
# drives Studio's own authoring routes and a research session is a warm worker on the backend,
|
|
562
|
+
# and nothing on this computer resembles either. Each declares its arguments once, in its own
|
|
563
|
+
# module, and the local parser calls the same function, so the two front doors cannot disagree.
|
|
564
|
+
from mostlyright.data_harness.thin import propose as propose_module
|
|
565
|
+
from mostlyright.data_harness.thin import research
|
|
566
|
+
|
|
567
|
+
propose_module.declare_arguments(_add("propose"))
|
|
568
|
+
for name in research.COMMANDS:
|
|
569
|
+
research.declare_arguments(name, _add(name))
|
|
570
|
+
return parser
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _handlers() -> dict[str, Callable[[argparse.Namespace], dict[str, Any]]]:
|
|
574
|
+
from mostlyright.data_harness.thin import (
|
|
575
|
+
acquire,
|
|
576
|
+
acquire_cancel,
|
|
577
|
+
approvals,
|
|
578
|
+
commands,
|
|
579
|
+
narrative,
|
|
580
|
+
parity,
|
|
581
|
+
propose,
|
|
582
|
+
research,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
return {
|
|
586
|
+
"acquire": acquire.acquire,
|
|
587
|
+
"acquire-cancel": acquire_cancel.acquire_cancel,
|
|
588
|
+
"approve": approvals.approve,
|
|
589
|
+
"recipe-approve": approvals.recipe_approve,
|
|
590
|
+
"propose": propose.propose,
|
|
591
|
+
**research.HANDLERS,
|
|
592
|
+
"build": commands.submit,
|
|
593
|
+
"run": commands.submit,
|
|
594
|
+
"watch": commands.watch,
|
|
595
|
+
"status": commands.status,
|
|
596
|
+
"deploy-dataset": commands.download,
|
|
597
|
+
"show": parity.show,
|
|
598
|
+
"inspect": parity.inspect,
|
|
599
|
+
"verify": parity.verify,
|
|
600
|
+
"list": parity.listing,
|
|
601
|
+
"fleet": parity.fleet,
|
|
602
|
+
"diff": parity.diff,
|
|
603
|
+
"note": narrative.note,
|
|
604
|
+
"notebook": parity.notebook,
|
|
605
|
+
"view": parity.view,
|
|
606
|
+
"peek": parity.peek,
|
|
607
|
+
"deploy-status": parity.deployment_status,
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _exit_codes() -> dict[str, Callable[[dict[str, Any]], int]]:
|
|
612
|
+
"""The hosted commands whose answer is a gate a script acts on rather than a report.
|
|
613
|
+
|
|
614
|
+
Derived from the payload, never returned alongside it, for the reason
|
|
615
|
+
``ux.commands.CommandSpec`` gives: an exit code that can disagree with the printed answer is
|
|
616
|
+
an exit code somebody will trust over the answer.
|
|
617
|
+
"""
|
|
618
|
+
|
|
619
|
+
from mostlyright.data_harness.thin import acquire, approvals, parity, propose, research
|
|
620
|
+
|
|
621
|
+
return {
|
|
622
|
+
"verify": parity.verify_exit_code,
|
|
623
|
+
"acquire": acquire.acquire_exit_code,
|
|
624
|
+
"approve": approvals.approve_exit_code,
|
|
625
|
+
"recipe-approve": approvals.approve_exit_code,
|
|
626
|
+
"propose": propose.propose_exit_code,
|
|
627
|
+
**research.EXIT_CODES,
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _emit(payload: dict[str, Any], *, as_json: bool) -> None:
|
|
632
|
+
if as_json:
|
|
633
|
+
print(json.dumps(payload, ensure_ascii=False, allow_nan=False, sort_keys=True), flush=True)
|
|
634
|
+
return
|
|
635
|
+
from mostlyright.data_harness.ux.render import render_human
|
|
636
|
+
|
|
637
|
+
print(render_human(payload), flush=True)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _refuse(
|
|
641
|
+
code: str,
|
|
642
|
+
detail: str,
|
|
643
|
+
*,
|
|
644
|
+
as_json: bool,
|
|
645
|
+
retry_after_seconds: int | None = None,
|
|
646
|
+
extra: Mapping[str, Any] | None = None,
|
|
647
|
+
) -> int:
|
|
648
|
+
"""Report one refusal the way every other ``mr-data`` refusal is reported.
|
|
649
|
+
|
|
650
|
+
Both renderings go to stderr and both carry the remediation the error map already holds for
|
|
651
|
+
this code, exactly as ``cli._print_error`` does. A hosted refusal is not a different kind of
|
|
652
|
+
refusal, so it must not read like one -- and routing it through the same map is what makes the
|
|
653
|
+
`hosted lane` family paragraph reach a person rather than sit in a table.
|
|
654
|
+
|
|
655
|
+
``retry_after_seconds`` is the window Studio stamped on a refusal it expects to be retried --
|
|
656
|
+
a workspace that already holds its bound of live research sessions answers that way -- and it
|
|
657
|
+
is carried under the same key ``cli`` gives it, so a script reads one number in both profiles
|
|
658
|
+
rather than parsing it out of the sentence.
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
from mostlyright.data_harness.ux.remediation import remediation_for
|
|
662
|
+
from mostlyright.data_harness.ux.render import render_error
|
|
663
|
+
|
|
664
|
+
remediation = remediation_for(code)
|
|
665
|
+
if as_json:
|
|
666
|
+
error: dict[str, Any] = {
|
|
667
|
+
"schema_version": "mr-data-error.v1",
|
|
668
|
+
"code": code,
|
|
669
|
+
"error": detail,
|
|
670
|
+
"headline": detail,
|
|
671
|
+
"remediation": list(remediation),
|
|
672
|
+
}
|
|
673
|
+
if retry_after_seconds is not None:
|
|
674
|
+
error["retry_after_seconds"] = retry_after_seconds
|
|
675
|
+
if extra:
|
|
676
|
+
error.update(extra)
|
|
677
|
+
print(
|
|
678
|
+
json.dumps(error, ensure_ascii=False, sort_keys=True),
|
|
679
|
+
file=sys.stderr,
|
|
680
|
+
flush=True,
|
|
681
|
+
)
|
|
682
|
+
else:
|
|
683
|
+
print(render_error(detail, code=code, remediation=remediation), file=sys.stderr, flush=True)
|
|
684
|
+
return 2
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _refusal_detail(command: str, lane: str) -> str:
|
|
688
|
+
"""One sentence naming the command, saying why it is where it is, and what to do next.
|
|
689
|
+
|
|
690
|
+
The reason comes out of the classification table rather than out of a template, because the
|
|
691
|
+
three lanes are three different situations and only one of them is answered by installing
|
|
692
|
+
something. ``mr-data init`` says install the extra; ``mr-data approve`` says what is missing
|
|
693
|
+
upstream and does not pretend an install would fix it; ``mr-data-producer``'s command-line
|
|
694
|
+
twin says it is not a command anybody runs.
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
reason = reason_for(command)
|
|
698
|
+
if lane == LOCAL:
|
|
699
|
+
if not local_lane_platform_supported():
|
|
700
|
+
# ⚠ NOT the install hint. Naming an install that cannot help is the specific dishonesty
|
|
701
|
+
# this branch exists to prevent: `pip install "...[local]"` SUCCEEDS on Windows and
|
|
702
|
+
# buys nothing, so a person who followed that advice would arrive back here having
|
|
703
|
+
# downloaded two hundred megabytes for no reason.
|
|
704
|
+
return (
|
|
705
|
+
f"{command!r} runs on this computer -- {reason} -- and "
|
|
706
|
+
f"{LOCAL_LANE_PLATFORM_REASON}. The hosted lane runs this work instead: "
|
|
707
|
+
"mr-data --help lists the commands that run here"
|
|
708
|
+
)
|
|
709
|
+
return f"{command!r} runs on this computer -- {reason}; {LOCAL_INSTALL_HINT}"
|
|
710
|
+
if lane == HOSTED_PENDING:
|
|
711
|
+
return (
|
|
712
|
+
f"{command!r} has no hosted lane yet -- {reason}. The local execution lane still runs "
|
|
713
|
+
f"it: {LOCAL_INSTALL_HINT}"
|
|
714
|
+
)
|
|
715
|
+
return (
|
|
716
|
+
f"{command!r} is a backend operator step, not part of the hosted command surface -- "
|
|
717
|
+
f"{reason}"
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _thin_help() -> str:
|
|
722
|
+
"""The help a thin install prints: every command name, and which of them run here."""
|
|
723
|
+
|
|
724
|
+
from mostlyright.data_harness import package_version
|
|
725
|
+
|
|
726
|
+
lines = [
|
|
727
|
+
f"mr-data {package_version()} -- hosted profile (pure Python, no local execution)",
|
|
728
|
+
"",
|
|
729
|
+
"usage: mr-data <command> [options]",
|
|
730
|
+
"",
|
|
731
|
+
"Commands that run here:",
|
|
732
|
+
]
|
|
733
|
+
for name in names_in(HOSTED):
|
|
734
|
+
lines.append(f" {name:<24}{HOSTED_SUMMARY.get(name, '')}")
|
|
735
|
+
for name in names_in(PORTABLE):
|
|
736
|
+
lines.append(f" {name:<24}{CLASSIFICATION[name].reason}")
|
|
737
|
+
for lane, heading in (
|
|
738
|
+
(
|
|
739
|
+
HOSTED_PENDING,
|
|
740
|
+
"Commands that should be hosted and are not yet -- run mr-data <name> to be told why:",
|
|
741
|
+
),
|
|
742
|
+
(
|
|
743
|
+
LOCAL,
|
|
744
|
+
"Commands that need the local execution lane, which "
|
|
745
|
+
+ (
|
|
746
|
+
"is not installed:"
|
|
747
|
+
if local_lane_platform_supported()
|
|
748
|
+
else "this operating system cannot host:"
|
|
749
|
+
),
|
|
750
|
+
),
|
|
751
|
+
(WORKER, "Backend operator steps, which are never run from here:"),
|
|
752
|
+
):
|
|
753
|
+
names = names_in(lane)
|
|
754
|
+
if not names:
|
|
755
|
+
continue
|
|
756
|
+
lines.extend(["", heading])
|
|
757
|
+
# Three to a row: the longest name is `agent-request-validate` at twenty-two characters,
|
|
758
|
+
# and four columns of that would wrap on an eighty-column terminal.
|
|
759
|
+
for index in range(0, len(names), 3):
|
|
760
|
+
row = names[index : index + 3]
|
|
761
|
+
lines.append((" " + " ".join(f"{name:<24}" for name in row)).rstrip())
|
|
762
|
+
if local_lane_platform_supported():
|
|
763
|
+
lines.extend(["", f" install the local lane with: {LOCAL_INSTALL_HINT}", ""])
|
|
764
|
+
else:
|
|
765
|
+
lines.extend(["", f" {LOCAL_LANE_PLATFORM_REASON}", ""])
|
|
766
|
+
return "\n".join(lines)
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
770
|
+
"""Route one invocation to the lane that can serve it, or refuse it by name."""
|
|
771
|
+
|
|
772
|
+
arguments = list(sys.argv[1:] if argv is None else argv)
|
|
773
|
+
hosted_requested, local_requested = _lane_flags(arguments)
|
|
774
|
+
local_available = local_lane_available()
|
|
775
|
+
as_json = "--json" in arguments
|
|
776
|
+
|
|
777
|
+
if local_requested and not local_available:
|
|
778
|
+
if not local_lane_platform_supported():
|
|
779
|
+
return _refuse(
|
|
780
|
+
LOCAL_LANE_UNSUPPORTED_PLATFORM_CODE,
|
|
781
|
+
f"--local was asked for and {LOCAL_LANE_PLATFORM_REASON}",
|
|
782
|
+
as_json=as_json,
|
|
783
|
+
)
|
|
784
|
+
return _refuse(
|
|
785
|
+
"THIN_LOCAL_LANE_UNAVAILABLE",
|
|
786
|
+
f"--local was asked for and the local execution lane is not installed; "
|
|
787
|
+
f"{LOCAL_INSTALL_HINT}",
|
|
788
|
+
as_json=as_json,
|
|
789
|
+
)
|
|
790
|
+
if local_available and not hosted_requested:
|
|
791
|
+
# The ordinary case on a full install: nothing about this invocation changes.
|
|
792
|
+
from mostlyright.data_harness.cli import main as local_main
|
|
793
|
+
|
|
794
|
+
return int(local_main(_without_lane_flags(arguments) if local_requested else arguments))
|
|
795
|
+
|
|
796
|
+
command = selected_command(arguments)
|
|
797
|
+
if command is None:
|
|
798
|
+
if "--version" in arguments:
|
|
799
|
+
from mostlyright.data_harness import package_version
|
|
800
|
+
|
|
801
|
+
print(f"mr-data {package_version()}", flush=True)
|
|
802
|
+
return 0
|
|
803
|
+
print(_thin_help(), flush=True)
|
|
804
|
+
return 0 if {"-h", "--help"} & set(arguments) else 2
|
|
805
|
+
|
|
806
|
+
lane = VOCABULARY.get(command)
|
|
807
|
+
if lane is None:
|
|
808
|
+
return _refuse(
|
|
809
|
+
"THIN_UNKNOWN_COMMAND",
|
|
810
|
+
f"there is no mr-data command named {command!r}",
|
|
811
|
+
as_json=as_json,
|
|
812
|
+
)
|
|
813
|
+
if lane in REFUSAL_CODE:
|
|
814
|
+
if hosted_requested:
|
|
815
|
+
return _refuse(
|
|
816
|
+
"THIN_COMMAND_HAS_NO_HOSTED_LANE",
|
|
817
|
+
f"{command!r} has no hosted lane: {reason_for(command)}",
|
|
818
|
+
as_json=as_json,
|
|
819
|
+
)
|
|
820
|
+
return _refuse(REFUSAL_CODE[lane], _refusal_detail(command, lane), as_json=as_json)
|
|
821
|
+
if lane == PORTABLE:
|
|
822
|
+
return _portable(command, _without_lane_flags(arguments), as_json=as_json)
|
|
823
|
+
return _hosted(command, _without_lane_flags(arguments), as_json=as_json)
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def _portable(command: str, arguments: Sequence[str], *, as_json: bool) -> int:
|
|
827
|
+
"""Run one command whose existing implementation is already pure Python."""
|
|
828
|
+
|
|
829
|
+
from mostlyright.data_harness.ux.commands import discovered_commands
|
|
830
|
+
|
|
831
|
+
spec = next(
|
|
832
|
+
(item for item in discovered_commands() if item.name == PORTABLE_MODULES[command]), None
|
|
833
|
+
)
|
|
834
|
+
if spec is None: # pragma: no cover - the descriptor tuple is closed and checked by a test
|
|
835
|
+
return _refuse(
|
|
836
|
+
"THIN_COMMAND_UNAVAILABLE", f"{command!r} is not registered", as_json=as_json
|
|
837
|
+
)
|
|
838
|
+
parser = argparse.ArgumentParser(prog=f"mr-data {command}")
|
|
839
|
+
parser.add_argument("--json", action="store_true")
|
|
840
|
+
resolved = spec.resolve()
|
|
841
|
+
resolved.add_arguments(parser)
|
|
842
|
+
remaining = list(arguments)
|
|
843
|
+
remaining.remove(command)
|
|
844
|
+
parsed = parser.parse_args(remaining)
|
|
845
|
+
try:
|
|
846
|
+
payload = resolved.run(parsed)
|
|
847
|
+
except Exception as error:
|
|
848
|
+
code = getattr(error, "code", None)
|
|
849
|
+
detail = getattr(error, "detail", None) or str(error)
|
|
850
|
+
return _refuse(code or "THIN_COMMAND_FAILED", detail, as_json=as_json)
|
|
851
|
+
_emit(payload, as_json=as_json or bool(getattr(parsed, "json", False)))
|
|
852
|
+
return 0 if resolved.exit_code is None else int(resolved.exit_code(payload))
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def _hosted(command: str, arguments: Sequence[str], *, as_json: bool) -> int:
|
|
856
|
+
"""Run one hosted command, rendering a typed refusal rather than a traceback."""
|
|
857
|
+
|
|
858
|
+
from mostlyright.data_harness.thin.acquire import AcquireRefusal
|
|
859
|
+
from mostlyright.data_harness.thin.acquire_cancel import CancellationRefusal
|
|
860
|
+
|
|
861
|
+
parsed = _hosted_parser().parse_args(list(arguments))
|
|
862
|
+
try:
|
|
863
|
+
payload = _handlers()[command](parsed)
|
|
864
|
+
except AcquireRefusal as refusal:
|
|
865
|
+
# ⚠ NOT `_refuse`. A Courier that reached the source and was told no, a document whose
|
|
866
|
+
# limits are over the hosted caps, an address nobody allowlisted -- these are the local
|
|
867
|
+
# lane's own `status: "refused"` receipts, with a headline, an operator action and exit 1.
|
|
868
|
+
# Rendering them as a client-side error would report a publisher's answer in the words
|
|
869
|
+
# this product uses for a broken client.
|
|
870
|
+
_emit(refusal.to_receipt(), as_json=as_json or parsed.json)
|
|
871
|
+
return 1
|
|
872
|
+
except CancellationRefusal as refusal:
|
|
873
|
+
coordinate: dict[str, Any] = {}
|
|
874
|
+
if refusal.workspace_id is not None and refusal.crawler_session_id is not None:
|
|
875
|
+
coordinate["active_public_acquisition"] = {
|
|
876
|
+
"workspace_id": refusal.workspace_id,
|
|
877
|
+
"crawler_session_id": refusal.crawler_session_id,
|
|
878
|
+
}
|
|
879
|
+
return _refuse(
|
|
880
|
+
refusal.code,
|
|
881
|
+
refusal.detail,
|
|
882
|
+
as_json=as_json or parsed.json,
|
|
883
|
+
retry_after_seconds=refusal.retry_after_seconds,
|
|
884
|
+
extra=coordinate,
|
|
885
|
+
)
|
|
886
|
+
except ThinLaneError as refusal:
|
|
887
|
+
return _refuse(
|
|
888
|
+
refusal.code,
|
|
889
|
+
refusal.detail,
|
|
890
|
+
as_json=as_json or parsed.json,
|
|
891
|
+
retry_after_seconds=getattr(refusal, "retry_after_seconds", None),
|
|
892
|
+
)
|
|
893
|
+
except Exception as error:
|
|
894
|
+
code = getattr(error, "code", None)
|
|
895
|
+
detail = getattr(error, "detail", None) or str(error)
|
|
896
|
+
return _refuse(code or "THIN_COMMAND_FAILED", detail, as_json=as_json or parsed.json)
|
|
897
|
+
_emit(payload, as_json=as_json or parsed.json)
|
|
898
|
+
exit_code = _exit_codes().get(command)
|
|
899
|
+
return 0 if exit_code is None else int(exit_code(payload))
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
__all__ = [
|
|
903
|
+
"CLASSIFICATION",
|
|
904
|
+
"HOSTED",
|
|
905
|
+
"HOSTED_FLAG",
|
|
906
|
+
"HOSTED_PENDING",
|
|
907
|
+
"LANES",
|
|
908
|
+
"LOCAL",
|
|
909
|
+
"LOCAL_FLAG",
|
|
910
|
+
"LOCAL_INSTALL_HINT",
|
|
911
|
+
"LOCAL_LANE_DISTRIBUTIONS",
|
|
912
|
+
"LOCAL_LANE_PLATFORM_REASON",
|
|
913
|
+
"LOCAL_LANE_UNSUPPORTED_PLATFORM_CODE",
|
|
914
|
+
"PORTABLE",
|
|
915
|
+
"REFUSAL_CODE",
|
|
916
|
+
"VOCABULARY",
|
|
917
|
+
"WORKER",
|
|
918
|
+
"local_lane_available",
|
|
919
|
+
"local_lane_platform_supported",
|
|
920
|
+
"main",
|
|
921
|
+
"names_in",
|
|
922
|
+
"reason_for",
|
|
923
|
+
"selected_command",
|
|
924
|
+
]
|