Source code for schrodinger.utils.deprecation

"""
Deprecation handling

Copyright Schrodinger LLC, All Rights Reserved.
"""

import warnings
from contextlib import contextmanager
from typing import Callable
from typing import Optional

import decorator

from schrodinger import get_release_name
from schrodinger.test import decorate_fflag
from schrodinger.utils import mmutil

USE_DEPRECATED = "USE_DEPRECATED"


[docs]class DeprecationError(RuntimeError): """Exception indicating API deprecation""" pass
def _valid_release(release: str) -> bool: split_version = lambda x: (int(y) for y in x.split("-")) try: year, quarter = split_version(release) cur_year, cur_quarter = split_version(get_release_name()) except (AttributeError, ValueError): return False # Valid if the current release and onwards return year > cur_year or (year == cur_year and quarter >= cur_quarter)
[docs]def raise_deprecation_error(msg: str, to_remove_in: str): """ Raise a `DeprecationError` unless USE_DEPRECATED feature flag is set, which will issue a DeprecationWarning only. :param msg: deprecation message to isse :param to_remove_in: Schrodinger core suite release version (XXXX-X) """ if not _valid_release(to_remove_in): raise RuntimeError(f"Must provide valid to_remove_in: {to_remove_in}") # Under feature flag, print deprecation warning only if mmutil.feature_flag_is_enabled(mmutil.USE_DEPRECATED): msg += f"\nWARNING: to be completely removed in {to_remove_in} release." warnings.warn(msg, DeprecationWarning, stacklevel=3) return # Otherwise, raise an exception to force callers to update deprecated calls raise DeprecationError(msg)
[docs]def deprecate_module(module_name: str, to_remove_in: str, replacement: str = None): """ Raises a deprecation error (or only prints a warning when USE_DEPRECATED is enabled), indicating to the caller that the import should be removed, and updated to a new module if applicable. :param module_name: name of module which is deprecated :param to_remove_in: Schrodinger core suite release version (XXXX-X) :param replacement: replacement module name """ msg = (f"import deprecated: {module_name}\n" f"The requested import statement is deprecated in the Schrodinger" f" suite and is no longer available in this release.") if replacement: msg += (f"\nPlease update as:\n import {replacement.__name__}") raise_deprecation_error(msg, to_remove_in)
[docs]@decorator.decorator def deprecated(func: Callable, to_remove_in: Optional[str] = None, replacement: Optional[Callable] = None, msg: Optional[str] = None, *args, **kwargs): """ Raises a deprecation error (or only prints a warning when USE_DEPRECATED is enabled), indicating to the caller that the decorated call should be removed, and updated to a new function/method call if applicable. Note: If you're deprecating a staticmethod or a classmethod, this decorator should be innermost, i.e. :: @staticmethod @deprecated() def foo(): pass See the tests for this module for examples of deprecating functions, methods, classmethods and staticmethods. :param func: deprecated function/method :param to_remove_in: Schrodinger core suite release version (XXXX-X) :param replacement: current function/method to use :param msg: specific message if default message is not desired """ def name(x): if isinstance(x, staticmethod) or isinstance(x, classmethod): x = x.__func__ return f"{x.__module__}.{x.__name__}" if not msg: msg = (f"function deprecated: {name(func)}\n" f"The requested call is deprecated in the Schrodinger" f" suite and is no longer available in this release.") if replacement: msg += (f"\nPlease update as:\n {name(replacement)}(...)") raise_deprecation_error(msg, to_remove_in) # If no exception was raised because the feature flag has been enabled return func(*args, **kwargs)
[docs]@contextmanager def enable_deprecated(): """Enables deprecated imports/functions within managed context""" with decorate_fflag.enable_feature_flag(USE_DEPRECATED): yield