Repository: Tinche/aiofiles

Issue:
TemporaryDirectory is missing keyword arguments from upstream
The [synchronous equivalent from the standard library](https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryDirectory) accepts a few keyword arguments to control the name and location of the created directory. However, `aiofiles.tempfile.TemporaryDirectory` does not accept the same keyword arguments.

```
...
    async with aiofiles.tempfile.TemporaryDirectory(dir=working_dir) as temp_dir_str:
TypeError: TemporaryDirectory() got an unexpected keyword argument 'dir'
```

Tested using aiofiles version 0.7.0.

Comments:

Patch:
diff --git a/README.rst b/README.rst
index 067ddbc..cb6383e 100644
--- a/README.rst
+++ b/README.rst
@@ -173,6 +173,7 @@ History
 * Added ``aiofiles.os.{makedirs, removedirs}``.
 * Added ``aiofiles.os.path.{exists, isfile, isdir, getsize, getatime, getctime, samefile, sameopenfile}``.
   `#63 <https://github.com/Tinche/aiofiles/pull/63>`_
+* Added `suffix`, `prefix`, `dir` args to ``aiofiles.tempfile.TemporaryDirectory``
 
 0.7.0 (2021-05-17)
 ``````````````````
diff --git a/src/aiofiles/base.py b/src/aiofiles/base.py
index 6e3e80d..f64d00d 100644
--- a/src/aiofiles/base.py
+++ b/src/aiofiles/base.py
@@ -12,9 +12,9 @@ class AsyncBase:
     def __aiter__(self):
         """We are our own iterator."""
         return self
-    
+
     def __repr__(self):
-        return super().__repr__() + ' wrapping ' + repr(self._file)
+        return super().__repr__() + " wrapping " + repr(self._file)
 
     async def __anext__(self):
         """Simulate normal file iteration."""
diff --git a/src/aiofiles/tempfile/__init__.py b/src/aiofiles/tempfile/__init__.py
index e3363f0..21753aa 100644
--- a/src/aiofiles/tempfile/__init__.py
+++ b/src/aiofiles/tempfile/__init__.py
@@ -113,10 +113,12 @@ def SpooledTemporaryFile(
     )
 
 
-def TemporaryDirectory(loop=None, executor=None):
+def TemporaryDirectory(suffix=None, prefix=None, dir=None, loop=None, executor=None):
     """Async open a temporary directory"""
     return AiofilesContextManagerTempDir(
-        _temporary_directory(loop=loop, executor=executor)
+        _temporary_directory(
+            suffix=suffix, prefix=prefix, dir=dir, loop=loop, executor=executor
+        )
     )
 
 
