Skip to content

Commit 68a152a

Browse files
committed
[276] recursive collection put and get
1 parent 232a2d7 commit 68a152a

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-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: 61 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,66 @@
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+
# Python 2 only
29+
from string import maketrans as _maketrans
30+
except:
31+
_maketrans = str.maketrans
32+
33+
_sep2slash = _maketrans(os.path.sep,"/")
34+
_slash2sep = _maketrans("/",os.path.sep)
35+
_from_mswin = (lambda path: str.translate(path,_sep2slash)) if os.path.sep != '/' else (lambda x:x)
36+
_to_mswin = (lambda path: str.translate(path,_slash2sep)) if os.path.sep != '/' else (lambda x:x)
37+
1238
class CollectionManager(Manager):
1339

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