Jinja: überschreibbares Autoescape

Erstellt am 15. Okt. 2015  ·  11Kommentare  ·  Quelle: pallets/jinja

Bei der Arbeit verwenden wir Jinja, um Vorlagen als YAML zu rendern. Wir möchten alle {{ variable }} Erweiterungen anpassen, um etwas Sinnvolles für Python-Werte wie None (was null in YAML ist) und eine leere Zeichenfolge (die als '' codiert ist) zu tun. Markup und escape in jinja2.runtime . Es wäre schön, wenn es dafür eine offiziell sanktionierte Methode gäbe, zum Beispiel indem man etwas in der Umgebung überschreibt.

Alle 11 Kommentare

Ich bin mir nicht sicher, ob Autoescaping der richtige Weg ist, dies zu tun.

Ich würde lieber einen Filter verwenden. tojson scheint ein guter Kandidat zu sein, da yaml eine Obermenge von json ist.


Abgesehen davon: Gibt es einen Grund, warum Sie String-basierte Vorlagen zum Erstellen von YAML verwenden? Das Serialisieren eines Python-Wörterbuchs als YAML scheint viel sauberer zu sein. Der einzige Fall, in dem dies möglicherweise nicht ausreicht, ist, wenn Sie Kommentare einfügen möchten. In diesem Fall könnten Sie jedoch eine YAML-Engine verwenden, die zerstörungsfreies Lesen/Schreiben verarbeitet, sodass Sie einfach eine YAML-Vorlage mit Ihren Kommentaren und leeren Elementen laden und dann die Daten aktualisieren und als YAML erneut serialisieren.

Der Sinn der Verwendung von Autoescaping besteht darin, dass ich nicht auf jede einzelne Variablenerweiterung einen Filter setzen muss.

Unser Anwendungsfall ist das Schreiben von Konfigurationsdateien für unsere Microservices. Jedem Dienst ist eine "Vorlage" beigefügt, die wir mit einer umgebungsspezifischen Konfiguration füllen möchten, die für diese Vorlage spezifisch ist. Ich stimme zu, dass die direkte Verwendung von YAML sauberer ist, aber wir konnten keine generische "Engine" finden, die weiß, wie Variablen zu ersetzen sind, deren Position sie noch nicht kennt. (Es gibt YAML-Aliasse und -Referenzen, aber dafür sind sie nicht wirklich gut geeignet – sie sind eher zum Serialisieren zyklischer Strukturen.) Es gibt Möglichkeiten, dies mit YAML zum Laufen zu bringen, aber sie sind auch irgendwie hackig. Dieses Setup gibt auch mehr Kontrolle über die Arten von Auswechslungen, die wir ausführen können, obwohl wir das in der Praxis nicht wirklich für irgendetwas verwenden.

Sie können ein Jinja-Plugin verwenden, um den Filter auf alle Variablen anzuwenden. Es ist nicht allzu schwer, solange Sie keine i18n-Blöcke verwenden müssen. Vielleicht können Sie einige Ideen aus etwas entnehmen, das ich vor einiger Zeit geschrieben habe: https://github.com/indico/indico/blob/master/indico/web/flask/templating.py#L187

Das ist sehr hilfreich, danke! Ich habe das Gefühl, dass Autoescaping immer noch ein OK-Ansatz ist, um dieses Problem zu lösen, da Sie beispielsweise Variablen als |safe kennzeichnen können. Aber ich gebe zu, dass dies konzeptionell nicht wirklich wie Autoescaping ist, weil wir nicht nur sicherstellen, dass Variablen gültiges YAML sind, sondern sie auf eine bestimmte Weise formatieren. Ich schreibe eine Erweiterung.

@glasserc ist dir diese Erweiterung gelungen? Ich habe ein ähnliches Problem, können Sie auf eine Lösung hinweisen / einige Ideen teilen?

Der erste Teil meiner Lösung war die Erweiterung, die alle Variablenaufrufe durch einen Filter laufen lässt. Dies ist das absolute Minimum, das ich brauchte, um das zum Laufen zu bringen.

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

Dies könnte intelligenter sein, zum Beispiel könnte es versuchen, das Einfügen des Filters zu überspringen, wenn es einen safe oder yaml Filter explizit im Variablenblock sieht. Aber dafür brauchte/hatte ich nie Zeit.

Der zweite Teil ist der Filter selbst. Die bloße Verwendung von yaml.dump war nicht ausgereift genug, also musste ich ein wenig in den Yaml-Interna herumstöbern.

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()

Dies verbindet alles miteinander, einschließlich der Sicherstellung, dass der Filter des angegebenen Namens in der Umgebung verfügbar ist:

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

Dies wäre immer noch großartig, um Nicht-HTML-Vorlagen zu entkommen. Ich verwende Jinja2 zum Generieren von JSON und möchte JSON-Escape anstelle von HTML-Escape verwenden.

Die Verwendung von Jinja-Vorlagen oder einer anderen string-basierten Vorlagensprache oder String-Operationen zum Generieren von JSON ist einfach falsch. Ich kann verstehen, warum Sie dies für YAML tun würden, da es auch menschenfreundlich sein soll, aber JSON sollte beispielsweise aus einem Python-Dict und nicht aus einem Jinja-Template generiert werden.

(das heißt, ich bin neugierig, warum du das tun willst und was du versuchst zu tun :p)

Ich denke, das macht Sinn. :-) Ich schließe #571.

Vielleicht ist JSON nicht gut, um von Jinja generiert zu werden, aber ich interessiere mich sehr für diese Art von Funktion, weil ich LaTeX mit Jinja generiere. Ich habe den größten Teil des Weges dorthin geschafft, indem ich block_start_string und block_end_string und andere in Environment . Im Moment habe ich autoescape = False , aber ich möchte idealerweise etwas übergeben, um selbst zu entkommen (vielleicht wie eine Regex).

Dies gilt auch für die Generierung von Klartext und Markdown. Jinja war bisher ein großartiges Werkzeug zum Erstellen von Vorlagen für Nicht-HTML-Dokumente.

Ich habe mich mit der Verwendung von Jinja zum Generieren von SQL-Abfragen befasst, da die Verwendung von str.format() chaotisch werden kann, wenn Sie mehrere bedingte Abfrageteile benötigen.

Dafür gibt es bereits Bibliotheken, nämlich jinja-vanish , das benutzerdefinierte Escape-Funktionen ermöglicht und jinjasql .
Der erste muss die Konstantenauswertung beim Kompilieren deaktivieren, was ein wenig schlecht erscheint. Der andere
verwendet den Filter-Ansatz, um allen variablen Ausdrücken einen speziellen Filter hinzuzufügen, der in Ordnung ist, sich aber auch nicht richtig anfühlt.

Ich habe mich nicht intensiv damit befasst, aber es scheint nicht so schwer zu sein, es zu implementieren. Könnte dies in Betracht gezogen werden oder liegt es außerhalb des Anwendungsbereichs der Bibliothek?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen