Repository: pypa/pipx (python)

Issue:
Version check for standalone python is wrong.
<!--
Thank you for filing a bug! Please feel free to answer as much or as little of this template as you can.

Please check pipx's Troubleshooting page to see if any of those solutions help solve your issue:
https://pipx.pypa.io/stable/troubleshooting/
-->

**Describe the bug**

<!-- Please be as detailed as possible! -->
When specify python version to `3.1`, python 3.12 will be downloaded

**How to reproduce**

<!-- If possible, include output of `pipx --verbose ...` -->
```shell
pipx install --python 3.1 --fetch-missing-python pycowsay
```

**Expected behavior**

<!-- What should have happened? -->
Raise an error that no valid Python found.


Pull request:
fix: version check for standalone python
<!-- add an 'x' in the brackets below -->

- [x] I have added a news fragment under `changelog.d/` (if the patch affects the end users)

## Summary of changes

Fix #1349 

BTW, the changes also include some code refactor for related part.

## Test plan

<!-- provide evidence of testing, preferably with command(s) that can be copy+pasted by others -->

Tested by running

```
# command(s) to exercise these changes
pipx install --python 3.1 --fetch-missing-python pycowsay
```


Patch:
diff --git a/changelog.d/1349.bugfix.md b/changelog.d/1349.bugfix.md
new file mode 100644
index 0000000..5327b96
--- /dev/null
+++ b/changelog.d/1349.bugfix.md
@@ -0,0 +1,1 @@
+fix version check for standalone python
diff --git a/src/pipx/standalone_python.py b/src/pipx/standalone_python.py
index 8d772f5..6883fe4 100644
--- a/src/pipx/standalone_python.py
+++ b/src/pipx/standalone_python.py
@@ -44,10 +44,6 @@ GITHUB_API_URL = "https://api.github.com/repos/indygreg/python-build-standalone/
 PYTHON_VERSION_REGEX = re.compile(r"cpython-(\d+\.\d+\.\d+)")
 
 
-class NotAvailable(Exception):
-    """Raised when the asked Python version is not available."""
-
-
 def download_python_build_standalone(python_version: str):
     """When all other python executable resolutions have failed,
     attempt to download and use an appropriate python build
@@ -68,10 +64,7 @@ def download_python_build_standalone(python_version: str):
         logger.warning(f"A previous attempt to install python {python_version} failed. Retrying.")
         shutil.rmtree(install_dir)
 
-    try:
-        full_version, download_link = resolve_python_version(python_version)
-    except NotAvailable as e:
-        raise PipxError(f"Unable to acquire a standalone python build matching {python_version}.") from e
+    full_version, download_link = resolve_python_version(python_version)
 
     with tempfile.TemporaryDirectory() as tempdir:
         archive = Path(tempdir) / f"python-{full_version}.tar.gz"
@@ -192,13 +185,11 @@ def list_pythons() -> Dict[str, str]:
 
 def resolve_python_version(requested_version: str):
     pythons = list_pythons()
+    requested_release = requested_version.split(".")
 
-    for version, version_download_link in pythons.items():
-        if version.startswith(requested_version):
-            python_version = version
-            download_link = version_download_link
-            break
-    else:
-        raise NotAvailable(f"Python version {requested_version} is not available.")
+    for full_version, download_link in pythons.items():
+        standalone_release = full_version.split(".")
+        if requested_release == standalone_release[: len(requested_release)]:
+            return full_version, download_link
 
-    return python_version, download_link
+    raise PipxError(f"Unable to acquire a standalone python build matching {requested_version}.")


Test patch:
diff --git a/tests/test_install.py b/tests/test_install.py
index 359b2c6..268c297 100644
--- a/tests/test_install.py
+++ b/tests/test_install.py
@@ -369,9 +369,11 @@ def test_passed_python_and_force_flag_warning(pipx_temp_env, capsys):
     ), "Should not print warning if package does not exist yet"
 
 
-def test_install_run_in_separate_directory(caplog, capsys, pipx_temp_env, monkeypatch, tmp_path):
-    monkeypatch.chdir(tmp_path)
-    f = Path("argparse.py")
-    f.touch()
-
-    install_packages(capsys, pipx_temp_env, caplog, ["pycowsay"], ["pycowsay"])
+@pytest.mark.parametrize(
+    "python_version",
+    ["3.0", "3.1"],
+)
+def test_install_fetch_missing_python_invalid(capsys, python_version):
+    assert run_pipx_cli(["install", "--python", python_version, "--fetch-missing-python", "pycowsay"])
+    captured = capsys.readouterr()
+    assert f"No executable for the provided Python version '{python_version}' found" in captured.out