Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f2afac4
initial commit module initializer
saimonation Nov 12, 2025
e967420
support edge filer dual network interfaces
saimonation Dec 25, 2025
0b76653
edge file system module: commands specific exceptions
saimonation Dec 28, 2025
4cf7863
update method documentation
saimonation Dec 29, 2025
5ec2760
add get metadata exception to edge file system module, add async rena…
saimonation Dec 29, 2025
e495281
update to support copy into a directory, and rename at the target for…
saimonation Jan 3, 2026
de3abf4
flake8:fix 1
saimonation Jan 3, 2026
8dd5a7c
pass flake8
saimonation Jan 3, 2026
278bf8d
update modules to pass lint check
saimonation Jan 3, 2026
d369b70
pass flake after lint
saimonation Jan 3, 2026
4e01304
resolve pylint
saimonation Jan 4, 2026
8f4c7ca
change method to static function
saimonation Jan 4, 2026
a9e60b1
fox doc8 errors
saimonation Jan 4, 2026
95bbebf
update test versions
saimonation Jan 4, 2026
94c52f3
update to pass listing versions ut
Jan 4, 2026
63968f2
update ut
saimonation Jan 4, 2026
1a8af30
update directory services ut
saimonation Jan 4, 2026
c3447ff
allow parsing of listdir response and patch call to ensure directory
saimonation Jan 4, 2026
e9d7468
update to pass listdir test
Jan 4, 2026
ace9304
update browser
Jan 4, 2026
b3c3225
update response when renaming objects in the portal to return the upd…
saimonation Jan 4, 2026
6ecf728
update rename response
saimonation Jan 4, 2026
7b27d68
update test browser
saimonation Jan 4, 2026
1ec5d60
update network module test
saimonation Jan 4, 2026
f15ab51
update to pass lint ut and flake
Jan 4, 2026
da77a8e
ensure functionality of file access is complete
saimonation Jan 4, 2026
2b30198
update to pass doc build
saimonation Jan 4, 2026
9c3c46f
update to pass doc build
saimonation Jan 4, 2026
3cf0de6
resolve remaining documentation errors
saimonation Jan 4, 2026
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
322 changes: 192 additions & 130 deletions cterasdk/asynchronous/core/files/browser.py

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions cterasdk/asynchronous/core/files/io.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ....cio import core as fs
from ....cio.core import commands as fs


async def listdir(core, param):
Expand Down Expand Up @@ -33,9 +33,8 @@ async def handle(core, param):
return await core.io.download(param)


async def handle_many(core, param, directory):
async with fs.EnsureDirectory(listdir, core, directory) as (_, resource):
return await core.io.download_zip(str(resource.cloudFolderInfo.uid), param)
async def handle_many(core, cloudfolder, param):
return await core.io.download_zip(cloudfolder, param)


async def upload(core, cloudfolder, param):
Expand Down
181 changes: 122 additions & 59 deletions cterasdk/asynchronous/edge/files/browser.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,100 @@
from ..base_command import BaseCommand
from ....cio.edge import EdgePath, ListDirectory, RecursiveIterator, GetMetadata, Open, OpenMany, Upload, \
UploadFile, CreateDirectory, Copy, Move, Delete, Download, DownloadMany
from ....cio.edge.commands import ListDirectory, RecursiveIterator, GetMetadata, Open, OpenMany, Upload, \
CreateDirectory, Copy, Move, Delete, Download, DownloadMany, Rename, EnsureDirectory
from ....lib.storage import commonfs
from . import io


class FileBrowser(BaseCommand):
""" Edge Filer File Browser APIs """
"""Edge Filer File Browser API."""

async def listdir(self, path):
"""
List Directory
List directory contents.

:param str path: Path
:param str path: Path.
:returns: Directory contents.
:rtype: AsyncIterator[cterasdk.cio.edge.types.EdgeResource]
:raises cterasdk.exceptions.io.core.GetMetadataError: If the directory was not found.
:raises cterasdk.exceptions.io.core.NotADirectoryException: If the target path is not a directory.
:raises cterasdk.exceptions.io.edge.ListDirectoryError: Raised on error to fetch directory contents.
"""
for o in await ListDirectory(io.listdir, self._edge, self.normalize(path)).a_execute():
yield o
async with EnsureDirectory(io.listdir, self._edge, path):
for o in await ListDirectory(io.listdir, self._edge, path).a_execute():
yield o

async def walk(self, path=None):
"""
Walk Directory Contents
Walk directory contents.

:param str, defaults to the root directory path: Path to walk
:param str, optional path: Path to walk. Defaults to the root directory.
:returns: A generator of file-system objects.
:rtype: AsyncIterator[cterasdk.cio.edge.types.EdgeResource]
:raises cterasdk.exceptions.io.core.GetMetadataError: If the directory was not found.
:raises cterasdk.exceptions.io.core.NotADirectoryException: If the target path is not a directory.
"""
async for o in RecursiveIterator(io.listdir, self._edge, self.normalize(path)).a_generate():
yield o
async with EnsureDirectory(io.listdir, self._edge, path):
async for o in RecursiveIterator(io.listdir, self._edge, path).a_generate():
yield o

