diff --git a/erpnext/deprecation_dumpster.py b/erpnext/deprecation_dumpster.py new file mode 100644 index 00000000000..7a386ad3f8d --- /dev/null +++ b/erpnext/deprecation_dumpster.py @@ -0,0 +1,127 @@ +""" +Welcome to the Deprecation Dumpster: Where Old Code Goes to Party! 🎉🗑️ + +This file is the final resting place (or should we say, "retirement home"?) for all the deprecated functions and methods of the ERPNext app. It's like a code nursing home, but with more monkey-patching and less bingo. + +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) +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! + +Remember, deprecated doesn't mean useless - it just means these functions are enjoying their golden years before their final bow. Treat them with respect, and maybe bring them some virtual prune juice. + +Enjoy your stay in the Deprecation Dumpster, where every function gets a second chance to shine (or at least, to not break everything). +""" + +import inspect +import os +import sys +import warnings + + +def colorize(text, color_code): + if sys.stdout.isatty(): + return f"\033[{color_code}m{text}\033[0m" + return text + + +class Color: + RED = 91 + YELLOW = 93 + CYAN = 96 + + +class ERPNextDeprecationWarning(Warning): + ... + + +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 + + 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) + + return wrapper + wrapper.__deprecated__ = True # hint for the type checker + + return decorator + + +def deprecated(original: str, marked: str, graduation: str, msg: str, stacklevel: int = 1): + """Decorator to wrap a function/method as deprecated. + + Arguments: + - original: frappe.utils.make_esc (fully qualified) + - marked: 2024-09-13 (the date it has been marked) + - graduation: v17 (generally: current version + 2) + - msg: additional instructions + """ + + def decorator(func): + # Get the filename of the caller + frame = inspect.currentframe() + caller_filepath = frame.f_back.f_code.co_filename + if os.path.basename(caller_filepath) != "deprecation_dumpster.py": + raise RuntimeError( + colorize("The deprecated function ", Color.YELLOW) + + colorize(func.__name__, Color.CYAN) + + colorize(" can only be called from ", Color.YELLOW) + + colorize("erpnext/deprecation_dumpster.py\n", Color.CYAN) + + colorize("Move the entire function there and import it back via adding\n ", Color.YELLOW) + + colorize(f"from erpnext.deprecation_dumpster import {func.__name__}\n", Color.CYAN) + + colorize("to file\n ", Color.YELLOW) + + colorize(caller_filepath, Color.CYAN) + ) + + func.__name__ = original + wrapper = _deprecated( + colorize(f"It was marked on {marked} for removal from {graduation} with note: ", Color.RED) + + colorize(f"{msg}", Color.YELLOW), + stacklevel=stacklevel, + ) + + return functools.update_wrapper(wrapper, func)(func) + + return decorator + + +def deprecation_warning(marked: str, graduation: str, msg: str): + """Warn in-place from a deprecated code path, for objects use `@deprecated` decorator from the deprectation_dumpster" + + Arguments: + - marked: 2024-09-13 (the date it has been marked) + - graduation: v17 (generally: current version + 2) + - msg: additional instructions + """ + + warnings.warn( + colorize( + f"This codepath was marked (DATE: {marked}) deprecated" + f" for removal (from {graduation} onwards); note:\n ", + Color.RED, + ) + + colorize(f"{msg}\n", Color.YELLOW), + category=ERPNextDeprecationWarning, + stacklevel=2, + ) + + +### Party starts here