Injeção de Dependências

No sistema de injeção de dependências, você pode definir tipos que podem ser injetados em outros servios e handlers. Eles são definidos com o decorador @service.

from typing import Annotated
from zayt.di import Inject, service


@service
class MyService:
    pass


@service
class MyOtherService:
    dependency: Annotated[MyService, Inject]


class SomeClass:
    pass


class OtherClass:
    def __init__(self, dependency: MyService):
        self.dependency = dependency


@service
async def factory() -> SomeClass:
    return SomeClass()


@service
async def other_factory(dependency: MyService) -> OtherClass:
    return OtherClass(dependency)

Classes de serviço

Serviços definidos como classes tem dependências como anotações de classe.

from typing import Annotated
from zayt.di import Inject, service


@service
class MyService:
    pass


@service
class OtherService:
    property: Annotated[MyService, Inject]

Quando o tipo do serviço é requisitado do contêiner de injeção de dependências, a classe será inspecionada pelas dependências anotadas que serão criadas e então injetadas no serviço requisitado.

Anotações sem Inject será ignoradas.

Inicializadores e finalizadores

Opcionalmente, classes de serviço podem definir dois métodos: initialize(), que será chamado após a criação do serviço e injeção de dependências; e finalize, que será chamado no desligamento da aplicação.

from zayt.di import service


@service
class MyService:
    async def initialize(self):
        """perform initialization logic"""

    async def finalize(self):
        """perform finalization logic"""

Os métodos initialize() e finalize() não precisam ser async.

Serviços provendo uma interface

Você pode ter serviços que provêem uma interface ao invés de seu próprio tipo, de forma que nós requisitamos a interface como dependência ao invés do tipo concreto.

from typing import Annotated

from zayt.di import Inject, service


class Interface:
    def some_method(self): pass


@service(provides=Interface)
class MyService:
    def some_method(self): pass


@service
class OtherService:
    dependency: Annotated[Interface, Inject]

Quando OtherService é criado, o contêiner de injeção de dependências procura por um serviço do tipo Interface e produzirá uma instância da classe MyService.

Serviços nomeados

Serviços podem ser registrados com um nome, de forma que você pode ter mais de um serviço do mesmo tipo, desde que eles tenham nomes distintos. Sem um nome, um serviço é registrado como o padrão para aquele tipo.

from typing import Annotated

from zayt.di import Inject, service


class Interface: pass


@service(name="A", provides=Interface)
class ServiceA: pass


@service(name="B", provides=Interface)
class ServiceB: pass


@service
class OtherService:
    dependency_a: Annotated[Interface, Inject("A")]
    dependency_b: Annotated[Interface, Inject("B")]

Dependências opcionais

If a requested dependency is not registered, an error is raised, unless there is a default value declared, in that case the property will have that value when the service is created.

from typing import Annotated

from zayt.di import Inject, service


@service
class SomeService:
    pass


@service
class MyService:
    dependency: Annotated[SomeService, Inject] = None

    def some_method(self):
        if self.dependency:
            ...

Serviços como funções factory

De forma a registrar tipos que nós não possuímos, por exemplo, um tipo vindo de uma biblioteca externa, nós podemos usar uma função factory:

from zayt.di import service
from some_library import SomeClass


@service
async def some_class_factory() -> SomeClass:
    return SomeClass()

A anotação de tipo de retorno é requerida para funções factory, já que esta será o tipo provido pela função. Se a anotação de tipo de retorno não for provida, um erro será lançado.

O valor do parâmetro provides em @service é ignorado quando estiver decorando uma função factory, e um warning será lançado.

Parâmetros de funções factory não precisam da anotação Inject, a não ser que eles precisem especificar uma dependência nomeada:

from typing import Annotated

from zayt.di import Inject, service
from some_library import SomeClass


@service
async def some_class_factory(
    dependency: MyService,
    other: Annotated[OtherService, Inject("service_name")]
) -> SomeClass:
    return SomeClass(dependency, other)

Inicialização e finalização

Para executar inicialização em funções factory, você precisa apenas executar a lógica antes de retornar o serviço.

from zayt.di import service


class SomeClass:
    pass


@service
async def factory() -> SomeClass:
    some_service = SomeClass()
    # perform initialization login
    return some_service

Para executar finalização, você deve utilizar yield ao invés de return e executar a lógica de finalização após isto.

from zayt.di import service


class SomeClass:
    pass


@service
async def factory() -> SomeClass:
    some_service = SomeClass()
    # perform initialization logic
    yield some_service
    # perform finalization logic

Serviços nomeados

Serviços nomeados funcionam da mesma forma que nos serviços como classes.

from typing import Annotated

from zayt.di import Inject, service

from some_library import SomeClass


@service(name="service_name")
def factory() -> SomeClass:
    return SomeClass


class MyService:
    def __init__(self, dependency: SomeClass):
        self.dependency = dependency


@service
def my_service(
    dependency: Annotated[SomeClass, Inject("service_name")]
) -> MyService:
    return MyService(dependency)

Dependências opcionais

Dependências opcionais funcionam da mesma forma que nos serviços como classes, em que você especifica um valor padrão para o argumento.

from zayt.di import Inject, service
from some_library import SomeClass


@service
async def some_class_factory(
    dependency: OtherService = None
) -> SomeClass:
    if dependency:
        ...

    return SomeClass()