modern-python / that-depends

DI-framework, inspired by python-dependency-injector, but without wiring. Python 3.12 is supported
https://that-depends.readthedocs.io/
MIT License
156 stars 12 forks source link

Resources are created twice if used in different async contexts #68

Closed vrslev closed 2 months ago

vrslev commented 2 months ago

Caught a regression after 1.15.0 (via #67). Works fine on 1.14.1.

If an async resource has any IO in creator function, this function will be caused twice resulting in different resources.

Consider the following example:

import asyncio
import typing
from dataclasses import dataclass

import that_depends
from that_depends.providers import AsyncResource

@dataclass
class Client: ...

async def create_client() -> typing.AsyncIterator[Client]:
    print("created client")  # noqa: T201
    await asyncio.sleep(0)
    yield Client()

class IOCContainer(that_depends.BaseContainer):
    client = AsyncResource(create_client)

@that_depends.inject
async def uses_client(_: Client = that_depends.Provide[IOCContainer.client]) -> None: ...

async def main() -> None:
    async with asyncio.TaskGroup() as task_group:
        task_group.create_task(uses_client())  # Can be reproduced only if background_task goes first
        await uses_client()

asyncio.run(main())

It prints "created client" twice, though expected once.

lesnik512 commented 2 months ago

@vrslev thank you, working on fix. Looks like that calling init_resources beforehand helps

lesnik512 commented 2 months ago

fixed in https://github.com/modern-python/that-depends/releases/tag/1.16.2

I checked by hand, but without test cases