Skip to content

Conversation

da-maltsev
Copy link
Contributor

@da-maltsev da-maltsev commented Jul 3, 2025

Добавил точный вывод возвращаемого типа для вызовов сервисов вместо Any. (до этого всегда брался Any из базового класса)

Как-то лёг глаз на то, что не совсем корректная сигнатура у __call__ и act у нас была, и решил поправить.
До этого проблем не было из-за всеядного Any, но и вывода типа из вызова SomeService()() не было нормального, а теперь вывод типов будет точным.

Мелочь, а приятно.

@da-maltsev
Copy link
Contributor Author

@kazqvaizer @nvo87 @e-stepanov @nkiryanov
Что думаете о таком улучшении? Будет полезно?

@nkiryanov
Copy link
Contributor

Что думаете о таком улучшении? Будет полезно?

Мне кажется может даже помещать — бывает, что хочу, чтоб сервис возвращал не связанный с вызовом тип или ничего не возвращал (None). Или я чёт пропустил и такие кейсы тоже ок будут работать?
Сейчас вроде с таким кодом порядок и mypy типы ок разруливает.

@dataclass
class OrderCreator(BaseService):
    def act(self) -> Order:
        ...

order = OrderCreator()()
reveal_type(order)

Вот чего мне в сервисе не хватает, но получается сложным и не уверен, что нужно в базовый шаблон:

  1. Придумать как собирать список возможных ошибок которые могут произойти + назначить им коды. Мы частично такое сделали на одном из проектов, но не сказать, что получилось хорошо.
  2. Добавить возможность запускать валидаторы независимо и вернуть список ошибок/exceptions — это когда хочу проверить какую-то бизнес-логику в сервисе и вернуть пользователю все ошибки которые нашли, а не только первую.

@da-maltsev
Copy link
Contributor Author

da-maltsev commented Jul 3, 2025

@nkiryanov спасибо, что подчеркнул важные нюансы!

Сейчас вроде с таким кодом порядок и mypy типы ок разруливает

Это да, но внутри редактора с этим не всегда порядок. Зря я на mypy гнал, проблема судя по всему только для LSP чтобы на лету типы показывать.

Это в старой реализации:
image

Это в новой с дженериком:
image


Мне кажется может даже помещать — бывает, что хочу, чтоб сервис возвращал не связанный с вызовом тип или ничего не возвращал (None)

В случае если у нас такой сервис с сайдэффектом, который ничего не возвращает можно сделать вот так:
image

А в старой реализации IDE опять думает, что там Any:
image

С нас вроде как не убудет от таких изменений, но бонусом получим лучшую поддержку LSP в редакторах.


А по теме списка ошибок и вообще более глубокой работы с ними, я бы вынес на обсуждение вообще Result паттерн популярный в Rust/Gleam/прочие около фп языки. С типами дружит, всякие match case вообще только так использовать можно.

Есть либа для питона , базовый пример оттуда:

from returns.result import Result, Success, Failure

def find_user(user_id: int) -> Result['User', str]:
    user = User.objects.filter(id=user_id)
    if user.exists():
        return Success(user[0])
    return Failure('User was not found')  # ВОТ ТУТ можно enum | list[enum] для ошибок пихать как вариант

user_search_result = find_user(1)
# => Success(User{id: 1, ...})

user_search_result = find_user(0)  # id 0 does not exist!
# => Failure('User was not found')

Я фанат такой темы, но это сильно нас к фп толкает (а я и не против). Зато решает проблему с не очень четкими ошибками, размазанными по коду. Ну и сам паттерн уже опробованный и доказавший свою состоятельность.

Я б это отдельно обсудил, может даже уже внутри bc (но если идея нравится, то у меня в голове уже есть примерная реализация базового сервиса и валидаторов по этому принципу)

@nkiryanov
Copy link
Contributor

С нас вроде как не убудет от таких изменений, но бонусом получим лучшую поддержку LSP в редакторах.

Я бы забил: думать потом ещё почему это тут появилось + может докрутят типы или LSP.
У меня в vscode кстати норм тип определят (чего не скажешь о менеджерах и кастомных quesryset в django).

