Repository: jazzband/pip-tools (python)

Issue:
pip-sync broken on Windows
Calling pip-sync on Windows returns:
```
Could not open requirements file: [Errno 13] Permission denied: 'c:\\users\\<username>\\appdata\\local\\temp\\tmpe9jswo'
```

This was introduced in 3.3.1 by the change to using `tempfile.NamedTemporaryFile` in [sync.py](https://github.com/jazzband/pip-tools/blob/3.3.1/piptools/sync.py#L167) to create the requirements file before passing it to `pip install` via `subprocess.check_call`. This is caused by Windows requiring reopened temporary files to be opened with the `O_TEMPORARY`  flag. For context see https://stackoverflow.com/a/15235559. 

##### Environment Versions

1. OS Type: Windows 10
1. Python version: Python 2.7.12
1. pip version: pip 9.0.3
1. pip-tools version: pip-compile, version 3.3.1

##### Steps to replicate

1. `pip-sync -r <requirements_file>`

##### Expected result

Command should complete successfully.

##### Actual result

```
Could not open requirements file: [Errno 13] Permission denied: 'c:\\users\\<username>\\appdata\\local\\temp\\tmpe9jswo'
```


Pull request:
Fix sync with a temp file on Windows
Fixes #722

<!--- Describe the changes here. --->

**Changelog-friendly one-liner**: Fix sync with a temporary requirement file on Windows

##### Contributor checklist

- [X] Provided the tests for the changes
- [X] Requested (or received) a review from another contributor
- [X] Gave a clear one-line description in the PR (that the maintainers can add to CHANGELOG.md afterwards).


Patch:
diff --git a/piptools/sync.py b/piptools/sync.py
index 619ada0..614fa01 100644
--- a/piptools/sync.py
+++ b/piptools/sync.py
@@ -163,9 +163,13 @@ def sync(to_install, to_uninstall, verbose=False, dry_run=False, pip_flags=None,
                 req_lines.append(format_requirement(ireq, hashes=ireq_hashes))
 
             # save requirement lines to a temporary file
-            with tempfile.NamedTemporaryFile(mode='wt') as tmp_req_file:
-                tmp_req_file.write('\n'.join(req_lines))
-                tmp_req_file.flush()
+            tmp_req_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
+            tmp_req_file.write('\n'.join(req_lines))
+            tmp_req_file.close()
 
+            try:
                 check_call([pip, 'install', '-r', tmp_req_file.name] + pip_flags + install_flags)
+            finally:
+                os.unlink(tmp_req_file.name)
+
     return 0


Test patch:
diff --git a/tests/test_sync.py b/tests/test_sync.py
index 51d7efd..32e820b 100644
--- a/tests/test_sync.py
+++ b/tests/test_sync.py
@@ -12,10 +12,16 @@ from piptools.sync import dependency_tree, diff, merge, sync
 
 
 @pytest.fixture
-def mocked_tmp_req_file():
+def mocked_tmp_file():
     with mock.patch.object(tempfile, 'NamedTemporaryFile') as m:
-        m.return_value.__enter__.return_value.name = 'requirements.txt'
-        yield m.return_value.__enter__.return_value
+        yield m.return_value
+
+
+@pytest.fixture
+def mocked_tmp_req_file(mocked_tmp_file):
+    with mock.patch('os.unlink'):
+        mocked_tmp_file.name = 'requirements.txt'
+        yield mocked_tmp_file
 
 
 @pytest.mark.parametrize(
@@ -227,6 +233,16 @@ def test_sync_install_temporary_requirement_file(from_line, from_editable, mocke
         check_call.assert_called_once_with(['pip', 'install', '-r', mocked_tmp_req_file.name, '-q'])
 
 
+def test_temporary_requirement_file_deleted(from_line, from_editable, mocked_tmp_file):
+    with mock.patch('piptools.sync.check_call'):
+        to_install = {from_line('django==1.8')}
+
+        with mock.patch('os.unlink') as unlink:
+            sync(to_install, set())
+
+            unlink.assert_called_once_with(mocked_tmp_file.name)
+
+
 def test_sync_requirement_file(from_line, from_editable, mocked_tmp_req_file):
     with mock.patch('piptools.sync.check_call'):
         to_install = {