From e68f54220f0bf279dd8e6905b83de0326730623c Mon Sep 17 00:00:00 2001 From: Scott Barlow <31610422+Spectre5@users.noreply.github.com> Date: Fri, 25 Dec 2020 21:58:09 -0800 Subject: [PATCH 1/5] add path_generator --- atomicwrites/__init__.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/atomicwrites/__init__.py b/atomicwrites/__init__.py index 0b9f92a..00db74e 100644 --- a/atomicwrites/__init__.py +++ b/atomicwrites/__init__.py @@ -2,6 +2,7 @@ import io import os import sys +import errno import tempfile try: @@ -133,7 +134,7 @@ class AtomicWriter(object): ''' def __init__(self, path, mode=DEFAULT_MODE, overwrite=False, - **open_kwargs): + path_generator=None, **open_kwargs): if 'a' in mode: raise ValueError( 'Appending to an existing file is not supported, because that ' @@ -153,7 +154,9 @@ def __init__(self, path, mode=DEFAULT_MODE, overwrite=False, self._path = path self._mode = mode self._overwrite = overwrite + self._path_generator = path_generator self._open_kwargs = open_kwargs + self.final_path = None def open(self): ''' @@ -169,10 +172,11 @@ def _open(self, get_fileobject): with get_fileobject(**self._open_kwargs) as f: yield f self.sync(f) - self.commit(f) + self.final_path = self.commit(f) success = True finally: if not success: + self.final_path = None try: self.rollback(f) except Exception: @@ -203,8 +207,31 @@ def commit(self, f): '''Move the temporary file to the target location.''' if self._overwrite: replace_atomic(f.name, self._path) + return self._path else: - move_atomic(f.name, self._path) + if self._path_generator is not None: + seen = set() + for path in self._path_generator(self._path): + if path in seen: + # avoid infinite loop if the path generator returns a + # path that was already attempted + raise ValueError( + 'path_generator must return unique values, but' + f'{path} was returned multiple times.' + ) + seen.add(path) + try: + move_atomic(f.name, path) + except OSError as exc: + if exc.errno == errno.EEXIST: + pass + else: + raise + else: + return path + else: + move_atomic(f.name, self._path) + return self._path def rollback(self, f): '''Clean up all temporary resources.''' From c0b258839749a6777dcd75910d84bf5f46baf8fe Mon Sep 17 00:00:00 2001 From: Scott Barlow <31610422+Spectre5@users.noreply.github.com> Date: Fri, 25 Dec 2020 22:08:07 -0800 Subject: [PATCH 2/5] remove f-string --- atomicwrites/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atomicwrites/__init__.py b/atomicwrites/__init__.py index 00db74e..a8b2d1e 100644 --- a/atomicwrites/__init__.py +++ b/atomicwrites/__init__.py @@ -217,7 +217,7 @@ def commit(self, f): # path that was already attempted raise ValueError( 'path_generator must return unique values, but' - f'{path} was returned multiple times.' + '{} was returned multiple times.'.format(path) ) seen.add(path) try: From e3dcbcde62ec782d6afcc763514468eff023b092 Mon Sep 17 00:00:00 2001 From: Scott Barlow <31610422+Spectre5@users.noreply.github.com> Date: Fri, 25 Dec 2020 22:11:13 -0800 Subject: [PATCH 3/5] import order --- atomicwrites/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atomicwrites/__init__.py b/atomicwrites/__init__.py index a8b2d1e..48ec0c1 100644 --- a/atomicwrites/__init__.py +++ b/atomicwrites/__init__.py @@ -1,8 +1,8 @@ import contextlib import io import os -import sys import errno +import sys import tempfile try: From d803fdf587ecc0e82b5643b7fffe7649f4619c53 Mon Sep 17 00:00:00 2001 From: Scott Barlow <31610422+Spectre5@users.noreply.github.com> Date: Fri, 25 Dec 2020 22:11:46 -0800 Subject: [PATCH 4/5] import order --- atomicwrites/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atomicwrites/__init__.py b/atomicwrites/__init__.py index 48ec0c1..297b857 100644 --- a/atomicwrites/__init__.py +++ b/atomicwrites/__init__.py @@ -1,7 +1,7 @@ import contextlib +import errno import io import os -import errno import sys import tempfile From b3b64a256b9ae85594176e97130a704a8cd8195a Mon Sep 17 00:00:00 2001 From: Scott Barlow <31610422+Spectre5@users.noreply.github.com> Date: Sat, 26 Dec 2020 15:20:23 -0800 Subject: [PATCH 5/5] add doc string for path_generator --- atomicwrites/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/atomicwrites/__init__.py b/atomicwrites/__init__.py index 297b857..596ba1f 100644 --- a/atomicwrites/__init__.py +++ b/atomicwrites/__init__.py @@ -125,6 +125,12 @@ class AtomicWriter(object): :param overwrite: If set to false, an error is raised if ``path`` exists. Errors are only raised after the file has been written to. Either way, the operation is atomic. + :param path_generator: A generator function that takes the `path` as an + argument and returns the next path to attempt to create. If creation + fails, the generator will be repeatedly called to get the next path to + try writing until the write succeeds or the a previously attempted path + is generated again. A `ValueError` is raised if a previously attempted + path is provided by the generator. :param open_kwargs: Keyword-arguments to pass to the underlying :py:func:`open` call. This can be used to set the encoding when opening files in text-mode.