Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
66657bf
resolve typo in ContextError exception, and update change log for v2.…
saimonation Aug 25, 2025
504efa6
add missing exceptions to docs, catch login errors in async edge modu…
saimonation Aug 26, 2025
91024e5
return file path when creating diretories, update error check
saimonation Aug 27, 2025
e157fee
commit all file operations to use command design pattern
saimonation Oct 14, 2025
c6248df
update collaborators code to support async and sync and inherit from …
saimonation Oct 30, 2025
d8e0639
Stabilize file access across all sync and async modules
saimonation Nov 1, 2025
4023d81
resolve flake8 errors
saimonation Nov 1, 2025
8464bca
update to pass pylint 1
saimonation Nov 1, 2025
08a6223
pass flake8
Nov 1, 2025
ec724bb
fix pylint for errors
saimonation Nov 1, 2025
d210f57
remove io module import
Nov 1, 2025
0655278
update ut v1
saimonation Nov 2, 2025
147feb3
update synchronous browser
saimonation Nov 2, 2025
c3f146b
update synchronous browser test
saimonation Nov 2, 2025
9f3aadd
update to use iterator
saimonation Nov 2, 2025
f8612bf
pass ut for file browsing
Nov 2, 2025
e6f3c44
add ut for mkdir
saimonation Nov 2, 2025
812914f
update to pass mkdir tests
Nov 2, 2025
c89e4da
update delete and undelete tests
saimonation Nov 2, 2025
635e218
respond with task reference
saimonation Nov 2, 2025
7dc8de9
side effect for execute
saimonation Nov 2, 2025
0d9cef9
update mock
saimonation Nov 2, 2025
64bfa12
update class attributes
saimonation Nov 2, 2025
ee4284e
update portal uid
saimonation Nov 2, 2025
fb764f1
mock calls
saimonation Nov 2, 2025
935af4d
solve param issue
Nov 2, 2025
a4f1637
update tuples
saimonation Nov 2, 2025
149fe2f
remove unnecessary mehtod from recover class
saimonation Nov 2, 2025
7b52976
add ut copy and move
saimonation Nov 2, 2025
ac17603
update fstring
saimonation Nov 2, 2025
f3ddb38
update to pass file browser ut
Nov 2, 2025
bfead23
add public link test
saimonation Nov 2, 2025
d19557b
update to add link tests
Nov 2, 2025
3482a76
update files
saimonation Nov 2, 2025
d52d5b2
update edge tests and remove admin copy test
saimonation Nov 2, 2025
82a71d0
update indent
saimonation Nov 2, 2025
8f7f0c0
new line eof
Nov 2, 2025
47db9a5
add exceptions for directory not found error
saimonation Nov 2, 2025
7d94611
update yiled from
Nov 2, 2025
e8c74c6
resolve pylint issues
saimonation Nov 2, 2025
19b14fe
resolve async walk issue
saimonation Nov 2, 2025
a47a33d
Merge branch 'master' into saimon/improved-file-browsing-exception-ha…
saimonation Nov 2, 2025
4b7e1c8
update changelog for v2.20.22
saimonation Nov 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 78 additions & 55 deletions cterasdk/asynchronous/core/files/browser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ....cio.core import CorePath, a_await_or_future
from ....lib.storage import asynfs, commonfs
from ....exceptions.io import FileConflict
from .. import query
from ....cio.core import CorePath, Open, OpenMany, Upload, UploadFile, Download, \
DownloadMany, UnShare, CreateDirectory, GetMetadata, ListVersions, RecursiveIterator, \
Delete, Recover, Rename, GetShareMetadata, Link, Copy, Move, ResourceIterator, GetPermalink
from ..base_command import BaseCommand
from . import io

Expand All @@ -17,8 +18,7 @@ async def handle(self, path):

