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
Resource is not detected by container.init_resources() when declaration is not at class root level #380
Comments
Hey @approxit , Few things:
import asyncio
storage = None
async def get_async_singleton():
global storage
if storage is None:
await asyncio.sleep(0.1)
storage = object()
return storage
async def main():
object1 = await get_async_singleton()
object2 = await get_async_singleton()
assert object1 is object2
...
if __name__ == '__main__':
asyncio.run(main()) Function |
Okay, it looks like I've stretch async Resources understanding way too much, and assumed that their asyncness can be somewhat "transparent" as everything besides from dependency_injector import providers, containers
import asyncio
async def async_connect(resource):
await resource.connect()
yield resource
await resource.disconnect()
class StatefullClient:
async def connect(self):
pass
async def disconnect(self):
pass
class SomeService:
def __init__(self, client):
self._client = client
def hello_world(self):
print('Hello world!')
class SomeContainer(containers.DeclarativeContainer):
# lets just force async init_resources by line below
other_client = providers.Resource(
async_connect,
providers.Factory(
StatefullClient
)
)
some_service = providers.Singleton(
SomeService,
client=providers.Resource(
async_connect,
providers.Factory(
StatefullClient
)
)
)
async def main():
container = SomeContainer()
await container.init_resources()
service = container.some_service()
service.hello_world() # Awaitable instead of actual service here!
await container.shutdown_resources()
asyncio.run(main()) Should be modified to keep connection/resource handling away from sync providers stack as example below? from dependency_injector import providers, containers
import asyncio
async def async_connect(resource):
await resource.connect()
yield resource
await resource.disconnect()
class StatefullClient:
async def connect(self):
pass
async def disconnect(self):
pass
class SomeService:
def __init__(self, client):
self._client = client
def hello_world(self):
print('Hello world!')
class SomeContainer(containers.DeclarativeContainer):
other_client = providers.Resource(
async_connect,
providers.Factory(
StatefullClient
)
)
some_service_client = providers.Factory(
StatefullClient
)
some_service_client_connection = providers.Resource(
async_connect,
some_service_client
)
some_service = providers.Singleton(
SomeService,
client=some_service_client
)
async def main():
container = SomeContainer()
await container.init_resources()
service = container.some_service()
service.hello_world() # Actual service here!
await container.shutdown_resources()
asyncio.run(main()) I get it now, but I'm not gonna lie, my wrong assumption had quite appealing syntax to keep things packed. With "all declaration level" resource initialization it would work so nice... |
I had the same vision on async resources when started working on the feature. I thought about The goods news is that truly async design brings outstanding feature: framework can collect async dependencies asynchronously. For instance next code will block 3 times: object = SomeClass(
resource1=await resource1(),
resource2=await resource2(),
resource3=await resource3(),
) Resource 2 will start initialization only after resource 1 is ready, resource 3 - only after 2 is ready. Dependency Injector prepares all dependencies concurrently instead: resource1, resource2, resource3 = await asyncio.gather(resource1(), resource2(), resource3())
object = SomeClass(
resource1=resource1,
resource2=resource2,
resource3=resource3,
) Resource 1,2,3 are initialized at the same time. Injections are done when the last is ready. As of your example, there are 2 things.
async def main():
container = SomeContainer()
await container.init_resources()
service = await container.some_service() # <-- Add await here
service.hello_world() # Actual service here!
await container.shutdown_resources()
|
Hey @approxit , I have published version async def main():
container = RootResourceContainer()
await container.init_resources()
print('after init')
print(await container.obj_factory())
await container.shutdown_resources()
print('----')
container = NonRootResourceContainer()
await container.init_resources() # Is not crashing anymore
print('after init')
print(await container.obj_factory())
await container.shutdown_resources()
# Output:
#
# sync foo triggered!
# async foo triggered!
# after init
# <__main__.TestObject object at 0x106564fa0>
# ----
# sync foo triggered!
# async foo triggered!
# after init
# <__main__.TestObject object at 0x106564fa0> |
Hey @approxit , I think I mislead you with my pre-last response and I'm sorry about that. So few things:
Below is an example of the container that requires await for retrieving the service: import asyncio
from dependency_injector import providers, containers
async def async_connect(resource):
await resource.connect()
yield resource
await resource.disconnect()
class StatefullClient:
async def connect(self):
pass
async def disconnect(self):
pass
class SomeService:
def __init__(self, client):
self._client = client
def hello_world(self):
print('Hello world!')
class SomeContainer(containers.DeclarativeContainer):
# Flat version
client = providers.Factory(StatefullClient)
resource = providers.Resource(
async_connect,
resource=client,
)
some_service1 = providers.Singleton(
SomeService,
client=resource,
)
# Nested version
some_service2 = providers.Singleton(
SomeService,
client=providers.Resource(
async_connect,
resource=providers.Factory(
StatefullClient
),
),
)
async def main():
container = SomeContainer()
await container.init_resources()
service1 = await container.some_service1()
service1.hello_world() # Actual service here!
service2 = await container.some_service2()
service2.hello_world() # Actual service here!
await container.shutdown_resources()
asyncio.run(main())
import asyncio
from dependency_injector import providers, containers
async def async_connect(resource):
await resource.connect()
yield resource
await resource.disconnect()
class StatefullClient:
async def connect(self):
pass
async def disconnect(self):
pass
class SomeService:
def __init__(self, client):
self._client = client
def hello_world(self):
print('Hello world!')
class SomeContainer(containers.DeclarativeContainer):
# Flat version
client = providers.Factory(StatefullClient)
resource = providers.Resource(
async_connect,
resource=client,
)
some_service1 = providers.Singleton(
SomeService,
client=resource,
)
# Nested version
some_service2 = providers.Singleton(
SomeService,
client=providers.Resource(
async_connect,
resource=providers.Factory(
StatefullClient
),
),
)
async def main():
container = SomeContainer()
await container.init_resources()
for resource in container.traverse(types=[providers.Resource]):
resource.disable_async_mode() # <-- all resources provide just what was initialized
service1 = container.some_service1()
service1.hello_world() # Actual service here!
service2 = container.some_service2()
service2.hello_world() # Actual service here!
await container.shutdown_resources()
asyncio.run(main())
class SomeContainer(containers.DeclarativeContainer):
# Flat version
client = providers.Factory(StatefullClient)
resource = providers.Resource(
async_connect,
resource=client,
)
some_service1 = providers.Singleton(
SomeService,
client=resource.synchronized, # <-- inject async resource synchronously
)
# Nested version
some_service2 = providers.Singleton(
SomeService,
client=providers.Synchronized( # <-- inject async resource synchronously
providers.Resource(
async_connect,
resource=providers.Factory(
StatefullClient
),
),
),
) PS: This doesn't work yet. Appreciate your feedback. |
Don't worry @rmk135, there was no misleading in your answers. 1: Yup, if we want to access any provider, which have async resource in its deps, outside of container, we need await it - got that from your first comments. from dependency_injector import providers, containers
import asyncio
async def async_connect(resource):
print('Connecting resource!')
await resource.connect()
yield resource
await resource.disconnect()
class StatefullClient:
async def connect(self):
pass
async def disconnect(self):
pass
class SomeService:
def __init__(self, client):
self._client = client
def hello_world(self):
print('Hello world!')
def container_main(some_service):
print('Calling service!')
some_service.hello_world()
class SomeContainer(containers.DeclarativeContainer):
other_client=providers.Resource(
async_connect,
providers.Factory(
StatefullClient
)
)
some_service = providers.Singleton(
SomeService,
client=providers.Resource(
async_connect,
providers.Factory(
StatefullClient
)
)
)
main = providers.Callable(
container_main,
some_service=some_service,
)
async def main():
container = SomeContainer()
await container.init_resources()
container.main()
await container.shutdown_resources()
asyncio.run(main()) Either way, all of these solutions fails with 2: That's quite neat idea! So you can force async resource to sync after all... 3: I knew that you will mention that deleted comment... ...but taking the Of course when we need somehow handle case when synchronized async Resource is not yet initialized but already accessed. And it's just me, or we just came to implementation of our original idea? |
Example with callable is terrific: async def main():
container = SomeContainer()
await container.init_resources()
container.main()
await container.shutdown_resources() I have no idea why that works :) I mean why As of the Despite for resource in container.traverse():
resource.enable_async_mode() In that case you will need to always use await to get dependencies from the container. If you have any other thoughts or idea - please, share. Feedback helps to improve the framework. |
Howdy, another somewhat similar issue like #379, this time with
providers.Resource
.Consider this example:
First container with everything defined at class root level works, as both Resources are detected and initialized by
container.init_resources()
.Second container with nested Resources definition can't figure it out, and
container.init_resources()
is not picking up all resources as stated in docs. In result, if no async resources are found,container.init_resources()
will not be awaitable, and awaiting it will crash.Also in first container by calling
container.obj_factory()
we are receiving awaitable, not actual factory result. As I'm expecting to receive awaitable when there was no previous async Resource initialization and handle this somewhere in my code (as stated in docs), that I'm expectingcontainer.init_resources()
to do the job and resolve every declared resource and get rid of awaiting anything inside my code. Or in another words - I'd like to keep Resource-specific logic at the container layer, without bleeding it into my code, which will be sensitive for IoC layer changes.The text was updated successfully, but these errors were encountered: