1
1
"""Class for recording and reporting deprecations
2
2
"""
3
+ from __future__ import annotations
3
4
4
5
import functools
5
6
import re
7
+ import typing as ty
6
8
import warnings
7
9
10
+ if ty .TYPE_CHECKING : # pragma: no cover
11
+ T = ty .TypeVar ('T' )
12
+ P = ty .ParamSpec ('P' )
13
+
8
14
_LEADING_WHITE = re .compile (r'^(\s*)' )
9
15
10
16
TESTSETUP = """
@@ -38,15 +44,20 @@ class ExpiredDeprecationError(RuntimeError):
38
44
pass
39
45
40
46
41
- def _ensure_cr (text ) :
47
+ def _ensure_cr (text : str ) -> str :
42
48
"""Remove trailing whitespace and add carriage return
43
49
44
50
Ensures that `text` always ends with a carriage return
45
51
"""
46
52
return text .rstrip () + '\n '
47
53
48
54
49
- def _add_dep_doc (old_doc , dep_doc , setup = '' , cleanup = '' ):
55
+ def _add_dep_doc (
56
+ old_doc : str ,
57
+ dep_doc : str ,
58
+ setup : str = '' ,
59
+ cleanup : str = '' ,
60
+ ) -> str :
50
61
"""Add deprecation message `dep_doc` to docstring in `old_doc`
51
62
52
63
Parameters
@@ -55,6 +66,10 @@ def _add_dep_doc(old_doc, dep_doc, setup='', cleanup=''):
55
66
Docstring from some object.
56
67
dep_doc : str
57
68
Deprecation warning to add to top of docstring, after initial line.
69
+ setup : str, optional
70
+ Doctest setup text
71
+ cleanup : str, optional
72
+ Doctest teardown text
58
73
59
74
Returns
60
75
-------
@@ -76,7 +91,9 @@ def _add_dep_doc(old_doc, dep_doc, setup='', cleanup=''):
76
91
if next_line >= len (old_lines ):
77
92
# nothing following first paragraph, just append message
78
93
return old_doc + '\n ' + dep_doc
79
- indent = _LEADING_WHITE .match (old_lines [next_line ]).group ()
94
+ leading_white = _LEADING_WHITE .match (old_lines [next_line ])
95
+ assert leading_white is not None # Type narrowing, since this always matches
96
+ indent = leading_white .group ()
80
97
setup_lines = [indent + L for L in setup .splitlines ()]
81
98
dep_lines = [indent + L for L in ['' ] + dep_doc .splitlines () + ['' ]]
82
99
cleanup_lines = [indent + L for L in cleanup .splitlines ()]
@@ -113,15 +130,15 @@ class Deprecator:
113
130
114
131
def __init__ (
115
132
self ,
116
- version_comparator ,
117
- warn_class = DeprecationWarning ,
118
- error_class = ExpiredDeprecationError ,
119
- ):
133
+ version_comparator : ty . Callable [[ str ], int ] ,
134
+ warn_class : type [ Warning ] = DeprecationWarning ,
135
+ error_class : type [ Exception ] = ExpiredDeprecationError ,
136
+ ) -> None :
120
137
self .version_comparator = version_comparator
121
138
self .warn_class = warn_class
122
139
self .error_class = error_class
123
140
124
- def is_bad_version (self , version_str ) :
141
+ def is_bad_version (self , version_str : str ) -> bool :
125
142
"""Return True if `version_str` is too high
126
143
127
144
Tests `version_str` with ``self.version_comparator``
@@ -139,7 +156,14 @@ def is_bad_version(self, version_str):
139
156
"""
140
157
return self .version_comparator (version_str ) == - 1
141
158
142
- def __call__ (self , message , since = '' , until = '' , warn_class = None , error_class = None ):
159
+ def __call__ (
160
+ self ,
161
+ message : str ,
162
+ since : str = '' ,
163
+ until : str = '' ,
164
+ warn_class : type [Warning ] | None = None ,
165
+ error_class : type [Exception ] | None = None ,
166
+ ) -> ty .Callable [[ty .Callable [P , T ]], ty .Callable [P , T ]]:
143
167
"""Return decorator function function for deprecation warning / error
144
168
145
169
Parameters
@@ -164,8 +188,8 @@ def __call__(self, message, since='', until='', warn_class=None, error_class=Non
164
188
deprecator : func
165
189
Function returning a decorator.
166
190
"""
167
- warn_class = warn_class or self .warn_class
168
- error_class = error_class or self .error_class
191
+ exception = error_class if error_class is not None else self .error_class
192
+ warning = warn_class if warn_class is not None else self .warn_class
169
193
messages = [message ]
170
194
if (since , until ) != ('' , '' ):
171
195
messages .append ('' )
@@ -174,19 +198,21 @@ def __call__(self, message, since='', until='', warn_class=None, error_class=Non
174
198
if until :
175
199
messages .append (
176
200
f"* { 'Raises' if self .is_bad_version (until ) else 'Will raise' } "
177
- f'{ error_class } as of version: { until } '
201
+ f'{ exception } as of version: { until } '
178
202
)
179
203
message = '\n ' .join (messages )
180
204
181
- def deprecator (func ) :
205
+ def deprecator (func : ty . Callable [ P , T ]) -> ty . Callable [ P , T ] :
182
206
@functools .wraps (func )
183
- def deprecated_func (* args , ** kwargs ) :
207
+ def deprecated_func (* args : P . args , ** kwargs : P . kwargs ) -> T :
184
208
if until and self .is_bad_version (until ):
185
- raise error_class (message )
186
- warnings .warn (message , warn_class , stacklevel = 2 )
209
+ raise exception (message )
210
+ warnings .warn (message , warning , stacklevel = 2 )
187
211
return func (* args , ** kwargs )
188
212
189
213
keep_doc = deprecated_func .__doc__
214
+ if keep_doc is None :
215
+ keep_doc = ''
190
216
setup = TESTSETUP
191
217
cleanup = TESTCLEANUP
192
218
# After expiration, remove all but the first paragraph.
0 commit comments