Skip to content
Merged

Test #241

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## v2.3.2 (2025-12-24)

### Fix

- **eventmanager**: prevent skipping listeners during event emission

## v2.3.1 (2025-12-23)

### Fix
Expand Down
6 changes: 6 additions & 0 deletions cz.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "$version"
version_scheme = "pep440"
version_provider = "uv"
update_changelog_on_bump = true
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "funcnodes-core"

version = "2.3.1"
version = "2.3.2"

description = "core package for funcnodes"
authors = [{name = "Julian Kimmig", email = "julian.kimmig@linkdlab.de"}]
Expand Down
4 changes: 2 additions & 2 deletions src/funcnodes_core/eventmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,11 @@ def emit(self, event_name: str, msg: MessageInArgs | None = None) -> bool:
msg["src"] = self
listened = False
if event_name in self._events:
for callback in self._events[event_name]:
for callback in self._events[event_name][:]:
callback(**msg)
listened = True
if "*" in self._events:
for callback in self._events["*"]:
for callback in self._events["*"][:]:
callback(event=event_name, **msg)
listened = True

Expand Down
49 changes: 49 additions & 0 deletions tests/test_eventmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,52 @@ async def async_test_function(self):
{"src": emitter, "result": "async_function_result"},
)
assert result == "async_function_result"


class EmitterTest(EventEmitterMixin):
pass


@pytest.mark.asyncio
async def test_event_manager_modification_during_emit():
"""
Test that modifying the event listener list (e.g., via once()) during
event emission does not cause listeners to be skipped.
"""
emitter = EmitterTest()
called = []

def cb1(**kwargs):
called.append(1)

def cb2(**kwargs):
called.append(2)

def cb3(**kwargs):
called.append(3)

# Scenario: cb1 is 'once', cb2 is normal.
# When event fires:
# 1. cb1 is called. It calls 'off' which removes itself from the list.
# 2. cb2 should still be called.

emitter.once("test", cb1)
emitter.on("test", cb2)
emitter.emit("test")

assert 1 in called
assert 2 in called

called.clear()
emitter.off("test")

# Scenario: cb1 is normal, cb2 is once, cb3 is normal.
emitter.on("test", cb1)
emitter.once("test", cb2)
emitter.on("test", cb3)

emitter.emit("test")

assert 1 in called
assert 2 in called
assert 3 in called
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.