diff --git a/ignite/contrib/engines/common.py b/ignite/contrib/engines/common.py index c38f6f4dc8bd..97fedbcbc16e 100644 --- a/ignite/contrib/engines/common.py +++ b/ignite/contrib/engines/common.py @@ -30,6 +30,7 @@ from ignite.handlers import Checkpoint, DiskSaver, EarlyStopping, TerminateOnNan from ignite.handlers.checkpoint import BaseSaveHandler from ignite.metrics import RunningAverage +from ignite.utils import deprecated def setup_common_training_handlers( @@ -274,11 +275,21 @@ def empty_cuda_cache(_: Engine) -> None: gc.collect() -def setup_any_logging(logger, logger_module, trainer, optimizers, evaluators, log_every_iters) -> None: # type: ignore - raise DeprecationWarning( - "ignite.contrib.engines.common.setup_any_logging is deprecated since 0.4.0. and will be remove in 0.6.0. " - "Please use instead: setup_tb_logging, setup_visdom_logging or setup_mlflow_logging etc." - ) +@deprecated( + "0.4.0", + "0.6.0", + ("Please use instead: setup_tb_logging, setup_visdom_logging or setup_mlflow_logging etc.",), + raise_exception=True, +) +def setup_any_logging( + logger: BaseLogger, + logger_module: Any, + trainer: Engine, + optimizers: Optional[Union[Optimizer, Dict[str, Optimizer], Dict[None, Optimizer]]], + evaluators: Optional[Union[Engine, Dict[str, Engine]]], + log_every_iters: int, +) -> None: + pass def _setup_logging( diff --git a/ignite/utils.py b/ignite/utils.py index fcee45a166d2..b21f9c79b3b0 100644 --- a/ignite/utils.py +++ b/ignite/utils.py @@ -1,8 +1,10 @@ import collections.abc as collections +import functools import logging import random import sys -from typing import Any, Callable, Optional, TextIO, Tuple, Type, Union, cast +import warnings +from typing import Any, Callable, Dict, Optional, TextIO, Tuple, Type, TypeVar, Union, cast import torch @@ -171,3 +173,34 @@ def manual_seed(seed: int) -> None: np.random.seed(seed) except ImportError: pass + + +def deprecated( + deprecated_in: str, removed_in: str = "", reasons: Tuple[str, ...] = (), raise_exception: bool = False +) -> Callable: + + F = TypeVar("F", bound=Callable[..., Any]) + + def decorator(func: F) -> F: + func_doc = func.__doc__ if func.__doc__ else "" + deprecation_warning = ( + f"This function has been deprecated since version {deprecated_in}" + + (f" and will be removed in version {removed_in}" if removed_in else "") + + ".\n Please refer to the documentation for more details." + ) + + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Callable: + if raise_exception: + raise DeprecationWarning(deprecation_warning) + warnings.warn(deprecation_warning, DeprecationWarning, stacklevel=2) + return func(*args, **kwargs) + + appended_doc = f".. deprecated:: {deprecated_in}" + ("\n\n\t" if len(reasons) else "") + + for reason in reasons: + appended_doc += "\n\t- " + reason + wrapper.__doc__ = f"**Deprecated function**.\n\n {func_doc}{appended_doc}" + return cast(F, wrapper) + + return decorator diff --git a/tests/ignite/contrib/engines/test_common.py b/tests/ignite/contrib/engines/test_common.py index f87122cced4b..fcdc3831e1ba 100644 --- a/tests/ignite/contrib/engines/test_common.py +++ b/tests/ignite/contrib/engines/test_common.py @@ -274,7 +274,7 @@ def set_eval_metric(engine): def test_deprecated_setup_any_logging(): - with pytest.raises(DeprecationWarning, match=r"is deprecated since 0\.4\.0\."): + with pytest.raises(DeprecationWarning, match=r"deprecated since version 0.4.0"): setup_any_logging(None, None, None, None, None, None) diff --git a/tests/ignite/test_utils.py b/tests/ignite/test_utils.py index 1b523e8691d6..21d8ae5b4671 100644 --- a/tests/ignite/test_utils.py +++ b/tests/ignite/test_utils.py @@ -1,13 +1,14 @@ import logging import os import sys +import warnings from collections import namedtuple import pytest import torch from ignite.engine import Engine, Events -from ignite.utils import convert_tensor, setup_logger, to_onehot +from ignite.utils import convert_tensor, deprecated, setup_logger, to_onehot def test_convert_tensor(): @@ -150,3 +151,72 @@ def _(_): # Needed by windows to release FileHandler in the loggers logging.shutdown() + + +def test_deprecated(): + + # Test on function without docs, @deprecated without reasons + @deprecated("0.4.2", "0.6.0") + def func_no_docs(): + return 24 + + assert func_no_docs.__doc__ == "**Deprecated function**.\n\n .. deprecated:: 0.4.2" + + # Test on function with docs, @deprecated without reasons + @deprecated("0.4.2", "0.6.0") + def func_no_reasons(): + """Docs are cool + """ + return 24 + + assert func_no_reasons.__doc__ == "**Deprecated function**.\n\n Docs are cool\n .. deprecated:: 0.4.2" + + # Test on function with docs, @deprecated with reasons + @deprecated("0.4.2", "0.6.0", reasons=("r1", "r2")) + def func_no_warnings(): + """Docs are very cool + """ + return 24 + + assert ( + func_no_warnings.__doc__ + == "**Deprecated function**.\n\n Docs are very cool\n .. deprecated:: 0.4.2\n\n\t\n\t- r1\n\t- r2" + ) + + # Tests that the function emits DeprecationWarning + @deprecated("0.4.2", "0.6.0", reasons=("r1", "r2")) + def func_check_warning(): + """Docs are very ... + """ + return 24 + + with pytest.deprecated_call(): + func_check_warning() + assert func_check_warning() == 24 + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + func_check_warning() + # Verify some things + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert ( + "This function has been deprecated since version 0.4.2 and will be removed in version 0.6.0." + + "\n Please refer to the documentation for more details." + in str(w[-1].message) + ) + + # Test that the function raises Exception + @deprecated("0.4.2", "0.6.0", reasons=("reason1", "reason2"), raise_exception=True) + def func_with_everything(): + return 1 + + with pytest.raises(Exception) as exec_info: + func_with_everything() + + assert ( + str(exec_info.value) + == "This function has been deprecated since version 0.4.2 and will be removed in version 0.6.0." + + "\n Please refer to the documentation for more details." + )