Skip to content

Commit

Permalink
feat: add more fine-grained deprecation warning control (#44307)
Browse files Browse the repository at this point in the history
* feat: add more fine-grained deprecation warning control

see: https://github.com/frappe/frappe/wiki/Deprecations

* chore: ensure frappe deprecation dumpster is reused where possible; and loaded to parse PYTHONWARNINGS (!)
  • Loading branch information
blaggacao authored Nov 23, 2024
1 parent f1bd6e4 commit a9485b9
Showing 1 changed file with 45 additions and 33 deletions.
78 changes: 45 additions & 33 deletions erpnext/deprecation_dumpster.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Each function or method that checks in here comes with its own personalized decorator, complete with:
1. The date it was marked for deprecation (its "over the hill" birthday)
2. The ERPNext version in which it will be removed (its "graduation" to the great codebase in the sky)
2. The ERPNext version at the beginning of which it becomes an error and at the end of which it will be removed (its "graduation" to the great codebase in the sky)
3. A user-facing note on alternative solutions (its "parting wisdom")
Warning: The global namespace herein is more patched up than a sailor's favorite pair of jeans. Proceed with caution and a sense of humor!
Expand All @@ -15,52 +15,63 @@
Enjoy your stay in the Deprecation Dumpster, where every function gets a second chance to shine (or at least, to not break everything).
"""

import functools
import re
import sys
import warnings

from frappe.deprecation_dumpster import Color, _deprecated, colorize

def colorize(text, color_code):
if sys.stdout.isatty():
return f"\033[{color_code}m{text}\033[0m"
return text

# we use Warning because DeprecationWarning has python default filters which would exclude them from showing
# see also frappe.__init__ enabling them when a dev_server
class ERPNextDeprecationError(Warning):
"""Deprecated feature in current version.
class Color:
RED = 91
YELLOW = 93
CYAN = 96
Raises an error by default but can be configured via PYTHONWARNINGS in an emergency.
"""


class ERPNextDeprecationWarning(Warning):
...
"""Deprecated feature in next version"""


class PendingERPNextDeprecationWarning(ERPNextDeprecationWarning):
"""Deprecated feature in develop beyond next version.
Warning ignored by default.
The deprecation decision may still be reverted or deferred at this stage.
Regardless, using the new variant is encouraged and stable.
"""


warnings.simplefilter("error", ERPNextDeprecationError)
warnings.simplefilter("ignore", PendingERPNextDeprecationWarning)


class V15ERPNextDeprecationWarning(ERPNextDeprecationError):
pass


try:
# since python 3.13, PEP 702
from warnings import deprecated as _deprecated
except ImportError:
import functools
import warnings
from collections.abc import Callable
from typing import Optional, TypeVar, Union, overload
class V16ERPNextDeprecationWarning(ERPNextDeprecationWarning):
pass

T = TypeVar("T", bound=Callable)

def _deprecated(message: str, category=ERPNextDeprecationWarning, stacklevel=1) -> Callable[[T], T]:
def decorator(func: T) -> T:
@functools.wraps(func)
def wrapper(*args, **kwargs):
if message:
warning_msg = f"{func.__name__} is deprecated.\n{message}"
else:
warning_msg = f"{func.__name__} is deprecated."
warnings.warn(warning_msg, category=category, stacklevel=stacklevel + 1)
return func(*args, **kwargs)
class V17ERPNextDeprecationWarning(PendingERPNextDeprecationWarning):
pass

return wrapper
wrapper.__deprecated__ = True # hint for the type checker

return decorator
def __get_deprecation_class(graduation: str | None = None, class_name: str | None = None) -> type:
if graduation:
# Scrub the graduation string to ensure it's a valid class name
cleaned_graduation = re.sub(r"\W|^(?=\d)", "_", graduation.upper())
class_name = f"{cleaned_graduation}ERPNextDeprecationWarning"
current_module = sys.modules[__name__]
try:
return getattr(current_module, class_name)
except AttributeError:
return PendingDeprecationWarning


def deprecated(original: str, marked: str, graduation: str, msg: str, stacklevel: int = 1):
Expand All @@ -79,6 +90,7 @@ def decorator(func):
wrapper = _deprecated(
colorize(f"It was marked on {marked} for removal from {graduation} with note: ", Color.RED)
+ colorize(f"{msg}", Color.YELLOW),
category=__get_deprecation_class(graduation),
stacklevel=stacklevel,
)

Expand All @@ -103,7 +115,7 @@ def deprecation_warning(marked: str, graduation: str, msg: str):
Color.RED,
)
+ colorize(f"{msg}\n", Color.YELLOW),
category=ERPNextDeprecationWarning,
category=__get_deprecation_class(graduation),
stacklevel=2,
)

Expand Down

0 comments on commit a9485b9

Please sign in to comment.