From 719f758d7ac1ee1ddfe31bb86e41d72d450b84e2 Mon Sep 17 00:00:00 2001 From: Loren Gordon Date: Thu, 23 Aug 2018 10:46:03 -0400 Subject: [PATCH 1/4] Passes kwargs through AtomicWriter to tempfile.NamedTemporaryFile Fixes #37 --- atomicwrites/__init__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/atomicwrites/__init__.py b/atomicwrites/__init__.py index a182c07..108af8e 100644 --- a/atomicwrites/__init__.py +++ b/atomicwrites/__init__.py @@ -118,7 +118,7 @@ class AtomicWriter(object): subclass. ''' - def __init__(self, path, mode='w', overwrite=False): + def __init__(self, path, mode='w', overwrite=False, **tempfile_kwargs): if 'a' in mode: raise ValueError( 'Appending to an existing file is not supported, because that ' @@ -134,6 +134,7 @@ def __init__(self, path, mode='w', overwrite=False): self._path = path self._mode = mode self._overwrite = overwrite + self._tempfile_kwargs = tempfile_kwargs def open(self): ''' @@ -146,7 +147,7 @@ def _open(self, get_fileobject): f = None # make sure f exists even if get_fileobject() fails try: success = False - with get_fileobject() as f: + with get_fileobject(**self._tempfile_kwargs) as f: yield f self.sync(f) self.commit(f) @@ -162,8 +163,10 @@ def get_fileobject(self, dir=None, **kwargs): '''Return the temporary file to use.''' if dir is None: dir = os.path.normpath(os.path.dirname(self._path)) - return tempfile.NamedTemporaryFile(mode=self._mode, dir=dir, - delete=False, **kwargs) + kwargs['dir'] = dir + kwargs['delete'] = False + kwargs['mode'] = self._mode + return tempfile.NamedTemporaryFile(**kwargs) def sync(self, f): '''responsible for clearing as many file caches as possible before From a8b4adfdb965a0f592e6a28e68a1be1b0d6d632c Mon Sep 17 00:00:00 2001 From: Loren Gordon Date: Fri, 24 Aug 2018 07:22:19 -0400 Subject: [PATCH 2/4] Uses io.open for py2/py3 api compatiblity --- atomicwrites/__init__.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/atomicwrites/__init__.py b/atomicwrites/__init__.py index 108af8e..3a83761 100644 --- a/atomicwrites/__init__.py +++ b/atomicwrites/__init__.py @@ -1,4 +1,5 @@ import contextlib +import io import os import sys import tempfile @@ -118,7 +119,7 @@ class AtomicWriter(object): subclass. ''' - def __init__(self, path, mode='w', overwrite=False, **tempfile_kwargs): + def __init__(self, path, mode='w', overwrite=False, **open_kwargs): if 'a' in mode: raise ValueError( 'Appending to an existing file is not supported, because that ' @@ -134,7 +135,7 @@ def __init__(self, path, mode='w', overwrite=False, **tempfile_kwargs): self._path = path self._mode = mode self._overwrite = overwrite - self._tempfile_kwargs = tempfile_kwargs + self._open_kwargs = open_kwargs def open(self): ''' @@ -147,7 +148,7 @@ def _open(self, get_fileobject): f = None # make sure f exists even if get_fileobject() fails try: success = False - with get_fileobject(**self._tempfile_kwargs) as f: + with get_fileobject(**self._open_kwargs) as f: yield f self.sync(f) self.commit(f) @@ -163,10 +164,14 @@ def get_fileobject(self, dir=None, **kwargs): '''Return the temporary file to use.''' if dir is None: dir = os.path.normpath(os.path.dirname(self._path)) - kwargs['dir'] = dir - kwargs['delete'] = False + descriptor, name = tempfile.mkstemp(dir=dir) + # io.open() will take either the descriptor or the name, but we need + # the name later for commit()/replace_atomic() and couldn't find a way + # to get the filename from the descriptor. + os.close(descriptor) kwargs['mode'] = self._mode - return tempfile.NamedTemporaryFile(**kwargs) + kwargs['file'] = name + return io.open(**kwargs) def sync(self, f): '''responsible for clearing as many file caches as possible before From 42f2913e1a2fccf7ad435ea3b022b4d3a53c12b1 Mon Sep 17 00:00:00 2001 From: Loren Gordon Date: Fri, 24 Aug 2018 12:57:24 -0400 Subject: [PATCH 3/4] Updates reraise test since ame is not writeable on _io.TextIOWrapper objects --- tests/test_atomicwrites.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_atomicwrites.py b/tests/test_atomicwrites.py index 9577199..1564255 100644 --- a/tests/test_atomicwrites.py +++ b/tests/test_atomicwrites.py @@ -60,10 +60,10 @@ def test_open_reraise(tmpdir): fname = tmpdir.join('ha') with pytest.raises(AssertionError): with atomic_write(str(fname), overwrite=False) as f: - # Mess with f, so rollback will trigger an OSError. We're testing + # Mess with f, so commit will trigger a ValueError. We're testing # that the initial AssertionError triggered below is propagated up - # the stack, not the second exception triggered during rollback. - f.name = "asdf" + # the stack, not the second exception triggered during commit. + f.close() # Now trigger our own exception. assert False, "Intentional failure for testing purposes" From 714ed6743dfbb278ec841c1605ae073018fa990a Mon Sep 17 00:00:00 2001 From: Loren Gordon Date: Fri, 24 Aug 2018 13:40:03 -0400 Subject: [PATCH 4/4] Writes unicode strings in tests to support the change to io.open --- tests/test_atomicwrites.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_atomicwrites.py b/tests/test_atomicwrites.py index 1564255..3bdcd5e 100644 --- a/tests/test_atomicwrites.py +++ b/tests/test_atomicwrites.py @@ -10,11 +10,11 @@ def test_atomic_write(tmpdir): fname = tmpdir.join('ha') for i in range(2): with atomic_write(str(fname), overwrite=True) as f: - f.write('hoho') + f.write(u'hoho') with pytest.raises(OSError) as excinfo: with atomic_write(str(fname), overwrite=False) as f: - f.write('haha') + f.write(u'haha') assert excinfo.value.errno == errno.EEXIST @@ -34,7 +34,7 @@ def test_teardown(tmpdir): def test_replace_simultaneously_created_file(tmpdir): fname = tmpdir.join('ha') with atomic_write(str(fname), overwrite=True) as f: - f.write('hoho') + f.write(u'hoho') fname.write('harhar') assert fname.read() == 'harhar' assert fname.read() == 'hoho' @@ -45,7 +45,7 @@ def test_dont_remove_simultaneously_created_file(tmpdir): fname = tmpdir.join('ha') with pytest.raises(OSError) as excinfo: with atomic_write(str(fname), overwrite=False) as f: - f.write('hoho') + f.write(u'hoho') fname.write('harhar') assert fname.read() == 'harhar' @@ -75,11 +75,11 @@ def test_atomic_write_in_pwd(tmpdir): fname = 'ha' for i in range(2): with atomic_write(str(fname), overwrite=True) as f: - f.write('hoho') + f.write(u'hoho') with pytest.raises(OSError) as excinfo: with atomic_write(str(fname), overwrite=False) as f: - f.write('haha') + f.write(u'haha') assert excinfo.value.errno == errno.EEXIST