Repository: pypa/pipx (python)

Issue:
`pipx install --force` without `--python` complains about the ignorance of `--python`
<!--
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**

When run `pipx install --force` without `--python`, `pipx` still complains about the ignorance of `--python`

<!-- Please be as detailed as possible! -->

**How to reproduce**

```shell
> pipx install pycowsay
> pipx install pycowsay --force
--python is ignored when --force is passed. If you want to reinstall pycowsay with <path/to/default/python>, run `pipx reinstall pycowsay
--python <path/to/default/python>` instead.
```

<!-- If possible, include output of `pipx --verbose ...` -->

**Expected behavior**

No `--python` ignorance warning.

<!-- What should have happened? -->

BTW, seems the bug is introduced by the default value of `--python` [1]. And there are at least two approaches to resolve it. One is to remove the default value and check whether there is any side effects. The other way is to change the condition when warning [2]. Please let me know if there is any preference. 

[1] https://github.com/pypa/pipx/blob/ac70dc0d2221bc6a78e39bef86f43b5c73ef03d7/src/pipx/main.py#L376
[2] https://github.com/pypa/pipx/blob/ac70dc0d2221bc6a78e39bef86f43b5c73ef03d7/src/pipx/commands/install.py#L60

Pull request:
bug: Removed --python flag dependency when --force is passed
<!-- add an 'x' in the brackets below -->
Fixes #1304

- [ ] I have added a news fragment under `changelog.d/` (if the patch affects the end users)

## Summary of changes

