import os import msvcrt import _winapi __all__ = ['shareopen', 'shareopener', 'FILE_SHARE_READ', 'FILE_SHARE_WRITE', 'FILE_SHARE_DELETE'] CREATE_NEW = 1 CREATE_ALWAYS = 2 OPEN_EXISTING = 3 OPEN_ALWAYS = 4 TRUNCATE_EXISTING = 5 FILE_SHARE_READ = 0x1 FILE_SHARE_WRITE = 0x2 FILE_SHARE_DELETE = 0x4 FILE_ATTRIBUTE_READONLY = 0x00000001 FILE_ATTRIBUTE_NORMAL = 0x00000080 FILE_ATTRIBUTE_TEMPORARY = 0x00000100 FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000 FILE_FLAG_RANDOM_ACCESS = 0x10000000 _ACCESS_MASK = os.O_RDONLY | os.O_WRONLY | os.O_RDWR _CREATE_MASK = os.O_CREAT | os.O_EXCL | os.O_TRUNC _ALL_FLAGS_MASK = (os.O_TEMPORARY | os.O_SHORT_LIVED | os.O_SEQUENTIAL | os.O_RANDOM | os.O_NOINHERIT | os.O_TEXT | os.O_BINARY | _ACCESS_MASK | _CREATE_MASK) _FILE_SHARE_MASK = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE _ACCESS_MAP = {os.O_RDONLY : _winapi.GENERIC_READ, os.O_WRONLY : _winapi.GENERIC_WRITE, os.O_RDWR : _winapi.GENERIC_READ | _winapi.GENERIC_WRITE} _CREATE_MAP = {os.O_EXCL : OPEN_EXISTING, os.O_CREAT : OPEN_ALWAYS, os.O_CREAT | os.O_EXCL : CREATE_NEW, os.O_CREAT | os.O_TRUNC | os.O_EXCL : CREATE_NEW, os.O_TRUNC : TRUNCATE_EXISTING, os.O_TRUNC | os.O_EXCL : TRUNCATE_EXISTING, os.O_CREAT | os.O_TRUNC : CREATE_ALWAYS} def shareopener(name, flags, mode=0o777, *, share_flags=_FILE_SHARE_MASK): ''' Replacement for os.open() which allows control over sharing. share_flags should be obtained by bitwise combining the flags FILE_SHARE_READ, FILE_SHARE_WRITE and FILE_SHARE_DELETE. The default is FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE. See documentation for the Win32 function CreateFile() for details of FILE_SHARE_READ, FILE_SHARE_WRITE and FILE_SHARE_DELETE. The underlying handle is *not* inherited by child processes. ''' if mode & ~0o777 != 0: raise ValueError('bad mode: %r' % mode) if flags & ~_ALL_FLAGS_MASK != 0: raise ValueError('bad flags: %r' % flags) if share_flags & ~_FILE_SHARE_MASK: raise ValueError('bad share_flags: %r' % share_flags) access_flags = _ACCESS_MAP[flags & _ACCESS_MASK] create_flags = _CREATE_MAP[flags & _CREATE_MASK] attrib_flags = FILE_ATTRIBUTE_NORMAL if mode & ~0o400 == 0: attrib_flags = FILE_ATTRIBUTE_READONLY if flags & os.O_TEMPORARY: share_flags |= FILE_SHARE_DELETE attrib_flags |= FILE_FLAG_DELETE_ON_CLOSE access_flags |= DELETE if flags & os.O_SHORT_LIVED: attrib_flags |= FILE_ATTRIBUTE_TEMPORARY if flags & os.O_SEQUENTIAL: attrib_flags |= FILE_FLAG_SEQUENTIAL_SCAN if flags & os.O_RANDOM: attrib_flags |= FILE_FLAG_RANDOM_ACCESS h = _winapi.CreateFile(name, access_flags, share_flags, _winapi.NULL, create_flags, attrib_flags, _winapi.NULL) return msvcrt.open_osfhandle(h, flags) def shareopen(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=shareopener): ''' Replacement for open() which opens files in FILE_SHARE_DELETE mode. Files created this way can be renamed and unlinked while still open, and the file object will still work, so one has Unix-like behaviour. The underlying handle is *not* inherited by child processes. ''' return open(file, mode, buffering=buffering, encoding=encoding, errors=errors, newline=newline, closefd=closefd, opener=opener) if __name__ == '__main__': FNAME1 = "somefile.txt" FNAME2 = "otherfile.txt" DATA1 = "hffjkdfd" DATA2 = "dfdgshlfsd" DATA3 = "jgfdsgsfdgsd" with shareopen(FNAME1, "w+") as f: f.write(DATA1) f.flush() # rename the file os.rename(FNAME1, FNAME2) assert not os.path.exists(FNAME1) assert os.path.exists(FNAME2) f.write(DATA2) f.flush() # unlink the file os.unlink(FNAME2) assert not os.path.exists(FNAME2) f.write(DATA3) f.flush() # check file object still works f.seek(0) assert f.read() == DATA1 + DATA2 + DATA3