Serviços
Serviços são tipos registrados no contêiner de injeção de dependências que podem
ser injetados em outros serviços e handlers. Eles são definidos com o decorador
@service.
from typing import Annotated
from selva.di import Inject, service
@service
class MyService:
pass
@service
class MyOtherService:
dependency: Annotated[MyService, Inject]
class SomeClass:
pass
class OtherClass:
def __init__(self, dependency: SomeClass):
self.dependency = dependency
@service
async def factory() -> SomeClass:
return SomeClass()
@service
async def other_factory(dependency: SomeClass) -> OtherClass:
return OtherClass(dependency)
Serviços como classes
Serviços definidos como classes tem dependências como anotações da classe.
from typing import Annotated
from selva.di import Inject, service
@service
class MyService:
pass
@service
class OtherService:
property: Annotated[MyService, Inject]
Quando um serviço de um tipo é requisitado no contêiner de injeção de dependências, a classe será inspecionada pelas dependências anotadas que serão criadas e injetadas no serviço requisitado.
Anotações sem Inject serão ignoradas.
Inicializadores e finalizadores
Opcionalmente, classes de serviços podem definir dois métodos: initialize(), que
será chamado após a criação do serviço e injeção das dependências; e finalize(),
que será chamado na finalização da aplicação.
from selva.di import service
@service
class MyService:
async def initialize(self):
"""executa lógica de inicialização"""
async def finalize(self):
"""executa lógica de finalização"""
Os métodos initialize() e finalize() não precisam ser async.
Serviços que proveem uma interface
Você pode ter serviços que proveem uma interface ao invés do seu próprio tipo, de forma que você requisita a interface como dependência ao invés do tipo concreto.
from typing import Annotated
from selva.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 for criado, o contêiner de injeção de dependências procurará
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 tenham nomes distintos. Sem um nome, o serviço é registrado como o padrão para aquele tipo.
from typing import Annotated
from selva.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(name="A")]
dependency_b: Annotated[Interface, Inject(name="B")]
Dependências opcionais
Se uma dependência requisitada não for registrada, um erro é lançado, a não ser que haja um valor padrão declarado, onde a propriedade terá esse valor quando o serviço for criado.
from typing import Annotated
from selva.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 geradoras
Para registrar um tipo que nós não temos controle, por exemplo, um tipo de uma biblioteca externa, nós podemos uma uma função geradora:
from selva.di import service
from some_library import SomeClass
@service
async def some_class_factory() -> SomeClass:
return SomeClass()
A anotação de tipo de retorno é requirida em funções geradoras, já que esta será o serviço 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 geradora, e um warning será lançado.
Parâmetros de funções geradoras não precisam da anotação Inject, a não ser que elas
precisem especificar uma dependência nomeada.
from typing import Annotated
from selva.di import Inject, service
from some_library import SomeClass
@service
async def some_class_factory(
dependency: MyService,
other: Annotated[OtherService, Inject(name="service_name")]
) -> SomeClass:
return SomeClass()
Inicialização e finalização
Para executar inicialização em funções geradoras, você apenas executa a lógica antes de retornar o serviço.
from selva.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ê usa yield ao invés de return e executa a lógica
de finalização logo após
from selva.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 nas classes.
from typing import Annotated
from selva.di import Inject, service
from some_library import SomeClass
@service(name="service_name")
def factory() -> SomeClass:
return SomeClass
@service
class MyService:
dependency: Annotated[SomeClass, Inject(name="service_name")]
Dependências opcionais
Dependências opcionais funcionam da mesma forma que nas classes, onde você especifica um valor padrão para o argumento.