Skip to content

Commit c93e169

Browse files
authored
Merge pull request #306 from Duke-GCB/287-sdk-create-folder
add sdk create_folder function
2 parents 397c28f + a528fd2 commit c93e169

5 files changed

Lines changed: 109 additions & 44 deletions

File tree

DukeDS/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
list_projects = DukeDS.list_projects
33
create_project = DukeDS.create_project
44
delete_project = DukeDS.delete_project
5+
create_folder = DukeDS.create_folder
56
list_files = DukeDS.list_files
67
download_file = DukeDS.download_file
78
upload_file = DukeDS.upload_file
@@ -11,6 +12,7 @@
1112
can_deliver_to_user_with_username = DukeDS.can_deliver_to_user_with_username
1213

1314
__all__ = ['list_projects', 'create_project', 'delete_project',
15+
'create_folder',
1416
'list_files', 'download_file', 'upload_file', 'delete_file',
1517
'move_file_or_folder',
1618
'can_deliver_to_user_with_email', 'can_deliver_to_user_with_username']

ddsc/sdk/client.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -333,21 +333,15 @@ def get_child_for_path(self, path):
333333
:param path: str: path within a project specifying a file or folder to download
334334
:return: File|Folder
335335
"""
336-
child_finder = ChildFinder(path, self)
337-
return child_finder.get_child()
336+
return ChildFinder.get_child_for_path(self, path)
338337

339338
def try_get_item_for_path(self, path):
340339
"""
341340
Based on a remote path get a single remote child. When not found returns None.
342341
:param path: str: path within a project specifying a file or folder to download
343342
:return: File|Folder|Project|None
344343
"""
345-
try:
346-
if path == REMOTE_PATH_SEP:
347-
return self
348-
return self.get_child_for_path(path)
349-
except ItemNotFound:
350-
return None
344+
return ChildFinder.try_get_item_for_path(self, path)
351345

352346
def create_folder(self, folder_name):
353347
"""
@@ -471,6 +465,22 @@ def create_folder(self, folder_name):
471465
"""
472466
return self.dds_connection.create_folder(folder_name, KindType.folder_str, self.id)
473467

468+
def get_child_for_path(self, path):
469+
"""
470+
Based on a remote path get a single remote child. When not found raises ItemNotFound.
471+
:param path: str: path within a project specifying a file or folder to download
472+
:return: File|Folder
473+
"""
474+
return ChildFinder.get_child_for_path(self, path)
475+
476+
def try_get_item_for_path(self, path):
477+
"""
478+
Based on a remote path get a single remote child. When not found returns None.
479+
:param path: str: path within a project specifying a file or folder to download
480+
:return: File|Folder|Project|None
481+
"""
482+
return ChildFinder.try_get_item_for_path(self, path)
483+
474484
def upload_file(self, local_path, remote_filename=None):
475485
"""
476486
Upload a new file based on a file on the file system as a top level child of this folder.
@@ -626,13 +636,13 @@ class ChildFinder(object):
626636
"""
627637
Recursively looks for a child based on a path
628638
"""
629-
def __init__(self, remote_path, node):
639+
def __init__(self, node, remote_path):
630640
"""
631-
:param remote_path: path under a project in DDSConnection
632641
:param node: Project|Folder to find children under
642+
:param remote_path: path under a project in DDSConnection
633643
"""
634-
self.remote_path = remote_path
635644
self.node = node
645+
self.remote_path = remote_path
636646

