From faa23159f00955332d4f8477151a315a79855202 Mon Sep 17 00:00:00 2001 From: Florian Maas Date: Thu, 29 Aug 2024 15:30:05 +0200 Subject: [PATCH 1/2] Accept Path for workspaceclient.files --- databricks/sdk/service/files.py | 130 ++++++++++++++++++-------------- tests/integration/test_files.py | 38 ++++++---- 2 files changed, 99 insertions(+), 69 deletions(-) diff --git a/databricks/sdk/service/files.py b/databricks/sdk/service/files.py index 255e1c1a0..4d3f904db 100755 --- a/databricks/sdk/service/files.py +++ b/databricks/sdk/service/files.py @@ -6,6 +6,8 @@ from dataclasses import dataclass from typing import BinaryIO, Dict, Iterator, List, Optional +from pathlib import Path + from ._internal import _escape_multi_segment_path_parameter, _repeated_dict _LOG = logging.getLogger('databricks.sdk') @@ -81,12 +83,15 @@ def from_dict(cls, d: Dict[str, any]) -> CloseResponse: @dataclass class Create: - path: str + path: str | Path """The path of the new file. The path should be the absolute DBFS path.""" overwrite: Optional[bool] = None """The flag that specifies whether to overwrite existing file/files.""" + def __post_init__(self): + self.path = str(self.path) + def as_dict(self) -> dict: """Serializes the Create into a dictionary suitable for use as a JSON request body.""" body = {} @@ -134,13 +139,16 @@ def from_dict(cls, d: Dict[str, any]) -> CreateResponse: @dataclass class Delete: - path: str + path: str | Path """The path of the file or directory to delete. The path should be the absolute DBFS path.""" recursive: Optional[bool] = None """Whether or not to recursively delete the directory's contents. Deleting empty directories can be done without providing the recursive flag.""" + def __post_init__(self): + self.path = str(self.path) + def as_dict(self) -> dict: """Serializes the Delete into a dictionary suitable for use as a JSON request body.""" body = {} @@ -358,9 +366,12 @@ def from_dict(cls, d: Dict[str, any]) -> ListStatusResponse: @dataclass class MkDirs: - path: str + path: str | Path """The path of the new directory. The path should be the absolute DBFS path.""" + def __post_init__(self): + self.path = str(self.path) + def as_dict(self) -> dict: """Serializes the MkDirs into a dictionary suitable for use as a JSON request body.""" body = {} @@ -389,12 +400,16 @@ def from_dict(cls, d: Dict[str, any]) -> MkDirsResponse: @dataclass class Move: - source_path: str + source_path: str | Path """The source path of the file or directory. The path should be the absolute DBFS path.""" - destination_path: str + destination_path: str | Path """The destination path of the file or directory. The path should be the absolute DBFS path.""" + def __post_init__(self): + self.source_path = str(self.source_path) + self.destination_path = str(self.destination_path) + def as_dict(self) -> dict: """Serializes the Move into a dictionary suitable for use as a JSON request body.""" body = {} @@ -424,7 +439,7 @@ def from_dict(cls, d: Dict[str, any]) -> MoveResponse: @dataclass class Put: - path: str + path: str | Path """The path of the new file. The path should be the absolute DBFS path.""" contents: Optional[str] = None @@ -433,6 +448,9 @@ class Put: overwrite: Optional[bool] = None """The flag that specifies whether to overwrite existing file/files.""" + def __post_init__(self): + self.path = str(self.path) + def as_dict(self) -> dict: """Serializes the Put into a dictionary suitable for use as a JSON request body.""" body = {} @@ -545,7 +563,7 @@ def close(self, handle: int): self._api.do('POST', '/api/2.0/dbfs/close', body=body, headers=headers) - def create(self, path: str, *, overwrite: Optional[bool] = None) -> CreateResponse: + def create(self, path: str | Path, *, overwrite: Optional[bool] = None) -> CreateResponse: """Open a stream. Opens a stream to write to a file and returns a handle to this stream. There is a 10 minute idle @@ -557,7 +575,7 @@ def create(self, path: str, *, overwrite: Optional[bool] = None) -> CreateRespon 1. Issue a ``create`` call and get a handle. 2. Issue one or more ``add-block`` calls with the handle you have. 3. Issue a ``close`` call with the handle you have. - :param path: str + :param path: str | Path The path of the new file. The path should be the absolute DBFS path. :param overwrite: bool (optional) The flag that specifies whether to overwrite existing file/files. @@ -566,13 +584,13 @@ def create(self, path: str, *, overwrite: Optional[bool] = None) -> CreateRespon """ body = {} if overwrite is not None: body['overwrite'] = overwrite - if path is not None: body['path'] = path + if path is not None: body['path'] = str(path) headers = {'Accept': 'application/json', 'Content-Type': 'application/json', } res = self._api.do('POST', '/api/2.0/dbfs/create', body=body, headers=headers) return CreateResponse.from_dict(res) - def delete(self, path: str, *, recursive: Optional[bool] = None): + def delete(self, path: str | Path, *, recursive: Optional[bool] = None): """Delete a file/directory. Delete the file or directory (optionally recursively delete all files in the directory). This call @@ -590,7 +608,7 @@ def delete(self, path: str, *, recursive: Optional[bool] = None): control and manageability, such as selective deletes, and the possibility to automate periodic delete jobs. - :param path: str + :param path: str | Path The path of the file or directory to delete. The path should be the absolute DBFS path. :param recursive: bool (optional) Whether or not to recursively delete the directory's contents. Deleting empty directories can be @@ -599,32 +617,32 @@ def delete(self, path: str, *, recursive: Optional[bool] = None): """ body = {} - if path is not None: body['path'] = path + if path is not None: body['path'] = str(path) if recursive is not None: body['recursive'] = recursive headers = {'Accept': 'application/json', 'Content-Type': 'application/json', } self._api.do('POST', '/api/2.0/dbfs/delete', body=body, headers=headers) - def get_status(self, path: str) -> FileInfo: + def get_status(self, path: str | Path) -> FileInfo: """Get the information of a file or directory. Gets the file information for a file or directory. If the file or directory does not exist, this call throws an exception with `RESOURCE_DOES_NOT_EXIST`. - :param path: str + :param path: str | Path The path of the file or directory. The path should be the absolute DBFS path. :returns: :class:`FileInfo` """ query = {} - if path is not None: query['path'] = path + if path is not None: query['path'] = str(path) headers = {'Accept': 'application/json', } res = self._api.do('GET', '/api/2.0/dbfs/get-status', query=query, headers=headers) return FileInfo.from_dict(res) - def list(self, path: str) -> Iterator[FileInfo]: + def list(self, path: str | Path) -> Iterator[FileInfo]: """List directory contents or file details. List the contents of a directory, or details of the file. If the file or directory does not exist, @@ -637,21 +655,21 @@ def list(self, path: str) -> Iterator[FileInfo]: (dbutils.fs)](/dev-tools/databricks-utils.html#dbutils-fs), which provides the same functionality without timing out. - :param path: str + :param path: str | Path The path of the file or directory. The path should be the absolute DBFS path. :returns: Iterator over :class:`FileInfo` """ query = {} - if path is not None: query['path'] = path + if path is not None: query['path'] = str(path) headers = {'Accept': 'application/json', } json = self._api.do('GET', '/api/2.0/dbfs/list', query=query, headers=headers) parsed = ListStatusResponse.from_dict(json).files return parsed if parsed is not None else [] - def mkdirs(self, path: str): + def mkdirs(self, path: str | Path): """Create a directory. Creates the given directory and necessary parent directories if they do not exist. If a file (not a @@ -659,18 +677,18 @@ def mkdirs(self, path: str): `RESOURCE_ALREADY_EXISTS`. **Note**: If this operation fails, it might have succeeded in creating some of the necessary parent directories. - :param path: str + :param path: str | Path The path of the new directory. The path should be the absolute DBFS path. """ body = {} - if path is not None: body['path'] = path + if path is not None: body['path'] = str(path) headers = {'Accept': 'application/json', 'Content-Type': 'application/json', } self._api.do('POST', '/api/2.0/dbfs/mkdirs', body=body, headers=headers) - def move(self, source_path: str, destination_path: str): + def move(self, source_path: str | Path, destination_path: str | Path): """Move a file. Moves a file from one location to another location within DBFS. If the source file does not exist, @@ -678,21 +696,21 @@ def move(self, source_path: str, destination_path: str): destination path, this call throws an exception with `RESOURCE_ALREADY_EXISTS`. If the given source path is a directory, this call always recursively moves all files. - :param source_path: str + :param source_path: str | Path The source path of the file or directory. The path should be the absolute DBFS path. - :param destination_path: str + :param destination_path: str | Path The destination path of the file or directory. The path should be the absolute DBFS path. """ body = {} - if destination_path is not None: body['destination_path'] = destination_path - if source_path is not None: body['source_path'] = source_path + if destination_path is not None: body['destination_path'] = str(destination_path) + if source_path is not None: body['source_path'] = str(source_path) headers = {'Accept': 'application/json', 'Content-Type': 'application/json', } self._api.do('POST', '/api/2.0/dbfs/move', body=body, headers=headers) - def put(self, path: str, *, contents: Optional[str] = None, overwrite: Optional[bool] = None): + def put(self, path: str | Path, *, contents: Optional[str] = None, overwrite: Optional[bool] = None): """Upload a file. Uploads a file through the use of multipart form post. It is mainly used for streaming uploads, but @@ -706,7 +724,7 @@ def put(self, path: str, *, contents: Optional[str] = None, overwrite: Optional[ If you want to upload large files, use the streaming upload. For details, see :method:dbfs/create, :method:dbfs/addBlock, :method:dbfs/close. - :param path: str + :param path: str | Path The path of the new file. The path should be the absolute DBFS path. :param contents: str (optional) This parameter might be absent, and instead a posted file will be used. @@ -718,12 +736,12 @@ def put(self, path: str, *, contents: Optional[str] = None, overwrite: Optional[ body = {} if contents is not None: body['contents'] = contents if overwrite is not None: body['overwrite'] = overwrite - if path is not None: body['path'] = path + if path is not None: body['path'] = str(path) headers = {'Accept': 'application/json', 'Content-Type': 'application/json', } self._api.do('POST', '/api/2.0/dbfs/put', body=body, headers=headers) - def read(self, path: str, *, length: Optional[int] = None, offset: Optional[int] = None) -> ReadResponse: + def read(self, path: str | Path, *, length: Optional[int] = None, offset: Optional[int] = None) -> ReadResponse: """Get the contents of a file. Returns the contents of a file. If the file does not exist, this call throws an exception with @@ -734,7 +752,7 @@ def read(self, path: str, *, length: Optional[int] = None, offset: Optional[int] If `offset + length` exceeds the number of bytes in a file, it reads the contents until the end of file. - :param path: str + :param path: str | Path The path of the file to read. The path should be the absolute DBFS path. :param length: int (optional) The number of bytes to read starting from the offset. This has a limit of 1 MB, and a default value @@ -748,7 +766,7 @@ def read(self, path: str, *, length: Optional[int] = None, offset: Optional[int] query = {} if length is not None: query['length'] = length if offset is not None: query['offset'] = offset - if path is not None: query['path'] = path + if path is not None: query['path'] = str(path) headers = {'Accept': 'application/json', } res = self._api.do('GET', '/api/2.0/dbfs/read', query=query, headers=headers) @@ -774,14 +792,14 @@ class FilesAPI: def __init__(self, api_client): self._api = api_client - def create_directory(self, directory_path: str): + def create_directory(self, directory_path: str | Path): """Create a directory. Creates an empty directory. If necessary, also creates any parent directories of the new, empty directory (like the shell command `mkdir -p`). If called on an existing directory, returns a success response; this method is idempotent (it will succeed if the directory already exists). - :param directory_path: str + :param directory_path: str | Path The absolute path of a directory. @@ -790,15 +808,15 @@ def create_directory(self, directory_path: str): headers = {} self._api.do('PUT', - f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(directory_path)}', + f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(str(directory_path))}', headers=headers) - def delete(self, file_path: str): + def delete(self, file_path: str | Path): """Delete a file. Deletes a file. If the request is successful, there is no response body. - :param file_path: str + :param file_path: str | Path The absolute path of the file. @@ -807,10 +825,10 @@ def delete(self, file_path: str): headers = {} self._api.do('DELETE', - f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(file_path)}', + f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(str(file_path))}', headers=headers) - def delete_directory(self, directory_path: str): + def delete_directory(self, directory_path: str | Path): """Delete a directory. Deletes an empty directory. @@ -818,7 +836,7 @@ def delete_directory(self, directory_path: str): To delete a non-empty directory, first delete all of its contents. This can be done by listing the directory contents and deleting each file and subdirectory recursively. - :param directory_path: str + :param directory_path: str | Path The absolute path of a directory. @@ -827,16 +845,16 @@ def delete_directory(self, directory_path: str): headers = {} self._api.do('DELETE', - f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(directory_path)}', + f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(str(directory_path))}', headers=headers) - def download(self, file_path: str) -> DownloadResponse: + def download(self, file_path: str | Path) -> DownloadResponse: """Download a file. Downloads a file of up to 5 GiB. The file contents are the response body. This is a standard HTTP file download, not a JSON RPC. - :param file_path: str + :param file_path: str | Path The absolute path of the file. :returns: :class:`DownloadResponse` @@ -845,13 +863,13 @@ def download(self, file_path: str) -> DownloadResponse: headers = {'Accept': 'application/octet-stream', } response_headers = ['content-length', 'content-type', 'last-modified', ] res = self._api.do('GET', - f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(file_path)}', + f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(str(file_path))}', headers=headers, response_headers=response_headers, raw=True) return DownloadResponse.from_dict(res) - def get_directory_metadata(self, directory_path: str): + def get_directory_metadata(self, directory_path: str | Path): """Get directory metadata. Get the metadata of a directory. The response HTTP headers contain the metadata. There is no response @@ -862,7 +880,7 @@ def get_directory_metadata(self, directory_path: str): If you wish to ensure the directory exists, you can instead use `PUT`, which will create the directory if it does not exist, and is idempotent (it will succeed if the directory already exists). - :param directory_path: str + :param directory_path: str | Path The absolute path of a directory. @@ -871,15 +889,15 @@ def get_directory_metadata(self, directory_path: str): headers = {} self._api.do('HEAD', - f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(directory_path)}', + f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(str(directory_path))}', headers=headers) - def get_metadata(self, file_path: str) -> GetMetadataResponse: + def get_metadata(self, file_path: str | Path) -> GetMetadataResponse: """Get file metadata. Get the metadata of a file. The response HTTP headers contain the metadata. There is no response body. - :param file_path: str + :param file_path: str | Path The absolute path of the file. :returns: :class:`GetMetadataResponse` @@ -888,13 +906,13 @@ def get_metadata(self, file_path: str) -> GetMetadataResponse: headers = {} response_headers = ['content-length', 'content-type', 'last-modified', ] res = self._api.do('HEAD', - f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(file_path)}', + f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(str(file_path))}', headers=headers, response_headers=response_headers) return GetMetadataResponse.from_dict(res) def list_directory_contents(self, - directory_path: str, + directory_path: str | Path, *, page_size: Optional[int] = None, page_token: Optional[str] = None) -> Iterator[DirectoryEntry]: @@ -903,7 +921,7 @@ def list_directory_contents(self, Returns the contents of a directory. If there is no directory at the specified path, the API returns a HTTP 404 error. - :param directory_path: str + :param directory_path: str | Path The absolute path of a directory. :param page_size: int (optional) The maximum number of directory entries to return. The response may contain fewer entries. If the @@ -934,7 +952,7 @@ def list_directory_contents(self, while True: json = self._api.do( 'GET', - f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(directory_path)}', + f'/api/2.0/fs/directories{_escape_multi_segment_path_parameter(str(directory_path))}', query=query, headers=headers) if 'contents' in json: @@ -944,7 +962,7 @@ def list_directory_contents(self, return query['page_token'] = json['next_page_token'] - def upload(self, file_path: str, contents: BinaryIO, *, overwrite: Optional[bool] = None): + def upload(self, file_path: str | Path, contents: BinaryIO, *, overwrite: Optional[bool] = None): """Upload a file. Uploads a file of up to 5 GiB. The file contents should be sent as the request body as raw bytes (an @@ -952,7 +970,7 @@ def upload(self, file_path: str, contents: BinaryIO, *, overwrite: Optional[bool resulting file will be exactly the bytes sent in the request body. If the request is successful, there is no response body. - :param file_path: str + :param file_path: str | Path The absolute path of the file. :param contents: BinaryIO :param overwrite: bool (optional) @@ -966,7 +984,7 @@ def upload(self, file_path: str, contents: BinaryIO, *, overwrite: Optional[bool headers = {'Content-Type': 'application/octet-stream', } self._api.do('PUT', - f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(file_path)}', + f'/api/2.0/fs/files{_escape_multi_segment_path_parameter(str(file_path))}', query=query, headers=headers, data=contents) diff --git a/tests/integration/test_files.py b/tests/integration/test_files.py index 7b9ede556..91555fcde 100644 --- a/tests/integration/test_files.py +++ b/tests/integration/test_files.py @@ -4,6 +4,7 @@ import platform import time from typing import Callable, List, Tuple, Union +from pathlib import Path import pytest @@ -216,7 +217,8 @@ def create_volume(w, catalog, schema, volume): return ResourceWithCleanup(lambda: w.volumes.delete(res.full_name)) -def test_files_api_upload_download(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_upload_download(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() @@ -224,12 +226,14 @@ def test_files_api_upload_download(ucws, random): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): f = io.BytesIO(b"some text data") target_file = f'/Volumes/main/{schema}/{volume}/filesit-with-?-and-#-{random()}.txt' + target_file = path_type(target_file) w.files.upload(target_file, f) with w.files.download(target_file).contents as f: assert f.read() == b"some text data" -def test_files_api_read_twice_from_one_download(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_read_twice_from_one_download(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() @@ -237,6 +241,7 @@ def test_files_api_read_twice_from_one_download(ucws, random): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): f = io.BytesIO(b"some text data") target_file = f'/Volumes/main/{schema}/{volume}/filesit-{random()}.txt' + target_file = path_type(target_file) w.files.upload(target_file, f) res = w.files.download(target_file).contents @@ -249,7 +254,8 @@ def test_files_api_read_twice_from_one_download(ucws, random): res.read() -def test_files_api_delete_file(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_delete_file(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() @@ -257,11 +263,12 @@ def test_files_api_delete_file(ucws, random): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): f = io.BytesIO(b"some text data") target_file = f'/Volumes/main/{schema}/{volume}/filesit-{random()}.txt' + target_file = path_type(target_file) w.files.upload(target_file, f) w.files.delete(target_file) - -def test_files_api_get_metadata(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_get_metadata(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() @@ -269,30 +276,33 @@ def test_files_api_get_metadata(ucws, random): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): f = io.BytesIO(b"some text data") target_file = f'/Volumes/main/{schema}/{volume}/filesit-{random()}.txt' + target_file = path_type(target_file) w.files.upload(target_file, f) m = w.files.get_metadata(target_file) assert m.content_type == 'application/octet-stream' assert m.content_length == 14 assert m.last_modified is not None - -def test_files_api_create_directory(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_create_directory(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() with ResourceWithCleanup.create_schema(w, 'main', schema): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): target_directory = f'/Volumes/main/{schema}/{volume}/filesit-{random()}/' + target_directory = path_type(target_directory) w.files.create_directory(target_directory) - -def test_files_api_list_directory_contents(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_list_directory_contents(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() with ResourceWithCleanup.create_schema(w, 'main', schema): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): target_directory = f'/Volumes/main/{schema}/{volume}/filesit-{random()}' + target_directory = path_type(target_directory) w.files.upload(target_directory + "/file1.txt", io.BytesIO(b"some text data")) w.files.upload(target_directory + "/file2.txt", io.BytesIO(b"some text data")) w.files.upload(target_directory + "/file3.txt", io.BytesIO(b"some text data")) @@ -300,25 +310,27 @@ def test_files_api_list_directory_contents(ucws, random): result = list(w.files.list_directory_contents(target_directory)) assert len(result) == 3 - -def test_files_api_delete_directory(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_delete_directory(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() with ResourceWithCleanup.create_schema(w, 'main', schema): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): target_directory = f'/Volumes/main/{schema}/{volume}/filesit-{random()}/' + target_directory = path_type(target_directory) w.files.create_directory(target_directory) w.files.delete_directory(target_directory) - -def test_files_api_get_directory_metadata(ucws, random): +@pytest.mark.parametrize("path_type", [str, Path]) +def test_files_api_get_directory_metadata(ucws, random, path_type): w = ucws schema = 'filesit-' + random() volume = 'filesit-' + random() with ResourceWithCleanup.create_schema(w, 'main', schema): with ResourceWithCleanup.create_volume(w, 'main', schema, volume): target_directory = f'/Volumes/main/{schema}/{volume}/filesit-{random()}/' + target_directory = path_type(target_directory) w.files.create_directory(target_directory) w.files.get_directory_metadata(target_directory) From 9e17c2dfc6a37525908a488fadf10d81b5a6e08d Mon Sep 17 00:00:00 2001 From: Florian Maas Date: Thu, 29 Aug 2024 15:36:53 +0200 Subject: [PATCH 2/2] format --- databricks/sdk/service/files.py | 9 ++++++--- tests/integration/test_files.py | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/databricks/sdk/service/files.py b/databricks/sdk/service/files.py index 4d3f904db..b97edbe1b 100755 --- a/databricks/sdk/service/files.py +++ b/databricks/sdk/service/files.py @@ -4,9 +4,8 @@ import logging from dataclasses import dataclass -from typing import BinaryIO, Dict, Iterator, List, Optional - from pathlib import Path +from typing import BinaryIO, Dict, Iterator, List, Optional from ._internal import _escape_multi_segment_path_parameter, _repeated_dict @@ -741,7 +740,11 @@ def put(self, path: str | Path, *, contents: Optional[str] = None, overwrite: Op self._api.do('POST', '/api/2.0/dbfs/put', body=body, headers=headers) - def read(self, path: str | Path, *, length: Optional[int] = None, offset: Optional[int] = None) -> ReadResponse: + def read(self, + path: str | Path, + *, + length: Optional[int] = None, + offset: Optional[int] = None) -> ReadResponse: """Get the contents of a file. Returns the contents of a file. If the file does not exist, this call throws an exception with diff --git a/tests/integration/test_files.py b/tests/integration/test_files.py index 91555fcde..7cbabae6a 100644 --- a/tests/integration/test_files.py +++ b/tests/integration/test_files.py @@ -3,8 +3,8 @@ import pathlib import platform import time -from typing import Callable, List, Tuple, Union from pathlib import Path +from typing import Callable, List, Tuple, Union import pytest @@ -267,6 +267,7 @@ def test_files_api_delete_file(ucws, random, path_type): w.files.upload(target_file, f) w.files.delete(target_file) + @pytest.mark.parametrize("path_type", [str, Path]) def test_files_api_get_metadata(ucws, random, path_type): w = ucws @@ -283,6 +284,7 @@ def test_files_api_get_metadata(ucws, random, path_type): assert m.content_length == 14 assert m.last_modified is not None + @pytest.mark.parametrize("path_type", [str, Path]) def test_files_api_create_directory(ucws, random, path_type): w = ucws @@ -294,6 +296,7 @@ def test_files_api_create_directory(ucws, random, path_type): target_directory = path_type(target_directory) w.files.create_directory(target_directory) + @pytest.mark.parametrize("path_type", [str, Path]) def test_files_api_list_directory_contents(ucws, random, path_type): w = ucws @@ -310,6 +313,7 @@ def test_files_api_list_directory_contents(ucws, random, path_type): result = list(w.files.list_directory_contents(target_directory)) assert len(result) == 3 + @pytest.mark.parametrize("path_type", [str, Path]) def test_files_api_delete_directory(ucws, random, path_type): w = ucws @@ -322,6 +326,7 @@ def test_files_api_delete_directory(ucws, random, path_type): w.files.create_directory(target_directory) w.files.delete_directory(target_directory) + @pytest.mark.parametrize("path_type", [str, Path]) def test_files_api_get_directory_metadata(ucws, random, path_type): w = ucws