deprecated-params 0.2.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vizonex
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,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: deprecated-params
3
+ Version: 0.2.0
4
+ Summary: A Wrapper for functions, class objects and methods for deprecating keyword parameters
5
+ Author-email: Vizonex <VizonexBusiness@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/Vizonex/deprecated-params
8
+ Project-URL: repository, https://github.com/Vizonex/deprecated-params.git
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: typing-extensions; python_version < "3.10"
13
+ Dynamic: license-file
14
+
15
+ # Deprecated Params
16
+ [![PyPI version](https://badge.fury.io/py/deprecated-params.svg)](https://badge.fury.io/py/deprecated-params)
17
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/deprecated-params)
18
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
19
+ [![License: Appache-2.0](https://img.shields.io/badge/License-Appache-yellow.svg)](https://opensource.org/licenses/Appache-2-0)
20
+
21
+
22
+ Inspired after python's warning.deprecated wrapper, deprecated_params is made to serve the single purpose of deprecating parameter names to warn users
23
+ about incoming changes as well as retaining typehinting.
24
+
25
+
26
+
27
+ ## How to Deprecate Parameters
28
+ Parameters should be keyword arguments, not positional, Reason
29
+ for this implementation is that in theory you should've already
30
+ planned an alternative approach to an argument you wish
31
+ to deprecate. Most of the times these arguments will most
32
+ likely be one of 3 cases.
33
+ - misspellings
34
+ - better functionality that replaces old arguments with better ones.
35
+ - removed parameters but you want to warn developers
36
+ to move without being aggressive about it.
37
+
38
+
39
+ ```python
40
+ from deprecated_params import deprecated_params
41
+
42
+ @deprecated_params(['x'])
43
+ def func(y, *, x:int = 0):
44
+ pass
45
+
46
+ # DeprecationWarning: Parameter "x" is deprecated
47
+ func(None, x=20)
48
+
49
+ # NOTE: **kw is accepted but also you could put down more than one
50
+ # parameter if needed...
51
+ @deprecated_params(['foo'], {"foo":"foo was removed in ... don't use it"}, display_kw=False)
52
+ class MyClass:
53
+ def __init__(self, spam:object, **kw):
54
+ self.spam = spam
55
+ self.foo = kw.get("foo", None)
56
+
57
+ # DeprecationWarning: foo was removed in ... don't use it
58
+ mc = MyClass("spam", foo="X")
59
+ ```
60
+
61
+ ## Why I wrote Deprecated Params
62
+ I got tired of throwing random warnings in my code and wanted something cleaner that didn't
63
+ interfere with a function's actual code and didn't blind anybody trying to go through it.
64
+ Contributors and Reviewers should be able to utilize a library that saves them from these problems
65
+ while improving the readability of a function. After figuring out that the functionality I was
66
+ looking for didn't exist I took the opportunity to implement it.
67
+
68
+ ## Deprecated Params used in real-world Examples
69
+ Deprecated-params is now used with two of my own libraries by default.
70
+
71
+ - [aiothreading (up until 0.1.6)](https://github.com/Vizonex/aiothreading)
72
+ - Originally aiothreading had it's own wrapper but I split it off to this library along with a rewrite after finding out that
73
+ parameter names were not showing up ides such as vs-code. The rewrite felt a bit bigger and knowing that users would want to utilize
74
+ this concept in other places was how this library ultimately got started.
75
+ - Lots of interior changes were made and with many arguments being suddenly dropped to increase the performance, the best solution was to warn
76
+ developers to stop using certain parameters as they will be deleted in the future.
77
+ - It is planned to be dropped as many of the things we wanted to remove have been slowly removed from the library which means this library will
78
+ be removed from it but that doesn't mean I won't keep maintaining it, it is invented for short-use cases and can be added and removed freely
79
+ without needing additional dependencies.
80
+
81
+ - [aiocallback (mainly used in version 1.6)](https://github.com/Vizonex/aiocallback)
82
+ - Same situation as aiothreading but I decided to buy users more time due to how fast some releases were going and it also allowed
83
+ - Currently I removed deprecated-params from aiocallback since it wasn't needed anymore but this is what deprecated-param's purpose
84
+ was for, being there only when its need. I desired nothing more or less.
85
+
86
+ If you would like to add examples of your own libraries that have used this library feel free to throw me an issue or send me a pull request.
87
+
@@ -0,0 +1,73 @@
1
+ # Deprecated Params
2
+ [![PyPI version](https://badge.fury.io/py/deprecated-params.svg)](https://badge.fury.io/py/deprecated-params)
3
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/deprecated-params)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![License: Appache-2.0](https://img.shields.io/badge/License-Appache-yellow.svg)](https://opensource.org/licenses/Appache-2-0)
6
+
7
+
8
+ Inspired after python's warning.deprecated wrapper, deprecated_params is made to serve the single purpose of deprecating parameter names to warn users
9
+ about incoming changes as well as retaining typehinting.
10
+
11
+
12
+
13
+ ## How to Deprecate Parameters
14
+ Parameters should be keyword arguments, not positional, Reason
15
+ for this implementation is that in theory you should've already
16
+ planned an alternative approach to an argument you wish
17
+ to deprecate. Most of the times these arguments will most
18
+ likely be one of 3 cases.
19
+ - misspellings
20
+ - better functionality that replaces old arguments with better ones.
21
+ - removed parameters but you want to warn developers
22
+ to move without being aggressive about it.
23
+
24
+
25
+ ```python
26
+ from deprecated_params import deprecated_params
27
+
28
+ @deprecated_params(['x'])
29
+ def func(y, *, x:int = 0):
30
+ pass
31
+
32
+ # DeprecationWarning: Parameter "x" is deprecated
33
+ func(None, x=20)
34
+
35
+ # NOTE: **kw is accepted but also you could put down more than one
36
+ # parameter if needed...
37
+ @deprecated_params(['foo'], {"foo":"foo was removed in ... don't use it"}, display_kw=False)
38
+ class MyClass:
39
+ def __init__(self, spam:object, **kw):
40
+ self.spam = spam
41
+ self.foo = kw.get("foo", None)
42
+
43
+ # DeprecationWarning: foo was removed in ... don't use it
44
+ mc = MyClass("spam", foo="X")
45
+ ```
46
+
47
+ ## Why I wrote Deprecated Params
48
+ I got tired of throwing random warnings in my code and wanted something cleaner that didn't
49
+ interfere with a function's actual code and didn't blind anybody trying to go through it.
50
+ Contributors and Reviewers should be able to utilize a library that saves them from these problems
51
+ while improving the readability of a function. After figuring out that the functionality I was
52
+ looking for didn't exist I took the opportunity to implement it.
53
+
54
+ ## Deprecated Params used in real-world Examples
55
+ Deprecated-params is now used with two of my own libraries by default.
56
+
57
+ - [aiothreading (up until 0.1.6)](https://github.com/Vizonex/aiothreading)
58
+ - Originally aiothreading had it's own wrapper but I split it off to this library along with a rewrite after finding out that
59
+ parameter names were not showing up ides such as vs-code. The rewrite felt a bit bigger and knowing that users would want to utilize
60
+ this concept in other places was how this library ultimately got started.
61
+ - Lots of interior changes were made and with many arguments being suddenly dropped to increase the performance, the best solution was to warn
62
+ developers to stop using certain parameters as they will be deleted in the future.
63
+ - It is planned to be dropped as many of the things we wanted to remove have been slowly removed from the library which means this library will
64
+ be removed from it but that doesn't mean I won't keep maintaining it, it is invented for short-use cases and can be added and removed freely
65
+ without needing additional dependencies.
66
+
67
+ - [aiocallback (mainly used in version 1.6)](https://github.com/Vizonex/aiocallback)
68
+ - Same situation as aiothreading but I decided to buy users more time due to how fast some releases were going and it also allowed
69
+ - Currently I removed deprecated-params from aiocallback since it wasn't needed anymore but this is what deprecated-param's purpose
70
+ was for, being there only when its need. I desired nothing more or less.
71
+
72
+ If you would like to add examples of your own libraries that have used this library feel free to throw me an issue or send me a pull request.
73
+
@@ -0,0 +1,434 @@
1
+ """
2
+ Deprecated Params
3
+ -----------------
4
+
5
+ A Library dedicated for warning users about deprecated parameter
6
+ names and changes
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import inspect
12
+ import sys
13
+ import warnings
14
+ from functools import wraps
15
+ from types import MethodType
16
+ from typing import (
17
+ Any,
18
+ Callable,
19
+ Iterable,
20
+ Mapping,
21
+ Sequence,
22
+ TypeVar,
23
+ overload,
24
+ )
25
+
26
+
27
+ if sys.version_info < (3, 10):
28
+ from typing_extensions import ParamSpec
29
+ else:
30
+ from typing import ParamSpec
31
+
32
+
33
+ __version__ = "0.2.0"
34
+ __license__ = "Apache 2.0 / MIT"
35
+ __author__ = "Vizonex"
36
+
37
+ _T = TypeVar("_T", covariant=True)
38
+ _P = ParamSpec("_P")
39
+
40
+ KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY
41
+ VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD
42
+
43
+
44
+ __all__ = (
45
+ "MissingKeywordsError",
46
+ "InvalidParametersError",
47
+ "deprecated_params",
48
+ "__author__",
49
+ "__license__",
50
+ "__version__",
51
+ )
52
+
53
+ # Word of Warning:
54
+ # All functions marked with underscores should be treated as do not use
55
+ # directly. If you want these parts of code, they are under an MIT License and you
56
+ # are allowed to copy and paste them freely as long as it's not apart of python's
57
+ # warnings.deprecated function which then you will need to license under an APACHE 2.0
58
+
59
+
60
+ class _KeywordsBaseException(Exception):
61
+ def __init__(self, keywords: set[str], *args: Any) -> None:
62
+ self._keywords = frozenset(keywords)
63
+ super().__init__(*args)
64
+
65
+ @property
66
+ def keywords(self) -> frozenset[str]:
67
+ """tells what keywords were bad"""
68
+ return self._keywords
69
+
70
+ @keywords.setter
71
+ def keywords(self, _: frozenset[str]) -> None:
72
+ """Throws ValueError because keywords is an
73
+ immutable property that shouldn't be edited."""
74
+ raise ValueError("keywords property is immutable")
75
+
76
+
77
+ class MissingKeywordsError(_KeywordsBaseException):
78
+ """Raised when Missing a keyword for an argument"""
79
+
80
+ def __init__(self, keywords: set[str], *args: Any) -> None:
81
+ """Initializes missing keywords"""
82
+ super().__init__(
83
+ keywords,
84
+ f"Missing Keyword arguments for: {list(keywords)!r}",
85
+ *args,
86
+ )
87
+
88
+
89
+ class InvalidParametersError(_KeywordsBaseException):
90
+ """Raised when Parameters were positional arguments without defaults or keyword arguments"""
91
+
92
+ def __init__(self, keywords: set[str], *args: Any) -> None:
93
+ """initializes invalid keywords, deprecated parameters should not be positional arguments
94
+ as that would defeat the purpose of deprecating a function's parameters."""
95
+ super().__init__(
96
+ keywords,
97
+ f"Arguments :{list(keywords)!r} should not be positional",
98
+ *args,
99
+ )
100
+
101
+
102
+ def join_version_if_sequence(ver: str | Sequence[int]) -> str:
103
+ return ".".join(map(str, ver)) if not isinstance(ver, str) else ver
104
+
105
+
106
+ def convert_removed_in_sequences(
107
+ removed_in: Mapping[str, str | Sequence[int]],
108
+ ) -> dict[str, str]:
109
+ return {k: join_version_if_sequence(v) for k, v in removed_in.items()}
110
+
111
+
112
+ class deprecated_params:
113
+ """
114
+ A Wrapper inspired by python's wrapper deprecated from 3.13
115
+ and is used to deprecate parameters.
116
+
117
+ Since version 0.1.8 this wrapper also passes along an attribute
118
+ called `__deprecated_params__` with a dictionary of all the
119
+ preloaded deprecation warnings to each given parameter. Ides
120
+ such as VSCode, Pycharm and more could theoretically utilize
121
+ `__deprecated_params__` elsewhere help to assist users and developers
122
+ while writing and editing code.
123
+ """
124
+
125
+ # __slots__ was an optimizations since subclassing deprecated_params should really be discouraged
126
+ # if this is not your case scenario and you must subclass this object throw me an issue.
127
+
128
+ __slots__ = (
129
+ "params",
130
+ "message",
131
+ "message_is_dict",
132
+ "display_kw",
133
+ "category",
134
+ "stacklevel",
135
+ "default_message",
136
+ "removed_in",
137
+ "_warning_messages",
138
+ )
139
+
140
+ def __init__(
141
+ self,
142
+ params: Sequence[str] | Iterable[str] | str,
143
+ message: str | Mapping[str, str] = "is deprecated",
144
+ /,
145
+ *,
146
+ # default_message should be utilized when a keyword isn't
147
+ # given in message if messaged is defined as a dictionary.
148
+ default_message: str | None = None,
149
+ category: type[Warning] | None = DeprecationWarning,
150
+ stacklevel: int = 1,
151
+ display_kw: bool = True,
152
+ # removed_in is inspired by the deprecation library
153
+ removed_in: str
154
+ | Sequence[int]
155
+ | Mapping[str, str | Sequence[int]]
156
+ | None = None,
157
+ ) -> None:
158
+ """
159
+ Initializes deprecated parameters to pass along to different functions
160
+
161
+ :param params: A Sequence of keyword parameters of single keyword parameter to deprecate and warn the removal of.
162
+ :param message: A single message for to assign to each parameter to be deprecated otherwise
163
+ you can deprecate multiple under different reasons::
164
+
165
+ @deprecated_params(
166
+ ['mispel', 'x'],
167
+ message={
168
+ 'mispel': 'mispel was deprecated due to misspelling the word',
169
+ 'x':'you get the idea...'
170
+ }
171
+ )
172
+ def mispelled_func(misspelling = None, *, mispel:str, x:int): ...
173
+
174
+ :param category: Used to warrant a custom warning category if required or needed to specify what
175
+ Deprecation warning should appear.
176
+ :param stacklevel: What level should this wanring appear at? Default: 1
177
+ :param default_message: When a parameter doesn't have a warning message try using this message instead
178
+ :param display_kw: Displays which parameter is deprecated in the warning message under `Parameter "%s" ...`
179
+ followed by the rest of the message
180
+ :param removed_in: Displays which version of your library's program will remove this keyword parameter in::
181
+
182
+ @deprecated_params(
183
+ ['mispel', 'x'],
184
+ removed_in={
185
+ 'mispel':'0.1.4',
186
+ 'x':(0, 1, 3)
187
+ } # sequences of numbers are also allowed if preferred.
188
+ )
189
+
190
+ def mispelled_func(misspelling = None, *, mispel:str, x:int): ...
191
+
192
+ you can also say that all parameters will be removed in one version::
193
+
194
+ @deprecated_params(
195
+ ['mispel', 'x'],
196
+ removed_in='0.1.5' # or (0, 1, 5)
197
+ )
198
+ def mispelled_func(misspelling = None, *, mispel:str, x:int): ...
199
+
200
+
201
+ """
202
+ if not params:
203
+ raise ValueError(f"params should not be empty got {params!r}")
204
+ if not isinstance(message, (str, dict, Mapping)):
205
+ raise TypeError(
206
+ f"Expected an object of type str or dict or Mappable type for 'message', not {type(message).__name__!r}"
207
+ )
208
+
209
+ self.params = (
210
+ set(params) if not isinstance(params, str) else set([params])
211
+ )
212
+
213
+ self.message = message or "is deprecated"
214
+ self.message_is_dict = isinstance(message, (Mapping, dict))
215
+ self.display_kw = display_kw
216
+ self.category = category
217
+ self.stacklevel = stacklevel
218
+ self.default_message = default_message or "do not use"
219
+
220
+ if removed_in:
221
+ if isinstance(removed_in, (dict, Mapping)):
222
+ # Some people might be more comfortable giving versions in tuples or lists.
223
+ self.removed_in = convert_removed_in_sequences(removed_in)
224
+ else:
225
+ # single removed version meaning that all parameters will be removed in this version
226
+ ver = join_version_if_sequence(removed_in)
227
+ self.removed_in = {k: ver for k in params}
228
+ else:
229
+ self.removed_in = {}
230
+
231
+ # Preloaded previews of all warning messages new in deprecated-params 0.1.8 for extra speed
232
+ # upon loading the message
233
+ self._warning_messages: dict[str, str] = {
234
+ p: self.__write_warning(p) for p in self.params
235
+ }
236
+
237
+ def __check_with_missing(
238
+ self,
239
+ fn: Callable[..., Any],
240
+ missing: set[str] | None = None,
241
+ invalid_params: set[str] | None = None,
242
+ skip_missing: bool | None = None,
243
+ allow_miss: bool = False,
244
+ ) -> tuple[set[str], set[str], bool]:
245
+ sig = inspect.signature(fn)
246
+
247
+ missing = missing if missing is not None else set(self.params)
248
+ invalid_params = set() if invalid_params is None else invalid_params
249
+
250
+ skip_missing = (
251
+ any([p.kind == VAR_KEYWORD for p in sig.parameters.values()])
252
+ if skip_missing is None
253
+ else skip_missing
254
+ )
255
+
256
+ for m in self.params:
257
+ if not allow_miss:
258
+ p = sig.parameters[m]
259
+ else:
260
+ p = sig.parameters.get(m) # type: ignore
261
+ if p is None:
262
+ continue
263
+
264
+ # Check if were keyword only or aren't carrying a default param
265
+ if p.kind != KEYWORD_ONLY:
266
+ # Anything this isn't a keyword should be considered as deprecated
267
+ # as were still technically using it.
268
+ invalid_params.add(p.name)
269
+
270
+ if not skip_missing:
271
+ missing.remove(p.name)
272
+
273
+ return missing, invalid_params, skip_missing
274
+
275
+ def __check_for_missing_kwds(
276
+ self,
277
+ fn: Callable[..., Any],
278
+ missing: set[str] | None = None,
279
+ invalid_params: set[str] | None = None,
280
+ skip_missing: bool | None = None,
281
+ allow_miss: bool = False,
282
+ ) -> None:
283
+ # copy sequence to check for missing parameter names
284
+ missing, invalid_params, skip_missing = self.__check_with_missing(
285
+ fn, missing, invalid_params, skip_missing, allow_miss
286
+ )
287
+
288
+ if invalid_params:
289
+ raise InvalidParametersError(invalid_params)
290
+
291
+ if missing and not skip_missing:
292
+ raise MissingKeywordsError(missing)
293
+
294
+ def __write_warning(self, kw_name: str) -> str:
295
+ msg = ""
296
+ if self.display_kw:
297
+ msg += 'Parameter "%s" ' % kw_name
298
+
299
+ if self.message_is_dict:
300
+ msg += self.message.get(kw_name, self.default_message) # type: ignore
301
+ else:
302
+ msg += self.message # type: ignore
303
+
304
+ if self.removed_in:
305
+ if kw_removed_in := self.removed_in.get(kw_name):
306
+ msg += " [Removed In: "
307
+ msg += kw_removed_in
308
+ msg += "]"
309
+
310
+ return msg
311
+
312
+ def __warn(self, kw_name: str, source: Any) -> None:
313
+ warnings.warn(
314
+ self._warning_messages[kw_name],
315
+ self.category,
316
+ stacklevel=self.stacklevel + 1,
317
+ source=source,
318
+ )
319
+
320
+ @overload
321
+ def __call__(self, arg: type[_T]) -> type[_T]: ...
322
+
323
+ @overload
324
+ def __call__(self, arg: Callable[_P, _T]) -> Callable[_P, _T]: ...
325
+
326
+ # Mirrors python's deprecated wrapper with a few changes
327
+ # Since 0.1.8 a new attribute is added called __deprecated_params__
328
+ # based off and inspired by python's __deprecated__ dunder value.
329
+
330
+ def __call__(
331
+ self, arg: type[_T] | Callable[_P, _T]
332
+ ) -> type[_T] | Callable[_P, _T]:
333
+ def check_kw_arguments(kw: dict[str, Any]) -> None:
334
+ captured = self.params.intersection(kw.keys())
335
+ for k in captured:
336
+ self.__warn(k, arg)
337
+
338
+ if isinstance(arg, type):
339
+ # NOTE: Combining init and new together is done to
340
+ # solve deprecation of both new_args and init_args
341
+
342
+ missing, invalid_params, skip_missing = self.__check_with_missing(
343
+ arg, allow_miss=True
344
+ )
345
+
346
+ original_new: Callable[..., type[_T]] = arg.__new__
347
+ self.__check_for_missing_kwds(
348
+ original_new,
349
+ missing,
350
+ invalid_params,
351
+ skip_missing,
352
+ allow_miss=True,
353
+ )
354
+
355
+ @wraps(original_new)
356
+ def __new__(
357
+ cls: type[_T], *args: _P.args, **kwargs: _P.kwargs
358
+ ) -> type[_T]:
359
+ check_kw_arguments(kwargs)
360
+ if original_new is not object.__new__:
361
+ return original_new(cls, *args, **kwargs)
362
+ # Python Comment: Mirrors a similar check in object.__new__.
363
+ elif cls.__init__ is object.__init__ and (args or kwargs):
364
+ raise TypeError(f"{cls.__name__}() takes no arguments")
365
+ else:
366
+ return original_new(cls)
367
+
368
+ arg.__new__ = staticmethod(__new__) # type: ignore
369
+ arg.__new__.__deprecated_params__ = self._warning_messages.copy() # type: ignore
370
+
371
+ original_init_subclass = arg.__init_subclass__
372
+ # Python Comment: We need slightly different behavior if __init_subclass__
373
+ # is a bound method (likely if it was implemented in Python)
374
+ if isinstance(original_init_subclass, MethodType):
375
+ self.__check_for_missing_kwds(
376
+ original_init_subclass,
377
+ missing,
378
+ invalid_params,
379
+ skip_missing,
380
+ allow_miss=True,
381
+ )
382
+ original_init_subclass = original_init_subclass.__func__
383
+
384
+ @wraps(original_init_subclass)
385
+ def __init_subclass__(
386
+ *args: _P.args, **kwargs: _P.kwargs
387
+ ) -> Any:
388
+ check_kw_arguments(kwargs)
389
+ return original_init_subclass(*args, **kwargs)
390
+
391
+ arg.__init_subclass__ = classmethod(__init_subclass__) # type: ignore
392
+ arg.__init_subclass__.__deprecated_params__ = ( # type: ignore[attr-defined]
393
+ self._warning_messages.copy()
394
+ )
395
+
396
+ # Python Comment: Or otherwise, which likely means it's a builtin such as
397
+ # object's implementation of __init_subclass__.
398
+ else:
399
+
400
+ @wraps(original_init_subclass)
401
+ def __init_subclass__(
402
+ *args: _P.args, **kwargs: _P.kwargs
403
+ ) -> None:
404
+ check_kw_arguments(kwargs)
405
+ return original_init_subclass(*args, **kwargs)
406
+
407
+ arg.__init_subclass__ = __init_subclass__ # type: ignore
408
+ arg.__init_subclass__.__deprecated_params__ = ( # type: ignore[attr-defined]
409
+ self._warning_messages.copy()
410
+ )
411
+ return arg
412
+
413
+ elif callable(arg):
414
+ # Check for missing function arguments
415
+ self.__check_for_missing_kwds(arg)
416
+
417
+ @wraps(arg)
418
+ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
419
+ check_kw_arguments(kwargs)
420
+ return arg(*args, **kwargs)
421
+
422
+ if sys.version_info >= (3, 12):
423
+ if inspect.iscoroutinefunction(arg):
424
+ wrapper = inspect.markcoroutinefunction(wrapper)
425
+
426
+ # Wrapper now contains a shadow copy of deprecated parameters
427
+ wrapper.__deprecated_params__ = self._warning_messages.copy() # type: ignore
428
+ return wrapper
429
+
430
+ else:
431
+ raise TypeError(
432
+ "@deprecated_params decorator with non-None category must be applied to "
433
+ f"a class or callable, not {arg!r}"
434
+ )
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: deprecated-params
3
+ Version: 0.2.0
4
+ Summary: A Wrapper for functions, class objects and methods for deprecating keyword parameters
5
+ Author-email: Vizonex <VizonexBusiness@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/Vizonex/deprecated-params
8
+ Project-URL: repository, https://github.com/Vizonex/deprecated-params.git
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: typing-extensions; python_version < "3.10"
13
+ Dynamic: license-file
14
+
15
+ # Deprecated Params
16
+ [![PyPI version](https://badge.fury.io/py/deprecated-params.svg)](https://badge.fury.io/py/deprecated-params)
17
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/deprecated-params)
18
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
19
+ [![License: Appache-2.0](https://img.shields.io/badge/License-Appache-yellow.svg)](https://opensource.org/licenses/Appache-2-0)
20
+
21
+
22
+ Inspired after python's warning.deprecated wrapper, deprecated_params is made to serve the single purpose of deprecating parameter names to warn users
23
+ about incoming changes as well as retaining typehinting.
24
+
25
+
26
+
27
+ ## How to Deprecate Parameters
28
+ Parameters should be keyword arguments, not positional, Reason
29
+ for this implementation is that in theory you should've already
30
+ planned an alternative approach to an argument you wish
31
+ to deprecate. Most of the times these arguments will most
32
+ likely be one of 3 cases.
33
+ - misspellings
34
+ - better functionality that replaces old arguments with better ones.
35
+ - removed parameters but you want to warn developers
36
+ to move without being aggressive about it.
37
+
38
+
39
+ ```python
40
+ from deprecated_params import deprecated_params
41
+
42
+ @deprecated_params(['x'])
43
+ def func(y, *, x:int = 0):
44
+ pass
45
+
46
+ # DeprecationWarning: Parameter "x" is deprecated
47
+ func(None, x=20)
48
+
49
+ # NOTE: **kw is accepted but also you could put down more than one
50
+ # parameter if needed...
51
+ @deprecated_params(['foo'], {"foo":"foo was removed in ... don't use it"}, display_kw=False)
52
+ class MyClass:
53
+ def __init__(self, spam:object, **kw):
54
+ self.spam = spam
55
+ self.foo = kw.get("foo", None)
56
+
57
+ # DeprecationWarning: foo was removed in ... don't use it
58
+ mc = MyClass("spam", foo="X")
59
+ ```
60
+
61
+ ## Why I wrote Deprecated Params
62
+ I got tired of throwing random warnings in my code and wanted something cleaner that didn't
63
+ interfere with a function's actual code and didn't blind anybody trying to go through it.
64
+ Contributors and Reviewers should be able to utilize a library that saves them from these problems
65
+ while improving the readability of a function. After figuring out that the functionality I was
66
+ looking for didn't exist I took the opportunity to implement it.
67
+
68
+ ## Deprecated Params used in real-world Examples
69
+ Deprecated-params is now used with two of my own libraries by default.
70
+
71
+ - [aiothreading (up until 0.1.6)](https://github.com/Vizonex/aiothreading)
72
+ - Originally aiothreading had it's own wrapper but I split it off to this library along with a rewrite after finding out that
73
+ parameter names were not showing up ides such as vs-code. The rewrite felt a bit bigger and knowing that users would want to utilize
74
+ this concept in other places was how this library ultimately got started.
75
+ - Lots of interior changes were made and with many arguments being suddenly dropped to increase the performance, the best solution was to warn
76
+ developers to stop using certain parameters as they will be deleted in the future.
77
+ - It is planned to be dropped as many of the things we wanted to remove have been slowly removed from the library which means this library will
78
+ be removed from it but that doesn't mean I won't keep maintaining it, it is invented for short-use cases and can be added and removed freely
79
+ without needing additional dependencies.
80
+
81
+ - [aiocallback (mainly used in version 1.6)](https://github.com/Vizonex/aiocallback)
82
+ - Same situation as aiothreading but I decided to buy users more time due to how fast some releases were going and it also allowed
83
+ - Currently I removed deprecated-params from aiocallback since it wasn't needed anymore but this is what deprecated-param's purpose
84
+ was for, being there only when its need. I desired nothing more or less.
85
+
86
+ If you would like to add examples of your own libraries that have used this library feel free to throw me an issue or send me a pull request.
87
+
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ deprecated_params/__init__.py
6
+ deprecated_params.egg-info/PKG-INFO
7
+ deprecated_params.egg-info/SOURCES.txt
8
+ deprecated_params.egg-info/dependency_links.txt
9
+ deprecated_params.egg-info/requires.txt
10
+ deprecated_params.egg-info/top_level.txt
11
+ tests/test_wrapper.py
@@ -0,0 +1,3 @@
1
+
2
+ [:python_version < "3.10"]
3
+ typing-extensions
@@ -0,0 +1 @@
1
+ deprecated_params
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools >= 47"
4
+ ]
5
+ [project]
6
+ name = "deprecated-params"
7
+ version = "0.2.0"
8
+ description = "A Wrapper for functions, class objects and methods for deprecating keyword parameters"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Vizonex", email="VizonexBusiness@gmail.com"},
12
+ ]
13
+ license = "MIT"
14
+ requires-python = ">=3.9"
15
+ dependencies = [
16
+ 'typing-extensions; python_version<"3.10"'
17
+ ]
18
+
19
+
20
+ [project.urls]
21
+ homepage = "https://github.com/Vizonex/deprecated-params"
22
+ repository = "https://github.com/Vizonex/deprecated-params.git"
23
+
24
+ [tool.ruff]
25
+ line-length = 79
26
+ indent-width = 4
27
+ target-version = "py39"
28
+ fix = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+ if __name__ == "__main__":
4
+ setup()
@@ -0,0 +1,142 @@
1
+ import pytest
2
+ from typing import Optional
3
+ from deprecated_params import deprecated_params
4
+ import sys
5
+ import warnings
6
+
7
+
8
+ # should carry w x y
9
+ def test_deprecated_param() -> None:
10
+ @deprecated_params(["x"], "is deprecated")
11
+ def my_func(w: int, *, x: int = 0, y: int = 0) -> None:
12
+ pass
13
+
14
+ with pytest.warns(DeprecationWarning, match='Parameter "x" is deprecated'):
15
+ my_func(0, x=0)
16
+
17
+
18
+ def test_single_deprecated_param() -> None:
19
+ @deprecated_params("x", "is deprecated")
20
+ def my_func(w: int, *, x: int = 0, y: int = 0) -> None:
21
+ pass
22
+
23
+ with pytest.warns(DeprecationWarning, match='Parameter "x" is deprecated'):
24
+ my_func(0, x=0)
25
+
26
+
27
+ def test_no_warn_if_deprecated_parameter_not_passed() -> None:
28
+ @deprecated_params("x", "is deprecated")
29
+ def my_func(w: int, *, x: int = 0, y: int = 0) -> None:
30
+ pass
31
+
32
+ # Do not raise or print a warning when X is not passed...
33
+ with warnings.catch_warnings():
34
+ warnings.simplefilter("error")
35
+ my_func(1, y=0)
36
+
37
+
38
+ def test_deprecated_param_removed_in() -> None:
39
+ @deprecated_params(["x"], "is deprecated", removed_in={"x": (0, 1, 5)})
40
+ def my_func(w: int, *, x: int = 0, y: int = 0) -> None:
41
+ pass
42
+
43
+ with pytest.warns(
44
+ DeprecationWarning,
45
+ match=r"Parameter \"x\" is deprecated \[Removed In\: 0.1.5\]",
46
+ ):
47
+ my_func(0, x=0)
48
+
49
+
50
+ def test_deprecated_params_dunder_attribute() -> None:
51
+ @deprecated_params(["x"], "is deprecated", removed_in={"x": (0, 1, 5)})
52
+ def my_func(w: int, *, x: int = 0, y: int = 0) -> None:
53
+ pass
54
+
55
+ assert getattr(my_func, "__deprecated_params__") == {
56
+ "x": 'Parameter "x" is deprecated [Removed In: 0.1.5]'
57
+ }
58
+
59
+
60
+ # Since 0.2.0 we no longer repeat warnings once. We do it
61
+ # so that developers are more willing to remove specific keyword parameters
62
+ def test_deprecated_param_repeat_twice() -> None:
63
+ @deprecated_params(["x"], "is deprecated", removed_in={"x": (0, 1, 5)})
64
+ def my_func(w: int, *, x: int = 0, y: int = 0) -> None:
65
+ pass
66
+
67
+ with pytest.warns(
68
+ DeprecationWarning,
69
+ match=r"Parameter \"x\" is deprecated \[Removed In\: 0.1.5\]",
70
+ ):
71
+ my_func(0, x=0)
72
+
73
+ with pytest.warns(
74
+ DeprecationWarning,
75
+ match=r"Parameter \"x\" is deprecated \[Removed In\: 0.1.5\]",
76
+ ):
77
+ my_func(0, x=0)
78
+
79
+
80
+ def test_class_wrapper_and_kw_display_disabled() -> None:
81
+ @deprecated_params(["foo"], "foo is deprecated", display_kw=False)
82
+ class MyClass:
83
+ def __init__(self, spam: str, *, foo: Optional[str] = None):
84
+ self.spam = spam
85
+ self.foo = foo
86
+
87
+ mc = MyClass("spam")
88
+ assert mc.spam == "spam"
89
+ assert mc.foo is None
90
+
91
+ with pytest.warns(DeprecationWarning, match="foo is deprecated"):
92
+ MyClass("spam", foo="foo")
93
+
94
+
95
+ def test_class_wrapper_and_kw_display_disabled_one_param() -> None:
96
+ @deprecated_params("foo", "foo is deprecated", display_kw=False)
97
+ class MyClass:
98
+ def __init__(self, spam: str, *, foo: Optional[str] = None):
99
+ self.spam = spam
100
+ self.foo = foo
101
+
102
+ mc = MyClass("spam")
103
+ assert mc.spam == "spam"
104
+ assert mc.foo is None
105
+
106
+ with pytest.warns(DeprecationWarning, match="foo is deprecated"):
107
+ MyClass("spam", foo="foo")
108
+
109
+
110
+ # There was nothing sillier than this...
111
+ class TornadoWarning(DeprecationWarning):
112
+ pass
113
+
114
+
115
+ @pytest.mark.skipif(sys.version_info < (3, 10), reason="kw_only not on 3.9")
116
+ def test_dataclasses_with_wrapper_message_dicts_custom_warning() -> None:
117
+ from dataclasses import dataclass, field
118
+
119
+ @deprecated_params(
120
+ ["foo", "spam"],
121
+ {"foo": "got foo", "spam": "got spam"},
122
+ display_kw=False,
123
+ category=TornadoWarning,
124
+ )
125
+ @dataclass
126
+ class Class:
127
+ foo: Optional[str] = field(kw_only=True, default=None)
128
+ spam: Optional[str] = field(kw_only=True, default=None)
129
+
130
+ with pytest.warns(TornadoWarning, match="got foo"):
131
+ Class(foo="foo")
132
+
133
+ with pytest.warns(TornadoWarning, match="got spam"):
134
+ Class(spam="foo")
135
+
136
+ # Do Not raise if class doesn't pass a deprecated parameter
137
+ with warnings.catch_warnings():
138
+ warnings.simplefilter(action="error")
139
+ Class()
140
+
141
+
142
+ # TODO: Metaclasses...