Skip to content

Commit c887405

Browse files
Wheemercursoragent
andcommitted
Avoid duplicate HTTP view registration on reload.
Home Assistant keeps PlainResource routes for the process lifetime, so unload now clears tracked views and setup skips paths that are already registered. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a250f9e commit c887405

2 files changed

Lines changed: 37 additions & 25 deletions

File tree

custom_components/ip_ban_manager/__init__.py

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,8 +1187,35 @@ async def post(self, request: Request) -> Response:
11871187
return self.json(_panel_payload(hass, entry))
11881188

11891189

1190+
def _integration_view_urls() -> set[str]:
1191+
"""Return the HTTP paths owned by IP Ban Manager."""
1192+
return {
1193+
url
1194+
for url in (
1195+
SilenceAllowlistedLoginNotificationsView.url,
1196+
IPBanManagerStatusView.url,
1197+
IPBanManagerManageView.url,
1198+
)
1199+
if url
1200+
}
1201+
1202+
1203+
def _registered_integration_view_urls(hass: HomeAssistant) -> set[str]:
1204+
"""Return integration view paths already registered on the HTTP router."""
1205+
owned_urls = _integration_view_urls()
1206+
registered_urls = set()
1207+
for route in hass.http.app.router.routes():
1208+
resource = route.resource
1209+
if resource is None:
1210+
continue
1211+
canonical = resource.canonical
1212+
if canonical in owned_urls:
1213+
registered_urls.add(canonical)
1214+
return registered_urls
1215+
1216+
11901217
def _register_http_views(hass: HomeAssistant) -> None:
1191-
"""Register HTTP API views once per setup cycle."""
1218+
"""Register HTTP API views once per Home Assistant process."""
11921219
if hass.data.get(KEY_HTTP_VIEWS):
11931220
return
11941221

@@ -1197,34 +1224,17 @@ def _register_http_views(hass: HomeAssistant) -> None:
11971224
IPBanManagerStatusView(),
11981225
IPBanManagerManageView(),
11991226
)
1227+
registered_urls = _registered_integration_view_urls(hass)
12001228
for view in views:
1229+
if view.url in registered_urls:
1230+
continue
12011231
hass.http.register_view(view)
12021232
hass.data[KEY_HTTP_VIEWS] = views
12031233

12041234

12051235
def _unregister_http_views(hass: HomeAssistant) -> None:
1206-
"""Remove HTTP API views registered by this integration."""
1207-
views = hass.data.pop(KEY_HTTP_VIEWS, None)
1208-
if not views:
1209-
return
1210-
1211-
urls = set()
1212-
for view in views:
1213-
if view.url:
1214-
urls.add(view.url)
1215-
urls.update(view.extra_urls)
1216-
1217-
resources_to_unregister: set[object] = set()
1218-
for route in hass.http.app.router.routes():
1219-
resource = route.resource
1220-
if resource is None:
1221-
continue
1222-
if resource.canonical in urls:
1223-
resources_to_unregister.add(resource)
1224-
1225-
for resource in resources_to_unregister:
1226-
with suppress(ValueError):
1227-
resource.unregister()
1236+
"""Drop tracked HTTP views without removing Home Assistant routes."""
1237+
hass.data.pop(KEY_HTTP_VIEWS, None)
12281238

12291239

12301240
def _coerce_panel_boolean(value: object) -> bool:

tests/ip_ban_manager/test_setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2828,7 +2828,7 @@ async def test_unload_restores_home_assistant_hooks(
28282828
async def test_setup_entry_reregisters_http_views_after_unload(
28292829
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
28302830
) -> None:
2831-
"""Test reloading does not leave duplicate HTTP view routes behind."""
2831+
"""Test reloading does not register duplicate HTTP view routes."""
28322832
await setup_ip_ban_manager(hass)
28332833
entry = hass.config_entries.async_entries(DOMAIN)[0]
28342834
view_urls = {
@@ -2849,12 +2849,14 @@ def count_view_resources() -> int:
28492849

28502850
assert await hass.config_entries.async_unload(entry.entry_id)
28512851
await hass.async_block_till_done()
2852-
assert count_view_resources() == 0
2852+
assert KEY_HTTP_VIEWS not in hass.data
2853+
assert count_view_resources() == 3
28532854

28542855
assert await hass.config_entries.async_setup(entry.entry_id)
28552856
await hass.async_block_till_done()
28562857
check_records(caplog.records)
28572858
assert count_view_resources() == 3
2859+
assert KEY_HTTP_VIEWS in hass.data
28582860

28592861

28602862
@pytest.mark.asyncio

0 commit comments

Comments
 (0)