Repository: mlcommons/GaNDLF

Issue:
[BUG] Moving from version `0.0.XX` to `0.Y.0` creates compatibility issues.
### Describe the bug
When the new CLI is ready (#818), we will transition to version `0.1.0`. However, this is creating issues with the [`version_check`](https://github.com/mlcommons/GaNDLF/blob/2b2445cba24e98b3dd51dd6645324ed5d5cd2769/GANDLF/utils/generic.py#L106) function.

### To Reproduce
Steps to reproduce the behavior:
1. Put min/max version in the config as `0.0.XX` and `0.1.0`.
2. Run any training command.
3. See version compatibility error.

### Expected behavior
Clearly, `0.1.0` is greater than `0.0.XX`. However, the custom [`version_check`](https://github.com/mlcommons/GaNDLF/blob/2b2445cba24e98b3dd51dd6645324ed5d5cd2769/GANDLF/utils/generic.py#L106)  cannot seem to comprehend this.

### Media
N.A.

### Environment information
<!-- Put the output of the following command:
python ./gandlf_debugInfo
-->
```
GANDLF version: 0.0.20-dev
Git hash: 3c61892f9d5ffc9d17720ccfd413c1b23cfd50f0
Platform: Windows-10-10.0.22631-SP0
Machine: AMD64
Processor: Intel64 Family 6 Model 165 Stepping 2, GenuineIntel
Architecture: 64bit WindowsPE
Python environment:
  Version: 3.11.6
  Implementation: CPython
  Compiler: MSC v.1935 64 bit (AMD64)
  Build: main Oct  3 2023 10:29:11
```

### Additional context
N.A.



Discussion:
Please assign this to me.
I believe using [`Packaging`](https://packaging.pypa.io/en/latest/version.html#packaging.version.parse) would be a good solution:

```python
>>> from packaging.version import Version
>>> v1 = Version("0.0.19") 
>>> v2 = Version("0.1.0")
>>> v1 > v2
False
```

This will ensure that we don't have to maintain any custom code. Additionally, if we move to a more formal release schedule (with Alpha/Beta/Gamma/RC versions), we won't need to make any further edits, since this is already covered:

```python
>>> from packaging.version import Version
>>> v1 = Version("0.2.0-Alpha")
>>> v2 = Version("0.2.0-RC")   
>>> v1 > v2 
False
>>> v1 = Version("0.2.0-Alpha5") 
>>> v1 > v2
False
```

Resolving patch:
diff --git a/GANDLF/utils/generic.py b/GANDLF/utils/generic.py
--- a/GANDLF/utils/generic.py
+++ b/GANDLF/utils/generic.py
@@ -1,12 +1,14 @@
+from typing import Optional, Union, Dict
 import os, datetime, subprocess, sys
 from copy import deepcopy
 import random
+from packaging.version import Version
+
 import numpy as np
 import torch
 import SimpleITK as sitk
 from contextlib import contextmanager, redirect_stderr, redirect_stdout
 from os import devnull
-from typing import Optional, Union
 
 
 @contextmanager
@@ -86,43 +88,38 @@ def get_filename_extension_sanitized(filename: str) -> str:
     return ext
 
 
-def parse_version(version_string: str) -> int:
-    """
-    Parses version string, discards last identifier (NR/alpha/beta) and returns an integer for comparison.
-
-    Args:
-        version_string (str): The string to be parsed.
-
-    Returns:
-        int: The version number.
-    """
-    version_string_split = version_string.replace("-dev", "")
-    version_string_split = version_string_split.split(".")
-    if len(version_string_split) > 3:
-        del version_string_split[-1]
-    return int("".join(version_string_split))
-
-
-def version_check(version_from_config: str, version_to_check: str) -> bool:
+def version_check(version_from_config: Dict[str, str], version_to_check: str) -> bool:
     """
     This function checks if the version of the config file is compatible with the version of the code.
 
     Args:
-        version_from_config (str): The version of the config file.
+        version_from_config (Dict[str, str]): The version from the config file which contains minimum and maximum versions.
         version_to_check (str): The version of the code or model to check.
 
     Returns:
         bool: If the version of the config file is compatible with the version of the code.
     """
-    version_to_check_int = parse_version(version_to_check)
-    min_ver = parse_version(version_from_config["minimum"])
-    max_ver = parse_version(version_from_config["maximum"])
-    if (min_ver > version_to_check_int) or (max_ver < version_to_check_int):
-        sys.exit(
-            "Incompatible version of GaNDLF detected ("
-            + str(version_to_check_int)
-            + ")"
-        )
+    versions_from_config = {}
+    for key_to_check in ["minimum", "maximum"]:
+        versions_from_config[key_to_check] = version_from_config.get(key_to_check, None)
+        assert (
+            versions_from_config[key_to_check] is not None
+        ), f"Version {key_to_check} is not specified in the config file"
+
+    # create Version objects
+    version_to_check_obj = Version(version_to_check)
+    min_ver_obj = Version(versions_from_config["minimum"])
+    max_ver_obj = Version(versions_from_config["maximum"])
+
+    assert (
+        min_ver_obj <= max_ver_obj
+    ), f"Minimum version ({min_ver_obj}) is greater than maximum version in the config file. ({max_ver_obj})"
+    assert (
+        min_ver_obj <= version_to_check_obj
+    ), f"Minimum version ({min_ver_obj}) requested in config is greater than the GaNDLF version ({version_to_check_obj})"
+    assert (
+        max_ver_obj >= version_to_check_obj
+    ), f"Maximum version ({max_ver_obj}) requested in config is less than the GaNDLF version ({version_to_check_obj})"
 
     return True
 
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -113,6 +113,8 @@ def run(self):
     "zarr",
     "keyring",
     "monai==1.3.0",
+    "packaging==24.0",
+    "typer==0.9.0",
 ]
 
 if __name__ == "__main__":


Test patch:
diff --git a/testing/config_classification.yaml b/testing/config_classification.yaml
--- a/testing/config_classification.yaml
+++ b/testing/config_classification.yaml
@@ -55,7 +55,7 @@ save_output: false
 scaling_factor: 1
 scheduler: triangle
 version:
-  maximum: 0.0.20
+  maximum: 0.0.20-dev
   minimum: 0.0.14
 weighted_loss: True
 
diff --git a/testing/config_regression.yaml b/testing/config_regression.yaml
--- a/testing/config_regression.yaml
+++ b/testing/config_regression.yaml
@@ -38,7 +38,7 @@ save_output: false
 scaling_factor: 1
 scheduler: triangle
 version:
-  maximum: 0.0.20
+  maximum: 0.0.20-dev
   minimum: 0.0.14
 weighted_loss: false
 
diff --git a/testing/config_segmentation.yaml b/testing/config_segmentation.yaml
--- a/testing/config_segmentation.yaml
+++ b/testing/config_segmentation.yaml
@@ -3,7 +3,7 @@
 version:
   {
     minimum: 0.0.14,
-    maximum: 0.0.20
+    maximum: 0.0.20-dev
   }
 model:
   {