2017-08-18 5 views
1

Ich habe Probleme YAML-Serialisierung Klassen, die Typ Referenzen als Mitglieder haben. Ich benutze den Safe Loader von ramel.yaml.YAML - Serialisierung Attribute, die Typen sind

Ich führte alle folgenden von einer REPL-Eingabeaufforderung (um mehrere Fehler zu erhalten).

Initialisierung:

import sys 
from ruamel.yaml import YAML, yaml_object 

Y = YAML(typ="safe",pure=True) 

# ============== 

@yaml_object(Y) 
class A(object): 
    """Object I want to serialize""" 
    yaml_tag = "!Aclass" 
    def __init__(self, type): 
     self.type = type 
    def f(self): 
     return self.type() 
    pass 

class T1(object): 
    """This will be referenced.""" 
    pass 

@yaml_object(Y) 
class T2(object): 
    """Another referenced object""" 
    pass 

class T3(object): 
    """Yet another try""" 
    pass 
Y.register_class(T3.__class__) 

Code, der einen Fehler verursacht:

Y.dump(A(T1), sys.stdout) 
Y.dump(A(T2), sys.stdout) 
Y.dump(A(T3), sys.stdout) 
Y.dump(A(int), sys.stdout) 

Diese Ausgänge (nur die letzte Zeile von Tracebacks):

ruamel.yaml.representer.RepresenterError: cannot represent an object: <attribute '__dict__' of 'T1' objects> 
ruamel.yaml.representer.RepresenterError: cannot represent an object: <attribute '__dict__' of 'T2' objects> 
ruamel.yaml.representer.RepresenterError: cannot represent an object: <attribute '__dict__' of 'T3' objects> 
ruamel.yaml.representer.RepresenterError: cannot represent an object: <slot wrapper '__abs__' of 'int' objects> 

Jede Lösung, die mich lässt (sicher) einmalig den Typ speichern (ich muss Objekte vom Typ generieren und prüfen, ob ein ankommendes Objekt ist von einem bestimmten Typ) würde geschätzt werden. Eine Funktion oder Klasse, die meinen erforderlichen Typ generiert, hätte das gleiche Problem, dass sie auch nicht serialisierbar sind.


P.S. Ich habe möglicherweise auch einen Fehler gefunden, bei dem der Parser aus irgendeinem Grund ein unterschiedliches Verhalten hat, abhängig davon, ob das gleiche effektive Argument versucht wurde, serialisiert zu werden.

Y.dump(A(str), sys.stdout) 
Y.dump(A(str), sys.stdout) 
Y.dump(A(str), sys.stdout) 
Y.dump(A(str), sys.stdout) 

Ausgänge:

>>> Y.dump(A(str), sys.stdout) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\main.py", line 352, in dump 
    return self.dump_all([data], stream, _kw, transform=transform) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\main.py", line 383, in dump_all 
    self.representer.represent(data) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 73, in represent 
    node = self.represent_data(data) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 101, in represent_data 
    node = self.yaml_representers[data_types[0]](self, data) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\main.py", line 552, in t_y 
    tag, data, cls, flow_style=representer.default_flow_style) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 371, in represent_yaml_object 
    return self.represent_mapping(tag, state, flow_style=flow_style) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 206, in represent_mapping 
    node_value = self.represent_data(item_value) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 101, in represent_data 
    node = self.yaml_representers[data_types[0]](self, data) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\main.py", line 492, in t_y 
    tag, data, cls, flow_style=representer.default_flow_style) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 371, in represent_yaml_object 
    return self.represent_mapping(tag, state, flow_style=flow_style) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 206, in represent_mapping 
    node_value = self.represent_data(item_value) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 111, in represent_data 
    node = self.yaml_representers[None](self, data) 
    File "C:\Program Files\Anaconda3\lib\site-packages\ruamel\yaml\representer.py", line 375, in represent_undefined 
    raise RepresenterError("cannot represent an object: %s" % data) 
ruamel.yaml.representer.RepresenterError: cannot represent an object: <slot wrapper '__add__' of 'str' objects> 
>>> Y.dump(A(str), sys.stdout) 
!Aclass 
type: !type {} 
>>> Y.dump(A(str), sys.stdout) 
Traceback (most recent call last): 
# same traceback here 
ruamel.yaml.representer.RepresenterError: cannot represent an object: <slot wrapper '__add__' of 'str' objects> 
>>> Y.dump(A(str), sys.stdout) 
!Aclass 
type: !type {} 
>>> 

Antwort

1

YAML erwartet Objekte abzuladen, und schließlich tut dies durch skalare Strings auszuschreiben. T1 ist kein Objekt (noch ist T2 oder T3), und das ist, wo das Problem herkommt. Sie können versuchen, jede Klassenreferenz zu einem Objekt zu machen, und verwenden dafür Tags, aber IMO macht die Dinge nur komplizierter.

