Coverage for python/lsst/daf/butler/_compat.py: 43%

56 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-08-05 01:26 +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 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 <https://www.gnu.org/licenses/>. 

21 

22"""Code to support backwards compatibility.""" 

23 

24__all__ = ["PYDANTIC_V2", "_BaseModelCompat"] 

25 

26import sys 

27from collections.abc import Callable 

28from typing import TYPE_CHECKING, Any 

29 

30from pydantic import BaseModel 

31from pydantic.fields import FieldInfo 

32from pydantic.version import VERSION as PYDANTIC_VERSION 

33 

34if sys.version_info >= (3, 11, 0): 34 ↛ 37line 34 didn't jump to line 37, because the condition on line 34 was never false

35 from typing import Self 

36else: 

37 from typing import TypeVar 

38 

39 Self = TypeVar("Self", bound="_BaseModelCompat") # type: ignore 

40 

41 

42PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.") 

43 

44 

45if PYDANTIC_V2: 45 ↛ 47line 45 didn't jump to line 47, because the condition on line 45 was never true

46 

47 class _BaseModelCompat(BaseModel): 

48 """Methods from pydantic v1 that we want to emulate in v2. 

49 

50 Some of these methods are provided by v2 but issue deprecation 

51 warnings. We need to decide whether we are also okay with deprecating 

52 them or want to support them without the deprecation message. 

53 """ 

54 

55 def json( 

56 self, 

57 *, 

58 include: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, # type: ignore 

59 exclude: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, # type: ignore 

60 by_alias: bool = False, 

61 skip_defaults: bool | None = None, 

62 exclude_unset: bool = False, 

63 exclude_defaults: bool = False, 

64 exclude_none: bool = False, 

65 encoder: Callable[[Any], Any] | None = None, 

66 models_as_dict: bool = True, 

67 **dumps_kwargs: Any, 

68 ) -> str: 

69 if dumps_kwargs: 

70 raise TypeError("dumps_kwargs no longer supported.") 

71 if encoder is not None: 

72 raise TypeError("json encoder is no longer supported.") 

73 # Can catch warnings and call BaseModel.json() directly. 

74 return self.model_dump_json( 

75 include=include, 

76 exclude=exclude, 

77 by_alias=by_alias, 

78 exclude_defaults=exclude_defaults, 

79 exclude_none=exclude_none, 

80 exclude_unset=exclude_unset, 

81 ) 

82 

83 @classmethod 

84 def parse_obj(cls, obj: Any) -> Self: 

85 # Catch warnings and call BaseModel.parse_obj directly? 

86 return cls.model_validate(obj) 

87 

88 if TYPE_CHECKING and not PYDANTIC_V2: 

89 # mypy sees the first definition of a class and ignores any 

90 # redefinition. This means that if mypy is run with pydantic v1 

91 # it will not see the classes defined in the else block below. 

92 

93 @classmethod 

94 def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: 

95 return cls() 

96 

97 @classmethod 

98 def model_validate( 

99 cls, 

100 obj: Any, 

101 *, 

102 strict: bool | None = None, 

103 from_attributes: bool | None = None, 

104 context: dict[str, Any] | None = None, 

105 ) -> Self: 

106 return cls() 

107 

108 def model_dump_json( 

109 self, 

110 *, 

111 indent: int | None = None, 

112 include: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, 

113 exclude: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, 

114 by_alias: bool = False, 

115 exclude_unset: bool = False, 

116 exclude_defaults: bool = False, 

117 exclude_none: bool = False, 

118 round_trip: bool = False, 

119 warnings: bool = True, 

120 ) -> str: 

121 return "" 

122 

123 @property 

124 def model_fields(self) -> dict[str, FieldInfo]: # type: ignore 

125 return {} 

126 

127 @classmethod 

128 def model_rebuild( 

129 cls, 

130 *, 

131 force: bool = False, 

132 raise_errors: bool = True, 

133 _parent_namespace_depth: int = 2, 

134 _types_namespace: dict[str, Any] | None = None, 

135 ) -> bool | None: 

136 return None 

137 

138else: 

139 from astropy.utils.decorators import classproperty 

140 

141 class _BaseModelCompat(BaseModel): # type:ignore[no-redef] 

142 """Methods from pydantic v2 that can be used in pydantic v1.""" 

143 

144 @classmethod 

145 def model_validate( 

146 cls, 

147 obj: Any, 

148 *, 

149 strict: bool | None = None, 

150 from_attributes: bool | None = None, 

151 context: dict[str, Any] | None = None, 

152 ) -> Self: 

153 return cls.parse_obj(obj) 

154 

155 def model_dump_json( 

156 self, 

157 *, 

158 indent: int | None = None, 

159 include: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, 

160 exclude: set[int] | set[str] | dict[int, Any] | dict[str, Any] | None = None, 

161 by_alias: bool = False, 

162 exclude_unset: bool = False, 

163 exclude_defaults: bool = False, 

164 exclude_none: bool = False, 

165 round_trip: bool = False, 

166 warnings: bool = True, 

167 ) -> str: 

168 return self.json( 

169 include=include, # type: ignore 

170 exclude=exclude, # type: ignore 

171 by_alias=by_alias, 

172 exclude_unset=exclude_unset, 

173 exclude_defaults=exclude_defaults, 

174 exclude_none=exclude_none, 

175 ) 

176 

177 @classmethod # type: ignore 

178 def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: 

179 # BaseModel.construct() is very close to what we previously 

180 # implemented manually in each direct() method but does have one 

181 # extra loop in it to fill in defaults and handle aliases. 

182 return cls.construct(_fields_set=_fields_set, **values) 

183 

184 @classmethod 

185 @classproperty 

186 def model_fields(cls) -> dict[str, FieldInfo]: # type: ignore 

187 return cls.__fields__ # type: ignore 

188 

189 @classmethod 

190 def model_rebuild( 

191 cls, 

192 *, 

193 force: bool = False, 

194 raise_errors: bool = True, 

195 _parent_namespace_depth: int = 2, 

196 _types_namespace: dict[str, Any] | None = None, 

197 ) -> bool | None: 

198 return cls.update_forward_refs()