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,63 @@
|
|
|
1
|
+
"""``mr-data login`` -- sign in to the Mostly Right cloud with a device code.
|
|
2
|
+
|
|
3
|
+
This module is the command and nothing else: its arguments and the one call that turns them into
|
|
4
|
+
the payload both renderings are built from. The device flow itself lives in
|
|
5
|
+
:mod:`mostlyright.data_harness.ux.login`, so the same flow is available to a caller that is not
|
|
6
|
+
going through argparse.
|
|
7
|
+
|
|
8
|
+
The only thing this command prints outside the one payload ``--json`` renders or ``_emit`` turns
|
|
9
|
+
into plain lines is the device code and approval address, on stderr, and only in plain mode --
|
|
10
|
+
matching how ``build``, ``run`` and ``recipe-run`` already print a stderr warning beside a result
|
|
11
|
+
that still goes through ``_emit``. ``--json`` gets none of it, so a caller asking for one
|
|
12
|
+
machine-readable object gets exactly one.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
21
|
+
from mostlyright.data_harness.ux.login import CLOUD_URL_ENV, announce_device_code, run_login
|
|
22
|
+
|
|
23
|
+
DESCRIPTION = (
|
|
24
|
+
"Sign in to the Mostly Right cloud from this machine. Prints a short code and opens a browser "
|
|
25
|
+
"to approve it there, then stores a durable device credential at ~/.mostlyright/credentials, "
|
|
26
|
+
f"mode 0600. The raw credential is never printed; only its prefix and last four characters "
|
|
27
|
+
f"are. Set {CLOUD_URL_ENV} to sign in against a different cloud."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
32
|
+
parser.description = DESCRIPTION
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--device-name",
|
|
35
|
+
help="what to call this device in the cloud; defaults to this machine's hostname",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--force",
|
|
39
|
+
action="store_true",
|
|
40
|
+
help="compatibility alias for safe auth rotate; never truncates credentials in place",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--credential-store",
|
|
44
|
+
choices=("secure-file",),
|
|
45
|
+
help="explicitly use the secure-file credential store",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
50
|
+
return run_login(
|
|
51
|
+
device_name=args.device_name,
|
|
52
|
+
force=args.force,
|
|
53
|
+
credential_store=args.credential_store,
|
|
54
|
+
announce=None if args.json else announce_device_code,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
SPEC = CommandSpec(
|
|
59
|
+
name="login",
|
|
60
|
+
help="sign in to the Mostly Right cloud and store a device credential",
|
|
61
|
+
add_arguments=add_arguments,
|
|
62
|
+
run=run,
|
|
63
|
+
)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""``mr-data peek`` -- open any file or public https address and show what is in it.
|
|
2
|
+
|
|
3
|
+
This module is the command: its arguments, and the one call that turns them into the payload both
|
|
4
|
+
renderings are built from. Everything the command actually does lives in
|
|
5
|
+
:mod:`mostlyright.data_harness.ux.peek`, so browser rendering can use the same facts
|
|
6
|
+
without going through argparse.
|
|
7
|
+
|
|
8
|
+
Nothing here prints, and nothing here catches: a refusal is a typed error, and the command line
|
|
9
|
+
renders it with the sentences the remediation map supplies.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import re
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from mostlyright.data_harness.canonical import parse_json
|
|
20
|
+
from mostlyright.data_harness.readers.contracts import ReaderError, identifier, semver
|
|
21
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
22
|
+
from mostlyright.data_harness.ux.peek import (
|
|
23
|
+
DEFAULT_SAMPLE_ROWS,
|
|
24
|
+
MAX_SAMPLE_ROWS,
|
|
25
|
+
PEEK_FORMATS,
|
|
26
|
+
peek_path,
|
|
27
|
+
peek_url,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# An address is a scheme followed by `://`. Requiring the slashes is what keeps a Windows path
|
|
31
|
+
# such as `C:\data\rows.csv` a path rather than a scheme called `c`.
|
|
32
|
+
_ADDRESS = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _rows(value: str) -> int:
|
|
36
|
+
"""Bound the row count at the command line, before anything is opened."""
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
rows = int(value)
|
|
40
|
+
except ValueError:
|
|
41
|
+
raise argparse.ArgumentTypeError(f"{value!r} is not a whole number of rows") from None
|
|
42
|
+
if not 0 <= rows <= MAX_SAMPLE_ROWS:
|
|
43
|
+
raise argparse.ArgumentTypeError(
|
|
44
|
+
f"the number of rows to show must be from 0 to {MAX_SAMPLE_ROWS}"
|
|
45
|
+
)
|
|
46
|
+
return rows
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _json_column(value: str) -> tuple[str, str]:
|
|
50
|
+
name, separator, pointer = value.partition("=")
|
|
51
|
+
if not separator or not name:
|
|
52
|
+
raise argparse.ArgumentTypeError("must be NAME=/json/pointer (the pointer may be empty)")
|
|
53
|
+
return name, pointer
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _reader_coordinate(value: str) -> tuple[str, str]:
|
|
57
|
+
family, separator, version = value.rpartition("@")
|
|
58
|
+
if not separator:
|
|
59
|
+
raise argparse.ArgumentTypeError("must be an exact FAMILY@VERSION Reader coordinate")
|
|
60
|
+
try:
|
|
61
|
+
return identifier(family, "peek.reader.family"), semver(version, "peek.reader.version")
|
|
62
|
+
except ReaderError as error:
|
|
63
|
+
raise argparse.ArgumentTypeError(error.detail) from None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _reader_options(value: str) -> Any:
|
|
67
|
+
"""Parse strict JSON; the selected certified family owns the closed option shape."""
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
return parse_json(value)
|
|
71
|
+
except ValueError as error:
|
|
72
|
+
raise argparse.ArgumentTypeError(str(error)) from None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"target",
|
|
78
|
+
help="a file on this machine, or an https address",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"--format",
|
|
82
|
+
dest="data_format",
|
|
83
|
+
choices=PEEK_FORMATS,
|
|
84
|
+
default=None,
|
|
85
|
+
help="say what the file is when its name does not; the bytes are still checked against it",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--rows",
|
|
89
|
+
type=_rows,
|
|
90
|
+
default=DEFAULT_SAMPLE_ROWS,
|
|
91
|
+
help=f"how many rows to show, from 0 to {MAX_SAMPLE_ROWS} (default {DEFAULT_SAMPLE_ROWS})",
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"--allow-redirect-host",
|
|
95
|
+
dest="allow_redirect_hosts",
|
|
96
|
+
action="append",
|
|
97
|
+
default=None,
|
|
98
|
+
metavar="HOST",
|
|
99
|
+
help="also allow this host when an address redirects; repeat for more than one",
|
|
100
|
+
)
|
|
101
|
+
parser.add_argument(
|
|
102
|
+
"--reader",
|
|
103
|
+
type=_reader_coordinate,
|
|
104
|
+
default=None,
|
|
105
|
+
metavar="FAMILY@VERSION",
|
|
106
|
+
help="open the source with this exact certified Reader coordinate",
|
|
107
|
+
)
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"--reader-options",
|
|
110
|
+
type=_reader_options,
|
|
111
|
+
default=argparse.SUPPRESS,
|
|
112
|
+
metavar="JSON",
|
|
113
|
+
help=(
|
|
114
|
+
"strict JSON settings for --reader; the selected Reader rejects unknown or "
|
|
115
|
+
"invalid settings"
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"--json-records-pointer",
|
|
120
|
+
default=None,
|
|
121
|
+
metavar="POINTER",
|
|
122
|
+
help="project nested JSON records selected by this RFC 6901 pointer",
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--json-expand",
|
|
126
|
+
action="append",
|
|
127
|
+
default=None,
|
|
128
|
+
metavar="POINTER",
|
|
129
|
+
help="expand this array pointer in order; repeat for nested arrays",
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--json-column",
|
|
133
|
+
action="append",
|
|
134
|
+
type=_json_column,
|
|
135
|
+
default=None,
|
|
136
|
+
metavar="NAME=POINTER",
|
|
137
|
+
help="required projected column; repeat for more columns",
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"--json-optional-column",
|
|
141
|
+
action="append",
|
|
142
|
+
type=_json_column,
|
|
143
|
+
default=None,
|
|
144
|
+
metavar="NAME=POINTER",
|
|
145
|
+
help="optional projected column; repeat for more columns",
|
|
146
|
+
)
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"--json-document-format",
|
|
149
|
+
choices=("json", "ndjson"),
|
|
150
|
+
default=None,
|
|
151
|
+
help="nested document encoding (default: json)",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
156
|
+
target = str(args.target)
|
|
157
|
+
json_requested = any(
|
|
158
|
+
item is not None
|
|
159
|
+
for item in (
|
|
160
|
+
args.json_records_pointer,
|
|
161
|
+
args.json_expand,
|
|
162
|
+
args.json_column,
|
|
163
|
+
args.json_optional_column,
|
|
164
|
+
args.json_document_format,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
reader_coordinate = args.reader
|
|
168
|
+
reader_options_supplied = hasattr(args, "reader_options")
|
|
169
|
+
reader_options = getattr(args, "reader_options", None)
|
|
170
|
+
if reader_options_supplied and reader_coordinate is None:
|
|
171
|
+
raise ReaderError(
|
|
172
|
+
"READER_OPTIONS",
|
|
173
|
+
"peek.reader_options",
|
|
174
|
+
"--reader-options requires an exact --reader FAMILY@VERSION",
|
|
175
|
+
)
|
|
176
|
+
if reader_options_supplied and not isinstance(reader_options, dict):
|
|
177
|
+
raise ReaderError(
|
|
178
|
+
"READER_OPTIONS",
|
|
179
|
+
"peek.reader_options",
|
|
180
|
+
"--reader-options must be a JSON object",
|
|
181
|
+
)
|
|
182
|
+
if reader_coordinate is not None and args.data_format is not None:
|
|
183
|
+
raise ReaderError(
|
|
184
|
+
"READER_OPTIONS",
|
|
185
|
+
"peek.format",
|
|
186
|
+
"--format is for direct tabular preview and cannot be combined with --reader",
|
|
187
|
+
)
|
|
188
|
+
if reader_coordinate is not None and json_requested:
|
|
189
|
+
raise ReaderError(
|
|
190
|
+
"READER_OPTIONS",
|
|
191
|
+
"peek.reader",
|
|
192
|
+
"--reader cannot be combined with the JSON projection shorthand flags",
|
|
193
|
+
)
|
|
194
|
+
if reader_coordinate is not None and not reader_options_supplied:
|
|
195
|
+
reader_options = {}
|
|
196
|
+
if json_requested:
|
|
197
|
+
reader_coordinate = ("json.tabular", "1.0.0")
|
|
198
|
+
reader_options = {
|
|
199
|
+
"document_format": args.json_document_format or "json",
|
|
200
|
+
"records_pointer": args.json_records_pointer or "",
|
|
201
|
+
"expand": list(args.json_expand or ()),
|
|
202
|
+
"columns": [
|
|
203
|
+
{"name": name, "pointer": pointer, "required": required}
|
|
204
|
+
for required, items in (
|
|
205
|
+
(True, args.json_column or ()),
|
|
206
|
+
(False, args.json_optional_column or ()),
|
|
207
|
+
)
|
|
208
|
+
for name, pointer in items
|
|
209
|
+
],
|
|
210
|
+
}
|
|
211
|
+
if _ADDRESS.match(target):
|
|
212
|
+
result = peek_url(
|
|
213
|
+
target,
|
|
214
|
+
allow_redirect_hosts=tuple(args.allow_redirect_hosts or ()),
|
|
215
|
+
data_format=args.data_format,
|
|
216
|
+
sample_rows=args.rows,
|
|
217
|
+
reader_coordinate=reader_coordinate,
|
|
218
|
+
reader_options=reader_options,
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
result = peek_path(
|
|
222
|
+
Path(target),
|
|
223
|
+
data_format=args.data_format,
|
|
224
|
+
sample_rows=args.rows,
|
|
225
|
+
reader_coordinate=reader_coordinate,
|
|
226
|
+
reader_options=reader_options,
|
|
227
|
+
)
|
|
228
|
+
return result.to_dict()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
SPEC = CommandSpec(
|
|
232
|
+
name="peek",
|
|
233
|
+
help="open any file or public https address and show its columns, types, and first rows",
|
|
234
|
+
add_arguments=add_arguments,
|
|
235
|
+
run=run,
|
|
236
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""``mr-data plan-check`` -- try a plan by building it, into a folder that is thrown away after.
|
|
2
|
+
|
|
3
|
+
This module is the command and nothing else: its arguments, and the one call that turns them into
|
|
4
|
+
the payload both renderings are built from. The build itself lives in
|
|
5
|
+
:mod:`mostlyright.data_harness.ux.plan_check`, so the same answer is available to a browser page
|
|
6
|
+
without going through argparse.
|
|
7
|
+
|
|
8
|
+
The engines offered here are the same closed set ``build`` offers, imported from
|
|
9
|
+
:mod:`mostlyright.data_harness.pipeline` rather than restated, so the two cannot drift into
|
|
10
|
+
disagreeing about what this machine can build with.
|
|
11
|
+
|
|
12
|
+
Nothing here prints, and nothing here catches: a plan that does not build is refused in exactly the
|
|
13
|
+
words the real build refuses it in, and the command line renders that refusal with the sentences
|
|
14
|
+
the error-remediation map supplies.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from mostlyright.data_harness.pipeline import (
|
|
24
|
+
_VERIFY_ALLOWLISTED_ENGINES,
|
|
25
|
+
REFERENCE_BACKEND_NAME,
|
|
26
|
+
)
|
|
27
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
28
|
+
from mostlyright.data_harness.ux.plan_check import check_plan
|
|
29
|
+
|
|
30
|
+
# Said in the command's own help, not only in a design document. Both halves matter: it really
|
|
31
|
+
# builds, so what it says is true; and it really deletes, so it can be run again and again.
|
|
32
|
+
DESCRIPTION = (
|
|
33
|
+
"Build this plan into a folder that is deleted afterwards, to find out whether it works "
|
|
34
|
+
"before you commit to it. This is the same build the build command runs, with nothing "
|
|
35
|
+
"skipped and no limit relaxed, so a plan that fails here would have failed there. The data "
|
|
36
|
+
"fingerprint it reports is the one your real build will produce; the build's own fingerprint "
|
|
37
|
+
"is not reported, because a dry run is not run by anybody."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
42
|
+
parser.description = DESCRIPTION
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"plan",
|
|
45
|
+
type=Path,
|
|
46
|
+
help="the plan to try",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--input-root",
|
|
50
|
+
type=Path,
|
|
51
|
+
required=True,
|
|
52
|
+
help="the folder your sources are read from",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--engine",
|
|
56
|
+
choices=sorted(_VERIFY_ALLOWLISTED_ENGINES),
|
|
57
|
+
default=REFERENCE_BACKEND_NAME,
|
|
58
|
+
help="which engine to build with; the same choices the build command takes",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--keep",
|
|
62
|
+
action="store_true",
|
|
63
|
+
help=(
|
|
64
|
+
"leave the folder it built into in place and name it, for looking at a failure; "
|
|
65
|
+
"run chmod -R u+w on it before removing it"
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
71
|
+
# Imported here, not at the top: this package is loaded while the command line is still being
|
|
72
|
+
# set up. Reading the plan through the command line's own reader is what keeps a malformed
|
|
73
|
+
# plan reading the same here as it does everywhere else.
|
|
74
|
+
from mostlyright.data_harness import cli
|
|
75
|
+
|
|
76
|
+
result = check_plan(
|
|
77
|
+
cli._read_json(args.plan),
|
|
78
|
+
input_root=args.input_root,
|
|
79
|
+
engine=args.engine,
|
|
80
|
+
keep=args.keep,
|
|
81
|
+
)
|
|
82
|
+
return result.to_dict()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
SPEC = CommandSpec(
|
|
86
|
+
name="plan-check",
|
|
87
|
+
help="build this plan into a throwaway folder to see whether it works, then throw it away",
|
|
88
|
+
add_arguments=add_arguments,
|
|
89
|
+
run=run,
|
|
90
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""``mr-data preflight`` -- what would stop a build here, before you start one.
|
|
2
|
+
|
|
3
|
+
This module is the command and nothing else: its arguments, the one call that turns them into the
|
|
4
|
+
payload both renderings are built from, and the exit code a script can act on. The checks live in
|
|
5
|
+
:mod:`mostlyright.data_harness.ux.preflight`, so the same answer is available to a browser page
|
|
6
|
+
without going through argparse.
|
|
7
|
+
|
|
8
|
+
Every argument is optional. With none of them the three platform checks still run, because those
|
|
9
|
+
are the ones that cost a whole build to discover the hard way.
|
|
10
|
+
|
|
11
|
+
``--workload`` is the argument that asks about the *job* rather than about the machine: whether
|
|
12
|
+
this plan's own sources fit the ceilings the engine holds whatever it is run on. It costs one
|
|
13
|
+
``lstat`` per source and opens none of them, and it is the answer that otherwise arrives as a
|
|
14
|
+
container's ``MemoryError`` after the upload and the approval.
|
|
15
|
+
|
|
16
|
+
The exit code is 1 when anything is blocking, so ``mr-data preflight && mr-data build ...`` does
|
|
17
|
+
what it looks like it does. Nothing is refused and nothing is raised: a machine that cannot build
|
|
18
|
+
is an answer this command was asked for, and it is printed in full either way.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from mostlyright.data_harness.pipeline import _VERIFY_ALLOWLISTED_ENGINES
|
|
29
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
30
|
+
from mostlyright.data_harness.ux.preflight import BLOCKED_KEY, run_preflight
|
|
31
|
+
|
|
32
|
+
DESCRIPTION = (
|
|
33
|
+
"Check what would stop a build before you start one. With no arguments it reports what this "
|
|
34
|
+
"machine itself can and cannot do; name a workbench folder, an input folder, a path to write "
|
|
35
|
+
"to, or an engine and each of those is checked too. Name a workload -- a workbench folder, a "
|
|
36
|
+
"plan, or a recipe -- and the job itself is measured against the ceilings the engine will "
|
|
37
|
+
"judge it by, so a job that cannot run is refused here rather than inside a container after "
|
|
38
|
+
"its sources have been uploaded. Nothing is built, nothing is written, no source is opened "
|
|
39
|
+
"and nothing is sent anywhere. The exit code is 1 when something would stop you."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
44
|
+
parser.description = DESCRIPTION
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--workspace",
|
|
47
|
+
type=Path,
|
|
48
|
+
help="a workbench folder to report the stage of",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--input-root",
|
|
52
|
+
type=Path,
|
|
53
|
+
help="the folder your sources would be read from",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--output",
|
|
57
|
+
type=Path,
|
|
58
|
+
help="the path a build would be written to",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--engine",
|
|
62
|
+
choices=sorted(_VERIFY_ALLOWLISTED_ENGINES),
|
|
63
|
+
help="which engine you mean to build with; the same choices the build command takes",
|
|
64
|
+
)
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"--workload",
|
|
67
|
+
type=Path,
|
|
68
|
+
help=(
|
|
69
|
+
"the job to measure: a workbench folder, a plan, or a recipe. A workbench folder "
|
|
70
|
+
"already holds its sources; a plan or a recipe names them relative to --input-root"
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
76
|
+
return run_preflight(
|
|
77
|
+
workspace=args.workspace,
|
|
78
|
+
input_root=args.input_root,
|
|
79
|
+
output=args.output,
|
|
80
|
+
engine=args.engine,
|
|
81
|
+
workload=args.workload,
|
|
82
|
+
).to_dict()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def exit_code(payload: Mapping[str, Any]) -> int:
|
|
86
|
+
"""1 when something would stop a build, so this reads as a gate in a script."""
|
|
87
|
+
|
|
88
|
+
return 1 if payload.get(BLOCKED_KEY) else 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
SPEC = CommandSpec(
|
|
92
|
+
name="preflight",
|
|
93
|
+
help="check what would stop a build before you start it",
|
|
94
|
+
add_arguments=add_arguments,
|
|
95
|
+
run=run,
|
|
96
|
+
exit_code=exit_code,
|
|
97
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""``mr-data record`` -- turn recorded live messages into sealed batches a build can read.
|
|
2
|
+
|
|
3
|
+
A live connection can never be replayed, so a build never holds one. Capture is a separate
|
|
4
|
+
job: it takes the messages a venue sent, groups them into bounded batches, seals each batch
|
|
5
|
+
under the digest of its own bytes, and writes down what arrived on which connection. A build
|
|
6
|
+
then reads those sealed batches and reaches nothing at all.
|
|
7
|
+
|
|
8
|
+
This command is the second half of that job. It does not connect to anything. It reads
|
|
9
|
+
messages somebody already recorded, applies the venue's own rules for grouping and naming them,
|
|
10
|
+
and writes the sealed result. Opening the connection belongs to a trusted role that does not
|
|
11
|
+
exist here yet, and this build reaches no network anywhere.
|
|
12
|
+
|
|
13
|
+
Nothing here needs a clock. Every time in the sealed result comes out of the recording, so the
|
|
14
|
+
same recording always seals to the same bytes and the same names, on any machine, on any day.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from mostlyright.data_harness.sources.stream_connector import (
|
|
24
|
+
STREAM_CONNECTOR_IDS,
|
|
25
|
+
stream_connector,
|
|
26
|
+
)
|
|
27
|
+
from mostlyright.data_harness.sources.stream_recorder import (
|
|
28
|
+
capture_root,
|
|
29
|
+
seal_recorded_capture,
|
|
30
|
+
)
|
|
31
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
32
|
+
|
|
33
|
+
DESCRIPTION = (
|
|
34
|
+
"Turn messages already recorded from a live source into sealed batches a build can read. "
|
|
35
|
+
"This command connects to nothing: you point it at a file of recorded messages and at a "
|
|
36
|
+
"folder to write into, and it groups, names and seals them under the chosen source's own "
|
|
37
|
+
"rules. Every dropped connection is written down as one, because a build that spans a hole "
|
|
38
|
+
"in what was captured has to be able to see the hole."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
CAPTURE_RECORD_VERSION = "mr-data-stream-capture.v1"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
45
|
+
parser.description = DESCRIPTION
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--connector",
|
|
48
|
+
choices=STREAM_CONNECTOR_IDS,
|
|
49
|
+
required=True,
|
|
50
|
+
help="which live source these messages came from",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--replay",
|
|
54
|
+
type=Path,
|
|
55
|
+
required=True,
|
|
56
|
+
help="the file of recorded messages to seal, one JSON object per line",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--output",
|
|
60
|
+
type=Path,
|
|
61
|
+
required=True,
|
|
62
|
+
help="the folder to write the sealed batches into",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
67
|
+
# Imported here, not at the top: this package is loaded while the command line is still
|
|
68
|
+
# being set up, and the boundary module is what sets it up.
|
|
69
|
+
from mostlyright.data_harness import cli
|
|
70
|
+
|
|
71
|
+
config = stream_connector(args.connector)
|
|
72
|
+
recording = cli._read_bytes(args.replay)
|
|
73
|
+
captures_root = args.output.absolute()
|
|
74
|
+
manifest = seal_recorded_capture(
|
|
75
|
+
config=config,
|
|
76
|
+
recording=recording,
|
|
77
|
+
captures_root=captures_root,
|
|
78
|
+
)
|
|
79
|
+
return {
|
|
80
|
+
"schema_version": CAPTURE_RECORD_VERSION,
|
|
81
|
+
"status": "stream_recorded",
|
|
82
|
+
"connector_id": manifest.connector_id,
|
|
83
|
+
"connector_config_digest": manifest.connector_config_digest,
|
|
84
|
+
"stream_id": manifest.stream_id,
|
|
85
|
+
"capture_digest": manifest.digest,
|
|
86
|
+
"capture_folder": str(capture_root(captures_root, manifest.stream_id)),
|
|
87
|
+
"data_format": manifest.data_format,
|
|
88
|
+
"batch_filename": manifest.batch_filename,
|
|
89
|
+
"connection_count": len(manifest.epochs),
|
|
90
|
+
"recorded_gap_count": manifest.gap_count,
|
|
91
|
+
"kept_message_count": manifest.admitted_event_count,
|
|
92
|
+
"skipped_message_count": manifest.ignored_message_count,
|
|
93
|
+
"batch_count": len(manifest.batches),
|
|
94
|
+
"sealed_batch_names": [batch.storage_name for batch in manifest.batches],
|
|
95
|
+
"bookmarks": [batch.next_bookmark for batch in manifest.batches],
|
|
96
|
+
"recorded_through": manifest.recorded_through,
|
|
97
|
+
"network_fetch_performed": False,
|
|
98
|
+
"live_connection_opened": False,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
SPEC = CommandSpec(
|
|
103
|
+
name="record",
|
|
104
|
+
help="seal already-recorded live messages into batches a build can read",
|
|
105
|
+
add_arguments=add_arguments,
|
|
106
|
+
run=run,
|
|
107
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""``mr-data review-setup`` -- create protected material for independent local review."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
13
|
+
parser.description = (
|
|
14
|
+
"Create protected operator material for one sealed Build. This setup does not approve "
|
|
15
|
+
"the Build and does not mint a release attestation."
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument("workspace", type=Path, help="the work folder containing the sealed Build")
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--operator-root",
|
|
20
|
+
type=Path,
|
|
21
|
+
required=True,
|
|
22
|
+
help="a new protected folder for enrollment, private keys, and role tokens",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--anchor-output",
|
|
26
|
+
type=Path,
|
|
27
|
+
required=True,
|
|
28
|
+
help="a new file to install separately at the coordinator's fixed trust-anchor path",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
33
|
+
from mostlyright.data_harness.operator_setup import provision_review_setup
|
|
34
|
+
|
|
35
|
+
return provision_review_setup(
|
|
36
|
+
args.workspace,
|
|
37
|
+
operator_root=args.operator_root,
|
|
38
|
+
anchor_path=args.anchor_output,
|
|
39
|
+
).receipt()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
SPEC = CommandSpec(
|
|
43
|
+
name="review-setup",
|
|
44
|
+
help="create protected review material; this does not approve a Build",
|
|
45
|
+
add_arguments=add_arguments,
|
|
46
|
+
run=run,
|
|
47
|
+
)
|