mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00

This adds Python 3.9 and 3.10 typing syntax to files that import attributions from __future___. Python 3.9 should be able to cope with the 3.10 syntax, but Python 3.8 will no longer work. On Windows/Mac, install the latest Python 3.9 version from python.org. There are currently no orjson wheels for Python 3.10 on Windows/Mac, which will break the build unless you have Rust installed separately. On Linux, modern distros should have Python 3.9 available already. If you're on an older distro, you'll need to build Python from source first.
88 lines
2.7 KiB
Python
88 lines
2.7 KiB
Python
# Copyright: Ankitects Pty Ltd and contributors
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import os
|
|
import pathlib
|
|
import traceback
|
|
from typing import Any, Callable, Union, no_type_check
|
|
|
|
import stringcase
|
|
|
|
VariableTarget = tuple[Any, str]
|
|
DeprecatedAliasTarget = Union[Callable, VariableTarget]
|
|
|
|
|
|
def _target_to_string(target: DeprecatedAliasTarget) -> str:
|
|
if name := getattr(target, "__name__", None):
|
|
return name
|
|
else:
|
|
return target[1] # type: ignore
|
|
|
|
|
|
def partial_path(full_path: str, components: int) -> str:
|
|
path = pathlib.Path(full_path)
|
|
return os.path.join(*path.parts[-components:])
|
|
|
|
|
|
def print_deprecation_warning(msg: str, frame: int = 2) -> None:
|
|
path, linenum, _, _ = traceback.extract_stack(limit=5)[frame]
|
|
path = partial_path(path, components=3)
|
|
print(f"{path}:{linenum}:{msg}")
|
|
|
|
|
|
def _print_warning(old: str, doc: str) -> None:
|
|
return print_deprecation_warning(f"{old} is deprecated: {doc}", frame=1)
|
|
|
|
|
|
class DeprecatedNamesMixin:
|
|
"Expose instance methods/vars as camelCase for legacy callers."
|
|
|
|
# the @no_type_check lines are required to prevent mypy allowing arbitrary
|
|
# attributes on the consuming class
|
|
|
|
_deprecated_aliases: dict[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
|
|
|
|
out = getattr(self, remapped)
|
|
_print_warning(f"'{name}'", f"please use '{remapped}'")
|
|
|
|
return out
|
|
|
|
@no_type_check
|
|
@classmethod
|
|
def register_deprecated_aliases(cls, **kwargs: DeprecatedAliasTarget) -> None:
|
|
"""Manually add aliases that are not a simple transform.
|
|
|
|
Either pass in a method, or a tuple of (variable, "variable"). The
|
|
latter is required because we want to ensure the provided arguments
|
|
are valid symbols, and we can't get a variable's name easily.
|
|
"""
|
|
cls._deprecated_aliases = {k: _target_to_string(v) 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`."""
|
|
|
|
def decorator(func: Callable) -> Callable:
|
|
@functools.wraps(func)
|
|
def decorated_func(*args: Any, **kwargs: Any) -> Any:
|
|
if replaced_by:
|
|
doc = f"please use {replaced_by.__name__} instead."
|
|
else:
|
|
doc = info
|
|
|
|
_print_warning(f"{func.__name__}()", doc)
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
return decorated_func
|
|
|
|
return decorator
|