From e3b73ae89418c6eab166c0b586b64ab20c9e3bbe Mon Sep 17 00:00:00 2001 From: Saimon Michelson Date: Tue, 8 Jul 2025 10:36:41 -0400 Subject: [PATCH 1/2] support many to many copy and move for both async and sync operations, update changelog --- cterasdk/asynchronous/core/files/browser.py | 10 ++++++---- cterasdk/cio/core.py | 17 +++++++++++++---- cterasdk/core/files/browser.py | 10 ++++++---- .../UserGuides/Miscellaneous/Changelog.rst | 18 ++++++++++++++++++ 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/cterasdk/asynchronous/core/files/browser.py b/cterasdk/asynchronous/core/files/browser.py index 07a61a38..5364ea45 100644 --- a/cterasdk/asynchronous/core/files/browser.py +++ b/cterasdk/asynchronous/core/files/browser.py @@ -112,9 +112,10 @@ async def copy(self, *paths, destination=None): :param list[str] paths: List of paths :param str destination: Destination """ - if destination is None: + try: + return await io.copy(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) + except ValueError: raise ValueError('Copy destination was not specified.') - return await io.copy(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) async def permalink(self, path): """ @@ -206,6 +207,7 @@ async def move(self, *paths, destination=None): :param list[str] paths: List of paths :param str destination: Destination """ - if destination is None: + try: + return await io.move(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) + except ValueError: raise ValueError('Move destination was not specified.') - return await io.move(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) diff --git a/cterasdk/cio/core.py b/cterasdk/cio/core.py index 0e5f6231..6d948fde 100644 --- a/cterasdk/cio/core.py +++ b/cterasdk/cio/core.py @@ -5,6 +5,7 @@ from ..common import Object, DateTimeUtils from ..core.enum import ProtectionLevel, CollaboratorType, SearchType, PortalAccountType, FileAccessMode from ..core.types import PortalAccount, UserAccount, GroupAccount +from ..exceptions import InputError from ..exceptions.io import ResourceExistsError, PathValidationError, NameSyntaxError, ReservedNameError from . import common @@ -70,6 +71,9 @@ def absolute(self): def instance(scope, entries): if isinstance(entries, list): return [CorePath(scope, e) for e in entries] + if isinstance(entries, tuple): + source, destination = entries + return (CorePath(scope, source), CorePath(scope, destination)) return CorePath(scope, entries) @@ -237,11 +241,16 @@ def recover(*paths): 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_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()) + src, dest = path, destination + if isinstance(path, tuple): + src, dest = path + else: + if not dest.reference.name: + raise ValueError(f'Error: No destination specified for: {src}') + dest = dest.join(src.name) + param.add(SrcDstParam.instance(src=src.absolute_encode, dest=dest.absolute_encode)) + logger.info('%s from: %s to: %s', message, src.reference.as_posix(), dest.reference.as_posix()) yield param diff --git a/cterasdk/core/files/browser.py b/cterasdk/core/files/browser.py index d043c3cc..37fbef1b 100644 --- a/cterasdk/core/files/browser.py +++ b/cterasdk/core/files/browser.py @@ -118,9 +118,10 @@ def copy(self, *paths, destination=None): :param list[str] paths: List of paths :param str destination: Destination """ - if destination is None: + try: + return io.copy(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) + except ValueError: raise ValueError('Copy destination was not specified.') - return io.copy(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) def permalink(self, path): """ @@ -212,9 +213,10 @@ def move(self, *paths, destination=None): :param list[str] paths: List of paths :param str destination: Destination """ - if destination is None: + try: + return io.move(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) + except ValueError: raise ValueError('Move destination was not specified.') - return io.move(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination)) def get_share_info(self, path): """ diff --git a/docs/source/UserGuides/Miscellaneous/Changelog.rst b/docs/source/UserGuides/Miscellaneous/Changelog.rst index d44c94b2..db16da33 100644 --- a/docs/source/UserGuides/Miscellaneous/Changelog.rst +++ b/docs/source/UserGuides/Miscellaneous/Changelog.rst @@ -8,6 +8,7 @@ Improvements ^^^^^^^^^^^^ * Added support for enabling or disabling Direct Mode on CTERA Portal Storage Nodes. +* Support copying and moving multiple sources to multiple destinations on CTERA Portal. Bug Fixes ^^^^^^^^^ @@ -16,6 +17,23 @@ Bug Fixes Related issues and pull requests on GitHub: `#310 `_, `#311 `_ +`#312 `_ + +.. code:: python + + """ + Copy multiple sources: the 'Sample.docx' file and the 'Spreadsheets' directory to 'My Files/Archive' + """ + user.files.copy('My Files/Documents/Sample.docx', 'My Files/Spreadsheets', destination='My Files/Archive') + + """ + Copy multiple sources to different destinations under a different name. + """ + user.files.copy( + ("Docs/Report_January.docx", "Archive/Jan_Report_Final.docx"), + ("Budget/Budget_2024.xlsx", "Finance/2024_Annual_Budget.xlsx"), + ("Presentations/Presentation.pptx", "Sales/Q2_Sales_Pitch.pptx") + ) 2.20.15 ------- From 0dc4a28a6d4b029b26d7b97eae2bb012a6f1c2f5 Mon Sep 17 00:00:00 2001 From: Saimon Michelson Date: Tue, 8 Jul 2025 10:42:58 -0400 Subject: [PATCH 2/2] remove redundant exception --- cterasdk/cio/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cterasdk/cio/core.py b/cterasdk/cio/core.py index 6d948fde..af494266 100644 --- a/cterasdk/cio/core.py +++ b/cterasdk/cio/core.py @@ -5,7 +5,6 @@ from ..common import Object, DateTimeUtils from ..core.enum import ProtectionLevel, CollaboratorType, SearchType, PortalAccountType, FileAccessMode from ..core.types import PortalAccount, UserAccount, GroupAccount -from ..exceptions import InputError from ..exceptions.io import ResourceExistsError, PathValidationError, NameSyntaxError, ReservedNameError from . import common