Skip to content

Commit 19f976c

Browse files
committed
-
1 parent df1334f commit 19f976c

File tree

3 files changed

+114
-5
lines changed

3 files changed

+114
-5
lines changed

misc/IDE files/Wing/python_toolbox_py3.wpr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ proj.directory-list = [{'dirloc': loc('../../..'),
3434
'watch_for_changes': True}]
3535
proj.file-type = 'shared'
3636
proj.home-dir = loc('../../..')
37-
proj.launch-config = {loc('../../../../../../Desktop/ultimate.py'): ('projec'\
38-
't',
37+
proj.launch-config = {loc('../../../source_py3/python_toolbox/cute_iter_tools.py'): (''\
38+
'project',
3939
(u'',
4040
'launch-OHU716PSo2P5T54y'))}
4141
testing.auto-test-file-specs = [('glob',

source_py3/python_toolbox/file_tools.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
'''Defines various tools related to temporary files.'''
55

66
import pathlib
7+
import uuid
78
import os
89
import re
910

@@ -101,8 +102,8 @@ def create_file_renaming_if_taken(path, mode='x',
101102
N_MAX_ATTEMPTS):
102103
try:
103104
return path.open(mode, buffering=buffering,
104-
encoding=encoding, errors=errors,
105-
newline=newline)
105+
encoding=encoding, errors=errors,
106+
newline=newline)
106107
except FileExistsError:
107108
pass
108109
else:
@@ -126,4 +127,59 @@ def write_to_file_renaming_if_taken(path, data, mode='x',
126127
newline=newline) as file:
127128

128129
return file.write(data)
129-
130+
131+
132+
def atomic_create_and_write(path, data=None, binary=False):
133+
'''
134+
Write data to file, but use a temporary file as a buffer.
135+
136+
The data you write to this file is actuall written to a temporary file in
137+
the same folder, and only after you close it, without having an exception
138+
raised, it renames the temporary file to your original file name. If an
139+
exception was raised during writing it deletes the temporary file.
140+
141+
This way you're sure you're not getting a half-baked file.
142+
'''
143+
with atomic_create(path, binary=binary) as file:
144+
return file.write(data)
145+
146+
147+
@context_management.ContextManagerType
148+
def atomic_create(path, binary=False):
149+
'''
150+
Create a file for writing, but use a temporary file as a buffer.
151+
152+
Use as a context manager:
153+
154+
with atomic_create(path) as my_file:
155+
my_file.write('Whatever')
156+
157+
When you write to this file it actually writes to a temporary file in the
158+
same folder, and only after you close it, without having an exception
159+
raised, it renames the temporary file to your original file name. If an
160+
exception was raised during writing it deletes the temporary file.
161+
162+
This way you're sure you're not getting a half-baked file.
163+
'''
164+
path = pathlib.Path(path)
165+
if path.exists():
166+
raise Exception("There's already a file called %s" % path)
167+
desired_temp_file_path = path.parent / ('._%s.tmp' % path.stem)
168+
try:
169+
with create_file_renaming_if_taken(desired_temp_file_path,
170+
'xb' if binary else 'x') as temp_file:
171+
actual_temp_file_path = pathlib.Path(temp_file.name)
172+
yield temp_file
173+
174+
# This part runs only if there was no exception when writing to the
175+
# file:
176+
if path.exists():
177+
raise Exception("There's already a file called %s" % path)
178+
actual_temp_file_path.rename(path)
179+
assert path.exists()
180+
181+
finally:
182+
if actual_temp_file_path.exists():
183+
actual_temp_file_path.unlink()
184+
185+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2009-2014 Ram Rachum.
2+
# This program is distributed under the MIT license.
3+
4+
import pathlib
5+
6+
import python_toolbox
7+
from python_toolbox import temp_file_tools
8+
from python_toolbox import cute_testing
9+
10+
from python_toolbox import file_tools
11+
12+
13+
def test():
14+
with temp_file_tools.create_temp_folder(
15+
prefix='test_python_toolbox_') as temp_folder:
16+
assert set(temp_folder.glob('*')) == set()
17+
file_1 = temp_folder / 'file_1.txt'
18+
assert not file_1.exists()
19+
file_tools.atomic_create_and_write(file_1, "Meow meow I'm a cat.")
20+
assert set(temp_folder.glob('*')) == {file_1}
21+
with file_1.open('r') as file:
22+
assert file.read() == "Meow meow I'm a cat."
23+
24+
#######################################################################
25+
26+
file_2 = temp_folder / 'file_2.txt'
27+
with file_tools.atomic_create(file_2) as file:
28+
file.write('Hurr durr')
29+
assert not file_2.exists()
30+
assert len(set(temp_folder.glob('*'))) == 2
31+
32+
assert file_2.exists()
33+
assert len(set(temp_folder.glob('*'))) == 2
34+
assert set(temp_folder.glob('*')) == {file_1, file_2}
35+
with file_2.open('r') as file:
36+
assert file.read() == 'Hurr durr'
37+
38+
#######################################################################
39+
40+
41+
file_3 = temp_folder / 'file_3.txt'
42+
43+
with cute_testing.RaiseAssertor(ZeroDivisionError):
44+
with file_tools.atomic_create(file_3) as file:
45+
file.write('bloop bloop bloop')
46+
assert not file_3.exists()
47+
assert len(set(temp_folder.glob('*'))) == 3
48+
1 / 0
49+
50+
assert not file_3.exists()
51+
assert len(set(temp_folder.glob('*'))) == 2
52+
assert set(temp_folder.glob('*')) == {file_1, file_2}
53+

0 commit comments

Comments
 (0)