Cargo is a dependency injection library for Python.
Cargo is simple to use, typed, flexible, extensible and easy to debug.
Getting started
Step 1: Install cargo
With pip:
$ pip install cargo
With pipenv:
$ pipenv install cargo
With poetry:
$ poetry add cargo
Step 2: Use 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)
Features
All the examples are located in the examples directory.
Cargo is typed
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]
Cargo is flexible
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 is extensible
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 is easy to debug
Dependency not found
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]
Circular dependency
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]
Contributors
License
Cargo is licensed under the terms of the MIT license.