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/11] 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 325622b66a..add1f4a610 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 c025c685f4..0000000000 --- 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/11] 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 0000000000..a7c58d335c --- /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 add1f4a610..44830ad113 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 b2c36f8de6..0000000000 --- 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 ddda9064a6..0000000000 --- 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 7d283182a4..0000000000 --- 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/11] 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 44830ad113..ee6161100f 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/11] 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 a9637c2716..1f5e43b362 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 d48e3a02f2..88bab7090b 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 3b4b427c99..ce1a0b9893 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 a8539b284c..650434eb37 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 8ff912f528..90d1bcbd05 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 bef17d0936..921d8f5c89 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 785945de03..7b9336debb 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 bfc2f62e28..8378d2742a 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 af49cb292b..b080942bf3 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 056b99edff..33d63efb8f 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 7e7349ec93..f5577abcf1 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 3240da7ded..9158f5d0ab 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 3027d40164..9e023cec5e 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 b2457006c8..0000000000 --- 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 a7c58d335c..c7082e0795 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 ee6161100f..b08d00eeba 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 431e9298c3..01ae608758 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/11] 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 0000000000..6c4d4ed93e --- /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/11] 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 9158f5d0ab..3cc4b1eb36 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/11] 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 3cc4b1eb36..4fe9fb8992 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 45e15ddcd9..9fdd991129 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 f234227104..d14412be66 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/11] 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 4fe9fb8992..8b2bf75f62 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 b08d00eeba..89bd2c6f6d 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 4d9badca41..2ebc7a9ba6 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 5d0a606c91..1e3753e63f 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/11] 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 8b2bf75f62..473e588704 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 1b3cf3bcd4..9231165d97 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 c7082e0795..f8ce4394f4 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 89bd2c6f6d..3e7584a938 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/11] 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 9231165d97..aae3d2638a 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 2d9e2a7f0e..55b4124fa8 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/11] [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 6c4d4ed93e..a9c79cc1be 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 f8ce4394f4..2b65ee1146 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 3e7584a938..2183e67413 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",