Commit 5d2b22ac authored by Cage, Gregory's avatar Cage, Gregory
Browse files

Add connection class and address other issues

parent 3224cfb9
Loading
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .nova import Nova  # Only imports for type checking
    from .nova import NovaConnection  # Only imports for type checking


class Datastore:
@@ -12,7 +12,11 @@ class Datastore:
    The constructor is not intended for external use. Use nova.galaxy.Nova.create_data_store() instead.
    """

    def __init__(self, name: str, nova_instance: "Nova", history_id: str):
    def __init__(self, name: str, nova_connection: "NovaConnection", history_id: str) -> None:
        self.name = name
        self.nova = nova_instance
        self.nova_connection = nova_connection
        self.history_id = history_id
        self.persist_store = False

    def persist(self) -> None:
        self.persist_store = True
+31 −7
Original line number Diff line number Diff line
@@ -53,7 +53,11 @@ class AbstractData(ABC):
        raise NotImplementedError()

    @abstractmethod
    def download(self, local_path: str) -> None:
    def download(self, local_path: str) -> Any:
        raise NotImplementedError()

    @abstractmethod
    def get_content(self) -> Any:
        raise NotImplementedError()

    def cancel_upload(self) -> None:
@@ -70,7 +74,7 @@ class Dataset(AbstractData):
        self.store: Datastore

    def upload(self, store: Datastore) -> None:
        galaxy_instance = store.nova.galaxy_instance
        galaxy_instance = store.nova_connection.galaxy_instance
        dataset_client = DatasetClient(galaxy_instance)
        history_id = galaxy_instance.histories.get_histories(name=store.name)[0]["id"]
        dataset_id = galaxy_instance.tools.upload_file(path=self.path, history_id=history_id)
@@ -78,11 +82,19 @@ class Dataset(AbstractData):
        self.store = store
        dataset_client.wait_for_dataset(self.id)

    def download(self, local_path: str) -> None:
    def download(self, local_path: str) -> AbstractData:
        """Downloads this dataset to the local path given."""
        if self.store and self.id:
            dataset_client = DatasetClient(self.store.nova.galaxy_instance)
            dataset_client = DatasetClient(self.store.nova_connection.galaxy_instance)
            dataset_client.download_dataset(self.id, use_default_filename=False, file_path=local_path)
            return self
        else:
            raise Exception("Dataset is not present in Galaxy.")

    def get_content(self) -> Any:
        if self.store and self.id:
            dataset_client = DatasetClient(self.store.nova_connection.galaxy_instance)
            return dataset_client.download_dataset(self.id, use_default_filename=False, file_path=None).decode("utf-8")
        else:
            raise Exception("Dataset is not present in Galaxy.")

@@ -100,18 +112,30 @@ class DatasetCollection(AbstractData):
        """Will need to handle this differently than single datasets."""
        raise NotImplementedError

    def download(self, local_path: str) -> None:
    def download(self, local_path: str) -> AbstractData:
        """Downloads this dataset collection to the local path given."""
        if self.store and self.id:
            dataset_client = DatasetCollectionClient(self.store.nova.galaxy_instance)
            dataset_client = DatasetCollectionClient(self.store.nova_connection.galaxy_instance)
            dataset_client.download_dataset_collection(self.id, file_path=local_path)
            return self
        else:
            raise Exception("Dataset collection is not present in Galaxy.")

    def get_content(self) -> Any:
        if self.store and self.id:
            dataset_client = DatasetCollectionClient(self.store.nova_connection.galaxy_instance)
            info = dataset_client.show_dataset_collection(self.id)
            output = ""
            for element in info["elements"]:
                output += f"{element['element_identifier']}\n"
            return output
        else:
            raise Exception("Dataset collection is not present in Galaxy.")


def upload_datasets(store: Datastore, datasets: Dict[str, AbstractData]) -> Dict[str, str]:
    """Helper method to upload multiple datasets or collections in parallel."""
    galaxy_instance = store.nova.galaxy_instance
    galaxy_instance = store.nova_connection.galaxy_instance
    dataset_client = DatasetClient(galaxy_instance)
    history_id = galaxy_instance.histories.get_histories(name=store.name)[0]["id"]
    dataset_ids = {}
+39 −15
Original line number Diff line number Diff line
"""The NOVA class is responsible for managing interactions with a Galaxy server instance."""

from typing import Optional
from contextlib import contextmanager
from typing import Generator, List, Optional

from bioblend import galaxy

@@ -20,6 +21,35 @@ class GalaxyConnectionError(Exception):
        super().__init__(self.message)


class NovaConnection:
    """Manages datastore for current connection.

    Should not be instantiated manually. Use Nova.connect() instead. Any stores created using the connection will
    be automatically purged after connection is closed, unless Datastore.persist() is called for that store.
    """

    def __init__(self, galaxy_instance: galaxy.GalaxyInstance):
        self.galaxy_instance = galaxy_instance
        self.datastores: List[Datastore] = []

    def create_data_store(self, name: str) -> Datastore:
        """Creates a datastore with the given name."""
        histories = self.galaxy_instance.histories.get_histories(name=name)
        if len(histories) > 0:
            store = Datastore(name, self, histories[0]["id"])
            self.datastores.append(store)
            return store
        history_id = self.galaxy_instance.histories.create_history(name=name)["id"]
        store = Datastore(name, self, history_id)
        self.datastores.append(store)
        return store

    def remove_data_store(self, store: Datastore) -> None:
        """Permanently deletes the data store with the given name."""
        history = self.galaxy_instance.histories.get_histories(name=store.name)[0]["id"]
        self.galaxy_instance.histories.delete_history(history_id=history, purge=True)


class Nova:
    """
    Class to manage a Galaxy connection.
