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,611 @@
|
|
|
1
|
+
"""Source configuration types for database connections and data sources.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE
|
|
4
|
+
Purpose: Define Pydantic models for source configurations.
|
|
5
|
+
|
|
6
|
+
Sources represent "where data comes from" - databases, CSV files, HTTP APIs, etc.
|
|
7
|
+
Field names match dbt's profiles.yml exactly where applicable.
|
|
8
|
+
|
|
9
|
+
Design Principles:
|
|
10
|
+
- Match dbt field names exactly for database types
|
|
11
|
+
- Support env_var() syntax like dbt profiles
|
|
12
|
+
- Use discriminated union via 'type' field
|
|
13
|
+
- DB source configs inherit extra="allow" from _DbSourceShim (ADR-009 passthrough contract)
|
|
14
|
+
- File/HTTP/dbt_profile configs inherit extra="forbid" from BaseSourceConfig (closed contracts)
|
|
15
|
+
|
|
16
|
+
Dependencies:
|
|
17
|
+
- pydantic (BaseModel, Field, field_validator)
|
|
18
|
+
|
|
19
|
+
Read-only posture per warehouse:
|
|
20
|
+
DuckDB opens connections with read_only=True in-process (see SqlAdapter.read_only).
|
|
21
|
+
Every other warehouse lacks a native read-only driver flag. For those, the operator
|
|
22
|
+
must bind SELECT-only credentials at the database/IAM layer:
|
|
23
|
+
|
|
24
|
+
| Warehouse | In-process flag | Operator posture |
|
|
25
|
+
|-----------------|-----------------|-------------------------------------------------------|
|
|
26
|
+
| DuckDB | read_only=True | Handled in-process by SqlAdapter |
|
|
27
|
+
| Postgres | none | Bind a SELECT-only role to the dbt profile credential |
|
|
28
|
+
| MySQL/MariaDB | none | Bind a SELECT-only user to the dbt profile credential |
|
|
29
|
+
| Snowflake | none | Bind a SELECT-only role to the Snowflake user/key-pair|
|
|
30
|
+
| BigQuery | none | Bind credentials to roles/bigquery.dataViewer |
|
|
31
|
+
| Databricks | none | Unity Catalog: bind service principal to SELECT-only |
|
|
32
|
+
| Redshift | none | IAM/grants: bind to a SELECT-only IAM role or DB user |
|
|
33
|
+
| SQL Server | none | Login-level SELECT-only grants |
|
|
34
|
+
|
|
35
|
+
sql_guard (allowlist enforcement) provides the in-process defense for all warehouses.
|
|
36
|
+
SELECT-only credentials are the connection-level defense for non-DuckDB warehouses.
|
|
37
|
+
|
|
38
|
+
See also:
|
|
39
|
+
- config.py: Project-level configuration
|
|
40
|
+
- models/query/compiled.py: Query interface types
|
|
41
|
+
- normalizer.py: Resolves source references
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
import os
|
|
47
|
+
import re
|
|
48
|
+
from typing import Any, Literal
|
|
49
|
+
|
|
50
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
51
|
+
|
|
52
|
+
# ============================================================================
|
|
53
|
+
# BIGQUERY AUTH HELPERS
|
|
54
|
+
# ============================================================================
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def infer_bq_method(
|
|
58
|
+
keyfile: str | None, keyfile_json: dict[str, Any] | None
|
|
59
|
+
) -> Literal["oauth", "service-account", "service-account-json"]:
|
|
60
|
+
"""Return the dbt-bigquery method for the given credential shape."""
|
|
61
|
+
if keyfile_json is not None:
|
|
62
|
+
return "service-account-json"
|
|
63
|
+
if keyfile is not None:
|
|
64
|
+
return "service-account"
|
|
65
|
+
return "oauth"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ============================================================================
|
|
69
|
+
# ENVIRONMENT VARIABLE RESOLUTION
|
|
70
|
+
# ============================================================================
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resolve_env_var(value: str) -> str:
|
|
74
|
+
"""Resolve {{ env_var('VAR') }} or {{ env_var('VAR', 'default') }} syntax.
|
|
75
|
+
|
|
76
|
+
Matches dbt's env_var() function behavior.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
value: String that may contain env_var() expressions
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
String with env_var() resolved to actual values
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ValueError: If required env var is not set
|
|
86
|
+
"""
|
|
87
|
+
if not isinstance(value, str):
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
# Pattern: {{ env_var('VAR') }} or {{ env_var('VAR', 'default') }}
|
|
91
|
+
pattern = r"\{\{\s*env_var\(\s*['\"]([^'\"]+)['\"]\s*(?:,\s*['\"]([^'\"]*)['\"])?\s*\)\s*\}\}"
|
|
92
|
+
|
|
93
|
+
def replace_env_var(match: re.Match[str]) -> str:
|
|
94
|
+
var_name = match.group(1)
|
|
95
|
+
default_value = match.group(2)
|
|
96
|
+
|
|
97
|
+
env_value = os.environ.get(var_name)
|
|
98
|
+
if env_value is not None:
|
|
99
|
+
return env_value
|
|
100
|
+
elif default_value is not None:
|
|
101
|
+
return default_value
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"Environment variable '{var_name}' is not set and no default provided"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return re.sub(pattern, replace_env_var, value)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def resolve_env_vars_in_dict(data: dict[str, Any]) -> dict[str, Any]:
|
|
111
|
+
"""Recursively resolve env_var() expressions in a dictionary.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
data: Dictionary that may contain env_var() expressions
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Dictionary with all env_var() expressions resolved
|
|
118
|
+
"""
|
|
119
|
+
result: dict[str, Any] = {}
|
|
120
|
+
for key, value in data.items():
|
|
121
|
+
if isinstance(value, str):
|
|
122
|
+
result[key] = resolve_env_var(value)
|
|
123
|
+
elif isinstance(value, dict):
|
|
124
|
+
result[key] = resolve_env_vars_in_dict(value)
|
|
125
|
+
else:
|
|
126
|
+
result[key] = value
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ============================================================================
|
|
131
|
+
# BASE SOURCE CONFIG
|
|
132
|
+
# ============================================================================
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class BaseSourceConfig(BaseModel):
|
|
136
|
+
"""Closed-contract root for all source configurations.
|
|
137
|
+
|
|
138
|
+
extra="forbid" — file/HTTP/dbt_profile source configs have defined Dataface
|
|
139
|
+
contracts; unknown fields are validation errors, not silent pass-throughs.
|
|
140
|
+
DB source configs widen to extra="allow" via _DbSourceShim (ADR-009).
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
model_config = ConfigDict(extra="forbid")
|
|
144
|
+
|
|
145
|
+
@model_validator(mode="before")
|
|
146
|
+
@classmethod
|
|
147
|
+
def _resolve_all_env_vars(cls, data: Any) -> Any:
|
|
148
|
+
"""Resolve {{ env_var('VAR') }} in all string fields before Pydantic validates."""
|
|
149
|
+
if isinstance(data, dict):
|
|
150
|
+
return resolve_env_vars_in_dict(data)
|
|
151
|
+
return data
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _DbSourceShim(BaseSourceConfig):
|
|
155
|
+
"""Passthrough shim for DB source configs.
|
|
156
|
+
|
|
157
|
+
extra="allow" — dbt profiles.yml passthrough contract (ADR-009): dbt can
|
|
158
|
+
add or rename target fields across versions; Dataface reads whatever the
|
|
159
|
+
user's profile contains without raising a ValidationError. Only DB source
|
|
160
|
+
configs (Postgres, Snowflake, BigQuery, Redshift, MySQL, DuckDB) inherit
|
|
161
|
+
from this shim. File/HTTP/dbt_profile configs inherit BaseSourceConfig directly.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
model_config = ConfigDict(extra="allow")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ============================================================================
|
|
168
|
+
# DATABASE SOURCE CONFIGS
|
|
169
|
+
# ============================================================================
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class PostgresSourceConfig(_DbSourceShim):
|
|
173
|
+
"""Postgres source configuration.
|
|
174
|
+
|
|
175
|
+
Field names match dbt profiles.yml exactly.
|
|
176
|
+
|
|
177
|
+
Read-only posture: no native driver flag. Bind a SELECT-only role to the
|
|
178
|
+
dbt profile credential at the database level.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
type: Literal["postgres"] = Field(description="Source type identifier.")
|
|
182
|
+
host: str = Field(description="Database host name or IP address.")
|
|
183
|
+
port: int = Field(default=5432, description="Database port number.")
|
|
184
|
+
dbname: str = Field(description="Database name (matches dbt 'dbname').")
|
|
185
|
+
schema_: str = Field(
|
|
186
|
+
alias="schema", default="public", description="Default schema for queries."
|
|
187
|
+
)
|
|
188
|
+
user: str = Field(description="Database user name.")
|
|
189
|
+
password: str = Field(description="Database password.")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class SnowflakeSourceConfig(_DbSourceShim):
|
|
193
|
+
"""Snowflake source configuration.
|
|
194
|
+
|
|
195
|
+
Field names match dbt profiles.yml exactly.
|
|
196
|
+
|
|
197
|
+
Read-only posture: no native driver flag. Bind a SELECT-only role to the
|
|
198
|
+
Snowflake user or key-pair in the dbt profile.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
type: Literal["snowflake"] = Field(description="Source type identifier.")
|
|
202
|
+
account: str = Field(
|
|
203
|
+
description="Snowflake account identifier (e.g. xy12345.us-east-1)."
|
|
204
|
+
)
|
|
205
|
+
user: str = Field(description="Snowflake user name.")
|
|
206
|
+
password: str | None = Field(
|
|
207
|
+
default=None,
|
|
208
|
+
description="Snowflake password. Omit when using OAuth or key-pair auth.",
|
|
209
|
+
)
|
|
210
|
+
database: str = Field(description="Snowflake database name.")
|
|
211
|
+
warehouse: str = Field(description="Snowflake virtual warehouse name.")
|
|
212
|
+
schema_: str = Field(
|
|
213
|
+
alias="schema", default="PUBLIC", description="Default schema for queries."
|
|
214
|
+
)
|
|
215
|
+
role: str | None = Field(
|
|
216
|
+
default=None, description="Snowflake role to assume for the session."
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class BigQuerySourceConfig(_DbSourceShim):
|
|
221
|
+
"""BigQuery source configuration.
|
|
222
|
+
|
|
223
|
+
Field names match dbt profiles.yml exactly.
|
|
224
|
+
|
|
225
|
+
Read-only posture: no native driver flag. Bind credentials to
|
|
226
|
+
roles/bigquery.dataViewer (or equivalent) at the IAM layer.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
type: Literal["bigquery"] = Field(description="Source type identifier.")
|
|
230
|
+
project: str = Field(description="GCP project ID.")
|
|
231
|
+
dataset: str = Field(description="BigQuery dataset name (equivalent to schema).")
|
|
232
|
+
keyfile: str | None = Field(
|
|
233
|
+
default=None, description="Path to service account JSON key file."
|
|
234
|
+
)
|
|
235
|
+
keyfile_json: dict[str, Any] | None = Field(
|
|
236
|
+
default=None, description="Inline service account JSON dict."
|
|
237
|
+
)
|
|
238
|
+
location: str | None = Field(
|
|
239
|
+
default=None, description="Dataset location (e.g. US, EU)."
|
|
240
|
+
)
|
|
241
|
+
method: Literal["oauth", "service-account", "service-account-json"] | None = Field(
|
|
242
|
+
default=None,
|
|
243
|
+
description=(
|
|
244
|
+
"dbt-bigquery authentication method. "
|
|
245
|
+
"Inferred from keyfile/keyfile_json when omitted: "
|
|
246
|
+
"'service-account-json' if keyfile_json set, "
|
|
247
|
+
"'service-account' if keyfile set, "
|
|
248
|
+
"'oauth' (Application Default Credentials) otherwise."
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
@model_validator(mode="after")
|
|
253
|
+
def _resolve_auth(self) -> BigQuerySourceConfig:
|
|
254
|
+
"""Validate credential fields and infer method when not explicitly set."""
|
|
255
|
+
if self.keyfile is not None and self.keyfile_json is not None:
|
|
256
|
+
raise ValueError(
|
|
257
|
+
"BigQuery credentials must use either 'keyfile' (path to JSON file) "
|
|
258
|
+
"or 'keyfile_json' (inline dict), not both."
|
|
259
|
+
)
|
|
260
|
+
if self.method is None:
|
|
261
|
+
self.method = infer_bq_method(self.keyfile, self.keyfile_json)
|
|
262
|
+
return self
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class RedshiftSourceConfig(_DbSourceShim):
|
|
266
|
+
"""Redshift source configuration.
|
|
267
|
+
|
|
268
|
+
Field names match dbt profiles.yml exactly.
|
|
269
|
+
|
|
270
|
+
Read-only posture: no native driver flag. Bind to a SELECT-only IAM role
|
|
271
|
+
or database user at the Redshift/IAM layer.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
type: Literal["redshift"] = Field(description="Source type identifier.")
|
|
275
|
+
host: str = Field(description="Redshift cluster host name.")
|
|
276
|
+
port: int = Field(default=5439, description="Redshift port number.")
|
|
277
|
+
dbname: str = Field(description="Redshift database name.")
|
|
278
|
+
schema_: str = Field(
|
|
279
|
+
alias="schema", default="public", description="Default schema for queries."
|
|
280
|
+
)
|
|
281
|
+
user: str = Field(description="Redshift user name.")
|
|
282
|
+
password: str = Field(description="Redshift password.")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class MySQLSourceConfig(_DbSourceShim):
|
|
286
|
+
"""MySQL source configuration.
|
|
287
|
+
|
|
288
|
+
Field names match dbt profiles.yml exactly.
|
|
289
|
+
|
|
290
|
+
Read-only posture: no native driver flag. Bind a SELECT-only user to the
|
|
291
|
+
dbt profile credential at the database level.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
type: Literal["mysql"] = Field(description="Source type identifier.")
|
|
295
|
+
host: str = Field(description="MySQL host name or IP address.")
|
|
296
|
+
port: int = Field(default=3306, description="MySQL port number.")
|
|
297
|
+
database: str = Field(description="MySQL database name.")
|
|
298
|
+
schema_: str = Field(
|
|
299
|
+
alias="schema", default="", description="Default schema for queries."
|
|
300
|
+
)
|
|
301
|
+
user: str = Field(description="MySQL user name.")
|
|
302
|
+
password: str = Field(description="MySQL password.")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class DuckDBSourceConfig(_DbSourceShim):
|
|
306
|
+
"""DuckDB source configuration.
|
|
307
|
+
|
|
308
|
+
Read-only posture: handled in-process. SqlAdapter opens file-based DuckDB
|
|
309
|
+
connections with read_only=True and forces enable_external_access=False.
|
|
310
|
+
Callers that need external access (e.g. the playground for local dev) can
|
|
311
|
+
pass allow_external_access_in_readonly=True on SqlAdapter /
|
|
312
|
+
build_adapter_registry together with duckdb_config={"enable_external_access":
|
|
313
|
+
True} to explicitly opt back in.
|
|
314
|
+
|
|
315
|
+
Example:
|
|
316
|
+
sources:
|
|
317
|
+
analytics:
|
|
318
|
+
type: duckdb
|
|
319
|
+
path: ./data/analytics.duckdb
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
type: Literal["duckdb"] = Field(description="Source type identifier.")
|
|
323
|
+
path: str = Field(
|
|
324
|
+
default=":memory:",
|
|
325
|
+
description="DuckDB file path or ':memory:' for an in-memory database.",
|
|
326
|
+
)
|
|
327
|
+
schema_: str | None = Field(
|
|
328
|
+
alias="schema",
|
|
329
|
+
default=None,
|
|
330
|
+
description="Default schema for unqualified table names (sets search_path).",
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
@field_validator("schema_", mode="before")
|
|
334
|
+
@classmethod
|
|
335
|
+
def validate_schema_identifier(cls, v: Any) -> Any:
|
|
336
|
+
if v is None:
|
|
337
|
+
return v
|
|
338
|
+
if not isinstance(v, str) or not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", v):
|
|
339
|
+
raise ValueError(
|
|
340
|
+
f"schema must be a valid SQL identifier (letters, digits, underscores; "
|
|
341
|
+
f"no leading digit): got {v!r}"
|
|
342
|
+
)
|
|
343
|
+
return v
|
|
344
|
+
|
|
345
|
+
@model_validator(mode="before")
|
|
346
|
+
@classmethod
|
|
347
|
+
def reject_database_key(cls, data: Any) -> Any:
|
|
348
|
+
"""Reject the legacy 'database' key at parse time.
|
|
349
|
+
|
|
350
|
+
The runtime already raises on 'database', so reject it here too so
|
|
351
|
+
the two layers agree and the error fires as early as possible.
|
|
352
|
+
"""
|
|
353
|
+
if isinstance(data, dict) and "database" in data:
|
|
354
|
+
raise ValueError(
|
|
355
|
+
"DuckDB source config uses 'path', not 'database'. "
|
|
356
|
+
"Replace 'database' with 'path' "
|
|
357
|
+
'(e.g. {"type": "duckdb", "path": "db.duckdb"}).'
|
|
358
|
+
)
|
|
359
|
+
return data
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# ============================================================================
|
|
363
|
+
# FILE SOURCE CONFIGS
|
|
364
|
+
# ============================================================================
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class CsvSourceConfig(BaseSourceConfig):
|
|
368
|
+
"""CSV file source configuration."""
|
|
369
|
+
|
|
370
|
+
type: Literal["csv"] = Field(description="Source type identifier.")
|
|
371
|
+
file: str | None = Field(default=None, description="Local path to the CSV file.")
|
|
372
|
+
url: str | None = Field(default=None, description="Remote URL of the CSV file.")
|
|
373
|
+
headers: dict[str, str] | None = Field(
|
|
374
|
+
default=None, description="HTTP headers for authenticated URLs."
|
|
375
|
+
)
|
|
376
|
+
delimiter: str = Field(default=",", description="Field delimiter character.")
|
|
377
|
+
encoding: str = Field(default="utf-8", description="File encoding.")
|
|
378
|
+
|
|
379
|
+
@model_validator(mode="after")
|
|
380
|
+
def validate_file_or_url(self) -> CsvSourceConfig:
|
|
381
|
+
"""Ensure either file or url is provided."""
|
|
382
|
+
if not self.file and not self.url:
|
|
383
|
+
raise ValueError("CsvSourceConfig requires either 'file' or 'url'")
|
|
384
|
+
return self
|
|
385
|
+
|
|
386
|
+
@field_validator("file", "url", mode="before")
|
|
387
|
+
@classmethod
|
|
388
|
+
def resolve_env_vars(cls, v: Any) -> Any:
|
|
389
|
+
"""Resolve env_var() syntax in string fields."""
|
|
390
|
+
if isinstance(v, str):
|
|
391
|
+
return resolve_env_var(v)
|
|
392
|
+
return v
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class ParquetSourceConfig(BaseSourceConfig):
|
|
396
|
+
"""Parquet file source configuration."""
|
|
397
|
+
|
|
398
|
+
type: Literal["parquet"] = Field(description="Source type identifier.")
|
|
399
|
+
file: str | None = Field(
|
|
400
|
+
default=None, description="Local path to the Parquet file."
|
|
401
|
+
)
|
|
402
|
+
url: str | None = Field(default=None, description="Remote URL of the Parquet file.")
|
|
403
|
+
headers: dict[str, str] | None = Field(
|
|
404
|
+
default=None, description="HTTP headers for authenticated URLs."
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
@model_validator(mode="after")
|
|
408
|
+
def validate_file_or_url(self) -> ParquetSourceConfig:
|
|
409
|
+
"""Ensure either file or url is provided."""
|
|
410
|
+
if not self.file and not self.url:
|
|
411
|
+
raise ValueError("ParquetSourceConfig requires either 'file' or 'url'")
|
|
412
|
+
return self
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class JsonSourceConfig(BaseSourceConfig):
|
|
416
|
+
"""JSON file source configuration."""
|
|
417
|
+
|
|
418
|
+
type: Literal["json"] = Field(description="Source type identifier.")
|
|
419
|
+
file: str | None = Field(default=None, description="Local path to the JSON file.")
|
|
420
|
+
url: str | None = Field(default=None, description="Remote URL of the JSON file.")
|
|
421
|
+
headers: dict[str, str] | None = Field(
|
|
422
|
+
default=None, description="HTTP headers for authenticated URLs."
|
|
423
|
+
)
|
|
424
|
+
json_path: str | None = Field(
|
|
425
|
+
default=None, description="JSONPath expression to extract the data array."
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
@model_validator(mode="after")
|
|
429
|
+
def validate_file_or_url(self) -> JsonSourceConfig:
|
|
430
|
+
"""Ensure either file or url is provided."""
|
|
431
|
+
if not self.file and not self.url:
|
|
432
|
+
raise ValueError("JsonSourceConfig requires either 'file' or 'url'")
|
|
433
|
+
return self
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# ============================================================================
|
|
437
|
+
# HTTP/API SOURCE CONFIG
|
|
438
|
+
# ============================================================================
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class HttpSourceConfig(BaseSourceConfig):
|
|
442
|
+
"""HTTP/REST API source configuration."""
|
|
443
|
+
|
|
444
|
+
type: Literal["http"] = Field(description="Source type identifier.")
|
|
445
|
+
url: str = Field(description="Base URL for HTTP requests.")
|
|
446
|
+
headers: dict[str, str] | None = Field(
|
|
447
|
+
default=None, description="Default HTTP headers (e.g. Authorization)."
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
@field_validator("url", mode="before")
|
|
451
|
+
@classmethod
|
|
452
|
+
def resolve_url_env_var(cls, v: Any) -> Any:
|
|
453
|
+
"""Resolve env_var() syntax in URL."""
|
|
454
|
+
if isinstance(v, str):
|
|
455
|
+
return resolve_env_var(v)
|
|
456
|
+
return v
|
|
457
|
+
|
|
458
|
+
@field_validator("headers", mode="before")
|
|
459
|
+
@classmethod
|
|
460
|
+
def resolve_headers_env_vars(cls, v: Any) -> Any:
|
|
461
|
+
"""Resolve env_var() syntax in headers."""
|
|
462
|
+
if isinstance(v, dict):
|
|
463
|
+
return resolve_env_vars_in_dict(v)
|
|
464
|
+
return v
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# ============================================================================
|
|
468
|
+
# DBT PROFILE SOURCE CONFIG
|
|
469
|
+
# ============================================================================
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class DbtProfileSourceConfig(BaseSourceConfig):
|
|
473
|
+
"""Reference to a dbt profile (for backward compatibility).
|
|
474
|
+
|
|
475
|
+
Instead of defining connection details, references an existing
|
|
476
|
+
dbt profile from profiles.yml.
|
|
477
|
+
"""
|
|
478
|
+
|
|
479
|
+
type: Literal["dbt_profile"] = Field(description="Source type identifier.")
|
|
480
|
+
profile: str = Field(description="dbt profile name from profiles.yml.")
|
|
481
|
+
target: str | None = Field(
|
|
482
|
+
default=None,
|
|
483
|
+
description="dbt target to use; defaults to the profile's default target.",
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# ============================================================================
|
|
488
|
+
# UNION TYPE
|
|
489
|
+
# ============================================================================
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# Union of all source config types
|
|
493
|
+
SourceConfig = (
|
|
494
|
+
PostgresSourceConfig
|
|
495
|
+
| SnowflakeSourceConfig
|
|
496
|
+
| BigQuerySourceConfig
|
|
497
|
+
| RedshiftSourceConfig
|
|
498
|
+
| MySQLSourceConfig
|
|
499
|
+
| DuckDBSourceConfig
|
|
500
|
+
| CsvSourceConfig
|
|
501
|
+
| ParquetSourceConfig
|
|
502
|
+
| JsonSourceConfig
|
|
503
|
+
| HttpSourceConfig
|
|
504
|
+
| DbtProfileSourceConfig
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
# Valid source type strings
|
|
509
|
+
VALID_SOURCE_TYPES = {
|
|
510
|
+
"postgres",
|
|
511
|
+
"snowflake",
|
|
512
|
+
"bigquery",
|
|
513
|
+
"redshift",
|
|
514
|
+
"mysql",
|
|
515
|
+
"duckdb",
|
|
516
|
+
"csv",
|
|
517
|
+
"parquet",
|
|
518
|
+
"json",
|
|
519
|
+
"http",
|
|
520
|
+
"dbt_profile",
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
# ============================================================================
|
|
525
|
+
# HELPER FUNCTIONS
|
|
526
|
+
# ============================================================================
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def parse_source_config(data: dict[str, Any]) -> SourceConfig:
|
|
530
|
+
"""Parse a source configuration dictionary into the appropriate type.
|
|
531
|
+
|
|
532
|
+
Uses the 'type' field to determine which Pydantic model to use.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
data: Source configuration dictionary
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Appropriate SourceConfig subclass instance
|
|
539
|
+
|
|
540
|
+
Raises:
|
|
541
|
+
ValueError: If type is missing or invalid
|
|
542
|
+
"""
|
|
543
|
+
if "type" not in data:
|
|
544
|
+
raise ValueError("Source configuration must have a 'type' field")
|
|
545
|
+
|
|
546
|
+
source_type = data["type"]
|
|
547
|
+
|
|
548
|
+
type_map = {
|
|
549
|
+
"postgres": PostgresSourceConfig,
|
|
550
|
+
"snowflake": SnowflakeSourceConfig,
|
|
551
|
+
"bigquery": BigQuerySourceConfig,
|
|
552
|
+
"redshift": RedshiftSourceConfig,
|
|
553
|
+
"mysql": MySQLSourceConfig,
|
|
554
|
+
"duckdb": DuckDBSourceConfig,
|
|
555
|
+
"csv": CsvSourceConfig,
|
|
556
|
+
"parquet": ParquetSourceConfig,
|
|
557
|
+
"json": JsonSourceConfig,
|
|
558
|
+
"http": HttpSourceConfig,
|
|
559
|
+
"dbt_profile": DbtProfileSourceConfig,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if source_type not in type_map:
|
|
563
|
+
raise ValueError(
|
|
564
|
+
f"Unknown source type: '{source_type}'. "
|
|
565
|
+
f"Valid types: {', '.join(sorted(type_map.keys()))}"
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
return type_map[source_type](**data)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def is_database_source(config: SourceConfig) -> bool:
|
|
572
|
+
"""Check if source config is a database type.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
config: Source configuration
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
True if source is a database connection
|
|
579
|
+
"""
|
|
580
|
+
return config.type in {
|
|
581
|
+
"postgres",
|
|
582
|
+
"snowflake",
|
|
583
|
+
"bigquery",
|
|
584
|
+
"redshift",
|
|
585
|
+
"mysql",
|
|
586
|
+
"duckdb",
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def is_file_source(config: SourceConfig) -> bool:
|
|
591
|
+
"""Check if source config is a file type.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
config: Source configuration
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
True if source is a file-based source
|
|
598
|
+
"""
|
|
599
|
+
return config.type in {"csv", "parquet", "json"}
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def is_api_source(config: SourceConfig) -> bool:
|
|
603
|
+
"""Check if source config is an API type.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
config: Source configuration
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
True if source is an HTTP API
|
|
610
|
+
"""
|
|
611
|
+
return config.type == "http"
|
|
File without changes
|