:param str path: Path to a file
"""
handle_function = io.handle(self.normalize(path))
return await handle_function(self._core)
return await Open(io.handle, self._core, self.normalize(path)).a_execute()

async def handle_many(self, directory, *objects):
"""
Expand All @@ -27,8 +27,7 @@ async def handle_many(self, directory, *objects):
:param str directory: Path to a folder
:param args objects: List of files and folders
"""
handle_many_function = io.handle_many(self.normalize(directory), *objects)
return await handle_many_function(self._core)
return await OpenMany(io.handle_many, self._core, self.normalize(directory), *objects).a_execute()

async def download(self, path, destination=None):
"""
Expand All @@ -38,9 +37,7 @@ async def download(self, path, destination=None):
:param str,optional destination:
File destination, if it is a directory, the original filename will be kept, defaults to the default directory
"""
directory, name = commonfs.determine_directory_and_filename(path, destination=destination)
handle = await self.handle(path)
return await asynfs.write(directory, name, handle)
return await Download(io.handle, self._core, self.normalize(path), destination).a_execute()

async def download_many(self, target, objects, destination=None):
"""
Expand All @@ -58,9 +55,7 @@ async def download_many(self, target, objects, destination=None):
Optional. Path to the destination file or directory. If a directory is provided,
the original filename will be preserved. Defaults to the default download directory.
"""
directory, name = commonfs.determine_directory_and_filename(target, objects, destination=destination, archive=True)
handle = await self.handle_many(target, *objects)
return await asynfs.write(directory, name, handle)
return await DownloadMany(io.handle_many, self._core, self.normalize(target), objects, destination).a_execute()

async def listdir(self, path=None, depth=None, include_deleted=False):
"""
Expand All @@ -69,23 +64,25 @@ async def listdir(self, path=None, depth=None, include_deleted=False):
:param str,optional path: Path, defaults to the Cloud Drive root
:param bool,optional include_deleted: Include deleted files, defaults to False
"""
return await io.listdir(self._core, self.normalize(path), depth=depth, include_deleted=include_deleted)
async for o in ResourceIterator(query.iterator, self._core, self.normalize(path), depth, include_deleted, None, None).a_execute():
yield o

async def exists(self, path):
"""
Check if item exists

:param str path: Path
"""
return await io.exists(self._core, self.normalize(path))
async with GetMetadata(io.listdir, self._core, self.normalize(path), True) as (exists, *_):
return exists

async def versions(self, path):
"""
List snapshots of a file or directory

