O autor Pony ORM está aqui.
O Pony traduz o gerador Python em consulta SQL em três etapas:
- Descompilação do bytecode do gerador e reconstrução do gerador AST (árvore de sintaxe abstrata)
- Tradução de Python AST em "SQL abstrato" - representação universal baseada em lista de uma consulta SQL
- Converter representação SQL abstrata em dialeto SQL dependente de banco de dados específico
A parte mais complexa é a segunda etapa, onde Pony deve entender o "significado" das expressões Python. Parece que você está mais interessado na primeira etapa, então deixe-me explicar como funciona a descompilação.
Vamos considerar esta consulta:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Que será traduzido no seguinte SQL:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
E a seguir está o resultado dessa consulta que será impressa:
id|email |password|name |country|address
--+-------------------+--------+--------------+-------+---------
1 |john@example.com |*** |John Smith |USA |address 1
2 |matthew@example.com|*** |Matthew Reed |USA |address 2
4 |rebecca@example.com|*** |Rebecca Lawson|USA |address 4
A select()
função aceita um gerador Python como argumento e analisa seu bytecode. Podemos obter instruções de bytecode deste gerador usando o dis
módulo Python padrão :
>>> gen = (c for c in Customer if c.country == 'USA')
>>> import dis
>>> dis.dis(gen.gi_frame.f_code)
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 26 (to 32)
6 STORE_FAST 1 (c)
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
21 POP_JUMP_IF_FALSE 3
24 LOAD_FAST 1 (c)
27 YIELD_VALUE
28 POP_TOP
29 JUMP_ABSOLUTE 3
>> 32 LOAD_CONST 1 (None)
35 RETURN_VALUE
Pony ORM tem a função decompile()
dentro do módulo pony.orm.decompiling
que pode restaurar um AST do bytecode:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Aqui, podemos ver a representação textual dos nós AST:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Vamos agora ver como o decompile()
função funciona.
A decompile()
função cria umDecompiler
objeto, que implementa o padrão Visitor. A instância do decompiler obtém instruções de bytecode uma a uma. Para cada instrução, o objeto descompilador chama seu próprio método. O nome deste método é igual ao nome da instrução bytecode atual.
Quando o Python calcula uma expressão, ele usa pilha, que armazena um resultado intermediário do cálculo. O objeto descompilador também tem sua própria pilha, mas essa pilha não armazena o resultado do cálculo da expressão, mas o nó AST da expressão.
Quando o método descompilador para a próxima instrução de bytecode é chamado, ele pega os nós AST da pilha, combina-os em um novo nó AST e então coloca esse nó no topo da pilha.
Por exemplo, vamos ver como a subexpressão c.country == 'USA'
é calculada. O fragmento de bytecode correspondente é:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Portanto, o objeto descompilador faz o seguinte:
- Chamadas
decompiler.LOAD_FAST('c')
. Este método coloca o Name('c')
nó no topo da pilha do descompilador.
- Chamadas
decompiler.LOAD_ATTR('country')
. Este método tira o Name('c')
nó da pilha, cria o Geattr(Name('c'), 'country')
nó e o coloca no topo da pilha.
- Chamadas
decompiler.LOAD_CONST('USA')
. Este método coloca oConst('USA')
nó no topo da pilha.
- Chamadas
decompiler.COMPARE_OP('==')
. Este método pega dois nós (Getattr e Const) da pilha e, em seguida, coloca Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
no topo da pilha.
Depois que todas as instruções de bytecode são processadas, a pilha do descompilador contém um único nó AST que corresponde a toda a expressão do gerador.
Como o Pony ORM precisa descompilar apenas geradores e lambdas, isso não é tão complexo, porque o fluxo de instruções para um gerador é relativamente simples - é apenas um monte de loops aninhados.
Atualmente Pony ORM cobre todo o conjunto de instruções do gerador, exceto duas coisas:
- Expressões if inline:
a if b else c
- Comparações de compostos:
a < b < c
Se Pony encontrar tal expressão, ele gerará a NotImplementedError
exceção. Mas, mesmo neste caso, você pode fazer funcionar passando a expressão do gerador como uma string. Quando você passa um gerador como string, o Pony não usa o módulo descompilador. Em vez disso, ele obtém o AST usando o Python padrãocompiler.parse
função .
espero que isso responda sua pergunta.
p
objeto é um objeto de um tipo implementado por Pony que olha para o que métodos / propriedades estão sendo acessados nele (por exemplo,name
,startswith
) e os converte em SQL.