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,94 @@
|
|
|
1
|
+
"""Inspect command — OSS template-management verbs only.
|
|
2
|
+
|
|
3
|
+
The profiler verbs (inspect_command, inspect_all_command, audit_command) live
|
|
4
|
+
in the private ``dataface-super-schema`` package and are registered via the
|
|
5
|
+
``dataface.cli_plugins`` entry-point when that package is installed.
|
|
6
|
+
|
|
7
|
+
This module contains only the template-management commands that ship with the
|
|
8
|
+
OSS ``dataface`` wheel: eject, templates, and validate-templates.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from dataface.agent_api import inspect as _api
|
|
16
|
+
from dataface.cli._console import dft_console
|
|
17
|
+
|
|
18
|
+
console = dft_console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def eject_command(
|
|
22
|
+
templates: list[str],
|
|
23
|
+
all_templates: bool = False,
|
|
24
|
+
force: bool = False,
|
|
25
|
+
output_dir: Path | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
target_dir = output_dir or (Path.cwd() / "faces" / "inspect")
|
|
28
|
+
to_eject: list[str] | None = None if all_templates else (templates or None)
|
|
29
|
+
if not all_templates and not templates:
|
|
30
|
+
console.print("[bold red]Error:[/bold red] Specify template names or --all")
|
|
31
|
+
console.print(f"Available: {', '.join(t.name for t in _api.list_templates())}")
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
try:
|
|
34
|
+
ejected = _api.eject_templates(target_dir, templates=to_eject, force=force)
|
|
35
|
+
except ValueError as e:
|
|
36
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
37
|
+
raise typer.Exit(1) from None
|
|
38
|
+
skipped_count = len(to_eject or [t.name for t in _api.list_templates()]) - len(
|
|
39
|
+
ejected
|
|
40
|
+
)
|
|
41
|
+
console.print(f"[dim]Output directory: {target_dir}[/dim]\n")
|
|
42
|
+
for p in ejected:
|
|
43
|
+
console.print(f" [green]✓[/green] {p.name}")
|
|
44
|
+
console.print()
|
|
45
|
+
if ejected:
|
|
46
|
+
console.print(
|
|
47
|
+
f"[bold green]✓[/bold green] Ejected {len(ejected)} template(s) to {target_dir}"
|
|
48
|
+
)
|
|
49
|
+
if skipped_count > 0:
|
|
50
|
+
console.print(f"[dim]Skipped {skipped_count} existing file(s)[/dim]")
|
|
51
|
+
if ejected and not force:
|
|
52
|
+
console.print(
|
|
53
|
+
"\n[dim]To reset to built-in defaults: dft inspect eject --all --force[/dim]"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def validate_ejected_templates_command(output_dir: Path | None = None) -> None:
|
|
58
|
+
try:
|
|
59
|
+
result = _api.validate_ejected_templates(output_dir)
|
|
60
|
+
except FileNotFoundError as e:
|
|
61
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
62
|
+
raise typer.Exit(1) from None
|
|
63
|
+
for label, names, style in [
|
|
64
|
+
("Missing ejected templates", result.missing, "bold red"),
|
|
65
|
+
(
|
|
66
|
+
"Upstream changed since eject (manual rebase needed)",
|
|
67
|
+
result.upstream_changed,
|
|
68
|
+
"bold yellow",
|
|
69
|
+
),
|
|
70
|
+
(
|
|
71
|
+
"Customized templates still aligned to built-ins",
|
|
72
|
+
result.custom_safe,
|
|
73
|
+
"bold cyan",
|
|
74
|
+
),
|
|
75
|
+
("Unchanged templates", result.unchanged, "dim"),
|
|
76
|
+
]:
|
|
77
|
+
if names:
|
|
78
|
+
console.print(f"[{style}]{label}:[/{style}]")
|
|
79
|
+
for name in names:
|
|
80
|
+
console.print(f" - {name}")
|
|
81
|
+
if not result.success:
|
|
82
|
+
raise typer.Exit(1)
|
|
83
|
+
console.print("\n[bold green]✓[/bold green] Ejected templates are compatible.")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def templates_command() -> None:
|
|
87
|
+
console.print("\n[bold]Available Inspect Templates[/bold]\n")
|
|
88
|
+
for t in _api.list_templates():
|
|
89
|
+
console.print(f" • {t.name}")
|
|
90
|
+
console.print("\n[dim]To customize a template:[/dim]")
|
|
91
|
+
console.print(" dft inspect eject <template> # Copy to faces/inspect/")
|
|
92
|
+
console.print(" dft inspect eject --all # Copy all templates")
|
|
93
|
+
console.print(" dft inspect validate-templates # Check compatibility")
|
|
94
|
+
console.print()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""`dft init mcp` thin wrapper: parse args, call agent_api.mcp_install, format output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
from dataface.agent_api import mcp_install
|
|
13
|
+
from dataface.core.compile.meta import find_project_root
|
|
14
|
+
from dataface.core.project_roots import PROJECT_MARKERS, find_dataface_project
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _resolve_dft_executable() -> str:
|
|
18
|
+
"""Path to this `dft` install for embedding in MCP client configs.
|
|
19
|
+
|
|
20
|
+
GUI apps (Cursor, VS Code) often spawn MCP with a minimal ``PATH``. A
|
|
21
|
+
config entry ``"command": "dft"`` then fails with ``spawn dft ENOENT``.
|
|
22
|
+
When this code runs as the real ``dft`` console script, ``sys.argv[0]``
|
|
23
|
+
is an absolute (or resolvable) path to that binary — use it first, then
|
|
24
|
+
``shutil.which``, then fall back to the bare name.
|
|
25
|
+
"""
|
|
26
|
+
invoked = Path(sys.argv[0]).expanduser()
|
|
27
|
+
try:
|
|
28
|
+
resolved = invoked.resolve(strict=False)
|
|
29
|
+
except OSError:
|
|
30
|
+
resolved = invoked
|
|
31
|
+
name = resolved.name.casefold()
|
|
32
|
+
if name in ("dft", "dft.exe") and resolved.is_file():
|
|
33
|
+
return str(resolved)
|
|
34
|
+
return shutil.which("dft") or "dft"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_project_dir(explicit: Path | None) -> Path:
|
|
38
|
+
markers_str = ", ".join(PROJECT_MARKERS)
|
|
39
|
+
if explicit is not None:
|
|
40
|
+
nearest = find_dataface_project(explicit)
|
|
41
|
+
if nearest != explicit:
|
|
42
|
+
msg = (
|
|
43
|
+
f"Error: --project-dir {explicit} does not contain a Dataface "
|
|
44
|
+
f"or dbt project.\n"
|
|
45
|
+
f"Looked for: {markers_str}."
|
|
46
|
+
)
|
|
47
|
+
if nearest is not None:
|
|
48
|
+
msg += f"\nTip: did you mean {nearest}?"
|
|
49
|
+
typer.echo(msg, err=True)
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
return explicit
|
|
52
|
+
|
|
53
|
+
found = find_dataface_project(Path.cwd())
|
|
54
|
+
if found is None:
|
|
55
|
+
typer.echo(
|
|
56
|
+
f"Error: No Dataface or dbt project found at or above {Path.cwd()}.\n"
|
|
57
|
+
f"Looked for: {markers_str}.\n"
|
|
58
|
+
f"Re-run from inside your Dataface project, "
|
|
59
|
+
f"or pass --project-dir <path>.",
|
|
60
|
+
err=True,
|
|
61
|
+
)
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
return found
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run_init(
|
|
67
|
+
client: str | None,
|
|
68
|
+
all_clients: bool,
|
|
69
|
+
force: bool,
|
|
70
|
+
project_dir: Path | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Shared implementation for `dft init mcp`."""
|
|
73
|
+
config_root = find_project_root(Path.cwd())
|
|
74
|
+
project_dir_resolved = _resolve_project_dir(project_dir)
|
|
75
|
+
|
|
76
|
+
dft_path = _resolve_dft_executable()
|
|
77
|
+
server_command = [dft_path, "mcp", "serve"]
|
|
78
|
+
if project_dir_resolved != config_root:
|
|
79
|
+
server_command += ["--project-dir", str(project_dir_resolved)]
|
|
80
|
+
|
|
81
|
+
if client and client.lower() == "print":
|
|
82
|
+
entry = {"command": server_command[0], "args": server_command[1:]}
|
|
83
|
+
typer.echo(json.dumps({"mcpServers": {"dataface": entry}}, indent=2))
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
if client and all_clients:
|
|
87
|
+
typer.echo("Specify either a client or --all, not both.", err=True)
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
|
|
90
|
+
all_known = mcp_install.list_clients()
|
|
91
|
+
known_names = {c.name for c in all_known}
|
|
92
|
+
|
|
93
|
+
if all_clients:
|
|
94
|
+
targets = all_known
|
|
95
|
+
elif client:
|
|
96
|
+
target = client.lower()
|
|
97
|
+
if target not in known_names:
|
|
98
|
+
typer.echo(
|
|
99
|
+
f"Unknown client: {client}. "
|
|
100
|
+
f"Supported: {', '.join(sorted(known_names) + ['print'])}",
|
|
101
|
+
err=True,
|
|
102
|
+
)
|
|
103
|
+
raise typer.Exit(1)
|
|
104
|
+
targets = [c for c in all_known if c.name == target]
|
|
105
|
+
else:
|
|
106
|
+
targets = [
|
|
107
|
+
c
|
|
108
|
+
for c in all_known
|
|
109
|
+
if any((config_root / dp).exists() for dp in c.detect_paths)
|
|
110
|
+
]
|
|
111
|
+
if not targets:
|
|
112
|
+
typer.echo(
|
|
113
|
+
"No AI client markers detected (.cursor/, .codex/, .vscode/, "
|
|
114
|
+
".github/, CLAUDE.md, AGENTS.md, ~/.config/claude/)."
|
|
115
|
+
)
|
|
116
|
+
typer.echo("")
|
|
117
|
+
typer.echo("To configure manually, specify a client:")
|
|
118
|
+
typer.echo(" dft init mcp cursor # Cursor")
|
|
119
|
+
typer.echo(" dft init mcp vscode # VS Code")
|
|
120
|
+
typer.echo(" dft init mcp claude # Claude Desktop")
|
|
121
|
+
typer.echo(" dft init mcp claude-code # Claude Code")
|
|
122
|
+
typer.echo(" dft init mcp codex # OpenAI Codex CLI")
|
|
123
|
+
typer.echo(" dft init mcp copilot # GitHub Copilot Coding Agent")
|
|
124
|
+
typer.echo(" dft init mcp --all # Write every supported config")
|
|
125
|
+
typer.echo(" dft init mcp print # Print JSON config to stdout")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
configured = []
|
|
129
|
+
for c in targets:
|
|
130
|
+
result = mcp_install.install_for_client(
|
|
131
|
+
c, server_command=server_command, config_root=config_root, force=force
|
|
132
|
+
)
|
|
133
|
+
typer.echo(result.message)
|
|
134
|
+
if not result.already_configured:
|
|
135
|
+
configured.append(c.name)
|
|
136
|
+
|
|
137
|
+
if configured:
|
|
138
|
+
typer.echo("")
|
|
139
|
+
# Trailer pinned to dataface.ai.tool_schemas.ALL_TOOLS and
|
|
140
|
+
# dataface.ai.mcp.server._BASE_RESOURCES by
|
|
141
|
+
# test_configured_trailer_advertises_every_tool_and_resource_family.
|
|
142
|
+
# Update these strings when either registry changes.
|
|
143
|
+
typer.echo(
|
|
144
|
+
" MCP tools: describe_dashboard, describe_query, docs, execute_query, "
|
|
145
|
+
"get_skill, list_skills, query_face, render_dashboard, schema, "
|
|
146
|
+
"schema_search, search_dashboards, search_skills, validate_dashboard"
|
|
147
|
+
)
|
|
148
|
+
typer.echo(
|
|
149
|
+
" Resources: dataface://dashboards, dataface://dashboard/{path}, "
|
|
150
|
+
"dataface://docs/{all,reference,<topic>}, "
|
|
151
|
+
"dataface://guide/{dashboard-design,dashboard-build,dashboard-review,report-design}"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
typer.echo("")
|
|
155
|
+
if configured:
|
|
156
|
+
if "cursor" in configured:
|
|
157
|
+
typer.echo(
|
|
158
|
+
" Next: Open Cursor Settings → MCP and enable the 'dataface' server."
|
|
159
|
+
)
|
|
160
|
+
elif "claude" in configured:
|
|
161
|
+
typer.echo(" Restart Claude Desktop for changes to take effect.")
|
|
162
|
+
else:
|
|
163
|
+
clients_str = ", ".join(sorted(configured))
|
|
164
|
+
typer.echo(f" Restart {clients_str} for changes to take effect.")
|
|
165
|
+
typer.echo(
|
|
166
|
+
" Install workflow skills: dft init skills (separate from MCP wiring)."
|
|
167
|
+
)
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""Query command implementation — named face queries and raw SQL dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import difflib
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any, NoReturn
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich.markup import escape
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
|
|
17
|
+
from dataface.agent_api._paths import project_root_for
|
|
18
|
+
from dataface.agent_api.describe_query import DescribeQueryResult, describe_query
|
|
19
|
+
from dataface.agent_api.query import (
|
|
20
|
+
ExecuteQueryResult,
|
|
21
|
+
FaceQueryLookupResult,
|
|
22
|
+
QueryFaceResult,
|
|
23
|
+
execute_query,
|
|
24
|
+
lookup_face_query_sql,
|
|
25
|
+
query_face,
|
|
26
|
+
)
|
|
27
|
+
from dataface.agent_api.validate_query import ValidateQueryResult, validate_query
|
|
28
|
+
from dataface.cli._console import dft_console
|
|
29
|
+
from dataface.cli._json_output import print_json_result
|
|
30
|
+
from dataface.core.execute.adapters import build_adapter_registry
|
|
31
|
+
|
|
32
|
+
console = dft_console()
|
|
33
|
+
err_console = dft_console(stderr=True)
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Output helpers
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
_SEVERITY_ICON = {"error": "❌", "warning": "⚠️ ", "info": "ℹ️ "}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _rows_table(columns: list[str], data: list[dict[str, Any]]) -> Table:
|
|
43
|
+
t = Table(show_header=True, header_style="bold", box=None, padding=(0, 2, 0, 0))
|
|
44
|
+
for col in columns:
|
|
45
|
+
t.add_column(escape(col))
|
|
46
|
+
for row in data:
|
|
47
|
+
t.add_row(*(escape(str(row.get(col, ""))) for col in columns))
|
|
48
|
+
return t
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _print_rows(
|
|
52
|
+
columns: list[str], data: list[dict[str, Any]], row_count: int, truncated: bool
|
|
53
|
+
) -> None:
|
|
54
|
+
if columns:
|
|
55
|
+
console.print(_rows_table(columns, data))
|
|
56
|
+
err_console.print(f"\n{row_count} rows{' (truncated)' if truncated else ''}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _print_diagnostic(d: dict[str, Any], target: Console = err_console) -> None:
|
|
60
|
+
icon = _SEVERITY_ICON.get(d.get("severity", ""), "⚠️ ")
|
|
61
|
+
target.print(f"{icon} [{d['code']}] {d['message']}", markup=False)
|
|
62
|
+
if d.get("detail"):
|
|
63
|
+
target.print(f" {d['detail']}", markup=False)
|
|
64
|
+
if d.get("recommendation"):
|
|
65
|
+
target.print(f" → {d['recommendation']}", markup=False)
|
|
66
|
+
if d.get("confidence") is not None:
|
|
67
|
+
target.print(f" confidence: {d['confidence']:.0%}")
|
|
68
|
+
for ev in d.get("evidence", []):
|
|
69
|
+
target.print(f" evidence: {ev}", markup=False)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _print_query_face_rich(result: QueryFaceResult) -> None:
|
|
73
|
+
if not result.success:
|
|
74
|
+
for err in result.errors:
|
|
75
|
+
err_console.print(f"[red]Error:[/red] {escape(err)}")
|
|
76
|
+
if result.available_queries:
|
|
77
|
+
matches = difflib.get_close_matches(
|
|
78
|
+
result.name, result.available_queries, n=3
|
|
79
|
+
)
|
|
80
|
+
if matches:
|
|
81
|
+
err_console.print(f"Did you mean: {escape(', '.join(matches))}?")
|
|
82
|
+
elif _looks_like_sql(result.name):
|
|
83
|
+
err_console.print("Did you mean to drop --in for raw SQL?")
|
|
84
|
+
err_console.print(
|
|
85
|
+
f"Available queries: {escape(', '.join(result.available_queries))}"
|
|
86
|
+
)
|
|
87
|
+
raise typer.Exit(1)
|
|
88
|
+
_print_rows(result.columns, result.data, result.row_count, result.truncated)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _print_execute_query_rich(result: ExecuteQueryResult) -> None:
|
|
92
|
+
if not result.success:
|
|
93
|
+
err_console.print(f"[red]Error:[/red] {escape(result.error or '')}")
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
_print_rows(result.columns, result.data, result.row_count, result.truncated)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _print_validate_result(
|
|
99
|
+
result: ValidateQueryResult,
|
|
100
|
+
show_suppressed: bool = False,
|
|
101
|
+
json_output: bool = False,
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Print validate result and exit 1 on errors."""
|
|
104
|
+
if json_output:
|
|
105
|
+
payload: Any = (
|
|
106
|
+
{"diagnostics": result.diagnostics, "suppressed": result.suppressed or []}
|
|
107
|
+
if show_suppressed
|
|
108
|
+
else result.diagnostics
|
|
109
|
+
)
|
|
110
|
+
typer.echo(json.dumps(payload, indent=2))
|
|
111
|
+
elif not result.diagnostics and not (show_suppressed and result.suppressed):
|
|
112
|
+
console.print("No issues found.")
|
|
113
|
+
else:
|
|
114
|
+
for d in result.diagnostics:
|
|
115
|
+
_print_diagnostic(d, target=console)
|
|
116
|
+
if show_suppressed and result.suppressed:
|
|
117
|
+
console.print(f"\nSuppressed ({len(result.suppressed)}):")
|
|
118
|
+
for d in result.suppressed:
|
|
119
|
+
console.print(f" ~ [{d['code']}] {d['message']}", markup=False)
|
|
120
|
+
|
|
121
|
+
if result.has_errors:
|
|
122
|
+
raise typer.Exit(1)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _print_describe_result(
|
|
126
|
+
result: DescribeQueryResult, json_output: bool = False
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Print describe result and exit 1 on failure."""
|
|
129
|
+
if json_output:
|
|
130
|
+
print_json_result(result)
|
|
131
|
+
if not result.success:
|
|
132
|
+
raise typer.Exit(1)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
if not result.success:
|
|
136
|
+
err_console.print(f"[red]Error:[/red] {escape(result.error or '')}")
|
|
137
|
+
for d in result.diagnostics:
|
|
138
|
+
_print_diagnostic(d)
|
|
139
|
+
raise typer.Exit(1)
|
|
140
|
+
|
|
141
|
+
if result.columns:
|
|
142
|
+
t = Table(show_header=True, header_style="bold", box=None, padding=(0, 2, 0, 0))
|
|
143
|
+
t.add_column("name")
|
|
144
|
+
t.add_column("type")
|
|
145
|
+
for col in result.columns:
|
|
146
|
+
t.add_row(escape(col.name), escape(col.type))
|
|
147
|
+
console.print(t)
|
|
148
|
+
|
|
149
|
+
for d in result.diagnostics:
|
|
150
|
+
_print_diagnostic(d)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# SQL-source resolution helpers
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _resolve_sql_text(
|
|
159
|
+
target: str | None,
|
|
160
|
+
file: Path | None,
|
|
161
|
+
face: Path | None,
|
|
162
|
+
) -> tuple[str, bool, str | None]:
|
|
163
|
+
"""Resolve the SQL text and whether we are in named-query mode.
|
|
164
|
+
|
|
165
|
+
Returns (sql_text, mode_is_named, name_or_none).
|
|
166
|
+
Raises typer.Exit(1) on any resolution error.
|
|
167
|
+
"""
|
|
168
|
+
if face and file:
|
|
169
|
+
typer.echo(
|
|
170
|
+
"Error: use --in (named query) or --file (SQL file), not both.", err=True
|
|
171
|
+
)
|
|
172
|
+
raise typer.Exit(1)
|
|
173
|
+
|
|
174
|
+
if face:
|
|
175
|
+
# Named-query mode — TARGET is the query name, SQL resolved later.
|
|
176
|
+
if not target:
|
|
177
|
+
typer.echo(
|
|
178
|
+
"Error: provide a query name when using --in"
|
|
179
|
+
" (e.g. dft query revenue --in face.yml).",
|
|
180
|
+
err=True,
|
|
181
|
+
)
|
|
182
|
+
raise typer.Exit(1)
|
|
183
|
+
return "", True, target
|
|
184
|
+
|
|
185
|
+
if file:
|
|
186
|
+
if not file.exists():
|
|
187
|
+
typer.echo(f"Error: file not found: {file}", err=True)
|
|
188
|
+
raise typer.Exit(1)
|
|
189
|
+
return file.read_text(), False, None
|
|
190
|
+
|
|
191
|
+
if not target:
|
|
192
|
+
typer.echo(
|
|
193
|
+
"Error: provide SQL, --file, or NAME --in face.yml.",
|
|
194
|
+
err=True,
|
|
195
|
+
)
|
|
196
|
+
raise typer.Exit(1)
|
|
197
|
+
|
|
198
|
+
return target, False, None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
_SQL_SHAPE_CHARS = frozenset(" \t\n()")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _looks_like_sql(name: str) -> bool:
|
|
205
|
+
"""Return True if name resembles a SQL expression rather than a query identifier."""
|
|
206
|
+
upper = name.upper()
|
|
207
|
+
return (
|
|
208
|
+
upper.startswith("SELECT")
|
|
209
|
+
or upper.startswith("WITH")
|
|
210
|
+
or any(c in name for c in _SQL_SHAPE_CHARS)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _handle_lookup_failure(lr: FaceQueryLookupResult, query_name: str) -> NoReturn:
|
|
215
|
+
"""Emit errors from a failed FaceQueryLookupResult and raise typer.Exit(1)."""
|
|
216
|
+
for err in lr.errors:
|
|
217
|
+
typer.echo(f"Error: {err}", err=True)
|
|
218
|
+
if lr.available_queries:
|
|
219
|
+
matches = difflib.get_close_matches(query_name, lr.available_queries, n=3)
|
|
220
|
+
if matches:
|
|
221
|
+
typer.echo("Did you mean: " + ", ".join(matches) + "?", err=True)
|
|
222
|
+
elif _looks_like_sql(query_name):
|
|
223
|
+
typer.echo("Did you mean to drop --in for raw SQL?", err=True)
|
|
224
|
+
typer.echo("Available queries: " + ", ".join(lr.available_queries), err=True)
|
|
225
|
+
raise typer.Exit(1)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _resolve_source(source_flag: str | None, face_source: str | None) -> str:
|
|
229
|
+
"""Resolve the effective source, raising if neither is available."""
|
|
230
|
+
resolved = source_flag or face_source
|
|
231
|
+
if resolved is None:
|
|
232
|
+
typer.echo(
|
|
233
|
+
"Error: SQL execute requires --source. "
|
|
234
|
+
"Add --validate for static lint, or --source <name> to execute.",
|
|
235
|
+
err=True,
|
|
236
|
+
)
|
|
237
|
+
raise typer.Exit(1)
|
|
238
|
+
return resolved
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
# Public entry point
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def query_command(
|
|
247
|
+
target: str | None,
|
|
248
|
+
face: Path | None = None,
|
|
249
|
+
source: str | None = None,
|
|
250
|
+
validate: bool = False,
|
|
251
|
+
describe: bool = False,
|
|
252
|
+
file: Path | None = None,
|
|
253
|
+
dialect: str | None = None,
|
|
254
|
+
vars: dict[str, Any] | None = None,
|
|
255
|
+
limit: int = 20,
|
|
256
|
+
show_suppressed: bool = False,
|
|
257
|
+
json_output: bool = False,
|
|
258
|
+
project_dir: Path | None = None,
|
|
259
|
+
) -> None:
|
|
260
|
+
"""Run a named face query, raw SQL, or static SQL lint/describe — selected from the input args."""
|
|
261
|
+
project_root = project_root_for(project_dir)
|
|
262
|
+
|
|
263
|
+
# --- 1. Resolve input shape ---
|
|
264
|
+
sql_text, mode_is_named, query_name = _resolve_sql_text(target, file, face)
|
|
265
|
+
|
|
266
|
+
# --- 2. Apply mode flags ---
|
|
267
|
+
if not validate and not describe:
|
|
268
|
+
# Execute path — keep the named-query branch as a direct query_face call so
|
|
269
|
+
# that file-not-found and compile errors are returned as structured JSON
|
|
270
|
+
# (QueryFaceResult.success=False) rather than plain stderr messages.
|
|
271
|
+
if mode_is_named:
|
|
272
|
+
assert query_name is not None
|
|
273
|
+
assert face is not None
|
|
274
|
+
adapter_registry = build_adapter_registry(project_root, read_only=True)
|
|
275
|
+
face_result = query_face(
|
|
276
|
+
name=query_name,
|
|
277
|
+
path=face,
|
|
278
|
+
project_dir=project_root,
|
|
279
|
+
vars=vars,
|
|
280
|
+
limit=limit,
|
|
281
|
+
adapter_registry=adapter_registry,
|
|
282
|
+
)
|
|
283
|
+
if json_output:
|
|
284
|
+
print_json_result(face_result)
|
|
285
|
+
if not face_result.success:
|
|
286
|
+
raise SystemExit(1)
|
|
287
|
+
else:
|
|
288
|
+
_print_query_face_rich(face_result)
|
|
289
|
+
else:
|
|
290
|
+
# Raw SQL — if the positional doesn't look like SQL, hint --in instead.
|
|
291
|
+
if not _looks_like_sql(sql_text):
|
|
292
|
+
typer.echo(
|
|
293
|
+
f"Did you mean `dft query {sql_text} --in <face.yml>`?",
|
|
294
|
+
err=True,
|
|
295
|
+
)
|
|
296
|
+
raise typer.Exit(1)
|
|
297
|
+
# Requires --source
|
|
298
|
+
resolved_source = _resolve_source(source, None)
|
|
299
|
+
adapter_registry = build_adapter_registry(project_root, read_only=True)
|
|
300
|
+
exec_result = execute_query(
|
|
301
|
+
sql_text,
|
|
302
|
+
variables=vars,
|
|
303
|
+
source=resolved_source,
|
|
304
|
+
limit=limit,
|
|
305
|
+
adapter_registry=adapter_registry,
|
|
306
|
+
)
|
|
307
|
+
if json_output:
|
|
308
|
+
print_json_result(exec_result)
|
|
309
|
+
if not exec_result.success:
|
|
310
|
+
raise SystemExit(1)
|
|
311
|
+
else:
|
|
312
|
+
_print_execute_query_rich(exec_result)
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
# For validate/describe modes on named queries, extract SQL from the compiled face.
|
|
316
|
+
face_source: str | None = None
|
|
317
|
+
if mode_is_named:
|
|
318
|
+
assert query_name is not None
|
|
319
|
+
assert face is not None
|
|
320
|
+
lr = lookup_face_query_sql(query_name, face, project_root)
|
|
321
|
+
if not lr.success:
|
|
322
|
+
_handle_lookup_failure(lr, query_name)
|
|
323
|
+
sql_text = lr.sql
|
|
324
|
+
face_source = lr.source
|
|
325
|
+
|
|
326
|
+
if validate and describe:
|
|
327
|
+
# Run validate first; skip describe on error-severity diagnostics.
|
|
328
|
+
vr = validate_query(
|
|
329
|
+
sql_text, dialect=dialect, return_suppressed=show_suppressed
|
|
330
|
+
)
|
|
331
|
+
# Build the validate payload the same way on both error and success paths.
|
|
332
|
+
validate_payload: Any = (
|
|
333
|
+
{"diagnostics": vr.diagnostics, "suppressed": vr.suppressed or []}
|
|
334
|
+
if show_suppressed
|
|
335
|
+
else vr.diagnostics
|
|
336
|
+
)
|
|
337
|
+
if vr.has_errors:
|
|
338
|
+
if json_output:
|
|
339
|
+
typer.echo(json.dumps({"validate": validate_payload}, indent=2))
|
|
340
|
+
else:
|
|
341
|
+
_print_validate_result(vr, show_suppressed=show_suppressed)
|
|
342
|
+
raise typer.Exit(1)
|
|
343
|
+
|
|
344
|
+
resolved_source = _resolve_source(source, face_source)
|
|
345
|
+
adapter_registry = build_adapter_registry(project_root, read_only=True)
|
|
346
|
+
dr = describe_query(
|
|
347
|
+
sql_text,
|
|
348
|
+
source=resolved_source,
|
|
349
|
+
dialect=dialect,
|
|
350
|
+
adapter_registry=adapter_registry,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if json_output:
|
|
354
|
+
typer.echo(
|
|
355
|
+
json.dumps(
|
|
356
|
+
{
|
|
357
|
+
"validate": validate_payload,
|
|
358
|
+
"describe": dr.model_dump(mode="json", exclude_none=True),
|
|
359
|
+
},
|
|
360
|
+
indent=2,
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
if not dr.success:
|
|
364
|
+
raise typer.Exit(1)
|
|
365
|
+
else:
|
|
366
|
+
_print_validate_result(vr, show_suppressed=show_suppressed)
|
|
367
|
+
_print_describe_result(dr)
|
|
368
|
+
|
|
369
|
+
elif validate:
|
|
370
|
+
vr = validate_query(
|
|
371
|
+
sql_text, dialect=dialect, return_suppressed=show_suppressed
|
|
372
|
+
)
|
|
373
|
+
_print_validate_result(
|
|
374
|
+
vr, show_suppressed=show_suppressed, json_output=json_output
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
else: # describe only
|
|
378
|
+
resolved_source = _resolve_source(source, face_source)
|
|
379
|
+
adapter_registry = build_adapter_registry(project_root, read_only=True)
|
|
380
|
+
dr = describe_query(
|
|
381
|
+
sql_text,
|
|
382
|
+
source=resolved_source,
|
|
383
|
+
dialect=dialect,
|
|
384
|
+
adapter_registry=adapter_registry,
|
|
385
|
+
)
|
|
386
|
+
_print_describe_result(dr, json_output=json_output)
|