Coverage for python/lsst/daf/butler/formatters/yaml.py: 27%
53 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-06 12:40 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-06 12:40 -0800
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = ("YamlFormatter",)
26import builtins
27import dataclasses
28from typing import TYPE_CHECKING, Any, Optional, Type
30import yaml
32from .file import FileFormatter
34if TYPE_CHECKING: 34 ↛ 35line 34 didn't jump to line 35, because the condition on line 34 was never true
35 from lsst.daf.butler import StorageClass
38class YamlFormatter(FileFormatter):
39 """Formatter implementation for YAML files."""
41 extension = ".yaml"
43 unsupportedParameters = None
44 """This formatter does not support any parameters"""
46 supportedWriteParameters = frozenset({"unsafe_dump"})
47 """Allow the normal yaml.dump to be used to write the YAML. Use this
48 if you know that your class has registered representers."""
50 def _readFile(self, path: str, pytype: Type[Any] = None) -> Any:
51 """Read a file from the path in YAML format.
53 Parameters
54 ----------
55 path : `str`
56 Path to use to open YAML format file.
57 pytype : `class`, optional
58 Not used by this implementation.
60 Returns
61 -------
62 data : `object`
63 Either data as Python object read from YAML file, or None
64 if the file could not be opened.
66 Notes
67 -----
68 The `~yaml.SafeLoader` is used when parsing the YAML file.
69 """
70 try:
71 with open(path, "rb") as fd:
72 data = self._fromBytes(fd.read(), pytype)
73 except FileNotFoundError:
74 data = None
76 return data
78 def _fromBytes(self, serializedDataset: bytes, pytype: Optional[Type[Any]] = None) -> Any:
79 """Read the bytes object as a python object.
81 Parameters
82 ----------
83 serializedDataset : `bytes`
84 Bytes object to unserialize.
85 pytype : `class`, optional
86 Not used by this implementation.
88 Returns
89 -------
90 inMemoryDataset : `object`
91 The requested data as an object, or None if the string could
92 not be read.
94 Notes
95 -----
96 The `~yaml.SafeLoader` is used when parsing the YAML.
97 """
98 data = yaml.safe_load(serializedDataset)
100 try:
101 data = data.exportAsDict()
102 except AttributeError:
103 pass
104 return data
106 def _writeFile(self, inMemoryDataset: Any) -> None:
107 """Write the in memory dataset to file on disk.
109 Will look for `_asdict()` method to aid YAML serialization, following
110 the approach of the simplejson module. The `dict` will be passed
111 to the relevant constructor on read.
113 Parameters
114 ----------
115 inMemoryDataset : `object`
116 Object to serialize.
118 Raises
119 ------
120 Exception
121 The file could not be written.
123 Notes
124 -----
125 The `~yaml.SafeDumper` is used when generating the YAML serialization.
126 This will fail for data structures that have complex python classes
127 without a registered YAML representer.
128 """
129 self.fileDescriptor.location.uri.write(self._toBytes(inMemoryDataset))
131 def _toBytes(self, inMemoryDataset: Any) -> bytes:
132 """Write the in memory dataset to a bytestring.
134 Will look for `_asdict()` method to aid YAML serialization, following
135 the approach of the simplejson module. The `dict` will be passed
136 to the relevant constructor on read.
138 Parameters
139 ----------
140 inMemoryDataset : `object`
141 Object to serialize
143 Returns
144 -------
145 serializedDataset : `bytes`
146 YAML string encoded to bytes.
148 Raises
149 ------
150 Exception
151 The object could not be serialized.
153 Notes
154 -----
155 The `~yaml.SafeDumper` is used when generating the YAML serialization.
156 This will fail for data structures that have complex python classes
157 without a registered YAML representer.
158 """
159 if dataclasses.is_dataclass(inMemoryDataset):
160 inMemoryDataset = dataclasses.asdict(inMemoryDataset)
161 elif hasattr(inMemoryDataset, "_asdict"):
162 inMemoryDataset = inMemoryDataset._asdict()
163 unsafe_dump = self.writeParameters.get("unsafe_dump", False)
164 if unsafe_dump:
165 serialized = yaml.dump(inMemoryDataset)
166 else:
167 serialized = yaml.safe_dump(inMemoryDataset)
168 return serialized.encode()
170 def _coerceType(
171 self, inMemoryDataset: Any, writeStorageClass: StorageClass, readStorageClass: StorageClass
172 ) -> Any:
173 """Coerce the supplied inMemoryDataset to the correct python type.
175 Parameters
176 ----------
177 inMemoryDataset : `object`
178 Object to coerce to expected type.
179 writeStorageClass : `StorageClass`
180 Storage class used to serialize this data.
181 readStorageClass : `StorageClass`
182 Storage class requested as the outcome.
184 Returns
185 -------
186 inMemoryDataset : `object`
187 Object of expected type ``readStorageClass.pytype``.
188 """
189 if inMemoryDataset is not None and not hasattr(builtins, readStorageClass.pytype.__name__):
190 if readStorageClass.isComposite():
191 inMemoryDataset = readStorageClass.delegate().assemble(
192 inMemoryDataset, pytype=readStorageClass.pytype
193 )
194 return readStorageClass.coerce_type(inMemoryDataset)
195 elif not isinstance(inMemoryDataset, readStorageClass.pytype):
196 if not isinstance(inMemoryDataset, writeStorageClass.pytype):
197 # This does not look like the written type or the required
198 # type. Try to cast it to the written type and then coerce
199 # to requested type.
200 if dataclasses.is_dataclass(writeStorageClass.pytype):
201 # dataclasses accept key/value parameters.
202 inMemoryDataset = writeStorageClass.pytype(**inMemoryDataset)
203 else:
204 # Hope that we can pass the arguments in directly.
205 inMemoryDataset = writeStorageClass.pytype(inMemoryDataset)
206 # Coerce to the read storage class if necessary.
207 inMemoryDataset = readStorageClass.coerce_type(inMemoryDataset)
208 return inMemoryDataset