Skip to content

Commit 4aac7f8

Browse files
committed
[276] recursive collection Put and Get
1 parent 232a2d7 commit 4aac7f8

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

irods/collection.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ def data_objects(self):
4343
for _, replicas in grouped
4444
]
4545

46+
def get_recursive( self , *arg, **kw):
47+
self.manager.get_recursive( self.path, *arg, **kw )
48+
4649
def remove(self, recurse=True, force=False, **options):
4750
self.manager.remove(self.path, recurse, force, **options)
4851

irods/manager/collection_manager.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
from __future__ import absolute_import
2+
import os
3+
import stat
4+
import itertools
25
from irods.models import Collection
36
from irods.manager import Manager
47
from irods.message import iRODSMessage, CollectionRequest, FileOpenRequest, ObjCopyRequest, StringStringMap
@@ -9,8 +12,65 @@
912
import irods.keywords as kw
1013

1114

15+
class GetPathCreationError ( RuntimeError ):
16+
"""Error denoting the failure to create a new directory for writing.
17+
"""
18+
19+
def make_writable_dir_if_none_exists( path ):
20+
if not os.path.exists(path):
21+
os.mkdir(path)
22+
if os.path.isdir( path ):
23+
os.chmod(path, os.stat(path).st_mode | stat.S_IWUSR)
24+
if not os.path.isdir( path ) or not os.access( path, os.W_OK ):
25+
raise GetPathCreationError( '{!r} not a writable directory'.format(path) )
26+
27+
try:
28+
from string import maketrans as _maketrans
29+
except:
30+
_maketrans = str.maketrans
31+
32+
_sep2slash = _maketrans(os.path.sep,"/")
33+
_slash2sep = _maketrans("/",os.path.sep)
34+
_from_mswin = (lambda path: str.translate(path,_sep2slash)) if os.path.sep != '/' else (lambda x:x)
35+
_to_mswin = (lambda path: str.translate(path,_slash2sep)) if os.path.sep != '/' else (lambda x:x)
36+
1237
class CollectionManager(Manager):
1338

39+
def put_recursive (self, localpath, path, abort_if_not_empty = True, **put_options):
40+
c = self.sess.collections.create( path )
41+
w = list(itertools.islice(c.walk(), 0, 2)) # dereference first 1 to 2 elements of the walk
42+
if abort_if_not_empty and (len(w) > 1 or len(w[0][-1]) > 0):
43+
raise RuntimeError('collection {path!r} exists and is non-empty'.format(**locals()))
44+
localpath = os.path.normpath(localpath)
45+
for my_dir,sub_dirs,sub_files in os.walk(localpath,topdown=True):
46+
dir_without_prefix = os.path.relpath( my_dir, localpath )
47+
subcoll = self.sess.collections.create(path if dir_without_prefix == os.path.curdir
48+
else path + "/" + _from_mswin(dir_without_prefix))
49+
for file_ in sub_files:
50+
self.sess.data_objects.put( os.path.join(my_dir,file_), subcoll.path + "/" + file_, **put_options)
51+
52+
53+
def get_recursive (self, path, localpath, abort_if_not_empty = True, **get_options):
54+
if os.path.isdir(localpath):
55+
w = list(itertools.islice(os.walk(localpath), 0, 2))
56+
if abort_if_not_empty and (len(w) > 1 or len(w[0][-1]) > 0):
57+
raise RuntimeError('local directory {localpath!r} exists and is non-empty'.format(**locals()))
58+
def unprefix (path,prefix=''):
59+
return path if not path.startswith(prefix) else path[len(prefix):]
60+
c = self.get(path)
61+
# TODO ## For a visible percent-complete status:
62+
# # nbytes = sum(d.size for el in c.walk() for d in el[2])
63+
# ## (Then use eg tqdm module to create progress-bar.)
64+
c_prefix = c.path + "/"
65+
for coll,sub_colls,sub_datas in c.walk(topdown=True):
66+
relative_collpath = unprefix (coll.path + "/", c_prefix)
67+
new_target_dir = os.path.join(localpath, _to_mswin(relative_collpath))
68+
make_writable_dir_if_none_exists( new_target_dir )
69+
for data in sub_datas:
70+
local_data_path = os.path.join(new_target_dir, data.name)
71+
self.sess.data_objects.get( data.path, local_data_path, **get_options )
72+
73+
1474
def get(self, path):
1575
query = self.sess.query(Collection).filter(Collection.name == path)
1676
try:

irods/test/collection_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,35 @@ def test_create_recursive_collection(self):
6565
with self.assertRaises(CollectionDoesNotExist):
6666
self.sess.collections.get(root_coll_path)
6767

68+
69+
def test_recursive_collection_get_and_put_276(self):
70+
try:
71+
test_dir = '/tmp/testdir_276'
72+
root_coll_path = self.test_coll_path + "/my_deep_collection_276"
73+
Depth = 5
74+
Objs_per_level = 2
75+
Content = 'hello-world'
76+
helpers.make_deep_collection(
77+
self.sess, root_coll_path, depth=Depth, objects_per_level=Objs_per_level, object_content=Content)
78+
79+
self.sess.collections.get_recursive(root_coll_path, test_dir)
80+
self.sess.collections.put_recursive(test_dir,root_coll_path+"_2")
81+
summary = [(elem[0].path,elem[2]) for elem in self.sess.collections.get(root_coll_path+"_2").walk(topdown=True)]
82+
83+
# check final destination for objects' expected content
84+
obj_payloads = [d.open('r').read() for cpath,datas in summary for d in datas]
85+
self.assertEqual(obj_payloads, [Content] * (Depth * Objs_per_level))
86+
87+
# check for increase by 1 in successive collection depths
88+
coll_depths = [elem[0].count('/') for elem in summary]
89+
self.assertEqual(list(range(len(coll_depths))), [depth - coll_depths[0] for depth in coll_depths])
90+
finally:
91+
for c in root_coll_path , root_coll_path + "_2":
92+
if self.sess.collections.exists(c):
93+
self.sess.collections.remove(c, force = True)
94+
shutil.rmtree(test_dir,ignore_errors = True)
95+
96+
6897
def test_remove_deep_collection(self):
6998
# depth = 100
7099
depth = 20 # placeholder

0 commit comments

Comments
 (0)