Repository: napari/napari

Issue:
[4.16rc1] Not able to uninstall plugins in Windows
## 🐛 Bug

When clicking on <Uninstall> button, "uninstall" legend shows up next to plugin name but plugin is not being uninstalled successfully

## To Reproduce

Steps to reproduce the behavior:

1. Open napari 4.16rc1 in Windows
2. Click on Plugins -> Install/Uninstall Plugins menu
3. In opened modal, click on <Uninstall> button

## Expected behavior

Plugin should show up now in "Available Plugins" section and no longer be listed in Plugins menu

## Environment

napari: 0.4.16rc1
Platform: Windows-10-10.0.17763-SP0
Python: 3.8.13 | packaged by conda-forge | (default, Mar 25 2022, 05:59:45) [MSC v.1929 64 bit (AMD64)]
Qt: 5.12.9
PySide2: 5.13.2
NumPy: 1.22.3
SciPy: 1.8.0
Dask: 2022.05.0
VisPy: 0.9.6

OpenGL:
- GL version: 4.5.0 NVIDIA 370.41
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1691x989, scale 1.0

Plugins:
- affinder: 0.2.3
- bbii-decon: 0.0.1
- console: 0.0.4
- scikit-image: 0.4.16rc1
- svg: 0.1.5

https://user-images.githubusercontent.com/63799148/167011044-ee62684c-7bbf-4e36-9028-27362b8dc8f8.mov





Discussion:
does this happen for all plugins?  or is bbii-decon the only one you've found so far?

does the status (bottom left) show any errors?
@tlambert03 Yes, it happens with all plugins and no, no error on bottom left but i get the following error:

`FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Windows\\gen_py\\3.8\\__init__.py'`
that's certainly intriguing... is there any more to that trace?
@tlambert03 this is the whole error:

```
Traceback (most recent call last):
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\Scripts\mamba-script.py", line 6, in <module>
    from mamba.mamba import main
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\mamba\mamba.py", line 53, in <module>
    from mamba.mamba_shell_init import shell_init
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\mamba\mamba_shell_init.py", line 8, in <module>
    from conda.core.initialize import initialize, initialize_dev, make_initialize_plan
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\conda\core\initialize.py", line 75, in <module>
    from menuinst._legacy.knownfolders import get_folder_path, FOLDERID
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\menuinst\__init__.py", line 4, in <module>
    from .api import *
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\menuinst\api.py", line 10, in <module>
    from .platforms import Menu, MenuItem
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\menuinst\platforms\__init__.py", line 20, in <module>
    Menu, MenuItem = menu_api_for_platform()
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\menuinst\platforms\__init__.py", line 6, in menu_api_for_platform
    from .win import WindowsMenu as Menu, WindowsMenuItem as MenuItem
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\menuinst\platforms\win.py", line 10, in <module>
    from win32com.client import Dispatch
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\win32com\client\__init__.py", line 11, in <module>
    from . import gencache
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\win32com\client\gencache.py", line 761, in <module>
    __init__()
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\win32com\client\gencache.py", line 62, in __init__
    Rebuild()
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\win32com\client\gencache.py", line 746, in Rebuild
    _SaveDicts()
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\win32com\client\gencache.py", line 73, in _SaveDicts
    f = open(os.path.join(GetGeneratePath(), "dicts.dat"), "wb")
  File "D:\Users\lmarcos\AppData\Local\napari-app-0.1\envs\napari-0.4.16rc1\lib\site-packages\win32com\client\gencache.py", line 153, in GetGeneratePath
    f = open(fname, "w")
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Windows\\gen_py\\3.8\\__init__.py'
```
Will take a look @lauramarcos, thanks for the report!
This is the same issue reported in #4247, which means that #4462 was not the only fix we needed. I guess there's a separate method for uninstalls I didn't see, because _installing_ plugins does work, right?
That said, I can only see one method that creates the mamba process... and that one is patched already. Maybe we should pass `--no-shortcuts` to the plugin install commands anyway. I'll see if a PR with those changes fixes it!

Resolving patch:
diff --git a/napari/_qt/dialogs/qt_plugin_dialog.py b/napari/_qt/dialogs/qt_plugin_dialog.py
--- a/napari/_qt/dialogs/qt_plugin_dialog.py
+++ b/napari/_qt/dialogs/qt_plugin_dialog.py
@@ -89,36 +89,54 @@ def _create_process(
         installer: InstallerTypes = "pip",
     ):
         process = QProcess()