async def properties(self, path):
"""
Get object properties.

:param str path: Path.
:returns: Object properties.
:rtype: cterasdk.cio.edge.types.EdgeResource
:raises cterasdk.exceptions.io.core.GetMetadataError: Raised on error to obtain object metadata.
"""
async with GetMetadata(io.listdir, self._edge, path, False) as (_, metadata):
return metadata

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

:param str path: Path
:param str path: Path.
:returns: ``True`` if the item exists, ``False`` otherwise.
:rtype: bool
"""
async with GetMetadata(io.listdir, self._edge, self.normalize(path), True) as (exists, *_):
async with GetMetadata(io.listdir, self._edge, path, True) as (exists, *_):
return exists

async def handle(self, path):
"""
Get File Handle.
Get file handle.

:param str path: Path to a file
:param str path: Path to a file.
:returns: File handle.
:rtype: object
:raises cterasdk.exceptions.io.edge.OpenError: Raised on error to obtain a file handle.
"""
return await Open(io.handle, self._edge, self.normalize(path)).a_execute()
return await Open(io.handle, self._edge, path).a_execute()

async def handle_many(self, directory, *objects):
"""
Get a Zip Archive File Handle.
Get a ZIP archive file handle.

:param str directory: Path to a folder
:param args objects: List of files and folders
:param str directory: Path to a folder.
:param args objects: Files and folders to include.
:returns: File handle.
:rtype: object
"""
return await OpenMany(io.handle_many, self._edge, directory, *objects).a_execute()

async def download(self, path, destination=None):
"""
Download a file
Download a file.

:param str path: The file path on the Edge Filer
:param str,optional destination:
File destination, if it is a directory, the original filename will be kept, defaults to the default directory
:param str path: The file path on the Edge Filer.
:param str, optional destination:
File destination. If a directory is provided, the original filename is preserved.
Defaults to the default download directory.
:returns: Path to the local file.
:rtype: str
:raises cterasdk.exceptions.io.edge.OpenError: Raised on error to obtain a file handle.
"""
return await Download(io.handle, self._edge, self.normalize(path), destination).a_execute()
return await Download(io.handle, self._edge, path, destination).a_execute()

async def download_many(self, target, objects, destination=None):
"""
Expand All @@ -75,77 +110,105 @@ async def download_many(self, target, objects, destination=None):
List of file and/or directory names to include in the download.
:param str destination:
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.
the original filename is preserved. Defaults to the default download directory.
:returns: Path to the local file.
:rtype: str
"""
return await DownloadMany(io.handle_many, self._edge, self.normalize(target), objects, destination).a_execute()
return await DownloadMany(io.handle_many, self._edge, target, objects, destination).a_execute()

async def upload(self, name, destination, handle):
async def upload(self, destination, handle, name=None):
"""
Upload from file handle.
Upload from a file handle.

:param str name: File name.
:param str destination: Path to remote directory.
:param object handle: Handle.
:param str destination: Remote path.
:param object handle: File-like handle.
:param str, optional name: Filename to use if it cannot be derived from ``destination``
:returns: Remote file path.
:rtype: str
:raises cterasdk.exceptions.io.edge.UploadError: Raised on upload failure.
"""
return await Upload(io.upload, self._edge, io.listdir, name, self.normalize(destination), handle).a_execute()
return await Upload(io.upload, self._edge, io.listdir, destination, handle, name).a_execute()

async def upload_file(self, path, destination):
"""
Upload a file.

:param str path: Local path
:param str destination: Remote path
:param str path: Local path.
:param str destination: Remote path.
:returns: Remote file path.
:rtype: str
:raises cterasdk.exceptions.io.edge.UploadError: Raised on upload failure.
"""
return await UploadFile(io.upload, self._edge, io.listdir, path, self.normalize(destination)).a_execute()
_, name = commonfs.split_file_directory(path)
with open(path, 'rb') as handle:
return await self.upload(destination, handle, name)

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

:param str path: Directory path
:param str path: Directory path.
:returns: Remote directory path.
:rtype: str
:raises cterasdk.exceptions.io.edge.CreateDirectoryError: Raised on error to create a directory.
"""
return await CreateDirectory(io.mkdir, self._edge, self.normalize(path)).a_execute()
return await CreateDirectory(io.mkdir, self._edge, path).a_execute()

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

:param str path: Directory path
:param str path: Directory path.
:returns: Remote directory path.
:rtype: str
:raises cterasdk.exceptions.io.edge.CreateDirectoryError: Raised on error to create a directory.
"""
return await CreateDirectory(io.mkdir, self._edge, self.normalize(path), True).a_execute()
return await CreateDirectory(io.mkdir, self._edge, path, True).a_execute()

async def copy(self, path, destination=None, overwrite=False):
"""
Copy a file or a folder
Copy a file or directory.

