1- import codecs
21import contextlib
32import os
3+ import sys
44import tempfile
55
66__version__ = '0.1.1'
77
8+ if sys .platform != 'win32' :
9+ def replace_atomic (src , dst ):
10+ os .rename (src , dst )
811
9- class AtomicWriterBase (object ):
12+ def move_atomic (src , dst ):
13+ os .link (src , dst )
14+ os .unlink (src )
15+ else :
16+ import win32api
17+ import win32con
18+
19+ _windows_default_flags = win32con .WRITE_THROUGH
20+
21+ def replace_atomic (src , dst ):
22+ win32api .MoveFileEx (
23+ src , dst ,
24+ win32con .MOVEFILE_REPLACE_EXISTING | _windows_default_flags
25+ )
26+
27+ def move_atomic (src , dst ):
28+ win32api .MoveFileEx (
29+ src , dst ,
30+ _windows_default_flags
31+ )
32+
33+
34+ replace_atomic .__doc__ = \
1035 '''
11- A helper class for performing atomic writes. Not fully implemented, you
12- should use a subclass of it instead. Usage::
36+ Move ``src`` to ``dst``. If ``dst`` exists, it will be silently
37+ overwritten.
38+
39+ Both paths must reside on the same filesystem for the operation to be
40+ atomic.
41+ ''' + (replace_atomic .__doc__ or '' )
42+
43+
44+ move_atomic .__doc__ = \
45+ '''
46+ Move ``src`` to ``dst``. There might a timewindow where both filesystem
47+ entries exist. If ``dst`` already exists, an error will be raised.
48+
49+ Both paths must reside on the same filesystem for the operation to be
50+ atomic.
51+ ''' + (move_atomic .__doc__ or '' )
1352
14- with MyWriter(path).open() as f:
53+
54+ class AtomicWriter (object ):
55+ '''
56+ A helper class for performing atomic writes. Usage::
57+
58+ with AtomicWriter(path).open() as f:
1559 f.write(...)
1660
61+
62+ It uses a temporary file in the same directory as the given path. This
63+ ensures that the temporary file resides on the same filesystem.
64+
65+ The temporary file will then be atomically moved to the target location: On
66+ POSIX, it will use ``rename`` if files should be overwritten, otherwise a
67+ combination of ``link`` and ``unlink``. On Windows, it uses ``MoveFileEx``
68+ (see MSDN_) with the appropriate flags.
69+
70+ .. _MSDN::
71+ https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240%28v=vs.85%29.aspx
72+
1773 :param path: The destination filepath. May or may not exist.
1874 :param overwrite: If set to false, an error is raised if ``path`` exists.
1975 Either way, the operation is atomic.
@@ -24,16 +80,10 @@ def __init__(self, path, overwrite=False):
2480 self ._overwrite = overwrite
2581
2682 def open (self , mode = 'w' ):
27- '''Prepare the temporary file object and return it.'''
28- raise NotImplementedError ()
29-
30- def commit (self ):
31- '''Move the temporary file to the target location.'''
32- raise NotImplementedError ()
33-
34- def rollback (self ):
35- '''Clean up all temporary resources.'''
36- raise NotImplementedError ()
83+ '''
84+ Open the temporary file.
85+ '''
86+ return self ._open (lambda : self .get_fileobject (mode = mode ))
3787
3888 @contextlib .contextmanager
3989 def _open (self , get_fileobject ):
@@ -46,39 +96,25 @@ def _open(self, get_fileobject):
4696 else :
4797 self .commit ()
4898
49-
50- class PosixAtomicWriter (AtomicWriterBase ):
51- '''
52- An implementation of :py:class:`AtomicWriterBase` for POSIX-compliant
53- filesystems. It uses temporary files in the same directory.
54- '''
55-
5699 def get_fileobject (self , mode ):
57100 '''Return the temporary path to use.'''
58101 tmpdir = os .path .dirname (self ._path )
59102 f = tempfile .NamedTemporaryFile (mode = mode , dir = tmpdir , delete = False )
60103 self ._tmppath = f .name
61104 return f
62105
63- def open (self , mode = 'w' , ** open_kwargs ):
64- '''
65- Open the temporary file. Any arguments will be passed on to the builtin
66- ``open`` function.
67- '''
68- return self ._open (lambda : self .get_fileobject (mode = mode ))
69-
70106 def commit (self ):
107+ '''Move the temporary file to the target location.'''
71108 if self ._overwrite :
72109 os .rename (self ._tmppath , self ._path ) # atomic
73110 else :
74111 os .link (self ._tmppath , self ._path ) # atomic, fails if file exists
75112 os .unlink (self ._tmppath )
76113
77114 def rollback (self ):
115+ '''Clean up all temporary resources.'''
78116 os .unlink (self ._tmppath )
79117
80- AtomicWriter = PosixAtomicWriter # for current OS
81-
82118
83119def atomic_write (path , mode = 'w' , overwrite = False , writer_cls = AtomicWriter ,
84120 ** open_kwargs ):
0 commit comments