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,823 @@
|
|
|
1
|
+
"""Compilation configuration module.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE
|
|
4
|
+
Purpose: Provide default values and configuration for compilation.
|
|
5
|
+
|
|
6
|
+
Entry Points:
|
|
7
|
+
- get_config() -> Config
|
|
8
|
+
- get_theme(name) -> ThemeConfig
|
|
9
|
+
- reset_config() -> None
|
|
10
|
+
|
|
11
|
+
Configuration is loaded from the defaults stack and accessed via dot notation:
|
|
12
|
+
config = get_config()
|
|
13
|
+
config.style.board.width # 1200.0
|
|
14
|
+
config.style.background # "#ffffff"
|
|
15
|
+
config.style.charts.aspect_ratio # 1.6
|
|
16
|
+
|
|
17
|
+
Runtime defaults are assembled from:
|
|
18
|
+
- core/defaults/default_config.yml (app-wide defaults + vega.default_theme)
|
|
19
|
+
- core/defaults/themes/*.yaml (unified theme system)
|
|
20
|
+
- core/defaults/palettes/<family>/*.yml (color palettes)
|
|
21
|
+
|
|
22
|
+
YAML is the single source of truth - no Python dataclass defaults.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from collections.abc import Mapping
|
|
28
|
+
from copy import deepcopy
|
|
29
|
+
from functools import cache
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
import yaml
|
|
34
|
+
|
|
35
|
+
from dataface.core.compile.models.config import (
|
|
36
|
+
Config,
|
|
37
|
+
ConfigNode,
|
|
38
|
+
ConfigPatch,
|
|
39
|
+
MarkdownConfig,
|
|
40
|
+
RenderingConfig,
|
|
41
|
+
VegaRuntimeConfig,
|
|
42
|
+
as_plain_mapping,
|
|
43
|
+
is_mapping_like,
|
|
44
|
+
)
|
|
45
|
+
from dataface.core.compile.models.source import resolve_env_vars_in_dict
|
|
46
|
+
from dataface.core.compile.models.theme import ThemeConfig, normalize_raw_theme_config
|
|
47
|
+
|
|
48
|
+
# ============================================================================
|
|
49
|
+
# GLOBAL CONFIGURATION
|
|
50
|
+
# ============================================================================
|
|
51
|
+
|
|
52
|
+
_config: Config | None = None
|
|
53
|
+
_defaults_dir: Path = Path(__file__).parent.parent / "defaults"
|
|
54
|
+
_default_config_path: Path = _defaults_dir / "default_config.yml"
|
|
55
|
+
_built_in_unified_theme_dir: Path = _defaults_dir / "themes"
|
|
56
|
+
_palettes_dir: Path = _defaults_dir / "palettes"
|
|
57
|
+
|
|
58
|
+
# Per-module default YAML files colocated with their consumers.
|
|
59
|
+
# Each file contributes one or more top-level config namespaces.
|
|
60
|
+
# Add a path here when a new module ships its own defaults.
|
|
61
|
+
_core_dir: Path = Path(__file__).parent.parent
|
|
62
|
+
_MODULE_DEFAULT_PATHS: list[Path] = [
|
|
63
|
+
_core_dir / "inspect" / "defaults.yml",
|
|
64
|
+
_core_dir / "render" / "geo_defaults.yml",
|
|
65
|
+
_core_dir / "render" / "markdown_defaults.yml",
|
|
66
|
+
_core_dir / "render" / "terminal_defaults.yml",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# Theme caches (avoid repeated resolution of inheritance)
|
|
70
|
+
_theme_cache: dict[str, ThemeConfig] = {}
|
|
71
|
+
_compiled_theme_cache: dict[str, Any] = {} # str -> Style
|
|
72
|
+
|
|
73
|
+
#: Name of the built-in default theme shipped with Dataface.
|
|
74
|
+
DEFAULT_THEME_NAME: str = "editorial"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _load_yaml_data(path: Path) -> Any:
|
|
78
|
+
"""Load YAML as plain Python containers."""
|
|
79
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
80
|
+
result = yaml.safe_load(fh)
|
|
81
|
+
return {} if result is None else result
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _as_mapping(value: Any, context: str) -> dict[str, Any]:
|
|
85
|
+
"""Normalize mapping-like content into a plain dict."""
|
|
86
|
+
if is_mapping_like(value):
|
|
87
|
+
return as_plain_mapping(value)
|
|
88
|
+
raise TypeError(f"{context} must decode to a mapping")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_config() -> Config:
|
|
92
|
+
"""Get the current configuration.
|
|
93
|
+
|
|
94
|
+
Loads the defaults stack and built-in chart themes on first call.
|
|
95
|
+
All config is accessed via dot notation:
|
|
96
|
+
config.style.board.width
|
|
97
|
+
config.style.layout.rows.gap
|
|
98
|
+
config.style.charts.bar.padding
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Config with typed configuration values
|
|
102
|
+
"""
|
|
103
|
+
global _config
|
|
104
|
+
|
|
105
|
+
if _config is None:
|
|
106
|
+
merged = _load_base_config()
|
|
107
|
+
merged = _resolve_style_extends(merged)
|
|
108
|
+
_config = Config.model_validate(merged)
|
|
109
|
+
|
|
110
|
+
return _config
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def reset_config() -> None:
|
|
114
|
+
"""Reset configuration to reload from YAML."""
|
|
115
|
+
global _config, _theme_cache, _compiled_theme_cache
|
|
116
|
+
from dataface.core.compile.models.style.merged import clear_resolve_style_cache
|
|
117
|
+
|
|
118
|
+
_config = None
|
|
119
|
+
_theme_cache = {}
|
|
120
|
+
_compiled_theme_cache = {}
|
|
121
|
+
clear_resolve_style_cache()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_chart_rendering() -> ConfigNode:
|
|
125
|
+
"""Narrow getter — engine constants for chart layout and formatting."""
|
|
126
|
+
return get_config().chart_rendering
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_markdown_config() -> MarkdownConfig:
|
|
130
|
+
"""Narrow getter — markdown rendering color config."""
|
|
131
|
+
return get_config().markdown
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_terminal_config() -> ConfigNode:
|
|
135
|
+
"""Narrow getter — terminal rendering defaults."""
|
|
136
|
+
return get_config().terminal
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_rendering_config() -> RenderingConfig:
|
|
140
|
+
"""Narrow getter — export/output rendering settings."""
|
|
141
|
+
return get_config().rendering
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def get_vega_config() -> VegaRuntimeConfig:
|
|
145
|
+
"""Narrow getter — Vega-Lite runtime config."""
|
|
146
|
+
return get_config().vega
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def load_config(path: Path) -> Config:
|
|
150
|
+
"""Load configuration from a custom YAML file.
|
|
151
|
+
|
|
152
|
+
Merges with defaults - file values override defaults.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
path: Path to configuration file
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Config with merged configuration
|
|
159
|
+
"""
|
|
160
|
+
global _config, _theme_cache, _compiled_theme_cache
|
|
161
|
+
from dataface.core.compile.models.style.merged import clear_resolve_style_cache
|
|
162
|
+
|
|
163
|
+
# Start with the assembled defaults stack
|
|
164
|
+
config = _load_base_config()
|
|
165
|
+
|
|
166
|
+
# Merge with custom file
|
|
167
|
+
if path.exists():
|
|
168
|
+
custom_data = _load_yaml_data(path)
|
|
169
|
+
custom = ConfigPatch.model_validate(custom_data)
|
|
170
|
+
config = _deep_merge(
|
|
171
|
+
config,
|
|
172
|
+
custom.to_plain_dict(exclude_none=True),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
config = _resolve_style_extends(config)
|
|
176
|
+
compiled = Config.model_validate(config)
|
|
177
|
+
|
|
178
|
+
_config = compiled
|
|
179
|
+
_theme_cache = {}
|
|
180
|
+
_compiled_theme_cache = {}
|
|
181
|
+
clear_resolve_style_cache()
|
|
182
|
+
|
|
183
|
+
return compiled
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _load_base_config() -> dict[str, Any]:
|
|
187
|
+
"""Load and merge built-in default config files and themes."""
|
|
188
|
+
raw_default = _load_yaml_data(_default_config_path)
|
|
189
|
+
# Extract style before ConfigPatch validation — the style section contains
|
|
190
|
+
# {extends: "..."} which is not a valid Style. We restore it below
|
|
191
|
+
# and resolve it to a Style just before Config.model_validate.
|
|
192
|
+
raw_style = raw_default.pop("style", {}) if isinstance(raw_default, dict) else {}
|
|
193
|
+
|
|
194
|
+
config = ConfigPatch.model_validate(raw_default)
|
|
195
|
+
# ConfigPatch uses None as "not authored in this overlay". Preserve only
|
|
196
|
+
# explicitly authored keys when projecting patch models back to dicts.
|
|
197
|
+
config_data = config.to_plain_dict(exclude_none=True)
|
|
198
|
+
if raw_style:
|
|
199
|
+
config_data["style"] = raw_style
|
|
200
|
+
|
|
201
|
+
for module_path in _MODULE_DEFAULT_PATHS:
|
|
202
|
+
config_data = _deep_merge(config_data, _load_yaml_data(module_path))
|
|
203
|
+
|
|
204
|
+
# Load palette data from defaults/palettes/ into the config tree.
|
|
205
|
+
config_data = _deep_merge(config_data, _load_palettes())
|
|
206
|
+
|
|
207
|
+
return config_data
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
_SCAFFOLD_META_KEYS: frozenset[str] = frozenset({"name", "family", "description"})
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _load_palettes() -> dict[str, Any]:
|
|
214
|
+
"""Load palette YAMLs from defaults/palettes/ into the config tree.
|
|
215
|
+
|
|
216
|
+
Returns a dict with:
|
|
217
|
+
- ``palettes``: categorical palettes keyed by name (category-10, hero-6, …)
|
|
218
|
+
from ``defaults/palettes/categorical/*.yml``.
|
|
219
|
+
- ``dft_grays`` / ``dft_creams``: flat hex mappings from
|
|
220
|
+
``defaults/palettes/scaffold/dft-grays.yml`` and ``dft-creams.yml``.
|
|
221
|
+
|
|
222
|
+
Sequential, diverging, and tone palettes are accessed via
|
|
223
|
+
``dataface.core.compile.palette`` — not the config tree.
|
|
224
|
+
|
|
225
|
+
Shipped YAMLs are expected to be well-formed. Any malformed file raises —
|
|
226
|
+
no silent skip.
|
|
227
|
+
"""
|
|
228
|
+
overlay: dict[str, Any] = {}
|
|
229
|
+
cat_dir = _palettes_dir / "categorical"
|
|
230
|
+
if cat_dir.is_dir():
|
|
231
|
+
palettes: dict[str, list[str]] = {}
|
|
232
|
+
for path in sorted(cat_dir.glob("*.yml")):
|
|
233
|
+
data = _load_yaml_data(path)
|
|
234
|
+
name = data.get("name") if isinstance(data, Mapping) else None
|
|
235
|
+
# Categorical files use colors: (renamed from stops:).
|
|
236
|
+
colors = data.get("colors") if isinstance(data, Mapping) else None
|
|
237
|
+
if not isinstance(name, str) or not isinstance(colors, list):
|
|
238
|
+
raise ValueError(
|
|
239
|
+
f"{path}: categorical palette YAML must have string 'name' "
|
|
240
|
+
f"and list 'colors' keys"
|
|
241
|
+
)
|
|
242
|
+
palettes[name] = list(colors)
|
|
243
|
+
if palettes:
|
|
244
|
+
overlay["palettes"] = palettes
|
|
245
|
+
|
|
246
|
+
for legacy_attr, palette_name in (
|
|
247
|
+
("dft_grays", "dft-grays"),
|
|
248
|
+
("dft_creams", "dft-creams"),
|
|
249
|
+
):
|
|
250
|
+
# Use _load_spine (which resolves extends: inheritance) so that child
|
|
251
|
+
# palettes like dft-creams inherit the full alias graph from their parent.
|
|
252
|
+
from dataface.core.compile.palette import _load_spine, resolve_alias_chain
|
|
253
|
+
|
|
254
|
+
spine = _load_spine(palette_name)
|
|
255
|
+
if spine.colors is None or spine.aliases is None:
|
|
256
|
+
raise ValueError(
|
|
257
|
+
f"scaffold palette '{palette_name}' must have 'colors:' list and "
|
|
258
|
+
f"'aliases:' mapping (unified palette shape)"
|
|
259
|
+
)
|
|
260
|
+
# Flatten: resolve every alias to a terminal hex for legacy consumers.
|
|
261
|
+
flat: dict[str, str] = {
|
|
262
|
+
alias_key: resolve_alias_chain(
|
|
263
|
+
alias_key, dict(spine.aliases), colors=list(spine.colors)
|
|
264
|
+
)
|
|
265
|
+
for alias_key in spine.aliases
|
|
266
|
+
}
|
|
267
|
+
if not flat:
|
|
268
|
+
raise ValueError(
|
|
269
|
+
f"scaffold palette '{palette_name}' has no aliases — check palette YAML shape"
|
|
270
|
+
)
|
|
271
|
+
overlay[legacy_attr] = flat
|
|
272
|
+
|
|
273
|
+
return overlay
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _deep_merge(base: Mapping[str, Any], overlay: Mapping[str, Any]) -> dict[str, Any]:
|
|
277
|
+
"""Recursively merge overlay into base.
|
|
278
|
+
|
|
279
|
+
Rebuild only the branches touched by the overlay. Overlay leaves are
|
|
280
|
+
deep-copied so later mutation cannot bleed back into reusable defaults.
|
|
281
|
+
"""
|
|
282
|
+
result = dict(base)
|
|
283
|
+
for key, value in overlay.items():
|
|
284
|
+
if (
|
|
285
|
+
key in result
|
|
286
|
+
and isinstance(result[key], Mapping)
|
|
287
|
+
and isinstance(value, Mapping)
|
|
288
|
+
):
|
|
289
|
+
result[key] = _deep_merge(result[key], value)
|
|
290
|
+
else:
|
|
291
|
+
result[key] = deepcopy(value)
|
|
292
|
+
return result
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _load_built_in_unified_themes() -> dict[str, dict[str, Any]]:
|
|
296
|
+
"""Load unified theme files from themes/ (.yaml extension).
|
|
297
|
+
|
|
298
|
+
These are pure Dataface Style-format files. Uses .yaml extension
|
|
299
|
+
to distinguish from the now-deleted chart_themes/*.yml legacy format.
|
|
300
|
+
"""
|
|
301
|
+
themes: dict[str, dict[str, Any]] = {}
|
|
302
|
+
if not _built_in_unified_theme_dir.exists():
|
|
303
|
+
return themes
|
|
304
|
+
|
|
305
|
+
for path in sorted(_built_in_unified_theme_dir.glob("*.yaml")):
|
|
306
|
+
raw = _as_mapping(_load_yaml_data(path), f"Unified theme file {path.name}")
|
|
307
|
+
# Unified theme files have the style dict wrapped under 'style:'.
|
|
308
|
+
# Keep the full structure (extends + style) for _resolve_named_config to handle.
|
|
309
|
+
themes[path.stem] = raw
|
|
310
|
+
|
|
311
|
+
return themes
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@cache
|
|
315
|
+
def list_built_in_themes() -> list[str]:
|
|
316
|
+
"""Return sorted stem names of all built-in unified themes."""
|
|
317
|
+
return sorted(p.stem for p in _built_in_unified_theme_dir.glob("*.yaml"))
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def get_compiled_theme(theme_name: str | None) -> Any: # -> Style
|
|
321
|
+
"""Load a unified theme as a Style, resolving extends: inheritance.
|
|
322
|
+
|
|
323
|
+
Unified themes live in themes/*.yaml. Each file has:
|
|
324
|
+
extends: <parent-name> (optional)
|
|
325
|
+
style:
|
|
326
|
+
background: ...
|
|
327
|
+
charts: ...
|
|
328
|
+
|
|
329
|
+
The style dicts from the inheritance chain are deep-merged (child wins).
|
|
330
|
+
The merged dict is validated via Style.model_validate().
|
|
331
|
+
|
|
332
|
+
Returns Style with all defaults filled in.
|
|
333
|
+
"""
|
|
334
|
+
from dataface.core.compile.models.style.compiled import Style
|
|
335
|
+
|
|
336
|
+
name = theme_name or DEFAULT_THEME_NAME
|
|
337
|
+
|
|
338
|
+
if name in _compiled_theme_cache:
|
|
339
|
+
return _compiled_theme_cache[name]
|
|
340
|
+
|
|
341
|
+
if not _built_in_unified_theme_dir.exists():
|
|
342
|
+
raise FileNotFoundError(
|
|
343
|
+
f"Unified themes directory not found: {_built_in_unified_theme_dir}."
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
unified_themes = _load_built_in_unified_themes()
|
|
347
|
+
if not unified_themes:
|
|
348
|
+
raise FileNotFoundError(
|
|
349
|
+
f"No unified theme files (*.yaml) found in {_built_in_unified_theme_dir}."
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Build a themes dict that maps name → style dict (without the 'style:' wrapper)
|
|
353
|
+
# for _resolve_named_config to merge. We expose 'extends' at top level and
|
|
354
|
+
# the style keys merged in at the same level so the resolver sees them.
|
|
355
|
+
flat_themes: dict[str, dict[str, Any]] = {}
|
|
356
|
+
for tname, raw in unified_themes.items():
|
|
357
|
+
entry: dict[str, Any] = {}
|
|
358
|
+
if "extends" in raw:
|
|
359
|
+
entry["extends"] = raw["extends"]
|
|
360
|
+
style_data = raw.get("style", {})
|
|
361
|
+
if style_data:
|
|
362
|
+
entry.update(style_data)
|
|
363
|
+
flat_themes[tname] = entry
|
|
364
|
+
|
|
365
|
+
if name not in flat_themes:
|
|
366
|
+
raise ValueError(
|
|
367
|
+
f"Theme '{name}' not found in {_built_in_unified_theme_dir}. "
|
|
368
|
+
f"Available themes: {sorted(flat_themes)}"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
from dataface.core.compile.models.style.merged import (
|
|
372
|
+
_resolve_color_tokens,
|
|
373
|
+
_resolve_self_tokens,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
resolved = _resolve_named_config(name, flat_themes, set())
|
|
377
|
+
compiled = Style.model_validate(resolved)
|
|
378
|
+
# Substitute theme-self tokens (e.g. ``theme.background`` for canvas-coupled
|
|
379
|
+
# fields like ``arc.stroke``) BEFORE color-token resolution so the
|
|
380
|
+
# substituted palette token is itself resolved by the next pass.
|
|
381
|
+
compiled = _resolve_self_tokens(compiled)
|
|
382
|
+
# Themes carry palette tokens (e.g. "dft-grays.gray-30") for human readability.
|
|
383
|
+
# Resolve them to concrete hex once, at theme-load time, so every consumer of
|
|
384
|
+
# Style sees concrete colors.
|
|
385
|
+
compiled = _resolve_color_tokens(compiled)
|
|
386
|
+
_compiled_theme_cache[name] = compiled
|
|
387
|
+
return compiled
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _resolve_style_extends(config: dict[str, Any]) -> dict[str, Any]:
|
|
391
|
+
"""Resolve style.extends into a Style for Config.style.
|
|
392
|
+
|
|
393
|
+
Called after preset application, just before Config.model_validate.
|
|
394
|
+
All visual defaults live in themes/*.yaml; style.extends names the base theme.
|
|
395
|
+
"""
|
|
396
|
+
style_section = config.get("style", {})
|
|
397
|
+
if isinstance(style_section, dict) and "extends" in style_section:
|
|
398
|
+
config = dict(config)
|
|
399
|
+
config["style"] = get_compiled_theme(style_section["extends"])
|
|
400
|
+
return config
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ============================================================================
|
|
404
|
+
# THEME ACCESS
|
|
405
|
+
# ============================================================================
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def get_theme(theme_name: str | None) -> ThemeConfig:
|
|
409
|
+
"""Get a typed theme config by name, resolving inheritance.
|
|
410
|
+
|
|
411
|
+
Themes can inherit via 'extends':
|
|
412
|
+
my-theme:
|
|
413
|
+
extends: default
|
|
414
|
+
charts:
|
|
415
|
+
table:
|
|
416
|
+
header_background: "#f0f0f0"
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
theme_name: Name of theme, or None for empty theme
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
ThemeConfig with typed access to theme values
|
|
423
|
+
"""
|
|
424
|
+
if not theme_name:
|
|
425
|
+
return ThemeConfig()
|
|
426
|
+
|
|
427
|
+
if theme_name in _theme_cache:
|
|
428
|
+
return _theme_cache[theme_name]
|
|
429
|
+
|
|
430
|
+
config = get_config()
|
|
431
|
+
resolved_dict = _resolve_named_config(
|
|
432
|
+
theme_name, _as_mapping(config.themes, "themes"), set()
|
|
433
|
+
)
|
|
434
|
+
resolved = ThemeConfig.model_validate(normalize_raw_theme_config(resolved_dict))
|
|
435
|
+
_theme_cache[theme_name] = resolved
|
|
436
|
+
return resolved
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def get_theme_dict(theme_name: str | None) -> dict[str, Any]:
|
|
440
|
+
"""Get a theme as raw dict for passing to Vega-Lite.
|
|
441
|
+
|
|
442
|
+
Use this when you need the raw dict to merge into a Vega-Lite spec.
|
|
443
|
+
For typed access to theme values, use get_theme() instead.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
theme_name: Name of theme, or None for empty dict
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Raw theme dictionary suitable for Vega-Lite config
|
|
450
|
+
"""
|
|
451
|
+
if not theme_name:
|
|
452
|
+
return {}
|
|
453
|
+
return get_theme(theme_name).to_vega_config()
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _resolve_named_config(
|
|
457
|
+
config_name: str, configs: Mapping[str, Any], seen: set[str]
|
|
458
|
+
) -> dict[str, Any]:
|
|
459
|
+
"""Resolve a named theme/preset config, handling inheritance via extends."""
|
|
460
|
+
if config_name in seen:
|
|
461
|
+
raise ValueError(
|
|
462
|
+
f"Circular config inheritance: {' -> '.join(seen)} -> {config_name}"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
config = configs.get(config_name)
|
|
466
|
+
if not config:
|
|
467
|
+
return {}
|
|
468
|
+
if not is_mapping_like(config):
|
|
469
|
+
raise TypeError(f"Config '{config_name}' must be a mapping")
|
|
470
|
+
|
|
471
|
+
config_data = _as_mapping(config, config_name)
|
|
472
|
+
|
|
473
|
+
extends = config_data.get("extends")
|
|
474
|
+
if not extends:
|
|
475
|
+
return {k: v for k, v in config_data.items() if k != "extends"}
|
|
476
|
+
|
|
477
|
+
seen_with_current = seen | {config_name}
|
|
478
|
+
parent = _resolve_named_config(extends, configs, seen_with_current)
|
|
479
|
+
child = {k: v for k, v in config_data.items() if k != "extends"}
|
|
480
|
+
return _deep_merge(parent, child)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# ============================================================================
|
|
484
|
+
# HELPER FUNCTIONS
|
|
485
|
+
# ============================================================================
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def get_default_theme_name() -> str:
|
|
489
|
+
"""Return the designated default chart theme name."""
|
|
490
|
+
config = get_config()
|
|
491
|
+
return config.vega.default_theme
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def set_default_theme_name(theme_name: str) -> None:
|
|
495
|
+
"""Override the in-process default theme name.
|
|
496
|
+
|
|
497
|
+
Validates the theme exists before patching. Raises ValueError on unknown names.
|
|
498
|
+
Called at serve startup by apply_default_theme_from_env(); not for general use.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
theme_name: A built-in theme stem (e.g. "carbong100", "light").
|
|
502
|
+
|
|
503
|
+
Raises:
|
|
504
|
+
ValueError: If theme_name is not a built-in theme.
|
|
505
|
+
"""
|
|
506
|
+
available = list_built_in_themes()
|
|
507
|
+
if theme_name not in available:
|
|
508
|
+
raise ValueError(
|
|
509
|
+
f"Theme '{theme_name}' not found. Available built-in themes: {available}"
|
|
510
|
+
)
|
|
511
|
+
# Force initialization so the patch lands on the live singleton.
|
|
512
|
+
config = get_config()
|
|
513
|
+
config.vega.default_theme = theme_name
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def get_palette(name: str = "default") -> list[str]:
|
|
517
|
+
"""Get a named color palette."""
|
|
518
|
+
config = get_config()
|
|
519
|
+
palettes = config.palettes
|
|
520
|
+
palette_name = "category-10" if name == "default" else name
|
|
521
|
+
if palette_name not in palettes:
|
|
522
|
+
raise KeyError(f"Unknown palette '{palette_name}'")
|
|
523
|
+
palette = palettes[palette_name]
|
|
524
|
+
if not isinstance(palette, list):
|
|
525
|
+
raise TypeError(f"Palette '{palette_name}' must be a list")
|
|
526
|
+
return list(palette)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
# ============================================================================
|
|
530
|
+
# PROJECT SOURCES
|
|
531
|
+
# ============================================================================
|
|
532
|
+
|
|
533
|
+
_project_sources: dict[Path, ProjectSourcesConfig] = {}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def get_project_sources(project_dir: Path | None = None) -> ProjectSourcesConfig:
|
|
537
|
+
"""Get project-level sources configuration.
|
|
538
|
+
|
|
539
|
+
Loads from:
|
|
540
|
+
1. Config sources section
|
|
541
|
+
2. Project dataface.yml sources section
|
|
542
|
+
3. _sources.yaml file (merged, takes precedence)
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
project_dir: Project directory (default: cwd)
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
ProjectSourcesConfig with 'default' and 'sources' attributes
|
|
549
|
+
"""
|
|
550
|
+
global _project_sources
|
|
551
|
+
|
|
552
|
+
project_dir = (project_dir or Path.cwd()).resolve()
|
|
553
|
+
|
|
554
|
+
cached_sources = _project_sources.get(project_dir)
|
|
555
|
+
if cached_sources is not None:
|
|
556
|
+
return cached_sources
|
|
557
|
+
|
|
558
|
+
config = get_config()
|
|
559
|
+
|
|
560
|
+
# Start with config sources
|
|
561
|
+
default_source = None
|
|
562
|
+
all_sources: dict[str, dict[str, Any]] = {}
|
|
563
|
+
|
|
564
|
+
if config.sources:
|
|
565
|
+
sources_data = _as_mapping(config.sources, "config.sources")
|
|
566
|
+
default_source = sources_data.pop("default", None)
|
|
567
|
+
all_sources.update(
|
|
568
|
+
{k: v for k, v in sources_data.items() if isinstance(v, dict)}
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Project dataface.yml overrides global defaults.
|
|
572
|
+
project_config_file = None
|
|
573
|
+
for candidate in (project_dir / "dataface.yml", project_dir / "dataface.yaml"):
|
|
574
|
+
if candidate.exists():
|
|
575
|
+
project_config_file = candidate
|
|
576
|
+
break
|
|
577
|
+
|
|
578
|
+
if project_config_file is not None:
|
|
579
|
+
file_data = _as_mapping(
|
|
580
|
+
_load_yaml_data(project_config_file), project_config_file.name
|
|
581
|
+
)
|
|
582
|
+
if "sources" in file_data:
|
|
583
|
+
sources_section = _as_mapping(
|
|
584
|
+
file_data["sources"], f"{project_config_file.name} sources section"
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
if "default" in sources_section:
|
|
588
|
+
default_source = sources_section.pop("default")
|
|
589
|
+
|
|
590
|
+
all_sources.update(
|
|
591
|
+
{k: v for k, v in sources_section.items() if isinstance(v, dict)}
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# Load _sources.yaml if it exists
|
|
595
|
+
sources_file = project_dir / "_sources.yaml"
|
|
596
|
+
if not sources_file.exists():
|
|
597
|
+
sources_file = project_dir / "_sources.yml"
|
|
598
|
+
|
|
599
|
+
if sources_file.exists():
|
|
600
|
+
file_data = _as_mapping(_load_yaml_data(sources_file), sources_file.name)
|
|
601
|
+
# Handle structure where sources are nested under 'sources' key
|
|
602
|
+
if "sources" in file_data:
|
|
603
|
+
sources_section = _as_mapping(
|
|
604
|
+
file_data["sources"], f"{sources_file.name} sources section"
|
|
605
|
+
)
|
|
606
|
+
else:
|
|
607
|
+
sources_section = _as_mapping(file_data, sources_file.name)
|
|
608
|
+
|
|
609
|
+
# Extract default
|
|
610
|
+
if "default" in sources_section:
|
|
611
|
+
default_source = sources_section.pop("default")
|
|
612
|
+
|
|
613
|
+
# Merge source definitions
|
|
614
|
+
all_sources.update(
|
|
615
|
+
{k: dict(v) for k, v in sources_section.items() if isinstance(v, dict)}
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
project_sources = ProjectSourcesConfig(
|
|
619
|
+
default=default_source,
|
|
620
|
+
sources=_normalize_project_sources(all_sources),
|
|
621
|
+
)
|
|
622
|
+
_project_sources[project_dir] = project_sources
|
|
623
|
+
return project_sources
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def reset_project_sources() -> None:
|
|
627
|
+
"""Reset project sources cache."""
|
|
628
|
+
global _project_sources
|
|
629
|
+
_project_sources = {}
|
|
630
|
+
reset_project_warnings_ignore()
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def invalidate_project_sources(project_dir: Path | None) -> None:
|
|
634
|
+
"""Clear cached project sources for a single project directory."""
|
|
635
|
+
if project_dir is None:
|
|
636
|
+
return
|
|
637
|
+
|
|
638
|
+
_project_sources.pop(project_dir.resolve(), None)
|
|
639
|
+
invalidate_project_warnings_ignore(project_dir)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
# ============================================================================
|
|
643
|
+
# PROJECT WARNINGS IGNORE
|
|
644
|
+
# ============================================================================
|
|
645
|
+
|
|
646
|
+
_project_warnings_ignore: dict[Path, frozenset[str]] = {}
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def get_project_warnings_ignore(project_dir: Path | None = None) -> frozenset[str]:
|
|
650
|
+
"""Return the set of warning codes to suppress project-wide.
|
|
651
|
+
|
|
652
|
+
Reads `warnings.ignore` from `dataface.yml` (or `dataface.yaml`) in
|
|
653
|
+
`project_dir`. Returns an empty frozenset when no config file exists or
|
|
654
|
+
the key is absent.
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
project_dir: Project directory. Defaults to cwd when None.
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
Frozenset of warning code strings.
|
|
661
|
+
"""
|
|
662
|
+
global _project_warnings_ignore
|
|
663
|
+
|
|
664
|
+
resolved_dir = (project_dir or Path.cwd()).resolve()
|
|
665
|
+
cached = _project_warnings_ignore.get(resolved_dir)
|
|
666
|
+
if cached is not None:
|
|
667
|
+
return cached
|
|
668
|
+
|
|
669
|
+
for candidate in (resolved_dir / "dataface.yml", resolved_dir / "dataface.yaml"):
|
|
670
|
+
if candidate.exists():
|
|
671
|
+
file_data = _as_mapping(_load_yaml_data(candidate), candidate.name)
|
|
672
|
+
warnings_section = file_data.get("warnings")
|
|
673
|
+
if warnings_section is not None:
|
|
674
|
+
warnings_map = _as_mapping(
|
|
675
|
+
warnings_section, f"{candidate.name} warnings section"
|
|
676
|
+
)
|
|
677
|
+
raw_ignore = warnings_map.get("ignore")
|
|
678
|
+
if raw_ignore is not None:
|
|
679
|
+
if not isinstance(raw_ignore, list):
|
|
680
|
+
raise TypeError(
|
|
681
|
+
f"{candidate.name}: warnings.ignore must be a list of strings"
|
|
682
|
+
)
|
|
683
|
+
for entry in raw_ignore:
|
|
684
|
+
if not isinstance(entry, str):
|
|
685
|
+
raise TypeError(
|
|
686
|
+
f"{candidate.name}: warnings.ignore entries must be "
|
|
687
|
+
f"strings, got {type(entry).__name__}: {entry!r}"
|
|
688
|
+
)
|
|
689
|
+
codes = frozenset(raw_ignore)
|
|
690
|
+
_project_warnings_ignore[resolved_dir] = codes
|
|
691
|
+
return codes
|
|
692
|
+
break
|
|
693
|
+
|
|
694
|
+
empty: frozenset[str] = frozenset()
|
|
695
|
+
_project_warnings_ignore[resolved_dir] = empty
|
|
696
|
+
return empty
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def reset_project_warnings_ignore() -> None:
|
|
700
|
+
"""Reset project warnings ignore cache (for tests)."""
|
|
701
|
+
global _project_warnings_ignore
|
|
702
|
+
_project_warnings_ignore = {}
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def invalidate_project_warnings_ignore(project_dir: Path | None) -> None:
|
|
706
|
+
"""Clear cached warnings ignore for a single project directory."""
|
|
707
|
+
if project_dir is None:
|
|
708
|
+
return
|
|
709
|
+
_project_warnings_ignore.pop(project_dir.resolve(), None)
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
# ============================================================================
|
|
713
|
+
# META CONFIG CLASSES
|
|
714
|
+
# ============================================================================
|
|
715
|
+
# These are used by meta.py for handling meta.yaml files
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
class AccessConfig:
|
|
719
|
+
"""Access control configuration for meta.yaml."""
|
|
720
|
+
|
|
721
|
+
def __init__(
|
|
722
|
+
self,
|
|
723
|
+
rules: list[dict[str, Any]] | None = None,
|
|
724
|
+
ignore_meta: bool = False,
|
|
725
|
+
):
|
|
726
|
+
self.rules = rules or []
|
|
727
|
+
self.ignore_meta = ignore_meta
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
class MetaConfig:
|
|
731
|
+
"""Configuration from meta.yaml file."""
|
|
732
|
+
|
|
733
|
+
def __init__(
|
|
734
|
+
self,
|
|
735
|
+
path: Path | None = None,
|
|
736
|
+
ignore_meta: bool = False,
|
|
737
|
+
access: AccessConfig | None = None,
|
|
738
|
+
source: str | None = None,
|
|
739
|
+
queries: dict[str, Any] | None = None,
|
|
740
|
+
charts: dict[str, Any] | None = None,
|
|
741
|
+
face: dict[str, Any] | None = None,
|
|
742
|
+
style: dict[str, Any] | None = None,
|
|
743
|
+
typography: dict[str, Any] | None = None,
|
|
744
|
+
lint_ignore: list[str] | None = None,
|
|
745
|
+
lint_ignore_queries: dict[str, list[str]] | None = None,
|
|
746
|
+
):
|
|
747
|
+
self.path = path
|
|
748
|
+
self.ignore_meta = ignore_meta
|
|
749
|
+
self.access = access
|
|
750
|
+
self.source = source
|
|
751
|
+
self.queries = queries or {}
|
|
752
|
+
self.charts = charts or {}
|
|
753
|
+
self.face = face or {}
|
|
754
|
+
self.style = style or {}
|
|
755
|
+
self.typography = typography or {}
|
|
756
|
+
self.lint_ignore: list[str] = lint_ignore or []
|
|
757
|
+
self.lint_ignore_queries: dict[str, list[str]] = lint_ignore_queries or {}
|
|
758
|
+
|
|
759
|
+
@classmethod
|
|
760
|
+
def from_dict(cls, data: dict[str, Any], path: Path | None = None) -> MetaConfig:
|
|
761
|
+
"""Create MetaConfig from dictionary."""
|
|
762
|
+
access_data = data.get("access")
|
|
763
|
+
access_config = None
|
|
764
|
+
if access_data:
|
|
765
|
+
if isinstance(access_data, dict):
|
|
766
|
+
access_config = AccessConfig(
|
|
767
|
+
rules=access_data.get("rules", []),
|
|
768
|
+
ignore_meta=access_data.get("ignore_meta", False),
|
|
769
|
+
)
|
|
770
|
+
elif isinstance(access_data, list):
|
|
771
|
+
access_config = AccessConfig(rules=access_data)
|
|
772
|
+
|
|
773
|
+
lint_data = data.get("lint", {})
|
|
774
|
+
if lint_data and not isinstance(lint_data, dict):
|
|
775
|
+
raise ValueError(
|
|
776
|
+
f"meta.yaml 'lint' must be a mapping, got {type(lint_data).__name__}"
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
return cls(
|
|
780
|
+
path=path,
|
|
781
|
+
ignore_meta=data.get("ignore_meta", False),
|
|
782
|
+
access=access_config,
|
|
783
|
+
source=data.get("source"),
|
|
784
|
+
queries=data.get("queries", {}),
|
|
785
|
+
charts=data.get("charts", {}),
|
|
786
|
+
face=data.get("face", {}),
|
|
787
|
+
style=data.get("style", {}),
|
|
788
|
+
typography=data.get("typography", {}),
|
|
789
|
+
lint_ignore=lint_data.get("ignore", []),
|
|
790
|
+
lint_ignore_queries=lint_data.get("ignore_queries", {}),
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
def is_empty(self) -> bool:
|
|
794
|
+
"""Check if this meta config has no actual configuration."""
|
|
795
|
+
return (
|
|
796
|
+
not self.access
|
|
797
|
+
and not self.source
|
|
798
|
+
and not self.queries
|
|
799
|
+
and not self.charts
|
|
800
|
+
and not self.face
|
|
801
|
+
and not self.style
|
|
802
|
+
and not self.typography
|
|
803
|
+
and not self.lint_ignore
|
|
804
|
+
and not self.lint_ignore_queries
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
class ProjectSourcesConfig:
|
|
809
|
+
"""Project-level sources configuration."""
|
|
810
|
+
|
|
811
|
+
def __init__(
|
|
812
|
+
self,
|
|
813
|
+
default: str | None = None,
|
|
814
|
+
sources: dict[str, dict[str, Any]] | None = None,
|
|
815
|
+
):
|
|
816
|
+
self.default = default
|
|
817
|
+
self.sources = sources or {}
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def _normalize_project_sources(
|
|
821
|
+
sources: dict[str, dict[str, Any]],
|
|
822
|
+
) -> dict[str, dict[str, Any]]:
|
|
823
|
+
return {name: resolve_env_vars_in_dict(config) for name, config in sources.items()}
|