2015-12-18 18 views
6

C'è un modo per causare yaml.load per generare un'eccezione ogni volta che una determinata chiave appare più volte nello stesso dizionario?Come impedire la ridefinizione delle chiavi in ​​YAML?

Ad esempio, l'analisi seguente YAML solleverebbe un'eccezione, perché some_key appare due volte:

{ 
    some_key: 0, 
    another_key: 1, 
    some_key: 1 
} 

In realtà, il comportamento sopra descritto corrisponde al criterio più semplice per quanto riguarda ridefinizioni chiave. Una politica un po 'più elaborata potrebbe, ad esempio, specificare che solo le ridefinizioni che modificano il valore assegnato alla chiave comporterebbero un'eccezione, o potrebbero consentire di impostare il livello di severità della ridefinizione delle chiavi su "warning" anziché su "error" . Ecc. Una risposta ideale a questa domanda sarebbe in grado di supportare tali varianti.

+2

Eh, sembra che PyYAML dovrebbe già fare questo (chiavi duplicate non sono consentite dalle specifiche YAML) e che doesn in realtà è [un bug che è stato aperto per gli ultimi sette anni] (http://pyyaml.org/ticket/128). –

+1

Quel biglietto è stato migrato [qui] (https://github.com/yaml/pyyaml/issues/41). Ancora aperto. sadpanda.jpg – wim

risposta

1

Se si desidera che il loader per lanciare un errore, allora si dovrebbe solo definire il proprio caricatore, con un costruttore che controlla se la chiave è già nella ¹ mappatura:

import collections 
import ruamel.yaml as yaml 

from ruamel.yaml.reader import Reader 
from ruamel.yaml.scanner import Scanner 
from ruamel.yaml.parser_ import Parser 
from ruamel.yaml.composer import Composer 
from ruamel.yaml.constructor import Constructor 
from ruamel.yaml.resolver import Resolver 
from ruamel.yaml.nodes import MappingNode 
from ruamel.yaml.compat import PY2, PY3 


class MyConstructor(Constructor): 
    def construct_mapping(self, node, deep=False): 
     if not isinstance(node, MappingNode): 
      raise ConstructorError(
       None, None, 
       "expected a mapping node, but found %s" % node.id, 
       node.start_mark) 
     mapping = {} 
     for key_node, value_node in node.value: 
      # keys can be list -> deep 
      key = self.construct_object(key_node, deep=True) 
      # lists are not hashable, but tuples are 
      if not isinstance(key, collections.Hashable): 
       if isinstance(key, list): 
        key = tuple(key) 
      if PY2: 
       try: 
        hash(key) 
       except TypeError as exc: 
        raise ConstructorError(
         "while constructing a mapping", node.start_mark, 
         "found unacceptable key (%s)" % 
         exc, key_node.start_mark) 
      else: 
       if not isinstance(key, collections.Hashable): 
        raise ConstructorError(
         "while constructing a mapping", node.start_mark, 
         "found unhashable key", key_node.start_mark) 

      value = self.construct_object(value_node, deep=deep) 
      # next two lines differ from original 
      if key in mapping: 
       raise KeyError 
      mapping[key] = value 
     return mapping 


class MyLoader(Reader, Scanner, Parser, Composer, MyConstructor, Resolver): 
    def __init__(self, stream): 
     Reader.__init__(self, stream) 
     Scanner.__init__(self) 
     Parser.__init__(self) 
     Composer.__init__(self) 
     MyConstructor.__init__(self) 
     Resolver.__init__(self) 



yaml_str = """\ 
some_key: 0, 
another_key: 1, 
some_key: 1 
""" 

data = yaml.load(yaml_str, Loader=MyLoader) 
print(data) 

e che getta una KeyError .

Si noti che le parentesi graffe che si utilizzano nell'esempio non sono necessarie.

Non sono sicuro se questo funzionerà con merge keys.


¹ Ciò è stato fatto utilizzando ruamel.yaml di cui sono l'autore. ruamel.yaml una versione avanzata di PyYAML e il codice del caricatore per quest'ultimo deve essere simile.

+1

Belle cose. Sono contento di vedere che YAML non viene dimenticato. YAML ha già una grande idea (JSON) e lo rende 10 volte migliore. Sto usando YAML per i file di configurazione e trovo che si prenda cura di ogni esigenza immaginabile in questo dominio problematico. Grazie per questa risposta, e soprattutto, grazie per 'ruamel.yaml'. – kjo

+0

Inoltre, grazie per l'indizio sulle parentesi graffe superflue. – kjo

+0

Questo codice non è aggiornato sulla versione corrente (0.15.34). 'ModuleNotFoundError: nessun modulo chiamato 'ruamel.yaml.parser_'', corregge che quindi TypeError: __init __() ha un argomento di parole chiave inaspettate' preserve_quotes'' – wim

1

Ecco il codice equivalente dalla risposta di Anthon se si sta utilizzando pyyaml:

import collections 
import yaml 
import sys 

from yaml.reader import Reader 
from yaml.scanner import Scanner 
from yaml.parser import Parser 
from yaml.composer import Composer 
from yaml.constructor import Constructor, ConstructorError 
from yaml.resolver import Resolver 
from yaml.nodes import MappingNode 


class NoDuplicateConstructor(Constructor): 
    def construct_mapping(self, node, deep=False): 
     if not isinstance(node, MappingNode): 
      raise ConstructorError(
       None, None, 
       "expected a mapping node, but found %s" % node.id, 
       node.start_mark) 
     mapping = {} 
     for key_node, value_node in node.value: 
      # keys can be list -> deep 
      key = self.construct_object(key_node, deep=True) 
      # lists are not hashable, but tuples are 
      if not isinstance(key, collections.Hashable): 
       if isinstance(key, list): 
        key = tuple(key) 

      if sys.version_info.major == 2: 
       try: 
        hash(key) 
       except TypeError as exc: 
        raise ConstructorError(
         "while constructing a mapping", node.start_mark, 
         "found unacceptable key (%s)" % 
         exc, key_node.start_mark) 
      else: 
       if not isinstance(key, collections.Hashable): 
        raise ConstructorError(
         "while constructing a mapping", node.start_mark, 
         "found unhashable key", key_node.start_mark) 

      value = self.construct_object(value_node, deep=deep) 

      # Actually do the check. 
      if key in mapping: 
       raise KeyError("Got duplicate key: {!r}".format(key)) 

      mapping[key] = value 
     return mapping 


class NoDuplicateLoader(Reader, Scanner, Parser, Composer, NoDuplicateConstructor, Resolver): 
    def __init__(self, stream): 
     Reader.__init__(self, stream) 
     Scanner.__init__(self) 
     Parser.__init__(self) 
     Composer.__init__(self) 
     NoDuplicateConstructor.__init__(self) 
     Resolver.__init__(self) 



yaml_str = """\ 
some_key: 0, 
another_key: 
    x: 1 
""" 

data = yaml.load(yaml_str, Loader=NoDuplicateLoader) 
print(data)