mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-19 02:21:32 +08:00
I guess there's a chance downstream users may run into version conflicts if we pin ro-cate to 0.7.0, and we also don't really want to bump stable version dependencies.
1114 lines
40 KiB
Python
1114 lines
40 KiB
Python
"""Unit tests for importing and exporting data from model stores."""
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import shutil
|
|
from tempfile import (
|
|
mkdtemp,
|
|
NamedTemporaryFile,
|
|
)
|
|
from typing import (
|
|
Any,
|
|
Dict,
|
|
NamedTuple,
|
|
Optional,
|
|
)
|
|
|
|
import pytest
|
|
from rocrate.rocrate import ROCrate
|
|
from sqlalchemy.orm.scoping import scoped_session
|
|
|
|
from galaxy import model
|
|
from galaxy.model import store
|
|
from galaxy.model.metadata import MetadataTempFile
|
|
from galaxy.model.orm.now import now
|
|
from galaxy.model.unittest_utils import GalaxyDataTestApp
|
|
from galaxy.model.unittest_utils.store_fixtures import (
|
|
deferred_hda_model_store_dict,
|
|
one_hda_model_store_dict,
|
|
one_ld_library_model_store_dict,
|
|
TEST_HASH_FUNCTION,
|
|
TEST_HASH_VALUE,
|
|
TEST_SOURCE_URI,
|
|
)
|
|
from galaxy.objectstore.unittest_utils import Config as TestConfig
|
|
from galaxy.util.compression_utils import CompressedFile
|
|
from ..test_galaxy_mapping import (
|
|
_invocation_for_workflow,
|
|
_workflow_from_steps,
|
|
)
|
|
|
|
TESTCASE_DIRECTORY = pathlib.Path(__file__).parent
|
|
TEST_PATH_1 = TESTCASE_DIRECTORY / "1.txt"
|
|
TEST_PATH_2 = TESTCASE_DIRECTORY / "2.bed"
|
|
DEFAULT_OBJECT_STORE_BY = "id"
|
|
|
|
|
|
def test_import_export_history():
|
|
"""Test a simple job import/export after decompressing an archive (like history import/export tool)."""
|
|
app = _mock_app()
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
|
|
imported_history = _import_export_history(app, h, export_files="copy")
|
|
|
|
_assert_simple_cat_job_imported(imported_history)
|
|
|
|
|
|
def test_import_export_history_failed_job():
|
|
"""Test a simple job import/export, make sure state is maintained correctly."""
|
|
app = _mock_app()
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app, state="error")
|
|
|
|
imported_history = _import_export_history(app, h, export_files="copy")
|
|
|
|
_assert_simple_cat_job_imported(imported_history, state="error")
|
|
|
|
|
|
def test_import_export_history_hidden_false_with_hidden_dataset():
|
|
app = _mock_app()
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
d2.visible = False
|
|
app.model.session.flush()
|
|
|
|
imported_history = _import_export_history(app, h, export_files="copy", include_hidden=False)
|
|
assert d1.dataset.get_size() == imported_history.datasets[0].get_size()
|
|
assert imported_history.datasets[1].get_size() == 0
|
|
|
|
|
|
def test_import_export_history_hidden_true_with_hidden_dataset():
|
|
app = _mock_app()
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
d2.visible = False
|
|
app.model.session.flush()
|
|
|
|
imported_history = _import_export_history(app, h, export_files="copy", include_hidden=True)
|
|
assert d1.dataset.get_size() == imported_history.datasets[0].get_size()
|
|
assert d2.dataset.get_size() == imported_history.datasets[1].get_size()
|
|
|
|
|
|
def test_import_export_history_allow_discarded_data():
|
|
"""Test an export and import without exporting dataset file data.
|
|
|
|
Experimental state that should result in 'discarded' datasets that are not
|
|
deleted.
|
|
"""
|
|
app = _mock_app()
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
|
|
import_options = store.ImportOptions(
|
|
discarded_data=store.ImportDiscardedDataType.ALLOW,
|
|
)
|
|
imported_history = _import_export_history(app, h, export_files=None, import_options=import_options)
|
|
assert imported_history.name == "imported from archive: Test History"
|
|
|
|
datasets = imported_history.datasets
|
|
assert len(datasets) == 2
|
|
assert datasets[0].state == datasets[1].state == model.Dataset.states.DISCARDED
|
|
assert datasets[0].deleted is False
|
|
|
|
imported_job = datasets[1].creating_job
|
|
assert imported_job
|
|
assert imported_job.state == "ok"
|
|
assert imported_job.output_datasets
|
|
assert imported_job.output_datasets[0].dataset == datasets[1]
|
|
|
|
|
|
def test_import_export_bag_archive():
|
|
"""Test a simple job import/export using a BagIt archive."""
|
|
dest_parent = mkdtemp()
|
|
dest_export = os.path.join(dest_parent, "moo.tgz")
|
|
|
|
app = _mock_app()
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
|
|
with store.BagArchiveModelExportStore(
|
|
dest_export, app=app, bag_archiver="tgz", export_files="copy"
|
|
) as export_store:
|
|
export_store.export_history(h)
|
|
|
|
model_store = store.BagArchiveImportModelStore(dest_export, app=app, user=u)
|
|
with model_store.target_history(default_history=None) as imported_history:
|
|
model_store.perform_import(imported_history)
|
|
|
|
_assert_simple_cat_job_imported(imported_history)
|
|
|
|
|
|
def test_import_export_datasets():
|
|
"""Test a simple job import/export using a directory."""
|
|
app, h, temp_directory, import_history = _setup_simple_export({"for_edit": False})
|
|
u = h.user
|
|
|
|
_perform_import_from_directory(temp_directory, app, u, import_history)
|
|
|
|
datasets = import_history.datasets
|
|
assert len(datasets) == 2
|
|
imported_job = datasets[1].creating_job
|
|
assert imported_job
|
|
assert imported_job.output_datasets
|
|
assert imported_job.output_datasets[0].dataset == datasets[1]
|
|
|
|
assert imported_job.input_datasets
|
|
assert imported_job.input_datasets[0].dataset == datasets[0]
|
|
|
|
|
|
def test_import_from_dict():
|
|
fixture_context = setup_fixture_context_with_history()
|
|
import_dict = one_hda_model_store_dict()
|
|
perform_import_from_store_dict(fixture_context, import_dict)
|
|
import_history = fixture_context.history
|
|
|
|
datasets = import_history.datasets
|
|
assert len(datasets) == 1
|
|
imported_hda = datasets[0]
|
|
assert imported_hda.name == "my cool name"
|
|
assert imported_hda.hid == 1
|
|
# it wasn't deleted going in but we delete discarded datasets by default
|
|
assert imported_hda.state == "deferred"
|
|
assert not imported_hda.deleted
|
|
|
|
assert len(imported_hda.dataset.hashes) == 1
|
|
assert len(imported_hda.dataset.sources) == 1
|
|
assert imported_hda.dataset.created_from_basename == "dataset.txt"
|
|
imported_dataset_hash = imported_hda.dataset.hashes[0]
|
|
imported_dataset_source = imported_hda.dataset.sources[0]
|
|
assert imported_dataset_hash.hash_function == TEST_HASH_FUNCTION
|
|
assert imported_dataset_hash.hash_value == TEST_HASH_VALUE
|
|
assert imported_dataset_source.source_uri == TEST_SOURCE_URI
|
|
|
|
|
|
def test_import_library_from_dict():
|
|
fixture_context = setup_fixture_context_with_history()
|
|
import_dict = one_ld_library_model_store_dict()
|
|
import_options = store.ImportOptions()
|
|
import_options.allow_library_creation = True
|
|
perform_import_from_store_dict(fixture_context, import_dict, import_options=import_options)
|
|
|
|
sa_session = fixture_context.sa_session
|
|
all_libraries = sa_session.query(model.Library).all()
|
|
assert len(all_libraries) == 1, len(all_libraries)
|
|
all_lddas = sa_session.query(model.LibraryDatasetDatasetAssociation).all()
|
|
assert len(all_lddas) == 1, len(all_lddas)
|
|
|
|
|
|
def test_import_allow_discarded():
|
|
fixture_context = setup_fixture_context_with_history()
|
|
import_dict = one_hda_model_store_dict(include_source=False)
|
|
import_options = store.ImportOptions(
|
|
discarded_data=store.ImportDiscardedDataType.ALLOW,
|
|
)
|
|
perform_import_from_store_dict(fixture_context, import_dict, import_options=import_options)
|
|
import_history = fixture_context.history
|
|
datasets = import_history.datasets
|
|
assert len(datasets) == 1
|
|
imported_hda = datasets[0]
|
|
assert imported_hda.name == "my cool name"
|
|
assert imported_hda.hid == 1
|
|
# it wasn't deleted going in but we delete discarded datasets by default
|
|
assert imported_hda.state == "discarded"
|
|
assert not imported_hda.deleted
|
|
assert not imported_hda.metadata_deferred
|
|
|
|
|
|
def test_import_deferred_metadata():
|
|
fixture_context = setup_fixture_context_with_history()
|
|
import_dict = deferred_hda_model_store_dict(metadata_deferred=True)
|
|
import_options = store.ImportOptions(
|
|
discarded_data=store.ImportDiscardedDataType.ALLOW,
|
|
)
|
|
perform_import_from_store_dict(fixture_context, import_dict, import_options=import_options)
|
|
import_history = fixture_context.history
|
|
datasets = import_history.datasets
|
|
assert len(datasets) == 1
|
|
imported_hda = datasets[0]
|
|
assert imported_hda.name == "my cool name"
|
|
assert imported_hda.hid == 1
|
|
# it wasn't deleted going in but we delete discarded datasets by default
|
|
assert imported_hda.state == "deferred"
|
|
assert not imported_hda.deleted
|
|
assert imported_hda.metadata_deferred
|
|
|
|
|
|
def test_import_library_require_permissions():
|
|
"""Verify library creation (import) is off by default."""
|
|
app = _mock_app()
|
|
sa_session = app.model.context
|
|
|
|
u = model.User(email="collection@example.com", password="password")
|
|
|
|
library = model.Library(name="my library 1", description="my library description", synopsis="my synopsis")
|
|
root_folder = model.LibraryFolder(name="my library 1", description="folder description")
|
|
library.root_folder = root_folder
|
|
sa_session.add_all((library, root_folder))
|
|
sa_session.flush()
|
|
|
|
temp_directory = mkdtemp()
|
|
with store.DirectoryModelExportStore(temp_directory, app=app) as export_store:
|
|
export_store.export_library(library)
|
|
|
|
error_caught = False
|
|
try:
|
|
import_model_store = store.get_import_model_store_for_directory(temp_directory, app=app, user=u)
|
|
import_model_store.perform_import()
|
|
except AssertionError:
|
|
# TODO: throw and catch a better exception...
|
|
error_caught = True
|
|
|
|
assert error_caught
|
|
|
|
|
|
def test_import_export_library():
|
|
"""Test basics of library, library folder, and library dataset import/export."""
|
|
app = _mock_app()
|
|
sa_session = app.model.context
|
|
|
|
u = model.User(email="collection@example.com", password="password")
|
|
|
|
library = model.Library(name="my library 1", description="my library description", synopsis="my synopsis")
|
|
root_folder = model.LibraryFolder(name="my library 1", description="folder description")
|
|
library.root_folder = root_folder
|
|
sa_session.add_all((library, root_folder))
|
|
sa_session.flush()
|
|
|
|
subfolder = model.LibraryFolder(name="sub folder 1", description="sub folder")
|
|
root_folder.add_folder(subfolder)
|
|
sa_session.add(subfolder)
|
|
|
|
ld = model.LibraryDataset(folder=root_folder, name="my name", info="my library dataset")
|
|
ldda = model.LibraryDatasetDatasetAssociation(create_dataset=True, flush=False)
|
|
ld.library_dataset_dataset_association = ldda
|
|
root_folder.add_library_dataset(ld)
|
|
|
|
sa_session.add(ld)
|
|
sa_session.add(ldda)
|
|
|
|
sa_session.flush()
|
|
assert len(root_folder.datasets) == 1
|
|
assert len(root_folder.folders) == 1
|
|
|
|
temp_directory = mkdtemp()
|
|
with store.DirectoryModelExportStore(temp_directory, app=app) as export_store:
|
|
export_store.export_library(library)
|
|
|
|
import_model_store = store.get_import_model_store_for_directory(
|
|
temp_directory, app=app, user=u, import_options=store.ImportOptions(allow_library_creation=True)
|
|
)
|
|
import_model_store.perform_import()
|
|
|
|
all_libraries = sa_session.query(model.Library).all()
|
|
assert len(all_libraries) == 2, len(all_libraries)
|
|
all_lddas = sa_session.query(model.LibraryDatasetDatasetAssociation).all()
|
|
assert len(all_lddas) == 2, len(all_lddas)
|
|
|
|
new_library = [lib for lib in all_libraries if lib.id != library.id][0]
|
|
assert new_library.name == "my library 1"
|
|
assert new_library.description == "my library description"
|
|
assert new_library.synopsis == "my synopsis"
|
|
|
|
new_root = new_library.root_folder
|
|
assert new_root
|
|
assert new_root.name == "my library 1"
|
|
|
|
assert len(new_root.folders) == 1
|
|
assert len(new_root.datasets) == 1
|
|
|
|
|
|
def test_import_export_invocation():
|
|
app = _mock_app()
|
|
workflow_invocation = _setup_invocation(app)
|
|
|
|
temp_directory = mkdtemp()
|
|
with store.DirectoryModelExportStore(temp_directory, app=app) as export_store:
|
|
export_store.export_workflow_invocation(workflow_invocation)
|
|
|
|
sa_session = app.model.context
|
|
h2 = model.History(user=workflow_invocation.user)
|
|
sa_session.add(h2)
|
|
sa_session.flush()
|
|
|
|
import_model_store = store.get_import_model_store_for_directory(
|
|
temp_directory, app=app, user=workflow_invocation.user, import_options=store.ImportOptions()
|
|
)
|
|
import_model_store.perform_import(history=h2)
|
|
|
|
|
|
def validate_crate_metadata(as_dict):
|
|
assert as_dict["@context"] == "https://w3id.org/ro/crate/1.1/context"
|
|
|
|
|
|
def validate_has_pl_galaxy(ro_crate: ROCrate):
|
|
programming_language = ro_crate.mainEntity.get("programmingLanguage")
|
|
assert programming_language
|
|
assert programming_language.id == "https://w3id.org/workflowhub/workflow-ro-crate#galaxy"
|
|
assert programming_language.name == "Galaxy"
|
|
assert programming_language.url == "https://galaxyproject.org/"
|
|
|
|
|
|
def validate_organize_action(ro_crate: ROCrate):
|
|
organize_action = next((x for x in ro_crate.contextual_entities if x.type == "OrganizeAction"), None)
|
|
assert organize_action
|
|
|
|
|
|
def validate_has_mit_license(ro_crate: ROCrate):
|
|
found_license = False
|
|
for e in ro_crate.get_entities():
|
|
if e.id == "./":
|
|
assert e["license"] == "MIT"
|
|
found_license = True
|
|
assert found_license
|
|
|
|
|
|
def validate_has_readme(ro_crate: ROCrate):
|
|
found_readme = False
|
|
for e in ro_crate.get_entities():
|
|
if e.id == "README.md":
|
|
assert e.type == "File"
|
|
assert e["encodingFormat"] == "text/markdown"
|
|
# assert e["about"] == "./"
|
|
found_readme = True
|
|
assert found_readme
|
|
|
|
|
|
def open_ro_crate(crate_directory):
|
|
metadata_json_path = crate_directory / "ro-crate-metadata.json"
|
|
with metadata_json_path.open() as f:
|
|
metadata_json = json.load(f)
|
|
validate_crate_metadata(metadata_json)
|
|
crate = ROCrate(crate_directory)
|
|
return crate
|
|
|
|
|
|
def validate_history_crate_directory(crate_directory):
|
|
crate = open_ro_crate(crate_directory)
|
|
validate_has_readme(crate)
|
|
|
|
|
|
def validate_main_entity(ro_crate: ROCrate):
|
|
workflow = ro_crate.mainEntity
|
|
assert workflow
|
|
assert workflow.id.endswith(".gxwf.yml")
|
|
assert workflow["name"]
|
|
assert workflow["name"] == "Test Workflow"
|
|
assert "SoftwareSourceCode" in workflow.type
|
|
assert "ComputationalWorkflow" in workflow.type
|
|
assert len(workflow["input"]) == 1
|
|
assert len(workflow["output"]) == 1
|
|
|
|
|
|
def validate_create_action(ro_crate: ROCrate):
|
|
workflow = ro_crate.mainEntity
|
|
actions = [_ for _ in ro_crate.contextual_entities if "CreateAction" in _.type]
|
|
assert len(actions) == 1
|
|
wf_action = actions[0]
|
|
assert wf_action["instrument"]
|
|
assert wf_action["instrument"] is workflow
|
|
wf_objects = wf_action["object"]
|
|
wf_results = wf_action["result"]
|
|
assert len(wf_objects) == 1
|
|
assert len(wf_results) == 1
|
|
for entity in wf_results:
|
|
if entity.id.endswith(".txt"):
|
|
assert "File" in entity.type
|
|
wf_output_file = entity
|
|
assert wf_output_file["encodingFormat"] == "text/plain"
|
|
assert wf_output_file["exampleOfWork"] is workflow["output"][0]
|
|
|
|
|
|
def validate_other_entities(ro_crate: ROCrate):
|
|
workflow = ro_crate.mainEntity
|
|
inputs = workflow["input"]
|
|
outputs = workflow["output"]
|
|
assert inputs[0]["additionalType"] == "File"
|
|
assert outputs[0]["additionalType"] == "File"
|
|
|
|
for entity in inputs + outputs:
|
|
assert "FormalParameter" in entity.type
|
|
|
|
sel = [_ for _ in ro_crate.contextual_entities if "OrganizeAction" in _.type]
|
|
assert len(sel) == 1
|
|
engine_action = sel[0]
|
|
assert "SoftwareApplication" in engine_action["instrument"].type
|
|
|
|
|
|
def validate_invocation_crate_directory(crate_directory):
|
|
crate = open_ro_crate(crate_directory)
|
|
for e in crate.contextual_entities:
|
|
print(e.type)
|
|
validate_main_entity(crate)
|
|
validate_create_action(crate)
|
|
validate_other_entities(crate)
|
|
validate_has_pl_galaxy(crate)
|
|
validate_organize_action(crate)
|
|
validate_has_mit_license(crate)
|
|
# validate_has_readme(crate)
|
|
|
|
|
|
def validate_invocation_collection_crate_directory(crate_directory):
|
|
ro_crate = open_ro_crate(crate_directory)
|
|
workflow = ro_crate.mainEntity
|
|
root = ro_crate.root_dataset
|
|
actions = [_ for _ in ro_crate.contextual_entities if "CreateAction" in _.type]
|
|
assert len(actions) == 1
|
|
wf_action = actions[0]
|
|
assert wf_action in root["mentions"]
|
|
assert len(workflow["input"]) == 2
|
|
assert len(workflow["output"]) == 1
|
|
assert len(root["mentions"]) == 4
|
|
collections = [_ for _ in ro_crate.contextual_entities if "Collection" in _.type]
|
|
assert len(collections) == 3
|
|
collection = collections[0]
|
|
assert collection.type == "Collection"
|
|
assert (
|
|
collection["additionalType"]
|
|
== "https://training.galaxyproject.org/training-material/faqs/galaxy/collections_build_list.html"
|
|
)
|
|
assert len(collection["hasPart"]) == 2
|
|
for dataset in collection["hasPart"]:
|
|
assert dataset in root["hasPart"]
|
|
|
|
|
|
def test_export_history_to_ro_crate(tmp_path):
|
|
app = _mock_app()
|
|
u, history, d1, d2, j = _setup_simple_cat_job(app)
|
|
|
|
crate_directory = tmp_path / "crate"
|
|
with store.ROCrateModelExportStore(crate_directory, app=app) as export_store:
|
|
export_store.export_history(history)
|
|
validate_history_crate_directory(crate_directory)
|
|
|
|
|
|
def test_export_invocation_to_ro_crate(tmp_path):
|
|
app = _mock_app()
|
|
workflow_invocation = _setup_invocation(app)
|
|
crate_directory = tmp_path / "crate"
|
|
with store.ROCrateModelExportStore(crate_directory, app=app) as export_store:
|
|
export_store.export_workflow_invocation(workflow_invocation)
|
|
validate_invocation_crate_directory(crate_directory)
|
|
|
|
|
|
def test_export_simple_invocation_to_ro_crate(tmp_path):
|
|
app = _mock_app()
|
|
workflow_invocation = _setup_simple_invocation(app)
|
|
crate_directory = tmp_path / "crate"
|
|
with store.ROCrateModelExportStore(crate_directory, app=app) as export_store:
|
|
export_store.export_workflow_invocation(workflow_invocation)
|
|
validate_invocation_crate_directory(crate_directory)
|
|
|
|
|
|
def test_export_collection_invocation_to_ro_crate(tmp_path):
|
|
app = _mock_app()
|
|
workflow_invocation = _setup_collection_invocation(app)
|
|
crate_directory = tmp_path / "crate"
|
|
with store.ROCrateModelExportStore(crate_directory, app=app) as export_store:
|
|
export_store.export_workflow_invocation(workflow_invocation)
|
|
validate_invocation_collection_crate_directory(crate_directory)
|
|
|
|
|
|
def test_export_invocation_to_ro_crate_archive(tmp_path):
|
|
app = _mock_app()
|
|
workflow_invocation = _setup_invocation(app)
|
|
|
|
crate_zip = tmp_path / "crate.zip"
|
|
with store.ROCrateArchiveModelExportStore(crate_zip, app=app, export_files="symlink") as export_store:
|
|
export_store.export_workflow_invocation(workflow_invocation)
|
|
compressed_file = CompressedFile(crate_zip)
|
|
assert compressed_file.file_type == "zip"
|
|
compressed_file.extract(tmp_path)
|
|
crate_directory = tmp_path / "crate"
|
|
validate_invocation_crate_directory(crate_directory)
|
|
|
|
|
|
def test_finalize_job_state():
|
|
"""Verify jobs are given finalized states on import."""
|
|
app, h, temp_directory, import_history = _setup_simple_export({"for_edit": False})
|
|
u = h.user
|
|
|
|
with open(os.path.join(temp_directory, store.ATTRS_FILENAME_JOBS)) as f:
|
|
job_attrs = json.load(f)
|
|
|
|
for job in job_attrs:
|
|
job["state"] = "queued"
|
|
|
|
with open(os.path.join(temp_directory, store.ATTRS_FILENAME_JOBS), "w") as f:
|
|
json.dump(job_attrs, f)
|
|
|
|
_perform_import_from_directory(temp_directory, app, u, import_history)
|
|
|
|
datasets = import_history.datasets
|
|
assert len(datasets) == 2
|
|
imported_job = datasets[1].creating_job
|
|
assert imported_job
|
|
assert imported_job.state == model.Job.states.ERROR
|
|
|
|
|
|
def test_import_traceback_handling():
|
|
app, h, temp_directory, import_history = _setup_simple_export({"for_edit": False})
|
|
u = h.user
|
|
traceback_message = "Oh no, a traceback here!!!"
|
|
|
|
with open(os.path.join(temp_directory, store.TRACEBACK), "w") as f:
|
|
f.write(traceback_message)
|
|
|
|
with pytest.raises(store.FileTracebackException) as exc:
|
|
_perform_import_from_directory(temp_directory, app, u, import_history)
|
|
assert exc.value.traceback == traceback_message
|
|
|
|
|
|
def test_import_export_edit_datasets():
|
|
"""Test modifying existing HDA and dataset metadata with import."""
|
|
app, h, temp_directory, import_history = _setup_simple_export({"for_edit": True})
|
|
u = h.user
|
|
|
|
# Fabric editing metadata...
|
|
datasets_metadata_path = os.path.join(temp_directory, store.ATTRS_FILENAME_DATASETS)
|
|
with open(datasets_metadata_path) as f:
|
|
datasets_metadata = json.load(f)
|
|
|
|
datasets_metadata[0]["name"] = "my new name 0"
|
|
datasets_metadata[1]["name"] = "my new name 1"
|
|
|
|
assert "dataset" in datasets_metadata[0]
|
|
datasets_metadata[0]["dataset"]["object_store_id"] = "foo1"
|
|
|
|
with open(datasets_metadata_path, "w") as f:
|
|
json.dump(datasets_metadata, f)
|
|
|
|
_perform_import_from_directory(temp_directory, app, u, import_history, store.ImportOptions(allow_edit=True))
|
|
|
|
datasets = import_history.datasets
|
|
assert len(datasets) == 0
|
|
|
|
d1 = h.datasets[0]
|
|
d2 = h.datasets[1]
|
|
|
|
assert d1.name == "my new name 0", d1.name
|
|
assert d2.name == "my new name 1", d2.name
|
|
assert d1.dataset.object_store_id == "foo1", d1.dataset.object_store_id
|
|
|
|
|
|
def test_import_export_edit_collection():
|
|
"""Test modifying existing collections with imports."""
|
|
app = _mock_app()
|
|
sa_session = app.model.context
|
|
|
|
u = model.User(email="collection@example.com", password="password")
|
|
h = model.History(name="Test History", user=u)
|
|
|
|
c1 = model.DatasetCollection(collection_type="list", populated=False)
|
|
hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=1, collection=c1, name="HistoryCollectionTest1")
|
|
|
|
sa_session.add(hc1)
|
|
sa_session.add(h)
|
|
import_history = model.History(name="Test History for Import", user=u)
|
|
sa_session.add(import_history)
|
|
sa_session.flush()
|
|
|
|
temp_directory = mkdtemp()
|
|
with store.DirectoryModelExportStore(temp_directory, app=app, for_edit=True) as export_store:
|
|
export_store.add_dataset_collection(hc1)
|
|
|
|
# Fabric editing metadata for collection...
|
|
collections_metadata_path = os.path.join(temp_directory, store.ATTRS_FILENAME_COLLECTIONS)
|
|
datasets_metadata_path = os.path.join(temp_directory, store.ATTRS_FILENAME_DATASETS)
|
|
with open(collections_metadata_path) as f:
|
|
hdcas_metadata = json.load(f)
|
|
|
|
assert len(hdcas_metadata) == 1
|
|
hdca_metadata = hdcas_metadata[0]
|
|
assert hdca_metadata
|
|
assert "id" in hdca_metadata
|
|
assert "collection" in hdca_metadata
|
|
collection_metadata = hdca_metadata["collection"]
|
|
assert "populated_state" in collection_metadata
|
|
assert collection_metadata["populated_state"] == model.DatasetCollection.populated_states.NEW
|
|
|
|
collection_metadata["populated_state"] = model.DatasetCollection.populated_states.OK
|
|
|
|
d1 = model.HistoryDatasetAssociation(extension="txt", create_dataset=True, flush=False)
|
|
d1.hid = 1
|
|
d2 = model.HistoryDatasetAssociation(extension="txt", create_dataset=True, flush=False)
|
|
d2.hid = 2
|
|
serialization_options = model.SerializationOptions(for_edit=True)
|
|
dataset_list = [
|
|
d1.serialize(app.security, serialization_options),
|
|
d2.serialize(app.security, serialization_options),
|
|
]
|
|
|
|
dc = model.DatasetCollection(
|
|
id=collection_metadata["id"],
|
|
collection_type="list",
|
|
element_count=2,
|
|
)
|
|
dc.populated_state = model.DatasetCollection.populated_states.OK
|
|
dce1 = model.DatasetCollectionElement(
|
|
element=d1,
|
|
element_index=0,
|
|
element_identifier="first",
|
|
)
|
|
dce2 = model.DatasetCollectionElement(
|
|
element=d2,
|
|
element_index=1,
|
|
element_identifier="second",
|
|
)
|
|
dc.elements = [dce1, dce2]
|
|
with open(datasets_metadata_path, "w") as datasets_f:
|
|
json.dump(dataset_list, datasets_f)
|
|
|
|
hdca_metadata["collection"] = dc.serialize(app.security, serialization_options)
|
|
with open(collections_metadata_path, "w") as collections_f:
|
|
json.dump(hdcas_metadata, collections_f)
|
|
|
|
_perform_import_from_directory(temp_directory, app, u, import_history, store.ImportOptions(allow_edit=True))
|
|
|
|
sa_session.refresh(c1)
|
|
assert c1.populated_state == model.DatasetCollection.populated_states.OK, c1.populated_state
|
|
assert len(c1.elements) == 2
|
|
|
|
|
|
def test_import_export_composite_datasets():
|
|
app = _mock_app()
|
|
sa_session = app.model.context
|
|
|
|
u = model.User(email="collection@example.com", password="password")
|
|
h = model.History(name="Test History", user=u)
|
|
|
|
d1 = _create_datasets(sa_session, h, 1, extension="html")[0]
|
|
d1.dataset.create_extra_files_path()
|
|
sa_session.add_all((h, d1))
|
|
sa_session.flush()
|
|
|
|
primary = NamedTemporaryFile("w")
|
|
primary.write("cool primary file")
|
|
primary.flush()
|
|
app.object_store.update_from_file(d1.dataset, file_name=primary.name, create=True, preserve_symlinks=True)
|
|
|
|
composite1 = NamedTemporaryFile("w")
|
|
composite1.write("cool composite file")
|
|
composite1.flush()
|
|
|
|
app.object_store.update_from_file(
|
|
d1.dataset,
|
|
extra_dir=os.path.normpath(os.path.join(d1.extra_files_path, "parent_dir")),
|
|
alt_name="child_file",
|
|
file_name=composite1.name,
|
|
create=True,
|
|
preserve_symlinks=True,
|
|
)
|
|
|
|
temp_directory = mkdtemp()
|
|
with store.DirectoryModelExportStore(temp_directory, app=app, export_files="copy") as export_store:
|
|
export_store.add_dataset(d1)
|
|
|
|
import_history = model.History(name="Test History for Import", user=u)
|
|
sa_session.add(import_history)
|
|
sa_session.flush()
|
|
_perform_import_from_directory(temp_directory, app, u, import_history)
|
|
assert len(import_history.datasets) == 1
|
|
import_dataset = import_history.datasets[0]
|
|
root_extra_files_path = import_dataset.extra_files_path
|
|
assert len(os.listdir(root_extra_files_path)) == 1
|
|
assert os.listdir(root_extra_files_path)[0] == "parent_dir"
|
|
composite_sub_dir = os.path.join(root_extra_files_path, "parent_dir")
|
|
child_files = os.listdir(composite_sub_dir)
|
|
assert len(child_files) == 1
|
|
with open(os.path.join(composite_sub_dir, child_files[0])) as f:
|
|
contents = f.read()
|
|
assert contents == "cool composite file"
|
|
|
|
|
|
def test_edit_metadata_files():
|
|
app = _mock_app(store_by="uuid")
|
|
sa_session = app.model.context
|
|
|
|
u = model.User(email="collection@example.com", password="password")
|
|
h = model.History(name="Test History", user=u)
|
|
|
|
d1 = _create_datasets(sa_session, h, 1, extension="bam")[0]
|
|
sa_session.add_all((h, d1))
|
|
sa_session.flush()
|
|
index = NamedTemporaryFile("w")
|
|
index.write("cool bam index")
|
|
metadata_dict = {"bam_index": MetadataTempFile.from_JSON({"kwds": {}, "filename": index.name})}
|
|
d1.metadata.from_JSON_dict(json_dict=metadata_dict)
|
|
assert d1.metadata.bam_index
|
|
assert isinstance(d1.metadata.bam_index, model.MetadataFile)
|
|
|
|
temp_directory = mkdtemp()
|
|
with store.DirectoryModelExportStore(
|
|
temp_directory, app=app, for_edit=True, strip_metadata_files=False
|
|
) as export_store:
|
|
export_store.add_dataset(d1)
|
|
|
|
import_history = model.History(name="Test History for Import", user=u)
|
|
sa_session.add(import_history)
|
|
sa_session.flush()
|
|
_perform_import_from_directory(temp_directory, app, u, import_history, store.ImportOptions(allow_edit=True))
|
|
|
|
|
|
def test_sessionless_import_edit_datasets():
|
|
app, h, temp_directory, import_history = _setup_simple_export({"for_edit": True})
|
|
# Create a model store without a session and import it.
|
|
import_model_store = store.get_import_model_store_for_directory(
|
|
temp_directory, import_options=store.ImportOptions(allow_dataset_object_edit=True, allow_edit=True)
|
|
)
|
|
import_model_store.perform_import()
|
|
# Not using app.sa_session but a session mock that has a query/find pattern emulating usage
|
|
# of real sa_session.
|
|
d1 = import_model_store.sa_session.query(model.HistoryDatasetAssociation).find(h.datasets[0].id)
|
|
d2 = import_model_store.sa_session.query(model.HistoryDatasetAssociation).find(h.datasets[1].id)
|
|
assert d1 is not None
|
|
assert d2 is not None
|
|
|
|
|
|
def test_import_datasets_with_ids_fails_if_not_editing_models():
|
|
app, h, temp_directory, import_history = _setup_simple_export({"for_edit": True})
|
|
u = h.user
|
|
|
|
caught = None
|
|
try:
|
|
_perform_import_from_directory(temp_directory, app, u, import_history, store.ImportOptions(allow_edit=False))
|
|
except AssertionError as e:
|
|
# TODO: catch a better exception
|
|
caught = e
|
|
assert caught
|
|
|
|
|
|
def _setup_simple_export(export_kwds):
|
|
app = _mock_app()
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
|
|
sa_session = app.model.context
|
|
|
|
import_history = model.History(name="Test History for Import", user=u)
|
|
sa_session.add(import_history)
|
|
sa_session.flush()
|
|
|
|
temp_directory = mkdtemp()
|
|
with store.DirectoryModelExportStore(temp_directory, app=app, **export_kwds) as export_store:
|
|
export_store.add_dataset(d1)
|
|
export_store.add_dataset(d2)
|
|
|
|
return app, h, temp_directory, import_history
|
|
|
|
|
|
def _assert_simple_cat_job_imported(imported_history, state="ok"):
|
|
assert imported_history.name == "imported from archive: Test History"
|
|
|
|
datasets = imported_history.datasets
|
|
assert len(datasets) == 2
|
|
assert datasets[0].state == datasets[1].state == state
|
|
imported_job = datasets[1].creating_job
|
|
assert imported_job
|
|
assert imported_job.state == state
|
|
assert imported_job.output_datasets
|
|
assert imported_job.output_datasets[0].dataset == datasets[1]
|
|
|
|
assert imported_job.input_datasets
|
|
assert imported_job.input_datasets[0].dataset == datasets[0]
|
|
|
|
with open(datasets[0].file_name) as f:
|
|
assert f.read().startswith("chr1 4225 19670")
|
|
with open(datasets[1].file_name) as f:
|
|
assert f.read().startswith("chr1\t147962192\t147962580\tNM_005997_cds_0_0_chr1_147962193_r\t0\t-")
|
|
|
|
|
|
def _setup_simple_cat_job(app, state="ok"):
|
|
sa_session = app.model.context
|
|
|
|
u = model.User(email="collection@example.com", password="password")
|
|
h = model.History(name="Test History", user=u)
|
|
|
|
d1, d2 = _create_datasets(sa_session, h, 2)
|
|
d1.state = d2.state = state
|
|
|
|
j = model.Job()
|
|
j.user = u
|
|
j.tool_id = "cat1"
|
|
j.state = state
|
|
|
|
j.add_input_dataset("input1", d1)
|
|
j.add_output_dataset("out_file1", d2)
|
|
|
|
sa_session.add_all((d1, d2, h, j))
|
|
sa_session.flush()
|
|
|
|
app.object_store.update_from_file(d1, file_name=TEST_PATH_1, create=True)
|
|
app.object_store.update_from_file(d2, file_name=TEST_PATH_2, create=True)
|
|
|
|
return u, h, d1, d2, j
|
|
|
|
|
|
def _setup_invocation(app):
|
|
sa_session = app.model.context
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
j.parameters = [model.JobParameter(name="index_path", value='"/old/path/human"')]
|
|
|
|
workflow_step_1 = model.WorkflowStep()
|
|
workflow_step_1.order_index = 0
|
|
workflow_step_1.type = "data_input"
|
|
sa_session.add(workflow_step_1)
|
|
workflow_1 = _workflow_from_steps(u, [workflow_step_1])
|
|
workflow_1.license = "MIT"
|
|
workflow_1.name = "Test Workflow"
|
|
sa_session.add(workflow_1)
|
|
workflow_invocation = _invocation_for_workflow(u, workflow_1)
|
|
invocation_step = model.WorkflowInvocationStep()
|
|
invocation_step.workflow_step = workflow_step_1
|
|
invocation_step.job = j
|
|
sa_session.add(invocation_step)
|
|
output_assoc = model.WorkflowInvocationStepOutputDatasetAssociation()
|
|
output_assoc.dataset = d2
|
|
invocation_step.output_datasets = [output_assoc]
|
|
workflow_invocation.steps = [invocation_step]
|
|
workflow_invocation.user = u
|
|
workflow_invocation.add_input(d1, step=workflow_step_1)
|
|
wf_output = model.WorkflowOutput(workflow_step_1, label="output_label")
|
|
workflow_invocation.add_output(wf_output, workflow_step_1, d2)
|
|
sa_session.add(workflow_invocation)
|
|
sa_session.flush()
|
|
return workflow_invocation
|
|
|
|
|
|
def _setup_simple_collection_job(app, state="ok"):
|
|
sa_session = app.model.context
|
|
|
|
u = model.User(email="collection@example.com", password="password")
|
|
h = model.History(name="Test History", user=u)
|
|
|
|
d1, d2, d3, d4 = _create_datasets(sa_session, h, 4)
|
|
|
|
c1 = model.DatasetCollection(collection_type="list")
|
|
hc1 = model.HistoryDatasetCollectionAssociation(history=h, hid=1, collection=c1, name="HistoryCollectionTest1")
|
|
dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0)
|
|
dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1)
|
|
|
|
c2 = model.DatasetCollection(collection_type="list")
|
|
hc2 = model.HistoryDatasetCollectionAssociation(history=h, hid=2, collection=c2, name="HistoryCollectionTest2")
|
|
dce3 = model.DatasetCollectionElement(collection=c2, element=d1, element_identifier="forward", element_index=0)
|
|
dce4 = model.DatasetCollectionElement(collection=c2, element=d3, element_identifier="reverse", element_index=1)
|
|
|
|
c3 = model.DatasetCollection(collection_type="list")
|
|
hc3 = model.HistoryDatasetCollectionAssociation(history=h, hid=3, collection=c3, name="HistoryCollectionTest3")
|
|
dce5 = model.DatasetCollectionElement(collection=c3, element=d4, element_identifier="out", element_index=0)
|
|
|
|
j = model.Job()
|
|
j.user = h.user
|
|
j.tool_id = "cat1"
|
|
j.add_input_dataset("input1", d1)
|
|
j.add_input_dataset("input2", d2)
|
|
j.add_input_dataset("input3", d3)
|
|
j.add_output_dataset("out_file1", d4)
|
|
j.add_input_dataset_collection("input1_collect", hc1)
|
|
j.add_input_dataset_collection("input2_collect", hc2)
|
|
j.add_output_dataset_collection("output", hc3)
|
|
|
|
sa_session.add(dce1)
|
|
sa_session.add(dce2)
|
|
sa_session.add(dce3)
|
|
sa_session.add(dce4)
|
|
sa_session.add(dce5)
|
|
sa_session.add(hc1)
|
|
sa_session.add(hc2)
|
|
sa_session.add(hc3)
|
|
sa_session.add(j)
|
|
sa_session.flush()
|
|
|
|
return u, h, c1, c2, c3, hc1, hc2, hc3, j
|
|
|
|
|
|
def _setup_collection_invocation(app):
|
|
sa_session = app.model.context
|
|
|
|
u, h, c1, c2, c3, hc1, hc2, hc3, j = _setup_simple_collection_job(app)
|
|
|
|
workflow_step_1 = model.WorkflowStep()
|
|
workflow_step_1.order_index = 0
|
|
workflow_step_1.type = "data_collection_input"
|
|
workflow_step_1.tool_inputs = {}
|
|
sa_session.add(workflow_step_1)
|
|
workflow_1 = _workflow_from_steps(u, [workflow_step_1])
|
|
workflow_1.license = "MIT"
|
|
workflow_1.name = "Test Workflow"
|
|
sa_session.add(workflow_1)
|
|
workflow_invocation = _invocation_for_workflow(u, workflow_1)
|
|
workflow_invocation.user = u
|
|
workflow_invocation.add_input(hc1, step=workflow_step_1)
|
|
workflow_invocation.add_input(hc2, step=workflow_step_1)
|
|
wf_output = model.WorkflowOutput(workflow_step_1, label="output_label")
|
|
workflow_invocation.add_output(wf_output, workflow_step_1, hc3)
|
|
|
|
sa_session.add(workflow_invocation)
|
|
sa_session.flush()
|
|
return workflow_invocation
|
|
|
|
|
|
def _setup_simple_invocation(app):
|
|
sa_session = app.model.context
|
|
|
|
u, h, d1, d2, j = _setup_simple_cat_job(app)
|
|
j.parameters = [model.JobParameter(name="index_path", value='"/old/path/human"')]
|
|
|
|
workflow_step_1 = model.WorkflowStep()
|
|
workflow_step_1.order_index = 0
|
|
workflow_step_1.type = "data_input"
|
|
workflow_step_1.tool_inputs = {}
|
|
sa_session.add(workflow_step_1)
|
|
workflow = _workflow_from_steps(u, [workflow_step_1])
|
|
workflow.license = "MIT"
|
|
workflow.name = "Test Workflow"
|
|
workflow.create_time = now()
|
|
workflow.update_time = now()
|
|
sa_session.add(workflow)
|
|
invocation = _invocation_for_workflow(u, workflow)
|
|
invocation.create_time = now()
|
|
invocation.update_time = now()
|
|
|
|
invocation.add_input(d1, step=workflow_step_1)
|
|
wf_output = model.WorkflowOutput(workflow_step_1, label="output_label")
|
|
invocation.add_output(wf_output, workflow_step_1, d2)
|
|
return invocation
|
|
|
|
|
|
def _import_export_history(app, h, dest_export=None, export_files=None, import_options=None, include_hidden=False):
|
|
if dest_export is None:
|
|
dest_parent = mkdtemp()
|
|
dest_export = os.path.join(dest_parent, "moo.tgz")
|
|
|
|
with store.TarModelExportStore(dest_export, app=app, export_files=export_files) as export_store:
|
|
export_store.export_history(h, include_hidden=include_hidden)
|
|
|
|
imported_history = import_archive(dest_export, app, h.user, import_options=import_options)
|
|
assert imported_history
|
|
return imported_history
|
|
|
|
|
|
def _perform_import_from_directory(directory, app, user, import_history, import_options=None):
|
|
import_model_store = store.get_import_model_store_for_directory(
|
|
directory, app=app, user=user, import_options=import_options
|
|
)
|
|
with import_model_store.target_history(default_history=import_history):
|
|
import_model_store.perform_import(import_history)
|
|
|
|
|
|
def _create_datasets(sa_session, history, n, extension="txt"):
|
|
return [
|
|
model.HistoryDatasetAssociation(
|
|
extension=extension, history=history, create_dataset=True, sa_session=sa_session, hid=i + 1
|
|
)
|
|
for i in range(n)
|
|
]
|
|
|
|
|
|
class MockWorkflowContentsManager:
|
|
def store_workflow_artifacts(self, directory, workflow_key, workflow, **kwd):
|
|
path = os.path.join(directory, f"{workflow_key}.gxwf.yml")
|
|
with open(path, "w") as f:
|
|
f.write("MY COOL WORKFLOW!!!")
|
|
path = os.path.join(directory, f"{workflow_key}.abstract.cwl")
|
|
with open(path, "w") as f:
|
|
f.write("MY COOL WORKFLOW as CWL!!!")
|
|
|
|
def read_workflow_from_path(self, app, user, path, allow_in_directory=None):
|
|
stored_workflow = model.StoredWorkflow()
|
|
stored_workflow.user = user
|
|
workflow_step_1 = model.WorkflowStep()
|
|
workflow_step_1.order_index = 0
|
|
workflow_step_1.type = "data_input"
|
|
workflow = model.Workflow()
|
|
workflow.steps = [workflow_step_1]
|
|
stored_workflow.latest_workflow = workflow
|
|
sa_session = app.model.context
|
|
sa_session.add_all((stored_workflow, workflow))
|
|
sa_session.flush()
|
|
return workflow
|
|
|
|
|
|
class TestApp(GalaxyDataTestApp):
|
|
workflow_contents_manager = MockWorkflowContentsManager()
|
|
|
|
|
|
def _mock_app(store_by=DEFAULT_OBJECT_STORE_BY):
|
|
app = TestApp()
|
|
test_object_store_config = TestConfig(store_by=store_by)
|
|
app.object_store = test_object_store_config.object_store
|
|
app.model.Dataset.object_store = app.object_store
|
|
return app
|
|
|
|
|
|
class StoreFixtureContextWithUser(NamedTuple):
|
|
app: TestApp
|
|
sa_session: scoped_session
|
|
user: model.User
|
|
|
|
|
|
def setup_fixture_context_with_user(
|
|
user_email="test@example.com", store_by=DEFAULT_OBJECT_STORE_BY
|
|
) -> StoreFixtureContextWithUser:
|
|
app = _mock_app(store_by=store_by)
|
|
sa_session = app.model.context
|
|
user = model.User(email=user_email, password="password")
|
|
return StoreFixtureContextWithUser(app=app, sa_session=sa_session, user=user)
|
|
|
|
|
|
class StoreFixtureContextWithHistory(NamedTuple):
|
|
app: TestApp
|
|
sa_session: scoped_session
|
|
user: model.User
|
|
history: model.History
|
|
|
|
|
|
def setup_fixture_context_with_history(
|
|
history_name="Test History for Model Store", **kwd
|
|
) -> StoreFixtureContextWithHistory:
|
|
app, sa_session, user = setup_fixture_context_with_user(**kwd)
|
|
history = model.History(name=history_name, user=user)
|
|
sa_session.add(history)
|
|
sa_session.flush()
|
|
return StoreFixtureContextWithHistory(app, sa_session, user, history)
|
|
|
|
|
|
def perform_import_from_store_dict(
|
|
fixture_context: StoreFixtureContextWithHistory,
|
|
import_dict: Dict[str, Any],
|
|
import_options: Optional[store.ImportOptions] = None,
|
|
) -> None:
|
|
import_options = import_options or store.ImportOptions()
|
|
import_model_store = store.get_import_model_store_for_dict(
|
|
import_dict, app=fixture_context.app, user=fixture_context.user, import_options=import_options
|
|
)
|
|
with import_model_store.target_history(default_history=fixture_context.history):
|
|
import_model_store.perform_import(fixture_context.history)
|
|
|
|
|
|
class Options:
|
|
is_url = False
|
|
is_file = True
|
|
is_b64encoded = False
|
|
|
|
|
|
def import_archive(archive_path, app, user, import_options=None):
|
|
dest_parent = mkdtemp()
|
|
dest_dir = CompressedFile(archive_path).extract(dest_parent)
|
|
|
|
import_options = import_options or store.ImportOptions()
|
|
new_history = None
|
|
model_store = store.get_import_model_store_for_directory(
|
|
dest_dir,
|
|
app=app,
|
|
user=user,
|
|
import_options=import_options,
|
|
)
|
|
with model_store.target_history(default_history=None) as new_history:
|
|
model_store.perform_import(new_history)
|
|
|
|
shutil.rmtree(dest_parent)
|
|
|
|
return new_history
|