From f115980875a0bf7fa444204abfa5282d140014ea Mon Sep 17 00:00:00 2001 From: Saimon Michelson Date: Thu, 26 Jun 2025 14:27:15 -0400 Subject: [PATCH 1/4] support special characters when copying, moving, renaming, sharing and deleting files --- cterasdk/cio/core.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cterasdk/cio/core.py b/cterasdk/cio/core.py index e8669fe9..0e5f6231 100644 --- a/cterasdk/cio/core.py +++ b/cterasdk/cio/core.py @@ -210,7 +210,7 @@ def makedir(path): @contextmanager def rename(path, name): param = ActionResourcesParam.instance() - param.add(SrcDstParam.instance(src=path.absolute, dest=path.parent.join(name).absolute)) + param.add(SrcDstParam.instance(src=path.absolute_encode, dest=path.parent.join(name).absolute_encode)) logger.info('Renaming item: %s to: %s', path.reference.as_posix(), name) yield param @@ -219,7 +219,7 @@ def _delete_or_recover(paths, *, message=None): param = ActionResourcesParam.instance() paths = [paths] if not isinstance(paths, list) else paths for path in paths: - param.add(SrcDstParam.instance(src=path.absolute)) + param.add(SrcDstParam.instance(src=path.absolute_encode)) if message: logger.info('%s: %s', message, path.reference.as_posix()) yield param @@ -239,7 +239,7 @@ def _copy_or_move(paths, destination, *, message=None): param = ActionResourcesParam.instance() paths = [paths] if not isinstance(paths, list) else paths for path in paths: - param.add(SrcDstParam.instance(src=path.absolute, dest=destination.join(path.name).absolute)) + param.add(SrcDstParam.instance(src=path.absolute_encode, dest=destination.join(path.name).absolute_encode)) if message: logger.info('%s from: %s to: %s', message, path.reference.as_posix(), destination.join(path.name).reference.as_posix()) yield param @@ -260,7 +260,7 @@ def public_link(path, access, expire_in): access = {'RO': 'ReadOnly', 'RW': 'ReadWrite', 'PO': 'PreviewOnly'}.get(access) expire_on = DateTimeUtils.get_expiration_date(expire_in).strftime('%Y-%m-%d') logger.info('Creating Public Link for: %s. Access: %s. Expires: %s', path.reference.as_posix(), access, expire_on) - param = CreateShareParam.instance(path=path.absolute, access=access, expire_on=expire_on) + param = CreateShareParam.instance(path=path.absolute_encode, access=access, expire_on=expire_on) yield param @@ -320,7 +320,7 @@ def share(path, as_project, allow_reshare, allow_sync, shares=None): param = Object() param._classname = 'ShareResourceParam' # pylint: disable=protected-access - param.url = path.absolute + param.url = path.absolute_encode param.teamProject = as_project param.allowReshare = allow_reshare param.shouldSync = allow_sync From 99a8c34fcd155b130d3989cbb7c006cf6dcc6675 Mon Sep 17 00:00:00 2001 From: Saimon Michelson Date: Thu, 26 Jun 2025 14:28:46 -0400 Subject: [PATCH 2/4] update change log --- docs/source/UserGuides/Miscellaneous/Changelog.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/UserGuides/Miscellaneous/Changelog.rst b/docs/source/UserGuides/Miscellaneous/Changelog.rst index e278b0c4..ee8acf8d 100644 --- a/docs/source/UserGuides/Miscellaneous/Changelog.rst +++ b/docs/source/UserGuides/Miscellaneous/Changelog.rst @@ -1,6 +1,16 @@ Changelog ========= +2.20.14 +------- + +Bug Fixes +^^^^^^^^^ + +* CTERA Portal: Added support for special characters when copying, moving, renaming, sharing, and deleting files. + +Related issues and pull requests on GitHub: `#304 `_ + 2.20.13 ------- From e5a1d12194e72b2d7edc4bfb441504b1f925e461 Mon Sep 17 00:00:00 2001 From: Saimon Michelson Date: Thu, 26 Jun 2025 14:50:45 -0400 Subject: [PATCH 3/4] encode ut to pass --- tests/ut/core/user/base_user.py | 4 ++++ tests/ut/core/user/test_copy.py | 4 ++-- tests/ut/core/user/test_delete.py | 2 +- tests/ut/core/user/test_move.py | 4 ++-- tests/ut/core/user/test_public_link.py | 2 +- tests/ut/core/user/test_rename.py | 4 ++-- tests/ut/core/user/test_undelete.py | 2 +- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/ut/core/user/base_user.py b/tests/ut/core/user/base_user.py index e61e2975..28c8d708 100644 --- a/tests/ut/core/user/base_user.py +++ b/tests/ut/core/user/base_user.py @@ -1,4 +1,5 @@ from unittest import mock +from urllib.parse import quote from cterasdk.common import Object from cterasdk.objects import ServicesPortal @@ -12,6 +13,9 @@ def setUp(self): self._services = ServicesPortal("") self._base = '/ServicesPortal/webdav' + def encode_path(self, path): + return quote(path) + def _init_services(self, execute_response=None, form_data_response=None): self._services.api.execute = mock.MagicMock(return_value=execute_response) self._services.api.form_data = mock.MagicMock(return_value=form_data_response) diff --git a/tests/ut/core/user/test_copy.py b/tests/ut/core/user/test_copy.py index c385c82f..3a79a080 100644 --- a/tests/ut/core/user/test_copy.py +++ b/tests/ut/core/user/test_copy.py @@ -22,5 +22,5 @@ def test_copy(self): self.assertEqual(ret, execute_response) def _create_copy_resource_param(self): - destinations = [self._dest + '/' + self._filename] - return self._create_action_resource_param([self._source], destinations) + destinations = [self.encode_path(self._dest + '/' + self._filename)] + return self._create_action_resource_param([self.encode_path(self._source)], destinations) diff --git a/tests/ut/core/user/test_delete.py b/tests/ut/core/user/test_delete.py index f5ce44ee..91e9a4ba 100644 --- a/tests/ut/core/user/test_delete.py +++ b/tests/ut/core/user/test_delete.py @@ -20,4 +20,4 @@ def test_delete(self): self.assertEqual(ret, execute_response) def _create_delete_resource_param(self): - return self._create_action_resource_param([self._path]) + return self._create_action_resource_param([self.encode_path(self._path)]) diff --git a/tests/ut/core/user/test_move.py b/tests/ut/core/user/test_move.py index c89b7b31..8a863c84 100644 --- a/tests/ut/core/user/test_move.py +++ b/tests/ut/core/user/test_move.py @@ -22,5 +22,5 @@ def test_move(self): self.assertEqual(ret, execute_response) def _create_move_resource_param(self): - destinations = [self._dest + '/' + self._filename] - return self._create_action_resource_param([self._source], destinations) + destinations = [self.encode_path(self._dest + '/' + self._filename)] + return self._create_action_resource_param([self.encode_path(self._source)], destinations) diff --git a/tests/ut/core/user/test_public_link.py b/tests/ut/core/user/test_public_link.py index 09ebeaf0..4201ca49 100644 --- a/tests/ut/core/user/test_public_link.py +++ b/tests/ut/core/user/test_public_link.py @@ -31,7 +31,7 @@ def _create_public_link_response(self): def _create_public_link_param(self, access_mode, expire_in): param = Object() param._classname = 'CreateShareParam' # pylint: disable=protected-access - param.url = f'{self._base}/{self._path}' + param.url = self.encode_path(f'{self._base}/{self._path}') param.share = Object() param.share._classname = 'ShareConfig' # pylint: disable=protected-access param.share.accessMode = access_mode diff --git a/tests/ut/core/user/test_rename.py b/tests/ut/core/user/test_rename.py index 91fa6f5f..1066b12d 100644 --- a/tests/ut/core/user/test_rename.py +++ b/tests/ut/core/user/test_rename.py @@ -22,6 +22,6 @@ def test_rename(self): self.assertEqual(ret, execute_response) def _create_rename_resource_param(self): - sources = [self._parent_directory + '/' + self._current_filename] - destinations = [self._parent_directory + '/' + self._new_filename] + sources = [self.encode_path(self._parent_directory + '/' + self._current_filename)] + destinations = [self.encode_path(self._parent_directory + '/' + self._new_filename)] return self._create_action_resource_param(sources, destinations) diff --git a/tests/ut/core/user/test_undelete.py b/tests/ut/core/user/test_undelete.py index 525096eb..82dd126c 100644 --- a/tests/ut/core/user/test_undelete.py +++ b/tests/ut/core/user/test_undelete.py @@ -20,4 +20,4 @@ def test_undelete(self): self.assertEqual(ret, execute_response) def _create_undelete_resource_param(self): - return self._create_action_resource_param([self._path]) + return self._create_action_resource_param([self.encode_path(self._path)]) From 1972662bbc5a262b86dbe21af4d508275ff42712 Mon Sep 17 00:00:00 2001 From: Saimon Michelson Date: Thu, 26 Jun 2025 15:57:04 -0400 Subject: [PATCH 4/4] pass lint --- tests/ut/core/user/base_user.py | 3 ++- tests/ut/core/user/test_copy.py | 4 ++-- tests/ut/core/user/test_delete.py | 2 +- tests/ut/core/user/test_move.py | 4 ++-- tests/ut/core/user/test_public_link.py | 2 +- tests/ut/core/user/test_rename.py | 4 ++-- tests/ut/core/user/test_undelete.py | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/ut/core/user/base_user.py b/tests/ut/core/user/base_user.py index 28c8d708..8c84f25a 100644 --- a/tests/ut/core/user/base_user.py +++ b/tests/ut/core/user/base_user.py @@ -13,7 +13,8 @@ def setUp(self): self._services = ServicesPortal("") self._base = '/ServicesPortal/webdav' - def encode_path(self, path): + @staticmethod + def encode_path(path): return quote(path) def _init_services(self, execute_response=None, form_data_response=None): diff --git a/tests/ut/core/user/test_copy.py b/tests/ut/core/user/test_copy.py index 3a79a080..6e58c8d8 100644 --- a/tests/ut/core/user/test_copy.py +++ b/tests/ut/core/user/test_copy.py @@ -22,5 +22,5 @@ def test_copy(self): self.assertEqual(ret, execute_response) def _create_copy_resource_param(self): - destinations = [self.encode_path(self._dest + '/' + self._filename)] - return self._create_action_resource_param([self.encode_path(self._source)], destinations) + destinations = [base_user.BaseCoreServicesTest.encode_path(self._dest + '/' + self._filename)] + return self._create_action_resource_param([base_user.BaseCoreServicesTest.encode_path(self._source)], destinations) diff --git a/tests/ut/core/user/test_delete.py b/tests/ut/core/user/test_delete.py index 91e9a4ba..1fcdaf9b 100644 --- a/tests/ut/core/user/test_delete.py +++ b/tests/ut/core/user/test_delete.py @@ -20,4 +20,4 @@ def test_delete(self): self.assertEqual(ret, execute_response) def _create_delete_resource_param(self): - return self._create_action_resource_param([self.encode_path(self._path)]) + return self._create_action_resource_param([base_user.BaseCoreServicesTest.encode_path(self._path)]) diff --git a/tests/ut/core/user/test_move.py b/tests/ut/core/user/test_move.py index 8a863c84..2a1167ee 100644 --- a/tests/ut/core/user/test_move.py +++ b/tests/ut/core/user/test_move.py @@ -22,5 +22,5 @@ def test_move(self): self.assertEqual(ret, execute_response) def _create_move_resource_param(self): - destinations = [self.encode_path(self._dest + '/' + self._filename)] - return self._create_action_resource_param([self.encode_path(self._source)], destinations) + destinations = [base_user.BaseCoreServicesTest.encode_path(self._dest + '/' + self._filename)] + return self._create_action_resource_param([base_user.BaseCoreServicesTest.encode_path(self._source)], destinations) diff --git a/tests/ut/core/user/test_public_link.py b/tests/ut/core/user/test_public_link.py index 4201ca49..c7dcc040 100644 --- a/tests/ut/core/user/test_public_link.py +++ b/tests/ut/core/user/test_public_link.py @@ -31,7 +31,7 @@ def _create_public_link_response(self): def _create_public_link_param(self, access_mode, expire_in): param = Object() param._classname = 'CreateShareParam' # pylint: disable=protected-access - param.url = self.encode_path(f'{self._base}/{self._path}') + param.url = base_user.BaseCoreServicesTest.encode_path(f'{self._base}/{self._path}') param.share = Object() param.share._classname = 'ShareConfig' # pylint: disable=protected-access param.share.accessMode = access_mode diff --git a/tests/ut/core/user/test_rename.py b/tests/ut/core/user/test_rename.py index 1066b12d..54e19523 100644 --- a/tests/ut/core/user/test_rename.py +++ b/tests/ut/core/user/test_rename.py @@ -22,6 +22,6 @@ def test_rename(self): self.assertEqual(ret, execute_response) def _create_rename_resource_param(self): - sources = [self.encode_path(self._parent_directory + '/' + self._current_filename)] - destinations = [self.encode_path(self._parent_directory + '/' + self._new_filename)] + sources = [base_user.BaseCoreServicesTest.encode_path(self._parent_directory + '/' + self._current_filename)] + destinations = [base_user.BaseCoreServicesTest.encode_path(self._parent_directory + '/' + self._new_filename)] return self._create_action_resource_param(sources, destinations) diff --git a/tests/ut/core/user/test_undelete.py b/tests/ut/core/user/test_undelete.py index 82dd126c..3399712d 100644 --- a/tests/ut/core/user/test_undelete.py +++ b/tests/ut/core/user/test_undelete.py @@ -20,4 +20,4 @@ def test_undelete(self): self.assertEqual(ret, execute_response) def _create_undelete_resource_param(self): - return self._create_action_resource_param([self.encode_path(self._path)]) + return self._create_action_resource_param([base_user.BaseCoreServicesTest.encode_path(self._path)])