マシュマロライブラリを使用したPythonでの厳密なYAMLデシリアライズ

初期タスク



  • .yamlファイルから重要な設定を読み取る必要があります。
  • 構成構造は、データクラスを使用して記述されます。
  • デシリアライズ中に型チェックを実行する必要があり、データが無効な場合は例外がスローされます。


つまり、簡単に言うと、次の形式の関数が必要です。







def strict_load_yaml(yaml: str, loaded_type: Type[Any]):
    """
    Here is some magic
    """
    pass
      
      





そして、この関数は次のように使用されます。







@dataclass
class MyConfig:
    """
    Here is object tree
    """
    pass

try:
    config = strict_load_yamp(open("config.yaml", "w").read(), MyConfig)
except Exception:
    logging.exception("Config is invalid")
      
      





構成クラス



ファイルconfig.py



は次のようになります。







from dataclasses import dataclass
from enum import Enum
from typing import Optional

class Color(Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

@dataclass
class BattleStationConfig:
    @dataclass
    class Processor:
        core_count: int
        manufacturer: str

    processor: Processor
    memory_gb: int
    led_color: Optional[Color] = None

      
      





動作しないオプション



, ? . yaml- ?







PyYaml load



:







from pprint import pprint

from yaml import load, SafeLoader

yaml = """
processor:
  core_count: 8
  manufacturer: Intel
memory_gb: 8
led_color: red
"""

loaded = load(yaml, Loader=SafeLoader)
pprint(loaded)

      
      





:







{'led_color': 'red',
 'memory_gb': 8,
 'processor': {'core_count': 8, 'manufacturer': 'Intel'}}
      
      





Yaml , . , **args



:







parsed_config = BattleStationConfig(**loaded)
pprint(parsed_config)
      
      





:







BattleStationConfig(processor={'core_count': 8, 'manufacturer': 'Intel'}, memory_gb=8, led_color='red')
      
      





! ! … -. processor ? .







Python Processor



. stackowerflow.







, yaml-



stackowerflow PyYaml , yaml- . YAMLObject



, config_with_tag.py



:







from dataclasses import dataclass
from enum import Enum
from typing import Optional

from yaml import YAMLObject, SafeLoader

class Color(Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

@dataclass
class BattleStationConfig(YAMLObject):
    yaml_tag = "!BattleStationConfig"
    yaml_loader = SafeLoader

    @dataclass
    class Processor(YAMLObject):
        yaml_tag = "!Processor"
        yaml_loader = SafeLoader

        core_count: int
        manufacturer: str

    processor: Processor
    memory_gb: int
    led_color: Optional[Color] = None
      
      





:







from pprint import pprint

from yaml import load, SafeLoader

from config_with_tag import BattleStationConfig

yaml = """
--- !BattleStationConfig
processor: !Processor
  core_count: 8
  manufacturer: Intel
memory_gb: 8
led_color: red
"""

a = BattleStationConfig

loaded = load(yaml, Loader=SafeLoader)
pprint(loaded)
      
      





?







BattleStationConfig(processor=BattleStationConfig.Processor(core_count=8, manufacturer='Intel'), memory_gb=8, led_color='red')
      
      





. yaml- . , Color



- . YAMLObject



? ? , .







class Color(Enum, YAMLObject):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"
      
      





:







TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
      
      





. yaml-, .







marshmallow



stackowerflow marshmallow , JSON-. , , , yaml JSON. class_schema



, -:







from pprint import pprint

from yaml import load, SafeLoader
from marshmallow_dataclass import class_schema

from config import BattleStationConfig

yaml = """
processor:
  core_count: 8
  manufacturer: Intel
memory_gb: 8
led_color: red
"""

loaded = load(yaml, Loader=SafeLoader)
pprint(loaded)

BattleStationConfigSchema = class_schema(BattleStationConfig)

result = BattleStationConfigSchema().load(loaded)
pprint(result)

      
      





, , :







marshmallow.exceptions.ValidationError: {'led_color': ['Invalid enum member red']}
      
      





, marshmallow enum, . yaml- :







processor:
  core_count: 8
  manufacturer: Intel
memory_gb: 8
led_color: RED
      
      





, , :







BattleStationConfig(processor=BattleStationConfig.Processor(core_count=8, manufacturer='Intel'), memory_gb=8, led_color=<Color.RED: 'red'>)
      
      





, yaml-. marshmallow :







Setting by_value=True



. This will cause both dumping and loading to use the value of the enum.

, metadata



field



:







@dataclass
class BattleStationConfig:
    led_color: Optional[Color] = field(default=None, metadata={"by_value": True})
      
      





, "" , yaml-.









, :







def strict_load_yaml(yaml: str, loaded_type: Type[Any]):
    schema = class_schema(loaded_type)
    return schema().load(load(yaml, Loader=SafeLoader))
      
      





-, yaml.







ForwardRef



- ForwardRef ( ) marshmallow .







,







from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, ForwardRef

@dataclass
class BattleStationConfig:
    processor: ForwardRef("Processor")
    memory_gb: int
    led_color: Optional["Color"] = field(default=None, metadata={"by_value": True})

    @dataclass
    class Processor:
        core_count: int
        manufacturer: str

class Color(Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

      
      











marshmallow.exceptions.RegistryError: Class with name 'Processor' was not found. You may need to import the class.
      
      





また、クラスを上に移動するとProcessor



、マシュマロはColor



同様のエラーでクラス失います。したがって、可能であれば、マシュマロでクラスを解析する場合は、クラスでForwardRefを使用しないでください。







コード



すべてのコードはGitHubリポジトリで入手できます








All Articles