Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pilot 4264: add new command to move/rename files #120

Merged
merged 6 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions app/commands/entry_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .file import file_export_manifest
from .file import file_list
from .file import file_metadata_download
from .file import file_move
from .file import file_put
from .file import file_resume

Expand Down Expand Up @@ -74,6 +75,7 @@ def user_group():
file_group.add_command(file_download)
file_group.add_command(file_resume)
file_group.add_command(file_metadata_download)
file_group.add_command(file_move)
project_group.add_command(project_list_all)
user_group.add_command(login)
user_group.add_command(logout)
Expand Down
28 changes: 28 additions & 0 deletions app/commands/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from app.services.file_manager.file_list import SrvFileList
from app.services.file_manager.file_manifests import SrvFileManifests
from app.services.file_manager.file_metadata.file_metadata_client import FileMetaClient
from app.services.file_manager.file_move.file_move_client import FileMoveClient
from app.services.file_manager.file_upload.file_upload import assemble_path
from app.services.file_manager.file_upload.file_upload import resume_upload
from app.services.file_manager.file_upload.file_upload import simple_upload
Expand Down Expand Up @@ -501,3 +502,30 @@ def file_metadata_download(**kwargs):
file_meta_client.download_file_metadata()

message_handler.SrvOutPutHandler.metadata_download_success()


@click.command(name='move')
@click.argument('project_code', type=click.STRING)
@click.argument('src_item_path', type=click.STRING)
@click.argument('dest_item_path', type=click.STRING)
@click.option(
'-z',
'--zone',
default=AppConfig.Env.green_zone,
required=True,
help=file_help.file_help_page(file_help.FileHELP.FILE_MOVE_Z),
show_default=False,
)
@require_valid_token()
@doc(file_help.file_help_page(file_help.FileHELP.FILE_MOVE))
def file_move(**kwargs):
project_code = kwargs.get('project_code')
src_item_path = kwargs.get('src_item_path')
dest_item_path = kwargs.get('dest_item_path')
zone = kwargs.get('zone')

zone = get_zone(zone) if zone else AppConfig.Env.green_zone.lower()
file_meta_client = FileMoveClient(zone, project_code, src_item_path, dest_item_path)
file_meta_client.move_file()

message_handler.SrvOutPutHandler.move_action_success(src_item_path, dest_item_path)
2 changes: 2 additions & 0 deletions app/resources/custom_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class HelpPage:
'FILE_META_G': 'The location of general metadata file',
'FILE_META_A': 'The location of attribute metadata file',
'FILE_META_T': 'The location of tag metadata file',
'FILE_MOVE': 'Move/Rename files/folders to a given Project path.',
'FILE_MOVE_Z': 'Target Zone (i.e., core/greenroom).',
},
'config': {
'SET_CONFIG': 'Chose config file and set for cli.',
Expand Down
3 changes: 3 additions & 0 deletions app/services/file_manager/file_move/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (C) 2022-2023 Indoc Systems
#
# Contact Indoc Systems for any questions regarding the use of this source code.
69 changes: 69 additions & 0 deletions app/services/file_manager/file_move/file_move_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Copyright (C) 2022-2023 Indoc Systems
#
# Contact Indoc Systems for any questions regarding the use of this source code.

import app.services.output_manager.message_handler as message_handler
from app.configs.app_config import AppConfig
from app.configs.user_config import UserConfig
from app.utils.aggregated import resilient_session


class FileMoveClient:
"""
Summary:
A client for interacting with file metadata. currently support to download
file metadata from metadata service.
"""

def __init__(
self,
zone: str,
project_code: str,
src_item_path: str,
dest_item_path: str,
) -> None:
"""
Summary:
Initialize file move client.
Parameters:
zone (str): zone.
project_code (str): project code.
src_item_path (str): source item path.
dest_item_path (str): destination item path.
"""

self.zone = zone
self.project_code = project_code
self.src_item_path = src_item_path
self.dest_item_path = dest_item_path

self.user = UserConfig()

def move_file(self) -> None:
"""
Summary:
Move file.
"""

try:
url = AppConfig.Connections.url_bff + f'/v1/{self.project_code}/files'
payload = {
'src_item_path': self.src_item_path,
'dest_item_path': self.dest_item_path,
'zone': self.zone,
}
headers = {'Authorization': 'Bearer ' + self.user.access_token, 'Session-ID': self.user.session_id}

response = resilient_session().patch(url, json=payload, headers=headers, timeout=None)
response.raise_for_status()

return response.json().get('result')
except Exception:
if response.status_code == 422:
error_message = ''
for x in response.json().get('detail'):
error_message += '\n' + x.get('msg')
else:
error_message = response.json().get('error_msg')
message_handler.SrvOutPutHandler.move_action_failed(self.src_item_path, self.dest_item_path, error_message)
exit(1)
3 changes: 3 additions & 0 deletions app/services/output_manager/help_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class FileHELP(enum.Enum):
FILE_META_A = 'FILE_META_A'
FILE_META_T = 'FILE_META_T'

FILE_MOVE = 'FILE_MOVE'
FILE_MOVE_Z = 'FILE_MOVE_Z'


def file_help_page(FileHELP: FileHELP):
helps = help_msg.get('file', 'default file help')
Expand Down
10 changes: 10 additions & 0 deletions app/services/output_manager/message_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ def cancel_metadata_download():
def metadata_download_success():
logger.succeed('Metadata download complete.')

@staticmethod
def move_action_success(src, dest):
"""e.g. Move action succeed."""
return logger.succeed(f'Successfully moved {src} to {dest}')

@staticmethod
def move_action_failed(src, dest, error):
"""e.g. Move action failed."""
return logger.error(f'Failed to move {src} to {dest}: {error}')

@staticmethod
def start_requests():
"""e.g. start requests."""
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "app"
version = "2.9.4"
version = "2.9.5"
description = "This service is designed to support pilot platform"
authors = ["Indoc Systems"]

Expand Down
2 changes: 2 additions & 0 deletions tests/app/commands/test_entry_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from app.commands.file import file_export_manifest
from app.commands.file import file_list
from app.commands.file import file_metadata_download
from app.commands.file import file_move
from app.commands.file import file_put
from app.commands.file import file_resume
from app.commands.project import project_list_all
Expand Down Expand Up @@ -64,6 +65,7 @@ def test_file_commands(user_login_true):
'download': file_download,
'resume': file_resume,
'metadata': file_metadata_download,
'move': file_move,
}
file_commands_object = entry_point.commands.get('file')
file_commands_object.callback()
Expand Down
18 changes: 18 additions & 0 deletions tests/app/commands/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from app.commands.file import file_download
from app.commands.file import file_list
from app.commands.file import file_metadata_download
from app.commands.file import file_move
from app.commands.file import file_put
from app.commands.file import file_resume
from app.services.file_manager.file_metadata.file_metadata_client import FileMetaClient
Expand Down Expand Up @@ -332,3 +333,20 @@ def test_download_file_metadata_file_duplicate_abort(mocker, cli_runner):
assert outputs == excepted_output

