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,358 @@
|
|
|
1
|
+
"""Chart decisions pipeline stage.
|
|
2
|
+
|
|
3
|
+
Stage: between EXECUTE and RENDER (layout/sizing)
|
|
4
|
+
Purpose: Enrich charts with data-aware defaults before rendering.
|
|
5
|
+
|
|
6
|
+
This module analyzes query result data and enriches Chart objects
|
|
7
|
+
with smarter defaults for axis domains and field assignment.
|
|
8
|
+
It runs for ALL charts (not just type: auto).
|
|
9
|
+
|
|
10
|
+
Pipeline position:
|
|
11
|
+
compile → execute → CHART DECISIONS → layout/sizing → render
|
|
12
|
+
|
|
13
|
+
Key principle: every auto-decision is overridable by explicit YAML.
|
|
14
|
+
If the user sets scale/fields, those win over any auto-detection.
|
|
15
|
+
|
|
16
|
+
Format defaults come from the theme cascade (axis_quantitative.format in stark.yaml),
|
|
17
|
+
not from column-name inference. Authors override per-chart via chart.format or
|
|
18
|
+
style.axis_y.format.
|
|
19
|
+
|
|
20
|
+
Dependencies:
|
|
21
|
+
- chart_type_detection (for column classification, reused here)
|
|
22
|
+
|
|
23
|
+
Note on database driver compatibility:
|
|
24
|
+
The type_code mapping in db_type_to_dtype works for DuckDB (which uses
|
|
25
|
+
string type names like "INTEGER", "TIMESTAMP"). Other DB-API drivers use
|
|
26
|
+
opaque type codes (psycopg2 uses integer OIDs, sqlite3 returns None).
|
|
27
|
+
When type_code can't be mapped, the stage falls back to data-value heuristics.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import math
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from decimal import Decimal
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from dataface.core.compile.models.chart.compiled import Chart
|
|
36
|
+
from dataface.core.compile.models.style.authored import ChartStylePatch
|
|
37
|
+
|
|
38
|
+
# Chart types that use x/y axes (not KPI, table, pie)
|
|
39
|
+
_AXIS_CHART_TYPES = frozenset({"bar", "line", "area", "scatter", "heatmap"})
|
|
40
|
+
|
|
41
|
+
# Chart types where zero anchoring is optional; the smart-auto heuristic applies.
|
|
42
|
+
# Bar and area/heatmap fall through to the legacy ratio > 0.8 rule below.
|
|
43
|
+
_OPTIONAL_ZERO_CHART_TYPES = frozenset({"line", "scatter"})
|
|
44
|
+
|
|
45
|
+
# Threshold for smart-auto zero extension. When data_min/data_max <= this value,
|
|
46
|
+
# data starts in the bottom 25% of [0, max] and extending to zero adds honest
|
|
47
|
+
# context without crushing the data. Above the threshold, keep the data-fitted
|
|
48
|
+
# domain.
|
|
49
|
+
_ZERO_EXTEND_THRESHOLD = 0.25
|
|
50
|
+
|
|
51
|
+
# PEP 249 cursor.description index constants
|
|
52
|
+
_DESC_TYPE_CODE = 1
|
|
53
|
+
|
|
54
|
+
# Database type strings → our dtype categories
|
|
55
|
+
_NUMERIC_DB_TYPES = frozenset(
|
|
56
|
+
{
|
|
57
|
+
"BIGINT",
|
|
58
|
+
"INTEGER",
|
|
59
|
+
"INT",
|
|
60
|
+
"SMALLINT",
|
|
61
|
+
"TINYINT",
|
|
62
|
+
"DOUBLE",
|
|
63
|
+
"FLOAT",
|
|
64
|
+
"REAL",
|
|
65
|
+
"DECIMAL",
|
|
66
|
+
"NUMERIC",
|
|
67
|
+
"HUGEINT",
|
|
68
|
+
"UBIGINT",
|
|
69
|
+
"UINTEGER",
|
|
70
|
+
"USMALLINT",
|
|
71
|
+
"UTINYINT",
|
|
72
|
+
"NUMBER",
|
|
73
|
+
"MONEY",
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
_TEMPORAL_DB_TYPES = frozenset(
|
|
77
|
+
{
|
|
78
|
+
"DATE",
|
|
79
|
+
"TIME",
|
|
80
|
+
"DATETIME",
|
|
81
|
+
"TIMESTAMP",
|
|
82
|
+
"TIMESTAMPTZ",
|
|
83
|
+
"TIMESTAMP WITH TIME ZONE",
|
|
84
|
+
"TIMESTAMP WITHOUT TIME ZONE",
|
|
85
|
+
"INTERVAL",
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
_BOOLEAN_DB_TYPES = frozenset({"BOOLEAN", "BOOL"})
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class ColumnProfile:
|
|
93
|
+
"""Lightweight profile of a query result column."""
|
|
94
|
+
|
|
95
|
+
name: str
|
|
96
|
+
dtype: str # "numeric", "temporal", "categorical"
|
|
97
|
+
min_val: float | None = None
|
|
98
|
+
max_val: float | None = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def db_type_to_dtype(db_type: str) -> str | None:
|
|
102
|
+
"""Map a database type string to our dtype classification."""
|
|
103
|
+
upper = db_type.upper().split("(")[0].strip()
|
|
104
|
+
if upper in _NUMERIC_DB_TYPES:
|
|
105
|
+
return "numeric"
|
|
106
|
+
if upper in _TEMPORAL_DB_TYPES:
|
|
107
|
+
return "temporal"
|
|
108
|
+
if upper in _BOOLEAN_DB_TYPES:
|
|
109
|
+
return "categorical"
|
|
110
|
+
if upper.startswith("VARCHAR") or upper in ("TEXT", "CHAR", "STRING"):
|
|
111
|
+
return "categorical"
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def enrich_chart(
|
|
116
|
+
chart: Chart,
|
|
117
|
+
data: list[dict[str, Any]],
|
|
118
|
+
column_descriptions: dict[str, tuple] | None = None,
|
|
119
|
+
) -> Chart:
|
|
120
|
+
"""Enrich chart with data-aware defaults.
|
|
121
|
+
|
|
122
|
+
Runs for ALL chart types. Layers applied in order (later override earlier):
|
|
123
|
+
1. Data value analysis (always available)
|
|
124
|
+
2. Database column metadata from cursor.description (when available; type_code
|
|
125
|
+
mapping works reliably for DuckDB which uses string type names; other drivers
|
|
126
|
+
fall back to value-based heuristics)
|
|
127
|
+
3. User YAML annotations (always win — never overridden)
|
|
128
|
+
|
|
129
|
+
Format defaults come from the theme cascade (axis_quantitative.format) —
|
|
130
|
+
not from inference here. Authors override via chart.format or style.axis_y.format.
|
|
131
|
+
"""
|
|
132
|
+
if not data:
|
|
133
|
+
return chart
|
|
134
|
+
|
|
135
|
+
col_profiles = _profile_columns(data, column_descriptions)
|
|
136
|
+
updates: dict[str, Any] = {}
|
|
137
|
+
|
|
138
|
+
effective_type = chart.type
|
|
139
|
+
|
|
140
|
+
# Field assignment: only for chart types that use x/y axes
|
|
141
|
+
if effective_type in _AXIS_CHART_TYPES:
|
|
142
|
+
field_updates = _pick_fields(chart, col_profiles)
|
|
143
|
+
updates.update(field_updates)
|
|
144
|
+
|
|
145
|
+
effective_y = updates.get("y", chart.y)
|
|
146
|
+
|
|
147
|
+
# Only enrich axis-based charts with format/scale decisions
|
|
148
|
+
if effective_type in _AXIS_CHART_TYPES and effective_y:
|
|
149
|
+
y_field = effective_y[0] if isinstance(effective_y, list) else effective_y
|
|
150
|
+
y_profile = col_profiles.get(y_field)
|
|
151
|
+
|
|
152
|
+
if y_profile and y_profile.dtype == "numeric":
|
|
153
|
+
style_updates = _pick_axis_style(chart, y_profile)
|
|
154
|
+
if style_updates:
|
|
155
|
+
from dataface.core.compile.normalize_charts import (
|
|
156
|
+
_AUTHORED_FAMILY_TO_MONOLITHIC_KEY,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
family_key = _AUTHORED_FAMILY_TO_MONOLITHIC_KEY.get(effective_type)
|
|
160
|
+
existing = (
|
|
161
|
+
chart.style.model_dump(exclude_none=True) if chart.style else {}
|
|
162
|
+
)
|
|
163
|
+
if family_key is not None:
|
|
164
|
+
existing_fam = existing.get(family_key, {})
|
|
165
|
+
existing_fam.update(style_updates)
|
|
166
|
+
existing[family_key] = existing_fam
|
|
167
|
+
updates["style"] = ChartStylePatch.model_validate(existing)
|
|
168
|
+
|
|
169
|
+
if updates:
|
|
170
|
+
return chart.model_copy(update=updates)
|
|
171
|
+
return chart
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _profile_columns(
|
|
175
|
+
data: list[dict[str, Any]],
|
|
176
|
+
column_descriptions: dict[str, tuple] | None = None,
|
|
177
|
+
) -> dict[str, ColumnProfile]:
|
|
178
|
+
"""Profile each column from query result data.
|
|
179
|
+
|
|
180
|
+
Uses two layers:
|
|
181
|
+
- Layer 1: Data value analysis (always available)
|
|
182
|
+
- Layer 2: Database column descriptions from cursor.description (when available)
|
|
183
|
+
Provides type for more confident classification.
|
|
184
|
+
"""
|
|
185
|
+
if not data:
|
|
186
|
+
return {}
|
|
187
|
+
|
|
188
|
+
from dataface.core.compile.chart_type_detection import classify_column_type
|
|
189
|
+
|
|
190
|
+
columns = list(data[0].keys())
|
|
191
|
+
profiles: dict[str, ColumnProfile] = {}
|
|
192
|
+
|
|
193
|
+
for col in columns:
|
|
194
|
+
sample_values = [row.get(col) for row in data[:100] if row.get(col) is not None]
|
|
195
|
+
if not sample_values:
|
|
196
|
+
profiles[col] = ColumnProfile(name=col, dtype="categorical")
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# Layer 2: Use database description if available
|
|
200
|
+
desc = column_descriptions.get(col) if column_descriptions else None
|
|
201
|
+
db_type_str = (
|
|
202
|
+
str(desc[_DESC_TYPE_CODE])
|
|
203
|
+
if desc and desc[_DESC_TYPE_CODE] is not None
|
|
204
|
+
else None
|
|
205
|
+
)
|
|
206
|
+
db_dtype = db_type_to_dtype(db_type_str) if db_type_str else None
|
|
207
|
+
|
|
208
|
+
# Layer 1: Fall back to value-based classification
|
|
209
|
+
dtype = db_dtype or classify_column_type(col, sample_values, db_type_str)
|
|
210
|
+
|
|
211
|
+
profile = ColumnProfile(name=col, dtype=dtype)
|
|
212
|
+
|
|
213
|
+
if dtype == "numeric":
|
|
214
|
+
numeric_vals = [
|
|
215
|
+
float(v)
|
|
216
|
+
for v in sample_values
|
|
217
|
+
if isinstance(v, (int, float, Decimal))
|
|
218
|
+
and not isinstance(v, bool)
|
|
219
|
+
and math.isfinite(float(v))
|
|
220
|
+
]
|
|
221
|
+
if numeric_vals:
|
|
222
|
+
profile.min_val = min(numeric_vals)
|
|
223
|
+
profile.max_val = max(numeric_vals)
|
|
224
|
+
|
|
225
|
+
profiles[col] = profile
|
|
226
|
+
|
|
227
|
+
return profiles
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _pick_fields(
|
|
231
|
+
chart: Chart,
|
|
232
|
+
col_profiles: dict[str, ColumnProfile],
|
|
233
|
+
) -> dict[str, Any]:
|
|
234
|
+
"""Pick best x/y fields when not user-specified."""
|
|
235
|
+
updates: dict[str, Any] = {}
|
|
236
|
+
|
|
237
|
+
if chart.x and chart.y:
|
|
238
|
+
return updates
|
|
239
|
+
|
|
240
|
+
numeric_cols = [p for p in col_profiles.values() if p.dtype == "numeric"]
|
|
241
|
+
temporal_cols = [p for p in col_profiles.values() if p.dtype == "temporal"]
|
|
242
|
+
categorical_cols = [p for p in col_profiles.values() if p.dtype == "categorical"]
|
|
243
|
+
|
|
244
|
+
if not chart.x and not chart.y:
|
|
245
|
+
if temporal_cols:
|
|
246
|
+
updates["x"] = temporal_cols[0].name
|
|
247
|
+
elif categorical_cols:
|
|
248
|
+
updates["x"] = categorical_cols[0].name
|
|
249
|
+
|
|
250
|
+
if numeric_cols:
|
|
251
|
+
updates["y"] = numeric_cols[0].name
|
|
252
|
+
|
|
253
|
+
elif chart.x and not chart.y:
|
|
254
|
+
if numeric_cols:
|
|
255
|
+
updates["y"] = numeric_cols[0].name
|
|
256
|
+
|
|
257
|
+
elif chart.y and not chart.x:
|
|
258
|
+
if temporal_cols:
|
|
259
|
+
updates["x"] = temporal_cols[0].name
|
|
260
|
+
elif categorical_cols:
|
|
261
|
+
updates["x"] = categorical_cols[0].name
|
|
262
|
+
|
|
263
|
+
return updates
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _pick_axis_style(
|
|
267
|
+
chart: Chart,
|
|
268
|
+
y_profile: ColumnProfile,
|
|
269
|
+
) -> dict[str, Any]:
|
|
270
|
+
"""Pick y-axis scale style updates.
|
|
271
|
+
|
|
272
|
+
Format defaults come from the theme cascade (axis_quantitative.format in stark.yaml).
|
|
273
|
+
This function only handles the zero-baseline heuristic.
|
|
274
|
+
|
|
275
|
+
Returns flat dict with a ``scale`` key when a scale override is needed — the caller
|
|
276
|
+
wraps it into the family sub-patch key before building the updated ChartStylePatch.
|
|
277
|
+
"""
|
|
278
|
+
updates: dict[str, Any] = {}
|
|
279
|
+
|
|
280
|
+
from dataface.core.compile.normalize_charts import (
|
|
281
|
+
_AUTHORED_FAMILY_TO_MONOLITHIC_KEY,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
family_key = _AUTHORED_FAMILY_TO_MONOLITHIC_KEY.get(chart.type)
|
|
285
|
+
existing_patch = (
|
|
286
|
+
getattr(chart.style, family_key, None) if chart.style and family_key else None
|
|
287
|
+
)
|
|
288
|
+
existing_scale = getattr(existing_patch, "scale", None) if existing_patch else None
|
|
289
|
+
existing_axis_y = (
|
|
290
|
+
getattr(existing_patch, "axis_y", None) if existing_patch else None
|
|
291
|
+
)
|
|
292
|
+
existing_axis_y_scale = (
|
|
293
|
+
getattr(existing_axis_y, "scale", None) if existing_axis_y else None
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
has_zero = (existing_scale is not None and existing_scale.zero is not None) or (
|
|
297
|
+
existing_axis_y_scale is not None and existing_axis_y_scale.zero is not None
|
|
298
|
+
)
|
|
299
|
+
if not has_zero:
|
|
300
|
+
scale = _pick_scale(y_profile, chart_type=chart.type)
|
|
301
|
+
if scale:
|
|
302
|
+
scale_data = (
|
|
303
|
+
existing_scale.model_dump(exclude_none=True) if existing_scale else {}
|
|
304
|
+
)
|
|
305
|
+
scale_data.update(scale)
|
|
306
|
+
updates["scale"] = scale_data
|
|
307
|
+
|
|
308
|
+
return updates
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _pick_scale(
|
|
312
|
+
profile: ColumnProfile, chart_type: str | None = None
|
|
313
|
+
) -> dict[str, Any] | None:
|
|
314
|
+
"""Decide the explicit zero setting for the y-axis scale.
|
|
315
|
+
|
|
316
|
+
Returns ``{"zero": False}`` to keep the domain data-fitted, ``{"zero": True}``
|
|
317
|
+
to extend to zero, or ``None`` when zero is not relevant (data spans or
|
|
318
|
+
touches zero, or no data profiled).
|
|
319
|
+
|
|
320
|
+
Optional-zero chart types (line, scatter): smart-auto heuristic.
|
|
321
|
+
Extend to zero when data_min/data_max <= 0.25 (data starts in the bottom
|
|
322
|
+
quarter of [0, max]). Return zero:False when data lives far from zero.
|
|
323
|
+
|
|
324
|
+
Non-optional-zero types (bar, area, …): always extend to zero for
|
|
325
|
+
all-positive data — bar marks encode absolute magnitude and suppressing
|
|
326
|
+
zero produces truncated bars (a known misleading chart pattern).
|
|
327
|
+
|
|
328
|
+
Returning an explicit True/False here means resolved_chart.zero carries
|
|
329
|
+
the full intent; callers (profile.py) do not need to know chart-type
|
|
330
|
+
defaults in VL.
|
|
331
|
+
|
|
332
|
+
All-negative data (max < 0): out of scope — skip the heuristic.
|
|
333
|
+
"""
|
|
334
|
+
if profile.min_val is None or profile.max_val is None:
|
|
335
|
+
return None
|
|
336
|
+
if profile.max_val == 0:
|
|
337
|
+
return None
|
|
338
|
+
# Negative-spanning or all-negative: zero already in domain, or out of scope.
|
|
339
|
+
if profile.min_val < 0:
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
if chart_type in _OPTIONAL_ZERO_CHART_TYPES:
|
|
343
|
+
# Smart-auto: keep data-fitted when data lives too far from zero.
|
|
344
|
+
ratio = profile.min_val / profile.max_val
|
|
345
|
+
if ratio > _ZERO_EXTEND_THRESHOLD:
|
|
346
|
+
return {"zero": False}
|
|
347
|
+
# Data is close to zero — let VL's zero=true default extend naturally.
|
|
348
|
+
# No explicit True needed: blank space is negligible and we preserve
|
|
349
|
+
# the tick range from the pre-existing behaviour.
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
# Non-optional-zero types (bar, area, …) with all-positive data: return
|
|
353
|
+
# explicit True so resolved_chart.zero carries the intent and callers
|
|
354
|
+
# (profile.py) need no chart-type knowledge to decide domain pinning.
|
|
355
|
+
if profile.min_val > 0:
|
|
356
|
+
return {"zero": True}
|
|
357
|
+
|
|
358
|
+
return None
|