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,8 @@
|
|
|
1
|
+
"""Shared JSON output helper for dft verbs."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def print_json_result(result: BaseModel) -> None:
|
|
7
|
+
"""Write result as JSON to stdout — stable wire contract for all dft verbs."""
|
|
8
|
+
print(result.model_dump_json(exclude_none=True, indent=2))
|
dataface/cli/_parsing.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Parse repeatable `--flag key=value` options into a dict."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parse_kv_pairs(items: Iterable[str], flag: str) -> dict[str, str]:
|
|
11
|
+
out: dict[str, str] = {}
|
|
12
|
+
for item in items:
|
|
13
|
+
if "=" not in item:
|
|
14
|
+
raise typer.BadParameter(f"{flag} expects key=value, got: {item!r}")
|
|
15
|
+
k, v = item.split("=", 1)
|
|
16
|
+
out[k] = v
|
|
17
|
+
return out
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Compose the `dft --version` line: version, install path, Python, editable flag."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import platform
|
|
7
|
+
import sys
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from importlib import metadata
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import dataface
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class VersionInfo:
|
|
17
|
+
version: str
|
|
18
|
+
package_dir: Path
|
|
19
|
+
python_version: str
|
|
20
|
+
python_executable: str
|
|
21
|
+
editable: bool
|
|
22
|
+
|
|
23
|
+
def render(self) -> str:
|
|
24
|
+
editable = " (editable)" if self.editable else ""
|
|
25
|
+
return (
|
|
26
|
+
f"dft {self.version} from {self.package_dir}{editable} "
|
|
27
|
+
f"(Python {self.python_version}, {self.python_executable})"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _read_direct_url_json() -> str | None:
|
|
32
|
+
try:
|
|
33
|
+
return metadata.distribution("dataface").read_text("direct_url.json")
|
|
34
|
+
except metadata.PackageNotFoundError:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _is_editable() -> bool:
|
|
39
|
+
raw = _read_direct_url_json()
|
|
40
|
+
if not raw:
|
|
41
|
+
return False
|
|
42
|
+
try:
|
|
43
|
+
data = json.loads(raw)
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
return False
|
|
46
|
+
return bool(data.get("dir_info", {}).get("editable"))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def collect() -> VersionInfo:
|
|
50
|
+
return VersionInfo(
|
|
51
|
+
version=dataface.__version__,
|
|
52
|
+
package_dir=Path(dataface.__file__).parent,
|
|
53
|
+
python_version=platform.python_version(),
|
|
54
|
+
python_executable=sys.executable,
|
|
55
|
+
editable=_is_editable(),
|
|
56
|
+
)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Input layer abstraction for dft chat.
|
|
2
|
+
|
|
3
|
+
Two concrete implementations:
|
|
4
|
+
- PromptToolkitInput: pinned prompt bar, multi-line edit, autocompletion,
|
|
5
|
+
arrow-key history. Used when stdin is a real TTY and TERM is not dumb.
|
|
6
|
+
- StdlibInput: wraps typer.prompt + readline history. Fallback for piped
|
|
7
|
+
stdin, dumb terminals, and environments where prompt_toolkit is unavailable.
|
|
8
|
+
|
|
9
|
+
select_input_layer() picks the right layer at runtime.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Protocol, runtime_checkable
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@runtime_checkable
|
|
23
|
+
class InputLayer(Protocol):
|
|
24
|
+
"""One method: read a prompt, return a string. EOFError on Ctrl+D."""
|
|
25
|
+
|
|
26
|
+
def read(self, prompt: str) -> str:
|
|
27
|
+
"""Read one user turn.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
prompt: The prompt string to display.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The user's input as a string.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
EOFError: When the user presses Ctrl+D (end of input).
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class StdlibInput:
|
|
42
|
+
"""Stdlib input: typer.prompt + readline history file."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, hist_path: Path) -> None:
|
|
45
|
+
self._hist_path = hist_path
|
|
46
|
+
self._setup_readline()
|
|
47
|
+
|
|
48
|
+
def _setup_readline(self) -> None:
|
|
49
|
+
try:
|
|
50
|
+
import readline # noqa: PLC0415 # type: ignore[import]
|
|
51
|
+
|
|
52
|
+
self._hist_path.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
if self._hist_path.exists():
|
|
54
|
+
readline.read_history_file(str(self._hist_path))
|
|
55
|
+
readline.set_history_length(1000)
|
|
56
|
+
import atexit # noqa: PLC0415
|
|
57
|
+
|
|
58
|
+
atexit.register(readline.write_history_file, str(self._hist_path))
|
|
59
|
+
except (ImportError, OSError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def read(self, prompt: str) -> str:
|
|
63
|
+
return typer.prompt(prompt.rstrip(": "))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DftCompleter:
|
|
67
|
+
"""Completer for dft chat: slash commands and @-path completions."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, slash_commands: set[str]) -> None:
|
|
70
|
+
self._slash_commands = sorted(slash_commands)
|
|
71
|
+
|
|
72
|
+
def get_completions(self, document: Any, complete_event: Any) -> list[Any]:
|
|
73
|
+
from prompt_toolkit.completion import Completion # noqa: PLC0415
|
|
74
|
+
|
|
75
|
+
# document is a prompt_toolkit Document
|
|
76
|
+
text: str = document.text_before_cursor
|
|
77
|
+
|
|
78
|
+
# Slash-command completions: only when buffer starts with /
|
|
79
|
+
if text.startswith("/"):
|
|
80
|
+
typed_cmd = text[1:] # strip leading /
|
|
81
|
+
completions = []
|
|
82
|
+
for cmd in self._slash_commands:
|
|
83
|
+
if cmd.startswith(typed_cmd):
|
|
84
|
+
# Completion.text is what gets appended to the buffer
|
|
85
|
+
completions.append(
|
|
86
|
+
Completion(cmd[len(typed_cmd) :], display=f"/{cmd}")
|
|
87
|
+
)
|
|
88
|
+
return completions
|
|
89
|
+
|
|
90
|
+
# @-path completions: delegate to PathCompleter when buffer contains @
|
|
91
|
+
if "@" in text:
|
|
92
|
+
at_pos = text.rfind("@")
|
|
93
|
+
before_at = text[:at_pos]
|
|
94
|
+
# Only trigger if @ is at word boundary (not inside a word like email)
|
|
95
|
+
if before_at and before_at[-1].isalnum():
|
|
96
|
+
return []
|
|
97
|
+
partial_path = text[at_pos + 1 :]
|
|
98
|
+
from prompt_toolkit.completion import PathCompleter # noqa: PLC0415
|
|
99
|
+
from prompt_toolkit.document import Document # noqa: PLC0415
|
|
100
|
+
|
|
101
|
+
# Build a sub-document for PathCompleter using just the path portion
|
|
102
|
+
sub_doc = Document(partial_path, len(partial_path))
|
|
103
|
+
path_completions = []
|
|
104
|
+
pc = PathCompleter(min_input_len=0)
|
|
105
|
+
for c in pc.get_completions(
|
|
106
|
+
sub_doc, complete_event
|
|
107
|
+
): # complete_event is CompleteEvent from prompt_toolkit
|
|
108
|
+
path_completions.append(
|
|
109
|
+
Completion(
|
|
110
|
+
c.text, start_position=c.start_position, display=c.display
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
return path_completions
|
|
114
|
+
|
|
115
|
+
return []
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class PromptToolkitInput:
|
|
119
|
+
"""prompt_toolkit input: pinned prompt, multi-line, history, completions.
|
|
120
|
+
|
|
121
|
+
Enter submits; Escape+Enter (Meta+Enter) inserts a newline.
|
|
122
|
+
The default for multiline=True is the opposite, so we override with
|
|
123
|
+
explicit KeyBindings — matching Claude Code / ChatGPT / Slack behavior.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(self, hist_path: Path, slash_commands: set[str]) -> None:
|
|
127
|
+
from prompt_toolkit import PromptSession # noqa: PLC0415
|
|
128
|
+
from prompt_toolkit.history import FileHistory # noqa: PLC0415
|
|
129
|
+
from prompt_toolkit.key_binding import KeyBindings # noqa: PLC0415
|
|
130
|
+
|
|
131
|
+
hist_path.parent.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
|
|
133
|
+
kb = KeyBindings()
|
|
134
|
+
|
|
135
|
+
@kb.add("enter")
|
|
136
|
+
def _submit(event: Any) -> None:
|
|
137
|
+
event.current_buffer.validate_and_handle()
|
|
138
|
+
|
|
139
|
+
@kb.add("escape", "enter")
|
|
140
|
+
def _newline_meta(event: Any) -> None:
|
|
141
|
+
event.current_buffer.insert_text("\n")
|
|
142
|
+
|
|
143
|
+
self._session: PromptSession = PromptSession(
|
|
144
|
+
history=FileHistory(str(hist_path)),
|
|
145
|
+
# DftCompleter is duck-typed against the Completer protocol; we
|
|
146
|
+
# don't inherit because prompt_toolkit imports are deferred to
|
|
147
|
+
# keep it an optional dep.
|
|
148
|
+
completer=DftCompleter(slash_commands), # type: ignore[arg-type]
|
|
149
|
+
multiline=True,
|
|
150
|
+
key_bindings=kb,
|
|
151
|
+
)
|
|
152
|
+
# Single-slot timestamp store for double-tap-exit. prompt_toolkit
|
|
153
|
+
# intercepts SIGINT and raises KeyboardInterrupt in the read thread,
|
|
154
|
+
# so the chat loop's signal handler never fires while we own the
|
|
155
|
+
# prompt. We track Ctrl+C state here instead.
|
|
156
|
+
self._last_sigint_ts: list[float] = []
|
|
157
|
+
|
|
158
|
+
def read(self, prompt: str) -> str:
|
|
159
|
+
try:
|
|
160
|
+
return self._session.prompt(prompt)
|
|
161
|
+
except KeyboardInterrupt:
|
|
162
|
+
import time # noqa: PLC0415
|
|
163
|
+
|
|
164
|
+
now = time.monotonic()
|
|
165
|
+
if self._last_sigint_ts and (now - self._last_sigint_ts[0]) < 1.0:
|
|
166
|
+
# Double-tap within 1s — end the session.
|
|
167
|
+
raise EOFError from None
|
|
168
|
+
if self._last_sigint_ts:
|
|
169
|
+
self._last_sigint_ts[0] = now
|
|
170
|
+
else:
|
|
171
|
+
self._last_sigint_ts.append(now)
|
|
172
|
+
print(
|
|
173
|
+
"(Press Ctrl+C again within 1s to exit, or Ctrl+D)",
|
|
174
|
+
file=sys.stderr,
|
|
175
|
+
)
|
|
176
|
+
return ""
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def select_input_layer(
|
|
180
|
+
hist_path: Path, slash_commands: set[str] | None = None
|
|
181
|
+
) -> InputLayer:
|
|
182
|
+
"""Select the right input layer for the current environment.
|
|
183
|
+
|
|
184
|
+
Returns PromptToolkitInput when:
|
|
185
|
+
- stdin is a TTY
|
|
186
|
+
- TERM is not "dumb"
|
|
187
|
+
- prompt_toolkit can be imported
|
|
188
|
+
|
|
189
|
+
Otherwise returns StdlibInput.
|
|
190
|
+
"""
|
|
191
|
+
if slash_commands is None:
|
|
192
|
+
slash_commands = set()
|
|
193
|
+
|
|
194
|
+
if not sys.stdin.isatty():
|
|
195
|
+
return StdlibInput(hist_path)
|
|
196
|
+
|
|
197
|
+
if os.environ.get("TERM") == "dumb":
|
|
198
|
+
return StdlibInput(hist_path)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
import prompt_toolkit # noqa: PLC0415, F401
|
|
202
|
+
except ImportError:
|
|
203
|
+
return StdlibInput(hist_path)
|
|
204
|
+
|
|
205
|
+
return PromptToolkitInput(hist_path, slash_commands)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Embedded HTTP server lifecycle for `dft chat`.
|
|
2
|
+
|
|
3
|
+
`dft chat` runs the same FastAPI app that `dft mcp serve` exposes alongside
|
|
4
|
+
its stdio MCP transport, so that URLs the model emits via `render_dashboard`
|
|
5
|
+
(e.g. `http://localhost:<port>/<face>`) actually open a live preview. Without
|
|
6
|
+
this, those URLs 404.
|
|
7
|
+
|
|
8
|
+
The agent's chat loop is sync; uvicorn needs asyncio. We host the server on a
|
|
9
|
+
dedicated background thread + asyncio loop owned by these helpers — separate
|
|
10
|
+
from the external-MCP manager's loop so neither lifecycle blocks the other.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import contextlib
|
|
17
|
+
import threading
|
|
18
|
+
from concurrent.futures import Future
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
import uvicorn
|
|
22
|
+
|
|
23
|
+
from dataface.core.serve.embedded import build_embedded_server
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class EmbeddedServerHandle:
|
|
28
|
+
"""Handle for the agent's embedded preview HTTP server."""
|
|
29
|
+
|
|
30
|
+
server: uvicorn.Server
|
|
31
|
+
port: int
|
|
32
|
+
loop: asyncio.AbstractEventLoop
|
|
33
|
+
thread: threading.Thread
|
|
34
|
+
serve_future: Future[None]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def start_http_server(*, port_hint: int = 8765) -> EmbeddedServerHandle:
|
|
38
|
+
"""Start the dataface FastAPI server on a background thread.
|
|
39
|
+
|
|
40
|
+
Resolves a free port starting at *port_hint* via `find_available_port` so
|
|
41
|
+
the agent doesn't collide with a `dft mcp serve` already on 8765.
|
|
42
|
+
|
|
43
|
+
Returns a handle the caller hands to `stop_http_server` for teardown.
|
|
44
|
+
Surfaces `ImportError` (uvicorn missing — install `dataface[mcp]`) and
|
|
45
|
+
`RuntimeError` (no free port near *port_hint*) directly from
|
|
46
|
+
`build_embedded_server`. If uvicorn binds but then raises (TOCTTOU race),
|
|
47
|
+
the underlying `OSError` is raised here too — no silent swallows.
|
|
48
|
+
"""
|
|
49
|
+
server, resolved = build_embedded_server(port_hint=port_hint)
|
|
50
|
+
|
|
51
|
+
loop = asyncio.new_event_loop()
|
|
52
|
+
thread = threading.Thread(
|
|
53
|
+
target=loop.run_forever, daemon=True, name="dft-chat-http"
|
|
54
|
+
)
|
|
55
|
+
thread.start()
|
|
56
|
+
|
|
57
|
+
serve_future = asyncio.run_coroutine_threadsafe(server.serve(), loop)
|
|
58
|
+
|
|
59
|
+
# Wait until uvicorn signals it has started OR the serve coroutine raises.
|
|
60
|
+
# Polling both protects against `serve()` failing to bind (in which case
|
|
61
|
+
# serve_future completes with an OSError) — without this, a bind failure
|
|
62
|
+
# would just look like a 5-second timeout with the real cause swallowed.
|
|
63
|
+
deadline = threading.Event()
|
|
64
|
+
|
|
65
|
+
async def _wait_started() -> None:
|
|
66
|
+
while not server.started:
|
|
67
|
+
await asyncio.sleep(0.05)
|
|
68
|
+
deadline.set()
|
|
69
|
+
|
|
70
|
+
asyncio.run_coroutine_threadsafe(_wait_started(), loop)
|
|
71
|
+
|
|
72
|
+
import time
|
|
73
|
+
|
|
74
|
+
deadline_ts = time.monotonic() + 5.0
|
|
75
|
+
while not deadline.wait(timeout=0.05):
|
|
76
|
+
if serve_future.done():
|
|
77
|
+
# serve() returned/raised before started flipped — surface the
|
|
78
|
+
# underlying error rather than the generic timeout below.
|
|
79
|
+
exc = serve_future.exception()
|
|
80
|
+
if exc is not None:
|
|
81
|
+
raise exc
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
f"HTTP server exited before signaling started on port {resolved}"
|
|
84
|
+
)
|
|
85
|
+
if time.monotonic() >= deadline_ts:
|
|
86
|
+
server.should_exit = True
|
|
87
|
+
raise RuntimeError(
|
|
88
|
+
f"HTTP server failed to start within 5s on port {resolved}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return EmbeddedServerHandle(
|
|
92
|
+
server=server,
|
|
93
|
+
port=resolved,
|
|
94
|
+
loop=loop,
|
|
95
|
+
thread=thread,
|
|
96
|
+
serve_future=serve_future,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def stop_http_server(handle: EmbeddedServerHandle) -> None:
|
|
101
|
+
"""Stop the HTTP server started by `start_http_server`.
|
|
102
|
+
|
|
103
|
+
The handle's attributes are guaranteed by `start_http_server` (which is
|
|
104
|
+
the only constructor) — no defensive `getattr` needed.
|
|
105
|
+
"""
|
|
106
|
+
handle.server.should_exit = True
|
|
107
|
+
|
|
108
|
+
# Best-effort: if serve() raises on shutdown, continue teardown anyway.
|
|
109
|
+
with contextlib.suppress(Exception):
|
|
110
|
+
handle.serve_future.result(timeout=5.0)
|
|
111
|
+
|
|
112
|
+
if not handle.loop.is_closed():
|
|
113
|
+
handle.loop.call_soon_threadsafe(handle.loop.stop)
|
|
114
|
+
|
|
115
|
+
handle.thread.join(timeout=5.0)
|