Repository: smarie/python-pytest-cases (language: python)

Issue:
`ModuleNotFoundError: distutils` on Python 3.12
[`distutils` has been removed in the upcoming Python 3.12](https://docs.python.org/3.11/library/distutils.html).

So the Python 3.12.0-alpha.7 version I am trying, I get the following traceback:
```pytb
(...)
  File ".../python3.12/site-packages/pytest_cases/common_pytest_marks.py", line 8, in <module>
    from distutils.version import LooseVersion
ModuleNotFoundError: No module named 'distutils'
```

Source: https://github.com/kdeldycke/click-extra/actions/runs/5061009247/jobs/9086125761#step:7:76
DeprecationWarning for distutils Version
Hi 

When I run pytest with pytest_cases I get the following deprecation warning on each run.
I am using python 3.10 and have [python dev mode](https://docs.python.org/3/library/devmode.html) on (`export PYTHONDEVMODE=1`)
```
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:30: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST_VERSION = LooseVersion(pytest.__version__)
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:31: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST3_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.0.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:32: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST32_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.2.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:33: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST33_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.3.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:34: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST34_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.4.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:35: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST35_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.5.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:36: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST361_36X = LooseVersion('3.6.0') < PYTEST_VERSION < LooseVersion('3.7.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:37: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST37_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.7.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:38: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST38_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.8.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:39: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST46_OR_GREATER = PYTEST_VERSION >= LooseVersion('4.6.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:40: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST53_OR_GREATER = PYTEST_VERSION >= LooseVersion('5.3.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:41: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST54_OR_GREATER = PYTEST_VERSION >= LooseVersion('5.4.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:42: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST421_OR_GREATER = PYTEST_VERSION >= LooseVersion('4.2.1')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:43: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST6_OR_GREATER = PYTEST_VERSION >= LooseVersion('6.0.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:44: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST7_OR_GREATER = PYTEST_VERSION >= LooseVersion('7.0.0')
/.../lib/python3.10/site-packages/pytest_cases/common_pytest_marks.py:45: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
  PYTEST71_OR_GREATER = PYTEST_VERSION >= LooseVersion('7.1.0')
```
Are you aware of this issue and interested in a fix / contribution?


Discussion / hints:
Thanks a lot @kdeldycke ! It seems that we should instead try

```python
from pkg_resources import parse_version
```

(other possibilities are discussed in https://stackoverflow.com/questions/11887762/how-do-i-compare-version-numbers-in-python but they seem a bit more risky to me)

Tried with Python 3.12.0b2:

```
$ python -c 'from distutils.version import LooseVersion; print(LooseVersion("10.0.0") > LooseVersion("2.10.0"))'
...
ModuleNotFoundError: No module named 'distutils'
```

A quick fix would be to install `setuptools`. The import will work with the following deprecation message:

```
$ pip install setuptools
...
Successfully installed setuptools-67.8.0
$ python -c 'from distutils.version import LooseVersion; print(LooseVersion("10.0.0") > LooseVersion("2.10.0"))'
<string>:1: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
True
```

Using the `packaging` library seems to work, too:

```
$ pip install packaging
...
Successfully installed packaging-23.1
$ python -c 'from packaging.version import Version; print(Version("10.0.0") > Version("2.10.0"))'
True
```

Already causing a deprecation warning in python 3.11:
```py
06/15/23 21:12:24] WARNING  /home/shauncutts/.cache/pypoetry/virtualenvs/moonshine-CuQK1AVF-py3.11/lib/python3.11/site-packages/pytest_cases/com warnings.py:109
                             mon_pytest_marks.py:30: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.                
                               PYTEST_VERSION = LooseVersion(pytest.__version__)                                                                                 
                                                                                                                                                                 
                    WARNING  /home/shauncutts/.cache/pypoetry/virtualenvs/moonshine-CuQK1AVF-py3.11/lib/python3.11/site-packages/pytest_cases/com warnings.py:109
                             mon_pytest_marks.py:31: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.                
                               PYTEST3_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.0.0')                                                                      
```
@smarie Will this be addressed soon? The [release candidate](https://www.python.org/downloads/release/python-3120rc1/) for Python 3.12 was published last week, and the planned release for Python 3.12 is in less than 2 months. I would like to start including Python 3.12 in my test matrix. 

Also, I don't think `pkg_resources.parse_version` is the right solution. The pkg_resources package has a [notice that it is deprecated](https://setuptools.pypa.io/en/latest/pkg_resources.html). The deprecation warnings about distutils recommend using `packaging.version`, which @mportesdev demonstrated above to work as expected.  
https://peps.python.org/pep-0632/#migration-advice

> For these modules or types, use the standards-defined Python Packaging Authority packages specified:
> 
> distutils.version — use the packaging package
It seems there is already a PR open which tries to address this: #303 

Fix patch:
diff --git a/setup.cfg b/setup.cfg
index a899dc1c..e2bbef1d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -38,6 +38,7 @@ setup_requires =
 install_requires =
     decopatch
     makefun>=1.9.5
+    packaging
     # note: pytest, too :)
     functools32;python_version<'3.2'
     # note: do not use double quotes in these, this triggers a weird bug in PyCharm in debug mode only


Test patch:
diff --git a/src/pytest_cases/common_pytest_marks.py b/src/pytest_cases/common_pytest_marks.py
index e42e6881..de9e2b58 100644
--- a/src/pytest_cases/common_pytest_marks.py
+++ b/src/pytest_cases/common_pytest_marks.py
@@ -5,7 +5,7 @@
 import itertools
 
 import warnings
-from distutils.version import LooseVersion
+from packaging.version import Version
 
 try:  # python 3.3+
     from inspect import signature
@@ -27,22 +27,22 @@
 from .common_mini_six import string_types
 
 
-PYTEST_VERSION = LooseVersion(pytest.__version__)
-PYTEST3_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.0.0')
-PYTEST32_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.2.0')
-PYTEST33_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.3.0')
-PYTEST34_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.4.0')
-PYTEST35_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.5.0')
-PYTEST361_36X = LooseVersion('3.6.0') < PYTEST_VERSION < LooseVersion('3.7.0')
-PYTEST37_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.7.0')
-PYTEST38_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.8.0')
-PYTEST46_OR_GREATER = PYTEST_VERSION >= LooseVersion('4.6.0')
-PYTEST53_OR_GREATER = PYTEST_VERSION >= LooseVersion('5.3.0')
-PYTEST54_OR_GREATER = PYTEST_VERSION >= LooseVersion('5.4.0')
-PYTEST421_OR_GREATER = PYTEST_VERSION >= LooseVersion('4.2.1')
-PYTEST6_OR_GREATER = PYTEST_VERSION >= LooseVersion('6.0.0')
-PYTEST7_OR_GREATER = PYTEST_VERSION >= LooseVersion('7.0.0')
-PYTEST71_OR_GREATER = PYTEST_VERSION >= LooseVersion('7.1.0')
+PYTEST_VERSION = Version(pytest.__version__)
+PYTEST3_OR_GREATER = PYTEST_VERSION >= Version('3.0.0')
+PYTEST32_OR_GREATER = PYTEST_VERSION >= Version('3.2.0')
+PYTEST33_OR_GREATER = PYTEST_VERSION >= Version('3.3.0')
+PYTEST34_OR_GREATER = PYTEST_VERSION >= Version('3.4.0')
+PYTEST35_OR_GREATER = PYTEST_VERSION >= Version('3.5.0')
+PYTEST361_36X = Version('3.6.0') < PYTEST_VERSION < Version('3.7.0')
+PYTEST37_OR_GREATER = PYTEST_VERSION >= Version('3.7.0')
+PYTEST38_OR_GREATER = PYTEST_VERSION >= Version('3.8.0')
+PYTEST46_OR_GREATER = PYTEST_VERSION >= Version('4.6.0')
+PYTEST53_OR_GREATER = PYTEST_VERSION >= Version('5.3.0')
+PYTEST54_OR_GREATER = PYTEST_VERSION >= Version('5.4.0')
+PYTEST421_OR_GREATER = PYTEST_VERSION >= Version('4.2.1')
+PYTEST6_OR_GREATER = PYTEST_VERSION >= Version('6.0.0')
+PYTEST7_OR_GREATER = PYTEST_VERSION >= Version('7.0.0')
+PYTEST71_OR_GREATER = PYTEST_VERSION >= Version('7.1.0')
 
 
 def get_param_argnames_as_list(argnames):
diff --git a/src/pytest_cases/plugin.py b/src/pytest_cases/plugin.py
index 5411ede3..a41c2a80 100644
--- a/src/pytest_cases/plugin.py
+++ b/src/pytest_cases/plugin.py
@@ -572,7 +572,7 @@ def _update_fixture_defs(self):
         all_fixture_defs = self.tree.get_all_fixture_defs(drop_fake_fixtures=False, try_to_sort=True)
 
         # # also sort all partitions (note that we cannot rely on the order in all_fixture_defs when scopes are same!)
-        # if LooseVersion(pytest.__version__) >= LooseVersion('3.5.0'):
+        # if Version(pytest.__version__) >= Version('3.5.0'):
         #     f_scope = get_pytest_function_scopeval()
         #     for p in self.partitions:
         #         def sort_by_scope2(fixture_name):  # noqa
diff --git a/tests/cases/issues/test_issue_242.py b/tests/cases/issues/test_issue_242.py
index a36b51cf..cb51667a 100644
--- a/tests/cases/issues/test_issue_242.py
+++ b/tests/cases/issues/test_issue_242.py
@@ -1,5 +1,5 @@
 import pytest
-from distutils.version import LooseVersion
+from packaging.version import Version
 
 import sys
 
@@ -9,8 +9,8 @@
 from functools import partial
 
 
-PYTEST_VERSION = LooseVersion(pytest.__version__)
-PYTEST3_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.0.0')
+PYTEST_VERSION = Version(pytest.__version__)
+PYTEST3_OR_GREATER = PYTEST_VERSION >= Version('3.0.0')
 PY3 = sys.version_info >= (3,)
 
 
diff --git a/tests/cases/issues/test_issue_246.py b/tests/cases/issues/test_issue_246.py
index ac6213ee..55950ace 100644
--- a/tests/cases/issues/test_issue_246.py
+++ b/tests/cases/issues/test_issue_246.py
@@ -1,10 +1,10 @@
-from distutils.version import LooseVersion
+from packaging.version import Version
 
 import pytest
 from pytest_cases import parametrize_with_cases
 
-PYTEST_VERSION = LooseVersion(pytest.__version__)
-PYTEST3_OR_GREATER = PYTEST_VERSION >= LooseVersion('3.0.0')
+PYTEST_VERSION = Version(pytest.__version__)
+PYTEST3_OR_GREATER = PYTEST_VERSION >= Version('3.0.0')
 
 if PYTEST3_OR_GREATER:
     @pytest.mark.foo