Я б это отдельно обсудил, может даже уже внутри bc (но если идея нравится, то у меня в голове уже есть примерная реализация базового сервиса и валидаторов по этому принципу)

Я бы посмотрел: мне нравится идея Result + слышал хорошие отзывы.
Интересно мнение коллег: в своём коде можно все на Result написать, но никто не застрахует что из неожиданной зависимости прилетит exception :)

@da-maltsev
Copy link
Contributor Author

Я бы забил: думать потом ещё почему это тут появилось + может докрутят типы или LSP.

Понимаю тебя. Но у меня прям внутренний перфекционист плакать начинает от того, что в шаблонном сервисе, который везде переиспользуем, типы выводятся через Any неявно 😅

Интересно, что у нас ребята еще скажут по этой теме, ну и по Result тоже особенно интересно 👀

@kazqvaizer
Copy link
Contributor

Видел уже несколько итераций приправить python функциональщиной вроде Result и пока ни одна из них не прижилась, по моим наблюдениям, потому что плохо бьется с семантикой python и не поддерживается нативно. Т.е. точно не стоит добавлять это в базовый шаблон.

Если что, я не против попробовать, только нужно выбрать более живую либу, эта не коммитилась уже год. Ну и надо поставить четкую задачу, которую решаем, мол "так вот было до - это плохо,а теперь делаем так, это хорошо потому-то"

@nvo87
Copy link
Contributor

nvo87 commented Jul 7, 2025

Добавил точный вывод возвращаемого типа для вызовов сервисов вместо Any.

Сходу мне сложно сказать, станет хуже или лучше. Я бы пожил на каком-то проекте и посмотрел. В целом в Pycharm мне вроде это не беспокоило никогда (но я не сильно упарываюсь по типизации).

ну и по Result тоже особенно интересно

2 года назад Никита Соболев в курсе по типизации (у Феди в школе) давал returns - вот [тут] запись (https://youtu.be/InHDRsjWmhs?t=3114) подробный его пример. Сама идея выглядит очень круто, но когда попробовал делать домашки - мозгу реально больно и очень не привычно. Я тогда подумал, что многим это тоже будет не привычно и непонятно сначала - и это лишний порог вхождения для будущих сотрудников, поэтому так и оставил эту идею с ФП.

Прикольно, что ты тоже предложил такой вариант!

@e-stepanov
Copy link
Contributor

Добавил точный вывод возвращаемого типа для вызовов сервисов вместо Any. (до этого всегда брался Any из базового класса)

У меня проблем с Any в базовом сервисе не возникало, но я вообще не пользуюсь поддержкой типов на уровне LSP. Но я не против добавить сюда Generic на возвращаемый тип. Единственно, попробывать бы для начала на каком-то реальном проекте, уже с нормальной кодовой базой. Порой с типами, особенно дженериками, у меня возникают проблемы в довольно неожиданных местах.

А по теме списка ошибок и вообще более глубокой работы с ними, я бы вынес на обсуждение вообще Result паттерн

Я тоже слушал Никиту на эту тему, хотя домашки на курсе по типизации не делал :) Честно, я не увидел сильного профита от такого подхода. Несколько лет назад использовал его на котлине, но там меня напрягали постоянные switch конструкции: код получался слишком развесистым.

Согласен с @kazqvaizer, что если ты видишь конкретное место для применения, то было бы интересно опробовать в качестве эксперимента.

@da-maltsev
Copy link
Contributor Author

Единственно, попробывать бы для начала на каком-то реальном проекте, уже с нормальной кодовой базой. Порой с типами, особенно дженериками, у меня возникают проблемы в довольно неожиданных местах.

Я на текущем проекте у себя добавил дженерик в базовый класс. Кодовая база ни сильно большая, но сервисов много, в т.ч. всякие вложенные друг в друга. И проблем не встретил. Единственное отличие от варианта в этом PR'е - у меня питон 3.11 со старым синтаксисом дженериков, чуть более массивном, чем тут. А так всё здорово.

@e-stepanov
Copy link
Contributor

И проблем не встретил.

Тогда я не против дженерика в базовом сервисе.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants