Skip to content

fix: name the event in the delayed+internal error and let send(delay=0) override - #683

Merged
fgmacedo merged 1 commit into
mainfrom
fgmacedo/fix-679-delay-internal-error
Oct 13, 2026
Merged

fgmacedo merged 1 commit into
mainfrom
fgmacedo/fix-679-delay-internal-error

Conversation

@fgmacedo

Copy link
Copy Markdown
Owner

Why

light = Event(dark.to(lit), delay=50, internal=True) raised InvalidDefinition: Event '__event__<uuid>' cannot be both delayed and internal. The check ran in Event.__new__, before the metaclass knew the attribute name, and the 3.2.2 release-notes doctest only passed because of IGNORE_EXCEPTION_DETAIL. Separately, send("go", delay=0) could not override a declared delay, because 0 counted as "not given".

Fixes #679

What changes

  • The delayed+internal error names the event by its attribute name, by its explicit id, or by the first id of an error_/done_state_/done_invoke_ prefix. This holds at the top level and inside State.Compound/State.Parallel bodies, and also for an Event(delay=..., internal=True) declared without transitions, which a nested body used to drop before checking.
  • send() and raise_() default delay to None, so an explicit delay=0 overrides a declared delay on both engines. Without delay, the declared one still applies, and raise_("go") on an event declared with a delay is still InvalidDefinition.
  • The SCXML/JSON/YAML <send> load-time messages are unchanged.
  • In docs/releases/3.2.2.md, the doctest now checks the message with # doctest: -IGNORE_EXCEPTION_DETAIL, and the fix: honor Event(internal=True) and give internal events from outside a step the external contract #668 entry documents the delay=0 override. processing_model.md and statechart.md mention the override too.

Evidence

$ timeout 120 uv run pytest tests/test_internal_triggers.py   # before the fix, new tests only
9 failed, 44 passed   # placeholder id in 5 declaration forms; delay=0 ignored by send/raise_ on sync+async

$ timeout 180 uv run pytest -n 4 --cov=statemachine --cov-report=term-missing:skip-covered
TOTAL    6197      0   1944      0   100%
2642 passed, 25 skipped, 44 xfailed

With the event.py change reverted, the release-notes doctest fails:

+statemachine.exceptions.InvalidDefinition: Event '__event__83ad8cb8…' cannot be both delayed and internal: ...

Pre-commit (ruff, mypy, pyright, pytest) passes on the commit.

Not verified

  • An Event(..., delay=50, internal=True) statement left unassigned in a class body is no longer rejected. Before, the placeholder check caught it by accident. Now the transition becomes eventless, which is what any unassigned Event(...) statement already did.
  • A class rejected for delayed+internal stays in the global registry. The check now runs in the metaclass after registry.register, which is how the other metaclass-time InvalidDefinition errors already behave.
  • sm.go() takes no delay argument, so there is no override to test on the bound-event call path. Only send()/raise_() were changed.

@fgmacedo fgmacedo added this to the 3.2.2 milestone Oct 13, 2026
@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (73bf57c) to head (2279678).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #683   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           53        53           
  Lines         5787      5816   +29     
  Branches       918       926    +8     
=========================================
+ Hits          5787      5816   +29     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…0) override

The delayed+internal check ran in Event.__new__ before the class attribute name was
known, so an Event declared without an explicit id reported its placeholder id. The
check now runs when the factory rebinds the event under its attribute name, for
top-level and State.Compound declarations alike.

send() and raise_() treated delay=0 as "not given", so an explicit zero could not
override a declared delay. Their default is now None.

Fixes #679

Signed-off-by: Fernando Macedo <fgmacedo@gmail.com>
@fgmacedo
fgmacedo force-pushed the fgmacedo/fix-679-delay-internal-error branch from ff368b0 to 2279678 Compare October 13, 2026 20:45
@sonarqubecloud

Copy link
Copy Markdown

@fgmacedo
fgmacedo merged commit 38968c1 into main Oct 13, 2026
14 checks passed
@fgmacedo
fgmacedo deleted the fgmacedo/fix-679-delay-internal-error branch October 13, 2026 21:02
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.

Event(..., delay=..., internal=True) error shows the placeholder id instead of the attribute name

1 participant