Jinja: autoescape anulable

Creado en 15 oct. 2015  ·  11Comentarios  ·  Fuente: pallets/jinja

En el trabajo, usamos Jinja para renderizar plantillas como YAML. Nos gustaría personalizar todas las expansiones {{ variable }} para hacer algo sensible a los valores de Python como None (que es null en YAML) y una cadena vacía (que está codificada como '' en YAML). Parece que el mecanismo de escape automático de Jinja sería perfecto para esto. Desafortunadamente, no veo ninguna forma de configurar la función de escape automático para que no se escape automáticamente a HTML. En su lugar, tuve que parchear Markup y escape en jinja2.runtime . Sería bueno si hubiera un método autorizado oficialmente para hacer esto, por ejemplo, anulando algo en el entorno.

Todos 11 comentarios

No estoy seguro de si el autoescaping es una forma adecuada de hacer esto.

Prefiero usar un filtro. tojson parece un buen candidato ya que yaml es un superconjunto de json ,.


Además de eso: ¿Alguna razón por la que usaste plantillas basadas en cadenas para construir YAML? Serializar un diccionario de Python como YAML parece mucho más limpio. El único caso que puedo ver en el que esto podría no ser suficiente es cuando desea incluir comentarios. Pero en ese caso, podría usar un motor YAML que maneje la lectura / escritura no destructiva para que simplemente cargue una plantilla YAML con sus comentarios y elementos vacíos y luego actualice los datos y vuelva a serializarlos como YAML.

El objetivo de usar el autoescaping es que no quiero tener que poner un filtro en cada expansión de variable.

Nuestro caso de uso es escribir archivos de configuración para nuestros microservicios. Tenemos una "plantilla" incluida con cada servicio y queremos llenarla con la configuración por entorno que es específica para esa plantilla. Estoy de acuerdo en que usar YAML directamente es más limpio, pero no pudimos encontrar un "motor" genérico que sepa cómo reemplazar las variables cuya ubicación aún no conoce. (Hay alias y referencias YAML, pero en realidad no son una buena opción para esto, son más para serializar estructuras cíclicas). Hay formas en las que puedo pensar para hacer que esto funcione usando YAML, pero son algo hacky también. Esta configuración también da más control a los tipos de sustituciones que podemos realizar, aunque en la práctica no lo usamos para nada.

Puede usar un complemento jinja para aplicar el filtro a todas las variables. No es demasiado difícil siempre que no tenga que usar bloques i18n. Quizás puedas tomar algunas ideas de algo que escribí hace algún tiempo: https://github.com/indico/indico/blob/master/indico/web/flask/templating.py#L187

¡Esto es muy útil, gracias! Siento que el autoescaping sigue siendo un enfoque correcto para resolver esto porque, por ejemplo, puede marcar variables como |safe . Pero reconozco que, conceptualmente, esto no es realmente como el escape automático, porque no solo nos aseguramos de que las variables sean YAML válidas, sino que las formateamos de cierta manera. Escribiré una extensión.

@glasserc , ¿tuvo éxito con esta extensión? Tengo un problema similar, ¿puede señalar una solución / compartir algunas ideas?

La primera parte de mi solución fue la extensión que ejecuta todas las llamadas de variables a través de un filtro. Este es el mínimo indispensable para que funcione.

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

Esto podría ser más inteligente, por ejemplo, podría intentar omitir la inserción del filtro si ve un filtro safe o yaml explícitamente en el bloque de variables. Pero nunca terminé necesitando / teniendo tiempo para eso.

La segunda parte es el filtro en sí. El simple hecho de usar yaml.dump no era lo suficientemente sofisticado, por lo que tuve que hurgar un poco en las partes internas de 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()

Esto lo une todo, incluido asegurarse de que el filtro del nombre de pila esté disponible en el entorno:

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

Esto aún sería excelente para escapar de plantillas que no sean HTML. Estoy usando Jinja2 para generar JSON y quiero usar el escape JSON en lugar del escape HTML.

Usar plantillas Jinja o cualquier otro lenguaje de plantilla basado en cadenas o operaciones de cadena para generar JSON es simplemente incorrecto. Puedo ver por qué haría esto para YAML, ya que también está destinado a ser amigable para los humanos, pero JSON debe generarse, por ejemplo, a partir de un dictado de Python y no de una plantilla de Jinja.

(Dicho esto, tengo curiosidad por saber por qué quieres hacer eso y qué estás tratando de hacer: p)

Supongo que eso tiene sentido. :-) Cerraré el # 571.

Tal vez JSON no sea bueno para ser generado por Jinja, pero estoy muy interesado en este tipo de característica porque estoy generando LaTeX usando jinja. He logrado la mayor parte del camino modificando block_start_string y block_end_string y otros en Environment . Por ahora, he configurado autoescape = False , pero idealmente me gustaría pasar algo para hacer el escape yo mismo (tal vez como una expresión regular).

Esto también se aplica a la generación de texto sin formato y rebajas. Jinja ha sido una gran herramienta hasta ahora para crear plantillas de documentos que no son HTML.

Estaba buscando usar jinja para generar consultas SQL porque usar str.format() puede resultar complicado si necesita múltiples partes de consulta condicional.

Ya existen bibliotecas para hacer eso, a saber, jinja-vanish que permite funciones de escape personalizadas y jinjasql .
El primero tiene que deshabilitar la evaluación constante en la compilación, lo que parece un poco malo. El otro
usa el enfoque de filtro para agregar un filtro especial a todas las expresiones variables que están bien pero que tampoco se sienten bien.

No lo he analizado a fondo, pero no parece ser tan difícil implementarlo. ¿Podría considerarse esto o está fuera del alcance de la biblioteca?

¿Fue útil esta página
0 / 5 - 0 calificaciones