:param str path: Path
"""
return await io.versions(self._core, self.normalize(path))
return await ListVersions(io.versions, self._core, self.normalize(path)).a_execute()

async def walk(self, path=None, include_deleted=False):
"""
Expand All @@ -94,7 +91,8 @@ async def walk(self, path=None, include_deleted=False):
:param str,optional path: Path to walk, defaults to the root directory
:param bool,optional include_deleted: Include deleted files, defaults to False
"""
return io.walk(self._core, self._scope, path, include_deleted=include_deleted)
async for o in RecursiveIterator(query.iterator, self._core, self.normalize(path), include_deleted).a_generate():
yield o

async def public_link(self, path, access='RO', expire_in=30):
"""
Expand All @@ -104,19 +102,7 @@ async def public_link(self, path, access='RO', expire_in=30):
:param str,optional access: Access policy of the link, defaults to 'RO'
:param int,optional expire_in: Number of days until the link expires, defaults to 30
"""
return await io.public_link(self._core, self.normalize(path), access, expire_in)

async def _try_with_resolver(self, func, *paths, destination=None, resolver=None, cursor=None, wait=False):
async def wrapper(resume_from=None):
ref = await func(self._core, *paths, destination=destination, resolver=resolver, cursor=resume_from)
return await a_await_or_future(self._core, ref, wait)

try:
return await wrapper(cursor)
except FileConflict as e:
if resolver:
return await wrapper(e.cursor)
raise
return await Link(io.public_link, self._core, self.normalize(path), access, expire_in).a_execute()

async def copy(self, *paths, destination=None, resolver=None, cursor=None, wait=False):
"""
Expand All @@ -131,9 +117,8 @@ async def copy(self, *paths, destination=None, resolver=None, cursor=None, wait=
:rtype: cterasdk.common.object.Object or :class:`cterasdk.lib.tasks.AwaitablePortalTask`
"""
try:
return await self._try_with_resolver(io.copy, *[self.normalize(path) for path in paths],
destination=self.normalize(destination),
resolver=resolver, cursor=cursor, wait=wait)
return await Copy(io.copy, self._core, wait, *[self.normalize(path) for path in paths],
destination=self.normalize(destination), resolver=resolver, cursor=cursor).a_execute()
except ValueError:
raise ValueError('Copy destination was not specified.')

Expand All @@ -143,11 +128,7 @@ async def permalink(self, path):

:param str path: Path.
"""
p = self.normalize(path)
async for e in await io.listdir(self._core, p.parent, 1, False, p.name, 1):
if e.name == p.name:
return e.permalink
raise FileNotFoundError('File not found.', path)
return await GetPermalink(io.listdir, self._core, self.normalize(path)).a_execute()

def normalize(self, entries):
return CorePath.instance(self._scope, entries)
Expand All @@ -164,8 +145,7 @@ async def upload(self, name, destination, handle, size=None):
:param object handle: Handle.
:param str,optional size: File size, defaults to content length
"""
upload_function = io.upload(name, size, self.normalize(destination), handle)
return await upload_function(self._core)
return await Upload(io.upload, self._core, io.listdir, name, self.normalize(destination), size, handle).a_execute()

async def upload_file(self, path, destination):
"""
Expand All @@ -174,26 +154,23 @@ async def upload_file(self, path, destination):
:param str path: Local path
:param str destination: Remote path
"""
with open(path, 'rb') as handle:
metadata = commonfs.properties(path)
response = await self.upload(metadata['name'], destination, handle, metadata['size'])
return response
return await UploadFile(io.upload, self._core, io.listdir, path, self.normalize(destination)).a_execute()

async def mkdir(self, path):
"""
Create a new directory

:param str path: Directory path
"""
return await io.mkdir(self._core, self.normalize(path))
return await CreateDirectory(io.mkdir, self._core, self.normalize(path)).a_execute()

async def makedirs(self, path):
"""
Create a directory recursively

:param str path: Directory path
"""
return await io.makedirs(self._core, self.normalize(path))
return await CreateDirectory(io.mkdir, self._core, self.normalize(path), True).a_execute()

async def rename(self, path, name, *, wait=False):
"""
Expand All @@ -205,8 +182,7 @@ async def rename(self, path, name, *, wait=False):
:returns: Task status object, or an awaitable task object
:rtype: cterasdk.common.object.Object or :class:`cterasdk.lib.tasks.AwaitablePortalTask`
"""
ref = await io.rename(self._core, self.normalize(path), name)
return await a_await_or_future(self._core, ref, wait)
return await Rename(io.move, self._core, self.normalize(path), name, wait).a_execute()

async def delete(self, *paths, wait=False):
"""
Expand All @@ -217,8 +193,7 @@ async def delete(self, *paths, wait=False):
:returns: Task status object, or an awaitable task object
:rtype: cterasdk.common.object.Object or :class:`cterasdk.lib.tasks.AwaitablePortalTask`
"""
ref = await io.remove(self._core, *[self.normalize(path) for path in paths])
return await a_await_or_future(self._core, ref, wait)
return await Delete(io.delete, self._core, wait, *[self.normalize(path) for path in paths]).a_execute()

async def undelete(self, *paths, wait=False):
"""
Expand All @@ -229,8 +204,7 @@ async def undelete(self, *paths, wait=False):
:returns: Task status object, or an awaitable task object
:rtype: cterasdk.common.object.Object or :class:`cterasdk.lib.tasks.AwaitablePortalTask`
"""
ref = await io.recover(self._core, *[self.normalize(path) for path in paths])
return await a_await_or_future(self._core, ref, wait)
return await Recover(io.undelete, self._core, wait, *[self.normalize(path) for path in paths]).a_execute()

async def move(self, *paths, destination=None, resolver=None, cursor=None, wait=False):
"""
Expand All @@ -245,8 +219,57 @@ async def move(self, *paths, destination=None, resolver=None, cursor=None, wait=
:rtype: cterasdk.common.object.Object or :class:`cterasdk.lib.tasks.AwaitablePortalTask`
"""
try:
return await self._try_with_resolver(io.move, *[self.normalize(path) for path in paths],
destination=self.normalize(destination),
resolver=resolver, cursor=cursor, wait=wait)
return await Move(io.move, self._core, wait, *[self.normalize(path) for path in paths],
destination=self.normalize(destination), resolver=resolver, cursor=cursor).a_execute()
except ValueError:
raise ValueError('Move destination was not specified.')

async def get_share_info(self, path):
"""
Get share settings and recipients

:param str path: Path
"""
return await GetShareMetadata(io.list_shares, self._core, self.normalize(path)).a_execute()

async def share(self, path, recipients, as_project=True, allow_reshare=True, allow_sync=True):
"""
Share a file or a folder

:param str path: The path of the file or folder to share
:param list[cterasdk.core.types.Collaborator] recipients: A list of share recipients
:param bool,optional as_project: Share as a team project, defaults to True when the item is a cloud folder else False
:param bool,optional allow_reshare: Allow recipients to re-share this item, defaults to True
:param bool,optional allow_sync: Allow recipients to sync this item, defaults to True when the item is a cloud folder else False
:return: A list of all recipients added to the collaboration share
:rtype: list[cterasdk.core.types.Collaborator]
"""
return await io.share(self._core, self.normalize(path), recipients, as_project, allow_reshare, allow_sync)

async def add_share_recipients(self, path, recipients):
"""
Add share recipients

:param str path: The path of the file or folder
:param list[cterasdk.core.types.Collaborator] recipients: A list of share recipients
:return: A list of all recipients added
:rtype: list[cterasdk.core.types.Collaborator]
"""
return await io.add_share_recipients(self._core, self.normalize(path), recipients)

async def remove_share_recipients(self, path, accounts):
"""
Remove share recipients

:param str path: The path of the file or folder
:param list[cterasdk.core.types.PortalAccount] accounts: A list of portal user or group accounts
:return: A list of all share recipients removed
:rtype: list[cterasdk.core.types.PortalAccount]
"""
return await io.remove_share_recipients(self._core, self.normalize(path), accounts)

async def unshare(self, path):
"""
Unshare a file or a folder
"""
return await UnShare(io.update_share, self._core, self.normalize(path)).a_execute()
Loading