We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DeprecationWarning: The distutils package is deprecated
## 🐛 Bug

TorchMetrics still uses distutils, but distutils is deprecated.

### To Reproduce

```console
> python3 -Wall
>>> import torchmetrics
lib/python3.11/site-packages/torchmetrics/utilities/imports.py:18: DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives
  from distutils.version import LooseVersion
```

### Expected behavior

I would not expect TorchMetrics to still be using distutils.

### Environment

- TorchMetrics version: 1.1, 1.2, master (spack)
- Python & PyTorch Version: 3.11.4 & 2.0.1
- Any other relevant information such as OS: macOS 13.5.2

### Additional context

I also sometimes see a different error message from pytest:
```
DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
```
This gives one possible solution.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/torchmetrics/utilities/imports.py`
Content:
```
1 # Copyright The Lightning team.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """Import utilities."""
15 import operator
16 import shutil
17 import sys
18 from distutils.version import LooseVersion
19 from typing import Optional
20 
21 from lightning_utilities.core.imports import compare_version, package_available
22 
23 _PYTHON_VERSION = ".".join(map(str, [sys.version_info.major, sys.version_info.minor, sys.version_info.micro]))
24 _PYTHON_LOWER_3_8 = LooseVersion(_PYTHON_VERSION) < LooseVersion("3.8")
25 _TORCH_LOWER_1_12_DEV: Optional[bool] = compare_version("torch", operator.lt, "1.12.0.dev")
26 _TORCH_GREATER_EQUAL_1_9: Optional[bool] = compare_version("torch", operator.ge, "1.9.0")
27 _TORCH_GREATER_EQUAL_1_10: Optional[bool] = compare_version("torch", operator.ge, "1.10.0")
28 _TORCH_GREATER_EQUAL_1_11: Optional[bool] = compare_version("torch", operator.ge, "1.11.0")
29 _TORCH_GREATER_EQUAL_1_12: Optional[bool] = compare_version("torch", operator.ge, "1.12.0")
30 _TORCH_GREATER_EQUAL_1_13: Optional[bool] = compare_version("torch", operator.ge, "1.13.0")
31 
32 _JIWER_AVAILABLE: bool = package_available("jiwer")
33 _NLTK_AVAILABLE: bool = package_available("nltk")
34 _ROUGE_SCORE_AVAILABLE: bool = package_available("rouge_score")
35 _BERTSCORE_AVAILABLE: bool = package_available("bert_score")
36 _SCIPY_AVAILABLE: bool = package_available("scipy")
37 _TORCH_FIDELITY_AVAILABLE: bool = package_available("torch_fidelity")
38 _LPIPS_AVAILABLE: bool = package_available("lpips")
39 _PYCOCOTOOLS_AVAILABLE: bool = package_available("pycocotools")
40 _TORCHVISION_AVAILABLE: bool = package_available("torchvision")
41 _TORCHVISION_GREATER_EQUAL_0_8: Optional[bool] = compare_version("torchvision", operator.ge, "0.8.0")
42 _TORCHVISION_GREATER_EQUAL_0_13: Optional[bool] = compare_version("torchvision", operator.ge, "0.13.0")
43 _TQDM_AVAILABLE: bool = package_available("tqdm")
44 _TRANSFORMERS_AVAILABLE: bool = package_available("transformers")
45 _TRANSFORMERS_GREATER_EQUAL_4_4: Optional[bool] = compare_version("transformers", operator.ge, "4.4.0")
46 _TRANSFORMERS_GREATER_EQUAL_4_10: Optional[bool] = compare_version("transformers", operator.ge, "4.10.0")
47 _PESQ_AVAILABLE: bool = package_available("pesq")
48 _GAMMATONE_AVAILABLE: bool = package_available("gammatone")
49 _TORCHAUDIO_AVAILABLE: bool = package_available("torchaudio")
50 _TORCHAUDIO_GREATER_EQUAL_0_10: Optional[bool] = compare_version("torchaudio", operator.ge, "0.10.0")
51 _SACREBLEU_AVAILABLE: bool = package_available("sacrebleu")
52 _REGEX_AVAILABLE: bool = package_available("regex")
53 _PYSTOI_AVAILABLE: bool = package_available("pystoi")
54 _FAST_BSS_EVAL_AVAILABLE: bool = package_available("fast_bss_eval")
55 _MATPLOTLIB_AVAILABLE: bool = package_available("matplotlib")
56 _SCIENCEPLOT_AVAILABLE: bool = package_available("scienceplots")
57 _MULTIPROCESSING_AVAILABLE: bool = package_available("multiprocessing")
58 _XLA_AVAILABLE: bool = package_available("torch_xla")
59 _PIQ_GREATER_EQUAL_0_8: Optional[bool] = compare_version("piq", operator.ge, "0.8.0")
60 _FASTER_COCO_EVAL_AVAILABLE: bool = package_available("faster_coco_eval")
61 
62 _LATEX_AVAILABLE: bool = shutil.which("latex") is not None
63
```

Path: `.github/assistant.py`
Content:
```
1 # Copyright The Lightning team.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 import glob
15 import json
16 import logging
17 import os
18 import re
19 import sys
20 import traceback
21 from distutils.version import LooseVersion
22 from typing import List, Optional, Tuple, Union
23 
24 import fire
25 import requests
26 from pkg_resources import parse_requirements
27 
28 _REQUEST_TIMEOUT = 10
29 _PATH_ROOT = os.path.dirname(os.path.dirname(__file__))
30 _PKG_WIDE_SUBPACKAGES = ("utilities", "helpers")
31 LUT_PYTHON_TORCH = {
32     "3.8": "1.4",
33     "3.9": "1.7.1",
34     "3.10": "1.11",
35     "3.11": "1.13",
36 }
37 _path = lambda *ds: os.path.join(_PATH_ROOT, *ds)
38 REQUIREMENTS_FILES = (*glob.glob(_path("requirements", "*.txt")), _path("requirements.txt"))
39 
40 
41 def request_url(url: str, auth_token: Optional[str] = None) -> Optional[dict]:
42     """General request with checking if request limit was reached."""
43     auth_header = {"Authorization": f"token {auth_token}"} if auth_token else {}
44     try:
45         req = requests.get(url, headers=auth_header, timeout=_REQUEST_TIMEOUT)
46     except requests.exceptions.Timeout:
47         traceback.print_exc()
48         return None
49     if req.status_code == 403:
50         return None
51     return json.loads(req.content.decode(req.encoding))
52 
53 
54 class AssistantCLI:
55     """CLI assistant for local CI."""
56 
57     @staticmethod
58     def prune_packages(req_file: str, *pkgs: str) -> None:
59         """Prune packages from requirement file."""
60         with open(req_file) as fp:
61             lines = fp.readlines()
62 
63         for pkg in pkgs:
64             lines = [ln for ln in lines if not ln.startswith(pkg)]
65         logging.info(lines)
66 
67         with open(req_file, "w", encoding="utf-8") as fp:
68             fp.writelines(lines)
69 
70     @staticmethod
71     def set_min_torch_by_python(fpath: str = "requirements/base.txt") -> None:
72         """Set minimal torch version according to Python actual version.
73 
74         >>> AssistantCLI.set_min_torch_by_python("../requirements/base.txt")
75 
76         """
77         py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
78         if py_ver not in LUT_PYTHON_TORCH:
79             return
80         with open(fpath) as fp:
81             reqs = parse_requirements(fp.readlines())
82         pkg_ver = next(p for p in reqs if p.name == "torch")
83         pt_ver = min([LooseVersion(v[1]) for v in pkg_ver.specs])
84         pt_ver = max(LooseVersion(LUT_PYTHON_TORCH[py_ver]), pt_ver)
85         with open(fpath) as fp:
86             requires = fp.read()
87         requires = re.sub(r"torch>=[\d\.]+", f"torch>={pt_ver}", requires)
88         with open(fpath, "w", encoding="utf-8") as fp:
89             fp.write(requires)
90 
91     @staticmethod
92     def replace_min_requirements(fpath: str) -> None:
93         """Replace all `>=` by `==` in given file."""
94         logging.info(f"processing: {fpath}")
95         with open(fpath, encoding="utf-8") as fp:
96             req = fp.read()
97         req = req.replace(">=", "==")
98         with open(fpath, "w", encoding="utf-8") as fp:
99             fp.write(req)
100 
101     @staticmethod
102     def set_oldest_versions(req_files: List[str] = REQUIREMENTS_FILES) -> None:
103         """Set the oldest version for requirements."""
104         AssistantCLI.set_min_torch_by_python()
105         for fpath in req_files:
106             AssistantCLI.replace_min_requirements(fpath)
107 
108     @staticmethod
109     def changed_domains(
110         pr: Optional[int] = None,
111         auth_token: Optional[str] = None,
112         as_list: bool = False,
113         general_sub_pkgs: Tuple[str] = _PKG_WIDE_SUBPACKAGES,
114     ) -> Union[str, List[str]]:
115         """Determine what domains were changed in particular PR."""
116         if not pr:
117             return "unittests"
118         url = f"https://api.github.com/repos/Lightning-AI/torchmetrics/pulls/{pr}/files"
119         logging.debug(url)
120         data = request_url(url, auth_token)
121         if not data:
122             logging.debug("WARNING: No data was received -> test everything.")
123             return "unittests"
124         files = [d["filename"] for d in data]
125 
126         # filter out all integrations as they run in separate suit
127         files = [fn for fn in files if not fn.startswith("tests/integrations")]
128         if not files:
129             logging.debug("Only integrations was changed so not reason for deep testing...")
130             return ""
131         # filter only docs files
132         files_ = [fn for fn in files if fn.startswith("docs")]
133         if len(files) == len(files_):
134             logging.debug("Only docs was changed so not reason for deep testing...")
135             return ""
136 
137         # filter only package files and skip inits
138         _is_in_test = lambda fn: fn.startswith("tests")
139         _filter_pkg = lambda fn: _is_in_test(fn) or (fn.startswith("src/torchmetrics") and "__init__.py" not in fn)
140         files_pkg = [fn for fn in files if _filter_pkg(fn)]
141         if not files_pkg:
142             return "unittests"
143 
144         # parse domains
145         def _crop_path(fname: str, paths: List[str]) -> str:
146             for p in paths:
147                 fname = fname.replace(p, "")
148             return fname
149 
150         files_pkg = [_crop_path(fn, ["src/torchmetrics/", "tests/unittests/", "functional/"]) for fn in files_pkg]
151         # filter domain names
152         tm_modules = [fn.split("/")[0] for fn in files_pkg if "/" in fn]
153         # filter general (used everywhere) sub-packages
154         tm_modules = [md for md in tm_modules if md not in general_sub_pkgs]
155         if len(files_pkg) > len(tm_modules):
156             logging.debug("Some more files was changed -> rather test everything...")
157             return "unittests"
158         # keep only unique
159         if as_list:
160             return list(tm_modules)
161         tm_modules = [f"unittests/{md}" for md in set(tm_modules)]
162         not_exists = [p for p in tm_modules if os.path.exists(p)]
163         if not_exists:
164             raise ValueError(f"Missing following paths: {not_exists}")
165         return " ".join(tm_modules)
166 
167 
168 if __name__ == "__main__":
169     logging.basicConfig(level=logging.INFO)
170     fire.Fire(AssistantCLI)
171
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/.github/assistant.py b/.github/assistant.py
--- a/.github/assistant.py
+++ b/.github/assistant.py
@@ -18,11 +18,11 @@
 import re
 import sys
 import traceback
