diff --git a/DukeDS/__init__.py b/DukeDS/__init__.py
index a3104a8..2a62a65 100644
--- a/DukeDS/__init__.py
+++ b/DukeDS/__init__.py
@@ -6,6 +6,9 @@ list_files = DukeDS.list_files
 download_file = DukeDS.download_file
 upload_file = DukeDS.upload_file
 delete_file = DukeDS.delete_file
+can_deliver_to_user_with_email = DukeDS.can_deliver_to_user_with_email
+can_deliver_to_user_with_username = DukeDS.can_deliver_to_user_with_username
 
 __all__ = ['list_projects', 'create_project', 'delete_project',
-           'list_files', 'download_file', 'upload_file', 'delete_file']
+           'list_files', 'download_file', 'upload_file', 'delete_file',
+           'can_deliver_to_user_with_email', 'can_deliver_to_user_with_username']
diff --git a/ddsc/core/ddsapi.py b/ddsc/core/ddsapi.py
index d0dd066..56bba2f 100644
--- a/ddsc/core/ddsapi.py
+++ b/ddsc/core/ddsapi.py
@@ -972,6 +972,18 @@ class DataServiceApi(object):
         """
         return self._get_single_item('/auth_providers/{}/'.format(auth_provider_id), {})
 
+    def get_default_auth_provider_id(self):
+        """
+        Get default auth provider.
+        :return: str: uuid of auth provider
+        """
+        response = self.get_auth_providers()
+        auth_providers = response.json()["results"]
+        for auth_provider in auth_providers:
+            if auth_provider["is_default"]:
+                return auth_provider["id"]
+        raise ValueError("Unable to find a default DukeDS auth provider.")
+
     def get_auth_provider_affiliates(self, auth_provider_id, full_name_contains=None, email=None, username=None):
         """
         List affiliates for a specific auth provider.
diff --git a/ddsc/core/userutil.py b/ddsc/core/userutil.py
new file mode 100644
index 0000000..d210c2d
--- /dev/null
+++ b/ddsc/core/userutil.py
@@ -0,0 +1,59 @@
+import logging
+
+DUKE_EMAIL_SUFFIX = "@duke.edu"
+
+
+class UserUtil(object):
+    def __init__(self, data_service, logging_func=logging.info):
+        self.data_service = data_service
+        self.auth_provider_id = data_service.get_default_auth_provider_id()
+        self.logging_func = logging_func
+
+    def find_user_by_username(self, username):
+        response = self.data_service.get_users(username=username)
+        return self._get_single_user_or_none(response, username)
+
+    def register_user_by_username(self, username):
+        response = self.data_service.auth_provider_add_user(self.auth_provider_id, username)
+        return response.json()
+
+    def find_user_by_email(self, email_address):
+        response = self.data_service.get_users(email=email_address)
+        return self._get_single_user_or_none(response, email_address)
+
+    def find_affiliate_by_email(self, email_address):
+        response = self.data_service.get_auth_provider_affiliates(self.auth_provider_id, email=email_address)
+        return self._get_single_user_or_none(response, email_address)
+
+    def find_affiliate_by_username(self, username):
+        response = self.data_service.get_auth_provider_affiliates(self.auth_provider_id, username=username)
+        return self._get_single_user_or_none(response, username)
+
+    def user_or_affiliate_exists_for_email(self, email_address):
+        if self.find_user_by_email(email_address):
+            self.logging_func("Found DukeDS user for email address {}.".format(email_address))
+            return True
+        if self.find_affiliate_by_email(email_address):
+            self.logging_func("Found affiliate for email address {}.".format(email_address))
+            return True
+        self.logging_func("No valid DukeDS user or affiliate found for email address {}.".format(email_address))
+        return False
+
+    def user_or_affiliate_exists_for_username(self, username):
+        if self.find_user_by_username(username):
+            self.logging_func("Found DukeDS user for username {}.".format(username))
+            return True
+        if self.find_affiliate_by_username(username):
+            self.logging_func("Found affiliate for username {}.".format(username))
+            return True
+        self.logging_func("No valid DukeDS user or affiliate found for username {}.".format(username))
+        return False
+
+    @staticmethod
+    def _get_single_user_or_none(response, lookup_value):
+        results = response.json()['results']
+        if not results:
+            return None
+        if len(results) == 1:
+            return results[0]
+        raise ValueError("Found multiple users for {}.".format(lookup_value))
diff --git a/ddsc/sdk/dukeds.py b/ddsc/sdk/dukeds.py
index 45fc425..ca42feb 100644
--- a/ddsc/sdk/dukeds.py
+++ b/ddsc/sdk/dukeds.py
@@ -1,6 +1,8 @@
 from __future__ import absolute_import
+import logging
 from ddsc.sdk.client import Client, FileUpload, PathToFiles, ItemNotFound, DuplicateNameError
 from ddsc.config import create_config
+from ddsc.core.userutil import UserUtil
 
 
 class DukeDS(object):
@@ -74,6 +76,26 @@ class DukeDS(object):
         """
         Session().delete_file(project_name, remote_path)
 
+    @staticmethod
+    def can_deliver_to_user_with_email(email_address, logging_func=logging.info):
+        """
+        Determine if we can deliver a project to a user
+        :param email_address: str: email address to lookup
+        :param logging_func: func(str): function that will receive log messages
+        :return: boolean: True if the specified user can receive deliveries
+        """
+        return Session().can_deliver_to_user_with_email(email_address, logging_func)
+
+    @staticmethod
+    def can_deliver_to_user_with_username(username, logging_func=logging.info):
+        """
+        Determine if we can deliver a project to a user
+        :param username: str: username to lookup
+        :param logging_func: func(str): function that will receive log messages
+        :return: boolean: True if the specified user can receive deliveries
+        """
+        return Session().can_deliver_to_user_with_username(username, logging_func)
+
 
 class Session(object):
     """
