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.
Files changed (23) hide show
  1. deprecated_parameters-0.1.0/LICENSE +21 -0
  2. deprecated_parameters-0.1.0/PKG-INFO +536 -0
  3. deprecated_parameters-0.1.0/README.md +493 -0
  4. deprecated_parameters-0.1.0/deprecated_parameters/__init__.py +14 -0
  5. deprecated_parameters-0.1.0/deprecated_parameters/_decorator.py +538 -0
  6. deprecated_parameters-0.1.0/deprecated_parameters/_mypy.py +280 -0
  7. deprecated_parameters-0.1.0/deprecated_parameters/_sphinx.py +63 -0
  8. deprecated_parameters-0.1.0/deprecated_parameters/_stubgen.py +217 -0
  9. deprecated_parameters-0.1.0/deprecated_parameters/py.typed +0 -0
  10. deprecated_parameters-0.1.0/deprecated_parameters.egg-info/PKG-INFO +536 -0
  11. deprecated_parameters-0.1.0/deprecated_parameters.egg-info/SOURCES.txt +21 -0
  12. deprecated_parameters-0.1.0/deprecated_parameters.egg-info/dependency_links.txt +1 -0
  13. deprecated_parameters-0.1.0/deprecated_parameters.egg-info/entry_points.txt +2 -0
  14. deprecated_parameters-0.1.0/deprecated_parameters.egg-info/requires.txt +19 -0
  15. deprecated_parameters-0.1.0/deprecated_parameters.egg-info/top_level.txt +2 -0
  16. deprecated_parameters-0.1.0/deprecated_parameters_tests/__init__.py +1 -0
  17. deprecated_parameters-0.1.0/deprecated_parameters_tests/__main__.py +22 -0
  18. deprecated_parameters-0.1.0/deprecated_parameters_tests/test_decorator.py +943 -0
  19. deprecated_parameters-0.1.0/deprecated_parameters_tests/test_mypy.py +612 -0
  20. deprecated_parameters-0.1.0/deprecated_parameters_tests/test_sphinx.py +188 -0
  21. deprecated_parameters-0.1.0/deprecated_parameters_tests/test_stubgen.py +244 -0
  22. deprecated_parameters-0.1.0/pyproject.toml +119 -0
  23. 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
+ [![PyPI version](https://img.shields.io/pypi/v/deprecated-parameters.svg)](https://pypi.org/project/deprecated-parameters)
45
+ [![Python versions](https://img.shields.io/pypi/pyversions/deprecated-parameters.svg)](https://pypi.org/project/deprecated-parameters)
46
+ [![tests](https://github.com/mauvilsa/deprecated-parameters/actions/workflows/tests.yaml/badge.svg)](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).