-from distutils.version import LooseVersion
 from typing import List, Optional, Tuple, Union
 
 import fire
 import requests
+from packaging.version import parse
 from pkg_resources import parse_requirements
 
 _REQUEST_TIMEOUT = 10
@@ -80,8 +80,8 @@
         with open(fpath) as fp:
             reqs = parse_requirements(fp.readlines())
         pkg_ver = next(p for p in reqs if p.name == "torch")
-        pt_ver = min([LooseVersion(v[1]) for v in pkg_ver.specs])
-        pt_ver = max(LooseVersion(LUT_PYTHON_TORCH[py_ver]), pt_ver)
+        pt_ver = min([parse(v[1]) for v in pkg_ver.specs])
+        pt_ver = max(parse(LUT_PYTHON_TORCH[py_ver]), pt_ver)
         with open(fpath) as fp:
             requires = fp.read()
         requires = re.sub(r"torch>=[\d\.]+", f"torch>={pt_ver}", requires)
diff --git a/src/torchmetrics/utilities/imports.py b/src/torchmetrics/utilities/imports.py
--- a/src/torchmetrics/utilities/imports.py
+++ b/src/torchmetrics/utilities/imports.py
@@ -15,13 +15,13 @@
 import operator
 import shutil
 import sys
-from distutils.version import LooseVersion
 from typing import Optional
 
 from lightning_utilities.core.imports import compare_version, package_available
