Skip to content

Fix _remove_plugin and get_hookcallers for multi-impl plugins#646

Open
veeceey wants to merge 1 commit intopytest-dev:mainfrom
veeceey:fix/remove-plugin-all-hookimpls
Open

Fix _remove_plugin and get_hookcallers for multi-impl plugins#646
veeceey wants to merge 1 commit intopytest-dev:mainfrom
veeceey:fix/remove-plugin-all-hookimpls

Conversation

@veeceey
Copy link

@veeceey veeceey commented Feb 10, 2026

Summary

Fixes #431

When a plugin registers multiple hook implementations on the same hook (e.g. via specname), two related issues exist:

  • HookCaller._remove_plugin() only removes the first matching HookImpl for a plugin, then returns. This means it relies on being called multiple times to remove all implementations.
  • PluginManager.get_hookcallers() returns duplicate HookCaller entries — one per HookImpl rather than one per HookCaller. This happens to compensate for _remove_plugin only removing one impl per call, but it's incorrect behavior for users of the public API.

Changes

  • _remove_plugin: Remove all hookimpls for the given plugin in a single call using a list comprehension filter, instead of deleting only the first match and returning.
  • get_hookcallers: Break out of the inner loop after finding the first matching hookimpl for a hookcaller, so each HookCaller appears at most once in the result.
  • Tests: Two new tests verify correct multi-hookimpl unregister behavior and that get_hookcallers returns no duplicates.
  • Changelog: Added 431.bugfix.rst towncrier fragment.

Test plan

  • All 126 existing + new tests pass (uv run pytest)
  • All pre-commit hooks pass (uv run pre-commit run -a)
  • CI passes on all Python versions (3.10-3.14, PyPy)

🤖 Generated with Claude Code

…uplicate

When a plugin registers multiple hook implementations on the same hook
(e.g. via specname), `_remove_plugin` only removed the first matching
hookimpl. This worked because `get_hookcallers` returned duplicate
HookCaller entries, causing `_remove_plugin` to be called multiple
times. This was fragile and made `get_hookcallers` return unexpected
duplicates to callers of the public API.

Fix `_remove_plugin` to remove all hookimpls for a plugin in a single
call, and fix `get_hookcallers` to return each HookCaller at most once.

Closes pytest-dev#431

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@veeceey
Copy link
Author

veeceey commented Feb 19, 2026

Friendly ping - any chance someone could take a look at this when they get a chance? Happy to make any changes if needed.

for hookimpl in hookcaller.get_hookimpls():
if hookimpl.plugin is plugin:
hookcallers.append(hookcaller)
break
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a plugin can register more than one impl per hook its even used in some pytest plugins

remaining = [impl for impl in self._hookimpls if impl.plugin != plugin]
if len(remaining) == len(self._hookimpls):
raise ValueError(f"plugin {plugin!r} not found")
self._hookimpls[:] = remaining
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i vaguely recall that the bug that’s getting fixed here was for practical usage countered with another bug in the calling code, i'll ave to validate this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

multi hook registration does not unregister

2 participants

Comments