Typing error when trying to inject dependencies several containers deep
Closed this issue · 4 comments
Hello. We have a pretty big application, mostly based on an archictecture of units of work we call "services". The app has many layers, and therefore we have many levels of containers, in the shape of app.section.subsection.service
This is my configuration (the legacy_container is because we're in the middle of the effort to migrate to dependency-injector):
class AppContainer(containers.DeclarativeContainer):
# Provider for the legacy dependency injection container.
# This allows access to services defined in the old pyrovider system.
# Should be intialized with the result of create_legacy_container(app_services).
legacy_container = providers.DependenciesContainer()
entity = providers.Container(
EntityContainer,
legacy_container=legacy_container,
)
billing = providers.Container(
BillingContainer,
entity=entity,
legacy_container=legacy_container,
)
class BillingContainer(containers.DeclarativeContainer):
entity = providers.DependenciesContainer()
legacy_container = providers.DependenciesContainer()
use_cases = providers.Container(
BillingUseCasesContainer,
entity=entity,
aws_s3=legacy_container.aws__s3,
jobs_dispatcher=legacy_container.jobs__dispatcher,
)
class BillingUseCasesContainer(containers.DeclarativeContainer):
entity = providers.DependenciesContainer(
dereferencer=providers.Dependency(instance_of=SQLAlchemyDereferencer),
manager=providers.DependenciesContainer(
core=providers.Dependency(instance_of=SqlAlchemyEntityManager),
),
)
aws_s3 = providers.Dependency(instance_of=S3)
jobs_dispatcher = providers.Dependency(instance_of=JobDispatcher)
create_reconciliation = providers.ThreadLocalSingleton(
CreateReconciliation,
em=entity.manager.core,
s3=aws_s3,
jobs_dispatcher=jobs_dispatcher,
)and this is how I'm trying to wire a graphql endpoint that uses create_reconciliation:
class CreateReconciliationMutation(BaseMutation):
...
@staticmethod
@inject
def mutate(
root,
info,
data,
create_reconciliation: CreateReconciliation = Provide[
AppContainer.billing.use_cases.create_reconciliation
],
):
reconciliation = create_reconciliation(...)
....But mypy doesn't like that:
<my_app>/billing/graphql/mutations/reconciliations.py:33:13: error: "Provider[Any]" has no attribute "create_reconciliation" [attr-defined]
I'm not sure why .billing.use_cases works fine but can't go further down a level
The same warning on em=entity.manager.core:
<my_app>/billing/infrastructure/containers/use_cases.py:24:12: error: "Provider[Any]" has no attribute "core" [attr-defined]
This is a know problem. Unfortunately there is no proper solution atm. My recommendation is either to slap # type: ignore[attr-defined] or use string identifiers (Provide["billing.use_cases.create_reconciliation"]) for now.
thanks
@ZipFile is there a solution for the provider definition other than # type: ignore? Some way of using string identifiers there?
You can provide string identifier that is resolved relatively to the container doing the wiring, see docs for details.
Also, consider using Annotated:
class CreateReconciliationMutation(BaseMutation):
...
@staticmethod
@inject
def mutate(
root,
info,
data,
create_reconciliation: Annotated[
CreateReconciliation,
Provide["billing.use_cases.create_reconciliation"],
],
):
reconciliation = create_reconciliation(...)
...This should yield 0 mypy warnings. Only downside is that there is no (existence) validation on identifier now, so be careful.