637647
def get_child(self):
638648
"""
@@ -651,6 +661,30 @@ def _get_child_recurse(self, path_parts, node):
651661
return self._get_child_recurse(tail, child)
652662
raise ItemNotFound("No item at path {}".format(self.remote_path))
653663

664+
@staticmethod
665+
def get_child_for_path(node, path):
666+
"""
667+
Based on a remote path get a single remote child. When not found raises ItemNotFound.
668+
:param path: str: path within a project specifying a file or folder to download
669+
:return: File|Folder
670+
"""
671+
child_finder = ChildFinder(node, path)
672+
return child_finder.get_child()
673+
674+
@staticmethod
675+
def try_get_item_for_path(node, path):
676+
"""
677+
Based on a remote path get a single remote child. When not found returns None.
678+
:param path: str: path within a project specifying a file or folder to download
679+
:return: File|Folder|Project|None
680+
"""
681+
try:
682+
if path == REMOTE_PATH_SEP:
683+
return node
684+
return ChildFinder.get_child_for_path(node, path)
685+
except ItemNotFound:
686+
return None
687+
654688

655689
class PathToFiles(object):
656690
def __init__(self):

ddsc/sdk/dukeds.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import absolute_import
22
import logging
3-
from ddsc.sdk.client import Client, FileUpload, PathToFiles, ItemNotFound, DuplicateNameError
3+
from ddsc.sdk.client import Client, FileUpload, PathToFiles, ItemNotFound, DuplicateNameError, RemotePath
44
from ddsc.core.userutil import UserUtil
55

66

@@ -35,6 +35,15 @@ def delete_project(project_name):
3535
"""
3636
Session().delete_project(project_name)
3737

38+
@staticmethod
39+
def create_folder(project_name, remote_path):
40+
"""
41+
Create a folder and any necessary parent folders for the specified path.
42+
:param project_name: str: name of the project to create a folder in
43+
:param remote_path: str: remote path specifying which folder(s) to create
44+
"""
45+
return Session().create_folder(project_name, remote_path)
46+
3847
@staticmethod
3948
def list_files(project_name):
4049
"""
@@ -151,6 +160,21 @@ def delete_project(self, project_name):
151160
project.delete()
152161
self.clear_project_cache()
153162

163+
def create_folder(self, project_name, remote_path):
164+
"""
165+
Create a folder and any necessary parent folders for the specified path.
166+
:param project_name: str: name of the project to create a folder in
167+
:param remote_path: str: remote path specifying which folder(s) to create
168+
"""
169+
project = self._get_project_for_name(project_name)
170+
remote_path_parts = RemotePath.split(remote_path)
171+
parent = project
172+
for path_part in remote_path_parts:
173+
parent_folder = parent.try_get_item_for_path(path_part)
174+
if not parent_folder:
175+
parent_folder = parent.create_folder(path_part)
176+
parent = parent_folder
177+
154178
def list_files(self, project_name):
155179
"""
156180
Return a list of file paths that make up project_name

ddsc/sdk/tests/test_client.py

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from unittest import TestCase
22
from ddsc.sdk.client import Client, DDSConnection, BaseResponseItem, Project, Folder, File, FileDownload, FileUpload, \
3-
ChildFinder, PathToFiles, ItemNotFound, ProjectSummary
3+
ChildFinder, PathToFiles, ItemNotFound, ProjectSummary, REMOTE_PATH_SEP
44
from ddsc.core.util import KindType
55
from mock import patch, Mock, call
66

@@ -472,40 +472,14 @@ def test_get_children(self):
472472
def test_get_child_for_path(self, mock_child_finder):
473473
mock_dds_connection = Mock()
474474
mock_child = Mock()
475-
mock_child_finder.return_value.get_child.return_value = mock_child
475+
mock_child_finder.get_child_for_path.return_value = mock_child
476476

477477
project = Project(mock_dds_connection, self.project_dict)
478478
child = project.get_child_for_path('/data/file1.dat')
479479

480-
mock_child_finder.assert_called_with('/data/file1.dat', project)
480+
mock_child_finder.get_child_for_path.assert_called_with(project, '/data/file1.dat')
481481
self.assertEqual(child, mock_child)
482482

