Я пытаюсь реализовать класс-утилиту, который бы мог оборачивать результаты выполнения моих сервисов.
Суть заключается в том, что каждый мой сервис может вернуть либо объект любого типа, либо объект, принадлежащий типу написанного мной исключения.
Работающий на 100% результат, которого мне удалось добиться, без явной аннотации внутреннего типа:
from typing import Any
class ServiceResultWrapper:
def __init__(self, result: Any | ServiceException):
self._result = result
self._success = not isinstance(result, ServiceException)
def handle(self) -> Any:
if not self._success:
raise result
return self._result
Суть заключается в том, что сервис не вызывает исключения внутри себя, а возвращает его в обертке для дальнейшей обработки.
Однако, у этого подхода есть недостаток, заключающийся в том, что мне хотелось бы видеть тип, который возвращает сервис, непосредственно на стороне вызова.
Способ, который я применил в самом начале:
from typing import TypeVar, Generic
ResultType = TypeVar("ResultType")
class ResultWrapper(Generic[ResultType]):
def __init__(self, result: ResultType | ServiceException):
self._result = result
self._success = not isinstance(result, ServiceException)
def handle(self) -> ResultType:
if not self._success:
raise result
return self._result
По-началу я думал, что должно сработать корректно, да только вот я не заметил следующую ошибку:
def computing_service(param: int) -> ServiceResultWrapper[int]:
if param == 42:
return ServiceResultWrapper(
ServiceException("This isn't the answer for every question!")
)
return ServiceResultWrapper(100) # An example of a successful result.
Expected type 'ServiceResultWrapper[int]', got 'ServiceResultWrapper[ServiceException]' instead
Вполне логично, ведь аннотация результата говорит, что ожидается контейнер, содержащий int, а я возвращаю ServiceResultWrapper[ServiceException].
Текущее решение:
from typing import Generic, TypeVar
from src.services.base.service_exception import ServiceException
ResultType = TypeVar("ResultType")
class ServiceResultWrapper(Generic[ResultType]):
def __init__(self, result: ResultType):
self._result = result
self._success = not isinstance(result, ServiceException)
def handle(self) -> ResultType:
if not self._success:
raise self._result
return self._result
И использование Union-аннотации
def computing_service(param: int) -> ServiceResultWrapper[int] | ServiceResultWrapper[ServiceException]:
if param == 42:
return ServiceResultWrapper(
ServiceException("This isn't the answer for every question!")
)
return ServiceResultWrapper(100) # An example of a successful result.
Решение в принципе работает: выдает нужный тип, соответственно IDE анализирует атрибуты; mypy не ругается.
Однако запись кажется мне немного громоздкой, и в целом присутствует ощущение, будто я использую generic'и неправильно. Возможно, есть способы лучше справиться с моей задачей? "Best practice".