Metadata-Version: 2.1
Name: applipy-inject
Version: 1.2.0
Summary: Library that provides a dependency injector that works with type hinting.
Home-page: https://gitlab.com/applipy/applipy_inject
Author: Alessio Linares
Author-email: mail@alessio.cc
License: Apache 2.0
Project-URL: Source, https://gitlab.com/applipy/applipy_inject
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.6
Description-Content-Type: text/markdown

[![pipeline status](https://gitlab.com/applipy/applipy_inject/badges/master/pipeline.svg)](https://gitlab.com/applipy/applipy_inject/-/pipelines?scope=branches&ref=master)
[![coverage report](https://gitlab.com/applipy/applipy_inject/badges/master/coverage.svg)](https://gitlab.com/applipy/applipy_inject/-/graphs/master/charts)
[![PyPI Status](https://img.shields.io/pypi/status/applipy-inject.svg)](https://pypi.org/project/applipy-inject/)
[![PyPI Version](https://img.shields.io/pypi/v/applipy-inject.svg)](https://pypi.org/project/applipy-inject/)
[![PyPI Python](https://img.shields.io/pypi/pyversions/applipy-inject.svg)](https://pypi.org/project/applipy-inject/)
[![PyPI License](https://img.shields.io/pypi/l/applipy-inject.svg)](https://pypi.org/project/applipy-inject/)
[![PyPI Format](https://img.shields.io/pypi/format/applipy-inject.svg)](https://pypi.org/project/applipy-inject/)

# Applipy Inject

    pip install applipy_inject

Library that provides a dependency injector that works with type hinting.

## Basic usage

```python
from applipy_inject import Injector

injector = Injector()

# bind dictionary instance
injector.bind(dict, {'v': 143, 'k': 'bar'})

class A:
    def __init__(self, v: int):
        self.v = v

class B(A):
    def __init__(self, d: dict):
        super().__init__(d['v'])
        self.d = d

# bind subtype B to type A
injector.bind(A, B)

class C:
    def __init__(self, a: A):
        self.a = a

# bind type C
injector.bind(C)

# get an instace of type C
c = injector.get(C)

# the instance is initialized correctly, resolving the dependecy chain
print(c.a.v)
```
Output:
```
143
```

### bind(...)

The `bind()` function lets bind an object to a type. The meaning on the binding
depends on the type of the object. In general the object can be one of:
 - instance: an instance of a type
 - type: a type
 - provider: a callable object that has type annotations for its arguments and
   a return type annotation.

The bind function can be used in multiple ways:

**bind(type, instance)**

Bind an instance to a type. The instance must be an instance of the type or of
a subtype of the type.

```python
injector.bind(int, 5)
```

**bind((typeA, typeB, ...), instance)**

Bind an instance to multiple types. The instance must be an instance of all the
types or of a subtype of all the types.

```python
injector.bind((str, object), 'hello')
```

**bind(type)**

The type is bound as a "provider" of its own type and dependecy annotations are
taken from the `__init__` function.

```python
injector.bind(A)
```

**bind(type, subtype)**

Similar to `bind(type)` but subtype is bound to the specified type.

```python
injector.bind(A, B)
```

**bind((typeA, typeB, ...), subtype)**

Similar to `bind(type, subtype)` but subtype is bound to the specified types.

```python
injector.bind((A, B), B)
# or
injector.bind((A, B), C)
```

**bind(provider)**

The provider will be bound to the type it returns (as per the type annotation).

```python
def provide_A(s: str) -> A:
    return A(int(s))

injector.bind(provide_A)
```

**bind(type, provider)**

Similar to `bind(provider)` but the provider is bound to the specified type.
The annotated return type of the provider must be a subtype of the specified
type.

```python
def provide_B(v: int, k: str) -> B:
    return B({'v': v, 'k': k})

injector.bind(A, provide_B)
```

**bind((typeA, typeB, ...), provider)**

Similar to `bind(type, provider)` but the provider is bound to all the
specified types. The annotated return type of the provider must be a subtype
of all the specified types.

```python
def provide_B(v: int, k: str) -> B:
    return B({'v': v, 'k': k})

injector.bind((A, B), provide_B)
```

**Common optional parameters:**
 - `name`: defaults to `None`. Lets the user give a name to the binding. This
   allows to make multiple bindings to the same type and be able to select
   which one you want to get by using the name.

 - `singleton`: defaults to `True`. Define whether the injector should
   instantiate or call the provider only once and inject always the same
   instance or return a new result every time. It does not applipy to bound
   instances.

```python
injector.bind(provide_A, name='foo', singleton=False)
```

### get(...)

Get an instance registered to a given type.

Similar to `bind()`, there is an optional `name` parameter that tells the
injector the name of the instance to get for that type.

```
injector.get(A, name='foo')
```

### get_all(...)

The `Injector` allows to bind multiple objects to the same type and name.
`get_all()` retrieves all instances for a given type and name combination as a
list, instead of just one as `get()` does.

Similar to `bind()`, there is an optional `name` parameter that tells the
injector the name of the instance to get for that type.

```
injector.get_all(A, name='foo')
```

## Utility functions

### with_names(provider, names)

Give names to the arguments of an existing provider or class. The injector will
use this to set the value for `name` with doing `get()`.

`names` can be:
 - `dict`, where the keys are the names of the arguments and the values are the
   names for their type.
 - `str`, all arguments will be named with the value

```python
injector.bind(with_names(provide_B, {'k': 'name_for_k'}))
```
> Note that `v` won't have a name

```python
injector.bind(with_names(provide_B, 'name_for_all'))
```
> Both `v` and `k` will have name `name_for_all`

It can also be used on classes:
```python
injector.bind(with_names(C, 'app'))
```

### named(...)

Similar to `with_names()` but it is a decorator that is used when defining a
provider or class `__init__`.

```python

class Z:
    @named({'a': 'conf'})
    def __init__(self, a: dict, b: str):
        ...


@named('foo')
def provide_int(x: int, b: str) -> int:
    ...
```