-        if installer != "pip":
-            process.setProgram(installer)
-        else:
-            process.setProgram(self._sys_executable_or_bundled_python())
-
         process.setProcessChannelMode(QProcess.MergedChannels)
         process.readyReadStandardOutput.connect(
             lambda process=process: self._on_stdout_ready(process)
         )
+        env = QProcessEnvironment.systemEnvironment()
 
-        # setup process path
-        env = QProcessEnvironment()
-        combined_paths = os.pathsep.join(
-            [user_site_packages(), env.systemEnvironment().value("PYTHONPATH")]
-        )
-        env.insert("PYTHONPATH", combined_paths)
-        # use path of parent process
-        env.insert(
-            "PATH", QProcessEnvironment.systemEnvironment().value("PATH")
-        )
+        if installer == "pip":
+            process.setProgram(self._sys_executable_or_bundled_python())
+            # patch process path
+            combined_paths = os.pathsep.join(
+                [
+                    user_site_packages(),
+                    env.systemEnvironment().value("PYTHONPATH"),
+                ]
+            )
+            env.insert("PYTHONPATH", combined_paths)
+        else:
+            process.setProgram(installer)
 
-        # workaround https://github.com/napari/napari/issues/4247
-        if (
-            installer == "mamba"
-            and os.name == "nt"
-            and not QProcessEnvironment.systemEnvironment().contains("TEMP")
-        ):
-            temp = gettempdir()
-            env.insert("TMP", temp)
-            env.insert("TEMP", temp)
+        if installer == "mamba":
+            from ..._version import version_tuple
+
+            # To avoid napari version changing when installing a plugin, we
+            # add a pin to the current napari version, that way we can
+            # restrict any changes to the actual napari application.
+            # Conda/mamba also pin python by default, so we effectively
+            # constrain python and napari versions from changing, when
+            # installing plugins inside the constructor bundled application.
+            # See: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-pkgs.html#preventing-packages-from-updating-pinning
+            napari_version = ".".join(str(v) for v in version_tuple[:3])
+            if env.contains("CONDA_PINNED_PACKAGES"):
+                # str delimiter is '&'
+                system_pins = f"&{env.value('CONDA_PINNED_PACKAGES')}"
+            else:
+                system_pins = ""
+            env.insert(
+                "CONDA_PINNED_PACKAGES",
+                f"napari={napari_version}{system_pins}",
+            )
+            if os.name == "nt":
+                # workaround https://github.com/napari/napari/issues/4247, 4484
+                if not env.contains("TEMP"):
+                    temp = gettempdir()
+                    env.insert("TMP", temp)
+                    env.insert("TEMP", temp)
+                if not env.contains("USERPROFILE"):
+                    env.insert("HOME", os.path.expanduser("~"))
+                    env.insert("USERPROFILE", os.path.expanduser("~"))
 
         process.setProcessEnvironment(env)
         self.set_output_widget(self._output_widget)
@@ -199,21 +217,8 @@ def _install(
         channels: Sequence[str] = ("conda-forge",),
     ):
         installer = installer or self._installer_type
-        process_environment = QProcessEnvironment.systemEnvironment()
+        process = self._create_process(installer)
         if installer != "pip":
-            from ..._version import version_tuple
-
-            # To avoid napari version changing when installing a plugin, we
-            # add a pin to the current napari version, that way we can
-            # restrict any changes to the actual napari application.
-            # Conda/mamba also pin python by default, so we effectively
-            # constrain python and napari versions from changing, when
-            # installing plugins inside the constructor bundled application.
-            # See: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-pkgs.html#preventing-packages-from-updating-pinning
-            napari_version = ".".join(str(v) for v in version_tuple[:3])
-            process_environment.insert(
-                "CONDA_PINNED_PACKAGES", f"napari={napari_version}"
-            )
             cmd = [
                 'install',
                 '-y',
@@ -236,8 +241,6 @@ def _install(
                 user_plugin_dir(),
             ]
 
-        process = self._create_process(installer)
-        process.setProcessEnvironment(process_environment)
         process.setArguments(cmd + list(pkg_list))
         if self._output_widget and self._queue:
             self._output_widget.clear()


Test patch: