python-dependency-injector
python-dependency-injector copied to clipboard
AttributeError: 'Provide' object has no attribute (boto3)
Hello, I am trying to inject a service (DynamoDB) to a class, but I keep getting the error AttributeError: 'Provide' object has no attribute 'Table'. I checked previous issues, but can seem to figure out what I did wrong. Could someone help me out?
I have dependency-injector version 4.36.2 installed
I have the following code:
# ./dependency_injection_setup.py
import controller
class Container(containers.DeclarativeContainer):
"""Specify the services that can be injected"""
config = providers.Configuration()
session = providers.Resource(
boto3.session.Session,
aws_access_key_id=config.aws_access_key_id,
aws_secret_access_key=config.aws_secret_access_key,
)
dynamo_db = providers.Resource(
session.provided.resource.call(),
service_name='dynamodb',
endpoint_url=config.endpoint,
)
def bootstrap() -> Container:
"""Initialize the injected services with default configuration"""
container = Container()
if os.getenv('AWS_SAM_LOCAL'):
container.config.endpoint.from_env('LOCALSTACK_ENDPOINT')
container.init_resources()
container.wire(modules=[controller])
return container
# ./api_gateway_handler.py (Entrypoint)
from dependency_injection_setup import bootstrap, Container
from controller.RecipesController import RecipesController
def lambda_handler(event: APIGatewayProxyEventV2, _: context_.Context) -> APIGatewayProxyResponseV2:
bootstrap()
RecipesController().post()
# ./controller/RecipesController.py (where I want to inject)
class RecipesController(Controller):
TABLE_NAME: str = "Recipes"
dynamo_db: ServiceResource
@inject
def __init__(self, dynamo_db_provider=Provide[Container.dynamo_db]):
self.dynamo_db = dynamo_db_provider()
def post(self) -> APIGatewayProxyResponseV2:
recipe = { "foo" = "bar" }
# exception takes place here
self.dynamo_db.Table(self.TABLE_NAME).put_item(Item=recipe)
return created(recipe)
I have an __init__.py file in the root and controller directory
If I explicitly pass the dependency when creating the controller, then it works alright:
def lambda_handler(event: APIGatewayProxyEventV2, _: context_.Context) -> APIGatewayProxyResponseV2:
container = bootstrap()
RecipesController(container.dynamo_db).post()
One option is define provider of the controller in Container itself:
class Container(containers.DeclarativeContainer):
...
recipes_controller = providers.Singleton(RecipesController, dynamo_db)
and then let IoC controller inverse control over the container and obtain an instance:
container = bootstrap()
container.recipes_controller().post()
Also I don't call the provider and use argument as an instance itself in my code:
@inject
def __init__(self, dynamo_db=Provide[Container.dynamo_db]):
self.dynamo_db = dynamo_db
Probably you meant to use Provider[] instead of Provide[]?
But related to your issue I think it might be related to wiring of your controller package - try to either wire module:
import controller.RecipesController as recipies_module
...
container.wire(modules=[recipies_module])
or package:
import controller as controller_package
...
container.wire(packages=[controller_package])
My apologies for the very late reply, I was unable to work on this side project during November.
I updated the Provide to Provider and the wiring to set as packages instead of modules and it now works without explicitly passing the dependence :tada: nice :partying_face:
About the Provide/Provider, if I recall correctly, I based my implementation on the Application example (single container)#Main module. Is it a typo in the example or does its usage differs from my implementation? If it does differ, could you clarify what is the difference for me?
Is this the preferred way to use this library with lambda handlers? Is it not possible to instantiate the container globally and decorate the handler with @inject? Only major hurdle I am seeing is that I don't know how to wire it up with that setup.