Schließlich es läuft alles auf immer eine skalare Darstellung, also eine String-Darstellung der Klasse in die Datei nach unten, so dass Sie auch A() anpassen könnte direkt eine String-Darstellung zu entleeren und lesen Sie es zurück:

import sys 
from ruamel.yaml import YAML, yaml_object 
from ruamel.yaml.compat import StringIO 
from ruamel.yaml.scalarstring import DoubleQuotedScalarString 


Y = YAML(typ="safe", pure=True) 

# ============== 

@yaml_object(Y) 
class A(object): 
    """Object I want to serialize""" 
    yaml_tag = "!Aclass" 
    def __init__(self, type): 
     self.type = type #.__class__.__name__ 

    @classmethod 
    def to_yaml(cls, representer, node): 
     return representer.represent_scalar(
      cls.yaml_tag, u'{}'.format(node.type.__name__) 
     ) 

    @classmethod 
    def from_yaml(cls, constructor, node): 
     if '.' in node.value: # in some other module 
      m, n = node.value.rsplit('.', 1) 
      return cls(getattr(sys.modules[m], n)) 
     else: 
      return cls(globals()[node.value]) 


class T1(object): 
    """This will be referenced.""" 
    pass 


@yaml_object(Y) 
class T2(object): 
    """Another referenced object""" 
    pass 


class T3(object): 
    """Yet another try""" 
    pass 
Y.register_class(T3) 


for t in T1, T2, T3, DoubleQuotedScalarString: 
    print('----------------------') 
    x = StringIO() 
    s = A(t) 
    print('s', s.type) 
    Y.dump(s, x) 
    print(x.getvalue()) 

    d = Y.load(x.getvalue()) 
    print('d', d.type) 

die gibt:

---------------------- 
s <class '__main__.T1'> 
!Aclass T1 
... 

d <class '__main__.T1'> 
---------------------- 
s <class '__main__.T2'> 
!Aclass T2 
... 

d <class '__main__.T2'> 
---------------------- 
s <class '__main__.T3'> 
!Aclass T3 
... 

d <class '__main__.T3'> 
---------------------- 
s <class 'ruamel.yaml.scalarstring.DoubleQuotedScalarString'> 
!Aclass DoubleQuotedScalarString 
... 

d <class 'ruamel.yaml.scalarstring.DoubleQuotedScalarString'> 

Wenn es andere Attribute auf A() sind, die abgeladen/geladen werden muss Sie ein Wörterbuch erstellen sollte (mit dem String .type umgewandelt) und Dump/l Oad das.

Ich glaube nicht, dass Sie einen echten Fehler gefunden haben, aber dass Sie nach einem Fehler einen Nebeneffekt haben: Das Y Objekt (und seine Komponenten) bleiben in einem undefinierten Zustand. Sie sollten eine YAML() Instanz nach dem Fangen von Fehlern nicht erneut verwenden. Das sollte in der Dokumentation klarer sein. Wenn Sie also einen try/except in der for-Schleife machen wollen, sollten Sie den Y = YAML(typ='safe', pure=True) innerhalb des try Teils verschieben.

+0

In Ordnung, das scheint gut zu funktionieren, wenn ich z.B. T2 in meinem. Da ich jedoch Objekte der angegebenen Klasse konstruiere und Y nicht sagen kann (zumindest nicht von Ihrer A-Implementierung), ob das Objekt sicher geladen werden kann, lade ich möglicherweise einen bösartigen Typ in meinen Code ein. In diesem Fall wurde T1 "geladen", obwohl die Klasse nicht registriert war. Eine mögliche Lösung wäre das Einchecken von A.from_yaml(), ob die gefundene Klasse von Y registriert wurde (d. H. Ist es in constructor.yaml_constructors?), Denke ich. Bezüglich des zweiten Teils macht das Sinn. –

+0

Sie haben Recht mit der Sicherheit, ich habe darüber nachgedacht, während ich an der Antwort gearbeitet habe, habe aber vergessen, das mit aufzunehmen. Ich würde alle relevanten Typen in ein Modul (oder mehrere Module in einem Unterverzeichnis) einfügen und sie von dort importieren. Dann können Sie den String im 'node.value' in' A''s 'from_yaml()' überprüfen. Sie können auch einen eigenen '@ yaml_type' erstellen, der ein (eindeutiges) Attribut festlegt, auf dem Sie testen (zum Zeitpunkt des Ladens und/oder Ladens). Es besteht keine Notwendigkeit, die Registrierung von ramel.yaml für Objekte zu speichern (was im Wesentlichen etwas anderes ist). – Anthon

