deprecated-parameters 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deprecated_parameters-0.1.0/LICENSE +21 -0
- deprecated_parameters-0.1.0/PKG-INFO +536 -0
- deprecated_parameters-0.1.0/README.md +493 -0
- deprecated_parameters-0.1.0/deprecated_parameters/__init__.py +14 -0
- deprecated_parameters-0.1.0/deprecated_parameters/_decorator.py +538 -0
- deprecated_parameters-0.1.0/deprecated_parameters/_mypy.py +280 -0
- deprecated_parameters-0.1.0/deprecated_parameters/_sphinx.py +63 -0
- deprecated_parameters-0.1.0/deprecated_parameters/_stubgen.py +217 -0
- deprecated_parameters-0.1.0/deprecated_parameters/py.typed +0 -0
- deprecated_parameters-0.1.0/deprecated_parameters.egg-info/PKG-INFO +536 -0
- deprecated_parameters-0.1.0/deprecated_parameters.egg-info/SOURCES.txt +21 -0
- deprecated_parameters-0.1.0/deprecated_parameters.egg-info/dependency_links.txt +1 -0
- deprecated_parameters-0.1.0/deprecated_parameters.egg-info/entry_points.txt +2 -0
- deprecated_parameters-0.1.0/deprecated_parameters.egg-info/requires.txt +19 -0
- deprecated_parameters-0.1.0/deprecated_parameters.egg-info/top_level.txt +2 -0
- deprecated_parameters-0.1.0/deprecated_parameters_tests/__init__.py +1 -0
- deprecated_parameters-0.1.0/deprecated_parameters_tests/__main__.py +22 -0
- deprecated_parameters-0.1.0/deprecated_parameters_tests/test_decorator.py +943 -0
- deprecated_parameters-0.1.0/deprecated_parameters_tests/test_mypy.py +612 -0
- deprecated_parameters-0.1.0/deprecated_parameters_tests/test_sphinx.py +188 -0
- deprecated_parameters-0.1.0/deprecated_parameters_tests/test_stubgen.py +244 -0
- deprecated_parameters-0.1.0/pyproject.toml +119 -0
- deprecated_parameters-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present, Mauricio Villegas <mauricio@omnius.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deprecated-parameters
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deprecation of parameters in function and method signatures.
|
|
5
|
+
Author-email: Mauricio Villegas <mauricio@omnius.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: GitHub, https://github.com/mauvilsa/deprecated-parameters
|
|
8
|
+
Project-URL: PyPI, https://pypi.org/project/deprecated-parameters
|
|
9
|
+
Project-URL: Changelog, https://github.com/mauvilsa/deprecated-parameters/blob/main/CHANGELOG.md
|
|
10
|
+
Platform: Any
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Classifier: Operating System :: MacOS
|
|
23
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: packaging>=22.0
|
|
28
|
+
Provides-Extra: mypy
|
|
29
|
+
Requires-Dist: mypy>=1.14.1; extra == "mypy"
|
|
30
|
+
Provides-Extra: doc
|
|
31
|
+
Requires-Dist: sphinx>=7.0.0; extra == "doc"
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: deprecated-parameters[doc,mypy]; extra == "test"
|
|
34
|
+
Requires-Dist: pytest>=6.2.5; extra == "test"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pre-commit>=2.19.0; extra == "dev"
|
|
38
|
+
Requires-Dist: tox>=3.25.0; extra == "dev"
|
|
39
|
+
Requires-Dist: build>=0.10.0; extra == "dev"
|
|
40
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: bump2version>=1.0.1; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/deprecated-parameters)
|
|
45
|
+
[](https://pypi.org/project/deprecated-parameters)
|
|
46
|
+
[](https://github.com/mauvilsa/deprecated-parameters/actions/workflows/tests.yaml)
|
|
47
|
+
|
|
48
|
+
# deprecated-parameters
|
|
49
|
+
|
|
50
|
+
Deprecation of parameters in function and method signatures, reported both at
|
|
51
|
+
runtime and by [mypy](https://mypy-lang.org).
|
|
52
|
+
|
|
53
|
+
Python has a standard way to deprecate an entire function, method or class,
|
|
54
|
+
`warnings.deprecated` from [PEP 702](https://peps.python.org/pep-0702/). It
|
|
55
|
+
deliberately does not cover *individual parameters*, and the PEP explicitly
|
|
56
|
+
rejected a `Deprecated[type, message]` modifier for that purpose. As a result,
|
|
57
|
+
every large project ends up writing its own decorator, for example
|
|
58
|
+
`pandas.util._decorators.deprecate_kwarg`,
|
|
59
|
+
`astropy.utils.decorators.deprecated_renamed_argument` and
|
|
60
|
+
`twisted.python.deprecate.deprecatedKeywordParameter`. They are all private, and
|
|
61
|
+
none of them are understood by a type checker.
|
|
62
|
+
|
|
63
|
+
This package provides a single reusable decorator for the same purpose, and a
|
|
64
|
+
mypy plugin so that misuse is reported statically, before the code runs.
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install deprecated-parameters
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
To also install mypy, so that deprecated parameters are reported statically:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install deprecated-parameters[mypy]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### Removing a parameter
|
|
81
|
+
|
|
82
|
+
Take the parameter out of the signature and declare it with `ParameterRemove`.
|
|
83
|
+
The function is then written the way it will stay, without the parameter and
|
|
84
|
+
without anything to handle it. Callers that still give it get a
|
|
85
|
+
`DeprecationWarning` instead of a `TypeError`, and the value they give is
|
|
86
|
+
dropped, which is what the warning tells them:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from deprecated_parameters import deprecated_parameters, ParameterRemove
|
|
90
|
+
|
|
91
|
+
@deprecated_parameters(
|
|
92
|
+
ParameterRemove(old_name="verbose", when="v2.0.0"),
|
|
93
|
+
)
|
|
94
|
+
def compute(data):
|
|
95
|
+
return data
|
|
96
|
+
|
|
97
|
+
compute([1], verbose=True)
|
|
98
|
+
# DeprecationWarning: Argument "verbose" for "compute" is deprecated, its value
|
|
99
|
+
# is ignored and it will no longer be accepted in v2.0.0
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Only the keyword form is recognized this way. For a parameter that callers could
|
|
103
|
+
also give positionally, add `old_index`, the position it had in the previous
|
|
104
|
+
signature:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# The previous signature was compute(data, workers, verbose).
|
|
108
|
+
@deprecated_parameters(
|
|
109
|
+
ParameterRemove(old_name="verbose", old_index=2, when="v2.0.0"),
|
|
110
|
+
)
|
|
111
|
+
def compute(data, workers=1):
|
|
112
|
+
return workers
|
|
113
|
+
|
|
114
|
+
compute([1], 4, True) # returns 4, the extra argument is dropped
|
|
115
|
+
compute([1], 4) # unchanged, no warning
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`old_index` is zero based and counts every positional parameter, so `self` is
|
|
119
|
+
index 0 for methods. A removed parameter has to have been the *last* positional
|
|
120
|
+
one, otherwise an argument given for it cannot be told apart from one given for
|
|
121
|
+
the parameter that took its place, and the decorator says so when it is applied:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
# The previous signature was compute(data, verbose, workers), so an argument at
|
|
125
|
+
# position 1 could be either verbose or workers.
|
|
126
|
+
@deprecated_parameters(
|
|
127
|
+
ParameterRemove(old_name="verbose", old_index=1),
|
|
128
|
+
)
|
|
129
|
+
def compute(data, workers=1):
|
|
130
|
+
return workers
|
|
131
|
+
# ValueError: The old_index values of the parameters of compute that are no longer
|
|
132
|
+
# positional must be [2], got [1]. ...
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
To keep receiving the value instead of dropping it, see
|
|
136
|
+
[transform=None](#transforms-and-the-signature) below.
|
|
137
|
+
|
|
138
|
+
### Renaming a parameter
|
|
139
|
+
|
|
140
|
+
Rename the parameter in the signature and declare the old name with
|
|
141
|
+
`ParameterRename`. Callers that still use the old name get a
|
|
142
|
+
`DeprecationWarning`, and the value is forwarded to the new name.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from deprecated_parameters import deprecated_parameters, ParameterRename
|
|
146
|
+
|
|
147
|
+
@deprecated_parameters(
|
|
148
|
+
ParameterRename(old_name="n_jobs", new_name="workers", when="v2.0.0"),
|
|
149
|
+
)
|
|
150
|
+
def compute(data, *, workers: int = 1):
|
|
151
|
+
return workers
|
|
152
|
+
|
|
153
|
+
compute([1], n_jobs=4) # returns 4
|
|
154
|
+
# DeprecationWarning: Argument "n_jobs" for "compute" is deprecated, it has been
|
|
155
|
+
# renamed to "workers" and "n_jobs" will no longer be accepted in v2.0.0
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Giving both the old and the new name in the same call raises a `ValueError`.
|
|
159
|
+
|
|
160
|
+
### Deprecating positional use of a parameter
|
|
161
|
+
|
|
162
|
+
To migrate a parameter to keyword-only, make it keyword-only in the signature and declare it with
|
|
163
|
+
`ParameterPositional`. Callers that still give it positionally get a `DeprecationWarning` instead of a
|
|
164
|
+
`TypeError`, and the value is moved to the keyword argument.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from deprecated_parameters import deprecated_parameters, ParameterPositional
|
|
168
|
+
|
|
169
|
+
@deprecated_parameters(
|
|
170
|
+
ParameterPositional(name="workers", old_index=1, when="v2.0.0"),
|
|
171
|
+
)
|
|
172
|
+
def compute(data, *, workers: int = 1):
|
|
173
|
+
return workers
|
|
174
|
+
|
|
175
|
+
compute([1], 4) # returns 4
|
|
176
|
+
# DeprecationWarning: Giving argument "workers" for "compute" positionally is deprecated,
|
|
177
|
+
# it must be given as a keyword argument in v2.0.0
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`old_index` is the position the parameter had in the previous signature, zero based and counting every
|
|
181
|
+
positional parameter, so `self` is index 0 for methods. It is given explicitly rather than taken from
|
|
182
|
+
the order of the deprecations, so that reordering them can not silently change which argument goes
|
|
183
|
+
where. Applying the decorator fails when the indexes do not match the signature, and the error states
|
|
184
|
+
which ones are expected:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
@deprecated_parameters(
|
|
188
|
+
ParameterPositional(name="flag", old_index=2),
|
|
189
|
+
ParameterPositional(name="workers", old_index=1),
|
|
190
|
+
)
|
|
191
|
+
def compute(data, *, workers: int = 1, flag: str = "x"):
|
|
192
|
+
return workers
|
|
193
|
+
|
|
194
|
+
compute([1], 4, "y") # workers is 4 and flag is "y", whatever order they were declared in
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
To only warn while the parameter is still accepted positionally, use `transform=None` and leave the
|
|
198
|
+
signature unchanged. `old_index` is then the position it currently has.
|
|
199
|
+
|
|
200
|
+
### Deprecating one accepted value of a parameter
|
|
201
|
+
|
|
202
|
+
`ParameterValueRemove` removes a single value from the set a parameter accepts, leaving the parameter
|
|
203
|
+
itself in place. Drop the value from the annotation, and callers that still pass it get a warning and
|
|
204
|
+
have it replaced with `new_value`.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from typing import Literal
|
|
208
|
+
from deprecated_parameters import deprecated_parameters, ParameterValueRemove
|
|
209
|
+
|
|
210
|
+
@deprecated_parameters(
|
|
211
|
+
ParameterValueRemove(name="method", old_value="linear", new_value="lstsq", when="v2.0.0"),
|
|
212
|
+
)
|
|
213
|
+
def solve(data, *, method: Literal["lstsq", "qr"] = "qr"):
|
|
214
|
+
return method
|
|
215
|
+
|
|
216
|
+
solve([1], method="linear") # returns "lstsq"
|
|
217
|
+
# DeprecationWarning: Value 'linear' for argument "method" of "solve" is deprecated and
|
|
218
|
+
# will not be supported in v2.0.0, use 'lstsq' instead
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This only ever looks at values the caller actually passes, whether by keyword or positionally. It is
|
|
222
|
+
not about defaults: omitting the argument never warns, regardless of what the default is.
|
|
223
|
+
|
|
224
|
+
For a value that is going away with nothing to replace it, simply do not give `new_value`. The
|
|
225
|
+
transform then has nothing to do, so the caller keeps receiving the deprecated value and only gets the
|
|
226
|
+
warning. `transform=None` is only needed to warn about a value that does have a replacement without
|
|
227
|
+
applying it.
|
|
228
|
+
|
|
229
|
+
### Several deprecations at once
|
|
230
|
+
|
|
231
|
+
The decorator accepts any number of deprecations, but can only be applied once
|
|
232
|
+
per callable.
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
@deprecated_parameters(
|
|
236
|
+
ParameterRemove(old_name="verbose"),
|
|
237
|
+
ParameterRename(old_name="n_jobs", new_name="workers"),
|
|
238
|
+
)
|
|
239
|
+
def compute(data, *, workers: int = 1):
|
|
240
|
+
return workers
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Transforms and the signature
|
|
244
|
+
|
|
245
|
+
The point of a transform is that the signature can already be written the way it
|
|
246
|
+
will stay once the deprecation period is over. `ParameterRemove` takes the
|
|
247
|
+
parameter out of it, `ParameterRename` renames it, `ParameterPositional` makes
|
|
248
|
+
it keyword-only and `ParameterValueRemove` drops a value from its annotation.
|
|
249
|
+
Calls that still use the old form are adapted to that signature, so they keep
|
|
250
|
+
working and only get a warning.
|
|
251
|
+
|
|
252
|
+
This is why the old form has to be gone from the signature. Declaring a
|
|
253
|
+
transform for a parameter that is still there raises a `ValueError` when the
|
|
254
|
+
decorator is applied.
|
|
255
|
+
|
|
256
|
+
`transform=None` is the opposite case: leave the signature as it is today and
|
|
257
|
+
only warn. Nothing about the call is changed, so the function receives exactly
|
|
258
|
+
what the caller passed, and the warning does not claim otherwise.
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
@deprecated_parameters(
|
|
262
|
+
ParameterRemove(old_name="verbose", transform=None),
|
|
263
|
+
)
|
|
264
|
+
def compute(data, verbose=False):
|
|
265
|
+
return verbose # the signature is unchanged, the call is only warned about
|
|
266
|
+
|
|
267
|
+
compute([1], verbose=True)
|
|
268
|
+
# DeprecationWarning: Argument "verbose" for "compute" is deprecated and will no
|
|
269
|
+
# longer be accepted in the future
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Since nothing is changed, the function has to be able to receive the argument.
|
|
273
|
+
Either the parameter is still in the signature, as above, or the signature has a
|
|
274
|
+
`**kwargs` for it to land in:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
@deprecated_parameters(
|
|
278
|
+
ParameterRemove(old_name="verbose", transform=None),
|
|
279
|
+
)
|
|
280
|
+
def compute(data, **kwargs):
|
|
281
|
+
return kwargs # {'verbose': True}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
When it has neither, every call giving the parameter would warn and then fail
|
|
285
|
+
with a `TypeError`, so the decorator refuses it:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
@deprecated_parameters(
|
|
289
|
+
ParameterRemove(old_name="verbose", transform=None),
|
|
290
|
+
)
|
|
291
|
+
def compute(data):
|
|
292
|
+
return data
|
|
293
|
+
# ValueError: Parameter 'verbose' is not in the signature of compute and there is
|
|
294
|
+
# no **kwargs to receive it, so with transform=None every call giving it would
|
|
295
|
+
# fail. ...
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### Removing a parameter from a function that has `**kwargs`
|
|
299
|
+
|
|
300
|
+
A `**kwargs` in the signature does not change what `ParameterRemove` does: with
|
|
301
|
+
its transform the argument is dropped, and it never reaches `**kwargs`. Passing
|
|
302
|
+
it through instead would be wrong more often than right, since `**kwargs` is
|
|
303
|
+
usually there to forward arguments somewhere else, where a parameter this
|
|
304
|
+
function has removed has no business turning up. It would also make the transform
|
|
305
|
+
a no-op, indistinguishable from `transform=None`.
|
|
306
|
+
|
|
307
|
+
So the choice is yours to state explicitly, and it is the same choice in every
|
|
308
|
+
signature:
|
|
309
|
+
|
|
310
|
+
- `transform="remove"`, the default, for *the value is gone, the function does
|
|
311
|
+
nothing with it any more*. The warning says that the value is ignored.
|
|
312
|
+
- `transform=None` for *the function still receives and handles it, this is only
|
|
313
|
+
an announcement*, with the parameter or a `**kwargs` there to receive it.
|
|
314
|
+
|
|
315
|
+
### Customizing the message
|
|
316
|
+
|
|
317
|
+
`when` is a free form string describing when the parameter stops being accepted,
|
|
318
|
+
`"the future"` by default. For full control, `message` accepts a printf style
|
|
319
|
+
template with the `func`, `old_name`, `when` and, for renames, `new_name` keys:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
@deprecated_parameters(
|
|
323
|
+
ParameterRemove(
|
|
324
|
+
old_name="verbose",
|
|
325
|
+
message='%(old_name)s is deprecated in %(func)s, use logging instead',
|
|
326
|
+
),
|
|
327
|
+
)
|
|
328
|
+
def compute(data):
|
|
329
|
+
return data
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`version` records the release in which the parameter was deprecated. It must be a valid
|
|
333
|
+
[PEP 440](https://peps.python.org/pep-0440/) version, is added to the default messages as
|
|
334
|
+
`deprecated since <version>`, and is available to custom messages as `%(version)s`:
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
@deprecated_parameters(
|
|
338
|
+
ParameterRemove(old_name="verbose", version="1.5.0", when="v2.0.0"),
|
|
339
|
+
)
|
|
340
|
+
def compute(data):
|
|
341
|
+
return data
|
|
342
|
+
|
|
343
|
+
compute([1], verbose=True)
|
|
344
|
+
# DeprecationWarning: Argument "verbose" for "compute" is deprecated since 1.5.0, its
|
|
345
|
+
# value is ignored and it will no longer be accepted in v2.0.0
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Choosing the warning category
|
|
349
|
+
|
|
350
|
+
`DeprecationWarning` is only shown by default in `__main__`, so it is invisible to the users of a
|
|
351
|
+
library. Projects such as pandas use `FutureWarning` for deprecations aimed at end users. The category
|
|
352
|
+
is set per deprecation:
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
@deprecated_parameters(
|
|
356
|
+
ParameterRemove(old_name="verbose", category=FutureWarning),
|
|
357
|
+
)
|
|
358
|
+
def compute(data):
|
|
359
|
+
return data
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Introspection
|
|
363
|
+
|
|
364
|
+
`get_deprecated_parameters` returns the deprecations of a callable, or `None` if
|
|
365
|
+
it has none:
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
from deprecated_parameters import get_deprecated_parameters
|
|
369
|
+
|
|
370
|
+
deprecations = get_deprecated_parameters(compute)
|
|
371
|
+
[x.old_name for x in deprecations.removed] # ['verbose']
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Static checking with mypy
|
|
375
|
+
|
|
376
|
+
Enable the plugin in your mypy configuration:
|
|
377
|
+
|
|
378
|
+
```ini
|
|
379
|
+
# mypy.ini
|
|
380
|
+
[mypy]
|
|
381
|
+
plugins = deprecated_parameters:mypy_plugin
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
or:
|
|
385
|
+
|
|
386
|
+
```toml
|
|
387
|
+
# pyproject.toml
|
|
388
|
+
[tool.mypy]
|
|
389
|
+
plugins = ["deprecated_parameters:mypy_plugin"]
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Calls that use a deprecated parameter are then reported under the
|
|
393
|
+
`deprecated-arg` error code, with the same message as at runtime:
|
|
394
|
+
|
|
395
|
+
```text
|
|
396
|
+
example.py:9: error: Argument "n_jobs" for "compute" is deprecated, it has been
|
|
397
|
+
renamed to "workers" and "n_jobs" will no longer be accepted in v2.0.0 [deprecated-arg]
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
The plugin widens the signature so that the deprecated form is not additionally
|
|
401
|
+
reported as an unexpected keyword argument or as too many positional arguments,
|
|
402
|
+
and a renamed parameter keeps the type of the parameter it was renamed to. To downgrade the errors to warnings,
|
|
403
|
+
disable the error code with `--disable-error-code deprecated-arg`.
|
|
404
|
+
|
|
405
|
+
Functions, methods and constructors are supported, in the module where they are
|
|
406
|
+
declared and in modules that import them. All four deprecation kinds are reported, with deprecated
|
|
407
|
+
values detected when given as a literal.
|
|
408
|
+
|
|
409
|
+
## Static checking with other type checkers
|
|
410
|
+
|
|
411
|
+
Pyright, ty and pyrefly have no plugin system, so the mypy plugin cannot serve them. What they do
|
|
412
|
+
support is [PEP 702](https://peps.python.org/pep-0702/), `warnings.deprecated` applied to an individual
|
|
413
|
+
`@overload`. The `deprecated-parameters-stubgen` command renders the declared deprecations in that
|
|
414
|
+
form:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
deprecated-parameters-stubgen mypackage.mymodule -o overloads.pyi
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
For each decorated callable it emits a deprecated overload taking the deprecated form as a *required*
|
|
421
|
+
argument, so that only calls using it match, followed by the real signature:
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
@overload
|
|
425
|
+
@deprecated("Argument \"n_jobs\" for \"compute\" is deprecated, it has been renamed to \"workers\" ...")
|
|
426
|
+
def compute(data: list, *, n_jobs: int) -> int: ...
|
|
427
|
+
@overload
|
|
428
|
+
def compute(data: list, *, workers: int = ...) -> int: ...
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
The same is available programmatically as `generate_stub(module_name)`. Add the overloads above the
|
|
432
|
+
corresponding function in the module itself, or merge them into its stub file. They import `deprecated` from `warnings` on python 3.13 and later, and from `typing_extensions`
|
|
433
|
+
before that, so `typing_extensions` must be available to the type checker in that case.
|
|
434
|
+
|
|
435
|
+
## Documenting the deprecations with sphinx
|
|
436
|
+
|
|
437
|
+
While sphinx is building, and only then, the decorator also appends a `.. deprecated::` directive to the
|
|
438
|
+
docstring, one per deprecation, so that the deprecations appear in the built documentation. There is
|
|
439
|
+
nothing to enable and no second decorator to import, and outside of a build the docstring is untouched:
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
from deprecated_parameters import deprecated_parameters, ParameterRemove
|
|
443
|
+
|
|
444
|
+
@deprecated_parameters(
|
|
445
|
+
ParameterRemove(old_name="verbose", version="1.5.0", when="v2.0.0"),
|
|
446
|
+
)
|
|
447
|
+
def compute(data):
|
|
448
|
+
"""Compute things."""
|
|
449
|
+
return data
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
autodoc then renders:
|
|
453
|
+
|
|
454
|
+
```text
|
|
455
|
+
compute(data)
|
|
456
|
+
|
|
457
|
+
Compute things.
|
|
458
|
+
|
|
459
|
+
Deprecated since version 1.5.0: Argument "verbose" for "compute" is
|
|
460
|
+
deprecated since 1.5.0, its value is ignored and it will no longer be
|
|
461
|
+
accepted in v2.0.0
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
The directive argument is the `version` of the deprecation, or its `when` when no version is given.
|
|
465
|
+
|
|
466
|
+
## Example
|
|
467
|
+
|
|
468
|
+
[example/](example) is a small library that uses every kind of deprecation once, with the calls that
|
|
469
|
+
an unmigrated caller would make. One command each shows the three ways in which they are reported:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
cd example
|
|
473
|
+
python mylibrary.py # the runtime warnings
|
|
474
|
+
mypy --config-file mypy.ini mylibrary.py # the same, before anything runs
|
|
475
|
+
sphinx-build -b html docs build # the deprecations in the documentation
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
See [example/README.md](example/README.md) for what each of them prints.
|
|
479
|
+
|
|
480
|
+
## Limitations
|
|
481
|
+
|
|
482
|
+
- Only mypy reports deprecated parameters directly. Other type checkers have no
|
|
483
|
+
plugin system, and need the generated overloads described above.
|
|
484
|
+
- A deprecated value is only detected statically when it is given as a literal,
|
|
485
|
+
and only widened in the signature for types that have a `Literal`, so not for
|
|
486
|
+
floats. At runtime any value is compared.
|
|
487
|
+
- Calls through an alias, `functools.partial` or `**kwargs` unpacking are not
|
|
488
|
+
detected by mypy.
|
|
489
|
+
- A parameter that is removed or becomes keyword-only can only be rescued from
|
|
490
|
+
the position it had if it was among the last positional ones. Otherwise an
|
|
491
|
+
argument given for it is indistinguishable from one given for the parameter
|
|
492
|
+
that now occupies that position, and the decorator refuses the `old_index`.
|
|
493
|
+
- The decorator cannot be applied to a class. Apply it to `__init__` instead,
|
|
494
|
+
which is reported by mypy on calls to the class.
|
|
495
|
+
- Decorated coroutine functions are recognized by `inspect.iscoroutinefunction`
|
|
496
|
+
only from python 3.12. In earlier versions only `asyncio.iscoroutinefunction`
|
|
497
|
+
recognizes them.
|
|
498
|
+
|
|
499
|
+
## Contributing
|
|
500
|
+
|
|
501
|
+
Contributions are welcome, please open an issue or pull request in
|
|
502
|
+
[GitHub](https://github.com/mauvilsa/deprecated-parameters).
|
|
503
|
+
|
|
504
|
+
### Development environment
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
git clone https://github.com/mauvilsa/deprecated-parameters.git
|
|
508
|
+
cd deprecated-parameters
|
|
509
|
+
python -m venv venv
|
|
510
|
+
source venv/bin/activate
|
|
511
|
+
pip install -e ".[test,dev]"
|
|
512
|
+
pre-commit install
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Run the tests with:
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
pytest
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Note that mypy is unable to resolve [PEP
|
|
522
|
+
660](https://peps.python.org/pep-0660/) editable installs, which is what `pip
|
|
523
|
+
install -e` creates. The tests work around this by passing the location of the
|
|
524
|
+
package to mypy as `mypy_path`, so they can be run from any working directory.
|
|
525
|
+
If you invoke mypy yourself on code that imports `deprecated_parameters` from an
|
|
526
|
+
editable install, you may need to do the same.
|
|
527
|
+
|
|
528
|
+
To run the tests against all supported python versions:
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
tox
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## License
|
|
535
|
+
|
|
536
|
+
MIT, see [LICENSE](LICENSE).
|