Tarefa inicial
- É necessário ler uma configuração não trivial do arquivo .yaml.
- A estrutura de configuração é descrita usando classes de dados.
- É necessário que as verificações de tipo sejam executadas durante a desserialização e uma exceção seja lançada se os dados forem inválidos.
Ou seja, para simplificar, você precisa de uma função da forma:
def strict_load_yaml(yaml: str, loaded_type: Type[Any]):
"""
Here is some magic
"""
pass
E esta função será usada assim:
@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")
Classes de configuração
O arquivo config.py
tem a seguinte aparência:
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
Opção que não funciona
O problema original é comum, não é? Portanto, a solução deve ser trivial. Basta importar a biblioteca padrão do yaml e pronto?
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 :
Settingby_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))
Esta função pode requerer configuração adicional para classes de dados, mas resolve o problema original e não requer tags no yaml.
Uma nota rápida sobre ForwardRef
Se você definir classes de dados com ForwardRef (string com nome de classe), o marshmallow ficará confuso e não será capaz de analisar esta classe.
Por exemplo, tal configuração
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"
resultará em um erro
marshmallow.exceptions.RegistryError: Class with name 'Processor' was not found. You may need to import the class.
E se você mover a classe para Processor
cima, o marshmallow perderá a classe Color
com um erro semelhante. Portanto, se possível, não use ForwardRef em suas classes se quiser analisá-las com marshmallow.
Código
Todo o código está disponível no repositório GitHub .