Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/add-delete-samplesheet-command' …
Browse files Browse the repository at this point in the history
…into add-delete-samplesheet-command
  • Loading branch information
Vince-janv committed Oct 23, 2023
2 parents 1ded4a6 + 8ec820a commit 2c3c6aa
Show file tree
Hide file tree
Showing 22 changed files with 387 additions and 98 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 4.8.21
current_version = 4.10.5
commit = True
tag = True
tag_name = {new_version}
Expand Down
2 changes: 1 addition & 1 deletion housekeeper/__init__.py
@@ -1,2 +1,2 @@
__title__ = "housekeeper"
__version__ = "4.8.21"
__version__ = "4.10.5"
4 changes: 3 additions & 1 deletion housekeeper/cli/core.py
Expand Up @@ -8,6 +8,7 @@
import yaml
from housekeeper.constants import ROOT
from housekeeper.store import Store
from housekeeper.store.database import initialize_database

from . import add, delete, get, include, init

Expand Down Expand Up @@ -44,7 +45,8 @@ def base(
raise click.Abort
context.obj["database"] = db_path
LOG.info("Use root path %s", root_path)
context.obj["store"] = Store(db_path, root_path)
initialize_database(db_path)
context.obj["store"] = Store(root=root_path)


base.add_command(init.init)
Expand Down
49 changes: 36 additions & 13 deletions housekeeper/cli/delete.py
Expand Up @@ -3,14 +3,15 @@
import logging
import shutil
from pathlib import Path
from typing import Optional

import click
import sqlalchemy
from sqlalchemy.exc import MultipleResultsFound

from housekeeper.date import get_date
from housekeeper.store.api.core import Store
from housekeeper.store.models import File
from housekeeper.store.models import File, Tag

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -42,7 +43,7 @@ def bundle_cmd(context, yes, bundle_name):

store.session.delete(bundle)
store.session.commit()
LOG.info("Bundle deleted: %s", bundle.name)
LOG.info(f"Deleted bundle {bundle.name}")


@delete.command("version")
Expand All @@ -51,7 +52,7 @@ def bundle_cmd(context, yes, bundle_name):
@click.option("-y", "--yes", is_flag=True, help="skip checks")
@click.pass_context
def version_cmd(context, bundle_name, version_id, yes):
"""Delete a version from database"""
"""Delete a version from database."""
store: Store = context.obj["store"]
if not (bundle_name or version_id):
LOG.info("Please select a bundle or a version")
Expand All @@ -60,28 +61,28 @@ def version_cmd(context, bundle_name, version_id, yes):
if bundle_name:
bundle = store.get_bundle_by_name(bundle_name=bundle_name)
if not bundle:
LOG.info("Could not find bundle %s", bundle_name)
LOG.info(f"Could not find bundle {bundle_name}")
return
if len(bundle.versions) == 0:
LOG.warning("Could not find versions for bundle %s", bundle_name)
LOG.warning(f"Could not find versions for bundle {bundle_name}")
return
LOG.info("Deleting the latest version of bundle %s", bundle_name)
LOG.info(f"Deleting the latest version of bundle {bundle_name}")
version_obj = bundle.versions[0]

if version_id:
version = store.get_version_by_id(version_id=version_id)
if not version:
LOG.warning("Could not find version %s", version_id)
LOG.warning(f"Could not find version {version_id}")
raise click.Abort
bundle = store.get_bundle_by_id(bundle_id=version.bundle_id)
for ver in bundle.versions:
if ver.id == version_id:
version_obj = ver

if version_obj.included_at:
question = f"remove bundle version from file system and database: {version_obj.full_path}"
question = f"Remove bundle version {version_obj.full_path} from file system and database?"
else:
question = f"remove bundle version from database: {version_obj.created_at.date()}"
question = f"Remove bundle version {version_obj.created_at.date()} from database?"

if not (yes or click.confirm(question)):
raise click.Abort
Expand All @@ -91,7 +92,7 @@ def version_cmd(context, bundle_name, version_id, yes):

store.session.delete(version_obj)
store.session.commit()
LOG.info("version deleted: %s", version_obj.full_path)
LOG.info(f"version deleted: {version_obj.full_path}")


@delete.command("files")
Expand Down Expand Up @@ -132,10 +133,32 @@ def files_cmd(context, yes, tag, bundle_name, before, notondisk, list_files, lis
raise click.Abort

for file in files:
if yes or click.confirm(f"remove file from disk and database: {file.full_path}"):
if yes or click.confirm(f"Remove file from disk and database: {file.full_path}?"):
delete_file(file=file, store=store)


@delete.command("tag")
@click.option("-n", "--name", help="Name of the tag to delete")
@click.option("-y", "--yes", is_flag=True, help="skip checks")
@click.pass_context
def tag_cmd(context, yes, name: str):
"""Delete a tag from the database.
Entries in the file_tag_link table associated with the tag will be removed."""
store: Store = context.obj["store"]
tag: Tag | None = store.get_tag(tag_name=name)
if not tag:
LOG.warning(f"Tag {name} not found")
raise click.Abort

tag_file_count: int = len(tag.files) if tag.files else 0
question = f"Delete tag {name} with {tag_file_count} associated files?"

if yes or click.confirm(question):
store.session.delete(tag)
store.session.commit()
LOG.info(f"Tag {name} deleted")


def validate_delete_options(tag: str, bundle_name: str):
"""Validate delete options."""
if not (tag or bundle_name):
Expand Down Expand Up @@ -202,9 +225,9 @@ def file_cmd(context, yes, file_id):
raise click.Abort

if file.is_included:
question = f"remove file from file system and database: {file.full_path}"
question = f"Remove file {file.full_path} from file system and database?"
else:
question = f"remove file from database: {file.full_path}"
question = f"Remove file {file.full_path} from database?"

if yes or click.confirm(question):
if file.is_included and Path(file.full_path).exists():
Expand Down
15 changes: 7 additions & 8 deletions housekeeper/cli/init.py
@@ -1,8 +1,9 @@
"""Initialise HK db from CLI"""
import logging
from typing import List
import click
from housekeeper.store.api.core import Store
from sqlalchemy import inspect

from housekeeper.store.database import create_all_tables, drop_all_tables, get_tables


LOG = logging.getLogger(__name__)
Expand All @@ -14,18 +15,16 @@
@click.pass_context
def init(context, reset, force):
"""Setup the database."""
store: Store = context.obj["store"]
inspector = inspect(store.engine)
existing_tables = inspector.get_table_names()
existing_tables: List[str] = get_tables()

if force or reset:
if existing_tables and not force:
message = f"Delete existing tables? [{', '.join(existing_tables)}]"
click.confirm(click.style(message, fg="yellow"), abort=True)
store.drop_all()
drop_all_tables()
elif existing_tables:
LOG.error("Database already exists, use '--reset'")
context.abort()

store.create_all()
LOG.info(f"Success! New tables: {', '.join(inspector.get_table_names())}")
create_all_tables()
LOG.info(f"Success! New tables: {', '.join(get_tables())}")
26 changes: 7 additions & 19 deletions housekeeper/store/api/core.py
Expand Up @@ -4,13 +4,11 @@
import logging
from pathlib import Path

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session

from housekeeper.store.database import get_session
from housekeeper.store.api.handlers.update import UpdateHandler
from housekeeper.store.models import File, Model, Version
from housekeeper.store.api.handlers.create import CreateHandler
from housekeeper.store.api.handlers.read import ReadHandler
from housekeeper.store.models import File, Version

LOG = logging.getLogger(__name__)

Expand All @@ -19,9 +17,9 @@ class CoreHandler(CreateHandler, ReadHandler, UpdateHandler):
"""Aggregating class for the store api handlers"""

def __init__(self, session):
ReadHandler(session=session)
CreateHandler(session=session)
UpdateHandler(session=session)
ReadHandler(session)
CreateHandler(session)
UpdateHandler(session)


class Store(CoreHandler):
Expand All @@ -34,21 +32,11 @@ class Store(CoreHandler):
uri (str): SQLAlchemy database connection str
"""

def __init__(self, uri: str, root: str):
self.engine = create_engine(uri)
session_factory = sessionmaker(bind=self.engine)
self.session = scoped_session(session_factory)
def __init__(self, root: str):
self.session = get_session()

LOG.debug("Initializing Store")
File.app_root = Path(root)
Version.app_root = Path(root)

super().__init__(self.session)

def create_all(self):
"""Create all tables in the database."""
Model.metadata.create_all(bind=self.session.get_bind())

def drop_all(self):
"""Drop all tables in the database."""
Model.metadata.drop_all(bind=self.session.get_bind())
2 changes: 1 addition & 1 deletion housekeeper/store/api/handlers/create.py
Expand Up @@ -31,7 +31,7 @@ def new_bundle(self, name: str, created_at: dt.datetime = None) -> Bundle:
LOG.debug("Created new bundle: %s", new_bundle.name)
return new_bundle

def add_bundle(self, data: dict) -> Tuple[Bundle, Version]:
def add_bundle(self, data: dict) -> Tuple[Bundle, Version] | None:
"""Build a new bundle version of files.
The format of the input dict is defined in the `schema` module.
Expand Down
67 changes: 42 additions & 25 deletions housekeeper/store/api/handlers/read.py
Expand Up @@ -9,21 +9,15 @@
from sqlalchemy.orm import Query, Session

from housekeeper.store.api.handlers.base import BaseHandler
from housekeeper.store.filters.archive_filters import (
ArchiveFilter,
apply_archive_filter,
)
from housekeeper.store.filters.archive_filters import ArchiveFilter, apply_archive_filter
from housekeeper.store.filters.bundle_filters import BundleFilters, apply_bundle_filter
from housekeeper.store.filters.file_filters import FileFilter, apply_file_filter
from housekeeper.store.filters.tag_filters import TagFilter, apply_tag_filter
from housekeeper.store.filters.version_bundle_filters import (
VersionBundleFilters,
apply_version_bundle_filter,
)
from housekeeper.store.filters.version_filters import (
VersionFilter,
apply_version_filter,
)
from housekeeper.store.filters.version_filters import VersionFilter, apply_version_filter
from housekeeper.store.models import Archive, Bundle, File, Tag, Version

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -91,7 +85,7 @@ def get_tags(self) -> Query:
LOG.debug("Fetching all tags")
return self._get_query(table=Tag)

def get_file_by_id(self, file_id: int):
def get_file_by_id(self, file_id: int) -> File | None:
"""Get a file by record id."""
return apply_file_filter(
files=self._get_query(table=File),
Expand Down Expand Up @@ -212,27 +206,21 @@ def get_non_archived_files(self, bundle_name: str, tags: Optional[list]) -> list
tag_names=tags,
).all()

def get_ongoing_archiving_tasks(self) -> Set[int]:
def get_ongoing_archivals(self) -> list[Archive]:
"""Returns all archiving tasks in the archive table, for entries where the archiving
field is empty."""
return {
archive.archiving_task_id
for archive in apply_archive_filter(
archives=self._get_query(table=Archive),
filter_functions=[ArchiveFilter.FILTER_ARCHIVING_ONGOING],
).all()
}
return apply_archive_filter(
archives=self._get_query(table=Archive),
filter_functions=[ArchiveFilter.FILTER_ARCHIVING_ONGOING],
).all()

def get_ongoing_retrieval_tasks(self) -> Set[int]:
def get_ongoing_retrievals(self) -> list[Archive]:
"""Returns all retrieval tasks in the archive table, for entries where the retrieved_at
field is empty."""
return {
archive.retrieval_task_id
for archive in apply_archive_filter(
archives=self._get_query(table=Archive),
filter_functions=[ArchiveFilter.FILTER_RETRIEVAL_ONGOING],
).all()
}
return apply_archive_filter(
archives=self._get_query(table=Archive),
filter_functions=[ArchiveFilter.FILTER_RETRIEVAL_ONGOING],
).all()

def get_bundle_name_from_file_path(self, file_path: str) -> str:
"""Return the bundle name for the specified file."""
Expand All @@ -249,3 +237,32 @@ def get_all_non_archived_files(self, tag_names: list[str]) -> list[File]:
tag_names=tag_names,
is_archived=False,
).all()

def get_archives(
self, archival_task_id: int = None, retrieval_task_id: int = None
) -> Optional[list[File]]:
"""Returns all entries in the archive table with the specified archival/retrieval task id."""
if not archival_task_id and not retrieval_task_id:
return self._get_query(table=Archive).all()
if archival_task_id and retrieval_task_id:
return apply_archive_filter(
archives=apply_archive_filter(
archives=self._get_query(table=Archive),
filter_functions=[ArchiveFilter.FILTER_BY_ARCHIVING_TASK_ID],
task_id=archival_task_id,
),
filter_functions=[ArchiveFilter.FILTER_BY_RETRIEVAL_TASK_ID],
task_id=retrieval_task_id,
).all()
if archival_task_id:
return apply_archive_filter(
archives=self._get_query(table=Archive),
filter_functions=[ArchiveFilter.FILTER_BY_ARCHIVING_TASK_ID],
task_id=archival_task_id,
).all()

return apply_archive_filter(
archives=self._get_query(table=Archive),
filter_functions=[ArchiveFilter.FILTER_BY_RETRIEVAL_TASK_ID],
task_id=retrieval_task_id,
).all()

0 comments on commit 2c3c6aa

Please sign in to comment.