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,140 @@
|
|
|
1
|
+
"""Data-aware variable input type refinement.
|
|
2
|
+
|
|
3
|
+
Stage: RENDER (post-query)
|
|
4
|
+
Purpose: Inspect resolved options data to refine the compile-time input type.
|
|
5
|
+
|
|
6
|
+
The compile-time detector (_detect_variable_input_type) handles YAML-structural
|
|
7
|
+
signals: options → select, min/max → slider, bool default → checkbox. This module
|
|
8
|
+
adds the second phase: data-aware refinement that runs after options queries
|
|
9
|
+
execute — mirroring how chart type auto-detection works at render time.
|
|
10
|
+
|
|
11
|
+
Detection priority: date > boolean > numeric-slider > unchanged.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import math
|
|
15
|
+
from datetime import date
|
|
16
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
17
|
+
|
|
18
|
+
from dataface.core.compile.models.variable.authored import VariableInputType
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from dataface.core.compile.models.variable.authored import Variable
|
|
22
|
+
|
|
23
|
+
# Thresholds
|
|
24
|
+
_SLIDER_MAX_DISTINCT = 200 # Don't offer slider for huge option sets
|
|
25
|
+
_SLIDER_MAX_RANGE_STEPS = 1000 # Range / step must be <= this
|
|
26
|
+
|
|
27
|
+
_BOOLEAN_PAIRS = frozenset(
|
|
28
|
+
{
|
|
29
|
+
frozenset({"true", "false"}),
|
|
30
|
+
frozenset({"yes", "no"}),
|
|
31
|
+
# Note: {"0", "1"} is ambiguous — could be numeric range.
|
|
32
|
+
# We treat it as boolean here since a 0/1 slider isn't useful.
|
|
33
|
+
frozenset({"0", "1"}),
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RefinedType(NamedTuple):
|
|
39
|
+
"""Result of data-aware input type refinement."""
|
|
40
|
+
|
|
41
|
+
input_type: VariableInputType
|
|
42
|
+
slider_min: int | float | None = None
|
|
43
|
+
slider_max: int | float | None = None
|
|
44
|
+
slider_step: int | float | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def refine_input_type_from_data(
|
|
48
|
+
var_def: "Variable",
|
|
49
|
+
option_values: list[str],
|
|
50
|
+
) -> RefinedType:
|
|
51
|
+
"""Refine a variable's input type based on its resolved option values.
|
|
52
|
+
|
|
53
|
+
Only refines when the compile-time type was auto-detected (input_auto_detected).
|
|
54
|
+
Explicit author-set types are never overridden. Does not mutate var_def.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
var_def: Variable definition (read-only).
|
|
58
|
+
option_values: Resolved distinct option values as strings.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
RefinedType with the input type and optional slider bounds.
|
|
62
|
+
"""
|
|
63
|
+
current = var_def.input
|
|
64
|
+
if not var_def.input_auto_detected:
|
|
65
|
+
return RefinedType(current)
|
|
66
|
+
|
|
67
|
+
if not option_values:
|
|
68
|
+
return RefinedType(current)
|
|
69
|
+
|
|
70
|
+
# --- Type-based detection (strongest signal) ---
|
|
71
|
+
|
|
72
|
+
if _looks_like_dates(option_values):
|
|
73
|
+
return RefinedType("datepicker")
|
|
74
|
+
|
|
75
|
+
if _looks_like_booleans(option_values):
|
|
76
|
+
return RefinedType("checkbox")
|
|
77
|
+
|
|
78
|
+
if len(option_values) <= _SLIDER_MAX_DISTINCT:
|
|
79
|
+
parsed = _try_parse_numbers(option_values)
|
|
80
|
+
if parsed is not None:
|
|
81
|
+
min_val, max_val = min(parsed), max(parsed)
|
|
82
|
+
val_range = max_val - min_val
|
|
83
|
+
if val_range > 0:
|
|
84
|
+
all_ints = all(v.is_integer() for v in parsed)
|
|
85
|
+
step = 1 if all_ints else round(val_range / 100, 6)
|
|
86
|
+
if val_range / step <= _SLIDER_MAX_RANGE_STEPS:
|
|
87
|
+
return RefinedType(
|
|
88
|
+
"slider",
|
|
89
|
+
slider_min=int(min_val) if all_ints else min_val,
|
|
90
|
+
slider_max=int(max_val) if all_ints else max_val,
|
|
91
|
+
slider_step=int(step) if all_ints else step,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# No type-based refinement matched — keep compile-time type (select).
|
|
95
|
+
return RefinedType(current)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _looks_like_dates(values: list[str]) -> bool:
|
|
99
|
+
"""True if >= 80% of values parse as ISO dates via datetime.date.fromisoformat.
|
|
100
|
+
|
|
101
|
+
Single-value lists return False — a single option is too little signal.
|
|
102
|
+
"""
|
|
103
|
+
if len(values) < 2:
|
|
104
|
+
return False
|
|
105
|
+
matches = 0
|
|
106
|
+
for v in values:
|
|
107
|
+
try:
|
|
108
|
+
# Require full YYYY-MM-DD (10 chars minimum)
|
|
109
|
+
if len(v) < 10:
|
|
110
|
+
continue
|
|
111
|
+
date.fromisoformat(v[:10])
|
|
112
|
+
# Reject if there's trailing content that isn't a time separator
|
|
113
|
+
if len(v) > 10 and v[10] not in ("T", " ", "\t"):
|
|
114
|
+
continue
|
|
115
|
+
matches += 1
|
|
116
|
+
except ValueError:
|
|
117
|
+
pass
|
|
118
|
+
return matches / len(values) >= 0.8
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _looks_like_booleans(values: list[str]) -> bool:
|
|
122
|
+
"""True if values are exactly a known boolean pair."""
|
|
123
|
+
if len(values) != 2:
|
|
124
|
+
return False
|
|
125
|
+
pair = frozenset(v.lower().strip() for v in values)
|
|
126
|
+
return pair in _BOOLEAN_PAIRS
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _try_parse_numbers(values: list[str]) -> list[float] | None:
|
|
130
|
+
"""Try to parse all values as finite numbers. Returns None if any fail."""
|
|
131
|
+
parsed: list[float] = []
|
|
132
|
+
for v in values:
|
|
133
|
+
try:
|
|
134
|
+
n = float(v)
|
|
135
|
+
if not math.isfinite(n):
|
|
136
|
+
return None
|
|
137
|
+
parsed.append(n)
|
|
138
|
+
except (ValueError, TypeError):
|
|
139
|
+
return None
|
|
140
|
+
return parsed
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Render-time warning system: models, registry, runner, and suppression."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataface.core.render.warnings.base import RenderWarning, WarningContext
|
|
6
|
+
from dataface.core.render.warnings.registry import DETECTORS, run_all
|
|
7
|
+
from dataface.core.render.warnings.suppression import partition
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"RenderWarning",
|
|
11
|
+
"WarningContext",
|
|
12
|
+
"DETECTORS",
|
|
13
|
+
"run_all",
|
|
14
|
+
"partition",
|
|
15
|
+
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Detector: BAR_COLOR_1_TO_1_WITH_X.
|
|
2
|
+
|
|
3
|
+
Fires on bar charts where the color field is 1:1 with the x field.
|
|
4
|
+
When this happens Vega-Lite allocates a grouped-bar sub-band for each
|
|
5
|
+
color value per x tick, but since each x tick only has one color value,
|
|
6
|
+
all other sub-bands are empty — the visible bar occupies 1/N of the slot
|
|
7
|
+
width, producing razor-thin bars.
|
|
8
|
+
|
|
9
|
+
Detection rule:
|
|
10
|
+
unique (x, color) pairs == unique x values AND len(unique x) >= 2
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataface.core.render.warnings.base import RenderWarning, WarningContext
|
|
16
|
+
|
|
17
|
+
CODE = "BAR_COLOR_1_TO_1_WITH_X"
|
|
18
|
+
|
|
19
|
+
# Minimum distinct x values before the rule fires; avoids degenerate 1-row data.
|
|
20
|
+
_MIN_UNIQUE_X = 2
|
|
21
|
+
|
|
22
|
+
# Bar chart types eligible for this detector.
|
|
23
|
+
_BAR_TYPES = frozenset({"bar"})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def detect(ctx: WarningContext) -> list[RenderWarning]:
|
|
27
|
+
"""Return one RenderWarning per bar chart with color 1:1 with x."""
|
|
28
|
+
warnings: list[RenderWarning] = []
|
|
29
|
+
|
|
30
|
+
for chart_id, chart in ctx.face_spec.charts.items():
|
|
31
|
+
# Only bar charts with a color encoding and an x encoding.
|
|
32
|
+
if chart.type not in _BAR_TYPES:
|
|
33
|
+
continue
|
|
34
|
+
if chart.color is None:
|
|
35
|
+
continue
|
|
36
|
+
if chart.x is None:
|
|
37
|
+
continue
|
|
38
|
+
# color == x is intentional reuse, not the thin-bar pathology.
|
|
39
|
+
if chart.color == chart.x:
|
|
40
|
+
continue
|
|
41
|
+
# Stacked bars don't produce sub-bands; the thin-bar pathology only
|
|
42
|
+
# appears in grouped layout, which requires chart.stack to be falsy.
|
|
43
|
+
if chart.stack:
|
|
44
|
+
continue
|
|
45
|
+
# Respect the sparse vega_specs contract.
|
|
46
|
+
if chart_id not in ctx.vega_specs:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
x_field: str = chart.x
|
|
50
|
+
color_field: str = chart.color
|
|
51
|
+
rows = ctx.chart_results.get(chart_id, [])
|
|
52
|
+
|
|
53
|
+
unique_x = {row[x_field] for row in rows if x_field in row}
|
|
54
|
+
if len(unique_x) < _MIN_UNIQUE_X:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
unique_pairs = {
|
|
58
|
+
(row[x_field], row[color_field])
|
|
59
|
+
for row in rows
|
|
60
|
+
if x_field in row and color_field in row
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if len(unique_x) == len(unique_pairs):
|
|
64
|
+
warnings.append(
|
|
65
|
+
RenderWarning(
|
|
66
|
+
code=CODE,
|
|
67
|
+
chart=chart_id,
|
|
68
|
+
field=color_field,
|
|
69
|
+
message=(
|
|
70
|
+
f"Color field '{color_field}' has the same cardinality as x field "
|
|
71
|
+
f"'{x_field}'; every bar gets a solo sub-band, producing razor-thin bars."
|
|
72
|
+
),
|
|
73
|
+
fix=(
|
|
74
|
+
"Drop the color encoding or pick a color field with lower "
|
|
75
|
+
"cardinality than the x axis."
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return warnings
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Pydantic models for render-time warnings.
|
|
2
|
+
|
|
3
|
+
RenderWarning is the wire shape for a single warning (named RenderWarning, not
|
|
4
|
+
Warning, to avoid shadowing the stdlib Warning base class).
|
|
5
|
+
|
|
6
|
+
WarningContext carries everything detectors need:
|
|
7
|
+
- face_spec: the compiled Face (typed — detectors use attribute access).
|
|
8
|
+
- chart_results: chart id → list of row dicts (query output).
|
|
9
|
+
- vega_specs: chart id → Vega-Lite spec dict.
|
|
10
|
+
|
|
11
|
+
vega_specs is SPARSE: KPI, text, and markdown charts do not compile to
|
|
12
|
+
Vega-Lite, so their ids are OMITTED from this dict (not present as None).
|
|
13
|
+
Detectors that key into vega_specs must guard with:
|
|
14
|
+
if chart_id not in ctx.vega_specs:
|
|
15
|
+
continue
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict
|
|
23
|
+
|
|
24
|
+
from dataface.core.compile.models.face.compiled import Face
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RenderWarning(BaseModel):
|
|
28
|
+
model_config = ConfigDict(extra="forbid")
|
|
29
|
+
|
|
30
|
+
chart: str | None = None
|
|
31
|
+
code: str
|
|
32
|
+
field: str | None = None
|
|
33
|
+
message: str
|
|
34
|
+
fix: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WarningContext(BaseModel):
|
|
38
|
+
model_config = ConfigDict(extra="forbid")
|
|
39
|
+
|
|
40
|
+
face_spec: Face
|
|
41
|
+
# chart id → list of row dicts from the executed query
|
|
42
|
+
chart_results: dict[str, list[dict[str, Any]]]
|
|
43
|
+
# chart id → Vega-Lite spec dict (sparse — non-vega charts are omitted)
|
|
44
|
+
vega_specs: dict[str, dict[str, Any]]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Emitter: FANOUT_RISK.
|
|
2
|
+
|
|
3
|
+
Fires when query validation detects a join that may multiply rows beyond
|
|
4
|
+
what the chart's aggregation can recover. The diagnostic comes from
|
|
5
|
+
``dataface.core.inspect.query_validator`` (code ``fanout_risk``); when a
|
|
6
|
+
cached relationship context (super-schema profiles) is available, severity
|
|
7
|
+
is calibrated against known multiplicities.
|
|
8
|
+
|
|
9
|
+
Emitted by ``dataface.core.compile.compiler.validate_compiled_queries``
|
|
10
|
+
(invoked indirectly via ``dataface_super_schema.inspect.relationship_warnings``).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
CODE = "FANOUT_RISK"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Adapter: QueryDiagnostic → RenderWarning.
|
|
2
|
+
|
|
3
|
+
The query validator emits ``QueryDiagnostic`` with lowercase snake_case codes
|
|
4
|
+
(``fanout_risk``, ``missing_join_predicate``, etc.). The unified warning
|
|
5
|
+
channel uses ``RenderWarning`` with SCREAMING_SNAKE_CASE codes owned by
|
|
6
|
+
per-code modules. This adapter maps between the two.
|
|
7
|
+
|
|
8
|
+
Each ``QueryDiagnostic.code`` value has a matching module in this package
|
|
9
|
+
that owns the stable RenderWarning ``CODE``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
from dataface.core.render.warnings import (
|
|
17
|
+
fanout_risk,
|
|
18
|
+
missing_join_predicate,
|
|
19
|
+
query_parse_error,
|
|
20
|
+
reaggregation,
|
|
21
|
+
)
|
|
22
|
+
from dataface.core.render.warnings.base import RenderWarning
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from dataface.core.inspect.query_validator import QueryDiagnostic
|
|
26
|
+
|
|
27
|
+
_DIAGNOSTIC_CODE_TO_RENDER_CODE: dict[str, str] = {
|
|
28
|
+
"fanout_risk": fanout_risk.CODE,
|
|
29
|
+
"missing_join_predicate": missing_join_predicate.CODE,
|
|
30
|
+
"parse_error": query_parse_error.CODE,
|
|
31
|
+
"reaggregation": reaggregation.CODE,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
KNOWN_RENDER_CODES: frozenset[str] = frozenset(_DIAGNOSTIC_CODE_TO_RENDER_CODE.values())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def from_query_diagnostic(query_name: str, diag: QueryDiagnostic) -> RenderWarning:
|
|
38
|
+
"""Convert a query validator diagnostic into a RenderWarning.
|
|
39
|
+
|
|
40
|
+
The query name is woven into the message; ``detail`` and ``evidence`` (which
|
|
41
|
+
carry table names and relationship info for fanout/join diagnostics) are
|
|
42
|
+
appended so context survives the lossy conversion.
|
|
43
|
+
"""
|
|
44
|
+
render_code = _DIAGNOSTIC_CODE_TO_RENDER_CODE.get(diag.code)
|
|
45
|
+
if render_code is None:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"No RenderWarning code mapped for QueryDiagnostic.code={diag.code!r}. "
|
|
48
|
+
f"Add an entry to _DIAGNOSTIC_CODE_TO_RENDER_CODE and a matching module "
|
|
49
|
+
f"under dataface.core.render.warnings."
|
|
50
|
+
)
|
|
51
|
+
parts = [f"Query '{query_name}': {diag.message}", diag.detail, *diag.evidence]
|
|
52
|
+
stripped = [p.removesuffix(".") for p in parts if p]
|
|
53
|
+
message = ". ".join(stripped)
|
|
54
|
+
if message and message[-1] not in "?!":
|
|
55
|
+
message += "."
|
|
56
|
+
return RenderWarning(code=render_code, message=message, fix=diag.recommendation)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Emitter: MISSING_JOIN_PREDICATE.
|
|
2
|
+
|
|
3
|
+
Fires when query validation detects an implicit cross join or an explicit
|
|
4
|
+
CROSS JOIN without a predicate. These almost always indicate a missing
|
|
5
|
+
``ON`` clause and produce wildly fanned-out result sets.
|
|
6
|
+
|
|
7
|
+
Emitted by ``dataface.core.compile.compiler.validate_compiled_queries`` via
|
|
8
|
+
the query_validator's ``missing_join_predicate`` diagnostic.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
CODE = "MISSING_JOIN_PREDICATE"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Emitter: QUERY_PARSE_ERROR.
|
|
2
|
+
|
|
3
|
+
Fires when the query validator cannot parse a SQL query as a structured
|
|
4
|
+
AST. The query may still execute against the warehouse — this warning
|
|
5
|
+
surfaces that semantic validation (fanout, reaggregation) cannot run on
|
|
6
|
+
unparseable SQL.
|
|
7
|
+
|
|
8
|
+
Emitted by ``dataface.core.compile.compiler.validate_compiled_queries`` via
|
|
9
|
+
the query_validator's ``parse_error`` diagnostic.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
CODE = "QUERY_PARSE_ERROR"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Detector: QUERY_RETURNED_ZERO_ROWS.
|
|
2
|
+
|
|
3
|
+
Fires on any chart whose query returned zero rows. An empty chart renders as a
|
|
4
|
+
blank panel with axes — no signal to the viewer that the query returned nothing.
|
|
5
|
+
Most common cause: a WHERE clause or date filter that excludes all data.
|
|
6
|
+
|
|
7
|
+
Detection rule:
|
|
8
|
+
len(ctx.chart_results[chart_id]) == 0
|
|
9
|
+
|
|
10
|
+
This detector intentionally does NOT gate on ctx.vega_specs. KPI, text, and
|
|
11
|
+
markdown charts are omitted from vega_specs, but zero rows is still meaningful
|
|
12
|
+
for them — e.g. a KPI with no data is a silent failure.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataface.core.render.warnings.base import RenderWarning, WarningContext
|
|
18
|
+
|
|
19
|
+
CODE = "QUERY_RETURNED_ZERO_ROWS"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def detect(ctx: WarningContext) -> list[RenderWarning]:
|
|
23
|
+
"""Return one RenderWarning per chart whose query returned zero rows."""
|
|
24
|
+
warnings: list[RenderWarning] = []
|
|
25
|
+
|
|
26
|
+
for chart_id in ctx.face_spec.charts:
|
|
27
|
+
# Charts absent from chart_results failed to execute — not a zero-row result.
|
|
28
|
+
if chart_id not in ctx.chart_results:
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
if len(ctx.chart_results[chart_id]) == 0:
|
|
32
|
+
warnings.append(
|
|
33
|
+
RenderWarning(
|
|
34
|
+
code=CODE,
|
|
35
|
+
chart=chart_id,
|
|
36
|
+
field=None,
|
|
37
|
+
message="Query returned zero rows.",
|
|
38
|
+
fix="Check WHERE clause or date filter.",
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return warnings
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Emitter: REAGGREGATION.
|
|
2
|
+
|
|
3
|
+
Fires when query validation detects aggregation applied on top of an
|
|
4
|
+
already-aggregated input (e.g. ``SUM(SUM(...))`` patterns or aggregation
|
|
5
|
+
over a query result that itself aggregates). The result is usually not
|
|
6
|
+
what the author intended.
|
|
7
|
+
|
|
8
|
+
Emitted by ``dataface.core.compile.compiler.validate_compiled_queries`` via
|
|
9
|
+
the query_validator's ``reaggregation`` diagnostic.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
CODE = "REAGGREGATION"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Warning detector registry.
|
|
2
|
+
|
|
3
|
+
DETECTORS is an explicit list of detector modules. Each module must expose:
|
|
4
|
+
CODE: str — stable SCREAMING_SNAKE_CASE identifier for this warning.
|
|
5
|
+
detect(ctx: WarningContext) -> list[RenderWarning]
|
|
6
|
+
|
|
7
|
+
Detector tasks append to DETECTORS as they land. Registered detectors run
|
|
8
|
+
sequentially; per-detector exceptions are isolated so a single bad detector
|
|
9
|
+
cannot abort the render pipeline.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from types import ModuleType
|
|
16
|
+
|
|
17
|
+
import dataface.core.render.warnings.bar_color_1_to_1_with_x as bar_color_1_to_1_with_x
|
|
18
|
+
import dataface.core.render.warnings.query_returned_zero_rows as query_returned_zero_rows
|
|
19
|
+
import dataface.core.render.warnings.temporal_single_point as temporal_single_point
|
|
20
|
+
import dataface.core.render.warnings.y_encoding_mostly_null as y_encoding_mostly_null
|
|
21
|
+
from dataface.core.render.warnings.base import RenderWarning, WarningContext
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
DETECTORS: list[ModuleType] = [
|
|
26
|
+
bar_color_1_to_1_with_x,
|
|
27
|
+
query_returned_zero_rows,
|
|
28
|
+
temporal_single_point,
|
|
29
|
+
y_encoding_mostly_null,
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_all(ctx: WarningContext) -> list[RenderWarning]:
|
|
34
|
+
"""Run every registered detector and return the concatenated warnings.
|
|
35
|
+
|
|
36
|
+
A detector that raises must not abort the render — exceptions are caught
|
|
37
|
+
here, logged, and skipped.
|
|
38
|
+
"""
|
|
39
|
+
results: list[RenderWarning] = []
|
|
40
|
+
for detector in DETECTORS:
|
|
41
|
+
try:
|
|
42
|
+
results.extend(detector.detect(ctx))
|
|
43
|
+
except Exception: # noqa: BLE001 — detector failures must not break renders
|
|
44
|
+
logger.exception("Warning detector %r raised; skipping", detector)
|
|
45
|
+
return results
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Warning suppression: partition a list of RenderWarnings into active vs suppressed.
|
|
2
|
+
|
|
3
|
+
Three ignore layers form a union — a warning is dropped if its code appears in ANY:
|
|
4
|
+
1. cli_codes: caller-provided set (e.g. --ignore-warning flags).
|
|
5
|
+
2. project_codes: global ignore list from dataface.yml `warnings.ignore`.
|
|
6
|
+
3. per_chart_codes: per-chart ignore map, keyed by chart id. Only warnings whose
|
|
7
|
+
`chart` field matches the chart id are suppressed (face-level warnings with
|
|
8
|
+
chart=None are never suppressed by per_chart_codes).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataface.core.render.warnings.base import RenderWarning
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def partition(
|
|
17
|
+
warnings: list[RenderWarning],
|
|
18
|
+
cli_codes: set[str],
|
|
19
|
+
project_codes: set[str],
|
|
20
|
+
per_chart_codes: dict[str, set[str]],
|
|
21
|
+
) -> tuple[list[RenderWarning], list[RenderWarning]]:
|
|
22
|
+
"""Partition warnings into (active, suppressed).
|
|
23
|
+
|
|
24
|
+
A warning is suppressed when its code is in cli_codes, project_codes, or
|
|
25
|
+
(warning.chart is not None AND code is in per_chart_codes[warning.chart]).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
warnings: Full pre-suppression list from run_all().
|
|
29
|
+
cli_codes: Caller-supplied ignore set (CLI --ignore-warning flags).
|
|
30
|
+
project_codes: Project-global ignore set from dataface.yml.
|
|
31
|
+
per_chart_codes: Chart-id → set of codes to suppress for that chart only.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
(active, suppressed) — both lists preserve original order.
|
|
35
|
+
"""
|
|
36
|
+
active: list[RenderWarning] = []
|
|
37
|
+
suppressed: list[RenderWarning] = []
|
|
38
|
+
global_codes = cli_codes | project_codes
|
|
39
|
+
for w in warnings:
|
|
40
|
+
if w.code in global_codes or (
|
|
41
|
+
w.chart is not None and w.code in per_chart_codes.get(w.chart, set())
|
|
42
|
+
):
|
|
43
|
+
suppressed.append(w)
|
|
44
|
+
else:
|
|
45
|
+
active.append(w)
|
|
46
|
+
return active, suppressed
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Detector: TEMPORAL_SINGLE_POINT.
|
|
2
|
+
|
|
3
|
+
Fires on line and area charts where the x-axis is temporal and the query
|
|
4
|
+
result has exactly one row. A one-point line is rendered as a single dot;
|
|
5
|
+
a one-point area is a vertical line. Both render but convey nothing about a
|
|
6
|
+
trend — this almost always means the date filter is too narrow.
|
|
7
|
+
|
|
8
|
+
Detection rule:
|
|
9
|
+
chart.type in {"line", "area"}
|
|
10
|
+
AND ctx.vega_specs[chart_id].encoding.x.type == "temporal"
|
|
11
|
+
AND len(ctx.chart_results[chart_id]) == 1
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataface.core.render.warnings.base import RenderWarning, WarningContext
|
|
17
|
+
|
|
18
|
+
CODE = "TEMPORAL_SINGLE_POINT"
|
|
19
|
+
|
|
20
|
+
# Chart types where a single temporal data point is misleading.
|
|
21
|
+
_TEMPORAL_LINE_TYPES = frozenset({"line", "area"})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def detect(ctx: WarningContext) -> list[RenderWarning]:
|
|
25
|
+
"""Return one RenderWarning per line/area chart with a single temporal data point."""
|
|
26
|
+
warnings: list[RenderWarning] = []
|
|
27
|
+
|
|
28
|
+
for chart_id, chart in ctx.face_spec.charts.items():
|
|
29
|
+
if chart.type not in _TEMPORAL_LINE_TYPES:
|
|
30
|
+
continue
|
|
31
|
+
# Respect the sparse vega_specs contract — non-Vega charts are omitted.
|
|
32
|
+
if chart_id not in ctx.vega_specs:
|
|
33
|
+
continue
|
|
34
|
+
# Orphan charts (query not in pre_executed_query_names) are absent from chart_results.
|
|
35
|
+
if chart_id not in ctx.chart_results:
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
vega_x_type: str = (
|
|
39
|
+
ctx.vega_specs[chart_id].get("encoding", {}).get("x", {}).get("type", "")
|
|
40
|
+
)
|
|
41
|
+
if vega_x_type != "temporal":
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
if len(ctx.chart_results[chart_id]) != 1:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
warnings.append(
|
|
48
|
+
RenderWarning(
|
|
49
|
+
code=CODE,
|
|
50
|
+
chart=chart_id,
|
|
51
|
+
field=chart.x,
|
|
52
|
+
message=(
|
|
53
|
+
f"Chart '{chart_id}' has only one data point on a temporal x-axis; "
|
|
54
|
+
f"a single-point {chart.type} communicates no trend."
|
|
55
|
+
),
|
|
56
|
+
fix=(
|
|
57
|
+
"Broaden the time range to include more data points, "
|
|
58
|
+
"or switch to a chart type better suited to a single value (e.g. kpi)."
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return warnings
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Emitter: UNREFERENCED_CHART.
|
|
2
|
+
|
|
3
|
+
Fires at compile time for charts that are defined somewhere in the face tree
|
|
4
|
+
but never placed in any layout (rows/cols/grid/tabs). The face still compiles
|
|
5
|
+
because content-only faces are valid; this warning surfaces lazy authoring —
|
|
6
|
+
an author defined a chart and forgot to display it.
|
|
7
|
+
|
|
8
|
+
Emitted by ``dataface.core.compile.compiler._collect_orphan_charts`` (not by
|
|
9
|
+
the render-time detector registry). Lives next to the render-time detectors
|
|
10
|
+
so every stable warning code has one home.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
CODE = "UNREFERENCED_CHART"
|