+0

Ich habe ein kleines Mock-Up in meiner Antwort gemacht, ich kann es gerne ändern (oder deine eigene Antwort), um es sicher zu machen. EDIT: Ich verpasste Ihren Kommentar beim Posten, nicht ganz sicher, was Sie damit meinen? Meinst du einen @yaml_type Dekorator verwenden, um ein verstecktes Attribut in einem Objekt zu setzen? Was hindert jemanden daran, das gleiche für seine Klasse zu tun und uns zu verkörpern? –

0

Mit dem Hinzufügen auf Anthon's answer habe ich angefangen, A.from_yaml zu modifizieren, um sicherer zu sein, obwohl ich nicht alle Fälle für _check_registered() durchgegangen bin. Die Idee ist, alle Typen zu laden, die das Laden von Instanzen zulassen und alle anderen Typen verhindern.Betrachten Sie diese ein WIP:

import sys 
from ruamel.yaml import YAML, yaml_object 
from ruamel.yaml.compat import StringIO 
from ruamel.yaml.scalarstring import DoubleQuotedScalarString 


Y = YAML(typ="safe", pure=True) 

# ============== 

@yaml_object(Y) 
class A(object): 
    """Object I want to serialize""" 
    yaml_tag = "!Aclass" 
    def __init__(self, type): 
     self.type = type #.__class__.__name__ 

    @classmethod 
    def to_yaml(cls, representer, node): 
     return representer.represent_scalar(
      cls.yaml_tag, u'{}'.format(node.type.__name__) 
     ) 

    @classmethod 
    def from_yaml(cls, constructor, node): 
     if '.' in node.value: # in some other module 
      m, n = node.value.rsplit('.', 1) 
      t = getattr(sys.modules[m], n) 
     else: 
      t = globals()[node.value] 
     cls._check_registered(t,constructor, node) 
     return cls(t) 

    @classmethod 
    def _check_registered(cls, t, constructor, node): 
     # Check if type "t" is registered in "constr" 
     # Note: only a very basic check, 
     # and ideally should be made more secure 

     if hasattr(t,"yaml_tag"): 
      if t.yaml_tag in constructor.yaml_constructors: 

       return 
      raise Exception("Error: Tag not registered!") 
     else: 
      # 
      raise Exception("Error: No attribute 'yaml_tag'!") 
     pass 

    pass 

class T1(object): 
    """This will be referenced.""" 
    yaml_tag = u"!T1" 
    pass 


@yaml_object(Y) 
class T2(object): 
    """Another referenced object""" 
    yaml_tag = u"!T2" 

    def __init__(self): 
     print("Initializing...") 
     pass 
    pass 

class T2_bad(object): 
    """Malicious class impersonating T2""" 
    # Note: It's not registered 
    yaml_tag = u"!T2" 

    def __init__(self): 
     print("Evil code here!") 
     pass 

    pass 


class T3(object): 
    """Yet another try""" 
    yaml_tag = u"!T3" 
    pass 
Y.register_class(T3) 



for t in T1, T2, T2_bad, T3, DoubleQuotedScalarString: 
    try: 
     print('----------------------') 
     x = StringIO() 
     s = A(t) 
     print('s', s.type) 
     Y.dump(s, x) 
     print(x.getvalue()) 
     d = Y.load(x.getvalue()) 
     print('d', d.type) 
     d.type() 
    except Exception as e: 
     print(e) 
     continue 
    pass 

Das gibt:

---------------------- 
s <class '__main__.T1'> 
!Aclass T1 
... 

Error: Tag not registered! 
---------------------- 
s <class '__main__.T2'> 
!Aclass T2 
... 

d <class '__main__.T2'> 
Initializing... 
<__main__.T2 object at 0x0000015B8EC82F60> 
---------------------- 
s <class '__main__.T2_bad'> 
!Aclass T2_bad 
... 

d <class '__main__.T2_bad'> 
Evil code here! 
<__main__.T2_bad object at 0x0000015B8EC82EF0> 
---------------------- 
s <class '__main__.T3'> 
!Aclass T3 
... 

d <class '__main__.T3'> 
<__main__.T3 object at 0x0000015B8EC82E10> 
---------------------- 
s <class 'ruamel.yaml.scalarstring.DoubleQuotedScalarString'> 
!Aclass DoubleQuotedScalarString 
... 

Error: No attribute 'yaml_tag'! 

Wie man sehen kann, ist es noch nicht sicher ist („Schlechter Code“ ausgeführt wurde), auch nicht erlauben Typen ohne yaml_tag definiert. Fühlen Sie sich frei zu ändern, um dies zu beheben.

Verwandte Themen