Skip to content

Commit

Permalink
Merge pull request #113 from pepkit/veiw
Browse files Browse the repository at this point in the history
Added views module
  • Loading branch information
nleroy917 authored Jan 12, 2024
2 parents 6d9fd85 + 50110fc commit 407dc32
Show file tree
Hide file tree
Showing 9 changed files with 776 additions and 18 deletions.
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

__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):
"""
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

0 comments on commit 407dc32

Please sign in to comment.