Commit a2feb339 authored by Jose Borreguero's avatar Jose Borreguero
Browse files

deprecator_alias and alias deprecated method


Signed-off-by: default avatarJose Borreguero <borreguero@gmail.com>
parent e328e392
......@@ -168,12 +168,14 @@ public:
/// Function to return all of the seeAlso (these are not validated) algorithms
/// related to this algorithm.A default implementation is provided.
const std::vector<std::string> seeAlso() const override { return {}; };
/// Function to return any aliases to the algorithm; A default implementation
/// is provided
/// function to return any aliases to the algorithm; A default implementation is provided
const std::string alias() const override { return ""; }
/// Flag to indicate if the algorithm is called by its alias.
bool calledByAlias = false;
/// Expiration date (in ISO8601 format) for the algorithm aliases; default implementation for no expiration date
const std::string aliasDeprecated() const override { return ""; }
/// function to return URL for algorithm documentation; A default
/// implementation is provided.
/// Override if the algorithm is not part of the Mantid distribution.
......
......@@ -68,6 +68,9 @@ public:
/// Function to return all of the seeAlso algorithms related to this algorithm
virtual const std::vector<std::string> seeAlso() const = 0;
/// Expiration date (in ISO8601 format) for the algorithm aliases. Empty if no expiration date
virtual const std::string aliasDeprecated() const = 0;
/// function to return any aliases of the algorithm.
virtual const std::string alias() const = 0;
......
......@@ -183,6 +183,12 @@ loading.multifilelimit =
# Hide algorithms that use a Property Manager by default.
algorithms.categories.hidden=Workflow\\Inelastic\\UsesPropertyManager;Workflow\\SANS\\UsesPropertyManager;DataHandling\\LiveData\\Support;Deprecated;Utility\\Development;Remote
# Response upon invoking an algorithm with its alias, when the alias is deprecated.
# Allowed values are:
# "Warn": log a warning indicating the alias is deprecated, along with the name to be used
# "Raise": raise a RuntimeError if the deprecated deadline has been met
algorithms.alias.deprecated = Warn
# All interface categories are shown by default.
interfaces.categories.hidden =
......
......@@ -54,8 +54,13 @@ public:
/// Returns seeAlso related algorithms.
const std::vector<std::string> seeAlso() const override;
/// Allow the method returning the algorithm aliases to be overridden
const std::string alias() const override;
/// Returns optional documentation URL of the algorithm
/// Allow the method returning the expiration date (in ISO8601 format) for the algorithm aliases to be overridden
const std::string aliasDeprecated() const override;
const std::string helpURL() const override;
/// Allow the isRunning method to be overridden
bool isRunning() const override;
......
......@@ -365,6 +365,9 @@ void export_ialgorithm() {
class_<IAlgorithm, bases<IPropertyManager>, boost::noncopyable>("IAlgorithm", "Interface for all algorithms", no_init)
.def("name", &IAlgorithm::name, arg("self"), "Returns the name of the algorithm")
.def("alias", &IAlgorithm::alias, arg("self"), "Return the aliases for the algorithm")
.def("aliasDeprecated", &IAlgorithm::aliasDeprecated, arg("self"),
"Deprecation date (in ISO8601 format) for the algorithm aliases. "
"Returns empty string if no deprecation date")
.def("version", &IAlgorithm::version, arg("self"), "Returns the version number of the algorithm")
.def("cancel", &IAlgorithm::cancel, arg("self"), "Request that the algorithm stop running")
.def("category", &IAlgorithm::category, arg("self"), "Returns the category containing the algorithm")
......
......@@ -118,6 +118,18 @@ template <typename BaseAlgorithm> const std::string AlgorithmAdapter<BaseAlgorit
}
}
/**
* Returns the expiration date (in ISO8601 format) of algorithm aliases. If not overridden, returns the
* base algorithm implementation
*/
template <typename BaseAlgorithm> const std::string AlgorithmAdapter<BaseAlgorithm>::aliasDeprecated() const {
try {
return callMethod<std::string>(getSelf(), "aliasDeprecated");
} catch (UndefinedAttributeError &) {
return BaseAlgorithm::aliasDeprecated();
}
}
/**
* Returns the summary of the algorithm. If not overridden
* it returns defaultSummary
......
......@@ -29,7 +29,8 @@ Importing this module starts the FrameworkManager instance.
# std libs
from collections import OrderedDict, namedtuple
from contextlib import contextmanager
import inspect
import datetime
from dateutil.parser import parse as parse_date
import os
import sys
......@@ -37,6 +38,7 @@ import mantid
# This is a simple API so give access to the aliases by default as well
from mantid import __gui__, api as _api, kernel as _kernel, apiVersion
from mantid.kernel import plugins as _plugin_helper
from mantid.kernel import ConfigService, logger
from mantid.kernel.funcinspect import customise_func as _customise_func, lhs_info as _lhs_info, \
replace_signature as _replace_signature, LazyFunctionSignature
......@@ -963,7 +965,7 @@ def set_properties(alg_object, *args, **kwargs):
do_set_property(key, value)
def _create_algorithm_function(name, version, algm_object):
def _create_algorithm_function(name, version, algm_object): # noqa: C901
"""
Create a function that will set up and execute an algorithm.
The help that will be displayed is that of the most recent version.
......@@ -972,12 +974,34 @@ def _create_algorithm_function(name, version, algm_object):
:param algm_object: the created algorithm object.
"""
def algorithm_wrapper():
"""
Creates a wrapper object around the algorithm functions.
def algorithm_wrapper(alias=None):
r"""
@brief Creates a wrapper object around the algorithm functions.
@param str alias: Non-empty when the algorithm is to be invoked with this alias instead of its name.
Default `None` indicates an alias is not being used.
"""
class Wrapper:
__slots__ = ["__name__", "__signature__"]
__slots__ = ["__name__", "__signature__", "_alias"]
@staticmethod
def _init_alias(algm_alias):
r"""
@brief Encapsulate alias features on a namedtuple
@param str algm_alias
"""
deprecated = algm_object.aliasDeprecated() # non-empty string when alias set to be deprecated
if deprecated:
try:
parse_date(deprecated)
except ValueError:
deprecated = ''
logger.error(f'Alias deprecation date {deprecated} must be in ISO8601 format')
AlgorithmAlias = namedtuple('AlgorithmAlias', 'name, deprecated')
return AlgorithmAlias(algm_alias, deprecated)
def __init__(self, algm_alias=None):
self._alias = self._init_alias(algm_alias) if algm_alias else None
def __getattribute__(self, item):
obj = object.__getattribute__(self, item)
......@@ -999,6 +1023,14 @@ def _create_algorithm_function(name, version, algm_object):
If both startProgress and endProgress are supplied they will
be used.
"""
# Check at runtime whether to throw upon alias deprecation.
if self._alias and self._alias.deprecated:
deprecated = parse_date(self._alias.deprecated) < datetime.datetime.today()
deprecated_action = ConfigService.Instance().get('algorithms.alias.deprecated', 'Warn').lower()
if deprecated and deprecated_action == 'raise':
raise RuntimeError(f'Use of algorithm alias {self._alias.name} not allowed. Use {name} instead')
_version = version
if "Version" in kwargs:
_version = kwargs["Version"]
......@@ -1047,21 +1079,23 @@ def _create_algorithm_function(name, version, algm_object):
msg = '{}-v{}: {}'.format(algm.name(), algm.version(), str(e))
raise RuntimeError(msg) from e
if self._alias and self._alias.deprecated:
logger.error(f'Algorithm alias {self._alias.name} is deprecated. Use {name} instead')
return _gather_returns(name, lhs, algm)
# Set the signature of the callable to be one that is only generated on request.
Wrapper.__call__.__signature__ = LazyFunctionSignature(alg_name=name)
return Wrapper()
# enddef
# Insert definition in to global dict
algm_wrapper = algorithm_wrapper()
algm_wrapper.__name__ = name
wrapper = Wrapper(algm_alias=alias)
wrapper.__name__ = name
return wrapper
globals()[name] = algm_wrapper
# Register aliases - split on whitespace
for alias in algm_object.alias().strip().split():
globals()[alias] = algm_wrapper
# endfor
globals()[alias] = algorithm_wrapper(alias=alias)
algm_wrapper = algorithm_wrapper()
globals()[name] = algorithm_wrapper() # Insert definition in to global dict
return algm_wrapper
......
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# package imports
from mantid.kernel import ConfigService, logger
# third-party imports
from dateutil.parser import parse as parse_date
# standard imports
import functools
def deprecated_alias(deprecation_date): # decorator factory
r"""
:brief: Class decorator marking the algorithm's alias as deprecated
:details: It's assumed that the algorithm implements the alias method
:param str deprecation_date: date of deprecation for the alias, in ISO8601 format
"""
try:
parse_date(deprecation_date)
except ValueError:
logger.error(f'Alias deprecation date {deprecation_date} must be in ISO8601 format')
def decorator_instance(cls):
cls_aliasDeprecated = cls.aliasDeprecated
@functools.wraps(cls_aliasDeprecated)
def new_aliasDeprecated(self):
return deprecation_date
cls.aliasDeprecated = new_aliasDeprecated
return cls
return decorator_instance
def deprecated_algorithm(new_name, deprecation_date): # decorator factory
r"""
:brief: Class decorator marking the algorithm as deprecated
:param str new_name: Algorithm's name that should be used in place of this algorithm
:param str deprecation_date: date of deprecation for this algorithm, in ISO8601 format
"""
try:
parse_date(deprecation_date)
except ValueError:
logger.error(f'Algorithm deprecation date {deprecation_date} must be in ISO8601 format')
def decorator_instance(cls):
depr_msg = f'Algorithm "{cls.__name__}" is deprecated since {deprecation_date}. Use "{new_name}" instead'
raise_msg = f'Configuration "algorithms.deprecated" set to raise upon use of deprecated algorithms'
cls_init = cls.__init__
@functools.wraps(cls_init)
def new_init(self, *args, **kwargs):
r"""decorate __init__ to possibly raise"""
if ConfigService.Instance()['algorithms.deprecated'] == 'Raise':
logger.error(depr_msg)
raise RuntimeError(raise_msg)
cls_init(self, *args, **kwargs)
cls.__init__ = new_init
cls_PyExec = cls.PyExec
@functools.wraps(cls_PyExec)
def new_PyExec(self):
r"""decorate PyExec to throw an error message"""
cls_PyExec(self)
logger.error(depr_msg) # show error after execution
cls.PyExec = new_PyExec
return cls
return decorator_instance
......@@ -5,7 +5,7 @@
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# 3rd party
# package imports
from mantid import config, FileFinder
# standard
......
......@@ -36,6 +36,11 @@ General properties
| ``algorithms.categories.hidden`` | A comma separated list of any categories of | ``Muons,Testing`` |
| | algorithms that should be hidden in Mantid. | |
+----------------------------------+--------------------------------------------------+------------------------+
| ``algorithms.alias.expired`` | Action upon invoking the algorithm via one of | ``Warn`` or ``Raise`` |
| | its alias when the grace period for use of the | |
| | aliases has already expired. | |
| | ``Raise`` will cause a ``RuntimError``. | |
+----------------------------------+--------------------------------------------------+------------------------+
| ``curvefitting.guiExclude`` | A semicolon separated list of function names | ``ExpDecay;Gaussian;`` |
| | that should be hidden in Mantid. | |
+----------------------------------+--------------------------------------------------+------------------------+
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment