Repository: dropbox/pyannotate

Issue:
Permission denied for Temp file on Windows
When I run "pyannotate --type-info ./annotate.json ." with on Windows (both 2.7.14 and 3.6.3, probably others) , I get the following error:
Traceback (most recent call last):
  File "c:\python27\lib\runpy.py", line 174, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "c:\python27\lib\runpy.py", line 72, in _run_code
    exec code in run_globals
  File "C:\Python27\Scripts\pyannotate.exe\__main__.py", line 9, in <module>
  File "c:\python27\lib\site-packages\pyannotate_tools\annotations\__main__.py", line 45, in main
    generate_annotations_json(infile, tf.name)
  File "c:\python27\lib\site-packages\pyannotate_tools\annotations\main.py", line 58, in generate_annotations_json
    with open(target_path, 'w') as f:
IOError: [Errno 13] Permission denied: 'c:\\temp\\tmp2ui1ku'

A little bit of googling suggests this might be the problem [Permission Denied To Write To My Temporary File](https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file)

Discussion:
NeonGraal: I've signed the CLA now too

Patch:
diff --git a/pyannotate_tools/annotations/__main__.py b/pyannotate_tools/annotations/__main__.py
index 709fafa..f1c8190 100644
--- a/pyannotate_tools/annotations/__main__.py
+++ b/pyannotate_tools/annotations/__main__.py
@@ -42,7 +42,7 @@ def main():
 
     # Run pass 2 with output written to a temporary file.
     infile = args.type_info
-    generate_annotations_json(infile, tf.name)
+    generate_annotations_json(infile, tf.name, target_stream=tf)
 
     # Run pass 3 reading from a temporary file.
     FixAnnotateJson.stub_json_file = tf.name
diff --git a/pyannotate_tools/annotations/main.py b/pyannotate_tools/annotations/main.py
index 123fa0c..aea22c2 100644
--- a/pyannotate_tools/annotations/main.py
+++ b/pyannotate_tools/annotations/main.py
@@ -2,7 +2,7 @@
 
 import json
 
-from typing import List
+from typing import List, Optional, IO
 from mypy_extensions import TypedDict
 
 from pyannotate_tools.annotations.types import ARG_STAR, ARG_STARSTAR
@@ -22,8 +22,8 @@ FunctionData = TypedDict('FunctionData', {'path': str,
                                           'samples': int})
 
 
-def generate_annotations_json(source_path, target_path):
-    # type: (str, str) -> None
+def generate_annotations_json(source_path, target_path, source_stream=None, target_stream=None):
+    # type: (str, str, Optional[IO[str]], Optional[IO[str]]) -> None
     """Produce annotation data JSON file from a JSON file with runtime-collected types.
 
     Data formats:
@@ -31,7 +31,7 @@ def generate_annotations_json(source_path, target_path):
     * The source JSON is a list of pyannotate_tools.annotations.parse.RawEntry items.
     * The output JSON is a list of FunctionData items.
     """
-    items = parse_json(source_path)
+    items = parse_json(source_path, source_stream)
     results = []
     for item in items:
         arg_types, return_type = infer_annotation(item.type_comments)
@@ -55,5 +55,8 @@ def generate_annotations_json(source_path, target_path):
             'samples': item.samples
         }  # type: FunctionData
         results.append(data)
-    with open(target_path, 'w') as f:
-        json.dump(results, f, sort_keys=True, indent=4)
+    if target_stream:
+        json.dump(results, target_stream, sort_keys=True, indent=4)
+    else:
+        with open(target_path, 'w') as f:
+            json.dump(results, f, sort_keys=True, indent=4)
diff --git a/pyannotate_tools/annotations/parse.py b/pyannotate_tools/annotations/parse.py
index 28a7851..bc9d3c1 100644
--- a/pyannotate_tools/annotations/parse.py
+++ b/pyannotate_tools/annotations/parse.py
@@ -9,7 +9,7 @@ import json
 import re
 import sys
 
