Skip to content

fix: validate individual values in async generator functions - #147

Open
gaoflow wants to merge 1 commit into
life4:masterfrom
gaoflow:fix-async-generator-contracts
Open

fix: validate individual values in async generator functions#147
gaoflow wants to merge 1 commit into
life4:masterfrom
gaoflow:fix-async-generator-contracts

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 25, 2026

Copy link
Copy Markdown

Problem

When @deal.post, @deal.ensure, or @deal.pre was applied to an async generator function, the contracts did not work correctly.

Python's iscoroutinefunction() and isgeneratorfunction() both return False for async generators — only isasyncgenfunction() identifies them. Because _ensure_wrapped had no branch for async generators, they fell through to the synchronous wrapper, which called self.func(*args, **kwargs) and received an async generator object as the result. @deal.post then validated that object instead of each yielded value.

import asyncio, deal

@deal.post(lambda x: x > 0)
async def positive_values(n):
    yield n        # should pass
    yield -n       # should fail

async def main():
    async for v in positive_values(3):
        print(v)   # prints 3 and 6, -3 is never caught

asyncio.run(main())
# No PostContractError raised — the async_generator object itself
# evaluated as truthy and passed the contract silently.

Fix

Add an isasyncgenfunction branch in _ensure_wrapped and a new _run_async_iter method that:

  1. Checks pre-conditions before iteration starts.
  2. Iterates the underlying async generator with async for.
  3. Checks post/ensure conditions after each yielded value.

The change is 31 lines, mirrors the existing _run_iter logic, and adds tests for both @deal.post and @deal.ensure on async generators.

This pull request was prepared with the assistance of AI, under my direction and review.

When a contract decorator (@deal.post, @deal.ensure, @deal.pre) was
applied to an async generator function, the wrapper fell through to the
synchronous branch because isasyncgenfunction() is not covered by either
iscoroutinefunction() or isgeneratorfunction(). This caused @deal.post
and @deal.ensure to validate the async_generator object itself instead
of each yielded value, producing either a confusing TypeError or silently
passing/failing based on the object's truthiness rather than the actual
yielded data.

Add a dedicated _run_async_iter path that iterates the async generator
with `async for` and validates pre-conditions before iteration starts
and post/ensure conditions after each yield.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant