From 5023720ee6804b63186f72083010d0442a66aeef Mon Sep 17 00:00:00 2001 From: Mason Cleveland Date: Wed, 7 Jan 2026 22:12:33 -0500 Subject: [PATCH 01/12] Add import_optional_dependency util function --- src/highdicom/utils.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 4fdfaa24..357e4f62 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -14,6 +14,9 @@ is_tiled_image, ) +from importlib import import_module +from types import ModuleType +from typing import Optional # Several functions that were initially defined in this module were moved to # highdicom.spatial to consolidate similar functionality and prevent circular @@ -243,3 +246,41 @@ def are_plane_positions_tiled_full( return False return True + + +def import_optional_dependency( + module_name: str, + feature: Optional[str] = None +) -> ModuleType: + """Import an optional dependency. + + This function is designed to support interaction with other common + libraries that are not required for `highdicom`. + + Parameters + ---------- + module_name: str + Name of the module to be imported. + feature: Optional[str] + Optional description of the feature that requires this dependency. + This is used only for improving the error message when the module + is not installed. + + Returns + ------- + ModuleType: + Imported module. + + Raises + ------ + ImportError + When the specified module cannot be imported. + """ + try: + return import_module(name=module_name) + + except ImportError as error: + raise ImportError( + f"Optional dependency `{module_name}` could not be imported" + + (f" but is required for {feature}." if feature else ".") + ) from error From c5f7398e34c21ba3f3132c3904f37988ae90d9e9 Mon Sep 17 00:00:00 2001 From: Mason Cleveland Date: Fri, 23 Jan 2026 08:41:09 -0500 Subject: [PATCH 02/12] Update optional import function. --- src/highdicom/utils.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 357e4f62..779cac29 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -250,21 +250,20 @@ def are_plane_positions_tiled_full( def import_optional_dependency( module_name: str, - feature: Optional[str] = None + feature: str ) -> ModuleType: """Import an optional dependency. This function is designed to support interaction with other common - libraries that are not required for `highdicom`. + libraries that are not required for `highdicom` by default. Parameters ---------- module_name: str Name of the module to be imported. - feature: Optional[str] - Optional description of the feature that requires this dependency. - This is used only for improving the error message when the module - is not installed. + feature: str + Name or description of the feature that requires this dependency. + This is used for improving the clarity of error messages. Returns ------- @@ -276,11 +275,19 @@ def import_optional_dependency( ImportError When the specified module cannot be imported. """ + + #Update as new optional dependencies are supported + versions = {} + try: return import_module(name=module_name) except ImportError as error: + module_version = versions[module_name] if module_name in versions.keys() else "Unknown" + raise ImportError( f"Optional dependency `{module_name}` could not be imported" + (f" but is required for {feature}." if feature else ".") + + f" The minimum required version for `{module_name}` is {module_version}." + ) from error From feef10be6c79e954153c41bf75c76a49b0dc733b Mon Sep 17 00:00:00 2001 From: Mason Cleveland Date: Fri, 23 Jan 2026 08:56:56 -0500 Subject: [PATCH 03/12] Reformat code --- src/highdicom/utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 779cac29..7a7eeef9 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -283,11 +283,13 @@ def import_optional_dependency( return import_module(name=module_name) except ImportError as error: - module_version = versions[module_name] if module_name in versions.keys() else "Unknown" + module_version = ( + versions[module_name] if module_name in versions.keys() else 'Unknown' + ) raise ImportError( - f"Optional dependency `{module_name}` could not be imported" - + (f" but is required for {feature}." if feature else ".") - + f" The minimum required version for `{module_name}` is {module_version}." + f'Optional dependency `{module_name}` could not be imported' + + (f' but is required for {feature}.' if feature else '.') + + f' The minimum required version for `{module_name}` is {module_version}.' ) from error From bc41ecfc95a5f48452f412560fabe41efebafc64 Mon Sep 17 00:00:00 2001 From: Mason Cleveland Date: Wed, 4 Feb 2026 23:52:23 -0500 Subject: [PATCH 04/12] Add module version compatibility check. --- src/highdicom/utils.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 7a7eeef9..cafb6f7f 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -14,9 +14,9 @@ is_tiled_image, ) -from importlib import import_module +from importlib import import_module, metadata +from packaging.requirements import Requirement from types import ModuleType -from typing import Optional # Several functions that were initially defined in this module were moved to # highdicom.spatial to consolidate similar functionality and prevent circular @@ -275,21 +275,33 @@ def import_optional_dependency( ImportError When the specified module cannot be imported. """ + for req_str in metadata.requires('highdicom'): + req = Requirement(req_str) + if req.name == module_name: + break - #Update as new optional dependencies are supported - versions = {} + else: + raise ValueError( + f'`{module_name}` is not a requirement of highdicom' + f' but is required for {feature}.' + ) try: - return import_module(name=module_name) + module = import_module(name=module_name) + installed_version = metadata.version(module_name) - except ImportError as error: - module_version = ( - versions[module_name] if module_name in versions.keys() else 'Unknown' - ) + if installed_version not in req.specifier: + raise ImportError( + f'Optional dependency `{module_name}` has an unsuitable' + f' version. Found {installed_version}, but highdicom requires' + f' {module_name}{req.specifier}.' + ) + return module + + except ImportError as error: raise ImportError( f'Optional dependency `{module_name}` could not be imported' - + (f' but is required for {feature}.' if feature else '.') - + f' The minimum required version for `{module_name}` is {module_version}.' - + f' but is required for {feature}.' + f' highdicom requires {module_name}{req.specifier}.' ) from error From ce4998ae51ace41eca025a4bf32cb1b98a90aa62 Mon Sep 17 00:00:00 2001 From: Mason Cleveland Date: Thu, 5 Feb 2026 00:16:56 -0500 Subject: [PATCH 05/12] Move version incompatibility error raise location and update error message --- src/highdicom/utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index cafb6f7f..f5c88214 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -290,18 +290,18 @@ def import_optional_dependency( module = import_module(name=module_name) installed_version = metadata.version(module_name) - if installed_version not in req.specifier: - raise ImportError( - f'Optional dependency `{module_name}` has an unsuitable' - f' version. Found {installed_version}, but highdicom requires' - f' {module_name}{req.specifier}.' - ) - - return module - except ImportError as error: raise ImportError( f'Optional dependency `{module_name}` could not be imported' f' but is required for {feature}.' f' highdicom requires {module_name}{req.specifier}.' ) from error + + if installed_version not in req.specifier: + raise ImportError( + f'Optional dependency `{module_name}` has an unsuitable' + f' version. Found {module_name}=={installed_version}, but' + f' highdicom requires {module_name}{req.specifier}.' + ) + + return module From 5c5e9527723b71cb46c24f3813531b27f0147fdd Mon Sep 17 00:00:00 2001 From: "Mason C. Cleveland" <104479423+mccle@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:47:47 -0500 Subject: [PATCH 06/12] Update src/highdicom/utils.py Co-authored-by: Chris Bridge --- src/highdicom/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index f5c88214..2fe9e897 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -274,6 +274,7 @@ def import_optional_dependency( ------ ImportError When the specified module cannot be imported. + """ for req_str in metadata.requires('highdicom'): req = Requirement(req_str) From 1ce1bc7495aa227151a3d243bc7f743f43292d66 Mon Sep 17 00:00:00 2001 From: "Mason C. Cleveland" <104479423+mccle@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:48:40 -0500 Subject: [PATCH 07/12] Update src/highdicom/utils.py Co-authored-by: Chris Bridge --- src/highdicom/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 2fe9e897..5225ae56 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -272,7 +272,7 @@ def import_optional_dependency( Raises ------ - ImportError + ImportError: When the specified module cannot be imported. """ From bdd363955c6c769107ad5ccf04959e6bb9996a0b Mon Sep 17 00:00:00 2001 From: "Mason C. Cleveland" <104479423+mccle@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:49:10 -0500 Subject: [PATCH 08/12] Update src/highdicom/utils.py Co-authored-by: Chris Bridge --- src/highdicom/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 5225ae56..9f07c005 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -300,9 +300,9 @@ def import_optional_dependency( if installed_version not in req.specifier: raise ImportError( - f'Optional dependency `{module_name}` has an unsuitable' - f' version. Found {module_name}=={installed_version}, but' - f' highdicom requires {module_name}{req.specifier}.' + f'Optional dependency `{module_name}` has an unsuitable ' + f'version. Found {module_name}=={installed_version}, but ' + f'highdicom requires {module_name}{req.specifier}.' ) return module From b4cd2403732fc46c9672e18b5bbbd3c72cc23c99 Mon Sep 17 00:00:00 2001 From: "Mason C. Cleveland" <104479423+mccle@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:49:35 -0500 Subject: [PATCH 09/12] Update src/highdicom/utils.py Co-authored-by: Chris Bridge --- src/highdicom/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 9f07c005..5f6e275a 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -283,8 +283,8 @@ def import_optional_dependency( else: raise ValueError( - f'`{module_name}` is not a requirement of highdicom' - f' but is required for {feature}.' + f'`{module_name}` is not a requirement of highdicom ' + f'but is required for {feature}.' ) try: From 36a8fb5a8c882154b514ef57283cb204ebef3499 Mon Sep 17 00:00:00 2001 From: "Mason C. Cleveland" <104479423+mccle@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:50:33 -0500 Subject: [PATCH 10/12] Update src/highdicom/utils.py Co-authored-by: Chris Bridge --- src/highdicom/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index 5f6e275a..fdf34fd6 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -289,7 +289,6 @@ def import_optional_dependency( try: module = import_module(name=module_name) - installed_version = metadata.version(module_name) except ImportError as error: raise ImportError( @@ -297,6 +296,8 @@ def import_optional_dependency( f' but is required for {feature}.' f' highdicom requires {module_name}{req.specifier}.' ) from error + + installed_version = metadata.version(module_name) if installed_version not in req.specifier: raise ImportError( From 7ff4a135fcf72c04d6b99a87965313ccf8437c59 Mon Sep 17 00:00:00 2001 From: Mason Cleveland Date: Fri, 20 Feb 2026 09:49:04 -0500 Subject: [PATCH 11/12] Move to new utils file --- src/highdicom/_dependency_utils.py | 64 ++++++++++++++++++++++++++++++ src/highdicom/utils.py | 64 ------------------------------ 2 files changed, 64 insertions(+), 64 deletions(-) create mode 100644 src/highdicom/_dependency_utils.py diff --git a/src/highdicom/_dependency_utils.py b/src/highdicom/_dependency_utils.py new file mode 100644 index 00000000..9c7643c6 --- /dev/null +++ b/src/highdicom/_dependency_utils.py @@ -0,0 +1,64 @@ +from importlib import import_module, metadata +from packaging.requirements import Requirement +from types import ModuleType + + +def import_optional_dependency( + module_name: str, + feature: str +) -> ModuleType: + """Import an optional dependency. + + This function is designed to support interaction with other common + libraries that are not required for `highdicom` by default. + + Parameters + ---------- + module_name: str + Name of the module to be imported. + feature: str + Name or description of the feature that requires this dependency. + This is used for improving the clarity of error messages. + + Returns + ------- + ModuleType: + Imported module. + + Raises + ------ + ImportError: + When the specified module cannot be imported. + + """ + for req_str in metadata.requires('highdicom'): + req = Requirement(req_str) + if req.name == module_name: + break + + else: + raise ValueError( + f'`{module_name}` is not a requirement of highdicom ' + f'but is required for {feature}.' + ) + + try: + module = import_module(name=module_name) + + except ImportError as error: + raise ImportError( + f'Optional dependency `{module_name}` could not be imported' + f' but is required for {feature}.' + f' highdicom requires {module_name}{req.specifier}.' + ) from error + + installed_version = metadata.version(module_name) + + if installed_version not in req.specifier: + raise ImportError( + f'Optional dependency `{module_name}` has an unsuitable ' + f'version. Found {module_name}=={installed_version}, but ' + f'highdicom requires {module_name}{req.specifier}.' + ) + + return module diff --git a/src/highdicom/utils.py b/src/highdicom/utils.py index fdf34fd6..4fdfaa24 100644 --- a/src/highdicom/utils.py +++ b/src/highdicom/utils.py @@ -14,9 +14,6 @@ is_tiled_image, ) -from importlib import import_module, metadata -from packaging.requirements import Requirement -from types import ModuleType # Several functions that were initially defined in this module were moved to # highdicom.spatial to consolidate similar functionality and prevent circular @@ -246,64 +243,3 @@ def are_plane_positions_tiled_full( return False return True - - -def import_optional_dependency( - module_name: str, - feature: str -) -> ModuleType: - """Import an optional dependency. - - This function is designed to support interaction with other common - libraries that are not required for `highdicom` by default. - - Parameters - ---------- - module_name: str - Name of the module to be imported. - feature: str - Name or description of the feature that requires this dependency. - This is used for improving the clarity of error messages. - - Returns - ------- - ModuleType: - Imported module. - - Raises - ------ - ImportError: - When the specified module cannot be imported. - - """ - for req_str in metadata.requires('highdicom'): - req = Requirement(req_str) - if req.name == module_name: - break - - else: - raise ValueError( - f'`{module_name}` is not a requirement of highdicom ' - f'but is required for {feature}.' - ) - - try: - module = import_module(name=module_name) - - except ImportError as error: - raise ImportError( - f'Optional dependency `{module_name}` could not be imported' - f' but is required for {feature}.' - f' highdicom requires {module_name}{req.specifier}.' - ) from error - - installed_version = metadata.version(module_name) - - if installed_version not in req.specifier: - raise ImportError( - f'Optional dependency `{module_name}` has an unsuitable ' - f'version. Found {module_name}=={installed_version}, but ' - f'highdicom requires {module_name}{req.specifier}.' - ) - - return module From efb70aafb7074bedc953398ba5b7c881ce0c5244 Mon Sep 17 00:00:00 2001 From: Mason Cleveland Date: Fri, 20 Feb 2026 09:49:25 -0500 Subject: [PATCH 12/12] Add packaging to requirements --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 276f28dd..6fce1a04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "pydicom>=3.0.1", "pyjpegls>=1.0.0", "typing-extensions>=4.0.0", + "packaging>=25.0" ] [project.optional-dependencies]