2017-08-18 16 views
1

タイプリファレンスをメンバーとするクラスをYAML直列化するのに問題があります。私はruamel.yamlの安全なローダーを使用しています。YAML - 型である属性をシリアライズ

私はREPLプロンプトから以下のすべてを実行しました(複数のエラーを取得するため)。

初期化:故障の原因となる

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

コード:

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

この出力(トレースバックの最後の行):

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> 

私をすることができます任意の溶液(安全に)型を一意に保存する(私は型のオブジェクトを生成し、入ってくるオブジェクトが特定のタイプのものであることが理解されよう)。必要な型を生成する関数またはクラスは、直列化できないという同じ問題を抱えています。


P.S.私はバグを発見したこともあります。パーサーは何らかの理由で、同じ有効な議論が連載されようとしているかどうかに応じて動作が異なります。

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

出力:

>>> 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 {} 
>>> 

答えて

1

YAMLはオブジェクトをダンプする予定で、最終的にスカラーの文字列を書き込むことによってそうします。 T1はオブジェクトではなく(T2またはT3)、これが問題の原因です。それぞれのクラス参照をオブジェクトにしようとすることができますし、それらのタグを使用して、IMOは物事を複雑にするだけです。

最終的にそれはすべてあなたが同様に直接文字列表現をダンプし、それをリードバックするA()を適応させる可能性があるので、ファイルにクラスの文字列表現、すなわち、スカラ表現を得るに沸く:

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) 

---------------------- 
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'> 

をロード/ダンプする必要がA()上の他の属性がある場合、あなたは辞書(文字列で.type変換)し、ダンプ/リットルを作成する必要があります提供します

それはオード。

実際のバグは見つかりませんでしたが、エラーが発生した後に続行すると副作用が発生します。Yオブジェクト(およびそのコンポーネント)は未定義の状態のままです。エラーをキャッチした後にYAML()インスタンスを再利用しないでください。それはドキュメントではっきりしているはずです。したがって、try/exceptをforループで実行する場合はY = YAML(typ='safe', pure=True)try部分内に移動する必要があります。

+0

いいえ、それはうまくいくようです。私の中のT2。 _However_、私は与えられたクラスのオブジェクトを構築するので、Yはオブジェクトをロードするのが安全かどうかを(少なくともあなたの実装からは)知ることができないので、コードに悪質な型をロードしている可能性があります。この場合、クラスが登録されていなくても、T1は「ロードされました」。 1つの可能な修正は、見つかったクラスがYによって登録されているかどうか(つまり、constructor.yaml_constructors?)A.from_yaml()をチェックすることです。第2の部分に関しては、それは理にかなっている。 –

+0

あなたは安全については正しいと思っていますが、答えに取り組んでいる間にそれについて考えましたが、それを含めるのを忘れました。私はすべての関連する型をモジュール(またはサブディレクトリ内の複数のモジュール)に入れ、そこからインポートします。次に、 'A'の' from_yaml() 'の' node.value'の文字列をチェックすることができます。テストする(一意の)属性を設定する独自の '@ yaml_type'を作成することもできます(ダンプやロード時に)。 ruamel.yamlのオブジェクトの登録を再利用する必要はありません(本質的には別のものです)。 – Anthon

+0

私は私の答えで小さなモックアップを作った、それを安全にするためにそれを(または自分の答え)を自由に変更してください。編集:私は投稿している間あなたのコメントを逃した、あなたはそれが何を意味するかはっきりしない? @yaml_typeデコレータを使ってオブジェクトの隠し属性を設定することを意味しますか?誰かが自分のクラスに同じことをして私たちを偽装するのを妨げるのは何ですか? –

0

Anthon's answerに追加すると、_check_registered()のすべてのケースを調べていないのに、A.from_yamlをより安全に変更し始めました。アイデアは、Yがインスタンスのロードを許可するすべてのタイプをロードし、他のすべてのタイプを禁止することです。

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 

この返します:

---------------------- 
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'! 

を見ることができるように、それはまだ(「バイオハザードコードが」実行された)安全ではないですが、またそれが定義されてyaml_tagなしタイプを認めていません。このWIPを考えてみましょう。これを修正するために自由に変更してください。

関連する問題