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,543 @@
|
|
|
1
|
+
"""Normalization module for transforming input types to compiled types.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE (Step 3 of 4)
|
|
4
|
+
Purpose: Transform AuthoredFace input types to Face output types.
|
|
5
|
+
|
|
6
|
+
Entry Points:
|
|
7
|
+
- normalize_face(face: AuthoredFace) -> Face
|
|
8
|
+
|
|
9
|
+
This is the core transformation step of compilation:
|
|
10
|
+
1. Resolve all chart references (queries, extends, partials)
|
|
11
|
+
2. Resolve remote dataface references
|
|
12
|
+
3. Generate unique IDs for all entities
|
|
13
|
+
4. Apply defaults (title from ID, empty description)
|
|
14
|
+
5. Transform input types → compiled types
|
|
15
|
+
6. Create unified layout structure
|
|
16
|
+
|
|
17
|
+
After this step, all references are resolved and downstream code can
|
|
18
|
+
rely on guaranteed field presence.
|
|
19
|
+
|
|
20
|
+
Cross-file Query References:
|
|
21
|
+
Charts can reference queries from external files using the syntax:
|
|
22
|
+
`query: path/to/file.yml#query_name`
|
|
23
|
+
|
|
24
|
+
The path is resolved relative to the current file's directory.
|
|
25
|
+
External queries are loaded once and cached in the query registry.
|
|
26
|
+
|
|
27
|
+
Dependencies:
|
|
28
|
+
- .models.face.authored (AuthoredFace)
|
|
29
|
+
- .models.face.compiled (Face, Layout)
|
|
30
|
+
- .models.chart.compiled (Chart)
|
|
31
|
+
- .models.query.compiled (AnyQuery)
|
|
32
|
+
- .jinja (resolve_jinja_template)
|
|
33
|
+
- .errors (ReferenceError, CompilationError)
|
|
34
|
+
|
|
35
|
+
See also:
|
|
36
|
+
- compile/validator.py: Previous step
|
|
37
|
+
- compile/sizing.py: Next step
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import re
|
|
41
|
+
from datetime import datetime, timezone
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
from typing import Any
|
|
44
|
+
|
|
45
|
+
from dataface.core.compile.errors import CompilationError
|
|
46
|
+
from dataface.core.compile.models.chart.compiled import Chart
|
|
47
|
+
from dataface.core.compile.models.face.authored import AuthoredFace
|
|
48
|
+
from dataface.core.compile.models.face.compiled import (
|
|
49
|
+
Face,
|
|
50
|
+
VariableValues,
|
|
51
|
+
)
|
|
52
|
+
from dataface.core.compile.models.query.compiled import AnyQuery
|
|
53
|
+
from dataface.core.compile.models.style.authored import StylePatch
|
|
54
|
+
from dataface.core.compile.models.style.merged import (
|
|
55
|
+
MergedStyle,
|
|
56
|
+
resolve_style,
|
|
57
|
+
)
|
|
58
|
+
from dataface.core.compile.models.variable.authored import Variable
|
|
59
|
+
from dataface.core.compile.normalize_charts import (
|
|
60
|
+
_collect_charts_from_layout,
|
|
61
|
+
_normalize_chart,
|
|
62
|
+
)
|
|
63
|
+
from dataface.core.compile.normalize_layout import build_unified_layout
|
|
64
|
+
from dataface.core.compile.normalize_queries import (
|
|
65
|
+
normalize_query, # noqa: F401 — re-exported (used by compiler.py, tests)
|
|
66
|
+
)
|
|
67
|
+
from dataface.core.compile.normalize_variables import (
|
|
68
|
+
_build_variable_registry,
|
|
69
|
+
_compute_variable_dependencies,
|
|
70
|
+
_detect_variable_input_type,
|
|
71
|
+
_expand_chart_variable_dependencies,
|
|
72
|
+
_generate_layout_variables,
|
|
73
|
+
_promote_inline_option_queries,
|
|
74
|
+
_validate_variable_references,
|
|
75
|
+
_validate_variable_value,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def compile_board_resolved_style(
|
|
80
|
+
face_style: StylePatch | None,
|
|
81
|
+
parent_resolved: MergedStyle | None,
|
|
82
|
+
theme_name: str | None = None,
|
|
83
|
+
) -> MergedStyle:
|
|
84
|
+
"""Build authoritative MergedStyle for this board scope.
|
|
85
|
+
|
|
86
|
+
Cascade:
|
|
87
|
+
1. Theme defaults (theme_name → compiled theme → resolve_style)
|
|
88
|
+
2. Parent semantic tokens (muted, accent) — propagate tonal identity
|
|
89
|
+
3. Face's style: block as StylePatch — all fields cascade normally
|
|
90
|
+
"""
|
|
91
|
+
from dataface.core.compile.config import get_compiled_theme, get_config
|
|
92
|
+
|
|
93
|
+
base = get_compiled_theme(theme_name) if theme_name else get_config().style
|
|
94
|
+
|
|
95
|
+
parent_cascade = None
|
|
96
|
+
if parent_resolved is not None:
|
|
97
|
+
parent_cascade = StylePatch.model_validate(
|
|
98
|
+
{"muted": parent_resolved.muted, "accent": parent_resolved.accent}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if face_style is None:
|
|
102
|
+
return parent_resolved or resolve_style(base)
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
resolve_style(base, parent_cascade, face_style)
|
|
106
|
+
if parent_cascade is not None
|
|
107
|
+
else resolve_style(base, face_style)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_cascade_context(
|
|
112
|
+
face: AuthoredFace, parent_context: dict[str, Any]
|
|
113
|
+
) -> dict[str, Any]:
|
|
114
|
+
"""Build the cascade dict from Cascade-annotated fields on AuthoredFace.
|
|
115
|
+
|
|
116
|
+
Iterates model_fields, detects Cascade markers, and resolves each cascade
|
|
117
|
+
key's effective value (child's explicit value, or parent's propagated value).
|
|
118
|
+
Fields sharing a context key (e.g. source + sources → default_source) are
|
|
119
|
+
deduplicated: the first Cascade with that key wins, calling via= once.
|
|
120
|
+
"""
|
|
121
|
+
from dataface.core.compile.models.markers import Cascade
|
|
122
|
+
|
|
123
|
+
result: dict[str, Any] = {}
|
|
124
|
+
seen_keys: set[str] = set()
|
|
125
|
+
|
|
126
|
+
for field_name, field_info in AuthoredFace.model_fields.items():
|
|
127
|
+
cascade = next(
|
|
128
|
+
(m for m in (field_info.metadata or []) if isinstance(m, Cascade)), None
|
|
129
|
+
)
|
|
130
|
+
if cascade is None:
|
|
131
|
+
continue
|
|
132
|
+
key = cascade.key or field_name
|
|
133
|
+
if key in seen_keys:
|
|
134
|
+
continue
|
|
135
|
+
seen_keys.add(key)
|
|
136
|
+
value = (
|
|
137
|
+
getattr(face, cascade.via)() if cascade.via else getattr(face, field_name)
|
|
138
|
+
)
|
|
139
|
+
result[key] = value or parent_context.get(key)
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def sync_face_resolved_style(
|
|
145
|
+
face: Any, parent_resolved: MergedStyle | None = None
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Re-resolve face.resolved_style from face.theme and recurse into nested faces.
|
|
148
|
+
|
|
149
|
+
Passes the freshly-computed resolved style as parent_resolved to each
|
|
150
|
+
nested sub-board so the parent→child semantic color cascade (muted, accent)
|
|
151
|
+
is preserved. Call this when face.theme has been mutated after compile.
|
|
152
|
+
"""
|
|
153
|
+
face.resolved_style = compile_board_resolved_style(
|
|
154
|
+
face.authored_style, parent_resolved, theme_name=face.theme
|
|
155
|
+
)
|
|
156
|
+
for item in face.layout.items or []:
|
|
157
|
+
if item.face is not None:
|
|
158
|
+
sync_face_resolved_style(item.face, face.resolved_style)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _propagate_resolved_style(
|
|
162
|
+
layout: Any,
|
|
163
|
+
parent_resolved: MergedStyle,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Walk the layout tree and set resolved_style on nested Faces."""
|
|
166
|
+
from dataface.core.compile.models.face.compiled import Layout
|
|
167
|
+
|
|
168
|
+
if not isinstance(layout, Layout):
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
for item in layout.items:
|
|
172
|
+
if item.face is not None:
|
|
173
|
+
item.face.resolved_style = compile_board_resolved_style(
|
|
174
|
+
item.face.authored_style,
|
|
175
|
+
parent_resolved,
|
|
176
|
+
theme_name=item.face.theme,
|
|
177
|
+
)
|
|
178
|
+
_propagate_resolved_style(item.face.layout, item.face.resolved_style)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def slugify(text: str) -> str:
|
|
182
|
+
"""Convert text to a URL-safe slug.
|
|
183
|
+
|
|
184
|
+
"Raw Data" → "raw_data"
|
|
185
|
+
"My Tab!" → "my_tab"
|
|
186
|
+
"""
|
|
187
|
+
slug = re.sub(r"[^\w\s-]", "", text.lower().strip())
|
|
188
|
+
return re.sub(r"[\s-]+", "_", slug).strip("_")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def normalize_face(
|
|
192
|
+
face: AuthoredFace,
|
|
193
|
+
face_id: str | None = None,
|
|
194
|
+
parent_context: dict[str, Any] | None = None,
|
|
195
|
+
query_registry: dict[str, AnyQuery] | None = None,
|
|
196
|
+
chart_registry: dict[str, Any] | None = None,
|
|
197
|
+
depth: int = 0,
|
|
198
|
+
base_path: Path | None = None,
|
|
199
|
+
parent_level: int = 0,
|
|
200
|
+
) -> Face:
|
|
201
|
+
"""Normalize a AuthoredFace into a Face with guaranteed structure.
|
|
202
|
+
|
|
203
|
+
Stage: COMPILE (Step 3 of 4: Normalization)
|
|
204
|
+
|
|
205
|
+
This is the core transformation from user-provided YAML structure to the
|
|
206
|
+
internal compiled representation. After this step, all references are resolved,
|
|
207
|
+
IDs are assigned, and downstream code can rely on guaranteed field presence.
|
|
208
|
+
|
|
209
|
+
Normalization performs:
|
|
210
|
+
- Resolve all chart references (queries, extends, partials)
|
|
211
|
+
- Generate unique IDs for all entities
|
|
212
|
+
- Apply defaults (title from ID, empty description)
|
|
213
|
+
- Transform input types to compiled types
|
|
214
|
+
- Create unified Layout structure
|
|
215
|
+
- Propagate default source to queries without explicit source
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
face: Input AuthoredFace from parsing/validation step.
|
|
219
|
+
face_id: Optional explicit ID for this face
|
|
220
|
+
parent_context: Context from parent face (for nested faces)
|
|
221
|
+
query_registry: Complete query registry for reference resolution
|
|
222
|
+
chart_registry: Complete chart registry for reference resolution
|
|
223
|
+
depth: Structural nesting depth (for cycle detection and root detection)
|
|
224
|
+
base_path: Base path for resolving external file references
|
|
225
|
+
parent_level: Semantic heading level of the parent face. The current
|
|
226
|
+
face's level = parent_level + (1 if face.title else 0).
|
|
227
|
+
Root call passes 0 so a titled root face gets level=1.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Face with guarantees:
|
|
231
|
+
- All fields required by compiled types are present
|
|
232
|
+
- All references resolved to actual objects
|
|
233
|
+
- All IDs are unique and deterministic
|
|
234
|
+
- Layout is unified (rows/cols/grid/tabs → Layout type)
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
ReferenceError: When a chart references an undefined query
|
|
238
|
+
CompilationError: When normalization fails
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
>>> face = parse_yaml(yaml_content)
|
|
242
|
+
>>> errors = validate_face(face)
|
|
243
|
+
>>> if not errors:
|
|
244
|
+
... compiled = normalize_face(face)
|
|
245
|
+
... print(compiled.charts['revenue'].id) # Guaranteed to exist
|
|
246
|
+
'revenue'
|
|
247
|
+
"""
|
|
248
|
+
# Prevent infinite recursion
|
|
249
|
+
MAX_DEPTH = 50
|
|
250
|
+
if depth > MAX_DEPTH:
|
|
251
|
+
raise CompilationError(f"Maximum nesting depth ({MAX_DEPTH}) exceeded")
|
|
252
|
+
|
|
253
|
+
if depth > 0 and face.card_gap:
|
|
254
|
+
raise CompilationError(
|
|
255
|
+
"card_gap can only be set on the root face, not on nested faces"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Root-level faces must have renderable content (layout or text). A title-only
|
|
259
|
+
# face is valid at depth>0 (AuthoredFace.validate_layout allows it for section
|
|
260
|
+
# headers and style wrappers), but is not renderable as a standalone dashboard.
|
|
261
|
+
if (
|
|
262
|
+
depth == 0
|
|
263
|
+
and not face.text
|
|
264
|
+
and all(f is None for f in (face.rows, face.cols, face.grid, face.tabs))
|
|
265
|
+
):
|
|
266
|
+
raise CompilationError("Face must have at least one layout type or text")
|
|
267
|
+
|
|
268
|
+
parent_context = parent_context or {}
|
|
269
|
+
if base_path is None:
|
|
270
|
+
base_path = parent_context.get("base_path")
|
|
271
|
+
|
|
272
|
+
# ════════════════════════════════════════════════════════════════════
|
|
273
|
+
# STEP 1: Setup — default source, registries, face ID
|
|
274
|
+
# ════════════════════════════════════════════════════════════════════
|
|
275
|
+
_cascade = get_cascade_context(face, parent_context)
|
|
276
|
+
default_source = _cascade.get("default_source")
|
|
277
|
+
|
|
278
|
+
# Build registries if not provided (allows direct normalize_face() calls in tests)
|
|
279
|
+
if query_registry is None:
|
|
280
|
+
from dataface.core.compile.compiler import build_query_registry
|
|
281
|
+
|
|
282
|
+
query_registry = build_query_registry(
|
|
283
|
+
face, base_dir=base_path, default_source=default_source
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if chart_registry is None:
|
|
287
|
+
from dataface.core.compile.compiler import build_chart_registry
|
|
288
|
+
|
|
289
|
+
chart_registry = build_chart_registry(face, base_dir=base_path)
|
|
290
|
+
|
|
291
|
+
if not face_id:
|
|
292
|
+
face_id = _generate_face_id(face, depth, parent_context.get("parent_id"))
|
|
293
|
+
|
|
294
|
+
# ════════════════════════════════════════════════════════════════════
|
|
295
|
+
# STEP 2: Variables
|
|
296
|
+
# ════════════════════════════════════════════════════════════════════
|
|
297
|
+
# Variable names are globally unique across the face tree; the renderer
|
|
298
|
+
# builds the global registry at render time. Here we collect local defs only.
|
|
299
|
+
local_variables: dict[str, Variable] = {}
|
|
300
|
+
if face.variables:
|
|
301
|
+
for var_name, var_def in face.variables.items():
|
|
302
|
+
if isinstance(var_def, Variable):
|
|
303
|
+
local_variables[var_name] = var_def
|
|
304
|
+
else:
|
|
305
|
+
# VariableRef — cross-file reference (e.g., "file.variables.var_name")
|
|
306
|
+
from dataface.core.compile.compiler import load_from_reference
|
|
307
|
+
|
|
308
|
+
local_variables[var_name] = load_from_reference(
|
|
309
|
+
var_def, base_dir=base_path
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Auto-detect input type when set to "auto" (the default)
|
|
313
|
+
for var in local_variables.values():
|
|
314
|
+
was_auto = var.input == "auto"
|
|
315
|
+
var.input = _detect_variable_input_type(var)
|
|
316
|
+
# Only flag select as refinable — multiselect has multi-value semantics
|
|
317
|
+
# that can't map to datepicker/checkbox/slider, and strong structural
|
|
318
|
+
# signals (bool → checkbox, min/max → slider) shouldn't be overridden.
|
|
319
|
+
if was_auto and var.input == "select":
|
|
320
|
+
var.input_auto_detected = True
|
|
321
|
+
|
|
322
|
+
# Promote inline SQL in variable options.query to synthetic named queries
|
|
323
|
+
_promote_inline_option_queries(local_variables, query_registry, default_source)
|
|
324
|
+
|
|
325
|
+
# Compute cascading dropdown dependencies from Jinja variable references
|
|
326
|
+
_compute_variable_dependencies(local_variables, query_registry)
|
|
327
|
+
|
|
328
|
+
# Validate and collect local defaults (global defaults built by renderer)
|
|
329
|
+
variable_defaults: VariableValues = {}
|
|
330
|
+
for var_name, var in local_variables.items():
|
|
331
|
+
if var.default is not None:
|
|
332
|
+
_validate_variable_value(var_name, var, var.default)
|
|
333
|
+
variable_defaults[var_name] = var.default
|
|
334
|
+
|
|
335
|
+
# ════════════════════════════════════════════════════════════════════
|
|
336
|
+
# STEP 3: Queries
|
|
337
|
+
# ════════════════════════════════════════════════════════════════════
|
|
338
|
+
# Extract locally defined queries from the global registry (for Face.queries).
|
|
339
|
+
local_queries: dict[str, AnyQuery] = {}
|
|
340
|
+
if face.queries:
|
|
341
|
+
for query_name in face.queries:
|
|
342
|
+
if query_name in query_registry:
|
|
343
|
+
local_queries[query_name] = query_registry[query_name]
|
|
344
|
+
else:
|
|
345
|
+
raise CompilationError(
|
|
346
|
+
f"Query '{query_name}' not found in registry. "
|
|
347
|
+
"This may indicate a compilation error."
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# ════════════════════════════════════════════════════════════════════
|
|
351
|
+
# STEP 4: Charts
|
|
352
|
+
# ════════════════════════════════════════════════════════════════════
|
|
353
|
+
# Normalize all charts from the global registry — layout resolution needs
|
|
354
|
+
# all of them by name. Then extract locally defined ones for Face.charts.
|
|
355
|
+
normalized_charts: dict[str, Chart] = {}
|
|
356
|
+
for chart_name, chart_def in chart_registry.items():
|
|
357
|
+
normalized_charts[chart_name] = _normalize_chart(
|
|
358
|
+
chart_name,
|
|
359
|
+
chart_def,
|
|
360
|
+
query_registry,
|
|
361
|
+
base_path,
|
|
362
|
+
default_source,
|
|
363
|
+
source_path=f"charts.{chart_name}",
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
local_charts: dict[str, Chart] = {}
|
|
367
|
+
if face.charts:
|
|
368
|
+
for chart_name in face.charts:
|
|
369
|
+
if chart_name in normalized_charts:
|
|
370
|
+
local_charts[chart_name] = normalized_charts[chart_name]
|
|
371
|
+
else:
|
|
372
|
+
raise CompilationError(
|
|
373
|
+
f"Chart '{chart_name}' not found in registry. "
|
|
374
|
+
"Charts must be defined in the face tree or imported."
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
charts = local_charts
|
|
378
|
+
|
|
379
|
+
# Expand transitive variable dependencies (A→B→C means chart depends on A, B, C)
|
|
380
|
+
_expand_chart_variable_dependencies(charts, local_variables)
|
|
381
|
+
|
|
382
|
+
# ════════════════════════════════════════════════════════════════════
|
|
383
|
+
# STEP 5: Style & theme
|
|
384
|
+
# ════════════════════════════════════════════════════════════════════
|
|
385
|
+
# Faces should inherit the same effective theme choice as their charts so
|
|
386
|
+
# nested boards resolve typography/palette consistently. The face canvas
|
|
387
|
+
# background itself is intentionally not copied from chart themes: board
|
|
388
|
+
# chrome follows face/style defaults (white in Playground review mode),
|
|
389
|
+
# while chart surfaces keep the theme-owned canvas/background colors.
|
|
390
|
+
# If YAML does not specify a theme explicitly, inherit the parent theme or
|
|
391
|
+
# fall back to the configured default theme.
|
|
392
|
+
from dataface.core.compile.config import get_default_theme_name
|
|
393
|
+
|
|
394
|
+
theme = _cascade.get("theme") or get_default_theme_name()
|
|
395
|
+
|
|
396
|
+
# Compile board-scoped resolved style before layout so nested faces can inherit
|
|
397
|
+
resolved_style = compile_board_resolved_style(
|
|
398
|
+
face.style,
|
|
399
|
+
parent_context.get("resolved_style"),
|
|
400
|
+
theme_name=theme,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# ════════════════════════════════════════════════════════════════════
|
|
404
|
+
# STEP 6: Layout
|
|
405
|
+
# ════════════════════════════════════════════════════════════════════
|
|
406
|
+
# parent_variables: inherited compile-time variables (foreach loop vars) from
|
|
407
|
+
# parent + local variable defaults. These are needed by foreach to generate
|
|
408
|
+
# static per-iteration content at compile time. Interactive variable defaults
|
|
409
|
+
# are included here so foreach items get them baked in (correct, since foreach
|
|
410
|
+
# produces static faces). For regular nested boards, _resolve_dict_templates is
|
|
411
|
+
# only called when _resolve_jinja=True, so interactive defaults don't get baked
|
|
412
|
+
# into regular board content at compile time.
|
|
413
|
+
parent_variables: dict[str, Any] = {}
|
|
414
|
+
if parent_context.get("variables"):
|
|
415
|
+
parent_variables.update(parent_context["variables"])
|
|
416
|
+
for var_name, var in local_variables.items():
|
|
417
|
+
if var.default is not None:
|
|
418
|
+
parent_variables[var_name] = var.default
|
|
419
|
+
|
|
420
|
+
# Semantic heading level: count only titled ancestors.
|
|
421
|
+
# Bare wrappers (title is empty/None) do not advance the level counter.
|
|
422
|
+
# If style.title.level is an integer override (from theme or face YAML), it wins;
|
|
423
|
+
# that value also becomes the parent_level for descendants (cascade as if structural truth).
|
|
424
|
+
style_level_override = resolved_style.title.level
|
|
425
|
+
if isinstance(style_level_override, int):
|
|
426
|
+
this_level = style_level_override
|
|
427
|
+
else:
|
|
428
|
+
this_level = parent_level + (1 if face.title else 0)
|
|
429
|
+
|
|
430
|
+
_resolve_jinja = parent_context.get("_resolve_jinja", False)
|
|
431
|
+
layout = build_unified_layout(
|
|
432
|
+
face,
|
|
433
|
+
normalized_charts,
|
|
434
|
+
query_registry,
|
|
435
|
+
face_id,
|
|
436
|
+
depth,
|
|
437
|
+
base_path,
|
|
438
|
+
default_source,
|
|
439
|
+
chart_registry,
|
|
440
|
+
theme,
|
|
441
|
+
parent_variables,
|
|
442
|
+
_resolve_jinja=_resolve_jinja,
|
|
443
|
+
resolved_style=resolved_style,
|
|
444
|
+
parent_level=this_level,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
_propagate_resolved_style(layout, resolved_style)
|
|
448
|
+
|
|
449
|
+
# Auto-generate hidden variables for tabs/details widgets
|
|
450
|
+
auto_variables = _generate_layout_variables(layout)
|
|
451
|
+
local_variables.update(auto_variables)
|
|
452
|
+
for var_name, var in auto_variables.items():
|
|
453
|
+
if var.default is not None:
|
|
454
|
+
variable_defaults[var_name] = var.default
|
|
455
|
+
|
|
456
|
+
# Collect inline charts from layout so all charts are accessible via face.charts
|
|
457
|
+
# without traversing the layout tree.
|
|
458
|
+
all_charts = dict(charts)
|
|
459
|
+
_collect_charts_from_layout(layout, all_charts)
|
|
460
|
+
charts = all_charts
|
|
461
|
+
|
|
462
|
+
# ════════════════════════════════════════════════════════════════════
|
|
463
|
+
# STEP 7: Assemble Face
|
|
464
|
+
# ════════════════════════════════════════════════════════════════════
|
|
465
|
+
|
|
466
|
+
compiled_face = Face(
|
|
467
|
+
id=face_id,
|
|
468
|
+
title=face.title or "",
|
|
469
|
+
description=face.description or "",
|
|
470
|
+
tags=face.tags or [],
|
|
471
|
+
text=face.text or "",
|
|
472
|
+
variables=local_variables, # Local variables for UI controls
|
|
473
|
+
queries=local_queries,
|
|
474
|
+
charts=charts,
|
|
475
|
+
layout=layout,
|
|
476
|
+
variable_defaults=variable_defaults, # Local variable defaults
|
|
477
|
+
card_gap=face.card_gap,
|
|
478
|
+
authored_style=face.style,
|
|
479
|
+
theme=theme,
|
|
480
|
+
resolved_style=resolved_style,
|
|
481
|
+
level=this_level,
|
|
482
|
+
meta={
|
|
483
|
+
"compiled_at": datetime.now(timezone.utc).isoformat(),
|
|
484
|
+
"version": "0.1.0",
|
|
485
|
+
},
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# ════════════════════════════════════════════════════════════════════
|
|
489
|
+
# STEP 8: Variable registry (root face only)
|
|
490
|
+
# ════════════════════════════════════════════════════════════════════
|
|
491
|
+
if depth == 0:
|
|
492
|
+
compiled_face.variable_registry = _build_variable_registry(compiled_face)
|
|
493
|
+
|
|
494
|
+
# Pre-compute global defaults to avoid repeated comprehension at render time
|
|
495
|
+
compiled_face.variable_defaults = {
|
|
496
|
+
name: var.default
|
|
497
|
+
for name, var in compiled_face.variable_registry.items()
|
|
498
|
+
if var.default is not None
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
# Validate that all referenced variables are defined
|
|
502
|
+
warnings = _validate_variable_references(compiled_face)
|
|
503
|
+
if warnings:
|
|
504
|
+
compiled_face.meta["variable_warnings"] = warnings
|
|
505
|
+
|
|
506
|
+
# ════════════════════════════════════════════════════════════════════
|
|
507
|
+
# STEP 9: Chart focus (root face only)
|
|
508
|
+
# ════════════════════════════════════════════════════════════════════
|
|
509
|
+
# Narrows the face to a single chart and its dependent variables.
|
|
510
|
+
if depth == 0 and face.chart_focus:
|
|
511
|
+
from dataface.core.compile.chart_focus import focus_on_chart
|
|
512
|
+
|
|
513
|
+
compiled_face = focus_on_chart(compiled_face, face.chart_focus)
|
|
514
|
+
|
|
515
|
+
return compiled_face
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _generate_face_id(
|
|
519
|
+
face: AuthoredFace,
|
|
520
|
+
depth: int,
|
|
521
|
+
parent_id: str | None = None,
|
|
522
|
+
) -> str:
|
|
523
|
+
"""Generate a unique face ID.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
face: AuthoredFace to generate ID for
|
|
527
|
+
depth: Nesting depth
|
|
528
|
+
parent_id: Parent face ID if nested
|
|
529
|
+
|
|
530
|
+
Returns:
|
|
531
|
+
Unique face ID
|
|
532
|
+
"""
|
|
533
|
+
if face.id:
|
|
534
|
+
return face.id
|
|
535
|
+
|
|
536
|
+
if parent_id:
|
|
537
|
+
return f"{parent_id}_nested{depth}"
|
|
538
|
+
|
|
539
|
+
if face.title:
|
|
540
|
+
slug = slugify(face.title)
|
|
541
|
+
return slug or "untitled_face"
|
|
542
|
+
|
|
543
|
+
return "untitled_face"
|