Skip to content

[BUG]init_actions skips event hooks with underscore-prefixed names since v2.1.0 #1316

@tingQian

Description

@tingQian

Issue Title

[Bug] init_actions skips event hooks with underscore-prefixed names since v2.1.0


Issue Body

Description

In Beanie 2.1.0, PR #1293 (chore: modernize Beanie codebase and types for py310+) introduced a change in init_actions() that skips all attributes starting with _. This silently breaks any @before_event / @after_event hooks whose method names begin with an underscore.

Location

beanie/odm/utils/init.py, method init_actions:

for attr in dir(cls):
    if attr.startswith("_"):  # Skip all private/magic attributes  <-- THIS LINE
        continue

Reproduction

from beanie import Document, before_event, Insert

class MyDocument(Document):
    name: str
    creator_id: str | None = None

    @before_event(Insert)
    async def _set_creator(self):
        """This hook is silently skipped in 2.1.0!"""
        self.creator_id = "some_user"

# After init_beanie(...) and calling:
doc = MyDocument(name="test")
await doc.insert()
print(doc.creator_id)  # None in 2.1.0, "some_user" in 2.0.0

Beanie 2.0.0: _set_creator is correctly registered and fires on insert. ✅
Beanie 2.1.0: _set_creator is skipped by init_actions because it starts with _, and creator_id remains None. ❌

Expected Behavior

Hooks decorated with @before_event / @after_event should be registered regardless of whether the method name starts with _. The has_action attribute check is already sufficient to filter relevant methods.

Actual Behavior

All methods starting with _ are silently skipped during action registration, even if they are valid user-defined hooks with @before_event / @after_event decorators. No error or warning is raised.

Impact

  • Silent data loss: No error, no warning — hooks simply stop executing.
  • Breaking change: Not documented in any changelog or migration guide.
  • Users who followed a naming convention like _before_create / _after_update for hooks are affected.
  • In our case, this caused creator_id and updater_id fields to silently become null in production after upgrading from 2.0.0 to 2.1.0.

Suggested Fix

Instead of skipping all _-prefixed attributes, only skip dunder attributes:

for attr in dir(cls):
    if attr.startswith("__"):  # Skip only dunder/magic attributes
        continue

Or, even better, keep the optimization but still check has_action for _-prefixed methods:

for attr in dir(cls):
    if attr.startswith("__"):  # Skip magic/dunder attributes (performance optimization)
        continue
    f = getattr(cls, attr)
    if inspect.isfunction(f) and hasattr(f, "has_action"):
        ActionRegistry.add_action(
            document_class=cls,
            event_types=f.event_types,
            action_direction=f.action_direction,
            funct=f,
        )

This preserves the performance optimization (skipping __init__, __repr__, etc.) while not breaking user-defined hooks with single-underscore prefixes.

Environment

Labels suggestion

bug, regression

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions