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,518 @@
|
|
|
1
|
+
"""Source detection utilities for database and dbt profiles.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE
|
|
4
|
+
Purpose: Centralized utilities for detecting database types and reading dbt profiles.
|
|
5
|
+
|
|
6
|
+
Entry Points:
|
|
7
|
+
- detect_dbt_database_type(profiles_path, profile_name, target_name) -> dict
|
|
8
|
+
- detect_dbt_connections(project_path) -> list[dict]
|
|
9
|
+
- detect_database_type_from_registry(registry) -> dict
|
|
10
|
+
- detect_dbt_connection_string(cwd) -> tuple[str, str] | None
|
|
11
|
+
- DB_INFO_MAP: Database type information dictionary
|
|
12
|
+
|
|
13
|
+
This module is the single canonical home for all database/source detection logic.
|
|
14
|
+
Surfaces (playground, cloud, CLI) import from here rather than implementing their own.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
from urllib.parse import quote_plus
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from dataface.core.execute.adapters.adapter_registry import AdapterRegistry
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Single source of truth for database-specific SQL syntax and introspection
|
|
30
|
+
DB_INFO_MAP: dict[str, dict[str, str]] = {
|
|
31
|
+
"duckdb": {
|
|
32
|
+
"type": "duckdb",
|
|
33
|
+
"engine": "DuckDB",
|
|
34
|
+
"dialect": "DuckDB SQL",
|
|
35
|
+
"introspection": (
|
|
36
|
+
"List tables: `SHOW TABLES;` | "
|
|
37
|
+
"Describe table: `DESCRIBE table_name;` or `PRAGMA table_info('table_name');`"
|
|
38
|
+
),
|
|
39
|
+
"notes": (
|
|
40
|
+
"DuckDB does NOT support `SHOW COLUMNS FROM` or `PRAGMA show_columns()`. "
|
|
41
|
+
"Use DESCRIBE or PRAGMA table_info instead."
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
"postgresql": {
|
|
45
|
+
"type": "postgresql",
|
|
46
|
+
"engine": "PostgreSQL",
|
|
47
|
+
"dialect": "PostgreSQL SQL",
|
|
48
|
+
"introspection": (
|
|
49
|
+
"List tables: `SELECT table_name FROM information_schema.tables "
|
|
50
|
+
"WHERE table_schema = 'public';` | "
|
|
51
|
+
"Describe table: `SELECT column_name, data_type FROM information_schema.columns "
|
|
52
|
+
"WHERE table_name = 'table_name';`"
|
|
53
|
+
),
|
|
54
|
+
"notes": "PostgreSQL supports full information_schema queries.",
|
|
55
|
+
},
|
|
56
|
+
"snowflake": {
|
|
57
|
+
"type": "snowflake",
|
|
58
|
+
"engine": "Snowflake",
|
|
59
|
+
"dialect": "Snowflake SQL",
|
|
60
|
+
"introspection": (
|
|
61
|
+
"List tables: `SHOW TABLES;` | "
|
|
62
|
+
"Describe table: `DESCRIBE TABLE table_name;`"
|
|
63
|
+
),
|
|
64
|
+
"notes": "Snowflake has its own SHOW/DESCRIBE commands and information_schema.",
|
|
65
|
+
},
|
|
66
|
+
"bigquery": {
|
|
67
|
+
"type": "bigquery",
|
|
68
|
+
"engine": "BigQuery",
|
|
69
|
+
"dialect": "BigQuery SQL",
|
|
70
|
+
"introspection": (
|
|
71
|
+
"List tables: `SELECT table_name FROM project.dataset.INFORMATION_SCHEMA.TABLES;` | "
|
|
72
|
+
"Describe table: `SELECT column_name, data_type FROM "
|
|
73
|
+
"project.dataset.INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'table_name';`"
|
|
74
|
+
),
|
|
75
|
+
"notes": "BigQuery uses backtick-quoted identifiers for projects/datasets.",
|
|
76
|
+
},
|
|
77
|
+
"redshift": {
|
|
78
|
+
"type": "redshift",
|
|
79
|
+
"engine": "Amazon Redshift",
|
|
80
|
+
"dialect": "Redshift SQL",
|
|
81
|
+
"introspection": (
|
|
82
|
+
"List tables: `SELECT tablename FROM pg_tables WHERE schemaname = 'public';` | "
|
|
83
|
+
"Describe table: `SELECT column_name, data_type FROM information_schema.columns "
|
|
84
|
+
"WHERE table_name = 'table_name';`"
|
|
85
|
+
),
|
|
86
|
+
"notes": "Redshift supports PostgreSQL-style information_schema.",
|
|
87
|
+
},
|
|
88
|
+
"mysql": {
|
|
89
|
+
"type": "mysql",
|
|
90
|
+
"engine": "MySQL",
|
|
91
|
+
"dialect": "MySQL SQL",
|
|
92
|
+
"introspection": (
|
|
93
|
+
"List tables: `SHOW TABLES;` | "
|
|
94
|
+
"Describe table: `DESCRIBE table_name;` or `SHOW COLUMNS FROM table_name;`"
|
|
95
|
+
),
|
|
96
|
+
"notes": "MySQL supports SHOW commands and information_schema.",
|
|
97
|
+
},
|
|
98
|
+
"databricks": {
|
|
99
|
+
"type": "databricks",
|
|
100
|
+
"engine": "Databricks",
|
|
101
|
+
"dialect": "Databricks SQL (Spark SQL)",
|
|
102
|
+
"introspection": (
|
|
103
|
+
"List tables: `SHOW TABLES;` | "
|
|
104
|
+
"Describe table: `DESCRIBE TABLE table_name;`"
|
|
105
|
+
),
|
|
106
|
+
"notes": "Databricks supports Spark SQL syntax with SHOW/DESCRIBE commands.",
|
|
107
|
+
},
|
|
108
|
+
"athena": {
|
|
109
|
+
"type": "athena",
|
|
110
|
+
"engine": "AWS Athena",
|
|
111
|
+
"dialect": "Athena SQL (Presto/Trino)",
|
|
112
|
+
"introspection": (
|
|
113
|
+
"List tables: `SHOW TABLES IN database_name;` | "
|
|
114
|
+
"Describe table: `DESCRIBE table_name;`"
|
|
115
|
+
),
|
|
116
|
+
"notes": "Athena uses Presto/Trino syntax.",
|
|
117
|
+
},
|
|
118
|
+
"sqlserver": {
|
|
119
|
+
"type": "sqlserver",
|
|
120
|
+
"engine": "Microsoft SQL Server",
|
|
121
|
+
"dialect": "T-SQL",
|
|
122
|
+
"introspection": (
|
|
123
|
+
"List tables: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES;` | "
|
|
124
|
+
"Describe table: `SELECT COLUMN_NAME, DATA_TYPE FROM "
|
|
125
|
+
"INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'table_name';`"
|
|
126
|
+
),
|
|
127
|
+
"notes": "SQL Server supports information_schema and sp_help procedures.",
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_database_info(db_type: str) -> dict[str, str] | None:
|
|
133
|
+
"""Get database info for a given type.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
db_type: Database type string (e.g., "duckdb", "postgres", "snowflake")
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Database info dict with type, engine, dialect, introspection, notes
|
|
140
|
+
Returns None if db_type is not recognized
|
|
141
|
+
"""
|
|
142
|
+
normalized_type = db_type.lower().strip()
|
|
143
|
+
# Normalize common aliases
|
|
144
|
+
if normalized_type == "postgres":
|
|
145
|
+
normalized_type = "postgresql"
|
|
146
|
+
return DB_INFO_MAP.get(normalized_type)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def detect_dbt_database_type(
|
|
150
|
+
profiles_path: Path,
|
|
151
|
+
profile_name: str,
|
|
152
|
+
target_name: str | None = None,
|
|
153
|
+
*,
|
|
154
|
+
fallback_type: str | None = None,
|
|
155
|
+
) -> dict[str, Any] | None:
|
|
156
|
+
"""Detect database type from dbt profiles.yml file.
|
|
157
|
+
|
|
158
|
+
Reads the profiles.yml configuration and extracts database type information
|
|
159
|
+
for the specified profile and target.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
profiles_path: Path to profiles.yml file
|
|
163
|
+
profile_name: Profile name to read (e.g., "my_project")
|
|
164
|
+
target_name: Target name to read (e.g., "dev", "prod"). If None, uses
|
|
165
|
+
the default target or first available.
|
|
166
|
+
fallback_type: If set and the profile is not found but the file exists,
|
|
167
|
+
return info for this database type instead of None.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Dict with database type info including:
|
|
171
|
+
- type: Database type (e.g., "duckdb", "postgresql")
|
|
172
|
+
- engine: Human-readable engine name
|
|
173
|
+
- dialect: SQL dialect name
|
|
174
|
+
- introspection: Introspection query examples
|
|
175
|
+
- notes: Important notes about SQL syntax
|
|
176
|
+
- description: Auto-generated description
|
|
177
|
+
- profile_name: The profile name used
|
|
178
|
+
- target_name: The target name used
|
|
179
|
+
Returns None if profile not found or file doesn't exist
|
|
180
|
+
(unless fallback_type is set and the file exists)
|
|
181
|
+
|
|
182
|
+
Example:
|
|
183
|
+
>>> info = detect_dbt_database_type(
|
|
184
|
+
... Path("profiles.yml"), "my_project", "dev"
|
|
185
|
+
... )
|
|
186
|
+
>>> info["engine"]
|
|
187
|
+
'DuckDB'
|
|
188
|
+
"""
|
|
189
|
+
if not profiles_path.exists():
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
import yaml
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
with open(profiles_path) as f:
|
|
196
|
+
profiles = yaml.safe_load(f)
|
|
197
|
+
|
|
198
|
+
if not profiles or profile_name not in profiles:
|
|
199
|
+
return _fallback_info(fallback_type) if fallback_type else None
|
|
200
|
+
|
|
201
|
+
profile = profiles[profile_name]
|
|
202
|
+
if not isinstance(profile, dict) or "outputs" not in profile:
|
|
203
|
+
return _fallback_info(fallback_type) if fallback_type else None
|
|
204
|
+
|
|
205
|
+
outputs = profile["outputs"]
|
|
206
|
+
if not outputs:
|
|
207
|
+
return _fallback_info(fallback_type) if fallback_type else None
|
|
208
|
+
|
|
209
|
+
# Use specified target, default target, or first available
|
|
210
|
+
actual_target = target_name
|
|
211
|
+
if actual_target is None or actual_target not in outputs:
|
|
212
|
+
# Try default target from profile
|
|
213
|
+
actual_target = profile.get("target")
|
|
214
|
+
if actual_target is None or actual_target not in outputs:
|
|
215
|
+
# Fall back to first available target
|
|
216
|
+
actual_target = next(iter(outputs.keys()))
|
|
217
|
+
|
|
218
|
+
target_config = outputs[actual_target]
|
|
219
|
+
db_type = target_config.get("type", "").lower()
|
|
220
|
+
|
|
221
|
+
db_info = get_database_info(db_type)
|
|
222
|
+
if db_info:
|
|
223
|
+
result = db_info.copy()
|
|
224
|
+
result["description"] = f"SQL queries execute via {db_info['engine']}"
|
|
225
|
+
result["profile_name"] = profile_name
|
|
226
|
+
result["target_name"] = actual_target
|
|
227
|
+
return result
|
|
228
|
+
|
|
229
|
+
# Unknown type - return basic info
|
|
230
|
+
return {
|
|
231
|
+
"type": db_type or "unknown",
|
|
232
|
+
"engine": db_type.title() if db_type else "Unknown",
|
|
233
|
+
"dialect": "SQL",
|
|
234
|
+
"introspection": "",
|
|
235
|
+
"notes": f"Unknown database type: {db_type}",
|
|
236
|
+
"description": f"SQL queries execute via unknown database type: {db_type}",
|
|
237
|
+
"profile_name": profile_name,
|
|
238
|
+
"target_name": actual_target,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
except (OSError, yaml.YAMLError, AttributeError, KeyError, TypeError) as e:
|
|
242
|
+
logger.debug(f"Failed to read or parse profiles.yml: {e}")
|
|
243
|
+
return _fallback_info(fallback_type) if fallback_type else None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _fallback_info(fallback_type: str) -> dict[str, Any]:
|
|
247
|
+
"""Build a fallback database info dict for a given type."""
|
|
248
|
+
db_info = get_database_info(fallback_type)
|
|
249
|
+
if db_info:
|
|
250
|
+
result = db_info.copy()
|
|
251
|
+
result["description"] = (
|
|
252
|
+
f"SQL queries execute via {db_info['engine']} (fallback)"
|
|
253
|
+
)
|
|
254
|
+
return result
|
|
255
|
+
return {
|
|
256
|
+
"type": fallback_type,
|
|
257
|
+
"engine": fallback_type.title(),
|
|
258
|
+
"dialect": "SQL",
|
|
259
|
+
"introspection": "",
|
|
260
|
+
"notes": f"Fallback database type: {fallback_type}",
|
|
261
|
+
"description": f"SQL queries execute via {fallback_type} (fallback)",
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def detect_dbt_connections(project_path: Path) -> list[dict[str, Any]]:
|
|
266
|
+
"""Detect connections from dbt profiles.yml.
|
|
267
|
+
|
|
268
|
+
Searches for profiles.yml in the project directory or ~/.dbt and extracts
|
|
269
|
+
all connection configurations for all profiles and targets.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
project_path: Path to the project directory
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
List of connection configuration dicts, each containing:
|
|
276
|
+
- name: Connection name (format: "profile_name (target_name)")
|
|
277
|
+
- type: Database type
|
|
278
|
+
- host: Database host
|
|
279
|
+
- port: Database port
|
|
280
|
+
- database: Database name
|
|
281
|
+
- schema: Schema name
|
|
282
|
+
- username: Username
|
|
283
|
+
- account: Snowflake account (if applicable)
|
|
284
|
+
- project: BigQuery project (if applicable)
|
|
285
|
+
|
|
286
|
+
Example:
|
|
287
|
+
>>> connections = detect_dbt_connections(Path("/my/project"))
|
|
288
|
+
>>> connections[0]
|
|
289
|
+
{'name': 'my_project (dev)', 'type': 'duckdb', ...}
|
|
290
|
+
"""
|
|
291
|
+
import yaml
|
|
292
|
+
|
|
293
|
+
# Look for profiles.yml in project or ~/.dbt
|
|
294
|
+
profiles_path = project_path / "profiles.yml"
|
|
295
|
+
if not profiles_path.exists():
|
|
296
|
+
profiles_path = Path.home() / ".dbt" / "profiles.yml"
|
|
297
|
+
|
|
298
|
+
if not profiles_path.exists():
|
|
299
|
+
return []
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
profiles = yaml.safe_load(profiles_path.read_text())
|
|
303
|
+
except (OSError, yaml.YAMLError):
|
|
304
|
+
return [] # Can't read or invalid YAML - no connections to detect
|
|
305
|
+
|
|
306
|
+
if not profiles:
|
|
307
|
+
return []
|
|
308
|
+
|
|
309
|
+
connections: list[dict[str, Any]] = []
|
|
310
|
+
for profile_name, profile in profiles.items():
|
|
311
|
+
if not isinstance(profile, dict):
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
for target_name, target in profile.get("outputs", {}).items():
|
|
315
|
+
if not isinstance(target, dict):
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
connections.append(
|
|
319
|
+
{
|
|
320
|
+
"name": f"{profile_name} ({target_name})",
|
|
321
|
+
"type": target.get("type"),
|
|
322
|
+
"host": target.get("host", target.get("server", "")),
|
|
323
|
+
"port": target.get("port"),
|
|
324
|
+
"database": target.get("database", target.get("dbname", "")),
|
|
325
|
+
"schema": target.get("schema", ""),
|
|
326
|
+
"username": target.get("user", target.get("username", "")),
|
|
327
|
+
"account": target.get("account", ""), # Snowflake
|
|
328
|
+
"project": target.get("project", ""), # BigQuery
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return connections
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def detect_database_type_from_registry(
|
|
336
|
+
registry: AdapterRegistry,
|
|
337
|
+
*,
|
|
338
|
+
default_profile_name: str = "default",
|
|
339
|
+
default_target_name: str = "dev",
|
|
340
|
+
) -> dict[str, str]:
|
|
341
|
+
"""Detect database type information from an adapter registry.
|
|
342
|
+
|
|
343
|
+
Inspects registered SQL adapters (DbtAdapter, SqlAdapter) to determine
|
|
344
|
+
the database type by reading their dbt profiles.yml configuration.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
registry: AdapterRegistry instance to inspect.
|
|
348
|
+
default_profile_name: Profile name to use when adapter doesn't specify one.
|
|
349
|
+
default_target_name: Target name to use when adapter doesn't specify one.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Dict with database type information. Always returns a dict — never None.
|
|
353
|
+
Keys: type, engine, dialect, notes (and optionally profile_name, target_name).
|
|
354
|
+
"""
|
|
355
|
+
from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
|
|
356
|
+
from dataface.core.execute.adapters.sql_adapter import SqlAdapter
|
|
357
|
+
|
|
358
|
+
sql_adapters = registry.get_adapters_for_type("sql")
|
|
359
|
+
|
|
360
|
+
for adapter in sql_adapters:
|
|
361
|
+
if isinstance(adapter, DbtAdapter):
|
|
362
|
+
dbt_path = Path(adapter.dbt_project_path)
|
|
363
|
+
profiles_path = dbt_path / "profiles.yml"
|
|
364
|
+
profile_name = adapter.profile_name or default_profile_name
|
|
365
|
+
target_name = adapter.target_name or default_target_name
|
|
366
|
+
|
|
367
|
+
db_info = detect_dbt_database_type(
|
|
368
|
+
profiles_path, profile_name, target_name, fallback_type="duckdb"
|
|
369
|
+
)
|
|
370
|
+
if db_info:
|
|
371
|
+
db_info["profile_name"] = profile_name
|
|
372
|
+
db_info["target_name"] = target_name
|
|
373
|
+
return db_info
|
|
374
|
+
|
|
375
|
+
# DbtAdapter but can't determine type — default to DuckDB
|
|
376
|
+
info = DB_INFO_MAP["duckdb"].copy()
|
|
377
|
+
info["description"] = "SQL queries execute via DuckDB (default)"
|
|
378
|
+
return info
|
|
379
|
+
|
|
380
|
+
elif isinstance(adapter, SqlAdapter):
|
|
381
|
+
if adapter.dbt_project_path:
|
|
382
|
+
dbt_path = Path(adapter.dbt_project_path)
|
|
383
|
+
profiles_path = dbt_path / "profiles.yml"
|
|
384
|
+
|
|
385
|
+
db_info = detect_dbt_database_type(
|
|
386
|
+
profiles_path,
|
|
387
|
+
default_profile_name,
|
|
388
|
+
default_target_name,
|
|
389
|
+
fallback_type="duckdb",
|
|
390
|
+
)
|
|
391
|
+
if db_info:
|
|
392
|
+
return db_info
|
|
393
|
+
|
|
394
|
+
# SqlAdapter without dbt project
|
|
395
|
+
return {
|
|
396
|
+
"type": "unknown",
|
|
397
|
+
"engine": "Unknown",
|
|
398
|
+
"description": "SQL adapter configured but database type unknown",
|
|
399
|
+
"dialect": "SQL",
|
|
400
|
+
"notes": "Database type could not be determined. Use generic SQL syntax.",
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
"type": "none",
|
|
405
|
+
"engine": "None",
|
|
406
|
+
"description": "No SQL adapter available",
|
|
407
|
+
"dialect": "N/A",
|
|
408
|
+
"notes": "",
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def detect_dbt_connection_string(cwd: Path) -> tuple[str, str] | None:
|
|
413
|
+
"""Auto-detect database connection from dbt project files.
|
|
414
|
+
|
|
415
|
+
Walks up from *cwd* to find ``dbt_project.yml``, reads the matching
|
|
416
|
+
``profiles.yml``, and builds a connection string + dialect pair.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
cwd: Directory to start searching from.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
``(connection_string, dialect)`` or ``None`` if no dbt project found.
|
|
423
|
+
"""
|
|
424
|
+
import yaml
|
|
425
|
+
|
|
426
|
+
# Walk up to find dbt_project.yml
|
|
427
|
+
search_path = cwd
|
|
428
|
+
dbt_project_path = None
|
|
429
|
+
for _ in range(10):
|
|
430
|
+
candidate = search_path / "dbt_project.yml"
|
|
431
|
+
if candidate.exists():
|
|
432
|
+
dbt_project_path = candidate
|
|
433
|
+
break
|
|
434
|
+
if search_path.parent == search_path:
|
|
435
|
+
break
|
|
436
|
+
search_path = search_path.parent
|
|
437
|
+
|
|
438
|
+
if not dbt_project_path:
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
project_dir = dbt_project_path.parent
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
with open(dbt_project_path) as f:
|
|
445
|
+
dbt_project = yaml.safe_load(f)
|
|
446
|
+
profile_name = dbt_project.get("profile", "default")
|
|
447
|
+
except (FileNotFoundError, yaml.YAMLError, TypeError):
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
# Find profiles.yml
|
|
451
|
+
profiles_path = project_dir / "profiles.yml"
|
|
452
|
+
if not profiles_path.exists():
|
|
453
|
+
profiles_path = Path.home() / ".dbt" / "profiles.yml"
|
|
454
|
+
if not profiles_path.exists():
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
try:
|
|
458
|
+
with open(profiles_path) as f:
|
|
459
|
+
profiles = yaml.safe_load(f)
|
|
460
|
+
|
|
461
|
+
profile = profiles.get(profile_name, {})
|
|
462
|
+
target_name = profile.get("target", "dev")
|
|
463
|
+
outputs = profile.get("outputs", {})
|
|
464
|
+
target = outputs.get(target_name, {})
|
|
465
|
+
db_type = target.get("type", "duckdb")
|
|
466
|
+
|
|
467
|
+
if db_type == "duckdb":
|
|
468
|
+
path = target.get("path", ":memory:")
|
|
469
|
+
if not path.startswith(":") and not Path(path).is_absolute():
|
|
470
|
+
path = str(project_dir / path)
|
|
471
|
+
return (path, "duckdb")
|
|
472
|
+
elif db_type == "postgres":
|
|
473
|
+
host = target.get("host", "localhost")
|
|
474
|
+
port = target.get("port", 5432)
|
|
475
|
+
user = quote_plus(target.get("user", "postgres"))
|
|
476
|
+
password = quote_plus(target.get("password", ""))
|
|
477
|
+
dbname = target.get("dbname", "postgres")
|
|
478
|
+
return (
|
|
479
|
+
f"postgresql://{user}:{password}@{host}:{port}/{dbname}",
|
|
480
|
+
"postgres",
|
|
481
|
+
)
|
|
482
|
+
elif db_type == "bigquery":
|
|
483
|
+
project = target.get("project", target.get("database", ""))
|
|
484
|
+
if project:
|
|
485
|
+
return (f"bigquery://{project}", "bigquery")
|
|
486
|
+
elif db_type == "snowflake":
|
|
487
|
+
account = target.get("account", "")
|
|
488
|
+
database = target.get("database", "")
|
|
489
|
+
db_schema = target.get("schema", "")
|
|
490
|
+
warehouse = target.get("warehouse", "")
|
|
491
|
+
if account:
|
|
492
|
+
conn = f"snowflake://{account}/{database}/{db_schema}"
|
|
493
|
+
if warehouse:
|
|
494
|
+
conn += f"?warehouse={warehouse}"
|
|
495
|
+
return (conn, "snowflake")
|
|
496
|
+
elif db_type == "databricks":
|
|
497
|
+
host = target.get("host", "")
|
|
498
|
+
http_path = target.get("http_path", "")
|
|
499
|
+
token = target.get("token", "")
|
|
500
|
+
if host:
|
|
501
|
+
conn = f"databricks://{host}/{http_path}"
|
|
502
|
+
if token:
|
|
503
|
+
conn += f"?token={token}"
|
|
504
|
+
return (conn, "databricks")
|
|
505
|
+
except (FileNotFoundError, yaml.YAMLError, TypeError, AttributeError):
|
|
506
|
+
return None
|
|
507
|
+
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
__all__ = [
|
|
512
|
+
"DB_INFO_MAP",
|
|
513
|
+
"detect_database_type_from_registry",
|
|
514
|
+
"detect_dbt_connection_string",
|
|
515
|
+
"detect_dbt_connections",
|
|
516
|
+
"detect_dbt_database_type",
|
|
517
|
+
"get_database_info",
|
|
518
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Compile-time authoring checks for SQL query strings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sqlglot
|
|
6
|
+
import sqlglot.errors
|
|
7
|
+
|
|
8
|
+
from dataface.core.execute.sql_guard import sqlglot_dialect
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _sql_parses(sql: str, dialect: str | None) -> bool:
|
|
12
|
+
"""Return True if sqlglot.parse succeeds without error, False otherwise."""
|
|
13
|
+
try:
|
|
14
|
+
sqlglot.parse(sql, read=sqlglot_dialect(dialect))
|
|
15
|
+
except (sqlglot.errors.ParseError, sqlglot.errors.TokenError):
|
|
16
|
+
return False
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def has_literal_escaped_newlines(sql: str, *, dialect: str | None) -> bool:
|
|
21
|
+
"""Return True if sql contains literal \\n that is the cause of a parse failure.
|
|
22
|
+
|
|
23
|
+
Three-step parse-replace-parse heuristic:
|
|
24
|
+
1. If '\\n' not in sql → False (fast path, no parse needed)
|
|
25
|
+
2. If sqlglot.parse(sql) succeeds → False (\\n is inside a SQL string literal
|
|
26
|
+
or otherwise accepted — step 1 guards the cost of this parse)
|
|
27
|
+
3. If sqlglot.parse(sql.replace('\\n', newline)) succeeds → True
|
|
28
|
+
(the replacement fixed the failure: author used single-quoted YAML instead of
|
|
29
|
+
a block scalar, so \\n was preserved literally instead of becoming a real newline)
|
|
30
|
+
4. Else → False (genuine syntax/Jinja issue unrelated to \\n; step 4 is the
|
|
31
|
+
absence of step 3)
|
|
32
|
+
|
|
33
|
+
Note: SQL with Jinja expressions ({{ ref('orders') }}) bypasses detection — both
|
|
34
|
+
step 2 and step 3 fail to parse Jinja skeletons, so the function returns False
|
|
35
|
+
and the existing UnparseableSqlError path handles it at runtime. This is intentional.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
sql: Raw SQL string after YAML parsing.
|
|
39
|
+
dialect: Dataface dialect name (e.g. "duckdb", "sqlserver"), or None for the default.
|
|
40
|
+
Normalised to the sqlglot equivalent via sqlglot_dialect().
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if the literal-\\n authoring error is detected, False otherwise.
|
|
44
|
+
"""
|
|
45
|
+
# Step 1: fast exit — no literal \n present, skip all parsing.
|
|
46
|
+
if "\\n" not in sql:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
# Step 2: parses fine as-is — \n is inside a SQL string literal or similar.
|
|
50
|
+
if _sql_parses(sql, dialect):
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
# Step 3: parses after replacing literal \n with a real newline — authoring mistake.
|
|
54
|
+
# Step 4 is the implicit False when step 3 also fails (genuine syntax/Jinja issue).
|
|
55
|
+
# Author wrote sql: 'SELECT\n...' in YAML single-quotes instead of sql: | block scalar.
|
|
56
|
+
return _sql_parses(sql.replace("\\n", "\n"), dialect)
|