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,266 @@
|
|
|
1
|
+
"""Group C · code bodies (C12, C14, C15): code input, LaTeX math, raw cell.
|
|
2
|
+
|
|
3
|
+
Dispatched from ``document`` (markdown C13 lives in ``markdown_body``). The six-color syntax theme
|
|
4
|
+
is driven by the stdlib ``tokenize`` module over the cell source — never a pip highlighter — and a
|
|
5
|
+
tokenizer error must fall back to the escaped source, never a raw dump. Math (C14) renders TeX
|
|
6
|
+
source, not glyphs: no formula renderer is bundled, so the display well shows escaped TeX under the
|
|
7
|
+
mono label ``math · TeX source``. Every rendered string originates in notebook JSON and must pass
|
|
8
|
+
through :func:`parse.esc`.
|
|
9
|
+
|
|
10
|
+
Reconstruction discipline — copying a code panel must yield exactly the cell source: the highlighter
|
|
11
|
+
rebuilds the panel text by slicing the original source between token spans, so the concatenated
|
|
12
|
+
text content of the ``<pre>`` is byte-identical to ``cell.source``. Only the five accent
|
|
13
|
+
categories are wrapped in a span; identifiers, operators, and whitespace stay bare and inherit the
|
|
14
|
+
base ``#CFC9BB`` from ``.nb-code pre``. Line numbers, when enabled, live in a separate
|
|
15
|
+
``aria-hidden`` element that the copy selection excludes.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import tokenize
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from mostlyright.data_harness.nbrender.parse import Cell, RenderContext, esc
|
|
24
|
+
|
|
25
|
+
# --- C12 six-color syntax theme ---------------------------------------------------------------
|
|
26
|
+
# Keywords / imports / control flow -> keyword color. True/False/None are excluded here so they
|
|
27
|
+
# land in the number color, which covers "number, boolean, None". The ``keyword`` module is not
|
|
28
|
+
# on the stdlib allow-list, so the hard-keyword set is pinned locally; Python's keywords are
|
|
29
|
+
# stable and soft keywords (match/case/type) are intentionally left as identifiers.
|
|
30
|
+
_KEYWORDS = frozenset(
|
|
31
|
+
{
|
|
32
|
+
"and",
|
|
33
|
+
"as",
|
|
34
|
+
"assert",
|
|
35
|
+
"async",
|
|
36
|
+
"await",
|
|
37
|
+
"break",
|
|
38
|
+
"class",
|
|
39
|
+
"continue",
|
|
40
|
+
"def",
|
|
41
|
+
"del",
|
|
42
|
+
"elif",
|
|
43
|
+
"else",
|
|
44
|
+
"except",
|
|
45
|
+
"finally",
|
|
46
|
+
"for",
|
|
47
|
+
"from",
|
|
48
|
+
"global",
|
|
49
|
+
"if",
|
|
50
|
+
"import",
|
|
51
|
+
"in",
|
|
52
|
+
"is",
|
|
53
|
+
"lambda",
|
|
54
|
+
"nonlocal",
|
|
55
|
+
"not",
|
|
56
|
+
"or",
|
|
57
|
+
"pass",
|
|
58
|
+
"raise",
|
|
59
|
+
"return",
|
|
60
|
+
"try",
|
|
61
|
+
"while",
|
|
62
|
+
"with",
|
|
63
|
+
"yield",
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
_CONSTANTS = frozenset({"True", "False", "None"})
|
|
67
|
+
|
|
68
|
+
# STRING plus the 3.12+ f-string token trio, resolved by name so older/newer runtimes both work.
|
|
69
|
+
_STRING_TYPES = {tokenize.STRING}
|
|
70
|
+
for _name in ("FSTRING_START", "FSTRING_MIDDLE", "FSTRING_END"):
|
|
71
|
+
_tok = getattr(tokenize, _name, None)
|
|
72
|
+
if _tok is not None:
|
|
73
|
+
_STRING_TYPES.add(_tok)
|
|
74
|
+
_STRING_TYPES = frozenset(_STRING_TYPES)
|
|
75
|
+
|
|
76
|
+
_ENCODING = getattr(tokenize, "ENCODING", None)
|
|
77
|
+
|
|
78
|
+
_TOK_COMMENT = "nb-tok-com"
|
|
79
|
+
_TOK_STRING = "nb-tok-str"
|
|
80
|
+
_TOK_NUMBER = "nb-tok-num"
|
|
81
|
+
_TOK_KEYWORD = "nb-tok-kw"
|
|
82
|
+
_TOK_FN = "nb-tok-fn"
|
|
83
|
+
|
|
84
|
+
_MATH_LABEL = "math · TeX source"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _row_slice(lines: list[str], start: tuple[int, int], end: tuple[int, int]) -> str:
|
|
88
|
+
"""Exact source text between two ``(row, col)`` tokenize positions (rows 1-based).
|
|
89
|
+
|
|
90
|
+
Out-of-range rows (the EOF ``NEWLINE``/``ENDMARKER`` sit one row past the last line) clamp to
|
|
91
|
+
an empty string so reconstruction never indexes past the source.
|
|
92
|
+
"""
|
|
93
|
+
(srow, scol), (erow, ecol) = start, end
|
|
94
|
+
count = len(lines)
|
|
95
|
+
|
|
96
|
+
def row(index: int) -> str:
|
|
97
|
+
return lines[index - 1] if 1 <= index <= count else ""
|
|
98
|
+
|
|
99
|
+
if srow == erow:
|
|
100
|
+
return row(srow)[scol:ecol]
|
|
101
|
+
parts = [row(srow)[scol:]]
|
|
102
|
+
parts.extend(row(index) for index in range(srow + 1, erow))
|
|
103
|
+
parts.append(row(erow)[:ecol])
|
|
104
|
+
return "".join(parts)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _is_call(toks: list[Any], index: int) -> bool:
|
|
108
|
+
"""True when the token after ``index`` is an opening paren — a function call name.
|
|
109
|
+
|
|
110
|
+
Whitespace is not a token, so the immediate successor is the syntactically adjacent one;
|
|
111
|
+
``foo(`` and ``foo (`` both qualify while ``foo.bar`` and ``foo[0]`` do not.
|
|
112
|
+
"""
|
|
113
|
+
nxt = toks[index + 1] if index + 1 < len(toks) else None
|
|
114
|
+
return nxt is not None and nxt.type == tokenize.OP and nxt.string == "("
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _readline(source: str):
|
|
118
|
+
"""A ``readline`` callable over ``source`` without importing ``io`` (off the allow-list)."""
|
|
119
|
+
lines = iter(source.splitlines(keepends=True))
|
|
120
|
+
|
|
121
|
+
def readline() -> str:
|
|
122
|
+
return next(lines, "")
|
|
123
|
+
|
|
124
|
+
return readline
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _highlight(source: str) -> str:
|
|
128
|
+
"""Six-color tokenize highlight of ``source``; any tokenizer fault degrades to escaped text.
|
|
129
|
+
|
|
130
|
+
The whole tokenize-and-reconstruct is guarded: a ``TokenError``, ``IndentationError``, a null
|
|
131
|
+
byte, or any other malformed-source failure returns ``esc(source)`` — highlighted source and
|
|
132
|
+
the fallback are both exactly the escaped source, so a bad cell never blanks or dumps raw.
|
|
133
|
+
"""
|
|
134
|
+
if not source:
|
|
135
|
+
return ""
|
|
136
|
+
lines = source.splitlines(keepends=True)
|
|
137
|
+
try:
|
|
138
|
+
toks = list(tokenize.generate_tokens(_readline(source)))
|
|
139
|
+
out: list[str] = []
|
|
140
|
+
last = (1, 0)
|
|
141
|
+
at_line_start = True
|
|
142
|
+
in_decorator = False
|
|
143
|
+
for i, tok in enumerate(toks):
|
|
144
|
+
ttype = tok.type
|
|
145
|
+
tstr = tok.string
|
|
146
|
+
if _ENCODING is not None and ttype == _ENCODING:
|
|
147
|
+
continue
|
|
148
|
+
start, end = tok.start, tok.end
|
|
149
|
+
if start >= last:
|
|
150
|
+
gap = _row_slice(lines, last, start)
|
|
151
|
+
if gap:
|
|
152
|
+
out.append(esc(gap))
|
|
153
|
+
text = _row_slice(lines, start, end)
|
|
154
|
+
|
|
155
|
+
cls: str | None = None
|
|
156
|
+
if ttype == tokenize.COMMENT:
|
|
157
|
+
cls = _TOK_COMMENT
|
|
158
|
+
elif ttype in _STRING_TYPES:
|
|
159
|
+
cls = _TOK_STRING
|
|
160
|
+
elif ttype == tokenize.NUMBER:
|
|
161
|
+
cls = _TOK_NUMBER
|
|
162
|
+
elif ttype == tokenize.NAME:
|
|
163
|
+
if tstr in _KEYWORDS:
|
|
164
|
+
cls = _TOK_KEYWORD
|
|
165
|
+
elif tstr in _CONSTANTS:
|
|
166
|
+
cls = _TOK_NUMBER
|
|
167
|
+
elif in_decorator:
|
|
168
|
+
cls = _TOK_FN
|
|
169
|
+
elif _is_call(toks, i):
|
|
170
|
+
cls = _TOK_FN
|
|
171
|
+
elif ttype == tokenize.OP:
|
|
172
|
+
if tstr == "@" and at_line_start:
|
|
173
|
+
in_decorator = True
|
|
174
|
+
cls = _TOK_FN
|
|
175
|
+
elif in_decorator and tstr == ".":
|
|
176
|
+
cls = _TOK_FN
|
|
177
|
+
elif in_decorator and tstr == "(":
|
|
178
|
+
in_decorator = False
|
|
179
|
+
|
|
180
|
+
if text:
|
|
181
|
+
out.append(f'<span class="{cls}">{esc(text)}</span>' if cls else esc(text))
|
|
182
|
+
if end >= last:
|
|
183
|
+
last = end
|
|
184
|
+
|
|
185
|
+
if ttype in (tokenize.NEWLINE, tokenize.NL):
|
|
186
|
+
at_line_start = True
|
|
187
|
+
in_decorator = False
|
|
188
|
+
elif ttype in (tokenize.INDENT, tokenize.DEDENT, tokenize.COMMENT):
|
|
189
|
+
pass # leading trivia keeps the line-start flag for a following decorator
|
|
190
|
+
else:
|
|
191
|
+
at_line_start = False
|
|
192
|
+
return "".join(out)
|
|
193
|
+
except Exception:
|
|
194
|
+
return esc(source)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _wants_line_numbers(cell: Cell) -> bool:
|
|
198
|
+
"""Line numbers are off by default; a cell opts in via ``metadata.line_numbers`` (or the
|
|
199
|
+
``metadata.jupyter.line_numbers`` nesting)."""
|
|
200
|
+
meta = cell.metadata if isinstance(cell.metadata, dict) else {}
|
|
201
|
+
if meta.get("line_numbers") is True:
|
|
202
|
+
return True
|
|
203
|
+
jupyter = meta.get("jupyter")
|
|
204
|
+
return isinstance(jupyter, dict) and jupyter.get("line_numbers") is True
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _line_number_column(source: str) -> str:
|
|
208
|
+
"""The right-aligned, copy-excluded line-number rail for the code panel."""
|
|
209
|
+
count = max(1, len(source.splitlines()))
|
|
210
|
+
numbers = "\n".join(str(n) for n in range(1, count + 1))
|
|
211
|
+
return f'<pre class="nb-lineno" aria-hidden="true">{esc(numbers)}</pre>'
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def render_code_input(cell: Cell, ctx: RenderContext) -> str:
|
|
215
|
+
"""C12 · code input — dark panel, tokenize-driven six-color theme, optional line numbers.
|
|
216
|
+
|
|
217
|
+
Returns the panel's inner content (the ``<pre>``, wrapped with a line-number rail when the
|
|
218
|
+
cell opts in). The enclosing ``.nb-panel.nb-code`` div — including the welded/head radius and
|
|
219
|
+
padding modifiers — belongs to ``document``, not to this module. The ``<pre>`` is
|
|
220
|
+
``tabindex=0`` with an accessible name; its text content equals ``cell.source`` exactly so a
|
|
221
|
+
copy yields the source with prompts and line numbers excluded.
|
|
222
|
+
"""
|
|
223
|
+
source = cell.source or ""
|
|
224
|
+
status = "executed" if cell.execution_count is not None else "not executed"
|
|
225
|
+
label = f"code cell {ctx.cell_index + 1} of {ctx.cell_count}, {status}"
|
|
226
|
+
pre = f'<pre tabindex="0" aria-label="{esc(label)}">{_highlight(source)}</pre>'
|
|
227
|
+
if _wants_line_numbers(cell):
|
|
228
|
+
return f'<div class="nb-code-lines">{_line_number_column(source)}{pre}</div>'
|
|
229
|
+
return pre
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def render_math(tex: str, ctx: RenderContext, *, display: bool) -> str:
|
|
233
|
+
"""C14 · LaTeX math — escaped TeX source in the display well, labeled ``math · TeX source``.
|
|
234
|
+
|
|
235
|
+
No formula renderer (KaTeX or otherwise) is bundled, so the well shows the escaped TeX source,
|
|
236
|
+
never rendered glyphs and never a raw dump. ``display`` distinguishes ``$$…$$`` block math from
|
|
237
|
+
inline ``$…$`` for the accessible name and a ``data-math`` hook; both use the display well.
|
|
238
|
+
"""
|
|
239
|
+
kind = "display" if display else "inline"
|
|
240
|
+
body = esc((tex or "").strip())
|
|
241
|
+
return (
|
|
242
|
+
f'<div class="nb-math" data-math="{kind}" role="math" '
|
|
243
|
+
f'aria-label="{kind} math, TeX source">'
|
|
244
|
+
f'<span class="nb-math-label">{_MATH_LABEL}</span>'
|
|
245
|
+
f"<code>{body}</code>"
|
|
246
|
+
"</div>"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def render_raw(cell: Cell, ctx: RenderContext) -> str:
|
|
251
|
+
"""C15 · raw cell — receding well, mimetype label, escaped pre. Excluded from the outline.
|
|
252
|
+
|
|
253
|
+
The mimetype comes from ``metadata.raw_mimetype`` (nbformat) or ``metadata.format`` (legacy),
|
|
254
|
+
defaulting to ``text/plain``; a non-string value falls back to the default. The source is
|
|
255
|
+
emitted verbatim through :func:`parse.esc`.
|
|
256
|
+
"""
|
|
257
|
+
meta = cell.metadata if isinstance(cell.metadata, dict) else {}
|
|
258
|
+
mimetype = meta.get("raw_mimetype") or meta.get("format") or "text/plain"
|
|
259
|
+
if not isinstance(mimetype, str):
|
|
260
|
+
mimetype = "text/plain"
|
|
261
|
+
return (
|
|
262
|
+
'<div class="nb-raw">'
|
|
263
|
+
f'<span class="nb-raw-label">{esc(mimetype)}</span>'
|
|
264
|
+
f"<pre>{esc(cell.source or '')}</pre>"
|
|
265
|
+
"</div>"
|
|
266
|
+
)
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""Top-level assembly: raw nbformat JSON to one self-contained HTML string.
|
|
2
|
+
|
|
3
|
+
This module owns the shell and layout and the ``.ipynb`` -> component routing. It renders no
|
|
4
|
+
component itself — it dispatches through the routing below and wraps each call so a component that
|
|
5
|
+
raises ``NotImplementedError`` degrades to a visible pending well instead of blanking the cell. No
|
|
6
|
+
component in this package raises it, so that well is a defensive fallback rather than an expected
|
|
7
|
+
state. Unknown mimetypes and cell types resolve to the labeled unsupported well here, never a raw
|
|
8
|
+
dump.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from mostlyright.data_harness.nbrender import (
|
|
17
|
+
chrome,
|
|
18
|
+
code_body,
|
|
19
|
+
frame,
|
|
20
|
+
interactive,
|
|
21
|
+
markdown_body,
|
|
22
|
+
mr_components,
|
|
23
|
+
outputs_data,
|
|
24
|
+
outputs_rich,
|
|
25
|
+
outputs_source,
|
|
26
|
+
outputs_stage,
|
|
27
|
+
outputs_text,
|
|
28
|
+
tokens,
|
|
29
|
+
)
|
|
30
|
+
from mostlyright.data_harness.nbrender.parse import (
|
|
31
|
+
Cell,
|
|
32
|
+
Notebook,
|
|
33
|
+
Output,
|
|
34
|
+
RenderContext,
|
|
35
|
+
coalesce_stream_outputs,
|
|
36
|
+
detect_tqdm,
|
|
37
|
+
esc,
|
|
38
|
+
parse_notebook,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_WIDGET_MIME = "application/vnd.jupyter.widget-view+json"
|
|
42
|
+
_COLUMN_PROFILE_MIME = "application/vnd.mostlyright.column-profile.v1+json"
|
|
43
|
+
_STAGE_OBSERVATION_MIMES = frozenset(outputs_stage.MIMES)
|
|
44
|
+
_SOURCE_OBSERVATION_MIME = outputs_source.MIME
|
|
45
|
+
_STALE_WIDGET_MSG = "Widget state is frozen — no live kernel in this export."
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _pending_well(component_id: str) -> str:
|
|
49
|
+
"""Placeholder for a component that reported itself unimplemented."""
|
|
50
|
+
return (
|
|
51
|
+
f'<div class="nb-pending" data-component="{esc(component_id)}">'
|
|
52
|
+
f"component pending: {esc(component_id)}</div>"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _unsupported_well(label: str) -> str:
|
|
57
|
+
"""Labeled well for an unknown mimetype or cell type — never a raw dump."""
|
|
58
|
+
return f'<div class="nb-unsupported">unsupported output · {esc(label)}</div>'
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _slot(component_id: str, fn: Callable[..., str], *args: Any, **kwargs: Any) -> str:
|
|
62
|
+
"""Call a component, substituting a pending well if it raises ``NotImplementedError``.
|
|
63
|
+
|
|
64
|
+
Only ``NotImplementedError`` is caught; any real fault surfaces instead of hiding behind a
|
|
65
|
+
well. No component raises it, so this guard is a fallback, not a routine path.
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
return fn(*args, **kwargs)
|
|
69
|
+
except NotImplementedError:
|
|
70
|
+
return _pending_well(component_id)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _attr_slot(fn: Callable[..., str], *args: Any, **kwargs: Any) -> str:
|
|
74
|
+
"""Call a component yielding an attribute/class fragment; unimplemented falls back to ``''``."""
|
|
75
|
+
try:
|
|
76
|
+
return fn(*args, **kwargs)
|
|
77
|
+
except NotImplementedError:
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _render_output(out: Output, ctx: RenderContext) -> str:
|
|
82
|
+
"""Route one output to its component by kind then mimetype, else the unsupported well."""
|
|
83
|
+
if out.kind == "stream-stderr":
|
|
84
|
+
return _slot("D17", outputs_text.render_stderr, out, ctx)
|
|
85
|
+
if out.kind == "stream-stdout":
|
|
86
|
+
if detect_tqdm(out.text) is not None:
|
|
87
|
+
return _slot("D24", outputs_text.render_progress, out, ctx)
|
|
88
|
+
return _slot("D16", outputs_text.render_stdout, out, ctx)
|
|
89
|
+
if out.kind == "error":
|
|
90
|
+
return _slot("D23", outputs_text.render_traceback, out, ctx)
|
|
91
|
+
if out.kind in ("execute_result", "display_data"):
|
|
92
|
+
mimetype = out.primary_mimetype()
|
|
93
|
+
if mimetype is None:
|
|
94
|
+
return _unsupported_well("(empty output)")
|
|
95
|
+
if mimetype == _WIDGET_MIME:
|
|
96
|
+
html = ""
|
|
97
|
+
if ctx.mode in ("static", "embed"):
|
|
98
|
+
html += _slot(
|
|
99
|
+
"E26", interactive.render_stale_banner, ctx, message=_STALE_WIDGET_MSG
|
|
100
|
+
)
|
|
101
|
+
return html + _slot("E25", interactive.render_widget, out, ctx)
|
|
102
|
+
if mimetype == _COLUMN_PROFILE_MIME:
|
|
103
|
+
if outputs_data.is_valid_column_profiles_payload(out.data.get(_COLUMN_PROFILE_MIME)):
|
|
104
|
+
return _slot("MR01", outputs_data.render_column_profiles, out, ctx)
|
|
105
|
+
# Custom MIME is an enhancement, never a single point of failure. Strip only the bad
|
|
106
|
+
# representation and route the same normal Jupyter output through its portable HTML or
|
|
107
|
+
# text fallback. With neither fallback, show a labeled well rather than a blank cell.
|
|
108
|
+
fallback = Output(
|
|
109
|
+
kind=out.kind,
|
|
110
|
+
text=out.text,
|
|
111
|
+
data={key: value for key, value in out.data.items() if key != _COLUMN_PROFILE_MIME},
|
|
112
|
+
metadata=out.metadata,
|
|
113
|
+
execution_count=out.execution_count,
|
|
114
|
+
)
|
|
115
|
+
return (
|
|
116
|
+
_render_output(fallback, ctx)
|
|
117
|
+
if fallback.primary_mimetype() is not None
|
|
118
|
+
else _unsupported_well("invalid column inspector")
|
|
119
|
+
)
|
|
120
|
+
if mimetype == _SOURCE_OBSERVATION_MIME:
|
|
121
|
+
if outputs_source.valid_payload(out.data.get(mimetype)):
|
|
122
|
+
return _slot("MR03", outputs_source.render_source_observation, out, ctx)
|
|
123
|
+
fallback = Output(
|
|
124
|
+
kind=out.kind,
|
|
125
|
+
text=out.text,
|
|
126
|
+
data={key: value for key, value in out.data.items() if key != mimetype},
|
|
127
|
+
metadata=out.metadata,
|
|
128
|
+
execution_count=out.execution_count,
|
|
129
|
+
)
|
|
130
|
+
return (
|
|
131
|
+
_render_output(fallback, ctx)
|
|
132
|
+
if fallback.primary_mimetype() is not None
|
|
133
|
+
else _unsupported_well("invalid source evidence")
|
|
134
|
+
)
|
|
135
|
+
if mimetype in _STAGE_OBSERVATION_MIMES:
|
|
136
|
+
if outputs_stage.valid_payload(out.data.get(mimetype)):
|
|
137
|
+
return _slot(
|
|
138
|
+
"MR02", outputs_stage.render_stage_observation, out, ctx, mimetype=mimetype
|
|
139
|
+
)
|
|
140
|
+
fallback = Output(
|
|
141
|
+
kind=out.kind,
|
|
142
|
+
text=out.text,
|
|
143
|
+
data={key: value for key, value in out.data.items() if key != mimetype},
|
|
144
|
+
metadata=out.metadata,
|
|
145
|
+
execution_count=out.execution_count,
|
|
146
|
+
)
|
|
147
|
+
return (
|
|
148
|
+
_render_output(fallback, ctx)
|
|
149
|
+
if fallback.primary_mimetype() is not None
|
|
150
|
+
else _unsupported_well("invalid stage observation")
|
|
151
|
+
)
|
|
152
|
+
if mimetype == "text/html":
|
|
153
|
+
source = out.text_of("text/html")
|
|
154
|
+
if 'class="dataframe"' in source or "class='dataframe'" in source:
|
|
155
|
+
return _slot("D19", outputs_rich.render_dataframe, out, ctx)
|
|
156
|
+
return _slot("D21", outputs_rich.render_html, out, ctx)
|
|
157
|
+
if mimetype in ("image/svg+xml", "image/png"):
|
|
158
|
+
return _slot("D20", outputs_rich.render_figure, out, ctx)
|
|
159
|
+
if mimetype == "application/json":
|
|
160
|
+
return _slot("D22", outputs_rich.render_json, out, ctx)
|
|
161
|
+
if mimetype == "text/plain":
|
|
162
|
+
if detect_tqdm(out.text_of("text/plain")) is not None:
|
|
163
|
+
return _slot("D24", outputs_text.render_progress, out, ctx)
|
|
164
|
+
return _slot("D18", outputs_text.render_result_text, out, ctx)
|
|
165
|
+
return _unsupported_well(mimetype)
|
|
166
|
+
return _unsupported_well("(unknown output)")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _output_prompt_variant(out: Output, cell: Cell) -> tuple[str | None, int | None]:
|
|
170
|
+
"""Pick the B06 gutter variant + count for an output row.
|
|
171
|
+
|
|
172
|
+
``execute_result``/``display_data`` -> ``Out [n]`` (orange, n from the output's or the cell's
|
|
173
|
+
execution count); a widget bundle -> ``Wgt`` (grey); a stream -> ``Out [*]`` (grey); an error ->
|
|
174
|
+
``Err`` (rust). An ``unknown`` record carries no prompt.
|
|
175
|
+
"""
|
|
176
|
+
if out.kind in ("stream-stdout", "stream-stderr"):
|
|
177
|
+
return "stream", None
|
|
178
|
+
if out.kind == "error":
|
|
179
|
+
return "err", None
|
|
180
|
+
if out.kind in ("execute_result", "display_data"):
|
|
181
|
+
if out.primary_mimetype() == _WIDGET_MIME:
|
|
182
|
+
return "wgt", None
|
|
183
|
+
count = out.execution_count if out.execution_count is not None else cell.execution_count
|
|
184
|
+
return "out", count
|
|
185
|
+
return None, None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _io_row(prompt: str, output_html: str) -> str:
|
|
189
|
+
"""Re-enter an output into the 62px gutter grid so its ``Out`` prompt aligns under ``In [n]``.
|
|
190
|
+
|
|
191
|
+
The ``.nb-io`` wrapper pulls back ``margin-left: -62px`` and re-establishes the
|
|
192
|
+
``[62px][content]`` grid, so ``In [n]`` and ``Out [n]`` share one pixel-aligned
|
|
193
|
+
column across every output type. The prompt column is ``aria-hidden`` and ``user-select:
|
|
194
|
+
none`` (excluded from copy).
|
|
195
|
+
"""
|
|
196
|
+
return (
|
|
197
|
+
'<div class="nb-io">'
|
|
198
|
+
f'<div class="nb-io-gutter" aria-hidden="true">{prompt}</div>'
|
|
199
|
+
f'<div class="nb-io-body">{output_html}</div>'
|
|
200
|
+
"</div>"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _content_row(content: str, modifier: str) -> str:
|
|
205
|
+
"""Place a document-level block in the content column with an empty gutter cell."""
|
|
206
|
+
return (
|
|
207
|
+
f'<section class="nb-cell nb-cell--{esc(modifier)}">'
|
|
208
|
+
'<div class="nb-gutter" aria-hidden="true"></div>'
|
|
209
|
+
f'<div class="nb-content">{content}</div>'
|
|
210
|
+
"</section>"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _is_weldable_well(out: Output) -> bool:
|
|
215
|
+
"""A first output welds flush to the code panel only when it is a stdout well (D16).
|
|
216
|
+
|
|
217
|
+
stderr (D17) carries its own dashed-separator rule and the rich containers (df/fig/json/
|
|
218
|
+
traceback) own a top border, so only a non-tqdm stdout stream is the ``.nb-well`` that welds
|
|
219
|
+
directly beneath the panel. Every other output re-enters the gutter inside ``.nb-io``, which
|
|
220
|
+
owns the 10px input-to-output gap and zeroes the top margin of every child of its body, so the
|
|
221
|
+
gap is the same for every output shape — including one that renders as several sibling roots.
|
|
222
|
+
The weld itself is expressed by
|
|
223
|
+
the ``.nb-code--welded + .nb-well`` adjacency rule in ``build_css`` — the well's own markup is
|
|
224
|
+
left pristine, so this is a panel-class decision here, not a rewrite of the output.
|
|
225
|
+
"""
|
|
226
|
+
return out.kind == "stream-stdout" and detect_tqdm(out.text) is None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _render_code_cell(cell: Cell, ctx: RenderContext) -> tuple[str, str]:
|
|
230
|
+
"""Return (gutter_html, content_html) for a code cell.
|
|
231
|
+
|
|
232
|
+
Consecutive stdout/stderr stream records are coalesced (D16) before routing; a panel header is
|
|
233
|
+
always present (B08 copy), so the panel takes ``nb-code--head`` for the 16px 18px pre padding;
|
|
234
|
+
and when the first output is a stdout well the panel takes ``nb-code--welded`` so the shared
|
|
235
|
+
adjacency rule squares the panel's bottom and drops the well's top seam — the two read as one
|
|
236
|
+
dark-to-paper unit. Every other output re-enters the gutter grid (``.nb-io``) with its B06
|
|
237
|
+
``Out``/``Err``/``Wgt`` prompt so ``In [n]`` and ``Out [n]`` share the one 62px column; the
|
|
238
|
+
welded first stdout is the single exception (it reads as a panel continuation, with no prompt).
|
|
239
|
+
"""
|
|
240
|
+
activity = cell.research_activity
|
|
241
|
+
gutter = _slot(
|
|
242
|
+
"B06",
|
|
243
|
+
frame.render_prompt,
|
|
244
|
+
cell,
|
|
245
|
+
ctx,
|
|
246
|
+
variant="running" if activity == "executing" else None,
|
|
247
|
+
)
|
|
248
|
+
state = _attr_slot(
|
|
249
|
+
frame.cell_state_classes,
|
|
250
|
+
cell,
|
|
251
|
+
ctx,
|
|
252
|
+
state="running" if activity == "executing" else None,
|
|
253
|
+
)
|
|
254
|
+
panel_head = _slot("B08", frame.render_hover_toolbar, cell, ctx)
|
|
255
|
+
activity_chip = _slot("B10", frame.render_research_activity, cell, ctx)
|
|
256
|
+
if activity_chip:
|
|
257
|
+
panel_head = panel_head.replace(
|
|
258
|
+
'<div class="nb-panel-actions">',
|
|
259
|
+
f'{activity_chip}<div class="nb-panel-actions">',
|
|
260
|
+
1,
|
|
261
|
+
)
|
|
262
|
+
code = _slot("C12", code_body.render_code_input, cell, ctx)
|
|
263
|
+
|
|
264
|
+
outputs = coalesce_stream_outputs(cell.outputs)
|
|
265
|
+
welded = bool(outputs) and _is_weldable_well(outputs[0])
|
|
266
|
+
|
|
267
|
+
classes = ["nb-panel", "nb-code"]
|
|
268
|
+
if panel_head:
|
|
269
|
+
classes.append("nb-code--head")
|
|
270
|
+
if welded:
|
|
271
|
+
classes.append("nb-code--welded")
|
|
272
|
+
if state:
|
|
273
|
+
classes.append(state)
|
|
274
|
+
panel = f'<div class="{esc(" ".join(classes))}">{panel_head}{code}</div>'
|
|
275
|
+
|
|
276
|
+
parts = [panel]
|
|
277
|
+
for index, out in enumerate(outputs):
|
|
278
|
+
rendered = _render_output(out, ctx)
|
|
279
|
+
if index == 0 and welded:
|
|
280
|
+
parts.append(rendered) # flush beneath the panel, no gutter re-entry
|
|
281
|
+
continue
|
|
282
|
+
variant, count = _output_prompt_variant(out, cell)
|
|
283
|
+
prompt = (
|
|
284
|
+
_slot("B06", frame.render_prompt, cell, ctx, variant=variant, count=count)
|
|
285
|
+
if variant is not None
|
|
286
|
+
else ""
|
|
287
|
+
)
|
|
288
|
+
parts.append(_io_row(prompt, rendered))
|
|
289
|
+
if cell.execution_count is not None:
|
|
290
|
+
metadata = _slot("B11", frame.render_exec_meta, cell, ctx)
|
|
291
|
+
if metadata:
|
|
292
|
+
parts.append(metadata.replace(">", " data-cell-exec-meta>", 1))
|
|
293
|
+
return gutter, "".join(parts)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _render_markdown_cell(cell: Cell, ctx: RenderContext) -> tuple[str, str]:
|
|
297
|
+
gutter = _slot("B06", frame.render_prompt, cell, ctx)
|
|
298
|
+
tags = _slot("B10", frame.render_tags, cell, ctx) if cell.tags else ""
|
|
299
|
+
body = _slot("C13", markdown_body.render_markdown, cell, ctx)
|
|
300
|
+
activity = _slot("B10", frame.render_research_activity, cell, ctx)
|
|
301
|
+
return gutter, activity + tags + body
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _render_raw_cell(cell: Cell, ctx: RenderContext) -> tuple[str, str]:
|
|
305
|
+
gutter = _slot("B06", frame.render_prompt, cell, ctx)
|
|
306
|
+
return gutter, _slot("C15", code_body.render_raw, cell, ctx)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _render_cell(cell: Cell, ctx: RenderContext) -> str:
|
|
310
|
+
if cell.cell_type == "code":
|
|
311
|
+
gutter, content = _render_code_cell(cell, ctx)
|
|
312
|
+
elif cell.cell_type == "markdown":
|
|
313
|
+
gutter, content = _render_markdown_cell(cell, ctx)
|
|
314
|
+
elif cell.cell_type == "raw":
|
|
315
|
+
gutter, content = _render_raw_cell(cell, ctx)
|
|
316
|
+
else:
|
|
317
|
+
gutter, content = "", _unsupported_well(f"cell · {cell.cell_type}")
|
|
318
|
+
label = f"cell {ctx.cell_index + 1} of {ctx.cell_count}"
|
|
319
|
+
cell_id = cell.cell_id or f"cell-{ctx.cell_index}"
|
|
320
|
+
activity = cell.research_activity
|
|
321
|
+
activity_attr = f' data-research-activity="{esc(activity)}"' if activity else ""
|
|
322
|
+
activity_class = f" is-{activity}" if activity else ""
|
|
323
|
+
return (
|
|
324
|
+
f'<section class="nb-cell nb-cell--{esc(cell.cell_type)}{activity_class}" '
|
|
325
|
+
f'data-cell-id="{esc(cell_id)}" aria-label="{esc(label)}"{activity_attr}>'
|
|
326
|
+
f'<div class="nb-gutter" data-cell-gutter aria-hidden="true">{gutter}</div>'
|
|
327
|
+
f'<div class="nb-content">{content}</div>'
|
|
328
|
+
"</section>"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _leakage_placement(
|
|
333
|
+
nb: Notebook,
|
|
334
|
+
) -> tuple[dict[int, list[dict[str, Any]]], list[dict[str, Any]]]:
|
|
335
|
+
"""Split leakage entries into per-cell (by integer ``cell`` index) and trailing groups."""
|
|
336
|
+
by_cell: dict[int, list[dict[str, Any]]] = {}
|
|
337
|
+
trailing: list[dict[str, Any]] = []
|
|
338
|
+
for entry in nb.leakage:
|
|
339
|
+
index = entry.get("cell")
|
|
340
|
+
if isinstance(index, int) and not isinstance(index, bool):
|
|
341
|
+
by_cell.setdefault(index, []).append(entry)
|
|
342
|
+
else:
|
|
343
|
+
trailing.append(entry)
|
|
344
|
+
return by_cell, trailing
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def render_notebook(nb: dict[str, Any], mode: str = "static", filename: str = "table.ipynb") -> str:
|
|
348
|
+
"""Render a notebook to one HTML string: shell > A01 > [A04 | cells] > A05.
|
|
349
|
+
|
|
350
|
+
``mode`` is one of static/connected/embed; embed drops all chrome. The result is always
|
|
351
|
+
well-formed even against a malformed notebook — the parser degrades bad records and every
|
|
352
|
+
component call is guarded.
|
|
353
|
+
"""
|
|
354
|
+
if mode not in tokens.VALID_MODES:
|
|
355
|
+
raise ValueError(f"unknown render mode: {mode!r} (expected one of {tokens.VALID_MODES})")
|
|
356
|
+
|
|
357
|
+
model = parse_notebook(nb)
|
|
358
|
+
doc_ctx = RenderContext(mode=mode, cell_index=-1, cell_count=len(model.cells))
|
|
359
|
+
css = tokens.build_css(mode)
|
|
360
|
+
|
|
361
|
+
body_parts: list[str] = []
|
|
362
|
+
if model.provenance is not None:
|
|
363
|
+
body_parts.append(_slot("F27", mr_components.render_provenance, model, doc_ctx))
|
|
364
|
+
|
|
365
|
+
by_cell, trailing_leakage = _leakage_placement(model)
|
|
366
|
+
prev_exec: int | None = None
|
|
367
|
+
for index, cell in enumerate(model.cells):
|
|
368
|
+
ctx = RenderContext(
|
|
369
|
+
mode=mode,
|
|
370
|
+
cell_index=index,
|
|
371
|
+
cell_count=len(model.cells),
|
|
372
|
+
prev_execution_count=prev_exec,
|
|
373
|
+
)
|
|
374
|
+
body_parts.append(_render_cell(cell, ctx))
|
|
375
|
+
for entry in by_cell.get(index, []):
|
|
376
|
+
body_parts.append(
|
|
377
|
+
_content_row(_slot("F28", mr_components.render_leakage, entry, ctx), "leak")
|
|
378
|
+
)
|
|
379
|
+
if cell.cell_type == "code" and cell.execution_count is not None:
|
|
380
|
+
prev_exec = cell.execution_count
|
|
381
|
+
for entry in trailing_leakage:
|
|
382
|
+
body_parts.append(
|
|
383
|
+
_content_row(_slot("F28", mr_components.render_leakage, entry, doc_ctx), "leak")
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
document = f'<div class="nb-document">{"".join(body_parts)}</div>'
|
|
387
|
+
|
|
388
|
+
outline = ""
|
|
389
|
+
if mode != "embed":
|
|
390
|
+
outline = _slot("A04", chrome.render_outline, model, doc_ctx)
|
|
391
|
+
body = f'<div class="nb-body">{outline}{document}</div>'
|
|
392
|
+
|
|
393
|
+
chrome_top = ""
|
|
394
|
+
chrome_bottom = ""
|
|
395
|
+
if mode != "embed":
|
|
396
|
+
chrome_top += _slot("A01", chrome.render_header, model, doc_ctx, filename)
|
|
397
|
+
if mode == "connected":
|
|
398
|
+
chrome_top += _slot("A02", chrome.render_toolbar, model, doc_ctx)
|
|
399
|
+
if mode != "embed":
|
|
400
|
+
chrome_bottom = _slot("A05", chrome.render_footer, model, doc_ctx)
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
f'<div class="nb-shell nb-shell--{esc(mode)}">'
|
|
404
|
+
f"<style>{css}</style>"
|
|
405
|
+
f"{chrome_top}{body}{chrome_bottom}"
|
|
406
|
+
"</div>"
|
|
407
|
+
)
|