33
44import re
55import os
6+ import errno
67import atexit
7-
8+ import operator
89import six
910from functools import reduce
10-
11+ import uuid
1112from six .moves import map
1213import pytest
1314import py
1617import attr
1718import shutil
1819import tempfile
20+ import itertools
21+
22+
23+ get_lock_path = operator .methodcaller ("joinpath" , ".lock" )
1924
2025
2126def find_prefixed (root , prefix ):
@@ -25,22 +30,32 @@ def find_prefixed(root, prefix):
2530 yield x
2631
2732
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+
2850def _max (iterable , default ):
2951 # needed due to python2.7 lacking the default argument for max
3052 return reduce (max , iterable , default )
3153
3254
3355def 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-
4156 for i in range (10 ):
4257 # 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 )
4459 new_number = max_existing + 1
4560 new_path = root .joinpath ("{}{}" .format (prefix , new_number ))
4661 try :
@@ -58,20 +73,29 @@ def parse_num(p, cut=len(prefix)):
5873
5974
6075def 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 ):
7599 pid = os .getpid ()
76100
77101 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):
84108 except (OSError , IOError ):
85109 pass
86110
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
88117
118+ garbage = parent .joinpath ("garbage-{}" .format (uuid .uuid4 ()))
119+ path .rename (garbage )
120+ shutil .rmtree (str (garbage ))
89121
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 )
93138
94139
95140def 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)
101146 except Exception :
102147 raise
103148 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+ )
105155 return p
106156
107157
@@ -244,3 +294,8 @@ def tmpdir(request, tmpdir_factory):
244294 name = name [:MAXVAL ]
245295 x = tmpdir_factory .mktemp (name , numbered = True )
246296 return x
297+
298+
299+ @pytest .fixture
300+ def tmp_path (tmpdir ):
301+ return Path (tmpdir )
0 commit comments