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,659 @@
|
|
|
1
|
+
"""Authored face types — YAML input representation.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE (Input)
|
|
4
|
+
Purpose: Types that map directly to the YAML face schema.
|
|
5
|
+
|
|
6
|
+
Contains the top-level AuthoredFace and its layout sub-shapes, plus
|
|
7
|
+
LayoutType for face layout variants.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Annotated, Any, Literal
|
|
14
|
+
|
|
15
|
+
from pydantic import (
|
|
16
|
+
BaseModel,
|
|
17
|
+
BeforeValidator,
|
|
18
|
+
ConfigDict,
|
|
19
|
+
Discriminator,
|
|
20
|
+
Field,
|
|
21
|
+
Tag,
|
|
22
|
+
model_validator,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from dataface.core.compile.models.chart.authored import AuthoredChart
|
|
26
|
+
from dataface.core.compile.models.markers import Cascade
|
|
27
|
+
from dataface.core.compile.models.query.authored import AuthoredQuery
|
|
28
|
+
from dataface.core.compile.models.refs import (
|
|
29
|
+
ChartRef,
|
|
30
|
+
QueryRef,
|
|
31
|
+
VariableRef,
|
|
32
|
+
normalize_query_value,
|
|
33
|
+
)
|
|
34
|
+
from dataface.core.compile.models.source import SourceConfig
|
|
35
|
+
from dataface.core.compile.models.style.authored import StylePatch
|
|
36
|
+
from dataface.core.compile.models.variable.authored import SingleRowBoolProbe, Variable
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _ref_or_inline(v: object, ref_cls: type) -> str:
|
|
40
|
+
"""Classify a union value as '@ref' or '@inline'.
|
|
41
|
+
|
|
42
|
+
Returns '@ref' when v is already a typed ref instance, a bare cross-file ref
|
|
43
|
+
string, or a dict that contains a 'ref' key. Returns '@inline' otherwise.
|
|
44
|
+
|
|
45
|
+
Tag names start with '@' to prevent collision with user-chosen YAML keys
|
|
46
|
+
('ref', 'inline') in Pydantic error locs.
|
|
47
|
+
|
|
48
|
+
Routing any dict with a 'ref' key to the ref branch produces a clean
|
|
49
|
+
'extra inputs not permitted' error from the ref model (extra="forbid")
|
|
50
|
+
rather than an opaque 'extra inputs' error on the inline model.
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(v, ref_cls):
|
|
53
|
+
return "@ref"
|
|
54
|
+
if isinstance(v, str):
|
|
55
|
+
return "@ref"
|
|
56
|
+
if isinstance(v, dict) and "ref" in v:
|
|
57
|
+
return "@ref"
|
|
58
|
+
return "@inline"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _var_discriminator(v: object) -> str:
|
|
62
|
+
return _ref_or_inline(v, VariableRef)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _query_discriminator(v: object) -> str:
|
|
66
|
+
return _ref_or_inline(v, QueryRef)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _chart_discriminator(v: object) -> str:
|
|
70
|
+
return _ref_or_inline(v, ChartRef)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
VariableOrRef = Annotated[
|
|
74
|
+
Annotated[Variable, Tag("@inline")] | Annotated[VariableRef, Tag("@ref")],
|
|
75
|
+
Discriminator(_var_discriminator),
|
|
76
|
+
]
|
|
77
|
+
QueryOrRef = Annotated[
|
|
78
|
+
Annotated[AuthoredQuery, Tag("@inline")] | Annotated[QueryRef, Tag("@ref")],
|
|
79
|
+
Discriminator(_query_discriminator),
|
|
80
|
+
]
|
|
81
|
+
ChartOrRef = Annotated[
|
|
82
|
+
Annotated[AuthoredChart, Tag("@inline")] | Annotated[ChartRef, Tag("@ref")],
|
|
83
|
+
Discriminator(_chart_discriminator),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# ============================================================================
|
|
87
|
+
# LAYOUT SUB-SHAPES
|
|
88
|
+
# ============================================================================
|
|
89
|
+
|
|
90
|
+
_CHARTREF_KEYS = frozenset({"chart", "width", "height", "description", "visible"})
|
|
91
|
+
_CHARTREF_MSG = (
|
|
92
|
+
"Layout item uses removed `chart:` reference form. "
|
|
93
|
+
"Use a bare chart name (`- chart_name`) or a nested face wrapper:\n"
|
|
94
|
+
" - height: 600\n"
|
|
95
|
+
" rows:\n"
|
|
96
|
+
" - chart_name"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _reject_chartref_dict(v: Any) -> Any:
|
|
101
|
+
if (
|
|
102
|
+
isinstance(v, dict)
|
|
103
|
+
and isinstance(v.get("chart"), str)
|
|
104
|
+
and v.keys() <= _CHARTREF_KEYS
|
|
105
|
+
):
|
|
106
|
+
raise ValueError(_CHARTREF_MSG)
|
|
107
|
+
return v
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _check_layout_list(v: Any) -> Any:
|
|
111
|
+
if not isinstance(v, list):
|
|
112
|
+
return v
|
|
113
|
+
for item in v:
|
|
114
|
+
_reject_chartref_dict(item)
|
|
115
|
+
return v
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _check_layout_item(v: Any) -> Any:
|
|
119
|
+
return _reject_chartref_dict(v)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ForeachQuery(BaseModel):
|
|
123
|
+
"""Inline data source for compile-time foreach loops."""
|
|
124
|
+
|
|
125
|
+
model_config = ConfigDict(extra="forbid")
|
|
126
|
+
|
|
127
|
+
data: list[dict[str, Any]] | None = Field(
|
|
128
|
+
default=None,
|
|
129
|
+
description="Inline row data for compile-time iteration.",
|
|
130
|
+
)
|
|
131
|
+
static_data: list[dict[str, Any]] | None = Field(
|
|
132
|
+
default=None,
|
|
133
|
+
description="Alias for data; inline row data for compile-time iteration.",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ForeachConfig(BaseModel):
|
|
138
|
+
"""foreach loop configuration.
|
|
139
|
+
|
|
140
|
+
`as` is a Python keyword so the field is declared as `as_` with an alias.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
144
|
+
|
|
145
|
+
query: ForeachQuery = Field(
|
|
146
|
+
description="Inline data source providing the rows to iterate over."
|
|
147
|
+
)
|
|
148
|
+
as_: str = Field(
|
|
149
|
+
alias="as",
|
|
150
|
+
description="Variable name bound to each row (accessible as {{ as.field }}).",
|
|
151
|
+
)
|
|
152
|
+
items: Annotated[
|
|
153
|
+
list[
|
|
154
|
+
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
155
|
+
],
|
|
156
|
+
BeforeValidator(_check_layout_list),
|
|
157
|
+
] = Field(description="Layout item templates resolved once per data row.")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class ForeachItem(BaseModel):
|
|
161
|
+
"""Top-level foreach construct in a layout list."""
|
|
162
|
+
|
|
163
|
+
model_config = ConfigDict(extra="forbid")
|
|
164
|
+
|
|
165
|
+
foreach: ForeachConfig = Field(
|
|
166
|
+
description="foreach loop configuration with query, as, and items."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class GridItem(BaseModel):
|
|
171
|
+
"""Grid layout item with position and span.
|
|
172
|
+
|
|
173
|
+
Uses grid terminology (not pixels):
|
|
174
|
+
col: Column position (0-indexed)
|
|
175
|
+
row: Row position (0-indexed)
|
|
176
|
+
col_span: Number of columns to span (default 1)
|
|
177
|
+
row_span: Number of rows to span (default 1)
|
|
178
|
+
width: Alias for col_span (more intuitive)
|
|
179
|
+
height: Alias for row_span (more intuitive)
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
- item: my_chart
|
|
183
|
+
col: 0
|
|
184
|
+
row: 0
|
|
185
|
+
width: 12 # Takes half of 24-column grid (alias for col_span)
|
|
186
|
+
height: 2 # Spans 2 rows (alias for row_span)
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
model_config = ConfigDict(extra="forbid")
|
|
190
|
+
|
|
191
|
+
item: Annotated[
|
|
192
|
+
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart],
|
|
193
|
+
BeforeValidator(_check_layout_item),
|
|
194
|
+
] = Field(
|
|
195
|
+
description="Chart name or inline chart/face definition to place in this grid cell."
|
|
196
|
+
)
|
|
197
|
+
col: int | None = Field(
|
|
198
|
+
default=None, description="Column position (0-indexed). Auto-placed if omitted."
|
|
199
|
+
)
|
|
200
|
+
row: int | None = Field(
|
|
201
|
+
default=None, description="Row position (0-indexed). Auto-placed if omitted."
|
|
202
|
+
)
|
|
203
|
+
col_span: int | None = Field(
|
|
204
|
+
default=None, description="Number of columns to span (width in grid units)."
|
|
205
|
+
)
|
|
206
|
+
row_span: int | None = Field(
|
|
207
|
+
default=None, description="Number of rows to span (height in grid units)."
|
|
208
|
+
)
|
|
209
|
+
width: int | None = Field(
|
|
210
|
+
default=None, description="Alias for col_span (more intuitive name)."
|
|
211
|
+
)
|
|
212
|
+
height: int | None = Field(
|
|
213
|
+
default=None, description="Alias for row_span (more intuitive name)."
|
|
214
|
+
)
|
|
215
|
+
description: str | None = Field(
|
|
216
|
+
default=None,
|
|
217
|
+
description="Optional metadata for AI search and context tooltips.",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class GridLayout(BaseModel):
|
|
222
|
+
"""Grid layout configuration."""
|
|
223
|
+
|
|
224
|
+
model_config = ConfigDict(extra="forbid")
|
|
225
|
+
|
|
226
|
+
columns: int = Field(
|
|
227
|
+
default=24, description="Number of grid columns (default: 24)."
|
|
228
|
+
)
|
|
229
|
+
row_height: str | None = Field(
|
|
230
|
+
default=None, description="Default row height as a CSS value (e.g., '200px')."
|
|
231
|
+
)
|
|
232
|
+
gap: Literal["sm", "md", "lg", "xl"] | None = Field(
|
|
233
|
+
default=None, description="Gap between grid cells (sm, md, lg, xl)."
|
|
234
|
+
)
|
|
235
|
+
default_width: int | None = Field(
|
|
236
|
+
default=None,
|
|
237
|
+
description="Default column span for items that don't specify width.",
|
|
238
|
+
)
|
|
239
|
+
default_height: int | None = Field(
|
|
240
|
+
default=None,
|
|
241
|
+
description="Default row span for items that don't specify height.",
|
|
242
|
+
)
|
|
243
|
+
items: list[GridItem] = Field(
|
|
244
|
+
description="List of grid items with position and span configuration."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TabItem(BaseModel):
|
|
249
|
+
"""Tab layout item."""
|
|
250
|
+
|
|
251
|
+
model_config = ConfigDict(extra="forbid")
|
|
252
|
+
|
|
253
|
+
title: str = Field(description="Tab label displayed in the tab bar.")
|
|
254
|
+
icon: str | None = Field(
|
|
255
|
+
default=None,
|
|
256
|
+
description="Optional icon shown in the tab (e.g., emoji or icon name).",
|
|
257
|
+
)
|
|
258
|
+
description: str | None = Field(
|
|
259
|
+
default=None,
|
|
260
|
+
description="Optional metadata for AI search and context tooltips.",
|
|
261
|
+
)
|
|
262
|
+
text: str | None = Field(
|
|
263
|
+
default=None, description="Markdown text content shown in this tab."
|
|
264
|
+
)
|
|
265
|
+
style: StylePatch | None = Field(
|
|
266
|
+
default=None, description="Style patch for this tab's content area."
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Layout fields (tab can contain nested layouts)
|
|
270
|
+
rows: Annotated[
|
|
271
|
+
list[
|
|
272
|
+
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
273
|
+
]
|
|
274
|
+
| None,
|
|
275
|
+
BeforeValidator(_check_layout_list),
|
|
276
|
+
] = Field(default=None, description="Vertical stack layout for this tab's content.")
|
|
277
|
+
cols: Annotated[
|
|
278
|
+
list[
|
|
279
|
+
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
280
|
+
]
|
|
281
|
+
| None,
|
|
282
|
+
BeforeValidator(_check_layout_list),
|
|
283
|
+
] = Field(default=None, description="Horizontal layout for this tab's content.")
|
|
284
|
+
grid: GridLayout | None = Field(
|
|
285
|
+
default=None, description="CSS-grid layout for this tab's content."
|
|
286
|
+
)
|
|
287
|
+
tabs: TabLayout | None = Field(
|
|
288
|
+
default=None, description="Nested tab layout (tabs within tabs)."
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class TabLayout(BaseModel):
|
|
293
|
+
"""Tab layout configuration.
|
|
294
|
+
|
|
295
|
+
The `id` field controls the variable name and URL param for tab selection.
|
|
296
|
+
If not provided, auto-generates as 'tab', 'tab_1', etc.
|
|
297
|
+
|
|
298
|
+
Example YAML:
|
|
299
|
+
tabs:
|
|
300
|
+
id: view # → URL param ?view=overview
|
|
301
|
+
default: overview
|
|
302
|
+
items:
|
|
303
|
+
- title: Overview
|
|
304
|
+
rows: [kpi_row]
|
|
305
|
+
- title: Details
|
|
306
|
+
rows: [detail_table]
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
model_config = ConfigDict(extra="forbid")
|
|
310
|
+
|
|
311
|
+
id: str | None = Field(
|
|
312
|
+
default=None,
|
|
313
|
+
description="Variable name and URL param base for tab selection (auto-generated if omitted).",
|
|
314
|
+
)
|
|
315
|
+
position: Literal["top", "left"] = Field(
|
|
316
|
+
default="top", description="Tab bar position (top or left)."
|
|
317
|
+
)
|
|
318
|
+
default: str | None = Field(
|
|
319
|
+
default=None, description="Default tab title to activate on load."
|
|
320
|
+
)
|
|
321
|
+
items: list[TabItem] = Field(description="List of tab items.")
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# ============================================================================
|
|
325
|
+
# FACE (TOP-LEVEL)
|
|
326
|
+
# ============================================================================
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class FaceDetails(BaseModel):
|
|
330
|
+
"""Collapsible section metadata for a face.
|
|
331
|
+
|
|
332
|
+
Authored as a block or string shorthand:
|
|
333
|
+
|
|
334
|
+
details: "Show more" # str shorthand
|
|
335
|
+
details:
|
|
336
|
+
summary: "Show more"
|
|
337
|
+
expanded_title: "Hide"
|
|
338
|
+
expanded: false
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
model_config = ConfigDict(extra="forbid")
|
|
342
|
+
|
|
343
|
+
summary: str = Field(description="Label shown when the section is collapsed.")
|
|
344
|
+
expanded_title: str | None = Field(
|
|
345
|
+
default=None,
|
|
346
|
+
description="Label shown when the section is expanded. Defaults to summary.",
|
|
347
|
+
)
|
|
348
|
+
expanded: bool = Field(
|
|
349
|
+
default=False, description="Whether the section is open by default."
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _coerce_details(v: Any) -> Any:
|
|
354
|
+
"""Coerce str → FaceDetails dict for the BeforeValidator on AuthoredFace.details."""
|
|
355
|
+
if isinstance(v, str):
|
|
356
|
+
return {"summary": v}
|
|
357
|
+
return v
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class SourcesSection(BaseModel):
|
|
361
|
+
"""Sources section for faces.
|
|
362
|
+
|
|
363
|
+
Contains a default source and optionally inline source definitions.
|
|
364
|
+
Extra keys are validated as SourceConfig (inline source definitions).
|
|
365
|
+
|
|
366
|
+
Example YAML:
|
|
367
|
+
sources:
|
|
368
|
+
default: my_postgres # Default source for all queries
|
|
369
|
+
local_csv: # Inline source definition
|
|
370
|
+
type: csv
|
|
371
|
+
file: data/local.csv
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
model_config = ConfigDict(extra="allow")
|
|
375
|
+
|
|
376
|
+
__pydantic_extra__: dict[str, SourceConfig]
|
|
377
|
+
|
|
378
|
+
default: str | None = Field(
|
|
379
|
+
default=None,
|
|
380
|
+
description="Default source name used for all queries that don't specify a source.",
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class AuthoredFace(BaseModel):
|
|
385
|
+
"""AuthoredFace (dataface) definition from YAML.
|
|
386
|
+
|
|
387
|
+
This is the top-level input type representing a complete dataface.
|
|
388
|
+
A face contains definitions (variables, queries, charts) and a layout.
|
|
389
|
+
|
|
390
|
+
Example YAML:
|
|
391
|
+
title: Sales Dataface
|
|
392
|
+
description: Overview of sales metrics
|
|
393
|
+
|
|
394
|
+
sources:
|
|
395
|
+
default: my_postgres # Default source for all queries
|
|
396
|
+
|
|
397
|
+
variables:
|
|
398
|
+
date_range:
|
|
399
|
+
input: daterange
|
|
400
|
+
default: ["2024-01-01", "2024-12-31"]
|
|
401
|
+
|
|
402
|
+
queries:
|
|
403
|
+
sales: SELECT * FROM sales WHERE date BETWEEN ...
|
|
404
|
+
|
|
405
|
+
charts:
|
|
406
|
+
revenue:
|
|
407
|
+
query: sales
|
|
408
|
+
type: line
|
|
409
|
+
x: date
|
|
410
|
+
y: amount
|
|
411
|
+
|
|
412
|
+
rows:
|
|
413
|
+
- revenue
|
|
414
|
+
|
|
415
|
+
Layout:
|
|
416
|
+
Exactly one layout type should be present (rows, cols, grid, or tabs).
|
|
417
|
+
Layout items can be:
|
|
418
|
+
- Chart name (string reference)
|
|
419
|
+
- Inline chart definition (dict with query and type)
|
|
420
|
+
- Nested face (dict with layout keys)
|
|
421
|
+
|
|
422
|
+
Sources:
|
|
423
|
+
Optional sources section with:
|
|
424
|
+
- default: Default source name for all queries in this face
|
|
425
|
+
- Inline source definitions (source_name: {...config...})
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
model_config = ConfigDict(extra="forbid")
|
|
429
|
+
|
|
430
|
+
@model_validator(mode="before")
|
|
431
|
+
@classmethod
|
|
432
|
+
def _normalize_queries(cls, data: object) -> object:
|
|
433
|
+
"""Normalize string query values before Pydantic sees them.
|
|
434
|
+
|
|
435
|
+
Called for all faces (top-level and nested) via the model_validator.
|
|
436
|
+
Top-level faces are also processed by parser._normalize_query_definitions
|
|
437
|
+
before reaching this validator; nested faces embedded in rows/cols/tabs/grid
|
|
438
|
+
bypass the parser and rely solely on this validator.
|
|
439
|
+
|
|
440
|
+
Delegates to normalize_query_value from models.refs (the single shared
|
|
441
|
+
normalization helper used by parser and this validator).
|
|
442
|
+
"""
|
|
443
|
+
if not isinstance(data, dict):
|
|
444
|
+
return data
|
|
445
|
+
raw_queries = data.get("queries")
|
|
446
|
+
if not isinstance(raw_queries, dict):
|
|
447
|
+
return data
|
|
448
|
+
return {
|
|
449
|
+
**data,
|
|
450
|
+
"queries": {
|
|
451
|
+
name: normalize_query_value(q) for name, q in raw_queries.items()
|
|
452
|
+
},
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
title: str | None = Field(
|
|
456
|
+
default=None, description="Dashboard title displayed at the top."
|
|
457
|
+
)
|
|
458
|
+
description: str | None = Field(
|
|
459
|
+
default=None, description="Description text for the dashboard."
|
|
460
|
+
)
|
|
461
|
+
tags: list[str] | None = Field(
|
|
462
|
+
default=None, description="Tags for categorization and search."
|
|
463
|
+
)
|
|
464
|
+
docs: str | None = Field(
|
|
465
|
+
default=None,
|
|
466
|
+
description=(
|
|
467
|
+
"Relative path under the docs site for the canonical doc page that "
|
|
468
|
+
"explains this face. Surfaced as a 'Docs →' link in the playground "
|
|
469
|
+
"gallery when DFT_DOCS_URL is set."
|
|
470
|
+
),
|
|
471
|
+
)
|
|
472
|
+
text: str | None = Field(
|
|
473
|
+
default=None, description="Markdown text content for text-only sections."
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# Source configuration
|
|
477
|
+
sources: Annotated[
|
|
478
|
+
SourcesSection | None,
|
|
479
|
+
Cascade(key="default_source", via="get_default_source"),
|
|
480
|
+
] = Field(
|
|
481
|
+
default=None,
|
|
482
|
+
description="Database source configuration. Use 'default:' to set the default source for all queries.",
|
|
483
|
+
)
|
|
484
|
+
# Single source shorthand (alternative to sources.default)
|
|
485
|
+
source: Annotated[
|
|
486
|
+
str | None, Cascade(key="default_source", via="get_default_source")
|
|
487
|
+
] = Field(
|
|
488
|
+
default=None,
|
|
489
|
+
description="Default source name shorthand (equivalent to sources.default). Sets the connection for all queries.",
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Scoped definitions
|
|
493
|
+
variables: dict[str, VariableOrRef] | None = Field(
|
|
494
|
+
default_factory=dict,
|
|
495
|
+
description="Variable definitions for dynamic filtering and UI controls.",
|
|
496
|
+
)
|
|
497
|
+
queries: dict[str, QueryOrRef] | None = Field(
|
|
498
|
+
default_factory=dict,
|
|
499
|
+
description="Named query definitions (SQL, CSV, MetricFlow, HTTP, etc.).",
|
|
500
|
+
)
|
|
501
|
+
charts: dict[str, ChartOrRef] | None = Field(
|
|
502
|
+
default_factory=dict,
|
|
503
|
+
description="Named chart definitions referenced in the layout.",
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Layout (exactly one should be present)
|
|
507
|
+
rows: Annotated[
|
|
508
|
+
list[
|
|
509
|
+
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
510
|
+
]
|
|
511
|
+
| None,
|
|
512
|
+
BeforeValidator(_check_layout_list),
|
|
513
|
+
] = Field(
|
|
514
|
+
default=None,
|
|
515
|
+
description="Vertical stack layout: list of chart names or inline chart/face definitions.",
|
|
516
|
+
)
|
|
517
|
+
cols: Annotated[
|
|
518
|
+
list[
|
|
519
|
+
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
520
|
+
]
|
|
521
|
+
| None,
|
|
522
|
+
BeforeValidator(_check_layout_list),
|
|
523
|
+
] = Field(
|
|
524
|
+
default=None,
|
|
525
|
+
description="Horizontal layout: list of chart names or inline chart/face definitions.",
|
|
526
|
+
)
|
|
527
|
+
grid: GridLayout | None = Field(
|
|
528
|
+
default=None,
|
|
529
|
+
description="CSS-grid style layout with explicit row/column placement.",
|
|
530
|
+
)
|
|
531
|
+
tabs: TabLayout | None = Field(
|
|
532
|
+
default=None,
|
|
533
|
+
description="Tabbed navigation layout where each tab contains its own layout.",
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Card gap toggle: when true, adds gap between cards.
|
|
537
|
+
card_gap: bool = Field(
|
|
538
|
+
default=False,
|
|
539
|
+
description="When True, adds gap between cards. Default: cards are edge-to-edge (0 gap).",
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# Focus mode
|
|
543
|
+
chart_focus: str | None = Field(
|
|
544
|
+
default=None,
|
|
545
|
+
description="Render only this named chart with its dependent variables (useful for embedding or SVG export).",
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Collapsible section (details)
|
|
549
|
+
details: Annotated[FaceDetails | None, BeforeValidator(_coerce_details)] = Field(
|
|
550
|
+
default=None,
|
|
551
|
+
description=(
|
|
552
|
+
"Collapsible section metadata. "
|
|
553
|
+
"String shorthand: details: 'text' → FaceDetails(summary='text'). "
|
|
554
|
+
"Block form: details: {summary: ..., expanded_title: ..., expanded: false}."
|
|
555
|
+
),
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# Styling & dimensions (when nested)
|
|
559
|
+
id: str | None = Field(
|
|
560
|
+
default=None,
|
|
561
|
+
description="Explicit ID for this face. Auto-generated from filename if omitted.",
|
|
562
|
+
)
|
|
563
|
+
style: Annotated[StylePatch | None, Cascade()] = Field(
|
|
564
|
+
default=None,
|
|
565
|
+
description="Style patch (background, padding, border, etc.). Background and semantic color tokens (accent, muted) cascade to nested child faces.",
|
|
566
|
+
)
|
|
567
|
+
width: str | int | None = Field(
|
|
568
|
+
default=None,
|
|
569
|
+
description="Width when nested (e.g., '50%', '400px', or an integer in pixels).",
|
|
570
|
+
)
|
|
571
|
+
height: str | int | None = Field(
|
|
572
|
+
default=None,
|
|
573
|
+
description="Height when nested (e.g., '300px' or an integer in pixels).",
|
|
574
|
+
)
|
|
575
|
+
visible: bool | str | SingleRowBoolProbe | None = Field(
|
|
576
|
+
default=None,
|
|
577
|
+
description=(
|
|
578
|
+
"Controls whether this layout item is rendered. "
|
|
579
|
+
"Accepts a bool, variable name, Jinja expression, "
|
|
580
|
+
"or {query, column} probe."
|
|
581
|
+
),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Vega-Lite theme
|
|
585
|
+
theme: Annotated[str | None, Cascade()] = Field(
|
|
586
|
+
default=None,
|
|
587
|
+
description="Theme name (e.g., 'editorial', 'cream', 'stark').",
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
@model_validator(mode="before")
|
|
591
|
+
@classmethod
|
|
592
|
+
def reject_face_key(cls, data: Any) -> Any:
|
|
593
|
+
"""Reject 'face' as a top-level key."""
|
|
594
|
+
if isinstance(data, dict) and "face" in data:
|
|
595
|
+
raise ValueError(
|
|
596
|
+
"'face:' is not valid. Face properties (title, rows, queries, etc.) "
|
|
597
|
+
"should be at the top level of the YAML file, not nested under 'face:'."
|
|
598
|
+
)
|
|
599
|
+
return data
|
|
600
|
+
|
|
601
|
+
@model_validator(mode="after")
|
|
602
|
+
def validate_layout(self) -> AuthoredFace:
|
|
603
|
+
"""Ensure at least one layout type or content is defined."""
|
|
604
|
+
layouts = [self.rows, self.cols, self.grid, self.tabs]
|
|
605
|
+
defined = [layout for layout in layouts if layout is not None]
|
|
606
|
+
if len(defined) > 1:
|
|
607
|
+
raise ValueError(
|
|
608
|
+
"Face can only have one layout type (rows, cols, grid, or tabs)"
|
|
609
|
+
)
|
|
610
|
+
if (
|
|
611
|
+
len(defined) == 0
|
|
612
|
+
and self.text is None
|
|
613
|
+
and not self.title
|
|
614
|
+
and not self.description
|
|
615
|
+
):
|
|
616
|
+
raise ValueError(
|
|
617
|
+
"Face must have at least one layout type, text, title, or description"
|
|
618
|
+
)
|
|
619
|
+
return self
|
|
620
|
+
|
|
621
|
+
def get_default_source(self) -> str | None:
|
|
622
|
+
"""Return the default source name, or None.
|
|
623
|
+
|
|
624
|
+
source: shorthand takes precedence over sources.default.
|
|
625
|
+
"""
|
|
626
|
+
if self.source:
|
|
627
|
+
return self.source
|
|
628
|
+
if self.sources is None:
|
|
629
|
+
return None
|
|
630
|
+
return self.sources.default
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
# Resolve forward references (ForeachConfig.items references ForeachItem and
|
|
634
|
+
# AuthoredFace; GridItem.item and TabItem.rows/cols also reference types defined
|
|
635
|
+
# later in the file — rebuild after all classes are in scope)
|
|
636
|
+
ForeachConfig.model_rebuild()
|
|
637
|
+
GridItem.model_rebuild()
|
|
638
|
+
TabItem.model_rebuild()
|
|
639
|
+
AuthoredFace.model_rebuild()
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
# ============================================================================
|
|
643
|
+
# LAYOUT TYPE
|
|
644
|
+
# ============================================================================
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
class LayoutType(str, Enum):
|
|
648
|
+
"""Layout types for organizing charts.
|
|
649
|
+
|
|
650
|
+
- rows: Vertical stack of items
|
|
651
|
+
- cols: Horizontal arrangement of items
|
|
652
|
+
- grid: CSS-grid style layout with columns
|
|
653
|
+
- tabs: Tabbed navigation between views
|
|
654
|
+
"""
|
|
655
|
+
|
|
656
|
+
ROWS = "rows"
|
|
657
|
+
COLS = "cols"
|
|
658
|
+
GRID = "grid"
|
|
659
|
+
TABS = "tabs"
|