Tarea inicial
- Es necesario leer una configuración no trivial del archivo .yaml.
- La estructura de configuración se describe mediante clases de datos.
- Es necesario que se realicen comprobaciones de tipo durante la deserialización y se lanza una excepción si los datos no son válidos.
Es decir, en pocas palabras, necesita una función de la forma:
def strict_load_yaml(yaml: str, loaded_type: Type[Any]):
"""
Here is some magic
"""
pass
Y esta función se usará así:
@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")
Clases de configuración
El archivo se config.py
ve así:
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
Opción que no funciona
El problema original es común, ¿no? Entonces la solución debería ser trivial. ¿Simplemente importa la biblioteca yaml estándar y listo?
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 función puede requerir una configuración adicional para las clases de datos, pero resuelve el problema original y no requiere etiquetas en yaml.
Una nota rápida sobre ForwardRef
Si define clases de datos con ForwardRef (cadena con el nombre de la clase), marshmallow se confundirá y no podrá analizar esta clase.
Por ejemplo, tal configuración
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á en un error
marshmallow.exceptions.RegistryError: Class with name 'Processor' was not found. You may need to import the class.
Y si mueve la clase Processor
más arriba, marshmallow perderá la clase Color
con un error similar. Entonces, si es posible, no use ForwardRef en sus clases si desea analizarlas con marshmallow.
Código
Todo el código está disponible en el repositorio de GitHub .