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,154 @@
|
|
|
1
|
+
"""Markdown report file loader and transformer.
|
|
2
|
+
|
|
3
|
+
Translates .md files with YAML frontmatter and {{ chart <id> }} embeds
|
|
4
|
+
into YAML face definitions that the normal compile pipeline accepts.
|
|
5
|
+
|
|
6
|
+
This is a pre-compile step — the output is a YAML string that feeds
|
|
7
|
+
directly into compile().
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
_CHART_EMBED_RE = re.compile(r"^\s*\{\{\s*chart\s+(\w+)\s*\}\}\s*$", re.MULTILINE)
|
|
16
|
+
|
|
17
|
+
# Frontmatter keys that indicate a Dataface markdown report
|
|
18
|
+
_DATAFACE_KEYS = {"queries", "charts", "variables", "source", "sources"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_chart_embeds(body: str) -> list[tuple[str, str]]:
|
|
22
|
+
"""Split markdown body into ordered (type, value) blocks.
|
|
23
|
+
|
|
24
|
+
Returns a list of tuples:
|
|
25
|
+
("text", <markdown text>)
|
|
26
|
+
("chart", <chart_id>)
|
|
27
|
+
|
|
28
|
+
Empty/whitespace-only blocks are dropped.
|
|
29
|
+
"""
|
|
30
|
+
if not body or not body.strip():
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
blocks: list[tuple[str, str]] = []
|
|
34
|
+
last_end = 0
|
|
35
|
+
|
|
36
|
+
for match in _CHART_EMBED_RE.finditer(body):
|
|
37
|
+
# Text before this embed
|
|
38
|
+
text = body[last_end : match.start()].strip()
|
|
39
|
+
if text:
|
|
40
|
+
blocks.append(("text", text))
|
|
41
|
+
blocks.append(("chart", match.group(1)))
|
|
42
|
+
last_end = match.end()
|
|
43
|
+
|
|
44
|
+
# Trailing text after last embed
|
|
45
|
+
text = body[last_end:].strip()
|
|
46
|
+
if text:
|
|
47
|
+
blocks.append(("text", text))
|
|
48
|
+
|
|
49
|
+
return blocks
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _extract_frontmatter(md_text: str) -> tuple[dict, str]:
|
|
53
|
+
"""Extract YAML frontmatter and markdown body from text.
|
|
54
|
+
|
|
55
|
+
Raises ValueError if frontmatter is missing or empty.
|
|
56
|
+
"""
|
|
57
|
+
if not md_text.startswith("---\n"):
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"Markdown face file must have YAML frontmatter (missing opening ---)"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
end = md_text.find("\n---\n", 4)
|
|
63
|
+
if end == -1:
|
|
64
|
+
# Check for --- at very end of file
|
|
65
|
+
if md_text.endswith("\n---"):
|
|
66
|
+
end = len(md_text) - 3
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
"Markdown face file must have YAML frontmatter (missing closing ---)"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
fm_text = md_text[4:end]
|
|
73
|
+
body = md_text[end + 5 :] if end + 5 <= len(md_text) else ""
|
|
74
|
+
|
|
75
|
+
fm = yaml.safe_load(fm_text)
|
|
76
|
+
if not fm or not isinstance(fm, dict):
|
|
77
|
+
raise ValueError("Markdown face file must have non-empty YAML frontmatter")
|
|
78
|
+
|
|
79
|
+
return fm, body
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def markdown_to_yaml(md_text: str) -> str:
|
|
83
|
+
"""Transform a markdown face file into a YAML string for the compiler.
|
|
84
|
+
|
|
85
|
+
Parses frontmatter, splits the body into content/chart blocks,
|
|
86
|
+
and emits a face definition with a rows layout.
|
|
87
|
+
"""
|
|
88
|
+
fm, body = _extract_frontmatter(md_text)
|
|
89
|
+
|
|
90
|
+
# Build rows from markdown body
|
|
91
|
+
blocks = parse_chart_embeds(body)
|
|
92
|
+
rows: list = []
|
|
93
|
+
for block_type, value in blocks:
|
|
94
|
+
if block_type == "text":
|
|
95
|
+
rows.append({"text": value})
|
|
96
|
+
elif block_type == "chart":
|
|
97
|
+
rows.append(value)
|
|
98
|
+
|
|
99
|
+
# If body had content but no embeds, ensure we have at least one row
|
|
100
|
+
if not rows and body.strip():
|
|
101
|
+
rows.append({"text": body.strip()})
|
|
102
|
+
|
|
103
|
+
# Build the face dict from frontmatter + generated rows
|
|
104
|
+
face_dict = dict(fm)
|
|
105
|
+
if rows:
|
|
106
|
+
face_dict["rows"] = rows
|
|
107
|
+
elif "text" not in face_dict:
|
|
108
|
+
# No body content and no content in frontmatter — add empty content
|
|
109
|
+
face_dict["text"] = ""
|
|
110
|
+
|
|
111
|
+
return yaml.dump(face_dict, default_flow_style=False, allow_unicode=True)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_markdown_face(path: Path | str) -> bool:
|
|
115
|
+
"""Check if a .md file is a Dataface markdown report.
|
|
116
|
+
|
|
117
|
+
Detection rules:
|
|
118
|
+
1. Must have .md extension
|
|
119
|
+
2. Must have YAML frontmatter
|
|
120
|
+
3. Either in a faces/ directory, OR frontmatter contains Dataface keys
|
|
121
|
+
"""
|
|
122
|
+
path = Path(path)
|
|
123
|
+
if path.suffix.lower() != ".md":
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
if not path.exists():
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
text = path.read_text(encoding="utf-8")
|
|
131
|
+
except (OSError, UnicodeDecodeError):
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
# Must have frontmatter
|
|
135
|
+
if not text.startswith("---\n"):
|
|
136
|
+
return False
|
|
137
|
+
end = text.find("\n---\n", 4)
|
|
138
|
+
if end == -1:
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
# In faces/ directory — any frontmatter is enough
|
|
142
|
+
if "faces" in path.parts:
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
# Outside faces/ — require Dataface-specific keys in frontmatter
|
|
146
|
+
try:
|
|
147
|
+
fm = yaml.safe_load(text[4:end])
|
|
148
|
+
except yaml.YAMLError:
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
if not isinstance(fm, dict):
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
return bool(fm.keys() & _DATAFACE_KEYS)
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Meta.yaml cascading configuration resolution.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE
|
|
4
|
+
Purpose: Resolve meta.yaml chain from face file to repo root and merge
|
|
5
|
+
configurations according to cascading rules.
|
|
6
|
+
|
|
7
|
+
Entry Points:
|
|
8
|
+
- resolve_meta_chain(face_path: Path) -> MetaConfig
|
|
9
|
+
- merge_meta_with_face(meta: MetaConfig, face_data: Dict) -> Dict
|
|
10
|
+
- load_meta_file(meta_path: Path) -> MetaConfig
|
|
11
|
+
|
|
12
|
+
This module implements directory-level configuration files (meta.yaml) that
|
|
13
|
+
cascade settings to all faces in that directory and subdirectories.
|
|
14
|
+
|
|
15
|
+
Merge Rules:
|
|
16
|
+
- EXTEND (merge lists/dicts): access, queries, charts
|
|
17
|
+
- OVERRIDE (child wins): source, face, style, typography
|
|
18
|
+
|
|
19
|
+
ignore_meta Flag:
|
|
20
|
+
- Root level: ignore_meta: true ignores ALL parent meta
|
|
21
|
+
- Field level: access: { ignore_meta: true, ... } ignores just that field
|
|
22
|
+
|
|
23
|
+
Dependencies:
|
|
24
|
+
- yaml
|
|
25
|
+
- pathlib
|
|
26
|
+
|
|
27
|
+
See also:
|
|
28
|
+
- config.py: MetaConfig type definition
|
|
29
|
+
- compiler.py: Integration point
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
import yaml
|
|
36
|
+
|
|
37
|
+
from dataface.core.compile.config import AccessConfig, MetaConfig
|
|
38
|
+
from dataface.core.compile.errors import CompilationError
|
|
39
|
+
|
|
40
|
+
# ============================================================================
|
|
41
|
+
# META.YAML FILE LOADING
|
|
42
|
+
# ============================================================================
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_meta_file(meta_path: Path) -> MetaConfig:
|
|
46
|
+
"""Load a meta.yaml file and parse it into MetaConfig.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
meta_path: Path to the meta.yaml file
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
MetaConfig instance
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
CompilationError: If file cannot be parsed
|
|
56
|
+
"""
|
|
57
|
+
if not meta_path.exists():
|
|
58
|
+
raise CompilationError(f"Meta file not found: {meta_path}")
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
with open(meta_path) as f:
|
|
62
|
+
data = yaml.safe_load(f) or {}
|
|
63
|
+
except yaml.YAMLError as e:
|
|
64
|
+
raise CompilationError(f"Failed to parse meta.yaml: {e}") from e
|
|
65
|
+
|
|
66
|
+
# Validate no unsupported fields
|
|
67
|
+
unsupported = {"sources", "variables", "faces"}
|
|
68
|
+
found_unsupported = unsupported.intersection(data.keys())
|
|
69
|
+
if found_unsupported:
|
|
70
|
+
raise CompilationError(
|
|
71
|
+
f"Unsupported fields in meta.yaml: {found_unsupported}. "
|
|
72
|
+
f"'sources' must be in dataface.yml, 'variables' and 'faces' "
|
|
73
|
+
f"must be defined in face files."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return MetaConfig.from_dict(data, path=meta_path)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def find_meta_files(face_path: Path, root_path: Path | None = None) -> list[Path]:
|
|
80
|
+
"""Find all meta.yaml files from face directory up to root.
|
|
81
|
+
|
|
82
|
+
Walks up the directory tree from the face file's directory to the
|
|
83
|
+
root path, collecting all meta.yaml files found.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
face_path: Path to the face file
|
|
87
|
+
root_path: Root directory to stop searching (default: filesystem root)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
List of meta.yaml paths, ordered from root to face directory
|
|
91
|
+
(so child configs come last for proper override order)
|
|
92
|
+
"""
|
|
93
|
+
meta_files: list[Path] = []
|
|
94
|
+
|
|
95
|
+
# Start from the face's directory
|
|
96
|
+
current_dir = face_path.parent.resolve()
|
|
97
|
+
|
|
98
|
+
# If no root specified, try to find git root or use filesystem root
|
|
99
|
+
if root_path is None:
|
|
100
|
+
root_path = find_project_root(current_dir)
|
|
101
|
+
|
|
102
|
+
root_path = root_path.resolve()
|
|
103
|
+
|
|
104
|
+
# Walk up from face directory to root
|
|
105
|
+
while True:
|
|
106
|
+
# Check for meta.yaml (and meta.yml variant)
|
|
107
|
+
for meta_name in ["meta.yaml", "meta.yml"]:
|
|
108
|
+
meta_path = current_dir / meta_name
|
|
109
|
+
if meta_path.exists():
|
|
110
|
+
meta_files.append(meta_path)
|
|
111
|
+
break # Only use one variant per directory
|
|
112
|
+
|
|
113
|
+
# Stop if we've reached the root
|
|
114
|
+
if current_dir == root_path or current_dir.parent == current_dir:
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
current_dir = current_dir.parent
|
|
118
|
+
|
|
119
|
+
# Reverse so root comes first (proper merge order: parent -> child)
|
|
120
|
+
meta_files.reverse()
|
|
121
|
+
|
|
122
|
+
return meta_files
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def find_project_root(start_dir: Path) -> Path:
|
|
126
|
+
"""Find the project root directory.
|
|
127
|
+
|
|
128
|
+
Looks for common project markers (.git, dataface.yml, pyproject.toml).
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
start_dir: Directory to start searching from
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Project root path, or filesystem root if not found
|
|
135
|
+
"""
|
|
136
|
+
current = start_dir.resolve()
|
|
137
|
+
markers = [".git", "dataface.yml", "dataface.yaml", "pyproject.toml"]
|
|
138
|
+
|
|
139
|
+
while current.parent != current:
|
|
140
|
+
for marker in markers:
|
|
141
|
+
if (current / marker).exists():
|
|
142
|
+
return current
|
|
143
|
+
current = current.parent
|
|
144
|
+
|
|
145
|
+
return start_dir # Fallback to start directory
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ============================================================================
|
|
149
|
+
# META CONFIGURATION MERGING
|
|
150
|
+
# ============================================================================
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def resolve_meta_chain(
|
|
154
|
+
face_path: Path,
|
|
155
|
+
root_path: Path | None = None,
|
|
156
|
+
) -> MetaConfig:
|
|
157
|
+
"""Resolve the complete meta.yaml chain for a face file.
|
|
158
|
+
|
|
159
|
+
Walks up directory tree from face file to root, collects all meta.yaml
|
|
160
|
+
files, and merges them according to the cascading rules.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
face_path: Path to the face file
|
|
164
|
+
root_path: Root directory to stop searching
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Merged MetaConfig combining all parent configurations
|
|
168
|
+
"""
|
|
169
|
+
meta_files = find_meta_files(face_path, root_path)
|
|
170
|
+
|
|
171
|
+
if not meta_files:
|
|
172
|
+
# No meta files found, return empty config
|
|
173
|
+
return MetaConfig()
|
|
174
|
+
|
|
175
|
+
# Load all meta configs
|
|
176
|
+
meta_configs: list[MetaConfig] = []
|
|
177
|
+
for meta_path in meta_files:
|
|
178
|
+
try:
|
|
179
|
+
meta_config = load_meta_file(meta_path)
|
|
180
|
+
meta_configs.append(meta_config)
|
|
181
|
+
except CompilationError:
|
|
182
|
+
# Skip invalid meta files with a warning (could log here)
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
if not meta_configs:
|
|
186
|
+
return MetaConfig()
|
|
187
|
+
|
|
188
|
+
# Merge configs from root to leaf
|
|
189
|
+
result = meta_configs[0]
|
|
190
|
+
for child_config in meta_configs[1:]:
|
|
191
|
+
result = _merge_meta_configs(result, child_config)
|
|
192
|
+
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _merge_meta_configs(parent: MetaConfig, child: MetaConfig) -> MetaConfig:
|
|
197
|
+
"""Merge two MetaConfig objects according to cascading rules.
|
|
198
|
+
|
|
199
|
+
Merge rules:
|
|
200
|
+
- EXTEND (merge lists/dicts): access, queries, charts
|
|
201
|
+
- OVERRIDE (child wins): source, face, style, typography
|
|
202
|
+
|
|
203
|
+
ignore_meta handling:
|
|
204
|
+
- If child.ignore_meta is True, return child (ignore all parent)
|
|
205
|
+
- Field-level ignore_meta (e.g., access.ignore_meta) ignores just that field
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
parent: Parent (ancestor) configuration
|
|
209
|
+
child: Child (closer to face) configuration
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Merged MetaConfig
|
|
213
|
+
"""
|
|
214
|
+
# If child ignores all parent meta, return child as-is
|
|
215
|
+
if child.ignore_meta:
|
|
216
|
+
return child
|
|
217
|
+
|
|
218
|
+
# Merge lint config: union of ignore lists, union inner lists per query
|
|
219
|
+
merged_lint_ignore = list(dict.fromkeys(parent.lint_ignore + child.lint_ignore))
|
|
220
|
+
merged_lint_ignore_queries: dict[str, list[str]] = dict(parent.lint_ignore_queries)
|
|
221
|
+
for k, v in child.lint_ignore_queries.items():
|
|
222
|
+
merged_lint_ignore_queries[k] = list(
|
|
223
|
+
dict.fromkeys((merged_lint_ignore_queries.get(k) or []) + v)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Merge each field according to its rule
|
|
227
|
+
return MetaConfig(
|
|
228
|
+
path=child.path,
|
|
229
|
+
ignore_meta=False,
|
|
230
|
+
access=_merge_access(parent.access, child.access),
|
|
231
|
+
source=child.source if child.source else parent.source, # Override
|
|
232
|
+
queries=_merge_dicts(parent.queries, child.queries), # Extend
|
|
233
|
+
charts=_merge_dicts(parent.charts, child.charts), # Extend
|
|
234
|
+
face=_merge_dicts(parent.face, child.face), # Override (child wins)
|
|
235
|
+
style=_merge_dicts(parent.style, child.style), # Override (child wins)
|
|
236
|
+
typography=_merge_dicts(
|
|
237
|
+
parent.typography, child.typography
|
|
238
|
+
), # Override (child wins)
|
|
239
|
+
lint_ignore=merged_lint_ignore,
|
|
240
|
+
lint_ignore_queries=merged_lint_ignore_queries,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _merge_access(
|
|
245
|
+
parent: AccessConfig | None,
|
|
246
|
+
child: AccessConfig | None,
|
|
247
|
+
) -> AccessConfig | None:
|
|
248
|
+
"""Merge access configurations.
|
|
249
|
+
|
|
250
|
+
Access rules are EXTENDED (merged), unless child has ignore_meta.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
parent: Parent access config
|
|
254
|
+
child: Child access config
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Merged access config
|
|
258
|
+
"""
|
|
259
|
+
if child is None:
|
|
260
|
+
return parent
|
|
261
|
+
if parent is None:
|
|
262
|
+
return child
|
|
263
|
+
|
|
264
|
+
# If child ignores parent access, use only child
|
|
265
|
+
if child.ignore_meta:
|
|
266
|
+
return AccessConfig(rules=child.rules, ignore_meta=False)
|
|
267
|
+
|
|
268
|
+
# Extend: combine rules from both
|
|
269
|
+
merged_rules = parent.rules + child.rules
|
|
270
|
+
return AccessConfig(rules=merged_rules, ignore_meta=False)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _merge_dicts(parent: dict[str, Any], child: dict[str, Any]) -> dict[str, Any]:
|
|
274
|
+
"""Merge two dictionaries with child overriding parent.
|
|
275
|
+
|
|
276
|
+
For EXTEND fields (queries, charts), items are merged with child taking
|
|
277
|
+
precedence for same keys.
|
|
278
|
+
|
|
279
|
+
For OVERRIDE fields (face, style, typography), child values completely
|
|
280
|
+
replace parent values for the same keys.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
parent: Parent dictionary
|
|
284
|
+
child: Child dictionary
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Merged dictionary
|
|
288
|
+
"""
|
|
289
|
+
if not parent:
|
|
290
|
+
return dict(child)
|
|
291
|
+
if not child:
|
|
292
|
+
return dict(parent)
|
|
293
|
+
|
|
294
|
+
result = dict(parent)
|
|
295
|
+
result.update(child)
|
|
296
|
+
return result
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# ============================================================================
|
|
300
|
+
# FACE INTEGRATION
|
|
301
|
+
# ============================================================================
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def merge_meta_with_face(
|
|
305
|
+
meta: MetaConfig,
|
|
306
|
+
face_data: dict[str, Any],
|
|
307
|
+
) -> dict[str, Any]:
|
|
308
|
+
"""Merge resolved meta configuration with face's own definitions.
|
|
309
|
+
|
|
310
|
+
The face's own definitions take precedence over meta settings.
|
|
311
|
+
This applies the meta configuration as "defaults" that the face can override.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
meta: Resolved MetaConfig from the directory chain
|
|
315
|
+
face_data: Raw face data from YAML
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Face data with meta settings applied as defaults
|
|
319
|
+
"""
|
|
320
|
+
if meta.is_empty():
|
|
321
|
+
return face_data
|
|
322
|
+
|
|
323
|
+
result = dict(face_data)
|
|
324
|
+
|
|
325
|
+
# Check if face ignores meta entirely
|
|
326
|
+
if result.get("ignore_meta", False):
|
|
327
|
+
# Remove ignore_meta key and return face as-is
|
|
328
|
+
result.pop("ignore_meta", None)
|
|
329
|
+
return result
|
|
330
|
+
|
|
331
|
+
# Apply source as default (face can override)
|
|
332
|
+
if meta.source and "source" not in result:
|
|
333
|
+
result["source"] = meta.source
|
|
334
|
+
|
|
335
|
+
# Merge queries (meta queries are defaults, face can override)
|
|
336
|
+
if meta.queries:
|
|
337
|
+
merged_queries = dict(meta.queries)
|
|
338
|
+
merged_queries.update(result.get("queries", {}))
|
|
339
|
+
result["queries"] = merged_queries
|
|
340
|
+
|
|
341
|
+
# Merge charts (meta charts are defaults, face can override)
|
|
342
|
+
if meta.charts:
|
|
343
|
+
merged_charts = dict(meta.charts)
|
|
344
|
+
merged_charts.update(result.get("charts", {}))
|
|
345
|
+
result["charts"] = merged_charts
|
|
346
|
+
|
|
347
|
+
# Apply style as defaults (face style overrides)
|
|
348
|
+
if meta.style:
|
|
349
|
+
merged_style = dict(meta.style)
|
|
350
|
+
merged_style.update(result.get("style", {}))
|
|
351
|
+
result["style"] = merged_style
|
|
352
|
+
|
|
353
|
+
# Apply face config as defaults
|
|
354
|
+
if meta.face:
|
|
355
|
+
for key, value in meta.face.items():
|
|
356
|
+
if key not in result:
|
|
357
|
+
result[key] = value
|
|
358
|
+
|
|
359
|
+
# Apply typography as defaults
|
|
360
|
+
if meta.typography:
|
|
361
|
+
merged_typography = dict(meta.typography)
|
|
362
|
+
merged_typography.update(result.get("typography", {}))
|
|
363
|
+
result["typography"] = merged_typography
|
|
364
|
+
|
|
365
|
+
# Note: access is typically handled at a different layer (routing/auth)
|
|
366
|
+
# We store it in the result for downstream processing
|
|
367
|
+
if meta.access and "access" not in result:
|
|
368
|
+
result["access"] = {"rules": meta.access.rules}
|
|
369
|
+
|
|
370
|
+
return result
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def get_meta_for_face(
|
|
374
|
+
face_path: Path,
|
|
375
|
+
root_path: Path | None = None,
|
|
376
|
+
) -> MetaConfig:
|
|
377
|
+
"""Convenience function to get resolved meta config for a face.
|
|
378
|
+
|
|
379
|
+
This is the main entry point for compiler integration.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
face_path: Path to the face file
|
|
383
|
+
root_path: Optional project root path
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Resolved MetaConfig for the face
|
|
387
|
+
"""
|
|
388
|
+
return resolve_meta_chain(face_path, root_path)
|
|
File without changes
|
|
File without changes
|