Форматирование кода SQLAlchemy

Мы пытаемся следовать рекомендациям PEP8 для форматирования нашего кода на Python и оставаться ниже 80 символов в строке.

Наши строки SQLAlchemy особенно сложны, имеют множество цепочечных методов и множество сложных параметров, логических и вложенных функций.

Существуют ли какие-либо конкретные рекомендации по форматированию SQLAlchemy Python с ограничениями PEP8?

Самый близкий ответ, который я нашел, здесь, но код, с которым я имею дело, намного сложнее.

Ответы

Ответ 1

Пришел сюда, надеясь на лучшее решение, но я думаю, что предпочитаю стиль круглых скобок:

subkeyword = (
    Session.query(
        Subkeyword.subkeyword_id, 
        Subkeyword.subkeyword_word
    )
    .filter_by(subkeyword_company_id=self.e_company_id)
    .filter_by(subkeyword_word=subkeyword_word)
    .filter_by(subkeyword_active=True)
    .one()
)

Это хорошо и понятно, и избегает ужасной обратной косой черты.

Ответ 2

pep-8 обескураживает обратную косую черту, но для кода SQLAlchemy я не могу не думать, что они наиболее читаемы, так как вы можете сохранить каждую генеративную функцию в начале своей собственной строки. Если в круглых скобках есть много аргументов, я разломаю их и на отдельных строках.

subkeyword = Session.query(
                  Subkeyword.subkeyword_id, 
                  Subkeyword.subkeyword_word
             ).\
               filter_by(subkeyword_company_id=self.e_company_id).\
               filter_by(subkeyword_word=subkeyword_word).\
               filter_by(subkeyword_active=True).\
               one()

Конечно, неважно, насколько сложным является код, шаблон отступов может быть перенесен для любого количества кода, однако в Python мы хотим избежать чрезмерного вложения. Обычно с Query вложение происходит, потому что вы составляете много подзапросов вместе. Поэтому определите заранее построенные подзапросы:

subq = Session.query(
                Bat.id, 
                func.foo(Bat.x, Bat.y).label('foo')
               ).\
                filter(Bat.id==Bar.name).\
                correlate(Bar).\
                subquery()

subq2 = Session.query(Foo.id, Foo.bar).\
                filter_by(flag>5).\
                subquery()

result = Session.query(
                  subq.c.id,
                  subq.c.foo,
                  subq2.c.bar
                ).\
                join(subq2, 
                     and_(
                      subq.c.id > subq2.c.foo, 
                      subq.bar == subq2.id
                     )
                ).\
                order_by(subq.c.id, subq2.c.bar)

Я бы приветствовал другие мнения по поводу обратной косой черты.

Ответ 3

Я часто использую обратную косую черту аналогично тому, что указал zzzeek в его ответе. PEP8 - это просто руководство, не теряйте сон над ним, когда вы его нарушаете!

Однако, я также часто использую тип форматирования ниже, где я украл первый пример zzzeek, ​​слегка изменил его и переформатировал:

q = Session.query(
    Subkeyword.subkeyword_id, 
    Subkeyword.subkeyword_word,
)
q = q.filter_by(subkeyword_company_id=self.e_company_id)  # first filter
q = q.filter_by(subkeyword_word=subkeyword_word)  # 2nd filter
q = q.filter_by(subkeyword_active=True)

if filter_by_foo:
    q = q.filter(Subkeyword.foo == True)

# Run the query (I usually wrap in a try block)...
subkeyword = q.one()

Повторное переназначение q кажется сначала неприятным, но я его преодолел. Эффективное влияние фактически равно нулю. Большим преимуществом этого способа является то, что вы можете смешивать как завершающие комментарии, так и строки комментариев для документирования ваших запросов (как я сделал с бесполезными дополнениями выше). Цепочки с обратной косой чертой ограничивают вас здесь.

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

В качестве другого примера у меня есть довольно большой ( > 150 строк) запрос CTE, который я генерирую в SQLAlchemy, который имеет много смешанной логики, псевдонимов и меток (что необходимо для читаемости сгенерированного запроса), который смешивает оба метода. Серьезно сокращенная (и искаженная) версия запускает что-то вроде ниже:

cte_init = session.\
    query(
        child1.foo.label("child1_foo"),
        sa.literal(1).label("indent"),  # can comment on non-slashed lines
        child2.bar.label("child2bar"),
        #comments between non-slashed lines ok, too 
        sa.func.MAX(toplevel.baz).label("max_baz"),
    ).\
    select_from(top_level).\
    join(child1,
         child1.id == toplevel.fk_child1_id).\
    join(child2.
         child2.id == toplevel.fk_child2.id).\
    filter(top_level.name == "bogus").\
    cte(name = "cte", recursive = True)

if(use_filter_x):
    cte_init = cte_init.filter_by(x = "whatever")

# etc (no, the above doesn't make any sense)...

В общем, если вы убедитесь, что ваши строки отключены с помощью новых операций (как это делают многие распространенные схемы форматирования SQL), он остается вполне читаемым. Не бойтесь строк новой строки в скобках.

Ответ 4

Да, это будет неприятно, независимо от того, что вы делаете, поэтому в той мере, в какой вы можете разделить эти конструкции на более короткие строки, определенно сделайте это.

Когда вы не можете, вы, вероятно, можете избавиться от всех этих обратных косых черт, поставив всю RHS в скобки. Затем Python будет разбирать многострочные конструкции без обратных косых черт, но также трудно сказать, лучше это или нет. В таких случаях, я думаю, вам просто нужно использовать свое лучшее суждение, держать нос и погружаться.