Coverage for tests/test_deprecated.py: 100%

85 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-09 01:48 -0700

1# This file is part of utils. 

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 

22import copy 

23import unittest 

24import warnings 

25 

26import lsst.utils 

27import lsst.utils.tests 

28from lsst.utils.deprecated import DeprecatedDict 

29 

30 

31class DeprecatedDictTestCase(lsst.utils.tests.TestCase): 

32 """Test `~lsst.utils.deprecated.DeprecatedDict`.""" 

33 

34 def makeDict(self, **kwargs): 

35 # A DeprecatedDict with one deprecated key ("old") and one live key 

36 # ("new"). Construction with a non-empty `deprecations` never warns. 

37 return DeprecatedDict( 

38 {"old": 1, "new": 2}, 

39 deprecations={"old": "Use `.new` instead."}, 

40 version="v30.0", 

41 **kwargs, 

42 ) 

43 

44 def testIsADict(self): 

45 d = self.makeDict() 

46 self.assertIsInstance(d, dict) 

47 self.assertEqual(d["new"], 2) # Live key never warns. 

48 

49 def testNoDeprecatedKeysWarns(self): 

50 with self.assertWarns(UserWarning): 

51 DeprecatedDict({"a": 1}, deprecations={}) 

52 

53 def testGetItemWarnsOnce(self): 

54 d = self.makeDict() 

55 with self.assertWarns(FutureWarning) as cm: 

56 self.assertEqual(d["old"], 1) 

57 message = str(cm.warning) 

58 self.assertIn("v30.0", message) 

59 self.assertIn("Use `.new` instead.", message) 

60 # A second access of the same key is silent. 

61 with warnings.catch_warnings(): 

62 warnings.simplefilter("error") 

63 self.assertEqual(d["old"], 1) 

64 

65 def testTargetedAccessorsWarn(self): 

66 cases = { 

67 "getitem": lambda d: d["old"], 

68 "get": lambda d: d.get("old"), 

69 "pop": lambda d: d.pop("old"), 

70 "setdefault": lambda d: d.setdefault("old", 9), 

71 "setitem": lambda d: d.__setitem__("old", 9), 

72 "delitem": lambda d: d.__delitem__("old"), 

73 } 

74 for name, op in cases.items(): 

75 with self.subTest(accessor=name): 

76 d = self.makeDict() 

77 with self.assertWarns(FutureWarning): 

78 op(d) 

79 

80 def testCustomCategory(self): 

81 d = self.makeDict(category=DeprecationWarning) 

82 with self.assertWarns(DeprecationWarning): 

83 _ = d["old"] 

84 

85 def testPerKeyReasonAndCategory(self): 

86 d = DeprecatedDict( 

87 {"a": 1, "b": 2}, 

88 deprecations={"a": "Use `.a` instead.", "b": "Use `.b` instead."}, 

89 deprecated_categories={"b": DeprecationWarning}, 

90 ) 

91 # "a" uses the default category and its own reason. 

92 with self.assertWarns(FutureWarning) as cm: 

93 _ = d["a"] 

94 self.assertIn("Use `.a` instead.", str(cm.warning)) 

95 # "b" overrides the category and carries its own reason. 

96 with self.assertWarns(DeprecationWarning) as cm: 

97 _ = d["b"] 

98 self.assertIn("Use `.b` instead.", str(cm.warning)) 

99 

100 def testLiveKeyNeverWarns(self): 

101 d = self.makeDict() 

102 with warnings.catch_warnings(): 

103 warnings.simplefilter("error") 

104 _ = d["new"] 

105 _ = d.get("new") 

106 d["new"] = 3 

107 

108 def testBulkOperationsAreSilent(self): 

109 d = self.makeDict() 

110 with warnings.catch_warnings(): 

111 warnings.simplefilter("error") 

112 self.assertEqual(dict(d), {"old": 1, "new": 2}) 

113 self.assertEqual({**d}, {"old": 1, "new": 2}) 

114 self.assertEqual(sorted(d.keys()), ["new", "old"]) 

115 self.assertEqual(sorted(d.items()), [("new", 2), ("old", 1)]) 

116 self.assertIn("old", d) 

117 other = {} 

118 other.update(d) 

119 self.assertEqual(other, {"old": 1, "new": 2}) 

120 

121 def testDeepcopyIsSilentAndIndependent(self): 

122 d = self.makeDict() 

123 with warnings.catch_warnings(): 

124 warnings.simplefilter("ignore") 

125 _ = d["old"] # Latch the original. 

126 with warnings.catch_warnings(): 

127 warnings.simplefilter("error") 

128 clone = copy.deepcopy(d) 

129 # The clone inherits the "already warned" latch, so it too is 

130 # silent for the key already warned on the original. 

131 _ = clone["old"] 

132 self.assertIsInstance(clone, DeprecatedDict) 

133 self.assertEqual(dict(clone), {"old": 1, "new": 2}) 

134 self.assertEqual(dict(clone), dict(d)) 

135 

136 

137class DeprecatedTestCase(lsst.utils.tests.TestCase): 

138 """Test depreaction.""" 

139 

140 def test_deprecate_pybind11(self): 

141 def old(x): 

142 """Docstring.""" 

143 return x + 1 

144 

145 # Use an unusual category 

146 old = lsst.utils.deprecate_pybind11( 

147 old, reason="For testing.", version="unknown", category=PendingDeprecationWarning 

148 ) 

149 with self.assertWarnsRegex( 

150 PendingDeprecationWarning, 

151 r"Call to deprecated function \(or staticmethod\) old\. \(For testing\.\) " 

152 "-- Deprecated since version unknown.$", 

153 ): 

154 # Check that the function still works 

155 self.assertEqual(old(3), 4) 

156 self.assertIn("Docstring", old.__doc__) 

157 self.assertIn("For testing.", old.__doc__) 

158 

159 

160if __name__ == "__main__": 

161 unittest.main()