From ad1122c5fe627e542a42a40e8cf9c0ac04ec6f21 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:40:27 +0100 Subject: [PATCH 01/26] Combine information into pyproject.toml Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- pyproject.toml | 214 +++++++++++++++++++++++++++++++++++++- setup.cfg | 271 ------------------------------------------------- 2 files changed, 212 insertions(+), 273 deletions(-) delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index 325622b66a7..add1f4a6105 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,222 @@ + [build-system] requires = [ - "wheel", - "setuptools", + "setuptools>=69", + "wheel", + "versioneer[toml]", "more-itertools>=8.0", "torch>=2.8.0", "ninja", "packaging" ] +build-backend = "setuptools.build_meta" + +[project] +name = "monai" +description = "AI Toolkit for Healthcare Imaging" +readme = { file = "README.md", content-type = "text/markdown" } +requires-python = ">=3.10" +license = { text = "Apache License 2.0" } +authors = [{ name = "MONAI Consortium", email = "monai.contact@gmail.com" }] +classifiers = [ + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Intended Audience :: Healthcare Industry", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Medical Science Apps.", + "Topic :: Scientific/Engineering :: Information Analysis", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Typing :: Typed", +] +dependencies = [ + "torch>=2.8.0", + "numpy>=1.24,<3.0", +] + +[project.urls] +Homepage = "https://project-monai.github.io/" +Documentation = "https://monai.readthedocs.io/" +"Bug Tracker" = "https://github.com/Project-MONAI/MONAI/issues" +"Source Code" = "https://github.com/Project-MONAI/MONAI" + +[project.optional-dependencies] +all = [ + "nibabel", + "ninja", + "scikit-image>=0.14.2", + "scipy>=1.12.0", + "pillow", + "tensorboard", + "gdown>=4.7.3", + "pytorch-ignite==0.4.11", + "torchio", + "torchvision", + "itk>=5.2", + "tqdm>=4.47.0", + "lmdb", + "psutil", + "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", + "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'", + "openslide-python", + "openslide-bin", + "tifffile; platform_system == 'Linux' or platform_system == 'Darwin'", + "imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'", + "pandas", + "einops", + "transformers>=4.53.0", + "mlflow>=2.12.2,<3.13", + "clearml>=1.10.0rc0", + "matplotlib>=3.6.3", + "tensorboardX", + "pyyaml", + "fire", + "jsonschema", + "pynrrd", + "pydicom", + "h5py", + "nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine", + "optuna", + "onnx>=1.13.0", + "onnxruntime", + "zarr", + "lpips==0.1.4", + "nvidia-ml-py", + "huggingface_hub", + "pyamg>=5.0.0,<5.3.0", +] +nibabel = ["nibabel"] +ninja = ["ninja"] +skimage = ["scikit-image>=0.14.2"] +scipy = ["scipy>=1.12.0"] +pillow = ["pillow!=8.3.0"] +tensorboard = ["tensorboard"] +gdown = ["gdown>=4.7.3"] +ignite = ["pytorch-ignite==0.4.11"] +torchio = ["torchio"] +torchvision = ["torchvision"] +itk = ["itk>=5.2"] +tqdm = ["tqdm>=4.47.0"] +lmdb = ["lmdb"] +psutil = ["psutil"] +cucim = [ + "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", + "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'", +] +openslide = ["openslide-python", "openslide-bin"] +tifffile = ["tifffile; platform_system == 'Linux' or platform_system == 'Darwin'"] +imagecodecs = ["imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'"] +pandas = ["pandas"] +einops = ["einops"] +hyena = ["nvsubquadratic>=0.1.1"] +transformers = ["transformers>=4.36.0,<4.41.0; python_version <= '3.10'"] +mlflow = ["mlflow>=2.12.2,<3.13"] +matplotlib = ["matplotlib>=3.6.3"] +clearml = ["clearml"] +tensorboardX = ["tensorboardX"] +pyyaml = ["pyyaml"] +fire = ["fire"] +packaging = ["packaging"] +jsonschema = ["jsonschema"] +pynrrd = ["pynrrd"] +pydicom = ["pydicom"] +h5py = ["h5py"] +nni = ["nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine"] +optuna = ["optuna"] +onnx = ["onnx>=1.13.0", "onnxruntime; python_version <= '3.10'"] +zarr = ["zarr"] +lpips = ["lpips==0.1.4"] +pynvml = ["nvidia-ml-py"] +polygraphy = ["polygraphy"] +huggingface_hub = ["huggingface_hub"] +pyamg = ["pyamg>=5.0.0,<5.3.0"] + +[tool.setuptools] +license-files = ["LICENSE"] + +[tool.versioneer] +VCS = "git" +style = "pep440" +versionfile_source = "monai/_version.py" +versionfile_build = "monai/_version.py" +tag_prefix = "" +parentdir_prefix = "" + +[tool.isort] +known_first_party = ["monai"] +profile = "black" +line_length = 120 +skip = [".git", ".eggs", "venv", ".venv", "versioneer.py", "_version.py", "conf.py", "monai/__init__.py"] +skip_glob = ["*.pyi"] +add_imports = ["from __future__ import annotations"] +append_only = true + +[tool.mypy] +ignore_missing_imports = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = false +warn_return_any = true +strict_equality = true +show_column_numbers = true +show_error_codes = true +pretty = false +warn_unused_configs = true +extra_checks = true +exclude = ["venv/"] + +[[tool.mypy.overrides]] +module = ["versioneer", "monai._version", "monai.eggs"] +ignore_errors = true + +[[tool.mypy.overrides]] +module = ["monai.*"] +check_untyped_defs = true +disallow_untyped_decorators = true + +[[tool.mypy.overrides]] +module = [ + "monai.visualize.*", + "monai.utils.*", + "monai.optimizers.*", + "monai.losses.*", + "monai.inferers.*", + "monai.config.*", + "monai._extensions.*", + "monai.fl.*", + "monai.engines.*", + "monai.handlers.*", + "monai.auto3dseg.*", + "monai.bundle.*", +] +disallow_incomplete_defs = true + +[tool.coverage.run] +concurrency = ["multiprocessing"] +source = ["."] +data_file = ".coverage/.coverage" +omit = ["setup.py"] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", + "raise NotImplementedError", + "if __name__ == .__main__.:", +] +show_missing = true +skip_covered = true + +[tool.coverage.xml] +output = "coverage.xml" [tool.black] line-length = 120 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c025c685f4d..00000000000 --- a/setup.cfg +++ /dev/null @@ -1,271 +0,0 @@ -[metadata] -name = monai -author = MONAI Consortium -author_email = monai.contact@gmail.com -url = https://project-monai.github.io/ -description = AI Toolkit for Healthcare Imaging -long_description = file:README.md -long_description_content_type = text/markdown; charset=UTF-8 -platforms = OS Independent -license = Apache License 2.0 -license_files = - LICENSE -project_urls = - Documentation=https://monai.readthedocs.io/ - Bug Tracker=https://github.com/Project-MONAI/MONAI/issues - Source Code=https://github.com/Project-MONAI/MONAI -classifiers = - Intended Audience :: Developers - Intended Audience :: Education - Intended Audience :: Science/Research - Intended Audience :: Healthcare Industry - Programming Language :: C++ - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Programming Language :: Python :: 3.13 - Topic :: Scientific/Engineering - Topic :: Scientific/Engineering :: Artificial Intelligence - Topic :: Scientific/Engineering :: Medical Science Apps. - Topic :: Scientific/Engineering :: Information Analysis - Topic :: Software Development - Topic :: Software Development :: Libraries - Typing :: Typed - -[options] -python_requires = >= 3.10 -# for compiling and develop setup only -# no need to specify the versions so that we could -# compile for multiple targeted versions. -setup_requires = - torch - ninja - packaging -install_requires = - torch>=2.8.0 - numpy>=1.24,<3.0 - -[options.extras_require] -all = - nibabel - ninja - scikit-image>=0.14.2 - scipy>=1.12.0 - pillow - tensorboard - gdown>=4.7.3 - pytorch-ignite==0.4.11 - torchio - torchvision - itk>=5.2 - tqdm>=4.47.0 - lmdb - psutil - cucim-cu12; platform_system == "Linux" and python_version <= '3.10' - cucim-cu13; platform_system == "Linux" and python_version >= '3.11' - openslide-python - openslide-bin - tifffile; platform_system == "Linux" or platform_system == "Darwin" - imagecodecs; platform_system == "Linux" or platform_system == "Darwin" - pandas - einops - transformers>=4.53.0 - mlflow>=2.12.2,<3.13 - clearml>=1.10.0rc0 - matplotlib>=3.6.3 - tensorboardX - pyyaml - fire - jsonschema - pynrrd - pydicom - h5py - nni; platform_system == "Linux" and "arm" not in platform_machine and "aarch" not in platform_machine - optuna - onnx>=1.13.0 - onnxruntime - zarr - lpips==0.1.4 - nvidia-ml-py - huggingface_hub - pyamg>=5.0.0, <5.3.0 -nibabel = - nibabel -ninja = - ninja -skimage = - scikit-image>=0.14.2 -scipy = - scipy>=1.12.0 -pillow = - pillow!=8.3.0 -tensorboard = - tensorboard -gdown = - gdown>=4.7.3 -ignite = - pytorch-ignite==0.4.11 -torchio = - torchio -torchvision = - torchvision -itk = - itk>=5.2 -tqdm = - tqdm>=4.47.0 -lmdb = - lmdb -psutil = - psutil -cucim = - cucim-cu12; platform_system == "Linux" and python_version <= '3.10' - cucim-cu13; platform_system == "Linux" and python_version >= '3.11' -openslide = - openslide-python - openslide-bin -tifffile = - tifffile; platform_system == "Linux" or platform_system == "Darwin" -imagecodecs = - imagecodecs; platform_system == "Linux" or platform_system == "Darwin" -pandas = - pandas -einops = - einops -hyena = - nvsubquadratic>=0.1.1 -transformers = - transformers>=4.36.0, <4.41.0; python_version <= '3.10' -mlflow = - mlflow>=2.12.2,<3.13 -matplotlib = - matplotlib>=3.6.3 -clearml = - clearml -tensorboardX = - tensorboardX -pyyaml = - pyyaml -fire = - fire -packaging = - packaging -jsonschema = - jsonschema -pynrrd = - pynrrd -pydicom = - pydicom -h5py = - h5py -nni = - nni; platform_system == "Linux" and "arm" not in platform_machine and "aarch" not in platform_machine -optuna = - optuna -onnx = - onnx>=1.13.0 - onnxruntime; python_version <= '3.10' -zarr = - zarr -lpips = - lpips==0.1.4 -pynvml = - nvidia-ml-py -polygraphy = - polygraphy - -# # workaround https://github.com/Project-MONAI/MONAI/issues/5882 -# MetricsReloaded = - # MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support#egg=MetricsReloaded -huggingface_hub = - huggingface_hub -pyamg = - pyamg>=5.0.0, <5.3.0 -# segment-anything = -# segment-anything @ git+https://github.com/facebookresearch/segment-anything@6fdee8f2727f4506cfbbe553e23b895e27956588#egg=segment-anything - -[isort] -known_first_party = monai -profile = black -line_length = 120 -skip = .git, .eggs, venv, .venv, versioneer.py, _version.py, conf.py, monai/__init__.py -skip_glob = *.pyi -add_imports = from __future__ import annotations -append_only = true - -[versioneer] -VCS = git -style = pep440 -versionfile_source = monai/_version.py -versionfile_build = monai/_version.py -tag_prefix = -parentdir_prefix = - -[mypy] -# Suppresses error messages about imports that cannot be resolved. -ignore_missing_imports = True -# Changes the treatment of arguments with a default value of None by not implicitly making their type Optional. -no_implicit_optional = True -# Warns about casting an expression to its inferred type. -warn_redundant_casts = True -# No error on unneeded # type: ignore comments. -warn_unused_ignores = False -# Shows a warning when returning a value with type Any from a function declared with a non-Any return type. -warn_return_any = True -# Prohibit equality checks, identity checks, and container checks between non-overlapping types. -strict_equality = True -# Shows column numbers in error messages. -show_column_numbers = True -# Shows error codes in error messages. -show_error_codes = True -# Use visually nicer output in error messages: use soft word wrap, show source code snippets, and show error location markers. -pretty = False -# Warns about per-module sections in the config file that do not match any files processed when invoking mypy. -warn_unused_configs = True -# Make arguments prepended via Concatenate be truly positional-only. -extra_checks = True -# Allows variables to be redefined with an arbitrary type, -# as long as the redefinition is in the same block and nesting level as the original definition. -# allow_redefinition = True - -exclude = venv/ - -[mypy-versioneer] -# Ignores all non-fatal errors. -ignore_errors = True - -[mypy-monai._version] -# Ignores all non-fatal errors. -ignore_errors = True - -[mypy-monai.eggs] -# Ignores all non-fatal errors. -ignore_errors = True - -[mypy-monai.*] -# Also check the body of functions with no types in their type signature. -check_untyped_defs = True -# Warns about usage of untyped decorators. -disallow_untyped_decorators = True - -[mypy-monai.visualize.*,monai.utils.*,monai.optimizers.*,monai.losses.*,monai.inferers.*,monai.config.*,monai._extensions.*,monai.fl.*,monai.engines.*,monai.handlers.*,monai.auto3dseg.*,monai.bundle.*,monai.metrics.*,monai.apps.*] -disallow_incomplete_defs = True - -[coverage:run] -concurrency = multiprocessing -source = . -data_file = .coverage/.coverage -omit = setup.py - -[coverage:report] -exclude_lines = - pragma: no cover - if TYPE_CHECKING: - # Don't complain if tests don't hit code: - raise NotImplementedError - if __name__ == .__main__.: -show_missing = True -skip_covered = True - -[coverage:xml] -output = coverage.xml From f2b546084fad0ecfcfc1c5ad53131810543e4688 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:38:22 +0100 Subject: [PATCH 02/26] Moving all dependencies into the toml file, adding script to extract requirements Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- monai/config/print_dependencies.py | 59 +++++++++ pyproject.toml | 190 ++++++++++++++++++----------- requirements-dev.txt | 66 ---------- requirements-min.txt | 8 -- requirements.txt | 2 - 5 files changed, 176 insertions(+), 149 deletions(-) create mode 100644 monai/config/print_dependencies.py delete mode 100644 requirements-dev.txt delete mode 100644 requirements-min.txt delete mode 100644 requirements.txt diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py new file mode 100644 index 00000000000..a7c58d335ce --- /dev/null +++ b/monai/config/print_dependencies.py @@ -0,0 +1,59 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This program prints the MONAI dependencies for the optional names given on the command line. The printed values can +be piped to a requirements file to work with pip. All required dependencies are always printed, those for builing are +included in "build-system" is given as an argument, and all optional requirements are included if "*" is given. This +assumes the pyproject.toml file is in the current working directory. +""" + +import sys + +if sys.version_info.minor >= 11: + import tomllib + + load_func = tomllib.loads +else: + import tomlkit + + load_func = tomlkit.parse + + +BUILD_SYSTEM_KEY = "build-system" +PROJ_KEY = "project" +OPTS_KEY = "optional-dependencies" +DEP_KEY = "dependencies" +REQ_KEY = "requires" +TOML_FILE = "pyproject.toml" + +if __name__ == "__main__": + sections = set(sys.argv[1:]) + + with open(TOML_FILE, "rb") as o: + data = load_func(o.read()) + + proj = data[PROJ_KEY] + opts = proj[OPTS_KEY] + dependencies = proj[DEP_KEY] + + if BUILD_SYSTEM_KEY in sections: + sections.remove(BUILD_SYSTEM_KEY) + dependencies += data[BUILD_SYSTEM_KEY][REQ_KEY] + + if "*" in sections: + dependencies += sum(opts.values(), []) + else: + for s in sections: + dependencies += opts[s] + + for d in sorted(set(dependencies)): + print(d) diff --git a/pyproject.toml b/pyproject.toml index add1f4a6105..44830ad1133 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,14 @@ [build-system] requires = [ - "setuptools>=69", + "setuptools<71", # pkg_resources removed in setuptools>=71; needed by MetricsReloaded setup.py "wheel", "versioneer[toml]", "more-itertools>=8.0", "torch>=2.8.0", "ninja", - "packaging" + "packaging", + "backports.tarfile" # see #8792 ] build-backend = "setuptools.build_meta" @@ -50,94 +51,137 @@ Documentation = "https://monai.readthedocs.io/" [project.optional-dependencies] all = [ - "nibabel", - "ninja", - "scikit-image>=0.14.2", - "scipy>=1.12.0", - "pillow", - "tensorboard", - "gdown>=4.7.3", - "pytorch-ignite==0.4.11", - "torchio", - "torchvision", - "itk>=5.2", - "tqdm>=4.47.0", - "lmdb", - "psutil", + "black>=26.3.1", + "clearml>=1.10.0rc0", + "coverage>=5.5", "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'", - "openslide-python", - "openslide-bin", - "tifffile; platform_system == 'Linux' or platform_system == 'Darwin'", - "imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'", - "pandas", "einops", - "transformers>=4.53.0", - "mlflow>=2.12.2,<3.13", - "clearml>=1.10.0rc0", - "matplotlib>=3.6.3", - "tensorboardX", - "pyyaml", + "filelock<3.12.0", "fire", - "jsonschema", - "pynrrd", - "pydicom", + "gdown>=4.7.3", + "git+https://github.com/Project-MONAI/MetricsReloaded@monai-support#egg=MetricsReloaded", + "git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588", "h5py", - "nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine", - "optuna", - "onnx>=1.13.0", - "onnxruntime", - "zarr", + "huggingface_hub", + "imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'", + "isort>=5.1, <6, !=6.0.0", + "itk>=5.2", + "jsonschema", + "lmdb", "lpips==0.1.4", + "matplotlib>=3.6.3", + "mlflow>=2.12.2,<3.13", + "mypy>=1.5.0, <1.12.0", + "nibabel", + "ninja", + "nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine", "nvidia-ml-py", - "huggingface_hub", + "onnx_graphsurgeon", + "onnx>=1.13.0", + "onnxruntime; python_version <= '3.10'", + "onnxscript", + "openslide-bin", + "openslide-python", + "optuna", + "pandas", + "parameterized", + "pep8-naming", + "pillow!=8.3.0", + "polygraphy", + "pre-commit", + "psutil", "pyamg>=5.0.0,<5.3.0", + "pybind11", + "pycodestyle", + "pydicom", + "pyflakes", + "pynrrd", + "pytest", + "pytorch-ignite", + "pyyaml", + "requests", + "ruff>=0.14.11,<0.15", + "scikit-image>=0.19.0", + "scipy>=1.12.0", + "tensorboard>=2.12.0", + "tensorboardX", + "tifffile; platform_system == 'Linux' or platform_system == 'Darwin'", + "torchio", + "torchvision", + "tqdm>=4.47.0", + "transformers>=4.53.0, <5.0", + "typeguard<3", + "types-PyYAML", + "types-setuptools", + "zarr" ] -nibabel = ["nibabel"] -ninja = ["ninja"] -skimage = ["scikit-image>=0.14.2"] -scipy = ["scipy>=1.12.0"] -pillow = ["pillow!=8.3.0"] -tensorboard = ["tensorboard"] -gdown = ["gdown>=4.7.3"] -ignite = ["pytorch-ignite==0.4.11"] -torchio = ["torchio"] -torchvision = ["torchvision"] -itk = ["itk>=5.2"] -tqdm = ["tqdm>=4.47.0"] -lmdb = ["lmdb"] -psutil = ["psutil"] +black = ["black>=26.3.1"] +clearml = ["clearml>=1.10.0rc0"] cucim = [ - "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", - "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'", + "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", + "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'" ] -openslide = ["openslide-python", "openslide-bin"] -tifffile = ["tifffile; platform_system == 'Linux' or platform_system == 'Darwin'"] -imagecodecs = ["imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'"] -pandas = ["pandas"] einops = ["einops"] -hyena = ["nvsubquadratic>=0.1.1"] -transformers = ["transformers>=4.36.0,<4.41.0; python_version <= '3.10'"] -mlflow = ["mlflow>=2.12.2,<3.13"] -matplotlib = ["matplotlib>=3.6.3"] -clearml = ["clearml"] -tensorboardX = ["tensorboardX"] -pyyaml = ["pyyaml"] fire = ["fire"] -packaging = ["packaging"] -jsonschema = ["jsonschema"] -pynrrd = ["pynrrd"] -pydicom = ["pydicom"] +gdown = ["gdown>=4.7.3"] h5py = ["h5py"] -nni = ["nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine"] -optuna = ["optuna"] -onnx = ["onnx>=1.13.0", "onnxruntime; python_version <= '3.10'"] -zarr = ["zarr"] +huggingface_hub = ["huggingface_hub"] +hyena = ["nvsubquadratic>=0.1.1"] # omitted from all for compatibility +ignite = ["pytorch-ignite"] +imagecodecs = ["imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'"] +isort = ["isort>=5.1, <6, !=6.0.0"] +itk = ["itk>=5.2"] +jsonschema = ["jsonschema"] +lmdb = ["lmdb"] lpips = ["lpips==0.1.4"] -pynvml = ["nvidia-ml-py"] +matplotlib = ["matplotlib>=3.6.3"] +mccabe = ["mccabe"] +metrics_reloaded = ["git+https://github.com/Project-MONAI/MetricsReloaded@monai-support#egg=MetricsReloaded"] +mlflow = ["mlflow>=2.12.2,<3.13"] +mypy = ["mypy>=1.5.0, <1.12.0"] +nibabel = ["nibabel"] +ninja = ["ninja"] +nni = [ + "nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine", + "filelock<3.12.0" # https://github.com/microsoft/nni/issues/5523 +] +onnx = ["onnx>=1.13.0", "onnxruntime; python_version <= '3.10'", "onnx_graphsurgeon", "onnxscript"] +openslide = ["openslide-python", "openslide-bin"] +optuna = ["optuna"] +packaging = ["packaging"] +pandas = ["pandas"] +pep8-naming = ["pep8-naming"] +pillow = ["pillow!=8.3.0"] # https://github.com/python-pillow/Pillow/issues/5571 polygraphy = ["polygraphy"] -huggingface_hub = ["huggingface_hub"] +pre-commit = ["pre-commit"] +psutil = ["psutil"] pyamg = ["pyamg>=5.0.0,<5.3.0"] +pybind11 = ["pybind11"] +pycodestyle = ["pycodestyle"] +pydicom = ["pydicom"] +pyflakes = ["pyflakes"] +pynrrd = ["pynrrd"] +pytest = ["pytest"] +pynvml = ["nvidia-ml-py"] +pyyaml = ["pyyaml"] +requests = ["requests"] +ruff = ["ruff>=0.14.11,<0.15"] +segment_anything = ["git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588"] +scipy = ["scipy>=1.12.0"] +skimage = ["scikit-image>=0.19.0"] +tensorboard = ["tensorboard>=2.12.0"] # https://github.com/Project-MONAI/MONAI/issues/7434 +tensorboardX = ["tensorboardX"] +testing = ["coverage>=5.5", "parameterized"] +tifffile = ["tifffile; platform_system == 'Linux' or platform_system == 'Darwin'"] +torchio = ["torchio"] +torchvision = ["torchvision"] +tqdm = ["tqdm>=4.47.0"] +transformers = ["transformers>=4.53.0, <5.0"] # 5.x references torch.float8_e8m0fnu absent in older PyTorch builds +typeguard = ["typeguard<3"] # https://github.com/microsoft/nni/issues/5457 +types-PyYAML = ["types-PyYAML"] +types-setuptools = ["types-setuptools"] +zarr = ["zarr"] [tool.setuptools] license-files = ["LICENSE"] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index b2c36f8de62..00000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,66 +0,0 @@ -# Full requirements for developments --r requirements-min.txt -pytorch-ignite -gdown>=4.7.3 -scipy>=1.12.0 -itk>=5.2 -nibabel -pillow!=8.3.0 # https://github.com/python-pillow/Pillow/issues/5571 -tensorboard>=2.12.0 # https://github.com/Project-MONAI/MONAI/issues/7434 -scikit-image>=0.19.0 -tqdm>=4.47.0 -lmdb -mccabe -pep8-naming -pycodestyle -pyflakes -black>=26.3.1 -isort>=5.1, <6, !=6.0.0 -ruff>=0.14.11,<0.15 -pybind11 -setuptools<71 # pkg_resources removed in setuptools>=71; needed by MetricsReloaded setup.py -types-setuptools -mypy>=1.5.0, <1.12.0 -ninja -torchio -torchvision -psutil -cucim-cu12; platform_system == "Linux" and python_version <= "3.10" -cucim-cu13; platform_system == "Linux" and python_version >= '3.11' -openslide-python -openslide-bin -imagecodecs; platform_system == "Linux" or platform_system == "Darwin" -tifffile; platform_system == "Linux" or platform_system == "Darwin" -pandas -requests -einops -transformers>=4.53.0, <5.0 # 5.x references torch.float8_e8m0fnu absent in older PyTorch builds -mlflow>=2.12.2, <3.0 # 3.x broken on Python 3.12 (relative import in mlflow.utils.uv_utils) -clearml>=1.10.0rc0 -matplotlib>=3.6.3 -tensorboardX -types-PyYAML -pyyaml -fire -jsonschema -pynrrd -pre-commit -pydicom -h5py -nni==2.10.1; platform_system == "Linux" and "arm" not in platform_machine and "aarch" not in platform_machine -optuna -git+https://github.com/Project-MONAI/MetricsReloaded@monai-support#egg=MetricsReloaded -onnx>=1.13.0 -onnxscript -onnxruntime -typeguard<3 # https://github.com/microsoft/nni/issues/5457 -filelock<3.12.0 # https://github.com/microsoft/nni/issues/5523 -zarr -lpips==0.1.4 -nvidia-ml-py -huggingface_hub -pyamg>=5.0.0, <5.3.0 -git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588 -onnx_graphsurgeon -polygraphy -pytest # FIXME: added to get around cupy 14.1.0 creating the requirement through polygraphy and trt_compiler somehow diff --git a/requirements-min.txt b/requirements-min.txt deleted file mode 100644 index ddda9064a6b..00000000000 --- a/requirements-min.txt +++ /dev/null @@ -1,8 +0,0 @@ -# Requirements for minimal tests --r requirements.txt -setuptools>=50.3.0,<66.0.0,!=60.6.0 ; python_version < "3.12" -setuptools>=70.2.0,<=79.0.1; python_version >= "3.12" -coverage>=5.5 -parameterized -packaging -backports.tarfile diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7d283182a40..00000000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -torch>=2.8.0 -numpy>=1.24,<3.0 From 1a7be997d4ef9b92a4595b75ef1c540cee12e98d Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 18 Jul 2026 23:17:56 +0100 Subject: [PATCH 03/26] Collect testing dependencies into one optional name Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- pyproject.toml | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 44830ad1133..ee6161100f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,9 +51,7 @@ Documentation = "https://monai.readthedocs.io/" [project.optional-dependencies] all = [ - "black>=26.3.1", "clearml>=1.10.0rc0", - "coverage>=5.5", "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'", "einops", @@ -65,7 +63,6 @@ all = [ "h5py", "huggingface_hub", "imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'", - "isort>=5.1, <6, !=6.0.0", "itk>=5.2", "jsonschema", "lmdb", @@ -85,23 +82,17 @@ all = [ "openslide-python", "optuna", "pandas", - "parameterized", - "pep8-naming", "pillow!=8.3.0", "polygraphy", - "pre-commit", "psutil", "pyamg>=5.0.0,<5.3.0", "pybind11", - "pycodestyle", "pydicom", - "pyflakes", "pynrrd", "pytest", "pytorch-ignite", "pyyaml", "requests", - "ruff>=0.14.11,<0.15", "scikit-image>=0.19.0", "scipy>=1.12.0", "tensorboard>=2.12.0", @@ -111,12 +102,8 @@ all = [ "torchvision", "tqdm>=4.47.0", "transformers>=4.53.0, <5.0", - "typeguard<3", - "types-PyYAML", - "types-setuptools", "zarr" ] -black = ["black>=26.3.1"] clearml = ["clearml>=1.10.0rc0"] cucim = [ "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", @@ -151,37 +138,44 @@ openslide = ["openslide-python", "openslide-bin"] optuna = ["optuna"] packaging = ["packaging"] pandas = ["pandas"] -pep8-naming = ["pep8-naming"] pillow = ["pillow!=8.3.0"] # https://github.com/python-pillow/Pillow/issues/5571 polygraphy = ["polygraphy"] -pre-commit = ["pre-commit"] psutil = ["psutil"] pyamg = ["pyamg>=5.0.0,<5.3.0"] pybind11 = ["pybind11"] -pycodestyle = ["pycodestyle"] pydicom = ["pydicom"] -pyflakes = ["pyflakes"] pynrrd = ["pynrrd"] pytest = ["pytest"] pynvml = ["nvidia-ml-py"] pyyaml = ["pyyaml"] requests = ["requests"] -ruff = ["ruff>=0.14.11,<0.15"] segment_anything = ["git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588"] scipy = ["scipy>=1.12.0"] skimage = ["scikit-image>=0.19.0"] tensorboard = ["tensorboard>=2.12.0"] # https://github.com/Project-MONAI/MONAI/issues/7434 tensorboardX = ["tensorboardX"] -testing = ["coverage>=5.5", "parameterized"] tifffile = ["tifffile; platform_system == 'Linux' or platform_system == 'Darwin'"] torchio = ["torchio"] torchvision = ["torchvision"] tqdm = ["tqdm>=4.47.0"] transformers = ["transformers>=4.53.0, <5.0"] # 5.x references torch.float8_e8m0fnu absent in older PyTorch builds -typeguard = ["typeguard<3"] # https://github.com/microsoft/nni/issues/5457 -types-PyYAML = ["types-PyYAML"] -types-setuptools = ["types-setuptools"] zarr = ["zarr"] +# these dependencies are for testing/development only, they aren't needed for regular use so don't appear in "all" +testing = [ + "black>=26.3.1", + "coverage>=5.5", + "isort>=5.1, <6, !=6.0.0", + "parameterized", + "pep8-naming", + "pre-commit", + "pycodestyle", + "pyflakes", + "ruff>=0.14.11,<0.15", + "typeguard<3", # https://github.com/microsoft/nni/issues/5457 + "types-PyYAML", + "types-setuptools" +] + [tool.setuptools] license-files = ["LICENSE"] From 4e410ee4064a112ff3652563a731b049028d5a1e Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:42:20 +0100 Subject: [PATCH 04/26] Updating install process in other places Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/build_docs.yml | 4 ++- .github/workflows/cicd_tests.yml | 21 +++++------ .github/workflows/codeql-analysis.yml | 4 +-- .github/workflows/cron-ngc-bundle.yml | 4 +-- .github/workflows/cron.yml | 8 ++--- .github/workflows/docker.yml | 2 -- .github/workflows/integration.yml | 6 ++-- .github/workflows/pythonapp-gpu.yml | 2 +- .github/workflows/pythonapp-hyena-gpu.yml | 3 +- .github/workflows/release.yml | 2 -- .github/workflows/weekly-preview.yml | 4 +-- Dockerfile | 44 ++++++++++++----------- docs/requirements.txt | 1 + environment-dev.yml | 15 -------- monai/config/print_dependencies.py | 35 ++++++++++-------- pyproject.toml | 24 ++++++------- runtests.sh | 9 +++-- 17 files changed, 89 insertions(+), 99 deletions(-) delete mode 100644 environment-dev.yml diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index a9637c27167..1f5e43b362c 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -23,6 +23,8 @@ jobs: env: # minimum supported version of Python PYTHON_VER1: '3.10' + # force installation of CPU-only PyTorch + PIP_EXTRA_INDEX_URL: 'https://download.pytorch.org/whl/cpu' steps: - uses: actions/checkout@v7 - name: Set up Python ${{ env.PYTHON_VER1 }} @@ -33,7 +35,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip wheel - python -m pip install -r docs/requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu + python -m pip install .[all,testing] - name: Make html run: | cd docs/ diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index d48e3a02f22..88bab7090b6 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -75,7 +75,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip wheel - python -m pip install --no-build-isolation -r requirements-dev.txt + python -m pip install .[all,testing] - name: Lint and type check run: | # clean up temporary files @@ -144,9 +144,8 @@ jobs: run: | # min. requirements python -m pip install torch==${{ matrix.pytorch-version }} - python -m pip install -r requirements-min.txt + python -m pip install -e .[testing] python -m pip list - BUILD_MONAI=0 python setup.py develop # no compile of extensions shell: bash - if: matrix.os == 'linux-gpu-runner' name: Print GPU Info @@ -220,10 +219,8 @@ jobs: - name: Install the complete dependencies run: | python -m pip install --user --upgrade pip wheel pybind11 # TODO: pybind11 added for macOS, may not be needed - cat "requirements-dev.txt" - python -m pip install --no-build-isolation -r requirements-dev.txt + python -m pip install -e .[all,testing] python -m pip list - python -m pip install --no-build-isolation -e . # test no compile installation shell: bash - name: Run compiled (${{ runner.os }}) run: | @@ -277,12 +274,12 @@ jobs: run: | python -m pip install --upgrade pip wheel python -m pip install torch==${PYTORCH_VER1} torchvision==${TORCHVISION_VER1} - python -m pip install --no-build-isolation -r requirements-dev.txt - python -m pip install -e . - # nvsubquadratic runtime imports need only torch + einops + omegaconf; install - # the package itself without its core dependency tree (see job comment above). - python -m pip install omegaconf - python -m pip install --no-deps 'nvsubquadratic>=0.1.1' + python -m pip install -e .[all,hyena,testing] + # # nvsubquadratic runtime imports need only torch + einops + omegaconf; install + # # the package itself without its core dependency tree (see job comment above). + # python -m pip install omegaconf + # python -m pip install --no-deps 'nvsubquadratic>=0.1.1' + python -m pip install -e .[hyena] python -m pip list shell: bash - name: Run Hyena tests (CUDA-required cases skip cleanly) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3b4b427c990..ce1a0b98934 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -67,8 +67,8 @@ jobs: rm -rf /opt/hostedtoolcache/{node,go,Ruby,Java*} ls -al /opt/hostedtoolcache rm -rf /usr/share/dotnet/ - python -m pip install -U --no-build-isolation pip wheel wheel-stub - python -m pip install --no-build-isolation -r requirements-dev.txt + python -m pip install -U pip wheel wheel-stub + python -m pip install .[all,testing] BUILD_MONAI=1 ./runtests.sh --build - name: Perform CodeQL Analysis diff --git a/.github/workflows/cron-ngc-bundle.yml b/.github/workflows/cron-ngc-bundle.yml index a8539b284c5..650434eb37e 100644 --- a/.github/workflows/cron-ngc-bundle.yml +++ b/.github/workflows/cron-ngc-bundle.yml @@ -29,8 +29,8 @@ jobs: - name: Install dependencies run: | rm -rf /github/home/.cache/torch/hub/bundle/ - python -m pip install --no-build-isolation --upgrade pip wheel wheel-stub - python -m pip install --no-build-isolation -r requirements-dev.txt + python -m pip install -U pip wheel wheel-stub + python -m pip install .[all,testing] - name: Loading Bundles run: | # clean up temporary files diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 8ff912f5281..90d1bcbd05c 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -43,7 +43,7 @@ jobs: python -m pip install --upgrade pip wheel python -m pip uninstall -y torch torchvision python -m pip install ${{ matrix.pytorch }} - python -m pip install -r requirements-dev.txt + python -m pip install .[all,testing] python -m pip list - name: Run tests report coverage env: @@ -91,7 +91,7 @@ jobs: run: | which python python -m pip install --upgrade pip wheel - python -m pip install -r requirements-dev.txt + python -m pip install .[all,testing] python -m pip list - name: Run tests report coverage env: @@ -186,7 +186,7 @@ jobs: python -c $'import torch\na,b=torch.zeros(1,device="cuda:0"),torch.zeros(1,device="cuda:1");\nwhile True:print(a,b)' > /dev/null & python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" - python -m pip install -r requirements-dev.txt + python -m pip install .[all,testing] PYTHONPATH="$tmp_dir":$PYTHONPATH BUILD_MONAI=1 python ./tests/runner.py -p 'test_((?!integration).)' # unit tests if pgrep python; then pkill python; fi @@ -239,7 +239,7 @@ jobs: run: | which python python -m pip install --upgrade pip wheel - python -m pip install -r requirements-dev.txt + python -m pip install .[all,testing] BUILD_MONAI=1 python setup.py develop # install monai nvidia-smi export CUDA_VISIBLE_DEVICES=$(python -m tests.utils | tail -n 1) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bef17d09362..921d8f5c89a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -69,8 +69,6 @@ jobs: cat _version.py mv _version.py monai/ - # build "latest": remove flake package as it is not needed on hub.docker.com - sed -i '/flake/d' requirements-dev.txt docker build -t projectmonai/monai:latest -f Dockerfile . # distribute as always w/ tag "latest" to hub.docker.com diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 785945de036..7b9336debbf 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -37,8 +37,7 @@ jobs: pip uninstall -y monai pip uninstall -y monai-weekly pip uninstall -y monai-weekly - python -m pip install --upgrade torch torchvision torchaudio torchtext - python -m pip install -r requirements-dev.txt + python -m pip install .[all,testing] rm -rf /github/home/.cache/torch/hub/mmars/ - name: Clean directory run: | @@ -113,8 +112,7 @@ jobs: pip uninstall -y monai pip uninstall -y monai-weekly pip uninstall -y monai-weekly - python -m pip install --upgrade torch torchvision torchaudio torchtext - python -m pip install -r requirements-dev.txt + python -m pip install .[all,testing] rm -rf /github/home/.cache/torch/hub/mmars/ - name: Clean directory run: | diff --git a/.github/workflows/pythonapp-gpu.yml b/.github/workflows/pythonapp-gpu.yml index bfc2f62e28c..8378d2742ab 100644 --- a/.github/workflows/pythonapp-gpu.yml +++ b/.github/workflows/pythonapp-gpu.yml @@ -96,7 +96,7 @@ jobs: rm -rf $(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")/ruamel* rm -rf $(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")/llvmlite* #6377 python -m pip install ${{ matrix.pytorch }} - python -m pip install -r requirements-dev.txt + python -m pip install .[all,testing] python -m pip list - name: Run quick tests (GPU) if: github.event.pull_request.merged != true diff --git a/.github/workflows/pythonapp-hyena-gpu.yml b/.github/workflows/pythonapp-hyena-gpu.yml index af49cb292bc..b080942bf3c 100644 --- a/.github/workflows/pythonapp-hyena-gpu.yml +++ b/.github/workflows/pythonapp-hyena-gpu.yml @@ -47,8 +47,7 @@ jobs: run: | python -m pip install --upgrade pip wheel python -c "import sys; assert sys.version_info >= (3, 10), f'Python >= 3.10 required for nvsubquadratic, got {sys.version}'" - python -m pip install -r requirements-dev.txt - python -m pip install -e . + python -m pip install -e .[all,testing] # Install nvsubquadratic with --no-deps: the default torch_fft path needs only # torch + einops + omegaconf, and nvsubquadratic pins torch>=2.10,<2.11 which can # clash with the container's torch. To exercise the accelerated fused CUDA diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 056b99edffe..33d63efb8f3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -159,8 +159,6 @@ jobs: echo "unmatched version string, please check the tagging branch." exit 1 fi - # remove flake package as it is not needed on hub.docker.com - sed -i '/flake/d' requirements-dev.txt docker build -t projectmonai/monai:"$RELEASE_VERSION" -f Dockerfile . # distribute with a tag to hub.docker.com echo "${{ secrets.DOCKER_PW }}" | docker login -u projectmonai --password-stdin diff --git a/.github/workflows/weekly-preview.yml b/.github/workflows/weekly-preview.yml index 7e7349ec931..f5577abcf19 100644 --- a/.github/workflows/weekly-preview.yml +++ b/.github/workflows/weekly-preview.yml @@ -32,8 +32,8 @@ jobs: cache: 'pip' - name: Install dependencies run: | - python -m pip install --upgrade pip wheel - python -m pip install --no-build-isolation -r requirements-dev.txt + python -m pip install -U pip wheel + python -m pip install .[all,testing] - name: Lint and type check run: | # clean up temporary files diff --git a/Dockerfile b/Dockerfile index 3240da7deda..9158f5d0ab5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,37 +36,39 @@ RUN (grep '^numpy' /etc/pip/constraint.txt || true) > /tmp/new_constraints.txt \ && cp /tmp/new_constraints.txt /etc/pip/constraint.txt # install full deps -COPY requirements.txt requirements-min.txt requirements-dev.txt /tmp/ -RUN cp /tmp/requirements.txt /tmp/req.bak \ - && awk '!/torch/' /tmp/requirements.txt > /tmp/tmp && mv /tmp/tmp /tmp/requirements.txt \ - && python -m pip install --upgrade --no-cache-dir --no-build-isolation pip wheel wheel-stub \ - && python -m pip install --no-cache-dir --no-build-isolation -r /tmp/requirements-dev.txt +# COPY requirements.txt requirements-min.txt requirements-dev.txt /tmp/ +# RUN cp /tmp/requirements.txt /tmp/req.bak \ +# && awk '!/torch/' /tmp/requirements.txt > /tmp/tmp && mv /tmp/tmp /tmp/requirements.txt \ +# && python -m pip install --upgrade --no-cache-dir --no-build-isolation pip wheel wheel-stub \ +# && python -m pip install --no-cache-dir --no-build-isolation -r /tmp/requirements-dev.txt # compile ext and remove temp files # TODO: remark for issue [revise the dockerfile #1276](https://github.com/Project-MONAI/MONAI/issues/1276) # please specify exact files and folders to be copied -- else, basically always, the Docker build process cannot cache # this or anything below it and always will build from at most here; one file change leads to no caching from here on... -COPY LICENSE CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md README.md versioneer.py setup.py setup.cfg runtests.sh MANIFEST.in ./ +COPY LICENSE CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md README.md versioneer.py setup.py pyproject.toml runtests.sh MANIFEST.in ./ COPY tests ./tests COPY monai ./monai -RUN BUILD_MONAI=1 FORCE_CUDA=1 python setup.py develop \ - && rm -rf build __pycache__ +# RUN BUILD_MONAI=1 FORCE_CUDA=1 python setup.py develop \ +# && rm -rf build __pycache__ -# NGC Client -WORKDIR /opt/tools -ARG NGC_CLI_URI="https://ngc.nvidia.com/downloads/ngccli_linux.zip" -RUN wget -q ${NGC_CLI_URI} && unzip ngccli_linux.zip && chmod u+x ngc-cli/ngc && \ - find ngc-cli/ -type f -exec md5sum {} + | LC_ALL=C sort | md5sum -c ngc-cli.md5 && \ - rm -rf ngccli_linux.zip ngc-cli.md5 -ENV PATH=${PATH}:/opt/tools:/opt/tools/ngc-cli -RUN apt-get update \ - && DEBIAN_FRONTEND="noninteractive" apt-get install -y libopenslide0 \ - && rm -rf /var/lib/apt/lists/* -# append /opt/tools to runtime path for NGC CLI to be accessible from all file system locations -ENV PATH=${PATH}:/opt/tools -ENV POLYGRAPHY_AUTOINSTALL_DEPS=1 +RUN BUILD_MONAI=1 FORCE_CUDA=1 pip install -e .[all,testing] + +# # NGC Client +# WORKDIR /opt/tools +# ARG NGC_CLI_URI="https://ngc.nvidia.com/downloads/ngccli_linux.zip" +# RUN wget -q ${NGC_CLI_URI} && unzip ngccli_linux.zip && chmod u+x ngc-cli/ngc && \ +# find ngc-cli/ -type f -exec md5sum {} + | LC_ALL=C sort | md5sum -c ngc-cli.md5 && \ +# rm -rf ngccli_linux.zip ngc-cli.md5 +# ENV PATH=${PATH}:/opt/tools:/opt/tools/ngc-cli +# RUN apt-get update \ +# && DEBIAN_FRONTEND="noninteractive" apt-get install -y libopenslide0 \ +# && rm -rf /var/lib/apt/lists/* +# # append /opt/tools to runtime path for NGC CLI to be accessible from all file system locations +# ENV PATH=${PATH}:/opt/tools +# ENV POLYGRAPHY_AUTOINSTALL_DEPS=1 WORKDIR /opt/monai diff --git a/docs/requirements.txt b/docs/requirements.txt index 3027d401646..9e023cec5eb 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -33,6 +33,7 @@ pynrrd pydicom h5py nni==2.10.1; platform_system == "Linux" and "arm" not in platform_machine and "aarch" not in platform_machine +filelock<3.12.0 optuna opencv-python-headless onnx>=1.13.0 diff --git a/environment-dev.yml b/environment-dev.yml deleted file mode 100644 index b2457006c89..00000000000 --- a/environment-dev.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: monai -channels: - - pytorch - - defaults - - nvidia - - conda-forge -dependencies: - - numpy>=1.24,<3.0 - - pytorch>=2.8.0 - - torchio - - torchvision - - pytorch-cuda>=11.6 - - pip - - pip: - - -r requirements-dev.txt diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index a7c58d335ce..c7082e0795e 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -18,16 +18,6 @@ import sys -if sys.version_info.minor >= 11: - import tomllib - - load_func = tomllib.loads -else: - import tomlkit - - load_func = tomlkit.parse - - BUILD_SYSTEM_KEY = "build-system" PROJ_KEY = "project" OPTS_KEY = "optional-dependencies" @@ -35,15 +25,24 @@ REQ_KEY = "requires" TOML_FILE = "pyproject.toml" -if __name__ == "__main__": - sections = set(sys.argv[1:]) - with open(TOML_FILE, "rb") as o: +def parse_dependencies(filename=TOML_FILE, sections=[]): + # these imports should be here to avoid attempting to import when MONAI is imported and both packages are missing + if sys.version_info.minor >= 11: + import tomlib + + load_func = tomlib.loads + else: + import tomli + + load_func = tomli.loads + + with open(filename, "r") as o: data = load_func(o.read()) proj = data[PROJ_KEY] opts = proj[OPTS_KEY] - dependencies = proj[DEP_KEY] + dependencies = list(proj[DEP_KEY]) if BUILD_SYSTEM_KEY in sections: sections.remove(BUILD_SYSTEM_KEY) @@ -55,5 +54,11 @@ for s in sections: dependencies += opts[s] - for d in sorted(set(dependencies)): + return sorted(set(dependencies)) + + +if __name__ == "__main__": + dependencies = parse_dependencies(sections=set(sys.argv[1:])) + + for d in dependencies: print(d) diff --git a/pyproject.toml b/pyproject.toml index ee6161100f3..b08d00eeba6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,13 @@ [build-system] requires = [ - "setuptools<71", # pkg_resources removed in setuptools>=71; needed by MetricsReloaded setup.py + "setuptools<71.0.0", # pkg_resources removed in setuptools>=71; needed by MetricsReloaded setup.py "wheel", "versioneer[toml]", "more-itertools>=8.0", - "torch>=2.8.0", "ninja", "packaging", - "backports.tarfile" # see #8792 + "backports.tarfile" # see https://github.com/Project-MONAI/MONAI/issues/8791 ] build-backend = "setuptools.build_meta" @@ -42,6 +41,7 @@ dependencies = [ "torch>=2.8.0", "numpy>=1.24,<3.0", ] +dynamic = ["version"] [project.urls] Homepage = "https://project-monai.github.io/" @@ -58,8 +58,10 @@ all = [ "filelock<3.12.0", "fire", "gdown>=4.7.3", - "git+https://github.com/Project-MONAI/MetricsReloaded@monai-support#egg=MetricsReloaded", - "git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588", + # TODO: fix this URL before finalising + "MetricsReloaded @ git+https://github.com/ericspod/MetricsReloaded@monai-support-fix", + # "MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support", + "segment_anything @ git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588", "h5py", "huggingface_hub", "imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'", @@ -114,21 +116,19 @@ fire = ["fire"] gdown = ["gdown>=4.7.3"] h5py = ["h5py"] huggingface_hub = ["huggingface_hub"] -hyena = ["nvsubquadratic>=0.1.1"] # omitted from all for compatibility +hyena = ["nvsubquadratic>=0.1.1", "omegaconf"] # omitted from all for compatibility ignite = ["pytorch-ignite"] imagecodecs = ["imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'"] -isort = ["isort>=5.1, <6, !=6.0.0"] itk = ["itk>=5.2"] jsonschema = ["jsonschema"] lmdb = ["lmdb"] lpips = ["lpips==0.1.4"] matplotlib = ["matplotlib>=3.6.3"] mccabe = ["mccabe"] -metrics_reloaded = ["git+https://github.com/Project-MONAI/MetricsReloaded@monai-support#egg=MetricsReloaded"] +metrics_reloaded = ["MetricsReloaded @ git+https://github.com/ericspod/MetricsReloaded@monai-support-fix"] mlflow = ["mlflow>=2.12.2,<3.13"] mypy = ["mypy>=1.5.0, <1.12.0"] nibabel = ["nibabel"] -ninja = ["ninja"] nni = [ "nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine", "filelock<3.12.0" # https://github.com/microsoft/nni/issues/5523 @@ -136,7 +136,6 @@ nni = [ onnx = ["onnx>=1.13.0", "onnxruntime; python_version <= '3.10'", "onnx_graphsurgeon", "onnxscript"] openslide = ["openslide-python", "openslide-bin"] optuna = ["optuna"] -packaging = ["packaging"] pandas = ["pandas"] pillow = ["pillow!=8.3.0"] # https://github.com/python-pillow/Pillow/issues/5571 polygraphy = ["polygraphy"] @@ -149,7 +148,7 @@ pytest = ["pytest"] pynvml = ["nvidia-ml-py"] pyyaml = ["pyyaml"] requests = ["requests"] -segment_anything = ["git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588"] +segment_anything = ["segment_anything @ git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588"] scipy = ["scipy>=1.12.0"] skimage = ["scikit-image>=0.19.0"] tensorboard = ["tensorboard>=2.12.0"] # https://github.com/Project-MONAI/MONAI/issues/7434 @@ -165,18 +164,19 @@ testing = [ "black>=26.3.1", "coverage>=5.5", "isort>=5.1, <6, !=6.0.0", + "packaging", "parameterized", "pep8-naming", "pre-commit", "pycodestyle", "pyflakes", "ruff>=0.14.11,<0.15", + "tomli", # used in print_dependencies.py for Python<3.11 "typeguard<3", # https://github.com/microsoft/nni/issues/5457 "types-PyYAML", "types-setuptools" ] - [tool.setuptools] license-files = ["LICENSE"] diff --git a/runtests.sh b/runtests.sh index 431e9298c35..01ae6087587 100755 --- a/runtests.sh +++ b/runtests.sh @@ -137,8 +137,13 @@ function print_version { } function install_deps { - echo "Pip installing MONAI development dependencies and compile MONAI cpp extensions..." - ${cmdPrefix}"${PY_EXE}" -m pip install --no-build-isolation -r requirements-dev.txt + echo "Pip installing MONAI development dependencies..." + # needed for Python<3.11 + ${cmdPrefix}"${PY_EXE}" -m pip install -U tomli + # create a temporary requirements file and install using it + REQ=$(mktemp --tmpdir XXX.txt) + ${cmdPrefix}"${PY_EXE}" monai/config/print_dependencies.py all testing > $REQ + ${cmdPrefix}"${PY_EXE}" -m pip install -r $REQ } function compile_cpp { From 43da203a6da4a006720db8446bb2a19661f53cc5 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:30:07 +0100 Subject: [PATCH 05/26] Re-adding file Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- environment-dev.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 environment-dev.yml diff --git a/environment-dev.yml b/environment-dev.yml new file mode 100644 index 00000000000..6c4d4ed93eb --- /dev/null +++ b/environment-dev.yml @@ -0,0 +1,7 @@ +name: monai +channels: + - defaults +dependencies: + - pip + - pip: + - -e .[all,testing] \ No newline at end of file From 7725ded76601583063a30c59a6df28d5902d1b92 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:38:50 +0100 Subject: [PATCH 06/26] Updates to Dockerfile, not done yet Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- Dockerfile | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9158f5d0ab5..3cc4b1eb36c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,20 @@ RUN if [[ $(uname -m) =~ "aarch64" ]]; then \ pip install numcodecs; \ fi +# NGC Client +WORKDIR /opt/tools +ARG NGC_CLI_URI="https://ngc.nvidia.com/downloads/ngccli_linux.zip" +RUN wget -q ${NGC_CLI_URI} && unzip ngccli_linux.zip && chmod u+x ngc-cli/ngc && \ + find ngc-cli/ -type f -exec md5sum {} + | LC_ALL=C sort | md5sum -c ngc-cli.md5 && \ + rm -rf ngccli_linux.zip ngc-cli.md5 +ENV PATH=${PATH}:/opt/tools:/opt/tools/ngc-cli +RUN apt-get update \ + && DEBIAN_FRONTEND="noninteractive" apt-get install -y libopenslide0 \ + && rm -rf /var/lib/apt/lists/* +# append /opt/tools to runtime path for NGC CLI to be accessible from all file system locations +ENV PATH=${PATH}:/opt/tools +ENV POLYGRAPHY_AUTOINSTALL_DEPS=1 + WORKDIR /opt/monai # Patch NVIDIA's pip constraint file: @@ -54,21 +68,5 @@ COPY monai ./monai # RUN BUILD_MONAI=1 FORCE_CUDA=1 python setup.py develop \ # && rm -rf build __pycache__ -RUN BUILD_MONAI=1 FORCE_CUDA=1 pip install -e .[all,testing] - -# # NGC Client -# WORKDIR /opt/tools -# ARG NGC_CLI_URI="https://ngc.nvidia.com/downloads/ngccli_linux.zip" -# RUN wget -q ${NGC_CLI_URI} && unzip ngccli_linux.zip && chmod u+x ngc-cli/ngc && \ -# find ngc-cli/ -type f -exec md5sum {} + | LC_ALL=C sort | md5sum -c ngc-cli.md5 && \ -# rm -rf ngccli_linux.zip ngc-cli.md5 -# ENV PATH=${PATH}:/opt/tools:/opt/tools/ngc-cli -# RUN apt-get update \ -# && DEBIAN_FRONTEND="noninteractive" apt-get install -y libopenslide0 \ -# && rm -rf /var/lib/apt/lists/* -# # append /opt/tools to runtime path for NGC CLI to be accessible from all file system locations -# ENV PATH=${PATH}:/opt/tools -# ENV POLYGRAPHY_AUTOINSTALL_DEPS=1 +RUN BUILD_MONAI=1 FORCE_CUDA=1 pip install --no-cache-dir --build-constraint /etc/pip/constraint.txt -e .[all,testing] - -WORKDIR /opt/monai From 2103022f6f3050e43ad93e7192c3fda22b4b1f95 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:01:51 +0100 Subject: [PATCH 07/26] Dockerfile works and versioneer updated Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- Dockerfile | 16 ++++++-- monai/__init__.py | 4 +- monai/_version.py | 96 ++++++++++++++++++++++++++++++----------------- 3 files changed, 75 insertions(+), 41 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3cc4b1eb36c..4fe9fb89924 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,8 @@ FROM ${PYTORCH_IMAGE} LABEL maintainer="monai.contact@gmail.com" +ENV BUILD_MONAI=1 + # TODO: remark for issue [revise the dockerfile](https://github.com/zarr-developers/numcodecs/issues/431) RUN if [[ $(uname -m) =~ "aarch64" ]]; then \ export CFLAGS="-O3" && \ @@ -45,9 +47,9 @@ WORKDIR /opt/monai # their torch was compiled against NumPy 1.x; newer images may ship an empty file) # - add setuptools<71 (setuptools>=71 removed pkg_resources, breaking MetricsReloaded) # - pin urllib3>=2 to prevent inadvertent downgrades by pip-installing legacy packages -RUN (grep '^numpy' /etc/pip/constraint.txt || true) > /tmp/new_constraints.txt \ - && printf 'setuptools<71\nurllib3>=2\n' >> /tmp/new_constraints.txt \ - && cp /tmp/new_constraints.txt /etc/pip/constraint.txt +# RUN (grep '^numpy' /etc/pip/constraint.txt || true) > /tmp/new_constraints.txt \ +# && printf 'setuptools<71\nurllib3>=2\n' >> /tmp/new_constraints.txt \ +# && cp /tmp/new_constraints.txt /etc/pip/constraint.txt # install full deps # COPY requirements.txt requirements-min.txt requirements-dev.txt /tmp/ @@ -68,5 +70,11 @@ COPY monai ./monai # RUN BUILD_MONAI=1 FORCE_CUDA=1 python setup.py develop \ # && rm -rf build __pycache__ -RUN BUILD_MONAI=1 FORCE_CUDA=1 pip install --no-cache-dir --build-constraint /etc/pip/constraint.txt -e .[all,testing] +# Need to install build requirements explicitly so that no-build-isolation can be used to ensure compiled libraries are +# built against the in-build PyTorch, otherwise in isolation the build system installs a different version. Update these +# requirements if the build configuration changes, setuptools is limited to <71 to account for MetricsReloaded issue. +RUN pip install --no-cache-dir setuptools\<71 versioneer[toml]\ + && FORCE_CUDA=1 pip install --no-cache-dir --no-build-isolation -e . + +#FORCE_CUDA=1 pip install --build-constraint /etc/pip/constraint.txt -e . diff --git a/monai/__init__.py b/monai/__init__.py index 45e15ddcd9a..9fdd9911296 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -62,8 +62,8 @@ def filter(self, record): PY_REQUIRED_MINOR = 9 version_dict = get_versions() -__version__: str = version_dict.get("version", "0+unknown") -__revision_id__: str = version_dict.get("full-revisionid") +__version__: str = str(version_dict.get("version", "0+unknown")) +__revision_id__: str = str(version_dict.get("full-revisionid")) del get_versions, version_dict __copyright__ = "(c) MONAI Consortium" diff --git a/monai/_version.py b/monai/_version.py index f2342271048..d14412be66c 100644 --- a/monai/_version.py +++ b/monai/_version.py @@ -5,8 +5,9 @@ # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. -# This file is released into the public domain. Generated by -# versioneer-0.23 (https://github.com/python-versioneer/python-versioneer) +# This file is released into the public domain. +# Generated by versioneer-0.29 +# https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" @@ -15,11 +16,11 @@ import re import subprocess import sys -from collections.abc import Callable +from typing import Any, Callable, Dict, List, Optional, Tuple import functools -def get_keywords(): +def get_keywords() -> Dict[str, str]: """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must @@ -35,8 +36,15 @@ def get_keywords(): class VersioneerConfig: """Container for Versioneer configuration parameters.""" + VCS: str + style: str + tag_prefix: str + parentdir_prefix: str + versionfile_source: str + verbose: bool -def get_config(): + +def get_config() -> VersioneerConfig: """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py @@ -54,13 +62,13 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY: dict[str, str] = {} -HANDLERS: dict[str, dict[str, Callable]] = {} +LONG_VERSION_PY: Dict[str, str] = {} +HANDLERS: Dict[str, Dict[str, Callable]] = {} -def register_vcs_handler(vcs, method): # decorator +def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} @@ -69,13 +77,19 @@ def decorate(f): return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command( + commands: List[str], + args: List[str], + cwd: Optional[str] = None, + verbose: bool = False, + hide_stderr: bool = False, + env: Optional[Dict[str, str]] = None, +) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) process = None - popen_kwargs = {} + popen_kwargs: Dict[str, Any] = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() @@ -91,8 +105,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break - except OSError: - e = sys.exc_info()[1] + except OSError as e: if e.errno == errno.ENOENT: continue if verbose: @@ -112,7 +125,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, return stdout, process.returncode -def versions_from_parentdir(parentdir_prefix, root, verbose): +def versions_from_parentdir( + parentdir_prefix: str, + root: str, + verbose: bool, +) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both @@ -137,15 +154,15 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): @register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): +def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. - keywords = {} + keywords: Dict[str, str] = {} try: - with open(versionfile_abs) as fobj: + with open(versionfile_abs, "r") as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) @@ -165,7 +182,11 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): +def git_versions_from_keywords( + keywords: Dict[str, str], + tag_prefix: str, + verbose: bool, +) -> Dict[str, Any]: """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") @@ -229,7 +250,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): +def git_pieces_from_vcs( + tag_prefix: str, + root: str, + verbose: bool, + runner: Callable = run_command +) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -248,7 +274,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -259,7 +285,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" - ], cwd=root) + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -269,7 +295,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() - pieces = {} + pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None @@ -361,14 +387,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): return pieces -def plus_or_dot(pieces): +def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" -def render_pep440(pieces): +def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you @@ -393,7 +419,7 @@ def render_pep440(pieces): return rendered -def render_pep440_branch(pieces): +def render_pep440_branch(pieces: Dict[str, Any]) -> str: """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards @@ -423,7 +449,7 @@ def render_pep440_branch(pieces): return rendered -def pep440_split_post(ver): +def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the @@ -433,7 +459,7 @@ def pep440_split_post(ver): return vc[0], int(vc[1] or 0) if len(vc) == 2 else None -def render_pep440_pre(pieces): +def render_pep440_pre(pieces: Dict[str, Any]) -> str: """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: @@ -457,7 +483,7 @@ def render_pep440_pre(pieces): return rendered -def render_pep440_post(pieces): +def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards @@ -484,7 +510,7 @@ def render_pep440_post(pieces): return rendered -def render_pep440_post_branch(pieces): +def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. @@ -513,7 +539,7 @@ def render_pep440_post_branch(pieces): return rendered -def render_pep440_old(pieces): +def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. @@ -535,7 +561,7 @@ def render_pep440_old(pieces): return rendered -def render_git_describe(pieces): +def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. @@ -555,7 +581,7 @@ def render_git_describe(pieces): return rendered -def render_git_describe_long(pieces): +def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. @@ -575,7 +601,7 @@ def render_git_describe_long(pieces): return rendered -def render(pieces, style): +def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", @@ -611,7 +637,7 @@ def render(pieces, style): "date": pieces.get("date")} -def get_versions(): +def get_versions() -> Dict[str, Any]: """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some From c0ee0c55a39cfa669be016e97633bd6441634a06 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 23 Jul 2026 00:02:54 +0100 Subject: [PATCH 08/26] Update Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- Dockerfile | 21 -- pyproject.toml | 5 + setup.py | 3 +- versioneer.py | 651 ++++++++++++++++++++++++++++--------------------- 4 files changed, 383 insertions(+), 297 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4fe9fb89924..8b2bf75f628 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,33 +42,13 @@ ENV POLYGRAPHY_AUTOINSTALL_DEPS=1 WORKDIR /opt/monai -# Patch NVIDIA's pip constraint file: -# - keep the base image's numpy pin if present (older images pin numpy==1.26.4 as -# their torch was compiled against NumPy 1.x; newer images may ship an empty file) -# - add setuptools<71 (setuptools>=71 removed pkg_resources, breaking MetricsReloaded) -# - pin urllib3>=2 to prevent inadvertent downgrades by pip-installing legacy packages -# RUN (grep '^numpy' /etc/pip/constraint.txt || true) > /tmp/new_constraints.txt \ -# && printf 'setuptools<71\nurllib3>=2\n' >> /tmp/new_constraints.txt \ -# && cp /tmp/new_constraints.txt /etc/pip/constraint.txt - -# install full deps -# COPY requirements.txt requirements-min.txt requirements-dev.txt /tmp/ -# RUN cp /tmp/requirements.txt /tmp/req.bak \ -# && awk '!/torch/' /tmp/requirements.txt > /tmp/tmp && mv /tmp/tmp /tmp/requirements.txt \ -# && python -m pip install --upgrade --no-cache-dir --no-build-isolation pip wheel wheel-stub \ -# && python -m pip install --no-cache-dir --no-build-isolation -r /tmp/requirements-dev.txt - -# compile ext and remove temp files # TODO: remark for issue [revise the dockerfile #1276](https://github.com/Project-MONAI/MONAI/issues/1276) # please specify exact files and folders to be copied -- else, basically always, the Docker build process cannot cache # this or anything below it and always will build from at most here; one file change leads to no caching from here on... - COPY LICENSE CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md README.md versioneer.py setup.py pyproject.toml runtests.sh MANIFEST.in ./ COPY tests ./tests COPY monai ./monai -# RUN BUILD_MONAI=1 FORCE_CUDA=1 python setup.py develop \ -# && rm -rf build __pycache__ # Need to install build requirements explicitly so that no-build-isolation can be used to ensure compiled libraries are # built against the in-build PyTorch, otherwise in isolation the build system installs a different version. Update these @@ -76,5 +56,4 @@ COPY monai ./monai RUN pip install --no-cache-dir setuptools\<71 versioneer[toml]\ && FORCE_CUDA=1 pip install --no-cache-dir --no-build-isolation -e . -#FORCE_CUDA=1 pip install --build-constraint /etc/pip/constraint.txt -e . diff --git a/pyproject.toml b/pyproject.toml index b08d00eeba6..89bd2c6f6d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ requires = [ "more-itertools>=8.0", "ninja", "packaging", + "torch>=2.8.0", + "numpy>=1.24,<3.0", "backports.tarfile" # see https://github.com/Project-MONAI/MONAI/issues/8791 ] build-backend = "setuptools.build_meta" @@ -180,6 +182,9 @@ testing = [ [tool.setuptools] license-files = ["LICENSE"] +[tool.setuptools.dynamic] +version = {attr = "monai.__version__"} + [tool.versioneer] VCS = "git" style = "pep440" diff --git a/setup.py b/setup.py index 4d9badca415..2ebc7a9ba63 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ BUILD_CPP = BUILD_CUDA = False TORCH_VERSION = 0 + try: import torch @@ -126,7 +127,7 @@ def get_extensions(): ext_modules = [ extension( name="monai._C", - sources=sources, + sources=list(map(os.path.relpath, sources)), include_dirs=include_dirs, define_macros=define_macros, extra_compile_args=extra_compile_args, diff --git a/versioneer.py b/versioneer.py index 5d0a606c91d..1e3753e63fb 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,4 +1,5 @@ -# Version: 0.23 + +# Version: 0.29 """The Versioneer - like a rocketeer, but for versions. @@ -8,12 +9,12 @@ * like a rocketeer, but for versions! * https://github.com/python-versioneer/python-versioneer * Brian Warner -* License: Public Domain (CC0-1.0) -* Compatible with: Python 3.7, 3.8, 3.9, 3.10 and pypy3 +* License: Public Domain (Unlicense) +* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3 * [![Latest Version][pypi-image]][pypi-url] * [![Build Status][travis-image]][travis-url] -This is a tool for managing a recorded version number in distutils/setuptools-based +This is a tool for managing a recorded version number in setuptools-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control @@ -22,10 +23,38 @@ ## Quick Install +Versioneer provides two installation modes. The "classic" vendored mode installs +a copy of versioneer into your repository. The experimental build-time dependency mode +is intended to allow you to skip this step and simplify the process of upgrading. + +### Vendored mode + +* `pip install versioneer` to somewhere in your $PATH + * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is + available, so you can also use `conda install -c conda-forge versioneer` +* add a `[tool.versioneer]` section to your `pyproject.toml` or a + `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) + * Note that you will need to add `tomli; python_version < "3.11"` to your + build-time dependencies if you use `pyproject.toml` +* run `versioneer install --vendor` in your source tree, commit the results +* verify version information with `python setup.py version` + +### Build-time dependency mode + * `pip install versioneer` to somewhere in your $PATH -* add a `[versioneer]` section to your setup.cfg (see [Install](INSTALL.md)) -* run `versioneer install` in your source tree, commit the results -* Verify version information with `python setup.py version` + * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is + available, so you can also use `conda install -c conda-forge versioneer` +* add a `[tool.versioneer]` section to your `pyproject.toml` or a + `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md)) +* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`) + to the `requires` key of the `build-system` table in `pyproject.toml`: + ```toml + [build-system] + requires = ["setuptools", "versioneer[toml]"] + build-backend = "setuptools.build_meta" + ``` +* run `versioneer install --no-vendor` in your source tree, commit the results +* verify version information with `python setup.py version` ## Version Identifiers @@ -230,9 +259,10 @@ To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) -* edit `setup.cfg`, if necessary, to include any new configuration settings - indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. -* re-run `versioneer install` in your source tree, to replace +* edit `setup.cfg` and `pyproject.toml`, if necessary, + to include any new configuration settings indicated by the release notes. + See [UPGRADING](./UPGRADING.md) for details. +* re-run `versioneer install --[no-]vendor` in your source tree, to replace `SRC/_version.py` * commit any changed files @@ -262,9 +292,8 @@ To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. -Specifically, both are released under the Creative Commons "Public Domain -Dedication" license (CC0-1.0), as described in -https://creativecommons.org/publicdomain/zero/1.0/ . +Specifically, both are released under the "Unlicense", as described in +https://unlicense.org/. [pypi-image]: https://img.shields.io/pypi/v/versioneer.svg [pypi-url]: https://pypi.python.org/pypi/versioneer/ @@ -273,7 +302,6 @@ [travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer """ - # pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring # pylint:disable=missing-class-docstring,too-many-branches,too-many-statements # pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error @@ -287,15 +315,34 @@ import re import subprocess import sys -from typing import Callable, Dict +from pathlib import Path +from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union +from typing import NoReturn import functools +have_tomllib = True +if sys.version_info >= (3, 11): + import tomllib +else: + try: + import tomli as tomllib + except ImportError: + have_tomllib = False + class VersioneerConfig: """Container for Versioneer configuration parameters.""" + VCS: str + style: str + tag_prefix: str + versionfile_source: str + versionfile_build: Optional[str] + parentdir_prefix: Optional[str] + verbose: Optional[bool] + -def get_root(): +def get_root() -> str: """Get the project root directory. We require that all commands are run from the project root, i.e. the @@ -303,20 +350,28 @@ def get_root(): """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") + pyproject_toml = os.path.join(root, "pyproject.toml") versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): + if not ( + os.path.exists(setup_py) + or os.path.exists(pyproject_toml) + or os.path.exists(versioneer_py) + ): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") + pyproject_toml = os.path.join(root, "pyproject.toml") versioneer_py = os.path.join(root, "versioneer.py") - if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - err = ( - "Versioneer was unable to run the project root directory. " - "Versioneer requires setup.py to be executed from " - "its immediate directory (like 'python setup.py COMMAND'), " - "or in a way that lets it use sys.argv[0] to find the root " - "(like 'python path/to/setup.py COMMAND')." - ) + if not ( + os.path.exists(setup_py) + or os.path.exists(pyproject_toml) + or os.path.exists(versioneer_py) + ): + err = ("Versioneer was unable to run the project root directory. " + "Versioneer requires setup.py to be executed from " + "its immediate directory (like 'python setup.py COMMAND'), " + "or in a way that lets it use sys.argv[0] to find the root " + "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools @@ -328,38 +383,59 @@ def get_root(): my_path = os.path.realpath(os.path.abspath(__file__)) me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) - if me_dir != vsr_dir: - print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(my_path), versioneer_py)) + if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals(): + print("Warning: build in %s is using versioneer.py from %s" + % (os.path.dirname(my_path), versioneer_py)) except NameError: pass return root -def get_config_from_root(root): +def get_config_from_root(root: str) -> VersioneerConfig: """Read the project setup.cfg file to determine Versioneer config.""" # This might raise OSError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . - setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.ConfigParser() - with open(setup_cfg, "r") as cfg_file: - parser.read_file(cfg_file) - VCS = parser.get("versioneer", "VCS") # mandatory - - # Dict-like interface for non-mandatory entries - section = parser["versioneer"] + root_pth = Path(root) + pyproject_toml = root_pth / "pyproject.toml" + setup_cfg = root_pth / "setup.cfg" + section: Union[Dict[str, Any], configparser.SectionProxy, None] = None + if pyproject_toml.exists() and have_tomllib: + try: + with open(pyproject_toml, 'rb') as fobj: + pp = tomllib.load(fobj) + section = pp['tool']['versioneer'] + except (tomllib.TOMLDecodeError, KeyError) as e: + print(f"Failed to load config from {pyproject_toml}: {e}") + print("Try to load it from setup.cfg") + if not section: + parser = configparser.ConfigParser() + with open(setup_cfg) as cfg_file: + parser.read_file(cfg_file) + parser.get("versioneer", "VCS") # raise error if missing + + section = parser["versioneer"] + + # `cast`` really shouldn't be used, but its simplest for the + # common VersioneerConfig users at the moment. We verify against + # `None` values elsewhere where it matters cfg = VersioneerConfig() - cfg.VCS = VCS + cfg.VCS = section['VCS'] cfg.style = section.get("style", "") - cfg.versionfile_source = section.get("versionfile_source") + cfg.versionfile_source = cast(str, section.get("versionfile_source")) cfg.versionfile_build = section.get("versionfile_build") - cfg.tag_prefix = section.get("tag_prefix") + cfg.tag_prefix = cast(str, section.get("tag_prefix")) if cfg.tag_prefix in ("''", '""', None): cfg.tag_prefix = "" cfg.parentdir_prefix = section.get("parentdir_prefix") - cfg.verbose = section.get("verbose") + if isinstance(section, configparser.SectionProxy): + # Make sure configparser translates to bool + cfg.verbose = section.getboolean("verbose") + else: + cfg.verbose = section.get("verbose") + return cfg @@ -372,23 +448,28 @@ class NotThisMethod(Exception): HANDLERS: Dict[str, Dict[str, Callable]] = {} -def register_vcs_handler(vcs, method): # decorator +def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator """Create decorator to mark a method as the handler of a VCS.""" - - def decorate(f): + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" HANDLERS.setdefault(vcs, {})[method] = f return f - return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): +def run_command( + commands: List[str], + args: List[str], + cwd: Optional[str] = None, + verbose: bool = False, + hide_stderr: bool = False, + env: Optional[Dict[str, str]] = None, +) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) process = None - popen_kwargs = {} + popen_kwargs: Dict[str, Any] = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() @@ -399,17 +480,12 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env= try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen( - [command] + args, - cwd=cwd, - env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr else None), - **popen_kwargs, - ) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None), **popen_kwargs) break - except OSError: - e = sys.exc_info()[1] + except OSError as e: if e.errno == errno.ENOENT: continue if verbose: @@ -429,17 +505,16 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env= return stdout, process.returncode -LONG_VERSION_PY[ - "git" -] = r''' +LONG_VERSION_PY['git'] = r''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. -# This file is released into the public domain. Generated by -# versioneer-0.23 (https://github.com/python-versioneer/python-versioneer) +# This file is released into the public domain. +# Generated by versioneer-0.29 +# https://github.com/python-versioneer/python-versioneer """Git implementation of _version.py.""" @@ -448,11 +523,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env= import re import subprocess import sys -from typing import Callable, Dict +from typing import Any, Callable, Dict, List, Optional, Tuple import functools -def get_keywords(): +def get_keywords() -> Dict[str, str]: """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must @@ -468,8 +543,15 @@ def get_keywords(): class VersioneerConfig: """Container for Versioneer configuration parameters.""" + VCS: str + style: str + tag_prefix: str + parentdir_prefix: str + versionfile_source: str + verbose: bool + -def get_config(): +def get_config() -> VersioneerConfig: """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py @@ -491,9 +573,9 @@ class NotThisMethod(Exception): HANDLERS: Dict[str, Dict[str, Callable]] = {} -def register_vcs_handler(vcs, method): # decorator +def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f): + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} @@ -502,13 +584,19 @@ def decorate(f): return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command( + commands: List[str], + args: List[str], + cwd: Optional[str] = None, + verbose: bool = False, + hide_stderr: bool = False, + env: Optional[Dict[str, str]] = None, +) -> Tuple[Optional[str], Optional[int]]: """Call the given command(s).""" assert isinstance(commands, list) process = None - popen_kwargs = {} + popen_kwargs: Dict[str, Any] = {} if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() @@ -524,8 +612,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) break - except OSError: - e = sys.exc_info()[1] + except OSError as e: if e.errno == errno.ENOENT: continue if verbose: @@ -545,7 +632,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, return stdout, process.returncode -def versions_from_parentdir(parentdir_prefix, root, verbose): +def versions_from_parentdir( + parentdir_prefix: str, + root: str, + verbose: bool, +) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both @@ -570,13 +661,13 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): @register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): +def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. - keywords = {} + keywords: Dict[str, str] = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: @@ -598,7 +689,11 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): +def git_versions_from_keywords( + keywords: Dict[str, str], + tag_prefix: str, + verbose: bool, +) -> Dict[str, Any]: """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") @@ -662,7 +757,12 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): +def git_pieces_from_vcs( + tag_prefix: str, + root: str, + verbose: bool, + runner: Callable = run_command +) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -681,7 +781,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): runner = functools.partial(runner, env=env) _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) @@ -692,7 +792,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): describe_out, rc = runner(GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*" - ], cwd=root) + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -702,7 +802,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() - pieces = {} + pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None @@ -794,14 +894,14 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): return pieces -def plus_or_dot(pieces): +def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" -def render_pep440(pieces): +def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you @@ -826,7 +926,7 @@ def render_pep440(pieces): return rendered -def render_pep440_branch(pieces): +def render_pep440_branch(pieces: Dict[str, Any]) -> str: """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards @@ -856,7 +956,7 @@ def render_pep440_branch(pieces): return rendered -def pep440_split_post(ver): +def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the @@ -866,7 +966,7 @@ def pep440_split_post(ver): return vc[0], int(vc[1] or 0) if len(vc) == 2 else None -def render_pep440_pre(pieces): +def render_pep440_pre(pieces: Dict[str, Any]) -> str: """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: @@ -890,7 +990,7 @@ def render_pep440_pre(pieces): return rendered -def render_pep440_post(pieces): +def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards @@ -917,7 +1017,7 @@ def render_pep440_post(pieces): return rendered -def render_pep440_post_branch(pieces): +def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. @@ -946,7 +1046,7 @@ def render_pep440_post_branch(pieces): return rendered -def render_pep440_old(pieces): +def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. @@ -968,7 +1068,7 @@ def render_pep440_old(pieces): return rendered -def render_git_describe(pieces): +def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. @@ -988,7 +1088,7 @@ def render_git_describe(pieces): return rendered -def render_git_describe_long(pieces): +def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. @@ -1008,7 +1108,7 @@ def render_git_describe_long(pieces): return rendered -def render(pieces, style): +def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", @@ -1044,7 +1144,7 @@ def render(pieces, style): "date": pieces.get("date")} -def get_versions(): +def get_versions() -> Dict[str, Any]: """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some @@ -1092,13 +1192,13 @@ def get_versions(): @register_vcs_handler("git", "get_keywords") -def git_get_keywords(versionfile_abs): +def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. - keywords = {} + keywords: Dict[str, str] = {} try: with open(versionfile_abs, "r") as fobj: for line in fobj: @@ -1120,7 +1220,11 @@ def git_get_keywords(versionfile_abs): @register_vcs_handler("git", "keywords") -def git_versions_from_keywords(keywords, tag_prefix, verbose): +def git_versions_from_keywords( + keywords: Dict[str, str], + tag_prefix: str, + verbose: bool, +) -> Dict[str, Any]: """Get version information from git keywords.""" if "refnames" not in keywords: raise NotThisMethod("Short version file found") @@ -1146,7 +1250,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)} + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -1155,7 +1259,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = {r for r in refs if re.search(r"\d", r)} + tags = {r for r in refs if re.search(r'\d', r)} if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -1163,35 +1267,33 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix) :] + r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') - if not re.match(r"\d", r): + if not re.match(r'\d', r): continue if verbose: print("picking %s" % r) - return { - "version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, - "error": None, - "date": date, - } + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None, + "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") - return { - "version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, - "error": "no suitable tags", - "date": None, - } + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") -def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): +def git_pieces_from_vcs( + tag_prefix: str, + root: str, + verbose: bool, + runner: Callable = run_command +) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* @@ -1209,7 +1311,8 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=not verbose) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -1217,9 +1320,10 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner( - GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", f"{tag_prefix}[[:digit:]]*"], cwd=root - ) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", f"{tag_prefix}[[:digit:]]*" + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -1229,12 +1333,13 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() - pieces = {} + pieces: Dict[str, Any] = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root) + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") @@ -1274,16 +1379,17 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: - git_describe = git_describe[: git_describe.rindex("-dirty")] + git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX - mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) + mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) return pieces # tag @@ -1292,9 +1398,10 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix) :] + pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) @@ -1318,7 +1425,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command): return pieces -def do_vcs_install(versionfile_source, ipy): +def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None: """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py @@ -1330,14 +1437,15 @@ def do_vcs_install(versionfile_source, ipy): files = [versionfile_source] if ipy: files.append(ipy) - try: - my_path = __file__ - if my_path.endswith(".pyc") or my_path.endswith(".pyo"): - my_path = os.path.splitext(my_path)[0] + ".py" - versioneer_file = os.path.relpath(my_path) - except NameError: - versioneer_file = "versioneer.py" - files.append(versioneer_file) + if "VERSIONEER_PEP518" not in globals(): + try: + my_path = __file__ + if my_path.endswith((".pyc", ".pyo")): + my_path = os.path.splitext(my_path)[0] + ".py" + versioneer_file = os.path.relpath(my_path) + except NameError: + versioneer_file = "versioneer.py" + files.append(versioneer_file) present = False try: with open(".gitattributes", "r") as fobj: @@ -1355,7 +1463,11 @@ def do_vcs_install(versionfile_source, ipy): run_command(GITS, ["add", "--"] + files) -def versions_from_parentdir(parentdir_prefix, root, verbose): +def versions_from_parentdir( + parentdir_prefix: str, + root: str, + verbose: bool, +) -> Dict[str, Any]: """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both @@ -1367,23 +1479,20 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return { - "version": dirname[len(parentdir_prefix) :], - "full-revisionid": None, - "dirty": False, - "error": None, - "date": None, - } + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) + print("Tried directories %s but none started with prefix %s" % + (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") SHORT_VERSION_PY = """ -# This file was generated by 'versioneer.py' (0.23) from +# This file was generated by 'versioneer.py' (0.29) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. @@ -1400,39 +1509,41 @@ def get_versions(): """ -def versions_from_file(filename): +def versions_from_file(filename: str) -> Dict[str, Any]: """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except OSError: raise NotThisMethod("unable to read _version.py") - mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) + mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", + contents, re.M | re.S) if not mo: - mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) + mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", + contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) -def write_to_version_file(filename, versions): +def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None: """Write the given version number to the given _version.py file.""" - os.unlink(filename) - contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) + contents = json.dumps(versions, sort_keys=True, + indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) -def plus_or_dot(pieces): +def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" -def render_pep440(pieces): +def render_pep440(pieces: Dict[str, Any]) -> str: """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you @@ -1450,13 +1561,14 @@ def render_pep440(pieces): rendered += ".dirty" else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered -def render_pep440_branch(pieces): +def render_pep440_branch(pieces: Dict[str, Any]) -> str: """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] . The ".dev0" means not master branch. Note that .dev0 sorts backwards @@ -1479,13 +1591,14 @@ def render_pep440_branch(pieces): rendered = "0" if pieces["branch"] != "master": rendered += ".dev0" - rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) + rendered += "+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered -def pep440_split_post(ver): +def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: """Split pep440 version string at the post-release segment. Returns the release segments before the post-release and the @@ -1495,7 +1608,7 @@ def pep440_split_post(ver): return vc[0], int(vc[1] or 0) if len(vc) == 2 else None -def render_pep440_pre(pieces): +def render_pep440_pre(pieces: Dict[str, Any]) -> str: """TAG[.postN.devDISTANCE] -- No -dirty. Exceptions: @@ -1519,7 +1632,7 @@ def render_pep440_pre(pieces): return rendered -def render_pep440_post(pieces): +def render_pep440_post(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards @@ -1546,7 +1659,7 @@ def render_pep440_post(pieces): return rendered -def render_pep440_post_branch(pieces): +def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] . The ".dev0" means not master branch. @@ -1575,7 +1688,7 @@ def render_pep440_post_branch(pieces): return rendered -def render_pep440_old(pieces): +def render_pep440_old(pieces: Dict[str, Any]) -> str: """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. @@ -1597,7 +1710,7 @@ def render_pep440_old(pieces): return rendered -def render_git_describe(pieces): +def render_git_describe(pieces: Dict[str, Any]) -> str: """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. @@ -1617,7 +1730,7 @@ def render_git_describe(pieces): return rendered -def render_git_describe_long(pieces): +def render_git_describe_long(pieces: Dict[str, Any]) -> str: """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. @@ -1637,16 +1750,14 @@ def render_git_describe_long(pieces): return rendered -def render(pieces, style): +def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" if pieces["error"]: - return { - "version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None, - } + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None} if not style or style == "default": style = "pep440" # the default @@ -1670,20 +1781,16 @@ def render(pieces, style): else: raise ValueError("unknown style '%s'" % style) - return { - "version": rendered, - "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], - "error": None, - "date": pieces.get("date"), - } + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" -def get_versions(verbose=False): +def get_versions(verbose: bool = False) -> Dict[str, Any]: """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. @@ -1698,8 +1805,9 @@ def get_versions(verbose=False): assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS - verbose = verbose or cfg.verbose - assert cfg.versionfile_source is not None, "please set versioneer.versionfile_source" + verbose = verbose or bool(cfg.verbose) # `bool()` used to avoid `None` + assert cfg.versionfile_source is not None, \ + "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) @@ -1753,21 +1861,17 @@ def get_versions(verbose=False): if verbose: print("unable to compute version") - return { - "version": "0+unknown", - "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", - "date": None, - } + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, "error": "unable to compute version", + "date": None} -def get_version(): +def get_version() -> str: """Get the short version string for this project.""" return get_versions()["version"] -def get_cmdclass(cmdclass=None): +def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None): """Get the custom setuptools subclasses used by Versioneer. If the package uses a different cmdclass (e.g. one from numpy), it @@ -1795,16 +1899,16 @@ def get_cmdclass(cmdclass=None): class cmd_version(Command): description = "report generated version string" - user_options = [] - boolean_options = [] + user_options: List[Tuple[str, str, str]] = [] + boolean_options: List[str] = [] - def initialize_options(self): + def initialize_options(self) -> None: pass - def finalize_options(self): + def finalize_options(self) -> None: pass - def run(self): + def run(self) -> None: vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) @@ -1812,7 +1916,6 @@ def run(self): print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) - cmds["version"] = cmd_version # we override "build_py" in setuptools @@ -1834,13 +1937,13 @@ def run(self): # but the build_py command is not expected to copy any files. # we override different "build_py" commands for both environments - if "build_py" in cmds: - _build_py = cmds["build_py"] + if 'build_py' in cmds: + _build_py: Any = cmds['build_py'] else: from setuptools.command.build_py import build_py as _build_py class cmd_build_py(_build_py): - def run(self): + def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() @@ -1852,19 +1955,19 @@ def run(self): # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: - target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) - cmds["build_py"] = cmd_build_py - if "build_ext" in cmds: - _build_ext = cmds["build_ext"] + if 'build_ext' in cmds: + _build_ext: Any = cmds['build_ext'] else: from setuptools.command.build_ext import build_ext as _build_ext class cmd_build_ext(_build_ext): - def run(self): + def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() @@ -1877,22 +1980,21 @@ def run(self): return # now locate _version.py in the new build/ directory and replace # it with an updated value - target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) + if not cfg.versionfile_build: + return + target_versionfile = os.path.join(self.build_lib, + cfg.versionfile_build) if not os.path.exists(target_versionfile): - print( - f"Warning: {target_versionfile} does not exist, skipping " - "version update. This can happen if you are running build_ext " - "without first running build_py." - ) + print(f"Warning: {target_versionfile} does not exist, skipping " + "version update. This can happen if you are running build_ext " + "without first running build_py.") return print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) - cmds["build_ext"] = cmd_build_ext if "cx_Freeze" in sys.modules: # cx_freeze enabled? - from cx_Freeze.dist import build_exe as _build_exe - + from cx_Freeze.dist import build_exe as _build_exe # type: ignore # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ @@ -1901,7 +2003,7 @@ def run(self): # ... class cmd_build_exe(_build_exe): - def run(self): + def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() @@ -1913,25 +2015,24 @@ def run(self): os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] - f.write( - LONG - % { - "DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - } - ) - + f.write(LONG % + {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] - if "py2exe" in sys.modules: # py2exe enabled? - from py2exe.distutils_buildexe import py2exe as _py2exe + if 'py2exe' in sys.modules: # py2exe enabled? + try: + from py2exe.setuptools_buildexe import py2exe as _py2exe # type: ignore + except ImportError: + from py2exe.distutils_buildexe import py2exe as _py2exe # type: ignore class cmd_py2exe(_py2exe): - def run(self): + def run(self) -> None: root = get_root() cfg = get_config_from_root(root) versions = get_versions() @@ -1943,27 +2044,23 @@ def run(self): os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] - f.write( - LONG - % { - "DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - } - ) - + f.write(LONG % + {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) cmds["py2exe"] = cmd_py2exe # sdist farms its file list building out to egg_info - if "egg_info" in cmds: - _sdist = cmds["egg_info"] + if 'egg_info' in cmds: + _egg_info: Any = cmds['egg_info'] else: from setuptools.command.egg_info import egg_info as _egg_info class cmd_egg_info(_egg_info): - def find_sources(self): + def find_sources(self) -> None: # egg_info.find_sources builds the manifest list and writes it # in one shot super().find_sources() @@ -1971,7 +2068,7 @@ def find_sources(self): # Modify the filelist and normalize it root = get_root() cfg = get_config_from_root(root) - self.filelist.append("versioneer.py") + self.filelist.append('versioneer.py') if cfg.versionfile_source: # There are rare cases where versionfile_source might not be # included by default, so we must be explicit @@ -1984,23 +2081,23 @@ def find_sources(self): # We will instead replicate their final normalization (to unicode, # and POSIX-style paths) from setuptools import unicode_utils + normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/') + for f in self.filelist.files] - normalized = [unicode_utils.filesys_decode(f).replace(os.sep, "/") for f in self.filelist.files] + manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt') + with open(manifest_filename, 'w') as fobj: + fobj.write('\n'.join(normalized)) - manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") - with open(manifest_filename, "w") as fobj: - fobj.write("\n".join(normalized)) - - cmds["egg_info"] = cmd_egg_info + cmds['egg_info'] = cmd_egg_info # we override different "sdist" commands for both environments - if "sdist" in cmds: - _sdist = cmds["sdist"] + if 'sdist' in cmds: + _sdist: Any = cmds['sdist'] else: from setuptools.command.sdist import sdist as _sdist class cmd_sdist(_sdist): - def run(self): + def run(self) -> None: versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old @@ -2008,7 +2105,7 @@ def run(self): self.distribution.metadata.version = versions["version"] return _sdist.run(self) - def make_release_tree(self, base_dir, files): + def make_release_tree(self, base_dir: str, files: List[str]) -> None: root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) @@ -2017,8 +2114,8 @@ def make_release_tree(self, base_dir, files): # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, self._versioneer_generated_versions) - + write_to_version_file(target_versionfile, + self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds @@ -2073,14 +2170,16 @@ def make_release_tree(self, base_dir, files): """ -def do_setup(): +def do_setup() -> int: """Do main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) - except (OSError, configparser.NoSectionError, configparser.NoOptionError) as e: + except (OSError, configparser.NoSectionError, + configparser.NoOptionError) as e: if isinstance(e, (OSError, configparser.NoSectionError)): - print("Adding sample versioneer config to setup.cfg", file=sys.stderr) + print("Adding sample versioneer config to setup.cfg", + file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) @@ -2089,18 +2188,16 @@ def do_setup(): print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] - f.write( - LONG - % { - "DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - } - ) - - ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") + f.write(LONG % {"DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + }) + + ipy = os.path.join(os.path.dirname(cfg.versionfile_source), + "__init__.py") + maybe_ipy: Optional[str] = ipy if os.path.exists(ipy): try: with open(ipy, "r") as f: @@ -2121,16 +2218,16 @@ def do_setup(): print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) - ipy = None + maybe_ipy = None # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. - do_vcs_install(cfg.versionfile_source, ipy) + do_vcs_install(cfg.versionfile_source, maybe_ipy) return 0 -def scan_setup_py(): +def scan_setup_py() -> int: """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False @@ -2167,10 +2264,14 @@ def scan_setup_py(): return errors +def setup_command() -> NoReturn: + """Set up Versioneer and exit with appropriate error code.""" + errors = do_setup() + errors += scan_setup_py() + sys.exit(1 if errors else 0) + + if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": - errors = do_setup() - errors += scan_setup_py() - if errors: - sys.exit(1) + setup_command() From ce9b4ab9edfe4b4ade29d34b679d07e306278acc Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:32:12 +0100 Subject: [PATCH 09/26] Docker updates Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- Dockerfile | 12 ++++-------- Dockerfile.slim | 20 ++++++++------------ monai/config/print_dependencies.py | 4 ++-- pyproject.toml | 20 +++++++++++--------- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8b2bf75f628..473e588704b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,11 +49,7 @@ COPY LICENSE CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md README.md versionee COPY tests ./tests COPY monai ./monai - -# Need to install build requirements explicitly so that no-build-isolation can be used to ensure compiled libraries are -# built against the in-build PyTorch, otherwise in isolation the build system installs a different version. Update these -# requirements if the build configuration changes, setuptools is limited to <71 to account for MetricsReloaded issue. -RUN pip install --no-cache-dir setuptools\<71 versioneer[toml]\ - && FORCE_CUDA=1 pip install --no-cache-dir --no-build-isolation -e . - - +# Need to install build requirements explicitly so that no-build-isolation can be used. This needed to make pip build +# against the included version of PyTorch, rather than install a new version in the isolated environment. +RUN python monai/config/print_dependencies.py build-system | xargs pip install --no-cache-dir --no-build-isolation \ + && FORCE_CUDA=1 pip install --no-cache-dir --no-build-isolation -e .[all,testing] diff --git a/Dockerfile.slim b/Dockerfile.slim index 1b3cf3bcd4c..9231165d971 100644 --- a/Dockerfile.slim +++ b/Dockerfile.slim @@ -28,9 +28,9 @@ RUN apt update && apt upgrade -y && \ wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb && \ dpkg -i cuda-keyring_1.1-1_all.deb && \ apt update && \ - ${APT_INSTALL} cuda-toolkit-12-9 && \ + ${APT_INSTALL} cuda-toolkit-13-3 && \ rm -rf /usr/lib/python*/EXTERNALLY-MANAGED /var/lib/apt/lists/* && \ - python -m pip install --upgrade --no-cache-dir --no-build-isolation pip + python -m pip install --upgrade --no-cache-dir pip # TODO: remark for issue [revise the dockerfile](https://github.com/zarr-developers/numcodecs/issues/431) RUN if [[ $(uname -m) =~ "aarch64" ]]; then \ @@ -46,18 +46,14 @@ RUN wget -q ${NGC_CLI_URI} && unzip ngccli_linux.zip && chmod u+x ngc-cli/ngc && WORKDIR /opt/monai -# copy relevant parts of repo -COPY requirements.txt requirements-min.txt requirements-dev.txt versioneer.py setup.py setup.cfg pyproject.toml ./ -COPY LICENSE CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md README.md MANIFEST.in runtests.sh ./ +# TODO: remark for issue [revise the dockerfile #1276](https://github.com/Project-MONAI/MONAI/issues/1276) +# please specify exact files and folders to be copied -- else, basically always, the Docker build process cannot cache +# this or anything below it and always will build from at most here; one file change leads to no caching from here on... +COPY LICENSE CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md README.md versioneer.py setup.py pyproject.toml runtests.sh MANIFEST.in ./ COPY tests ./tests COPY monai ./monai -# install full deps -RUN python -m pip install --no-cache-dir --no-build-isolation -U wheel wheel-stub -RUN python -m pip install --no-cache-dir --no-build-isolation "torch>=2.8.0,<2.11" -r requirements-dev.txt - -# compile ext -RUN CUDA_HOME=/usr/local/cuda FORCE_CUDA=1 USE_COMPILED=1 BUILD_MONAI=1 python setup.py develop +RUN BUILD_MONAI=1 FORCE_CUDA=1 pip install --no-cache-dir -e .[all,testing] # recreate the image without the installed CUDA packages then copy the installed MONAI and Python directories FROM ${IMAGE} AS build2 @@ -77,7 +73,7 @@ ARG PYTHON_VERSION=3.11 COPY --from=build /usr/local/lib/python${PYTHON_VERSION}/dist-packages /usr/local/lib/python${PYTHON_VERSION}/dist-packages COPY --from=build /usr/local/bin /usr/local/bin -RUN rm -rf /opt/monai/build /opt/monai/monai.egg-info && \ +RUN rm -rf /opt/monai/build /opt/monai/monai.egg-info /opt/monai/tests/testing_data && \ find /opt /usr/local/lib -type d -name __pycache__ -exec rm -rf {} + # flatten all layers down to one diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index c7082e0795e..f8ce4394f43 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -29,9 +29,9 @@ def parse_dependencies(filename=TOML_FILE, sections=[]): # these imports should be here to avoid attempting to import when MONAI is imported and both packages are missing if sys.version_info.minor >= 11: - import tomlib + import tomllib - load_func = tomlib.loads + load_func = tomllib.loads else: import tomli diff --git a/pyproject.toml b/pyproject.toml index 89bd2c6f6d8..3e7584a9380 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ classifiers = [ dependencies = [ "torch>=2.8.0", "numpy>=1.24,<3.0", + "setuptools<71.0.0" # used here to match requirement in build-system and prevent different PyTorch installations ] dynamic = ["version"] @@ -60,10 +61,6 @@ all = [ "filelock<3.12.0", "fire", "gdown>=4.7.3", - # TODO: fix this URL before finalising - "MetricsReloaded @ git+https://github.com/ericspod/MetricsReloaded@monai-support-fix", - # "MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support", - "segment_anything @ git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588", "h5py", "huggingface_hub", "imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'", @@ -72,8 +69,9 @@ all = [ "lmdb", "lpips==0.1.4", "matplotlib>=3.6.3", + # TODO: change URL to https://github.com/Project-MONAI/MetricsReloaded@monai-support before finalising + "MetricsReloaded @ git+https://github.com/ericspod/MetricsReloaded@monai-support-fix", "mlflow>=2.12.2,<3.13", - "mypy>=1.5.0, <1.12.0", "nibabel", "ninja", "nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine", @@ -93,10 +91,11 @@ all = [ "pybind11", "pydicom", "pynrrd", - "pytest", + "pytest", # FIXME: CuPy 14.1.0 required this through polygraphy and trt_compiler https://github.com/Project-MONAI/MONAI/pull/8867 "pytorch-ignite", "pyyaml", "requests", + "segment_anything @ git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588", "scikit-image>=0.19.0", "scipy>=1.12.0", "tensorboard>=2.12.0", @@ -113,6 +112,7 @@ cucim = [ "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'" ] +cupy = ["cupy"] einops = ["einops"] fire = ["fire"] gdown = ["gdown>=4.7.3"] @@ -127,7 +127,7 @@ lmdb = ["lmdb"] lpips = ["lpips==0.1.4"] matplotlib = ["matplotlib>=3.6.3"] mccabe = ["mccabe"] -metrics_reloaded = ["MetricsReloaded @ git+https://github.com/ericspod/MetricsReloaded@monai-support-fix"] +metrics_reloaded = ["MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support"] mlflow = ["mlflow>=2.12.2,<3.13"] mypy = ["mypy>=1.5.0, <1.12.0"] nibabel = ["nibabel"] @@ -161,11 +161,13 @@ torchvision = ["torchvision"] tqdm = ["tqdm>=4.47.0"] transformers = ["transformers>=4.53.0, <5.0"] # 5.x references torch.float8_e8m0fnu absent in older PyTorch builds zarr = ["zarr"] -# these dependencies are for testing/development only, they aren't needed for regular use so don't appear in "all" +# these dependencies are for testing/building only, they aren't needed for regular use so don't appear in "all" testing = [ "black>=26.3.1", "coverage>=5.5", - "isort>=5.1, <6, !=6.0.0", + "isort>=5.1,<6,!=6.0.0", + "mccabe", + "mypy>=1.5.0,<1.12.0", "packaging", "parameterized", "pep8-naming", From 1d619adf280d42e69b6029c94ee7285ec8ee3dcf Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:15:44 +0100 Subject: [PATCH 10/26] Docs update Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- Dockerfile.slim | 2 +- docs/source/installation.md | 55 +++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/Dockerfile.slim b/Dockerfile.slim index 9231165d971..aae3d2638a5 100644 --- a/Dockerfile.slim +++ b/Dockerfile.slim @@ -73,7 +73,7 @@ ARG PYTHON_VERSION=3.11 COPY --from=build /usr/local/lib/python${PYTHON_VERSION}/dist-packages /usr/local/lib/python${PYTHON_VERSION}/dist-packages COPY --from=build /usr/local/bin /usr/local/bin -RUN rm -rf /opt/monai/build /opt/monai/monai.egg-info /opt/monai/tests/testing_data && \ +RUN rm -rf /opt/monai/build /opt/monai/monai.egg-info && \ find /opt /usr/local/lib -type d -name __pycache__ -exec rm -rf {} + # flatten all layers down to one diff --git a/docs/source/installation.md b/docs/source/installation.md index 2d9e2a7f0ed..55b4124fa8d 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -19,13 +19,13 @@ --- -MONAI's core functionality is written in Python 3 (>= 3.10) and only requires [Numpy](https://numpy.org/) and [Pytorch](https://pytorch.org/). +MONAI's core functionality is written in Python 3 (>= 3.10) and only requires [Numpy](https://numpy.org/) and [PyTorch](https://pytorch.org/). The package is currently distributed via Github as the primary source code repository, and the Python package index (PyPI). The pre-built Docker images are made available on DockerHub. To install optional features such as handling the NIfTI files using -[Nibabel](https://nipy.org/nibabel/), or building workflows using [Pytorch +[Nibabel](https://nipy.org/nibabel/), or building workflows using [PyTorch Ignite](https://pytorch.org/ignite/), please follow the instructions: - [Installing the recommended dependencies](#installing-the-recommended-dependencies) @@ -49,6 +49,25 @@ To install the [current milestone release](https://pypi.org/project/monai/): pip install monai ``` +MONAI supports the extras syntax such as `pip install 'monai[nibabel]'`. The options are + +``` +clearml, cucim, cupy, einops, fire, gdown, h5py, huggingface_hub, hyena, ignite, imagecodecs, itk, jsonschema, lmdb, lpips, matplotlib, mccabe, metrics_reloaded, mlflow, mypy, nibabel, nni, onnx, openslide, optuna, pandas, pillow, polygraphy, psutil, pyamg, pybind11, pydicom, pynrrd, pytest, pynvml, pyyaml, requests, segment_anything, scipy, skimage, tensorboard, tensorboardX, tifffile, torchio, torchvision, tqdm, transformers, zarr +``` + +which correspond to the packages: `clearml`, `cupy`, `einops`, `fire`, `gdown`, `h5py`, `huggingface_hub`, `nvsubquadratic`, `omegaconf`, `pytorch-ignite`, `imagecodecs`, `itk`, `jsonschema`, `lmdb`, `lpips`, `matplotlib`, `mccabe`, `MetricsReloaded`, `mlflow`, `mypy`, `nibabel`, `nni`, `filelock`, `onnx`, `onnxruntime`, `onnx_graphsurgeon`, `onnxscript`, `openslide-python`, `openslide-bin`, `optuna`, `pandas`, `pillow`, `polygraphy`, `psutil`, `pyamg`, `pybind11`, `pydicom`, `pynrrd`, `pytest`, `nvidia-ml-py`, `pyyaml`, `requests`, `segment_anything`, `scipy`, `scikit-image`, `tensorboard`, `tensorboardX`, `tifffile`, `torchio`, `torchvision`, `tqdm`, `transformers`, `zarr`. + +Almost all of these can be installed together with the `all` option. For development on MONAI, this should be accompanied by `testing` which will install the testing static checking packages. + +The `hyena` extra pulls in [`nvsubquadratic`](https://github.com/NVIDIA-BioNeMo/nvSubquadratic), +required by `HyenaNDUNETR` / `HyenaMixer` / `HyenaTransformerBlock` (subquadratic +O(N log N) alternatives to windowed self-attention). Install with +`pip install 'monai[hyena]'`. + +The command `pip install 'monai[all,hyena,testing]'` installs all the optional dependencies. + +When installing MONAI, the compiled extensions are not compiled by default. Set the environment variable `BUILD_MONAI` to `1` when invoking `pip` to compile these, see below for details. + ### Weekly preview release To install the [weekly preview release](https://pypi.org/project/monai-weekly/): @@ -110,13 +129,19 @@ or, to build with MONAI C++/CUDA extensions: BUILD_MONAI=1 pip install git+https://github.com/Project-MONAI/MONAI ``` -To build the extensions, if the system environment already has a version of Pytorch installed, +To build the extensions, if the system environment already has a version of PyTorch installed, `--no-build-isolation` might be preferred: ```bash BUILD_MONAI=1 pip install --no-build-isolation git+https://github.com/Project-MONAI/MONAI ``` +When using build isolation (pip's default behaviour), a version of PyTorch must be installed which may not be the same as an existing install. This can cause the compiled libraries to be built against an ABI-incompatible PyTorch and thus not function at runtime. Building without isolation requires the current environment to have the necessary building libraries already installed. See the `build-system` section of `pyproject.toml` for these libraries, or use the following to install them: + +```bash +python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation +``` + On Windows the inline `BUILD_MONAI=1 pip install ...` form is not supported by `cmd.exe` or PowerShell. Set the environment variable first, then run either install command shown above: @@ -263,12 +288,14 @@ cd MONAI/ pip install -e ".[all]" ``` -To install all optional dependencies with `pip` based on MONAI development environment settings: +To install all optional dependencies with `pip` based on MONAI development environment settings without installing +MONAI itself: ```bash git clone https://github.com/Project-MONAI/MONAI.git cd MONAI/ -pip install -r requirements-dev.txt +python monai/config/print_dependencies.py \* > requirements.txt +pip install -r requirements.txt ``` To install all optional dependencies with `conda` based on MONAI development environment settings (`environment-dev.yml`; @@ -280,21 +307,3 @@ cd MONAI/ conda create -n python= # eg 3.10 conda env update -n -f environment-dev.yml ``` - -Since MONAI v0.2.0, the extras syntax such as `pip install 'monai[nibabel]'` is available via PyPI. - -- The options are - -``` -[nibabel, skimage, scipy, pillow, tensorboard, gdown, ignite, torchvision, itk, tqdm, lmdb, psutil, cucim, openslide, pandas, einops, transformers, mlflow, clearml, matplotlib, tensorboardX, tifffile, imagecodecs, pyyaml, fire, jsonschema, ninja, pynrrd, pydicom, h5py, nni, optuna, onnx, onnxruntime, zarr, lpips, pynvml, huggingface_hub, hyena] -``` - -which correspond to `nibabel`, `scikit-image`,`scipy`, `pillow`, `tensorboard`, -`gdown`, `pytorch-ignite`, `torchvision`, `itk`, `tqdm`, `lmdb`, `psutil`, `cucim`, `openslide-python`, `pandas`, `einops`, `transformers`, `mlflow`, `clearml`, `matplotlib`, `tensorboardX`, `tifffile`, `imagecodecs`, `pyyaml`, `fire`, `jsonschema`, `ninja`, `pynrrd`, `pydicom`, `h5py`, `nni`, `optuna`, `onnx`, `onnxruntime`, `zarr`, `lpips`, `nvidia-ml-py`, `huggingface_hub`, `pyamg`, and `nvsubquadratic` respectively. - -The `hyena` extra pulls in [`nvsubquadratic`](https://github.com/NVIDIA-BioNeMo/nvSubquadratic), -required by `HyenaNDUNETR` / `HyenaMixer` / `HyenaTransformerBlock` (subquadratic -O(N log N) alternatives to windowed self-attention). Install with -`pip install 'monai[hyena]'`. - -- `pip install 'monai[all]'` installs all the optional dependencies. From bbdb0bc38953f15377a98b15791f7ca8ef39f53b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:21:06 +0000 Subject: [PATCH 11/26] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- environment-dev.yml | 2 +- monai/config/print_dependencies.py | 2 +- pyproject.toml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/environment-dev.yml b/environment-dev.yml index 6c4d4ed93eb..a9c79cc1be7 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -4,4 +4,4 @@ channels: dependencies: - pip - pip: - - -e .[all,testing] \ No newline at end of file + - -e .[all,testing] diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index f8ce4394f43..2b65ee11462 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -37,7 +37,7 @@ def parse_dependencies(filename=TOML_FILE, sections=[]): load_func = tomli.loads - with open(filename, "r") as o: + with open(filename) as o: data = load_func(o.read()) proj = data[PROJ_KEY] diff --git a/pyproject.toml b/pyproject.toml index 3e7584a9380..2183e674139 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ [build-system] requires = [ "setuptools<71.0.0", # pkg_resources removed in setuptools>=71; needed by MetricsReloaded setup.py - "wheel", + "wheel", "versioneer[toml]", "more-itertools>=8.0", "ninja", @@ -98,7 +98,7 @@ all = [ "segment_anything @ git+https://github.com/facebookresearch/segment-anything.git@6fdee8f2727f4506cfbbe553e23b895e27956588", "scikit-image>=0.19.0", "scipy>=1.12.0", - "tensorboard>=2.12.0", + "tensorboard>=2.12.0", "tensorboardX", "tifffile; platform_system == 'Linux' or platform_system == 'Darwin'", "torchio", @@ -109,7 +109,7 @@ all = [ ] clearml = ["clearml>=1.10.0rc0"] cucim = [ - "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", + "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'" ] cupy = ["cupy"] @@ -164,7 +164,7 @@ zarr = ["zarr"] # these dependencies are for testing/building only, they aren't needed for regular use so don't appear in "all" testing = [ "black>=26.3.1", - "coverage>=5.5", + "coverage>=5.5", "isort>=5.1,<6,!=6.0.0", "mccabe", "mypy>=1.5.0,<1.12.0", From 8a8fc41fa947153ae778948b1c5a58e5e15f670d Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:24:22 +0100 Subject: [PATCH 12/26] Fix Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- monai/config/print_dependencies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index f8ce4394f43..b644d8ec8b6 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -26,7 +26,7 @@ TOML_FILE = "pyproject.toml" -def parse_dependencies(filename=TOML_FILE, sections=[]): +def parse_dependencies(filename=TOML_FILE, sections=None): # these imports should be here to avoid attempting to import when MONAI is imported and both packages are missing if sys.version_info.minor >= 11: import tomllib @@ -43,6 +43,7 @@ def parse_dependencies(filename=TOML_FILE, sections=[]): proj = data[PROJ_KEY] opts = proj[OPTS_KEY] dependencies = list(proj[DEP_KEY]) + sections = sections or [] if BUILD_SYSTEM_KEY in sections: sections.remove(BUILD_SYSTEM_KEY) From 573176c6562a7a873a05c59d69d6105d76cc5c27 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:45:33 +0100 Subject: [PATCH 13/26] Review fixes Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/build_docs.yml | 2 +- .github/workflows/cicd_tests.yml | 17 +++++++++-------- .github/workflows/cron.yml | 9 +++++---- .github/workflows/integration.yml | 5 +++-- docs/source/installation.md | 2 +- monai/__init__.py | 2 +- monai/config/print_dependencies.py | 4 +++- pyproject.toml | 5 +++-- 8 files changed, 26 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 1f5e43b362c..aeb2b8a2996 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -35,7 +35,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip wheel - python -m pip install .[all,testing] + python -m pip install -r docs/requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu - name: Make html run: | cd docs/ diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index 88bab7090b6..1b796ce5cb7 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -138,13 +138,13 @@ jobs: - name: Prepare pip wheel run: | which python - python -m pip install --upgrade pip wheel - python -m pip install --user more-itertools>=8.0 + python -m pip install --upgrade pip wheel tomli - name: Install the minimum dependencies run: | # min. requirements python -m pip install torch==${{ matrix.pytorch-version }} - python -m pip install -e .[testing] + python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation + python -m pip install --no-build-isolation -e .[testing] python -m pip list shell: bash - if: matrix.os == 'linux-gpu-runner' @@ -218,8 +218,9 @@ jobs: shell: bash - name: Install the complete dependencies run: | - python -m pip install --user --upgrade pip wheel pybind11 # TODO: pybind11 added for macOS, may not be needed - python -m pip install -e .[all,testing] + python -m pip install --user --upgrade pip wheel tomli + python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation + python -m pip install -e --no-build-isolation .[all,testing] python -m pip list shell: bash - name: Run compiled (${{ runner.os }}) @@ -272,14 +273,14 @@ jobs: cache: 'pip' - name: Install dependencies + nvsubquadratic (no-deps) run: | - python -m pip install --upgrade pip wheel + python -m pip install --upgrade pip wheel tomli + python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation python -m pip install torch==${PYTORCH_VER1} torchvision==${TORCHVISION_VER1} - python -m pip install -e .[all,hyena,testing] # # nvsubquadratic runtime imports need only torch + einops + omegaconf; install # # the package itself without its core dependency tree (see job comment above). # python -m pip install omegaconf # python -m pip install --no-deps 'nvsubquadratic>=0.1.1' - python -m pip install -e .[hyena] + python -m pip install --no-build-isolation -e .[hyena] python -m pip list shell: bash - name: Run Hyena tests (CUDA-required cases skip cleanly) diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 90d1bcbd05c..fc9dcbbf5cb 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -137,7 +137,7 @@ jobs: - name: Install the dependencies run: | which python - python -m pip install --upgrade pip wheel twine + python -m pip install --upgrade pip wheel twine tomli python -m pip list - name: Run tests report coverage shell: bash @@ -159,6 +159,7 @@ jobs: # move packages to a temp dir tmp_dir=$(mktemp -d) cp dist/monai* "$tmp_dir" + python monai/config/print_dependencies.py build-system all testing > "$tmp_dir/requirements.txt" rm -r build dist monai.egg-info cd "$tmp_dir" ls -al @@ -186,7 +187,7 @@ jobs: python -c $'import torch\na,b=torch.zeros(1,device="cuda:0"),torch.zeros(1,device="cuda:1");\nwhile True:print(a,b)' > /dev/null & python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" - python -m pip install .[all,testing] + pip install --no-build-isolation -r requirements.txt PYTHONPATH="$tmp_dir":$PYTHONPATH BUILD_MONAI=1 python ./tests/runner.py -p 'test_((?!integration).)' # unit tests if pgrep python; then pkill python; fi @@ -238,8 +239,8 @@ jobs: id: monai-install run: | which python - python -m pip install --upgrade pip wheel - python -m pip install .[all,testing] + python -m pip install --upgrade pip wheel tomli + python monai/config/print_dependencies.py build-system | xargs pip install --no-cache-dir --no-build-isolation BUILD_MONAI=1 python setup.py develop # install monai nvidia-smi export CUDA_VISIBLE_DEVICES=$(python -m tests.utils | tail -n 1) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 7b9336debbf..ee9cf7e3c28 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -37,7 +37,8 @@ jobs: pip uninstall -y monai pip uninstall -y monai-weekly pip uninstall -y monai-weekly - python -m pip install .[all,testing] + python monai/config/print_dependencies.py build-system | xargs pip install --no-cache-dir --no-build-isolation + python -m pip install --no-build-isolation .[all,testing] rm -rf /github/home/.cache/torch/hub/mmars/ - name: Clean directory run: | @@ -122,7 +123,7 @@ jobs: nvidia-smi export CUDA_VISIBLE_DEVICES=$(python -m tests.utils -c 1 | tail -n 1) echo $CUDA_VISIBLE_DEVICES - python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" + python -c "import torch; print(torch.__version__); print(f'{torch.cuda.device_count()} of GPUs available')" python -c 'import torch; print(torch.rand(5,3, device=torch.device("cuda:0")))' - name: Auto3dseg latest algo diff --git a/docs/source/installation.md b/docs/source/installation.md index 55b4124fa8d..a5fe151f5f6 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -55,7 +55,7 @@ MONAI supports the extras syntax such as `pip install 'monai[nibabel]'`. The opt clearml, cucim, cupy, einops, fire, gdown, h5py, huggingface_hub, hyena, ignite, imagecodecs, itk, jsonschema, lmdb, lpips, matplotlib, mccabe, metrics_reloaded, mlflow, mypy, nibabel, nni, onnx, openslide, optuna, pandas, pillow, polygraphy, psutil, pyamg, pybind11, pydicom, pynrrd, pytest, pynvml, pyyaml, requests, segment_anything, scipy, skimage, tensorboard, tensorboardX, tifffile, torchio, torchvision, tqdm, transformers, zarr ``` -which correspond to the packages: `clearml`, `cupy`, `einops`, `fire`, `gdown`, `h5py`, `huggingface_hub`, `nvsubquadratic`, `omegaconf`, `pytorch-ignite`, `imagecodecs`, `itk`, `jsonschema`, `lmdb`, `lpips`, `matplotlib`, `mccabe`, `MetricsReloaded`, `mlflow`, `mypy`, `nibabel`, `nni`, `filelock`, `onnx`, `onnxruntime`, `onnx_graphsurgeon`, `onnxscript`, `openslide-python`, `openslide-bin`, `optuna`, `pandas`, `pillow`, `polygraphy`, `psutil`, `pyamg`, `pybind11`, `pydicom`, `pynrrd`, `pytest`, `nvidia-ml-py`, `pyyaml`, `requests`, `segment_anything`, `scipy`, `scikit-image`, `tensorboard`, `tensorboardX`, `tifffile`, `torchio`, `torchvision`, `tqdm`, `transformers`, `zarr`. +which correspond to the packages: `clearml`, `cucim` (`cucim-cu12` or `cucim-cu13`), `cupy`, `einops`, `fire`, `gdown`, `h5py`, `huggingface_hub`, `nvsubquadratic`, `omegaconf`, `pytorch-ignite`, `imagecodecs`, `itk`, `jsonschema`, `lmdb`, `lpips`, `matplotlib`, `mccabe`, `MetricsReloaded`, `mlflow`, `mypy`, `nibabel`, `nni`, `filelock`, `onnx`, `onnxruntime`, `onnx_graphsurgeon`, `onnxscript`, `openslide-python`, `openslide-bin`, `optuna`, `pandas`, `pillow`, `polygraphy`, `psutil`, `pyamg`, `pybind11`, `pydicom`, `pynrrd`, `pytest`, `nvidia-ml-py`, `pyyaml`, `requests`, `segment_anything`, `scipy`, `scikit-image`, `tensorboard`, `tensorboardX`, `tifffile`, `torchio`, `torchvision`, `tqdm`, `transformers`, `zarr`. Almost all of these can be installed together with the `all` option. For development on MONAI, this should be accompanied by `testing` which will install the testing static checking packages. diff --git a/monai/__init__.py b/monai/__init__.py index 9fdd9911296..2d6f5dfc37b 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -63,7 +63,7 @@ def filter(self, record): version_dict = get_versions() __version__: str = str(version_dict.get("version", "0+unknown")) -__revision_id__: str = str(version_dict.get("full-revisionid")) +__revision_id__: str = str(version_dict.get("full-revisionid") or "") del get_versions, version_dict __copyright__ = "(c) MONAI Consortium" diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index cef5ff602e5..bdb20e82cd7 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -28,6 +28,7 @@ def parse_dependencies(filename=TOML_FILE, sections=None): # these imports should be here to avoid attempting to import when MONAI is imported and both packages are missing + # isort: off if sys.version_info.minor >= 11: import tomllib @@ -36,6 +37,7 @@ def parse_dependencies(filename=TOML_FILE, sections=None): import tomli load_func = tomli.loads + # isort: on with open(filename) as o: data = load_func(o.read()) @@ -43,7 +45,7 @@ def parse_dependencies(filename=TOML_FILE, sections=None): proj = data[PROJ_KEY] opts = proj[OPTS_KEY] dependencies = list(proj[DEP_KEY]) - sections = sections or [] + sections = set(sections or []) if BUILD_SYSTEM_KEY in sections: sections.remove(BUILD_SYSTEM_KEY) diff --git a/pyproject.toml b/pyproject.toml index 2183e674139..ca0dba708f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,8 @@ Documentation = "https://monai.readthedocs.io/" "Source Code" = "https://github.com/Project-MONAI/MONAI" [project.optional-dependencies] +# All dependencies are included here except some omitted for compatibility. Testing dependencies are typically not +# needed and so present only in "testing". Ensure requirement changes in other lists are reflected here as well. all = [ "clearml>=1.10.0rc0", "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", @@ -118,7 +120,7 @@ fire = ["fire"] gdown = ["gdown>=4.7.3"] h5py = ["h5py"] huggingface_hub = ["huggingface_hub"] -hyena = ["nvsubquadratic>=0.1.1", "omegaconf"] # omitted from all for compatibility +hyena = ["nvsubquadratic>=0.1.1", "omegaconf", "einops"] # omitted from all for compatibility ignite = ["pytorch-ignite"] imagecodecs = ["imagecodecs; platform_system == 'Linux' or platform_system == 'Darwin'"] itk = ["itk>=5.2"] @@ -126,7 +128,6 @@ jsonschema = ["jsonschema"] lmdb = ["lmdb"] lpips = ["lpips==0.1.4"] matplotlib = ["matplotlib>=3.6.3"] -mccabe = ["mccabe"] metrics_reloaded = ["MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support"] mlflow = ["mlflow>=2.12.2,<3.13"] mypy = ["mypy>=1.5.0, <1.12.0"] From 1c8f626d5b5acc0fe988f5a977c63fd891e1a119 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 20:45:58 +0000 Subject: [PATCH 14/26] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ca0dba708f5..615f18d2c3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ Documentation = "https://monai.readthedocs.io/" "Source Code" = "https://github.com/Project-MONAI/MONAI" [project.optional-dependencies] -# All dependencies are included here except some omitted for compatibility. Testing dependencies are typically not +# All dependencies are included here except some omitted for compatibility. Testing dependencies are typically not # needed and so present only in "testing". Ensure requirement changes in other lists are reflected here as well. all = [ "clearml>=1.10.0rc0", From 6df3abe45ac987a5b4878888258fcbdcf56560ac Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:55:56 +0100 Subject: [PATCH 15/26] Fix Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index 1b796ce5cb7..9d72f29239c 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -144,7 +144,7 @@ jobs: # min. requirements python -m pip install torch==${{ matrix.pytorch-version }} python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation - python -m pip install --no-build-isolation -e .[testing] + python -m pip install --no-build-isolation .[testing] python -m pip list shell: bash - if: matrix.os == 'linux-gpu-runner' @@ -220,13 +220,13 @@ jobs: run: | python -m pip install --user --upgrade pip wheel tomli python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation - python -m pip install -e --no-build-isolation .[all,testing] + python -m pip install --no-build-isolation .[all,testing] python -m pip list shell: bash - name: Run compiled (${{ runner.os }}) run: | python -m pip uninstall -y monai - BUILD_MONAI=1 python -m pip install --no-build-isolation -e . # compile the cpp extensions + BUILD_MONAI=1 python -m pip install --no-build-isolation . # compile the cpp extensions shell: bash - if: runner.os != 'macOS' name: Run full tests @@ -280,7 +280,7 @@ jobs: # # the package itself without its core dependency tree (see job comment above). # python -m pip install omegaconf # python -m pip install --no-deps 'nvsubquadratic>=0.1.1' - python -m pip install --no-build-isolation -e .[hyena] + python -m pip install --no-build-isolation .[hyena] python -m pip list shell: bash - name: Run Hyena tests (CUDA-required cases skip cleanly) From 2f0b0dcabc30e4a194e3af69b8d346a87eb28ba5 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:15:41 +0100 Subject: [PATCH 16/26] Fix again Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 2 +- monai/config/print_dependencies.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index 9d72f29239c..7a1de350fe0 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -278,7 +278,7 @@ jobs: python -m pip install torch==${PYTORCH_VER1} torchvision==${TORCHVISION_VER1} # # nvsubquadratic runtime imports need only torch + einops + omegaconf; install # # the package itself without its core dependency tree (see job comment above). - # python -m pip install omegaconf + # python -m pip install omegaconf pytest # python -m pip install --no-deps 'nvsubquadratic>=0.1.1' python -m pip install --no-build-isolation .[hyena] python -m pip list diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index bdb20e82cd7..9cb76f9f7b8 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -16,6 +16,8 @@ assumes the pyproject.toml file is in the current working directory. """ +from __future__ import annotations + import sys BUILD_SYSTEM_KEY = "build-system" From 9458b12d8ed77678cfc5ca9a3e81d2ed78a8484d Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:57:50 +0100 Subject: [PATCH 17/26] Fixes Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- docs/source/installation.md | 2 +- monai/config/print_dependencies.py | 6 +++++- runtests.sh | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/source/installation.md b/docs/source/installation.md index a5fe151f5f6..8d70dcda82c 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -51,7 +51,7 @@ pip install monai MONAI supports the extras syntax such as `pip install 'monai[nibabel]'`. The options are -``` +```text clearml, cucim, cupy, einops, fire, gdown, h5py, huggingface_hub, hyena, ignite, imagecodecs, itk, jsonschema, lmdb, lpips, matplotlib, mccabe, metrics_reloaded, mlflow, mypy, nibabel, nni, onnx, openslide, optuna, pandas, pillow, polygraphy, psutil, pyamg, pybind11, pydicom, pynrrd, pytest, pynvml, pyyaml, requests, segment_anything, scipy, skimage, tensorboard, tensorboardX, tifffile, torchio, torchvision, tqdm, transformers, zarr ``` diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index 9cb76f9f7b8..a789819a0e7 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -19,6 +19,7 @@ from __future__ import annotations import sys +from typing import Collection BUILD_SYSTEM_KEY = "build-system" PROJ_KEY = "project" @@ -28,7 +29,10 @@ TOML_FILE = "pyproject.toml" -def parse_dependencies(filename=TOML_FILE, sections=None): +def parse_dependencies(filename: str = TOML_FILE, sections: Collection[str] | None = None) -> list[str]: + """ + Parse the toml file given by `filename` and return the dependency sections selected by `sections`. + """ # these imports should be here to avoid attempting to import when MONAI is imported and both packages are missing # isort: off if sys.version_info.minor >= 11: diff --git a/runtests.sh b/runtests.sh index 01ae6087587..12ac37510a2 100755 --- a/runtests.sh +++ b/runtests.sh @@ -142,8 +142,8 @@ function install_deps { ${cmdPrefix}"${PY_EXE}" -m pip install -U tomli # create a temporary requirements file and install using it REQ=$(mktemp --tmpdir XXX.txt) - ${cmdPrefix}"${PY_EXE}" monai/config/print_dependencies.py all testing > $REQ - ${cmdPrefix}"${PY_EXE}" -m pip install -r $REQ + ${cmdPrefix}"${PY_EXE}" monai/config/print_dependencies.py all testing > "$REQ" + ${cmdPrefix}"${PY_EXE}" -m pip install -r "$REQ" } function compile_cpp { From 0371c28d252de3ffa2d1bbe65d0f020c46667e63 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:58:29 +0000 Subject: [PATCH 18/26] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/config/print_dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/config/print_dependencies.py b/monai/config/print_dependencies.py index a789819a0e7..ab656f61ded 100644 --- a/monai/config/print_dependencies.py +++ b/monai/config/print_dependencies.py @@ -19,7 +19,7 @@ from __future__ import annotations import sys -from typing import Collection +from collections.abc import Collection BUILD_SYSTEM_KEY = "build-system" PROJ_KEY = "project" From c54cfb1d2f64a1a0a3a552b2cd15b0b24d30212e Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:32:01 +0100 Subject: [PATCH 19/26] Oopsy Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 4 ++-- environment-dev.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index 7a1de350fe0..d9aa69f45d2 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -273,12 +273,12 @@ jobs: cache: 'pip' - name: Install dependencies + nvsubquadratic (no-deps) run: | - python -m pip install --upgrade pip wheel tomli + python -m pip install --upgrade pip wheel tomli pytest python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation python -m pip install torch==${PYTORCH_VER1} torchvision==${TORCHVISION_VER1} # # nvsubquadratic runtime imports need only torch + einops + omegaconf; install # # the package itself without its core dependency tree (see job comment above). - # python -m pip install omegaconf pytest + # python -m pip install omegaconf # python -m pip install --no-deps 'nvsubquadratic>=0.1.1' python -m pip install --no-build-isolation .[hyena] python -m pip list diff --git a/environment-dev.yml b/environment-dev.yml index a9c79cc1be7..7d6d95a306e 100644 --- a/environment-dev.yml +++ b/environment-dev.yml @@ -2,6 +2,7 @@ name: monai channels: - defaults dependencies: + - python>=3.10 - pip - pip: - -e .[all,testing] From 95bbd24435fceec9e4357b2b8fced9319238dcbd Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:59:53 +0100 Subject: [PATCH 20/26] Fixes Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 2 +- docs/source/installation.md | 4 ++-- pyproject.toml | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index d9aa69f45d2..d7134cfb186 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -280,7 +280,7 @@ jobs: # # the package itself without its core dependency tree (see job comment above). # python -m pip install omegaconf # python -m pip install --no-deps 'nvsubquadratic>=0.1.1' - python -m pip install --no-build-isolation .[hyena] + python -m pip install --no-build-isolation .[hyena,testing] python -m pip list shell: bash - name: Run Hyena tests (CUDA-required cases skip cleanly) diff --git a/docs/source/installation.md b/docs/source/installation.md index 8d70dcda82c..4d57727fe30 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -52,10 +52,10 @@ pip install monai MONAI supports the extras syntax such as `pip install 'monai[nibabel]'`. The options are ```text -clearml, cucim, cupy, einops, fire, gdown, h5py, huggingface_hub, hyena, ignite, imagecodecs, itk, jsonschema, lmdb, lpips, matplotlib, mccabe, metrics_reloaded, mlflow, mypy, nibabel, nni, onnx, openslide, optuna, pandas, pillow, polygraphy, psutil, pyamg, pybind11, pydicom, pynrrd, pytest, pynvml, pyyaml, requests, segment_anything, scipy, skimage, tensorboard, tensorboardX, tifffile, torchio, torchvision, tqdm, transformers, zarr +clearml, cucim, cupy, einops, fire, gdown, h5py, huggingface_hub, hyena, ignite, imagecodecs, itk, jsonschema, lmdb, lpips, matplotlib, metrics_reloaded, mlflow, nibabel, nni, onnx, openslide, optuna, pandas, pillow, polygraphy, psutil, pyamg, pybind11, pydicom, pynrrd, pytest, pynvml, pyyaml, requests, segment_anything, scipy, skimage, tensorboard, tensorboardX, tifffile, torchio, torchvision, tqdm, transformers, zarr ``` -which correspond to the packages: `clearml`, `cucim` (`cucim-cu12` or `cucim-cu13`), `cupy`, `einops`, `fire`, `gdown`, `h5py`, `huggingface_hub`, `nvsubquadratic`, `omegaconf`, `pytorch-ignite`, `imagecodecs`, `itk`, `jsonschema`, `lmdb`, `lpips`, `matplotlib`, `mccabe`, `MetricsReloaded`, `mlflow`, `mypy`, `nibabel`, `nni`, `filelock`, `onnx`, `onnxruntime`, `onnx_graphsurgeon`, `onnxscript`, `openslide-python`, `openslide-bin`, `optuna`, `pandas`, `pillow`, `polygraphy`, `psutil`, `pyamg`, `pybind11`, `pydicom`, `pynrrd`, `pytest`, `nvidia-ml-py`, `pyyaml`, `requests`, `segment_anything`, `scipy`, `scikit-image`, `tensorboard`, `tensorboardX`, `tifffile`, `torchio`, `torchvision`, `tqdm`, `transformers`, `zarr`. +which correspond to the packages: `clearml`, `cucim` (`cucim-cu12` or `cucim-cu13`), `cupy`, `einops`, `fire`, `gdown`, `h5py`, `huggingface_hub`, `nvsubquadratic`, `omegaconf`, `pytorch-ignite`, `imagecodecs`, `itk`, `jsonschema`, `lmdb`, `lpips`, `matplotlib`, `MetricsReloaded`, `mlflow`, `nibabel`, `nni`, `filelock`, `onnx`, `onnxruntime`, `onnx_graphsurgeon`, `onnxscript`, `openslide-python`, `openslide-bin`, `optuna`, `pandas`, `pillow`, `polygraphy`, `psutil`, `pyamg`, `pybind11`, `pydicom`, `pynrrd`, `pytest`, `nvidia-ml-py`, `pyyaml`, `requests`, `segment_anything`, `scipy`, `scikit-image`, `tensorboard`, `tensorboardX`, `tifffile`, `torchio`, `torchvision`, `tqdm`, `transformers`, `zarr`. Almost all of these can be installed together with the `all` option. For development on MONAI, this should be accompanied by `testing` which will install the testing static checking packages. diff --git a/pyproject.toml b/pyproject.toml index 615f18d2c3e..95e12c9e2e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,6 @@ lpips = ["lpips==0.1.4"] matplotlib = ["matplotlib>=3.6.3"] metrics_reloaded = ["MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support"] mlflow = ["mlflow>=2.12.2,<3.13"] -mypy = ["mypy>=1.5.0, <1.12.0"] nibabel = ["nibabel"] nni = [ "nni; platform_system == 'Linux' and 'arm' not in platform_machine and 'aarch' not in platform_machine", From ae2e55f5335a1752c4fd39c6a7a5764660e6a1b3 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:41:51 +0100 Subject: [PATCH 21/26] Updates --- docs/source/installation.md | 9 +++++---- pyproject.toml | 7 ++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/source/installation.md b/docs/source/installation.md index 4d57727fe30..fa2a5be3071 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -52,19 +52,20 @@ pip install monai MONAI supports the extras syntax such as `pip install 'monai[nibabel]'`. The options are ```text -clearml, cucim, cupy, einops, fire, gdown, h5py, huggingface_hub, hyena, ignite, imagecodecs, itk, jsonschema, lmdb, lpips, matplotlib, metrics_reloaded, mlflow, nibabel, nni, onnx, openslide, optuna, pandas, pillow, polygraphy, psutil, pyamg, pybind11, pydicom, pynrrd, pytest, pynvml, pyyaml, requests, segment_anything, scipy, skimage, tensorboard, tensorboardX, tifffile, torchio, torchvision, tqdm, transformers, zarr +clearml, cucim, cupy, einops, fire, gdown, h5py, huggingface_hub, hyena, ignite, imagecodecs, itk, jsonschema, lmdb, lpips, matplotlib, metrics_reloaded, mlflow, nibabel, nni, onnx, openslide, optuna, pandas, pillow, polygraphy, psutil, pyamg, pybind11, pydicom, pynrrd, pynvml, pyyaml, requests, segment_anything, scipy, skimage, tensorboard, tensorboardX, tifffile, torchio, torchvision, tqdm, transformers, zarr ``` -which correspond to the packages: `clearml`, `cucim` (`cucim-cu12` or `cucim-cu13`), `cupy`, `einops`, `fire`, `gdown`, `h5py`, `huggingface_hub`, `nvsubquadratic`, `omegaconf`, `pytorch-ignite`, `imagecodecs`, `itk`, `jsonschema`, `lmdb`, `lpips`, `matplotlib`, `MetricsReloaded`, `mlflow`, `nibabel`, `nni`, `filelock`, `onnx`, `onnxruntime`, `onnx_graphsurgeon`, `onnxscript`, `openslide-python`, `openslide-bin`, `optuna`, `pandas`, `pillow`, `polygraphy`, `psutil`, `pyamg`, `pybind11`, `pydicom`, `pynrrd`, `pytest`, `nvidia-ml-py`, `pyyaml`, `requests`, `segment_anything`, `scipy`, `scikit-image`, `tensorboard`, `tensorboardX`, `tifffile`, `torchio`, `torchvision`, `tqdm`, `transformers`, `zarr`. +which correspond to the packages: `clearml`, `cucim` (`cucim-cu12` or `cucim-cu13`), `cupy-cuda13x`, `einops`, `fire`, `gdown`, `h5py`, `huggingface_hub`, `nvsubquadratic`, `omegaconf`, `pytorch-ignite`, `imagecodecs`, `itk`, `jsonschema`, `lmdb`, `lpips`, `matplotlib`, `MetricsReloaded`, `mlflow`, `nibabel`, `nni`, `filelock`, `onnx`, `onnxruntime`, `onnx_graphsurgeon`, `onnxscript`, `openslide-python`, `openslide-bin`, `optuna`, `pandas`, `pillow`, `polygraphy`, `psutil`, `pyamg`, `pybind11`, `pydicom`, `pynrrd`, `nvidia-ml-py`, `pyyaml`, `requests`, `segment_anything`, `scipy`, `scikit-image`, `tensorboard`, `tensorboardX`, `tifffile`, `torchio`, `torchvision`, `tqdm`, `transformers`, `zarr`. -Almost all of these can be installed together with the `all` option. For development on MONAI, this should be accompanied by `testing` which will install the testing static checking packages. +Almost all of these can be installed together with the `all` option. For development on MONAI, this should be accompanied by `testing` which will install the testing static checking packages. Cupy is omitted from `all` since the choice between +Cuda 12 and 13 versions of the library can't be resolved when installing and must be manually installed. The `hyena` extra pulls in [`nvsubquadratic`](https://github.com/NVIDIA-BioNeMo/nvSubquadratic), required by `HyenaNDUNETR` / `HyenaMixer` / `HyenaTransformerBlock` (subquadratic O(N log N) alternatives to windowed self-attention). Install with `pip install 'monai[hyena]'`. -The command `pip install 'monai[all,hyena,testing]'` installs all the optional dependencies. +The command `pip install 'monai[all,hyena,testing]'` installs almost all the optional dependencies. When installing MONAI, the compiled extensions are not compiled by default. Set the environment variable `BUILD_MONAI` to `1` when invoking `pip` to compile these, see below for details. diff --git a/pyproject.toml b/pyproject.toml index 95e12c9e2e2..c6c633834d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,8 +71,7 @@ all = [ "lmdb", "lpips==0.1.4", "matplotlib>=3.6.3", - # TODO: change URL to https://github.com/Project-MONAI/MetricsReloaded@monai-support before finalising - "MetricsReloaded @ git+https://github.com/ericspod/MetricsReloaded@monai-support-fix", + "MetricsReloaded @ git+https://github.com/Project-MONAI/MetricsReloaded@monai-support", "mlflow>=2.12.2,<3.13", "nibabel", "ninja", @@ -93,7 +92,6 @@ all = [ "pybind11", "pydicom", "pynrrd", - "pytest", # FIXME: CuPy 14.1.0 required this through polygraphy and trt_compiler https://github.com/Project-MONAI/MONAI/pull/8867 "pytorch-ignite", "pyyaml", "requests", @@ -114,7 +112,7 @@ cucim = [ "cucim-cu12; platform_system == 'Linux' and python_version <= '3.10'", "cucim-cu13; platform_system == 'Linux' and python_version >= '3.11'" ] -cupy = ["cupy"] +cupy = ["cupy-cuda13x!=14.1.0"] # not in all, the choice between cuda12x and cuda13x that can't be resolved here einops = ["einops"] fire = ["fire"] gdown = ["gdown>=4.7.3"] @@ -146,7 +144,6 @@ pyamg = ["pyamg>=5.0.0,<5.3.0"] pybind11 = ["pybind11"] pydicom = ["pydicom"] pynrrd = ["pynrrd"] -pytest = ["pytest"] pynvml = ["nvidia-ml-py"] pyyaml = ["pyyaml"] requests = ["requests"] From 2bcc87013e8c281b9e971a07e21fa83fdfad5dd3 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:06:37 +0100 Subject: [PATCH 22/26] Updates from comments Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 7 +++++-- .github/workflows/cron.yml | 4 +--- .github/workflows/release.yml | 10 ++++++---- .github/workflows/setupapp.yml | 2 +- .github/workflows/weekly-preview.yml | 6 +++--- CONTRIBUTING.md | 12 +++--------- README.md | 2 +- pyproject.toml | 20 +++++++++++--------- runtests.sh | 1 + tests/min_tests.py | 4 ++-- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index d7134cfb186..ce140e41d2b 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -226,7 +226,9 @@ jobs: - name: Run compiled (${{ runner.os }}) run: | python -m pip uninstall -y monai - BUILD_MONAI=1 python -m pip install --no-build-isolation . # compile the cpp extensions + BUILD_MONAI=1 python -m pip install --no-build-isolation -e . # compile the cpp extensions in-place with -e + # ensure extensions were compiled + python -c 'import monai._C' &> /dev/null shell: bash - if: runner.os != 'macOS' name: Run full tests @@ -315,11 +317,12 @@ jobs: cache: 'pip' - name: Install dependencies run: | - python -m pip install --user --upgrade pip setuptools wheel twine packaging + python -m pip install --user --upgrade pip setuptools wheel twine packaging tomli # install the latest pytorch for testing # however, "pip install monai*.tar.gz" will build cpp/cuda with an isolated # fresh torch installation according to pyproject.toml python -m pip install torch==${PYTORCH_VER1} torchvision --extra-index-url https://download.pytorch.org/whl/cpu + python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation - name: Check packages run: | python -m pip uninstall -y monai diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index fc9dcbbf5cb..ff098e8303c 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -138,6 +138,7 @@ jobs: run: | which python python -m pip install --upgrade pip wheel twine tomli + python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation python -m pip list - name: Run tests report coverage shell: bash @@ -159,7 +160,6 @@ jobs: # move packages to a temp dir tmp_dir=$(mktemp -d) cp dist/monai* "$tmp_dir" - python monai/config/print_dependencies.py build-system all testing > "$tmp_dir/requirements.txt" rm -r build dist monai.egg-info cd "$tmp_dir" ls -al @@ -172,7 +172,6 @@ jobs: python -c 'import monai; print(monai.__file__)' # run tests - cp $root_dir/requirements*.txt "$tmp_dir" cp -r $root_dir/tests "$tmp_dir" pwd ls -al @@ -187,7 +186,6 @@ jobs: python -c $'import torch\na,b=torch.zeros(1,device="cuda:0"),torch.zeros(1,device="cuda:1");\nwhile True:print(a,b)' > /dev/null & python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" - pip install --no-build-isolation -r requirements.txt PYTHONPATH="$tmp_dir":$PYTHONPATH BUILD_MONAI=1 python ./tests/runner.py -p 'test_((?!integration).)' # unit tests if pgrep python; then pkill python; fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 33d63efb8f3..8cbe1d1992d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,9 +22,10 @@ jobs: uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - - name: Install setuptools + - name: Install Dependencies run: | - python -m pip install --user --upgrade setuptools wheel packaging + python -m pip install --user --upgrade setuptools wheel packaging tomli + python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation - name: Build and test source archive and wheel file run: | find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; @@ -59,7 +60,7 @@ jobs: ls -al . - name: Quick test installed run: | - python -m pip install -r requirements-min.txt + python -m pip install -e .[testing] python -m tests.min_tests env: QUICKTEST: True @@ -105,7 +106,8 @@ jobs: run: | find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; git describe - python -m pip install --user --upgrade setuptools wheel packaging + python -m pip install --user --upgrade setuptools wheel packaging tomli + python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation python setup.py build cat build/lib/monai/_version.py - name: Upload version diff --git a/.github/workflows/setupapp.yml b/.github/workflows/setupapp.yml index 5d92ad2081d..68079f53287 100644 --- a/.github/workflows/setupapp.yml +++ b/.github/workflows/setupapp.yml @@ -165,7 +165,7 @@ jobs: cd $GITHUB_WORKSPACE rm -rf monai/ ls -al . - python -m pip install -r requirements-min.txt + python -m pip install -e .[testing] python -m tests.min_tests env: QUICKTEST: True diff --git a/.github/workflows/weekly-preview.yml b/.github/workflows/weekly-preview.yml index f5577abcf19..70e190b4bf9 100644 --- a/.github/workflows/weekly-preview.yml +++ b/.github/workflows/weekly-preview.yml @@ -59,12 +59,12 @@ jobs: - name: Build distribution run: | export HEAD_COMMIT_ID=$(git rev-parse HEAD) - sed -i 's/name\ =\ monai$/name\ =\ monai-weekly/g' setup.cfg + sed -i 's/name\ =\ "monai"$/name\ =\ "monai-weekly"/g' pyproject.toml echo "__commit_id__ = \"$HEAD_COMMIT_ID\"" >> monai/__init__.py - git diff setup.cfg monai/__init__.py + git diff pyproject.toml monai/__init__.py git config user.name "CI Builder" git config user.email "monai.contact@gmail.com" - git add setup.cfg monai/__init__.py + git add pyproject.toml monai/__init__.py git commit -m "Weekly build at $HEAD_COMMIT_ID" export YEAR_WEEK=$(date +'%y%U') echo "Year week for tag is ${YEAR_WEEK}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 50f496af3ff..d19a01670a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,10 +57,6 @@ Before submitting a pull request, we recommend that all linting should pass, by ```bash # optionally update the dependencies and dev tools python -m pip install -U pip -python -m pip install -U -r requirements-dev.txt - -# run the linting and type checking tools -./runtests.sh --codeformat # try to fix the coding style errors automatically ./runtests.sh --autofix @@ -132,7 +128,7 @@ It is recommended that the new test `test_[module_name].py` is constructed by us python 3.9+ build-in functions, `torch`, `numpy`, `coverage` (for reporting code coverages) and `parameterized` (for organising test cases) packages. If it requires any other external packages, please make sure: -- the packages are listed in [`requirements-dev.txt`](requirements-dev.txt) +- the packages are listed in [`pyproject.toml`](pyproject.toml) - the new test `test_[module_name].py` is added to the `exclude_cases` in [`./tests/min_tests.py`](./tests/min_tests.py) so that the minimal CI runner will not execute it. @@ -219,10 +215,8 @@ Integration tests with minimal requirements are deployed to ensure this strategy To add new optional dependencies, please communicate with the core team during pull request reviews, and add the necessary information (at least) to the following files: -- [setup.cfg](https://github.com/Project-MONAI/MONAI/blob/dev/setup.cfg) (for package's `[options.extras_require]` config) -- [requirements-dev.txt](https://github.com/Project-MONAI/MONAI/blob/dev/requirements-dev.txt) (pip requirements file) +- [pyproject.toml](https://github.com/Project-MONAI/MONAI/blob/dev/pyproject.toml) (for package's `[project.optional-dependencies]` config) - [docs/requirements.txt](https://github.com/Project-MONAI/MONAI/blob/dev/docs/requirements.txt) (docs pip requirements file) -- [environment-dev.yml](https://github.com/Project-MONAI/MONAI/blob/dev/environment-dev.yml) (conda environment file) - [installation.md](https://github.com/Project-MONAI/MONAI/blob/dev/docs/source/installation.md) (documentation) When writing unit tests that use 3rd-party packages, it is a good practice to always consider @@ -437,7 +431,7 @@ Do **not** use `[skip ci]` for commits that change: - Source code in `monai/` - Test files in `tests/` -- Dependencies (`requirements*.txt`, `setup.cfg`, `setup.py`) +- Dependencies (`pyproject.toml`, `setup.py`, `docs/requirements.txt`) - Anything that could affect correctness or compatibility ### Quick example diff --git a/README.md b/README.md index d0927ad8c3a..92e4ca7eba8 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ MONAI works with the [currently supported versions of Python](https://devguide.p * Major releases of MONAI will have dependency versions stated for them. The current state of the `dev` branch in this repository is the unreleased development version of MONAI which typically will support current versions of dependencies and include updates and bug fixes to do so. * PyTorch support covers [the current version](https://github.com/pytorch/pytorch/releases) plus three previous minor versions. If compatibility issues with a PyTorch version and other dependencies arise, support for a version may be delayed until a major release. * Our support policy for other dependencies adheres for the most part to [SPEC0](https://scientific-python.org/specs/spec-0000), where dependency versions are supported where possible for up to two years. Discovered vulnerabilities or defects may require certain versions to be explicitly not supported. -* See the `requirements*.txt` files for dependency version information. +* See the `pyproject.toml` file for dependency version information. ## Installation diff --git a/pyproject.toml b/pyproject.toml index c6c633834d3..35e1ff0362d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -226,18 +226,20 @@ disallow_untyped_decorators = true [[tool.mypy.overrides]] module = [ - "monai.visualize.*", - "monai.utils.*", - "monai.optimizers.*", - "monai.losses.*", - "monai.inferers.*", - "monai.config.*", "monai._extensions.*", - "monai.fl.*", - "monai.engines.*", - "monai.handlers.*", + "monai.apps.*", "monai.auto3dseg.*", "monai.bundle.*", + "monai.config.*", + "monai.engines.*", + "monai.fl.*", + "monai.handlers.*", + "monai.inferers.*", + "monai.losses.*", + "monai.metrics.*", + "monai.optimizers.*", + "monai.utils.*", + "monai.visualize.*" ] disallow_incomplete_defs = true diff --git a/runtests.sh b/runtests.sh index 12ac37510a2..e87474bd590 100755 --- a/runtests.sh +++ b/runtests.sh @@ -142,6 +142,7 @@ function install_deps { ${cmdPrefix}"${PY_EXE}" -m pip install -U tomli # create a temporary requirements file and install using it REQ=$(mktemp --tmpdir XXX.txt) + trap "rm -f \"$REQ\"" EXIT ${cmdPrefix}"${PY_EXE}" monai/config/print_dependencies.py all testing > "$REQ" ${cmdPrefix}"${PY_EXE}" -m pip install -r "$REQ" } diff --git a/tests/min_tests.py b/tests/min_tests.py index f98bf4b7397..c0ab8c2325b 100644 --- a/tests/min_tests.py +++ b/tests/min_tests.py @@ -20,10 +20,10 @@ def run_testsuit(): """ Load test cases by excluding those need external dependencies. - The loaded cases should work with "requirements-min.txt":: + The loaded cases should work with testing requirements:: # in the monai repo folder: - pip install -r requirements-min.txt + pip install -e .[testing] QUICKTEST=true python -m tests.min_tests :return: a test suite From f20ad99a7539b37f5aadc03e65d506fd5ebd0556 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:38:25 +0100 Subject: [PATCH 23/26] Fixes Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 9 +++++---- .github/workflows/cron.yml | 4 ++-- .github/workflows/integration.yml | 2 +- .github/workflows/release.yml | 4 ++-- Dockerfile | 2 +- docs/source/installation.md | 2 +- runtests.sh | 2 +- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index ce140e41d2b..47226fe7f0e 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -143,7 +143,7 @@ jobs: run: | # min. requirements python -m pip install torch==${{ matrix.pytorch-version }} - python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation python -m pip install --no-build-isolation .[testing] python -m pip list shell: bash @@ -219,7 +219,7 @@ jobs: - name: Install the complete dependencies run: | python -m pip install --user --upgrade pip wheel tomli - python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation python -m pip install --no-build-isolation .[all,testing] python -m pip list shell: bash @@ -276,7 +276,7 @@ jobs: - name: Install dependencies + nvsubquadratic (no-deps) run: | python -m pip install --upgrade pip wheel tomli pytest - python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation python -m pip install torch==${PYTORCH_VER1} torchvision==${TORCHVISION_VER1} # # nvsubquadratic runtime imports need only torch + einops + omegaconf; install # # the package itself without its core dependency tree (see job comment above). @@ -322,7 +322,8 @@ jobs: # however, "pip install monai*.tar.gz" will build cpp/cuda with an isolated # fresh torch installation according to pyproject.toml python -m pip install torch==${PYTORCH_VER1} torchvision --extra-index-url https://download.pytorch.org/whl/cpu - python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation + python monai/config/print_dependencies.py build-system all testing > requirements.txt + pip install --no-build-isolation -r requirements.txt - name: Check packages run: | python -m pip uninstall -y monai diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index ff098e8303c..74294306844 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -138,7 +138,7 @@ jobs: run: | which python python -m pip install --upgrade pip wheel twine tomli - python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation + python monai/config/print_dependencies.py build-system all testing | xargs -0 pip install --no-build-isolation python -m pip list - name: Run tests report coverage shell: bash @@ -238,7 +238,7 @@ jobs: run: | which python python -m pip install --upgrade pip wheel tomli - python monai/config/print_dependencies.py build-system | xargs pip install --no-cache-dir --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-cache-dir --no-build-isolation BUILD_MONAI=1 python setup.py develop # install monai nvidia-smi export CUDA_VISIBLE_DEVICES=$(python -m tests.utils | tail -n 1) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ee9cf7e3c28..fa121af2d1c 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -37,7 +37,7 @@ jobs: pip uninstall -y monai pip uninstall -y monai-weekly pip uninstall -y monai-weekly - python monai/config/print_dependencies.py build-system | xargs pip install --no-cache-dir --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-cache-dir --no-build-isolation python -m pip install --no-build-isolation .[all,testing] rm -rf /github/home/.cache/torch/hub/mmars/ - name: Clean directory diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8cbe1d1992d..cec0b2a442a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --user --upgrade setuptools wheel packaging tomli - python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation + python monai/config/print_dependencies.py build-system all testing | xargs -0 pip install --no-build-isolation - name: Build and test source archive and wheel file run: | find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; @@ -107,7 +107,7 @@ jobs: find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; git describe python -m pip install --user --upgrade setuptools wheel packaging tomli - python monai/config/print_dependencies.py build-system all testing | xargs pip install --no-build-isolation + python monai/config/print_dependencies.py build-system all testing | xargs -0 pip install --no-build-isolation python setup.py build cat build/lib/monai/_version.py - name: Upload version diff --git a/Dockerfile b/Dockerfile index 473e588704b..3d5d957c5a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,5 +51,5 @@ COPY monai ./monai # Need to install build requirements explicitly so that no-build-isolation can be used. This needed to make pip build # against the included version of PyTorch, rather than install a new version in the isolated environment. -RUN python monai/config/print_dependencies.py build-system | xargs pip install --no-cache-dir --no-build-isolation \ +RUN python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-cache-dir --no-build-isolation \ && FORCE_CUDA=1 pip install --no-cache-dir --no-build-isolation -e .[all,testing] diff --git a/docs/source/installation.md b/docs/source/installation.md index fa2a5be3071..569db58e464 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -140,7 +140,7 @@ BUILD_MONAI=1 pip install --no-build-isolation git+https://github.com/Project-MO When using build isolation (pip's default behaviour), a version of PyTorch must be installed which may not be the same as an existing install. This can cause the compiled libraries to be built against an ABI-incompatible PyTorch and thus not function at runtime. Building without isolation requires the current environment to have the necessary building libraries already installed. See the `build-system` section of `pyproject.toml` for these libraries, or use the following to install them: ```bash -python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation +python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation ``` On Windows the inline `BUILD_MONAI=1 pip install ...` form is not supported by diff --git a/runtests.sh b/runtests.sh index e87474bd590..0cb8907b6ad 100755 --- a/runtests.sh +++ b/runtests.sh @@ -142,7 +142,7 @@ function install_deps { ${cmdPrefix}"${PY_EXE}" -m pip install -U tomli # create a temporary requirements file and install using it REQ=$(mktemp --tmpdir XXX.txt) - trap "rm -f \"$REQ\"" EXIT + trap 'rm -f -- "$REQ"' EXIT ${cmdPrefix}"${PY_EXE}" monai/config/print_dependencies.py all testing > "$REQ" ${cmdPrefix}"${PY_EXE}" -m pip install -r "$REQ" } From 50a5d148a33d55693022b1503ff4bdd4a78e86d8 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:45:54 +0100 Subject: [PATCH 24/26] DCO Remediation Commit for Eric Kerfoot <17726042+ericspod@users.noreply.github.com> I, Eric Kerfoot <17726042+ericspod@users.noreply.github.com>, hereby add my Signed-off-by to this commit: ae2e55f5335a1752c4fd39c6a7a5764660e6a1b3 Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> From e7ed501f98e6c955c28692813694a5402b4ba106 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 13 Aug 2026 17:57:45 +0100 Subject: [PATCH 25/26] xargs fix Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 9 +++++---- .github/workflows/cron.yml | 4 ++-- .github/workflows/integration.yml | 2 +- .github/workflows/release.yml | 5 ++--- .github/workflows/setupapp.yml | 1 - Dockerfile | 2 +- Dockerfile.slim | 5 ++--- docs/source/installation.md | 2 +- 8 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index 47226fe7f0e..085184ecfad 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -143,7 +143,7 @@ jobs: run: | # min. requirements python -m pip install torch==${{ matrix.pytorch-version }} - python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-build-isolation python -m pip install --no-build-isolation .[testing] python -m pip list shell: bash @@ -174,6 +174,7 @@ jobs: timeout-minutes: 120 env: QUICKTEST: True + BUILD_MONAI: 1 steps: - if: runner.os == 'Linux' name: Clean unused tools @@ -219,14 +220,14 @@ jobs: - name: Install the complete dependencies run: | python -m pip install --user --upgrade pip wheel tomli - python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-build-isolation python -m pip install --no-build-isolation .[all,testing] python -m pip list shell: bash - name: Run compiled (${{ runner.os }}) run: | python -m pip uninstall -y monai - BUILD_MONAI=1 python -m pip install --no-build-isolation -e . # compile the cpp extensions in-place with -e + python -m pip install --no-build-isolation -e . # compile the cpp extensions in-place with -e # ensure extensions were compiled python -c 'import monai._C' &> /dev/null shell: bash @@ -276,7 +277,7 @@ jobs: - name: Install dependencies + nvsubquadratic (no-deps) run: | python -m pip install --upgrade pip wheel tomli pytest - python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-build-isolation python -m pip install torch==${PYTORCH_VER1} torchvision==${TORCHVISION_VER1} # # nvsubquadratic runtime imports need only torch + einops + omegaconf; install # # the package itself without its core dependency tree (see job comment above). diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 74294306844..8fcce6af3ab 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -138,7 +138,7 @@ jobs: run: | which python python -m pip install --upgrade pip wheel twine tomli - python monai/config/print_dependencies.py build-system all testing | xargs -0 pip install --no-build-isolation + python monai/config/print_dependencies.py build-system all testing | xargs -d '\n' pip install --no-build-isolation python -m pip list - name: Run tests report coverage shell: bash @@ -238,7 +238,7 @@ jobs: run: | which python python -m pip install --upgrade pip wheel tomli - python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-cache-dir --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-cache-dir --no-build-isolation BUILD_MONAI=1 python setup.py develop # install monai nvidia-smi export CUDA_VISIBLE_DEVICES=$(python -m tests.utils | tail -n 1) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index fa121af2d1c..d3c1e473c72 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -37,7 +37,7 @@ jobs: pip uninstall -y monai pip uninstall -y monai-weekly pip uninstall -y monai-weekly - python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-cache-dir --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-cache-dir --no-build-isolation python -m pip install --no-build-isolation .[all,testing] rm -rf /github/home/.cache/torch/hub/mmars/ - name: Clean directory diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cec0b2a442a..d4c55e1f08b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --user --upgrade setuptools wheel packaging tomli - python monai/config/print_dependencies.py build-system all testing | xargs -0 pip install --no-build-isolation + python monai/config/print_dependencies.py build-system all testing | xargs -d '\n' pip install --no-build-isolation - name: Build and test source archive and wheel file run: | find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; @@ -56,7 +56,6 @@ jobs: # clean up cd "$root_dir" rm -r "$tmp_dir" - rm -rf monai/ ls -al . - name: Quick test installed run: | @@ -107,7 +106,7 @@ jobs: find /opt/hostedtoolcache/* -maxdepth 0 ! -name 'Python' -exec rm -rf {} \; git describe python -m pip install --user --upgrade setuptools wheel packaging tomli - python monai/config/print_dependencies.py build-system all testing | xargs -0 pip install --no-build-isolation + python monai/config/print_dependencies.py build-system all testing | xargs -d '\n' pip install --no-build-isolation python setup.py build cat build/lib/monai/_version.py - name: Upload version diff --git a/.github/workflows/setupapp.yml b/.github/workflows/setupapp.yml index 68079f53287..d7bf1dc6dd0 100644 --- a/.github/workflows/setupapp.yml +++ b/.github/workflows/setupapp.yml @@ -163,7 +163,6 @@ jobs: if: github.ref == 'refs/heads/dev' run: | cd $GITHUB_WORKSPACE - rm -rf monai/ ls -al . python -m pip install -e .[testing] python -m tests.min_tests diff --git a/Dockerfile b/Dockerfile index 3d5d957c5a1..307fc988e07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,5 +51,5 @@ COPY monai ./monai # Need to install build requirements explicitly so that no-build-isolation can be used. This needed to make pip build # against the included version of PyTorch, rather than install a new version in the isolated environment. -RUN python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-cache-dir --no-build-isolation \ +RUN python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-cache-dir --no-build-isolation \ && FORCE_CUDA=1 pip install --no-cache-dir --no-build-isolation -e .[all,testing] diff --git a/Dockerfile.slim b/Dockerfile.slim index aae3d2638a5..bfaf897967f 100644 --- a/Dockerfile.slim +++ b/Dockerfile.slim @@ -62,10 +62,9 @@ ENV DEBIAN_FRONTEND=noninteractive ENV APT_INSTALL="apt install -y --no-install-recommends" RUN apt update && apt upgrade -y && \ - ${APT_INSTALL} ca-certificates python3-pip python-is-python3 git libopenslide0 && \ + ${APT_INSTALL} ca-certificates python-is-python3 git libopenslide0 && \ apt clean && \ - rm -rf /usr/lib/python*/EXTERNALLY-MANAGED /var/lib/apt/lists/* && \ - python -m pip install --upgrade --no-cache-dir --no-build-isolation pip + rm -rf /usr/lib/python*/EXTERNALLY-MANAGED /var/lib/apt/lists/* COPY --from=build /opt/monai /opt/monai COPY --from=build /opt/tools /opt/tools diff --git a/docs/source/installation.md b/docs/source/installation.md index 569db58e464..6b68799d3e0 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -140,7 +140,7 @@ BUILD_MONAI=1 pip install --no-build-isolation git+https://github.com/Project-MO When using build isolation (pip's default behaviour), a version of PyTorch must be installed which may not be the same as an existing install. This can cause the compiled libraries to be built against an ABI-incompatible PyTorch and thus not function at runtime. Building without isolation requires the current environment to have the necessary building libraries already installed. See the `build-system` section of `pyproject.toml` for these libraries, or use the following to install them: ```bash -python monai/config/print_dependencies.py build-system | xargs -0 pip install --no-build-isolation +python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-build-isolation ``` On Windows the inline `BUILD_MONAI=1 pip install ...` form is not supported by From beb96e7829db34654055b69e66e1a2f8e5c4abe4 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:03:55 +0100 Subject: [PATCH 26/26] macos nonsense Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> --- .github/workflows/cicd_tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd_tests.yml b/.github/workflows/cicd_tests.yml index 085184ecfad..807bc4ad2cc 100644 --- a/.github/workflows/cicd_tests.yml +++ b/.github/workflows/cicd_tests.yml @@ -143,7 +143,7 @@ jobs: run: | # min. requirements python -m pip install torch==${{ matrix.pytorch-version }} - python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation python -m pip install --no-build-isolation .[testing] python -m pip list shell: bash @@ -220,7 +220,7 @@ jobs: - name: Install the complete dependencies run: | python -m pip install --user --upgrade pip wheel tomli - python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation python -m pip install --no-build-isolation .[all,testing] python -m pip list shell: bash @@ -277,7 +277,7 @@ jobs: - name: Install dependencies + nvsubquadratic (no-deps) run: | python -m pip install --upgrade pip wheel tomli pytest - python monai/config/print_dependencies.py build-system | xargs -d '\n' pip install --no-build-isolation + python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation python -m pip install torch==${PYTORCH_VER1} torchvision==${TORCHVISION_VER1} # # nvsubquadratic runtime imports need only torch + einops + omegaconf; install # # the package itself without its core dependency tree (see job comment above).