Repository: pypa/pipx (python)

Issue:
`pipx upgrade -e ...` does not parse correctly
<!--
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**

I can't seem to figure out a way to upgrade a package that I installed locally with `pipx install --editable /path/to/my_package`. It seems like  each way results in an invalid `pip install --upgrade`. Possibly related to https://github.com/pypa/pipx/issues/1441


<!-- Please be as detailed as possible! -->

**How to reproduce**

Install `my_package` in the current directory `pipx install --editable `

Perhaps the package requirements have changed, so we want to update `my_package`. First we attempt to upgrade using a relative path

```
(.venv) richard@Richards-MBP % pipx upgrade -e -v .
pipx >(setup:1110): pipx version is 1.7.1
pipx >(setup:1111): Default python interpreter is '/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12'
pipx >(run_pipx_command:241): Virtual Environment locations are:
- . : /Users/richard/.local/pipx/venvs/-
Package is not installed. Expected to find /Users/richard/.local/pipx/venvs/-, but it does not exist.
```

It seems like the name of the package was not correctly determined. We can try an absolute path

```
(.venv) richard@Richards-MBP % pipx upgrade -e -v $PWD
pipx >(setup:1110): pipx version is 1.7.1
pipx >(setup:1111): Default python interpreter is '/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12'
Error: '/Users/richard/my_package' looks like a path. Expected the name of an installed package.
```

An extra -v flag indicates that `pipx` is explicitly raising this error. If we use the name of the package as suggested

```
(.venv) richard@Richards-MBP my_package % pipx upgrade -v -e my_package
pipx >(setup:1110): pipx version is 1.7.1
pipx >(setup:1111): Default python interpreter is '/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12'
pipx >(run_pipx_command:241): Virtual Environment locations are:
- my_package : /Users/richard/.local/pipx/venvs/my_package
pipx >(_upgrade_venv:154): Ignoring --python as not combined with --install
pipx >(run_subprocess:175): running <checking pip's availability>
pipx >(needs_upgrade:99): Time since last upgrade of shared libs, in seconds: 8. Upgrade will be run by pipx if greater than 2592000.
pipx >(run_subprocess:175): running <checking pip's availability>
pipx >(run_subprocess:175): running <checking pip's availability>
pipx >(upgrade:119): Upgrading shared libraries in /Users/richard/.local/pipx/shared
upgrading shared libraries...
pipx >(run_subprocess:175): running /Users/richard/.local/pipx/shared/bin/python -m pip --no-input --disable-pip-version-check install --upgrade pip >= 23.1
pipx >(_parsed_package_to_package_or_url:139): cleaned package spec: /Users/richard/my_package
pipx >(upgrade_package:457): Upgrading package from spec '/Users/richard/my_package'
upgrading package from spec '/Users/richard/my_package'...
pipx >(run_subprocess:175): running /Users/richard/.local/pipx/venvs/my_package/bin/python -m pip --no-input install --editable --upgrade /Users/richard/my_package
ERROR: --upgrade is not a valid editable requirement. It should either be a path to a local project or a VCS URL (beginning with bzr+http, bzr+https, bzr+ssh, bzr+sftp, bzr+ftp, bzr+lp, bzr+file, git+http, git+https, git+ssh, git+git, git+file, hg+file, hg+http, hg+https, hg+ssh, hg+static-http, svn+ssh, svn+http, svn+https, svn+svn, svn+file).
'/Users/richard/.local/pipx/venvs/my_package/bin/python -m pip --no-input install --editable --upgrade
/Users/richard/my_package' failed
```

Here the error comes from pip, since the syntax of the pip install is incorrect (`--upgrade` should come before `--editable`)


<!-- If possible, include output of `pipx --verbose ...` -->

**Expected behavior**

In all cases, I would expect the upgrade to be successful. In the first case, I'd expect pipx to find
/Users/richard/.local/pipx/venvs/my_package. In the second, I'd expect a path to be an acceptable argument in this
context. In the third, I'd expect pipx to generate a syntactically correct pip command.

<!-- What should have happened? -->


Pull request:
fix #1610: Reorder upgrade option and arguments
<!-- 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 #1610 

Changed order of `--upgrade` option and `pip_args` to match order required by pip when upgrading package and maintaining editable mode.

## Test plan
Implemented a unit test to confirm that pip command executes correctly.

Can be manually tested by running:
```
pipx upgrade --editable my_package
```


Patch:
diff --git a/changelog.d/1610.bugfix.md b/changelog.d/1610.bugfix.md
new file mode 100644
index 0000000..79d5fc1
--- /dev/null
+++ b/changelog.d/1610.bugfix.md
@@ -0,0 +1,1 @@
+Fix incorrect order of flags when using `pipx upgrade`.
diff --git a/src/pipx/venv.py b/src/pipx/venv.py
index 123eea6..be761d5 100644
--- a/src/pipx/venv.py
+++ b/src/pipx/venv.py
@@ -441,7 +441,7 @@ class Venv:
     def upgrade_package_no_metadata(self, package_name: str, pip_args: List[str]) -> None:
         logger.info("Upgrading %s", package_descr := full_package_description(package_name, package_name))
         with animate(f"upgrading {package_descr}", self.do_animation):
-            pip_process = self._run_pip(["--no-input", "install"] + pip_args + ["--upgrade", package_name])
+            pip_process = self._run_pip(["--no-input", "install", "--upgrade"] + pip_args + [package_name])
         subprocess_post_check(pip_process)
 
     def upgrade_package(
@@ -456,7 +456,7 @@ class Venv:
     ) -> None:
         logger.info("Upgrading %s", package_descr := full_package_description(package_name, package_or_url))
         with animate(f"upgrading {package_descr}", self.do_animation):
-            pip_process = self._run_pip(["--no-input", "install"] + pip_args + ["--upgrade", package_or_url])
+            pip_process = self._run_pip(["--no-input", "install", "--upgrade"] + pip_args + [package_or_url])
         subprocess_post_check(pip_process)
 
         self.update_package_metadata(


Test patch:
diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py
index b95bc68..a991909 100644
--- a/tests/test_upgrade.py
+++ b/tests/test_upgrade.py
@@ -78,6 +78,14 @@ def test_upgrade_specifier(pipx_temp_env, capsys):
     assert f"upgraded package {name} from {initial_version} to" in captured.out
 
 
+def test_upgrade_editable(pipx_temp_env, capsys, root):
+    empty_project_path_as_string = (root / "testdata" / "empty_project").as_posix()
+    assert not run_pipx_cli(["install", "--editable", empty_project_path_as_string, "--force"])
+    assert not run_pipx_cli(["upgrade", "--editable", "empty_project"])
+    captured = capsys.readouterr()
+    assert "empty-project is already at latest version" in captured.out
+
+
 def test_upgrade_include_injected(pipx_temp_env, capsys):
     assert not run_pipx_cli(["install", PKG["pylint"]["spec"]])
     assert not run_pipx_cli(["inject", "pylint", PKG["black"]["spec"]])