diff --git a/src/python/OBis/obis/dm/data_mgmt.py b/src/python/OBis/obis/dm/data_mgmt.py index c71bc541c3215f57432af879e347c1ce5d8c8b5c..e37fc61e02734ad7e73ff4b3b7aa0a64af4087f2 100644 --- a/src/python/OBis/obis/dm/data_mgmt.py +++ b/src/python/OBis/obis/dm/data_mgmt.py @@ -9,10 +9,13 @@ Module implementing data management operations. Created by Chandrasekhar Ramakrishnan on 2017-02-01. Copyright (c) 2017 Chandrasekhar Ramakrishnan. All rights reserved. """ +import abc +import os +import shutil import subprocess +from contextlib import contextmanager import pybis -import abc # noinspection PyPep8Naming @@ -68,6 +71,12 @@ class CommandResult(object): self.returncode = completed_process.returncode self.output = completed_process.stdout.decode('utf-8').strip() + def __str__(self): + return "CommandResult({},{})".format(self.returncode, self.output) + + def __repr__(self): + return "CommandResult({},{})".format(self.returncode, self.output) + def run_shell(args): return CommandResult(subprocess.run(args, stdout=subprocess.PIPE)) @@ -78,6 +87,17 @@ def locate_command(command): return run_shell(['type', '-p', command]) +@contextmanager +def cd(newdir): + """Safe cd -- return to original dir after execution, even if an exception is raised.""" + prevdir = os.getcwd() + os.chdir(os.path.expanduser(newdir)) + try: + yield + finally: + os.chdir(prevdir) + + class AbstractDataMgmt(metaclass=abc.ABCMeta): """Abstract object that implements operations. @@ -109,6 +129,25 @@ class AbstractDataMgmt(metaclass=abc.ABCMeta): @abc.abstractmethod def init_data(self, path, desc=None): + """Initialize a data repository at the path with the description.""" + return + + @abc.abstractmethod + def init_analysis(self, path): + """Initialize an analysis repository at the path.""" + return + + @abc.abstractmethod + def add_content(self, path): + """Add content to the repository.""" + return + + @abc.abstractmethod + def commit(self, msg): + """Commit the current repo. + + This issues a git commit and connects to openBIS and creates a data set in openBIS. + """ return @@ -118,12 +157,20 @@ class NoGitDataMgmt(AbstractDataMgmt): def init_data(self, path, desc=None): self.error_raise("init data", "No git command found.") + def init_analysis(self, path): + self.error_raise("init analysis", "No git command found.") + + def add_content(self, path): + self.error_raise("add", "No git command found.") + + def commit(self, msg): + self.error_raise("commit", "No git command found.") + class GitDataMgmt(AbstractDataMgmt): """DataMgmt operations in normal state.""" def init_data(self, path, desc=None): - """Initialize a data repository at the path with the description.""" result = self.git_wrapper.git_init(path) if result.returncode != 0: self.error(result.output) @@ -133,6 +180,24 @@ class GitDataMgmt(AbstractDataMgmt): self.error(result.output) return result + def init_analysis(self, path): + result = self.git_wrapper.git_init(path) + if result.returncode != 0: + self.error(result.output) + return result + + def add_content(self, path): + result = self.git_wrapper.git_add(path) + if result.returncode != 0: + self.error(result.output) + return result + + def commit(self, msg): + result = self.git_wrapper.git_commit(msg) + if result.returncode != 0: + self.error(result.output) + return result + class GitWrapper(object): """A wrapper on commands to git.""" @@ -159,7 +224,27 @@ class GitWrapper(object): return run_shell([self.git_path, "init", path]) def git_annex_init(self, path, desc): - cmd = [self.git_path, "-C", path, "annex", "init"] + cmd = [self.git_path, "-C", path, "annex", "init", "--version=6"] if desc is not None: cmd.append(desc) - return run_shell(cmd) + result = run_shell(cmd) + if result.returncode != 0: + return result + + attributes_src = os.path.join(os.path.dirname(__file__), "git-annex-attributes") + attributes_dst = os.path.join(path, ".gitattributes") + shutil.copyfile(attributes_src, attributes_dst) + cmd = [self.git_path, "-C", path, "add", ".gitattributes"] + result = run_shell(cmd) + if result.returncode != 0: + return result + + cmd = [self.git_path, "-C", path, "commit", "-m", "Initial commit."] + result = run_shell(cmd) + return result + + def git_add(self, path): + return run_shell([self.git_path, "add", path]) + + def git_commit(self, msg): + return run_shell([self.git_path, "commit", '-m', msg]) diff --git a/src/python/OBis/obis/dm/data_mgmt_test.py b/src/python/OBis/obis/dm/data_mgmt_test.py index 920ae053008780d71c153469dedd3a0e76c31ab6..9cf0f8c24f642d409d440d058e95cedc42f47b30 100644 --- a/src/python/OBis/obis/dm/data_mgmt_test.py +++ b/src/python/OBis/obis/dm/data_mgmt_test.py @@ -8,6 +8,8 @@ data_mgmt_test.py Created by Chandrasekhar Ramakrishnan on 2017-02-02. Copyright (c) 2017 Chandrasekhar Ramakrishnan. All rights reserved. """ +import os +import shutil from . import data_mgmt @@ -30,18 +32,61 @@ def test_locate_command(): assert result.returncode == 1 -def test_normal_use_case(shared_dm, tmpdir): - # The folder should not be a git repo at first. - result = data_mgmt.run_shell(['git', '-C', str(tmpdir), 'status', ]) - assert result.returncode == 128 +def git_status(path=None, annex=False): + cmd = ['git'] + if path: + cmd.extend(['-C', path]) + if annex: + cmd.extend(['annex', 'status']) + else: + cmd.extend(['status', '--porcelain']) + return data_mgmt.run_shell(cmd) - result = shared_dm.init_data(str(tmpdir), "test") - assert result.returncode == 0 - # The folder should be a git repo now - result = data_mgmt.run_shell(['git', '-C', str(tmpdir), 'status', str(tmpdir)]) - assert result.returncode == 0 +def test_data_use_case(shared_dm, tmpdir): + tmp_dir_path = str(tmpdir) + assert git_status(tmp_dir_path).returncode == 128 # The folder should not be a git repo at first. - # ...and a git-annex repo as well. - result = data_mgmt.run_shell(['git', '-C', str(tmpdir), 'annex', 'status', str(tmpdir)]) + result = shared_dm.init_data(tmp_dir_path, "test") assert result.returncode == 0 + + assert git_status(tmp_dir_path).returncode == 0 # The folder should be a git repo now + assert git_status(tmp_dir_path, annex=True).returncode == 0 # ...and a git-annex repo as well. + + copy_test_data(tmpdir) + + with data_mgmt.cd(tmp_dir_path): + result = shared_dm.add_content(".") + assert result.returncode == 0 + + result = git_status() + assert result.returncode == 0 + assert result.output == "A snb-data.zip\nA test.txt" + + result = shared_dm.commit("Added data.") + assert result.returncode == 0 + + # The zip should be in the annex + result = data_mgmt.run_shell(['git', 'annex', 'info', 'snb-data.zip']) + present_p = result.output.split('\n')[-1] + assert present_p == 'present: true' + + # The txt files should be in git normally + result = data_mgmt.run_shell(['git', 'annex', 'info', 'test.txt']) + present_p = result.output.split(' ')[-1] + assert present_p == 'failed' + + +def copy_test_data(tmpdir): + # Put some (binary) content into our new repository + test_data_folder = os.path.join(os.path.dirname(__file__), '..', 'test-data') + test_data_bin_src = os.path.join(test_data_folder, "snb-data.zip") + test_data_bin_path = str(tmpdir.join(os.path.basename(test_data_bin_src))) + shutil.copyfile(test_data_bin_src, test_data_bin_path) + + # Put some text content into our new repository + test_data_txt_src = os.path.join(test_data_folder, "test.txt") + test_data_txt_path = str(tmpdir.join(os.path.basename(test_data_txt_src))) + shutil.copyfile(test_data_txt_src, test_data_txt_path) + + return test_data_bin_path, test_data_bin_path diff --git a/src/python/OBis/obis/dm/git-annex-attributes b/src/python/OBis/obis/dm/git-annex-attributes new file mode 100644 index 0000000000000000000000000000000000000000..b868a67d46ecf033c1511a0c20b8c3a070775a0f --- /dev/null +++ b/src/python/OBis/obis/dm/git-annex-attributes @@ -0,0 +1,4 @@ +* annex.largefiles=(largerthan=100kb) +*.zip annex.largefiles=anything +*.py annex.largefiles=nothing +*.r annex.largefiles=nothing \ No newline at end of file diff --git a/src/python/OBis/obis/test-data/snb-data.zip b/src/python/OBis/obis/test-data/snb-data.zip new file mode 100644 index 0000000000000000000000000000000000000000..85dd312a6322fa7dce65aa6112499a04c24fd2f0 Binary files /dev/null and b/src/python/OBis/obis/test-data/snb-data.zip differ diff --git a/src/python/OBis/obis/test-data/test.txt b/src/python/OBis/obis/test-data/test.txt new file mode 100644 index 0000000000000000000000000000000000000000..959fc169b360231e0f1023a9a4b297866602506a --- /dev/null +++ b/src/python/OBis/obis/test-data/test.txt @@ -0,0 +1 @@ +Move along, nothing to see here. \ No newline at end of file