Path: blob/main/singlestoredb/fusion/handlers/files.py
801 views
#!/usr/bin/env python31from typing import Any2from typing import Dict3from typing import Optional45from .. import result6from ..handler import SQLHandler7from ..result import FusionSQLResult8from .utils import dt_isoformat9from .utils import get_file_space101112class ShowFilesHandler(SQLHandler):13"""14Generic handler for listing files in a personal/shared space.15""" # noqa: E5011617def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:18file_space = get_file_space(params)1920res = FusionSQLResult()21res.add_field('Name', result.STRING)2223if params['extended']:24res.add_field('Type', result.STRING)25res.add_field('Size', result.INTEGER)26res.add_field('Writable', result.STRING)27res.add_field('CreatedAt', result.DATETIME)28res.add_field('LastModifiedAt', result.DATETIME)2930files = []31for x in file_space.listdir(32params['at_path'] or '/',33recursive=params['recursive'],34return_objects=False,35):36info = file_space.info(x)37files.append(38tuple([39x, info.type, info.size or 0, info.writable,40dt_isoformat(info.created_at),41dt_isoformat(info.last_modified_at),42]),43)44res.set_rows(files)4546else:47res.set_rows([(x,) for x in file_space.listdir(48params['at_path'] or '/',49recursive=params['recursive'],50return_objects=False,51)])5253if params['like']:54res = res.like(Name=params['like'])5556return res.order_by(**params['order_by']).limit(params['limit'])575859class ShowPersonalFilesHandler(ShowFilesHandler):60"""61SHOW PERSONAL FILES62[ at_path ] [ <like> ]63[ <order-by> ]64[ <limit> ] [ recursive ] [ extended ];6566# File path to list67at_path = AT '<path>'6869# Should the listing be recursive?70recursive = RECURSIVE7172# Should extended attributes be shown?73extended = EXTENDED7475Description76-----------77Displays a list of files in a personal/shared space.7879Arguments80---------81* ``<path>``: A path in the personal/shared space.82* ``<pattern>``: A pattern similar to SQL LIKE clause.83Uses ``%`` as the wildcard character.8485Remarks86-------87* Use the ``LIKE`` clause to specify a pattern and return only the88files that match the specified pattern.89* The ``LIMIT`` clause limits the number of results to the90specified number.91* Use the ``ORDER BY`` clause to sort the results by the specified92key. By default, the results are sorted in the ascending order.93* The ``AT`` clause specifies the path in the personal/shared94space to list the files from.95* Use the ``RECURSIVE`` clause to list the files recursively.96* To return more information about the files, use the ``EXTENDED``97clause.9899Examples100--------101The following command lists the files at a specific path::102103SHOW PERSONAL FILES AT "/data/";104105The following command lists the files recursively with106additional information::107108SHOW PERSONAL FILES RECURSIVE EXTENDED;109110See Also111--------112* ``SHOW SHARED FILES``113* ``UPLOAD PERSONAL FILE``114* ``UPLOAD SHARED FILE``115* ``DOWNLOAD PERSONAL FILE``116* ``DOWNLOAD SHARED FILE``117118""" # noqa: E501119def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:120params['file_location'] = 'PERSONAL'121return super().run(params)122123124class ShowSharedFilesHandler(ShowFilesHandler):125"""126SHOW SHARED FILES127[ at_path ] [ <like> ]128[ <order-by> ]129[ <limit> ] [ recursive ] [ extended ];130131# File path to list132at_path = AT '<path>'133134# Should the listing be recursive?135recursive = RECURSIVE136137# Should extended attributes be shown?138extended = EXTENDED139140Description141-----------142Displays a list of files in a personal/shared space.143144Arguments145---------146* ``<path>``: A path in the personal/shared space.147* ``<pattern>``: A pattern similar to SQL LIKE clause.148Uses ``%`` as the wildcard character.149150Remarks151-------152* Use the ``LIKE`` clause to specify a pattern and return only the153files that match the specified pattern.154* The ``LIMIT`` clause limits the number of results to the155specified number.156* Use the ``ORDER BY`` clause to sort the results by the specified157key. By default, the results are sorted in the ascending order.158* The ``AT`` clause specifies the path in the personal/shared159space to list the files from.160* Use the ``RECURSIVE`` clause to list the files recursively.161* To return more information about the files, use the ``EXTENDED``162clause.163164Examples165--------166The following command lists the files at a specific path::167168SHOW SHARED FILES AT "/data/";169170The following command lists the files recursively with171additional information::172173SHOW SHARED FILES RECURSIVE EXTENDED;174175See Also176--------177* ``SHOW PERSONAL FILES``178* ``UPLOAD PERSONAL FILE``179* ``UPLOAD SHARED FILE``180* ``DOWNLOAD PERSONAL FILE``181* ``DOWNLOAD SHARED FILE``182183""" # noqa: E501184def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:185params['file_location'] = 'SHARED'186return super().run(params)187188189ShowPersonalFilesHandler.register(overwrite=True)190ShowSharedFilesHandler.register(overwrite=True)191192193class UploadFileHandler(SQLHandler):194"""195Generic handler for uploading files to a personal/shared space.196""" # noqa: E501197198def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:199file_space = get_file_space(params)200file_space.upload_file(201params['local_path'], params['path'],202overwrite=params['overwrite'],203)204return None205206207class UploadPersonalFileHandler(UploadFileHandler):208"""209UPLOAD PERSONAL FILE TO path210FROM local_path [ overwrite ];211212# Path to file213path = '<filename>'214215# Path to local file216local_path = '<local-path>'217218# Should an existing file be overwritten?219overwrite = OVERWRITE220221Description222-----------223Uploads a file to a personal/shared space.224225Arguments226---------227* ``<filename>``: The filename in the personal/shared space where the file is uploaded.228* ``<local-path>``: The path to the file to upload in the local229directory.230231Remarks232-------233* If the ``OVERWRITE`` clause is specified, any existing file at the234specified path in the personal/shared space is overwritten.235236Examples237--------238The following command uploads a file to a personal/shared space and overwrite any239existing files at the specified path::240241UPLOAD PERSONAL FILE TO 'stats.csv'242FROM '/tmp/user/stats.csv' OVERWRITE;243244See Also245--------246* ``UPLOAD SHARED FILE``247* ``DOWNLOAD PERSONAL FILE``248* ``DOWNLOAD SHARED FILE``249250""" # noqa: E501251252def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:253params['file_location'] = 'PERSONAL'254return super().run(params)255256257class UploadSharedFileHandler(UploadFileHandler):258"""259UPLOAD SHARED FILE TO path260FROM local_path [ overwrite ];261262# Path to file263path = '<filename>'264265# Path to local file266local_path = '<local-path>'267268# Should an existing file be overwritten?269overwrite = OVERWRITE270271Description272-----------273Uploads a file to a personal/shared space.274275Arguments276---------277* ``<filename>``: The filename in the personal/shared space where the file is uploaded.278* ``<local-path>``: The path to the file to upload in the local279directory.280281Remarks282-------283* If the ``OVERWRITE`` clause is specified, any existing file at the284specified path in the personal/shared space is overwritten.285286Examples287--------288The following command uploads a file to a personal/shared space and overwrite any289existing files at the specified path::290291UPLOAD SHARED FILE TO 'stats.csv'292FROM '/tmp/user/stats.csv' OVERWRITE;293294See Also295--------296* ``UPLOAD PERSONAL FILE``297* ``DOWNLOAD PERSONAL FILE``298* ``DOWNLOAD SHARED FILE``299300""" # noqa: E501301302def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:303params['file_location'] = 'SHARED'304return super().run(params)305306307UploadPersonalFileHandler.register(overwrite=True)308UploadSharedFileHandler.register(overwrite=True)309310311class DownloadFileHandler(SQLHandler):312"""313Generic handler for downloading files from a personal/shared space.314""" # noqa: E501315316def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:317file_space = get_file_space(params)318319out = file_space.download_file(320params['path'],321local_path=params['local_path'] or None,322overwrite=params['overwrite'],323encoding=params['encoding'] or None,324)325326if not params['local_path']:327res = FusionSQLResult()328if params['encoding']:329res.add_field('Data', result.STRING)330else:331res.add_field('Data', result.BLOB)332res.set_rows([(out,)])333return res334335return None336337338class DownloadPersonalFileHandler(DownloadFileHandler):339"""340DOWNLOAD PERSONAL FILE path341[ local_path ]342[ overwrite ]343[ encoding ];344345# Path to file346path = '<path>'347348# Path to local file349local_path = TO '<local-path>'350351# Should an existing file be overwritten?352overwrite = OVERWRITE353354# File encoding355encoding = ENCODING '<encoding>'356357Description358-----------359Download a file from a personal/shared space.360361Arguments362---------363* ``<path>``: The path to the file to download in a personal/shared space.364* ``<encoding>``: The encoding to apply to the downloaded file.365* ``<local-path>``: Specifies the path in the local directory366where the file is downloaded.367368Remarks369-------370* If the ``OVERWRITE`` clause is specified, any existing file at371the download location is overwritten.372* By default, files are downloaded in binary encoding. To view373the contents of the file on the standard output, use the374``ENCODING`` clause and specify an encoding.375* If ``<local-path>`` is not specified, the file is displayed376on the standard output.377378Examples379--------380The following command displays the contents of the file on the381standard output::382383DOWNLOAD PERSONAL FILE '/data/stats.csv' ENCODING 'utf8';384385The following command downloads a file to a specific location and386overwrites any existing file with the name ``stats.csv`` on the local storage::387388DOWNLOAD PERSONAL FILE '/data/stats.csv'389TO '/tmp/data.csv' OVERWRITE;390391See Also392--------393* ``DOWNLOAD SHARED FILE``394* ``UPLOAD PERSONAL FILE``395* ``UPLOAD SHARED FILE``396397""" # noqa: E501398399def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:400params['file_location'] = 'PERSONAL'401return super().run(params)402403404class DownloadSharedFileHandler(DownloadFileHandler):405"""406DOWNLOAD SHARED FILE path407[ local_path ]408[ overwrite ]409[ encoding ];410411# Path to file412path = '<path>'413414# Path to local file415local_path = TO '<local-path>'416417# Should an existing file be overwritten?418overwrite = OVERWRITE419420# File encoding421encoding = ENCODING '<encoding>'422423Description424-----------425Download a file from a personal/shared space.426427Arguments428---------429* ``<path>``: The path to the file to download in a personal/shared space.430* ``<encoding>``: The encoding to apply to the downloaded file.431* ``<local-path>``: Specifies the path in the local directory432where the file is downloaded.433434Remarks435-------436* If the ``OVERWRITE`` clause is specified, any existing file at437the download location is overwritten.438* By default, files are downloaded in binary encoding. To view439the contents of the file on the standard output, use the440``ENCODING`` clause and specify an encoding.441* If ``<local-path>`` is not specified, the file is displayed442on the standard output.443444Examples445--------446The following command displays the contents of the file on the447standard output::448449DOWNLOAD SHARED FILE '/data/stats.csv' ENCODING 'utf8';450451The following command downloads a file to a specific location and452overwrites any existing file with the name ``stats.csv`` on the local storage::453454DOWNLOAD SHARED FILE '/data/stats.csv'455TO '/tmp/data.csv' OVERWRITE;456457See Also458--------459* ``DOWNLOAD PERSONAL FILE``460* ``UPLOAD PERSONAL FILE``461* ``UPLOAD SHARED FILE``462463""" # noqa: E501464465def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:466params['file_location'] = 'SHARED'467return super().run(params)468469470DownloadPersonalFileHandler.register(overwrite=True)471DownloadSharedFileHandler.register(overwrite=True)472473474class DropHandler(SQLHandler):475"""476Generic handler for deleting files/folders from a personal/shared space.477""" # noqa: E501478479def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:480file_space = get_file_space(params)481482file_type = params['file_type']483if not file_type:484raise KeyError('file type was not specified')485486file_type = file_type.lower()487if file_type not in ['file', 'folder']:488raise ValueError('file type must be either FILE or FOLDER')489490if file_type == 'file':491file_space.remove(params['path'])492elif file_type == 'folder':493if params['recursive']:494file_space.removedirs(params['path'])495else:496file_space.rmdir(params['path'])497498return None499500501class DropPersonalHandler(DropHandler):502"""503DROP PERSONAL <file-type> path504[ recursive ];505506# Path to file507path = '<path>'508509# Should folders be deleted recursively?510recursive = RECURSIVE511512Description513-----------514Deletes a file/folder from a personal/shared space.515516Arguments517---------518* ``<file-type>``: The type of the file, it can519be either 'FILE' or 'FOLDER'.520* ``<path>``: The path to the file to delete in a personal/shared space.521522Remarks523-------524* The ``RECURSIVE`` clause indicates that the specified folder525is deleted recursively.526527Example528--------529The following commands delete a file/folder from a personal/shared space::530531DROP PERSONAL FILE '/data/stats.csv';532DROP PERSONAL FOLDER '/data/' RECURSIVE;533534See Also535--------536* ``DROP SHARED FILE``537* ``DROP SHARED FOLDER``538539""" # noqa: E501540541def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:542params['file_location'] = 'PERSONAL'543return super().run(params)544545546class DropSharedHandler(DropHandler):547"""548DROP SHARED <file-type> path549[ recursive ];550551# Path to file552path = '<path>'553554# Should folders be deleted recursively?555recursive = RECURSIVE556557Description558-----------559Deletes a file/folder from a personal/shared space.560561Arguments562---------563* ``<file-type>``: The type of the file, it can564be either 'FILE' or 'FOLDER'.565* ``<path>``: The path to the file to delete in a personal/shared space.566567Remarks568-------569* The ``RECURSIVE`` clause indicates that the specified folder570is deleted recursively.571572Example573--------574The following commands delete a file/folder from a personal/shared space::575576DROP SHARED FILE '/data/stats.csv';577DROP SHARED FOLDER '/data/' RECURSIVE;578579See Also580--------581* ``DROP PERSONAL FILE``582* ``DROP PERSONAL FOLDER``583584""" # noqa: E501585586def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:587params['file_location'] = 'SHARED'588return super().run(params)589590591DropPersonalHandler.register(overwrite=True)592DropSharedHandler.register(overwrite=True)593594595class CreateFolderHandler(SQLHandler):596"""597Generic handler for creating folders in a personal/shared space.598"""599600def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:601file_space = get_file_space(params)602file_space.mkdir(params['path'], overwrite=params['overwrite'])603return None604605606class CreatePersonalFolderHandler(CreateFolderHandler):607"""608CREATE PERSONAL FOLDER path609[ overwrite ];610611# Path to folder612path = '<path>'613614# Should an existing folder be overwritten?615overwrite = OVERWRITE616617Description618-----------619Creates a new folder at the specified path in a personal/shared space.620621Arguments622---------623* ``<path>``: The path in a personal/shared space where the folder624is created. The path must end with a trailing slash (/).625626Remarks627-------628* If the ``OVERWRITE`` clause is specified, any existing629folder at the specified path is overwritten.630631Example632-------633The following command creates a folder in a personal/shared space::634635CREATE PERSONAL FOLDER `/data/csv/`;636637See Also638--------639* ``CREATE SHARED FOLDER``640641"""642643def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:644params['file_location'] = 'PERSONAL'645return super().run(params)646647648class CreateSharedFolderHandler(CreateFolderHandler):649"""650CREATE SHARED FOLDER path651[ overwrite ];652653# Path to folder654path = '<path>'655656# Should an existing folder be overwritten?657overwrite = OVERWRITE658659Description660-----------661Creates a new folder at the specified path in a personal/shared space.662663Arguments664---------665* ``<path>``: The path in a personal/shared space where the folder666is created. The path must end with a trailing slash (/).667668Remarks669-------670* If the ``OVERWRITE`` clause is specified, any existing671folder at the specified path is overwritten.672673Example674-------675The following command creates a folder in a personal/shared space::676677CREATE SHARED FOLDER `/data/csv/`;678679See Also680--------681* ``CREATE PERSONAL FOLDER``682683"""684685def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:686params['file_location'] = 'SHARED'687return super().run(params)688689690CreatePersonalFolderHandler.register(overwrite=True)691CreateSharedFolderHandler.register(overwrite=True)692693694