Coverage for configzen/formats/yaml.py: 57%
45 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-15 02:42 +0000
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-15 02:42 +0000
1"""`configzen.formats.yaml`: The YAML data format."""
3from __future__ import annotations 1abcdefgh
5from typing import TYPE_CHECKING, ClassVar, Literal 1abcdefgh
7from ruamel.yaml import YAML 1abcdefgh
8from ruamel.yaml.comments import CommentedMap 1abcdefgh
9from runtime_generics import runtime_generic 1abcdefgh
11from configzen.data import DataFormatOptions, TextDataFormat 1abcdefgh
13if TYPE_CHECKING: 1abcdefgh
14 from typing import IO
16 from typing_extensions import TypeAlias, Unpack
18 from configzen.data import Data
20__all__ = ( 1abcdefgh
21 "YAMLDataFormat",
22 "YAMLOptions",
23)
26YAMLVersion: TypeAlias = "list[int] | str | tuple[int, int]" 1abcdefgh
27YAMLTyp: TypeAlias = Literal["rt", "rtsc", "safe", "unsafe", "base"] 1abcdefgh
30class YAMLOptions(DataFormatOptions, total=False): 1abcdefgh
31 """
32 Prototype of the allowed options for the YAML data format.
34 For more information, see the documentation of the `ruamel.yaml.YAML` class.
35 """
37 typ: YAMLTyp | list[YAMLTyp] | None 1abcdefgh
38 pure: bool 1abcdefgh
39 plug_ins: list[str] 1abcdefgh
41 classes: list[type] 1abcdefgh
42 """List of classes to automatically call YAML.register_class() on."""
44 version: str | tuple[int | str, ...] 1abcdefgh
45 """The YAML version to use."""
47 indent: int 1abcdefgh
48 """Indentation width."""
50 block_seq_indent: int 1abcdefgh
51 """Indentation for nested block sequences.""" 1abcdefgh
54@runtime_generic 1abcdefgh
55class YAMLDataFormat(TextDataFormat[YAMLOptions]): 1abcdefgh
56 """The YAML data format."""
58 option_name: ClassVar[str] = "yaml" 1abcdefgh
60 # Subclass and override for global effect.
61 yaml: YAML = YAML() 1abcdefgh
63 default_extension: ClassVar[str] = "yml" 1abcdefgh
64 file_extensions: ClassVar[set[str]] = {"yaml"} 1abcdefgh
66 def configure(self, **options: Unpack[YAMLOptions]) -> None: 1abcdefgh
67 """For the documentation of the options, see the YAMLOptions class."""
68 yaml_classes = options.pop("classes", None) or []
70 old_yaml = self.yaml
71 yaml_version = options.pop("version", None) or old_yaml.version
72 yaml_indent = options.pop("indent", None) or old_yaml.old_indent
73 yaml_block_seq_indent = (
74 options.pop("block_seq_indent", None) or old_yaml.block_seq_indent
75 )
77 yaml = YAML(**options) # type: ignore[arg-type,misc]
78 yaml.version = yaml_version # type: ignore[assignment]
79 yaml.indent = yaml_indent
80 yaml.block_seq_indent = yaml_block_seq_indent
82 for cls in yaml_classes:
83 yaml.register_class(cls)
85 self.yaml = yaml
87 def load(self, stream: IO[str]) -> Data: 1abcdefgh
88 """
89 Load the data from a stream.
91 Return a mutable mapping representing the loaded data
92 which is mutation-sensitive (for round-trip processing).
94 Every configuration source transforms the input data into a stream
95 to be processed by the data format, because most data format libraries
96 operate on streams.
98 This method is called by the configuration model.
99 """
100 data = self.yaml.load(stream) or CommentedMap()
101 if not isinstance(data, dict):
102 msg = f"Expected a dict, but got {type(data).__name__}."
103 raise TypeError(msg)
104 return data
106 def dump(self, data: Data, stream: IO[str]) -> None: 1abcdefgh
107 """
108 Load the data from a stream.
110 Every configuration source transforms the input data into a stream
111 to be processed by the data format, because most data format libraries
112 operate on streams.
114 This method is called by the configuration model.
115 """
116 self.yaml.dump(data, stream)