:param str path: Source file or folder path
:param str destination: Destination folder path
:param bool,optional overwrite: Overwrite on conflict, defaults to False
:param str path: Source file or directory path.
:param str destination: Destination directory path.
:param bool, optional overwrite: Overwrite on conflict. Defaults to ``False``.
:raises cterasdk.exceptions.io.edge.CopyError: Raised on error to copy a file or directory.
"""
if destination is None:
raise ValueError('Copy destination was not specified.')
return await Copy(io.copy, self._edge, self.normalize(path), self.normalize(destination), overwrite).a_execute()
return await Copy(io.copy, self._edge, io.listdir, path, destination, overwrite).a_execute()

async def move(self, path, destination=None, overwrite=False):
"""
Move a file or a folder
Move a file or directory.

:param str path: Source file or folder path
:param str destination: Destination folder path
:param bool,optional overwrite: Overwrite on conflict, defaults to False
:param str path: Source file or directory path.
:param str destination: Destination directory path.
:param bool, optional overwrite: Overwrite on conflict. Defaults to ``False``.
:raises cterasdk.exceptions.io.edge.MoveError: Raised on error to move a file or directory.
"""
if destination is None:
raise ValueError('Move destination was not specified.')
return await Move(io.move, self._edge, self.normalize(path), self.normalize(destination), overwrite).a_execute()
return await Move(io.move, self._edge, io.listdir, path, destination, overwrite).a_execute()

async def delete(self, path):
async def rename(self, path, name, overwrite=False):
"""
Delete a file
Rename a file or directory.

:param str path: File path
:param str path: Path of the file or directory to rename.
:param str name: New name for the file or directory.
:param bool, optional overwrite: Overwrite on conflict. Defaults to ``False``.
:returns: Remote object path.
:rtype: str
:raises cterasdk.exceptions.io.edge.RenameError: Raised on error to rename a file or directory.
"""
return await Delete(io.delete, self._edge, self.normalize(path)).a_execute()
return await Rename(io.move, self._edge, io.listdir, path, name, overwrite).a_execute()

@staticmethod
def normalize(path):
return EdgePath('/', path)
async def delete(self, path):
"""
Delete a file or directory.

:param str path: File or directory path.
:raises cterasdk.exceptions.io.edge.DeleteError: Raised on error to delete a file or directory.
"""
return await Delete(io.delete, self._edge, path).a_execute()
48 changes: 43 additions & 5 deletions cterasdk/cio/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import urllib.parse
from pathlib import PurePosixPath
from ..objects.uri import quote
from ..common import Object
from ..common.utils import utf8_decode
from ..convert.serializers import toxmlstr

Expand Down Expand Up @@ -33,40 +34,77 @@ def reference(self):
def relative(self):
return self._reference.as_posix()

@property
def relative_encode(self):
return urllib.parse.quote(self._reference.as_posix())

@property
def name(self):
return self._reference.name

@property
def parent(self):
return self.__class__(self._scope.as_posix(), self._reference.parent.as_posix())
return self.__class__(self._reference.parent.as_posix()) # pylint: disable=no-value-for-parameter

@property
def absolute(self):
return f'/{self.scope.joinpath(self.reference).as_posix()}'

@property
def absolute_encode(self):
return f'/{self.scope.joinpath(quote(self.reference.as_posix())).as_posix()}'
return f'/{self.scope.joinpath(urllib.parse.quote(self.reference.as_posix())).as_posix()}'

@property
def absolute_parent(self):
return self.parent.as_posix()

@property
def extension(self):
return self.reference.suffix

def join(self, p):
"""
Join Path.

:param str p: Path.
"""
return self.__class__(self._scope.as_posix(), self.reference.joinpath(p).as_posix())
return self.__class__(self.reference.joinpath(p).as_posix()) # pylint: disable=no-value-for-parameter

@property
def parts(self):
return self.reference.parts

def __eq__(self, p):
return self.absolute == p.absolute

def __str__(self):
return self.relative


class BaseResource(Object):
"""
Class for a Filesystem Resource.

:ivar str name: Resource name
:ivar cterasdk.cio.common.BasePath path: Path Object
:ivar bool is_dir: ``True`` if directory, ``False`` otherwise
:ivar int size: Size
:ivar datetime.datetime last_modified: Last Modified
:ivar str extension: Extension
"""
def __init__(self, name, path, is_dir, size, last_modified):
super().__init__(name=name, path=path, is_dir=is_dir, size=size, last_modified=last_modified, extension=path.extension)

def __repr__(self):
return (
f"{self.__class__.__name__}("
f"'is_dir': {self.is_dir}, "
f"'size': {self.size}, "
f"'path': {self.path}}})"
)

def __str__(self):
return self.absolute
return str(self.path)


def encode_stream(fd, size):
Expand Down
Empty file added cterasdk/cio/core/__init__.py
Empty file.
Loading