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,245 @@
|
|
|
1
|
+
"""Base adapter interface for query execution.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE
|
|
4
|
+
Purpose: Define the base interface and types for query adapters.
|
|
5
|
+
|
|
6
|
+
All adapters inherit from BaseAdapter and implement the unified query interface.
|
|
7
|
+
QueryResult is the standard return type for all adapter executions.
|
|
8
|
+
|
|
9
|
+
The unified interface uses:
|
|
10
|
+
- `supported_types` property: Set of query types this adapter handles
|
|
11
|
+
- `can_execute()`: Uses supported_types for type-based routing
|
|
12
|
+
- `execute()`: Executes the query using type guards for type safety
|
|
13
|
+
|
|
14
|
+
Security:
|
|
15
|
+
- Adapters support parameterized queries via the optional `params` argument
|
|
16
|
+
- When params are provided, values are passed to the database driver separately
|
|
17
|
+
from the SQL, preventing SQL injection
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from abc import ABC, abstractmethod
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
25
|
+
|
|
26
|
+
from dataface.core.compile.models.face.compiled import VariableValues
|
|
27
|
+
from dataface.core.compile.models.query.compiled import AnyQuery
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from dataface.core.compile.models.source import SourceConfig
|
|
31
|
+
|
|
32
|
+
# Type alias for query parameters
|
|
33
|
+
QueryParams = list[Any] | None
|
|
34
|
+
|
|
35
|
+
SchemaStatus = Literal["changed", "unchanged", "unknown"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class ResolvedRelation:
|
|
40
|
+
"""A dbt ref/source resolved during query execution.
|
|
41
|
+
|
|
42
|
+
Tracks which schema each relation resolved to and whether it
|
|
43
|
+
differs from the production baseline (changed vs unchanged).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
ref_name: str
|
|
47
|
+
schema: str
|
|
48
|
+
status: SchemaStatus
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class QueryResult:
|
|
52
|
+
"""Result of a query execution.
|
|
53
|
+
|
|
54
|
+
This is the standard return type for all adapter executions, providing
|
|
55
|
+
a consistent interface for data retrieval and error handling.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
data: List of dictionaries containing query results (each dict is a row)
|
|
59
|
+
columns: List of column names
|
|
60
|
+
column_descriptions: Full PEP 249 cursor.description tuples keyed by column name.
|
|
61
|
+
Each tuple: (name, type_code, display_size, internal_size, precision, scale, null_ok)
|
|
62
|
+
error: Error message if query failed, None if successful
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
data: list[dict[str, Any]],
|
|
68
|
+
columns: list[str] | None = None,
|
|
69
|
+
column_descriptions: dict[str, tuple] | None = None,
|
|
70
|
+
error: str | None = None,
|
|
71
|
+
resolved_relations: list[ResolvedRelation] | None = None,
|
|
72
|
+
):
|
|
73
|
+
"""Initialize query result.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
data: List of dictionaries containing query results (each dict is a row)
|
|
77
|
+
columns: Optional list of column names (inferred from data if not provided)
|
|
78
|
+
column_descriptions: Optional PEP 249 cursor.description tuples keyed by
|
|
79
|
+
column name. Each value is a 7-tuple: (name, type_code, display_size,
|
|
80
|
+
internal_size, precision, scale, null_ok). Populated from
|
|
81
|
+
cursor.description when a database adapter executes the query.
|
|
82
|
+
error: Optional error message if query failed
|
|
83
|
+
resolved_relations: dbt ref/source relations resolved during execution,
|
|
84
|
+
with dev-vs-prod schema status for each.
|
|
85
|
+
"""
|
|
86
|
+
self.data = data or []
|
|
87
|
+
if columns is None and self.data:
|
|
88
|
+
self.columns = list(self.data[0].keys())
|
|
89
|
+
else:
|
|
90
|
+
self.columns = columns or []
|
|
91
|
+
self.column_descriptions = column_descriptions
|
|
92
|
+
self.error = error
|
|
93
|
+
self.resolved_relations: list[ResolvedRelation] = resolved_relations or []
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def is_success(self) -> bool:
|
|
97
|
+
"""Check if query executed successfully."""
|
|
98
|
+
return self.error is None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handle_adapter_error(operation: str, error: Exception) -> QueryResult:
|
|
102
|
+
"""Standard error handling for adapters.
|
|
103
|
+
|
|
104
|
+
Provides consistent error handling across all adapters, ensuring
|
|
105
|
+
uniform error messages and making it easier to add logging/metrics
|
|
106
|
+
in one place.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
operation: Description of the operation that failed (e.g., "SQL execution", "HTTP request")
|
|
110
|
+
error: The exception that occurred
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
QueryResult with error information
|
|
114
|
+
"""
|
|
115
|
+
return QueryResult(
|
|
116
|
+
data=[],
|
|
117
|
+
error=f"{operation} failed: {str(error)}",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class BaseAdapter(ABC):
|
|
122
|
+
"""Base interface for query adapters.
|
|
123
|
+
|
|
124
|
+
All adapters must implement this interface to execute queries
|
|
125
|
+
against different backends (MetricFlow, SQL, HTTP, CSV, etc.).
|
|
126
|
+
|
|
127
|
+
The unified interface pattern uses:
|
|
128
|
+
- `supported_types`: Property returning set of query types this adapter handles
|
|
129
|
+
- Type guards for type-safe field access in execute methods
|
|
130
|
+
|
|
131
|
+
Subclasses must implement:
|
|
132
|
+
- supported_types: Property returning Set[str] of supported query types
|
|
133
|
+
- _execute(): Perform the actual query execution
|
|
134
|
+
|
|
135
|
+
Optional override:
|
|
136
|
+
- _can_execute(): Override for custom eligibility logic beyond type matching
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
>>> class MyAdapter(BaseAdapter):
|
|
140
|
+
... @property
|
|
141
|
+
... def supported_types(self) -> Set[str]:
|
|
142
|
+
... return {"sql"}
|
|
143
|
+
...
|
|
144
|
+
... def _execute(self, query, variables):
|
|
145
|
+
... if is_sql_query(query):
|
|
146
|
+
... # Type checker knows query.sql exists
|
|
147
|
+
... return self._run_sql(query.sql)
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def supported_types(self) -> set[str]:
|
|
153
|
+
"""Query types this adapter can execute.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Set of query type strings (e.g., {"sql"}, {"csv", "http"})
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> adapter.supported_types
|
|
160
|
+
{"sql"}
|
|
161
|
+
"""
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
def can_execute(self, query: AnyQuery) -> bool:
|
|
165
|
+
"""Check if this adapter can execute the given query.
|
|
166
|
+
|
|
167
|
+
Uses supported_types for type-based routing. Override _can_execute()
|
|
168
|
+
for additional custom eligibility logic.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
query: AnyQuery object (guaranteed by compiler)
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
True if this adapter can execute the query, False otherwise
|
|
175
|
+
"""
|
|
176
|
+
# First check if type is supported
|
|
177
|
+
if query.query_type not in self.supported_types:
|
|
178
|
+
return False
|
|
179
|
+
# Then check any additional conditions
|
|
180
|
+
return self._can_execute(query)
|
|
181
|
+
|
|
182
|
+
def _can_execute(self, query: AnyQuery) -> bool:
|
|
183
|
+
"""Additional eligibility check beyond type matching.
|
|
184
|
+
|
|
185
|
+
Override this method to add custom eligibility logic.
|
|
186
|
+
The base implementation always returns True.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
query: AnyQuery object (guaranteed)
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if this adapter can execute the query, False otherwise
|
|
193
|
+
"""
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
def execute(
|
|
197
|
+
self,
|
|
198
|
+
query: AnyQuery,
|
|
199
|
+
variables: VariableValues | None = None,
|
|
200
|
+
params: QueryParams = None,
|
|
201
|
+
source_config: SourceConfig | None = None,
|
|
202
|
+
) -> QueryResult:
|
|
203
|
+
"""Execute a query and return results.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
query: AnyQuery object (guaranteed by compiler)
|
|
207
|
+
variables: Optional dictionary of variable values for query resolution
|
|
208
|
+
params: Optional pre-computed parameter values for parameterized execution.
|
|
209
|
+
When provided, the adapter should use these params directly instead
|
|
210
|
+
of re-processing variables. This is used by batch execution where
|
|
211
|
+
parameterization happens before adapter execution.
|
|
212
|
+
source_config: Typed source config resolved by SourceResolver before
|
|
213
|
+
adapter dispatch. SqlAdapter consumes this; other adapters ignore it.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
QueryResult containing data or error information
|
|
217
|
+
"""
|
|
218
|
+
return self._execute(query, variables, params, source_config)
|
|
219
|
+
|
|
220
|
+
@abstractmethod
|
|
221
|
+
def _execute(
|
|
222
|
+
self,
|
|
223
|
+
query: AnyQuery,
|
|
224
|
+
variables: VariableValues | None = None,
|
|
225
|
+
params: QueryParams = None,
|
|
226
|
+
source_config: SourceConfig | None = None,
|
|
227
|
+
) -> QueryResult:
|
|
228
|
+
"""Internal method to execute query.
|
|
229
|
+
|
|
230
|
+
Use type guards for type-safe field access:
|
|
231
|
+
if is_sql_query(query):
|
|
232
|
+
sql = query.sql # Type checker knows this is str
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
query: AnyQuery object (guaranteed)
|
|
236
|
+
variables: Optional dictionary of variable values for query resolution
|
|
237
|
+
params: Optional pre-computed parameter values. When provided, skip
|
|
238
|
+
internal parameterization and use these params directly.
|
|
239
|
+
source_config: Typed source config from SourceResolver; SqlAdapter
|
|
240
|
+
reads it, other adapters accept and ignore.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
QueryResult containing data or error information
|
|
244
|
+
"""
|
|
245
|
+
pass
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""CSV adapter for executing queries against CSV files.
|
|
2
|
+
|
|
3
|
+
Stage: EXECUTE
|
|
4
|
+
Purpose: Load and query data from CSV files.
|
|
5
|
+
|
|
6
|
+
This adapter allows users to load data from CSV files stored in their
|
|
7
|
+
project's assets/data directory or other specified paths. It supports
|
|
8
|
+
basic filtering and column selection via query parameters.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import csv
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from dataface.core.compile.models.source import SourceConfig
|
|
19
|
+
|
|
20
|
+
from dataface.core.compile.jinja import resolve_jinja_template
|
|
21
|
+
from dataface.core.compile.models.face.compiled import VariableValues
|
|
22
|
+
from dataface.core.compile.models.query.compiled import (
|
|
23
|
+
AnyQuery,
|
|
24
|
+
is_csv_query,
|
|
25
|
+
)
|
|
26
|
+
from dataface.core.execute.adapters.base import (
|
|
27
|
+
BaseAdapter,
|
|
28
|
+
QueryParams,
|
|
29
|
+
QueryResult,
|
|
30
|
+
handle_adapter_error,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _coerce_csv_value(v: str) -> str | int | float:
|
|
35
|
+
"""Try to parse a CSV string as int, then float; return the original string on failure."""
|
|
36
|
+
try:
|
|
37
|
+
return int(v)
|
|
38
|
+
except ValueError:
|
|
39
|
+
pass
|
|
40
|
+
try:
|
|
41
|
+
return float(v)
|
|
42
|
+
except ValueError:
|
|
43
|
+
return v
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CsvAdapter(BaseAdapter):
|
|
47
|
+
"""Adapter for executing queries against CSV files.
|
|
48
|
+
|
|
49
|
+
Supported query types: csv
|
|
50
|
+
|
|
51
|
+
Loads data from CSV files and supports:
|
|
52
|
+
- Column selection
|
|
53
|
+
- Row filtering (exact match, AND logic)
|
|
54
|
+
- Result limiting
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> adapter = CsvAdapter()
|
|
58
|
+
>>> query = CsvQuery(file="data/sales.csv", columns=["date", "amount"])
|
|
59
|
+
>>> result = adapter.execute(query)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, project_root: Path | None = None):
|
|
63
|
+
"""Initialize CSV adapter.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
project_root: Root directory of the project (for resolving relative paths)
|
|
67
|
+
"""
|
|
68
|
+
self.project_root = project_root or Path.cwd()
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def supported_types(self) -> set[str]:
|
|
72
|
+
"""Return supported query types."""
|
|
73
|
+
return {"csv"}
|
|
74
|
+
|
|
75
|
+
def _resolve_file_path(self, file_path: str) -> Path:
|
|
76
|
+
"""Resolve CSV file path relative to project root or assets/data.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
file_path: File path from query (may be relative)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Resolved Path object
|
|
83
|
+
"""
|
|
84
|
+
# If absolute path, use as-is
|
|
85
|
+
if Path(file_path).is_absolute():
|
|
86
|
+
return Path(file_path)
|
|
87
|
+
|
|
88
|
+
# Try relative to project root first
|
|
89
|
+
project_path = self.project_root / file_path
|
|
90
|
+
if project_path.exists():
|
|
91
|
+
return project_path
|
|
92
|
+
|
|
93
|
+
# Try relative to assets/data
|
|
94
|
+
assets_data_path = self.project_root / "assets" / "data" / file_path
|
|
95
|
+
if assets_data_path.exists():
|
|
96
|
+
return assets_data_path
|
|
97
|
+
|
|
98
|
+
# Try just the filename in assets/data
|
|
99
|
+
filename_path = self.project_root / "assets" / "data" / Path(file_path).name
|
|
100
|
+
if filename_path.exists():
|
|
101
|
+
return filename_path
|
|
102
|
+
|
|
103
|
+
# Fall back to relative path (may not exist, but we'll handle error)
|
|
104
|
+
return self.project_root / file_path
|
|
105
|
+
|
|
106
|
+
def _execute(
|
|
107
|
+
self,
|
|
108
|
+
query: AnyQuery,
|
|
109
|
+
variables: VariableValues | None = None,
|
|
110
|
+
params: QueryParams = None,
|
|
111
|
+
source_config: SourceConfig | None = None,
|
|
112
|
+
) -> QueryResult:
|
|
113
|
+
"""Execute a CSV query.
|
|
114
|
+
|
|
115
|
+
Uses type guard for type-safe field access.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
query: AnyQuery object (CsvQuery expected)
|
|
119
|
+
variables: Variable values for Jinja resolution
|
|
120
|
+
params: Not used for CSV queries (CSV doesn't use SQL parameterization)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
QueryResult with data or error
|
|
124
|
+
"""
|
|
125
|
+
# Type guard ensures query.file is str
|
|
126
|
+
if not is_csv_query(query):
|
|
127
|
+
return QueryResult(
|
|
128
|
+
data=[],
|
|
129
|
+
error=f"Expected CSV query, got {query.query_type}",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
file_path = query.effective_file
|
|
133
|
+
if not file_path:
|
|
134
|
+
return QueryResult(
|
|
135
|
+
data=[],
|
|
136
|
+
error="CSV query requires a file path",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Resolve Jinja templates in file path
|
|
140
|
+
try:
|
|
141
|
+
resolved_file_path = resolve_jinja_template(file_path, variables)
|
|
142
|
+
except (ValueError, KeyError, TypeError) as e:
|
|
143
|
+
return handle_adapter_error("CSV file path template resolution", e)
|
|
144
|
+
|
|
145
|
+
# Resolve file path
|
|
146
|
+
try:
|
|
147
|
+
csv_path = self._resolve_file_path(resolved_file_path)
|
|
148
|
+
except (OSError, ValueError) as e:
|
|
149
|
+
return handle_adapter_error("CSV file path resolution", e)
|
|
150
|
+
|
|
151
|
+
# Check if file exists
|
|
152
|
+
if not csv_path.exists():
|
|
153
|
+
return QueryResult(
|
|
154
|
+
data=[],
|
|
155
|
+
error=f"CSV file not found: {csv_path}",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Read CSV file using stdlib csv module
|
|
159
|
+
try:
|
|
160
|
+
with open(csv_path, encoding="utf-8") as f:
|
|
161
|
+
reader = csv.DictReader(f)
|
|
162
|
+
raw_rows = list(reader)
|
|
163
|
+
except (OSError, csv.Error, UnicodeDecodeError) as e:
|
|
164
|
+
return handle_adapter_error("CSV file reading", e)
|
|
165
|
+
|
|
166
|
+
# Coerce numeric strings to Python int/float so downstream type inference works.
|
|
167
|
+
rows: list[dict[str, Any]] = [
|
|
168
|
+
{k: _coerce_csv_value(v) for k, v in row.items()} for row in raw_rows
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
if not rows:
|
|
172
|
+
return QueryResult(data=[])
|
|
173
|
+
|
|
174
|
+
# Get column names from first row
|
|
175
|
+
available_columns = list(rows[0].keys())
|
|
176
|
+
|
|
177
|
+
# Apply column selection if specified
|
|
178
|
+
columns = query.columns
|
|
179
|
+
if columns:
|
|
180
|
+
# Resolve Jinja in column names
|
|
181
|
+
try:
|
|
182
|
+
resolved_columns = [
|
|
183
|
+
resolve_jinja_template(col, variables) for col in columns
|
|
184
|
+
]
|
|
185
|
+
# Filter to only existing columns
|
|
186
|
+
existing_columns = [
|
|
187
|
+
col for col in resolved_columns if col in available_columns
|
|
188
|
+
]
|
|
189
|
+
if existing_columns:
|
|
190
|
+
# Keep only selected columns
|
|
191
|
+
rows = [{col: row[col] for col in existing_columns} for row in rows]
|
|
192
|
+
available_columns = existing_columns
|
|
193
|
+
except (ValueError, KeyError, TypeError) as e:
|
|
194
|
+
return handle_adapter_error("CSV column name resolution", e)
|
|
195
|
+
|
|
196
|
+
# Apply filtering if specified (basic exact match, AND logic)
|
|
197
|
+
filter_dict = query.filter
|
|
198
|
+
if filter_dict:
|
|
199
|
+
try:
|
|
200
|
+
# Resolve Jinja in filter values
|
|
201
|
+
resolved_filter: dict[str, Any] = {}
|
|
202
|
+
for key, value in filter_dict.items():
|
|
203
|
+
if isinstance(value, str):
|
|
204
|
+
resolved_value = resolve_jinja_template(value, variables)
|
|
205
|
+
else:
|
|
206
|
+
resolved_value = value
|
|
207
|
+
resolved_filter[key] = resolved_value
|
|
208
|
+
|
|
209
|
+
# Skip filters that resolved to None or empty string (conditional filters)
|
|
210
|
+
active_filters = {
|
|
211
|
+
k: v
|
|
212
|
+
for k, v in resolved_filter.items()
|
|
213
|
+
if v is not None and v != "" and str(v).lower() != "none"
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if active_filters:
|
|
217
|
+
# Apply filters (exact match, AND logic)
|
|
218
|
+
filtered_rows = []
|
|
219
|
+
for row in rows:
|
|
220
|
+
match = True
|
|
221
|
+
for key, value in active_filters.items():
|
|
222
|
+
if key not in available_columns:
|
|
223
|
+
continue
|
|
224
|
+
# Convert both to string for comparison (CSV reads everything as string)
|
|
225
|
+
if str(row.get(key, "")) != str(value):
|
|
226
|
+
match = False
|
|
227
|
+
break
|
|
228
|
+
if match:
|
|
229
|
+
filtered_rows.append(row)
|
|
230
|
+
rows = filtered_rows
|
|
231
|
+
except (ValueError, KeyError, TypeError) as e:
|
|
232
|
+
return handle_adapter_error("CSV filter application", e)
|
|
233
|
+
|
|
234
|
+
# Apply limit if specified
|
|
235
|
+
limit = query.limit
|
|
236
|
+
if limit is not None and limit > 0:
|
|
237
|
+
rows = rows[:limit]
|
|
238
|
+
|
|
239
|
+
return QueryResult(data=rows)
|