Comments 9
Ну а если надо играться с SQL, то точно следует предпочесть SQLAlchemy, он гарантирует безопасность и валидность результата.
В моем проекте я использую модельку шаблонов, для хранения их в базе, и тогда заготовки для будущих «артефактов» удобно править онлайн.
Я не пропагандирую какой-то конкретный шаблонизатор, но да, склоняюсь к минимизации логики внутри шаблонов (форматтеры и предикаты по спискам — ОК, выполнение произвольного кода — не ОК). Шаблонизирование через format хорошо, если в модели нет массивов и условий (то есть для ряда случаев — вполне себе метод).
Ну и скорее статья дискутирует с методом, когда подобные артефакты создаются путем конкатенации строчек внутри кода. Такой подход я встречал неоднократно (особенно почему-то любят так мучать SQL). Результат — очень мутный код.
Для сравнения — добавил в гитхабе в template_3.py класс, который формирует SQL путем конкатенации строк по той же модельке. Довольно мутная штука в плане наглядности:
class SqlAntipattern(object):
def render(self, m):
if m["typeList"]:
return self.render_typelist(m)
if m["typeCountGroupBy"]:
return self.render_groupby(m)
raise Exception("Unknown query type")
def render_where(self, m):
sql = "WHERE "
def eqtion(w):
return "{}={}{}{}".format(
w["field"],
"'" if "quot" in w else "",
w["eq_val"],
"'" if "quot" in w else "")
wheres = [eqtion(w) for w in m["where"]]
sql += ", ".join(wheres)
return sql + " "
def render_orderby(self, m):
return "ORDER BY " + m["orderBy"]
def render_typelist(self, m):
sql = "SELECT * FROM " + m["tableName"] + " "
if "hasWhere" in m:
sql += self.render_where(m)
if "orderBy" in m:
sql += self.render_orderby(m)
if "limit" in m:
sql += " LIMIT " + m["limit"]
return sql
def render_groupby(self, m):
sql = "SELECT " + m["groupColumn"]
sql += ", COUNT(*) FROM " + m["tableName"] + " "
sql += " GROUP BY " + m["groupColumn"]
return sql
например зачем так сложно, можно же без конкатенаций строк сделать:
render_where
def render_where(self, m):
returned_template = 'WHERE {my_where} '.format
whered_template = "{field}={separator}{eq_val}{separator}".format
wheres = ", ".join(whered_template(separator="'" if 'quot' in w else '', **w) for w in m["where"]))
return returned_template(my_where=wheres)
render_groupby
def render_groupby(self, m):
returned_template = 'SELECT {groupColumn}, COUNT(*) FROM {tableName} GROUP BY {groupColumn}'.format
return returned_template(**m)
Хотя это все от лукавого. Наличие подобного кода может сообщать об ошибке в архитектуре приложения.
P.s. Я люблю нагружать текстовыми обработками запросы к базе. На это у меня есть личные причины. Результат — очень прозрачный код.
Мы видим в шаблоне, что шапка документа для заказов в Урюпинске отличается от других городов.
Для вот этого конкретного примера с текстом акции урюпинской — некорректно.
Эти акции — это дело бизнес-логики, которая точно не в шаблоне должна быть.
В шаблоне должно быть что то вроде:
«Если есть дополнтельное примечание, то вот тут его выводим»
А что это конкретно за примечание и для кого оно Урюпинска или для Сызрани там — не шаблон должен это решать.
Согласен про бизнес-логику, но в этом месте статьи я изящно (смайлик) показал способ быстро захардкодить странное. А за странным бизнес иногда прибегает в пятницу вечером со словами что "все забыли, сорян, но в субботу оно должно работать, потому что Урюпинск с утра будет на федеральных каналах". Дописывать ядро бизнес-логики и выкатывать на прод в пятницу не всегда хочется, поэтому можно например вот так, по-простому. С одновременным постом в трекер задачи на техдолг.
(здесь был ответ на комментарий про бизнес-логику, который потом перенес в правильную ветку)
Три неочевидных примера использования шаблонизаторов в backend-е