+from packaging.version import Version, parse
 
 _PYTHON_VERSION = ".".join(map(str, [sys.version_info.major, sys.version_info.minor, sys.version_info.micro]))
-_PYTHON_LOWER_3_8 = LooseVersion(_PYTHON_VERSION) < LooseVersion("3.8")
+_PYTHON_LOWER_3_8 = parse(_PYTHON_VERSION) < Version("3.8")
 _TORCH_LOWER_1_12_DEV: Optional[bool] = compare_version("torch", operator.lt, "1.12.0.dev")
 _TORCH_GREATER_EQUAL_1_9: Optional[bool] = compare_version("torch", operator.ge, "1.9.0")
 _TORCH_GREATER_EQUAL_1_10: Optional[bool] = compare_version("torch", operator.ge, "1.10.0")

{"golden_diff": "diff --git a/.github/assistant.py b/.github/assistant.py\n--- a/.github/assistant.py\n+++ b/.github/assistant.py\n@@ -18,11 +18,11 @@\n import re\n import sys\n import traceback\n-from distutils.version import LooseVersion\n from typing import List, Optional, Tuple, Union\n \n import fire\n import requests\n+from packaging.version import parse\n from pkg_resources import parse_requirements\n \n _REQUEST_TIMEOUT = 10\n@@ -80,8 +80,8 @@\n         with open(fpath) as fp:\n             reqs = parse_requirements(fp.readlines())\n         pkg_ver = next(p for p in reqs if p.name == \"torch\")\n-        pt_ver = min([LooseVersion(v[1]) for v in pkg_ver.specs])\n-        pt_ver = max(LooseVersion(LUT_PYTHON_TORCH[py_ver]), pt_ver)\n+        pt_ver = min([parse(v[1]) for v in pkg_ver.specs])\n+        pt_ver = max(parse(LUT_PYTHON_TORCH[py_ver]), pt_ver)\n         with open(fpath) as fp:\n             requires = fp.read()\n         requires = re.sub(r\"torch>=[\\d\\.]+\", f\"torch>={pt_ver}\", requires)\ndiff --git a/src/torchmetrics/utilities/imports.py b/src/torchmetrics/utilities/imports.py\n--- a/src/torchmetrics/utilities/imports.py\n+++ b/src/torchmetrics/utilities/imports.py\n@@ -15,13 +15,13 @@\n import operator\n import shutil\n import sys\n-from distutils.version import LooseVersion\n from typing import Optional\n \n from lightning_utilities.core.imports import compare_version, package_available\n+from packaging.version import Version, parse\n \n _PYTHON_VERSION = \".\".join(map(str, [sys.version_info.major, sys.version_info.minor, sys.version_info.micro]))\n-_PYTHON_LOWER_3_8 = LooseVersion(_PYTHON_VERSION) < LooseVersion(\"3.8\")\n+_PYTHON_LOWER_3_8 = parse(_PYTHON_VERSION) < Version(\"3.8\")\n _TORCH_LOWER_1_12_DEV: Optional[bool] = compare_version(\"torch\", operator.lt, \"1.12.0.dev\")\n _TORCH_GREATER_EQUAL_1_9: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.9.0\")\n _TORCH_GREATER_EQUAL_1_10: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.10.0\")\n", "issue": "DeprecationWarning: The distutils package is deprecated\n## \ud83d\udc1b Bug\r\n\r\nTorchMetrics still uses distutils, but distutils is deprecated.\r\n\r\n### To Reproduce\r\n\r\n```console\r\n> python3 -Wall\r\n>>> import torchmetrics\r\nlib/python3.11/site-packages/torchmetrics/utilities/imports.py:18: DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives\r\n  from distutils.version import LooseVersion\r\n```\r\n\r\n### Expected behavior\r\n\r\nI would not expect TorchMetrics to still be using distutils.\r\n\r\n### Environment\r\n\r\n- TorchMetrics version: 1.1, 1.2, master (spack)\r\n- Python & PyTorch Version: 3.11.4 & 2.0.1\r\n- Any other relevant information such as OS: macOS 13.5.2\r\n\r\n### Additional context\r\n\r\nI also sometimes see a different error message from pytest:\r\n```\r\nDeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.\r\n```\r\nThis gives one possible solution.\n", "before_files": [{"content": "# Copyright The Lightning team.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Import utilities.\"\"\"\nimport operator\nimport shutil\nimport sys\nfrom distutils.version import LooseVersion\nfrom typing import Optional\n\nfrom lightning_utilities.core.imports import compare_version, package_available\n\n_PYTHON_VERSION = \".\".join(map(str, [sys.version_info.major, sys.version_info.minor, sys.version_info.micro]))\n_PYTHON_LOWER_3_8 = LooseVersion(_PYTHON_VERSION) < LooseVersion(\"3.8\")\n_TORCH_LOWER_1_12_DEV: Optional[bool] = compare_version(\"torch\", operator.lt, \"1.12.0.dev\")\n_TORCH_GREATER_EQUAL_1_9: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.9.0\")\n_TORCH_GREATER_EQUAL_1_10: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.10.0\")\n_TORCH_GREATER_EQUAL_1_11: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.11.0\")\n_TORCH_GREATER_EQUAL_1_12: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.12.0\")\n_TORCH_GREATER_EQUAL_1_13: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.13.0\")\n\n_JIWER_AVAILABLE: bool = package_available(\"jiwer\")\n_NLTK_AVAILABLE: bool = package_available(\"nltk\")\n_ROUGE_SCORE_AVAILABLE: bool = package_available(\"rouge_score\")\n_BERTSCORE_AVAILABLE: bool = package_available(\"bert_score\")\n_SCIPY_AVAILABLE: bool = package_available(\"scipy\")\n_TORCH_FIDELITY_AVAILABLE: bool = package_available(\"torch_fidelity\")\n_LPIPS_AVAILABLE: bool = package_available(\"lpips\")\n_PYCOCOTOOLS_AVAILABLE: bool = package_available(\"pycocotools\")\n_TORCHVISION_AVAILABLE: bool = package_available(\"torchvision\")\n_TORCHVISION_GREATER_EQUAL_0_8: Optional[bool] = compare_version(\"torchvision\", operator.ge, \"0.8.0\")\n_TORCHVISION_GREATER_EQUAL_0_13: Optional[bool] = compare_version(\"torchvision\", operator.ge, \"0.13.0\")\n_TQDM_AVAILABLE: bool = package_available(\"tqdm\")\n_TRANSFORMERS_AVAILABLE: bool = package_available(\"transformers\")\n_TRANSFORMERS_GREATER_EQUAL_4_4: Optional[bool] = compare_version(\"transformers\", operator.ge, \"4.4.0\")\n_TRANSFORMERS_GREATER_EQUAL_4_10: Optional[bool] = compare_version(\"transformers\", operator.ge, \"4.10.0\")\n_PESQ_AVAILABLE: bool = package_available(\"pesq\")\n_GAMMATONE_AVAILABLE: bool = package_available(\"gammatone\")\n_TORCHAUDIO_AVAILABLE: bool = package_available(\"torchaudio\")\n_TORCHAUDIO_GREATER_EQUAL_0_10: Optional[bool] = compare_version(\"torchaudio\", operator.ge, \"0.10.0\")\n_SACREBLEU_AVAILABLE: bool = package_available(\"sacrebleu\")\n_REGEX_AVAILABLE: bool = package_available(\"regex\")\n_PYSTOI_AVAILABLE: bool = package_available(\"pystoi\")\n_FAST_BSS_EVAL_AVAILABLE: bool = package_available(\"fast_bss_eval\")\n_MATPLOTLIB_AVAILABLE: bool = package_available(\"matplotlib\")\n_SCIENCEPLOT_AVAILABLE: bool = package_available(\"scienceplots\")\n_MULTIPROCESSING_AVAILABLE: bool = package_available(\"multiprocessing\")\n_XLA_AVAILABLE: bool = package_available(\"torch_xla\")\n_PIQ_GREATER_EQUAL_0_8: Optional[bool] = compare_version(\"piq\", operator.ge, \"0.8.0\")\n_FASTER_COCO_EVAL_AVAILABLE: bool = package_available(\"faster_coco_eval\")\n\n_LATEX_AVAILABLE: bool = shutil.which(\"latex\") is not None\n", "path": "src/torchmetrics/utilities/imports.py"}, {"content": "# Copyright The Lightning team.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport glob\nimport json\nimport logging\nimport os\nimport re\nimport sys\nimport traceback\nfrom distutils.version import LooseVersion\nfrom typing import List, Optional, Tuple, Union\n\nimport fire\nimport requests\nfrom pkg_resources import parse_requirements\n\n_REQUEST_TIMEOUT = 10\n_PATH_ROOT = os.path.dirname(os.path.dirname(__file__))\n_PKG_WIDE_SUBPACKAGES = (\"utilities\", \"helpers\")\nLUT_PYTHON_TORCH = {\n    \"3.8\": \"1.4\",\n    \"3.9\": \"1.7.1\",\n    \"3.10\": \"1.11\",\n    \"3.11\": \"1.13\",\n}\n_path = lambda *ds: os.path.join(_PATH_ROOT, *ds)\nREQUIREMENTS_FILES = (*glob.glob(_path(\"requirements\", \"*.txt\")), _path(\"requirements.txt\"))\n\n\ndef request_url(url: str, auth_token: Optional[str] = None) -> Optional[dict]:\n    \"\"\"General request with checking if request limit was reached.\"\"\"\n    auth_header = {\"Authorization\": f\"token {auth_token}\"} if auth_token else {}\n    try:\n        req = requests.get(url, headers=auth_header, timeout=_REQUEST_TIMEOUT)\n    except requests.exceptions.Timeout:\n        traceback.print_exc()\n        return None\n    if req.status_code == 403:\n        return None\n    return json.loads(req.content.decode(req.encoding))\n\n\nclass AssistantCLI:\n    \"\"\"CLI assistant for local CI.\"\"\"\n\n    @staticmethod\n    def prune_packages(req_file: str, *pkgs: str) -> None:\n        \"\"\"Prune packages from requirement file.\"\"\"\n        with open(req_file) as fp:\n            lines = fp.readlines()\n\n        for pkg in pkgs:\n            lines = [ln for ln in lines if not ln.startswith(pkg)]\n        logging.info(lines)\n\n        with open(req_file, \"w\", encoding=\"utf-8\") as fp:\n            fp.writelines(lines)\n\n    @staticmethod\n    def set_min_torch_by_python(fpath: str = \"requirements/base.txt\") -> None:\n        \"\"\"Set minimal torch version according to Python actual version.\n\n        >>> AssistantCLI.set_min_torch_by_python(\"../requirements/base.txt\")\n\n        \"\"\"\n        py_ver = f\"{sys.version_info.major}.{sys.version_info.minor}\"\n        if py_ver not in LUT_PYTHON_TORCH:\n            return\n        with open(fpath) as fp:\n            reqs = parse_requirements(fp.readlines())\n        pkg_ver = next(p for p in reqs if p.name == \"torch\")\n        pt_ver = min([LooseVersion(v[1]) for v in pkg_ver.specs])\n        pt_ver = max(LooseVersion(LUT_PYTHON_TORCH[py_ver]), pt_ver)\n        with open(fpath) as fp:\n            requires = fp.read()\n        requires = re.sub(r\"torch>=[\\d\\.]+\", f\"torch>={pt_ver}\", requires)\n        with open(fpath, \"w\", encoding=\"utf-8\") as fp:\n            fp.write(requires)\n\n    @staticmethod\n    def replace_min_requirements(fpath: str) -> None:\n        \"\"\"Replace all `>=` by `==` in given file.\"\"\"\n        logging.info(f\"processing: {fpath}\")\n        with open(fpath, encoding=\"utf-8\") as fp:\n            req = fp.read()\n        req = req.replace(\">=\", \"==\")\n        with open(fpath, \"w\", encoding=\"utf-8\") as fp:\n            fp.write(req)\n\n    @staticmethod\n    def set_oldest_versions(req_files: List[str] = REQUIREMENTS_FILES) -> None:\n        \"\"\"Set the oldest version for requirements.\"\"\"\n        AssistantCLI.set_min_torch_by_python()\n        for fpath in req_files:\n            AssistantCLI.replace_min_requirements(fpath)\n\n    @staticmethod\n    def changed_domains(\n        pr: Optional[int] = None,\n        auth_token: Optional[str] = None,\n        as_list: bool = False,\n        general_sub_pkgs: Tuple[str] = _PKG_WIDE_SUBPACKAGES,\n    ) -> Union[str, List[str]]:\n        \"\"\"Determine what domains were changed in particular PR.\"\"\"\n        if not pr:\n            return \"unittests\"\n        url = f\"https://api.github.com/repos/Lightning-AI/torchmetrics/pulls/{pr}/files\"\n        logging.debug(url)\n        data = request_url(url, auth_token)\n        if not data:\n            logging.debug(\"WARNING: No data was received -> test everything.\")\n            return \"unittests\"\n        files = [d[\"filename\"] for d in data]\n\n        # filter out all integrations as they run in separate suit\n        files = [fn for fn in files if not fn.startswith(\"tests/integrations\")]\n        if not files:\n            logging.debug(\"Only integrations was changed so not reason for deep testing...\")\n            return \"\"\n        # filter only docs files\n        files_ = [fn for fn in files if fn.startswith(\"docs\")]\n        if len(files) == len(files_):\n            logging.debug(\"Only docs was changed so not reason for deep testing...\")\n            return \"\"\n\n        # filter only package files and skip inits\n        _is_in_test = lambda fn: fn.startswith(\"tests\")\n        _filter_pkg = lambda fn: _is_in_test(fn) or (fn.startswith(\"src/torchmetrics\") and \"__init__.py\" not in fn)\n        files_pkg = [fn for fn in files if _filter_pkg(fn)]\n        if not files_pkg:\n            return \"unittests\"\n\n        # parse domains\n        def _crop_path(fname: str, paths: List[str]) -> str:\n            for p in paths:\n                fname = fname.replace(p, \"\")\n            return fname\n\n        files_pkg = [_crop_path(fn, [\"src/torchmetrics/\", \"tests/unittests/\", \"functional/\"]) for fn in files_pkg]\n        # filter domain names\n        tm_modules = [fn.split(\"/\")[0] for fn in files_pkg if \"/\" in fn]\n        # filter general (used everywhere) sub-packages\n        tm_modules = [md for md in tm_modules if md not in general_sub_pkgs]\n        if len(files_pkg) > len(tm_modules):\n            logging.debug(\"Some more files was changed -> rather test everything...\")\n            return \"unittests\"\n        # keep only unique\n        if as_list:\n            return list(tm_modules)\n        tm_modules = [f\"unittests/{md}\" for md in set(tm_modules)]\n        not_exists = [p for p in tm_modules if os.path.exists(p)]\n        if not_exists:\n            raise ValueError(f\"Missing following paths: {not_exists}\")\n        return \" \".join(tm_modules)\n\n\nif __name__ == \"__main__\":\n    logging.basicConfig(level=logging.INFO)\n    fire.Fire(AssistantCLI)\n", "path": ".github/assistant.py"}], "after_files": [{"content": "# Copyright The Lightning team.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Import utilities.\"\"\"\nimport operator\nimport shutil\nimport sys\nfrom typing import Optional\n\nfrom lightning_utilities.core.imports import compare_version, package_available\nfrom packaging.version import Version, parse\n\n_PYTHON_VERSION = \".\".join(map(str, [sys.version_info.major, sys.version_info.minor, sys.version_info.micro]))\n_PYTHON_LOWER_3_8 = parse(_PYTHON_VERSION) < Version(\"3.8\")\n_TORCH_LOWER_1_12_DEV: Optional[bool] = compare_version(\"torch\", operator.lt, \"1.12.0.dev\")\n_TORCH_GREATER_EQUAL_1_9: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.9.0\")\n_TORCH_GREATER_EQUAL_1_10: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.10.0\")\n_TORCH_GREATER_EQUAL_1_11: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.11.0\")\n_TORCH_GREATER_EQUAL_1_12: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.12.0\")\n_TORCH_GREATER_EQUAL_1_13: Optional[bool] = compare_version(\"torch\", operator.ge, \"1.13.0\")\n\n_JIWER_AVAILABLE: bool = package_available(\"jiwer\")\n_NLTK_AVAILABLE: bool = package_available(\"nltk\")\n_ROUGE_SCORE_AVAILABLE: bool = package_available(\"rouge_score\")\n_BERTSCORE_AVAILABLE: bool = package_available(\"bert_score\")\n_SCIPY_AVAILABLE: bool = package_available(\"scipy\")\n_TORCH_FIDELITY_AVAILABLE: bool = package_available(\"torch_fidelity\")\n_LPIPS_AVAILABLE: bool = package_available(\"lpips\")\n_PYCOCOTOOLS_AVAILABLE: bool = package_available(\"pycocotools\")\n_TORCHVISION_AVAILABLE: bool = package_available(\"torchvision\")\n_TORCHVISION_GREATER_EQUAL_0_8: Optional[bool] = compare_version(\"torchvision\", operator.ge, \"0.8.0\")\n_TORCHVISION_GREATER_EQUAL_0_13: Optional[bool] = compare_version(\"torchvision\", operator.ge, \"0.13.0\")\n_TQDM_AVAILABLE: bool = package_available(\"tqdm\")\n_TRANSFORMERS_AVAILABLE: bool = package_available(\"transformers\")\n_TRANSFORMERS_GREATER_EQUAL_4_4: Optional[bool] = compare_version(\"transformers\", operator.ge, \"4.4.0\")\n_TRANSFORMERS_GREATER_EQUAL_4_10: Optional[bool] = compare_version(\"transformers\", operator.ge, \"4.10.0\")\n_PESQ_AVAILABLE: bool = package_available(\"pesq\")\n_GAMMATONE_AVAILABLE: bool = package_available(\"gammatone\")\n_TORCHAUDIO_AVAILABLE: bool = package_available(\"torchaudio\")\n_TORCHAUDIO_GREATER_EQUAL_0_10: Optional[bool] = compare_version(\"torchaudio\", operator.ge, \"0.10.0\")\n_SACREBLEU_AVAILABLE: bool = package_available(\"sacrebleu\")\n_REGEX_AVAILABLE: bool = package_available(\"regex\")\n_PYSTOI_AVAILABLE: bool = package_available(\"pystoi\")\n_FAST_BSS_EVAL_AVAILABLE: bool = package_available(\"fast_bss_eval\")\n_MATPLOTLIB_AVAILABLE: bool = package_available(\"matplotlib\")\n_SCIENCEPLOT_AVAILABLE: bool = package_available(\"scienceplots\")\n_MULTIPROCESSING_AVAILABLE: bool = package_available(\"multiprocessing\")\n_XLA_AVAILABLE: bool = package_available(\"torch_xla\")\n_PIQ_GREATER_EQUAL_0_8: Optional[bool] = compare_version(\"piq\", operator.ge, \"0.8.0\")\n_FASTER_COCO_EVAL_AVAILABLE: bool = package_available(\"faster_coco_eval\")\n\n_LATEX_AVAILABLE: bool = shutil.which(\"latex\") is not None\n", "path": "src/torchmetrics/utilities/imports.py"}, {"content": "# Copyright The Lightning team.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport glob\nimport json\nimport logging\nimport os\nimport re\nimport sys\nimport traceback\nfrom typing import List, Optional, Tuple, Union\n\nimport fire\nimport requests\nfrom packaging.version import parse\nfrom pkg_resources import parse_requirements\n\n_REQUEST_TIMEOUT = 10\n_PATH_ROOT = os.path.dirname(os.path.dirname(__file__))\n_PKG_WIDE_SUBPACKAGES = (\"utilities\", \"helpers\")\nLUT_PYTHON_TORCH = {\n    \"3.8\": \"1.4\",\n    \"3.9\": \"1.7.1\",\n    \"3.10\": \"1.11\",\n    \"3.11\": \"1.13\",\n}\n_path = lambda *ds: os.path.join(_PATH_ROOT, *ds)\nREQUIREMENTS_FILES = (*glob.glob(_path(\"requirements\", \"*.txt\")), _path(\"requirements.txt\"))\n\n\ndef request_url(url: str, auth_token: Optional[str] = None) -> Optional[dict]:\n    \"\"\"General request with checking if request limit was reached.\"\"\"\n    auth_header = {\"Authorization\": f\"token {auth_token}\"} if auth_token else {}\n    try:\n        req = requests.get(url, headers=auth_header, timeout=_REQUEST_TIMEOUT)\n    except requests.exceptions.Timeout:\n        traceback.print_exc()\n        return None\n    if req.status_code == 403:\n        return None\n    return json.loads(req.content.decode(req.encoding))\n\n\nclass AssistantCLI:\n    \"\"\"CLI assistant for local CI.\"\"\"\n\n    @staticmethod\n    def prune_packages(req_file: str, *pkgs: str) -> None:\n        \"\"\"Prune packages from requirement file.\"\"\"\n        with open(req_file) as fp:\n            lines = fp.readlines()\n\n        for pkg in pkgs:\n            lines = [ln for ln in lines if not ln.startswith(pkg)]\n        logging.info(lines)\n\n        with open(req_file, \"w\", encoding=\"utf-8\") as fp:\n            fp.writelines(lines)\n\n    @staticmethod\n    def set_min_torch_by_python(fpath: str = \"requirements/base.txt\") -> None:\n        \"\"\"Set minimal torch version according to Python actual version.\n\n        >>> AssistantCLI.set_min_torch_by_python(\"../requirements/base.txt\")\n\n        \"\"\"\n        py_ver = f\"{sys.version_info.major}.{sys.version_info.minor}\"\n        if py_ver not in LUT_PYTHON_TORCH:\n            return\n        with open(fpath) as fp:\n            reqs = parse_requirements(fp.readlines())\n        pkg_ver = next(p for p in reqs if p.name == \"torch\")\n        pt_ver = min([parse(v[1]) for v in pkg_ver.specs])\n        pt_ver = max(parse(LUT_PYTHON_TORCH[py_ver]), pt_ver)\n        with open(fpath) as fp:\n            requires = fp.read()\n        requires = re.sub(r\"torch>=[\\d\\.]+\", f\"torch>={pt_ver}\", requires)\n        with open(fpath, \"w\", encoding=\"utf-8\") as fp:\n            fp.write(requires)\n\n    @staticmethod\n    def replace_min_requirements(fpath: str) -> None:\n        \"\"\"Replace all `>=` by `==` in given file.\"\"\"\n        logging.info(f\"processing: {fpath}\")\n        with open(fpath, encoding=\"utf-8\") as fp:\n            req = fp.read()\n        req = req.replace(\">=\", \"==\")\n        with open(fpath, \"w\", encoding=\"utf-8\") as fp:\n            fp.write(req)\n\n    @staticmethod\n    def set_oldest_versions(req_files: List[str] = REQUIREMENTS_FILES) -> None:\n        \"\"\"Set the oldest version for requirements.\"\"\"\n        AssistantCLI.set_min_torch_by_python()\n        for fpath in req_files:\n            AssistantCLI.replace_min_requirements(fpath)\n\n    @staticmethod\n    def changed_domains(\n        pr: Optional[int] = None,\n        auth_token: Optional[str] = None,\n        as_list: bool = False,\n        general_sub_pkgs: Tuple[str] = _PKG_WIDE_SUBPACKAGES,\n    ) -> Union[str, List[str]]:\n        \"\"\"Determine what domains were changed in particular PR.\"\"\"\n        if not pr:\n            return \"unittests\"\n        url = f\"https://api.github.com/repos/Lightning-AI/torchmetrics/pulls/{pr}/files\"\n        logging.debug(url)\n        data = request_url(url, auth_token)\n        if not data:\n            logging.debug(\"WARNING: No data was received -> test everything.\")\n            return \"unittests\"\n        files = [d[\"filename\"] for d in data]\n\n        # filter out all integrations as they run in separate suit\n        files = [fn for fn in files if not fn.startswith(\"tests/integrations\")]\n        if not files:\n            logging.debug(\"Only integrations was changed so not reason for deep testing...\")\n            return \"\"\n        # filter only docs files\n        files_ = [fn for fn in files if fn.startswith(\"docs\")]\n        if len(files) == len(files_):\n            logging.debug(\"Only docs was changed so not reason for deep testing...\")\n            return \"\"\n\n        # filter only package files and skip inits\n        _is_in_test = lambda fn: fn.startswith(\"tests\")\n        _filter_pkg = lambda fn: _is_in_test(fn) or (fn.startswith(\"src/torchmetrics\") and \"__init__.py\" not in fn)\n        files_pkg = [fn for fn in files if _filter_pkg(fn)]\n        if not files_pkg:\n            return \"unittests\"\n\n        # parse domains\n        def _crop_path(fname: str, paths: List[str]) -> str:\n            for p in paths:\n                fname = fname.replace(p, \"\")\n            return fname\n\n        files_pkg = [_crop_path(fn, [\"src/torchmetrics/\", \"tests/unittests/\", \"functional/\"]) for fn in files_pkg]\n        # filter domain names\n        tm_modules = [fn.split(\"/\")[0] for fn in files_pkg if \"/\" in fn]\n        # filter general (used everywhere) sub-packages\n        tm_modules = [md for md in tm_modules if md not in general_sub_pkgs]\n        if len(files_pkg) > len(tm_modules):\n            logging.debug(\"Some more files was changed -> rather test everything...\")\n            return \"unittests\"\n        # keep only unique\n        if as_list:\n            return list(tm_modules)\n        tm_modules = [f\"unittests/{md}\" for md in set(tm_modules)]\n        not_exists = [p for p in tm_modules if os.path.exists(p)]\n        if not_exists:\n            raise ValueError(f\"Missing following paths: {not_exists}\")\n        return \" \".join(tm_modules)\n\n\nif __name__ == \"__main__\":\n    logging.basicConfig(level=logging.INFO)\n    fire.Fire(AssistantCLI)\n", "path": ".github/assistant.py"}]}