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.5.0

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.5.0.tar.gz (17.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lagom-0.5.0-py2.py3-none-any.whl (13.5 kB view details)

Uploaded Python 2Python 3

File details

Details for the file lagom-0.5.0.tar.gz.

File metadata

  • Download URL: lagom-0.5.0.tar.gz
  • Upload date:
  • Size: 17.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.22.0

File hashes

Hashes for lagom-0.5.0.tar.gz
Algorithm Hash digest
SHA256 ec0ebf0d6ca50e24934944a015b69896ddfd729adf025a278e7bae867122904a
MD5 a3a9ba53ab09e7fb4655332366a07c69
BLAKE2b-256 139ead8c9e1793f0aa28e557254b730273b99ea74a1a4d339c46d4eaf4bb6cee

See more details on using hashes here.

File details

Details for the file lagom-0.5.0-py2.py3-none-any.whl.

File metadata

  • Download URL: lagom-0.5.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 13.5 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.22.0

File hashes

Hashes for lagom-0.5.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 8d8515cce7596bb4f6199ddfd09d20ed94052d44a679576960811eabb5a9bd81
MD5 2f1ec8589791e7f1ff8b76b43fe37616
BLAKE2b-256 221fc8b15dff41a07b644116b161606211f16f3c082213ac984d58e7d640261b

See more details on using hashes here.

Supported by

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