@@ -213,12 +215,15 @@ async def _spooled_temporary_file(
     return AsyncSpooledTemporaryFile(f, loop=loop, executor=executor)
 
 
-async def _temporary_directory(loop=None, executor=None):
+async def _temporary_directory(
+    suffix=None, prefix=None, dir=None, loop=None, executor=None
+):
     """Async method to open a temporary directory with async interface"""
     if loop is None:
         loop = asyncio.get_event_loop()
 
-    f = await loop.run_in_executor(executor, syncTemporaryDirectory)
+    cb = partial(syncTemporaryDirectory, suffix, prefix, dir)
+    f = await loop.run_in_executor(executor, cb)
 
     return AsyncTemporaryDirectory(f, loop=loop, executor=executor)
 


Test patch:
diff --git a/tests/test_os.py b/tests/test_os.py
index d87b86c..d32ef97 100644
--- a/tests/test_os.py
+++ b/tests/test_os.py
@@ -19,9 +19,9 @@ async def test_stat():
 @pytest.mark.asyncio
 async def test_remove():
     """Test the remove call."""
-    filename = join(dirname(__file__), 'resources', 'test_file2.txt')
-    with open(filename, 'w') as f:
-        f.write('Test file for remove call')
+    filename = join(dirname(__file__), "resources", "test_file2.txt")
+    with open(filename, "w") as f:
+        f.write("Test file for remove call")
 
     assert exists(filename)
     await aiofiles.os.remove(filename)
@@ -31,7 +31,7 @@ async def test_remove():
 @pytest.mark.asyncio
 async def test_mkdir_and_rmdir():
     """Test the mkdir and rmdir call."""
-    directory = join(dirname(__file__), 'resources', 'test_dir')
+    directory = join(dirname(__file__), "resources", "test_dir")
     await aiofiles.os.mkdir(directory)
     assert isdir(directory)
     await aiofiles.os.rmdir(directory)
@@ -41,8 +41,8 @@ async def test_mkdir_and_rmdir():
 @pytest.mark.asyncio
 async def test_rename():
     """Test the rename call."""
-    old_filename = join(dirname(__file__), 'resources', 'test_file1.txt')
-    new_filename = join(dirname(__file__), 'resources', 'test_file2.txt')
+    old_filename = join(dirname(__file__), "resources", "test_file1.txt")
+    new_filename = join(dirname(__file__), "resources", "test_file2.txt")
     await aiofiles.os.rename(old_filename, new_filename)
     assert exists(old_filename) is False and exists(new_filename)
     await aiofiles.os.rename(new_filename, old_filename)
@@ -128,9 +128,7 @@ async def test_sendfile_socket(unused_tcp_port):
 
     server = await asyncio.start_server(serve_file, port=unused_tcp_port)
 
-    reader, writer = await asyncio.open_connection(
-        "127.0.0.1", unused_tcp_port
-    )
+    reader, writer = await asyncio.open_connection("127.0.0.1", unused_tcp_port)
     actual_contents = await reader.read()
     writer.close()
 
@@ -143,7 +141,7 @@ async def test_sendfile_socket(unused_tcp_port):
 @pytest.mark.asyncio
 async def test_exists():
     """Test path.exists call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.exists(filename)
     assert result
 
@@ -151,7 +149,7 @@ async def test_exists():
 @pytest.mark.asyncio
 async def test_isfile():
     """Test path.isfile call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.isfile(filename)
     assert result
 
@@ -159,7 +157,7 @@ async def test_isfile():
 @pytest.mark.asyncio
 async def test_isdir():
     """Test path.isdir call."""
-    filename = join(dirname(__file__), 'resources')
+    filename = join(dirname(__file__), "resources")
     result = await aiofiles.os.path.isdir(filename)
     assert result
 
@@ -167,7 +165,7 @@ async def test_isdir():
 @pytest.mark.asyncio
 async def test_getsize():
     """Test path.getsize call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.getsize(filename)
     assert result == 10
 
@@ -175,7 +173,7 @@ async def test_getsize():
 @pytest.mark.asyncio
 async def test_samefile():
     """Test path.samefile call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.samefile(filename, filename)
     assert result
 
@@ -183,7 +181,7 @@ async def test_samefile():
 @pytest.mark.asyncio
 async def test_sameopenfile():
     """Test path.samefile call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.samefile(filename, filename)
     assert result
 
@@ -191,7 +189,7 @@ async def test_sameopenfile():
 @pytest.mark.asyncio
 async def test_getmtime():
     """Test path.getmtime call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.getmtime(filename)
     assert result
 
@@ -199,7 +197,7 @@ async def test_getmtime():
 @pytest.mark.asyncio
 async def test_getatime():
     """Test path.getatime call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.getatime(filename)
     assert result
 
@@ -207,6 +205,6 @@ async def test_getatime():
 @pytest.mark.asyncio
 async def test_getctime():
     """Test path. call."""
-    filename = join(dirname(__file__), 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "resources", "test_file1.txt")
     result = await aiofiles.os.path.getctime(filename)
     assert result
diff --git a/tests/test_tempfile.py b/tests/test_tempfile.py
index c6e9542..971f9b9 100644
--- a/tests/test_tempfile.py
+++ b/tests/test_tempfile.py
@@ -9,24 +9,24 @@ import io
 @pytest.mark.parametrize("mode", ["r+", "w+", "rb+", "wb+"])
 async def test_temporary_file(mode):
     """Test temporary file."""
-    data = b'Hello World!\n' if 'b' in mode else 'Hello World!\n' 
+    data = b"Hello World!\n" if "b" in mode else "Hello World!\n"
 
     async with tempfile.TemporaryFile(mode=mode) as f:
         for i in range(3):
-            await f.write(data) 
+            await f.write(data)
 
         await f.flush()
         await f.seek(0)
 
         async for line in f:
             assert line == data
-        
+
 
 @pytest.mark.asyncio
 @pytest.mark.parametrize("mode", ["r+", "w+", "rb+", "wb+"])
 async def test_named_temporary_file(mode):
     """Test named temporary file."""
-    data = b'Hello World!' if 'b' in mode else 'Hello World!' 
+    data = b"Hello World!" if "b" in mode else "Hello World!"
     filename = None
 
     async with tempfile.NamedTemporaryFile(mode=mode) as f:
@@ -42,22 +42,22 @@ async def test_named_temporary_file(mode):
 
     assert not os.path.exists(filename)
 
-        
+
 @pytest.mark.asyncio
 @pytest.mark.parametrize("mode", ["r+", "w+", "rb+", "wb+"])
 async def test_spooled_temporary_file(mode):
     """Test spooled temporary file."""
-    data = b'Hello World!' if 'b' in mode else 'Hello World!' 
+    data = b"Hello World!" if "b" in mode else "Hello World!"
 
-    async with tempfile.SpooledTemporaryFile(max_size=len(data)+1, mode=mode) as f:
+    async with tempfile.SpooledTemporaryFile(max_size=len(data) + 1, mode=mode) as f:
         await f.write(data)
         await f.flush()
-        if 'b' in mode:
+        if "b" in mode:
             assert type(f._file._file) is io.BytesIO
 
         await f.write(data)
         await f.flush()
-        if 'b' in mode:
+        if "b" in mode:
             assert type(f._file._file) is not io.BytesIO
 
         await f.seek(0)
@@ -65,13 +65,17 @@ async def test_spooled_temporary_file(mode):
 
 
 @pytest.mark.asyncio
-async def test_temporary_directory():
+@pytest.mark.parametrize("prefix, suffix", [("a", "b"), ("c", "d"), ("e", "f")])
+async def test_temporary_directory(prefix, suffix, tmp_path):
     """Test temporary directory."""
     dir_path = None
 
-    async with tempfile.TemporaryDirectory() as d:
+    async with tempfile.TemporaryDirectory(
+        suffix=suffix, prefix=prefix, dir=tmp_path
+    ) as d:
         dir_path = d
         assert os.path.exists(dir_path)
         assert os.path.isdir(dir_path)
-
+        assert d[-1] == suffix
+        assert d.split(os.sep)[-1][0] == prefix
     assert not os.path.exists(dir_path)
diff --git a/tests/threadpool/test_concurrency.py b/tests/threadpool/test_concurrency.py
index 2922d66..1411089 100644
--- a/tests/threadpool/test_concurrency.py
+++ b/tests/threadpool/test_concurrency.py
@@ -57,9 +57,7 @@ async def test_slow_file(monkeypatch, unused_tcp_port):
 
     spam_task = asyncio.ensure_future(spam_client())
 
-    reader, writer = await asyncio.open_connection(
-        "127.0.0.1", port=unused_tcp_port
-    )
+    reader, writer = await asyncio.open_connection("127.0.0.1", port=unused_tcp_port)
 
     actual_contents = await reader.read()
     writer.close()
diff --git a/tests/threadpool/test_text.py b/tests/threadpool/test_text.py
index 2a3dbcf..8d04039 100644
--- a/tests/threadpool/test_text.py
+++ b/tests/threadpool/test_text.py
@@ -6,10 +6,10 @@ import pytest
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_simple_iteration(mode):
     """Test iterating over lines from a file."""
-    filename = join(dirname(__file__), '..', 'resources', 'multiline_file.txt')
+    filename = join(dirname(__file__), "..", "resources", "multiline_file.txt")
 
     async with aioopen(filename, mode=mode) as file:
         # Append mode needs us to seek.
@@ -22,7 +22,7 @@ async def test_simple_iteration(mode):
             line = await file.readline()
             if not line:
                 break
-            assert line.strip() == 'line ' + str(counter)
+            assert line.strip() == "line " + str(counter)
             counter += 1
 
         await file.seek(0)
@@ -30,19 +30,19 @@ async def test_simple_iteration(mode):
 
         # The new iteration pattern:
         async for line in file:
-            assert line.strip() == 'line ' + str(counter)
+            assert line.strip() == "line " + str(counter)
             counter += 1
 
     assert file.closed
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_simple_readlines(mode):
     """Test the readlines functionality."""
-    filename = join(dirname(__file__), '..', 'resources', 'multiline_file.txt')
+    filename = join(dirname(__file__), "..", "resources", "multiline_file.txt")
 
-    with open(filename, mode='r') as f:
+    with open(filename, mode="r") as f:
         expected = f.readlines()
 
     async with aioopen(filename, mode=mode) as file:
@@ -57,50 +57,50 @@ async def test_simple_readlines(mode):
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r+', 'w', 'a'])
+@pytest.mark.parametrize("mode", ["r+", "w", "a"])
 async def test_simple_flush(mode, tmpdir):
     """Test flushing to a file."""
-    filename = 'file.bin'
+    filename = "file.bin"
 
     full_file = tmpdir.join(filename)
 
-    if 'r' in mode:
+    if "r" in mode:
         full_file.ensure()  # Read modes want it to already exist.
 
     async with aioopen(str(full_file), mode=mode) as file:
-        await file.write('0')  # Shouldn't flush.
+        await file.write("0")  # Shouldn't flush.
 
-        assert '' == full_file.read_text(encoding='utf8')
+        assert "" == full_file.read_text(encoding="utf8")
 
         await file.flush()
 
-        assert '0' == full_file.read_text(encoding='utf8')
+        assert "0" == full_file.read_text(encoding="utf8")
 
     assert file.closed
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_simple_read(mode):
     """Just read some bytes from a test file."""
-    filename = join(dirname(__file__), '..', 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "..", "resources", "test_file1.txt")
     async with aioopen(filename, mode=mode) as file:
         await file.seek(0)  # Needed for the append mode.
 
         actual = await file.read()
 
-        assert '' == (await file.read())
-    assert actual == open(filename, mode='r').read()
+        assert "" == (await file.read())
+    assert actual == open(filename, mode="r").read()
 
     assert file.closed
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['w', 'a'])
+@pytest.mark.parametrize("mode", ["w", "a"])
 async def test_simple_read_fail(mode, tmpdir):
     """Try reading some bytes and fail."""
-    filename = 'bigfile.bin'
-    content = '0123456789' * 4 * io.DEFAULT_BUFFER_SIZE
+    filename = "bigfile.bin"
+    content = "0123456789" * 4 * io.DEFAULT_BUFFER_SIZE
 
     full_file = tmpdir.join(filename)
     full_file.write(content)
@@ -114,10 +114,10 @@ async def test_simple_read_fail(mode, tmpdir):
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_staggered_read(mode):
     """Read bytes repeatedly."""
-    filename = join(dirname(__file__), '..', 'resources', 'test_file1.txt')
+    filename = join(dirname(__file__), "..", "resources", "test_file1.txt")
     async with aioopen(filename, mode=mode) as file:
         await file.seek(0)  # Needed for the append mode.
 
@@ -129,10 +129,10 @@ async def test_staggered_read(mode):
             else:
                 break
 
-        assert '' == (await file.read())
+        assert "" == (await file.read())
 
     expected = []
-    with open(filename, mode='r') as f:
+    with open(filename, mode="r") as f:
         while True:
             char = f.read(1)
             if char:
@@ -146,28 +146,28 @@ async def test_staggered_read(mode):
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_simple_seek(mode, tmpdir):
     """Test seeking and then reading."""
-    filename = 'bigfile.bin'
-    content = '0123456789' * 4 * io.DEFAULT_BUFFER_SIZE
+    filename = "bigfile.bin"
+    content = "0123456789" * 4 * io.DEFAULT_BUFFER_SIZE
 
     full_file = tmpdir.join(filename)
     full_file.write(content)
 
     async with aioopen(str(full_file), mode=mode) as file:
         await file.seek(4)
-        assert '4' == (await file.read(1))
+        assert "4" == (await file.read(1))
 
     assert file.closed
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['w', 'r', 'r+', 'w+', 'a', 'a+'])
+@pytest.mark.parametrize("mode", ["w", "r", "r+", "w+", "a", "a+"])
 async def test_simple_close(mode, tmpdir):
     """Open a file, read a byte, and close it."""
-    filename = 'bigfile.bin'
-    content = '0' * 4 * io.DEFAULT_BUFFER_SIZE
+    filename = "bigfile.bin"
+    content = "0" * 4 * io.DEFAULT_BUFFER_SIZE
 
     full_file = tmpdir.join(filename)
     full_file.write(content)
@@ -181,11 +181,11 @@ async def test_simple_close(mode, tmpdir):
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r+', 'w', 'a+'])
+@pytest.mark.parametrize("mode", ["r+", "w", "a+"])
 async def test_simple_truncate(mode, tmpdir):
     """Test truncating files."""
-    filename = 'bigfile.bin'
-    content = '0123456789' * 4 * io.DEFAULT_BUFFER_SIZE
+    filename = "bigfile.bin"
+    content = "0123456789" * 4 * io.DEFAULT_BUFFER_SIZE
 
     full_file = tmpdir.join(filename)
     full_file.write(content)
@@ -194,7 +194,7 @@ async def test_simple_truncate(mode, tmpdir):
         # The append modes want us to seek first.
         await file.seek(0)
 
-        if 'w' in mode:
+        if "w" in mode:
             # We've just erased the entire file.
             await file.write(content)
             await file.flush()
@@ -202,19 +202,19 @@ async def test_simple_truncate(mode, tmpdir):
 
         await file.truncate()
 
-    assert '' == full_file.read()
+    assert "" == full_file.read()
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['w', 'r+', 'w+', 'a', 'a+'])
+@pytest.mark.parametrize("mode", ["w", "r+", "w+", "a", "a+"])
 async def test_simple_write(mode, tmpdir):
     """Test writing into a file."""
-    filename = 'bigfile.bin'
-    content = '0' * 4 * io.DEFAULT_BUFFER_SIZE
+    filename = "bigfile.bin"
+    content = "0" * 4 * io.DEFAULT_BUFFER_SIZE
 
     full_file = tmpdir.join(filename)
 
-    if 'r' in mode:
+    if "r" in mode:
         full_file.ensure()  # Read modes want it to already exist.
 
     async with aioopen(str(full_file), mode=mode) as file:
@@ -228,13 +228,13 @@ async def test_simple_write(mode, tmpdir):
 @pytest.mark.asyncio
 async def test_simple_detach(tmpdir):
     """Test detaching for buffered streams."""
-    filename = 'file.bin'
+    filename = "file.bin"
 
     full_file = tmpdir.join(filename)
-    full_file.write('0123456789')
+    full_file.write("0123456789")
 
     with pytest.raises(ValueError):  # Close will error out.
-        async with aioopen(str(full_file), mode='r') as file:
+        async with aioopen(str(full_file), mode="r") as file:
             raw_file = file.detach()
 
             assert raw_file
@@ -242,14 +242,14 @@ async def test_simple_detach(tmpdir):
             with pytest.raises(ValueError):
                 await file.read()
 
-            assert b'0123456789' == raw_file.read(10)
+            assert b"0123456789" == raw_file.read(10)
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_simple_iteration_ctx_mgr(mode):
     """Test iterating over lines from a file."""
-    filename = join(dirname(__file__), '..', 'resources', 'multiline_file.txt')
+    filename = join(dirname(__file__), "..", "resources", "multiline_file.txt")
 
     async with aioopen(filename, mode=mode) as file:
         assert not file.closed
@@ -258,17 +258,17 @@ async def test_simple_iteration_ctx_mgr(mode):
         counter = 1
 
         async for line in file:
-            assert line.strip() == 'line ' + str(counter)
+            assert line.strip() == "line " + str(counter)
             counter += 1
 
     assert file.closed
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_name_property(mode):
     """Test iterating over lines from a file."""
-    filename = join(dirname(__file__), '..', 'resources', 'multiline_file.txt')
+    filename = join(dirname(__file__), "..", "resources", "multiline_file.txt")
 
     async with aioopen(filename, mode=mode) as file:
         assert file.name == filename
@@ -277,10 +277,10 @@ async def test_name_property(mode):
 
 
 @pytest.mark.asyncio
-@pytest.mark.parametrize('mode', ['r', 'r+', 'a+'])
+@pytest.mark.parametrize("mode", ["r", "r+", "a+"])
 async def test_mode_property(mode):
     """Test iterating over lines from a file."""
-    filename = join(dirname(__file__), '..', 'resources', 'multiline_file.txt')
+    filename = join(dirname(__file__), "..", "resources", "multiline_file.txt")
 
     async with aioopen(filename, mode=mode) as file:
         assert file.mode == mode