dataface 0.1.2__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.
- d3_format/__init__.py +14 -0
- d3_format/errors.py +19 -0
- d3_format/format.py +551 -0
- d3_format/spec.py +159 -0
- dataface/DATAFACE_SYNTAX.md +1135 -0
- dataface/__init__.py +93 -0
- dataface/_docs_site.py +20 -0
- dataface/_install_hint.py +26 -0
- dataface/agent_api/__init__.py +79 -0
- dataface/agent_api/_init_templates/__init__.py +0 -0
- dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
- dataface/agent_api/_init_templates/dataface.yml +15 -0
- dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
- dataface/agent_api/_init_templates/index.md +24 -0
- dataface/agent_api/_paths.py +118 -0
- dataface/agent_api/_project_agents_md.py +43 -0
- dataface/agent_api/_session_store.py +486 -0
- dataface/agent_api/_state.py +28 -0
- dataface/agent_api/chat.py +221 -0
- dataface/agent_api/dashboards.py +257 -0
- dataface/agent_api/describe.py +366 -0
- dataface/agent_api/describe_query.py +120 -0
- dataface/agent_api/docs/__init__.py +25 -0
- dataface/agent_api/docs/_loader.py +292 -0
- dataface/agent_api/docs/yaml-reference.md +2757 -0
- dataface/agent_api/file_refs.py +118 -0
- dataface/agent_api/init.py +126 -0
- dataface/agent_api/inspect.py +128 -0
- dataface/agent_api/mcp_install.py +170 -0
- dataface/agent_api/query.py +274 -0
- dataface/agent_api/schema.py +658 -0
- dataface/agent_api/schema_search.py +284 -0
- dataface/agent_api/search.py +270 -0
- dataface/agent_api/skill_install.py +141 -0
- dataface/agent_api/skill_render.py +90 -0
- dataface/agent_api/skills.py +293 -0
- dataface/agent_api/surface_aliases.yaml +128 -0
- dataface/agent_api/validate.py +175 -0
- dataface/agent_api/validate_query.py +84 -0
- dataface/ai/__init__.py +39 -0
- dataface/ai/agent.py +139 -0
- dataface/ai/context.py +45 -0
- dataface/ai/events.py +62 -0
- dataface/ai/external_mcp.py +610 -0
- dataface/ai/generate_sql.py +96 -0
- dataface/ai/llm.py +403 -0
- dataface/ai/mcp/__init__.py +51 -0
- dataface/ai/mcp/server.py +289 -0
- dataface/ai/memories.py +85 -0
- dataface/ai/prompts.py +177 -0
- dataface/ai/schema_context.py +138 -0
- dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
- dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
- dataface/ai/skills/dashboard-build/SKILL.md +212 -0
- dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
- dataface/ai/skills/dashboard-design/SKILL.md +182 -0
- dataface/ai/skills/dashboard-review/SKILL.md +113 -0
- dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
- dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
- dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
- dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
- dataface/ai/skills/drill-down-link/SKILL.md +112 -0
- dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
- dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
- dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
- dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
- dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
- dataface/ai/skills/kpi-row/SKILL.md +101 -0
- dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
- dataface/ai/skills/report-design/SKILL.md +184 -0
- dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
- dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
- dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
- dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
- dataface/ai/skills/time-series-trend/SKILL.md +93 -0
- dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
- dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
- dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
- dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
- dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
- dataface/ai/tool_schemas.py +132 -0
- dataface/ai/tools/__init__.py +312 -0
- dataface/ai/yaml_utils.py +57 -0
- dataface/cli/__init__.py +3 -0
- dataface/cli/_console.py +48 -0
- dataface/cli/_error_format.py +83 -0
- dataface/cli/_extras.py +190 -0
- dataface/cli/_json_output.py +8 -0
- dataface/cli/_parsing.py +17 -0
- dataface/cli/_version_info.py +56 -0
- dataface/cli/commands/__init__.py +3 -0
- dataface/cli/commands/_agent_input.py +205 -0
- dataface/cli/commands/_agent_server.py +115 -0
- dataface/cli/commands/chat.py +645 -0
- dataface/cli/commands/describe.py +107 -0
- dataface/cli/commands/docs.py +131 -0
- dataface/cli/commands/extension.py +179 -0
- dataface/cli/commands/init.py +240 -0
- dataface/cli/commands/inspect.py +94 -0
- dataface/cli/commands/mcp_init.py +167 -0
- dataface/cli/commands/query.py +386 -0
- dataface/cli/commands/render.py +291 -0
- dataface/cli/commands/schema.py +411 -0
- dataface/cli/commands/search.py +49 -0
- dataface/cli/commands/serve.py +114 -0
- dataface/cli/commands/skills.py +133 -0
- dataface/cli/commands/skills_init.py +161 -0
- dataface/cli/commands/validate.py +63 -0
- dataface/cli/main.py +1501 -0
- dataface/core/__init__.py +75 -0
- dataface/core/compile/__init__.py +244 -0
- dataface/core/compile/_jinja_helpers.py +78 -0
- dataface/core/compile/channel.py +222 -0
- dataface/core/compile/chart_focus.py +101 -0
- dataface/core/compile/chart_resolved.py +169 -0
- dataface/core/compile/chart_type_detection.py +489 -0
- dataface/core/compile/chart_update.py +261 -0
- dataface/core/compile/colors.py +64 -0
- dataface/core/compile/compiler.py +904 -0
- dataface/core/compile/config.py +823 -0
- dataface/core/compile/custom_chart_types.py +208 -0
- dataface/core/compile/data_table_attachment.py +1287 -0
- dataface/core/compile/detect.py +110 -0
- dataface/core/compile/errors.py +302 -0
- dataface/core/compile/filter_injection.py +319 -0
- dataface/core/compile/introspection.py +527 -0
- dataface/core/compile/jinja.py +511 -0
- dataface/core/compile/labels_env.py +52 -0
- dataface/core/compile/markdown.py +154 -0
- dataface/core/compile/meta.py +388 -0
- dataface/core/compile/models/__init__.py +0 -0
- dataface/core/compile/models/chart/__init__.py +0 -0
- dataface/core/compile/models/chart/authored.py +2137 -0
- dataface/core/compile/models/chart/compiled.py +398 -0
- dataface/core/compile/models/config.py +347 -0
- dataface/core/compile/models/face/__init__.py +0 -0
- dataface/core/compile/models/face/authored.py +659 -0
- dataface/core/compile/models/face/compiled.py +522 -0
- dataface/core/compile/models/factories.py +201 -0
- dataface/core/compile/models/markers.py +40 -0
- dataface/core/compile/models/palette.py +36 -0
- dataface/core/compile/models/primitives.py +415 -0
- dataface/core/compile/models/query/__init__.py +0 -0
- dataface/core/compile/models/query/authored.py +246 -0
- dataface/core/compile/models/query/compiled.py +710 -0
- dataface/core/compile/models/refs.py +137 -0
- dataface/core/compile/models/source.py +611 -0
- dataface/core/compile/models/style/__init__.py +0 -0
- dataface/core/compile/models/style/authored.py +481 -0
- dataface/core/compile/models/style/compiled.py +3399 -0
- dataface/core/compile/models/style/merged.py +1682 -0
- dataface/core/compile/models/theme.py +362 -0
- dataface/core/compile/models/variable/__init__.py +0 -0
- dataface/core/compile/models/variable/authored.py +254 -0
- dataface/core/compile/models/vega_lite/__init__.py +0 -0
- dataface/core/compile/models/vega_lite/config.py +510 -0
- dataface/core/compile/models/vega_lite/contracts.py +171 -0
- dataface/core/compile/normalize_charts.py +494 -0
- dataface/core/compile/normalize_layout.py +1000 -0
- dataface/core/compile/normalize_queries.py +297 -0
- dataface/core/compile/normalize_variables.py +489 -0
- dataface/core/compile/normalizer.py +543 -0
- dataface/core/compile/palette.py +1100 -0
- dataface/core/compile/parameterized.py +658 -0
- dataface/core/compile/parser.py +228 -0
- dataface/core/compile/schema.py +20 -0
- dataface/core/compile/schema_renderers/__init__.py +0 -0
- dataface/core/compile/schema_renderers/json_schema.py +163 -0
- dataface/core/compile/schema_renderers/prompt.py +152 -0
- dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
- dataface/core/compile/sizing.py +2126 -0
- dataface/core/compile/sources.py +518 -0
- dataface/core/compile/sql_authoring_lint.py +56 -0
- dataface/core/compile/style_cascade.py +471 -0
- dataface/core/compile/typography.py +299 -0
- dataface/core/compile/validator.py +301 -0
- dataface/core/compile/variables.py +53 -0
- dataface/core/compile/vega_config.py +98 -0
- dataface/core/compile/vega_lite/__init__.py +6 -0
- dataface/core/compile/vega_lite/validation.py +95 -0
- dataface/core/compile/yaml_error_formatter.py +838 -0
- dataface/core/connections.py +38 -0
- dataface/core/dashboard.py +358 -0
- dataface/core/defaults/default_config.yml +101 -0
- dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
- dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
- dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
- dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
- dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
- dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
- dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
- dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
- dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
- dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
- dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
- dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
- dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
- dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
- dataface/core/defaults/palettes/sequential/amber.yml +22 -0
- dataface/core/defaults/palettes/sequential/blue.yml +22 -0
- dataface/core/defaults/palettes/sequential/brown.yml +22 -0
- dataface/core/defaults/palettes/sequential/gray.yml +22 -0
- dataface/core/defaults/palettes/sequential/green.yml +22 -0
- dataface/core/defaults/palettes/sequential/purple.yml +22 -0
- dataface/core/defaults/palettes/sequential/rust.yml +22 -0
- dataface/core/defaults/palettes/sequential/teal.yml +22 -0
- dataface/core/defaults/palettes/tone/negative.yml +32 -0
- dataface/core/defaults/palettes/tone/positive.yml +22 -0
- dataface/core/defaults/palettes/tone/warning.yml +22 -0
- dataface/core/defaults/themes/_base.yaml +786 -0
- dataface/core/defaults/themes/bi.yaml +16 -0
- dataface/core/defaults/themes/carbong100.yaml +41 -0
- dataface/core/defaults/themes/cream.yaml +122 -0
- dataface/core/defaults/themes/dark.yaml +40 -0
- dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
- dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
- dataface/core/defaults/themes/editorial.yaml +147 -0
- dataface/core/defaults/themes/light.yaml +30 -0
- dataface/core/defaults/themes/looker.yaml +17 -0
- dataface/core/defaults/themes/stark.yaml +134 -0
- dataface/core/errors/__init__.py +67 -0
- dataface/core/errors/codes_compile.py +56 -0
- dataface/core/errors/codes_execute.py +177 -0
- dataface/core/errors/codes_render.py +106 -0
- dataface/core/errors/codes_unknown.py +15 -0
- dataface/core/errors/hints.py +74 -0
- dataface/core/errors/registry.py +42 -0
- dataface/core/errors/structured.py +92 -0
- dataface/core/execute/__init__.py +91 -0
- dataface/core/execute/adapters/__init__.py +49 -0
- dataface/core/execute/adapters/adapter_registry.py +400 -0
- dataface/core/execute/adapters/base.py +245 -0
- dataface/core/execute/adapters/csv_adapter.py +239 -0
- dataface/core/execute/adapters/dbt_adapter.py +283 -0
- dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
- dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
- dataface/core/execute/adapters/dbt_utils.py +150 -0
- dataface/core/execute/adapters/http_adapter.py +224 -0
- dataface/core/execute/adapters/metricflow_adapter.py +94 -0
- dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
- dataface/core/execute/adapters/sql_adapter.py +710 -0
- dataface/core/execute/adapters/values_adapter.py +58 -0
- dataface/core/execute/batch.py +744 -0
- dataface/core/execute/cache_backend.py +135 -0
- dataface/core/execute/cache_keys.py +66 -0
- dataface/core/execute/dbt_jinja.py +21 -0
- dataface/core/execute/dialects/__init__.py +121 -0
- dataface/core/execute/dialects/athena.py +75 -0
- dataface/core/execute/dialects/base.py +302 -0
- dataface/core/execute/dialects/bigquery.py +38 -0
- dataface/core/execute/dialects/databricks.py +68 -0
- dataface/core/execute/dialects/duckdb.py +35 -0
- dataface/core/execute/dialects/mysql.py +68 -0
- dataface/core/execute/dialects/postgres.py +39 -0
- dataface/core/execute/dialects/redshift.py +12 -0
- dataface/core/execute/dialects/snowflake.py +51 -0
- dataface/core/execute/dialects/sqlserver.py +92 -0
- dataface/core/execute/duckdb_cache.py +712 -0
- dataface/core/execute/duckdb_config.py +26 -0
- dataface/core/execute/errors.py +213 -0
- dataface/core/execute/executor.py +1249 -0
- dataface/core/execute/parallel.py +162 -0
- dataface/core/execute/setup_sql.py +58 -0
- dataface/core/execute/source_registry.py +72 -0
- dataface/core/execute/source_resolver.py +255 -0
- dataface/core/execute/sql_guard.py +387 -0
- dataface/core/execute/sql_literals.py +199 -0
- dataface/core/fonts.py +52 -0
- dataface/core/inspect/__init__.py +32 -0
- dataface/core/inspect/cache_factory.py +98 -0
- dataface/core/inspect/db_types.py +162 -0
- dataface/core/inspect/dbt_schema.py +96 -0
- dataface/core/inspect/defaults.yml +37 -0
- dataface/core/inspect/fanout_risk.py +109 -0
- dataface/core/inspect/manifest_utils.py +77 -0
- dataface/core/inspect/partials/categorical.yml +40 -0
- dataface/core/inspect/partials/date.yml +40 -0
- dataface/core/inspect/partials/numeric.yml +55 -0
- dataface/core/inspect/partition_types.py +38 -0
- dataface/core/inspect/query_validator.py +975 -0
- dataface/core/inspect/renderer.py +354 -0
- dataface/core/inspect/resolver.py +808 -0
- dataface/core/inspect/search.py +461 -0
- dataface/core/inspect/sources/__init__.py +32 -0
- dataface/core/inspect/sources/dbt.py +738 -0
- dataface/core/inspect/sources/duckdb_utils.py +66 -0
- dataface/core/inspect/templates/__init__.py +1 -0
- dataface/core/inspect/templates/categorical_column.yml +196 -0
- dataface/core/inspect/templates/charts.yml +109 -0
- dataface/core/inspect/templates/date_column.yml +248 -0
- dataface/core/inspect/templates/model.yml +138 -0
- dataface/core/inspect/templates/numeric_column.yml +261 -0
- dataface/core/inspect/templates/quality.yml +80 -0
- dataface/core/inspect/templates/string_column.yml +263 -0
- dataface/core/project_roots.py +165 -0
- dataface/core/render/__init__.py +87 -0
- dataface/core/render/board_links.py +176 -0
- dataface/core/render/chart/__init__.py +27 -0
- dataface/core/render/chart/arc_attached_table.py +251 -0
- dataface/core/render/chart/artifacts.py +16 -0
- dataface/core/render/chart/callout.py +225 -0
- dataface/core/render/chart/decisions.py +358 -0
- dataface/core/render/chart/geo.py +700 -0
- dataface/core/render/chart/kpi.py +916 -0
- dataface/core/render/chart/labels.py +76 -0
- dataface/core/render/chart/pipeline.py +818 -0
- dataface/core/render/chart/presentation.py +36 -0
- dataface/core/render/chart/profile.py +3438 -0
- dataface/core/render/chart/render_single.py +347 -0
- dataface/core/render/chart/renderers.py +193 -0
- dataface/core/render/chart/rendering.py +565 -0
- dataface/core/render/chart/serialization.py +90 -0
- dataface/core/render/chart/spark.py +496 -0
- dataface/core/render/chart/spark_bar.py +370 -0
- dataface/core/render/chart/spec_builders.py +154 -0
- dataface/core/render/chart/standard_renderer.py +2645 -0
- dataface/core/render/chart/table.py +2957 -0
- dataface/core/render/chart/table_support.py +1452 -0
- dataface/core/render/chart/tick_values.py +66 -0
- dataface/core/render/chart/time_unit_detect.py +809 -0
- dataface/core/render/chart/title_overflow.py +157 -0
- dataface/core/render/chart/type_inference.py +122 -0
- dataface/core/render/chart/validation.py +99 -0
- dataface/core/render/chart/vega_lite.py +125 -0
- dataface/core/render/chart/vega_lite_types.py +268 -0
- dataface/core/render/chart/vl_field_maps.py +346 -0
- dataface/core/render/chart_interactivity.py +24 -0
- dataface/core/render/control_registry.py +287 -0
- dataface/core/render/converters/__init__.py +24 -0
- dataface/core/render/converters/chart.py +276 -0
- dataface/core/render/converters/html.py +98 -0
- dataface/core/render/converters/pdf.py +40 -0
- dataface/core/render/converters/png.py +41 -0
- dataface/core/render/errors.py +144 -0
- dataface/core/render/face_api.py +160 -0
- dataface/core/render/faces.py +1194 -0
- dataface/core/render/font_measurement.py +48 -0
- dataface/core/render/font_support.py +197 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/InterVariable.ttf +0 -0
- dataface/core/render/fonts/InterVariable.woff2 +0 -0
- dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
- dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
- dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
- dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
- dataface/core/render/fonts/_emoji_font_face.css +43 -0
- dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
- dataface/core/render/format_utils.py +329 -0
- dataface/core/render/geo_defaults.yml +28 -0
- dataface/core/render/json_format.py +146 -0
- dataface/core/render/layout_sizing.py +865 -0
- dataface/core/render/layouts.py +541 -0
- dataface/core/render/markdown_defaults.yml +16 -0
- dataface/core/render/missing_vars_prompt.py +79 -0
- dataface/core/render/placeholder.py +389 -0
- dataface/core/render/render_result.py +14 -0
- dataface/core/render/renderer.py +467 -0
- dataface/core/render/script_embedding.py +16 -0
- dataface/core/render/svg_utils.py +212 -0
- dataface/core/render/template_loader.py +69 -0
- dataface/core/render/templates/controls/_styles.css +606 -0
- dataface/core/render/templates/controls/checkbox.html +16 -0
- dataface/core/render/templates/controls/date.html +16 -0
- dataface/core/render/templates/controls/number.html +19 -0
- dataface/core/render/templates/controls/readonly.html +9 -0
- dataface/core/render/templates/controls/select.html +21 -0
- dataface/core/render/templates/controls/slider.html +22 -0
- dataface/core/render/templates/controls/text.html +16 -0
- dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
- dataface/core/render/templates/scripts/variables.js +976 -0
- dataface/core/render/templates/svg/grid_pattern.svg +3 -0
- dataface/core/render/templates/svg/styles.css +51 -0
- dataface/core/render/terminal.py +311 -0
- dataface/core/render/terminal_charts.py +563 -0
- dataface/core/render/terminal_defaults.yml +2 -0
- dataface/core/render/terminal_layouts.py +299 -0
- dataface/core/render/terminal_text.py +31 -0
- dataface/core/render/text/__init__.py +1 -0
- dataface/core/render/text/case.py +113 -0
- dataface/core/render/text_format.py +129 -0
- dataface/core/render/utils.py +106 -0
- dataface/core/render/variable_controls.py +946 -0
- dataface/core/render/variable_input_refinement.py +140 -0
- dataface/core/render/warnings/__init__.py +15 -0
- dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
- dataface/core/render/warnings/base.py +44 -0
- dataface/core/render/warnings/fanout_risk.py +15 -0
- dataface/core/render/warnings/from_query_diagnostic.py +56 -0
- dataface/core/render/warnings/missing_join_predicate.py +13 -0
- dataface/core/render/warnings/query_parse_error.py +14 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
- dataface/core/render/warnings/reaggregation.py +14 -0
- dataface/core/render/warnings/registry.py +45 -0
- dataface/core/render/warnings/suppression.py +46 -0
- dataface/core/render/warnings/temporal_single_point.py +63 -0
- dataface/core/render/warnings/unreferenced_chart.py +15 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
- dataface/core/render/yaml_format.py +167 -0
- dataface/core/resolve_face.py +195 -0
- dataface/core/schema/__init__.py +0 -0
- dataface/core/schema/guidance.py +151 -0
- dataface/core/scoped_paths.py +59 -0
- dataface/core/serve/__init__.py +14 -0
- dataface/core/serve/bootstrap.py +39 -0
- dataface/core/serve/embedded.py +57 -0
- dataface/core/serve/port.py +129 -0
- dataface/core/serve/server.py +938 -0
- dataface/core/serve/templates/__init__.py +0 -0
- dataface/core/serve/templates/directory.yml +6 -0
- dataface/core/serve/templates/error.html.j2 +217 -0
- dataface/core/utils.py +121 -0
- dataface/core/validate.py +64 -0
- dataface/integrations/__init__.py +0 -0
- dataface/integrations/highlighting.py +351 -0
- dataface/integrations/markdown.py +537 -0
- dataface/py.typed +0 -0
- dataface-0.1.2.dist-info/METADATA +375 -0
- dataface-0.1.2.dist-info/RECORD +455 -0
- dataface-0.1.2.dist-info/WHEEL +4 -0
- dataface-0.1.2.dist-info/entry_points.txt +2 -0
- dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
- mdsvg/__init__.py +168 -0
- mdsvg/fonts.py +656 -0
- mdsvg/images.py +299 -0
- mdsvg/parser.py +629 -0
- mdsvg/playground.py +284 -0
- mdsvg/py.typed +2 -0
- mdsvg/renderer.py +1623 -0
- mdsvg/style.py +355 -0
- mdsvg/types.py +200 -0
- mdsvg/utils.py +86 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
"""Superfences handlers for embedding Dataface boards in markdown.
|
|
2
|
+
|
|
3
|
+
Two handlers for use with ``pymdownx.superfences`` custom fences:
|
|
4
|
+
|
|
5
|
+
- ``fence_dataface`` — render-only: YAML in, SVG/HTML out.
|
|
6
|
+
- ``fence_dataface_example`` — code + render: syntax-highlighted YAML
|
|
7
|
+
alongside rendered output, with playground links.
|
|
8
|
+
|
|
9
|
+
Register in ``mkdocs.yml``::
|
|
10
|
+
|
|
11
|
+
markdown_extensions:
|
|
12
|
+
- pymdownx.superfences:
|
|
13
|
+
custom_fences:
|
|
14
|
+
- name: dataface
|
|
15
|
+
class: dataface
|
|
16
|
+
format: !!python/name:dataface.integrations.markdown.fence_dataface
|
|
17
|
+
- name: dataface-example
|
|
18
|
+
class: dataface-example
|
|
19
|
+
format: !!python/name:dataface.integrations.markdown.fence_dataface_example
|
|
20
|
+
|
|
21
|
+
Then in any ``.md`` page::
|
|
22
|
+
|
|
23
|
+
```dataface
|
|
24
|
+
charts:
|
|
25
|
+
revenue:
|
|
26
|
+
type: bar
|
|
27
|
+
x: product
|
|
28
|
+
y: revenue
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```dataface-example {file=charts/examples/bar-charts/minimum.yml}
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import base64
|
|
38
|
+
import functools
|
|
39
|
+
import hashlib
|
|
40
|
+
import html
|
|
41
|
+
import logging
|
|
42
|
+
import os
|
|
43
|
+
import re
|
|
44
|
+
import zlib
|
|
45
|
+
from collections.abc import Iterator
|
|
46
|
+
from contextlib import contextmanager
|
|
47
|
+
from contextvars import ContextVar
|
|
48
|
+
from pathlib import Path
|
|
49
|
+
from typing import Any, cast
|
|
50
|
+
|
|
51
|
+
import yaml
|
|
52
|
+
|
|
53
|
+
from dataface.core.render.errors import RenderError
|
|
54
|
+
from dataface.core.render.face_api import compile_and_render, render_face
|
|
55
|
+
from dataface.integrations.highlighting import highlight_face_yaml
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger("dataface.integrations.markdown")
|
|
58
|
+
|
|
59
|
+
_PLAYGROUND_DEFAULT_URL = "https://play.dataface.com"
|
|
60
|
+
_VALID_LAYOUTS = {"side-by-side", "stacked", "render-only", "yaml-only"}
|
|
61
|
+
_EXTERNAL_QUERY_NOT_FOUND_RE = re.compile(r"External query file not found", re.I)
|
|
62
|
+
|
|
63
|
+
_PROJECT_DIR_OVERRIDE: ContextVar[Path | None] = ContextVar(
|
|
64
|
+
"dataface_markdown_project_dir", default=None
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@contextmanager
|
|
69
|
+
def project_dir_override(path: Path) -> Iterator[None]:
|
|
70
|
+
"""Pin fence rendering to *path* instead of walking up from ``cwd``."""
|
|
71
|
+
token = _PROJECT_DIR_OVERRIDE.set(path.resolve())
|
|
72
|
+
try:
|
|
73
|
+
yield
|
|
74
|
+
finally:
|
|
75
|
+
_PROJECT_DIR_OVERRIDE.reset(token)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@functools.cache
|
|
79
|
+
def _cached_project_dir_from_cwd() -> Path:
|
|
80
|
+
"""Walk up from ``cwd`` for a ``pyproject.toml`` or ``dbt_project.yml``."""
|
|
81
|
+
cwd = Path.cwd().resolve()
|
|
82
|
+
for parent in [cwd, *cwd.parents]:
|
|
83
|
+
if (parent / "pyproject.toml").exists() or (
|
|
84
|
+
parent / "dbt_project.yml"
|
|
85
|
+
).exists():
|
|
86
|
+
return parent
|
|
87
|
+
raise RuntimeError(
|
|
88
|
+
"Cannot resolve project root: no pyproject.toml or dbt_project.yml "
|
|
89
|
+
"found above cwd"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _resolve_project_dir() -> Path:
|
|
94
|
+
"""Resolve the project root for markdown fence rendering."""
|
|
95
|
+
override = _PROJECT_DIR_OVERRIDE.get()
|
|
96
|
+
if override is not None:
|
|
97
|
+
return override
|
|
98
|
+
return _cached_project_dir_from_cwd()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _highlight_yaml(source: str) -> str:
|
|
102
|
+
"""Syntax-highlight Dataface face YAML using DatafaceYamlLexer.
|
|
103
|
+
|
|
104
|
+
SQL inside ``sql: |`` and ``query: |`` block scalars is delegated to
|
|
105
|
+
SqlLexer so SQL keywords receive keyword styling.
|
|
106
|
+
|
|
107
|
+
Returns HTML wrapped in ``<div class="yaml"><pre><code>...</code></pre></div>``.
|
|
108
|
+
"""
|
|
109
|
+
highlighted = highlight_face_yaml(source)
|
|
110
|
+
return (
|
|
111
|
+
f'<div class="yaml"><pre><code class="language-yaml">'
|
|
112
|
+
f"{highlighted}</code></pre></div>"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _playground_url(
|
|
117
|
+
yaml_source: str,
|
|
118
|
+
base_url: str | None = None,
|
|
119
|
+
) -> str:
|
|
120
|
+
"""Generate a playground URL with compressed YAML payload."""
|
|
121
|
+
resolved_base_url = base_url or os.getenv(
|
|
122
|
+
"DFT_PLAYGROUND_URL", _PLAYGROUND_DEFAULT_URL
|
|
123
|
+
)
|
|
124
|
+
compressed = zlib.compress(yaml_source.encode("utf-8"), level=9)
|
|
125
|
+
encoded = base64.urlsafe_b64encode(compressed).decode("ascii").rstrip("=")
|
|
126
|
+
return f"{resolved_base_url}/?y={encoded}"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _resolve_file(file_path: str, project_dir: Path) -> Path:
|
|
130
|
+
"""Resolve a file= path against project_dir with traversal guard."""
|
|
131
|
+
resolved = (project_dir / file_path).resolve()
|
|
132
|
+
if not resolved.is_relative_to(project_dir.resolve()):
|
|
133
|
+
raise ValueError(f"Path escapes project directory: {file_path}")
|
|
134
|
+
if not resolved.exists():
|
|
135
|
+
raise FileNotFoundError(f"File not found: {resolved}")
|
|
136
|
+
return resolved
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _fence_options(
|
|
140
|
+
options: dict[str, Any],
|
|
141
|
+
kwargs: dict[str, Any],
|
|
142
|
+
) -> dict[str, Any]:
|
|
143
|
+
"""Merge legacy ``options`` with SuperFences attr-list kwargs."""
|
|
144
|
+
merged = dict(options)
|
|
145
|
+
attrs = kwargs.get("attrs") or {}
|
|
146
|
+
for key, value in attrs.items():
|
|
147
|
+
merged.setdefault(key, value)
|
|
148
|
+
return merged
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _shared_examples_dir(project_dir: Path) -> Path | None:
|
|
152
|
+
"""Return ``examples/`` when it ships the shared ``_sources.yaml`` project root."""
|
|
153
|
+
examples_dir = project_dir / "examples"
|
|
154
|
+
if examples_dir.is_dir() and (examples_dir / "_sources.yaml").is_file():
|
|
155
|
+
return examples_dir
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _inline_render_contexts(project_dir: Path) -> list[tuple[Path, Path]]:
|
|
160
|
+
"""Return candidate ``(project_root, base_dir)`` contexts for inline YAML."""
|
|
161
|
+
examples_dir = _shared_examples_dir(project_dir)
|
|
162
|
+
if examples_dir is not None:
|
|
163
|
+
# Docs inline examples resolve shared queries and named sources from here.
|
|
164
|
+
return [(examples_dir, examples_dir), (project_dir, project_dir)]
|
|
165
|
+
candidates: list[tuple[Path, Path]] = [(project_dir, project_dir)]
|
|
166
|
+
fallback = project_dir / "examples"
|
|
167
|
+
if fallback.is_dir():
|
|
168
|
+
candidates.append((fallback, fallback))
|
|
169
|
+
return candidates
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _file_render_contexts(
|
|
173
|
+
face_path: Path, project_dir: Path
|
|
174
|
+
) -> list[tuple[Path, Path]]:
|
|
175
|
+
"""Return candidate ``(project_root, base_dir)`` contexts for file-backed YAML."""
|
|
176
|
+
file_ctx = (project_dir, face_path.parent)
|
|
177
|
+
examples_dir = _shared_examples_dir(project_dir)
|
|
178
|
+
if examples_dir is not None:
|
|
179
|
+
return [(examples_dir, examples_dir), file_ctx]
|
|
180
|
+
candidates: list[tuple[Path, Path]] = [file_ctx]
|
|
181
|
+
fallback = project_dir / "examples"
|
|
182
|
+
if fallback.is_dir():
|
|
183
|
+
candidates.append((fallback, fallback))
|
|
184
|
+
return candidates
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
_SOURCE_NOT_FOUND_RE = re.compile(
|
|
188
|
+
r"Source .+ not found|No source profiles are configured",
|
|
189
|
+
re.I,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _should_retry_with_examples(exc: Exception, current_project_dir: Path) -> bool:
|
|
194
|
+
"""Retry against ``examples/`` for shared docs fixtures the repo root lacks."""
|
|
195
|
+
if current_project_dir.name == "examples":
|
|
196
|
+
return False
|
|
197
|
+
message = str(exc)
|
|
198
|
+
return bool(
|
|
199
|
+
_EXTERNAL_QUERY_NOT_FOUND_RE.search(message)
|
|
200
|
+
or _SOURCE_NOT_FOUND_RE.search(message)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _render_inline_with_fallback(yaml_source: str, project_dir: Path) -> str:
|
|
205
|
+
"""Render inline YAML, retrying against ``examples/`` when docs shared queries need it."""
|
|
206
|
+
last_exc: Exception | None = None
|
|
207
|
+
for candidate_project_dir, candidate_base_dir in _inline_render_contexts(
|
|
208
|
+
project_dir
|
|
209
|
+
):
|
|
210
|
+
try:
|
|
211
|
+
result = compile_and_render(
|
|
212
|
+
yaml_source,
|
|
213
|
+
project_root=candidate_project_dir,
|
|
214
|
+
dbt_project_path=None,
|
|
215
|
+
base_dir=candidate_base_dir,
|
|
216
|
+
format="svg",
|
|
217
|
+
)
|
|
218
|
+
return cast(str, result)
|
|
219
|
+
except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
|
|
220
|
+
last_exc = exc
|
|
221
|
+
if not _should_retry_with_examples(exc, candidate_project_dir):
|
|
222
|
+
raise
|
|
223
|
+
assert last_exc is not None
|
|
224
|
+
raise last_exc
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _render_file_with_fallback(face_path: Path, project_dir: Path) -> str:
|
|
228
|
+
"""Render a face file, retrying against ``examples/`` when docs shared queries need it."""
|
|
229
|
+
last_exc: Exception | None = None
|
|
230
|
+
for candidate_project_dir, candidate_base_dir in _file_render_contexts(
|
|
231
|
+
face_path, project_dir
|
|
232
|
+
):
|
|
233
|
+
try:
|
|
234
|
+
result = render_face(
|
|
235
|
+
face_path,
|
|
236
|
+
format="svg",
|
|
237
|
+
project_dir=candidate_project_dir,
|
|
238
|
+
base_dir=candidate_base_dir,
|
|
239
|
+
)
|
|
240
|
+
return cast(str, result)
|
|
241
|
+
except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
|
|
242
|
+
last_exc = exc
|
|
243
|
+
if not _should_retry_with_examples(exc, candidate_project_dir):
|
|
244
|
+
raise
|
|
245
|
+
assert last_exc is not None
|
|
246
|
+
raise last_exc
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _candidate_external_bases(
|
|
250
|
+
project_dir: Path,
|
|
251
|
+
source_base_dir: Path | None,
|
|
252
|
+
) -> list[Path]:
|
|
253
|
+
"""Return candidate base dirs for resolving external query references."""
|
|
254
|
+
candidates: list[Path] = []
|
|
255
|
+
for candidate in (
|
|
256
|
+
source_base_dir,
|
|
257
|
+
project_dir,
|
|
258
|
+
project_dir / "examples",
|
|
259
|
+
):
|
|
260
|
+
if candidate is None:
|
|
261
|
+
continue
|
|
262
|
+
resolved = candidate.resolve()
|
|
263
|
+
if resolved not in candidates:
|
|
264
|
+
candidates.append(resolved)
|
|
265
|
+
return candidates
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _load_external_query_definition(
|
|
269
|
+
query_ref: str,
|
|
270
|
+
project_dir: Path,
|
|
271
|
+
source_base_dir: Path | None,
|
|
272
|
+
) -> tuple[str, Any]:
|
|
273
|
+
"""Load a raw external query definition for playground-link inlining."""
|
|
274
|
+
file_part, query_name = query_ref.split("#", 1)
|
|
275
|
+
|
|
276
|
+
for candidate_base in _candidate_external_bases(project_dir, source_base_dir):
|
|
277
|
+
candidate_file = (candidate_base / file_part).resolve()
|
|
278
|
+
if not candidate_file.exists():
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
raw = yaml.safe_load(candidate_file.read_text(encoding="utf-8")) or {}
|
|
282
|
+
queries = raw.get("queries", {}) if isinstance(raw, dict) else {}
|
|
283
|
+
if query_name in queries:
|
|
284
|
+
return query_name, queries[query_name]
|
|
285
|
+
raise ValueError(
|
|
286
|
+
f"Query '{query_name}' not found in '{candidate_file}'. "
|
|
287
|
+
f"Available queries: {', '.join(sorted(queries)) if queries else 'none'}"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
raise FileNotFoundError(
|
|
291
|
+
f"External query file not found for playground link: {file_part}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _inline_playground_queries(
|
|
296
|
+
yaml_source: str,
|
|
297
|
+
project_dir: Path,
|
|
298
|
+
source_base_dir: Path | None,
|
|
299
|
+
) -> str:
|
|
300
|
+
"""Rewrite external query refs into local inline queries for playground URLs."""
|
|
301
|
+
doc = yaml.safe_load(yaml_source)
|
|
302
|
+
if not isinstance(doc, dict):
|
|
303
|
+
return yaml_source
|
|
304
|
+
|
|
305
|
+
query_defs = doc.get("queries")
|
|
306
|
+
if query_defs is None:
|
|
307
|
+
query_defs = {}
|
|
308
|
+
doc["queries"] = query_defs
|
|
309
|
+
if not isinstance(query_defs, dict):
|
|
310
|
+
return yaml_source
|
|
311
|
+
|
|
312
|
+
inlined_any = False
|
|
313
|
+
|
|
314
|
+
def visit(node: Any) -> None:
|
|
315
|
+
nonlocal inlined_any
|
|
316
|
+
if isinstance(node, dict):
|
|
317
|
+
query_ref = node.get("query")
|
|
318
|
+
if isinstance(query_ref, str) and "#" in query_ref:
|
|
319
|
+
query_name, query_def = _load_external_query_definition(
|
|
320
|
+
query_ref,
|
|
321
|
+
project_dir,
|
|
322
|
+
source_base_dir,
|
|
323
|
+
)
|
|
324
|
+
existing = query_defs.get(query_name)
|
|
325
|
+
if existing is None:
|
|
326
|
+
query_defs[query_name] = query_def
|
|
327
|
+
elif existing != query_def:
|
|
328
|
+
suffix = 2
|
|
329
|
+
rewritten_name = f"{query_name}_{suffix}"
|
|
330
|
+
while (
|
|
331
|
+
rewritten_name in query_defs
|
|
332
|
+
and query_defs[rewritten_name] != query_def
|
|
333
|
+
):
|
|
334
|
+
suffix += 1
|
|
335
|
+
rewritten_name = f"{query_name}_{suffix}"
|
|
336
|
+
query_defs.setdefault(rewritten_name, query_def)
|
|
337
|
+
query_name = rewritten_name
|
|
338
|
+
node["query"] = query_name
|
|
339
|
+
inlined_any = True
|
|
340
|
+
|
|
341
|
+
for value in node.values():
|
|
342
|
+
visit(value)
|
|
343
|
+
elif isinstance(node, list):
|
|
344
|
+
for item in node:
|
|
345
|
+
visit(item)
|
|
346
|
+
|
|
347
|
+
visit(doc)
|
|
348
|
+
if not inlined_any:
|
|
349
|
+
return yaml_source
|
|
350
|
+
return yaml.safe_dump(doc, sort_keys=False)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _pg_link(pg_url: str, extra_class: str = "") -> str:
|
|
354
|
+
"""Generate a playground link anchor tag."""
|
|
355
|
+
cls = f"playground-link {extra_class}".strip()
|
|
356
|
+
return (
|
|
357
|
+
f'<a href="{pg_url}" target="_blank" class="{cls}" '
|
|
358
|
+
f'rel="noopener noreferrer" title="Try in Playground">'
|
|
359
|
+
f'<span class="playground-icon">\u25b6</span></a>'
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _error_div(message: str) -> str:
|
|
364
|
+
escaped = html.escape(message)
|
|
365
|
+
return (
|
|
366
|
+
f'<div class="dataface-error">'
|
|
367
|
+
f"<strong>Dataface render error:</strong> {escaped}</div>"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# ---------------------------------------------------------------------------
|
|
372
|
+
# fence_dataface — render-only
|
|
373
|
+
# ---------------------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def fence_dataface(
|
|
377
|
+
source: str,
|
|
378
|
+
language: str,
|
|
379
|
+
class_name: str,
|
|
380
|
+
options: dict[str, Any],
|
|
381
|
+
md: Any,
|
|
382
|
+
**kwargs: Any,
|
|
383
|
+
) -> str:
|
|
384
|
+
"""Superfences handler: render YAML to embedded SVG/HTML.
|
|
385
|
+
|
|
386
|
+
Usage::
|
|
387
|
+
|
|
388
|
+
```dataface
|
|
389
|
+
charts:
|
|
390
|
+
c1:
|
|
391
|
+
type: bar
|
|
392
|
+
x: product
|
|
393
|
+
y: revenue
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
```dataface {file=path/to/face.yml}
|
|
397
|
+
```
|
|
398
|
+
"""
|
|
399
|
+
try:
|
|
400
|
+
project_dir = _resolve_project_dir()
|
|
401
|
+
except RuntimeError as exc:
|
|
402
|
+
return _error_div(str(exc))
|
|
403
|
+
|
|
404
|
+
resolved_options = _fence_options(options, kwargs)
|
|
405
|
+
file_opt = resolved_options.get("file")
|
|
406
|
+
try:
|
|
407
|
+
if file_opt:
|
|
408
|
+
resolved = _resolve_file(file_opt, project_dir)
|
|
409
|
+
rendered = _render_file_with_fallback(resolved, project_dir)
|
|
410
|
+
else:
|
|
411
|
+
yaml_source = source.strip()
|
|
412
|
+
if not yaml_source:
|
|
413
|
+
return _error_div(
|
|
414
|
+
"no YAML source provided (use inline content or file= option)"
|
|
415
|
+
)
|
|
416
|
+
rendered = _render_inline_with_fallback(yaml_source, project_dir)
|
|
417
|
+
return f'<div class="dataface-embed">{rendered}</div>'
|
|
418
|
+
except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
|
|
419
|
+
logger.warning("Dataface render failed: %s", exc)
|
|
420
|
+
return _error_div(str(exc))
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# ---------------------------------------------------------------------------
|
|
424
|
+
# fence_dataface_example — code + render
|
|
425
|
+
# ---------------------------------------------------------------------------
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def fence_dataface_example(
|
|
429
|
+
source: str,
|
|
430
|
+
language: str,
|
|
431
|
+
class_name: str,
|
|
432
|
+
options: dict[str, Any],
|
|
433
|
+
md: Any,
|
|
434
|
+
**kwargs: Any,
|
|
435
|
+
) -> str:
|
|
436
|
+
"""Superfences handler: code-plus-render example layout.
|
|
437
|
+
|
|
438
|
+
Options (via ``{key=value}`` in the info string):
|
|
439
|
+
|
|
440
|
+
- ``file=path`` — read YAML from a file instead of inline
|
|
441
|
+
- ``format=side-by-side`` (default), ``stacked``, ``render-only``, ``yaml-only``
|
|
442
|
+
|
|
443
|
+
Usage::
|
|
444
|
+
|
|
445
|
+
```dataface-example {file=charts/examples/bar-charts/minimum.yml}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
```dataface-example {format=stacked}
|
|
449
|
+
charts:
|
|
450
|
+
c1:
|
|
451
|
+
type: bar
|
|
452
|
+
```
|
|
453
|
+
"""
|
|
454
|
+
try:
|
|
455
|
+
project_dir = _resolve_project_dir()
|
|
456
|
+
except RuntimeError as exc:
|
|
457
|
+
return _error_div(str(exc))
|
|
458
|
+
|
|
459
|
+
resolved_options = _fence_options(options, kwargs)
|
|
460
|
+
layout = resolved_options.get("format", "side-by-side")
|
|
461
|
+
if layout not in _VALID_LAYOUTS:
|
|
462
|
+
return _error_div(
|
|
463
|
+
f"unknown format {layout!r}, expected one of: "
|
|
464
|
+
f"{', '.join(sorted(_VALID_LAYOUTS))}"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Resolve source: file or inline
|
|
468
|
+
file_opt = resolved_options.get("file")
|
|
469
|
+
resolved_path: Path | None = None
|
|
470
|
+
if file_opt:
|
|
471
|
+
try:
|
|
472
|
+
resolved_path = _resolve_file(file_opt, project_dir)
|
|
473
|
+
yaml_source = resolved_path.read_text(encoding="utf-8").strip()
|
|
474
|
+
except (FileNotFoundError, ValueError, OSError) as exc:
|
|
475
|
+
return _error_div(str(exc))
|
|
476
|
+
else:
|
|
477
|
+
yaml_source = source.strip()
|
|
478
|
+
|
|
479
|
+
if not yaml_source:
|
|
480
|
+
return _error_div("no YAML source provided")
|
|
481
|
+
|
|
482
|
+
example_id = hashlib.md5(yaml_source.encode(), usedforsecurity=False).hexdigest()[
|
|
483
|
+
:12
|
|
484
|
+
]
|
|
485
|
+
playground_yaml = yaml_source
|
|
486
|
+
try:
|
|
487
|
+
playground_yaml = _inline_playground_queries(
|
|
488
|
+
yaml_source,
|
|
489
|
+
project_dir,
|
|
490
|
+
resolved_path.parent if resolved_path else project_dir,
|
|
491
|
+
)
|
|
492
|
+
except (FileNotFoundError, ValueError, OSError, yaml.YAMLError) as exc:
|
|
493
|
+
logger.warning("Playground payload rewrite failed: %s", exc)
|
|
494
|
+
pg_url = _playground_url(playground_yaml)
|
|
495
|
+
|
|
496
|
+
# yaml-only: no rendering
|
|
497
|
+
if layout == "yaml-only":
|
|
498
|
+
highlighted = _highlight_yaml(yaml_source)
|
|
499
|
+
return (
|
|
500
|
+
f'<div class="example-container example-yaml-only" '
|
|
501
|
+
f'id="example-container-{example_id}">\n'
|
|
502
|
+
f'<div class="example-code">\n{highlighted}\n'
|
|
503
|
+
f"{_pg_link(pg_url, 'playground-link-inline')}\n"
|
|
504
|
+
f"</div>\n</div>"
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Render the face
|
|
508
|
+
rendered_html: str
|
|
509
|
+
try:
|
|
510
|
+
if resolved_path:
|
|
511
|
+
rendered = _render_file_with_fallback(resolved_path, project_dir)
|
|
512
|
+
else:
|
|
513
|
+
rendered = _render_inline_with_fallback(yaml_source, project_dir)
|
|
514
|
+
rendered_html = f'<div class="rendered-dashboard">{rendered}</div>'
|
|
515
|
+
except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
|
|
516
|
+
logger.warning("Dataface render failed: %s", exc)
|
|
517
|
+
rendered_html = _error_div(str(exc))
|
|
518
|
+
|
|
519
|
+
# render-only: no code panel
|
|
520
|
+
if layout == "render-only":
|
|
521
|
+
return (
|
|
522
|
+
f'<div class="example-container example-render-only" '
|
|
523
|
+
f'id="example-container-{example_id}">\n'
|
|
524
|
+
f'<div class="example-render" id="example-render-{example_id}">\n'
|
|
525
|
+
f"{rendered_html}\n{_pg_link(pg_url)}\n</div>\n</div>"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# side-by-side (default) or stacked
|
|
529
|
+
highlighted = _highlight_yaml(yaml_source)
|
|
530
|
+
layout_class = " example-stacked" if layout == "stacked" else ""
|
|
531
|
+
return (
|
|
532
|
+
f'<div class="example-container{layout_class}" '
|
|
533
|
+
f'id="example-container-{example_id}">\n'
|
|
534
|
+
f'<div class="example-code">\n{highlighted}\n</div>\n'
|
|
535
|
+
f'<div class="example-render" id="example-render-{example_id}">\n'
|
|
536
|
+
f"{rendered_html}\n{_pg_link(pg_url)}\n</div>\n</div>"
|
|
537
|
+
)
|
dataface/py.typed
ADDED
|
File without changes
|