@@ -196,3 +218,13 @@ class Session(object):
         project = self._get_or_create_project(project_name)
         remote_file = project.get_child_for_path(remote_path)
         remote_file.delete()
+
+    def can_deliver_to_user_with_email(self, email_address, logging_func):
+        data_service = self.client.dds_connection.data_service
+        dds_user_util = UserUtil(data_service, logging_func=logging_func)
+        return dds_user_util.user_or_affiliate_exists_for_email(email_address)
+
+    def can_deliver_to_user_with_username(self, username, logging_func):
+        data_service = self.client.dds_connection.data_service
+        dds_user_util = UserUtil(data_service, logging_func=logging_func)
+        return dds_user_util.user_or_affiliate_exists_for_username(username)

Make use of the improved affiliate DukeDS search
DukeDS requires users to be registered with DukeDS internal user list for them to receive project permissions or deliver a project to them. Currently DukeDSClient only registers users with DukeDS if we receive a username(Netid). Recent changes allow searching for affiliates based on their email address: https://api.dataservice.duke.edu/apiexplorer#!/auth_providers/getApiV1AuthProviders
We can use this to determine a Netid for a user's email address and register them.

Note this is similar to #209 but for affiliates and not DukeDS users.
diff --git a/ddsc/core/remotestore.py b/ddsc/core/remotestore.py
index 38668b0..d717cde 100644
--- a/ddsc/core/remotestore.py
+++ b/ddsc/core/remotestore.py
@@ -2,6 +2,7 @@ import os
 from ddsc.core.ddsapi import DataServiceApi, DataServiceError, DataServiceAuth
 from ddsc.core.util import KindType
 from ddsc.core.localstore import HashUtil
+from ddsc.core.userutil import UserUtil
 
 FETCH_ALL_USERS_PAGE_SIZE = 25
 DOWNLOAD_FILE_CHUNK_SIZE = 20 * 1024 * 1024
@@ -86,7 +87,7 @@ class RemoteStore(object):
     def lookup_or_register_user_by_email_or_username(self, email, username):
         """
         Lookup user by email or username. Only fill in one field.
-        For username it will try to register if not found.
+        For both cases it will try to register if not found.
         :param email: str: email address of the user
         :param username: netid of the user to find
         :return: RemoteUser
@@ -94,8 +95,7 @@ class RemoteStore(object):
         if username:
             return self.get_or_register_user_by_username(username)
         else:
-            # API doesn't support registering user by email address yet
-            return self.lookup_user_by_email(email)
+            return self.get_or_register_user_by_email(email)
 
     def lookup_user_by_name(self, full_name):
         """
@@ -116,51 +116,33 @@ class RemoteStore(object):
             raise NotFoundError("User not found:" + full_name)
         return user
 
-    def lookup_user_by_username(self, username):
-        """
-        Finds the single user who has this username or raises ValueError.
-        :param username: str username we are looking for
-        :return: RemoteUser: user we found
-        """
-        matches = self.fetch_users(username=username)
-        if not matches:
-            raise NotFoundError('Username not found: {}.'.format(username))
-        if len(matches) > 1:
-            raise ValueError('Multiple users with same username found: {}.'.format(username))
-        return matches[0]
-
     def get_or_register_user_by_username(self, username):
         """
         Try to lookup user by username. If not found try registering the user.
         :param username: str: username to lookup
         :return: RemoteUser: user we found
         """
-        try:
-            return self.lookup_user_by_username(username)
-        except NotFoundError:
-            return self.register_user_by_username(username)
-
-    def register_user_by_username(self, username):
-        """
-        Tries to register user with the first non-deprecated auth provider.
-        Raises ValueError if the data service doesn't have any non-deprecated providers.
-        :param username: str: netid of the user we are trying to register
-        :return: RemoteUser: user that was created for our netid
-        """
-        current_providers = [prov.id for prov in self.get_auth_providers() if not prov.is_deprecated]
-        if not current_providers:
-            raise ValueError("Unable to register user: no non-deprecated providers found!")
-        auth_provider_id = current_providers[0]
-        return self._register_user_by_username(auth_provider_id, username)
+        util = UserUtil(self.data_service)
+        user_json = util.find_user_by_username(username)
+        if not user_json:
+            user_json = util.register_user_by_username(username)
+        return RemoteUser(user_json)
 
-    def _register_user_by_username(self, auth_provider_id, username):
+    def get_or_register_user_by_email(self, email):
         """
-        Tries to register a user who has a valid netid but isn't registered with DukeDS yet under auth_provider_id.
-        :param auth_provider_id: str: id from RemoteAuthProvider to use for registering
-        :param username: str: netid of the user we are trying to register
-        :return: RemoteUser: user that was created for our netid
+        Try to lookup user by email. If not found try registering the user.
+        Raises ValueError when unable to find/register a user for the email.
+        :param email: str: email to lookup or register a user for
+        :return: RemoteUser: user we found
         """
