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,92 @@
|
|
|
1
|
+
"""StructuredError wire shape and build_structured helper."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from dataface.core.errors.registry import ErrorCode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NextCommand(BaseModel):
|
|
13
|
+
label: str
|
|
14
|
+
command: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StructuredError(BaseModel):
|
|
18
|
+
code: str
|
|
19
|
+
message: str
|
|
20
|
+
domain: str
|
|
21
|
+
fields: dict[str, Any] = {}
|
|
22
|
+
file: str | None = None
|
|
23
|
+
line: int | None = None
|
|
24
|
+
column: int | None = None
|
|
25
|
+
field_path: str | None = None
|
|
26
|
+
hint: str | None = None
|
|
27
|
+
doc_url: str
|
|
28
|
+
docs_topic: str | None = None
|
|
29
|
+
next_commands: list[NextCommand] = []
|
|
30
|
+
# Nested when __cause__ is itself a DatafaceError (preserves code/domain/
|
|
31
|
+
# doc_url through the chain); a bare string when __cause__ is a foreign
|
|
32
|
+
# exception type. None when there is no cause.
|
|
33
|
+
cause: StructuredError | str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_structured(exc: Any, *, file: str | None = None) -> StructuredError:
|
|
37
|
+
"""Build a StructuredError from any DatafaceError subclass.
|
|
38
|
+
|
|
39
|
+
The exc must have a populated .code attribute (an ErrorCode instance).
|
|
40
|
+
Raises RuntimeError if .code is None — that path should never happen once
|
|
41
|
+
every subclass __init__ stamps the legacy fallback code; raise (not
|
|
42
|
+
assert) so the guard survives `python -O`.
|
|
43
|
+
"""
|
|
44
|
+
ec = getattr(exc, "code", None)
|
|
45
|
+
if not isinstance(ec, ErrorCode):
|
|
46
|
+
raise RuntimeError(
|
|
47
|
+
f"build_structured called on {type(exc).__name__!r} whose .code is "
|
|
48
|
+
f"{type(ec).__name__!r}, not an ErrorCode. Every DatafaceError "
|
|
49
|
+
"subclass must stamp a code at construction."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
fields: dict[str, Any] = getattr(exc, "fields", {})
|
|
53
|
+
|
|
54
|
+
hint: str | None = None
|
|
55
|
+
if ec.hint_generator is not None:
|
|
56
|
+
hint = ec.hint_generator(**fields)
|
|
57
|
+
|
|
58
|
+
next_commands: list[NextCommand] = []
|
|
59
|
+
if file:
|
|
60
|
+
next_commands.append(
|
|
61
|
+
NextCommand(label="Validate", command=f"dft validate {file}")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
cause: StructuredError | str | None = None
|
|
65
|
+
raw_cause = exc.__cause__
|
|
66
|
+
if raw_cause is not None:
|
|
67
|
+
# Recurse for our own DatafaceError chain so structured consumers
|
|
68
|
+
# keep code/domain/doc_url; flatten foreign exceptions to a string.
|
|
69
|
+
# Use isinstance(ErrorCode) — duck-typing on `.code is not None`
|
|
70
|
+
# would falsely recurse into foreign exceptions like
|
|
71
|
+
# urllib.error.HTTPError that carry a non-ErrorCode `.code` attr.
|
|
72
|
+
raw_code = getattr(raw_cause, "code", None)
|
|
73
|
+
if isinstance(raw_code, ErrorCode):
|
|
74
|
+
cause = build_structured(raw_cause)
|
|
75
|
+
else:
|
|
76
|
+
cause = str(raw_cause)
|
|
77
|
+
|
|
78
|
+
return StructuredError(
|
|
79
|
+
code=ec.code,
|
|
80
|
+
message=str(exc),
|
|
81
|
+
domain=ec.domain,
|
|
82
|
+
fields=fields,
|
|
83
|
+
file=file,
|
|
84
|
+
line=getattr(exc, "line", None),
|
|
85
|
+
column=getattr(exc, "column", None),
|
|
86
|
+
field_path=getattr(exc, "field_path", None),
|
|
87
|
+
hint=hint,
|
|
88
|
+
doc_url=ec.doc_url,
|
|
89
|
+
docs_topic=ec.docs_topic,
|
|
90
|
+
next_commands=next_commands,
|
|
91
|
+
cause=cause,
|
|
92
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Query execution module.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE (Service)
|
|
4
|
+
Purpose: Execute queries for compiled datafaces.
|
|
5
|
+
|
|
6
|
+
This is a SERVICE module, not a sequential stage. It's called lazily
|
|
7
|
+
by the render module when charts need data.
|
|
8
|
+
|
|
9
|
+
Entry Points:
|
|
10
|
+
- Executor: Main executor class
|
|
11
|
+
- Executor.execute_query(query_name, variables) -> List[Dict]
|
|
12
|
+
- Executor.execute_chart(chart, variables) -> List[Dict]
|
|
13
|
+
|
|
14
|
+
Inputs:
|
|
15
|
+
- Face: Face with compiled queries
|
|
16
|
+
- Query name and variables
|
|
17
|
+
|
|
18
|
+
Outputs:
|
|
19
|
+
- List[Dict[str, Any]]: Query results (list of row dictionaries)
|
|
20
|
+
|
|
21
|
+
Dependencies:
|
|
22
|
+
- dataface.compile (for Face, AnyQuery types)
|
|
23
|
+
|
|
24
|
+
This module can import from compile/ (needs types) but should NOT
|
|
25
|
+
import from render/ (render imports us, not vice versa).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from dataface.core.execute.batch import (
|
|
29
|
+
TEMP_TABLE_PREFIX,
|
|
30
|
+
BatchStatement,
|
|
31
|
+
CrossSourceReferenceError,
|
|
32
|
+
DependencyGraph,
|
|
33
|
+
TempTableNotSupportedError,
|
|
34
|
+
build_dependency_graph,
|
|
35
|
+
collect_face_queries,
|
|
36
|
+
create_batch_execution_plan,
|
|
37
|
+
extract_query_refs,
|
|
38
|
+
generate_batch_sql,
|
|
39
|
+
group_by_source,
|
|
40
|
+
topological_sort,
|
|
41
|
+
validate_cross_source_references,
|
|
42
|
+
)
|
|
43
|
+
from dataface.core.execute.cache_backend import CachedQueryFailure, QueryResultCache
|
|
44
|
+
from dataface.core.execute.cache_keys import SECRET_FIELDS, source_identity
|
|
45
|
+
from dataface.core.execute.duckdb_cache import (
|
|
46
|
+
DuckDBCache,
|
|
47
|
+
compute_query_hash,
|
|
48
|
+
compute_source_hash,
|
|
49
|
+
compute_variables_hash,
|
|
50
|
+
)
|
|
51
|
+
from dataface.core.execute.errors import (
|
|
52
|
+
AdapterError,
|
|
53
|
+
ExecutionError,
|
|
54
|
+
QueryError,
|
|
55
|
+
QueryTimeoutError,
|
|
56
|
+
)
|
|
57
|
+
from dataface.core.execute.executor import Executor
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
# Executor
|
|
61
|
+
"Executor",
|
|
62
|
+
# Cache Protocol
|
|
63
|
+
"QueryResultCache",
|
|
64
|
+
# DuckDB Cache
|
|
65
|
+
"CachedQueryFailure",
|
|
66
|
+
"DuckDBCache",
|
|
67
|
+
"SECRET_FIELDS",
|
|
68
|
+
"compute_query_hash",
|
|
69
|
+
"compute_source_hash",
|
|
70
|
+
"compute_variables_hash",
|
|
71
|
+
"source_identity",
|
|
72
|
+
# Errors
|
|
73
|
+
"ExecutionError",
|
|
74
|
+
"QueryError",
|
|
75
|
+
"QueryTimeoutError",
|
|
76
|
+
"AdapterError",
|
|
77
|
+
"CrossSourceReferenceError",
|
|
78
|
+
"TempTableNotSupportedError",
|
|
79
|
+
# Batch execution
|
|
80
|
+
"BatchStatement",
|
|
81
|
+
"DependencyGraph",
|
|
82
|
+
"TEMP_TABLE_PREFIX",
|
|
83
|
+
"build_dependency_graph",
|
|
84
|
+
"collect_face_queries",
|
|
85
|
+
"create_batch_execution_plan",
|
|
86
|
+
"extract_query_refs",
|
|
87
|
+
"generate_batch_sql",
|
|
88
|
+
"group_by_source",
|
|
89
|
+
"topological_sort",
|
|
90
|
+
"validate_cross_source_references",
|
|
91
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Data source adapters.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE
|
|
4
|
+
Purpose: Provide adapters for different data sources.
|
|
5
|
+
|
|
6
|
+
Available Adapters:
|
|
7
|
+
- SqlAdapter: Raw SQL queries
|
|
8
|
+
- CsvAdapter: CSV file queries
|
|
9
|
+
- HttpAdapter: REST API queries
|
|
10
|
+
- DbtAdapter: dbt-integrated SQL queries
|
|
11
|
+
- MetricFlowAdapter: dbt Semantic Layer queries
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from dataface.core.execute.adapters.adapter_registry import (
|
|
15
|
+
LOCAL_AUTHORING_REGISTRY_KWARGS,
|
|
16
|
+
AdapterRegistry,
|
|
17
|
+
build_adapter_registry,
|
|
18
|
+
)
|
|
19
|
+
from dataface.core.execute.adapters.base import (
|
|
20
|
+
BaseAdapter,
|
|
21
|
+
QueryResult,
|
|
22
|
+
handle_adapter_error,
|
|
23
|
+
)
|
|
24
|
+
from dataface.core.execute.adapters.csv_adapter import CsvAdapter
|
|
25
|
+
from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
|
|
26
|
+
from dataface.core.execute.adapters.http_adapter import HttpAdapter
|
|
27
|
+
from dataface.core.execute.adapters.metricflow_adapter import MetricFlowAdapter
|
|
28
|
+
from dataface.core.execute.adapters.schema_resolver_adapter import SchemaResolverAdapter
|
|
29
|
+
from dataface.core.execute.adapters.sql_adapter import SqlAdapter
|
|
30
|
+
from dataface.core.execute.adapters.values_adapter import ValuesAdapter
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Base
|
|
34
|
+
"BaseAdapter",
|
|
35
|
+
"QueryResult",
|
|
36
|
+
"handle_adapter_error",
|
|
37
|
+
# Registry
|
|
38
|
+
"AdapterRegistry",
|
|
39
|
+
"LOCAL_AUTHORING_REGISTRY_KWARGS",
|
|
40
|
+
"build_adapter_registry",
|
|
41
|
+
# Adapters
|
|
42
|
+
"SqlAdapter",
|
|
43
|
+
"CsvAdapter",
|
|
44
|
+
"HttpAdapter",
|
|
45
|
+
"DbtAdapter",
|
|
46
|
+
"MetricFlowAdapter",
|
|
47
|
+
"ValuesAdapter",
|
|
48
|
+
"SchemaResolverAdapter",
|
|
49
|
+
]
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""Registry for query adapters.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE
|
|
4
|
+
Purpose: Manage and select appropriate adapters for query execution.
|
|
5
|
+
|
|
6
|
+
The AdapterRegistry maintains a list of available adapters and uses
|
|
7
|
+
type-based routing via the unified query interface.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from dataface.core.compile.errors import DatafaceError
|
|
16
|
+
from dataface.core.compile.models.face.compiled import VariableValues
|
|
17
|
+
from dataface.core.compile.models.query.compiled import AnyQuery
|
|
18
|
+
from dataface.core.errors.codes_execute import (
|
|
19
|
+
DF_EXECUTE_NO_DEFAULT_SOURCE,
|
|
20
|
+
DF_EXECUTE_SOURCE_NOT_FOUND,
|
|
21
|
+
DF_EXECUTE_SOURCE_NOT_FOUND_EMPTY,
|
|
22
|
+
)
|
|
23
|
+
from dataface.core.execute.adapters.base import BaseAdapter, QueryParams, QueryResult
|
|
24
|
+
from dataface.core.execute.source_registry import SourceRegistry
|
|
25
|
+
from dataface.core.execute.source_resolver import DbtContext, DefaultSourceResolver
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from dataface.core.compile.models.face.compiled import Face
|
|
29
|
+
from dataface.core.execute.source_resolver import SourceResolver
|
|
30
|
+
|
|
31
|
+
# Approved local-dev surfaces that need non-exclusive DuckDB locks AND SQL
|
|
32
|
+
# read_csv/read_json_auto (see m3-duckdb-concurrency-and-file-sources initiative).
|
|
33
|
+
# Grep this symbol before adding allow_external_access_in_readonly=True elsewhere.
|
|
34
|
+
LOCAL_AUTHORING_REGISTRY_KWARGS: dict[str, Any] = {
|
|
35
|
+
"read_only": True,
|
|
36
|
+
"allow_external_access_in_readonly": True,
|
|
37
|
+
"duckdb_config": {"enable_external_access": True},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_adapter_registry(
|
|
42
|
+
project_root: Path,
|
|
43
|
+
*,
|
|
44
|
+
read_only: bool = True,
|
|
45
|
+
allowed_types: set[str] | None = None,
|
|
46
|
+
dbt_project_path: Path | None = None,
|
|
47
|
+
connection_string: str | None = None,
|
|
48
|
+
profile_type: str = "duckdb",
|
|
49
|
+
target: str | None = None,
|
|
50
|
+
duckdb_config: dict[str, Any] | None = None,
|
|
51
|
+
resolver: SourceResolver | None = None,
|
|
52
|
+
allow_external_access_in_readonly: bool = False,
|
|
53
|
+
) -> AdapterRegistry:
|
|
54
|
+
"""Build an AdapterRegistry with standard adapters.
|
|
55
|
+
|
|
56
|
+
This is the single canonical way to create an adapter registry.
|
|
57
|
+
All entry points should use this instead of hand-wiring adapters.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
project_root: Root directory for resolving relative file paths.
|
|
61
|
+
read_only: Whether SQL adapters should be read-only (default True).
|
|
62
|
+
allowed_types: If set, only register adapters whose supported_types
|
|
63
|
+
intersect with this set. None means register all.
|
|
64
|
+
dbt_project_path: Explicit dbt project path. If None, auto-discovers
|
|
65
|
+
by looking for dbt_project.yml in/above project_root.
|
|
66
|
+
connection_string: Override SQL connection string.
|
|
67
|
+
profile_type: SQL dialect (default "duckdb").
|
|
68
|
+
target: dbt target name override.
|
|
69
|
+
duckdb_config: Optional DuckDB config dict passed to the default
|
|
70
|
+
connection. When read_only=True (the default) and
|
|
71
|
+
allow_external_access_in_readonly=False, enable_external_access is
|
|
72
|
+
hard-forced to False regardless of what this dict contains. To combine
|
|
73
|
+
read_only=True with external file access (read_csv, httpfs, etc.), set
|
|
74
|
+
both allow_external_access_in_readonly=True AND
|
|
75
|
+
duckdb_config={"enable_external_access": True}. The default path
|
|
76
|
+
(read_only=False) also allows external access without the flag.
|
|
77
|
+
resolver: Source resolver instance. Defaults to DefaultSourceResolver when
|
|
78
|
+
None. Pass an AllowlistedSourceResolver for deployed-mode enforcement.
|
|
79
|
+
allow_external_access_in_readonly: Security opt-in; passed through to
|
|
80
|
+
SqlAdapter. See SqlAdapter docstring for full semantics. Default False
|
|
81
|
+
preserves existing behavior. Approved callsites passing True are listed
|
|
82
|
+
on LOCAL_AUTHORING_REGISTRY_KWARGS in this module (playground, MCP,
|
|
83
|
+
chat, serve).
|
|
84
|
+
"""
|
|
85
|
+
from dataface.core.execute.adapters.csv_adapter import CsvAdapter
|
|
86
|
+
from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
|
|
87
|
+
from dataface.core.execute.adapters.http_adapter import HttpAdapter
|
|
88
|
+
from dataface.core.execute.adapters.metricflow_adapter import MetricFlowAdapter
|
|
89
|
+
from dataface.core.execute.adapters.sql_adapter import SqlAdapter
|
|
90
|
+
from dataface.core.execute.adapters.values_adapter import ValuesAdapter
|
|
91
|
+
|
|
92
|
+
registry = AdapterRegistry(project_root=project_root, resolver=resolver)
|
|
93
|
+
|
|
94
|
+
def _should_register(types: set[str]) -> bool:
|
|
95
|
+
return allowed_types is None or bool(types & allowed_types)
|
|
96
|
+
|
|
97
|
+
# Auto-discover dbt project if not explicitly provided
|
|
98
|
+
resolved_dbt_path = dbt_project_path
|
|
99
|
+
if resolved_dbt_path is None:
|
|
100
|
+
candidate = project_root.resolve()
|
|
101
|
+
while True:
|
|
102
|
+
if (candidate / "dbt_project.yml").exists():
|
|
103
|
+
resolved_dbt_path = candidate
|
|
104
|
+
break
|
|
105
|
+
parent = candidate.parent
|
|
106
|
+
if parent == candidate:
|
|
107
|
+
break
|
|
108
|
+
candidate = parent
|
|
109
|
+
|
|
110
|
+
# DbtAdapter first (handles SQL queries with dbt features, takes priority)
|
|
111
|
+
if resolved_dbt_path is not None and _should_register({"sql"}):
|
|
112
|
+
registry.register(
|
|
113
|
+
DbtAdapter(
|
|
114
|
+
dbt_project_path=resolved_dbt_path,
|
|
115
|
+
target_name=target,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# SqlAdapter (fallback for plain SQL)
|
|
120
|
+
if _should_register({"sql"}):
|
|
121
|
+
registry.register(
|
|
122
|
+
SqlAdapter(
|
|
123
|
+
dbt_project_path=str(resolved_dbt_path) if resolved_dbt_path else None,
|
|
124
|
+
connection_string=connection_string,
|
|
125
|
+
project_root=project_root,
|
|
126
|
+
profile_type=profile_type,
|
|
127
|
+
read_only=read_only,
|
|
128
|
+
duckdb_config=duckdb_config,
|
|
129
|
+
allow_external_access_in_readonly=allow_external_access_in_readonly,
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Lazy adapters — zero cost until actually called
|
|
134
|
+
if _should_register({"metricflow"}):
|
|
135
|
+
registry.register(MetricFlowAdapter())
|
|
136
|
+
if _should_register({"http"}):
|
|
137
|
+
registry.register(HttpAdapter())
|
|
138
|
+
|
|
139
|
+
# File-based adapters
|
|
140
|
+
if _should_register({"csv"}):
|
|
141
|
+
registry.register(CsvAdapter(project_root=project_root))
|
|
142
|
+
if _should_register({"values"}):
|
|
143
|
+
registry.register(ValuesAdapter())
|
|
144
|
+
|
|
145
|
+
if _should_register({"schema_resolver"}):
|
|
146
|
+
from dataface.core.execute.adapters.schema_resolver_adapter import (
|
|
147
|
+
SchemaResolverAdapter,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
registry.register(SchemaResolverAdapter(registry))
|
|
151
|
+
|
|
152
|
+
return registry
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class AdapterRegistry:
|
|
156
|
+
"""Registry for managing and selecting query adapters.
|
|
157
|
+
|
|
158
|
+
The registry uses type-based routing from the unified query interface.
|
|
159
|
+
Adapters declare their supported types via the `supported_types` property,
|
|
160
|
+
and the registry routes queries to the appropriate adapter.
|
|
161
|
+
|
|
162
|
+
Priority order:
|
|
163
|
+
1. First registered adapter that supports the query type wins
|
|
164
|
+
2. dbt adapter is registered first for SQL (with dbt features)
|
|
165
|
+
3. Fallback SQL adapter for plain SQL without dbt
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
>>> from dataface.core.execute.adapters import build_adapter_registry
|
|
169
|
+
>>> registry = build_adapter_registry(Path("."))
|
|
170
|
+
>>> result = registry.execute(SqlQuery(sql="SELECT 1"))
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
project_root: Path | None = None,
|
|
176
|
+
resolver: SourceResolver | None = None,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Initialize adapter registry.
|
|
179
|
+
|
|
180
|
+
Use ``build_adapter_registry()`` to get a fully configured registry.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
project_root: Root directory for resolving relative file paths.
|
|
184
|
+
resolver: Source resolver instance. Defaults to DefaultSourceResolver.
|
|
185
|
+
"""
|
|
186
|
+
self._adapters: list[BaseAdapter] = []
|
|
187
|
+
self._type_index: dict[str, list[BaseAdapter]] = {}
|
|
188
|
+
self._project_root = project_root
|
|
189
|
+
self._sources = SourceRegistry(project_root=project_root)
|
|
190
|
+
self._resolver: SourceResolver = (
|
|
191
|
+
resolver if resolver is not None else DefaultSourceResolver()
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def project_root(self) -> Path | None:
|
|
196
|
+
"""Return the registry's configured project root, if any."""
|
|
197
|
+
return self._project_root
|
|
198
|
+
|
|
199
|
+
def register(self, adapter: BaseAdapter) -> None:
|
|
200
|
+
"""Register an adapter.
|
|
201
|
+
|
|
202
|
+
Adapters are indexed by their supported types for fast lookup.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
adapter: Adapter instance to register
|
|
206
|
+
"""
|
|
207
|
+
self._adapters.append(adapter)
|
|
208
|
+
|
|
209
|
+
# Update type index
|
|
210
|
+
for query_type in adapter.supported_types:
|
|
211
|
+
if query_type not in self._type_index:
|
|
212
|
+
self._type_index[query_type] = []
|
|
213
|
+
self._type_index[query_type].append(adapter)
|
|
214
|
+
|
|
215
|
+
def close(self) -> None:
|
|
216
|
+
"""Close adapters that hold external resources (e.g. DuckDB file handles)."""
|
|
217
|
+
for adapter in self._adapters:
|
|
218
|
+
close = getattr(adapter, "close", None)
|
|
219
|
+
if close is not None:
|
|
220
|
+
close()
|
|
221
|
+
|
|
222
|
+
def get_adapters_for_type(self, query_type: str) -> list[BaseAdapter]:
|
|
223
|
+
"""Get all adapters that support a given query type.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
query_type: Query type string (e.g., "sql", "csv")
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List of adapters supporting this type (in registration order)
|
|
230
|
+
"""
|
|
231
|
+
return self._type_index.get(query_type, [])
|
|
232
|
+
|
|
233
|
+
def get_adapter(self, query: AnyQuery) -> BaseAdapter | None:
|
|
234
|
+
"""Get an adapter that can execute the given query.
|
|
235
|
+
|
|
236
|
+
Uses type-based routing for fast lookup, then checks additional
|
|
237
|
+
eligibility via can_execute().
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
query: AnyQuery object
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Adapter instance or None if no adapter can execute the query
|
|
244
|
+
"""
|
|
245
|
+
# Fast path: lookup by type
|
|
246
|
+
candidates = self.get_adapters_for_type(query.query_type)
|
|
247
|
+
|
|
248
|
+
for adapter in candidates:
|
|
249
|
+
if adapter.can_execute(query):
|
|
250
|
+
return adapter
|
|
251
|
+
|
|
252
|
+
# Fallback: check all adapters (for adapters with custom can_execute)
|
|
253
|
+
for adapter in self._adapters:
|
|
254
|
+
if adapter not in candidates and adapter.can_execute(query):
|
|
255
|
+
return adapter
|
|
256
|
+
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
def register_source(self, name: str, config: dict[str, Any]) -> None:
|
|
260
|
+
"""Register a synthetic source. Idempotent: first registration wins."""
|
|
261
|
+
self._sources.register(name, config)
|
|
262
|
+
|
|
263
|
+
def _derive_dbt_context(self) -> DbtContext | None:
|
|
264
|
+
from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
|
|
265
|
+
|
|
266
|
+
for adapter in self._adapters:
|
|
267
|
+
if isinstance(adapter, DbtAdapter):
|
|
268
|
+
return DbtContext()
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
def execute(
|
|
272
|
+
self,
|
|
273
|
+
query: AnyQuery,
|
|
274
|
+
variables: VariableValues | None = None,
|
|
275
|
+
params: QueryParams = None,
|
|
276
|
+
*,
|
|
277
|
+
face: Face | None = None,
|
|
278
|
+
dbt_context: DbtContext | None = None,
|
|
279
|
+
) -> QueryResult:
|
|
280
|
+
"""Execute a query using the appropriate adapter.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
query: AnyQuery object
|
|
284
|
+
variables: Variable values for query resolution
|
|
285
|
+
params: Optional pre-computed parameter values for parameterized execution.
|
|
286
|
+
When provided, the adapter should use these params directly instead
|
|
287
|
+
of re-processing variables. This is used by batch execution where
|
|
288
|
+
parameterization happens before adapter execution.
|
|
289
|
+
face: Compiled face; used to extract face-level sources for resolution.
|
|
290
|
+
dbt_context: Explicit dbt context override. When None, derived from
|
|
291
|
+
registered adapters.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
QueryResult with data or error
|
|
295
|
+
"""
|
|
296
|
+
adapter = self.get_adapter(query)
|
|
297
|
+
if not adapter:
|
|
298
|
+
return QueryResult(
|
|
299
|
+
data=[],
|
|
300
|
+
error=f"No adapter found for query type: {query.query_type}",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
authored = getattr(query, "source", None)
|
|
304
|
+
face_sources: dict[str, dict[str, Any]] = (
|
|
305
|
+
face.sources if face is not None else {}
|
|
306
|
+
)
|
|
307
|
+
project_sources = self._sources.project_sources_config()
|
|
308
|
+
if dbt_context is None:
|
|
309
|
+
dbt_context = self._derive_dbt_context()
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
source_config = self._resolver.resolve(
|
|
313
|
+
authored=authored,
|
|
314
|
+
face_sources=face_sources,
|
|
315
|
+
project_sources=project_sources,
|
|
316
|
+
dbt_context=dbt_context,
|
|
317
|
+
)
|
|
318
|
+
except DatafaceError as exc:
|
|
319
|
+
return QueryResult(data=[], error=str(exc))
|
|
320
|
+
|
|
321
|
+
return adapter.execute(query, variables, params, source_config=source_config)
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def adapters(self) -> list[BaseAdapter]:
|
|
325
|
+
"""Get all registered adapters."""
|
|
326
|
+
return list(self._adapters)
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def supported_types(self) -> set[str]:
|
|
330
|
+
"""Get all query types supported by registered adapters.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Set of query type strings
|
|
334
|
+
"""
|
|
335
|
+
return set(self._type_index.keys())
|
|
336
|
+
|
|
337
|
+
def list_sql_sources(self) -> list[dict[str, Any]]:
|
|
338
|
+
"""Return configured SQL sources from the project source registry."""
|
|
339
|
+
sources: dict[str, dict[str, Any]] = {}
|
|
340
|
+
for name, config in self._sources.all().items():
|
|
341
|
+
source_type = str(config.get("type", "unknown"))
|
|
342
|
+
source_info: dict[str, Any] = {"name": name, "type": source_type}
|
|
343
|
+
db_path = config.get("path")
|
|
344
|
+
if db_path:
|
|
345
|
+
source_info["path"] = str(db_path)
|
|
346
|
+
if source_type == "duckdb" and str(db_path) == ":memory:":
|
|
347
|
+
source_info["in_memory"] = True
|
|
348
|
+
sources[name] = source_info
|
|
349
|
+
return [sources[name] for name in sorted(sources)]
|
|
350
|
+
|
|
351
|
+
def resolve_source_config(self, source: str | None = None) -> dict[str, Any]:
|
|
352
|
+
"""Resolve a full source_config dict for use with InspectConnection.
|
|
353
|
+
|
|
354
|
+
Returns the warehouse-specific dict (type + credentials).
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
source: Named source profile. When None, falls back to the
|
|
358
|
+
registry's default SqlAdapter — but only if it's a DuckDB
|
|
359
|
+
adapter (the connection_string is :memory: or a .duckdb path).
|
|
360
|
+
For non-DuckDB SqlAdapters, raises DatafaceError because
|
|
361
|
+
reconstructing config from (connection_string, profile_type)
|
|
362
|
+
drops credentials, host, port, keyfile_json, etc.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
source_config dict with at least a 'type' key.
|
|
366
|
+
|
|
367
|
+
Raises:
|
|
368
|
+
DatafaceError: DF-EXECUTE-NO-DEFAULT-SOURCE if source is None and
|
|
369
|
+
the default adapter is non-DuckDB; DF-EXECUTE-SOURCE-NOT-FOUND
|
|
370
|
+
if the named source is missing but others are configured;
|
|
371
|
+
DF-EXECUTE-SOURCE-NOT-FOUND-EMPTY if no sources are configured.
|
|
372
|
+
"""
|
|
373
|
+
from dataface.core.execute.adapters.sql_adapter import SqlAdapter
|
|
374
|
+
|
|
375
|
+
if source is None:
|
|
376
|
+
# No-source fallback only works for DuckDB SqlAdapters — they have
|
|
377
|
+
# no credentials, host, port, keyfile_json to lose.
|
|
378
|
+
for adapter in self._adapters:
|
|
379
|
+
if isinstance(adapter, SqlAdapter) and adapter.profile_type == "duckdb":
|
|
380
|
+
# Inline DuckDB source config: matches what source_config_from_url
|
|
381
|
+
# returns for the duckdb branch ({"type": "duckdb", "path": ...}).
|
|
382
|
+
# Handles all DuckDB URL shapes: file paths, :memory:, md: (MotherDuck).
|
|
383
|
+
# The loop already filters to profile_type=="duckdb" so no dialect
|
|
384
|
+
# ambiguity; the inline form avoids importing from the private package.
|
|
385
|
+
return {
|
|
386
|
+
"type": "duckdb",
|
|
387
|
+
"path": adapter.connection_string,
|
|
388
|
+
}
|
|
389
|
+
raise DatafaceError.from_code(DF_EXECUTE_NO_DEFAULT_SOURCE)
|
|
390
|
+
|
|
391
|
+
config = self._sources.get(source)
|
|
392
|
+
if config and config.get("type"):
|
|
393
|
+
return config
|
|
394
|
+
|
|
395
|
+
available = ", ".join(item["name"] for item in self.list_sql_sources())
|
|
396
|
+
if available:
|
|
397
|
+
raise DatafaceError.from_code(
|
|
398
|
+
DF_EXECUTE_SOURCE_NOT_FOUND, source=source, available=available
|
|
399
|
+
)
|
|
400
|
+
raise DatafaceError.from_code(DF_EXECUTE_SOURCE_NOT_FOUND_EMPTY, source=source)
|