Cargo is a dependency injection library for Python.
Cargo is simple to use, typed, flexible, extensible and easy to debug.
With pip:
$ pip install cargo
With pipenv:
$ pipenv install cargo
With poetry:
$ poetry add cargo
import cargo
# 1. Define your components
class A:
def __str__(self):
return "A"
class B:
def __init__(self, a: A):
self.a = a
# 2. Create a cargo container
container = cargo.containers.Standard()
# 3. Register your components
container[A] = A
container[B] = B
# 4. Use cargo to initialize your components
b = container[B]
# 5. Use your components
print(b.a)
All the examples are located in the examples directory.
Cargo uses the argument types to inject the dependencies; not their names.
examples/hello_dependencies.py:
import cargo
class A:
pass
class B:
pass
class Hello:
def __init__(self, foo: A, bar: B):
print(f"Hello {foo} and {bar}")
container = cargo.containers.Standard()
container[A] = A
container[B] = B
container[Hello] = Hello
# Prints: Hello <__main__.A object at 0x7f863b0fd450> and <__main__.B object at 0x7f863b09b810>
container[Hello]
Functions and methods can be used as factories; and objects as values.
examples/factory_and_value.py:
...
DatabaseURL = typing.NewType("DatabaseURL", str)
def database_client_factory(db_url: DatabaseURL) -> DatabaseClient:
if db_url.startswith("mysql://"):
return MysqlClient(db_url)
if db_url.startswith("postgres://"):
return PostgresClient(db_url)
raise Exception(f"Invalid database url: {db_url}")
container = cargo.containers.Standard()
# Registers a factory
container[DatabaseClient] = database_client_factory
# Registers a value
container[DatabaseURL] = "mysql://user:password@host:3306/db"
db_client = container[DatabaseClient]
print(db_client) # Prints: <__main__.MysqlClient object at 0x7f681975b390>
Cargo composes middlewares to create containers. The
Standard
container is just a stack of
opiniated middlewares. You can create your own types of containers
with the middlewares you want, or even create
your own middlewares.
import cargo
class LoggerMiddleware(cargo.types.Middleware):
def execute(
self,
dependency_type: cargo.types.DependencyType,
next_middleware: cargo.types.NextMiddleware,
):
print(f"Start resolving {dependency_type}")
dependency_value = next_middleware()
print(f"End resolving {dependency_type}")
return dependency_value
middleware_factories = [
LoggerMiddleware,
cargo.middlewares.CircularDependencyCircuitBreaker,
cargo.middlewares.Singleton,
]
container = cargo.containers.create(middleware_factories)
class A:
pass
class B:
def __init__(self, a: A):
pass
container[A] = A
container[B] = B
# Prints:
# Start resolving <class '__main__.B'>
# Start resolving <class '__main__.A'>
# End resolving <class '__main__.A'>
# End resolving <class '__main__.B'>
container[B]
Cargo raises a DependencyNotFound
exception
with the missing dependency type when a dependency is not found.
examples/dependency_not_found.py:
...
class A:
def __init__(self, b: B):
pass
class B:
pass
container = cargo.containers.Standard()
container[A] = A
# Note: B has not been registered
# Raises cargo.exceptions.DependencyNotFound: <class '__main__.B'>
container[A]
Cargo raises a CircularDependency
exception
with the dependency cycle when a circular dependency is detected.
examples/circular_dependency.py:
...
# Dependencies:
# - A depends on B
# - B depends on C
# - C depends on D
# - D depends on B
#
# Circular dependency cycle is: B -> C -> D -> B
...
container = cargo.containers.Standard()
container[A] = A
container[B] = B
container[C] = C
container[D] = D
# Raises cargo.exceptions.CircularDependency:
# [<class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.B'>]
container[A]
Cargo is licensed under the terms of the MIT license.