From 27f5d542404ec6ac66c60b07a934f70e6117bea2 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 29 Jan 2015 12:12:40 +0100 Subject: [PATCH] Add atomicwrites dependency --- setup.py | 3 +- tests/utils/test_main.py | 14 ------- vdirsyncer/cli/utils.py | 2 +- vdirsyncer/storage/filesystem.py | 11 +++--- vdirsyncer/storage/singlefile.py | 2 +- vdirsyncer/utils/__init__.py | 63 ++++---------------------------- 6 files changed, 17 insertions(+), 78 deletions(-) diff --git a/setup.py b/setup.py index 4da6fad..bd29f05 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,8 @@ setup( 'lxml>=3.0', 'icalendar>=3.6', # https://github.com/sigmavirus24/requests-toolbelt/pull/28 - 'requests_toolbelt>=0.3.0' + 'requests_toolbelt>=0.3.0', + 'atomicwrites' ], extras_require={'keyring': ['keyring']} ) diff --git a/tests/utils/test_main.py b/tests/utils/test_main.py index e5802c2..62ef97f 100644 --- a/tests/utils/test_main.py +++ b/tests/utils/test_main.py @@ -220,17 +220,3 @@ def test_request_ssl(httpsserver): utils.request('GET', httpsserver.url, verify_fingerprint=''.join(reversed(sha1))) assert 'Fingerprints did not match' in str(excinfo.value) - - -def test_atomic_write(tmpdir): - x = utils.atomic_write - fname = tmpdir.join('ha') - for i in range(2): - with x(str(fname), binary=False, overwrite=True) as f: - f.write('hoho') - - with pytest.raises(OSError): - with x(str(fname), binary=False, overwrite=False) as f: - f.write('haha') - - assert fname.read() == 'hoho' diff --git a/vdirsyncer/cli/utils.py b/vdirsyncer/cli/utils.py index 7e7b29e..53e99c5 100644 --- a/vdirsyncer/cli/utils.py +++ b/vdirsyncer/cli/utils.py @@ -375,7 +375,7 @@ def save_status(base_path, pair, collection=None, data_type=None, data=None): if e.errno != errno.EEXIST: raise - with atomic_write(path, binary=False, overwrite=True) as f: + with atomic_write(path, mode='w', overwrite=True) as f: json.dump(data, f) diff --git a/vdirsyncer/storage/filesystem.py b/vdirsyncer/storage/filesystem.py index b8d81db..0b4dee4 100644 --- a/vdirsyncer/storage/filesystem.py +++ b/vdirsyncer/storage/filesystem.py @@ -5,7 +5,8 @@ import os from .base import Item, Storage from .. import exceptions, log -from ..utils import atomic_write, checkdir, expand_path, get_etag_from_file +from ..utils import atomic_write, checkdir, expand_path, \ + get_etag_from_file, get_etag_from_fileobject from ..utils.compat import text_type logger = log.get(__name__) @@ -99,9 +100,9 @@ class FilesystemStorage(Storage): raise TypeError('item.raw must be a unicode string.') try: - with atomic_write(fpath, binary=True, overwrite=False) as f: + with atomic_write(fpath, mode='wb', overwrite=False) as f: f.write(item.raw.encode(self.encoding)) - return href, f.get_etag() + return href, get_etag_from_fileobject(f) except OSError as e: import errno if e.errno == errno.EEXIST: @@ -123,9 +124,9 @@ class FilesystemStorage(Storage): if not isinstance(item.raw, text_type): raise TypeError('item.raw must be a unicode string.') - with atomic_write(fpath, binary=True, overwrite=True) as f: + with atomic_write(fpath, mode='wb', overwrite=True) as f: f.write(item.raw.encode(self.encoding)) - return f.get_etag() + return get_etag_from_fileobject(f) def delete(self, href, etag): fpath = self._get_filepath(href) diff --git a/vdirsyncer/storage/singlefile.py b/vdirsyncer/storage/singlefile.py index 28d03d0..2a95da2 100644 --- a/vdirsyncer/storage/singlefile.py +++ b/vdirsyncer/storage/singlefile.py @@ -159,7 +159,7 @@ class SingleFileStorage(Storage): (item.raw for item, etag in itervalues(self._items)), ) try: - with atomic_write(self.path, binary=True, overwrite=True) as f: + with atomic_write(self.path, mode='wb', overwrite=True) as f: f.write(text.encode(self.encoding)) finally: self._items = None diff --git a/vdirsyncer/utils/__init__.py b/vdirsyncer/utils/__init__.py index 7b38b8a..60ba7f7 100644 --- a/vdirsyncer/utils/__init__.py +++ b/vdirsyncer/utils/__init__.py @@ -4,6 +4,8 @@ import os import threading import uuid +from atomicwrites import atomic_write + import requests from requests.packages.urllib3.poolmanager import PoolManager @@ -235,66 +237,15 @@ def request(method, url, session=None, latin1_fallback=True, return r -class atomic_write(object): - ''' - A helper class for performing atomic writes. - - Usage:: - - with safe_write(fpath, binary=False, overwrite=False) as f: - f.write('hohoho') - - :param fpath: The destination filepath. May or may not exist. - :param binary: Whether binary write mode should be used. - :param overwrite: If set to false, an error is raised if ``fpath`` exists. - This should still be atomic. - :param tmppath: An alternative tmpfile location. Must reside on the same - filesystem to be atomic. - ''' - - f = None - tmppath = None - fpath = None - mode = None - - def __init__(self, fpath, binary, overwrite, tmppath=None): - if not tmppath: - base = os.path.dirname(fpath) - tmppath = os.path.join(base, str(uuid.uuid4()) + '.tmp') - - self.fpath = fpath - self.binary = binary - self.overwrite = overwrite - self.tmppath = tmppath - - def __enter__(self): - self.f = f = open(self.tmppath, 'wb' if self.binary else 'w') - self.write = f.write - return self - - def __exit__(self, cls, value, tb): - self.f.close() - if cls is None: - self._commit() - else: - os.remove(self.tmppath) - - def _commit(self): - if self.overwrite: - os.rename(self.tmppath, self.fpath) # atomic - else: - os.link(self.tmppath, self.fpath) # atomic, fails if file exists - os.unlink(self.tmppath) # doesn't matter if atomic - - def get_etag(self): - self.f.flush() - return get_etag_from_file(self.tmppath) - - def get_etag_from_file(fpath): return '{:.9f}'.format(os.path.getmtime(fpath)) +def get_etag_from_fileobject(f): + f.flush() + return get_etag_from_file(f.name) + + def get_class_init_specs(cls, stop_at=object): if cls is stop_at: return ()