3
3
4
4
import re
5
5
import os
6
+ import errno
6
7
import atexit
7
-
8
+ import operator
8
9
import six
9
10
from functools import reduce
10
-
11
+ import uuid
11
12
from six .moves import map
12
13
import pytest
13
14
import py
16
17
import attr
17
18
import shutil
18
19
import tempfile
20
+ import itertools
21
+
22
+
23
+ get_lock_path = operator .methodcaller ("joinpath" , ".lock" )
19
24
20
25
21
26
def find_prefixed (root , prefix ):
@@ -25,22 +30,32 @@ def find_prefixed(root, prefix):
25
30
yield x
26
31
27
32
33
+ def extract_suffixees (iter , prefix ):
34
+ p_len = len (prefix )
35
+ for p in iter :
36
+ yield p .name [p_len :]
37
+
38
+
39
+ def find_suffixes (root , prefix ):
40
+ return extract_suffixees (find_prefixed (root , prefix ), prefix )
41
+
42
+
43
+ def parse_num (maybe_num ):
44
+ try :
45
+ return int (maybe_num )
46
+ except ValueError :
47
+ return - 1
48
+
49
+
28
50
def _max (iterable , default ):
29
51
# needed due to python2.7 lacking the default argument for max
30
52
return reduce (max , iterable , default )
31
53
32
54
33
55
def make_numbered_dir (root , prefix ):
34
- def parse_num (p , cut = len (prefix )):
35
- maybe_num = p .name [cut :]
36
- try :
37
- return int (maybe_num )
38
- except ValueError :
39
- return - 1
40
-
41
56
for i in range (10 ):
42
57
# try up to 10 times to create the folder
43
- max_existing = _max (map (parse_num , find_prefixed (root , prefix )), - 1 )
58
+ max_existing = _max (map (parse_num , find_suffixes (root , prefix )), - 1 )
44
59
new_number = max_existing + 1
45
60
new_path = root .joinpath ("{}{}" .format (prefix , new_number ))
46
61
try :
@@ -58,20 +73,29 @@ def parse_num(p, cut=len(prefix)):
58
73
59
74
60
75
def create_cleanup_lock (p ):
61
- lock_path = p .joinpath (".lock" )
62
- fd = os .open (str (lock_path ), os .O_WRONLY | os .O_CREAT | os .O_EXCL , 0o644 )
63
- pid = os .getpid ()
64
- spid = str (pid )
65
- if not isinstance (spid , six .binary_type ):
66
- spid = spid .encode ("ascii" )
67
- os .write (fd , spid )
68
- os .close (fd )
69
- if not lock_path .is_file ():
70
- raise EnvironmentError ("lock path got renamed after sucessfull creation" )
71
- return lock_path
72
-
73
-
74
- def register_cleanup_lock_removal (lock_path ):
76
+ lock_path = get_lock_path (p )
77
+ try :
78
+ fd = os .open (str (lock_path ), os .O_WRONLY | os .O_CREAT | os .O_EXCL , 0o644 )
79
+ except OSError as e :
80
+ if e .errno == errno .EEXIST :
81
+ six .raise_from (
82
+ EnvironmentError ("cannot create lockfile in {path}" .format (path = p )), e
83
+ )
84
+ else :
85
+ raise
86
+ else :
87
+ pid = os .getpid ()
88
+ spid = str (pid )
89
+ if not isinstance (spid , six .binary_type ):
90
+ spid = spid .encode ("ascii" )
91
+ os .write (fd , spid )
92
+ os .close (fd )
93
+ if not lock_path .is_file ():
94
+ raise EnvironmentError ("lock path got renamed after sucessfull creation" )
95
+ return lock_path
96
+
97
+
98
+ def register_cleanup_lock_removal (lock_path , register = atexit .register ):
75
99
pid = os .getpid ()
76
100
77
101
def cleanup_on_exit (lock_path = lock_path , original_pid = pid ):
@@ -84,12 +108,33 @@ def cleanup_on_exit(lock_path=lock_path, original_pid=pid):
84
108
except (OSError , IOError ):
85
109
pass
86
110
87
- return atexit .register (cleanup_on_exit )
111
+ return register (cleanup_on_exit )
112
+
113
+
114
+ def delete_a_numbered_dir (path ):
115
+ create_cleanup_lock (path )
116
+ parent = path .parent
88
117
118
+ garbage = parent .joinpath ("garbage-{}" .format (uuid .uuid4 ()))
119
+ path .rename (garbage )
120
+ shutil .rmtree (str (garbage ))
89
121
90
- def cleanup_numbered_dir (root , prefix , keep ):
91
- # todo
92
- pass
122
+
123
+ def is_deletable (path , consider_lock_dead_after ):
124
+ lock = get_lock_path (path )
125
+ if not lock .exists ():
126
+ return True
127
+
128
+
129
+ def cleanup_numbered_dir (root , prefix , keep , consider_lock_dead_after ):
130
+ max_existing = _max (map (parse_num , find_suffixes (root , prefix )), - 1 )
131
+ max_delete = max_existing - keep
132
+ paths = find_prefixed (root , prefix )
133
+ paths , paths2 = itertools .tee (paths )
134
+ numbers = map (parse_num , extract_suffixees (paths2 , prefix ))
135
+ for path , number in zip (paths , numbers ):
136
+ if number <= max_delete and is_deletable (path , consider_lock_dead_after ):
137
+ delete_a_numbered_dir (path )
93
138
94
139
95
140
def make_numbered_dir_with_cleanup (root , prefix , keep , consider_lock_dead_after ):
@@ -101,7 +146,12 @@ def make_numbered_dir_with_cleanup(root, prefix, keep, consider_lock_dead_after)
101
146
except Exception :
102
147
raise
103
148
else :
104
- cleanup_numbered_dir (root = root , prefix = prefix , keep = keep )
149
+ cleanup_numbered_dir (
150
+ root = root ,
151
+ prefix = prefix ,
152
+ keep = keep ,
153
+ consider_lock_dead_after = consider_lock_dead_after ,
154
+ )
105
155
return p
106
156
107
157
@@ -244,3 +294,8 @@ def tmpdir(request, tmpdir_factory):
244
294
name = name [:MAXVAL ]
245
295
x = tmpdir_factory .mktemp (name , numbered = True )
246
296
return x
297
+
298
+
299
+ @pytest .fixture
300
+ def tmp_path (tmpdir ):
301
+ return Path (tmpdir )
0 commit comments