Comments 22
По поводу джанговских filter(blog__name='John')
смотрите, как pycharm умеет (у модели поле FK gmfoto
к другой модели, у которой есть поле-строка cover_hash
:
https://habrastorage.org/files/869/bb9/1c9/869bb91c98d948e9942c9294f1352960.png
Увы, в Алхимии нет возможности строить запросы столь динамично. Максимум, что она позволяет — простенькую фильтрацию типа "колонка=значение":
Странно, что вы не посмотрели на соседний метод (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)
Имею в планах сделать возможность валидации таких строк.
можно. но каждый раз придётся писать новый алгоритм.
я же, как и создатели Django ORM, решил иметь возможность описать нужные джоины декларативно и дать протестированному коду это джоины сделать.
Это — не серебряная пуля, здесь есть свои минусы. Однако это удобная дополнительная фишка, которая может сделать код короче.
Вопрос даже не в быстродействии, что мешает такими фильтрами подбирать пароли userpasswordstartswith='a'.
Удобный 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 наболело очень многое. Что и вылилось с описанный пакет.
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
- любит магию и не любит писать явный код
Это не так, поэтому чуть подправил некоторые формулировки и добавил спойлер в конце статьи с обоснованием актуальности проделанной работы.
Спасибо!
Это большая исследовательская работа.
Почему она в песочнице?!
Как я SQLAlchemy удобной сделал