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,281 @@
|
|
|
1
|
+
"""The device credential this machine stores, exchanged for a short-lived Studio token.
|
|
2
|
+
|
|
3
|
+
The flow is the one ADR 0015 settled and ``mr-data login`` already writes half of: a durable
|
|
4
|
+
``mr_cli_`` device key lives at ``~/.mostlyright/credentials`` at mode 0600, and every
|
|
5
|
+
cloud-touching command trades it at Cloud's ``/api/cli/studio-token`` for a token that expires in
|
|
6
|
+
minutes and names the workspace it is good for. The device key never reaches Studio and the
|
|
7
|
+
Studio token is never written to disk.
|
|
8
|
+
|
|
9
|
+
⚠ THE 503 RETRY IS NOT OPTIONAL POLISH. Studio scales to zero, and Cloud provisions this
|
|
10
|
+
workspace's tenancy on every exchange, so the request that WAKES Studio is the request that has to
|
|
11
|
+
wait for it. Cloud distinguishes the two 503 shapes by stamping ``retry-after`` on the one worth
|
|
12
|
+
retrying and nothing on the one no amount of waiting can fix, so the ABSENCE of the header is the
|
|
13
|
+
signal to stop. This mirrors ``hosted_deploy.exchange_studio_token``, which learned it from eight
|
|
14
|
+
recorded production failures on 2026-08-20.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
import time
|
|
21
|
+
from collections.abc import Callable, Mapping
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from datetime import UTC, datetime
|
|
24
|
+
from typing import Any
|
|
25
|
+
from urllib.parse import quote, urlencode
|
|
26
|
+
from uuid import UUID
|
|
27
|
+
|
|
28
|
+
from mostlyright.data_harness.canonical import CanonicalJSONError, parse_json
|
|
29
|
+
from mostlyright.data_harness.thin.transport import (
|
|
30
|
+
MAX_JSON_RESPONSE_BYTES,
|
|
31
|
+
ThinLaneError,
|
|
32
|
+
Transport,
|
|
33
|
+
UrlLibTransport,
|
|
34
|
+
service_origin,
|
|
35
|
+
)
|
|
36
|
+
from mostlyright.data_harness.ux.credentials import ResolvedCloudCredentials
|
|
37
|
+
|
|
38
|
+
#: Cloud's token-exchange route. Held identical to ``hosted_deploy.CLOUD_TOKEN_PATH`` by a test.
|
|
39
|
+
CLOUD_TOKEN_PATH = "/api/cli/studio-token"
|
|
40
|
+
|
|
41
|
+
#: The Cloud page a person opens about one run. ADR 0021 makes the live run view the place a
|
|
42
|
+
#: hosted submission points at, and ``mostlyright-cloud`` serves it at this route
|
|
43
|
+
#: (``dashboard/app/dashboard/runs/[runId]/page.tsx``). The origin is never hardcoded beyond the
|
|
44
|
+
#: configured Cloud URL, which ``MOSTLYRIGHT_CLOUD_URL`` overrides and the stored credential
|
|
45
|
+
#: carries.
|
|
46
|
+
CLOUD_RUN_DASHBOARD_PATH = "/dashboard/runs/{run_id}"
|
|
47
|
+
|
|
48
|
+
#: The durable Cloud home of one Dataset. Research starts here before a session exists, then the
|
|
49
|
+
#: same page follows the exact session run through :func:`dataset_research_dashboard_url`.
|
|
50
|
+
CLOUD_DATASET_DASHBOARD_PATH = "/dashboard/datasets/{dataset_id}"
|
|
51
|
+
|
|
52
|
+
#: The Cloud page a person opens to settle one approval. ``mostlyright-cloud`` serves it at
|
|
53
|
+
#: ``dashboard/app/dashboard/approvals/[approvalRequestId]/page.tsx`` -- the sibling of the run
|
|
54
|
+
#: page above, and the address ``mr-data propose`` prints for the human who must grant the Table
|
|
55
|
+
#: recipe approval it opened. It is where a person goes; the actual step-up or editor confirmation
|
|
56
|
+
#: is what ``mr-data recipe-approve`` records once they have.
|
|
57
|
+
CLOUD_APPROVAL_DASHBOARD_PATH = "/dashboard/approvals/{approval_request_id}"
|
|
58
|
+
|
|
59
|
+
MAX_TOKEN_RESPONSE_BYTES = 32 * 1024
|
|
60
|
+
TOKEN_EXCHANGE_RETRY_ATTEMPTS = 2
|
|
61
|
+
TOKEN_EXCHANGE_MAX_RETRY_SECONDS = 30.0
|
|
62
|
+
|
|
63
|
+
_CLI_KEY = re.compile(r"^mr_cli_[A-Za-z0-9_-]{64}$")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class StudioSession:
|
|
68
|
+
"""One short-lived Studio authority: where to call, as whom, and until when."""
|
|
69
|
+
|
|
70
|
+
studio_base_url: str
|
|
71
|
+
workspace_id: UUID
|
|
72
|
+
token: str
|
|
73
|
+
expires_at: datetime
|
|
74
|
+
cloud_url: str
|
|
75
|
+
|
|
76
|
+
def __repr__(self) -> str:
|
|
77
|
+
return (
|
|
78
|
+
"StudioSession("
|
|
79
|
+
f"studio_base_url={self.studio_base_url!r}, workspace_id={self.workspace_id!r}, "
|
|
80
|
+
f"token='<redacted>', expires_at={self.expires_at!r}, cloud_url={self.cloud_url!r})"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def authorization(self) -> dict[str, str]:
|
|
84
|
+
"""The bearer header every Studio call in this lane carries, and nothing else."""
|
|
85
|
+
|
|
86
|
+
return {"Authorization": f"Bearer {self.token}"}
|
|
87
|
+
|
|
88
|
+
def to_receipt(self) -> dict[str, Any]:
|
|
89
|
+
"""Everything about this session except the token."""
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"studio_base_url": self.studio_base_url,
|
|
93
|
+
"workspace_id": str(self.workspace_id),
|
|
94
|
+
"expires_at": self.expires_at.isoformat().replace("+00:00", "Z"),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def run_dashboard_url(cloud_url: str, run_id: str) -> str:
|
|
99
|
+
"""The Cloud live-run page for one run, from one validated origin."""
|
|
100
|
+
|
|
101
|
+
origin = service_origin(cloud_url, "Cloud URL", allow_loopback_http=False)
|
|
102
|
+
return origin + CLOUD_RUN_DASHBOARD_PATH.format(run_id=run_id)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def dataset_dashboard_url(cloud_url: str, dataset_id: str) -> str:
|
|
106
|
+
"""The Cloud detail page for one Dataset, with its identifier held to one path segment."""
|
|
107
|
+
|
|
108
|
+
origin = service_origin(cloud_url, "Cloud URL", allow_loopback_http=False)
|
|
109
|
+
encoded_dataset_id = quote(str(dataset_id), safe="")
|
|
110
|
+
return origin + CLOUD_DATASET_DASHBOARD_PATH.format(dataset_id=encoded_dataset_id)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def dataset_research_dashboard_url(cloud_url: str, dataset_id: str, run_id: str) -> str:
|
|
114
|
+
"""The exact research run embedded in one Dataset detail page.
|
|
115
|
+
|
|
116
|
+
Path and query coordinates are encoded independently so neither can introduce a sibling path,
|
|
117
|
+
another query member, or a fragment. The fragment is the fixed UI section identifier.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
base = dataset_dashboard_url(cloud_url, dataset_id)
|
|
121
|
+
query = urlencode({"run": str(run_id)}, quote_via=quote, safe="")
|
|
122
|
+
return f"{base}?{query}#research"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def approval_dashboard_url(cloud_url: str, approval_request_id: str) -> str:
|
|
126
|
+
"""The Cloud approvals page for one approval request, from the same validated origin.
|
|
127
|
+
|
|
128
|
+
The sibling of :func:`run_dashboard_url`: ``mr-data propose`` opens a Table recipe approval
|
|
129
|
+
and hands a person this address to settle it, the way ``mr-data approve`` prints the human
|
|
130
|
+
action Studio names. Both go through :func:`service_origin`, so the origin is checked once and
|
|
131
|
+
the two surfaces cannot drift into two spellings of one Cloud URL.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
origin = service_origin(cloud_url, "Cloud URL", allow_loopback_http=False)
|
|
135
|
+
return origin + CLOUD_APPROVAL_DASHBOARD_PATH.format(approval_request_id=approval_request_id)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def require_device_key(credentials: ResolvedCloudCredentials) -> None:
|
|
139
|
+
"""Refuse a credential that is not an ``mr_cli_`` device key.
|
|
140
|
+
|
|
141
|
+
A live data key authenticates too, and it has read-only scope, so accepting one here would
|
|
142
|
+
trade a clear refusal now for an opaque 403 from Studio three calls later.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
if _CLI_KEY.fullmatch(credentials.raw_key) is None:
|
|
146
|
+
raise ThinLaneError(
|
|
147
|
+
"THIN_AUTHENTICATION_FAILED",
|
|
148
|
+
"the hosted lane requires an mr_cli_ device credential; run mr-data login",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _text(value: Mapping[str, Any], field: str) -> str:
|
|
153
|
+
selected = value.get(field)
|
|
154
|
+
if not isinstance(selected, str) or not selected:
|
|
155
|
+
raise ThinLaneError("THIN_RESPONSE_INVALID", f"the response omitted {field}")
|
|
156
|
+
return selected
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _uuid(value: str, field: str) -> UUID:
|
|
160
|
+
try:
|
|
161
|
+
return UUID(value)
|
|
162
|
+
except ValueError as error:
|
|
163
|
+
raise ThinLaneError("THIN_RESPONSE_INVALID", f"{field} is not a UUID") from error
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _timestamp(value: str, field: str) -> datetime:
|
|
167
|
+
try:
|
|
168
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
169
|
+
except ValueError as error:
|
|
170
|
+
raise ThinLaneError("THIN_RESPONSE_INVALID", f"{field} is not a timestamp") from error
|
|
171
|
+
if parsed.tzinfo is None or parsed.utcoffset() != UTC.utcoffset(parsed):
|
|
172
|
+
raise ThinLaneError("THIN_RESPONSE_INVALID", f"{field} is not UTC")
|
|
173
|
+
return parsed
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _transient_retry_seconds(headers: Mapping[str, str]) -> float | None:
|
|
177
|
+
"""Seconds Cloud asked this command to wait, or ``None`` when it did not ask at all.
|
|
178
|
+
|
|
179
|
+
⚠ The absence of ``retry-after`` is the signal, not a missing detail to paper over: Cloud's
|
|
180
|
+
cold-start 503 stamps it and its "Studio is not configured here" 503 does not. A malformed or
|
|
181
|
+
out-of-range value fails toward refusing now rather than sleeping on a number we do not trust.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
raw = headers.get("retry-after", "")
|
|
185
|
+
if not raw.isdecimal():
|
|
186
|
+
return None
|
|
187
|
+
seconds = int(raw)
|
|
188
|
+
if not 1 <= seconds <= 3600:
|
|
189
|
+
return None
|
|
190
|
+
return float(min(seconds, TOKEN_EXCHANGE_MAX_RETRY_SECONDS))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def open_studio_session(
|
|
194
|
+
credentials: ResolvedCloudCredentials,
|
|
195
|
+
*,
|
|
196
|
+
transport: Transport | None = None,
|
|
197
|
+
now: Callable[[], datetime] = lambda: datetime.now(UTC),
|
|
198
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
199
|
+
) -> StudioSession:
|
|
200
|
+
"""Exchange the stored device key for a short-lived Studio token."""
|
|
201
|
+
|
|
202
|
+
require_device_key(credentials)
|
|
203
|
+
cloud_url = service_origin(credentials.cloud_url, "Cloud URL", allow_loopback_http=False)
|
|
204
|
+
selected = transport or UrlLibTransport()
|
|
205
|
+
attempts_remaining = TOKEN_EXCHANGE_RETRY_ATTEMPTS
|
|
206
|
+
while True:
|
|
207
|
+
try:
|
|
208
|
+
status, raw, headers = selected.request(
|
|
209
|
+
"POST",
|
|
210
|
+
f"{cloud_url}{CLOUD_TOKEN_PATH}",
|
|
211
|
+
{"Accept": "application/json", "x-api-key": credentials.raw_key},
|
|
212
|
+
b"",
|
|
213
|
+
MAX_TOKEN_RESPONSE_BYTES,
|
|
214
|
+
)
|
|
215
|
+
except ThinLaneError:
|
|
216
|
+
raise
|
|
217
|
+
except Exception as error:
|
|
218
|
+
raise ThinLaneError(
|
|
219
|
+
"THIN_TOKEN_EXCHANGE_FAILED", "the Cloud token exchange could not be reached"
|
|
220
|
+
) from error
|
|
221
|
+
if status != 503 or attempts_remaining == 0:
|
|
222
|
+
break
|
|
223
|
+
delay = _transient_retry_seconds(headers)
|
|
224
|
+
if delay is None:
|
|
225
|
+
break
|
|
226
|
+
attempts_remaining -= 1
|
|
227
|
+
sleep(delay)
|
|
228
|
+
if status == 401:
|
|
229
|
+
raise ThinLaneError(
|
|
230
|
+
"THIN_AUTHENTICATION_FAILED", "the stored device credential was rejected"
|
|
231
|
+
)
|
|
232
|
+
if status == 402:
|
|
233
|
+
raise ThinLaneError(
|
|
234
|
+
"THIN_SUBSCRIPTION_REQUIRED", "this workspace has no hosted execution access"
|
|
235
|
+
)
|
|
236
|
+
if status == 429:
|
|
237
|
+
raise ThinLaneError("THIN_RATE_LIMITED", "Cloud asked this command to retry later")
|
|
238
|
+
if status == 503:
|
|
239
|
+
raise ThinLaneError(
|
|
240
|
+
"THIN_STUDIO_UNAVAILABLE",
|
|
241
|
+
"Studio is not configured, or stayed unavailable across every attempt",
|
|
242
|
+
)
|
|
243
|
+
try:
|
|
244
|
+
parsed = parse_json(raw)
|
|
245
|
+
except CanonicalJSONError:
|
|
246
|
+
parsed = None
|
|
247
|
+
if status != 200 or not isinstance(parsed, dict):
|
|
248
|
+
raise ThinLaneError(
|
|
249
|
+
"THIN_TOKEN_EXCHANGE_FAILED", f"the Cloud token exchange failed (HTTP {status})"
|
|
250
|
+
)
|
|
251
|
+
expires_at = _timestamp(_text(parsed, "expires_at"), "expires_at")
|
|
252
|
+
if expires_at <= now():
|
|
253
|
+
raise ThinLaneError("THIN_TOKEN_EXCHANGE_FAILED", "Cloud returned an expired Studio token")
|
|
254
|
+
return StudioSession(
|
|
255
|
+
studio_base_url=service_origin(
|
|
256
|
+
_text(parsed, "studio_base_url"), "Studio base URL", allow_loopback_http=True
|
|
257
|
+
),
|
|
258
|
+
workspace_id=_uuid(_text(parsed, "workspace_id"), "workspace_id"),
|
|
259
|
+
token=_text(parsed, "token"),
|
|
260
|
+
expires_at=expires_at,
|
|
261
|
+
cloud_url=cloud_url,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
__all__ = [
|
|
266
|
+
"CLOUD_APPROVAL_DASHBOARD_PATH",
|
|
267
|
+
"CLOUD_DATASET_DASHBOARD_PATH",
|
|
268
|
+
"CLOUD_RUN_DASHBOARD_PATH",
|
|
269
|
+
"CLOUD_TOKEN_PATH",
|
|
270
|
+
"MAX_JSON_RESPONSE_BYTES",
|
|
271
|
+
"MAX_TOKEN_RESPONSE_BYTES",
|
|
272
|
+
"TOKEN_EXCHANGE_MAX_RETRY_SECONDS",
|
|
273
|
+
"TOKEN_EXCHANGE_RETRY_ATTEMPTS",
|
|
274
|
+
"StudioSession",
|
|
275
|
+
"approval_dashboard_url",
|
|
276
|
+
"dataset_dashboard_url",
|
|
277
|
+
"dataset_research_dashboard_url",
|
|
278
|
+
"open_studio_session",
|
|
279
|
+
"require_device_key",
|
|
280
|
+
"run_dashboard_url",
|
|
281
|
+
]
|