-        user_json = self.data_service.auth_provider_add_user(auth_provider_id, username).json()
+        util = UserUtil(self.data_service)
+        user_json = util.find_user_by_email(email)
+        if not user_json:
+            affiliate = util.find_affiliate_by_email(email)
+            if affiliate:
+                user_json = util.register_user_by_username(affiliate['uid'])
+            else:
+                raise ValueError("Unable to find or register a user with email {}".format(email))
         return RemoteUser(user_json)
 
     def get_auth_providers(self):
@@ -174,19 +156,6 @@ class RemoteStore(object):
             providers.append(RemoteAuthProvider(data))
         return providers
 
-    def lookup_user_by_email(self, email):
-        """
-        Finds the single user who has this email or raises ValueError.
-        :param email: str email we are looking for
-        :return: RemoteUser user we found
-        """
-        matches = self.fetch_users(email=email)
-        if not matches:
-            raise NotFoundError('Email not found: {}.'.format(email))
-        if len(matches) > 1:
-            raise ValueError('Multiple users with same email found: {}.'.format(email))
-        return matches[0]
-
     def get_current_user(self):
         """
         Fetch info about the current user
diff --git a/ddsc/core/tests/test_ddsapi.py b/ddsc/core/tests/test_ddsapi.py
index 1f093d5..1355bac 100644
--- a/ddsc/core/tests/test_ddsapi.py
+++ b/ddsc/core/tests/test_ddsapi.py
@@ -247,6 +247,35 @@ class TestDataServiceApi(TestCase):
         mock_requests.get.assert_called_with('something.com/v1/auth_providers/provider_id_123/',
                                              headers=ANY, params=ANY)
 
+    def test_get_default_auth_provider_id(self):
+        mock_requests = MagicMock()
+        mock_requests.get.side_effect = [
+            fake_response(status_code=200, json_return_value={"results": [
+                {"id": "456", "is_default": False},
+                {"id": "123", "is_default": True},
+            ]})
+        ]
+        api = DataServiceApi(auth=self.create_mock_auth(config_page_size=100), url="something.com/v1",
+                             http=mock_requests)
+        provider_id = api.get_default_auth_provider_id()
+        self.assertEqual('123', provider_id)
+        mock_requests.get.assert_called_with('something.com/v1/auth_providers', headers=ANY, params=ANY)
+
+    def test_get_default_auth_provider_id_no_default_provider(self):
+        mock_requests = MagicMock()
+        mock_requests.get.side_effect = [
+            fake_response(status_code=200, json_return_value={"results": [
+                {"id": "456", "is_default": False},
+                {"id": "123", "is_default": False},
+            ]})
+        ]
+        api = DataServiceApi(auth=self.create_mock_auth(config_page_size=100), url="something.com/v1",
+                             http=mock_requests)
+        with self.assertRaises(ValueError) as raised_exception:
+            api.get_default_auth_provider_id()
+        self.assertEqual(str(raised_exception.exception), 'Unable to find a default DukeDS auth provider.')
+        mock_requests.get.assert_called_with('something.com/v1/auth_providers', headers=ANY, params=ANY)
+
     def make_mock_requests_for_auth_provider_affiliates(self):
         user = {
             "id": "abc4e9-9987-47eb-bb4e-19f0203efbf6",
diff --git a/ddsc/core/tests/test_remotestore.py b/ddsc/core/tests/test_remotestore.py
index eb3a7c8..60c3a1f 100644
--- a/ddsc/core/tests/test_remotestore.py
+++ b/ddsc/core/tests/test_remotestore.py
@@ -9,7 +9,7 @@ from ddsc.core.remotestore import RemoteAuthRole
 from ddsc.core.remotestore import RemoteProjectChildren
 from ddsc.core.remotestore import RemoteAuthProvider
 from ddsc.core.remotestore import ProjectNameOrId
-from ddsc.core.remotestore import ProjectFile, RemoteFileUrl, NotFoundError
+from ddsc.core.remotestore import ProjectFile, RemoteFileUrl
 
 
 class TestProjectFolderFile(TestCase):
@@ -559,43 +559,90 @@ class TestRemoteStore(TestCase):
         self.assertEqual(users[0], mock_remote_user.return_value)
         mock_remote_user.assert_called_with(user_dict)
 
-    def test_lookup_user_by_username(self):
-        mock_user = Mock()
-        mock_user2 = Mock()
+    def test_lookup_or_register_user_by_email_or_username_with_username(self):
         remote_store = RemoteStore(config=MagicMock())
-        remote_store.fetch_users = Mock()
-
-        remote_store.fetch_users.return_value = []
-        with self.assertRaises(NotFoundError) as raised_exception:
-            remote_store.lookup_user_by_username('joe')
-        self.assertEqual(str(raised_exception.exception), 'Username not found: joe.')
-
-        remote_store.fetch_users.return_value = [mock_user]
-        self.assertEqual(remote_store.lookup_user_by_username('joe'), mock_user)
+        remote_store.get_or_register_user_by_username = Mock()
+        remote_store.get_or_register_user_by_email = Mock()
+        result = remote_store.lookup_or_register_user_by_email_or_username(email=None, username="myuser")
+        self.assertEqual(result, remote_store.get_or_register_user_by_username.return_value)
+        remote_store.get_or_register_user_by_username.assert_called_with("myuser")
+        remote_store.get_or_register_user_by_email.assert_not_called()
+
+    def test_lookup_or_register_user_by_email_or_username_with_email(self):
+        remote_store = RemoteStore(config=MagicMock())
+        remote_store.get_or_register_user_by_username = Mock()
+        remote_store.get_or_register_user_by_email = Mock()
+        result = remote_store.lookup_or_register_user_by_email_or_username(email="fakemail@duke.edu", username=None)
+        self.assertEqual(result, remote_store.get_or_register_user_by_email.return_value)
+        remote_store.get_or_register_user_by_username.assert_not_called()
+        remote_store.get_or_register_user_by_email.assert_called_with("fakemail@duke.edu")
+
+    @patch('ddsc.core.remotestore.UserUtil', autospec=True)
+    @patch('ddsc.core.remotestore.RemoteUser')
+    def test_get_or_register_user_by_username__finds_user(self, mock_remote_user, mock_user_util):
+        mock_user_util.return_value.find_user_by_username.return_value = {"id": "123"}
 
-        remote_store.fetch_users.return_value = [mock_user, mock_user2]
-        with self.assertRaises(ValueError) as raised_exception:
-            remote_store.lookup_user_by_username('joe')
-        self.assertEqual(str(raised_exception.exception), 'Multiple users with same username found: joe.')
+        remote_store = RemoteStore(config=MagicMock())
+        result = remote_store.get_or_register_user_by_username("user123")
+        self.assertEqual(result, mock_remote_user.return_value)
+        mock_remote_user.assert_called_with({"id": "123"})
+        mock_util = mock_user_util.return_value
+        mock_util.find_user_by_username.assert_called_with("user123")
+        mock_util.register_user_by_username.assert_not_called()
+
+    @patch('ddsc.core.remotestore.UserUtil', autospec=True)
+    @patch('ddsc.core.remotestore.RemoteUser')
+    def test_get_or_register_user_by_username__registers_user(self, mock_remote_user, mock_user_util):
+        mock_user_util.return_value.find_user_by_username.return_value = None
+        mock_user_util.return_value.register_user_by_username.return_value = {"id": "456"}
 
-    def test_lookup_user_by_email(self):
-        mock_user = Mock()
-        mock_user2 = Mock()
         remote_store = RemoteStore(config=MagicMock())
-        remote_store.fetch_users = Mock()
+        result = remote_store.get_or_register_user_by_username("user123")
+        self.assertEqual(result, mock_remote_user.return_value)
+        mock_remote_user.assert_called_with({"id": "456"})
+        mock_util = mock_user_util.return_value
+        mock_util.find_user_by_username.assert_called_with("user123")
+        mock_util.register_user_by_username.assert_called_with("user123")
+
+    @patch('ddsc.core.remotestore.UserUtil', autospec=True)
+    @patch('ddsc.core.remotestore.RemoteUser')
+    def test_get_or_register_user_by_email__finds_user_by_email(self, mock_remote_user, mock_user_util):
+        mock_user_util.return_value.find_user_by_email.return_value = {"id": "123"}
 
-        remote_store.fetch_users.return_value = []
-        with self.assertRaises(NotFoundError) as raised_exception:
-            remote_store.lookup_user_by_email('joe@joe.com')
-        self.assertEqual(str(raised_exception.exception), 'Email not found: joe@joe.com.')
+        remote_store = RemoteStore(config=MagicMock())
+        result = remote_store.get_or_register_user_by_email("user@user.user")
+        self.assertEqual(result, mock_remote_user.return_value)
+        mock_remote_user.assert_called_with({"id": "123"})
+        mock_util = mock_user_util.return_value
+        mock_util.find_user_by_email.assert_called_with("user@user.user")
+        mock_util.register_user_by_username.assert_not_called()
+
+    @patch('ddsc.core.remotestore.UserUtil', autospec=True)
+    @patch('ddsc.core.remotestore.RemoteUser')
+    def test_get_or_register_user_by_email__finds_affiliate_by_email(self, mock_remote_user, mock_user_util):
+        mock_user_util.return_value.find_user_by_email.return_value = None
+        mock_user_util.return_value.find_affiliate_by_email.return_value = {"uid": "user"}
 
-        remote_store.fetch_users.return_value = [mock_user]
-        self.assertEqual(remote_store.lookup_user_by_email('joe@joe.com'), mock_user)
+        remote_store = RemoteStore(config=MagicMock())
+        result = remote_store.get_or_register_user_by_email("user@user.user")
+        self.assertEqual(result, mock_remote_user.return_value)
+        mock_remote_user.assert_called_with(mock_user_util.return_value.register_user_by_username.return_value)
+        mock_util = mock_user_util.return_value
+        mock_util.find_user_by_email.assert_called_with("user@user.user")
+        mock_util.register_user_by_username.assert_called_with("user")
+
+    @patch('ddsc.core.remotestore.UserUtil', autospec=True)
+    @patch('ddsc.core.remotestore.RemoteUser')
+    def test_get_or_register_user_by_email__unable_to_find_user(self, mock_remote_user, mock_user_util):
+        mock_user_util.return_value.find_user_by_email.return_value = None
+        mock_user_util.return_value.find_affiliate_by_email.return_value = None
 
-        remote_store.fetch_users.return_value = [mock_user, mock_user2]
+        remote_store = RemoteStore(config=MagicMock())
         with self.assertRaises(ValueError) as raised_exception:
-            remote_store.lookup_user_by_email('joe@joe.com')
-        self.assertEqual(str(raised_exception.exception), 'Multiple users with same email found: joe@joe.com.')
+            remote_store.get_or_register_user_by_email("user@user.user")
+        self.assertEqual(str(raised_exception.exception), 'Unable to find or register a user with email user@user.user')
+        mock_util = mock_user_util.return_value
+        mock_util.find_user_by_email.assert_called_with("user@user.user")
 
 
 class TestRemoteProjectChildren(TestCase):
@@ -776,48 +823,6 @@ class TestRemoteAuthProvider(TestCase):
         self.assertEqual(len(providers), 1)
         self.assertEqual(providers[0].name, "Duke Authentication Service")
 
-    @patch("ddsc.core.remotestore.DataServiceApi")
-    def test_register_user_by_username(self, mock_data_service_api):
-        providers_response = MagicMock()
-        providers_response.json.return_value = {
-            "results": [self.provider_data1]
-        }
-        add_user_response = MagicMock()
-        add_user_response.json.return_value = {
-            "id": "123",
-            "username": "joe",
-            "full_name": "Joe Shoe",
-            "email": "",
-            "first_name": "Joe",
-            "last_name": "Shoe",
-        }
-        mock_data_service_api().get_auth_providers.return_value = providers_response
-        mock_data_service_api().auth_provider_add_user.return_value = add_user_response
-        remote_store = RemoteStore(MagicMock())
-        user = remote_store.register_user_by_username("joe")
-        self.assertEqual(user.id, "123")
-        self.assertEqual(user.username, "joe")
-        mock_data_service_api().auth_provider_add_user.assert_called_with(self.provider_data1['id'], 'joe')
-
-    @patch("ddsc.core.remotestore.DataServiceApi")
-    def test_register_user_by_username_with_no_default_provider(self, mock_data_service_api):
-        providers_response = MagicMock()
-        providers_response.json.return_value = {
-            "results": []
-        }
-        add_user_response = MagicMock()
-        add_user_response.json.return_value = {
-            "id": "123",
-            "username": "joe",
-            "full_name": "Joe Shoe",
-            "email": "",
-        }
-        mock_data_service_api().get_auth_providers.return_value = providers_response
-        mock_data_service_api().auth_provider_add_user.return_value = add_user_response
-        remote_store = RemoteStore(MagicMock())
-        with self.assertRaises(ValueError):
-            remote_store.register_user_by_username("joe")
-
 
 class TestProjectNameOrId(TestCase):
     def test_constructors(self):
diff --git a/ddsc/core/tests/test_userutil.py b/ddsc/core/tests/test_userutil.py
new file mode 100644
index 0000000..680a169
--- /dev/null
+++ b/ddsc/core/tests/test_userutil.py
@@ -0,0 +1,131 @@
+from unittest import TestCase
+from mock import Mock
+from ddsc.core.userutil import UserUtil
+
+
+class UserUtilTestCase(TestCase):
+    def setUp(self):
+        self.data_service = Mock()
+        self.logging_func = Mock()
+        self.user_util = UserUtil(self.data_service, self.logging_func)
+
+    def test_constructor(self):
+        self.assertEqual(self.user_util.auth_provider_id,
+                         self.data_service.get_default_auth_provider_id.return_value)
+
+    def test_find_user_by_username(self):
+        self.data_service.get_users.return_value.json.return_value = {"results": [{"id": "123"}]}
+        result = self.user_util.find_user_by_username("fakeuser")
+        self.assertEqual(result, {"id": "123"})
+        self.data_service.get_users.assert_called_with(username="fakeuser")
+
+    def test_register_user_by_username(self):
+        self.data_service.auth_provider_add_user.return_value.json.return_value = {"id": "123"}
+        result = self.user_util.register_user_by_username("fakeuser")
+        self.assertEqual(result, {"id": "123"})
+        self.data_service.auth_provider_add_user.assert_called_with(self.user_util.auth_provider_id, "fakeuser")
+
+    def test_find_user_by_email(self):
+        self.data_service.get_users.return_value.json.return_value = {"results": [{"id": "123"}]}
+        result = self.user_util.find_user_by_email("fakeuser@duke.edu")
+        self.assertEqual(result, {"id": "123"})
+        self.data_service.get_users.assert_called_with(email="fakeuser@duke.edu")
+
+    def test_find_affiliate_by_email(self):
+        self.data_service.get_auth_provider_affiliates.return_value.json.return_value = {"results": [{"id": "123"}]}
+        result = self.user_util.find_affiliate_by_email("fakeuser@duke.edu")
+        self.assertEqual(result, {"id": "123"})
+        self.data_service.get_auth_provider_affiliates.assert_called_with(self.user_util.auth_provider_id,
+                                                                          email="fakeuser@duke.edu")
+
+    def test_find_affiliate_by_username(self):
+        self.data_service.get_auth_provider_affiliates.return_value.json.return_value = {"results": [{"id": "123"}]}
+        result = self.user_util.find_affiliate_by_username("fakeuser")
+        self.assertEqual(result, {"id": "123"})
+        self.data_service.get_auth_provider_affiliates.assert_called_with(self.user_util.auth_provider_id,
+                                                                          username="fakeuser")
+
+    def test_user_or_affiliate_exists_for_email__dds_user_exists(self):
+        self.user_util.find_user_by_email = Mock()
+        self.user_util.find_user_by_email.return_value = {"id": "123"}
+
+        self.assertTrue(self.user_util.user_or_affiliate_exists_for_email("fakeuser@duke.edu"))
+        self.user_util.find_user_by_email.assert_called_with("fakeuser@duke.edu")
+        self.logging_func.assert_called_with(
+            "Found DukeDS user for email address fakeuser@duke.edu.")
+
+    def test_user_or_affiliate_exists_for_email__valid_affiliate_exists(self):
+        self.user_util.find_user_by_email = Mock()
+        self.user_util.find_user_by_email.return_value = None
+        self.user_util.find_affiliate_by_email = Mock()
+        self.user_util.find_affiliate_by_email.return_value = {"uid": "123"}
+
+        self.assertTrue(self.user_util.user_or_affiliate_exists_for_email("fakeuser@duke.edu"))
+        self.user_util.find_user_by_email.assert_called_with("fakeuser@duke.edu")
+        self.user_util.find_affiliate_by_email.assert_called_with("fakeuser@duke.edu")
+        self.logging_func.assert_called_with(
+            "Found affiliate for email address fakeuser@duke.edu.")
+
+    def test_valid_dds_user_or_affiliate_exists_for_email__no_user_found(self):
+        self.user_util.find_user_by_email = Mock()
+        self.user_util.find_user_by_email.return_value = None
+        self.user_util.find_affiliate_by_email = Mock()
+        self.user_util.find_affiliate_by_email.return_value = None
+
+        self.assertFalse(self.user_util.user_or_affiliate_exists_for_email("fakeuser@duke.edu"))
+        self.user_util.find_user_by_email.assert_called_with("fakeuser@duke.edu")
+        self.user_util.find_affiliate_by_email.assert_called_with("fakeuser@duke.edu")
+        self.logging_func.assert_called_with(
+            "No valid DukeDS user or affiliate found for email address fakeuser@duke.edu.")
+
+    def test_user_or_affiliate_exists_for_username__dds_user_exists(self):
+        self.user_util.find_user_by_username = Mock()
+        self.user_util.find_user_by_username.return_value = {"id": "123"}
+
+        self.assertTrue(self.user_util.user_or_affiliate_exists_for_username("fakeuser"))
+        self.user_util.find_user_by_username.assert_called_with("fakeuser")
+        self.logging_func.assert_called_with(
+            "Found DukeDS user for username fakeuser.")
+
+    def test_user_or_affiliate_exists_for_username__valid_affiliate_exists(self):
+        self.user_util.find_user_by_username = Mock()
+        self.user_util.find_user_by_username.return_value = None
+        self.user_util.find_affiliate_by_username = Mock()
+        self.user_util.find_affiliate_by_username.return_value = {"uid": "123"}
+
+        self.assertTrue(self.user_util.user_or_affiliate_exists_for_username("fakeuser"))
+        self.user_util.find_user_by_username.assert_called_with("fakeuser")
+        self.user_util.find_affiliate_by_username.assert_called_with("fakeuser")
+        self.logging_func.assert_called_with(
+            "Found affiliate for username fakeuser.")
+
+    def test_valid_dds_user_or_affiliate_exists_for_username__no_user_found(self):
+        self.user_util.find_user_by_username = Mock()
+        self.user_util.find_user_by_username.return_value = None
+        self.user_util.find_affiliate_by_username = Mock()
+        self.user_util.find_affiliate_by_username.return_value = None
+
+        self.assertFalse(self.user_util.user_or_affiliate_exists_for_username("fakeuser"))
+        self.user_util.find_user_by_username.assert_called_with("fakeuser")
+        self.user_util.find_affiliate_by_username.assert_called_with("fakeuser")
+        self.logging_func.assert_called_with(
+            "No valid DukeDS user or affiliate found for username fakeuser.")
+
+    def test__get_single_user_or_none(self):
+        response = Mock()
+
+        # When no items found should return None
+        response.json.return_value = {"results": []}
+        result = self.user_util._get_single_user_or_none(response, lookup_value="fakeuser@duke.edu")
+        self.assertEqual(result, None)
+
+        # When one item found should return first (only) item
+        response.json.return_value = {"results": [{"id": "123"}]}
+        result = self.user_util._get_single_user_or_none(response, lookup_value="fakeuser@duke.edu")
+        self.assertEqual(result, {"id": "123"})
+
+        # When multiple found raise exception
+        response.json.return_value = {"results": [{"id": "123"}, {"id": "456"}]}
+        with self.assertRaises(ValueError) as raised_exception:
+            self.user_util._get_single_user_or_none(response, lookup_value="fakeuser@duke.edu")
+        self.assertEqual(str(raised_exception.exception), 'Found multiple users for fakeuser@duke.edu.')
diff --git a/ddsc/sdk/tests/test_dukeds.py b/ddsc/sdk/tests/test_dukeds.py
index 3a9ef19..002cfa1 100644
--- a/ddsc/sdk/tests/test_dukeds.py
+++ b/ddsc/sdk/tests/test_dukeds.py
@@ -1,6 +1,6 @@
 from unittest import TestCase
 from ddsc import DukeDS, ItemNotFound, DuplicateNameError
-from mock import patch, Mock
+from mock import patch, Mock, ANY
 
 
 class TestDukeDS(TestCase):
@@ -154,3 +154,43 @@ class TestDukeDS(TestCase):
 
         self.mouse_rna_project.get_child_for_path.assert_called_with('data/file1.dat')
         mock_file1.delete.assert_called()
+
+    @patch('ddsc.sdk.dukeds.Client')
+    @patch('ddsc.sdk.dukeds.UserUtil')
+    def test_can_deliver_to_user_with_email(self, mock_user_util, mock_client):
+        result = DukeDS.can_deliver_to_user_with_email(email_address='fakeuser@duke.edu')
+        mock_user_util.assert_called_with(mock_client.return_value.dds_connection.data_service, logging_func=ANY)
+        dds_user_util = mock_user_util.return_value
+        dds_user_util.user_or_affiliate_exists_for_email.assert_called_with("fakeuser@duke.edu")
+        self.assertEqual(result, dds_user_util.user_or_affiliate_exists_for_email.return_value)
+
+    @patch('ddsc.sdk.dukeds.Client')
+    @patch('ddsc.sdk.dukeds.UserUtil')
+    def test_can_deliver_to_user_with_email_with_logging_func(self, mock_user_util, mock_client):
+        mock_log_func = Mock()
+        result = DukeDS.can_deliver_to_user_with_email(email_address='fakeuser@duke.edu', logging_func=mock_log_func)
+        mock_user_util.assert_called_with(mock_client.return_value.dds_connection.data_service,
+                                          logging_func=mock_log_func)
+        dds_user_util = mock_user_util.return_value
+        dds_user_util.user_or_affiliate_exists_for_email.assert_called_with("fakeuser@duke.edu")
+        self.assertEqual(result, dds_user_util.user_or_affiliate_exists_for_email.return_value)
+
+    @patch('ddsc.sdk.dukeds.Client')
+    @patch('ddsc.sdk.dukeds.UserUtil')
+    def test_can_deliver_to_user_with_username(self, mock_user_util, mock_client):
+        result = DukeDS.can_deliver_to_user_with_username(username='fakeuser')
+        mock_user_util.assert_called_with(mock_client.return_value.dds_connection.data_service, logging_func=ANY)
+        dds_user_util = mock_user_util.return_value
+        dds_user_util.user_or_affiliate_exists_for_username.assert_called_with("fakeuser")
+        self.assertEqual(result, dds_user_util.user_or_affiliate_exists_for_username.return_value)
+
+    @patch('ddsc.sdk.dukeds.Client')
+    @patch('ddsc.sdk.dukeds.UserUtil')
+    def test_can_deliver_to_user_with_username_with_logging_func(self, mock_user_util, mock_client):
+        mock_log_func = Mock()
+        result = DukeDS.can_deliver_to_user_with_username(username='fakeuser', logging_func=mock_log_func)
+        mock_user_util.assert_called_with(mock_client.return_value.dds_connection.data_service,
+                                          logging_func=mock_log_func)
+        dds_user_util = mock_user_util.return_value
+        dds_user_util.user_or_affiliate_exists_for_username.assert_called_with("fakeuser")
+        self.assertEqual(result, dds_user_util.user_or_affiliate_exists_for_username.return_value)

pip install -e .[dev]
parse_log_pytest
requirements.txt
nose mock pytest
3.4.1
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
-e git+https://github.com/Duke-GCB/DukeDSClient.git@d8ed4c16cb3b82bd5ae130c27c1e615703f1170f#egg=DukeDSClient
future==0.16.0
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
mock==5.2.0
nose==1.3.7
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
pytz==2025.2
PyYAML==5.1
requests==2.27.1
six==1.10.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: DukeDSClient
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - future==0.16.0
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - mock==5.2.0
      - nose==1.3.7
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytz==2025.2
      - pyyaml==5.1
      - requests==2.27.1
      - six==1.10.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/DukeDSClient

ddsc/core/tests/test_ddsapi.py::TestMultiJSONResponse::test_pass_through_works_with_one_response ddsc/core/tests/test_ddsapi.py::TestMultiJSONResponse::test_pass_through_works_with_three_responses ddsc/core/tests/test_ddsapi.py::TestMultiJSONResponse::test_pass_through_works_with_two_responses ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_auth_provider_add_user ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_auth_provider_affiliate_single ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_check_err_with_400 ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_check_err_with_404 ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_check_err_with_404_with_flag ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_check_err_with_500 ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_check_err_with_good_response ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_constructor_creates_session_when_passed_none ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_create_non_chunked_upload ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_create_upload ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_delete_file ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_delete_folder ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_delete_raises_error_on_paging_response ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_all_project_transfers ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_auth_provider ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_auth_provider_affiliates_email ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_auth_provider_affiliates_full_name_contains ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_auth_provider_affiliates_username ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_auth_providers ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_collection_one_page ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_collection_three_pages ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_collection_two_pages ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_default_auth_provider_id ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_default_auth_provider_id_no_default_provider ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_folder ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_project_children ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_project_files ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_project_permissions ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_project_transfers ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_projects ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_single_item_raises_error_on_paging_response ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_single_page_works_on_paging_response ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_users_no_filtering ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_get_users_with_filtering ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_list_auth_roles ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_post_raises_error_on_paging_response ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_put_create_upload_url ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_put_create_upload_url_invalid_number ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_put_raises_error_on_paging_response ddsc/core/tests/test_ddsapi.py::TestDataServiceApi::test_relations_methods ddsc/core/tests/test_ddsapi.py::TestDataServiceAuth::test_claim_new_token ddsc/core/tests/test_ddsapi.py::TestDataServiceAuth::test_claim_new_token_error ddsc/core/tests/test_ddsapi.py::TestDataServiceAuth::test_claim_new_token_missing_agent ddsc/core/tests/test_ddsapi.py::TestDataServiceAuth::test_claim_new_token_missing_setup ddsc/core/tests/test_ddsapi.py::TestMissingInitialSetupError::test_constructor ddsc/core/tests/test_ddsapi.py::TestSoftwareAgentNotFoundError::test_constructor ddsc/core/tests/test_ddsapi.py::TestUnexpectedPagingReceivedError::test_constructor ddsc/core/tests/test_ddsapi.py::TestAuthTokenCreationError::test_constructor ddsc/core/tests/test_ddsapi.py::TestInconsistentResourceMonitoring::test_retry_until_resource_is_consistent ddsc/core/tests/test_ddsapi.py::TestInconsistentResourceMonitoring::test_retry_until_resource_is_consistent_with_one_retry ddsc/core/tests/test_ddsapi.py::TestInconsistentResourceMonitoring::test_retry_until_resource_is_consistent_with_two_retries ddsc/core/tests/test_ddsapi.py::TestRetryWhenServiceDown::test_returns_value_when_ok ddsc/core/tests/test_ddsapi.py::TestRetryWhenServiceDown::test_will_just_raise_when_other_error ddsc/core/tests/test_ddsapi.py::TestRetryWhenServiceDown::test_will_retry_after_waiting ddsc/core/tests/test_remotestore.py::TestProjectFolderFile::test_file_item ddsc/core/tests/test_remotestore.py::TestProjectFolderFile::test_file_item_new_version ddsc/core/tests/test_remotestore.py::TestProjectFolderFile::test_folder_item ddsc/core/tests/test_remotestore.py::TestProjectFolderFile::test_project_list_item ddsc/core/tests/test_remotestore.py::TestRemoteUser::test_parse_user ddsc/core/tests/test_remotestore.py::TestRemoteAuthRole::test_deprecated_system_role ddsc/core/tests/test_remotestore.py::TestRemoteAuthRole::test_parse_auth_role ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_auth_roles_project ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_auth_roles_system ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_constructor ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_fetch_remote_project_exclude_response_fields ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_fetch_users_no_filter ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_fetch_users_with_filter ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_get_or_register_user_by_email__finds_affiliate_by_email ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_get_or_register_user_by_email__finds_user_by_email ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_get_or_register_user_by_email__unable_to_find_user ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_get_or_register_user_by_username__finds_user ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_get_or_register_user_by_username__registers_user ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_get_project_files ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_get_projects_with_auth_role ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_lookup_or_register_user_by_email_or_username_with_email ddsc/core/tests/test_remotestore.py::TestRemoteStore::test_lookup_or_register_user_by_email_or_username_with_username ddsc/core/tests/test_remotestore.py::TestRemoteProjectChildren::test_simple_case ddsc/core/tests/test_remotestore.py::TestRemoteProjectChildren::test_top_level_files ddsc/core/tests/test_remotestore.py::TestReadRemoteHash::test_new_way_one_item ddsc/core/tests/test_remotestore.py::TestReadRemoteHash::test_new_way_two_item ddsc/core/tests/test_remotestore.py::TestReadRemoteHash::test_old_way ddsc/core/tests/test_remotestore.py::TestRemoteAuthProvider::test_constructor ddsc/core/tests/test_remotestore.py::TestRemoteAuthProvider::test_get_auth_providers ddsc/core/tests/test_remotestore.py::TestProjectNameOrId::test_constructors ddsc/core/tests/test_remotestore.py::TestProjectNameOrId::test_contained_in_dict ddsc/core/tests/test_remotestore.py::TestProjectNameOrId::test_description ddsc/core/tests/test_remotestore.py::TestProjectFile::test_create_for_dds_file_dict ddsc/core/tests/test_remotestore.py::TestProjectFile::test_get_local_path ddsc/core/tests/test_remotestore.py::TestProjectFile::test_get_remote_parent_path ddsc/core/tests/test_remotestore.py::TestProjectFile::test_path ddsc/core/tests/test_remotestore.py::TestRemoteFileUrl::test_constructor ddsc/core/tests/test_userutil.py::UserUtilTestCase::test__get_single_user_or_none ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_constructor ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_find_affiliate_by_email ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_find_affiliate_by_username ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_find_user_by_email ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_find_user_by_username ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_register_user_by_username ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_user_or_affiliate_exists_for_email__dds_user_exists ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_user_or_affiliate_exists_for_email__valid_affiliate_exists ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_user_or_affiliate_exists_for_username__dds_user_exists ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_user_or_affiliate_exists_for_username__valid_affiliate_exists ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_valid_dds_user_or_affiliate_exists_for_email__no_user_found ddsc/core/tests/test_userutil.py::UserUtilTestCase::test_valid_dds_user_or_affiliate_exists_for_username__no_user_found ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_can_deliver_to_user_with_email ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_can_deliver_to_user_with_email_with_logging_func ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_can_deliver_to_user_with_username ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_can_deliver_to_user_with_username_with_logging_func ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_create_project ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_create_project_raises_for_duplicate_project_name ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_delete_file ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_delete_project ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_delete_project_not_found ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_download_file ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_list_files ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_list_projects ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_upload_file ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_upload_file_creating_project_and_parent_dir ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_upload_file_default_dest_is_local_basename ddsc/sdk/tests/test_dukeds.py::TestDukeDS::test_upload_file_makes_new_version
ddsc/core/userutil.py ddsc/sdk/dukeds.py ddsc/core/ddsapi.py DukeDS/__init__.py
ddsc/core/userutil.py ddsc/sdk/dukeds.py ddsc/core/ddsapi.py DukeDS/__init__.py