Skip to content

Commit f7717a5

Browse files
committed
Add function generating an unused directory given a path
The create_unused_dir function distinguishes between the parent directory and the name of the target subdirectory in 2 separate arguments. This interface requires additional parameter management when combined with newer path management interfaces such as pathlib. Add a function create_unused_path accepting the target path as a single argument.
1 parent b37f707 commit f7717a5

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

easybuild/tools/filetools.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2980,3 +2980,43 @@ def create_unused_dir(parent_folder, name):
29802980
set_gid_sticky_bits(path, recursive=True)
29812981

29822982
return path
2983+
2984+
2985+
def get_first_nonexisting_parent(path):
2986+
path = os.path.abspath(path)
2987+
2988+
first_nonexisting_parent = None
2989+
while not os.path.exists(path):
2990+
first_nonexisting_parent = path
2991+
path = os.path.dirname(path)
2992+
2993+
return first_nonexisting_parent
2994+
2995+
2996+
def create_unused_path(path):
2997+
"""
2998+
Create a new directory with a given path, including the parent directories
2999+
When a directory in the same path already exists, then '_0' is appended which is retried for increasing numbers
3000+
until an unused name was found.
3001+
"""
3002+
path = os.path.abspath(path)
3003+
3004+
for number in range(-1, 10000): # Start with no suffix and limit the number of attempts
3005+
if number < 0:
3006+
final_path = path
3007+
first_nonexisting_parent = get_first_nonexisting_parent(path)
3008+
else:
3009+
final_path = path + '_' + str(number)
3010+
first_nonexisting_parent = final_path
3011+
try:
3012+
os.makedirs(final_path)
3013+
break
3014+
except OSError as err:
3015+
# Distinguish between error due to existing folder and anything else
3016+
if not os.path.exists(final_path):
3017+
raise EasyBuildError("Failed to create directory %s: %s", final_path, err)
3018+
3019+
# set group ID and sticky bits, if desired
3020+
set_gid_sticky_bits(first_nonexisting_parent, recursive=True)
3021+
3022+
return final_path

test/framework/filetools.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3522,6 +3522,31 @@ def test_compat_makedirs(self):
35223522
py2vs3.makedirs(name, exist_ok=True) # No error
35233523
self.assertExists(name)
35243524

3525+
def test_get_first_nonexisting_parent(self):
3526+
"""Test get_first_nonexisting_parent function."""
3527+
test_root = os.path.join(self.test_prefix, 'a')
3528+
3529+
base_path = os.path.join(self.test_prefix, 'a')
3530+
target_path = os.path.join(self.test_prefix, 'a', 'b', 'c')
3531+
os.makedirs(base_path)
3532+
first_nonexisting_parent = ft.get_first_nonexisting_parent(target_path)
3533+
self.assertEqual(first_nonexisting_parent, os.path.join(self.test_prefix, 'a', 'b'))
3534+
shutil.rmtree(test_root)
3535+
3536+
base_path = os.path.join(self.test_prefix, 'a', 'b')
3537+
target_path = os.path.join(self.test_prefix, 'a', 'b', 'c')
3538+
os.makedirs(base_path)
3539+
first_nonexisting_parent = ft.get_first_nonexisting_parent(target_path)
3540+
self.assertEqual(first_nonexisting_parent, os.path.join(self.test_prefix, 'a', 'b', 'c'))
3541+
shutil.rmtree(test_root)
3542+
3543+
base_path = os.path.join(self.test_prefix, 'a', 'b', 'c')
3544+
target_path = os.path.join(self.test_prefix, 'a', 'b', 'c')
3545+
os.makedirs(base_path)
3546+
first_nonexisting_parent = ft.get_first_nonexisting_parent(target_path)
3547+
self.assertEqual(first_nonexisting_parent, None)
3548+
shutil.rmtree(test_root)
3549+
35253550
def test_create_unused_dir(self):
35263551
"""Test create_unused_dir function."""
35273552
path = ft.create_unused_dir(self.test_prefix, 'folder')
@@ -3560,6 +3585,55 @@ def test_create_unused_dir(self):
35603585
self.assertEqual(path, os.path.join(self.test_prefix, 'file_0'))
35613586
self.assertExists(path)
35623587

3588+
def test_create_unused_path(self):
3589+
"""Test create_unused_path function."""
3590+
requested_path = os.path.join(self.test_prefix, 'folder')
3591+
path = ft.create_unused_dir(requested_path)
3592+
self.assertEqual(path, requested_path)
3593+
self.assertExists(path)
3594+
3595+
# Repeat with existing folder(s) should create new ones
3596+
for i in range(10):
3597+
requested_path = os.path.join(self.test_prefix, 'folder')
3598+
path = ft.create_unused_dir(requested_path)
3599+
self.assertEqual(path, requested_path + '_%s' % i)
3600+
self.assertExists(path)
3601+
3602+
# Support creation of parent directories
3603+
requested_path = os.path.join(self.test_prefix, 'parent_folder', 'folder')
3604+
path = ft.create_unused_dir(requested_path)
3605+
self.assertEqual(path, requested_path)
3606+
self.assertExists(path)
3607+
3608+
# Not influenced by similar folder
3609+
requested_path = os.path.join(self.test_prefix, 'folder2')
3610+
path = ft.create_unused_dir(requested_path)
3611+
self.assertEqual(path, requested_path)
3612+
self.assertExists(path)
3613+
for i in range(10):
3614+
path = ft.create_unused_dir(requested_path)
3615+
self.assertEqual(path, requested_path + '_%s' % i)
3616+
self.assertExists(path)
3617+
3618+
# Fail cleanly if passed a readonly folder
3619+
readonly_dir = os.path.join(self.test_prefix, 'ro_folder')
3620+
ft.mkdir(readonly_dir)
3621+
old_perms = os.lstat(readonly_dir)[stat.ST_MODE]
3622+
ft.adjust_permissions(readonly_dir, stat.S_IREAD | stat.S_IEXEC, relative=False)
3623+
requested_path = os.path.join(readonly_dir, 'new_folder')
3624+
try:
3625+
self.assertErrorRegex(EasyBuildError, 'Failed to create directory',
3626+
ft.create_unused_dir, requested_path)
3627+
finally:
3628+
ft.adjust_permissions(readonly_dir, old_perms, relative=False)
3629+
3630+
# Ignore files same as folders. So first just create a file with no contents
3631+
requested_path = os.path.join(self.test_prefix, 'file')
3632+
ft.write_file(requested_path, '')
3633+
path = ft.create_unused_dir(requested_path)
3634+
self.assertEqual(path, requested_path + '_0')
3635+
self.assertExists(path)
3636+
35633637

35643638
def suite():
35653639
""" returns all the testcases in this module """

0 commit comments

Comments
 (0)