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,489 @@
|
|
|
1
|
+
"""Variable normalization, validation, and dependency analysis."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from dataface.core.compile.errors import CompilationError
|
|
7
|
+
from dataface.core.compile.jinja import extract_variable_dependencies
|
|
8
|
+
from dataface.core.compile.models.chart.compiled import (
|
|
9
|
+
Chart,
|
|
10
|
+
)
|
|
11
|
+
from dataface.core.compile.models.face.compiled import Face, Layout
|
|
12
|
+
from dataface.core.compile.models.query.compiled import AnyQuery, SqlQuery
|
|
13
|
+
from dataface.core.compile.models.variable.authored import (
|
|
14
|
+
Variable,
|
|
15
|
+
VariableInputType,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
_QUERY_NAME_RE = re.compile(r"^(?:queries\.)?[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _detect_variable_input_type(var: Variable) -> VariableInputType:
|
|
22
|
+
"""Infer the input type for a variable from its authored fields.
|
|
23
|
+
|
|
24
|
+
When input is "auto" (the default), examines options, default, min/max/step,
|
|
25
|
+
hidden, and column fields to determine the appropriate input type.
|
|
26
|
+
If input is explicitly set to something other than "auto", returns it unchanged.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The resolved VariableInputType string.
|
|
30
|
+
"""
|
|
31
|
+
if var.input != "auto":
|
|
32
|
+
return var.input
|
|
33
|
+
|
|
34
|
+
has_options = var.options and (var.options.static or var.options.query)
|
|
35
|
+
has_slider_fields = (
|
|
36
|
+
var.min is not None or var.max is not None or var.step is not None
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# List default (with or without options) → multiselect
|
|
40
|
+
if isinstance(var.default, list):
|
|
41
|
+
return "multiselect"
|
|
42
|
+
|
|
43
|
+
# Slider fields → slider
|
|
44
|
+
if has_slider_fields:
|
|
45
|
+
return "slider"
|
|
46
|
+
|
|
47
|
+
# Bool default → checkbox
|
|
48
|
+
if isinstance(var.default, bool):
|
|
49
|
+
return "checkbox"
|
|
50
|
+
|
|
51
|
+
# Options present → select
|
|
52
|
+
if has_options:
|
|
53
|
+
return "select"
|
|
54
|
+
|
|
55
|
+
# Column binding → select
|
|
56
|
+
if var.column:
|
|
57
|
+
return "select"
|
|
58
|
+
|
|
59
|
+
# Not visible (hidden) with no other signals → text
|
|
60
|
+
if not var.visible:
|
|
61
|
+
return "text"
|
|
62
|
+
|
|
63
|
+
# No signals → text (generic fallback)
|
|
64
|
+
return "text"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _promote_inline_option_queries(
|
|
68
|
+
variables: dict[str, Variable],
|
|
69
|
+
query_registry: dict[str, AnyQuery],
|
|
70
|
+
default_source: str | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Convert inline SQL in variable options.query to named queries.
|
|
73
|
+
|
|
74
|
+
When a variable defines options.query with raw SQL instead of a query
|
|
75
|
+
name reference, this creates a synthetic SqlQuery in the registry and
|
|
76
|
+
replaces options.query with the synthetic name.
|
|
77
|
+
|
|
78
|
+
Modifies both variables and query_registry in place.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
variables: Dict of variable definitions to process
|
|
82
|
+
query_registry: Query registry to add synthetic queries to
|
|
83
|
+
default_source: Default source for queries without explicit source
|
|
84
|
+
"""
|
|
85
|
+
for var_name, var in variables.items():
|
|
86
|
+
if not var.options or not var.options.query:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
query_ref = var.options.query.strip()
|
|
90
|
+
if not query_ref or _QUERY_NAME_RE.match(query_ref):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
synthetic_name = f"_var_{var_name}_options"
|
|
94
|
+
query_registry[synthetic_name] = SqlQuery(
|
|
95
|
+
sql=query_ref,
|
|
96
|
+
source=default_source,
|
|
97
|
+
)
|
|
98
|
+
var.options.query = synthetic_name
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _build_variable_registry(face: Face) -> dict[str, Variable]:
|
|
102
|
+
"""Build global variable registry by traversing compiled face tree.
|
|
103
|
+
|
|
104
|
+
Collects all variables from the entire face tree (root and nested faces).
|
|
105
|
+
Validates that variable names are unique across the entire tree.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
face: Root Face to traverse
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dict mapping variable name to Variable object (all variables from entire tree)
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
CompilationError: If duplicate variable names found
|
|
115
|
+
"""
|
|
116
|
+
registry: dict[str, Variable] = {}
|
|
117
|
+
_collect_variables_recursive(face, registry)
|
|
118
|
+
return registry
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _collect_variables_recursive(face: Face, registry: dict[str, Variable]) -> None:
|
|
122
|
+
"""Recursively collect variables from face and nested faces.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
face: Face to collect variables from
|
|
126
|
+
registry: Registry dict to add variables to
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
CompilationError: If duplicate variable names found
|
|
130
|
+
"""
|
|
131
|
+
# Add variables from this face
|
|
132
|
+
if face.variables:
|
|
133
|
+
for var_name, var in face.variables.items():
|
|
134
|
+
if var_name in registry:
|
|
135
|
+
raise CompilationError(
|
|
136
|
+
f"Duplicate variable name '{var_name}'. Variable names must be unique "
|
|
137
|
+
"within a file (and those that are imported)."
|
|
138
|
+
)
|
|
139
|
+
registry[var_name] = var
|
|
140
|
+
|
|
141
|
+
# Recursively collect from nested faces in layout
|
|
142
|
+
if face.layout.items:
|
|
143
|
+
for item in face.layout.items:
|
|
144
|
+
if item.type == "face" and item.face:
|
|
145
|
+
_collect_variables_recursive(item.face, registry)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _validate_variable_references(face: Face) -> list[str]:
|
|
149
|
+
"""Validate that all referenced variables are defined.
|
|
150
|
+
|
|
151
|
+
Checks that all variables referenced in queries and charts exist in
|
|
152
|
+
the variable registry. Returns warnings for any undefined references.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
face: Root Face with variable_registry set
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List of warning messages for undefined variable references
|
|
159
|
+
"""
|
|
160
|
+
warnings: list[str] = []
|
|
161
|
+
defined_vars = (
|
|
162
|
+
set(face.variable_registry.keys()) if face.variable_registry else set()
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Collect all referenced variables from charts
|
|
166
|
+
_collect_referenced_vars_recursive(face, defined_vars, warnings)
|
|
167
|
+
|
|
168
|
+
return warnings
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _collect_referenced_vars_recursive(
|
|
172
|
+
face: Face,
|
|
173
|
+
defined_vars: set,
|
|
174
|
+
warnings: list[str],
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Recursively check variable references in face and nested faces.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
face: Face to check
|
|
180
|
+
defined_vars: Set of defined variable names
|
|
181
|
+
warnings: List to append warnings to
|
|
182
|
+
"""
|
|
183
|
+
# Check charts in this face
|
|
184
|
+
for chart_name, chart in face.charts.items():
|
|
185
|
+
for var_name in chart.variable_dependencies:
|
|
186
|
+
if var_name not in defined_vars:
|
|
187
|
+
warnings.append(
|
|
188
|
+
f"Chart '{chart_name}' references undefined variable '{var_name}'"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Check queries in this face
|
|
192
|
+
for query_name, query in face.queries.items():
|
|
193
|
+
for var_name in query.variable_dependencies:
|
|
194
|
+
if var_name not in defined_vars:
|
|
195
|
+
warnings.append(
|
|
196
|
+
f"Query '{query_name}' references undefined variable '{var_name}'"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Recursively check nested faces
|
|
200
|
+
if face.layout.items:
|
|
201
|
+
for item in face.layout.items:
|
|
202
|
+
if item.type == "face" and item.face:
|
|
203
|
+
_collect_referenced_vars_recursive(item.face, defined_vars, warnings)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _generate_layout_variables(layout: Layout) -> dict[str, Variable]:
|
|
207
|
+
"""Generate hidden variables for tabs and details in this layout only.
|
|
208
|
+
|
|
209
|
+
Tabs get a hidden select variable (slug values).
|
|
210
|
+
Details get a hidden checkbox variable (boolean).
|
|
211
|
+
|
|
212
|
+
Only generates variables for the current layout level — nested faces
|
|
213
|
+
handle their own variables during their own normalize_face() call.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Dict of variable_name → Variable for all auto-generated variables.
|
|
217
|
+
"""
|
|
218
|
+
from dataface.core.compile.models.variable.authored import VariableOptions
|
|
219
|
+
|
|
220
|
+
variables: dict[str, Variable] = {}
|
|
221
|
+
|
|
222
|
+
# Tabs: generate a select variable
|
|
223
|
+
if layout.type == "tabs" and layout.tab_variable and layout.tab_slugs:
|
|
224
|
+
variables[layout.tab_variable] = Variable(
|
|
225
|
+
input="select",
|
|
226
|
+
visible=False,
|
|
227
|
+
default=layout.tab_slugs[layout.default_tab or 0],
|
|
228
|
+
options=VariableOptions(
|
|
229
|
+
static=[*layout.tab_slugs],
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Walk direct child items for details variables only.
|
|
234
|
+
# Don't recurse into nested faces — they already ran normalize_face()
|
|
235
|
+
# which called _generate_layout_variables() for their own layouts.
|
|
236
|
+
for item in layout.items:
|
|
237
|
+
if item.details_variable:
|
|
238
|
+
default = False
|
|
239
|
+
if item.face and item.face.meta:
|
|
240
|
+
default = item.face.meta.get("details_expanded_default", False)
|
|
241
|
+
variables[item.details_variable] = Variable(
|
|
242
|
+
input="checkbox",
|
|
243
|
+
visible=False,
|
|
244
|
+
default=default,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return variables
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Type validators: input_type -> (type_check_fn, expected_type_desc)
|
|
251
|
+
# text/select/input/textarea/radio accept any scalar type (no validation needed)
|
|
252
|
+
_TYPE_VALIDATORS: dict[str, tuple[type | tuple[type, ...], str]] = {
|
|
253
|
+
"number": ((int, float), "a number"),
|
|
254
|
+
"slider": ((int, float), "a number"),
|
|
255
|
+
"range": ((int, float), "a number"),
|
|
256
|
+
"checkbox": (bool, "a boolean"),
|
|
257
|
+
"multiselect": (list, "a list"),
|
|
258
|
+
"date": (str, "a string"),
|
|
259
|
+
"datepicker": (str, "a string"),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _validate_variable_value(var_name: str, var: Variable, value: Any) -> None:
|
|
264
|
+
"""Validate a variable value against its input type.
|
|
265
|
+
|
|
266
|
+
Called at compile time to validate default values. Uses data-driven
|
|
267
|
+
validation via _TYPE_VALIDATORS dict.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
var_name: Variable name (for error messages)
|
|
271
|
+
var: Variable definition with input type
|
|
272
|
+
value: Value to validate
|
|
273
|
+
|
|
274
|
+
Raises:
|
|
275
|
+
CompilationError: If value doesn't match expected type/format
|
|
276
|
+
"""
|
|
277
|
+
if value is None:
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
input_type = var.input
|
|
281
|
+
|
|
282
|
+
# Special case: daterange needs structural validation (2-element list)
|
|
283
|
+
if input_type == "daterange":
|
|
284
|
+
if not isinstance(value, (list, tuple)) or len(value) != 2:
|
|
285
|
+
raise CompilationError(
|
|
286
|
+
f"Variable '{var_name}': daterange default must be [start, end], "
|
|
287
|
+
f"got {type(value).__name__}"
|
|
288
|
+
)
|
|
289
|
+
for i, v in enumerate(value):
|
|
290
|
+
if v is not None and not isinstance(v, str):
|
|
291
|
+
raise CompilationError(
|
|
292
|
+
f"Variable '{var_name}': daterange[{i}] must be string, "
|
|
293
|
+
f"got {type(v).__name__}"
|
|
294
|
+
)
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
# Standard type validation from dict
|
|
298
|
+
if input_type in _TYPE_VALIDATORS:
|
|
299
|
+
expected_type, desc = _TYPE_VALIDATORS[input_type]
|
|
300
|
+
if not isinstance(value, expected_type):
|
|
301
|
+
raise CompilationError(
|
|
302
|
+
f"Variable '{var_name}': {input_type} default must be {desc}, "
|
|
303
|
+
f"got {type(value).__name__}"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Slider/range: additional min/max bounds check
|
|
307
|
+
if input_type in ("slider", "range"):
|
|
308
|
+
if var.min is not None and value < var.min:
|
|
309
|
+
raise CompilationError(
|
|
310
|
+
f"Variable '{var_name}': {input_type} default {value} < min {var.min}"
|
|
311
|
+
)
|
|
312
|
+
if var.max is not None and value > var.max:
|
|
313
|
+
raise CompilationError(
|
|
314
|
+
f"Variable '{var_name}': {input_type} default {value} > max {var.max}"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _compute_variable_dependencies(
|
|
319
|
+
variables: dict[str, Variable],
|
|
320
|
+
query_registry: dict[str, AnyQuery],
|
|
321
|
+
) -> None:
|
|
322
|
+
"""Compute variable dependencies for cascading dropdowns.
|
|
323
|
+
|
|
324
|
+
Analyzes each variable's options query SQL to find Jinja references
|
|
325
|
+
to other variables. Updates the variable_dependencies field in-place.
|
|
326
|
+
|
|
327
|
+
Also detects circular dependencies and raises an error if found.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
variables: Dict of variable definitions to analyze
|
|
331
|
+
query_registry: Query registry to look up options queries
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
CompilationError: If circular variable dependencies detected
|
|
335
|
+
|
|
336
|
+
Example:
|
|
337
|
+
If variable 'state' has options.query = 'state_options' and the
|
|
338
|
+
state_options query SQL contains {{ filter('country', country) }},
|
|
339
|
+
then state.variable_dependencies will be {'country'}.
|
|
340
|
+
"""
|
|
341
|
+
# Build dependency graph
|
|
342
|
+
dep_graph: dict[str, set] = {}
|
|
343
|
+
|
|
344
|
+
for var_name, var in variables.items():
|
|
345
|
+
deps: set = set()
|
|
346
|
+
|
|
347
|
+
# Get the options query name
|
|
348
|
+
options_query_name = var.get_option_query()
|
|
349
|
+
if options_query_name and options_query_name in query_registry:
|
|
350
|
+
query = query_registry[options_query_name]
|
|
351
|
+
|
|
352
|
+
# Extract variable dependencies from query SQL and setup_sql
|
|
353
|
+
if hasattr(query, "sql") and query.sql:
|
|
354
|
+
sql_deps = extract_variable_dependencies(query.sql)
|
|
355
|
+
# Filter to only include variables that actually exist
|
|
356
|
+
deps = sql_deps & set(variables.keys())
|
|
357
|
+
if hasattr(query, "setup_sql") and query.setup_sql:
|
|
358
|
+
setup_deps = extract_variable_dependencies(query.setup_sql)
|
|
359
|
+
deps |= setup_deps & set(variables.keys())
|
|
360
|
+
|
|
361
|
+
# Also check query filters
|
|
362
|
+
if hasattr(query, "filters") and query.filters:
|
|
363
|
+
for filter_value in query.filters.values():
|
|
364
|
+
if isinstance(filter_value, str):
|
|
365
|
+
filter_deps = extract_variable_dependencies(filter_value)
|
|
366
|
+
deps |= filter_deps & set(variables.keys())
|
|
367
|
+
|
|
368
|
+
# Also extract deps from disabled Jinja expressions so the UI
|
|
369
|
+
# re-evaluates disabled state when any referenced variable changes.
|
|
370
|
+
if isinstance(var.disabled, str):
|
|
371
|
+
disabled_deps = extract_variable_dependencies(var.disabled)
|
|
372
|
+
deps |= disabled_deps & set(variables.keys())
|
|
373
|
+
|
|
374
|
+
# Remove self-reference (a variable can't depend on itself)
|
|
375
|
+
deps.discard(var_name)
|
|
376
|
+
|
|
377
|
+
# Update the variable's dependencies
|
|
378
|
+
var.variable_dependencies = deps
|
|
379
|
+
dep_graph[var_name] = deps
|
|
380
|
+
|
|
381
|
+
# Check for circular dependencies
|
|
382
|
+
_detect_circular_variable_deps(dep_graph)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _detect_circular_variable_deps(dep_graph: dict[str, set]) -> None:
|
|
386
|
+
"""Detect circular dependencies in variable dependency graph.
|
|
387
|
+
|
|
388
|
+
Uses depth-first search to find cycles.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
dep_graph: Dict mapping variable name to set of dependencies
|
|
392
|
+
|
|
393
|
+
Raises:
|
|
394
|
+
CompilationError: If circular dependency found
|
|
395
|
+
"""
|
|
396
|
+
visited: set = set()
|
|
397
|
+
rec_stack: set = set()
|
|
398
|
+
|
|
399
|
+
def dfs(node: str, path: list[str]) -> None:
|
|
400
|
+
if node in rec_stack:
|
|
401
|
+
# Found a cycle - build the cycle path for error message
|
|
402
|
+
cycle_start = path.index(node)
|
|
403
|
+
cycle = path[cycle_start:] + [node]
|
|
404
|
+
raise CompilationError(
|
|
405
|
+
f"Circular variable dependency detected: {' → '.join(cycle)}. "
|
|
406
|
+
"Variables cannot depend on each other in a cycle. "
|
|
407
|
+
"Check the options queries for these variables."
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if node in visited:
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
visited.add(node)
|
|
414
|
+
rec_stack.add(node)
|
|
415
|
+
|
|
416
|
+
for dep in dep_graph.get(node, set()):
|
|
417
|
+
if dep in dep_graph: # Only follow known variables
|
|
418
|
+
dfs(dep, path + [node])
|
|
419
|
+
|
|
420
|
+
rec_stack.remove(node)
|
|
421
|
+
|
|
422
|
+
for var_name in dep_graph:
|
|
423
|
+
dfs(var_name, [])
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def get_transitive_variable_dependencies(
|
|
427
|
+
variable_name: str,
|
|
428
|
+
variables: dict[str, Variable],
|
|
429
|
+
) -> set:
|
|
430
|
+
"""Get all transitive dependencies for a variable.
|
|
431
|
+
|
|
432
|
+
If variable A depends on B, and B depends on C, then A's transitive
|
|
433
|
+
dependencies are {B, C}.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
variable_name: Variable to get dependencies for
|
|
437
|
+
variables: Dict of all variables
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
Set of all variable names that this variable depends on (directly or transitively)
|
|
441
|
+
"""
|
|
442
|
+
result: set = set()
|
|
443
|
+
visited: set = set()
|
|
444
|
+
|
|
445
|
+
def collect_deps(var_name: str) -> None:
|
|
446
|
+
if var_name in visited:
|
|
447
|
+
return
|
|
448
|
+
visited.add(var_name)
|
|
449
|
+
|
|
450
|
+
var = variables.get(var_name)
|
|
451
|
+
if not var:
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
for dep in var.variable_dependencies:
|
|
455
|
+
result.add(dep)
|
|
456
|
+
collect_deps(dep)
|
|
457
|
+
|
|
458
|
+
collect_deps(variable_name)
|
|
459
|
+
return result
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _expand_chart_variable_dependencies(
|
|
463
|
+
charts: dict[str, "Chart"],
|
|
464
|
+
variables: dict[str, Variable],
|
|
465
|
+
) -> None:
|
|
466
|
+
"""Expand chart variable dependencies to include transitive dependencies.
|
|
467
|
+
|
|
468
|
+
If a chart depends on variable A, and A depends on B (cascading dropdown),
|
|
469
|
+
then the chart should also track dependency on B so the UI knows to
|
|
470
|
+
re-render when B changes.
|
|
471
|
+
|
|
472
|
+
Modifies charts in place.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
charts: Dict of compiled charts
|
|
476
|
+
variables: Dict of variables with computed dependencies
|
|
477
|
+
"""
|
|
478
|
+
for chart in charts.values():
|
|
479
|
+
expanded_deps: set = set()
|
|
480
|
+
|
|
481
|
+
# For each variable the chart directly depends on
|
|
482
|
+
for var_name in chart.variable_dependencies:
|
|
483
|
+
expanded_deps.add(var_name)
|
|
484
|
+
# Add all transitive dependencies of that variable
|
|
485
|
+
transitive = get_transitive_variable_dependencies(var_name, variables)
|
|
486
|
+
expanded_deps |= transitive
|
|
487
|
+
|
|
488
|
+
# Update chart's variable dependencies
|
|
489
|
+
chart.variable_dependencies = expanded_deps
|