Repository: tox-dev/tox-conda

Issue:
Permission error on conda environment temp file when running in Windows OS
I am receiving the following error (in .tox/python/log/python-0.log) when running tox.
```python
# Error snippet
      File "C:\Users\xxx\Anaconda3\lib\site-packages\conda_env\env.py", line 163, in from_file
        with open(filename, 'rb') as fp:
    PermissionError: [Errno 13] Permission denied: 'C:\\Users\\yyy\\tox_conda_tmp13zv20n4.yaml'

`$ C:\Users\xxx\Anaconda3\Scripts\conda-env-script.py create -p python --file C:\Users\yyy\tox_conda_tmp13zv20n4.yaml`
```

### Steps to reproduce the error on Window OS (Windows 10).
#### tox.ini
```
[tox]
envlist = py310
skipsdist = True

[testenv]
conda_env =
    environment.yaml
```
#### environment.yaml 
```
name: tox_environment
dependencies:
  - python=3.10
  - pytest=7.1.2
  - tox=3.27.1
  - tox-conda=0.10.1
```
#### run the following commands on Windows OS
```bash
# create conda env
conda env create -f environment.yaml
conda activate tox_environment
# run tox
tox
```

### Suspected cause of error
I suspect that the error might be due to the use of tempfile.NamedTemporaryFile in  [plugins.py](https://github.com/tox-dev/tox-conda/blob/main/tox_conda/plugin.py#L171). As per [docs](https://docs.python.org/3/library/tempfile.html#tempfile.NamedTemporaryFile):

> That name can be retrieved from the name attribute of the returned file-like object. Whether the name can be used to open the file a second time, while the named temporary file is still open, varies across platforms (it can be so used on Unix; it cannot on Windows)

Any help is appreciated, thanks!

Comments:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 762d974..4592cf4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,7 +18,7 @@ repos:
         args:
           - --py3-plus
   - repo: https://github.com/PyCQA/isort
-    rev: 5.11.4
+    rev: 5.12.0
     hooks:
       - id: isort
   - repo: https://github.com/psf/black
diff --git a/tox.ini b/tox.ini
index ff58268..16db4ba 100644
--- a/tox.ini
+++ b/tox.ini
@@ -73,7 +73,7 @@ depends =
 
 [testenv:pkg_meta]
 description = check that the long description is valid
-basepython = python3.9
+basepython = python3.10
 skip_install = true
 deps =
     build>=0.0.4
diff --git a/tox_conda/plugin.py b/tox_conda/plugin.py
index d669718..f93631f 100644
--- a/tox_conda/plugin.py
+++ b/tox_conda/plugin.py
@@ -168,22 +168,27 @@ def tox_testenv_create(venv, action):
         env_file = yaml.load(env_path)
         env_file["dependencies"].append(python)
 
-        with tempfile.NamedTemporaryFile(
-            dir=env_path.parent, prefix="tox_conda_tmp", suffix=".yaml"
-        ) as tmp_env:
-            yaml.dump(env_file, tmp_env)
-
-            args = [
-                venv.envconfig.conda_exe,
-                "env",
-                "create",
-                "-p",
-                envdir,
-                "--file",
-                tmp_env.name,
-            ]
-
-            _run_conda_process(args, venv, action, basepath)
+        tmp_env = tempfile.NamedTemporaryFile(
+            dir=env_path.parent,
+            prefix="tox_conda_tmp",
+            suffix=".yaml",
+            delete=False,
+        )
+        yaml.dump(env_file, tmp_env)
+
+        args = [
+            venv.envconfig.conda_exe,
+            "env",
+            "create",
+            "-p",
+            envdir,
+            "--file",
+            tmp_env.name,
+        ]
+        tmp_env.close()
+        _run_conda_process(args, venv, action, basepath)
+        Path(tmp_env.name).unlink()
+
     else:
         args = [venv.envconfig.conda_exe, "create", "--yes", "-p", envdir]
         for channel in venv.envconfig.conda_channels:


Test patch:
diff --git a/tests/test_conda_env.py b/tests/test_conda_env.py
index 4435c15..1756846 100644
--- a/tests/test_conda_env.py
+++ b/tests/test_conda_env.py
@@ -1,5 +1,6 @@
 import io
 import os
+import pathlib
 import re
 from unittest.mock import mock_open, patch
 
@@ -278,10 +279,12 @@ def test_conda_env(tmpdir, newconfig, mocksession):
 
     mock_file = mock_open()
     with patch("tox_conda.plugin.tempfile.NamedTemporaryFile", mock_file):
-        with mocksession.newaction(venv.name, "getenv") as action:
-            tox_testenv_create(action=action, venv=venv)
+        with patch.object(pathlib.Path, "unlink", autospec=True) as mock_unlink:
+            with mocksession.newaction(venv.name, "getenv") as action:
+                tox_testenv_create(action=action, venv=venv)
+                mock_unlink.assert_called_once
 
-    mock_file.assert_called_with(dir=tmpdir, prefix="tox_conda_tmp", suffix=".yaml")
+    mock_file.assert_called_with(dir=tmpdir, prefix="tox_conda_tmp", suffix=".yaml", delete=False)
 
     pcalls = mocksession._pcalls
     assert len(pcalls) >= 1
@@ -335,10 +338,12 @@ def test_conda_env_and_spec(tmpdir, newconfig, mocksession):
 
     mock_file = mock_open()
     with patch("tox_conda.plugin.tempfile.NamedTemporaryFile", mock_file):
-        with mocksession.newaction(venv.name, "getenv") as action:
-            tox_testenv_create(action=action, venv=venv)
+        with patch.object(pathlib.Path, "unlink", autospec=True) as mock_unlink:
+            with mocksession.newaction(venv.name, "getenv") as action:
+                tox_testenv_create(action=action, venv=venv)
+                mock_unlink.assert_called_once
 
-    mock_file.assert_called_with(dir=tmpdir, prefix="tox_conda_tmp", suffix=".yaml")
+    mock_file.assert_called_with(dir=tmpdir, prefix="tox_conda_tmp", suffix=".yaml", delete=False)
 
     pcalls = mocksession._pcalls
     assert len(pcalls) >= 1