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,289 @@
|
|
|
1
|
+
"""MCP Server — thin shim over dataface.agent_api.
|
|
2
|
+
|
|
3
|
+
Resources: dataface://dashboards, dataface://dashboard/{path},
|
|
4
|
+
dataface://docs/all, dataface://docs/{topic}, dataface://guide/*
|
|
5
|
+
|
|
6
|
+
Tools: validate_dashboard, render_dashboard, execute_query, describe_query,
|
|
7
|
+
schema, schema_search, search_dashboards, query_face, docs,
|
|
8
|
+
describe_dashboard, list_skills, get_skill
|
|
9
|
+
|
|
10
|
+
Usage: dft mcp serve
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, cast
|
|
18
|
+
|
|
19
|
+
from pydantic import AnyUrl, TypeAdapter
|
|
20
|
+
|
|
21
|
+
from dataface._install_hint import install_hint
|
|
22
|
+
from dataface.ai.context import DatafaceAIContext
|
|
23
|
+
from dataface.ai.tool_schemas import (
|
|
24
|
+
DESCRIBE_DASHBOARD,
|
|
25
|
+
DESCRIBE_QUERY,
|
|
26
|
+
DOCS,
|
|
27
|
+
EXECUTE_QUERY,
|
|
28
|
+
GET_SKILL,
|
|
29
|
+
LIST_SKILLS,
|
|
30
|
+
QUERY_FACE,
|
|
31
|
+
RENDER_DASHBOARD,
|
|
32
|
+
SCHEMA,
|
|
33
|
+
SCHEMA_SEARCH,
|
|
34
|
+
SEARCH_DASHBOARDS,
|
|
35
|
+
VALIDATE_DASHBOARD,
|
|
36
|
+
)
|
|
37
|
+
from dataface.ai.tools import dispatch_tool_call
|
|
38
|
+
from dataface.core.execute.adapters import build_adapter_registry
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
_BASE_RESOURCES = [
|
|
43
|
+
(
|
|
44
|
+
"dataface://dashboards",
|
|
45
|
+
"application/json",
|
|
46
|
+
"Dashboard List",
|
|
47
|
+
"List of all dashboards in the project with metadata",
|
|
48
|
+
),
|
|
49
|
+
(
|
|
50
|
+
"dataface://docs/all",
|
|
51
|
+
"text/markdown",
|
|
52
|
+
"Dataface YAML Reference (full)",
|
|
53
|
+
"Complete YAML syntax reference — same content as `dft docs all`",
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
"dataface://docs/reference",
|
|
57
|
+
"text/markdown",
|
|
58
|
+
"Dataface YAML Field Reference (generated)",
|
|
59
|
+
"Auto-generated field-level spec for every YAML field. Run `just gen-yaml-reference` to refresh.",
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
"dataface://guide/dashboard-design",
|
|
63
|
+
"text/markdown",
|
|
64
|
+
"Dashboard Design Guide",
|
|
65
|
+
"Design principles for creating effective dashboards. Read this when creating a new dashboard, choosing chart types, or designing layouts for at-a-glance monitoring.",
|
|
66
|
+
),
|
|
67
|
+
(
|
|
68
|
+
"dataface://guide/report-design",
|
|
69
|
+
"text/markdown",
|
|
70
|
+
"Report Design Guide",
|
|
71
|
+
"Design principles for creating data-driven reports. Read this when creating narrative analyses, answering specific business questions, or writing investigative reports.",
|
|
72
|
+
),
|
|
73
|
+
(
|
|
74
|
+
"dataface://guide/dashboard-build",
|
|
75
|
+
"text/markdown",
|
|
76
|
+
"Dashboard Build Workflow",
|
|
77
|
+
"Best practices for building dashboards incrementally. Read this before starting work — covers the recommended build-test-iterate workflow, caching, and tool usage patterns.",
|
|
78
|
+
),
|
|
79
|
+
(
|
|
80
|
+
"dataface://guide/dashboard-review",
|
|
81
|
+
"text/markdown",
|
|
82
|
+
"Dashboard Review",
|
|
83
|
+
"Primary review skill for a Dataface face — runs structural review (YAML + dft validate) and visual review (PNG + vision-model evaluation), synthesizes a ranked findings list.",
|
|
84
|
+
),
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _docs_topic_resources() -> list[tuple[str, str, str, str]]:
|
|
89
|
+
"""One static resource per H2 in DATAFACE_SYNTAX.md, derived from the live topic index."""
|
|
90
|
+
from dataface.agent_api.docs import docs as docs_verb
|
|
91
|
+
|
|
92
|
+
return [
|
|
93
|
+
(
|
|
94
|
+
f"dataface://docs/{entry.id}",
|
|
95
|
+
"text/markdown",
|
|
96
|
+
f"Dataface YAML Reference — {entry.title}",
|
|
97
|
+
entry.description or f"`dft docs {entry.id}` section",
|
|
98
|
+
)
|
|
99
|
+
for entry in docs_verb().topics
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _read_resource_content(uri: str, context: DatafaceAIContext) -> str:
|
|
104
|
+
from dataface.agent_api.dashboards import get_dashboard, list_dashboards
|
|
105
|
+
from dataface.agent_api.docs import docs as docs_verb, read_full_text
|
|
106
|
+
from dataface.ai.prompts import load_shared_prompt
|
|
107
|
+
|
|
108
|
+
if uri == "dataface://dashboards":
|
|
109
|
+
return list_dashboards(
|
|
110
|
+
directory=context.dashboards_directory or Path("."),
|
|
111
|
+
recursive=True,
|
|
112
|
+
).model_dump_json(indent=2, exclude_none=True)
|
|
113
|
+
|
|
114
|
+
if uri == "dataface://docs/all":
|
|
115
|
+
return read_full_text()
|
|
116
|
+
|
|
117
|
+
if uri.startswith("dataface://docs/"):
|
|
118
|
+
slug = uri.replace("dataface://docs/", "")
|
|
119
|
+
result = docs_verb(topic=slug)
|
|
120
|
+
if result.topic is None:
|
|
121
|
+
return json.dumps({"error": f"Unknown topic: {slug}"})
|
|
122
|
+
return result.topic.content
|
|
123
|
+
|
|
124
|
+
if uri.startswith("dataface://guide/"):
|
|
125
|
+
slug = uri.removeprefix("dataface://guide/")
|
|
126
|
+
return load_shared_prompt(slug) or json.dumps(
|
|
127
|
+
{"error": f"Unknown resource: {uri}"}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if uri.startswith("dataface://dashboard/"):
|
|
131
|
+
path = uri.replace("dataface://dashboard/", "")
|
|
132
|
+
try:
|
|
133
|
+
resolved_path = context.resolve_dashboard_path(Path(path))
|
|
134
|
+
except ValueError as exc:
|
|
135
|
+
return json.dumps({"error": str(exc)})
|
|
136
|
+
return get_dashboard(path=resolved_path, include_raw=True).model_dump_json(
|
|
137
|
+
indent=2, exclude_none=True
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return json.dumps({"error": f"Unknown resource: {uri}"})
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _is_domain_error(result: dict[str, Any]) -> bool:
|
|
144
|
+
# Two failure shapes flow through dispatch: handlers emit
|
|
145
|
+
# {"success": false, ...}; dispatch fallbacks emit {"error": str}.
|
|
146
|
+
# The "success is not True" guard keeps a future {"success": True,
|
|
147
|
+
# "error": "warning..."} happy path from being misclassified.
|
|
148
|
+
if result.get("success") is False:
|
|
149
|
+
return True
|
|
150
|
+
if result.get("success") is True:
|
|
151
|
+
return False
|
|
152
|
+
err = result.get("error")
|
|
153
|
+
return isinstance(err, str) and bool(err)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def create_server(context: DatafaceAIContext) -> Any:
|
|
157
|
+
try:
|
|
158
|
+
from mcp.server import Server
|
|
159
|
+
from mcp.types import (
|
|
160
|
+
CallToolResult,
|
|
161
|
+
ContentBlock,
|
|
162
|
+
Resource,
|
|
163
|
+
ResourceTemplate,
|
|
164
|
+
TextContent,
|
|
165
|
+
Tool,
|
|
166
|
+
)
|
|
167
|
+
except ImportError as e:
|
|
168
|
+
raise ImportError(
|
|
169
|
+
"MCP server requires the 'mcp' package. "
|
|
170
|
+
f"Install with: {install_hint('mcp')}"
|
|
171
|
+
) from e
|
|
172
|
+
|
|
173
|
+
server = Server("dataface")
|
|
174
|
+
|
|
175
|
+
def _uri(value: str) -> AnyUrl:
|
|
176
|
+
return TypeAdapter(AnyUrl).validate_python(value)
|
|
177
|
+
|
|
178
|
+
@server.list_resources()
|
|
179
|
+
async def handle_list_resources() -> list[Resource]:
|
|
180
|
+
return [
|
|
181
|
+
Resource(uri=_uri(u), mimeType=m, name=n, description=d)
|
|
182
|
+
for u, m, n, d in (*_BASE_RESOURCES, *_docs_topic_resources())
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
@server.list_resource_templates()
|
|
186
|
+
async def handle_list_resource_templates() -> list[ResourceTemplate]:
|
|
187
|
+
return [
|
|
188
|
+
ResourceTemplate(
|
|
189
|
+
uriTemplate="dataface://dashboard/{path}",
|
|
190
|
+
name="Dashboard Content",
|
|
191
|
+
description="YAML content and compiled structure of a specific dashboard",
|
|
192
|
+
mimeType="application/json",
|
|
193
|
+
),
|
|
194
|
+
ResourceTemplate(
|
|
195
|
+
uriTemplate="dataface://docs/{topic}",
|
|
196
|
+
name="Dataface YAML Reference Section",
|
|
197
|
+
description="One H2 section of DATAFACE_SYNTAX.md (e.g. face, charts, cheatsheet, all)",
|
|
198
|
+
mimeType="text/markdown",
|
|
199
|
+
),
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
@server.read_resource()
|
|
203
|
+
async def handle_read_resource(uri: str) -> str:
|
|
204
|
+
return _read_resource_content(str(uri), context=context)
|
|
205
|
+
|
|
206
|
+
@server.list_tools()
|
|
207
|
+
async def handle_list_tools() -> list[Tool]:
|
|
208
|
+
return [
|
|
209
|
+
Tool(
|
|
210
|
+
name=cast(str, t["name"]),
|
|
211
|
+
description=cast(str | None, t["description"]),
|
|
212
|
+
inputSchema=cast(dict[str, Any], t["input_schema"]),
|
|
213
|
+
)
|
|
214
|
+
for t in [
|
|
215
|
+
VALIDATE_DASHBOARD,
|
|
216
|
+
RENDER_DASHBOARD,
|
|
217
|
+
EXECUTE_QUERY,
|
|
218
|
+
DESCRIBE_QUERY,
|
|
219
|
+
SCHEMA,
|
|
220
|
+
SCHEMA_SEARCH,
|
|
221
|
+
SEARCH_DASHBOARDS,
|
|
222
|
+
QUERY_FACE,
|
|
223
|
+
DOCS,
|
|
224
|
+
DESCRIBE_DASHBOARD,
|
|
225
|
+
LIST_SKILLS,
|
|
226
|
+
GET_SKILL,
|
|
227
|
+
]
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
@server.call_tool()
|
|
231
|
+
async def handle_call_tool(
|
|
232
|
+
name: str, arguments: dict[str, Any] | None
|
|
233
|
+
) -> CallToolResult:
|
|
234
|
+
arguments = arguments or {}
|
|
235
|
+
result = dispatch_tool_call(name, arguments, context=context)
|
|
236
|
+
content: list[ContentBlock] = [
|
|
237
|
+
TextContent(type="text", text=json.dumps(result, indent=2))
|
|
238
|
+
]
|
|
239
|
+
return CallToolResult(content=content, isError=_is_domain_error(result))
|
|
240
|
+
|
|
241
|
+
return server
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def run_server() -> None:
|
|
245
|
+
"""Run the MCP server in stdio mode with embedded HTTP server."""
|
|
246
|
+
try:
|
|
247
|
+
from mcp.server.stdio import stdio_server
|
|
248
|
+
except ImportError as e:
|
|
249
|
+
raise ImportError(
|
|
250
|
+
"MCP server requires the 'mcp' package. "
|
|
251
|
+
f"Install with: {install_hint('mcp')}"
|
|
252
|
+
) from e
|
|
253
|
+
|
|
254
|
+
from dataface.core.execute.adapters import LOCAL_AUTHORING_REGISTRY_KWARGS
|
|
255
|
+
from dataface.core.serve.embedded import build_embedded_server
|
|
256
|
+
|
|
257
|
+
http_server, resolved_http_port = build_embedded_server()
|
|
258
|
+
server = create_server(
|
|
259
|
+
context=DatafaceAIContext(
|
|
260
|
+
adapter_registry=build_adapter_registry(
|
|
261
|
+
Path.cwd(),
|
|
262
|
+
**LOCAL_AUTHORING_REGISTRY_KWARGS,
|
|
263
|
+
),
|
|
264
|
+
server_port=resolved_http_port,
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
logger.info("Starting Dataface MCP server (stdio mode)")
|
|
269
|
+
http_task = asyncio.create_task(http_server.serve())
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
273
|
+
await server.run(
|
|
274
|
+
read_stream,
|
|
275
|
+
write_stream,
|
|
276
|
+
server.create_initialization_options(),
|
|
277
|
+
)
|
|
278
|
+
finally:
|
|
279
|
+
http_server.should_exit = True
|
|
280
|
+
await http_task
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def main() -> None:
|
|
284
|
+
"""Entry point for running the MCP server."""
|
|
285
|
+
asyncio.run(run_server())
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
main()
|
dataface/ai/memories.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Project-scoped analyst memory loading and prompt formatting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
DEFAULT_MEMORY_FILENAME = "memories.yml"
|
|
11
|
+
MAX_PROMPT_MEMORIES = 20
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_memories(base_dir: Path | None = None) -> list[dict[str, Any]]:
|
|
15
|
+
root = base_dir if base_dir is not None else Path.cwd()
|
|
16
|
+
path = root / DEFAULT_MEMORY_FILENAME
|
|
17
|
+
if not path.exists():
|
|
18
|
+
return []
|
|
19
|
+
|
|
20
|
+
payload = yaml.safe_load(path.read_text()) or {}
|
|
21
|
+
rows = payload.get("memories") if isinstance(payload, dict) else None
|
|
22
|
+
if not isinstance(rows, list):
|
|
23
|
+
return []
|
|
24
|
+
|
|
25
|
+
memories: list[dict[str, Any]] = []
|
|
26
|
+
for row in rows:
|
|
27
|
+
if not isinstance(row, dict):
|
|
28
|
+
continue
|
|
29
|
+
status = str(row.get("status", "active")).strip().lower()
|
|
30
|
+
if status not in {"active", ""}:
|
|
31
|
+
continue
|
|
32
|
+
note = str(row.get("note", "")).strip()
|
|
33
|
+
if not note:
|
|
34
|
+
continue
|
|
35
|
+
memories.append(row)
|
|
36
|
+
memories.sort(
|
|
37
|
+
key=lambda row: (
|
|
38
|
+
str(row.get("updated", "")),
|
|
39
|
+
str(row.get("created", "")),
|
|
40
|
+
str(row.get("type", "")),
|
|
41
|
+
),
|
|
42
|
+
reverse=True,
|
|
43
|
+
)
|
|
44
|
+
return memories
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _scope_label(memory: dict[str, Any]) -> str:
|
|
48
|
+
table = str(memory.get("table", "")).strip()
|
|
49
|
+
column = str(memory.get("column", "")).strip()
|
|
50
|
+
tables = memory.get("tables")
|
|
51
|
+
if table and column:
|
|
52
|
+
return f"{table}.{column}"
|
|
53
|
+
if table:
|
|
54
|
+
return table
|
|
55
|
+
if isinstance(tables, list):
|
|
56
|
+
joined = ", ".join(str(item).strip() for item in tables if str(item).strip())
|
|
57
|
+
if joined:
|
|
58
|
+
return joined
|
|
59
|
+
scope = str(memory.get("scope", "")).strip()
|
|
60
|
+
return scope
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def format_memories_prompt(
|
|
64
|
+
memories: list[dict[str, Any]], *, limit: int = MAX_PROMPT_MEMORIES
|
|
65
|
+
) -> str:
|
|
66
|
+
if not memories:
|
|
67
|
+
return ""
|
|
68
|
+
|
|
69
|
+
lines = ["## Learned Context"]
|
|
70
|
+
for memory in memories[:limit]:
|
|
71
|
+
memory_type = str(memory.get("type", "memory")).strip() or "memory"
|
|
72
|
+
label = _scope_label(memory)
|
|
73
|
+
note = str(memory.get("note", "")).strip()
|
|
74
|
+
parts = [memory_type]
|
|
75
|
+
if label:
|
|
76
|
+
parts.append(label)
|
|
77
|
+
prefix = " | ".join(parts)
|
|
78
|
+
source = str(memory.get("source", "")).strip()
|
|
79
|
+
confidence = str(memory.get("confidence", "")).strip()
|
|
80
|
+
meta = ", ".join(part for part in [source, confidence] if part)
|
|
81
|
+
if meta:
|
|
82
|
+
lines.append(f"- {prefix}: {note} [{meta}]")
|
|
83
|
+
else:
|
|
84
|
+
lines.append(f"- {prefix}: {note}")
|
|
85
|
+
return "\n".join(lines)
|
dataface/ai/prompts.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Prompt loading and building utilities for AI services.
|
|
2
|
+
|
|
3
|
+
This module provides shared utilities for loading prompts from markdown files
|
|
4
|
+
and building system prompts with context.
|
|
5
|
+
|
|
6
|
+
Shared guides (design principles, workflow, etc.) live in dataface/ai/skills/
|
|
7
|
+
as SKILL.md files — the single source of truth for all AI consumers.
|
|
8
|
+
App-specific prompts (tool instructions, validation loops) live in each app's
|
|
9
|
+
own prompts/ directory.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from dataface.agent_api.skill_render import SkillSurface, render_skill_body
|
|
18
|
+
|
|
19
|
+
SKILLS_DIR = Path(__file__).parent / "skills"
|
|
20
|
+
|
|
21
|
+
_FRONTMATTER_RE = re.compile(r"^---\s*\n.*?\n---\s*\n", re.DOTALL)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _strip_frontmatter(text: str) -> str:
|
|
25
|
+
"""Strip YAML frontmatter (--- ... ---) from a skill file."""
|
|
26
|
+
return _FRONTMATTER_RE.sub("", text, count=1).lstrip("\n")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_prompt(prompt_name: str, prompts_dir: Path) -> str:
|
|
30
|
+
"""Load a prompt template from markdown file.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
prompt_name: Name of prompt file (without .md extension)
|
|
34
|
+
prompts_dir: Directory containing prompt files
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Prompt content as string, empty string if not found
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> prompt = load_prompt("yaml_generation", Path("/prompts"))
|
|
41
|
+
"""
|
|
42
|
+
prompt_file = prompts_dir / f"{prompt_name}.md"
|
|
43
|
+
if prompt_file.exists():
|
|
44
|
+
return prompt_file.read_text()
|
|
45
|
+
return ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PromptLoader:
|
|
49
|
+
"""Prompt loader with a fixed prompts directory.
|
|
50
|
+
|
|
51
|
+
This class provides a convenient way to load prompts from a specific directory,
|
|
52
|
+
useful when you need to load multiple prompts from the same location.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
prompts_dir: The directory containing prompt markdown files
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> loader = PromptLoader(Path("/prompts"))
|
|
59
|
+
>>> yaml_prompt = loader.load("yaml_generation")
|
|
60
|
+
>>> dashboard_prompt = loader.load("dashboard_design")
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, prompts_dir: Path) -> None:
|
|
64
|
+
"""Initialize the prompt loader.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
prompts_dir: Directory containing prompt markdown files
|
|
68
|
+
"""
|
|
69
|
+
self.prompts_dir = prompts_dir
|
|
70
|
+
|
|
71
|
+
def load(self, prompt_name: str) -> str:
|
|
72
|
+
"""Load a prompt template.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
prompt_name: Name of prompt file (without .md extension)
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Prompt content as string, empty string if not found
|
|
79
|
+
"""
|
|
80
|
+
return load_prompt(prompt_name, self.prompts_dir)
|
|
81
|
+
|
|
82
|
+
def load_required(self, prompt_name: str) -> str:
|
|
83
|
+
"""Load a prompt template, raising error if not found.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
prompt_name: Name of prompt file (without .md extension)
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Prompt content as string
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
FileNotFoundError: If prompt file doesn't exist
|
|
93
|
+
"""
|
|
94
|
+
prompt_file = self.prompts_dir / f"{prompt_name}.md"
|
|
95
|
+
if not prompt_file.exists():
|
|
96
|
+
raise FileNotFoundError(f"Prompt file not found: {prompt_file}")
|
|
97
|
+
return prompt_file.read_text()
|
|
98
|
+
|
|
99
|
+
def exists(self, prompt_name: str) -> bool:
|
|
100
|
+
"""Check if a prompt file exists.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
prompt_name: Name of prompt file (without .md extension)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if prompt file exists
|
|
107
|
+
"""
|
|
108
|
+
prompt_file = self.prompts_dir / f"{prompt_name}.md"
|
|
109
|
+
return prompt_file.exists()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def load_shared_prompt(skill_name: str, *, surface: SkillSurface = "mcp") -> str:
|
|
113
|
+
"""Load a shared guide from dataface/ai/skills/, rendered for ``surface``.
|
|
114
|
+
|
|
115
|
+
Skills are the single source of truth for design guides and reference
|
|
116
|
+
materials, shared across MCP, playground, cloud, and any other consumer.
|
|
117
|
+
Both call sites today are MCP-vocab (the terminal agent's tool loop and
|
|
118
|
+
the MCP ``dataface://guide/<slug>`` resource handler), so the default
|
|
119
|
+
surface is ``"mcp"``.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
skill_name: The skill directory name under dataface/ai/skills/
|
|
123
|
+
(kebab-case, matches the on-disk directory exactly — e.g.
|
|
124
|
+
"dashboard-build", "dashboard-design", "dashboard-review").
|
|
125
|
+
surface: Which surface's macro vocabulary to render. Default ``"mcp"``.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Guide content as string (frontmatter stripped, surface macros
|
|
129
|
+
expanded), empty string if not found.
|
|
130
|
+
"""
|
|
131
|
+
skill_file = SKILLS_DIR / skill_name / "SKILL.md"
|
|
132
|
+
if skill_file.exists():
|
|
133
|
+
body = _strip_frontmatter(skill_file.read_text())
|
|
134
|
+
return render_skill_body(body, surface=surface)
|
|
135
|
+
return ""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def build_context_section(
|
|
139
|
+
database_context: str | None = None,
|
|
140
|
+
yaml_context: str | None = None,
|
|
141
|
+
chart_context: str | None = None,
|
|
142
|
+
) -> str:
|
|
143
|
+
"""Build a context section for system prompts.
|
|
144
|
+
|
|
145
|
+
This function creates a formatted context section that can be prepended
|
|
146
|
+
to system prompts. Database context is given highest priority.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
database_context: Optional database schema information
|
|
150
|
+
yaml_context: Optional selected YAML code
|
|
151
|
+
chart_context: Optional chart information
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Formatted context string, empty if no context provided
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
>>> context = build_context_section(
|
|
158
|
+
... database_context="Tables: users, orders",
|
|
159
|
+
... yaml_context="title: My Dashboard"
|
|
160
|
+
... )
|
|
161
|
+
"""
|
|
162
|
+
context_parts = []
|
|
163
|
+
|
|
164
|
+
# Database context is critical and should be first
|
|
165
|
+
if database_context:
|
|
166
|
+
context_parts.append(f"## ⚠️ CRITICAL: Database Context\n\n{database_context}\n")
|
|
167
|
+
|
|
168
|
+
if yaml_context:
|
|
169
|
+
context_parts.append(f"## Current YAML Code\n\n```yaml\n{yaml_context}\n```\n")
|
|
170
|
+
|
|
171
|
+
if chart_context:
|
|
172
|
+
context_parts.append(f"## Chart Context\n\n{chart_context}\n")
|
|
173
|
+
|
|
174
|
+
if context_parts:
|
|
175
|
+
return "\n\n".join(context_parts)
|
|
176
|
+
|
|
177
|
+
return ""
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Schema context helper — dispatches to the private package when installed.
|
|
2
|
+
|
|
3
|
+
When ``dataface-super-schema`` is installed, its enriched ``get_schema_context``
|
|
4
|
+
is used (cache-first with warm-cache profiler data). Without the private
|
|
5
|
+
package, a bare table list is produced from the dbt adapter + manifest.
|
|
6
|
+
|
|
7
|
+
Import contracts: callers at ``dataface.ai.schema_context.get_schema_context``
|
|
8
|
+
are stable. The private package supplies enrichment; the OSS fallback ensures
|
|
9
|
+
basic functionality without it.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib.util
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from dataface.core.execute.adapters import AdapterRegistry
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
_SUPER_SCHEMA_AVAILABLE = importlib.util.find_spec("dataface_super_schema") is not None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_schema_context(
|
|
28
|
+
*,
|
|
29
|
+
adapter_registry: AdapterRegistry,
|
|
30
|
+
source: str | None = None,
|
|
31
|
+
cache_path: Path | None = None,
|
|
32
|
+
) -> str:
|
|
33
|
+
"""Build a schema context string for AI consumption.
|
|
34
|
+
|
|
35
|
+
Delegates to the private package's enriched version when installed.
|
|
36
|
+
Falls back to a bare dbt-only table list otherwise.
|
|
37
|
+
"""
|
|
38
|
+
if _SUPER_SCHEMA_AVAILABLE:
|
|
39
|
+
from dataface_super_schema.ai_schema_context import ( # noqa: PLC0415
|
|
40
|
+
get_schema_context as _enriched,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return _enriched(
|
|
44
|
+
adapter_registry=adapter_registry,
|
|
45
|
+
source=source,
|
|
46
|
+
cache_path=cache_path,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# OSS fallback: dbt-only, no profiler cache.
|
|
50
|
+
return _oss_schema_context(
|
|
51
|
+
adapter_registry=adapter_registry,
|
|
52
|
+
source=source,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _oss_schema_context(
|
|
57
|
+
*,
|
|
58
|
+
adapter_registry: AdapterRegistry,
|
|
59
|
+
source: str | None = None,
|
|
60
|
+
) -> str:
|
|
61
|
+
"""Bare table list from the dbt adapter + manifest (no profiler cache)."""
|
|
62
|
+
from dataface.core.compile.errors import DatafaceError
|
|
63
|
+
from dataface.core.inspect.resolver import LayeredSchemaResolver
|
|
64
|
+
|
|
65
|
+
resolved_source = (
|
|
66
|
+
source if source is not None else _default_source_name(adapter_registry)
|
|
67
|
+
)
|
|
68
|
+
if resolved_source is None:
|
|
69
|
+
return "No data source configured."
|
|
70
|
+
|
|
71
|
+
source_cfg: dict[str, Any] | None = None
|
|
72
|
+
dialect = "unknown"
|
|
73
|
+
try:
|
|
74
|
+
source_cfg = adapter_registry.resolve_source_config(resolved_source)
|
|
75
|
+
dialect = source_cfg.get("type", "unknown")
|
|
76
|
+
except DatafaceError as e:
|
|
77
|
+
logger.warning("No live DB connection for schema context: %s", e)
|
|
78
|
+
return "No database connection available."
|
|
79
|
+
|
|
80
|
+
project_root = Path(adapter_registry.project_root or ".").resolve()
|
|
81
|
+
resolver = LayeredSchemaResolver(
|
|
82
|
+
cache=None,
|
|
83
|
+
adapter_registry=adapter_registry,
|
|
84
|
+
project_root=project_root,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
schemas_resp = resolver.list_schemas(source=resolved_source)
|
|
89
|
+
except Exception as e: # noqa: BLE001 — context provider degrades gracefully
|
|
90
|
+
logger.warning("Failed to list schemas for context: %s", e)
|
|
91
|
+
return f"Error listing schemas: {e}"
|
|
92
|
+
|
|
93
|
+
schemas_branch = (
|
|
94
|
+
schemas_resp.get("sources", {}).get(resolved_source, {}).get("schemas", {})
|
|
95
|
+
)
|
|
96
|
+
lines: list[str] = [f"## Schema: {resolved_source} ({dialect})"]
|
|
97
|
+
for schema_name in schemas_branch:
|
|
98
|
+
try:
|
|
99
|
+
tables_resp = resolver.list_tables(
|
|
100
|
+
source=resolved_source, schema=schema_name
|
|
101
|
+
)
|
|
102
|
+
except Exception as e: # noqa: BLE001 — per-schema best effort
|
|
103
|
+
logger.warning("list_tables failed for %s: %s", schema_name, e)
|
|
104
|
+
continue
|
|
105
|
+
tables_branch = (
|
|
106
|
+
tables_resp.get("sources", {})
|
|
107
|
+
.get(resolved_source, {})
|
|
108
|
+
.get("schemas", {})
|
|
109
|
+
.get(schema_name, {})
|
|
110
|
+
.get("tables", {})
|
|
111
|
+
)
|
|
112
|
+
for table_name, summary in tables_branch.items():
|
|
113
|
+
desc = summary.get("description", "")
|
|
114
|
+
row_count = summary.get("row_count")
|
|
115
|
+
meta_parts = []
|
|
116
|
+
if row_count is not None:
|
|
117
|
+
meta_parts.append(f"{row_count:,} rows")
|
|
118
|
+
if desc:
|
|
119
|
+
meta_parts.append(desc)
|
|
120
|
+
suffix = f" — {'; '.join(meta_parts)}" if meta_parts else ""
|
|
121
|
+
lines.append(f"- {schema_name}.{table_name}{suffix}")
|
|
122
|
+
|
|
123
|
+
if len(lines) == 1:
|
|
124
|
+
return "No tables found in the configured data source."
|
|
125
|
+
|
|
126
|
+
return "\n".join(lines)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _default_source_name(registry: AdapterRegistry) -> str | None:
|
|
130
|
+
sources = registry.list_sql_sources()
|
|
131
|
+
if not sources:
|
|
132
|
+
return None
|
|
133
|
+
if len(sources) > 1:
|
|
134
|
+
names = ", ".join(item["name"] for item in sources)
|
|
135
|
+
raise ValueError(
|
|
136
|
+
"source is required when multiple SQL sources are configured: " f"{names}"
|
|
137
|
+
)
|
|
138
|
+
return sources[0]["name"]
|