web-dev-qa-db-fra.com

Format de vidage PyYAML

Je sais qu'il y a quelques questions à ce sujet sur SO, mais je n'ai pas trouvé ce que je cherchais.

J'utilise pyyaml pour lire (.load()) un fichier .yml, Modifier ou ajouter une clé, puis l'écrire (.dump() ) encore. Le problème est que je souhaite conserver le format de fichier après le vidage, mais il change.

Par exemple, j'édite la clé en.test.index.few Pour dire "Bye" Au lieu de "Hello"

Python:

with open(path, 'r', encoding = "utf-8") as yaml_file:
    self.dict = pyyaml.load(yaml_file)

Ensuite, après avoir changé la clé:

with open(path, 'w', encoding = "utf-8") as yaml_file:
    dump = pyyaml.dump(self.dict, default_flow_style = False, allow_unicode = True, encoding = None)
    yaml_file.write( dump )

Yaml:

Avant:

en:
  test:
    new: "Bye"
    index:
      few: "Hello"
  anothertest: "Something"

Après:

en:
  anothertest: Something
  test:
    index:
      few: Hello
    new: Bye

Existe-t-il un moyen de conserver le même format?, Par exemple les qoutes et l'ordre. Suis-je en train d'utiliser le mauvais outil pour cela?

Je sais que le fichier d'origine n'est peut-être pas tout à fait correct, mais je n'ai aucun contrôle sur lui (c'est un Ruby on Rails i18n file).

Merci beaucoup.

32
nicosantangelo

Utilisez plutôt ruamel.yaml

Bibliothèque Fight! Un conte de deux bibliothèques

PyYAML est effectivement mort et l'est depuis plusieurs années. Pour aggraver les choses, la maison officielle du projet à http://pyyaml.org semble avoir été supprimée récemment. Ce site a hébergé le traqueur de problèmes PyYAML, la documentation et les téléchargements. Au moment de la rédaction de cet article, tous sont partis. Ce n'est rien de moins que calamiteux. Bienvenue à juste un autre jour en open-source.

ruamel.yaml Est activement mainten . Contrairement à PyYAML, ruamel.yaml Prend en charge:

  • YAML <= 1.2. PyYAML ne prend en charge que YAML <= 1.1. Ceci est vital, car YAML 1.2 intentionnellement rompt la compatibilité descendante avec YAML 1.1 dans plusieurs cas Edge. Ce serait généralement une mauvaise chose. Dans ce cas, cela rend YAML 1.2 un sur-ensemble strict de JSON. Puisque YAML 1.1 est pas un sur-ensemble strict de JSON, c'est une bonne chose.
  • Conservation aller-retour. Lors de l'appel de yaml.dump() pour vider un dictionnaire chargé par un appel précédent à yaml.load():
    • PyYAML ignore naïvement tous les formats d'entrée - y compris les commentaires, les commandes, les citations et les espaces. Jeté comme autant de déchets numériques dans le seau de bits disponible le plus proche.
    • ruamel.yaml Respecte intelligemment tous le formatage d'entrée. Tout. Toute l'enchilada stylistique. Le Shebang littéraire entier. Tous.

Migration de bibliothèque: la piste des larmes de code

Étant donné que ruamel.yaml Est une fourchette PyYAML et est donc conforme à l'API PyYAML, le passage de PyYAML à ruamel.yaml Dans les applications existantes est généralement aussi simple que de remplacer toutes les instances de ceci:

# This imports PyYAML. Stop doing this.
import yaml

...avec ça:

# This imports "ruamel.yaml". Always do this.
from ruamel import yaml

C'est tout.

Aucun autre changement ne devrait être nécessaire. Les fonctions yaml.load() et yaml.dump() devraient continuer à se comporter comme prévu - avec les avantages supplémentaires de prendre désormais en charge YAML 1.2 et de recevoir activement des corrections de bogues.

Conservation aller-retour et ce qu'elle peut faire pour vous

Pour une compatibilité descendante avec PyYaml, les fonctions yaml.load() et yaml.dump() font pas effectuent la conservation aller-retour par défaut. Pour ce faire, passez explicitement:

  • Le paramètre de mot clé facultatif Loader=ruamel.yaml.RoundTripLoader Sur yaml.load().
  • Le paramètre de mot clé facultatif Dumper=ruamel.yaml.RoundTripDumper Sur yaml.dump().

Un exemple gentiment "emprunté" à la documentation ruamel.yaml :

import ruamel.yaml

inp = """\
# example
name:
  # Yet another Great Duke of Hell. He's not so bad, really.
  family: TheMighty
  given: Ashtaroth
"""

code = ruamel.yaml.load(inp, Loader=ruamel.yaml.RoundTripLoader)
code['name']['given'] = 'Astarte'  # Oh no you didn't.

print(ruamel.yaml.dump(code, Dumper=ruamel.yaml.RoundTripDumper), end='')

C'est fait. Les commentaires, la commande, les citations et les espaces blancs seront maintenant conservés intacts.

tl; dr

Utilisez toujours ruamel.yaml. N'utilisez jamais PyYAML. ruamel.yaml Vit. PyYAML est un cadavre fétide pourrissant dans le sol de charnier de moulage de PyPi.

Vive ruamel.yaml.

72
Cecil Curry

Premier

Pour représenter les données du dictionnaire est utilisé le code suivant:

mapping = list(mapping.items())
    try:
        mapping = sorted(mapping)
    except TypeError:
        pass

C'est pourquoi la commande est modifiée

Deuxième

Les informations sur la présentation du type scalaire (avec guillemets doubles ou non) sont perdues lors de la lecture (c'est l'approche principale de la bibliothèque)

Résumé

Vous pouvez créer votre propre classe basée sur 'Dumper' et surcharger la méthode 'represent_mapping' pour changer le comportement de présentation du dictionnaire

Pour enregistrer des informations sur les guillemets doubles pour scalaire, vous devez également créer votre propre classe basée sur "Loader", mais je crains que cela n'affecte et d'autres classes et qu'il soit difficile de le faire

2
Habibutsu

Dans mon cas, je veux " Si la valeur contient un { Ou un }, Sinon rien. Par exemple:

 en:
   key1: value is 1
   key2: 'value is {1}'

Pour cela, copiez la fonction represent_str() du fichier representer.py dans le module PyYaml et utilisez un autre style si la chaîne contient { Ou un }:

def represent_str(self, data):
    tag = None
    style = None
    # Add these two lines:
    if '{' in data or '}' in data:
        style = '"'
    try:
        data = unicode(data, 'ascii')
        tag = u'tag:yaml.org,2002:str'
    except UnicodeDecodeError:
        try:
            data = unicode(data, 'utf-8')
            tag = u'tag:yaml.org,2002:str'
        except UnicodeDecodeError:
            data = data.encode('base64')
            tag = u'tag:yaml.org,2002:binary'
            style = '|'
    return self.represent_scalar(tag, data, style=style)

Pour l'utiliser dans votre code:

import yaml

def represent_str(self, data):
  ...

yaml.add_representer(str, represent_str)

Dans ce cas, aucune différence entre les clés et les valeurs et cela me suffit. Si vous voulez un style différent pour les clés et les valeurs, effectuez la même chose avec la fonction represent_mapping

0
Nelson G.