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,461 @@
|
|
|
1
|
+
"""Full-text + filter search over the resolver's materialized schema search index.
|
|
2
|
+
|
|
3
|
+
Sibling to ``LayeredSchemaResolver``: not a ``SchemaSource`` Protocol
|
|
4
|
+
method. Find/filter is a different access pattern from drill-down.
|
|
5
|
+
|
|
6
|
+
Substring + regex match across text-bearing fields, layered on top of
|
|
7
|
+
structured predicate filters. Stable iteration order — no ranking, no
|
|
8
|
+
scoring, no top-N. The schema search index is materialized fresh each
|
|
9
|
+
call through the resolver; the resolver's own per-source caching keeps
|
|
10
|
+
it cheap.
|
|
11
|
+
|
|
12
|
+
Provenance flows through unchanged from the resolver: a hit on a name
|
|
13
|
+
that came from the adapter carries ``source='dbt_adapter'``; a hit on a
|
|
14
|
+
description that came from the cache carries ``source='super_schema'``;
|
|
15
|
+
a hit on a tag/test/relationships from the manifest carries
|
|
16
|
+
``source='dbt_manifest'``.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import fnmatch
|
|
22
|
+
import re
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import TYPE_CHECKING, Any
|
|
25
|
+
|
|
26
|
+
from pydantic import BaseModel, Field
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from dataface.core.inspect.resolver import LayeredSchemaResolver
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_SNIPPET_INLINE_LIMIT: int = 200
|
|
33
|
+
_SNIPPET_WINDOW: int = 80
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class InvalidRegex(ValueError):
|
|
37
|
+
"""Raised when ``regex=True`` is paired with a non-compilable pattern."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SchemaSearchHit(BaseModel):
|
|
41
|
+
"""One match emitted by ``SchemaSearch.search``.
|
|
42
|
+
|
|
43
|
+
``location`` is the dot-joined path: ``"<source>.<schema>.<table>"`` for a
|
|
44
|
+
table hit, ``"<source>.<schema>.<table>.<column>"`` for a column hit.
|
|
45
|
+
``matched_field`` says which text-bearing field matched ("name",
|
|
46
|
+
"description", "tag", "tests.<name>", "meta.<key>", "owner", or
|
|
47
|
+
"_missing:<field>" for synthetic missing-field hits). ``source`` is
|
|
48
|
+
provenance — which layer contributed the field ("super_schema",
|
|
49
|
+
"dbt_manifest", "dbt_adapter").
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
location: str
|
|
53
|
+
matched_field: str
|
|
54
|
+
snippet: str
|
|
55
|
+
source: str
|
|
56
|
+
value: str
|
|
57
|
+
attrs: dict[str, Any] = Field(
|
|
58
|
+
default_factory=dict,
|
|
59
|
+
description="Structured metadata attributes (role, tag, fk_to, etc.).",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class _SchemaSearchEntry:
|
|
65
|
+
"""One table or column row, with its searchable fields and structured attrs."""
|
|
66
|
+
|
|
67
|
+
location: str
|
|
68
|
+
is_column: bool
|
|
69
|
+
consulted: tuple[str, ...]
|
|
70
|
+
text_fields: list[tuple[str, str]] # (matched_field, value) — order-stable
|
|
71
|
+
attrs: dict[str, Any] # structured filter inputs (role, tags, tests, ...)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SchemaSearch:
|
|
75
|
+
"""Full-text + filter search over ``LayeredSchemaResolver``."""
|
|
76
|
+
|
|
77
|
+
def __init__(self, resolver: LayeredSchemaResolver) -> None:
|
|
78
|
+
self.resolver = resolver
|
|
79
|
+
|
|
80
|
+
def search(
|
|
81
|
+
self,
|
|
82
|
+
keyword: str,
|
|
83
|
+
*,
|
|
84
|
+
scope: list[str] | None = None,
|
|
85
|
+
regex: bool = False,
|
|
86
|
+
role: str | None = None,
|
|
87
|
+
tag: str | None = None,
|
|
88
|
+
has_test: str | None = None,
|
|
89
|
+
missing: str | None = None,
|
|
90
|
+
meta: dict[str, str] | None = None,
|
|
91
|
+
column_name: str | None = None,
|
|
92
|
+
table_name: str | None = None,
|
|
93
|
+
fk_to: str | None = None,
|
|
94
|
+
) -> tuple[list[SchemaSearchHit], list[str]]:
|
|
95
|
+
pattern = self._compile_keyword(keyword, regex=regex)
|
|
96
|
+
hits: list[SchemaSearchHit] = []
|
|
97
|
+
entries, warnings = self._materialize_index()
|
|
98
|
+
for entry in entries:
|
|
99
|
+
if not self._matches_filters(
|
|
100
|
+
entry,
|
|
101
|
+
role=role,
|
|
102
|
+
tag=tag,
|
|
103
|
+
has_test=has_test,
|
|
104
|
+
meta=meta,
|
|
105
|
+
column_name=column_name,
|
|
106
|
+
table_name=table_name,
|
|
107
|
+
fk_to=fk_to,
|
|
108
|
+
):
|
|
109
|
+
continue
|
|
110
|
+
if missing is not None:
|
|
111
|
+
hits.extend(self._missing_hits(entry, missing))
|
|
112
|
+
continue
|
|
113
|
+
if not keyword and any(
|
|
114
|
+
f is not None
|
|
115
|
+
for f in (role, tag, has_test, meta, column_name, table_name, fk_to)
|
|
116
|
+
):
|
|
117
|
+
# Empty keyword + structured filter = enumerate qualifying entries
|
|
118
|
+
# without text matching. One hit per scope-eligible field.
|
|
119
|
+
hits.extend(self._unfiltered_hits(entry, scope))
|
|
120
|
+
continue
|
|
121
|
+
hits.extend(self._keyword_hits(entry, pattern, scope))
|
|
122
|
+
return hits, warnings
|
|
123
|
+
|
|
124
|
+
# ---- Schema index walk --------------------------------------------------
|
|
125
|
+
|
|
126
|
+
def _materialize_index(self) -> tuple[list[_SchemaSearchEntry], list[str]]:
|
|
127
|
+
"""Walk source → schema → table → column. Stable order throughout.
|
|
128
|
+
|
|
129
|
+
Per-source / per-schema failures (unreachable warehouse, missing dbt
|
|
130
|
+
adapter package, etc.) are turned into ``warnings`` strings so the
|
|
131
|
+
verb wrapper can surface them — silent skips would let the user mistake
|
|
132
|
+
an incomplete schema walk for "no matches".
|
|
133
|
+
|
|
134
|
+
Calls ``profile_table`` per-table so each entry carries its own
|
|
135
|
+
``sources_consulted`` — required for accurate per-field provenance
|
|
136
|
+
when a schema has a mix of cached and uncached tables.
|
|
137
|
+
"""
|
|
138
|
+
entries: list[_SchemaSearchEntry] = []
|
|
139
|
+
warnings: list[str] = []
|
|
140
|
+
for source_entry in self.resolver.adapter_registry.list_sql_sources():
|
|
141
|
+
source_name = source_entry["name"]
|
|
142
|
+
try:
|
|
143
|
+
schemas_resp = self.resolver.list_schemas(source_name)
|
|
144
|
+
except Exception as exc: # noqa: BLE001 — surface as warning
|
|
145
|
+
warnings.append(f"source '{source_name}': {exc}")
|
|
146
|
+
continue
|
|
147
|
+
schemas = (
|
|
148
|
+
schemas_resp.get("sources", {}).get(source_name, {}).get("schemas", {})
|
|
149
|
+
)
|
|
150
|
+
for schema_name in schemas:
|
|
151
|
+
try:
|
|
152
|
+
tables_resp = self.resolver.list_tables(source_name, schema_name)
|
|
153
|
+
except Exception as exc: # noqa: BLE001 — surface as warning
|
|
154
|
+
warnings.append(
|
|
155
|
+
f"source '{source_name}' schema '{schema_name}': {exc}"
|
|
156
|
+
)
|
|
157
|
+
continue
|
|
158
|
+
table_names = (
|
|
159
|
+
tables_resp.get("sources", {})
|
|
160
|
+
.get(source_name, {})
|
|
161
|
+
.get("schemas", {})
|
|
162
|
+
.get(schema_name, {})
|
|
163
|
+
.get("tables", {})
|
|
164
|
+
)
|
|
165
|
+
for table_name in table_names:
|
|
166
|
+
try:
|
|
167
|
+
profile_resp = self.resolver.profile_table(
|
|
168
|
+
source=source_name, schema=schema_name, table=table_name
|
|
169
|
+
)
|
|
170
|
+
except Exception as exc: # noqa: BLE001 — surface as warning
|
|
171
|
+
warnings.append(
|
|
172
|
+
f"source '{source_name}' schema '{schema_name}' "
|
|
173
|
+
f"table '{table_name}': {exc}"
|
|
174
|
+
)
|
|
175
|
+
continue
|
|
176
|
+
consulted = tuple(
|
|
177
|
+
profile_resp.get("_meta", {}).get("sources_consulted", [])
|
|
178
|
+
)
|
|
179
|
+
table_profile = (
|
|
180
|
+
profile_resp.get("sources", {})
|
|
181
|
+
.get(source_name, {})
|
|
182
|
+
.get("schemas", {})
|
|
183
|
+
.get(schema_name, {})
|
|
184
|
+
.get("tables", {})
|
|
185
|
+
.get(table_name, {})
|
|
186
|
+
)
|
|
187
|
+
entries.append(
|
|
188
|
+
self._table_entry(
|
|
189
|
+
source_name,
|
|
190
|
+
schema_name,
|
|
191
|
+
table_name,
|
|
192
|
+
table_profile,
|
|
193
|
+
consulted,
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
for col_name, col_profile in (
|
|
197
|
+
table_profile.get("columns") or {}
|
|
198
|
+
).items():
|
|
199
|
+
entries.append(
|
|
200
|
+
self._column_entry(
|
|
201
|
+
source_name,
|
|
202
|
+
schema_name,
|
|
203
|
+
table_name,
|
|
204
|
+
col_name,
|
|
205
|
+
col_profile,
|
|
206
|
+
consulted,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
return entries, warnings
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _table_entry(
|
|
213
|
+
source: str,
|
|
214
|
+
schema: str,
|
|
215
|
+
table: str,
|
|
216
|
+
profile: dict[str, Any],
|
|
217
|
+
consulted: tuple[str, ...],
|
|
218
|
+
) -> _SchemaSearchEntry:
|
|
219
|
+
loc = f"{source}.{schema}.{table}"
|
|
220
|
+
text_fields: list[tuple[str, str]] = [("name", table)]
|
|
221
|
+
desc = profile.get("description")
|
|
222
|
+
if isinstance(desc, str) and desc:
|
|
223
|
+
text_fields.append(("description", desc))
|
|
224
|
+
owner = profile.get("owner")
|
|
225
|
+
if isinstance(owner, str) and owner:
|
|
226
|
+
text_fields.append(("owner", owner))
|
|
227
|
+
for t in profile.get("tags") or []:
|
|
228
|
+
if isinstance(t, str) and t:
|
|
229
|
+
text_fields.append(("tag", t))
|
|
230
|
+
attrs: dict[str, Any] = {}
|
|
231
|
+
for k in ("tags", "owner", "row_count", "kind", "grain"):
|
|
232
|
+
if k in profile:
|
|
233
|
+
attrs[k] = profile[k]
|
|
234
|
+
return _SchemaSearchEntry(
|
|
235
|
+
location=loc,
|
|
236
|
+
is_column=False,
|
|
237
|
+
consulted=consulted,
|
|
238
|
+
text_fields=text_fields,
|
|
239
|
+
attrs=attrs,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def _column_entry(
|
|
244
|
+
source: str,
|
|
245
|
+
schema: str,
|
|
246
|
+
table: str,
|
|
247
|
+
column: str,
|
|
248
|
+
profile: dict[str, Any],
|
|
249
|
+
consulted: tuple[str, ...],
|
|
250
|
+
) -> _SchemaSearchEntry:
|
|
251
|
+
loc = f"{source}.{schema}.{table}.{column}"
|
|
252
|
+
text_fields: list[tuple[str, str]] = [("name", column)]
|
|
253
|
+
desc = profile.get("description")
|
|
254
|
+
if isinstance(desc, str) and desc:
|
|
255
|
+
text_fields.append(("description", desc))
|
|
256
|
+
for t in profile.get("tags") or []:
|
|
257
|
+
if isinstance(t, str) and t:
|
|
258
|
+
text_fields.append(("tag", t))
|
|
259
|
+
for test in profile.get("tests") or []:
|
|
260
|
+
tname = test.get("name") if isinstance(test, dict) else None
|
|
261
|
+
if isinstance(tname, str) and tname:
|
|
262
|
+
text_fields.append((f"tests.{tname}", tname))
|
|
263
|
+
meta = profile.get("meta")
|
|
264
|
+
if isinstance(meta, dict):
|
|
265
|
+
for k, v in meta.items():
|
|
266
|
+
if isinstance(v, str) and v:
|
|
267
|
+
text_fields.append((f"meta.{k}", v))
|
|
268
|
+
attrs: dict[str, Any] = {}
|
|
269
|
+
for k in (
|
|
270
|
+
"role",
|
|
271
|
+
"key_role",
|
|
272
|
+
"semantic_type",
|
|
273
|
+
"tags",
|
|
274
|
+
"tests",
|
|
275
|
+
"relationships",
|
|
276
|
+
"meta",
|
|
277
|
+
"type",
|
|
278
|
+
"actual_type",
|
|
279
|
+
"declared_type",
|
|
280
|
+
):
|
|
281
|
+
if k in profile:
|
|
282
|
+
attrs[k] = profile[k]
|
|
283
|
+
return _SchemaSearchEntry(
|
|
284
|
+
location=loc,
|
|
285
|
+
is_column=True,
|
|
286
|
+
consulted=consulted,
|
|
287
|
+
text_fields=text_fields,
|
|
288
|
+
attrs=attrs,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# ---- Match helpers ------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def _compile_keyword(keyword: str, regex: bool) -> re.Pattern[str] | None:
|
|
295
|
+
if not keyword:
|
|
296
|
+
return None
|
|
297
|
+
if regex:
|
|
298
|
+
try:
|
|
299
|
+
return re.compile(keyword, re.IGNORECASE)
|
|
300
|
+
except re.error as exc:
|
|
301
|
+
raise InvalidRegex(f"invalid regex: {exc}") from exc
|
|
302
|
+
return re.compile(re.escape(keyword), re.IGNORECASE)
|
|
303
|
+
|
|
304
|
+
def _matches_filters(
|
|
305
|
+
self,
|
|
306
|
+
entry: _SchemaSearchEntry,
|
|
307
|
+
role: str | None,
|
|
308
|
+
tag: str | None,
|
|
309
|
+
has_test: str | None,
|
|
310
|
+
meta: dict[str, str] | None,
|
|
311
|
+
column_name: str | None,
|
|
312
|
+
table_name: str | None,
|
|
313
|
+
fk_to: str | None,
|
|
314
|
+
) -> bool:
|
|
315
|
+
if role is not None:
|
|
316
|
+
if not entry.is_column or entry.attrs.get("role") != role:
|
|
317
|
+
return False
|
|
318
|
+
if tag is not None:
|
|
319
|
+
tags = entry.attrs.get("tags") or []
|
|
320
|
+
if tag not in tags:
|
|
321
|
+
return False
|
|
322
|
+
if has_test is not None:
|
|
323
|
+
if has_test == "relationships":
|
|
324
|
+
if not entry.attrs.get("relationships"):
|
|
325
|
+
return False
|
|
326
|
+
else:
|
|
327
|
+
tests = entry.attrs.get("tests") or []
|
|
328
|
+
if not any(
|
|
329
|
+
isinstance(t, dict) and t.get("name") == has_test for t in tests
|
|
330
|
+
):
|
|
331
|
+
return False
|
|
332
|
+
if fk_to is not None:
|
|
333
|
+
rels = entry.attrs.get("relationships") or []
|
|
334
|
+
if not any(
|
|
335
|
+
isinstance(r, dict) and r.get("to_table") == fk_to for r in rels
|
|
336
|
+
):
|
|
337
|
+
return False
|
|
338
|
+
if column_name is not None:
|
|
339
|
+
if not entry.is_column:
|
|
340
|
+
return False
|
|
341
|
+
cname = entry.location.rsplit(".", 1)[-1]
|
|
342
|
+
if not fnmatch.fnmatch(cname, column_name):
|
|
343
|
+
return False
|
|
344
|
+
if table_name is not None:
|
|
345
|
+
parts = entry.location.split(".")
|
|
346
|
+
# source.schema.table[.column] — table is index 2.
|
|
347
|
+
if len(parts) < 3 or not fnmatch.fnmatch(parts[2], table_name):
|
|
348
|
+
return False
|
|
349
|
+
if meta is not None:
|
|
350
|
+
entry_meta = entry.attrs.get("meta") or {}
|
|
351
|
+
if not isinstance(entry_meta, dict):
|
|
352
|
+
return False
|
|
353
|
+
for k, v in meta.items():
|
|
354
|
+
if str(entry_meta.get(k)) != v:
|
|
355
|
+
return False
|
|
356
|
+
return True
|
|
357
|
+
|
|
358
|
+
def _missing_hits(
|
|
359
|
+
self, entry: _SchemaSearchEntry, field_name: str
|
|
360
|
+
) -> list[SchemaSearchHit]:
|
|
361
|
+
present = any(name == field_name for name, _ in entry.text_fields)
|
|
362
|
+
if present:
|
|
363
|
+
return []
|
|
364
|
+
return [
|
|
365
|
+
SchemaSearchHit(
|
|
366
|
+
location=entry.location,
|
|
367
|
+
matched_field=f"_missing:{field_name}",
|
|
368
|
+
snippet="",
|
|
369
|
+
source=self._provenance_for(entry, field_name),
|
|
370
|
+
value="",
|
|
371
|
+
attrs=dict(entry.attrs),
|
|
372
|
+
)
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
def _unfiltered_hits(
|
|
376
|
+
self, entry: _SchemaSearchEntry, scope: list[str] | None
|
|
377
|
+
) -> list[SchemaSearchHit]:
|
|
378
|
+
hits: list[SchemaSearchHit] = []
|
|
379
|
+
for matched_field, value in entry.text_fields:
|
|
380
|
+
if scope and not _scope_matches(matched_field, scope):
|
|
381
|
+
continue
|
|
382
|
+
hits.append(
|
|
383
|
+
SchemaSearchHit(
|
|
384
|
+
location=entry.location,
|
|
385
|
+
matched_field=matched_field,
|
|
386
|
+
snippet=_snippet(value, None),
|
|
387
|
+
source=self._provenance_for(entry, matched_field),
|
|
388
|
+
value=value,
|
|
389
|
+
attrs=dict(entry.attrs),
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
return hits
|
|
393
|
+
|
|
394
|
+
def _keyword_hits(
|
|
395
|
+
self,
|
|
396
|
+
entry: _SchemaSearchEntry,
|
|
397
|
+
pattern: re.Pattern[str] | None,
|
|
398
|
+
scope: list[str] | None,
|
|
399
|
+
) -> list[SchemaSearchHit]:
|
|
400
|
+
if pattern is None:
|
|
401
|
+
return []
|
|
402
|
+
hits: list[SchemaSearchHit] = []
|
|
403
|
+
for matched_field, value in entry.text_fields:
|
|
404
|
+
if scope and not _scope_matches(matched_field, scope):
|
|
405
|
+
continue
|
|
406
|
+
m = pattern.search(value)
|
|
407
|
+
if m is None:
|
|
408
|
+
continue
|
|
409
|
+
hits.append(
|
|
410
|
+
SchemaSearchHit(
|
|
411
|
+
location=entry.location,
|
|
412
|
+
matched_field=matched_field,
|
|
413
|
+
snippet=_snippet(value, m),
|
|
414
|
+
source=self._provenance_for(entry, matched_field),
|
|
415
|
+
value=value,
|
|
416
|
+
attrs=dict(entry.attrs),
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
return hits
|
|
420
|
+
|
|
421
|
+
@staticmethod
|
|
422
|
+
def _provenance_for(entry: _SchemaSearchEntry, matched_field: str) -> str:
|
|
423
|
+
"""Attribute a hit to a single layer using the resolver's consulted set.
|
|
424
|
+
|
|
425
|
+
Cache-only path: everything came from super_schema. Otherwise we
|
|
426
|
+
classify the matched field — manifest-only fields (description,
|
|
427
|
+
tag, tests, owner, meta, relationships, declared_type) attribute
|
|
428
|
+
to ``dbt_manifest`` when the manifest was consulted; everything
|
|
429
|
+
else (names, types) attributes to ``dbt_adapter``.
|
|
430
|
+
"""
|
|
431
|
+
if entry.consulted == ("super_schema",):
|
|
432
|
+
return "super_schema"
|
|
433
|
+
manifest_field = matched_field in {"description", "tag", "owner"} or any(
|
|
434
|
+
matched_field.startswith(p) for p in ("tests.", "meta.")
|
|
435
|
+
)
|
|
436
|
+
if manifest_field and "dbt_manifest" in entry.consulted:
|
|
437
|
+
return "dbt_manifest"
|
|
438
|
+
return "dbt_adapter"
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# ---- Module helpers --------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _scope_matches(matched_field: str, scope: list[str]) -> bool:
|
|
445
|
+
"""Match an emitted matched_field against a list of scope prefixes."""
|
|
446
|
+
head = matched_field.split(".", 1)[0]
|
|
447
|
+
return head in scope or matched_field in scope
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _snippet(value: str, match: re.Match[str] | None) -> str:
|
|
451
|
+
"""Whole-field snippet when short; an 80-char window centered on first match."""
|
|
452
|
+
if len(value) < _SNIPPET_INLINE_LIMIT:
|
|
453
|
+
return value
|
|
454
|
+
if match is None:
|
|
455
|
+
return value[:_SNIPPET_WINDOW]
|
|
456
|
+
span = match.start()
|
|
457
|
+
half = _SNIPPET_WINDOW // 2
|
|
458
|
+
start = max(0, span - half)
|
|
459
|
+
end = min(len(value), start + _SNIPPET_WINDOW)
|
|
460
|
+
start = max(0, end - _SNIPPET_WINDOW)
|
|
461
|
+
return value[start:end]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Plugin-boundary schema sources.
|
|
2
|
+
|
|
3
|
+
Schema sources are horizontal contributors of facts about a warehouse
|
|
4
|
+
schema. Implementations are independently testable; the resolver composes
|
|
5
|
+
contributions across sources into the wire response.
|
|
6
|
+
|
|
7
|
+
Two peers ship today:
|
|
8
|
+
|
|
9
|
+
* `SuperSchemaSource` — warm cache backed by ``target/super_schema.json``.
|
|
10
|
+
Surfaces the rich profile (stats, distributions, semantic types, role
|
|
11
|
+
tags, enum lists) that a profiling run computed.
|
|
12
|
+
* `DbtSchemaSource` — cold-start, composes a dbt-core adapter + lazy
|
|
13
|
+
``target/manifest.json`` reader. Surfaces only what's directly
|
|
14
|
+
available — no inference, no naming heuristics. Honest empty.
|
|
15
|
+
|
|
16
|
+
Each source exposes ``name``, ``generated_at``, ``list_schemas``,
|
|
17
|
+
``list_tables``, ``profile_table``, and ``describe_query``. Methods return
|
|
18
|
+
bare-scalar ``dict | None`` (no per-field provenance wrapping; provenance
|
|
19
|
+
lives only in the response-level ``_meta`` footer).
|
|
20
|
+
``None`` means "this source has nothing to contribute" — silent
|
|
21
|
+
no-contribution, not an error. The resolver falls through to the next
|
|
22
|
+
source. Field-name conventions communicate origin: ``actual_type`` came
|
|
23
|
+
from the adapter; ``declared_type`` / ``description`` / ``tests`` /
|
|
24
|
+
``relationships`` came from the manifest. The resolver stamps a single
|
|
25
|
+
response-level ``_meta`` footer with ``retrieved_at``, ``sources_consulted``,
|
|
26
|
+
and (when super_schema contributed) ``cache_built_at``.
|
|
27
|
+
|
|
28
|
+
Each source is *per-source* by construction: ``SuperSchemaSource`` is one
|
|
29
|
+
instance per project (its cache file is per-project); ``DbtSchemaSource``
|
|
30
|
+
is one instance per warehouse adapter. The resolver routes calls to the
|
|
31
|
+
right instance, so the methods don't take a ``source`` argument.
|
|
32
|
+
"""
|