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.
Potentially problematic release.
This version of mostlyright-data might be problematic. Click here for more details.
- 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,212 @@
|
|
|
1
|
+
# nbrender component contract
|
|
2
|
+
|
|
3
|
+
`nbrender` turns nbformat 4.x JSON into one self-contained HTML string using the stdlib alone.
|
|
4
|
+
This file records how its modules fit together: which module owns which surface, what shared API
|
|
5
|
+
each may rely on, and the conventions all of them follow. Every hex, size, and geometry of the
|
|
6
|
+
design system lives in `tokens.py`; no other module writes a literal color or pixel value.
|
|
7
|
+
|
|
8
|
+
Component ids (`A01` through `MR01`) name the individual rendered surfaces — header, gutter prompt,
|
|
9
|
+
DataFrame, traceback, and so on. They appear throughout the code comments and are defined by the
|
|
10
|
+
ownership map below.
|
|
11
|
+
|
|
12
|
+
## Hard rules
|
|
13
|
+
|
|
14
|
+
- **stdlib-only**: `json`, `html`, `html.parser`, `tokenize`, `re`, `textwrap`. No pip deps — no
|
|
15
|
+
nbconvert, pygments, markdown, or webfont fetch.
|
|
16
|
+
- **Escape everything from notebook JSON** through `parse.esc` (or a helper that calls it) at the
|
|
17
|
+
point of interpolation. Untrusted HTML output (D21) is sandboxed by its owner, not trusted.
|
|
18
|
+
- **No emoji / decorative unicode.** The glyphs `▸ ⋯ ↯ − ·` are the sanctioned iconography.
|
|
19
|
+
- **Terse technical copy** in any user-visible string: one fact per sentence, no fluff.
|
|
20
|
+
- **Sanctioned deviations** — the deliberate limits of this renderer; do not add others:
|
|
21
|
+
stdlib-only; `static` + `embed` modes (`connected` = CSS/states only, no kernel wiring);
|
|
22
|
+
C14 renders TeX **source** in the display well labeled `math · TeX source`; D19 omits the
|
|
23
|
+
"open in table view →" link in static mode; `mr-data view` keeps its SSE reload script.
|
|
24
|
+
|
|
25
|
+
## Ownership map
|
|
26
|
+
|
|
27
|
+
Shared layers:
|
|
28
|
+
|
|
29
|
+
| File | Owns |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `tokens.py` | Every design-system constant; `build_css(mode)` — the **only** stylesheet. |
|
|
32
|
+
| `parse.py` | Model dataclasses, `RenderContext`, and every shared parse/escape helper. |
|
|
33
|
+
| `document.py` | Shell + layout, `.ipynb` -> component routing, pending/unsupported wells. |
|
|
34
|
+
| `__init__.py` | Re-exports `render_notebook`. |
|
|
35
|
+
|
|
36
|
+
Component modules:
|
|
37
|
+
|
|
38
|
+
| File | Component IDs | Public functions |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `chrome.py` | A01–A05 | `render_header`, `render_toolbar`, `render_kernel_pill`, `render_outline`, `render_footer` |
|
|
41
|
+
| `frame.py` | B06–B11 | `render_prompt`, `cell_state_classes`, `render_hover_toolbar`, `render_collapse_bar`, `render_tags`, `render_exec_meta` |
|
|
42
|
+
| `code_body.py` | C12, C14, C15 | `render_code_input`, `render_math`, `render_raw` |
|
|
43
|
+
| `markdown_body.py` | C13, F29 | `render_markdown`, `render_entity_chip` |
|
|
44
|
+
| `outputs_text.py` | D16, D17, D18, D23, D24 | `render_stdout`, `render_stderr`, `render_result_text`, `render_traceback`, `render_progress` |
|
|
45
|
+
| `outputs_rich.py` | D19, D20, D21, D22 | `render_dataframe`, `render_figure`, `render_html`, `render_json` |
|
|
46
|
+
| `interactive.py` | E25, E26 | `render_widget`, `render_stale_banner` |
|
|
47
|
+
| `mr_components.py` | F27, F28 | `render_provenance`, `render_leakage` |
|
|
48
|
+
| `outputs_data.py` | MR01 | `render_column_profiles` |
|
|
49
|
+
|
|
50
|
+
Tests live in `tests/nbrender/`: `test_chrome.py`, `test_frame.py`, `test_code_body.py`,
|
|
51
|
+
`test_markdown.py`, `test_outputs_text.py`, `test_outputs_rich.py`, `test_interactive_mr.py`
|
|
52
|
+
(covers both `interactive.py` and `mr_components.py`), `test_acceptance.py` over whole rendered
|
|
53
|
+
documents, and the shared `test_scaffold.py` — do not weaken its assertions.
|
|
54
|
+
|
|
55
|
+
Every public function returns an **HTML string** (except `cell_state_classes`, which returns a
|
|
56
|
+
space-joined class fragment or `""`). `document._slot` wraps each call and substitutes a pending
|
|
57
|
+
well if a component raises `NotImplementedError`; no module raises it, so that path is a defensive
|
|
58
|
+
fallback, not an expected state.
|
|
59
|
+
|
|
60
|
+
## How `document` dispatches
|
|
61
|
+
|
|
62
|
+
Per cell, in the 62px grid:
|
|
63
|
+
|
|
64
|
+
- **Gutter** = `frame.render_prompt` (B06).
|
|
65
|
+
- **Code cell content** = `[frame.render_hover_toolbar` (B08)`] + code_body.render_code_input` (C12),
|
|
66
|
+
wrapped in `.nb-panel.nb-code` with `frame.cell_state_classes` (B07) applied; then each output via
|
|
67
|
+
the output router below; then `frame.render_exec_meta` (B11) for executed cells.
|
|
68
|
+
- **Markdown cell content** = `[frame.render_tags` (B10) when tags present`] + markdown_body.render_markdown`
|
|
69
|
+
(C13). C13 calls `parse.split_entity_refs` and `markdown_body.render_entity_chip` (F29) for refs,
|
|
70
|
+
and `code_body.render_math` (C14) for `$…$`/`$$…$$`.
|
|
71
|
+
- **Raw cell content** = `code_body.render_raw` (C15).
|
|
72
|
+
|
|
73
|
+
The `.nb-io` row owns the 10px (the value `IO_GAP_PX` records) input-to-output gap and resets the
|
|
74
|
+
top margin of every child of its `.nb-io-body`, so the row supplies that one gap and no output adds
|
|
75
|
+
a second — not even an output that renders as several sibling roots, as E26 + E25 does. The one
|
|
76
|
+
exception is the welded first output: a first plain stdout stream (`document._is_weldable_well`)
|
|
77
|
+
is appended outside any row and welds flush to the panel, with no gap at all. D17's own
|
|
78
|
+
`margin-top: 12px` was retired for that uniform 10px (SPEC D17, amended 2026-08-12); the stderr
|
|
79
|
+
well keeps its dashed `border-top` and 11px top padding.
|
|
80
|
+
|
|
81
|
+
Output router (`document._render_output`), by `Output.kind` then `Output.primary_mimetype()`:
|
|
82
|
+
|
|
83
|
+
| Condition | Component |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `stream-stdout`, not tqdm | D16 |
|
|
86
|
+
| `stream-stdout`, tqdm pattern | D24 |
|
|
87
|
+
| `stream-stderr` | D17 |
|
|
88
|
+
| `error` | D23 |
|
|
89
|
+
| `text/html` containing `class="dataframe"` | D19 |
|
|
90
|
+
| `text/html` otherwise | D21 |
|
|
91
|
+
| `image/svg+xml` or `image/png` | D20 |
|
|
92
|
+
| `application/json` | D22 |
|
|
93
|
+
| `text/plain`, tqdm pattern | D24 |
|
|
94
|
+
| `text/plain` otherwise | D18 |
|
|
95
|
+
| `application/vnd.jupyter.widget-view+json` | E26 (static/embed) then E25 |
|
|
96
|
+
| `application/vnd.mostlyright.column-profile.v1+json` | MR01 |
|
|
97
|
+
| any other mimetype | unsupported well (owned by `document`) |
|
|
98
|
+
|
|
99
|
+
Notebook level: F27 once at document top when `notebook.provenance` present; F28 after the cell an
|
|
100
|
+
entry's integer `cell` index names (else trailing).
|
|
101
|
+
|
|
102
|
+
## `RenderContext` and model reference (`parse.py`)
|
|
103
|
+
|
|
104
|
+
`RenderContext`: `mode: str` (static/connected/embed) · `cell_index: int` · `cell_count: int` ·
|
|
105
|
+
`prev_execution_count: int | None` (previous **code** cell's count, for B11 out-of-order).
|
|
106
|
+
|
|
107
|
+
`Notebook`: `nbformat: int` · `nbformat_minor: int` · `metadata: dict` · `cells: list[Cell]` ·
|
|
108
|
+
`.provenance -> dict | None` (F27) · `.leakage -> list[dict]` (F28).
|
|
109
|
+
|
|
110
|
+
`Cell`: `cell_id: str` (nbformat `id`, or a deterministic parse fallback) · `cell_type: str`
|
|
111
|
+
(markdown/code/raw/unknown) · `source: str` (raw, unescaped) ·
|
|
112
|
+
`metadata: dict` · `execution_count: int | None` · `outputs: list[Output]` ·
|
|
113
|
+
`.tags -> list[str]` (the four known B10 tags, in display order).
|
|
114
|
+
|
|
115
|
+
`Output`: `kind: str` (stream-stdout/stream-stderr/execute_result/display_data/error/unknown) ·
|
|
116
|
+
`text: str` (raw stream/plain text, ANSI intact) · `data: dict` (normalized mimetype bundle) ·
|
|
117
|
+
`metadata: dict` · `execution_count: int | None` · `ename/evalue: str | None` ·
|
|
118
|
+
`traceback: list[str]` · `.primary_mimetype() -> str | None` · `.text_of(mimetype) -> str`.
|
|
119
|
+
|
|
120
|
+
## MR01 portable column-profile output
|
|
121
|
+
|
|
122
|
+
The generator stores the inspector in a normal `display_data` MIME bundle. The structured
|
|
123
|
+
`application/vnd.mostlyright.column-profile.v1+json` member is the rich contract. The same bundle
|
|
124
|
+
must also contain `text/html` and `text/plain` fallbacks so an ordinary Jupyter viewer never shows
|
|
125
|
+
an empty or unsupported output. The structured member is data only; it contains no executable
|
|
126
|
+
markup.
|
|
127
|
+
|
|
128
|
+
Every Parquet column appears exactly once and records logical type, physical type, declared unit,
|
|
129
|
+
missing count/rate, distinct count/rate and exact-or-sampled status, a type-appropriate summary and
|
|
130
|
+
chart, source column, and transformations. Units come from a sealed Recipe's `output_semantics`.
|
|
131
|
+
The renderer never guesses a unit: older Builds say `Not declared`, while non-measure fields
|
|
132
|
+
with an explicit `unit: none` say `Not applicable`.
|
|
133
|
+
|
|
134
|
+
The v1 envelope may add an optional `mostlyright.column-semantics.v1` sub-document and additive
|
|
135
|
+
per-column semantic fields. Bump the outer MIME version only when an existing field changes
|
|
136
|
+
meaning or a required field is added or removed. An absent sub-document means “not declared,” not
|
|
137
|
+
an invalid payload. The four unit states are `declared`, `normalized`, `not_applicable`, and
|
|
138
|
+
`unknown`; the legacy `unit.label` must remain a truthful fallback for each state.
|
|
139
|
+
|
|
140
|
+
The generator may reduce optional rich fields to remain below the 1 MiB MIME bound. The complete
|
|
141
|
+
sealed text remains in the notebook Overview and Data dictionary markdown cells and in the HTML
|
|
142
|
+
and plain-text fallbacks. Every renderer escapes semantic strings and never treats source content
|
|
143
|
+
as approval.
|
|
144
|
+
|
|
145
|
+
The portable fallbacks carry bounded dataset and column semantic sections independently of any
|
|
146
|
+
rich-MIME reduction. Fixed limits cover text, limitations, categorical definitions, and columns;
|
|
147
|
+
an omitted tail always produces an explicit deterministic truncation notice. Recipe semantics may
|
|
148
|
+
say `approved_recipe` only after the generator has verified the sealed approval digest and current
|
|
149
|
+
Recipe execution/acquisition bindings, never from member names alone.
|
|
150
|
+
|
|
151
|
+
The interactive semantic banner renders summary, row grain, time/population/completeness coverage,
|
|
152
|
+
and limitations. If the bounded MIME reduction ladder omits semantic detail, `render_scope` remains
|
|
153
|
+
present and the banner explicitly names the omitted field groups and points to the portable
|
|
154
|
+
fallbacks; reduction never silently turns a semantic-bearing payload into a no-semantics payload.
|
|
155
|
+
Large verified string values may produce oversized summary or chart labels. The final rich-profile
|
|
156
|
+
reduction step truncates those derived presentation strings with an explicit `[truncated]` marker,
|
|
157
|
+
while retaining semantic authority and the analytical profile shape, before it may refuse the MIME.
|
|
158
|
+
Portable categorical definitions always include their `complete` or `partial` mapping status.
|
|
159
|
+
|
|
160
|
+
Semantic evidence references are bounded `source_id` values. The generator accepts them only
|
|
161
|
+
after the table-semantics contract has bound each identifier to a source in the same sealed
|
|
162
|
+
Recipe or local plan; the renderer presents them as references, never as approvals.
|
|
163
|
+
|
|
164
|
+
`outputs_data.py` escapes every value and emits all profiles into one in-place selector. The
|
|
165
|
+
selector is progressive enhancement owned by the local workbench; without JavaScript the first
|
|
166
|
+
profile and the portable fallbacks remain readable. Charts are passive inline SVG authored only by
|
|
167
|
+
the trusted renderer from escaped scalar values—never notebook-supplied SVG. Distribution-axis
|
|
168
|
+
labels use a deterministic two-row layout and geometry-bounded middle abbreviation. Each bar's
|
|
169
|
+
escaped `<title>` retains the complete label and count, and a visually hidden table exposes the
|
|
170
|
+
same bounded data to assistive technology.
|
|
171
|
+
|
|
172
|
+
## Helper inventory
|
|
173
|
+
|
|
174
|
+
`parse.py`:
|
|
175
|
+
- `esc(value) -> str` — HTML-escape any value, quotes included; coerces non-strings.
|
|
176
|
+
- `strip_ansi(value) -> StrippedAnsi(text, bold)` — strip SGR; `bold` flags any SGR-1 seen.
|
|
177
|
+
- `detect_tqdm(text) -> TqdmProgress(fraction, label, stat) | None`.
|
|
178
|
+
- `split_entity_refs(source) -> list[str | EntityRef(type, id)]`.
|
|
179
|
+
- `parse_notebook(nb: dict) -> Notebook` — tolerant; never raises on bad shapes.
|
|
180
|
+
- Constants: `MIMETYPE_PRECEDENCE`, `KNOWN_TAGS`.
|
|
181
|
+
|
|
182
|
+
`tokens.py`:
|
|
183
|
+
- Color constants (core + viewer-only), `CSS_TOKENS` (css-var name -> hex).
|
|
184
|
+
- `FONT_SANS`, `FONT_MONO`, `TYPE_SCALE` (role -> family/size/weight/line-height/notes).
|
|
185
|
+
- Geometry: `GUTTER_PX`, `CELL_GAP_PX`, `IO_GAP_PX`, `RADIUS`, `SHELL_*`, `OUTLINE_WIDTH_PX`.
|
|
186
|
+
- `KEYFRAMES`, `HOVER_TRANSITION`, `VALID_MODES`.
|
|
187
|
+
- `build_css(mode) -> str` — the whole stylesheet.
|
|
188
|
+
|
|
189
|
+
## Class-name convention
|
|
190
|
+
|
|
191
|
+
All classes are `nb-`-prefixed, kebab-case: block `nb-<component>` (e.g. `nb-df`), element
|
|
192
|
+
`nb-<component>-<part>` (e.g. `nb-df-caption`), modifier `nb-<component>--<variant>` (e.g.
|
|
193
|
+
`nb-kernel--busy`). Transient state uses `is-<state>` (e.g. `nb-panel.is-running`). Every class a
|
|
194
|
+
component emits must resolve against a rule in `build_css`.
|
|
195
|
+
|
|
196
|
+
## Changing shared behavior
|
|
197
|
+
|
|
198
|
+
`tokens.py` is the single stylesheet: add a rule or a token there rather than inlining a style in a
|
|
199
|
+
component, so one edit reaches every module and the palette cannot drift. The same holds for
|
|
200
|
+
`parse.py` (shared parse and escape helpers) and `document.py` (dispatch and layout) — a component
|
|
201
|
+
that needs new shared behavior gets it added to the shared file rather than copied into its own
|
|
202
|
+
module.
|
|
203
|
+
|
|
204
|
+
## Notes for a host stylesheet
|
|
205
|
+
|
|
206
|
+
A host may override `.nb-cell`'s gutter geometry, but it must then re-state `.nb-io` with
|
|
207
|
+
`margin-left` equal to its own gutter track plus its column gap — the row re-enters the gutter by
|
|
208
|
+
that exact distance, so leaving nbrender's base pull-back in place skews `Out [n]` away from
|
|
209
|
+
`In [n]`. Restack the host's notebook rules under `@container`, never `@media`: nbrender's
|
|
210
|
+
breakpoints resolve against `.nb-shell`'s inline size (`container-type: inline-size`), which is
|
|
211
|
+
narrower than the viewport by whatever the host's own layout spends on padding, so a viewport-keyed
|
|
212
|
+
override disagrees with nbrender across a band of widths.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Stdlib-only, static-first Jupyter notebook renderer.
|
|
2
|
+
|
|
3
|
+
nbformat 4.x JSON in, one self-contained HTML string out — no nbconvert, no pygments, no markdown
|
|
4
|
+
library, no webfont fetch. ``render_notebook`` is the single public entry point; the modules
|
|
5
|
+
behind it are documented in ``CONTRACT.md``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from mostlyright.data_harness.nbrender.document import render_notebook
|
|
11
|
+
|
|
12
|
+
__all__ = ["render_notebook"]
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""Group A · chrome (A01-A05): header, toolbar, kernel pill, outline rail, repro footer.
|
|
2
|
+
|
|
3
|
+
Dispatched from ``document.render_notebook``. Every string interpolated from notebook JSON —
|
|
4
|
+
filename, breadcrumb path, kernelspec, env hash — passes through :func:`parse.esc` at the point of
|
|
5
|
+
interpolation. Two header strings are fixed design copy rather than notebook data: the "saved"
|
|
6
|
+
line and the kernel pill labels (an exported notebook carries neither a save clock nor a kernel).
|
|
7
|
+
Header height is mode-invariant: in ``static`` the primary button reads "Download .ipynb" rather
|
|
8
|
+
than vanishing. Toolbar (A02) is emitted only in ``connected``; ``build_css`` already hides
|
|
9
|
+
``.nb-toolbar`` elsewhere.
|
|
10
|
+
|
|
11
|
+
Class-name convention (CONTRACT.md): every class emitted resolves against a rule in
|
|
12
|
+
``tokens.build_css`` — including the toolbar Run accent, the minimap type tints/heights (the two
|
|
13
|
+
tints ``#E5E9F6``/``#FBE4DA`` are ``--nb-minimap-*`` tokens, the three bar heights are
|
|
14
|
+
``.nb-minimap-h-*`` rules), and the outline "Cells" top gap (``.nb-outline-label--cells``). No
|
|
15
|
+
styling is inlined here; every design-system value lives in ``tokens``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
from mostlyright.data_harness.nbrender.parse import (
|
|
23
|
+
Notebook,
|
|
24
|
+
RenderContext,
|
|
25
|
+
esc,
|
|
26
|
+
heading_text,
|
|
27
|
+
slugify,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# --- A03 kernel pill --------------------------------------------------------------------------
|
|
31
|
+
# state key -> label; the key doubles as the ``nb-kernel--<state>`` class modifier. The labels are
|
|
32
|
+
# fixed design copy, not notebook data — nothing here reads the kernelspec, and the interpreter
|
|
33
|
+
# version and queue depth are literal text. ``render_header`` only ever asks for ``idle``
|
|
34
|
+
# (connected mode) or ``none``. The middot and em dash are literal punctuation, not iconography.
|
|
35
|
+
_KERNEL_LABELS: dict[str, str] = {
|
|
36
|
+
"idle": "python 3.12 · idle",
|
|
37
|
+
"busy": "python 3.12 · busy",
|
|
38
|
+
"queued": "python 3.12 · queued (3)",
|
|
39
|
+
"dead": "kernel dead — restart",
|
|
40
|
+
"none": "no kernel",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def render_kernel_pill(state: str, ctx: RenderContext) -> str:
|
|
45
|
+
"""A03 · kernel status pill — idle/busy/queued/dead/none dot and label. Composed by A01.
|
|
46
|
+
|
|
47
|
+
An unknown ``state`` degrades to ``none`` ("no kernel") rather than raising, so a caller that
|
|
48
|
+
passes a stale kernel status still yields a well-formed pill.
|
|
49
|
+
"""
|
|
50
|
+
key = state if state in _KERNEL_LABELS else "none"
|
|
51
|
+
label = _KERNEL_LABELS[key]
|
|
52
|
+
return (
|
|
53
|
+
f'<span class="nb-kernel nb-kernel--{key}">'
|
|
54
|
+
'<span class="nb-kernel-dot" aria-hidden="true"></span>'
|
|
55
|
+
f"{esc(label)}"
|
|
56
|
+
"</span>"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# --- A01 header -------------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _path_segments(filename: str) -> list[str]:
|
|
64
|
+
"""Split a notebook path into non-empty segments, tolerant of both slash styles."""
|
|
65
|
+
text = str(filename).replace("\\", "/")
|
|
66
|
+
return [seg for seg in text.split("/") if seg]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _basename(filename: str) -> str:
|
|
70
|
+
"""Last path segment — the filename shown in the header title line."""
|
|
71
|
+
segs = _path_segments(filename)
|
|
72
|
+
return segs[-1] if segs else str(filename)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _brand_mark(size: int = 26) -> str:
|
|
76
|
+
"""The multi-color facet mark: ``--m1`` paper plus the standard orange/yellow/cobalt facets.
|
|
77
|
+
|
|
78
|
+
Rendered as inline SVG so no external asset is fetched; facet fills reference palette tokens
|
|
79
|
+
(``--m1`` == paper) rather than re-declaring hexes. Decorative, so ``aria-hidden``.
|
|
80
|
+
"""
|
|
81
|
+
return (
|
|
82
|
+
f'<svg width="{size}" height="{size}" viewBox="0 0 26 26" '
|
|
83
|
+
'aria-hidden="true" focusable="false">'
|
|
84
|
+
'<rect x="0" y="0" width="11" height="11" rx="2" fill="var(--nb-paper)"></rect>'
|
|
85
|
+
'<rect x="15" y="0" width="11" height="11" rx="2" fill="var(--nb-orange)"></rect>'
|
|
86
|
+
'<rect x="0" y="15" width="11" height="11" rx="2" fill="var(--nb-yellow)"></rect>'
|
|
87
|
+
'<rect x="15" y="15" width="11" height="11" rx="2" fill="var(--nb-cobalt)"></rect>'
|
|
88
|
+
"</svg>"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _breadcrumb_html(filename: str) -> str:
|
|
93
|
+
"""Breadcrumb trail from the path: ``/`` separators, the deepest crumb highlighted."""
|
|
94
|
+
segs = _path_segments(filename) or [str(filename)]
|
|
95
|
+
last = len(segs) - 1
|
|
96
|
+
pieces: list[str] = []
|
|
97
|
+
for i, seg in enumerate(segs):
|
|
98
|
+
if i == last:
|
|
99
|
+
pieces.append(f'<span class="nb-header-crumb-last">{esc(seg)}</span>')
|
|
100
|
+
else:
|
|
101
|
+
pieces.append(f"<span>{esc(seg)}</span>")
|
|
102
|
+
sep = '<span class="nb-header-crumb-sep"> / </span>'
|
|
103
|
+
return '<div class="nb-header-crumbs">' + sep.join(pieces) + "</div>"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def render_header(nb: Notebook, ctx: RenderContext, filename: str) -> str:
|
|
107
|
+
"""A01 · notebook header — brand mark, breadcrumb + filename block, kernel pill, actions.
|
|
108
|
+
|
|
109
|
+
Height is mode-invariant. In ``connected`` the kernel pill shows ``idle`` and the primary
|
|
110
|
+
button runs the notebook; in every other mode the pill reads ``no kernel`` and the primary
|
|
111
|
+
button downloads the source, so the bar never collapses. The breadcrumb and filename come from
|
|
112
|
+
``filename``; the "saved" line below them is fixed copy.
|
|
113
|
+
"""
|
|
114
|
+
mark = _brand_mark(26)
|
|
115
|
+
crumbs = _breadcrumb_html(filename)
|
|
116
|
+
base = _basename(filename)
|
|
117
|
+
kernel_state = "idle" if ctx.mode == "connected" else "none"
|
|
118
|
+
pill = render_kernel_pill(kernel_state, ctx)
|
|
119
|
+
ghost = '<button class="nb-btn-ghost" type="button">Raw</button>'
|
|
120
|
+
primary_label = "Run all" if ctx.mode == "connected" else "Download .ipynb"
|
|
121
|
+
primary = f'<button class="nb-btn-primary" type="button">{esc(primary_label)}</button>'
|
|
122
|
+
return (
|
|
123
|
+
'<header class="nb-header">'
|
|
124
|
+
'<div class="nb-header-id">'
|
|
125
|
+
f"{mark}"
|
|
126
|
+
'<div class="nb-header-block">'
|
|
127
|
+
f"{crumbs}"
|
|
128
|
+
f'<div class="nb-header-file" title="{esc(base)}">{esc(base)}</div>'
|
|
129
|
+
# Fixed design copy, not a measured value: an exported notebook has no save clock.
|
|
130
|
+
'<div class="nb-header-saved">saved 4m ago</div>'
|
|
131
|
+
"</div>"
|
|
132
|
+
"</div>"
|
|
133
|
+
'<div class="nb-header-actions">'
|
|
134
|
+
f"{pill}{ghost}{primary}"
|
|
135
|
+
"</div>"
|
|
136
|
+
"</header>"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# --- A02 toolbar (connected only; CSS hides it in static/embed) -------------------------------
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def render_toolbar(nb: Notebook, ctx: RenderContext) -> str:
|
|
144
|
+
"""A02 · toolbar — run/stop/restart, add-cell, cell count, clear/download.
|
|
145
|
+
|
|
146
|
+
Full markup is always produced; visibility is mode-gated in ``build_css`` (hidden in static
|
|
147
|
+
and embed). ``document`` only calls this in ``connected``. The cell count is derived from the
|
|
148
|
+
notebook: total cells, then code cells carrying an execution count.
|
|
149
|
+
"""
|
|
150
|
+
total = len(nb.cells)
|
|
151
|
+
executed = sum(1 for c in nb.cells if c.cell_type == "code" and c.execution_count is not None)
|
|
152
|
+
count = f"{total} cells · {executed} executed"
|
|
153
|
+
|
|
154
|
+
# 9x10 solid play triangle · A02. Fill references the ink token; the rest accent (ink
|
|
155
|
+
# color + 6px gap) is carried by the registered `.nb-toolbar-btn--run` rule, no inline style.
|
|
156
|
+
triangle = (
|
|
157
|
+
'<svg width="9" height="10" viewBox="0 0 9 10" aria-hidden="true" focusable="false">'
|
|
158
|
+
'<path d="M0 0 L9 5 L0 10 Z" fill="var(--nb-ink)"></path></svg>'
|
|
159
|
+
)
|
|
160
|
+
run = f'<button class="nb-toolbar-btn nb-toolbar-btn--run" type="button">{triangle}Run</button>'
|
|
161
|
+
|
|
162
|
+
def btn(label: str) -> str:
|
|
163
|
+
return f'<button class="nb-toolbar-btn" type="button">{esc(label)}</button>'
|
|
164
|
+
|
|
165
|
+
divider = '<span class="nb-toolbar-divider" aria-hidden="true"></span>'
|
|
166
|
+
return (
|
|
167
|
+
'<div class="nb-toolbar">'
|
|
168
|
+
f"{run}{btn('Stop')}{btn('Restart')}"
|
|
169
|
+
f"{divider}"
|
|
170
|
+
f"{btn('+ Code')}{btn('+ Markdown')}"
|
|
171
|
+
'<span class="nb-toolbar-spacer"></span>'
|
|
172
|
+
f'<span class="nb-toolbar-count">{esc(count)}</span>'
|
|
173
|
+
f"{divider}"
|
|
174
|
+
f"{btn('Clear outputs')}{btn('Download .ipynb')}"
|
|
175
|
+
"</div>"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# --- A04 outline rail -------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$")
|
|
182
|
+
_FENCE_RE = re.compile(r"^\s*(```|~~~)")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _headings(nb: Notebook) -> list[tuple[int, str]]:
|
|
186
|
+
"""ATX headings from markdown cells only, skipping fenced code. Returns (level, text)."""
|
|
187
|
+
out: list[tuple[int, str]] = []
|
|
188
|
+
for cell in nb.cells:
|
|
189
|
+
if cell.cell_type != "markdown":
|
|
190
|
+
continue
|
|
191
|
+
in_fence = False
|
|
192
|
+
for line in cell.source.splitlines():
|
|
193
|
+
if _FENCE_RE.match(line):
|
|
194
|
+
in_fence = not in_fence
|
|
195
|
+
continue
|
|
196
|
+
if in_fence:
|
|
197
|
+
continue
|
|
198
|
+
m = _HEADING_RE.match(line)
|
|
199
|
+
if m:
|
|
200
|
+
text = heading_text(m.group(2))
|
|
201
|
+
if text:
|
|
202
|
+
out.append((len(m.group(1)), text))
|
|
203
|
+
return out
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _minimap_type(cell) -> str:
|
|
207
|
+
"""Minimap stripe/bar class for a cell: md/code/err/raw (unknown recedes to raw)."""
|
|
208
|
+
if cell.cell_type == "markdown":
|
|
209
|
+
return "md"
|
|
210
|
+
if cell.cell_type == "raw":
|
|
211
|
+
return "raw"
|
|
212
|
+
if cell.cell_type == "code":
|
|
213
|
+
if any(o.kind == "error" for o in cell.outputs):
|
|
214
|
+
return "err"
|
|
215
|
+
return "code"
|
|
216
|
+
return "raw"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _length_bucket(source: str) -> str:
|
|
220
|
+
"""Bucket a cell's length into the three minimap bar heights by trimmed source size."""
|
|
221
|
+
n = len((source or "").strip())
|
|
222
|
+
if n <= 120:
|
|
223
|
+
return "short"
|
|
224
|
+
if n <= 480:
|
|
225
|
+
return "medium"
|
|
226
|
+
return "long"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _minimap(nb: Notebook) -> str:
|
|
230
|
+
"""One decorative row per cell: a type stripe plus a length-encoded bar.
|
|
231
|
+
|
|
232
|
+
Stripe/bar tints (by cell type) and the three bar heights (by length bucket) are class-driven —
|
|
233
|
+
the two non-palette tints live as ``--nb-minimap-*`` tokens and the heights as
|
|
234
|
+
``.nb-minimap-h-*`` rules — so no styling is inlined here.
|
|
235
|
+
"""
|
|
236
|
+
rows: list[str] = []
|
|
237
|
+
for cell in nb.cells:
|
|
238
|
+
kind = _minimap_type(cell)
|
|
239
|
+
height = f"nb-minimap-h-{_length_bucket(cell.source)}"
|
|
240
|
+
rows.append(
|
|
241
|
+
'<div class="nb-minimap-row" aria-hidden="true">'
|
|
242
|
+
f'<span class="nb-minimap-stripe nb-minimap-stripe--{kind} {height}"></span>'
|
|
243
|
+
f'<span class="nb-minimap-bar nb-minimap-bar--{kind} {height}"></span>'
|
|
244
|
+
"</div>"
|
|
245
|
+
)
|
|
246
|
+
return "".join(rows)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def render_outline(nb: Notebook, ctx: RenderContext) -> str:
|
|
250
|
+
"""A04 · outline rail — heading tree plus per-cell minimap. Collapses below 1100px (CSS).
|
|
251
|
+
|
|
252
|
+
The heading tree is built from markdown cells; depth maps h1->0 through h4+->3 for the indent
|
|
253
|
+
ramp. The first heading is marked active as the document-top default. Anchor targets
|
|
254
|
+
(``#slug``) match the heading ids ``markdown_body`` emits, because both sides derive them from
|
|
255
|
+
:func:`parse.slugify` over the same heading text.
|
|
256
|
+
"""
|
|
257
|
+
headings = _headings(nb)
|
|
258
|
+
links: list[str] = []
|
|
259
|
+
for i, (level, text) in enumerate(headings):
|
|
260
|
+
depth = min(level - 1, 3)
|
|
261
|
+
cls = "nb-outline-link"
|
|
262
|
+
if i == 0:
|
|
263
|
+
cls += " nb-outline-link--active"
|
|
264
|
+
links.append(
|
|
265
|
+
f'<a class="{cls}" data-depth="{depth}" href="#{esc(slugify(text))}">{esc(text)}</a>'
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
tree = ""
|
|
269
|
+
if links:
|
|
270
|
+
tree = '<div class="nb-outline-label">Outline</div>' + "".join(links)
|
|
271
|
+
|
|
272
|
+
# A small top gap before the second label only when a heading tree precedes it — carried by the
|
|
273
|
+
# registered `.nb-outline-label--cells` rule rather than an inline style.
|
|
274
|
+
cells_class = "nb-outline-label nb-outline-label--cells" if links else "nb-outline-label"
|
|
275
|
+
return (
|
|
276
|
+
'<nav class="nb-outline" aria-label="Notebook outline">'
|
|
277
|
+
f"{tree}"
|
|
278
|
+
f'<div class="{cells_class}">Cells</div>'
|
|
279
|
+
f"{_minimap(nb)}"
|
|
280
|
+
"</nav>"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# --- A05 repro footer -------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _mostlyright_block(nb: Notebook) -> dict:
|
|
288
|
+
block = nb.metadata.get("mostlyright")
|
|
289
|
+
return block if isinstance(block, dict) else {}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _kernel_name(nb: Notebook) -> str | None:
|
|
293
|
+
ks = nb.metadata.get("kernelspec")
|
|
294
|
+
if not isinstance(ks, dict):
|
|
295
|
+
return None
|
|
296
|
+
name = ks.get("display_name") or ks.get("name")
|
|
297
|
+
return str(name) if name else None
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _footer_facts(nb: Notebook) -> list[str]:
|
|
301
|
+
"""Repro facts in display order; absent fields are omitted, never placeholdered.
|
|
302
|
+
|
|
303
|
+
``nbformat`` is always derivable, so the footer never renders empty. ``kernelspec`` comes from
|
|
304
|
+
notebook metadata; the env hash, ``mostlyright`` version, and wall time come from
|
|
305
|
+
``metadata.mostlyright`` when the run recorded them.
|
|
306
|
+
"""
|
|
307
|
+
facts: list[str] = []
|
|
308
|
+
mr = _mostlyright_block(nb)
|
|
309
|
+
env = mr.get("env") or mr.get("env_hash")
|
|
310
|
+
if env:
|
|
311
|
+
facts.append(f"env {env}")
|
|
312
|
+
version = mr.get("version")
|
|
313
|
+
if version:
|
|
314
|
+
facts.append(f"mostlyright {version}")
|
|
315
|
+
wall = mr.get("wall_time") or mr.get("walltime")
|
|
316
|
+
if wall:
|
|
317
|
+
facts.append(f"wall {wall}")
|
|
318
|
+
kernel = _kernel_name(nb)
|
|
319
|
+
if kernel:
|
|
320
|
+
facts.append(kernel)
|
|
321
|
+
facts.append(f"nbformat {nb.nbformat}.{nb.nbformat_minor}")
|
|
322
|
+
return facts
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _replay_oneliner(nb: Notebook) -> str:
|
|
326
|
+
"""The ``mr.replay(...)`` one-liner the reproduce link carries: env hash, else revision.
|
|
327
|
+
|
|
328
|
+
A provenance ``revision`` is display copy carrying a parenthetical tail (the harness emits
|
|
329
|
+
``"<digest12> (<manifest_version>)"``), and the tail is not part of the identifier. Only the
|
|
330
|
+
leading token is used, so the link reads ``mr.replay("1ef73f4ff396")``.
|
|
331
|
+
"""
|
|
332
|
+
mr = _mostlyright_block(nb)
|
|
333
|
+
ident = mr.get("env") or mr.get("env_hash")
|
|
334
|
+
if not ident:
|
|
335
|
+
prov = nb.provenance or {}
|
|
336
|
+
tokens = str(prov.get("revision") or "").split()
|
|
337
|
+
ident = tokens[0] if tokens else ""
|
|
338
|
+
if ident:
|
|
339
|
+
return f'mr.replay("{ident}")'
|
|
340
|
+
return "mr.replay()"
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def render_footer(nb: Notebook, ctx: RenderContext) -> str:
|
|
344
|
+
"""A05 · repro footer — env hash, version, wall time, kernel spec, nbformat, replay link.
|
|
345
|
+
|
|
346
|
+
The reproduce link stashes the one-liner in ``data-replay``/``title`` so a connected-mode
|
|
347
|
+
script can copy it; static has no script, so the link is inert by design.
|
|
348
|
+
"""
|
|
349
|
+
facts = _footer_facts(nb)
|
|
350
|
+
mid = '<span class="nb-footer-mid" aria-hidden="true">·</span>'
|
|
351
|
+
fact_html = mid.join(f"<span>{esc(text)}</span>" for text in facts)
|
|
352
|
+
replay = _replay_oneliner(nb)
|
|
353
|
+
link = (
|
|
354
|
+
'<span class="nb-footer-link" role="button" tabindex="0" '
|
|
355
|
+
f'data-replay="{esc(replay)}" title="{esc(replay)}">reproduce this run →</span>'
|
|
356
|
+
)
|
|
357
|
+
return (
|
|
358
|
+
f'<footer class="nb-footer"><div class="nb-footer-facts">{fact_html}</div>{link}</footer>'
|
|
359
|
+
)
|