Jinja: autoescape superável

Criado em 15 out. 2015  ·  11Comentários  ·  Fonte: pallets/jinja

No trabalho, estamos usando Jinja para renderizar modelos como YAML. Gostaríamos de personalizar todas as expansões de {{ variable }} para fazer algo sensível aos valores Python como None (que é null em YAML) e string vazia (que é codificada como '' em YAML). Parece que o mecanismo de escape automático Jinja seria perfeito para isso. Infelizmente, não vejo nenhuma maneira de configurar a funcionalidade de escape automático para não fazer o escape automático para HTML. Em vez disso, tive que fazer um monkeypatch de Markup e escape em jinja2.runtime . Seria bom se houvesse um método oficialmente sancionado para fazer isso, por exemplo, substituindo algo no ambiente.

Todos 11 comentários

Não tenho certeza se o escape automático é uma maneira adequada de fazer isso.

Prefiro usar um filtro. tojson parece um bom candidato, pois yaml é um superconjunto de json.


Além disso: alguma razão pela qual você usa templates baseados em string para construir YAML? Serializar um dicionário Python como YAML parece muito mais limpo. O único caso em que vejo onde isso pode não ser suficiente é quando você deseja incluir comentários. Mas, nesse caso, você pode usar um mecanismo YAML que lida com leitura / gravação não destrutiva, de forma que você simplesmente carregue um modelo YAML com seus comentários e itens vazios e, em seguida, atualize os dados e re-serialize-os como YAML.

O objetivo de usar o escape automático é que não quero colocar um filtro em cada expansão de variável.

Nosso caso de uso é escrever arquivos de configuração para nossos microsserviços. Temos um "modelo" incluído em cada serviço e queremos preenchê-lo com uma configuração por ambiente específica para esse modelo. Concordo que usar YAML diretamente é mais limpo, mas não conseguimos encontrar um "mecanismo" genérico que saiba como substituir variáveis ​​cuja localização ainda não conhece. (Existem aliases e referências YAML, mas eles não são realmente adequados para isso - eles são mais para serializar estruturas cíclicas.) Existem maneiras que eu posso pensar para fazer isso funcionar usando YAML, mas eles são meio hacky também. Essa configuração também dá mais controle aos tipos de substituições que podemos realizar, embora na prática não usemos isso para nada.

Você pode usar um plugin jinja para aplicar o filtro a todas as variáveis. Não é muito difícil, desde que você não precise usar blocos i18n. Talvez você possa tirar algumas ideias de algo que escrevi há algum tempo: https://github.com/indico/indico/blob/master/indico/web/flask/templating.py#L187

Isso é muito útil, obrigado! Eu sinto que o escape automático ainda é uma abordagem OK para resolver isso porque você pode, por exemplo, sinalizar variáveis ​​como |safe . Mas admito que, conceitualmente, isso não é realmente como escape automático, porque não estamos apenas garantindo que as variáveis ​​são YAML válidas, mas formatando-as de uma certa maneira. Vou escrever uma extensão.

@glasserc você teve sucesso com esta extensão? Eu tenho problema semelhante, você pode apontar para solução / compartilhar algumas idéias?

A primeira parte da minha solução foi a extensão que executa todas as chamadas de variáveis ​​por meio de um filtro. Este é o mínimo necessário para fazer isso funcionar.

import jinja2.ext
from jinja2.lexer import Token

class YAMLEverythingExtension(jinja2.ext.Extension):
    """
    Insert a `|yaml` filter at the end of every variable substitution.

    This will ensure that all injected values are converted to YAML.
    """
    def filter_stream(self, stream):
        # This is based on https://github.com/indico/indico/blob/master/indico/web/flask/templating.py.
        for token in stream:
            if token.type == 'variable_end':
                yield Token(token.lineno, 'pipe', '|')
                yield Token(token.lineno, 'name', 'yaml')
            yield token

Isso poderia ser mais inteligente, por exemplo, poderia tentar pular a inserção do filtro se ver um filtro safe ou yaml explicitamente no bloco de variáveis. Mas nunca acabei precisando / tendo tempo para isso.

A segunda parte é o próprio filtro. Usar apenas yaml.dump não era sofisticado o suficiente, então eu tive que vasculhar um pouco as partes internas do yaml.

import cStringIO
import yaml

def yaml_filter(val):
    """Serialize some value in isolation, not as part of any document.

    We can't just use yaml.dump because that outputs an entire document, including newlines, which isn't helpful for
    inserting into a YAML document."""
    if isinstance(val, jinja2.Undefined):
        val._fail_with_undefined_error()
    stream = cStringIO.StringIO()
    dumper = yaml.dumper.Dumper(stream)
    dumper.open()
    node = dumper.represent_data(val)
    dumper.serialize(node)
    # The serialized node tends to have a \n at the end.  The template might not
    # want a \n inserted here, e.g. if two variables are on the same line, so
    # strip.
    return stream.getvalue().strip()

Isso une tudo, incluindo a garantia de que o filtro do nome fornecido está disponível no ambiente:

from jinja2 import loaders
from jinja2 import Environment, StrictUndefined

def get_environment():
    """Create a standard Jinja environment that has everything in it.
    """
    jinja_env = Environment(extensions=(YAMLEverythingExtension,),
                            # some other options that we use at work
                            loader=loaders.FileSystemLoader(['.', '/']),
                            undefined=StrictUndefined)
    jinja_env.filters["yaml"] = yaml_filter
    return jinja_env

Isso ainda seria ótimo para escapar de modelos não HTML. Estou usando Jinja2 para gerar JSON e quero usar escape JSON em vez de escape HTML.

Usar modelos Jinja ou qualquer outra linguagem de modelo baseada em string ou operações de string para gerar JSON é simplesmente errado. Eu posso ver por que você faria isso para YAML, uma vez que é destinado a ser amigável também, mas JSON deve ser gerado a partir de, por exemplo, um dicionário Python e não de um modelo Jinja.

(Dito isso, estou curioso para saber por que você deseja fazer isso e o que está tentando fazer: p)

Acho que faz sentido. :-) Vou fechar # 571.

Talvez JSON não seja bom para ser gerado por Jinja, mas estou muito interessado neste tipo de recurso porque estou gerando LaTeX usando jinja. Obtive a maior parte do caminho modificando block_start_string e block_end_string e outros em Environment . Por enquanto, eu defini autoescape = False , mas eu gostaria de passar algo para fazer o escape sozinho (talvez como um regex).

Isso também se aplica à geração de texto simples e markdown. Jinja tem sido uma ótima ferramenta até agora para modelagem de documentos não-html.

Eu estava tentando usar o jinja para gerar consultas SQL porque usar str.format() pode ser complicado se você precisar de várias partes de consulta condicional.

Já existem bibliotecas para fazer isso, nomeadamente jinja-vanish que permite funções de escape personalizadas e jinjasql .
O primeiro tem que desabilitar a avaliação constante na compilação, o que parece um pouco ruim. O outro
usa a abordagem de filtro para adicionar um filtro especial a todas as expressões de variáveis, o que está ok, mas também parece não estar certo.

Eu não olhei profundamente para isso, mas não parece ser tão difícil de implementar. Isso poderia ser considerado ou está fora do escopo da biblioteca?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

htgoebel picture htgoebel  ·  4Comentários

DriverX picture DriverX  ·  4Comentários

mitsuhiko picture mitsuhiko  ·  3Comentários

navilan picture navilan  ·  5Comentários

priestc picture priestc  ·  5Comentários