From 54df350cdacaa92b85f1a66b608e8d76656ae7ef Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 20 Oct 2021 10:13:55 +0200 Subject: [PATCH] Enable registering of legacy attributes without exact substitutes (#1438) * Enable registering of legacy attributes without... ... exact substitutes. (See diff for an example.) * Take new callable instead of old name ... ... so we can ensure existence * Require old names to be passed as key words This is a lot simpler, less error prone, and works for all kinds of old names, not only those which are proper camelcase. --- pylib/anki/_legacy.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/pylib/anki/_legacy.py b/pylib/anki/_legacy.py index 7528de6a1..672978142 100644 --- a/pylib/anki/_legacy.py +++ b/pylib/anki/_legacy.py @@ -44,15 +44,21 @@ class DeprecatedNamesMixin: # attributes on the consuming class _deprecated_aliases: dict[str, str] = {} + _deprecated_attributes: dict[str, tuple[str, str]] = {} @no_type_check def __getattr__(self, name: str) -> Any: - remapped = self._deprecated_aliases.get(name) or stringcase.snakecase(name) - if remapped == name: - raise AttributeError + if some_tuple := self._deprecated_attributes.get(name): + remapped, replacement = some_tuple + else: + replacement = remapped = self._deprecated_aliases.get( + name + ) or stringcase.snakecase(name) + if remapped == name: + raise AttributeError out = getattr(self, remapped) - _print_warning(f"'{name}'", f"please use '{remapped}'") + _print_warning(f"'{name}'", f"please use '{replacement}'") return out @@ -67,6 +73,28 @@ class DeprecatedNamesMixin: """ cls._deprecated_aliases = {k: _target_to_string(v) for k, v in kwargs.items()} + @no_type_check + @classmethod + def register_deprecated_attributes( + cls, + **kwargs: tuple[DeprecatedAliasTarget, DeprecatedAliasTarget], + ) -> None: + """Manually add deprecated attributes without exact substitutes. + + Pass a tuple of (alias, replacement), where alias is the attribute's new + name (by convention: snakecase, prepended with '_legacy_'), and + replacement is any callable to be used instead in new code. + Also note the docstring of `register_deprecated_aliases`. + + E.g. given `def oldFunc(args): return new_func(additionalLogic(args))`, + rename `oldFunc` to `_legacy_old_func` and call + `register_deprecated_attributes(oldFunc=(_legacy_old_func, new_func))`. + """ + cls._deprecated_attributes = { + k: (_target_to_string(v[0]), _target_to_string(v[1])) + for k, v in kwargs.items() + } + def deprecated(replaced_by: Callable | None = None, info: str = "") -> Callable: """Print a deprecation warning, telling users to use `replaced_by`, or show `doc`."""