assert donwload_metadata_mock.call_count == 0


def test_file_move_success(mocker, cli_runner):
mocker.patch(
'app.services.user_authentication.token_manager.SrvTokenManager.decode_access_token',
return_value=decoded_token(),
)

file_move_mock = mocker.patch(
'app.services.file_manager.file_move.file_move_client.FileMoveClient.move_file',
return_value=None,
)

result = cli_runner.invoke(file_move, ['test_project', 'src_item_path', 'dest_item_path'])
outputs = result.output.split('\n')
assert outputs[0] == 'Successfully moved src_item_path to dest_item_path'
file_move_mock.assert_called_once()
73 changes: 73 additions & 0 deletions tests/app/services/file_manager/file_move/test_file_move_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright (C) 2022-2023 Indoc Systems
#
# Contact Indoc Systems for any questions regarding the use of this source code.

from app.configs.app_config import AppConfig
from app.services.file_manager.file_move.file_move_client import FileMoveClient
from tests.conftest import decoded_token


def test_file_move_success(mocker, httpx_mock):
project_code = 'test_code'
item_info = {'result': {'id': 'test_id', 'name': 'test_name'}}

mocker.patch(
'app.services.user_authentication.token_manager.SrvTokenManager.decode_access_token',
return_value=decoded_token(),
)

httpx_mock.add_response(
url=AppConfig.Connections.url_bff + f'/v1/{project_code}/files',
method='PATCH',
json={'result': item_info},
)

file_move_client = FileMoveClient('zone', project_code, 'src_item_path', 'dest_item_path')
res = file_move_client.move_file()
assert res == item_info


def test_file_move_error_with_permission_denied_403(mocker, httpx_mock, capfd):
project_code = 'test_code'

mocker.patch(
'app.services.user_authentication.token_manager.SrvTokenManager.decode_access_token',
return_value=decoded_token(),
)

httpx_mock.add_response(
url=AppConfig.Connections.url_bff + f'/v1/{project_code}/files',
method='PATCH',
json={'result': {}, 'error_msg': 'error_msg'},
status_code=403,
)

file_move_client = FileMoveClient('zone', project_code, 'src_item_path', 'dest_item_path')
try:
file_move_client.move_file()
except SystemExit:
out, _ = capfd.readouterr()
assert out == 'Failed to move src_item_path to dest_item_path: error_msg\n'


def test_file_move_error_with_wrong_input_422(mocker, httpx_mock, capfd):
project_code = 'test_code'

mocker.patch(
'app.services.user_authentication.token_manager.SrvTokenManager.decode_access_token',
return_value=decoded_token(),
)

httpx_mock.add_response(
url=AppConfig.Connections.url_bff + f'/v1/{project_code}/files',
method='PATCH',
json={'detail': [{'loc': ['body', 'src_item_path'], 'msg': 'error_msg', 'type': 'value_error'}]},
status_code=422,
)

file_move_client = FileMoveClient('zone', project_code, 'src_item_path', 'dest_item_path')
try:
file_move_client.move_file()
except SystemExit:
out, _ = capfd.readouterr()
assert out == 'Failed to move src_item_path to dest_item_path: \nerror_msg\n'
Loading