Skip to content

fix: await async predicates in condition expressions#557

Merged
fgmacedo merged 4 commits intodevelopfrom
fix/async-condition-expressions-535
Feb 13, 2026
Merged

fix: await async predicates in condition expressions#557
fgmacedo merged 4 commits intodevelopfrom
fix/async-condition-expressions-535

Conversation

@fgmacedo
Copy link
Owner

Summary

  • Fixes boolean expression combinators (custom_not, custom_and, custom_or, build_custom_operator) in spec_parser.py to properly handle async predicates
  • Each combinator now checks isawaitable() on predicate results and returns a coroutine when needed, which CallbackWrapper.__call__ already knows how to await
  • Added integration tests for all condition expression operators (not, and, or) with async predicates
  • Added unit tests for mixed sync/async operand combinations

Root cause

The combinators called predicates synchronously (e.g., not predicate(*args, **kwargs)). When predicates were async, they returned unawaited coroutine objects. Since coroutines are always truthy:

  • not <coroutine> always returned False
  • <coroutine> and x always evaluated x (skipping the actual check)
  • <coroutine> or x always short-circuited (returning the coroutine without checking)

Closes #535

…544)

When an async SM is pickled/deepcopied (e.g. via multiprocessing), the
engine queue is not preserved. __setstate__ recreated the engine but
never called start(), so the __initial__ event was never enqueued and
activate_initial_state() would fail with InvalidStateValue.

Closes #544
The boolean expression combinators (custom_not, custom_and, custom_or,
build_custom_operator) called predicates synchronously. When predicates
were async, they returned unawaited coroutine objects which are always
truthy, causing `not` to always return False, `and` to skip evaluation,
and `or` to short-circuit incorrectly.

Each combinator now checks `isawaitable()` on predicate results and
returns a coroutine when needed, which CallbackWrapper.__call__ already
knows how to await.

Closes #535
The pre-commit hook was using ruff v0.8.1 while the lockfile had v0.15.0,
causing import sorting differences between local and CI.
@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (1fd4b7e) to head (f428f9f).
⚠️ Report is 2 commits behind head on develop.

Additional details and impacted files
@@            Coverage Diff            @@
##           develop      #557   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           25        25           
  Lines         1656      1713   +57     
  Branches       204       219   +15     
=========================================
+ Hits          1656      1713   +57     
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 Sentry.
📢 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.

- Add docstrings to empty async on_enter_state methods (S1186)
- Use await asyncio.sleep(0) in async test hooks to satisfy S7503
@sonarqubecloud
Copy link

@fgmacedo fgmacedo merged commit 570687e into develop Feb 13, 2026
13 checks passed
@fgmacedo fgmacedo deleted the fix/async-condition-expressions-535 branch February 13, 2026 16:32
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.

Async conditions are not awaited when using certain condition expressions

1 participant

Comments