-from typing import Any, List, Mapping, Set, Tuple
+from typing import Any, List, Mapping, Set, Tuple, Optional, IO
 from typing_extensions import Text
 from mypy_extensions import NoReturn, TypedDict
 
@@ -86,14 +86,17 @@ class ParseError(Exception):
         self.comment = comment
 
 
-def parse_json(path):
-    # type: (str) -> List[FunctionInfo]
+def parse_json(path, stream=None):
+    # type: (str, Optional[IO[str]]) -> List[FunctionInfo]
     """Deserialize a JSON file containing runtime collected types.
 
     The input JSON is expected to to have a list of RawEntry items.
     """
-    with open(path) as f:
-        data = json.load(f)  # type: List[RawEntry]
+    if stream:
+        data = json.load(stream)  # type: List[RawEntry]
+    else:
+        with open(path) as f:
+            data = json.load(f)
     result = []
 
     def assert_type(value, typ):


Test patch:
diff --git a/pyannotate_tools/annotations/tests/main_test.py b/pyannotate_tools/annotations/tests/main_test.py
index 9609a7d..6c27a20 100644
--- a/pyannotate_tools/annotations/tests/main_test.py
+++ b/pyannotate_tools/annotations/tests/main_test.py
@@ -3,7 +3,7 @@ import tempfile
 import textwrap
 import unittest
 
-from typing import Iterator
+from typing import Iterator, Tuple, IO
 
 from pyannotate_tools.annotations.infer import InferError
 from pyannotate_tools.annotations.main import generate_annotations_json
@@ -25,10 +25,12 @@ class TestMain(unittest.TestCase):
             }
         ]
         """
-        target = tempfile.NamedTemporaryFile(mode='r')
-        with self.temporary_json_file(data) as source_path:
-            generate_annotations_json(source_path, target.name)
+        target = tempfile.NamedTemporaryFile(mode='w+')
+        with self.temporary_json_file(data) as source:
+            generate_annotations_json(source.name, target.name, source_stream=source, target_stream=target)
 
+        target.flush()
+        target.seek(0)
         actual = target.read()
         actual = actual.replace(' \n', '\n')
         expected = textwrap.dedent("""\
@@ -66,8 +68,8 @@ class TestMain(unittest.TestCase):
         ]
         """
         with self.assertRaises(InferError) as e:
-            with self.temporary_json_file(data) as source_path:
-                generate_annotations_json(source_path, '/dummy')
+            with self.temporary_json_file(data) as source:
+                generate_annotations_json(source.name, '/dummy', source_stream=source)
         assert str(e.exception) == textwrap.dedent("""\
             Ambiguous argument kinds:
             (List[int], str) -> None
@@ -75,8 +77,9 @@ class TestMain(unittest.TestCase):
 
     @contextlib.contextmanager
     def temporary_json_file(self, data):
-        # type: (str) -> Iterator[str]
-        with tempfile.NamedTemporaryFile(mode='w') as source:
+        # type: (str) -> Iterator[IO[str]]
+        with tempfile.NamedTemporaryFile(mode='w+') as source:
             source.write(data)
             source.flush()
-            yield source.name
+            source.seek(0)
+            yield source
diff --git a/pyannotate_tools/annotations/tests/parse_test.py b/pyannotate_tools/annotations/tests/parse_test.py
index c0daeb2..78650fb 100644
--- a/pyannotate_tools/annotations/tests/parse_test.py
+++ b/pyannotate_tools/annotations/tests/parse_test.py
@@ -40,11 +40,12 @@ class TestParseJson(unittest.TestCase):
             }
         ]
         """
-        with tempfile.NamedTemporaryFile(mode='w') as f:
+        with tempfile.NamedTemporaryFile(mode='w+') as f:
             f.write(data)
             f.flush()
+            f.seek(0)
 
-            result = parse_json(f.name)
+            result = parse_json(f.name, f)
         assert len(result) == 1
         item = result[0]
         assert item.path == 'pkg/thing.py'