Как стать автором
Обновить

Комментарии 22

Спасибо, буду знать!
Это только в Pro версии?
Увы, в Алхимии нет возможности строить запросы столь динамично. Максимум, что она позволяет — простенькую фильтрацию типа "колонка=значение":

Странно, что вы не посмотрели на соседний метод (filter)[http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.filter], который позволяет фильтровать куда более "динамично", и главное — читабельно:


session.query(MyClass).\
    filter(MyClass.name == 'some name', MyClass.id > 5)

Помимо этого, с помощью других методов атибутов можно записать и in ( MyClass.some_value.in([1, 2, 3]) ) и вообще что угодно.

я имел в виду, что запросы нельзя строить на основе магических строк, т.е. нельзя получить с фронтенда нужные фильтры и применить их на лету:


filters = {'rating__in': [2, 3, 4], 'body__like': '%search%'} # пришло с фронтенда
Post.where(*filters).all()

Придется писать ручками:


session.query(Post).filter(Post.rating.in_([2,3,4]), Post.body.like('%search%')).all()

тут дело именно в том, что нельзя задать фильтр/сортировку, передав строку


filters = {'rating__in': [2, 3, 4], 'body__like': '%post%'}

Хмм, ок, теперь я понял вашу идею.


Всё же, эта идея кажется мне достаточно стрёмной: по сути, мы позволяем фронтенду делать любые запросы, без явной реализации этих запросов в контроллере.
Я вижу в этом некоторую опасность: например, какой-нибудь злодей может подобрать максимально "тяжёлую" комбинацию параметров (фильрация по параметрам, у которых нет индекса, использование таких параметров для "like", которые не используют индекс) и создать аномальную нагрузку на БД.


Я предпочитаю такие вещи контролировать в явном виде.

Да, в таком подходе есть минусы. Ещё минус, что в таких "магических строках" легко ошибиться и, допустим, вместо user___name, написать yser___name, IDE такое не провалидирует, и получим ошибку в runtime.


Я тоже предпочитаю писать в явном виде, но бывают ситуации, когда надо строить запрос "на лету", когда фильтров не знаешь заранее. И вот такого как раз Алхимия не умеет.


А по поводу злодейской "тяжёлой комбинации" и вообще валидации: на боевом проекте мы валидируем такие магические строки, приходящие с UI (с помощью marshmallow)


Имею в планах сделать возможность валидации таких строк.

Вы можете хоть по условию, хоть в цикле, в зависимости от входных данных формировать динамическую фильтрацию через query.filter(...). Если вы можете написать алгоритм который по входным данным формирует магическую строку, то вы можете написать и алгоритм который применяет цепочку filter.

можно. но каждый раз придётся писать новый алгоритм.
я же, как и создатели Django ORM, решил иметь возможность описать нужные джоины декларативно и дать протестированному коду это джоины сделать.


Это — не серебряная пуля, здесь есть свои минусы. Однако это удобная дополнительная фишка, которая может сделать код короче.

Вопрос даже не в быстродействии, что мешает такими фильтрами подбирать пароли userpasswordstartswith='a'.

это скорее вопрос к проектировке безопасности приложения.


конечно, не следует разрешать фронтенду передавать что угодно в качестве фильтра.

Кстати, гляньте на Pony ORM: https://ponyorm.com/
Спасибо за статью, но блин простите, вы выбрали плохой путь, может вам так и удобней, но…
Удобный CRUD: для простого create в Алхимии надо создать объект, да добавить его в сессию, да сделать flush

Это вопрос скорее не удобства а стиля (и иногда производительности), лично для меня написать больше строк но сделать всё наглядным удобней, может конечно наверно и потому что в моих задачах обычно используется не простой CRUD.
Да и вообще, не зря же создатели Django, Ruby on Rails, Laravel, Yii выбрали Active Record ORM.

И всё из этого списка огромные раздутые монстры, которые очень не удобны и не логичны, не надо брать их в пример, люди не зря идут в сторону микрофреймворков.
Динамическое построение фильтров/сортировки на основе магических строк как в Django: Post.objects.filter(user__name__startswith='John')

Одно из списка, из за чего лично у меня возникает б**дская ненависть к Django, для меня это жутко не удобно и антипатерн. Ды и к тому же доверять клиенту нельзя, формировать магические строки на клиенте — плохая идея.
Вложенный eager load, когда нужно с комментарием сразу загрузить пост, а к посту его юзера (ладно, он есть, но не очень удобен)

Я не очень понимаю чем вам такой синтаксис не удобен?:
.options(
    joinedload_all(Watchdog.recipe, Recipe.recipeset, RecipeSet.job),
    joinedload_all(Watchdog.recipe, Recipe.recipeset, RecipeSet.lab_controller),
    joinedload_all(Watchdog.recipetask, RecipeTask.task)
)

