chore(deprecation): remove the 35 APIs deprecated for 3.8.0 (#708)

* chore(deprecation): remove the 35 APIs deprecated for 3.8.0

The usage scan (docs/DEPRECATIONS_3.8.md, regenerated 2026-10-01 and
committed here) finds no call or override of any of them in the 46
monorepo plugins or the 8 third-party plugins plugins.json lists; the
only core callers were other deprecated methods removed alongside.

- CacheManager: 13 methods, plus the private helpers only
  has_data_changed used (_has_*_changed, _is_market_open).
- DisplayManager: 7 methods, plus WEATHER_COLORS and the private
  _draw_sun/_cloud/_rain/_snow/_storm helpers only the icon methods used.
- FontManager: 14 methods, plus size_tokens, _save_overrides and
  _clear_plugin_font_cache. font_overrides and _load_overrides stay:
  resolve_font() still applies config/font_overrides.json.
  performance_stats stays: get_font() keeps it and tests read it.
- PluginManager.get_enabled_plugins.

test_deprecation.py pins only the two 3.9.0 markers now; the scanner
tests run against a stand-in core instead of the real markers. The
memory-tier tests read stats through log_memory_cache_stats() and the
component, and the test of the removed _clear_plugin_font_cache goes.
Docs drop the removed methods' reference entries; the Deprecated APIs
table becomes "Removed in 3.8.0". CHANGELOG gains a Removed section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore(deprecation): drop the test harness's copies of the removed icon methods

VisualTestDisplayManager still drew weather icons that DisplayManager no
longer has, so a plugin's visual tests could pass on calls that raise
AttributeError on the real display. Its draw_sun/draw_cloud/draw_rain/
draw_snow/draw_weather_icon/draw_text_with_icons, WEATHER_COLORS and the
private helpers go, with the tests that exercised them. The CHANGELOG's
Deprecations entries no longer say nothing is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-01 10:45:28 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 695ff92009
commit 2601cb4cbb
16 changed files with 169 additions and 1118 deletions
-39
View File
@@ -196,42 +196,3 @@ class TestVisualDisplayManager:
assert '11th' in result
class TestWeatherDrawing:
"""Test weather icon rendering."""
def test_draw_sun(self):
vdm = VisualTestDisplayManager(width=128, height=32)
vdm.draw_sun(0, 0, 16)
pixels = list(vdm.image.getdata())
non_black = [p for p in pixels if p != (0, 0, 0)]
assert len(non_black) > 0
def test_draw_cloud(self):
vdm = VisualTestDisplayManager(width=128, height=32)
vdm.draw_cloud(0, 0, 16)
pixels = list(vdm.image.getdata())
non_black = [p for p in pixels if p != (0, 0, 0)]
assert len(non_black) > 0
def test_draw_rain(self):
vdm = VisualTestDisplayManager(width=128, height=32)
vdm.draw_rain(0, 0, 16)
pixels = list(vdm.image.getdata())
non_black = [p for p in pixels if p != (0, 0, 0)]
assert len(non_black) > 0
def test_draw_snow(self):
vdm = VisualTestDisplayManager(width=128, height=32)
vdm.draw_snow(0, 0, 16)
pixels = list(vdm.image.getdata())
non_black = [p for p in pixels if p != (0, 0, 0)]
assert len(non_black) > 0
def test_draw_weather_icon_dispatches(self):
vdm = VisualTestDisplayManager(width=128, height=32)
for condition in ['clear', 'cloudy', 'rain', 'snow', 'storm', 'unknown']:
vdm.clear()
vdm.draw_weather_icon(condition, 0, 0, 16)
pixels = list(vdm.image.getdata())
non_black = [p for p in pixels if p != (0, 0, 0)]
assert len(non_black) > 0, f"draw_weather_icon('{condition}') should render pixels"
+10 -12
View File
@@ -40,11 +40,9 @@ def test_cleanup_and_stats_follow_a_replaced_component(cm):
assert cm._memory_cache_component.get("stale") is None
assert cm._memory_cache_component.get("fresh") == {"v": 1}
stats = cm.get_memory_cache_stats()
assert stats["size"] == 1
assert stats["max_size"] == 7
assert stats["cleanup_interval"] == 11.0
assert stats["usage_percent"] == pytest.approx(100 / 7)
with patch.object(cm.logger, "info") as info:
cm.log_memory_cache_stats()
assert "Size: 1/7 (14.3%)" in info.call_args[0][0]
def test_periodic_cleanup_is_throttled_and_records_its_run(cm):
@@ -60,16 +58,16 @@ def test_periodic_cleanup_is_throttled_and_records_its_run(cm):
before = time.time()
cm.get_cached_data("missing") # triggers the periodic sweep
assert mem.size() == 0
assert cm.get_memory_cache_stats()["last_cleanup"] >= before
assert mem.get_stats()["last_cleanup"] >= before
def test_stats_have_the_documented_shape(cm):
def test_memory_stats_log_reads_the_live_tier(cm):
cm.set("k", {"v": 1})
stats = cm.get_memory_cache_stats()
assert set(stats) == {"size", "max_size", "usage_percent",
"last_cleanup", "cleanup_interval"}
assert stats["size"] == 1
assert stats["max_size"] == cm._memory_cache_component.max_size()
with patch.object(cm.logger, "info") as info:
cm.log_memory_cache_stats()
message = info.call_args[0][0]
assert f"Size: 1/{cm._memory_cache_component.max_size()}" in message
assert "Last cleanup:" in message
def test_listing_the_cache_dir_does_not_hold_the_memory_lock(cm, tmp_path):
+62 -40
View File
@@ -20,31 +20,10 @@ from src.deprecation import deprecated
REPO = Path(__file__).resolve().parents[1]
#: Everything deprecated for removal in 3.8.0 (first announced for 3.7.0,
#: which shipped with all of them still in place). docs/DEPRECATIONS_3.8.md
#: says which are unused. Removing one of these, or deprecating another,
#: should be a deliberate edit here too.
DEPRECATED = {
"src.cache_manager.CacheManager": [
"has_data_changed", "update_cache", "setup_persistent_cache",
"get_sport_live_interval", "get_sport_key_from_cache_key",
"get_background_cached_data", "is_background_data_available",
"record_cache_hit", "record_cache_miss", "record_fetch_time",
"get_cache_metrics", "log_cache_metrics", "get_memory_cache_stats",
],
"src.display_manager.DisplayManager": [
"draw_sun", "draw_cloud", "draw_rain", "draw_snow", "draw_weather_icon",
"draw_text_with_icons", "get_scrolling_stats",
],
"src.font_manager.FontManager": [
"get_manager_fonts", "get_detected_fonts", "unregister_plugin_fonts",
"get_plugin_fonts", "set_override", "remove_override", "get_overrides",
"get_available_fonts", "get_size_tokens", "get_performance_stats",
"get_font_catalog", "add_font", "remove_font", "validate_font",
],
"src.plugin_system.plugin_manager.PluginManager": ["get_enabled_plugins"],
}
#: Everything still deprecated. (The 35 methods deprecated for 3.8.0 were
#: removed in it: docs/DEPRECATIONS_3.8.md found them unused.) Removing one
#: of these, or deprecating another, should be a deliberate edit here too.
#:
#: Deprecated with Vegas participation, for removal in 3.9.0: core never read
#: them (src.plugin_system.base_plugin.VEGAS_LEGACY_REMOVAL).
DEPRECATED_3_9 = {
@@ -55,7 +34,7 @@ DEPRECATED_3_9 = {
#: Every pinned marker: (class path, method) -> the release that removes it.
PINNED = {(path, name): removal
for removal, table in (("3.8.0", DEPRECATED), ("3.9.0", DEPRECATED_3_9))
for removal, table in (("3.9.0", DEPRECATED_3_9),)
for path, names in table.items() for name in names}
@@ -132,7 +111,57 @@ def test_usage_script_lists_exactly_the_pinned_markers(usage_script):
assert found == {(path, name, removal) for (path, name), removal in PINNED.items()}
def test_usage_script_tells_uses_from_name_collisions(usage_script, tmp_path):
#: A stand-in core for the scanner tests below, so they keep working whichever
#: real markers exist (the 3.8.0 ones they were written against are gone).
FAKE_CORE = {
"src/cache_manager.py": """\
class CacheManager:
@deprecated("9.9.0", "use set()")
def update_cache(self, key, data):
pass
""",
"src/display_manager.py": """\
class DisplayManager:
@deprecated("9.9.0")
def draw_sun(self, x, y):
pass
@deprecated("9.9.0")
def draw_cloud(self, x, y):
pass
@deprecated("9.9.0")
def draw_rain(self, x, y):
self.draw_cloud(x, y)
@deprecated("9.9.0")
def draw_snow(self, x, y):
pass
@deprecated("9.9.0")
def get_scrolling_stats(self):
return {}
""",
"src/font_manager.py": """\
class FontManager:
@deprecated("9.9.0")
def add_font(self, path, name):
return True
""",
}
@pytest.fixture
def fake_core(tmp_path):
root = tmp_path / "core"
for rel, source in FAKE_CORE.items():
path = root / rel
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(textwrap.dedent(source), encoding="utf-8")
return root
def test_usage_script_tells_uses_from_name_collisions(usage_script, fake_core, tmp_path):
"""Calls on the owning object and overrides count; same-named methods of
unrelated classes and hits in test files do not."""
plugin = tmp_path / "demo"
@@ -163,7 +192,7 @@ def test_usage_script_tells_uses_from_name_collisions(usage_script, tmp_path):
display_manager.draw_snow.assert_not_called()
"""), encoding="utf-8")
markers = usage_script.find_markers(REPO)
markers = usage_script.find_markers(fake_core)
source = usage_script.Source("demo", "monorepo", plugin)
usage_script.scan_tree(source, [plugin], plugin, markers, core=False)
kinds = {key: sorted(("test " if h.test else "") + h.kind for h in hits)
@@ -186,11 +215,12 @@ def test_usage_script_tells_uses_from_name_collisions(usage_script, tmp_path):
assert status["DisplayManager.draw_snow"][0] == "unused" # a test mock only
def test_usage_script_follows_calls_between_deprecated_core_methods(usage_script):
def test_usage_script_follows_calls_between_deprecated_core_methods(usage_script, fake_core):
"""draw_rain calls draw_cloud; with no outside callers both are unused."""
markers = usage_script.find_markers(REPO)
core = usage_script.Source("core", "core", REPO)
usage_script.scan_tree(core, [REPO / "src" / "display_manager.py"], REPO, markers, core=True)
markers = usage_script.find_markers(fake_core)
core = usage_script.Source("core", "core", fake_core)
usage_script.scan_tree(core, [fake_core / "src" / "display_manager.py"], fake_core,
markers, core=True)
kinds = {h.kind for h in core.hits["DisplayManager.draw_cloud"]}
assert kinds == {"internal"}
assert usage_script.verdicts(markers, [core])["DisplayManager.draw_cloud"][0] == "unused"
@@ -219,11 +249,3 @@ def test_first_call_warns_and_logs_then_stays_quiet(fresh, caplog):
assert caught[0].filename == __file__ # points at the caller
assert sum("will be removed in LEDMatrix 9.9.9" in r.message for r in caplog.records) == 1
assert old.__name__ == "old" and old.__doc__ == "Doc."
def test_decorated_methods_still_work(fresh):
from src.font_manager import FontManager
fm = FontManager({})
with warnings.catch_warnings():
warnings.simplefilter("ignore")
assert fm.get_font_catalog() == fm.font_catalog
-10
View File
@@ -150,16 +150,6 @@ class TestCacheLifecycle:
fm.clear_cache()
assert fm.cache_generation == gen_before + 1
def test_clearing_a_plugins_cached_fonts_bumps_generation(self, fm):
fm.font_cache["demo::tiny_8"] = object()
gen_before = fm.cache_generation
fm._clear_plugin_font_cache("demo")
assert "demo::tiny_8" not in fm.font_cache
assert fm.cache_generation == gen_before + 1
# Nothing to drop, nothing to rebuild.
fm._clear_plugin_font_cache("demo")
assert fm.cache_generation == gen_before + 1
class TestPluginFonts:
"""plugin:// sources resolve against the plugin's own directory, which