If two plugins claim the same viz_type, display_name_for_viz_type()
silently resolves to the iteration-order winner. Surface a warning at
register() time so plugin authors catch the shadowing immediately.
- big_number.py pre_validate: add sql_expression branch; return
MISSING_SQL_METRIC_LABEL when label is absent/non-string, so the
existing unit tests (and LLM callers) get a clear actionable error.
- xy.py normalize_column_refs: skip entries with sql_expression set
(name is None for these metrics); previously crashed with
AttributeError: 'NoneType'.lower() in _get_canonical_column_name.
- test_big_number_chart.py: replace three calls to deleted
SchemaValidator._pre_validate_big_number_config with
plugin.pre_validate() via get_registry().
- test_runtime_validator.py: replace call to deleted
RuntimeValidator._validate_cardinality with XYChartPlugin.get_runtime_warnings;
patch FormatTypeValidator to isolate cardinality guard.
- test_update_chart.py: set mock_create_preview.return_value to a
3-tuple so the update_chart unpack doesn't crash; change RuntimeError
to ValueError which is in NORMALIZATION_EXCEPTIONS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- schema_validator: collapse is_enabled()+get() double lookup into a
single get() call so operator-supplied enabled_func is invoked once
- update_chart: use guarded chart_datasource_id local var instead of
re-accessing chart.datasource_id after the None check
- chart_utils: propagate post_map_validate() details+suggestions into
the raised ValueError so callers log actionable context
- schemas: clarify chart_type_display_name description — prefer over
viz_type when present, fall back to viz_type when null
- schemas: add or-empty-string fallback to dedup key labels to satisfy
mypy (dict is typed dict[tuple[bool, str], str])
- plugins/xy: guard config.x.name is not None before cardinality check
- runtime/__init__, plugins/xy, registry: add noqa BLE001 to intentional
broad exception catches with inline rationale comments
- tests: add TestUpdateChartColumnNormalization covering normalization
called with guarded ID, graceful exception handling, and skip-when-null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- register() now holds _plugins_lock when writing to _REGISTRY, preventing
concurrent write races if plugins are registered outside the bootstrap path
- _ensure_plugins_loaded() now sets _plugins_load_failed=True on ImportError
so subsequent lookups return None immediately instead of retrying the import
on every call
- _isolated_registry fixture in test_registry.py resets _plugins_load_failed
- Two new tests cover the circuit-breaker skip path and the failure-flag path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _get_canonical_metric_name() to DatasetValidator that searches only
available_metrics, preventing a column with matching case-insensitive name
from shadowing a saved metric's canonical casing.
Update all 7 chart plugins (xy, table, pie, big_number, handlebars,
mixed_timeseries, pivot_table) to branch on saved_metric flag: saved
metrics now go through _get_canonical_metric_name while regular column
refs continue to use _get_canonical_column_name.
Fix pre_validate alias handling in xy and mixed_timeseries plugins to
accept Pydantic AliasChoices keys (metrics/x_axis/metrics_b) so payloads
using canonical Superset field names are not incorrectly rejected.
Add TestGetCanonicalMetricName, TestSavedMetricNormalizationCorrectness,
and TestPreValidateAliasHandling test classes covering the collision case
and alias acceptance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a dynamic filter layer in the chart type registry so operators can
disable individual plugins (e.g. `handlebars`) without a code deploy:
- `MCP_DISABLED_CHART_PLUGINS: frozenset[str]` — static deny-list in mcp_config.py
- `MCP_CHART_PLUGIN_ENABLED_FUNC: Callable[[str], bool] | None` — dynamic hook
for Harness/Split/per-user targeting; takes precedence over the deny-list
- Both keys are propagated through `get_mcp_config()` defaults
registry.py changes:
- `_PluginFilterConfig` frozen dataclass replaces two bare globals so
configure() replaces them atomically (no torn reads under concurrency)
- `configure(disabled, enabled_func)` — called at app init; accepts any
iterable for `disabled`; validates `enabled_func` is callable
- `_is_plugin_enabled()` — reads config once, fails closed on callable exception
- `get()` / `all_types()` / `is_enabled()` apply the filter at lookup time;
`is_registered()` and `display_name_for_viz_type()` intentionally bypass it
so callers can distinguish "unknown" vs "disabled" and existing charts still
resolve display names for disabled viz types
schema_validator.py: two-step pre-check — `is_registered()` for unknown types,
`is_enabled()` for disabled ones, with distinct `DISABLED_CHART_TYPE` error code.
Wiring:
- `SupersetAppInitializer.configure_mcp_chart_registry()` called after
`configure_feature_flags()` in `init_app()`
- `flask_singleton.py` re-calls `registry.configure()` after the MCP config
overlay so MCP-specific overrides in `superset_config.py` take effect in
standalone MCP mode
Tests: 28 cases in test_registry_filters.py covering deny-list, callable hook,
fail-closed on exception, all_types() filtering, display_name bypass, atomic
reconfigure, and configure() with list/tuple/frozenset inputs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split an 89-char comment line and an over-limit condition in update_chart.py
to satisfy the ruff E501 rule. Also applied ruff format.
Two TestUpdateChartValidationGate tests expected CHART_VALIDATION_FAILED but
received CHART_DATASET_NOT_FOUND because _validate_update_against_dataset calls
DatasetValidator.validate_against_dataset before validate_and_compile, and the
existing mocks provided a Mock() object for chart.datasource whose .id attribute
is an auto-generated MagicMock (not a real int). Added a patch for
DatasetValidator.validate_against_dataset returning (True, None) so the
column-validation tier is bypassed and the test reaches the mocked
validate_and_compile response as intended.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P1.1 registry.py: move _plugins_loaded=True to after successful import so a
failed load doesn't permanently poison the registry.
P1.3 schemas.py: remove overly restrictive ColumnRef.name / FilterClause.column
/ BigNumberChartConfig.temporal_column regex that blocked valid column names
containing parentheses, slashes, and other SQL-common characters.
P2.3 (DRY): eliminate _CHART_TYPE_ERROR_HINTS second-registry in
schema_validator.py by adding schema_error_hint() to ChartTypePlugin protocol,
BaseChartPlugin default, and all 7 plugin classes. SchemaValidator now delegates
to the plugin registry instead of maintaining a parallel dict.
P3.3 test_registry.py: add full registry unit-test coverage (register, get,
all_types, is_registered, display_name_for_viz_type, proxy methods, duplicate
warning, empty chart_type validation, insertion-order guarantee).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Split long string literal in schema_validator.py line 202 (E501, 94 > 88 chars)
- Apply ruff format auto-fixes to big_number.py, handlebars.py, and test_get_chart_data.py
On top of the dead-code elimination in the previous commit:
- Add lazy _ensure_plugins_loaded() bootstrap to ChartTypeRegistry so the
registry is populated even without importing app.py (fixes isolated test runs)
- Delegate _RegistryProxy methods to module-level functions so bootstrap runs
- Guard register() against empty chart_type strings
- Add generate_name + resolve_viz_type to ChartTypePlugin Protocol and
BaseChartPlugin; delegate generate_chart_name/_resolve_viz_type in
chart_utils to the plugin registry
- Add _with_context static helper to BaseChartPlugin (shared by all plugins)
- Fix stale 'five methods' → 'eight methods' docstring in plugin.py
- Add TypeVar _C to normalize_column_names so mypy infers correct return type
- Fix broken tests: update _pre_validate_big_number_config → _pre_validate_chart_type,
remove deleted TestNormalizeXYConfig/TestNormalizeTableConfig classes,
update runtime validator tests for removed _validate_format_compatibility /
_validate_cardinality methods, add x is not None narrowing guards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BigQuery rejects filter values containing apostrophes (e.g. O'Brien): the
sqlalchemy-bigquery dialect renders string literals via repr(), which switches
to double-quote delimiters that BigQuery parses as identifiers, causing a
syntax error.
Monkey-patch the dialect's colspecs with a TypeDecorator whose literal_processor
emits single-quoted literals using backslash escaping ('O\'Brien'). Doubled
single quotes ('O''Brien') are NOT valid in BigQuery (parsed as concatenated
literals). Control characters are emitted as named escapes with a \xhh fallback,
since BigQuery forbids literal control chars in quoted strings. Follows the
existing Databricks dialect pattern.
Fixes#35857