@@ -46,7 +76,8 @@ class Nova:
        self.galaxy_api_key = galaxy_key
        self.galaxy_instance: galaxy.GalaxyInstance

    def connect(self) -> None:
    @contextmanager
    def connect(self) -> Generator:
        """
        Connects to the Galaxy instance using the provided URL and API key.

@@ -62,16 +93,9 @@ class Nova:
            raise ValueError("Galaxy URL must be a string")
        self.galaxy_instance = galaxy.GalaxyInstance(url=self.galaxy_url, key=self.galaxy_api_key)
        self.galaxy_instance.config.get_version()

    def create_data_store(self, name: str) -> Datastore:
        """Creates a datastore with the given name."""
        histories = self.galaxy_instance.histories.get_histories(name=name)
        if len(histories) > 0:
            return Datastore(name, self, histories[0]["id"])
        history_id = self.galaxy_instance.histories.create_history(name=name)["id"]
        return Datastore(name, self, history_id)

    def remove_data_store(self, name: str) -> None:
        """Permanently deletes the data store with the given name."""
        history = self.galaxy_instance.histories.get_histories(name=name)[0]["id"]
        self.galaxy_instance.histories.delete_history(history_id=history, purge=True)
        conn = NovaConnection(self.galaxy_instance)
        yield conn
        # Remove all data stores after execution
        for store in conn.datastores:
            if not store.persist_store:
                conn.remove_data_store(store)
+8 −2
Original line number Diff line number Diff line
@@ -28,7 +28,13 @@ class Outputs:
        self.data.append(data)

    def get_dataset(self, name: str) -> AbstractData:
        try:
            return next(filter(lambda x: isinstance(x, Dataset) and x.name == name, self.data))
        except StopIteration as e:
            raise Exception(f"There is no dataset: {name}") from e

    def get_collection(self, name: str) -> AbstractData:
        try:
            return next(filter(lambda x: isinstance(x, DatasetCollection) and x.name == name, self.data))
        except StopIteration as e:
            raise Exception(f"There is no dataset collection: {name}") from e
+1 −1
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ class Tool(AbstractWork):
    def run(self, data_store: Datastore, params: Parameters) -> Outputs:
        """Runs this tool in a blocking manner and returns a map of the output datasets and collections."""
        outputs = Outputs()
        galaxy_instance = data_store.nova.galaxy_instance
        galaxy_instance = data_store.nova_connection.galaxy_instance
        datasets_to_upload = {}

        # Set Tool Inputs