| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- # -*- coding: utf-8 -*-
- """
- Deprecation-Decorators für kontrollierte API-Evolution.
- Bietet Warnmechanismen für veraltete Funktionen und Klassen
- mit konfigurierbarem Verhalten.
- """
- from __future__ import annotations
- import functools
- import warnings
- from dataclasses import dataclass, field
- from datetime import datetime
- from typing import Any, Callable, ParamSpec, Type, TypeVar
- P = ParamSpec("P")
- R = TypeVar("R")
- T = TypeVar("T")
- @dataclass
- class DeprecationInfo:
- """
- Informationen über eine Deprecation.
- Speichert alle Details zur Deprecation für Reporting
- und Dokumentation.
- """
- name: str
- """Name der deprecateten Funktion/Klasse."""
- message: str
- """Deprecation-Nachricht."""
- version: str | None = None
- """Version, in der deprecated wurde."""
- removal_version: str | None = None
- """Version, in der entfernt wird."""
- replacement: str | None = None
- """Empfohlene Alternative."""
- since: datetime = field(default_factory=datetime.now)
- """Zeitpunkt der Deprecation."""
- call_count: int = 0
- """Anzahl der Aufrufe seit Deprecation."""
- def format_warning(self) -> str:
- """
- Formatiert die Deprecation-Warnung.
- Returns:
- Formatierte Warnmeldung.
- """
- parts = [f"{self.name} ist deprecated"]
- if self.version:
- parts.append(f"seit Version {self.version}")
- if self.message:
- parts.append(f": {self.message}")
- if self.replacement:
- parts.append(f". Verwende stattdessen: {self.replacement}")
- if self.removal_version:
- parts.append(f". Wird entfernt in Version {self.removal_version}")
- return " ".join(parts)
- # Globales Registry für deprecatete APIs
- _deprecation_registry: dict[str, DeprecationInfo] = {}
- def get_deprecations() -> dict[str, DeprecationInfo]:
- """
- Gibt alle registrierten Deprecations zurück.
- Returns:
- Dictionary mit DeprecationInfo-Objekten.
- """
- return dict(_deprecation_registry)
- def deprecated(
- message: str = "",
- version: str | None = None,
- removal_version: str | None = None,
- replacement: str | None = None,
- warn_once: bool = True,
- category: Type[Warning] = DeprecationWarning,
- ) -> Callable[[Callable[P, R]], Callable[P, R]]:
- """
- Decorator um Funktionen als deprecated zu markieren.
- Gibt bei jedem Aufruf eine Warnung aus (oder nur einmal,
- wenn warn_once=True).
- Args:
- message: Beschreibung warum deprecated.
- version: Version, in der deprecated wurde.
- removal_version: Version, in der entfernt wird.
- replacement: Empfohlene Alternative.
- warn_once: Ob nur einmal gewarnt werden soll.
- category: Warnungskategorie.
- Returns:
- Decorator-Funktion.
- Example:
- @deprecated(
- message="Diese Funktion ist veraltet",
- version="2.0.0",
- replacement="new_function()"
- )
- def old_function():
- pass
- @deprecated(removal_version="3.0.0")
- def soon_gone():
- pass
- """
- def decorator(func: Callable[P, R]) -> Callable[P, R]:
- name = func.__qualname__
- # Info-Objekt erstellen
- info = DeprecationInfo(
- name=name,
- message=message,
- version=version,
- removal_version=removal_version,
- replacement=replacement,
- )
- _deprecation_registry[name] = info
- warned = False
- @functools.wraps(func)
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
- nonlocal warned
- info.call_count += 1
- if not warn_once or not warned:
- warnings.warn(
- info.format_warning(),
- category=category,
- stacklevel=2,
- )
- warned = True
- return func(*args, **kwargs)
- # Docstring aktualisieren
- if wrapper.__doc__:
- wrapper.__doc__ = f"**DEPRECATED**: {info.format_warning()}\n\n{wrapper.__doc__}"
- else:
- wrapper.__doc__ = f"**DEPRECATED**: {info.format_warning()}"
- # Info-Zugriff über die Funktion
- wrapper.deprecation_info = info # type: ignore
- return wrapper
- return decorator
- def pending_deprecation(
- message: str = "",
- version: str | None = None,
- replacement: str | None = None,
- warn_once: bool = True,
- ) -> Callable[[Callable[P, R]], Callable[P, R]]:
- """
- Decorator für zukünftige Deprecation.
- Wie @deprecated, aber mit PendingDeprecationWarning.
- Gedacht für APIs, die noch nicht deprecated sind, aber
- bald sein werden.
- Args:
- message: Beschreibung der geplanten Änderung.
- version: Version, ab der deprecated sein wird.
- replacement: Empfohlene Alternative.
- warn_once: Ob nur einmal gewarnt werden soll.
- Returns:
- Decorator-Funktion.
- Example:
- @pending_deprecation(
- message="Wird in Version 3.0 deprecated",
- replacement="new_api()"
- )
- def current_function():
- pass
- """
- return deprecated(
- message=f"Bald deprecated: {message}",
- version=version,
- replacement=replacement,
- warn_once=warn_once,
- category=PendingDeprecationWarning,
- )
- def deprecated_argument(
- arg_name: str,
- message: str = "",
- replacement: str | None = None,
- warn_once: bool = True,
- ) -> Callable[[Callable[P, R]], Callable[P, R]]:
- """
- Decorator um ein bestimmtes Argument als deprecated zu markieren.
- Warnt nur, wenn das deprecatete Argument tatsächlich verwendet wird.
- Args:
- arg_name: Name des deprecateten Arguments.
- message: Beschreibung warum deprecated.
- replacement: Empfohlenes Ersatzargument.
- warn_once: Ob nur einmal gewarnt werden soll.
- Returns:
- Decorator-Funktion.
- Example:
- @deprecated_argument("old_param", replacement="new_param")
- def my_function(new_param=None, old_param=None):
- if old_param is not None:
- new_param = old_param
- return new_param
- """
- def decorator(func: Callable[P, R]) -> Callable[P, R]:
- warned = False
- @functools.wraps(func)
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
- nonlocal warned
- if arg_name in kwargs:
- if not warn_once or not warned:
- warning_msg = (
- f"Argument '{arg_name}' in {func.__qualname__} "
- f"ist deprecated"
- )
- if message:
- warning_msg += f": {message}"
- if replacement:
- warning_msg += f". Verwende stattdessen: {replacement}"
- warnings.warn(
- warning_msg,
- DeprecationWarning,
- stacklevel=2,
- )
- warned = True
- return func(*args, **kwargs)
- return wrapper
- return decorator
- def deprecated_class(
- message: str = "",
- version: str | None = None,
- removal_version: str | None = None,
- replacement: str | None = None,
- warn_once: bool = True,
- ) -> Callable[[Type[T]], Type[T]]:
- """
- Decorator um eine Klasse als deprecated zu markieren.
- Warnt bei jeder Instanziierung.
- Args:
- message: Beschreibung warum deprecated.
- version: Version, in der deprecated wurde.
- removal_version: Version, in der entfernt wird.
- replacement: Empfohlene Ersatzklasse.
- warn_once: Ob nur einmal gewarnt werden soll.
- Returns:
- Decorator-Funktion.
- Example:
- @deprecated_class(replacement="NewClass")
- class OldClass:
- pass
- """
- def decorator(cls: Type[T]) -> Type[T]:
- name = cls.__qualname__
- info = DeprecationInfo(
- name=name,
- message=message,
- version=version,
- removal_version=removal_version,
- replacement=replacement,
- )
- _deprecation_registry[name] = info
- original_init = cls.__init__
- warned = False
- @functools.wraps(original_init)
- def new_init(self: T, *args: Any, **kwargs: Any) -> None:
- nonlocal warned
- info.call_count += 1
- if not warn_once or not warned:
- warnings.warn(
- info.format_warning(),
- DeprecationWarning,
- stacklevel=2,
- )
- warned = True
- original_init(self, *args, **kwargs)
- cls.__init__ = new_init # type: ignore
- # Docstring aktualisieren
- if cls.__doc__:
- cls.__doc__ = f"**DEPRECATED**: {info.format_warning()}\n\n{cls.__doc__}"
- else:
- cls.__doc__ = f"**DEPRECATED**: {info.format_warning()}"
- # Info-Zugriff über die Klasse
- cls.deprecation_info = info # type: ignore
- return cls
- return decorator
- def deprecated_alias(
- original: Callable[P, R],
- message: str = "",
- version: str | None = None,
- ) -> Callable[P, R]:
- """
- Erstellt einen deprecateten Alias für eine Funktion.
- Nützlich für Umbenennungen, wo der alte Name noch
- eine Weile unterstützt werden soll.
- Args:
- original: Die originale (neue) Funktion.
- message: Optionale zusätzliche Nachricht.
- version: Version, in der der Alias deprecated wurde.
- Returns:
- Wrapper-Funktion.
- Example:
- def new_function():
- return "result"
- # Alter Name als deprecateter Alias
- old_function = deprecated_alias(
- new_function,
- message="Wurde umbenannt zu new_function",
- version="2.0.0"
- )
- """
- @deprecated(
- message=message or f"Verwende {original.__qualname__} stattdessen",
- version=version,
- replacement=original.__qualname__,
- )
- @functools.wraps(original)
- def alias(*args: P.args, **kwargs: P.kwargs) -> R:
- return original(*args, **kwargs)
- alias.__name__ = f"{original.__name__}_deprecated"
- return alias
|