Coverage for python/lsst/daf/butler/_compat.py: 43%
68 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-27 09:44 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-27 09:44 +0000
1# This file is part of pipe_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://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 software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <https://www.gnu.org/licenses/>.
28"""Code to support backwards compatibility."""
30from __future__ import annotations
32__all__ = ["PYDANTIC_V2", "_BaseModelCompat"]
34import sys
35from collections.abc import Callable
36from typing import TYPE_CHECKING, Any, Literal
38from pydantic import BaseModel
39from pydantic.fields import FieldInfo
40from pydantic.version import VERSION as PYDANTIC_VERSION
42if sys.version_info >= (3, 11, 0): 42 ↛ 45line 42 didn't jump to line 45, because the condition on line 42 was never false
43 from typing import Self
44else:
45 from typing import TypeVar
47 Self = TypeVar("Self", bound="_BaseModelCompat") # type: ignore
50PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
52# This matches the pydantic v2 internal definition.
53IncEx = set[int] | set[str] | dict[int, Any] | dict[str, Any] | None
55if PYDANTIC_V2: 55 ↛ 57line 55 didn't jump to line 57, because the condition on line 55 was never true
57 class _BaseModelCompat(BaseModel):
58 """Methods from pydantic v1 that we want to emulate in v2.
60 Some of these methods are provided by v2 but issue deprecation
61 warnings. We need to decide whether we are also okay with deprecating
62 them or want to support them without the deprecation message.
63 """
65 def json(
66 self,
67 *,
68 include: IncEx = None, # type: ignore
69 exclude: IncEx = None, # type: ignore
70 by_alias: bool = False,
71 skip_defaults: bool | None = None,
72 exclude_unset: bool = False,
73 exclude_defaults: bool = False,
74 exclude_none: bool = False,
75 encoder: Callable[[Any], Any] | None = None,
76 models_as_dict: bool = True,
77 **dumps_kwargs: Any,
78 ) -> str:
79 if dumps_kwargs:
80 raise TypeError("dumps_kwargs no longer supported.")
81 if encoder is not None:
82 raise TypeError("json encoder is no longer supported.")
83 # Can catch warnings and call BaseModel.json() directly.
84 return self.model_dump_json(
85 include=include,
86 exclude=exclude,
87 by_alias=by_alias,
88 exclude_defaults=exclude_defaults,
89 exclude_none=exclude_none,
90 exclude_unset=exclude_unset,
91 )
93 @classmethod
94 def parse_obj(cls, obj: Any) -> Self:
95 # Catch warnings and call BaseModel.parse_obj directly?
96 return cls.model_validate(obj)
98 if TYPE_CHECKING and not PYDANTIC_V2:
99 # mypy sees the first definition of a class and ignores any
100 # redefinition. This means that if mypy is run with pydantic v1
101 # it will not see the classes defined in the else block below.
103 @classmethod
104 def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self:
105 return cls()
107 @classmethod
108 def model_validate(
109 cls,
110 obj: Any,
111 *,
112 strict: bool | None = None,
113 from_attributes: bool | None = None,
114 context: dict[str, Any] | None = None,
115 ) -> Self:
116 return cls()
118 def model_dump_json(
119 self,
120 *,
121 indent: int | None = None,
122 include: IncEx = None,
123 exclude: IncEx = None,
124 by_alias: bool = False,
125 exclude_unset: bool = False,
126 exclude_defaults: bool = False,
127 exclude_none: bool = False,
128 round_trip: bool = False,
129 warnings: bool = True,
130 ) -> str:
131 return ""
133 @property
134 def model_fields(self) -> dict[str, FieldInfo]: # type: ignore
135 return {}
137 @classmethod
138 def model_rebuild(
139 cls,
140 *,
141 force: bool = False,
142 raise_errors: bool = True,
143 _parent_namespace_depth: int = 2,
144 _types_namespace: dict[str, Any] | None = None,
145 ) -> bool | None:
146 return None
148 def model_dump(
149 self,
150 *,
151 mode: Literal["json", "python"] | str = "python",
152 include: IncEx = None,
153 exclude: IncEx = None,
154 by_alias: bool = False,
155 exclude_unset: bool = False,
156 exclude_defaults: bool = False,
157 exclude_none: bool = False,
158 round_trip: bool = False,
159 warnings: bool = True,
160 ) -> dict[str, Any]:
161 return {}
163 @classmethod
164 def model_validate_json(
165 cls,
166 json_data: str | bytes | bytearray,
167 *,
168 strict: bool | None = None,
169 context: dict[str, Any] | None = None,
170 ) -> Self:
171 return cls()
173else:
174 from astropy.utils.decorators import classproperty
176 class _BaseModelCompat(BaseModel): # type:ignore[no-redef]
177 """Methods from pydantic v2 that can be used in pydantic v1."""
179 @classmethod
180 def model_validate(
181 cls,
182 obj: Any,
183 *,
184 strict: bool | None = None,
185 from_attributes: bool | None = None,
186 context: dict[str, Any] | None = None,
187 ) -> Self:
188 return cls.parse_obj(obj)
190 def model_dump_json(
191 self,
192 *,
193 indent: int | None = None,
194 include: IncEx = None,
195 exclude: IncEx = None,
196 by_alias: bool = False,
197 exclude_unset: bool = False,
198 exclude_defaults: bool = False,
199 exclude_none: bool = False,
200 round_trip: bool = False,
201 warnings: bool = True,
202 ) -> str:
203 return self.json(
204 include=include, # type: ignore
205 exclude=exclude, # type: ignore
206 by_alias=by_alias,
207 exclude_unset=exclude_unset,
208 exclude_defaults=exclude_defaults,
209 exclude_none=exclude_none,
210 )
212 @classmethod # type: ignore
213 def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self:
214 # BaseModel.construct() is very close to what we previously
215 # implemented manually in each direct() method but does have one
216 # extra loop in it to fill in defaults and handle aliases.
217 return cls.construct(_fields_set=_fields_set, **values)
219 @classmethod
220 @classproperty
221 def model_fields(cls) -> dict[str, FieldInfo]: # type: ignore
222 return cls.__fields__ # type: ignore
224 @classmethod
225 def model_rebuild(
226 cls,
227 *,
228 force: bool = False,
229 raise_errors: bool = True,
230 _parent_namespace_depth: int = 2,
231 _types_namespace: dict[str, Any] | None = None,
232 ) -> bool | None:
233 return cls.update_forward_refs()
235 def model_dump(
236 self,
237 *,
238 mode: Literal["json", "python"] | str = "python",
239 include: IncEx = None,
240 exclude: IncEx = None,
241 by_alias: bool = False,
242 exclude_unset: bool = False,
243 exclude_defaults: bool = False,
244 exclude_none: bool = False,
245 round_trip: bool = False,
246 warnings: bool = True,
247 ) -> dict[str, Any]:
248 # Need to decide whether to warn if the mode parameter is given.
249 return self.dict(
250 include=include, # type: ignore
251 exclude=exclude, # type: ignore
252 by_alias=by_alias,
253 exclude_unset=exclude_unset,
254 exclude_defaults=exclude_defaults,
255 exclude_none=exclude_none,
256 )
258 @classmethod
259 def model_validate_json(
260 cls,
261 json_data: str | bytes | bytearray,
262 *,
263 strict: bool | None = None,
264 context: dict[str, Any] | None = None,
265 ) -> Self:
266 return cls.parse_raw(json_data)