Metadata-Version: 2.1
Name: enterprython
Version: 0.4.0
Summary: Type-based dependency-injection
Home-page: http://github.com/Dobiasd/enterprython
Author: Tobias Hermann
Author-email: editgym@gmail.com
License: UNKNOWN
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown

![logo](https://github.com/Dobiasd/enterprython/raw/master/logo/enterprython.png)

[![Build Status](https://travis-ci.org/Dobiasd/enterprython.svg?branch=master)][travis]
[![(License MIT 1.0)](https://img.shields.io/badge/license-MIT%201.0-blue.svg)][license]

[travis]: https://travis-ci.org/Dobiasd/enterprython
[license]: LICENSE


enterprython
============
**Python library providing type-based dependency-injection**


Table of contents
-----------------
  * [Introduction](#introduction)
  * [Features](#features)
    * [Abstract base classes](#abstract-base-classes)
    * [Factories](#factories)
    * [Non-singleton services](#non-singleton-services)
    * [Service lists](#service-lists)
    * [Mixing managed and manual injection](#mixing-managed-and-manual-injection)
    * [Free functions as clients](#free-functions-as-clients)

  * [Requirements and Installation](#requirements-and-installation)


Introduction
------------

If you plan to write enterprisey software, you probably want to apply [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) on your class constructors,
and use a library doing the lookup for you based on static type annotations, instead of configuring the object graph manually.

`enterprython` provides exactly that.

```python
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

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

```python
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`.


### Factories

Annotating a function with `@component()` is registered as a factory for its return type.

```python

from enterprython import assemble, component

class Service:
    ...

@component()
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. 

```python
@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.

```python
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.

```python

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.

```python

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


Requirements and Installation
-----------------------------

You need Python 3.6.5 or higher.

```bash
python3 -m pip install enterprython
```

Or, if you like to use latest version from this repository:
```bash
git clone https://github.com/Dobiasd/enterprython
cd enterprython
python3 -m pip install .
```


License
-------
Distributed under the MIT License.
(See accompanying file [`LICENSE`](https://github.com/Dobiasd/enterprython/blob/master/LICENSE) or at
[https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT))