483-
@patch('ddsc.sdk.client.ChildFinder')
484-
def test_try_get_item_for_path__with_project(self, mock_child_finder):
485-
mock_dds_connection = Mock()
486-
project = Project(mock_dds_connection, self.project_dict)
487-
project.get_child_for_path = Mock()
488-
item = project.try_get_item_for_path('/')
489-
self.assertEqual(item, project)
490-
project.get_child_for_path.assert_not_called()
491-
492-
def test_try_get_item_for_path__with_child(self):
493-
mock_dds_connection = Mock()
494-
project = Project(mock_dds_connection, self.project_dict)
495-
project.get_child_for_path = Mock()
496-
item = project.try_get_item_for_path('/data/file1.dat')
497-
self.assertEqual(item, project.get_child_for_path.return_value)
498-
project.get_child_for_path.assert_called_with('/data/file1.dat')
499-
500-
def test_try_get_item_for_path__child_not_found(self):
501-
mock_dds_connection = Mock()
502-
project = Project(mock_dds_connection, self.project_dict)
503-
project.get_child_for_path = Mock()
504-
project.get_child_for_path.side_effect = ItemNotFound("Not Found")
505-
item = project.try_get_item_for_path('/data/file1.dat')
506-
self.assertEqual(item, None)
507-
project.get_child_for_path.assert_called_with('/data/file1.dat')
508-
509483
def test_create_folder(self):
510484
mock_dds_connection = Mock()
511485
mock_folder = Mock()
@@ -764,7 +738,7 @@ def test_direct_children(self):
764738
mock_folder,
765739
mock_file
766740
]
767-
child_finder = ChildFinder('data.txt', mock_project)
741+
child_finder = ChildFinder(mock_project, 'data.txt')
768742
found_child = child_finder.get_child()
769743
self.assertEqual(found_child, mock_file)
770744

@@ -780,7 +754,7 @@ def test_grand_children(self):
780754
mock_project.get_children.return_value = [
781755
mock_folder,
782756
]
783-
child_finder = ChildFinder('results/data.txt', mock_project)
757+
child_finder = ChildFinder(mock_project, 'results/data.txt')
784758
found_child = child_finder.get_child()
785759
self.assertEqual(found_child, mock_file)
786760

@@ -790,10 +764,28 @@ def test_child_not_found(self):
790764
mock_project.get_children.return_value = [
791765
mock_folder,
792766
]
793-
child_finder = ChildFinder('data.txt', mock_project)
767+
child_finder = ChildFinder(mock_project, 'data.txt')
794768
with self.assertRaises(ItemNotFound):
795769
child_finder.get_child()
796770

771+
def test_try_get_item_for_path_with_slash(self):
772+
node = Mock()
773+
result = ChildFinder.try_get_item_for_path(node, REMOTE_PATH_SEP)
774+
self.assertEqual(result, node)
775+
776+
@patch('ddsc.sdk.client.ChildFinder.get_child_for_path')
777+
def test_try_get_item_for_path_with_child_found(self, mock_get_child_for_path):
778+
node = Mock()
779+
result = ChildFinder.try_get_item_for_path(node, '/file.txt')
780+
self.assertEqual(result, mock_get_child_for_path.return_value)
781+
782+
@patch('ddsc.sdk.client.ChildFinder.get_child_for_path')
783+
def test_try_get_item_for_path_with_child_not_found(self, mock_get_child_for_path):
784+
mock_get_child_for_path.side_effect = ItemNotFound()
785+
node = Mock()
786+
result = ChildFinder.try_get_item_for_path(node, '/file.txt')
787+
self.assertEqual(result, None)
788+
797789

798790
class TestPathToFiles(TestCase):
799791
def test_path_creation(self):

ddsc/sdk/tests/test_dukeds.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,19 @@ def test_move_file_or_folder(self, mock_client):
205205
DukeDS.move_file_or_folder('myproject', '/data/file1.txt', '/data/file1_bak.txt')
206206
mock_project.move_file_or_folder.assert_called_with('/data/file1.txt', '/data/file1_bak.txt')
207207

208+
@patch('ddsc.sdk.dukeds.Client')
209+
def test_create_folder(self, mock_client):
210+
mock_project = Mock()
211+
mock_project.try_get_item_for_path.return_value = None
212+
mock_project.create_folder.return_value.try_get_item_for_path.return_value = None
213+
mock_project.name = 'myproject'
214+
mock_client.return_value.get_projects.return_value = [
215+
mock_project
216+
]
217+
DukeDS.create_folder('myproject', '/data/raw')
218+
mock_project.create_folder.assert_called_with("data")
219+
mock_project.create_folder.return_value.create_folder.assert_called_with("raw")
220+
208221

209222
class TestSession(TestCase):
210223
@patch('ddsc.sdk.dukeds.Client', autospec=True)

0 commit comments

Comments
 (0)