From a8bb971cb2731e6b07a6e6a41a59bb0208a7f1d4 Mon Sep 17 00:00:00 2001 From: alaskowski <alaskowski@ethz.ch> Date: Thu, 23 Feb 2023 12:19:47 +0100 Subject: [PATCH] SSDM-13330: Added downloading flow for PHYSICAL data --- .../obis/dm/commands/download_physical.py | 46 ++++++++++++++ .../obis/dm/commands/openbis_command.py | 23 +++++-- .../src/python/obis/dm/data_mgmt.py | 62 +++++++++++-------- .../src/python/obis/dm/git.py | 9 ++- .../python/obis/scripts/data_mgmt_runner.py | 40 ++++++------ 5 files changed, 123 insertions(+), 57 deletions(-) create mode 100644 app-openbis-command-line/src/python/obis/dm/commands/download_physical.py diff --git a/app-openbis-command-line/src/python/obis/dm/commands/download_physical.py b/app-openbis-command-line/src/python/obis/dm/commands/download_physical.py new file mode 100644 index 00000000000..43f084b5f3e --- /dev/null +++ b/app-openbis-command-line/src/python/obis/dm/commands/download_physical.py @@ -0,0 +1,46 @@ +# Copyright ETH 2023 Zürich, Scientific IT Services +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .openbis_command import OpenbisCommand +from ..command_result import CommandResult +from ..utils import cd + + +class DownloadPhysical(OpenbisCommand): + """ + Command to download physical files of a data set. + """ + + def __init__(self, dm, data_set_id, file): + """ + :param dm: data management + :param data_set_id: permId of the data set to be cloned + """ + self.data_set_id = data_set_id + self.files = [file] if file is not None else None + self.load_global_config(dm) + super(DownloadPhysical, self).__init__(dm) + + def run(self): + if self.fileservice_url() is None: + return CommandResult(returncode=-1, + output="Configuration fileservice_url needs to be set for download.") + + data_set = self.openbis.get_dataset(self.data_set_id) + files = self.files if self.files is not None else data_set.file_list + + with cd(self.data_mgmt.invocation_path): + target_folder = data_set.download(files) + return CommandResult(returncode=0, output="Files downloaded to: %s" % target_folder) diff --git a/app-openbis-command-line/src/python/obis/dm/commands/openbis_command.py b/app-openbis-command-line/src/python/obis/dm/commands/openbis_command.py index 8aa0a3ce1f2..68e7db6d4ed 100644 --- a/app-openbis-command-line/src/python/obis/dm/commands/openbis_command.py +++ b/app-openbis-command-line/src/python/obis/dm/commands/openbis_command.py @@ -16,12 +16,13 @@ import getpass import hashlib import os import socket + import pybis -from ..command_result import CommandResult -from ..command_result import CommandException + from .. import config as dm_config +from ..command_result import CommandException +from ..command_result import CommandResult from ..utils import complete_openbis_config -from ...scripts import cli class OpenbisCommand(object): @@ -36,7 +37,14 @@ class OpenbisCommand(object): self.config_dict = dm.settings_resolver.config_dict() if self.openbis is None and dm.openbis_config.get('url') is not None: - self.openbis = pybis.Openbis(**dm.openbis_config) + self.openbis = pybis.Openbis(url=dm.openbis_config.get('url'), + verify_certificates=dm.openbis_config.get( + 'verify_certificates'), + token=dm.openbis_config.get('token'), + use_cache=dm.openbis_config.get('use_cache'), + allow_http_but_do_not_use_this_in_production_and_only_within_safe_networks=dm.openbis_config.get( + 'allow_http_but_do_not_use_this_in_production_and_only_within_safe_networks'), + ) if self.user() is not None: result = self.login() if result.failure(): @@ -198,8 +206,11 @@ class OpenbisCommand(object): except ValueError: # external dms does not exist - create it try: - external_dms = self.openbis.create_external_data_management_system(external_dms_id, external_dms_id, - "{}:/{}".format(hostname, edms_path)) + external_dms = self.openbis.create_external_data_management_system(external_dms_id, + external_dms_id, + "{}:/{}".format( + hostname, + edms_path)) except Exception as error: return CommandResult(returncode=-1, output=str(error)) return CommandResult(returncode=0, output=external_dms) diff --git a/app-openbis-command-line/src/python/obis/dm/data_mgmt.py b/app-openbis-command-line/src/python/obis/dm/data_mgmt.py index 23202e3f36d..ab26c845343 100644 --- a/app-openbis-command-line/src/python/obis/dm/data_mgmt.py +++ b/app-openbis-command-line/src/python/obis/dm/data_mgmt.py @@ -30,6 +30,7 @@ from .command_result import CommandResult from .commands.addref import Addref from .commands.clone import Clone from .commands.download import Download +from .commands.download_physical import DownloadPhysical from .commands.move import Move from .commands.openbis_sync import OpenbisSync from .commands.removeref import Removeref @@ -67,17 +68,20 @@ def DataMgmt(echo_func=None, settings_resolver=None, openbis_config={}, git_conf repository_type = Type.LINK if repository_type == Type.PHYSICAL: - return PhysicalDataMgmt(settings_resolver, None, None, openbis, log, data_path, metadata_path, invocation_path) + return PhysicalDataMgmt(settings_resolver, None, None, openbis, log, data_path, + metadata_path, invocation_path) else: complete_git_config(git_config) git_wrapper = GitWrapper(**git_config) if not git_wrapper.can_run(): # TODO We could just as well throw an error here instead of creating # creating the NoGitDataMgmt which will fail later. - return NoGitDataMgmt(settings_resolver, None, git_wrapper, openbis, log, data_path, metadata_path, invocation_path) + return NoGitDataMgmt(settings_resolver, None, git_wrapper, openbis, log, data_path, + metadata_path, invocation_path) complete_openbis_config(openbis_config, settings_resolver) - return GitDataMgmt(settings_resolver, openbis_config, git_wrapper, openbis, log, data_path, metadata_path, invocation_path, debug, login) + return GitDataMgmt(settings_resolver, openbis_config, git_wrapper, openbis, log, data_path, + metadata_path, invocation_path, debug, login) class AbstractDataMgmt(metaclass=abc.ABCMeta): @@ -86,7 +90,8 @@ class AbstractDataMgmt(metaclass=abc.ABCMeta): All operations throw an exepction if they fail. """ - def __init__(self, settings_resolver, openbis_config, git_wrapper, openbis, log, data_path, metadata_path, invocation_path, debug=False, login=True): + def __init__(self, settings_resolver, openbis_config, git_wrapper, openbis, log, data_path, + metadata_path, invocation_path, debug=False, login=True): self.settings_resolver = settings_resolver self.openbis_config = openbis_config self.git_wrapper = git_wrapper @@ -259,22 +264,25 @@ def restore_signal_handler(data_mgmt): def with_log(f): """ To be used with commands that use the CommandLog. """ + def f_with_log(self, *args): try: result = f(self, *args) except Exception as e: self.log.log_error(str(e)) raise e - if result.failure() == False: + if result.failure() == False: self.log.success() else: self.log.log_error(result.output) return result + return f_with_log def with_restore(f): """ Sets the restore point and restores on error. """ + def f_with_restore(self, *args): self.set_restorepoint() try: @@ -291,6 +299,7 @@ def with_restore(f): raise e self.clear_restorepoint() return CommandResult(returncode=-1, output="Error: " + str(e)) + return f_with_restore @@ -305,7 +314,6 @@ class GitDataMgmt(AbstractDataMgmt): settings_resolver.set_resolver_location_roots('data_set', relative_path) return settings_resolver - # TODO add this to abstract / other class def setup_local_settings(self, all_settings): self.settings_resolver.set_resolver_location_roots('data_set', '.') @@ -314,17 +322,14 @@ class GitDataMgmt(AbstractDataMgmt): for key, value in settings.items(): resolver.set_value_for_parameter(key, value, 'local') - def get_data_set_id(self, relative_path): settings_resolver = self.get_settings_resolver(relative_path) return settings_resolver.repository.config_dict().get('data_set_id') - def get_repository_id(self, relative_path): settings_resolver = self.get_settings_resolver(relative_path) return settings_resolver.repository.config_dict().get('id') - def init_data(self, desc=None): # check that repository does not already exist # TODO remove .git check after physical flow is implemented @@ -345,13 +350,13 @@ class GitDataMgmt(AbstractDataMgmt): self.settings_resolver.copy_global_to_local() return CommandResult(returncode=0, output="") - def init_analysis(self, parent_folder, desc=None): # get data_set_id of parent from current folder or explicit parent argument parent_data_set_id = self.get_data_set_id(parent_folder) # check that parent repository has been added to openBIS if self.get_repository_id(parent_folder) is None: - return CommandResult(returncode=-1, output="Parent data set must be committed to openBIS before creating an analysis data set.") + return CommandResult(returncode=-1, + output="Parent data set must be committed to openBIS before creating an analysis data set.") # init analysis repository result = self.init_data(desc) if result.failure(): @@ -366,20 +371,18 @@ class GitDataMgmt(AbstractDataMgmt): self.git_wrapper.git_ignore(analysis_folder_relative) # set data_set_id to analysis repository so it will be used as parent when committing - self.set_property(self.settings_resolver.repository, "data_set_id", parent_data_set_id, False, False) + self.set_property(self.settings_resolver.repository, "data_set_id", parent_data_set_id, + False, False) return result - @with_restore def sync(self): return self._sync() - def _sync(self): cmd = OpenbisSync(self, self.ignore_missing_parent) return cmd.run() - @with_restore def commit(self, msg, auto_add=True, sync=True): """ Git add, commit and sync with openBIS. """ @@ -398,7 +401,6 @@ class GitDataMgmt(AbstractDataMgmt): result = self._sync() return result - def status(self): git_status = self.git_wrapper.git_status() try: @@ -454,7 +456,8 @@ class GitDataMgmt(AbstractDataMgmt): # settings # - def config(self, category, is_global, is_data_set_property, prop=None, value=None, set=False, get=False, clear=False): + def config(self, category, is_global, is_data_set_property, prop=None, value=None, set=False, + get=False, clear=False): """ :param category: config, object, collection, data_set or repository :param is_global: act on global settings - local if false @@ -503,26 +506,31 @@ class GitDataMgmt(AbstractDataMgmt): config_str = json.dumps(little_dict, indent=4, sort_keys=True) click_echo("{}".format(config_str), with_timestamp=False) elif set == True: - return check_result("config", self.set_property(resolver, prop, value, is_global, is_data_set_property)) + return check_result("config", self.set_property(resolver, prop, value, is_global, + is_data_set_property)) elif clear == True: if prop is None: returncode = 0 for prop in config_dict.keys(): - returncode += check_result("config", self.set_property(resolver, prop, None, is_global, is_data_set_property)) + returncode += check_result("config", + self.set_property(resolver, prop, None, is_global, + is_data_set_property)) return returncode else: - return check_result("config", self.set_property(resolver, prop, None, is_global, is_data_set_property)) + return check_result("config", self.set_property(resolver, prop, None, is_global, + is_data_set_property)) def set_property(self, resolver, prop, value, is_global, is_data_set_property=False): """Helper function to implement the property setting semantics.""" loc = 'global' if is_global else 'local' try: if is_data_set_property: - resolver.set_value_for_json_parameter('properties', prop, value, loc, apply_rules=True) + resolver.set_value_for_json_parameter('properties', prop, value, loc, + apply_rules=True) else: resolver.set_value_for_parameter(prop, value, loc, apply_rules=True) except Exception as e: - if self.debug == True: + if self.debug == True: raise e return CommandResult(returncode=-1, output="Error: " + str(e)) else: @@ -544,7 +552,10 @@ class PhysicalDataMgmt(AbstractDataMgmt): self.settings_resolver.set_resolver_location_roots('data_set', '.') self.settings_resolver.copy_global_to_local() self.settings_resolver.config.set_value_for_parameter("is_physical", True, "local") - return CommandResult(returncode=0, output="Physical obis repository initialized!") + openbis_url = self.settings_resolver.config.config_dict()['openbis_url'] + self.settings_resolver.config.set_value_for_parameter("fileservice_url", + openbis_url, "local") + return CommandResult(returncode=0, output="Physical obis repository initialized.") def init_analysis(self, parent_folder, desc=None): self.error_raise("init analysis", "Not implemented.") @@ -570,5 +581,6 @@ class PhysicalDataMgmt(AbstractDataMgmt): def removeref(self, data_set_id=None): self.error_raise("removeref", "Not implemented.") - def download(self, data_set_id, content_copy_index, file, skip_integrity_check): - self.error_raise("download", "Not implemented.") \ No newline at end of file + def download(self, data_set_id, _content_copy_index, file, _skip_integrity_check): + cmd = DownloadPhysical(self, data_set_id, file) + return cmd.run() diff --git a/app-openbis-command-line/src/python/obis/dm/git.py b/app-openbis-command-line/src/python/obis/dm/git.py index ddc661904a2..c22df44daa7 100644 --- a/app-openbis-command-line/src/python/obis/dm/git.py +++ b/app-openbis-command-line/src/python/obis/dm/git.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import shutil import os -from pathlib import Path -from .utils import run_shell, cd -from .command_result import CommandResult, CommandException +import shutil + from .checksum import ChecksumGeneratorCrc32, ChecksumGeneratorGitAnnex +from .utils import run_shell class GitWrapper(object): @@ -77,7 +76,7 @@ class GitWrapper(object): else: return self._git(["annex", "status", path], strip_leading_whitespace=False) - def git_annex_backend(self, desc, git_annex_backend=None): + def git_annex_init(self, desc, git_annex_backend=None): """ Configures annex in a git repository.""" # We use annex --version=5 since that works better with big files. Version diff --git a/app-openbis-command-line/src/python/obis/scripts/data_mgmt_runner.py b/app-openbis-command-line/src/python/obis/scripts/data_mgmt_runner.py index 8ffabde717a..ea5e9ae58a8 100644 --- a/app-openbis-command-line/src/python/obis/scripts/data_mgmt_runner.py +++ b/app-openbis-command-line/src/python/obis/scripts/data_mgmt_runner.py @@ -26,7 +26,6 @@ from ..dm.utils import run_shell class DataMgmtRunner(object): - def __init__(self, context, halt_on_error_log=True, data_path=None, bootstrap_settings=None, check_result=True, login=True, openbis=None, is_physical=False): self.context = context @@ -40,7 +39,6 @@ class DataMgmtRunner(object): self.openbis = openbis self.repository_type = Type.PHYSICAL if is_physical else Type.UNKNOWN - def init_paths(self, repository=None): # data path if self.data_path is None: @@ -62,19 +60,19 @@ class DataMgmtRunner(object): if not os.path.exists(self.data_path): os.makedirs(self.data_path) - def _validate_obis_metadata_folder(self, obis_metadata_folder): if not os.path.isabs(obis_metadata_folder): return CommandResult( - returncode=-1, - output="Ignoring obis_metadata_folder. Must be absolute but is: {}".format(obis_metadata_folder)) + returncode=-1, + output="Ignoring obis_metadata_folder. Must be absolute but is: {}".format( + obis_metadata_folder)) if not os.path.exists(obis_metadata_folder): return CommandResult( - returncode=-1, - output="Ignoring obis_metadata_folder. Folder does not exist: {}".format(obis_metadata_folder)) + returncode=-1, + output="Ignoring obis_metadata_folder. Folder does not exist: {}".format( + obis_metadata_folder)) return CommandResult(returncode=0, output="") - def run(self, command, function, repository=None): self.init_paths(repository) with cd(self.metadata_path): @@ -84,7 +82,6 @@ class DataMgmtRunner(object): else: return result - def _run(self, function): try: dm = self._get_dm() @@ -98,13 +95,11 @@ class DataMgmtRunner(object): raise e return CommandResult(returncode=-1, output="Error: " + str(e)) - def get_settings(self, repository=None, do_cd=True): self.init_paths() with cd(self.metadata_path): return self.get_settings_resolver(do_cd).config_dict() - def get_settings_resolver(self, do_cd=True): if do_cd: self.init_paths() @@ -113,25 +108,28 @@ class DataMgmtRunner(object): else: return self._get_dm().get_settings_resolver() - def config(self, resolver, is_global, is_data_set_property, prop, value, set, get, clear): self.init_paths() with cd(self.metadata_path): - self._get_dm().config(resolver, is_global, is_data_set_property, prop, value, set, get, clear) - + self._get_dm().config(resolver, is_global, is_data_set_property, prop, value, set, get, + clear) def _get_dm(self): git_config = { - 'find_git': True, - 'data_path': self.data_path, - 'metadata_path': self.metadata_path, - 'invocation_path': self.invocation_path - } + 'find_git': True, + 'data_path': self.data_path, + 'metadata_path': self.metadata_path, + 'invocation_path': self.invocation_path + } openbis_config = {} if self.context.get('verify_certificates') is not None: openbis_config['verify_certificates'] = self.context['verify_certificates'] log = CommandLog() if self.halt_on_error_log and log.any_log_exists(): - click_echo("Error: A previous command did not finish. Please check the log ({}) and remove it when you want to continue using obis".format(log.folder_path)) + click_echo( + "Error: A previous command did not finish. Please check the log ({}) and remove it when you want to continue using obis".format( + log.folder_path)) sys.exit(-1) - return dm.DataMgmt(openbis_config=openbis_config, git_config=git_config, log=log, debug=self.context['debug'], login=self.login, openbis=self.openbis, repository_type=self.repository_type) + return dm.DataMgmt(openbis_config=openbis_config, git_config=git_config, log=log, + debug=self.context['debug'], login=self.login, openbis=self.openbis, + repository_type=self.repository_type) -- GitLab