diff --git a/src/python/OBis/obis/dm/config.py b/src/python/OBis/obis/dm/config.py index 3aea5734faff77e6178b0445c6ca92a1cc8f73c5..91a4b9bf3d2fa93c2b8965547e65404e80ec3055 100644 --- a/src/python/OBis/obis/dm/config.py +++ b/src/python/OBis/obis/dm/config.py @@ -123,7 +123,15 @@ class ConfigEnv(object): return True -class PropertiesEnv(ConfigEnv): +class DataSetEnv(ConfigEnv): + + # TODO remove data_set from property names + def initialize_params(self): + self.add_param(ConfigParam(name='data_set_type', private=False)) + self.add_param(ConfigParam(name='data_set_properties', private=False, is_json=True)) + + +class RepositoryEnv(ConfigEnv): """ These are properties which are not configured by the user but set by obis. """ def initialize_params(self): @@ -290,7 +298,7 @@ class ConfigResolver(object): def __init__(self, location_resolver=None): self.resolvers = [] self.resolvers.append(ConfigResolverImpl(location_resolver=location_resolver, env=ConfigEnv())) - self.resolvers.append(ConfigResolverImpl(location_resolver=location_resolver, env=PropertiesEnv(), config_file='properties.json')) + self.resolvers.append(ConfigResolverImpl(location_resolver=location_resolver, env=RepositoryEnv(), config_file='reposiory.json')) def config_dict(self, local_only=False): combined_dict = {} @@ -309,10 +317,13 @@ class ConfigResolver(object): if json_param_name in resolver.env.params: return resolver.set_value_for_json_parameter(json_param_name, name, value, loc) - def local_public_properties_path(self): + # TODO return a list + def local_public_properties_paths(self, get_usersettings=False): + paths = [] for resolver in self.resolvers: - if not resolver.is_usersetting(): - return resolver.local_public_properties_path() + if get_usersettings == resolver.is_usersetting(): + paths.append(resolver.local_public_properties_path()) + return paths def copy_global_to_local(self): for resolver in self.resolvers: diff --git a/src/python/OBis/obis/dm/config_test.py b/src/python/OBis/obis/dm/config_test.py index e5f5dc4cc65ef41df1935f8b825bb14c1d6fab2e..d635c70af3cfd71bdfb753cd66a8af5543d29bc9 100644 --- a/src/python/OBis/obis/dm/config_test.py +++ b/src/python/OBis/obis/dm/config_test.py @@ -46,7 +46,7 @@ def test_read_config(tmpdir): expected_dict = json.load(f) assert config_dict['user'] == expected_dict['user'] - assert './.obis/properties.json' == resolver.local_public_properties_path() + assert './.obis/properties.json' in resolver.local_public_properties_paths() def test_write_config(tmpdir): diff --git a/src/python/OBis/obis/dm/data_mgmt.py b/src/python/OBis/obis/dm/data_mgmt.py index 36d21714bb5a3a94ae658238278ccdaaf7e9280f..228e2c311adcc2b73e87408912184579ee917e3e 100644 --- a/src/python/OBis/obis/dm/data_mgmt.py +++ b/src/python/OBis/obis/dm/data_mgmt.py @@ -312,12 +312,16 @@ class GitDataMgmt(AbstractDataMgmt): return CommandResult(returncode=0, output=output) def commit_metadata_updates(self, msg_fragment=None): - properties_path = self.config_resolver.local_public_properties_path() - status = self.git_wrapper.git_status(properties_path) - if len(status.output.strip()) < 1: + properties_paths = self.config_resolver.local_public_properties_paths() + total_status = '' + for properties_path in properties_paths: + status = self.git_wrapper.git_status(properties_path).output.strip() + total_status += status + if len(status) > 0: + self.git_wrapper.git_add(properties_path) + if len(total_status) < 1: # Nothing to commit return CommandResult(returncode=0, output="") - self.git_wrapper.git_add(properties_path) if msg_fragment is None: msg = "OBIS: Update openBIS metadata cache." else: @@ -329,9 +333,10 @@ class GitDataMgmt(AbstractDataMgmt): def restore(self): self.git_wrapper.git_reset_to(self.previous_git_commit_hash) - properties_path = self.config_resolver.local_public_properties_path() - self.git_wrapper.git_checkout(properties_path) - self.git_wrapper.git_delete_if_untracked('.obis/properties.json') + properties_paths = self.config_resolver.local_public_properties_paths() + for properties_path in properties_paths: + self.git_wrapper.git_checkout(properties_path) + self.git_wrapper.git_delete_if_untracked(properties_path) def clone(self, data_set_id, ssh_user, content_copy_index): cmd = Clone(self, data_set_id, ssh_user, content_copy_index) diff --git a/src/python/OBis/obis/scripts/cli.py b/src/python/OBis/obis/scripts/cli.py index bc232b4da7df7129c15cbcc9cbe5a4701ee8a1ed..2b7aa84bc76a1b37cdec848e0105a55c6e50d3d3 100644 --- a/src/python/OBis/obis/scripts/cli.py +++ b/src/python/OBis/obis/scripts/cli.py @@ -71,79 +71,6 @@ def cli(ctx, quiet, skip_verification): ctx.obj['verify_certificates'] = False -@cli.command() -@click.pass_context -@click.argument('repository', type=click.Path(exists=True)) -def addref(ctx, repository): - """Add the given repository as a reference to openBIS. - """ - with cd(repository): - data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("addref", run(data_mgmt.addref)) - - -@cli.command() -@click.pass_context -@click.argument('repository', type=click.Path(exists=True)) -def removeref(ctx, repository): - """Remove the reference to the given repository from openBIS. - """ - with cd(repository): - data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("addref", run(data_mgmt.removeref)) - - -@cli.command() -@click.pass_context -@click.option('-u', '--ssh_user', default=None, help='User to connect to remote systems via ssh') -@click.option('-c', '--content_copy_index', type=int, default=None, help='Index of the content copy to clone from in case there are multiple copies') -@click.argument('data_set_id') -def clone(ctx, ssh_user, content_copy_index, data_set_id): - """Clone the repository found in the given data set id. - """ - data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("clone", run(lambda: data_mgmt.clone(data_set_id, ssh_user, content_copy_index))) - - -@cli.command() -@click.pass_context -@click.option('-m', '--msg', prompt=True, help='A message explaining what was done.') -@click.option('-a', '--auto_add', default=True, is_flag=True, help='Automatically add all untracked files.') -@click.option('-i', '--ignore_missing_parent', default=True, is_flag=True, help='If parent data set is missing, ignore it.') -def commit(ctx, msg, auto_add, ignore_missing_parent): - """Commit the repository to git and inform openBIS. - """ - data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("commit", run(lambda: data_mgmt.commit(msg, auto_add, ignore_missing_parent))) - - -@cli.command() -@click.option('-g', '--is_global', default=False, is_flag=True, help='Configure global or local.') -@click.option('-p', '--is_data_set_property', default=False, is_flag=True, help='Configure data set property.') -@click.argument('prop', default="") -@click.argument('value', default="") -@click.pass_context -def config(ctx, is_global, is_data_set_property, prop, value): - """Configure the openBIS setup. - - Configure the openBIS server url, the data set type, and the data set properties. - """ - data_mgmt = shared_data_mgmt(ctx.obj) - config_internal(data_mgmt, is_global, is_data_set_property, prop, value) - - -@cli.command() -@click.option('-c', '--content_copy_index', type=int, default=None, help='Index of the content copy to download from.') -@click.option('-f', '--file', help='File in the data set to download - downloading all if not given.') -@click.argument('data_set_id') -@click.pass_context -def download(ctx, content_copy_index, file, data_set_id): - """ Download files of a linked data set. - """ - data_mgmt = shared_data_mgmt(ctx.obj) - return check_result("download", run(lambda: data_mgmt.download(data_set_id, content_copy_index, file))) - - def config_internal(data_mgmt, is_global, is_data_set_property, prop, value): resolver = data_mgmt.config_resolver if is_global: @@ -212,28 +139,138 @@ def init_handle_cleanup(result, object_id, collection_id, folder, data_mgmt): return check_result("init_data", set_property(data_mgmt, 'collection_id', collection_id, False, False)) +# setting commands +# obis [type] [get|set] [-g]? [-p]? [[key]+ | [key=value]+]? + + +class GetOrSet(click.ParamType): + name = 'get_or_set' + + def convert(self, value, param, ctx): + result = {} + result['get'] = value == 'get' + result['set'] = value == 'set' + if result['get'] == False and result['set'] == False: + self.fail(param=param, message='Parameter has to be \'get\' or \'set\'.') + return result + + +class Settings(click.ParamType): + name = 'settings' + + def convert(self, value, param, ctx): + print(value) + try: + properties = {} + split = list(filter(lambda term: len(term) > 0, value.split(','))) + for setting in split: + setting_split = setting.split('=') + if len(setting_split) != 2: + self._fail(param) + key = setting_split[0] + value = setting_split[1] + properties[key] = value + return properties + except: + self._fail(param) + + def _fail(self, param): + self.fail(param=param, message='Settings must be in the format: key1=value1, key2=value2, ...') + + +def _join_settings(setting_dicts): + joined = {} + for setting_dict in setting_dicts: + for key, value in setting_dict.items(): + joined[key] = value + return joined + + @cli.command() +@click.argument('get_or_set', type=GetOrSet()) +@click.option('-g', '--is_global', default=False, is_flag=True, help='Configure global or local.') +@click.argument('settings', type=Settings(), nargs=-1) @click.pass_context -@click.option('-oi', '--object_id', help='Set the id of the owning sample.') -@click.option('-ci', '--collection_id', help='Set the id of the owning experiment.') -@click.argument('folder', type=click.Path(exists=False, file_okay=False)) -@click.argument('description', default="") -def init(ctx, object_id, collection_id, folder, description): - """Initialize the folder as a data folder (alias for init_data).""" - return init_data_impl(ctx, object_id, collection_id, folder, description) +def repository(ctx, get_or_set, is_global, settings): + print(get_or_set) + print(is_global) + settings_dict = _join_settings(settings) + print(settings_dict) + for prop, value in settings_dict.items(): + config(ctx, is_global, False, prop, value) + + +## repository -> properties.json +### repository_id +### external_dms_id +### data_set_id + +## config -> config.json +### fileservice_url +### git_annex_hash_as_checksum +### hostname +### openbis_url +### user +### verify_certificates + +## object -> config.json +### id + +## collection -> config.json +### id + +## data_set -> config.json +### type +### properties + + +# TODO replace by multiple commands +@cli.command() +@click.option('-g', '--is_global', default=False, is_flag=True, help='Configure global or local.') +@click.option('-p', '--is_data_set_property', default=False, is_flag=True, help='Configure data set property.') +@click.argument('prop', default="") +@click.argument('value', default="") +@click.pass_context +def config(ctx, is_global, is_data_set_property, prop, value): + """Configure the openBIS setup. + + Configure the openBIS server url, the data set type, and the data set properties. + """ + data_mgmt = shared_data_mgmt(ctx.obj) + config_internal(data_mgmt, is_global, is_data_set_property, prop, value) + + +# repository commands: status, sync, commit, init, addref, removeref, init_analysis + + +# TODO commit from without repository +# TODO add optional repository receiver +@cli.command() +@click.pass_context +@click.option('-m', '--msg', prompt=True, help='A message explaining what was done.') +@click.option('-a', '--auto_add', default=True, is_flag=True, help='Automatically add all untracked files.') +@click.option('-i', '--ignore_missing_parent', default=True, is_flag=True, help='If parent data set is missing, ignore it.') +def commit(ctx, msg, auto_add, ignore_missing_parent): + """Commit the repository to git and inform openBIS. + """ + data_mgmt = shared_data_mgmt(ctx.obj) + return check_result("commit", run(lambda: data_mgmt.commit(msg, auto_add, ignore_missing_parent))) +# TODO allow init from within repository +# TODO add optional repository receiver @cli.command() @click.pass_context @click.option('-oi', '--object_id', help='Set the id of the owning sample.') @click.option('-ci', '--collection_id', help='Set the id of the owning experiment.') @click.argument('folder', type=click.Path(exists=False, file_okay=False)) @click.argument('description', default="") -def init_data(ctx, object_id, collection_id, folder, description): - """Initialize the folder as a data folder.""" +def init(ctx, object_id, collection_id, folder, description): + """Initialize the folder as a data folder (alias for init_data).""" return init_data_impl(ctx, object_id, collection_id, folder, description) +# TODO add optional repository receiver @cli.command() @click.pass_context @click.option('-p', '--parent', type=click.Path(exists=False, file_okay=False)) @@ -246,6 +283,8 @@ def init_analysis(ctx, parent, object_id, collection_id, folder, description): return init_analysis_impl(ctx, parent, object_id, collection_id, folder, description) +# TODO allow from without repository with repository folder as parameter +# TODO add optional repository receiver @cli.command() @click.pass_context def status(ctx): @@ -256,6 +295,8 @@ def status(ctx): click.echo(result.output) +# TODO allow from without repository with repository folder as parameter +# TODO add optional repository receiver @cli.command() @click.pass_context @click.option('-i', '--ignore_missing_parent', default=True, is_flag=True, help='If parent data set is missing, ignore it.') @@ -266,6 +307,60 @@ def sync(ctx, ignore_missing_parent): return check_result("sync", run(lambda: data_mgmt.sync(ignore_missing_parent))) +# TODO allow to addref from within repository without argument +# TODO add optional repository receiver +@cli.command() +@click.pass_context +@click.argument('repository', type=click.Path(exists=True)) +def addref(ctx, repository): + """Add the given repository as a reference to openBIS. + """ + with cd(repository): + data_mgmt = shared_data_mgmt(ctx.obj) + return check_result("addref", run(data_mgmt.addref)) + + +# TODO allow to removeref from within repository without argument +# TODO add optional repository receiver +@cli.command() +@click.pass_context +@click.argument('repository', type=click.Path(exists=True)) +def removeref(ctx, repository): + """Remove the reference to the given repository from openBIS. + """ + with cd(repository): + data_mgmt = shared_data_mgmt(ctx.obj) + return check_result("addref", run(data_mgmt.removeref)) + + +# data set commands: download / clone + +# TODO obis data_set? clone data_set_id +@cli.command() +@click.option('-c', '--content_copy_index', type=int, default=None, help='Index of the content copy to download from.') +@click.option('-f', '--file', help='File in the data set to download - downloading all if not given.') +@click.argument('data_set_id') +@click.pass_context +def download(ctx, content_copy_index, file, data_set_id): + """ Download files of a linked data set. + """ + data_mgmt = shared_data_mgmt(ctx.obj) + return check_result("download", run(lambda: data_mgmt.download(data_set_id, content_copy_index, file))) + + +# TODO obis dataset? clone data_set_id +@cli.command() +@click.pass_context +@click.option('-u', '--ssh_user', default=None, help='User to connect to remote systems via ssh') +@click.option('-c', '--content_copy_index', type=int, default=None, help='Index of the content copy to clone from in case there are multiple copies') +@click.argument('data_set_id') +def clone(ctx, ssh_user, content_copy_index, data_set_id): + """Clone the repository found in the given data set id. + """ + data_mgmt = shared_data_mgmt(ctx.obj) + return check_result("clone", run(lambda: data_mgmt.clone(data_set_id, ssh_user, content_copy_index))) + + def main(): cli(obj={})