## 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
```


Patch:
diff --git a/changelog.d/1304.bugfix.md b/changelog.d/1304.bugfix.md
new file mode 100644
index 0000000..cd613cf
--- /dev/null
+++ b/changelog.d/1304.bugfix.md
@@ -0,0 +1,1 @@
+Only show `--python` and `--force` flag warning if both flags are present
diff --git a/src/pipx/commands/install.py b/src/pipx/commands/install.py
index 53fe038..7f4686d 100644
--- a/src/pipx/commands/install.py
+++ b/src/pipx/commands/install.py
@@ -29,11 +29,11 @@ def install(
     include_dependencies: bool,
     preinstall_packages: Optional[List[str]],
     suffix: str = "",
+    python_flag_passed=False,
 ) -> ExitCode:
     """Returns pipx exit code."""
     # package_spec is anything pip-installable, including package_name, vcs spec,
     #   zip file, or tar.gz file.
-    python_flag_was_passed = python is not None
 
     python = python or DEFAULT_PYTHON
 
@@ -57,7 +57,7 @@ def install(
         venv = Venv(venv_dir, python=python, verbose=verbose)
         venv.check_upgrade_shared_libs(pip_args=pip_args, verbose=verbose)
         if exists:
-            if not reinstall and force and python_flag_was_passed:
+            if not reinstall and force and python_flag_passed:
                 print(
                     pipx_wrap(
                         f"""
diff --git a/src/pipx/commands/reinstall.py b/src/pipx/commands/reinstall.py
index 9330fbe..2bd1a2a 100644
--- a/src/pipx/commands/reinstall.py
+++ b/src/pipx/commands/reinstall.py
@@ -26,6 +26,7 @@ def reinstall(
     python: str,
     verbose: bool,
     force_reinstall_shared_libs: bool = False,
+    python_flag_passed: bool = False,
 ) -> ExitCode:
     """Returns pipx exit code."""
     if not venv_dir.exists():
@@ -74,6 +75,7 @@ def reinstall(
         include_dependencies=venv.pipx_metadata.main_package.include_dependencies,
         preinstall_packages=[],
         suffix=venv.pipx_metadata.main_package.suffix,
+        python_flag_passed=python_flag_passed,
     )
 
     # now install injected packages
@@ -105,6 +107,7 @@ def reinstall_all(
     verbose: bool,
     *,
     skip: Sequence[str],
+    python_flag_passed: bool = False,
 ) -> ExitCode:
     """Returns pipx exit code."""
     failed: List[str] = []
@@ -124,6 +127,7 @@ def reinstall_all(
                 python=python,
                 verbose=verbose,
                 force_reinstall_shared_libs=first_reinstall,
+                python_flag_passed=python_flag_passed,
             )
         except PipxError as e:
             print(e, file=sys.stderr)
diff --git a/src/pipx/commands/upgrade.py b/src/pipx/commands/upgrade.py
index 0e33ad7..d7f6caf 100644
--- a/src/pipx/commands/upgrade.py
+++ b/src/pipx/commands/upgrade.py
@@ -104,6 +104,7 @@ def _upgrade_venv(
     force: bool,
     install: bool = False,
     python: Optional[str] = None,
+    python_flag_passed: bool = False,
 ) -> int:
     """Return number of packages with changed versions."""
     if not venv_dir.is_dir():
@@ -122,6 +123,7 @@ def _upgrade_venv(
                 reinstall=False,
                 include_dependencies=False,
                 preinstall_packages=None,
+                python_flag_passed=python_flag_passed,
             )
             return 0
         else:
@@ -186,6 +188,7 @@ def upgrade(
     include_injected: bool,
     force: bool,
     install: bool,
+    python_flag_passed: bool = False,
 ) -> ExitCode:
     """Return pipx exit code."""
 
@@ -198,6 +201,7 @@ def upgrade(
         force=force,
         install=install,
         python=python,
+        python_flag_passed=python_flag_passed,
     )
 
     # Any error in upgrade will raise PipxError (e.g. from venv.upgrade_package())
@@ -212,6 +216,7 @@ def upgrade_all(
     include_injected: bool,
     skip: Sequence[str],
     force: bool,
+    python_flag_passed: bool = False,
 ) -> ExitCode:
     """Returns pipx exit code."""
     venv_error = False
@@ -229,6 +234,7 @@ def upgrade_all(
                 include_injected=include_injected,
                 upgrading_all=True,
                 force=force,
+                python_flag_passed=python_flag_passed,
             )
 
         except PipxError as e:
diff --git a/src/pipx/main.py b/src/pipx/main.py
index a20c059..2279637 100644
--- a/src/pipx/main.py
+++ b/src/pipx/main.py
@@ -198,10 +198,15 @@ def run_pipx_command(args: argparse.Namespace, subparsers: Dict[str, argparse.Ar
     if "skip" in args:
         skip_list = [canonicalize_name(x) for x in args.skip]
 
-    if "python" in args and args.python is not None:
+    python_flag_passed = False
+
+    if "python" in args:
+        python_flag_passed = bool(args.python)
         fetch_missing_python = args.fetch_missing_python
         try:
-            interpreter = find_python_interpreter(args.python, fetch_missing_python=fetch_missing_python)
+            interpreter = find_python_interpreter(
+                args.python or DEFAULT_PYTHON, fetch_missing_python=fetch_missing_python
+            )
             args.python = interpreter
         except InterpreterResolutionError as e:
             logger.debug("Failed to resolve interpreter:", exc_info=True)
@@ -244,6 +249,7 @@ def run_pipx_command(args: argparse.Namespace, subparsers: Dict[str, argparse.Ar
             include_dependencies=args.include_deps,
             preinstall_packages=args.preinstall,
             suffix=args.suffix,
+            python_flag_passed=python_flag_passed,
         )
     elif args.command == "inject":
         return commands.inject(
@@ -275,6 +281,7 @@ def run_pipx_command(args: argparse.Namespace, subparsers: Dict[str, argparse.Ar
             include_injected=args.include_injected,
             force=args.force,
             install=args.install,
+            python_flag_passed=python_flag_passed,
         )
     elif args.command == "upgrade-all":
         return commands.upgrade_all(
@@ -284,6 +291,7 @@ def run_pipx_command(args: argparse.Namespace, subparsers: Dict[str, argparse.Ar
             skip=skip_list,
             force=args.force,
             pip_args=pip_args,
+            python_flag_passed=python_flag_passed,
         )
     elif args.command == "list":
         return commands.list_packages(
@@ -318,6 +326,7 @@ def run_pipx_command(args: argparse.Namespace, subparsers: Dict[str, argparse.Ar
             local_man_dir=paths.ctx.man_dir,
             python=args.python,
             verbose=verbose,
+            python_flag_passed=python_flag_passed,
         )
     elif args.command == "reinstall-all":
         return commands.reinstall_all(
@@ -327,6 +336,7 @@ def run_pipx_command(args: argparse.Namespace, subparsers: Dict[str, argparse.Ar
             args.python,
             verbose,
             skip=skip_list,
+            python_flag_passed=python_flag_passed,
         )
     elif args.command == "runpip":
         if not venv_dir:
@@ -373,7 +383,6 @@ def add_include_dependencies(parser: argparse.ArgumentParser) -> None:
 def add_python_options(parser: argparse.ArgumentParser) -> None:
     parser.add_argument(
         "--python",
-        default=DEFAULT_PYTHON,
         help=(
             "Python to install with. Possible values can be the executable name (python3.11), "
             "the version to pass to py launcher (3.11), or the full path to the executable."


Test patch:
diff --git a/tests/test_install.py b/tests/test_install.py
index f69e999..29a13a8 100644
--- a/tests/test_install.py
+++ b/tests/test_install.py
@@ -350,9 +350,17 @@ def test_passed_python_and_force_flag_warning(pipx_temp_env, capsys):
     captured = capsys.readouterr()
     assert "--python is ignored when --force is passed." in captured.out
 
+    assert not run_pipx_cli(["install", "black", "--force"])
+    captured = capsys.readouterr()
+    assert (
+        "--python is ignored when --force is passed." not in captured.out
+    ), "Should only print warning if both flags present"
+
     assert not run_pipx_cli(["install", "pycowsay", "--force"])
     captured = capsys.readouterr()
-    assert "--python is ignored when --force is passed." not in captured.out
+    assert (
+        "--python is ignored when --force is passed." not in captured.out
+    ), "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):