mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-16 20:09:13 +00:00
* chore: remove dead code, deprecate unused plugin APIs (over-engineering audit) Whole-tree audit. Every symbol was checked against core, the plugin monorepo and all eight third-party plugins in plugins.json first. - Deprecate (removal 3.10.0) plugin-facing methods nothing calls: LogoDownloader bulk download, ConfigManager backup/secret wrappers, APIHelper extras, BackgroundDataService poll API, PluginManager / PluginStateManager info readers, and a few CacheManager, FontManager, BaseOddsManager, DynamicTeamResolver methods and PluginTestCase. plugin_api_usage.py learns their receiver names; DEPRECATIONS doc regenerated. - Remove core-internal dead code: CacheMetrics, Vegas status/stats plumbing, sync "new cycle" message (followers ignore unknown types), unused operation types, test-only PluginCatalog readers, IPC to_dict and ping, _parse_form_value, CacheStrategyProtocol, ErrorAggregator callbacks, duplicate web response helpers. - Web UI: drop never-mounted json-file-manager.js, the example widget, utils/error_handler.js, four uncalled PluginAPI methods, and 29 escapeHtml shims (call window.LEDEscape directly). Public globals, BaseWidget and widget names unchanged. - Remove six one-off scripts (owner decision) and the unused markupsafe and pytest-mock pins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web): calendar picker error text goes in a text node, not innerHTML Same output as the escaped innerHTML it replaces; clears Codacy's XSS-pattern alerts on the line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
3.4 KiB
Python
76 lines
3.4 KiB
Python
"""Putting a submitted plugin config's lists back into list shape.
|
|
|
|
A list reaches a plugin-config save keyed by position more often than as a
|
|
list. The settings form posts one field per element (``feeds.custom_feeds.0.name``),
|
|
which ``_set_nested_value`` stores as ``{"0": {"name": ...}}``, and a JSON
|
|
save built by flattening then re-nesting dotted keys carries the same dict.
|
|
Validation expects an array there, so the save converts them first.
|
|
"""
|
|
from typing import Any, Dict
|
|
|
|
|
|
def _schema_type_is(prop: Any, wanted: str) -> bool:
|
|
"""Whether a schema property is of ``wanted`` type, unions included.
|
|
|
|
The one copy: ``web_interface/blueprints/api_v3`` imports it from here
|
|
(src/ must not import the Flask blueprint). A union such as
|
|
``["array", "null"]`` -- the per-element style overrides, where null means
|
|
"inherit" -- is still an array for recombining position-keyed inputs.
|
|
"""
|
|
if not isinstance(prop, dict):
|
|
return False
|
|
declared = prop.get('type')
|
|
if isinstance(declared, list):
|
|
return wanted in declared
|
|
return declared == wanted
|
|
|
|
|
|
def _is_index_dict(value: Any) -> bool:
|
|
"""True for a dict keyed only by list positions ("0", "1", ...), or empty."""
|
|
return isinstance(value, dict) and all(str(k).isdigit() for k in value)
|
|
|
|
|
|
def coerce_array_shapes(config: Any, schema_props: Dict[str, Any],
|
|
short_lists_take_default: bool = False) -> None:
|
|
"""Turn position-keyed dicts into lists wherever the schema has an array.
|
|
|
|
Walks ``config`` alongside the schema's ``properties``, in place: into
|
|
nested objects, and into the objects of an array's items. An empty dict
|
|
where an array belongs becomes ``[]``.
|
|
|
|
``short_lists_take_default`` is for form posts. A form draws a fixed-length
|
|
list (an RGB colour, say) as one input per element, and a blanked input
|
|
drops out of the parsed list; the schema default then stands in, as long as
|
|
it is itself long enough, instead of the save failing on ``minItems``.
|
|
|
|
Element types are left alone: normalization after this converts numeric
|
|
strings to the numbers the schema asks for.
|
|
"""
|
|
if not isinstance(config, dict):
|
|
return
|
|
for key, prop_schema in schema_props.items():
|
|
if key not in config or not isinstance(prop_schema, dict):
|
|
continue
|
|
value = config[key]
|
|
|
|
if _schema_type_is(prop_schema, 'array'):
|
|
if _is_index_dict(value):
|
|
value = config[key] = [value[k] for k in sorted(value, key=lambda k: int(str(k)))]
|
|
if not isinstance(value, list):
|
|
continue
|
|
min_items = prop_schema.get('minItems')
|
|
default = prop_schema.get('default')
|
|
if (short_lists_take_default and min_items is not None
|
|
and len(value) < min_items
|
|
and isinstance(default, list) and len(default) >= min_items):
|
|
value = config[key] = list(default)
|
|
items_schema: Any = prop_schema.get('items')
|
|
if (_schema_type_is(items_schema, 'object')
|
|
and 'properties' in items_schema):
|
|
for element in value:
|
|
coerce_array_shapes(element, items_schema['properties'],
|
|
short_lists_take_default)
|
|
|
|
elif _schema_type_is(prop_schema, 'object') and 'properties' in prop_schema:
|
|
coerce_array_shapes(value, prop_schema['properties'], short_lists_take_default)
|