Ваше предложение:
User.with_({
    User.posts: {
        Post.comments: {
            Comment.user: None
        }
    }
}.all()

Плохой синтаксис, пихать всё в словари, очень напоминает реализацию sequelize.js, только там так сделано от того что в js нет способа сделать это красиво как в python.

В целом у меня сложилось впечатление что вы только начали работать с алхимией и теперь несётесь на всех парах всё в ней «исправлять». Но в целом я только за ваши начинания, ведь для того чтобы придти к хорошему решению сначала нужно много надопускать ошибок, это естественно.

1. Тут вопрос стиля и личных предпочтений. Вы привыкли к одному, я — к другому.
Я не против стандартного синтаксиса Алхимии, просто в некоторых местах мне он неудобен. А в других местах, напротив, Алхимия очень рулит.


2.


Я не очень понимаю чем вам такой синтаксис не удобен?:
.options(
joinedload_all(Watchdog.recipe, Recipe.recipeset, RecipeSet.job),
joinedload_all(Watchdog.recipe, Recipe.recipeset, RecipeSet.lab_controller),
joinedload_all(Watchdog.recipetask, RecipeTask.task)
)

лично мне не видно иерархии.


3. С Алхимией работаю год. И за этот год после Active Record наболело очень многое. Что и вылилось с описанный пакет.

Как-то это… Не python-way совсем. Давайте оставим джангу в стороне. Алхимия в том виде, в каком она Вам кажется неудобной на самом деле заставляет вас писать более очевидный и правильный код:

bob = User(name='Bobby', age=1)
session.add(bob)
session.flush()


Я тут сразу вижу, где я добавил конкретно этот объект, а где сказал алхимии «пиши в базу».

bob = User.create(name='Bobby', age=1


А что если мне id нужен вставляемой сущности? Мы ж ещё не сделали commit, почему нет? Кстати, а когда у Вас там commit будет? А flush?

Про «динамические поля» для фильтров, и почему плохо с фронта фильтры текстом принимать я думаю Вы сами понимаете.

User.with_({
    User.posts: {
        Post.comments: {
            Comment.user: None
        }
    }
}.all()


Это вообще какая-то адская, совсем не очевидная магия…

Да и в целом советую забить на ORM, и использовать SQLA core. Он более выразителен и логичен. Лишнее абстрагирование от хранилища ни к чему хорошему не приведёт.

1.


Как-то это… Не python-way совсем. Давайте оставим джангу в стороне. Алхимия в том виде, в каком она Вам
кажется неудобной на самом деле заставляет вас писать более очевидный и правильный код:

Я не говорю что подход Алхимии плохой. Я говорю, что мне простота кода была важнее, чем правильность. Для обычного создания объекта в контроллере в чистой Алхимии надо писать одни и те же 3 строки вместо одной, как в Active Record ORM. Алхимия напрягала в простых задачах, где не нужно 10 объектов добавить в сессию, а потом только один раз flush.


Мне подход Алхимии нравится именно отсутствием магии и контролируемостью кода. Но в простых задачах это неудобно, и я решил сделать Active Record поверх, а не вместо Data Mapper, как дополнительную плюшку, а не как основной вариант работы.


Я люблю SQLAlchemy такой, какая она есть. И именно от любви к ней я сделал её ещё удобнее (как минимум, для себя и коллег на работе).


2.


Кстати, а когда у Вас там commit будет? А flush?

При create, update, delete будет сразу flush. При надобности можно сделать настройку, чтоб бы не flush, а commit (ну или autoflush выставить)


3 .


А что если мне id нужен вставляемой сущности?

Вы его получите после flush-а, т.е. после


user = User.create(...)

вы имеете user.id

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

Интерфейс алхимии сильно гибче аналогичных из перечисленного. Алхимия не прячет от вас SQL, наоборот в алхимии гораздо проще понять что в итоге получишь. Какой запрос для вас сготовит джанга — та хз, надо смотреть логи и в кишках копаться. Когда джанга кинет запрос, где словишь N+1 и тому подобные тонкости.

С другой стороны к алхимии у меня тоже есть пачка претензий, но она гораздо тоньше чем к django orm и аналогам.
Неспроста в RoR есть arel, а activerecord поверх.

Вашим пакетом лично я вряд ли буду пользоваться, лишний слой абстракции, и новичкам сильно бы не советовал. Но писать опенсорс достойно, желаю успехов )

Я люблю Алхимию именно за её гибкий интерфейс и явный SQL. В Алхимии можно гораздо больше, чем в любой Active Record ORM.


Но гибкость всегда выливается в более длинный код, и это нормально.
Хотелось иметь "ярлыки" к этому гибкому коду для самого простого CRUD-а или быстрых джоинов.
С ними можно простые задачи выполнять очень коротким кодом, имея возможность писать более гибко на обычной Алхимии.


Я рассматриваю свой пакет не как замену Алхимии, но как ярлыки для самых простых задач.


Также см. подобные мысли в ответе выше.

При чтении статьи могло сложиться неверное впечатление (см. комментарии вверху), что автор:


  • не любит Алхимию такой, какая она есть / считает её подход злом
  • не попытавшись понять философию Алхимии, полез её исправлять под привычный Active Record
  • любит магию и не любит писать явный код

Это не так, поэтому чуть подправил некоторые формулировки и добавил спойлер в конце статьи с обоснованием актуальности проделанной работы.


Спасибо!

Лично для меня статья очень полезная. Спасибо автору!
Это большая исследовательская работа.
Почему она в песочнице?!
Потому что это первая статья автора.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории