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,362 @@
|
|
|
1
|
+
"""Theme configuration types and canonical theme normalization.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE
|
|
4
|
+
Purpose: Define the Dataface-facing theme schema and map it to Vega-Lite config.
|
|
5
|
+
|
|
6
|
+
The public authoring surface is a canonical Dataface theme model:
|
|
7
|
+
- top-level shared tokens
|
|
8
|
+
- `charts:` for chart defaults and chart-type overrides
|
|
9
|
+
|
|
10
|
+
Vega-Lite remains an implementation detail. Raw Vega config still passes through as
|
|
11
|
+
an escape hatch, but `to_vega_config()` is the boundary that maps canonical theme
|
|
12
|
+
values into Vega-Lite config for rendering.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from copy import deepcopy
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, ConfigDict, model_validator
|
|
22
|
+
|
|
23
|
+
from dataface.core.compile.models.vega_lite.config import (
|
|
24
|
+
AxisConfig,
|
|
25
|
+
LegendConfig,
|
|
26
|
+
MarkConfig,
|
|
27
|
+
RangeConfig,
|
|
28
|
+
TitleConfig,
|
|
29
|
+
ViewConfig,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _deep_merge(base: dict[str, Any], overlay: Mapping[str, Any]) -> dict[str, Any]:
|
|
34
|
+
merged = deepcopy(base)
|
|
35
|
+
for key, value in overlay.items():
|
|
36
|
+
if (
|
|
37
|
+
key in merged
|
|
38
|
+
and isinstance(merged[key], dict)
|
|
39
|
+
and isinstance(value, Mapping)
|
|
40
|
+
):
|
|
41
|
+
merged[key] = _deep_merge(merged[key], dict(value))
|
|
42
|
+
else:
|
|
43
|
+
merged[key] = deepcopy(value)
|
|
44
|
+
return merged
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _to_plain_vega_value(value: object) -> object:
|
|
48
|
+
"""Recursively convert pydantic models and mapping-like values to plain Python."""
|
|
49
|
+
if isinstance(value, BaseModel):
|
|
50
|
+
data: dict[str, object] = {}
|
|
51
|
+
|
|
52
|
+
for key in type(value).model_fields:
|
|
53
|
+
field_value = getattr(value, key, None)
|
|
54
|
+
if field_value is not None:
|
|
55
|
+
data[key] = _to_plain_vega_value(field_value)
|
|
56
|
+
|
|
57
|
+
extra = getattr(value, "model_extra", None) or {}
|
|
58
|
+
for key, extra_value in extra.items():
|
|
59
|
+
if extra_value is not None:
|
|
60
|
+
data[key] = _to_plain_vega_value(extra_value)
|
|
61
|
+
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
if isinstance(value, dict):
|
|
65
|
+
return {
|
|
66
|
+
key: _to_plain_vega_value(item)
|
|
67
|
+
for key, item in value.items()
|
|
68
|
+
if item is not None
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if isinstance(value, list):
|
|
72
|
+
return [_to_plain_vega_value(item) for item in value]
|
|
73
|
+
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ThemeTokens(BaseModel):
|
|
78
|
+
"""Canonical shared theme tokens."""
|
|
79
|
+
|
|
80
|
+
model_config = ConfigDict(extra="forbid")
|
|
81
|
+
|
|
82
|
+
background: str | None = None
|
|
83
|
+
color: str | None = None
|
|
84
|
+
muted: str | None = None
|
|
85
|
+
border_color: str | None = None
|
|
86
|
+
accent_color: str | None = None
|
|
87
|
+
title_color: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ThemeMarkConfig(BaseModel):
|
|
91
|
+
model_config = ConfigDict(extra="forbid")
|
|
92
|
+
|
|
93
|
+
color: str | None = None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ThemeTableStripeConfig(BaseModel):
|
|
97
|
+
model_config = ConfigDict(extra="forbid")
|
|
98
|
+
|
|
99
|
+
color: str | None = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ThemeTableConfig(ThemeTokens):
|
|
103
|
+
header_background: str | None = None
|
|
104
|
+
header_color: str | None = None
|
|
105
|
+
stripe: ThemeTableStripeConfig | None = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ThemeChartsConfig(ThemeTokens):
|
|
109
|
+
palette: list[str] | None = None
|
|
110
|
+
dashes: list[list[int]] | None = None
|
|
111
|
+
table: ThemeTableConfig | None = None
|
|
112
|
+
bar: ThemeMarkConfig | None = None
|
|
113
|
+
arc: ThemeMarkConfig | None = None
|
|
114
|
+
area: ThemeMarkConfig | None = None
|
|
115
|
+
line: ThemeMarkConfig | None = None
|
|
116
|
+
path: ThemeMarkConfig | None = None
|
|
117
|
+
point: ThemeMarkConfig | None = None
|
|
118
|
+
rect: ThemeMarkConfig | None = None
|
|
119
|
+
shape: ThemeMarkConfig | None = None
|
|
120
|
+
symbol: ThemeMarkConfig | None = None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ThemeConfig(ThemeTokens):
|
|
124
|
+
"""Canonical Dataface theme plus optional raw Vega-Lite config overrides.
|
|
125
|
+
|
|
126
|
+
Typed fields (view, title, axis, legend, range, mark-type configs) cover the
|
|
127
|
+
most common VL config sections and validate their contents strictly.
|
|
128
|
+
|
|
129
|
+
top-level extra="allow" is a deliberate VL escape hatch: any Vega-Lite config
|
|
130
|
+
key not listed as a typed field passes through as-is to to_vega_config(). This
|
|
131
|
+
covers sections Dataface does not enumerate typed fields for — axisBand, axisX,
|
|
132
|
+
axisY, axisQuantitative, axisOrdinal, headerRow, headerColumn, projectionConfig,
|
|
133
|
+
scale, and others. Closing this surface would require enumerating every Vega-Lite
|
|
134
|
+
config section, which is out of scope and creates unnecessary churn as VL evolves.
|
|
135
|
+
|
|
136
|
+
Contrast with vega.config (VegaRuntimeConfig.config: VegaLiteConfig) — that path
|
|
137
|
+
is closed (extra="forbid") because it represents the Dataface-emitted style output,
|
|
138
|
+
not the user-authored theme surface.
|
|
139
|
+
|
|
140
|
+
Note: unknown keys in theme YAML are silently accepted (no typo detection). This
|
|
141
|
+
is the accepted trade-off for an escape hatch surface.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
# VL theme escape hatch — intentional, not a migration shim. See class docstring.
|
|
145
|
+
model_config = ConfigDict(extra="allow")
|
|
146
|
+
|
|
147
|
+
charts: ThemeChartsConfig | None = None
|
|
148
|
+
|
|
149
|
+
# Raw Vega-Lite override surface / pass-through.
|
|
150
|
+
view: ViewConfig | None = None
|
|
151
|
+
title: TitleConfig | None = None
|
|
152
|
+
axis: AxisConfig | None = None
|
|
153
|
+
legend: LegendConfig | None = None
|
|
154
|
+
range: RangeConfig | None = None
|
|
155
|
+
bar: MarkConfig | None = None
|
|
156
|
+
arc: MarkConfig | None = None
|
|
157
|
+
line: MarkConfig | None = None
|
|
158
|
+
area: MarkConfig | None = None
|
|
159
|
+
rect: MarkConfig | None = None
|
|
160
|
+
symbol: MarkConfig | None = None
|
|
161
|
+
point: MarkConfig | None = None
|
|
162
|
+
shape: MarkConfig | None = None
|
|
163
|
+
path: MarkConfig | None = None
|
|
164
|
+
|
|
165
|
+
@model_validator(mode="before")
|
|
166
|
+
@classmethod
|
|
167
|
+
def _reject_legacy_dataface_keys(cls, value: object) -> object:
|
|
168
|
+
if not isinstance(value, Mapping):
|
|
169
|
+
return value
|
|
170
|
+
|
|
171
|
+
legacy_root_keys = {
|
|
172
|
+
"table",
|
|
173
|
+
"variable",
|
|
174
|
+
"variables",
|
|
175
|
+
"borderColor",
|
|
176
|
+
"accentColor",
|
|
177
|
+
"titleColor",
|
|
178
|
+
}
|
|
179
|
+
present_legacy_keys = sorted(key for key in legacy_root_keys if key in value)
|
|
180
|
+
if present_legacy_keys:
|
|
181
|
+
invalid_keys = ", ".join(present_legacy_keys)
|
|
182
|
+
raise ValueError(
|
|
183
|
+
"Theme config uses unsupported legacy Dataface keys: "
|
|
184
|
+
f"{invalid_keys}. Use snake_case canonical keys instead."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return value
|
|
188
|
+
|
|
189
|
+
def resolved_top_level(self) -> ThemeTokens:
|
|
190
|
+
return ThemeTokens.model_validate(
|
|
191
|
+
self.model_dump(include=set(ThemeTokens.model_fields))
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def resolved_charts(self) -> ThemeChartsConfig:
|
|
195
|
+
base = self.resolved_top_level().model_dump(exclude_none=True)
|
|
196
|
+
overlay = (
|
|
197
|
+
self.charts.model_dump(exclude_none=True) if self.charts is not None else {}
|
|
198
|
+
)
|
|
199
|
+
return ThemeChartsConfig.model_validate(_deep_merge(base, overlay))
|
|
200
|
+
|
|
201
|
+
def resolved_table(self) -> ThemeTableConfig:
|
|
202
|
+
charts = self.resolved_charts()
|
|
203
|
+
base = charts.model_dump(
|
|
204
|
+
include=set(ThemeTokens.model_fields),
|
|
205
|
+
exclude_none=True,
|
|
206
|
+
)
|
|
207
|
+
overlay = (
|
|
208
|
+
charts.table.model_dump(exclude_none=True)
|
|
209
|
+
if charts.table is not None
|
|
210
|
+
else {}
|
|
211
|
+
)
|
|
212
|
+
return ThemeTableConfig.model_validate(_deep_merge(base, overlay))
|
|
213
|
+
|
|
214
|
+
def _generated_vega_config(self) -> dict[str, Any]:
|
|
215
|
+
charts = self.resolved_charts()
|
|
216
|
+
generated: dict[str, Any] = {}
|
|
217
|
+
|
|
218
|
+
if self.background is not None:
|
|
219
|
+
generated["background"] = self.background
|
|
220
|
+
|
|
221
|
+
title_color = charts.title_color
|
|
222
|
+
if title_color is not None:
|
|
223
|
+
generated["title"] = {"color": title_color}
|
|
224
|
+
generated["axis"] = {
|
|
225
|
+
"labelColor": charts.muted,
|
|
226
|
+
"titleColor": title_color,
|
|
227
|
+
"gridColor": charts.border_color,
|
|
228
|
+
"tickColor": charts.border_color,
|
|
229
|
+
"domainColor": charts.border_color,
|
|
230
|
+
}
|
|
231
|
+
generated["legend"] = {
|
|
232
|
+
"labelColor": charts.muted,
|
|
233
|
+
"titleColor": title_color,
|
|
234
|
+
}
|
|
235
|
+
elif charts.muted is not None or charts.border_color is not None:
|
|
236
|
+
generated["axis"] = {
|
|
237
|
+
"labelColor": charts.muted,
|
|
238
|
+
"gridColor": charts.border_color,
|
|
239
|
+
"tickColor": charts.border_color,
|
|
240
|
+
"domainColor": charts.border_color,
|
|
241
|
+
}
|
|
242
|
+
generated["legend"] = {"labelColor": charts.muted}
|
|
243
|
+
|
|
244
|
+
if charts.palette is not None:
|
|
245
|
+
generated["range"] = {"category": charts.palette}
|
|
246
|
+
|
|
247
|
+
default_mark_color = charts.accent_color
|
|
248
|
+
mark_map = {
|
|
249
|
+
"bar": ("fill", charts.bar.color if charts.bar else None),
|
|
250
|
+
"arc": ("fill", charts.arc.color if charts.arc else None),
|
|
251
|
+
"area": ("fill", charts.area.color if charts.area else None),
|
|
252
|
+
"line": ("stroke", charts.line.color if charts.line else None),
|
|
253
|
+
"path": ("stroke", charts.path.color if charts.path else None),
|
|
254
|
+
"point": ("fill", charts.point.color if charts.point else None),
|
|
255
|
+
"rect": ("fill", charts.rect.color if charts.rect else None),
|
|
256
|
+
"shape": ("stroke", charts.shape.color if charts.shape else None),
|
|
257
|
+
"symbol": ("fill", charts.symbol.color if charts.symbol else None),
|
|
258
|
+
}
|
|
259
|
+
for key, (field, value) in mark_map.items():
|
|
260
|
+
color = value or default_mark_color
|
|
261
|
+
if color is not None:
|
|
262
|
+
generated[key] = {field: color}
|
|
263
|
+
|
|
264
|
+
return _to_plain_vega_value(generated) # type: ignore[return-value]
|
|
265
|
+
|
|
266
|
+
def to_vega_config(self) -> dict:
|
|
267
|
+
"""Export theme as Vega-Lite config.
|
|
268
|
+
|
|
269
|
+
Canonical Dataface theme tokens synthesize the common Vega-Lite config.
|
|
270
|
+
Any explicit raw Vega-Lite keys on the theme override the generated values.
|
|
271
|
+
"""
|
|
272
|
+
raw = _to_plain_vega_value(self)
|
|
273
|
+
if not isinstance(raw, dict):
|
|
274
|
+
raise TypeError("ThemeConfig must serialize to a dictionary")
|
|
275
|
+
|
|
276
|
+
# Strip Dataface-internal keys that must not appear in Vega-Lite config.
|
|
277
|
+
# "table", "variable", "variables" are rejected by the model validator so
|
|
278
|
+
# they cannot reach here; the remaining keys are valid model fields that
|
|
279
|
+
# must be excluded from Vega output.
|
|
280
|
+
for key in (
|
|
281
|
+
"color",
|
|
282
|
+
"muted",
|
|
283
|
+
"border_color",
|
|
284
|
+
"accent_color",
|
|
285
|
+
"title_color",
|
|
286
|
+
"charts",
|
|
287
|
+
):
|
|
288
|
+
raw.pop(key, None)
|
|
289
|
+
|
|
290
|
+
generated = self._generated_vega_config()
|
|
291
|
+
if not isinstance(generated, dict):
|
|
292
|
+
raise TypeError("Generated theme config must serialize to a dictionary")
|
|
293
|
+
return _deep_merge(generated, raw)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def normalize_raw_theme_config(raw: Mapping[str, Any]) -> dict[str, Any]:
|
|
297
|
+
"""Normalize raw theme dictionaries into the canonical theme surface."""
|
|
298
|
+
data = deepcopy(dict(raw))
|
|
299
|
+
|
|
300
|
+
def _mapping(name: str) -> Mapping[str, Any]:
|
|
301
|
+
value = data.get(name)
|
|
302
|
+
return value if isinstance(value, Mapping) else {}
|
|
303
|
+
|
|
304
|
+
axis = _mapping("axis")
|
|
305
|
+
legend = _mapping("legend")
|
|
306
|
+
title = _mapping("title")
|
|
307
|
+
range_config = _mapping("range")
|
|
308
|
+
charts = _mapping("charts")
|
|
309
|
+
table_raw = charts.get("table")
|
|
310
|
+
table: Mapping[str, Any] = table_raw if isinstance(table_raw, Mapping) else {}
|
|
311
|
+
|
|
312
|
+
mark_sources = (
|
|
313
|
+
("bar", "fill"),
|
|
314
|
+
("arc", "fill"),
|
|
315
|
+
("line", "stroke"),
|
|
316
|
+
("area", "fill"),
|
|
317
|
+
("rect", "fill"),
|
|
318
|
+
("symbol", "fill"),
|
|
319
|
+
("point", "fill"),
|
|
320
|
+
("shape", "stroke"),
|
|
321
|
+
("path", "stroke"),
|
|
322
|
+
)
|
|
323
|
+
accent = None
|
|
324
|
+
chart_marks: dict[str, Any] = {}
|
|
325
|
+
for mark, field in mark_sources:
|
|
326
|
+
mark_value = _mapping(mark)
|
|
327
|
+
color = mark_value.get(field)
|
|
328
|
+
if color and accent is None:
|
|
329
|
+
accent = color
|
|
330
|
+
if color:
|
|
331
|
+
chart_marks[mark] = {"color": color}
|
|
332
|
+
if accent is None and isinstance(range_config.get("category"), list):
|
|
333
|
+
accent = range_config["category"][0] if range_config["category"] else None
|
|
334
|
+
|
|
335
|
+
derived_tokens: dict[str, Any] = {
|
|
336
|
+
"background": data.get("background"),
|
|
337
|
+
"color": table.get("color") or title.get("color"),
|
|
338
|
+
"muted": axis.get("labelColor") or legend.get("labelColor"),
|
|
339
|
+
"border_color": axis.get("gridColor")
|
|
340
|
+
or axis.get("tickColor")
|
|
341
|
+
or axis.get("domainColor"),
|
|
342
|
+
"accent_color": accent,
|
|
343
|
+
"title_color": table.get("header_color")
|
|
344
|
+
or axis.get("titleColor")
|
|
345
|
+
or title.get("color")
|
|
346
|
+
or legend.get("titleColor"),
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
normalized = dict(data)
|
|
350
|
+
for key, value in derived_tokens.items():
|
|
351
|
+
if value is not None and key not in normalized:
|
|
352
|
+
normalized[key] = value
|
|
353
|
+
|
|
354
|
+
chart_overrides: dict[str, Any] = {}
|
|
355
|
+
if isinstance(range_config.get("category"), list):
|
|
356
|
+
chart_overrides["palette"] = list(range_config["category"])
|
|
357
|
+
if chart_marks:
|
|
358
|
+
chart_overrides.update(chart_marks)
|
|
359
|
+
if chart_overrides:
|
|
360
|
+
normalized["charts"] = _deep_merge(dict(charts), chart_overrides)
|
|
361
|
+
|
|
362
|
+
return normalized
|
|
File without changes
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Authored variable models: Variable, VariableOptions, SingleRowBoolProbe, VariableInputType.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE (Input)
|
|
4
|
+
Purpose: Define variable types that map directly to the YAML schema.
|
|
5
|
+
|
|
6
|
+
Variables provide dynamic values to queries and charts. They can be bound to
|
|
7
|
+
user inputs (select, slider, etc.) or have static default values.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
# ============================================================================
|
|
15
|
+
# ENUMS & LITERALS
|
|
16
|
+
# ============================================================================
|
|
17
|
+
|
|
18
|
+
VariableInputType = Literal[
|
|
19
|
+
"auto",
|
|
20
|
+
"select",
|
|
21
|
+
"multiselect",
|
|
22
|
+
"input",
|
|
23
|
+
"text",
|
|
24
|
+
"number",
|
|
25
|
+
"textarea",
|
|
26
|
+
"slider",
|
|
27
|
+
"range",
|
|
28
|
+
"date",
|
|
29
|
+
"datepicker",
|
|
30
|
+
"daterange",
|
|
31
|
+
"checkbox",
|
|
32
|
+
"radio",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# ============================================================================
|
|
36
|
+
# VARIABLE TYPES
|
|
37
|
+
# ============================================================================
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class VariableOptions(BaseModel):
|
|
41
|
+
"""Options configuration for variable inputs.
|
|
42
|
+
|
|
43
|
+
Defines where the options come from for select/multiselect inputs.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(extra="forbid")
|
|
47
|
+
|
|
48
|
+
static: list[str | int | float] | None = Field(
|
|
49
|
+
default=None, description="List of static option values (strings or numbers)."
|
|
50
|
+
)
|
|
51
|
+
query: str | None = Field(
|
|
52
|
+
default=None, description="Query name whose result rows provide option values."
|
|
53
|
+
)
|
|
54
|
+
column: str | None = Field(
|
|
55
|
+
default=None, description="Column in the query result to use as option values."
|
|
56
|
+
)
|
|
57
|
+
label_column: str | None = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="Column in the query result to use as display labels (separate from values).",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SingleRowBoolProbe(BaseModel):
|
|
64
|
+
"""Single-row boolean query probe.
|
|
65
|
+
|
|
66
|
+
Executes a named query and reads one boolean cell to decide a yes/no
|
|
67
|
+
condition. Used by ``Variable.disabled`` and ``LayoutItem.visible``.
|
|
68
|
+
The query must return exactly one row; the named column must hold a
|
|
69
|
+
boolean-coercible value (true/false/1/0/yes/no).
|
|
70
|
+
|
|
71
|
+
Example YAML (disabled)::
|
|
72
|
+
|
|
73
|
+
disabled:
|
|
74
|
+
query: seed_control_state
|
|
75
|
+
column: is_disabled
|
|
76
|
+
|
|
77
|
+
Example YAML (visible)::
|
|
78
|
+
|
|
79
|
+
visible:
|
|
80
|
+
query: layout_flags
|
|
81
|
+
column: show_panel
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
model_config = ConfigDict(extra="forbid")
|
|
85
|
+
|
|
86
|
+
query: str = Field(description="Name of the query to execute.")
|
|
87
|
+
column: str = Field(
|
|
88
|
+
description="Column in the single result row holding the boolean value."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Variable(BaseModel):
|
|
93
|
+
"""Variable definition from YAML.
|
|
94
|
+
|
|
95
|
+
Variables provide dynamic values to queries and charts.
|
|
96
|
+
They can be bound to user inputs (select, slider, etc.) or
|
|
97
|
+
have static default values.
|
|
98
|
+
|
|
99
|
+
Cascading Variables:
|
|
100
|
+
Variables can depend on other variables when their options query
|
|
101
|
+
uses Jinja templates referencing other variables. For example:
|
|
102
|
+
|
|
103
|
+
variables:
|
|
104
|
+
country:
|
|
105
|
+
input: select
|
|
106
|
+
options:
|
|
107
|
+
query: country_options
|
|
108
|
+
|
|
109
|
+
state:
|
|
110
|
+
input: select
|
|
111
|
+
options:
|
|
112
|
+
query: state_options # SQL uses {{ filter('country', country) }}
|
|
113
|
+
|
|
114
|
+
When country changes, state's options are re-fetched with the new
|
|
115
|
+
country value, and state's current value is reset.
|
|
116
|
+
|
|
117
|
+
Invisible Variables:
|
|
118
|
+
Variables with visible=False are not rendered in the UI but can still
|
|
119
|
+
be used in queries. This is useful for:
|
|
120
|
+
- Server-side variables passed via URL query params
|
|
121
|
+
- Internal system values (theme, model name, connection strings)
|
|
122
|
+
- Variables that shouldn't be user-editable
|
|
123
|
+
|
|
124
|
+
Example YAML:
|
|
125
|
+
variables:
|
|
126
|
+
date_range:
|
|
127
|
+
input: daterange
|
|
128
|
+
label: "Date Range"
|
|
129
|
+
default: ["2024-01-01", "2024-12-31"]
|
|
130
|
+
category:
|
|
131
|
+
input: select
|
|
132
|
+
options:
|
|
133
|
+
static: ["All", "Electronics", "Clothing"]
|
|
134
|
+
default: "All"
|
|
135
|
+
model:
|
|
136
|
+
input: text
|
|
137
|
+
visible: false # Set via URL param, not shown in UI
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
model_config = ConfigDict(extra="forbid")
|
|
141
|
+
|
|
142
|
+
input: VariableInputType = Field(
|
|
143
|
+
default="auto",
|
|
144
|
+
description="UI control type (select, multiselect, slider, daterange, etc.). 'auto' detects from options.",
|
|
145
|
+
)
|
|
146
|
+
input_auto_detected: bool = Field(
|
|
147
|
+
default=False,
|
|
148
|
+
exclude=True,
|
|
149
|
+
description="Internal: True when input type was resolved from 'auto' to a concrete type.",
|
|
150
|
+
)
|
|
151
|
+
label: str | None = Field(
|
|
152
|
+
default=None, description="Display label shown above the input control."
|
|
153
|
+
)
|
|
154
|
+
description: str | None = Field(
|
|
155
|
+
default=None, description="Help text shown below the input control."
|
|
156
|
+
)
|
|
157
|
+
default: Any | None = Field(
|
|
158
|
+
default=None, description="Default value used when no URL param is set."
|
|
159
|
+
)
|
|
160
|
+
placeholder: str | None = Field(
|
|
161
|
+
default=None, description="Placeholder text shown in the input when empty."
|
|
162
|
+
)
|
|
163
|
+
required: bool | None = Field(
|
|
164
|
+
default=False,
|
|
165
|
+
description="When True, a value must be provided before queries execute.",
|
|
166
|
+
)
|
|
167
|
+
allow_null: bool | None = Field(
|
|
168
|
+
default=False,
|
|
169
|
+
description="When True, 'null' is a valid selection (useful for optional filters).",
|
|
170
|
+
)
|
|
171
|
+
visible: bool = Field(
|
|
172
|
+
default=True,
|
|
173
|
+
description="When False, the variable is not rendered in the UI but can still be set via URL params.",
|
|
174
|
+
)
|
|
175
|
+
disabled: bool | str | SingleRowBoolProbe | None = Field(
|
|
176
|
+
default=None,
|
|
177
|
+
description=(
|
|
178
|
+
"Disable this control. Accepts: static bool; a variable name or Jinja "
|
|
179
|
+
"boolean expression string (no {{ }} required — bare names auto-wrap); "
|
|
180
|
+
"or a {query, column} form that reads a single boolean cell from a named "
|
|
181
|
+
"query. Absent variable in a string expression raises (use a default)."
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Source binding (one of these for data-driven options)
|
|
186
|
+
column: str | None = Field(
|
|
187
|
+
default=None,
|
|
188
|
+
description="Column name in the query result to use as option values.",
|
|
189
|
+
)
|
|
190
|
+
query: str | dict[str, Any] | None = Field(
|
|
191
|
+
default=None,
|
|
192
|
+
description="Query name or inline query definition for populating options.",
|
|
193
|
+
)
|
|
194
|
+
dimension: str | None = Field(
|
|
195
|
+
default=None, description="MetricFlow dimension name for populating options."
|
|
196
|
+
)
|
|
197
|
+
measure: str | None = Field(
|
|
198
|
+
default=None, description="MetricFlow measure name for populating options."
|
|
199
|
+
)
|
|
200
|
+
model: str | None = Field(
|
|
201
|
+
default=None, description="dbt model name for populating options."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Options
|
|
205
|
+
options: VariableOptions | None = Field(
|
|
206
|
+
default=None, description="Static or query-driven option list configuration."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Migration metadata
|
|
210
|
+
data_type: str | None = Field(
|
|
211
|
+
default=None,
|
|
212
|
+
description=(
|
|
213
|
+
"Upstream data-type hint preserved through migrations (e.g. 'string', "
|
|
214
|
+
"'number'). Informational; not currently consumed at compile time."
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Slider-specific
|
|
219
|
+
min: int | float | None = Field(
|
|
220
|
+
default=None, description="Minimum value for slider/range inputs."
|
|
221
|
+
)
|
|
222
|
+
max: int | float | None = Field(
|
|
223
|
+
default=None, description="Maximum value for slider/range inputs."
|
|
224
|
+
)
|
|
225
|
+
step: int | float | None = Field(
|
|
226
|
+
default=None, description="Step size for slider/range inputs."
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Operator for filter generation
|
|
230
|
+
operator: str | None = Field(
|
|
231
|
+
default=None,
|
|
232
|
+
description="SQL operator used when generating filter expressions (e.g., '=', 'IN', 'LIKE').",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Computed during compilation — not an authored field.
|
|
236
|
+
variable_dependencies: set[str] = Field(
|
|
237
|
+
default_factory=set,
|
|
238
|
+
exclude=True,
|
|
239
|
+
description="Other variable names this variable's options query depends on (computed during compilation).",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def get_option_query(self) -> str | None:
|
|
243
|
+
"""Get the query name used for populating options.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Query name if options are populated from a query, None otherwise
|
|
247
|
+
"""
|
|
248
|
+
# Check options.query first (structured options)
|
|
249
|
+
if self.options and self.options.query:
|
|
250
|
+
return self.options.query
|
|
251
|
+
# Check direct query binding
|
|
252
|
+
if isinstance(self.query, str):
|
|
253
|
+
return self.query
|
|
254
|
+
return None
|
|
File without changes
|