Skip to main content

Lagom, a type based dependency injection container

Project description

Lagom - Dependency injection container

Build Status Scrutinizer Code Quality Code Coverage PyPI

Usage

Everything in Lagom is based on types. To create an object you pass the type to the container:

container = Container()
some_thing = container[SomeClass]

Defining a singleton

container[SomeExpensiveToCreateClass] = SomeExpensiveToCreateClass("up", "left")

alternatively if you want to defer construction until it's needed:

container[SomeExpensiveToCreateClass] = Singleton(SomeExpensiveToCreateClass)

Defining a type that gets recreated every time

container[SomeClass] = lambda: SomeClass("down", "spiral")

if the type needs things from the container the lambda can take a single argument which is the container:

container[SomeClass] = lambda c: SomeClass(c[SomeOtherDep], "spinning")

if your construction logic is longer than would fit in a lambda a function can also be bound to the container:

@dependency_definition(container)
def my_constructor() -> MyComplexDep:
    # Really long
    # stuff goes here
    return MyComplexDep(some_number=5)

Alias a concrete instance to an ABC

container[SomeAbc] = ConcreteClass

Partially bind a function

Apply a function decorator to any function.

@bind_to_container(container)
def handle_some_request(request: typing.Dict, game: Game):
    # do something to the game
    pass

This function can now be called omitting any arguments that the container knows how to build.

# we can now call the following. the game argument will automagically
# come from the container
handle_some_request(request={"roll_dice": 5})

Invocation level caching

Suppose you have a function and you want all the dependencies to share an instance of an object then you can define invocation level shared dependencies.

class ProfileLoader:
    def __init__(self, loader: DataLoader):
        pass

class AvatarLoader:
    def __init__(self, loader: DataLoader):
        pass

@bind_to_container(container, shared=[DataLoader])
def handle_some_request(request: typing.Dict, profile: ProfileLoader, user_avatar: AvatarLoader):
    # do something to the game
    pass

now each invocation of handle_some_request will get the same instance of loader so this class can cache values for the invocation lifetime.

Full Example

App setup

from abc import ABC
from dataclasses import dataclass

from lagom import Container

#--------------------------------------------------------------
# Here is an example of some classes your application may be built from


@dataclass
class DiceApiUrl:
    url: str


class RateLimitingConfig:
    pass


class DiceClient(ABC):
    pass


class HttpDiceClient(DiceClient):

    def __init__(self, url: DiceApiUrl, limiting: RateLimitingConfig):
        pass


class Game:
    def __init__(self, dice_roller: DiceClient):
        pass

#--------------------------------------------------------------
# Next we setup some definitions

container = Container()
# We need a specific url
container[DiceApiUrl] = DiceApiUrl("https://roll.diceapi.com")
# Wherever our code wants a DiceClient we get the http one
container[DiceClient] = HttpDiceClient

#--------------------------------------------------------------
# Now the container can build the game object

game = container[Game]

Testing without patching

Taking the container from above we can now swap out the dice client to a test double/fake. When we get an instance of the Game class it will have the new fake dice client injected in.

def some_test(container: Container):
    container[DiceClient] = FakeDice(always_roll=6)
    game_to_test = container[Game]
    # TODO: act & assert on something

Design Goals

  • Everything should be done by type. No reliance on names.
  • All domain code should remain unmodified. No special decorators.
  • Make use of modern python features (3.7 at the time of creation)
  • The API should expose sensible typing (for use in pycharm/mypy)

Project details


Release history Release notifications | RSS feed

This version

0.4.1

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lagom-0.4.1.tar.gz (16.7 kB view hashes)

Uploaded Source

Built Distribution

lagom-0.4.1-py2.py3-none-any.whl (13.3 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page