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,150 @@
|
|
|
1
|
+
"""Shared dbt utilities for SQL adapters."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from dataface.core.execute.adapters.base import ResolvedRelation, SchemaStatus
|
|
10
|
+
|
|
11
|
+
DBT_PROJECT_DB_NAMES = ["sample.duckdb", "dataface_examples.duckdb", "dev.duckdb"]
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def load_dbt_manifest(project_path: Path) -> dict[str, Any] | None:
|
|
16
|
+
"""Load a dbt manifest from target/ or the committed snapshot fallback."""
|
|
17
|
+
manifest_path = project_path / "target" / "manifest.json"
|
|
18
|
+
snapshot_path = project_path / "manifest.snapshot.json"
|
|
19
|
+
|
|
20
|
+
manifest_to_load = None
|
|
21
|
+
if manifest_path.exists():
|
|
22
|
+
manifest_to_load = manifest_path
|
|
23
|
+
elif snapshot_path.exists():
|
|
24
|
+
manifest_to_load = snapshot_path
|
|
25
|
+
|
|
26
|
+
if manifest_to_load is None:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
with open(manifest_to_load) as f:
|
|
31
|
+
return json.load(f)
|
|
32
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
33
|
+
logger.debug("Failed to load dbt manifest from %s: %s", manifest_to_load, e)
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def classify_schema_status(
|
|
38
|
+
dev_schema: str,
|
|
39
|
+
prod_schema: str | None,
|
|
40
|
+
*,
|
|
41
|
+
prod_manifest_available: bool = True,
|
|
42
|
+
) -> SchemaStatus:
|
|
43
|
+
"""Compare dev vs prod schema for a resolved dbt relation."""
|
|
44
|
+
if not prod_manifest_available:
|
|
45
|
+
return "unknown"
|
|
46
|
+
if prod_schema is None:
|
|
47
|
+
return "changed"
|
|
48
|
+
if dev_schema.lower() == prod_schema.lower():
|
|
49
|
+
return "unchanged"
|
|
50
|
+
return "changed"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _find_model_node(
|
|
54
|
+
manifest: dict[str, Any] | None, model_name: str
|
|
55
|
+
) -> dict[str, Any] | None:
|
|
56
|
+
if not manifest or "nodes" not in manifest:
|
|
57
|
+
return None
|
|
58
|
+
for node in manifest["nodes"].values():
|
|
59
|
+
if node.get("resource_type") == "model" and node.get("name") == model_name:
|
|
60
|
+
return node
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _find_source_node(
|
|
65
|
+
manifest: dict[str, Any] | None, source_name: str, table_name: str
|
|
66
|
+
) -> dict[str, Any] | None:
|
|
67
|
+
if not manifest or "sources" not in manifest:
|
|
68
|
+
return None
|
|
69
|
+
for node in manifest["sources"].values():
|
|
70
|
+
if node.get("source_name") == source_name and node.get("name") == table_name:
|
|
71
|
+
return node
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _node_schema_and_sql(node: dict[str, Any], fallback_name: str) -> tuple[str, str]:
|
|
76
|
+
"""Return (logical_schema_for_provenance, sql_fragment)."""
|
|
77
|
+
schema = str(node.get("schema", "main"))
|
|
78
|
+
if node.get("relation_name"):
|
|
79
|
+
rel = str(node["relation_name"])
|
|
80
|
+
return schema, rel
|
|
81
|
+
alias = str(node.get("alias", fallback_name))
|
|
82
|
+
return schema, f"{schema}.{alias}"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def resolve_dbt_refs_with_provenance(
|
|
86
|
+
sql: str,
|
|
87
|
+
manifest: dict[str, Any],
|
|
88
|
+
prod_manifest: dict[str, Any] | None,
|
|
89
|
+
) -> tuple[str, list[ResolvedRelation]]:
|
|
90
|
+
"""Resolve ref/source in SQL and record dev-vs-prod schema status per relation."""
|
|
91
|
+
relations: list[ResolvedRelation] = []
|
|
92
|
+
prod_available = prod_manifest is not None
|
|
93
|
+
|
|
94
|
+
def resolve_ref(match: re.Match[str]) -> str:
|
|
95
|
+
model_name = match.group(1)
|
|
96
|
+
node = _find_model_node(manifest, model_name)
|
|
97
|
+
if not node:
|
|
98
|
+
return model_name
|
|
99
|
+
schema, fragment = _node_schema_and_sql(node, model_name)
|
|
100
|
+
prod_node = _find_model_node(prod_manifest, model_name)
|
|
101
|
+
prod_schema = (
|
|
102
|
+
_node_schema_and_sql(prod_node, model_name)[0] if prod_node else None
|
|
103
|
+
)
|
|
104
|
+
status = classify_schema_status(
|
|
105
|
+
schema,
|
|
106
|
+
prod_schema,
|
|
107
|
+
prod_manifest_available=prod_available,
|
|
108
|
+
)
|
|
109
|
+
relations.append(
|
|
110
|
+
ResolvedRelation(ref_name=model_name, schema=schema, status=status)
|
|
111
|
+
)
|
|
112
|
+
return fragment
|
|
113
|
+
|
|
114
|
+
sql = re.sub(r"\{\{\s*ref\(['\"]([^'\"]+)['\"]\)\s*\}\}", resolve_ref, sql)
|
|
115
|
+
|
|
116
|
+
def resolve_source(match: re.Match[str]) -> str:
|
|
117
|
+
source_name = match.group(1)
|
|
118
|
+
table_name = match.group(2)
|
|
119
|
+
node = _find_source_node(manifest, source_name, table_name)
|
|
120
|
+
if not node:
|
|
121
|
+
return f"{source_name}.{table_name}"
|
|
122
|
+
schema, fragment = _node_schema_and_sql(node, table_name)
|
|
123
|
+
prod_node = _find_source_node(prod_manifest, source_name, table_name)
|
|
124
|
+
prod_schema = (
|
|
125
|
+
_node_schema_and_sql(prod_node, table_name)[0] if prod_node else None
|
|
126
|
+
)
|
|
127
|
+
status = classify_schema_status(
|
|
128
|
+
schema,
|
|
129
|
+
prod_schema,
|
|
130
|
+
prod_manifest_available=prod_available,
|
|
131
|
+
)
|
|
132
|
+
ref_name = f"{source_name}.{table_name}"
|
|
133
|
+
relations.append(
|
|
134
|
+
ResolvedRelation(ref_name=ref_name, schema=schema, status=status)
|
|
135
|
+
)
|
|
136
|
+
return fragment
|
|
137
|
+
|
|
138
|
+
sql = re.sub(
|
|
139
|
+
r"\{\{\s*source\(['\"]([^'\"]+)['\"],\s*['\"]([^'\"]+)['\"]\)\s*\}\}",
|
|
140
|
+
resolve_source,
|
|
141
|
+
sql,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return sql, relations
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def resolve_dbt_refs(sql: str, manifest: dict[str, Any]) -> str:
|
|
148
|
+
"""Resolve refs/sources in SQL using the dbt manifest."""
|
|
149
|
+
resolved_sql, _ = resolve_dbt_refs_with_provenance(sql, manifest, None)
|
|
150
|
+
return resolved_sql
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""HTTP adapter for executing REST API queries.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE
|
|
4
|
+
Purpose: Execute queries against HTTP/REST API endpoints.
|
|
5
|
+
|
|
6
|
+
This adapter allows fetching data from external APIs, supporting
|
|
7
|
+
GET, POST, PUT, DELETE, and PATCH methods with headers, params, and body.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from dataface.core.compile.models.source import SourceConfig
|
|
19
|
+
|
|
20
|
+
from dataface.core.compile.jinja import resolve_jinja_template
|
|
21
|
+
from dataface.core.compile.models.face.compiled import VariableValues
|
|
22
|
+
from dataface.core.compile.models.query.compiled import (
|
|
23
|
+
AnyQuery,
|
|
24
|
+
is_http_query,
|
|
25
|
+
)
|
|
26
|
+
from dataface.core.execute.adapters.base import (
|
|
27
|
+
BaseAdapter,
|
|
28
|
+
QueryParams,
|
|
29
|
+
QueryResult,
|
|
30
|
+
handle_adapter_error,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _resolve_json_path(data: Any, json_path: str) -> list[Any]:
|
|
35
|
+
"""Extract a list from data using a dot-notation JSONPath expression.
|
|
36
|
+
|
|
37
|
+
Supports $.key and $.key.subkey format. Raises ValueError if the path
|
|
38
|
+
is missing or the resolved value is not a list.
|
|
39
|
+
"""
|
|
40
|
+
path = json_path.lstrip("$").lstrip(".")
|
|
41
|
+
keys = path.split(".") if path else []
|
|
42
|
+
|
|
43
|
+
node: Any = data
|
|
44
|
+
traversed: list[str] = []
|
|
45
|
+
for key in keys:
|
|
46
|
+
if not isinstance(node, dict) or key not in node:
|
|
47
|
+
location = ".".join(traversed) or "$"
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"json_path '{json_path}': key '{key}' not found at '{location}'"
|
|
50
|
+
)
|
|
51
|
+
node = node[key]
|
|
52
|
+
traversed.append(key)
|
|
53
|
+
|
|
54
|
+
if not isinstance(node, list):
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"json_path '{json_path}': expected a list, got {type(node).__name__}"
|
|
57
|
+
)
|
|
58
|
+
return node
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class HttpAdapter(BaseAdapter):
|
|
62
|
+
"""Adapter for executing HTTP/REST API queries.
|
|
63
|
+
|
|
64
|
+
Supported query types: http
|
|
65
|
+
|
|
66
|
+
Fetches data from REST API endpoints with support for:
|
|
67
|
+
- Multiple HTTP methods (GET, POST, PUT, DELETE, PATCH)
|
|
68
|
+
- Custom headers
|
|
69
|
+
- Query parameters
|
|
70
|
+
- Request body (JSON)
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> adapter = HttpAdapter()
|
|
74
|
+
>>> query = HttpQuery(
|
|
75
|
+
... url="https://api.example.com/users",
|
|
76
|
+
... headers={"Authorization": "Bearer {{ token }}"}
|
|
77
|
+
... )
|
|
78
|
+
>>> result = adapter.execute(query, {"token": "xyz"})
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, timeout: int = 30):
|
|
82
|
+
"""Initialize HTTP adapter.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
timeout: Request timeout in seconds
|
|
86
|
+
"""
|
|
87
|
+
self.timeout = timeout
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def supported_types(self) -> set[str]:
|
|
91
|
+
"""Return supported query types."""
|
|
92
|
+
return {"http"}
|
|
93
|
+
|
|
94
|
+
def _execute(
|
|
95
|
+
self,
|
|
96
|
+
query: AnyQuery,
|
|
97
|
+
variables: VariableValues | None = None,
|
|
98
|
+
params: QueryParams = None,
|
|
99
|
+
source_config: SourceConfig | None = None,
|
|
100
|
+
) -> QueryResult:
|
|
101
|
+
"""Execute an HTTP query.
|
|
102
|
+
|
|
103
|
+
Uses type guard for type-safe field access.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
query: AnyQuery object (HttpQuery expected)
|
|
107
|
+
variables: Variable values for Jinja resolution
|
|
108
|
+
params: Not used for HTTP queries (HTTP doesn't use SQL parameterization)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
QueryResult with data or error
|
|
112
|
+
"""
|
|
113
|
+
if not is_http_query(query):
|
|
114
|
+
return QueryResult(
|
|
115
|
+
data=[],
|
|
116
|
+
error=f"Expected HTTP query, got {query.query_type}",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
url = query.url
|
|
120
|
+
|
|
121
|
+
# Resolve Jinja templates in URL and other fields
|
|
122
|
+
try:
|
|
123
|
+
resolved_url = resolve_jinja_template(url, variables)
|
|
124
|
+
method = (query.method or "GET").upper()
|
|
125
|
+
headers = query.headers or {}
|
|
126
|
+
params_dict: dict[str, Any] = query.params or {}
|
|
127
|
+
body = query.body
|
|
128
|
+
|
|
129
|
+
# Resolve templates in headers, params, body
|
|
130
|
+
resolved_headers: dict[str, Any] = {}
|
|
131
|
+
for k, v in headers.items():
|
|
132
|
+
if isinstance(v, str):
|
|
133
|
+
resolved_headers[k] = resolve_jinja_template(v, variables)
|
|
134
|
+
else:
|
|
135
|
+
resolved_headers[k] = v
|
|
136
|
+
|
|
137
|
+
resolved_params: dict[str, Any] = {}
|
|
138
|
+
for k, v in params_dict.items():
|
|
139
|
+
if isinstance(v, str):
|
|
140
|
+
resolved_params[k] = resolve_jinja_template(v, variables)
|
|
141
|
+
else:
|
|
142
|
+
resolved_params[k] = v
|
|
143
|
+
|
|
144
|
+
resolved_body: Any = None
|
|
145
|
+
if body:
|
|
146
|
+
if isinstance(body, str):
|
|
147
|
+
resolved_body = resolve_jinja_template(body, variables)
|
|
148
|
+
elif isinstance(body, dict):
|
|
149
|
+
resolved_body = {}
|
|
150
|
+
for k, v in body.items():
|
|
151
|
+
if isinstance(v, str):
|
|
152
|
+
resolved_body[k] = resolve_jinja_template(v, variables)
|
|
153
|
+
else:
|
|
154
|
+
resolved_body[k] = v
|
|
155
|
+
else:
|
|
156
|
+
resolved_body = body
|
|
157
|
+
|
|
158
|
+
except (ValueError, KeyError, TypeError) as e:
|
|
159
|
+
return handle_adapter_error("HTTP query template resolution", e)
|
|
160
|
+
|
|
161
|
+
# Execute HTTP request
|
|
162
|
+
try:
|
|
163
|
+
with httpx.Client(timeout=self.timeout) as client:
|
|
164
|
+
if method == "GET":
|
|
165
|
+
response = client.get(
|
|
166
|
+
resolved_url, headers=resolved_headers, params=resolved_params
|
|
167
|
+
)
|
|
168
|
+
elif method == "POST":
|
|
169
|
+
response = client.post(
|
|
170
|
+
resolved_url,
|
|
171
|
+
headers=resolved_headers,
|
|
172
|
+
json=resolved_body,
|
|
173
|
+
params=resolved_params,
|
|
174
|
+
)
|
|
175
|
+
elif method == "PUT":
|
|
176
|
+
response = client.put(
|
|
177
|
+
resolved_url,
|
|
178
|
+
headers=resolved_headers,
|
|
179
|
+
json=resolved_body,
|
|
180
|
+
params=resolved_params,
|
|
181
|
+
)
|
|
182
|
+
elif method == "DELETE":
|
|
183
|
+
response = client.delete(
|
|
184
|
+
resolved_url, headers=resolved_headers, params=resolved_params
|
|
185
|
+
)
|
|
186
|
+
elif method == "PATCH":
|
|
187
|
+
response = client.patch(
|
|
188
|
+
resolved_url,
|
|
189
|
+
headers=resolved_headers,
|
|
190
|
+
json=resolved_body,
|
|
191
|
+
params=resolved_params,
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
return QueryResult(
|
|
195
|
+
data=[],
|
|
196
|
+
error=f"Unsupported HTTP method: {method}",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
response.raise_for_status()
|
|
200
|
+
data = response.json()
|
|
201
|
+
|
|
202
|
+
if query.json_path is not None:
|
|
203
|
+
try:
|
|
204
|
+
result_data = _resolve_json_path(data, query.json_path)
|
|
205
|
+
except ValueError as e:
|
|
206
|
+
return QueryResult(data=[], error=str(e))
|
|
207
|
+
elif isinstance(data, list):
|
|
208
|
+
result_data = data
|
|
209
|
+
else:
|
|
210
|
+
return QueryResult(
|
|
211
|
+
data=[],
|
|
212
|
+
error=(
|
|
213
|
+
"HTTP response is a JSON object. "
|
|
214
|
+
"Set json_path to extract the data array "
|
|
215
|
+
"(e.g., json_path: $.data)."
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return QueryResult(data=result_data)
|
|
220
|
+
|
|
221
|
+
except httpx.HTTPError as e:
|
|
222
|
+
return handle_adapter_error("HTTP request", e)
|
|
223
|
+
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|
224
|
+
return handle_adapter_error("HTTP query execution", e)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""MetricFlow adapter for executing dbt Semantic Layer queries.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE
|
|
4
|
+
Purpose: Execute queries against dbt's Semantic Layer using MetricFlow.
|
|
5
|
+
|
|
6
|
+
This adapter enables querying dbt metrics and dimensions using the
|
|
7
|
+
MetricFlow query engine, supporting the dbt Semantic Layer.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from dataface.core.compile.models.source import SourceConfig
|
|
16
|
+
|
|
17
|
+
from dataface.core.compile.jinja import resolve_query_filters
|
|
18
|
+
from dataface.core.compile.models.face.compiled import VariableValues
|
|
19
|
+
from dataface.core.compile.models.query.compiled import (
|
|
20
|
+
AnyQuery,
|
|
21
|
+
is_metricflow_query,
|
|
22
|
+
)
|
|
23
|
+
from dataface.core.execute.adapters.base import BaseAdapter, QueryParams, QueryResult
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MetricFlowAdapter(BaseAdapter):
|
|
27
|
+
"""Adapter for executing MetricFlow (dbt Semantic Layer) queries.
|
|
28
|
+
|
|
29
|
+
Supported query types: metricflow
|
|
30
|
+
|
|
31
|
+
Executes queries against dbt's Semantic Layer using MetricFlow.
|
|
32
|
+
In a full implementation, this uses the MetricFlow Python API or CLI.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
>>> adapter = MetricFlowAdapter()
|
|
36
|
+
>>> query = MetricFlowQuery(
|
|
37
|
+
... metrics=["revenue", "orders"],
|
|
38
|
+
... dimensions=["date", "region"]
|
|
39
|
+
... )
|
|
40
|
+
>>> result = adapter.execute(query)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, config_path: str | None = None):
|
|
44
|
+
"""Initialize MetricFlow adapter.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
config_path: Optional path to MetricFlow configuration
|
|
48
|
+
"""
|
|
49
|
+
self.config_path = config_path
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def supported_types(self) -> set[str]:
|
|
53
|
+
"""Return supported query types."""
|
|
54
|
+
return {"metricflow"}
|
|
55
|
+
|
|
56
|
+
def _execute(
|
|
57
|
+
self,
|
|
58
|
+
query: AnyQuery,
|
|
59
|
+
variables: VariableValues | None = None,
|
|
60
|
+
params: QueryParams = None,
|
|
61
|
+
source_config: SourceConfig | None = None,
|
|
62
|
+
) -> QueryResult:
|
|
63
|
+
"""Execute a MetricFlow query.
|
|
64
|
+
|
|
65
|
+
Uses type guard for type-safe field access.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
query: AnyQuery object (MetricFlowQuery expected)
|
|
69
|
+
variables: Variable values for filter resolution
|
|
70
|
+
params: Not used for MetricFlow queries (handled differently)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
QueryResult with data or error
|
|
74
|
+
"""
|
|
75
|
+
# Type guard ensures query.metrics is List[str]
|
|
76
|
+
if not is_metricflow_query(query):
|
|
77
|
+
return QueryResult(
|
|
78
|
+
data=[],
|
|
79
|
+
error=f"Expected MetricFlow query, got {query.query_type}",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Resolve filters with variables (for future use)
|
|
83
|
+
filters = query.filters or {}
|
|
84
|
+
_ = resolve_query_filters(filters, variables)
|
|
85
|
+
|
|
86
|
+
# In a real implementation, this would:
|
|
87
|
+
# 1. Construct MetricFlow query object
|
|
88
|
+
# 2. Apply filters
|
|
89
|
+
# 3. Execute via MetricFlow API/CLI
|
|
90
|
+
# 4. Convert results to list of dicts
|
|
91
|
+
return QueryResult(
|
|
92
|
+
data=[],
|
|
93
|
+
error="MetricFlow execution not yet implemented - requires MetricFlow setup",
|
|
94
|
+
)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Schema resolver adapter — queries the LayeredSchemaResolver in-process.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE
|
|
4
|
+
Purpose: Dispatch face queries to the LayeredSchemaResolver without a subprocess.
|
|
5
|
+
|
|
6
|
+
Routing by field population:
|
|
7
|
+
- source only → list_schemas(source)
|
|
8
|
+
- source + schema → list_tables(source, schema)
|
|
9
|
+
- source + schema + table → profile_table → column rows
|
|
10
|
+
- source + schema + table + column → profile_column → single column row
|
|
11
|
+
|
|
12
|
+
Field-prerequisite and Jinja-rejection invariants are enforced at compile time
|
|
13
|
+
by SchemaResolverQuery's Pydantic validators — the adapter trusts them.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING, Any
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from dataface.core.compile.models.source import SourceConfig
|
|
22
|
+
|
|
23
|
+
from dataface.core.compile.models.face.compiled import VariableValues
|
|
24
|
+
from dataface.core.compile.models.query.compiled import (
|
|
25
|
+
AnyQuery,
|
|
26
|
+
is_schema_resolver_query,
|
|
27
|
+
)
|
|
28
|
+
from dataface.core.execute.adapters.base import BaseAdapter, QueryParams, QueryResult
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from dataface.core.execute.adapters import AdapterRegistry
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SchemaResolverAdapter(BaseAdapter):
|
|
35
|
+
"""Adapter for in-process schema resolver queries.
|
|
36
|
+
|
|
37
|
+
Supported query types: schema_resolver
|
|
38
|
+
|
|
39
|
+
Dispatches to list_schemas / list_tables / profile_table / profile_column
|
|
40
|
+
on the LayeredSchemaResolver based on which fields are populated.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, adapter_registry: AdapterRegistry) -> None:
|
|
44
|
+
self._registry = adapter_registry
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def supported_types(self) -> set[str]:
|
|
48
|
+
return {"schema_resolver"}
|
|
49
|
+
|
|
50
|
+
def _execute(
|
|
51
|
+
self,
|
|
52
|
+
query: AnyQuery,
|
|
53
|
+
variables: VariableValues | None = None,
|
|
54
|
+
params: QueryParams = None,
|
|
55
|
+
source_config: SourceConfig | None = None,
|
|
56
|
+
) -> QueryResult:
|
|
57
|
+
if not is_schema_resolver_query(query):
|
|
58
|
+
return QueryResult(
|
|
59
|
+
data=[],
|
|
60
|
+
error=f"Expected schema_resolver query, got {query.query_type}",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
from dataface.core.inspect.cache_factory import build_resolver # noqa: PLC0415
|
|
64
|
+
|
|
65
|
+
resolver = build_resolver(self._registry)
|
|
66
|
+
|
|
67
|
+
source = query.source
|
|
68
|
+
schema = query.schema_name
|
|
69
|
+
table = query.table
|
|
70
|
+
column = query.column
|
|
71
|
+
|
|
72
|
+
if schema is None:
|
|
73
|
+
envelope = resolver.list_schemas(source)
|
|
74
|
+
schemas: dict[str, Any] = (
|
|
75
|
+
envelope.get("sources", {}).get(source, {}).get("schemas", {})
|
|
76
|
+
)
|
|
77
|
+
rows = [{**m, "name": n} for n, m in schemas.items()]
|
|
78
|
+
return QueryResult(data=query.apply_limit(rows))
|
|
79
|
+
|
|
80
|
+
if table is None:
|
|
81
|
+
envelope = resolver.list_tables(source, schema)
|
|
82
|
+
source_schemas: dict[str, Any] = (
|
|
83
|
+
envelope.get("sources", {}).get(source, {}).get("schemas", {})
|
|
84
|
+
)
|
|
85
|
+
if schema not in source_schemas:
|
|
86
|
+
return QueryResult(
|
|
87
|
+
data=[],
|
|
88
|
+
error=f"Schema {schema!r} not found in source {source!r}",
|
|
89
|
+
)
|
|
90
|
+
tables: dict[str, Any] = source_schemas[schema].get("tables", {})
|
|
91
|
+
rows = [{**m, "name": n} for n, m in tables.items()]
|
|
92
|
+
return QueryResult(data=query.apply_limit(rows))
|
|
93
|
+
|
|
94
|
+
if column is None:
|
|
95
|
+
envelope = resolver.profile_table(source, schema, table)
|
|
96
|
+
all_tables: dict[str, Any] = (
|
|
97
|
+
envelope.get("sources", {})
|
|
98
|
+
.get(source, {})
|
|
99
|
+
.get("schemas", {})
|
|
100
|
+
.get(schema, {})
|
|
101
|
+
.get("tables", {})
|
|
102
|
+
)
|
|
103
|
+
if table not in all_tables:
|
|
104
|
+
return QueryResult(
|
|
105
|
+
data=[],
|
|
106
|
+
error=f"Table {schema}.{table} not found in source {source!r}",
|
|
107
|
+
)
|
|
108
|
+
columns: dict[str, Any] = all_tables[table].get("columns") or {}
|
|
109
|
+
rows = [{**m, "name": n} for n, m in columns.items()]
|
|
110
|
+
return QueryResult(data=query.apply_limit(rows))
|
|
111
|
+
|
|
112
|
+
# column specified → profile_column
|
|
113
|
+
envelope = resolver.profile_column(source, schema, table, column)
|
|
114
|
+
col_map: dict[str, Any] = (
|
|
115
|
+
envelope.get("sources", {})
|
|
116
|
+
.get(source, {})
|
|
117
|
+
.get("schemas", {})
|
|
118
|
+
.get(schema, {})
|
|
119
|
+
.get("tables", {})
|
|
120
|
+
.get(table, {})
|
|
121
|
+
.get("columns")
|
|
122
|
+
or {}
|
|
123
|
+
)
|
|
124
|
+
if column not in col_map:
|
|
125
|
+
# The resolver drops the schemas key for both "table missing" and
|
|
126
|
+
# "column missing" — probe profile_table to emit the right message.
|
|
127
|
+
table_env = resolver.profile_table(source, schema, table)
|
|
128
|
+
table_found = table in (
|
|
129
|
+
table_env.get("sources", {})
|
|
130
|
+
.get(source, {})
|
|
131
|
+
.get("schemas", {})
|
|
132
|
+
.get(schema, {})
|
|
133
|
+
.get("tables", {})
|
|
134
|
+
)
|
|
135
|
+
if not table_found:
|
|
136
|
+
return QueryResult(
|
|
137
|
+
data=[],
|
|
138
|
+
error=f"Table {schema}.{table} not found in source {source!r}",
|
|
139
|
+
)
|
|
140
|
+
return QueryResult(
|
|
141
|
+
data=[],
|
|
142
|
+
error=f"Column {column!r} not found in {schema}.{table}",
|
|
143
|
+
)
|
|
144
|
+
return QueryResult(data=[{**col_map[column], "name": column}])
|