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()