Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added views module #113

Merged
merged 9 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pepdbagent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
import coloredlogs
import logmuse

from ._version import __version__
from .pepdbagent import *
from pepdbagent._version import __version__
from pepdbagent.pepdbagent import PEPDatabaseAgent
nleroy917 marked this conversation as resolved.
Show resolved Hide resolved

__all__ = ["__version__", "PEPDatabaseAgent"]


_LOGGER = logmuse.init_logger("pepdbagent")
coloredlogs.install(
Expand Down
42 changes: 42 additions & 0 deletions pepdbagent/db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ class Projects(Base):
stars_mapping: Mapped[List["Stars"]] = relationship(
back_populates="project_mapping", cascade="all, delete-orphan"
)
views_mapping: Mapped[List["Views"]] = relationship(
back_populates="project_mapping", cascade="all, delete-orphan"
)

# Self-referential relationship. The parent project is the one that was forked to create this one.
forked_from_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("projects.id", ondelete="SET NULL"), nullable=True
Expand Down Expand Up @@ -133,6 +137,10 @@ class Samples(Base):
sample_name: Mapped[Optional[str]] = mapped_column()
sample_mapping: Mapped["Projects"] = relationship(back_populates="samples_mapping")

views: Mapped[Optional[List["ViewSampleAssociation"]]] = relationship(
back_populates="sample", cascade="all, delete-orphan"
)


class Subsamples(Base):
"""
Expand Down Expand Up @@ -176,6 +184,40 @@ class Stars(Base):
project_mapping: Mapped["Projects"] = relationship(back_populates="stars_mapping")


class Views(Base):
"""
Views table representation in the database
"""

__tablename__ = "views"

id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column()
description: Mapped[Optional[str]]

project_id = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
project_mapping = relationship("Projects", back_populates="views_mapping")

samples: Mapped[List["ViewSampleAssociation"]] = relationship(
back_populates="view", cascade="all, delete-orphan"
)

_table_args__ = (UniqueConstraint("namespace", "project_id"),)


class ViewSampleAssociation(Base):
"""
Association table between views and samples
"""

__tablename__ = "views_samples"

sample_id = mapped_column(ForeignKey("samples.id", ondelete="CASCADE"), primary_key=True)
view_id = mapped_column(ForeignKey("views.id", ondelete="CASCADE"), primary_key=True)
sample: Mapped["Samples"] = relationship(back_populates="views")
view: Mapped["Views"] = relationship(back_populates="samples")


class BaseEngine:
"""
A class with base methods, that are used in several classes. e.g. fetch_one or fetch_all
Expand Down
14 changes: 14 additions & 0 deletions pepdbagent/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,17 @@ def __init__(self, msg=""):
class SampleNotFoundError(PEPDatabaseAgentError):
def __init__(self, msg=""):
super().__init__(f"""Sample does not exist. {msg}""")


class ViewNotFoundError(PEPDatabaseAgentError):
def __init__(self, msg=""):
super().__init__(f"""View does not exist. {msg}""")


class SampleAlreadyInView(PEPDatabaseAgentError):
"""
Sample is already in the view exception
"""

def __init__(self, msg=""):
super().__init__(f"""Sample is already in the view. {msg}""")
32 changes: 32 additions & 0 deletions pepdbagent/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,35 @@ class ProjectRegistryPath(BaseModel):
namespace: str
name: str
tag: str = DEFAULT_TAG


class ViewAnnotation(BaseModel):
nleroy917 marked this conversation as resolved.
Show resolved Hide resolved
"""
Project views model
"""

name: str
description: Optional[str] = None
number_of_samples: int = 0


class ProjectViews(BaseModel):
"""
View annotation model
"""

namespace: str
name: str
tag: str = DEFAULT_TAG
views: List[ViewAnnotation] = []


class CreateViewDictModel(BaseModel):
"""
View creation dict model
"""

project_namespace: str
project_name: str
project_tag: str
sample_list: List[str]
101 changes: 88 additions & 13 deletions pepdbagent/modules/project.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import datetime
import json
import logging
from typing import Union, List, NoReturn
from typing import Union, List, NoReturn, Mapping

import peppy
from sqlalchemy import and_, delete, select
Expand Down Expand Up @@ -90,11 +90,16 @@ def get(
subsample_list = list(subsample_dict.values())
else:
subsample_list = []

# samples
samples_dict = {
sample_sa.row_number: sample_sa.sample
for sample_sa in found_prj.samples_mapping
}

project_value = {
CONFIG_KEY: found_prj.config,
SAMPLE_RAW_DICT_KEY: [
sample_sa.sample for sample_sa in found_prj.samples_mapping
],
SAMPLE_RAW_DICT_KEY: [samples_dict[key] for key in sorted(samples_dict)],
SUBSAMPLE_RAW_LIST_KEY: subsample_list,
}
# project_value = found_prj.project_value
Expand Down Expand Up @@ -466,16 +471,25 @@ def update(
found_prj.name = found_prj.config[NAME_KEY]

if "samples" in update_dict:
if found_prj.samples_mapping:
for sample in found_prj.samples_mapping:
_LOGGER.debug(f"deleting samples: {str(sample)}")
session.delete(sample)

self._add_samples_to_project(
found_prj,
update_dict["samples"],
sample_table_index=update_dict["config"].get(SAMPLE_TABLE_INDEX_KEY),
self._update_samples(
namespace=namespace,
name=name,
tag=tag,
samples_list=update_dict["samples"],
sample_name_key=update_dict["config"].get(
SAMPLE_TABLE_INDEX_KEY, "sample_name"
),
)
# if found_prj.samples_mapping:
# for sample in found_prj.samples_mapping:
# _LOGGER.debug(f"deleting samples: {str(sample)}")
# session.delete(sample)
#
# self._add_samples_to_project(
# found_prj,
# update_dict["samples"],
# sample_table_index=update_dict["config"].get(SAMPLE_TABLE_INDEX_KEY),
# )

if "subsamples" in update_dict:
if found_prj.subsamples_mapping:
Expand All @@ -496,6 +510,67 @@ def update(
else:
raise ProjectNotFoundError("No items will be updated!")

def _update_samples(
self,
namespace: str,
name: str,
tag: str,
samples_list: List[Mapping],
sample_name_key: str = "sample_name",
) -> None:
"""
Update samples in the project
This is a new method that instead of deleting all samples and adding new ones,
updates samples and adds new ones if they don't exist

:param samples_list: list of samples to be updated
:param sample_name_key: key of the sample name
:return: None
"""
new_sample_names = [sample[sample_name_key] for sample in samples_list]
with Session(self._sa_engine) as session:
project = session.scalar(
select(Projects).where(
and_(
Projects.namespace == namespace, Projects.name == name, Projects.tag == tag
)
)
)
old_sample_names = [sample.sample_name for sample in project.samples_mapping]
for old_sample in old_sample_names:
if old_sample not in new_sample_names:
session.execute(
delete(Samples).where(
and_(
Samples.sample_name == old_sample, Samples.project_id == project.id
)
)
)

order_number = 0
for new_sample in samples_list:
order_number += 1
if new_sample[sample_name_key] not in old_sample_names:
project.samples_mapping.append(
Samples(
sample=new_sample,
sample_name=new_sample[sample_name_key],
row_number=order_number,
)
)
else:
sample_mapping = session.scalar(
select(Samples).where(
and_(
Samples.sample_name == new_sample[sample_name_key],
Samples.project_id == project.id,
)
)
)
sample_mapping.sample = new_sample
sample_mapping.row_number = order_number
session.commit()

@staticmethod
def __create_update_dict(update_values: UpdateItems) -> dict:
"""
Expand Down
Loading
Loading