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,527 @@
|
|
|
1
|
+
"""Pydantic model introspection — Layer 1 of the two-layer schema IR.
|
|
2
|
+
|
|
3
|
+
Walks AuthoredFace and all reachable authored models once, producing an
|
|
4
|
+
AuthorableSchema IR. Renderers (json_schema, prompt, markdown, erd) consume
|
|
5
|
+
this IR — no rendering logic lives here.
|
|
6
|
+
|
|
7
|
+
Entry point: introspect() -> AuthorableSchema
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import dataclasses
|
|
13
|
+
import typing
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, ForwardRef, Literal, get_args, get_origin
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
from pydantic.fields import FieldInfo
|
|
19
|
+
from pydantic_core import PydanticUndefinedType
|
|
20
|
+
|
|
21
|
+
from dataface.core.compile.models.face.authored import AuthoredFace
|
|
22
|
+
from dataface.core.compile.models.factories import _PatchBase
|
|
23
|
+
from dataface.core.compile.models.query.authored import AuthoredQuery
|
|
24
|
+
from dataface.core.compile.models.source import (
|
|
25
|
+
BigQuerySourceConfig,
|
|
26
|
+
CsvSourceConfig,
|
|
27
|
+
DbtProfileSourceConfig,
|
|
28
|
+
DuckDBSourceConfig,
|
|
29
|
+
HttpSourceConfig,
|
|
30
|
+
JsonSourceConfig,
|
|
31
|
+
MySQLSourceConfig,
|
|
32
|
+
ParquetSourceConfig,
|
|
33
|
+
PostgresSourceConfig,
|
|
34
|
+
RedshiftSourceConfig,
|
|
35
|
+
SnowflakeSourceConfig,
|
|
36
|
+
)
|
|
37
|
+
from dataface.core.compile.models.style.compiled import PaginationConfig
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclasses.dataclass
|
|
41
|
+
class SchemaField:
|
|
42
|
+
name: str
|
|
43
|
+
description: str
|
|
44
|
+
type_repr: str
|
|
45
|
+
required: bool
|
|
46
|
+
default: Any
|
|
47
|
+
default_repr: (
|
|
48
|
+
str | None
|
|
49
|
+
) # human-readable default for display (None = no meaningful default)
|
|
50
|
+
enum_values: list[str] | None
|
|
51
|
+
nested_models: list[str] # class names of nested AuthorableModels (empty = none)
|
|
52
|
+
container: str | None = None # "list" or "dict" if the model is in a container
|
|
53
|
+
extra_union_types: list[str] = dataclasses.field(
|
|
54
|
+
default_factory=list
|
|
55
|
+
) # primitive type names alongside models or enums in a union
|
|
56
|
+
cascades: bool = False # True when value propagates (face or style cascade)
|
|
57
|
+
cascade_key: str | None = None # face cascade: context key
|
|
58
|
+
cascade_targets: tuple[str, ...] = () # style cascade: sibling fields this fills in
|
|
59
|
+
is_extra_key: bool = (
|
|
60
|
+
False # True for synthetic '<name>' fields from __pydantic_extra__
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclasses.dataclass
|
|
65
|
+
class AuthorableModel:
|
|
66
|
+
name: str
|
|
67
|
+
doc: str
|
|
68
|
+
fields: list[SchemaField]
|
|
69
|
+
generated: bool = False # True for _PatchBase subclasses (build_patch_model output)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclasses.dataclass
|
|
73
|
+
class AuthorableSchema:
|
|
74
|
+
root: str
|
|
75
|
+
models: dict[str, AuthorableModel]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _unwrap_annotated(annotation: Any) -> Any:
|
|
79
|
+
"""Strip Annotated[X, *metadata] → X. Idempotent on plain types."""
|
|
80
|
+
if hasattr(annotation, "__metadata__"):
|
|
81
|
+
return annotation.__origin__
|
|
82
|
+
return annotation
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _is_authored_model(cls: Any) -> bool:
|
|
86
|
+
"""Return True if cls should be included in the IR.
|
|
87
|
+
|
|
88
|
+
Includes authored.* models, primitives, and generated _PatchBase subclasses.
|
|
89
|
+
"""
|
|
90
|
+
if not (isinstance(cls, type) and issubclass(cls, BaseModel)):
|
|
91
|
+
return False
|
|
92
|
+
if issubclass(cls, _PatchBase):
|
|
93
|
+
return True
|
|
94
|
+
module = getattr(cls, "__module__", "")
|
|
95
|
+
return ".authored" in module or module.endswith("primitives")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _extract_enum_values(annotation: Any) -> list[str] | None:
|
|
99
|
+
"""Return string enum values if annotation is an Enum or Literal, else None."""
|
|
100
|
+
annotation = _unwrap_annotated(annotation)
|
|
101
|
+
origin = get_origin(annotation)
|
|
102
|
+
args = get_args(annotation)
|
|
103
|
+
|
|
104
|
+
# Literal["foo", "bar", ...]
|
|
105
|
+
if origin is Literal:
|
|
106
|
+
return [str(a) for a in args if a is not None]
|
|
107
|
+
|
|
108
|
+
# str Enum subclass
|
|
109
|
+
if isinstance(annotation, type) and issubclass(annotation, Enum):
|
|
110
|
+
return [e.value for e in annotation]
|
|
111
|
+
|
|
112
|
+
# Union — look for a Literal or Enum inside (e.g. ChartType | None)
|
|
113
|
+
if origin is type(None) or origin is None:
|
|
114
|
+
return None
|
|
115
|
+
# handle Union (typing.Union has origin = types.UnionType or typing.Union)
|
|
116
|
+
if args:
|
|
117
|
+
for arg in args:
|
|
118
|
+
result = _extract_enum_values(arg)
|
|
119
|
+
if result:
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _nested_model_names(annotation: Any) -> list[str]:
|
|
126
|
+
"""Return all authored model class names reachable from annotation.
|
|
127
|
+
|
|
128
|
+
Returns an empty list if no authored models are found.
|
|
129
|
+
For plain types, returns a one-element list; for discriminated unions,
|
|
130
|
+
returns one entry per branch.
|
|
131
|
+
"""
|
|
132
|
+
annotation = _unwrap_annotated(annotation)
|
|
133
|
+
if _is_authored_model(annotation):
|
|
134
|
+
return [annotation.__name__]
|
|
135
|
+
|
|
136
|
+
origin = get_origin(annotation)
|
|
137
|
+
args = get_args(annotation)
|
|
138
|
+
if not args:
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
# dict[K, V] — recurse into the value type only
|
|
142
|
+
if origin is dict and len(args) == 2:
|
|
143
|
+
return _nested_model_names(args[1])
|
|
144
|
+
|
|
145
|
+
# list[SomeModel] or Union/Optional — collect all authored branches, dedup by class name
|
|
146
|
+
result: list[str] = []
|
|
147
|
+
for arg in args:
|
|
148
|
+
result.extend(_nested_model_names(arg))
|
|
149
|
+
return list(dict.fromkeys(result))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _all_model_names_in_annotation(annotation: Any) -> list[str]:
|
|
153
|
+
"""Return class names of ALL BaseModel subclasses reachable from annotation.
|
|
154
|
+
|
|
155
|
+
Unlike _nested_model_names, this is not limited to "authored" modules.
|
|
156
|
+
Used for __pydantic_extra__ synthetic fields where the value type may
|
|
157
|
+
reference source connector configs (models.source module).
|
|
158
|
+
"""
|
|
159
|
+
annotation = _unwrap_annotated(annotation)
|
|
160
|
+
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
161
|
+
return [annotation.__name__]
|
|
162
|
+
|
|
163
|
+
origin = get_origin(annotation)
|
|
164
|
+
args = get_args(annotation)
|
|
165
|
+
if not args:
|
|
166
|
+
return []
|
|
167
|
+
|
|
168
|
+
if origin is dict and len(args) == 2:
|
|
169
|
+
return _all_model_names_in_annotation(args[1])
|
|
170
|
+
|
|
171
|
+
result: list[str] = []
|
|
172
|
+
for arg in args:
|
|
173
|
+
if arg is not type(None):
|
|
174
|
+
result.extend(_all_model_names_in_annotation(arg))
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _type_repr(annotation: Any) -> str:
|
|
179
|
+
"""Human-readable type string for display in docs/prompts."""
|
|
180
|
+
annotation = _unwrap_annotated(annotation)
|
|
181
|
+
if annotation is None or annotation is type(None):
|
|
182
|
+
return "None"
|
|
183
|
+
if isinstance(annotation, ForwardRef):
|
|
184
|
+
return annotation.__forward_arg__
|
|
185
|
+
|
|
186
|
+
origin = get_origin(annotation)
|
|
187
|
+
args = get_args(annotation)
|
|
188
|
+
|
|
189
|
+
if origin is Literal:
|
|
190
|
+
values = [repr(a) for a in args]
|
|
191
|
+
return f"one of: {', '.join(values)}"
|
|
192
|
+
|
|
193
|
+
# Guard: only match plain types, not generic aliases (list[X], dict[K,V])
|
|
194
|
+
# On Python 3.10, isinstance(list[X], type) can return True for GenericAlias.
|
|
195
|
+
if origin is None and isinstance(annotation, type):
|
|
196
|
+
if issubclass(annotation, Enum):
|
|
197
|
+
return f"one of: {', '.join(e.value for e in annotation)}"
|
|
198
|
+
return annotation.__name__
|
|
199
|
+
|
|
200
|
+
if origin is list:
|
|
201
|
+
if args:
|
|
202
|
+
return f"list[{_type_repr(args[0])}]"
|
|
203
|
+
return "list"
|
|
204
|
+
|
|
205
|
+
if origin is dict:
|
|
206
|
+
if len(args) == 2:
|
|
207
|
+
return f"dict[{_type_repr(args[0])}, {_type_repr(args[1])}]"
|
|
208
|
+
return "dict"
|
|
209
|
+
|
|
210
|
+
# Union types (X | Y | None) — deduplicate branches (discriminated unions may
|
|
211
|
+
# have the same class under multiple Tag() aliases, e.g. BarChart for bar+histogram)
|
|
212
|
+
if args:
|
|
213
|
+
non_none = [a for a in args if a is not type(None)]
|
|
214
|
+
nullable = any(a is type(None) for a in args)
|
|
215
|
+
parts = list(dict.fromkeys(_type_repr(a) for a in non_none))
|
|
216
|
+
result = " | ".join(parts)
|
|
217
|
+
if nullable and len(non_none) > 0:
|
|
218
|
+
result += " | None"
|
|
219
|
+
return result
|
|
220
|
+
|
|
221
|
+
if annotation is Any:
|
|
222
|
+
return "Any"
|
|
223
|
+
return str(annotation)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _get_container(annotation: Any) -> str | None:
|
|
227
|
+
"""Return 'list' or 'dict' if annotation wraps a nested model in a container."""
|
|
228
|
+
annotation = _unwrap_annotated(annotation)
|
|
229
|
+
origin = get_origin(annotation)
|
|
230
|
+
args = get_args(annotation)
|
|
231
|
+
if origin is list:
|
|
232
|
+
return "list"
|
|
233
|
+
if origin is dict:
|
|
234
|
+
return "dict"
|
|
235
|
+
if args:
|
|
236
|
+
non_none = [a for a in args if a is not type(None)]
|
|
237
|
+
if len(non_none) == 1:
|
|
238
|
+
return _get_container(non_none[0])
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
_PRIMITIVE_NAMES = frozenset({"str", "int", "float", "bool"})
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _default_repr(field_info: FieldInfo) -> str | None:
|
|
246
|
+
"""Return a human-readable default string for display, or None if no meaningful default."""
|
|
247
|
+
if field_info.is_required():
|
|
248
|
+
return None
|
|
249
|
+
default = field_info.default
|
|
250
|
+
if isinstance(default, PydanticUndefinedType):
|
|
251
|
+
factory = field_info.default_factory
|
|
252
|
+
if factory is dict:
|
|
253
|
+
return "{}"
|
|
254
|
+
if factory is list:
|
|
255
|
+
return "[]"
|
|
256
|
+
return None
|
|
257
|
+
if default is None:
|
|
258
|
+
return None
|
|
259
|
+
if isinstance(default, bool):
|
|
260
|
+
return str(default).lower()
|
|
261
|
+
if isinstance(default, str):
|
|
262
|
+
return f'"{default}"'
|
|
263
|
+
if isinstance(default, (int, float)):
|
|
264
|
+
return str(default)
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _extra_union_types(annotation: Any) -> list[str]:
|
|
269
|
+
"""Primitive type names that sit alongside models or enums in a union.
|
|
270
|
+
|
|
271
|
+
Returns the names (e.g. ["str", "bool"]) so the renderer can include
|
|
272
|
+
them in anyOf alongside $ref or enum branches.
|
|
273
|
+
Only meaningful for bare Union types — list/dict containers return [].
|
|
274
|
+
"""
|
|
275
|
+
annotation = _unwrap_annotated(annotation)
|
|
276
|
+
origin = get_origin(annotation)
|
|
277
|
+
args = get_args(annotation)
|
|
278
|
+
if not args or origin in (list, dict):
|
|
279
|
+
return []
|
|
280
|
+
|
|
281
|
+
result: list[str] = []
|
|
282
|
+
for arg in args:
|
|
283
|
+
if arg is type(None):
|
|
284
|
+
continue
|
|
285
|
+
if _is_authored_model(arg):
|
|
286
|
+
continue
|
|
287
|
+
arg_origin = get_origin(arg)
|
|
288
|
+
if arg_origin is Literal:
|
|
289
|
+
continue
|
|
290
|
+
if isinstance(arg, type) and issubclass(arg, Enum):
|
|
291
|
+
continue
|
|
292
|
+
if (
|
|
293
|
+
arg_origin is None
|
|
294
|
+
and isinstance(arg, type)
|
|
295
|
+
and arg.__name__ in _PRIMITIVE_NAMES
|
|
296
|
+
):
|
|
297
|
+
result.append(arg.__name__)
|
|
298
|
+
return result
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _build_schema_field(
|
|
302
|
+
name: str, field_info: FieldInfo, annotation: Any
|
|
303
|
+
) -> SchemaField:
|
|
304
|
+
from dataface.core.compile.models.markers import Cascade
|
|
305
|
+
|
|
306
|
+
required = field_info.is_required()
|
|
307
|
+
default = field_info.default if not required else dataclasses.MISSING
|
|
308
|
+
cascade = next(
|
|
309
|
+
(m for m in (field_info.metadata or []) if isinstance(m, Cascade)), None
|
|
310
|
+
)
|
|
311
|
+
return SchemaField(
|
|
312
|
+
name=name,
|
|
313
|
+
description=field_info.description or "",
|
|
314
|
+
type_repr=_type_repr(annotation),
|
|
315
|
+
required=required,
|
|
316
|
+
default=default,
|
|
317
|
+
default_repr=_default_repr(field_info),
|
|
318
|
+
enum_values=_extract_enum_values(annotation),
|
|
319
|
+
nested_models=_nested_model_names(annotation),
|
|
320
|
+
container=_get_container(annotation),
|
|
321
|
+
extra_union_types=_extra_union_types(annotation),
|
|
322
|
+
cascades=cascade is not None,
|
|
323
|
+
cascade_key=(cascade.key or name) if cascade is not None else None,
|
|
324
|
+
cascade_targets=cascade.targets if cascade is not None else (),
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _collect_models(
|
|
329
|
+
model_cls: type[BaseModel],
|
|
330
|
+
collected: dict[str, AuthorableModel],
|
|
331
|
+
) -> None:
|
|
332
|
+
"""Recursively walk model_cls and all reachable authored sub-models."""
|
|
333
|
+
name = model_cls.__name__
|
|
334
|
+
if name in collected:
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
# Use __doc__ directly (not inspect.getdoc) to avoid MRO walk that pulls in
|
|
338
|
+
# Pydantic BaseModel's "[Models](../concepts/models.md)" doc for generated patches.
|
|
339
|
+
raw_doc = model_cls.__doc__ or ""
|
|
340
|
+
doc = ""
|
|
341
|
+
for line in raw_doc.split("\n"):
|
|
342
|
+
stripped = line.strip()
|
|
343
|
+
if stripped and not stripped.startswith(("!!! ", "??? ", ":::")):
|
|
344
|
+
doc = stripped
|
|
345
|
+
break
|
|
346
|
+
|
|
347
|
+
fields: list[SchemaField] = []
|
|
348
|
+
|
|
349
|
+
for field_name, field_info in model_cls.model_fields.items():
|
|
350
|
+
if field_info.exclude:
|
|
351
|
+
continue
|
|
352
|
+
annotation = field_info.annotation
|
|
353
|
+
display_name = field_info.alias or field_name
|
|
354
|
+
sf = _build_schema_field(display_name, field_info, annotation)
|
|
355
|
+
fields.append(sf)
|
|
356
|
+
|
|
357
|
+
# Synthesize a '<name>' field for models with typed __pydantic_extra__.
|
|
358
|
+
# get_type_hints() is needed because from __future__ import annotations turns
|
|
359
|
+
# annotations into strings; vars() gives the raw (unresolved) form.
|
|
360
|
+
try:
|
|
361
|
+
hints = typing.get_type_hints(model_cls)
|
|
362
|
+
extra_annotation = hints.get("__pydantic_extra__")
|
|
363
|
+
except NameError:
|
|
364
|
+
extra_annotation = None
|
|
365
|
+
if extra_annotation is not None:
|
|
366
|
+
ea_origin = get_origin(extra_annotation)
|
|
367
|
+
ea_args = get_args(extra_annotation)
|
|
368
|
+
if ea_origin is dict and len(ea_args) == 2:
|
|
369
|
+
value_annotation = ea_args[1]
|
|
370
|
+
fields.append(
|
|
371
|
+
SchemaField(
|
|
372
|
+
name="<name>",
|
|
373
|
+
description="Additional named entry.",
|
|
374
|
+
type_repr=_type_repr(value_annotation),
|
|
375
|
+
required=False,
|
|
376
|
+
default=None,
|
|
377
|
+
default_repr=None,
|
|
378
|
+
enum_values=None,
|
|
379
|
+
nested_models=_all_model_names_in_annotation(value_annotation),
|
|
380
|
+
is_extra_key=True,
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
collected[name] = AuthorableModel(
|
|
385
|
+
name=name,
|
|
386
|
+
doc=doc,
|
|
387
|
+
fields=fields,
|
|
388
|
+
generated=issubclass(model_cls, _PatchBase),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Recurse into nested authored models
|
|
392
|
+
for sf in fields:
|
|
393
|
+
for model_name in sf.nested_models:
|
|
394
|
+
if model_name not in collected:
|
|
395
|
+
nested_cls = _find_nested_class(model_cls, model_name)
|
|
396
|
+
if nested_cls is not None and _is_authored_model(nested_cls):
|
|
397
|
+
_collect_models(nested_cls, collected)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _find_nested_class(parent_cls: type[BaseModel], class_name: str) -> type | None:
|
|
401
|
+
"""Find a nested model class by name from a parent model's field annotations."""
|
|
402
|
+
for field_info in parent_cls.model_fields.values():
|
|
403
|
+
annotation = field_info.annotation
|
|
404
|
+
cls = _find_in_annotation(annotation, class_name)
|
|
405
|
+
if cls is not None:
|
|
406
|
+
return cls
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _find_in_annotation(annotation: Any, class_name: str) -> type | None:
|
|
411
|
+
"""Recursively search annotation tree for a class with the given name."""
|
|
412
|
+
if isinstance(annotation, type) and annotation.__name__ == class_name:
|
|
413
|
+
return annotation
|
|
414
|
+
args = get_args(annotation)
|
|
415
|
+
for arg in args:
|
|
416
|
+
result = _find_in_annotation(arg, class_name)
|
|
417
|
+
if result is not None:
|
|
418
|
+
return result
|
|
419
|
+
return None
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def introspect() -> AuthorableSchema:
|
|
423
|
+
"""Walk AuthoredFace and all reachable authored models, returning an IR.
|
|
424
|
+
|
|
425
|
+
The returned AuthorableSchema is the single source of truth consumed by
|
|
426
|
+
render_json_schema, render_prompt, render_markdown, and render_erd.
|
|
427
|
+
"""
|
|
428
|
+
collected: dict[str, AuthorableModel] = {}
|
|
429
|
+
_collect_models(AuthoredFace, collected)
|
|
430
|
+
# Walk extra roots not reachable via AuthoredFace type annotations.
|
|
431
|
+
# AuthoredQuery uses untyped dicts in the authored YAML (normalizer coerces).
|
|
432
|
+
# Source connector configs are reached via sources: dict[str, Any] — no type annotation.
|
|
433
|
+
# Per-family chart patch classes are not reachable via AuthoredFace annotations either
|
|
434
|
+
# (AuthoredChart is a discriminated union alias, not a typed field on AuthoredFace).
|
|
435
|
+
from dataface.core.compile.models.chart.authored import ( # noqa: PLC0415
|
|
436
|
+
AreaChart,
|
|
437
|
+
BarChart,
|
|
438
|
+
CalloutChart,
|
|
439
|
+
GeoshapeChart,
|
|
440
|
+
HeatmapChart,
|
|
441
|
+
KpiChart,
|
|
442
|
+
LayeredChart,
|
|
443
|
+
LineChart,
|
|
444
|
+
PieChart,
|
|
445
|
+
PointMapChart,
|
|
446
|
+
ScatterChart,
|
|
447
|
+
SparkBarChart,
|
|
448
|
+
TableChart,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
for extra in (
|
|
452
|
+
BarChart,
|
|
453
|
+
LineChart,
|
|
454
|
+
AreaChart,
|
|
455
|
+
ScatterChart,
|
|
456
|
+
HeatmapChart,
|
|
457
|
+
PieChart,
|
|
458
|
+
KpiChart,
|
|
459
|
+
TableChart,
|
|
460
|
+
PointMapChart,
|
|
461
|
+
GeoshapeChart,
|
|
462
|
+
LayeredChart,
|
|
463
|
+
CalloutChart,
|
|
464
|
+
SparkBarChart,
|
|
465
|
+
AuthoredQuery,
|
|
466
|
+
PaginationConfig,
|
|
467
|
+
PostgresSourceConfig,
|
|
468
|
+
SnowflakeSourceConfig,
|
|
469
|
+
BigQuerySourceConfig,
|
|
470
|
+
RedshiftSourceConfig,
|
|
471
|
+
MySQLSourceConfig,
|
|
472
|
+
DuckDBSourceConfig,
|
|
473
|
+
CsvSourceConfig,
|
|
474
|
+
ParquetSourceConfig,
|
|
475
|
+
JsonSourceConfig,
|
|
476
|
+
HttpSourceConfig,
|
|
477
|
+
DbtProfileSourceConfig,
|
|
478
|
+
):
|
|
479
|
+
_collect_models(extra, collected)
|
|
480
|
+
# Build a synthetic "AuthoredChart" IR entry as a union of all per-family patches.
|
|
481
|
+
# This gives prompt.py and vscode_schema.py a single named surface to BFS from.
|
|
482
|
+
# The entry lists all fields across all families — renderers that need per-family
|
|
483
|
+
# discrimination should walk the individual *Patch entries directly.
|
|
484
|
+
all_patch_fields: dict[str, SchemaField] = {}
|
|
485
|
+
for patch_name in (
|
|
486
|
+
"BarChart",
|
|
487
|
+
"LineChart",
|
|
488
|
+
"AreaChart",
|
|
489
|
+
"ScatterChart",
|
|
490
|
+
"HeatmapChart",
|
|
491
|
+
"PieChart",
|
|
492
|
+
"KpiChart",
|
|
493
|
+
"TableChart",
|
|
494
|
+
"PointMapChart",
|
|
495
|
+
"GeoshapeChart",
|
|
496
|
+
"LayeredChart",
|
|
497
|
+
"CalloutChart",
|
|
498
|
+
"SparkBarChart",
|
|
499
|
+
):
|
|
500
|
+
if patch_name not in collected:
|
|
501
|
+
continue
|
|
502
|
+
for f in collected[patch_name].fields:
|
|
503
|
+
if f.name not in all_patch_fields:
|
|
504
|
+
all_patch_fields[f.name] = f
|
|
505
|
+
if all_patch_fields:
|
|
506
|
+
from dataface.core.compile.models.chart.authored import ( # noqa: PLC0415
|
|
507
|
+
ChartType,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
chart_type_values = [t.value for t in ChartType]
|
|
511
|
+
chart_patch_fields = [
|
|
512
|
+
(
|
|
513
|
+
dataclasses.replace(f, enum_values=chart_type_values)
|
|
514
|
+
if f.name == "type"
|
|
515
|
+
else f
|
|
516
|
+
)
|
|
517
|
+
for f in all_patch_fields.values()
|
|
518
|
+
]
|
|
519
|
+
collected["AuthoredChart"] = AuthorableModel(
|
|
520
|
+
name="AuthoredChart",
|
|
521
|
+
doc=(
|
|
522
|
+
"Discriminated union of per-family chart patches. "
|
|
523
|
+
"type: is mandatory; missing or unknown type raises ValidationError."
|
|
524
|
+
),
|
|
525
|
+
fields=chart_patch_fields,
|
|
526
|
+
)
|
|
527
|
+
return AuthorableSchema(root="AuthoredFace", models=collected)
|