diff --git a/requirements.txt b/requirements.txt index 85b83b0a6..80ca73fc2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,6 +22,7 @@ nibabel==4.0.* nilearn==0.9.* numpy==1.23.* openpyxl==3.0.* +packaging == 23.2.* Pillow==10.0.* pybids==0.15.* pyparsing==3.0.* @@ -41,7 +42,6 @@ trimeshpy==0.0.2 vtk==9.2.* # Dipy requirements h5py>=2.8.0 -packaging>=19.0 tqdm>=4.30.0 -e git+https://github.com/scilus/hot_dipy@1.8.0.dev0#egg=dipy \ No newline at end of file diff --git a/scilpy/io/deprecator.py b/scilpy/io/deprecator.py new file mode 100644 index 000000000..d46ec1c81 --- /dev/null +++ b/scilpy/io/deprecator.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +from dipy.utils.deprecator import cmp_pkg_version, ExpiredDeprecationError +from functools import wraps +from importlib import metadata +from packaging.version import parse +import warnings + + +class ScilpyExpiredDeprecation(ExpiredDeprecationError): + pass + + +DEFAULT_SEPARATOR = "=" +DEFAULT_DEPRECATION_WINDOW = 2 # Wait for 2 minor releases before rasing error + +DEPRECATION_HEADER = """ +!!! WARNING !!! THIS SCRIPT IS DEPRECATED !!! +""" + +DEPRECATION_FOOTER = """ +AS OF VERSION {EXP_VERSION}, CALLING THIS SCRIPT WILL RAISE {EXP_ERROR} +""" + +EXPIRATION_FOOTER = """ +SCRIPT {SCRIPT_NAME} HAS BEEN REMOVED SINCE {EXP_VERSION} +""" + +SEPARATOR_BLOCK = """ +{UP_SEPARATOR} +{MESSAGE} +{LOW_SEPARATOR} +""" + + +def _block(_msg, _sep_len=80): + _sep = f"{DEFAULT_SEPARATOR * _sep_len}" + return SEPARATOR_BLOCK.format( + UP_SEPARATOR=_sep, MESSAGE=_msg, LOW_SEPARATOR=_sep) + + +def _header(_msg, _sep_len=80): + _sep = f"{DEFAULT_SEPARATOR * _sep_len}" + return SEPARATOR_BLOCK.format( + UP_SEPARATOR=_sep, MESSAGE=_msg, LOW_SEPARATOR="").rstrip("\n") + + +def _raise_warning(header, footer, func, *args, **kwargs): + warnings.simplefilter('always', DeprecationWarning) + warnings.warn(header, DeprecationWarning, stacklevel=4) + + try: + return func(*args, **kwargs) + finally: + print("") + warnings.warn(footer, DeprecationWarning, stacklevel=4) + + +def deprecate_script(script, message, from_version): + from_version = parse(from_version) + expiration_minor = from_version.minor + DEFAULT_DEPRECATION_WINDOW + expiration_version = f"{from_version.major}.{expiration_minor}.0" + current_version = metadata.version('scilpy') + + def _deprecation_decorator(func): + @wraps(func) + def _wrapper(*args, **kwargs): + if cmp_pkg_version(current_version, expiration_version) >= 0: + footer = f"""\ + {message} + {EXPIRATION_FOOTER.format( + SCRIPT_NAME=script, + EXP_VERSION=expiration_version)}\ + """ + + raise ScilpyExpiredDeprecation(f"""\ + {_header(DEPRECATION_HEADER)} + {_block(footer)} + """) + else: + header = DEPRECATION_HEADER + footer = f"""\ + {message} + {DEPRECATION_FOOTER.format( + EXP_VERSION=expiration_version, + EXP_ERROR=ScilpyExpiredDeprecation)}\ + """ + + msg_length = max( + len(_l) for _l in (header + footer).splitlines()) + + return _raise_warning(_block(header, msg_length), + _block(footer, msg_length), + func, *args, **kwargs) + + return _wrapper + + return _deprecation_decorator diff --git a/scilpy/version.py b/scilpy/version.py index a79c64e19..5a283f48e 100644 --- a/scilpy/version.py +++ b/scilpy/version.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- +import itertools import glob +import os # Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" _version_major = 1 @@ -82,6 +84,9 @@ MINOR = _version_minor MICRO = _version_micro VERSION = __version__ -SCRIPTS = glob.glob("scripts/*.py") +LEGACY_SCRIPTS = filter(lambda s: not os.path.basename(s) == "__init__.py", + glob.glob("scripts/legacy/*.py")) +SCRIPTS = filter(lambda s: not os.path.basename(s) == "__init__.py", + glob.glob("scripts/*.py")) PREVIOUS_MAINTAINERS=["Jean-Christophe Houde"] diff --git a/scripts/legacy/__init__.py b/scripts/legacy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/legacy/scil_streamlines_math.py b/scripts/legacy/scil_streamlines_math.py new file mode 100755 index 000000000..7e1fac95e --- /dev/null +++ b/scripts/legacy/scil_streamlines_math.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from scilpy.io.deprecator import deprecate_script +from scripts.scil_tractogram_math import main as new_main + + +DEPRECATION_MSG = """ +This script has been renamed scil_tractogram_math.py. Please change +your existing pipelines accordingly. We will try to keep the following +convention: + +- Scripts on 'streamlines' treat each streamline individually. +- Scripts on 'tractograms' apply the same operation on all streamlines of the + tractogram. +""" + + +@deprecate_script("scil_streamlines_math.py", DEPRECATION_MSG, '1.7.0') +def main(): + new_main() + + +if __name__ == "__main__": + main() diff --git a/scripts/scil_streamlines_math.py b/scripts/scil_streamlines_math.py deleted file mode 100755 index 3723fb159..000000000 --- a/scripts/scil_streamlines_math.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import warnings - -from scripts.scil_tractogram_math import main as new_main - - -def main(): - warnings.simplefilter('always', DeprecationWarning) - warnings.warn( - "\n\n*** WARNING ***\n" - "This script will soon be renamed scil_tractogram_math.py.\n" - "You should change your existing pipelines accordingly.\n" - "We will try to keep the following convention: Scripts on " - "'streamlines' treat each streamline individually.\nScripts on " - "'tractograms' apply the same operation on all streamlines of the " - "tractogram.\n\n", - DeprecationWarning, - stacklevel=3) - - new_main() - - -if __name__ == "__main__": - main() diff --git a/setup.py b/setup.py index f77000a91..4c368e7bf 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,9 @@ import os -from setuptools import setup, find_packages, Extension +from setuptools import setup, find_packages, Extension, Command from setuptools.command.build_ext import build_ext +from setuptools.command.install_scripts import install_scripts +from setuptools.errors import SetupError with open('requirements.txt') as f: required_dependencies = f.read().splitlines() @@ -64,7 +66,9 @@ def run(self): platforms=PLATFORMS, version=VERSION, packages=find_packages(), - cmdclass={'build_ext': CustomBuildExtCommand}, + cmdclass={ + 'build_ext': CustomBuildExtCommand + }, ext_modules=get_extensions(), python_requires=PYTHON_VERSION, setup_requires=['cython', 'numpy'], @@ -72,7 +76,10 @@ def run(self): entry_points={ 'console_scripts': ["{}=scripts.{}:main".format( os.path.basename(s), - os.path.basename(s).split(".")[0]) for s in SCRIPTS] + os.path.basename(s).split(".")[0]) for s in SCRIPTS] + + ["{}=scripts.legacy.{}:main".format( + os.path.basename(s), + os.path.basename(s).split(".")[0]) for s in LEGACY_SCRIPTS] }, data_files=[('data/LUT', ["data/LUT/freesurfer_desikan_killiany.json",