Skip to main content

logo

CI (License MIT 1.0)

enterprython

Python library providing type-based dependency injection

Table of contents

Introduction

If you plan to develop SOLID / domain-driven (i.e., enterprisey) software, you probably want to apply inversion of control in the form of dependency injection when writing the constructors of your classes. Also, you likely want to use a library doing the needed lookups for you based on static type annotations, instead of manually configuring the object graph.

enterprython provides exactly that.

from enterprython import assemble, component

@component()
class Service:
    def __init__(self) -> None:
        self._greeting: str = 'Hello'

    def greet(self, name: str) -> str:
        return f'{self._greeting}, {name}!'

class Client:
    def __init__(self, service: Service) -> None:
        self._service = service

    def run(self) -> None:
        print(self._service.greet('World'))


assemble(Client).run()

Output:

Hello, World!

Features

Abstract base classes and profiles

A client may depend on an abstract base class. Enterprython will inject the matching implementation.

from abc import ABC
from enterprython import assemble, component

class ServiceInterface(ABC):
    ...

@component()
class ServiceImpl(ServiceInterface):
    ...

class Client:
    def __init__(self, services: ServiceInterface) -> None:
        ...

assemble(Client)

One singleton instance of ServiceImpl is created and injected into Client.

This feature enables the use of different profiles. For example, you might want to use different classes implementing an interface for your production environment compared to when running integration tests. By providing a profiles list, you can limit when the component is available.

@component(profiles=['prod'])
class ServiceImpl(ServiceInterface):
    ...

@component(profiles=['test'])
class ServiceMock(ServiceInterface):
    ...

assemble(Client, profile='test')

Factories

Annotating a function with @factory() registers a factory for its return type.

from enterprython import assemble, component

class Service:
    ...

@factory()
def service_factory() -> Service:
    return Service()

class Client:
    def __init__(self, service: Service) -> None:
        ...

assemble(Client)

service_factory is used to create the Service instance for calling the constructor of Client.

Non-singleton services

If a service is annotated with @component(singleton=False) a new instance of it is created with every injection.

@component(singleton=False)
class Service:
    ...

class Client:
    def __init__(self, service: Service) -> None:
        ...

Service lists

A client may depend on a list of implementations of a service interface.

from abc import ABC
from typing import List
from enterprython import assemble, component

class ServiceInterface(ABC):
    pass

@component()
class ServiceA(ServiceInterface):
    ...

@component()
class ServiceB(ServiceInterface):
    ...

class Client:
    def __init__(self, services: List[ServiceInterface]) -> None:
        ...

assemble(Client)

[ServiceA(), ServiceB()] is injected into Client.

Mixing managed and manual injection

One part of a client's dependencies might be injected manually, the rest automatically.

from enterprython import assemble, component

@component()
class ServiceA:
    ...

class ServiceB:
    ...

class Client:
    def __init__(self, service_a: ServiceA, service_b: ServiceB) -> None:
        ...

assemble(Client, service_b=ServiceB())

service_a comes from the DI container, service_b from user code.

If ServiceB also has a @component() annotation, the manually provided object is preferred.

Free functions as clients

Since class constructors are fundamentally just normal functions, we can inject dependencies into free functions too.

from enterprython import assemble, component

@component()
class Service:
    ...

def client(service: Service) -> None:
    ...

assemble(client)

A singleton instance of Service is created and used to call client.

Value Store

The value store supports merging multiple sources using the following precedence order:

  1. Configuration files using the list provided order. toml format is the only supported for now.
  2. Environment variables. Variables must be prefixed with the application name
  3. Command line arguments. Arguments must follow the format: --key=value.

Command-line arguments overwrite environment variables and environment variables overwrite configuration files.

To load the value store use the helper function load_config as below.

load_config(app_name="myapp", paths=["config.toml"])

app_name is the application name and is required to identify environment variables.

paths is a list of relative file paths, files will be loaded and merged in the same order.

Value Injection

Service's value-type attributes are automatically injected from the value store using either the traversal path or a given value store key.

Python dataclass and attrs are supported.

@attrs.define or @dc.dataclass decorators are provided after the @component decorator

Notice that attrs and dc module alias is being used to highlight what library is used.

Feel free to use the more compact @define and @dataclass versions in your production code.

import attrs

@component()
@attrs.define
class Service:
    attrib1: int
    attrib2: str
    attrib3: bool

    ...

class Client:
    service: Service

    ...

load_config("myapp", ["config.toml"])
assemble(Client)

config.toml:

service_attrib1 = 10
service_attrib2 = "mystring"
service_attrib3 = false

attrib1, attrib2, and attrib3 will be injected using the configuration entries listed above.

By default, enterprython will use the attribute path convention (notice the service_ prefix in each of the configuration entries )

If multiple services need to read the same configuration entry, the setting decorator let you provide your custom key:

@component()
@attrs.define
class Service:
    attrib1: int = setting("MYATTRIB1")
    attrib2: str = setting("MYATTRIB2")
    attrib3: bool = setting("MYATTRIB3")

    ...

class Client:
    service: Service

    ...

load_config("myapp", ["config.toml"])
assemble(Client)

config.toml:

MYATTRIB1 = 10
MYATTRIB2 = "mystring"
MYATTRIB3 = false

The value injection provides type-checking and enforces injection of any attribute without defaults.

To skip injecting an attribute, you can:

  1. Use the attribute default value.
  2. Use the attrs/dataclass field decorator providing init=False and default=... to opt-out from injection. Using this, the attribute will not get injected (even if a matching entry exists in the value store)
@component()
@attrs.define
class Service:
    # below attribute WILL be injected from the value store
    # an entry MUST exist in the value store
    attrib1: int
    # below attribute CAN be injected from the value store,
    # if not provided in value store, then the default is used
    attrib2: str = "test"
    # below attribute will not be injected
    # any entry in the value store will be ignored
    attrib3: bool = attrs.field(init=False, default=True)

Requirements and Installation

You need Python 3.7 or higher.

python3 -m pip install enterprython

Or, if you like to use the latest version from this repository:

git clone https://github.com/Dobiasd/enterprython
cd enterprython
python3 -m pip install .

License

Distributed under the MIT License. (See accompanying file LICENSE or at https://opensource.org/licenses/MIT)

Metadata

Release files for enterprython 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for enterprython 0.7.0
File Size Uploaded Provenance Actions
enterprython-0.7.0.tar.gz 15.8 kB Without provenance Details for enterprython-0.7.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for enterprython 0.7.0
File Interpreter ABI Platform Actions
enterprython-0.7.0-py3-none-any.whl Python 3 none any Details for enterprython-0.7.0-py3-none-any.whl

Total release size: 29.3 kB

Release files / enterprython-0.7.0.tar.gz

Download URL enterprython-0.7.0.tar.gz
Size 15.8 kB
Tags Source
SHA-256 checksum
How to use checksums
4393d21aa1838ad7b0583a94eca4dfad82f6053eca41214e8941418d51120ecf
BLAKE2b-256 checksum
How to use checksums
e887354ea86e1d412e5f0f698368d528a1f14d1ddabdbf885dba439e5ac07cf2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.2.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/45.2.0 requests-toolbelt/0.9.1 tqdm/4.45.0 CPython/3.8.10

Release files / enterprython-0.7.0-py3-none-any.whl

Download URL enterprython-0.7.0-py3-none-any.whl
Size 13.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4754e3f3613f905de9de3563d1bb1bd570ecaf62ab4315a141a101adfb96f1fa
BLAKE2b-256 checksum
How to use checksums
35bfbc71c4dc730dc2254b4e390ca346103f6a855d0fc13a7f6320a86efd1319
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.2.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/45.2.0 requests-toolbelt/0.9.1 tqdm/4.45.0 CPython/3.8.10

Security for enterprython 0.7.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page