Jinja: échappement automatique annulable

Créé le 15 oct. 2015  ·  11Commentaires  ·  Source: pallets/jinja

Au travail, nous utilisons Jinja pour rendre les modèles au format YAML. Nous aimerions personnaliser toutes les extensions {{ variable }} pour faire quelque chose de sensible aux valeurs Python comme None (qui est null en YAML) et une chaîne vide (qui est codée comme '' en YAML). Il semble que le mécanisme d'échappement automatique Jinja serait parfait pour cela. Malheureusement, je ne vois aucun moyen de configurer la fonctionnalité d'échappement automatique pour ne pas s'échapper automatiquement en HTML. Au lieu de cela, j'ai dû patcher Markup et escape dans jinja2.runtime . Ce serait bien s'il y avait une méthode officiellement approuvée pour le faire, par exemple en remplaçant quelque chose dans l'environnement.

Tous les 11 commentaires

Je ne sais pas si l'échappement automatique est un bon moyen de le faire.

Je préfère utiliser un filtre. tojson semble un bon candidat puisque yaml est un sur-ensemble de json.


En plus de cela : une raison pour laquelle vous utilisez des modèles basés sur des chaînes pour créer YAML ? Sérialiser un dictionnaire Python en YAML semble beaucoup plus propre. Le seul cas que je peux voir où cela pourrait ne pas être suffisant est lorsque vous souhaitez inclure des commentaires. Mais dans ce cas, vous pouvez utiliser un moteur YAML qui gère la lecture/écriture non destructive afin de simplement charger un modèle YAML avec vos commentaires et éléments vides, puis de mettre à jour les données et de les re-sérialiser en tant que YAML.

L'intérêt d'utiliser l'échappement automatique est que je ne veux pas avoir à mettre un filtre sur chaque extension de variable.

Notre cas d'utilisation consiste à écrire des fichiers de configuration pour nos microservices. Nous avons un "modèle" inclus avec chaque service et nous voulons le remplir avec une configuration par environnement qui est spécifique à ce modèle. Je suis d'accord que l'utilisation directe de YAML est plus propre, mais nous n'avons pas pu trouver de "moteur" générique qui sache comment remplacer les variables dont il ne connaît pas déjà l'emplacement. (Il existe des alias et des références YAML, mais ils ne conviennent pas vraiment à cela - ils sont plutôt destinés à la sérialisation de structures cycliques.) Il existe des moyens pour que cela fonctionne en utilisant YAML, mais ils sont genre de hacky aussi. Cette configuration donne également plus de contrôle sur les types de substitutions que nous pouvons effectuer, bien qu'en pratique nous ne l'utilisons pas vraiment pour quoi que ce soit.

Vous pouvez utiliser un plugin jinja pour appliquer le filtre à toutes les variables. Ce n'est pas trop difficile tant que vous n'avez pas à utiliser de blocs i18n. Peut-être que vous pouvez prendre quelques idées de quelque chose que j'ai écrit il y a quelque temps : https://github.com/indico/indico/blob/master/indico/web/flask/templating.py#L187

C'est très utile, merci ! J'ai l'impression que l'échappement automatique est toujours une approche correcte pour résoudre ce problème, car vous pouvez, par exemple, marquer les variables comme |safe . Mais je concède que conceptuellement, ce n'est pas vraiment comme l'échappement automatique, car nous ne nous assurons pas seulement que les variables sont YAML valides, mais nous les formatons d'une certaine manière. Je vais écrire une extension.

@glasserc as -tu réussi avec cette extension ? J'ai un problème similaire, pouvez-vous m'indiquer une solution / partager quelques idées ?

La première partie de ma solution était l'extension qui exécute tous les appels de variables à travers un filtre. C'est le strict minimum dont j'avais besoin pour que cela fonctionne.

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

Cela pourrait être plus intelligent, par exemple, il pourrait essayer de sauter l'insertion du filtre s'il voit un filtre safe ou yaml explicitement dans le bloc variable. Mais je n'ai jamais fini par avoir besoin/avoir du temps pour ça.

La deuxième partie est le filtre lui-même. Le simple fait d'utiliser yaml.dump n'était pas assez sophistiqué, j'ai donc dû fouiller un peu dans les composants internes 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()

Cela relie le tout, notamment en s'assurant que le filtre du nom donné est disponible dans l'environnement :

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

Ce serait toujours idéal pour échapper aux modèles non HTML. J'utilise Jinja2 pour générer du JSON et je souhaite utiliser l'échappement JSON au lieu de l'échappement HTML.

Utiliser des modèles Jinja ou tout autre langage de modèle basé sur des chaînes ou des opérations de chaîne pour générer JSON est tout simplement faux. Je peux voir pourquoi vous feriez cela pour YAML, car il est également conçu pour être convivial, mais JSON doit être généré à partir, par exemple, d'un dict Python et non d'un modèle Jinja.

(cela dit, je suis curieux de savoir pourquoi vous voulez faire ça et ce que vous essayez de faire :p)

Je suppose que cela a du sens. :-) Je ferme le #571.

Peut-être que JSON n'est pas bon pour être généré par Jinja, mais je suis très intéressé par ce genre de fonctionnalité car je génère du LaTeX en utilisant jinja. J'ai fait la majeure partie du chemin en modifiant les block_start_string et block_end_string et d'autres dans le Environment . Pour l'instant, j'ai défini autoescape = False , mais j'aimerais idéalement passer quelque chose pour faire l'échappement moi-même (peut-être comme une regex).

Cela s'applique également à la génération de texte en clair et de démarques. Jinja a été un excellent outil jusqu'à présent pour créer des modèles de documents non HTML.

Je cherchais à utiliser jinja pour générer des requêtes SQL, car l'utilisation de str.format() peut devenir compliquée si vous avez besoin de plusieurs parties de requête conditionnelles.

Il existe déjà des bibliothèques pour le faire, à savoir jinja-vanish qui permet des fonctions d'échappement personnalisées et jinjasql .
Le premier doit désactiver l'évaluation constante à la compilation, ce qui semble un peu mauvais. L'autre
utilise l'approche de filtre pour ajouter un filtre spécial à toutes les expressions variables, ce qui est correct mais ne semble pas correct.

Je ne l'ai pas examiné en profondeur, mais cela ne semble pas être si difficile à mettre en œuvre. Cela pourrait-il être envisagé ou est-il hors de portée pour la bibliothèque ?

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

priestc picture priestc  ·  5Commentaires

guettli picture guettli  ·  5Commentaires

DriverX picture DriverX  ·  4Commentaires

jp-costa picture jp-costa  ·  5Commentaires

Yannik picture Yannik  ·  4Commentaires