test_annotations.py
1 import re 2 from dataclasses import dataclass, fields 3 4 import pytest 5 6 from mlflow.utils.annotations import _get_min_indent_of_docstring, deprecated, keyword_only 7 8 # Suppress expected deprecation warnings from test fixtures 9 pytestmark = pytest.mark.filterwarnings("ignore:.*is deprecated.*:FutureWarning") 10 11 12 class MyClass: 13 @deprecated() 14 def method(self): 15 """ 16 Returns 0 17 """ 18 return 0 19 20 21 @deprecated() 22 def function(): 23 """ 24 Returns 1 25 """ 26 return 1 27 28 29 @keyword_only 30 @deprecated(since="0.0.0") 31 def deprecated_and_keyword_only_first(x): 32 """Description 33 34 Args: 35 x: x 36 37 Returns: 38 y 39 """ 40 return 1 41 42 43 @deprecated(since="0.0.0") 44 @keyword_only 45 def deprecated_and_keyword_only_second(x): 46 """ 47 Description 48 49 Args: 50 x: x 51 52 Returns: 53 y 54 """ 55 return 1 56 57 58 @deprecated() 59 class DeprecatedClass: 60 """ 61 A deprecated class. 62 """ 63 64 def __init__(self): 65 pass 66 67 def greet(self): 68 """ 69 Greets the user. 70 """ 71 return "Hello" 72 73 74 @deprecated(since="1.0.0") 75 @dataclass 76 class DeprecatedDataClass: 77 """ 78 A deprecated dataclass. 79 """ 80 81 x: int 82 y: int 83 84 def add(self): 85 return self.x + self.y 86 87 88 @deprecated(since="1.0.0") 89 @dataclass 90 class AnotherDeprecatedDataClass: 91 a: int 92 b: int 93 94 def add(self): 95 return self.a + self.b 96 97 98 @deprecated() 99 @dataclass 100 class AnotherDeprecatedDataClassOrder: 101 """ 102 A deprecated dataclass with decorators in different order. 103 """ 104 105 m: int 106 n: int 107 108 109 def test_deprecated_method(): 110 msg = "``tests.utils.test_annotations.MyClass.method`` is deprecated" 111 with pytest.warns(FutureWarning, match=re.escape(msg)): 112 assert MyClass().method() == 0 113 docstring = MyClass.method.__doc__ 114 assert docstring is not None 115 assert msg in docstring 116 117 118 def test_deprecated_function(): 119 msg = "``tests.utils.test_annotations.function`` is deprecated" 120 with pytest.warns(FutureWarning, match=re.escape(msg)): 121 assert function() == 1 122 docstring = function.__doc__ 123 assert docstring is not None 124 assert msg in docstring 125 126 127 def test_empty_docstring(): 128 docstring = "" 129 expected_indent = "" 130 assert _get_min_indent_of_docstring(docstring) == expected_indent 131 132 133 def test_single_line_docstring(): 134 docstring = """Single line with indent.""" 135 expected_indent = "" 136 assert _get_min_indent_of_docstring(docstring) == expected_indent 137 138 139 def test_multi_line_docstring_first_line(): 140 first_line_docstring = """Description 141 142 Args: 143 x: x 144 145 Returns: 146 y 147 """ 148 expected_indent = " " 149 assert _get_min_indent_of_docstring(first_line_docstring) == expected_indent 150 151 152 def test_multi_line_docstring_second_line(): 153 second_line_docstring = """ 154 Description 155 156 Args: 157 x: x 158 159 Returns: 160 y 161 """ 162 expected_indent = " " 163 assert _get_min_indent_of_docstring(second_line_docstring) == expected_indent 164 165 166 def test_deprecated_and_keyword_first(): 167 docstring = deprecated_and_keyword_only_first.__doc__ 168 assert docstring is not None 169 assert docstring.rstrip() == ( 170 """ .. note:: This method requires all argument be specified by keyword. 171 .. Warning:: ``tests.utils.test_annotations.deprecated_and_keyword_only_first`` is deprecated since 0.0.0. This method will be removed in a future release. 172 Description 173 174 Args: 175 x: x 176 177 Returns: 178 y""" # noqa: E501 179 ) 180 181 182 def test_deprecated_and_keyword_second(): 183 docstring = deprecated_and_keyword_only_second.__doc__ 184 assert docstring is not None 185 assert docstring.rstrip() == ( 186 """ .. Warning:: ``tests.utils.test_annotations.deprecated_and_keyword_only_second`` is deprecated since 0.0.0. This method will be removed in a future release. 187 .. note:: This method requires all argument be specified by keyword. 188 189 Description 190 191 Args: 192 x: x 193 194 Returns: 195 y""" # noqa: E501 196 ) 197 198 199 def test_deprecated_class(): 200 msg = "``tests.utils.test_annotations.DeprecatedClass`` is deprecated" 201 with pytest.warns(FutureWarning, match=re.escape(msg)): 202 DeprecatedClass() 203 docstring = DeprecatedClass.__doc__ 204 assert docstring is not None 205 assert msg in docstring 206 207 208 def test_deprecated_class_method(): 209 msg = "``tests.utils.test_annotations.DeprecatedClass`` is deprecated" 210 with pytest.warns(FutureWarning, match=re.escape(msg)): 211 instance = DeprecatedClass() 212 assert instance.greet() == "Hello" 213 docstring = DeprecatedClass.__doc__ 214 assert docstring is not None 215 assert msg in docstring 216 217 218 def test_deprecated_dataclass(): 219 msg = "``tests.utils.test_annotations.DeprecatedDataClass`` is deprecated since 1.0.0" 220 with pytest.warns(FutureWarning, match=re.escape(msg)): 221 DeprecatedDataClass(x=10, y=20) 222 docstring = DeprecatedDataClass.__doc__ 223 assert docstring is not None 224 assert msg in docstring 225 226 227 def test_deprecated_dataclass_fields(): 228 msg = "``tests.utils.test_annotations.DeprecatedDataClass`` is deprecated since 1.0.0" 229 with pytest.warns(FutureWarning, match=re.escape(msg)): 230 instance = DeprecatedDataClass(x=5, y=15) 231 assert instance.x == 5 232 assert instance.y == 15 233 docstring = DeprecatedDataClass.__doc__ 234 assert docstring is not None 235 assert msg in docstring 236 237 238 def test_deprecated_dataclass_method(): 239 msg = "``tests.utils.test_annotations.AnotherDeprecatedDataClass`` is deprecated since 1.0.0" 240 with pytest.warns(FutureWarning, match=re.escape(msg)): 241 instance = AnotherDeprecatedDataClass(a=3, b=4) 242 assert instance.add() == 7 243 docstring = AnotherDeprecatedDataClass.__doc__ 244 assert docstring is not None 245 assert msg in docstring 246 247 248 def test_deprecated_dataclass_different_order(): 249 msg = "``tests.utils.test_annotations.AnotherDeprecatedDataClassOrder`` is deprecated" 250 with pytest.warns(FutureWarning, match=re.escape(msg)): 251 AnotherDeprecatedDataClassOrder(m=7, n=8) 252 docstring = AnotherDeprecatedDataClassOrder.__doc__ 253 assert docstring is not None 254 assert msg in docstring 255 256 257 def test_deprecated_dataclass_dunder_methods(): 258 instance = DeprecatedDataClass(x=1, y=2) 259 260 assert instance.x == 1 261 assert instance.y == 2 262 263 expected_repr = "DeprecatedDataClass(x=1, y=2)" 264 assert repr(instance) == expected_repr 265 266 instance2 = DeprecatedDataClass(x=1, y=2) 267 instance3 = DeprecatedDataClass(x=2, y=3) 268 assert instance == instance2 269 assert instance != instance3 270 271 272 def test_deprecated_dataclass_preserves_fields(): 273 instance = DeprecatedDataClass(x=100, y=200) 274 field_names = {f.name for f in fields(DeprecatedDataClass)} 275 assert field_names == {"x", "y"} 276 assert instance.x == 100 277 assert instance.y == 200 278 279 280 def test_deprecated_dataclass_preserves_methods(): 281 instance = DeprecatedDataClass(x=10, y=20) 282 assert instance.add() == 30 283 284 285 def test_deprecated_dataclass_preserves_class_attributes(): 286 assert DeprecatedDataClass.__module__ == "tests.utils.test_annotations" 287 assert DeprecatedDataClass.__qualname__ == "DeprecatedDataClass" 288 289 290 def test_deprecated_dataclass_dunder_methods_not_mutated(): 291 instance = DeprecatedDataClass(x=5, y=10) 292 assert instance.x == 5 293 assert instance.y == 10 294 295 expected_repr = "DeprecatedDataClass(x=5, y=10)" 296 assert repr(instance) == expected_repr 297 298 same_instance = DeprecatedDataClass(x=5, y=10) 299 different_instance = DeprecatedDataClass(x=1, y=2) 300 assert instance == same_instance 301 assert instance != different_instance 302 303 assert instance.add() == 15 304 305 allowed_attrs = {"x", "y", "add"} 306 attrs = {attr for attr in dir(instance) if not attr.startswith("__")} 307 assert attrs == allowed_attrs 308 309 310 def test_deprecated_dataclass_special_methods_integrity(): 311 instance = DeprecatedDataClass(x=42, y=84) 312 313 assert instance.x == 42 314 assert instance.y == 84 315 316 expected_repr = "DeprecatedDataClass(x=42, y=84)" 317 assert repr(instance) == expected_repr 318 319 same_instance = DeprecatedDataClass(x=42, y=84) 320 different_instance = DeprecatedDataClass(x=1, y=2) 321 assert instance == same_instance 322 assert instance != different_instance 323 324 assert instance.add() == 126 325 326 allowed_attrs = {"x", "y", "add"} 327 attrs = {attr for attr in dir(instance) if not attr.startswith("__")} 328 assert attrs == allowed_attrs