This repository has been archived by the owner on Nov 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[HN-153/HN-155] feat: file management (#13)
* [HN-153/HN-155] feat: file management * update docs
- Loading branch information
Showing
7 changed files
with
498 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from .files import ( | ||
upload_files, | ||
list_files, | ||
delete_file, | ||
rename_file | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import httpx | ||
import logging | ||
import os | ||
import sys | ||
|
||
from typing import List | ||
|
||
|
||
def get_log_level(): | ||
HIVE_AGENT_LOG_LEVEL = os.getenv('HIVE_AGENT_LOG_LEVEL', 'INFO').upper() | ||
return getattr(logging, HIVE_AGENT_LOG_LEVEL, logging.INFO) | ||
|
||
|
||
logging.basicConfig(stream=sys.stdout, level=get_log_level()) | ||
logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout)) | ||
|
||
logger = logging.getLogger() | ||
logger.setLevel(get_log_level()) | ||
|
||
|
||
async def upload_files(http_client: httpx.AsyncClient, base_url: str, file_paths: List[str]) -> dict: | ||
""" | ||
Uploads files to the server. | ||
:param http_client: An HTTP client for sending requests. | ||
:param base_url: The base URL of the Hive Agent API. | ||
:param file_paths: A list of file paths to be uploaded. | ||
:return: A dictionary with the names of the uploaded files. | ||
:raises Exception: If the HTTP request fails or the API returns an error response. | ||
""" | ||
endpoint = "/uploadfiles/" | ||
url = f"{base_url}{endpoint}" | ||
files = [("files", (os.path.basename(file_path), open(file_path, "rb"), "multipart/form-data")) for file_path in file_paths] | ||
|
||
try: | ||
logger.debug(f"Uploading files to {url}") | ||
response = await http_client.post(url, files=files) | ||
response.raise_for_status() | ||
logger.debug(f"Response for uploading files to {url}: {response.json()}") | ||
return response.json() | ||
except httpx.HTTPStatusError as e: | ||
logging.error(f"Failed to upload files: {e}") | ||
raise Exception(f"Failed to upload files: {e.response.text}") from e | ||
finally: | ||
for _, (name, file, _) in files: | ||
file.close() | ||
|
||
|
||
async def list_files(http_client: httpx.AsyncClient, base_url: str) -> dict: | ||
""" | ||
Lists all files stored on the server. | ||
:param http_client: An HTTP client for sending requests. | ||
:param base_url: The base URL of the Hive Agent API. | ||
:return: A dictionary with a list of file names. | ||
:raises Exception: If the HTTP request fails or the API returns an error response. | ||
""" | ||
endpoint = "/files/" | ||
url = f"{base_url}{endpoint}" | ||
|
||
try: | ||
logger.debug(f"Listing files at {url}") | ||
response = await http_client.get(url) | ||
response.raise_for_status() | ||
logger.debug(f"Response for listing files at {url}: {response.json()}") | ||
return response.json() | ||
except httpx.HTTPStatusError as e: | ||
logging.error(f"Failed to list files: {e}") | ||
raise Exception(f"Failed to list files: {e.response.text}") from e | ||
|
||
|
||
async def delete_file(http_client: httpx.AsyncClient, base_url: str, filename: str) -> dict: | ||
""" | ||
Deletes a specified file from the server. | ||
:param http_client: An HTTP client for sending requests. | ||
:param base_url: The base URL of the Hive Agent API. | ||
:param filename: The name of the file to be deleted. | ||
:return: A dictionary with a message about the file deletion. | ||
:raises Exception: If the HTTP request fails or the API returns an error response. | ||
""" | ||
endpoint = f"/files/{filename}" | ||
url = f"{base_url}{endpoint}" | ||
|
||
try: | ||
logger.debug(f"Deleting file {filename} at {url}") | ||
response = await http_client.delete(url) | ||
response.raise_for_status() | ||
logger.debug(f"Response for deleting file {filename} at {url}: {response.json()}") | ||
return response.json() | ||
except httpx.HTTPStatusError as e: | ||
logging.error(f"Failed to delete file {filename}: {e}") | ||
raise Exception(f"Failed to delete file {filename}: {e.response.text}") from e | ||
|
||
|
||
async def rename_file(http_client: httpx.AsyncClient, base_url: str, old_filename: str, new_filename: str) -> dict: | ||
""" | ||
Renames a specified file on the server. | ||
:param http_client: An HTTP client for sending requests. | ||
:param base_url: The base URL of the Hive Agent API. | ||
:param old_filename: The current name of the file. | ||
:param new_filename: The new name for the file. | ||
:return: A dictionary with a message about the file renaming. | ||
:raises Exception: If the HTTP request fails or the API returns an error response. | ||
""" | ||
endpoint = f"/files/{old_filename}/{new_filename}" | ||
url = f"{base_url}{endpoint}" | ||
|
||
try: | ||
logger.debug(f"Renaming file from {old_filename} to {new_filename} at {url}") | ||
response = await http_client.put(url) | ||
response.raise_for_status() | ||
logger.debug(f"Response for renaming file from {old_filename} to {new_filename} at {url}: {response.json()}") | ||
return response.json() | ||
except httpx.HTTPStatusError as e: | ||
logging.error(f"Failed to rename file from {old_filename} to {new_filename}: {e}") | ||
raise Exception(f"Failed to rename file from {old_filename} to {new_filename}: {e.response.text}") from e |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import os | ||
import httpx | ||
import pytest | ||
import respx | ||
|
||
from hive_agent_client.files import ( | ||
upload_files, | ||
list_files, | ||
delete_file, | ||
rename_file | ||
) | ||
|
||
base_url = "http://example.com" | ||
|
||
|
||
@pytest.fixture | ||
def temp_files(): | ||
file_paths = ["test1.txt", "test2.txt"] | ||
for file_path in file_paths: | ||
with open(file_path, "w") as f: | ||
f.write("test content") | ||
yield file_paths | ||
for file_path in file_paths: | ||
os.remove(file_path) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_upload_files_success(temp_files): | ||
expected_response = {"filenames": ["test1.txt", "test2.txt"]} | ||
|
||
with respx.mock() as mock: | ||
mock.post(f"{base_url}/uploadfiles/").mock( | ||
return_value=httpx.Response(200, json=expected_response)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await upload_files(client, base_url, temp_files) | ||
assert response == expected_response | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_upload_files_http_error(temp_files): | ||
with respx.mock() as mock: | ||
mock.post(f"{base_url}/uploadfiles/").mock( | ||
return_value=httpx.Response(400)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
with pytest.raises(Exception) as excinfo: | ||
await upload_files(client, base_url, temp_files) | ||
assert "Failed to upload files" in str(excinfo.value) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_list_files_success(): | ||
expected_response = {"files": ["test1.txt", "test2.txt"]} | ||
|
||
with respx.mock() as mock: | ||
mock.get(f"{base_url}/files/").mock( | ||
return_value=httpx.Response(200, json=expected_response)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await list_files(client, base_url) | ||
assert response == expected_response | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_list_files_http_error(): | ||
with respx.mock() as mock: | ||
mock.get(f"{base_url}/files/").mock( | ||
return_value=httpx.Response(400)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
with pytest.raises(Exception) as excinfo: | ||
await list_files(client, base_url) | ||
assert "Failed to list files" in str(excinfo.value) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_delete_file_success(): | ||
filename = "test_delete.txt" | ||
expected_response = {"message": f"File {filename} deleted successfully."} | ||
|
||
with respx.mock() as mock: | ||
mock.delete(f"{base_url}/files/{filename}").mock( | ||
return_value=httpx.Response(200, json=expected_response)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await delete_file(client, base_url, filename) | ||
assert response == expected_response | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_delete_file_http_error(): | ||
filename = "test_delete.txt" | ||
|
||
with respx.mock() as mock: | ||
mock.delete(f"{base_url}/files/{filename}").mock( | ||
return_value=httpx.Response(400)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
with pytest.raises(Exception) as excinfo: | ||
await delete_file(client, base_url, filename) | ||
assert "Failed to delete file" in str(excinfo.value) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_rename_file_success(): | ||
old_filename = "old_name.txt" | ||
new_filename = "new_name.txt" | ||
expected_response = {"message": f"File {old_filename} renamed to {new_filename} successfully."} | ||
|
||
with respx.mock() as mock: | ||
mock.put(f"{base_url}/files/{old_filename}/{new_filename}").mock( | ||
return_value=httpx.Response(200, json=expected_response)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await rename_file(client, base_url, old_filename, new_filename) | ||
assert response == expected_response | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_rename_file_http_error(): | ||
old_filename = "old_name.txt" | ||
new_filename = "new_name.txt" | ||
|
||
with respx.mock() as mock: | ||
mock.put(f"{base_url}/files/{old_filename}/{new_filename}").mock( | ||
return_value=httpx.Response(400)) | ||
|
||
async with httpx.AsyncClient() as client: | ||
with pytest.raises(Exception) as excinfo: | ||
await rename_file(client, base_url, old_filename, new_filename) | ||
assert "Failed to rename file" in str(excinfo.value) |
Oops, something went wrong.