Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ahook class not compatible with passing 'firstresult=True' to a hookspec marker #35

Open
chevignon93 opened this issue Jul 8, 2024 · 0 comments

Comments

@chevignon93
Copy link

chevignon93 commented Jul 8, 2024

When passing firstresult=True to a hookspec marker, the HookCaller will only return 1 result (coroutine) instead of a list of results but asyncio.gather expect a list of "tasks" and not a single coroutine

Here's a minimally reproducible example to illustrate the issue.

import apluggy
import asyncio

hookspec = apluggy.HookspecMarker("my-project")
hookimpl = apluggy.HookimplMarker("my-project")

class Hookspec:
    @hookspec(firstresult=True)
    async def afunc(self, x, y): ...

class Plugin:
    @hookimpl
    async def afunc(self, x, y):
        return x + y

async def main():
    pm = apluggy.PluginManager("my-project")
    pm.add_hookspecs(Hookspec())
    pm.register(Plugin)
    print(await pm.ahook.afunc(x=1, y=2))

asyncio.run(main())

The code above fails with TypeError: asyncio.tasks.gather() argument after * must be an iterable, not coroutine

but changing the call function to actually check whether calling hook(*args, **kwargs) returns a list and acting accordingly if it doesn't fixes the issue.

class AHook:
    def __init__(self, pm: PluginManager_) -> None:
        self.pm = pm

    def __getattr__(self, name: str) -> Callable[..., Coroutine[Any, Any, list]]:
        async def call(*args: Any, **kwargs: Any) -> list:
            hook: HookCaller = getattr(self.pm.hook, name)
            coros: list[asyncio.Future] = hook(*args, **kwargs)
            if not isinstance(coros, Coroutine):
                return None
            if not isinstance(coros, list):  # Added an isinstance check to see whether a list or a single element is returned
                return await coros
            return await asyncio.gather(*coros)
        return call

After this small change the code works as expected:

print(await pm.ahook.afunc(x=1, y=2))
>>> 3
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

No branches or pull requests

1 participant