Implicit Bindings

To ensure that developers can reason about their code, all bindings in flexdi must be explicitly bound. This ensures that we do not create bindings for items where it is unclear about how dependencies are constructed.

Errors on implicit bindings

Going back to our previous Async Example, lets say that we want to move the logic of our main method into a helper class called QueryService.

from sqlalchemy.ext.asyncio import AsyncConnection


class QueryService:
    def __init__(self, conn: AsyncConnection) -> None:
        self.conn = conn

    async def query(self) -> str:
        ...

When rewriting the main module we might be tempted to write the following:

from database import provide_connection, provide_engine
from service import QueryService

from flexdi import FlexGraph

graph = FlexGraph()

graph.bind(provide_engine)
graph.bind(provide_connection)


# Oops, we didn't bind the QueryService!
@graph.entrypoint
async def main(service: QueryService) -> None:
    print(await service.query())


if __name__ == "__main__":
    main()

This fails with:

Warning

flexdi.errors.ImplicitBindingError:
   Requested a binding for <class 'service.QueryService'>
   that was not explicitly marked for binding.

Fixing the issue by adding explicit bindings

As noted in the error, the graph refused to resolve the function because a dependency was not explicitly bound to the graph. To overcome this, we can explicitly to bind it to the graph.

from database import provide_connection, provide_engine
from service import QueryService

from flexdi import FlexGraph

graph = FlexGraph()

graph.bind(provide_engine)
graph.bind(provide_connection)

# Explicitly add the binding for QueryService
graph.bind(QueryService)


@graph.entrypoint
async def main(service: QueryService) -> None:
    print(await service.query())


if __name__ == "__main__":
    main()

This example works, but this may become tedious if there are many dependencies to bind. We can overcome this hurlde with @implicitbinding.

@implicitbinding

Classes marked with @implicitbinding avoid the need for explicit wiring in the main module of your application. This is perfect for classes that have easily understandable dependencies.

The @implicitbinding decorator marks a class with a special attribute that tells flexdi that it is okay to inject this class even if it was not explicitly bound in the graph.

The modification to our QueryService is simple:

from sqlalchemy.ext.asyncio import AsyncConnection

from flexdi import implicitbinding


@implicitbinding
class QueryService:
    def __init__(self, conn: AsyncConnection) -> None:
        self.conn = conn

    async def query(self) -> str:
        ...

Now we can write the main module as we had initially expected:

from database import provide_connection, provide_engine
from service_implicit import QueryService

from flexdi import FlexGraph

graph = FlexGraph()

graph.bind(provide_engine)
graph.bind(provide_connection)


# QueryService is marked with `@implicitbinding` meaning that the
# graph will accept it and register it as a dependency when requested!
@graph.entrypoint
async def main(service: QueryService) -> None:
    print(await service.query())


if __name__ == "__main__":
    main()