Edição : Conforme observado nos comentários de @PanagiotisKanavos, este método para interagir com bancos de dados é redundante. Como a pergunta principal é sobre o uso de Genéricos, deixarei a pergunta em aberto.
Texto original:
Estou tentando escrever uma classe genérica que atua como Repositório de Banco de Dados para vários modelos SQLModel diferentes.
Eu vinculei a _ModelType
TypeVar na SQLModel
esperança de remover todos os avisos de tipo, mas ainda recebo avisos quando retorno SQLModel
tipos em métodos que esperam _ModelType
. Também tentei restringir a _ModelType
a um SQLModel, mas não fez diferença.
Aqui está a definição do repositório abstrato.
from sqlmodel import SQLModel
import typing
from sqlalchemy.engine.result import ScalarResult
_ModelType = typing.TypeVar(
"_ModelType",
bound=SQLModel,
)
class BaseRepository(typing.Generic[_ModelType]):
def __init__(self, session: Session, model_class: typing.Type[SQLModel]):
self.session = session
self.__model_class = model_class
# Static hinting works as expected
def save(self, obj: _ModelType) -> _ModelType:
"""
Generic method to save an object to the database.
"""
self.session.add(obj)
return obj
def get_one(self, obj_id: typing.Any) -> typing.Optional[_ModelType]:
obj = self.session.get(self.__model_class, obj_id) #SQLModel | None
return obj
# Warning: Type "SQLModel" is not assignable to type "_ModelType@BaseRepository"
def query(self,
*conditions: typing.Union[_ColumnExpressionArgument[bool], bool]
) -> ScalarResult[_ModelType]:
stmt = select(self.__model_class)
for c in conditions:
stmt = stmt.where(c)
return self.session.exec(stmt).all()
#Similar warning along with: Type parameter "_T_co@Sequence" is covariant,
#but "SQLModel" is not a subtype of "_ModelType@BaseRepository"
e aqui está um exemplo básico de uso (a implementação real envolve a criação da sessão, o gerenciamento, a criação de todos os repositórios e a confirmação automática na saída em um gerenciador de contexto UnitOfWork).
from utils.db_new import engine
from order_management.repository_new import BaseRepository
from order_management.models import Order, Customer
from sqlmodel import Session
import logging
logging.basicConfig(level=logging.INFO)
order_repo = BaseRepository[Order](Session(engine), Order)
all_orders = order_repo.query(
Order.pickup_type == "pickup",
Customer.name == "Diaa Malek",
)
[print(o.id) for o in all_orders]
order = order_repo.get_one(1088)
if order:
print(order.items)
Em termos de funcionalidade, funciona exatamente como o esperado. Cada tipo de retorno é anotado corretamente. Eu também pude simplesmente type: ignore
seguir com a minha vida.
Mas ainda não tenho certeza se entendi esses avisos ou como evitá-los. Estou usando Genéricos de forma incorreta ou isso é um antipadrão ou uma complicação exagerada que nem deveria existir?
Além disso, é possível usar _ModelType
diretamente em vez de passar o model_type no construtor do Repositório? Sei que não faz diferença, mas fico em dúvida.
Referências:
Diferença entre TypeVar('T', A, B) e TypeVar('T', bound=Union[A, B])