diff --git a/backend/app/.coveragerc b/backend/app/.coveragerc index f0e3d4f4de5bf2c00c1c7161b3d58a818a52b5b0..26b1eea7ddc1eb3d1d2fcb343ca048f66d7b8da4 100644 --- a/backend/app/.coveragerc +++ b/backend/app/.coveragerc @@ -12,3 +12,7 @@ omit = app/dependency_injections.py # omit start of app app/main.py +[report] +exclude_lines = + pragma: no cover + raise NotImplementedError diff --git a/backend/app/app/api/endpoints/catalogs.py b/backend/app/app/api/endpoints/catalogs.py index c3bfe9812e709e5a22571562eb4b4f3c25855fe1..089e73bc01af5fc0b4224e12eca025a45fea1ed4 100644 --- a/backend/app/app/api/endpoints/catalogs.py +++ b/backend/app/app/api/endpoints/catalogs.py @@ -45,9 +45,9 @@ async def create_catalog( return catalog -@router.put("/", response_model=CatalogUpdate) +@router.put("/", response_model=CatalogRead) async def update_catalog( - catalog: CatalogCreate, session: Session = Depends(get_session) + catalog: CatalogUpdate, session: Session = Depends(get_session) ): use_case = CrudCatalogUseCase() try: diff --git a/backend/app/app/api/endpoints/eggnogs.py b/backend/app/app/api/endpoints/eggnogs.py new file mode 100644 index 0000000000000000000000000000000000000000..0b196234fb050f611e9e328998608e1ea696a2e3 --- /dev/null +++ b/backend/app/app/api/endpoints/eggnogs.py @@ -0,0 +1,66 @@ +from typing import List + +from fastapi import Depends, APIRouter, HTTPException +from sqlalchemy.exc import NoResultFound, IntegrityError +from sqlmodel import Session + +from app.db import get_session +from app.core.schemas.entities.eggnog import EggnogRead, EggnogUpdate, EggnogCreate +from app.core.use_cases.crud.eggnog import CrudEggnogUseCase + + +router = APIRouter() + + +@router.get("/", response_model=List[EggnogRead]) +async def get_eggnogs(session: Session = Depends(get_session)): + use_case = CrudEggnogUseCase() + eggnogs = use_case.get_all(session=session) + return eggnogs + + +@router.get("/{eggnog_id}", response_model=EggnogRead) +async def get_eggnog(eggnog_id: str, session: Session = Depends(get_session)): + use_case = CrudEggnogUseCase() + try: + eggnog = use_case.get_eggnog(eggnog_id, session=session) + except NoResultFound: + raise HTTPException(status_code=404, detail=f"Eggnog [{eggnog_id}] not found.") + return eggnog + + +@router.post("/", response_model=EggnogRead) +async def create_eggnog(eggnog: EggnogCreate, session: Session = Depends(get_session)): + use_case = CrudEggnogUseCase() + eggnog = use_case.create_eggnog(eggnog, session=session) + return eggnog + + +@router.put("/", response_model=EggnogRead) +async def update_eggnog( + eggnog: EggnogUpdate, + exclude_none: bool = True, + session: Session = Depends(get_session), +): + use_case = CrudEggnogUseCase() + try: + eggnog = use_case.update_eggnog( + eggnog, exclude_none=exclude_none, session=session + ) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"Eggnog [{eggnog.eggnog_id}] not found." + ) + except IntegrityError: + raise HTTPException(status_code=422, detail="Missing data in input data.") + return eggnog + + +@router.delete("/{eggnog_id}") +async def delete_eggnog(eggnog_id: str, session: Session = Depends(get_session)): + use_case = CrudEggnogUseCase() + try: + use_case.delete_eggnog(eggnog_id, session=session) + except NoResultFound: + raise HTTPException(status_code=404, detail=f"Eggnog [{eggnog_id}] not found.") + return {"deleted": True} diff --git a/backend/app/app/api/endpoints/experiments.py b/backend/app/app/api/endpoints/experiments.py index b64d745b66ef83df15bbf351c86f2ed42fbb4c67..36e363097e308977d96bc997d26b2373af375ff3 100644 --- a/backend/app/app/api/endpoints/experiments.py +++ b/backend/app/app/api/endpoints/experiments.py @@ -1,11 +1,16 @@ from typing import List -from fastapi import Depends, APIRouter -from sqlmodel import Session, select +from fastapi import Depends, APIRouter, HTTPException +from sqlalchemy.exc import NoResultFound, IntegrityError +from sqlmodel import Session from app.db import get_session -from app.core.models.experiment import Experiment -from app.core.schemas.entities.experiment import ExperimentRead +from app.core.schemas.entities.experiment import ( + ExperimentRead, + ExperimentUpdate, + ExperimentCreate, +) +from app.core.use_cases.crud.experiment import CrudExperimentUseCase router = APIRouter() @@ -13,5 +18,61 @@ router = APIRouter() @router.get("/", response_model=List[ExperimentRead]) async def get_experiments(session: Session = Depends(get_session)): - experiments = session.exec(select(Experiment)).all() + use_case = CrudExperimentUseCase() + experiments = use_case.get_all(session=session) return experiments + + +@router.get("/{experiment_id}", response_model=ExperimentRead) +async def get_experiment(experiment_id: int, session: Session = Depends(get_session)): + use_case = CrudExperimentUseCase() + try: + experiment = use_case.get_experiment(experiment_id, session=session) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"Experiment [{experiment_id}] not found." + ) + return experiment + + +@router.post("/", response_model=ExperimentRead) +async def create_experiment( + experiment: ExperimentCreate, session: Session = Depends(get_session) +): + use_case = CrudExperimentUseCase() + experiment = use_case.create_experiment(experiment, session=session) + return experiment + + +@router.put("/", response_model=ExperimentRead) +async def update_experiment( + experiment: ExperimentUpdate, + exclude_none: bool = True, + session: Session = Depends(get_session), +): + use_case = CrudExperimentUseCase() + try: + experiment = use_case.update_experiment( + experiment, exclude_none=exclude_none, session=session + ) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"Experiment [{experiment.id}] not found." + ) + except IntegrityError: + raise HTTPException(status_code=422, detail="Missing data in input data.") + return experiment + + +@router.delete("/{experiment_id}") +async def delete_experiment( + experiment_id: int, session: Session = Depends(get_session) +): + use_case = CrudExperimentUseCase() + try: + use_case.delete_experiment(experiment_id, session=session) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"Experiment [{experiment_id}] not found." + ) + return {"deleted": True} diff --git a/backend/app/app/api/endpoints/keggs.py b/backend/app/app/api/endpoints/keggs.py new file mode 100644 index 0000000000000000000000000000000000000000..c60fc2a4226f6d5cd59e534dfd1d10f5963fcae9 --- /dev/null +++ b/backend/app/app/api/endpoints/keggs.py @@ -0,0 +1,79 @@ +from typing import List + +from fastapi import Depends, APIRouter, HTTPException +from sqlalchemy.exc import NoResultFound, IntegrityError +from sqlmodel import Session + +from app.db import get_session +from app.core.schemas.entities.kegg import ( + KeggOrthologyRead, + KeggOrthologyUpdate, + KeggOrthologyCreate, +) +from app.core.use_cases.crud.kegg import CrudKeggOrthologyUseCase + + +router = APIRouter() + + +@router.get("/", response_model=List[KeggOrthologyRead]) +async def get_kegg_orthology_entries(session: Session = Depends(get_session)): + use_case = CrudKeggOrthologyUseCase() + keggs = use_case.get_all(session=session) + return keggs + + +@router.get("/{kegg_id}", response_model=KeggOrthologyRead) +async def get_kegg_orthology(kegg_id: str, session: Session = Depends(get_session)): + use_case = CrudKeggOrthologyUseCase() + try: + kegg = use_case.get_kegg(kegg_id, session=session) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"KeggOrthology [{kegg_id}] not found." + ) + return kegg + + +@router.post("/", response_model=KeggOrthologyRead) +async def create_kegg_orthology( + kegg: KeggOrthologyCreate, session: Session = Depends(get_session) +): + use_case = CrudKeggOrthologyUseCase() + try: + kegg = use_case.create_kegg(kegg, session=session) + except IntegrityError: + raise HTTPException( + status_code=403, detail=f"KeggOrthology [{kegg.name}] already exists." + ) + return kegg + + +@router.put("/", response_model=KeggOrthologyRead) +async def update_kegg_orthology( + kegg: KeggOrthologyUpdate, + exclude_none: bool = True, + session: Session = Depends(get_session), +): + use_case = CrudKeggOrthologyUseCase() + try: + kegg = use_case.update_kegg(kegg, session=session, exclude_none=exclude_none) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"KeggOrthology [{kegg.kegg_id}] not found." + ) + except IntegrityError: + raise HTTPException(status_code=422, detail="Missing data in input data.") + return kegg + + +@router.delete("/{kegg_id}") +async def delete_kegg_orthology(kegg_id: str, session: Session = Depends(get_session)): + use_case = CrudKeggOrthologyUseCase() + try: + use_case.delete_kegg(kegg_id, session=session) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"KeggOrthology [{kegg_id}] not found." + ) + return {"deleted": True} diff --git a/backend/app/app/api/endpoints/ncbi_taxonomy.py b/backend/app/app/api/endpoints/ncbi_taxonomy.py new file mode 100644 index 0000000000000000000000000000000000000000..a8c2c6481888ad30036dbfd41d084631156d7fc6 --- /dev/null +++ b/backend/app/app/api/endpoints/ncbi_taxonomy.py @@ -0,0 +1,82 @@ +from typing import List + +from fastapi import Depends, APIRouter, HTTPException +from sqlalchemy.exc import NoResultFound, IntegrityError +from sqlmodel import Session + +from app.db import get_session +from app.core.schemas.entities.ncbi_taxonomy import ( + NcbiTaxonomyRead, + NcbiTaxonomyUpdate, + NcbiTaxonomyCreate, +) +from app.core.use_cases.crud.ncbi_taxonomy import CrudNcbiTaxonomyUseCase + + +router = APIRouter() + + +@router.get("/", response_model=List[NcbiTaxonomyRead]) +async def get_ncbi_taxonomy_entries(session: Session = Depends(get_session)): + use_case = CrudNcbiTaxonomyUseCase() + ncbi_taxonomys = use_case.get_all(session=session) + return ncbi_taxonomys + + +@router.get("/{tax_id}", response_model=NcbiTaxonomyRead) +async def get_ncbi_taxonomy(tax_id: str, session: Session = Depends(get_session)): + use_case = CrudNcbiTaxonomyUseCase() + try: + ncbi_taxonomy = use_case.get_ncbi_taxonomy(tax_id, session=session) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"NcbiTaxonomy [{tax_id}] not found." + ) + return ncbi_taxonomy + + +@router.post("/", response_model=NcbiTaxonomyRead) +async def create_ncbi_taxonomy( + ncbi_taxonomy: NcbiTaxonomyCreate, session: Session = Depends(get_session) +): + use_case = CrudNcbiTaxonomyUseCase() + try: + ncbi_taxonomy = use_case.create_ncbi_taxonomy(ncbi_taxonomy, session=session) + except IntegrityError: + raise HTTPException( + status_code=403, + detail=f"NcbiTaxonomy [{ncbi_taxonomy.name}] already exists.", + ) + return ncbi_taxonomy + + +@router.put("/", response_model=NcbiTaxonomyRead) +async def update_ncbi_taxonomy( + ncbi_taxonomy: NcbiTaxonomyUpdate, + exclude_none: bool = True, + session: Session = Depends(get_session), +): + use_case = CrudNcbiTaxonomyUseCase() + try: + ncbi_taxonomy = use_case.update_ncbi_taxonomy( + ncbi_taxonomy, session=session, exclude_none=exclude_none + ) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"NcbiTaxonomy [{ncbi_taxonomy.tax_id}] not found." + ) + except IntegrityError: + raise HTTPException(status_code=422, detail="Missing data in input data.") + return ncbi_taxonomy + + +@router.delete("/{tax_id}") +async def delete_ncbi_taxonomy(tax_id: str, session: Session = Depends(get_session)): + use_case = CrudNcbiTaxonomyUseCase() + try: + use_case.delete_ncbi_taxonomy(tax_id, session=session) + except NoResultFound: + raise HTTPException( + status_code=404, detail=f"NcbiTaxonomy [{tax_id}] not found." + ) + return {"deleted": True} diff --git a/backend/app/app/api/router.py b/backend/app/app/api/router.py index 3dfd0e244c3fa849c104c940737d2b013fbaee93..ea56edd711309ccc40d67e3d34afed858f5168bf 100644 --- a/backend/app/app/api/router.py +++ b/backend/app/app/api/router.py @@ -1,6 +1,13 @@ from fastapi import APIRouter -from app.api.endpoints import catalogs, experiments, genes +from app.api.endpoints import ( + catalogs, + experiments, + genes, + keggs, + ncbi_taxonomy, + eggnogs, +) api_router = APIRouter() api_router.include_router(genes.router, prefix="/genes", tags=["Genes"]) @@ -8,3 +15,10 @@ api_router.include_router(catalogs.router, prefix="/catalogs", tags=["Catalogs"] api_router.include_router( experiments.router, prefix="/experiments", tags=["Experiments"] ) +api_router.include_router( + keggs.router, prefix="/kegg-orthology", tags=["KeggOrthology"] +) +api_router.include_router(eggnogs.router, prefix="/eggnog", tags=["EggNOG"]) +api_router.include_router( + ncbi_taxonomy.router, prefix="/ncbi_taxonomy", tags=["NCBI Taxonomy"] +) diff --git a/backend/app/app/cli/create_test_db.py b/backend/app/app/cli/create_test_db.py index bc4025cd30feedd265a22a6bf4115e38dde4d3be..5c2e4ebfcc39a2e69415cb6bec19ceb6d61c5aa2 100644 --- a/backend/app/app/cli/create_test_db.py +++ b/backend/app/app/cli/create_test_db.py @@ -19,11 +19,11 @@ def create_db(): catalog_virgo = models.Catalog(name="Virgo") catalog_igc = models.Catalog(name="IGC") # Experiments - experiment_blast = models.Experiment(name="exp A") - experiment_kraken = models.Experiment(name="exp B") + experiment_blast = models.Experiment(name="exp A", date="2020-10-10") + experiment_kraken = models.Experiment(name="exp B", date="2020-10-15") # Annotation entities - kegg_a = models.Kegg(kegg_id="K00001", name="First KO") - kegg_b = models.Kegg(kegg_id="K00002", name="Second KO") + kegg_a = models.KeggOrthology(kegg_id="K00001", name="First KO") + kegg_b = models.KeggOrthology(kegg_id="K00002", name="Second KO") ncbi_tax_ecoli = models.NcbiTaxonomy( tax_id=562, name="Escherichia coli", rank="species" ) @@ -31,8 +31,12 @@ def create_db(): tax_id=1578, name="Lactobacillus", rank="genus" ) # Annotations - kegg_annot_a = models.KeggAnnotation(kegg=kegg_a, experiment=experiment_blast) - kegg_annot_b = models.KeggAnnotation(kegg=kegg_b, experiment=experiment_blast) + kegg_annot_a = models.KeggOrthologyAnnotation( + kegg=kegg_a, experiment=experiment_blast + ) + kegg_annot_b = models.KeggOrthologyAnnotation( + kegg=kegg_b, experiment=experiment_blast + ) ncbi_tax_annot_ecoli = models.NcbiTaxonomyAnnotation( ncbi_taxonomy=ncbi_tax_ecoli, experiment=experiment_kraken ) diff --git a/backend/app/app/cli/empty_db.py b/backend/app/app/cli/empty_db.py index 84b6d8335bdf766272d6fc184bd0dd5caa905a7a..8f425f6b1289472f77858827eeb6ccca36f1c34f 100644 --- a/backend/app/app/cli/empty_db.py +++ b/backend/app/app/cli/empty_db.py @@ -18,10 +18,10 @@ def empty_all_tables(): with Session(engine) as session: for table in [ models.NcbiTaxonomyAnnotation, - models.KeggAnnotation, + models.KeggOrthologyAnnotation, models.EggnogAnnotation, models.Experiment, - models.Kegg, + models.KeggOrthology, models.Eggnog, models.NcbiTaxonomy, models.Catalog, diff --git a/backend/app/app/core/models/__init__.py b/backend/app/app/core/models/__init__.py index 53ce8103996b5628397eb90e15934f0835f26abb..c6131a8ed4dd385259c467733cb8a25beadfe022 100644 --- a/backend/app/app/core/models/__init__.py +++ b/backend/app/app/core/models/__init__.py @@ -1,5 +1,5 @@ from .annotations import ( # noqa - KeggAnnotation, + KeggOrthologyAnnotation, EggnogAnnotation, NcbiTaxonomyAnnotation, ) @@ -7,11 +7,11 @@ from .catalog import Catalog # noqa from .experiment import Experiment # noqa from .gene import Gene # noqa from .ncbi_taxonomy import NcbiTaxonomy # noqa -from .kegg import Kegg # noqa +from .kegg import KeggOrthology # noqa from .eggnog import Eggnog # noqa from .gene_links import ( # noqa GeneNcbiTaxonomyLink, GeneEggnogAnnotationLink, - GeneKeggAnnotationLink, + GeneKeggOrthologyAnnotationLink, GeneCatalogLink, ) diff --git a/backend/app/app/core/models/annotations.py b/backend/app/app/core/models/annotations.py index e759fcd437714f8af6edd1e1f80754429e92915b..b16f457d0180c00a72c33f31adee857d54018e77 100644 --- a/backend/app/app/core/models/annotations.py +++ b/backend/app/app/core/models/annotations.py @@ -3,7 +3,7 @@ from typing import List from sqlmodel import Field, SQLModel, Relationship from app.core.models.gene_links import ( - GeneKeggAnnotationLink, + GeneKeggOrthologyAnnotationLink, GeneEggnogAnnotationLink, GeneNcbiTaxonomyLink, ) @@ -16,17 +16,17 @@ class AnnotationBase(SQLModel): # ------------------ KEGG ------------------ -class KeggAnnotationBase(AnnotationBase): - kegg_id: int = Field(foreign_key="kegg.id") +class KeggOrthologyAnnotationBase(AnnotationBase): + kegg_id: int = Field(foreign_key="keggorthology.id") -class KeggAnnotation(KeggAnnotationBase, table=True): +class KeggOrthologyAnnotation(KeggOrthologyAnnotationBase, table=True): id: int = Field(default=None, primary_key=True) genes: List["Gene"] = Relationship( - back_populates="kegg_annotations", link_model=GeneKeggAnnotationLink + back_populates="kegg_annotations", link_model=GeneKeggOrthologyAnnotationLink ) - kegg: "Kegg" = Relationship(back_populates="annotations") + kegg: "KeggOrthology" = Relationship(back_populates="annotations") experiment: "Experiment" = Relationship(back_populates="kegg_annotations") diff --git a/backend/app/app/core/models/experiment.py b/backend/app/app/core/models/experiment.py index 3a26bb41df7f8d22e58ff0eb006ba007e471146e..b08d45d116f8b340efcff7cedb7b11424a7fa055 100644 --- a/backend/app/app/core/models/experiment.py +++ b/backend/app/app/core/models/experiment.py @@ -1,3 +1,4 @@ +import datetime from typing import List, Optional from sqlmodel import Field, SQLModel, Relationship @@ -5,6 +6,7 @@ from sqlmodel import Field, SQLModel, Relationship class ExperimentBase(SQLModel): name: str = Field(index=False) + date: datetime.date = Field(index=False) description: Optional[str] = Field(index=False) doi: Optional[str] = Field(index=False) @@ -12,10 +14,18 @@ class ExperimentBase(SQLModel): class Experiment(ExperimentBase, table=True): id: int = Field(default=None, primary_key=True) - kegg_annotations: List["KeggAnnotation"] = Relationship(back_populates="experiment") + kegg_annotations: List["KeggOrthologyAnnotation"] = Relationship( + back_populates="experiment" + ) eggnog_annotations: List["EggnogAnnotation"] = Relationship( back_populates="experiment" ) ncbi_taxonomy_annotations: List["NcbiTaxonomyAnnotation"] = Relationship( back_populates="experiment" ) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: {self.name} (id={self.id}, {self.date})>" + + def __str__(self) -> str: + return f"<{self.__class__.__name__}: {self.name} (id={self.id}, {self.date})>" diff --git a/backend/app/app/core/models/gene.py b/backend/app/app/core/models/gene.py index f661572096e6df87a79dac8ac8f2ff0bdf0bf7d2..f3289ac973aebed4699b8fb4483d9bea31e3ef52 100644 --- a/backend/app/app/core/models/gene.py +++ b/backend/app/app/core/models/gene.py @@ -4,7 +4,7 @@ from sqlmodel import Field, SQLModel, Relationship from app.core.models.gene_links import ( GeneCatalogLink, - GeneKeggAnnotationLink, + GeneKeggOrthologyAnnotationLink, GeneEggnogAnnotationLink, GeneNcbiTaxonomyLink, ) @@ -22,8 +22,8 @@ class Gene(GeneBase, table=True): eggnog_annotations: List["EggnogAnnotation"] = Relationship( back_populates="genes", link_model=GeneEggnogAnnotationLink ) - kegg_annotations: List["KeggAnnotation"] = Relationship( - back_populates="genes", link_model=GeneKeggAnnotationLink + kegg_annotations: List["KeggOrthologyAnnotation"] = Relationship( + back_populates="genes", link_model=GeneKeggOrthologyAnnotationLink ) ncbi_taxonomy_annotations: List["NcbiTaxonomyAnnotation"] = Relationship( back_populates="genes", link_model=GeneNcbiTaxonomyLink diff --git a/backend/app/app/core/models/gene_links.py b/backend/app/app/core/models/gene_links.py index 3cdfb0c462f85bd4a5346f52bfc02c7f5034463f..4b8b5b68f65c53174e7766c07cdca8a060af3716 100644 --- a/backend/app/app/core/models/gene_links.py +++ b/backend/app/app/core/models/gene_links.py @@ -15,9 +15,9 @@ class GeneCatalogLink(GeneLinkBase, table=True): ) -class GeneKeggAnnotationLink(GeneLinkBase, table=True): +class GeneKeggOrthologyAnnotationLink(GeneLinkBase, table=True): kegg_annotation_id: Optional[int] = Field( - default=None, foreign_key="keggannotation.id", primary_key=True + default=None, foreign_key="keggorthologyannotation.id", primary_key=True ) diff --git a/backend/app/app/core/models/kegg.py b/backend/app/app/core/models/kegg.py index 12ebe7ec7bcf7d4723ebe4301d8920071a4c82fd..e1648c12db6527ea0abc2032444e8961fab3a554 100644 --- a/backend/app/app/core/models/kegg.py +++ b/backend/app/app/core/models/kegg.py @@ -1,11 +1,11 @@ from typing import List from slugify import slugify -from sqlmodel import Field, Relationship, SQLModel +from sqlmodel import Field, Relationship, SQLModel, VARCHAR, Column -class KeggBase(SQLModel): - kegg_id: str +class KeggOrthologyBase(SQLModel): + kegg_id: str = Field(sa_column=Column("kegg_id", VARCHAR, unique=True)) name: str = Field(index=False) @property @@ -13,7 +13,7 @@ class KeggBase(SQLModel): return slugify(self.name) -class Kegg(KeggBase, table=True): +class KeggOrthology(KeggOrthologyBase, table=True): id: int = Field(default=None, primary_key=True) - annotations: List["KeggAnnotation"] = Relationship(back_populates="kegg") + annotations: List["KeggOrthologyAnnotation"] = Relationship(back_populates="kegg") diff --git a/backend/app/app/core/models/ncbi_taxonomy.py b/backend/app/app/core/models/ncbi_taxonomy.py index 4729894071ff19c2820d72b1add8c64b4b1b89f5..e998d1908d3cfc7cade51eac8deffc2156e285c7 100644 --- a/backend/app/app/core/models/ncbi_taxonomy.py +++ b/backend/app/app/core/models/ncbi_taxonomy.py @@ -1,11 +1,11 @@ from typing import List, Optional import slugify -from sqlmodel import Field, Relationship, Column, JSON, SQLModel +from sqlmodel import Field, Relationship, Column, JSON, SQLModel, INTEGER class NcbiTaxonomyBase(SQLModel): - tax_id: int + tax_id: int = Field(sa_column=Column("tax_id", INTEGER, unique=True)) name: str = Field(index=False) rank: str diff --git a/backend/app/app/core/repositories/catalog.py b/backend/app/app/core/repositories/catalog.py index 4a1fbc908f47cfcee51b715d29b4c1a12004fc23..f0a01ea39377b8754abc1b8c34bfc226f28aad64 100644 --- a/backend/app/app/core/repositories/catalog.py +++ b/backend/app/app/core/repositories/catalog.py @@ -48,10 +48,12 @@ class SqlModelCatalogsRepo(CatalogsRepo): session.refresh(catalog) return catalog - def update(self, catalog_input: CatalogUpdate, session: Session) -> Catalog: + def update( + self, catalog_input: CatalogUpdate, session: Session, exclude_none: bool = True + ) -> Catalog: """Update a catalog entry.""" catalog = self.get(catalog_input.name, session) - for k, v in catalog_input.dict().items(): + for k, v in catalog_input.dict(exclude_none=exclude_none).items(): setattr(catalog, k, v) session.add(catalog) session.commit() diff --git a/backend/app/app/core/repositories/eggnog.py b/backend/app/app/core/repositories/eggnog.py new file mode 100644 index 0000000000000000000000000000000000000000..ee4dde27ad01d7de7cca1a7ca12c535d4419ca82 --- /dev/null +++ b/backend/app/app/core/repositories/eggnog.py @@ -0,0 +1,70 @@ +import abc +from typing import List + +from sqlmodel import Session, select + +from app.core.models.eggnog import Eggnog +from app.core.schemas.entities.eggnog import EggnogCreate, EggnogUpdate + + +class EggnogsRepo(abc.ABC): + @abc.abstractmethod + def get(self, eggnog_name: str) -> Eggnog: + raise NotImplementedError + + @abc.abstractmethod + def get_all(self) -> List[Eggnog]: + raise NotImplementedError + + @abc.abstractmethod + def create(self, eggnog_input: EggnogCreate) -> Eggnog: + raise NotImplementedError + + @abc.abstractmethod + def update(self, eggnog_input: EggnogUpdate) -> Eggnog: + raise NotImplementedError + + @abc.abstractmethod + def delete(self, eggnog_name: str): + raise NotImplementedError + + +class SqlModelEggnogsRepo(EggnogsRepo): + def get(self, eggnog_id, session: Session) -> Eggnog: + """Retrieve one eggnog from name.""" + statement = select(Eggnog).where(Eggnog.eggnog_id == eggnog_id) + return session.exec(statement).one() + + def get_all(self, session: Session) -> List[Eggnog]: + """Retrieve all eggnogs.""" + eggnogs = session.exec(select(Eggnog)).all() + return eggnogs + + def create(self, eggnog_input: EggnogCreate, session: Session) -> Eggnog: + """Create a eggnog entry.""" + eggnog = Eggnog( + eggnog_id=eggnog_input.eggnog_id, + name=eggnog_input.name, + version=eggnog_input.version, + ) + session.add(eggnog) + session.commit() + session.refresh(eggnog) + return eggnog + + def update( + self, eggnog_input: EggnogUpdate, session: Session, exclude_none: bool = True + ) -> Eggnog: + """Update a eggnog entry.""" + eggnog = self.get(eggnog_input.eggnog_id, session) + for k, v in eggnog_input.dict(exclude_none=exclude_none).items(): + setattr(eggnog, k, v) + session.add(eggnog) + session.commit() + session.refresh(eggnog) + return eggnog + + def delete(self, eggnog_id: str, session: Session): + eggnog = self.get(eggnog_id, session) + session.delete(eggnog) + session.commit() diff --git a/backend/app/app/core/repositories/experiment.py b/backend/app/app/core/repositories/experiment.py new file mode 100644 index 0000000000000000000000000000000000000000..b0783bb305af6b2503f0f10d4db79e4775602ab9 --- /dev/null +++ b/backend/app/app/core/repositories/experiment.py @@ -0,0 +1,76 @@ +import abc +from typing import List + +from sqlmodel import Session, select + +from app.core.models.experiment import Experiment +from app.core.schemas.entities.experiment import ExperimentCreate, ExperimentUpdate + + +class ExperimentsRepo(abc.ABC): + @abc.abstractmethod + def get(self, experiment_name: str) -> Experiment: + raise NotImplementedError + + @abc.abstractmethod + def get_all(self) -> List[Experiment]: + raise NotImplementedError + + @abc.abstractmethod + def create(self, experiment_input: ExperimentCreate) -> Experiment: + raise NotImplementedError + + @abc.abstractmethod + def update(self, experiment_input: ExperimentUpdate) -> Experiment: + raise NotImplementedError + + @abc.abstractmethod + def delete(self, experiment_name: str): + raise NotImplementedError + + +class SqlModelExperimentsRepo(ExperimentsRepo): + def get(self, experiment_id, session: Session) -> Experiment: + """Retrieve one experiment from name.""" + statement = select(Experiment).where(Experiment.id == experiment_id) + return session.exec(statement).one() + + def get_all(self, session: Session) -> List[Experiment]: + """Retrieve all experiments.""" + experiments = session.exec(select(Experiment)).all() + return experiments + + def create( + self, experiment_input: ExperimentCreate, session: Session + ) -> Experiment: + """Create a experiment entry.""" + experiment = Experiment( + name=experiment_input.name, + date=experiment_input.date, + description=experiment_input.description, + doi=experiment_input.doi, + ) + session.add(experiment) + session.commit() + session.refresh(experiment) + return experiment + + def update( + self, + experiment_input: ExperimentUpdate, + session: Session, + exclude_none: bool = True, + ) -> Experiment: + """Update a experiment entry.""" + experiment = self.get(experiment_input.id, session) + for k, v in experiment_input.dict(exclude_none=exclude_none).items(): + setattr(experiment, k, v) + session.add(experiment) + session.commit() + session.refresh(experiment) + return experiment + + def delete(self, experiment_id: str, session: Session): + experiment = self.get(experiment_id, session) + session.delete(experiment) + session.commit() diff --git a/backend/app/app/core/repositories/kegg.py b/backend/app/app/core/repositories/kegg.py new file mode 100644 index 0000000000000000000000000000000000000000..882a482b77983902468a4ce9a15ea8a13f2b465b --- /dev/null +++ b/backend/app/app/core/repositories/kegg.py @@ -0,0 +1,71 @@ +import abc +from typing import List + +from sqlmodel import Session, select + +from app.core.models.kegg import KeggOrthology +from app.core.schemas.entities.kegg import KeggOrthologyCreate, KeggOrthologyUpdate + + +class KeggOrthologysRepo(abc.ABC): + @abc.abstractmethod + def get(self, kegg_name: str) -> KeggOrthology: + raise NotImplementedError + + @abc.abstractmethod + def get_all(self) -> List[KeggOrthology]: + raise NotImplementedError + + @abc.abstractmethod + def create(self, kegg_input: KeggOrthologyCreate) -> KeggOrthology: + raise NotImplementedError + + @abc.abstractmethod + def update(self, kegg_input: KeggOrthologyUpdate) -> KeggOrthology: + raise NotImplementedError + + @abc.abstractmethod + def delete(self, kegg_name: str): + raise NotImplementedError + + +class SqlModelKeggOrthologysRepo(KeggOrthologysRepo): + def get(self, kegg_id, session: Session) -> KeggOrthology: + """Retrieve one kegg from name.""" + statement = select(KeggOrthology).where(KeggOrthology.kegg_id == kegg_id) + return session.exec(statement).one() + + def get_all(self, session: Session) -> List[KeggOrthology]: + """Retrieve all keggs.""" + keggs = session.exec(select(KeggOrthology)).all() + return keggs + + def create( + self, kegg_input: KeggOrthologyCreate, session: Session + ) -> KeggOrthology: + """Create a kegg entry.""" + kegg = KeggOrthology(kegg_id=kegg_input.kegg_id, name=kegg_input.name) + session.add(kegg) + session.commit() + session.refresh(kegg) + return kegg + + def update( + self, + kegg_input: KeggOrthologyUpdate, + session: Session, + exclude_none: bool = True, + ) -> KeggOrthology: + """Update a kegg entry.""" + kegg = self.get(kegg_input.kegg_id, session) + for k, v in kegg_input.dict(exclude_none=exclude_none).items(): + setattr(kegg, k, v) + session.add(kegg) + session.commit() + session.refresh(kegg) + return kegg + + def delete(self, kegg_id: str, session: Session): + kegg = self.get(kegg_id, session) + session.delete(kegg) + session.commit() diff --git a/backend/app/app/core/repositories/ncbi_taxonomy.py b/backend/app/app/core/repositories/ncbi_taxonomy.py new file mode 100644 index 0000000000000000000000000000000000000000..8c4afe7256041113b2df684d503ea111a8ddcdf1 --- /dev/null +++ b/backend/app/app/core/repositories/ncbi_taxonomy.py @@ -0,0 +1,78 @@ +import abc +from typing import List + +from sqlmodel import Session, select + +from app.core.models.ncbi_taxonomy import NcbiTaxonomy +from app.core.schemas.entities.ncbi_taxonomy import ( + NcbiTaxonomyCreate, + NcbiTaxonomyUpdate, +) + + +class NcbiTaxonomysRepo(abc.ABC): + @abc.abstractmethod + def get(self, ncbi_taxonomy_name: str) -> NcbiTaxonomy: + raise NotImplementedError + + @abc.abstractmethod + def get_all(self) -> List[NcbiTaxonomy]: + raise NotImplementedError + + @abc.abstractmethod + def create(self, ncbi_taxonomy_input: NcbiTaxonomyCreate) -> NcbiTaxonomy: + raise NotImplementedError + + @abc.abstractmethod + def update(self, ncbi_taxonomy_input: NcbiTaxonomyUpdate) -> NcbiTaxonomy: + raise NotImplementedError + + @abc.abstractmethod + def delete(self, ncbi_taxonomy_name: str): + raise NotImplementedError + + +class SqlModelNcbiTaxonomysRepo(NcbiTaxonomysRepo): + def get(self, tax_id, session: Session) -> NcbiTaxonomy: + """Retrieve one ncbi_taxonomy from name.""" + statement = select(NcbiTaxonomy).where(NcbiTaxonomy.tax_id == tax_id) + return session.exec(statement).one() + + def get_all(self, session: Session) -> List[NcbiTaxonomy]: + """Retrieve all ncbi_taxonomys.""" + ncbi_taxonomys = session.exec(select(NcbiTaxonomy)).all() + return ncbi_taxonomys + + def create( + self, ncbi_taxonomy_input: NcbiTaxonomyCreate, session: Session + ) -> NcbiTaxonomy: + """Create a ncbi_taxonomy entry.""" + ncbi_taxonomy = NcbiTaxonomy( + tax_id=ncbi_taxonomy_input.tax_id, + rank=ncbi_taxonomy_input.rank, + name=ncbi_taxonomy_input.name, + ) + session.add(ncbi_taxonomy) + session.commit() + session.refresh(ncbi_taxonomy) + return ncbi_taxonomy + + def update( + self, + ncbi_taxonomy_input: NcbiTaxonomyUpdate, + session: Session, + exclude_none: bool = True, + ) -> NcbiTaxonomy: + """Update a ncbi_taxonomy entry.""" + ncbi_taxonomy = self.get(ncbi_taxonomy_input.tax_id, session) + for k, v in ncbi_taxonomy_input.dict(exclude_none=exclude_none).items(): + setattr(ncbi_taxonomy, k, v) + session.add(ncbi_taxonomy) + session.commit() + session.refresh(ncbi_taxonomy) + return ncbi_taxonomy + + def delete(self, tax_id: str, session: Session): + ncbi_taxonomy = self.get(tax_id, session) + session.delete(ncbi_taxonomy) + session.commit() diff --git a/backend/app/app/core/schemas/entities/annotations.py b/backend/app/app/core/schemas/entities/annotations.py index e09e9b7a5e3c3ebe267e7202b5bbf767cf64480d..a531a6cfa75ff8095f0af31b46bcef526bf62c85 100644 --- a/backend/app/app/core/schemas/entities/annotations.py +++ b/backend/app/app/core/schemas/entities/annotations.py @@ -1,13 +1,13 @@ from sqlmodel import SQLModel from app.core.models.annotations import ( - KeggAnnotationBase, + KeggOrthologyAnnotationBase, EggnogAnnotationBase, NcbiTaxonomyAnnotationBase, ) from app.core.schemas.entities.eggnog import EggnogRead from app.core.schemas.entities.experiment import ExperimentRead -from app.core.schemas.entities.kegg import KeggRead +from app.core.schemas.entities.kegg import KeggOrthologyRead from app.core.schemas.entities.ncbi_taxonomy import NcbiTaxonomyRead @@ -18,11 +18,11 @@ class BaseAnnotationRead(SQLModel): # ------------------ KEGG ------------------ -class KeggAnnotationRead(BaseAnnotationRead): - kegg: KeggRead +class KeggOrthologyAnnotationRead(BaseAnnotationRead): + kegg: KeggOrthologyRead -class KeggAnnotationCreate(KeggAnnotationBase): +class KeggOrthologyAnnotationCreate(KeggOrthologyAnnotationBase): pass diff --git a/backend/app/app/core/schemas/entities/eggnog.py b/backend/app/app/core/schemas/entities/eggnog.py index b533a66b8ce334db7496c4f0457d466aab8ce91c..f0995fb5344ed2de353528454670e22f6889eece 100644 --- a/backend/app/app/core/schemas/entities/eggnog.py +++ b/backend/app/app/core/schemas/entities/eggnog.py @@ -1,3 +1,5 @@ +from typing import Optional + from app.core.models.eggnog import EggnogBase @@ -7,3 +9,9 @@ class EggnogRead(EggnogBase): class EggnogCreate(EggnogBase): pass + + +class EggnogUpdate(EggnogBase): + name: Optional[str] + version: Optional[str] + pass diff --git a/backend/app/app/core/schemas/entities/experiment.py b/backend/app/app/core/schemas/entities/experiment.py index efddfde8c6e5569eeaa6d7e687b4bcb2611dee51..6669efb1f276f3ea77c1a980138f501477cde5d9 100644 --- a/backend/app/app/core/schemas/entities/experiment.py +++ b/backend/app/app/core/schemas/entities/experiment.py @@ -1,3 +1,6 @@ +import datetime +from typing import Optional + from app.core.models.experiment import ExperimentBase @@ -6,4 +9,12 @@ class ExperimentCreate(ExperimentBase): class ExperimentRead(ExperimentBase): + id: int + pass + + +class ExperimentUpdate(ExperimentBase): + id: int + name: Optional[str] + date: Optional[datetime.time] pass diff --git a/backend/app/app/core/schemas/entities/kegg.py b/backend/app/app/core/schemas/entities/kegg.py index bbd565c98e8ccfdb4bb6b6139b4608b606bbd0e5..615b9d379cbde8ab4bf5776cb4339cb1ce2c85cf 100644 --- a/backend/app/app/core/schemas/entities/kegg.py +++ b/backend/app/app/core/schemas/entities/kegg.py @@ -1,9 +1,16 @@ -from app.core.models.kegg import KeggBase +from typing import Optional +from app.core.models.kegg import KeggOrthologyBase -class KeggRead(KeggBase): + +class KeggOrthologyRead(KeggOrthologyBase): + pass + + +class KeggOrthologyCreate(KeggOrthologyBase): pass -class KeggCreate(KeggBase): +class KeggOrthologyUpdate(KeggOrthologyBase): + name: Optional[str] pass diff --git a/backend/app/app/core/schemas/entities/ncbi_taxonomy.py b/backend/app/app/core/schemas/entities/ncbi_taxonomy.py index ff8ac2f3e206f0bd8e3bf2f02f90b5ed28835779..8dc8e5a7e3c178ba4310fc49d59055720d73702f 100644 --- a/backend/app/app/core/schemas/entities/ncbi_taxonomy.py +++ b/backend/app/app/core/schemas/entities/ncbi_taxonomy.py @@ -1,3 +1,7 @@ +from typing import Optional + +from sqlmodel import SQLModel + from app.core.models.ncbi_taxonomy import NcbiTaxonomyBase @@ -7,3 +11,9 @@ class NcbiTaxonomyRead(NcbiTaxonomyBase): class NcbiTaxonomyCreate(NcbiTaxonomyBase): pass + + +class NcbiTaxonomyUpdate(SQLModel): + tax_id: int + name: Optional[str] + rank: Optional[str] diff --git a/backend/app/app/core/schemas/nested/gene.py b/backend/app/app/core/schemas/nested/gene.py index dae2618032f5249f5eca519b80954cc7c2ad54f9..48411446ed673642e4b58c4f0696be9c57ef8f7e 100644 --- a/backend/app/app/core/schemas/nested/gene.py +++ b/backend/app/app/core/schemas/nested/gene.py @@ -6,7 +6,7 @@ from app.core.schemas.entities.gene import ( from app.core.schemas.entities.catalog import CatalogRead from app.core.schemas.entities.annotations import ( - KeggAnnotationRead, + KeggOrthologyAnnotationRead, EggnogAnnotationRead, NcbiTaxonomyAnnotationRead, ) @@ -15,5 +15,5 @@ from app.core.schemas.entities.annotations import ( class GeneReadWithAnnotations(GeneRead): catalogs: List[CatalogRead] = [] eggnog_annotations: List[EggnogAnnotationRead] = [] - kegg_annotations: List[KeggAnnotationRead] = [] + kegg_annotations: List[KeggOrthologyAnnotationRead] = [] ncbi_taxonomy_annotations: List[NcbiTaxonomyAnnotationRead] = [] diff --git a/backend/app/app/core/use_cases/crud/eggnog.py b/backend/app/app/core/use_cases/crud/eggnog.py new file mode 100644 index 0000000000000000000000000000000000000000..bf4e76deeeed007d8b4a00104913d0b409023b37 --- /dev/null +++ b/backend/app/app/core/use_cases/crud/eggnog.py @@ -0,0 +1,28 @@ +from typing import List + +import inject + +from app.core.models.eggnog import Eggnog +from app.core.schemas.entities.eggnog import EggnogCreate, EggnogUpdate +from app.core.repositories.eggnog import EggnogsRepo + + +class CrudEggnogUseCase: + @inject.autoparams("eggnogs_repo") + def __init__(self, eggnogs_repo: EggnogsRepo): + self._eggnogs_repo = eggnogs_repo + + def create_eggnog(self, eggnog_input: EggnogCreate, **kwargs) -> Eggnog: + return self._eggnogs_repo.create(eggnog_input, **kwargs) + + def update_eggnog(self, eggnog_input: EggnogUpdate, **kwargs) -> Eggnog: + return self._eggnogs_repo.update(eggnog_input, **kwargs) + + def delete_eggnog(self, eggnog_id: str, **kwargs): + return self._eggnogs_repo.delete(eggnog_id, **kwargs) + + def get_eggnog(self, eggnog_id: str, **kwargs) -> Eggnog: + return self._eggnogs_repo.get(eggnog_id, **kwargs) + + def get_all(self, **kwargs) -> List[Eggnog]: + return self._eggnogs_repo.get_all(**kwargs) diff --git a/backend/app/app/core/use_cases/crud/experiment.py b/backend/app/app/core/use_cases/crud/experiment.py new file mode 100644 index 0000000000000000000000000000000000000000..c0f47f9b962b399b03cbd91922d9328cd09e8eaf --- /dev/null +++ b/backend/app/app/core/use_cases/crud/experiment.py @@ -0,0 +1,32 @@ +from typing import List + +import inject + +from app.core.models.experiment import Experiment +from app.core.schemas.entities.experiment import ExperimentCreate, ExperimentUpdate +from app.core.repositories.experiment import ExperimentsRepo + + +class CrudExperimentUseCase: + @inject.autoparams("experiments_repo") + def __init__(self, experiments_repo: ExperimentsRepo): + self._experiments_repo = experiments_repo + + def create_experiment( + self, experiment_input: ExperimentCreate, **kwargs + ) -> Experiment: + return self._experiments_repo.create(experiment_input, **kwargs) + + def update_experiment( + self, experiment_input: ExperimentUpdate, **kwargs + ) -> Experiment: + return self._experiments_repo.update(experiment_input, **kwargs) + + def delete_experiment(self, experiment_id: str, **kwargs): + return self._experiments_repo.delete(experiment_id, **kwargs) + + def get_experiment(self, experiment_id: str, **kwargs) -> Experiment: + return self._experiments_repo.get(experiment_id, **kwargs) + + def get_all(self, **kwargs) -> List[Experiment]: + return self._experiments_repo.get_all(**kwargs) diff --git a/backend/app/app/core/use_cases/crud/kegg.py b/backend/app/app/core/use_cases/crud/kegg.py new file mode 100644 index 0000000000000000000000000000000000000000..502824a64294a5e8563b4c12f8a24d1ab9101d44 --- /dev/null +++ b/backend/app/app/core/use_cases/crud/kegg.py @@ -0,0 +1,28 @@ +from typing import List + +import inject + +from app.core.models.kegg import KeggOrthology +from app.core.schemas.entities.kegg import KeggOrthologyCreate, KeggOrthologyUpdate +from app.core.repositories.kegg import KeggOrthologysRepo + + +class CrudKeggOrthologyUseCase: + @inject.autoparams("keggs_repo") + def __init__(self, keggs_repo: KeggOrthologysRepo): + self._keggs_repo = keggs_repo + + def create_kegg(self, kegg_input: KeggOrthologyCreate, **kwargs) -> KeggOrthology: + return self._keggs_repo.create(kegg_input, **kwargs) + + def update_kegg(self, kegg_input: KeggOrthologyUpdate, **kwargs) -> KeggOrthology: + return self._keggs_repo.update(kegg_input, **kwargs) + + def delete_kegg(self, kegg_id: str, **kwargs): + return self._keggs_repo.delete(kegg_id, **kwargs) + + def get_kegg(self, kegg_id: str, **kwargs) -> KeggOrthology: + return self._keggs_repo.get(kegg_id, **kwargs) + + def get_all(self, **kwargs) -> List[KeggOrthology]: + return self._keggs_repo.get_all(**kwargs) diff --git a/backend/app/app/core/use_cases/crud/ncbi_taxonomy.py b/backend/app/app/core/use_cases/crud/ncbi_taxonomy.py new file mode 100644 index 0000000000000000000000000000000000000000..bf4d4fd21e922338228073530c4b23e246479864 --- /dev/null +++ b/backend/app/app/core/use_cases/crud/ncbi_taxonomy.py @@ -0,0 +1,35 @@ +from typing import List + +import inject + +from app.core.models.ncbi_taxonomy import NcbiTaxonomy +from app.core.schemas.entities.ncbi_taxonomy import ( + NcbiTaxonomyCreate, + NcbiTaxonomyUpdate, +) +from app.core.repositories.ncbi_taxonomy import NcbiTaxonomysRepo + + +class CrudNcbiTaxonomyUseCase: + @inject.autoparams("ncbi_taxonomys_repo") + def __init__(self, ncbi_taxonomys_repo: NcbiTaxonomysRepo): + self._ncbi_taxonomys_repo = ncbi_taxonomys_repo + + def create_ncbi_taxonomy( + self, ncbi_taxonomy_input: NcbiTaxonomyCreate, **kwargs + ) -> NcbiTaxonomy: + return self._ncbi_taxonomys_repo.create(ncbi_taxonomy_input, **kwargs) + + def update_ncbi_taxonomy( + self, ncbi_taxonomy_input: NcbiTaxonomyUpdate, **kwargs + ) -> NcbiTaxonomy: + return self._ncbi_taxonomys_repo.update(ncbi_taxonomy_input, **kwargs) + + def delete_ncbi_taxonomy(self, ncbi_taxonomy_name: str, **kwargs): + return self._ncbi_taxonomys_repo.delete(ncbi_taxonomy_name, **kwargs) + + def get_ncbi_taxonomy(self, ncbi_taxonomy_id: str, **kwargs) -> NcbiTaxonomy: + return self._ncbi_taxonomys_repo.get(ncbi_taxonomy_id, **kwargs) + + def get_all(self, **kwargs) -> List[NcbiTaxonomy]: + return self._ncbi_taxonomys_repo.get_all(**kwargs) diff --git a/backend/app/app/dependency_injections.py b/backend/app/app/dependency_injections.py index 2a409860856c2b130c4c9691b42f631d747c15a9..e8b6d3089569fe5c20cf558e4fe7b8a15cf1a66b 100644 --- a/backend/app/app/dependency_injections.py +++ b/backend/app/app/dependency_injections.py @@ -1,11 +1,26 @@ import inject from app.core.repositories.catalog import CatalogsRepo, SqlModelCatalogsRepo +from app.core.repositories.eggnog import EggnogsRepo, SqlModelEggnogsRepo +from app.core.repositories.experiment import ExperimentsRepo, SqlModelExperimentsRepo +from app.core.repositories.kegg import KeggOrthologysRepo, SqlModelKeggOrthologysRepo +from app.core.repositories.ncbi_taxonomy import ( + NcbiTaxonomysRepo, + SqlModelNcbiTaxonomysRepo, +) def di_configuration(binder): binder.bind(CatalogsRepo, SqlModelCatalogsRepo()) + binder.bind(EggnogsRepo, SqlModelEggnogsRepo()) + binder.bind(KeggOrthologysRepo, SqlModelKeggOrthologysRepo()) + binder.bind(NcbiTaxonomysRepo, SqlModelNcbiTaxonomysRepo()) + binder.bind(ExperimentsRepo, SqlModelExperimentsRepo()) def run_di(): inject.configure(di_configuration) + + +def clear_di(): + inject.clear() diff --git a/backend/app/tasks/tests.py b/backend/app/tasks/tests.py index bc1f79384336608d774715885910873c27f7e2a7..28ccb7172077781577a23f9c22adc33cfc91dad1 100644 --- a/backend/app/tasks/tests.py +++ b/backend/app/tasks/tests.py @@ -10,4 +10,4 @@ def unit(c): @task def cov(c): """Run the unit tests and the test coverage.""" - c.run("pytest --cov-report term --cov=app/ tests/") + c.run("pytest --cov-report term-missing --cov=app/ tests/") diff --git a/backend/app/tests/api/endpoints/base_api_test.py b/backend/app/tests/api/endpoints/base_api_test.py new file mode 100644 index 0000000000000000000000000000000000000000..9a83782f5e6c26c9c3a631cbf6377af0301499de --- /dev/null +++ b/backend/app/tests/api/endpoints/base_api_test.py @@ -0,0 +1,54 @@ +import logging +import unittest + +from sqlmodel import create_engine, Session, SQLModel +from sqlmodel.pool import StaticPool +from fastapi.testclient import TestClient + +from app.db import get_session +from app.dependency_injections import run_di, clear_di +from app.main import app + +logging.basicConfig() +logger = logging.getLogger() + + +class BaseApiTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + # Dependency injections + run_di() + + cls.engine = create_engine( + "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool + ) + SQLModel.metadata.create_all(cls.engine) + with Session(cls.engine) as session: + cls._create_test_db(session) + + @classmethod + def _create_test_db(cls, session: Session): + pass + + def setUp(self) -> None: + self.session = Session(self.engine) + + def get_session_override(): + return self.session + + app.dependency_overrides[get_session] = get_session_override + self.client = TestClient(app) + + def tearDown(self) -> None: + app.dependency_overrides.clear() + self.session.close() + + @classmethod + def tearDownClass(cls) -> None: + clear_di() + + def _delete_entry(self, entry): + self.session.rollback() + logger.info(f"Deleting {entry}...") + self.session.delete(entry) + self.session.commit() diff --git a/backend/app/tests/api/endpoints/test_catalogs.py b/backend/app/tests/api/endpoints/test_catalogs.py index 4bb7f4cf61b37d48114d19f21ef90a64713851b4..51279c207b1c22ac9fbf174dc0b16647ffd45205 100644 --- a/backend/app/tests/api/endpoints/test_catalogs.py +++ b/backend/app/tests/api/endpoints/test_catalogs.py @@ -1,46 +1,16 @@ -import unittest +from sqlmodel import Session, select -from sqlmodel import create_engine, Session, SQLModel, select -from sqlmodel.pool import StaticPool -from fastapi.testclient import TestClient - -from app.db import get_session -from app.dependency_injections import run_di -from app.main import app from app.core.models.catalog import Catalog - -def create_test_db(session: Session): - test_catalog = Catalog(name="test-catalog") - session.add(test_catalog) - session.commit() +from .base_api_test import BaseApiTests -class TestCatalogs(unittest.TestCase): +class TestCatalogs(BaseApiTests): @classmethod - def setUpClass(cls) -> None: - # Dependency injections - run_di() - - cls.engine = create_engine( - "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool - ) - SQLModel.metadata.create_all(cls.engine) - with Session(cls.engine) as session: - create_test_db(session) - - def setUp(self) -> None: - self.session = Session(self.engine) - - def get_session_override(): - return self.session - - app.dependency_overrides[get_session] = get_session_override - self.client = TestClient(app) - - def tearDown(self) -> None: - app.dependency_overrides.clear() - self.session.close() + def _create_test_db(cls, session: Session): + test_catalog = Catalog(name="test-catalog") + session.add(test_catalog) + session.commit() def test_get_catalog_non_existing(self): # When @@ -72,15 +42,17 @@ class TestCatalogs(unittest.TestCase): cat_name = "created_catalog" json_input = {"name": cat_name} expected_data = {"name": cat_name, "doi": None} - # When - response = self.client.post("/api/catalogs/", json=json_input) - # Then - self.assertEqual(response.status_code, 200) - self.assertDictEqual(response.json(), expected_data) - # Finally delete item - cat = self.session.exec(select(Catalog).where(Catalog.name == cat_name)).one() - self.session.delete(cat) - self.session.commit() + try: + # When + response = self.client.post("/api/catalogs/", json=json_input) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + cat = self.session.exec( + select(Catalog).where(Catalog.name == cat_name) + ).one() + self._delete_entry(cat) def test_create_existing_catalog(self): # Given @@ -125,11 +97,11 @@ class TestCatalogs(unittest.TestCase): self.session.commit() json_input = {"name": name_to_update, "doi": "12345"} expected_data = json_input - # When - response = self.client.put(f"/api/catalogs/", json=json_input) - # Then - self.assertEqual(response.status_code, 200) - self.assertDictEqual(response.json(), expected_data) - # Finally - self.session.delete(catalog_to_update) - self.session.commit() + try: + # When + response = self.client.put(f"/api/catalogs/", json=json_input) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + self._delete_entry(catalog_to_update) diff --git a/backend/app/tests/api/endpoints/test_eggnogs.py b/backend/app/tests/api/endpoints/test_eggnogs.py new file mode 100644 index 0000000000000000000000000000000000000000..52ac9b964783e0e8f4abd399ccdbfbaec3966667 --- /dev/null +++ b/backend/app/tests/api/endpoints/test_eggnogs.py @@ -0,0 +1,155 @@ +from sqlmodel import Session, select + +from app.core.models.eggnog import Eggnog + +from .base_api_test import BaseApiTests + + +class TestEggnogs(BaseApiTests): + @classmethod + def _create_test_db(cls, session: Session): + test_eggnog = Eggnog(eggnog_id="COG1", name="test-eggnog", version="5.0") + session.add(test_eggnog) + session.commit() + + def test_get_eggnog_non_existing(self): + # When + response = self.client.get("/api/eggnog/non-existing") + # Then + self.assertEqual(response.status_code, 404) + + def test_get_eggnog_existing(self): + # Given + expected_id = "COG1" + expected_data = { + "eggnog_id": expected_id, + "name": "test-eggnog", + "version": "5.0", + } + # When + response = self.client.get(f"/api/eggnog/{expected_id}") + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + + def test_get_all_eggnogs(self): + # Given + expected_data = [{"eggnog_id": "COG1", "name": "test-eggnog", "version": "5.0"}] + # When + response = self.client.get(f"/api/eggnog/") + # Then + self.assertEqual(response.status_code, 200) + self.assertListEqual(response.json(), expected_data) + + def test_create_eggnog(self): + # Given + eggnog_id = "COG9" + eggnog_name = "created_eggnog" + json_input = {"eggnog_id": eggnog_id, "name": eggnog_name, "version": "5.0"} + expected_data = {"eggnog_id": eggnog_id, "name": eggnog_name, "version": "5.0"} + try: + # When + response = self.client.post("/api/eggnog/", json=json_input) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + eggnog = self.session.exec( + select(Eggnog).where(Eggnog.eggnog_id == eggnog_id) + ).one() + self._delete_entry(eggnog) + + def test_create_eggnog_invalid_version(self): + # Given + eggnog_id = "COG9" + eggnog_name = "created_eggnog" + json_input = {"eggnog_id": eggnog_id, "name": eggnog_name, "version": "8.0"} + # When + response = self.client.post("/api/eggnog/", json=json_input) + # Then + self.assertEqual(response.status_code, 422) + + def test_create_existing_eggnog(self): + """This test should fail and need to be dealt with in issue#15""" + # # Given + # eggnog_id = "COG1" + # eggnog_name = "test-eggnog" + # json_input = {"eggnog_id": eggnog_id, "name": eggnog_name, 'version':"5.0"} + # # When + # response = self.client.post("/api/eggnog/", json=json_input) + # # Then + # self.assertEqual(response.status_code, 403) + + def test_delete_eggnog_non_existing(self): + # When + response = self.client.delete(f"/api/eggnog/non-existing") + # Then + self.assertEqual(response.status_code, 404) + + def test_delete_eggnog(self): + # Given + eggnog_id = "COG5" + name_to_delete = "delete-me" + # - create eggnog to delete + eggnog_to_delete = Eggnog( + eggnog_id=eggnog_id, name=name_to_delete, version="5.0" + ) + self.session.add(eggnog_to_delete) + self.session.commit() + # When + response = self.client.delete(f"/api/eggnog/{eggnog_id}") + # Then + self.assertEqual(response.status_code, 200) + + def test_update_eggnog_non_existing(self): + # When + json_input = {"eggnog_id": "i-dont-exist", "name": "12345", "version": "5.0"} + response = self.client.put(f"/api/eggnog/", json=json_input) + # Then + self.assertEqual(response.status_code, 404) + + def test_update_eggnog_no_version_exclude_none_true(self): + # Given + eggnog_id = "COG2" + name_to_update = "update-me" + new_name = "new-name" + # - create eggnog to update + eggnog_to_update = Eggnog( + eggnog_id=eggnog_id, name=name_to_update, version="5.0" + ) + self.session.add(eggnog_to_update) + self.session.commit() + json_input = {"eggnog_id": eggnog_id, "name": new_name} + expected_data = {"eggnog_id": eggnog_id, "name": new_name, "version": "5.0"} + try: + # When + response = self.client.put( + f"/api/eggnog/?exclude_none=true", json=json_input + ) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + self._delete_entry(eggnog_to_update) + + def test_update_eggnog_no_version_exclude_none_false(self): + # Given + eggnog_id = "COG2" + name_to_update = "update-me" + new_name = "new-name" + # - create eggnog to update + eggnog_to_update = Eggnog( + eggnog_id=eggnog_id, name=name_to_update, version="5.0" + ) + self.session.add(eggnog_to_update) + self.session.commit() + json_input = {"eggnog_id": eggnog_id, "name": new_name} + try: + # When + response = self.client.put( + f"/api/eggnog/?exclude_none=false", json=json_input + ) + # Then + self.assertEqual(response.status_code, 422) + finally: + self._delete_entry(eggnog_to_update) diff --git a/backend/app/tests/api/endpoints/test_experiments.py b/backend/app/tests/api/endpoints/test_experiments.py new file mode 100644 index 0000000000000000000000000000000000000000..45ce4fb9ae72b7b68b0f03e340d7006685461499 --- /dev/null +++ b/backend/app/tests/api/endpoints/test_experiments.py @@ -0,0 +1,185 @@ +from sqlmodel import Session, select + +from app.core.models.experiment import Experiment + +from .base_api_test import BaseApiTests + + +class TestExperiments(BaseApiTests): + @classmethod + def _create_test_db(cls, session: Session): + test_experiment = Experiment(name="test-experiment", date="2020-10-10") + session.add(test_experiment) + session.commit() + session.refresh(test_experiment) + cls.experiment_id_existing = test_experiment.id + + def test_get_experiment_non_existing(self): + # When + response = self.client.get("/api/experiments/654") + # Then + self.assertEqual(response.status_code, 404) + + def test_get_experiment_existing(self): + # Given + expected_id = self.experiment_id_existing + expected_data = { + "id": expected_id, + "name": "test-experiment", + "date": "2020-10-10", + "description": None, + "doi": None, + } + # When + response = self.client.get(f"/api/experiments/{expected_id}") + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + + def test_get_all_experiments(self): + # Given + expected_data = [ + { + "id": 1, + "name": "test-experiment", + "date": "2020-10-10", + "description": None, + "doi": None, + } + ] + # When + response = self.client.get(f"/api/experiments/") + # Then + self.assertEqual(response.status_code, 200) + self.assertListEqual(response.json(), expected_data) + + def test_create_experiment(self): + # Given + experiment_name = "created_experiment" + json_input = { + "name": experiment_name, + "date": "2021-11-11", + } + expected_data = { + "name": experiment_name, + "date": "2021-11-11", + "description": None, + "doi": None, + } + created_id = None + try: + # When + response = self.client.post("/api/experiments/", json=json_input) + # Then + self.assertEqual(response.status_code, 200) + for k, v in response.json().items(): + if k == "id": + created_id = v + else: + self.assertEqual(v, expected_data[k]) + finally: + try: + experiment = self.session.exec( + select(Experiment).where(Experiment.id == created_id) + ).one() + self._delete_entry(experiment) + except: + pass + + def test_create_experiment_invalid_date(self): + # Given + json_input = { + "name": "invalid-date_experiment", + "date": "20-2", + } + # When + response = self.client.post("/api/experiments/", json=json_input) + # Then + self.assertEqual(response.status_code, 422) + + def test_delete_experiment_non_existing(self): + # When + response = self.client.delete(f"/api/experiments/123456") + # Then + self.assertEqual(response.status_code, 404) + + def test_delete_experiment(self): + # Given + name_to_delete = "delete-me" + # - create experiment to delete + experiment_to_delete = Experiment(name=name_to_delete, date="2010-10-10") + self.session.add(experiment_to_delete) + self.session.commit() + self.session.refresh(experiment_to_delete) + # When + response = self.client.delete(f"/api/experiments/{experiment_to_delete.id}") + # Then + self.assertEqual(response.status_code, 200) + + def test_update_experiment_non_existing(self): + # When + json_input = { + "id": 123456, + "name": "never-updated-experiment", + } + response = self.client.put(f"/api/experiments/", json=json_input) + # Then + self.assertEqual(response.status_code, 404) + + def test_update_experiment_no_date_exclude_none_true(self): + # Given + name_to_update = "update-me" + new_name = "new-name" + new_description = "this experiment was difficult" + # - create experiment to update + experiment_to_update = Experiment(name=name_to_update, date="2021-10-10") + self.session.add(experiment_to_update) + self.session.commit() + self.session.refresh(experiment_to_update) + json_input = { + "id": experiment_to_update.id, + "name": new_name, + "description": new_description, + } + expected_data = { + "id": experiment_to_update.id, + "name": new_name, + "description": new_description, + "date": "2021-10-10", + "doi": None, + } + try: + # When + response = self.client.put( + f"/api/experiments/?exclude_none=true", json=json_input + ) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + self._delete_entry(experiment_to_update) + + def test_update_experiment_no_date_exclude_none_false(self): + # Given + name_to_update = "update-me" + new_name = "new-name" + new_description = "this experiment was difficult" + # - create experiment to update + experiment_to_update = Experiment(name=name_to_update, date="2021-10-10") + self.session.add(experiment_to_update) + self.session.commit() + self.session.refresh(experiment_to_update) + json_input = { + "id": experiment_to_update.id, + "name": new_name, + "description": new_description, + } + try: + # When + response = self.client.put( + f"/api/experiments/?exclude_none=false", json=json_input + ) + # Then + self.assertEqual(response.status_code, 422) + finally: + self._delete_entry(experiment_to_update) diff --git a/backend/app/tests/api/endpoints/test_keggs.py b/backend/app/tests/api/endpoints/test_keggs.py new file mode 100644 index 0000000000000000000000000000000000000000..ee407cc7eddbec8a6db71696490113aa73e0a75c --- /dev/null +++ b/backend/app/tests/api/endpoints/test_keggs.py @@ -0,0 +1,127 @@ +from sqlmodel import Session, select + +from app.core.models.kegg import KeggOrthology + +from .base_api_test import BaseApiTests + + +class TestKeggOrthologys(BaseApiTests): + @classmethod + def _create_test_db(cls, session: Session): + test_kegg = KeggOrthology(kegg_id="k1", name="test-kegg") + session.add(test_kegg) + session.commit() + + def test_get_kegg_non_existing(self): + # When + response = self.client.get("/api/kegg-orthology/non-existing") + # Then + self.assertEqual(response.status_code, 404) + + def test_get_kegg_existing(self): + # Given + expected_id = "k1" + expected_data = {"kegg_id": expected_id, "name": "test-kegg"} + # When + response = self.client.get(f"/api/kegg-orthology/{expected_id}") + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + + def test_get_all_keggs(self): + # Given + expected_data = [{"kegg_id": "k1", "name": "test-kegg"}] + # When + response = self.client.get(f"/api/kegg-orthology/") + # Then + self.assertEqual(response.status_code, 200) + self.assertListEqual(response.json(), expected_data) + + def test_create_kegg(self): + # Given + kegg_id = "k9" + kegg_name = "created_kegg" + json_input = {"kegg_id": kegg_id, "name": kegg_name} + expected_data = {"kegg_id": kegg_id, "name": kegg_name} + try: + # When + response = self.client.post("/api/kegg-orthology/", json=json_input) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + kegg = self.session.exec(select(KeggOrthology).where(KeggOrthology.kegg_id == kegg_id)).one() + self._delete_entry(kegg) + + def test_create_existing_kegg(self): + # Given + kegg_id = "k1" + kegg_name = "test-kegg" + json_input = {"kegg_id": kegg_id, "name": kegg_name} + # When + response = self.client.post("/api/kegg-orthology/", json=json_input) + # Then + self.assertEqual(response.status_code, 403) + + def test_delete_kegg_non_existing(self): + # When + response = self.client.delete(f"/api/kegg-orthology/non-existing") + # Then + self.assertEqual(response.status_code, 404) + + def test_delete_kegg(self): + # Given + kegg_id = "k5" + name_to_delete = "delete-me" + # - create kegg to delete + kegg_to_delete = KeggOrthology(kegg_id=kegg_id, name=name_to_delete) + self.session.add(kegg_to_delete) + self.session.commit() + # When + response = self.client.delete(f"/api/kegg-orthology/{kegg_id}") + # Then + self.assertEqual(response.status_code, 200) + + def test_update_kegg_non_existing(self): + # When + json_input = {"kegg_id": "i-dont-exist", "name": "12345"} + response = self.client.put(f"/api/kegg-orthology/", json=json_input) + # Then + self.assertEqual(response.status_code, 404) + + def test_update_kegg(self): + # Given + kegg_id = "k2" + name_to_update = "update-me" + new_name = "new-name" + # - create kegg to update + kegg_to_update = KeggOrthology(kegg_id=kegg_id, name=name_to_update) + self.session.add(kegg_to_update) + self.session.commit() + json_input = {"kegg_id": kegg_id, "name": new_name} + expected_data = json_input + try: + # When + response = self.client.put(f"/api/kegg-orthology/", json=json_input) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + self._delete_entry(kegg_to_update) + + def test_update_kegg_no_name_exclude_none_false(self): + # Given + kegg_id = "k2" + name_to_update = "update-me" + # - create kegg to update + kegg_to_update = KeggOrthology(kegg_id=kegg_id, name=name_to_update) + self.session.add(kegg_to_update) + self.session.commit() + json_input = {"kegg_id": kegg_id} + try: + # When + response = self.client.put(f"/api/kegg-orthology/?exclude_none=false", json=json_input) + # Then + self.assertEqual(response.status_code, 422) + finally: + self._delete_entry(kegg_to_update) diff --git a/backend/app/tests/api/endpoints/test_ncbi_taxonomy.py b/backend/app/tests/api/endpoints/test_ncbi_taxonomy.py new file mode 100644 index 0000000000000000000000000000000000000000..a0e7377e12bccbee88b550da0ef80ef3d47694e6 --- /dev/null +++ b/backend/app/tests/api/endpoints/test_ncbi_taxonomy.py @@ -0,0 +1,158 @@ +from sqlmodel import Session, select + +from app.core.models.ncbi_taxonomy import NcbiTaxonomy + +from .base_api_test import BaseApiTests + + +class TestNcbiTaxonomys(BaseApiTests): + @classmethod + def _create_test_db(cls, session: Session): + test_ncbi_taxonomy = NcbiTaxonomy( + tax_id=1, name="test-ncbi_taxonomy", rank="species" + ) + session.add(test_ncbi_taxonomy) + session.commit() + + def test_get_ncbi_taxonomy_non_existing(self): + # When + response = self.client.get("/api/ncbi_taxonomy/non-existing") + # Then + self.assertEqual(response.status_code, 404) + + def test_get_ncbi_taxonomy_existing(self): + # Given + expected_id = 1 + expected_data = { + "tax_id": expected_id, + "name": "test-ncbi_taxonomy", + "rank": "species", + } + # When + response = self.client.get(f"/api/ncbi_taxonomy/{expected_id}") + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + + def test_get_all_ncbi_taxonomys(self): + # Given + expected_data = [{"tax_id": 1, "name": "test-ncbi_taxonomy", "rank": "species"}] + # When + response = self.client.get(f"/api/ncbi_taxonomy/") + # Then + self.assertEqual(response.status_code, 200) + self.assertListEqual(response.json(), expected_data) + + def test_create_ncbi_taxonomy(self): + # Given + tax_id = 2 + ncbi_taxonomy_name = "created_ncbi_taxonomy" + rank_created = "species" + json_input = { + "tax_id": tax_id, + "name": ncbi_taxonomy_name, + "rank": rank_created, + } + expected_data = { + "tax_id": tax_id, + "name": ncbi_taxonomy_name, + "rank": rank_created, + } + try: + # When + response = self.client.post("/api/ncbi_taxonomy/", json=json_input) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + ncbi_tax = self.session.exec( + select(NcbiTaxonomy).where(NcbiTaxonomy.tax_id == tax_id) + ).one() + self._delete_entry(ncbi_tax) + + def test_create_existing_ncbi_taxonomy(self): + # Given + tax_id = 1 + ncbi_taxonomy_name = "test-ncbi_taxonomy" + json_input = {"tax_id": tax_id, "name": ncbi_taxonomy_name, "rank": "species"} + # When + response = self.client.post("/api/ncbi_taxonomy/", json=json_input) + # Then + self.assertEqual(response.status_code, 403) + + def test_delete_ncbi_taxonomy_non_existing(self): + # When + response = self.client.delete(f"/api/ncbi_taxonomy/35") + # Then + self.assertEqual(response.status_code, 404) + + def test_delete_ncbi_taxonomy(self): + # Given + tax_id = 4 + name_to_delete = "delete-me" + # - create ncbi_taxonomy to delete + ncbi_taxonomy_to_delete = NcbiTaxonomy( + tax_id=tax_id, name=name_to_delete, rank="species" + ) + self.session.add(ncbi_taxonomy_to_delete) + self.session.commit() + # When + response = self.client.delete(f"/api/ncbi_taxonomy/{tax_id}") + # Then + self.assertEqual(response.status_code, 200) + + def test_update_ncbi_taxonomy_non_existing_no_rank_exclude_none_true(self): + # When + json_input = {"tax_id": 29, "name": "12345"} + response = self.client.put( + f"/api/ncbi_taxonomy/?exclude_none=true", json=json_input + ) + # Then + self.assertEqual(response.status_code, 404) + + def test_update_ncbi_taxonomy_no_rank_exclude_none_true(self): + # Given + tax_id = 56 + name_to_update = "update-me" + new_name = "new-name" + rank = "species" + # - create ncbi_taxonomy to update + ncbi_taxonomy_to_update = NcbiTaxonomy( + tax_id=tax_id, name=name_to_update, rank=rank + ) + self.session.add(ncbi_taxonomy_to_update) + self.session.commit() + json_input = {"tax_id": tax_id, "name": new_name} + expected_data = {"tax_id": tax_id, "name": new_name, "rank": rank} + try: + # When + response = self.client.put( + f"/api/ncbi_taxonomy/?exclude_none=true", json=json_input + ) + # Then + self.assertEqual(response.status_code, 200) + self.assertDictEqual(response.json(), expected_data) + finally: + self._delete_entry(ncbi_taxonomy_to_update) + + def test_update_ncbi_taxonomy_no_rank_exclude_none_false(self): + # Given + tax_id = 56 + name_to_update = "update-me" + new_name = "new-name" + rank = "species" + # - create ncbi_taxonomy to update + ncbi_taxonomy_to_update = NcbiTaxonomy( + tax_id=tax_id, name=name_to_update, rank=rank + ) + self.session.add(ncbi_taxonomy_to_update) + self.session.commit() + json_input = {"tax_id": tax_id, "name": new_name} + try: + # When + response = self.client.put( + f"/api/ncbi_taxonomy/?exclude_none=false", json=json_input + ) + self.assertEqual(response.status_code, 422) + finally: + self._delete_entry(ncbi_taxonomy_to_update) diff --git a/backend/app/tests/core/use_cases/crud/test_eggnog.py b/backend/app/tests/core/use_cases/crud/test_eggnog.py new file mode 100644 index 0000000000000000000000000000000000000000..2fdd980d9f46c67b5ef05824a94040a5e1c9e776 --- /dev/null +++ b/backend/app/tests/core/use_cases/crud/test_eggnog.py @@ -0,0 +1,76 @@ +import unittest + +from app.core.use_cases.crud.eggnog import CrudEggnogUseCase +from app.core.schemas.entities.eggnog import EggnogCreate, EggnogUpdate + +GET_ACTION = "Returning eggnog" +GET_ALL_ACTION = "Returning all eggnogs" +CREATE_ACTION = "Creating eggnog" +UPDATE_ACTION = "Updating eggnog" +DELETE_ACTION = "Deleting eggnog" + + +class MockEggnogsRepo: + def get(self, eggnog_id: str): + return GET_ACTION + + def get_all(self): + return GET_ALL_ACTION + + def create(self, eggnog_input: EggnogCreate): + return CREATE_ACTION + + def update(self, eggnog_input: EggnogUpdate): + return UPDATE_ACTION + + def delete(self, eggnog_id: str): + return DELETE_ACTION + + +class TestCrudEggnogUseCase(unittest.TestCase): + def setUp(self) -> None: + self.use_case_test = CrudEggnogUseCase(eggnogs_repo=MockEggnogsRepo()) + + def test_create_eggnog(self): + # Given + eggnog_test = EggnogCreate(eggnog_id="COG01", name="test-eggnog", version="5.0") + expected_output = CREATE_ACTION + # When + test_output = self.use_case_test.create_eggnog(eggnog_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_update_eggnog(self): + # Given + eggnog_test = EggnogCreate(eggnog_id="COG01", name="test-eggnog", version="5.0") + expected_output = UPDATE_ACTION + # When + test_output = self.use_case_test.update_eggnog(eggnog_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_delete_eggnog(self): + # Given + eggnog_id_test = "test-eggnog" + expected_output = DELETE_ACTION + # When + test_output = self.use_case_test.delete_eggnog(eggnog_id_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_eggnog(self): + # Given + eggnog_id_test = "test-eggnog" + expected_output = GET_ACTION + # When + test_output = self.use_case_test.get_eggnog(eggnog_id_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_all(self): + # Given + expected_output = GET_ALL_ACTION + # When + output = self.use_case_test.get_all() + # Then + self.assertEqual(output, expected_output) diff --git a/backend/app/tests/core/use_cases/crud/test_experiment.py b/backend/app/tests/core/use_cases/crud/test_experiment.py new file mode 100644 index 0000000000000000000000000000000000000000..5c4f6b50cd947c6e862da76a54dd23964baa45e0 --- /dev/null +++ b/backend/app/tests/core/use_cases/crud/test_experiment.py @@ -0,0 +1,78 @@ +import unittest + +from app.core.use_cases.crud.experiment import CrudExperimentUseCase +from app.core.schemas.entities.experiment import ExperimentCreate, ExperimentUpdate + +GET_ACTION = "Returning experiment" +GET_ALL_ACTION = "Returning all experiments" +CREATE_ACTION = "Creating experiment" +UPDATE_ACTION = "Updating experiment" +DELETE_ACTION = "Deleting experiment" + + +class MockExperimentsRepo: + def get(self, experiment_id: str): + return GET_ACTION + + def get_all(self): + return GET_ALL_ACTION + + def create(self, experiment_input: ExperimentCreate): + return CREATE_ACTION + + def update(self, experiment_input: ExperimentUpdate): + return UPDATE_ACTION + + def delete(self, experiment_id: str): + return DELETE_ACTION + + +class TestCrudExperimentUseCase(unittest.TestCase): + def setUp(self) -> None: + self.use_case_test = CrudExperimentUseCase( + experiments_repo=MockExperimentsRepo() + ) + + def test_create_experiment(self): + # Given + experiment_test = ExperimentCreate(name="test-experiment", date="2020-10-10") + expected_output = CREATE_ACTION + # When + test_output = self.use_case_test.create_experiment(experiment_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_update_experiment(self): + # Given + experiment_test = ExperimentCreate(name="test-experiment", date="2020-10-10") + expected_output = UPDATE_ACTION + # When + test_output = self.use_case_test.update_experiment(experiment_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_delete_experiment(self): + # Given + experiment_id_test = "test-experiment" + expected_output = DELETE_ACTION + # When + test_output = self.use_case_test.delete_experiment(experiment_id_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_experiment(self): + # Given + experiment_id_test = "test-experiment" + expected_output = GET_ACTION + # When + test_output = self.use_case_test.get_experiment(experiment_id_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_all(self): + # Given + expected_output = GET_ALL_ACTION + # When + output = self.use_case_test.get_all() + # Then + self.assertEqual(output, expected_output) diff --git a/backend/app/tests/core/use_cases/crud/test_kegg.py b/backend/app/tests/core/use_cases/crud/test_kegg.py new file mode 100644 index 0000000000000000000000000000000000000000..c68feb70ce5dbf66ff379ea7c76010493858281f --- /dev/null +++ b/backend/app/tests/core/use_cases/crud/test_kegg.py @@ -0,0 +1,76 @@ +import unittest + +from app.core.use_cases.crud.kegg import CrudKeggOrthologyUseCase +from app.core.schemas.entities.kegg import KeggOrthologyCreate, KeggOrthologyUpdate + +GET_ACTION = "Returning kegg" +GET_ALL_ACTION = "Returning all keggs" +CREATE_ACTION = "Creating kegg" +UPDATE_ACTION = "Updating kegg" +DELETE_ACTION = "Deleting kegg" + + +class MockKeggOrthologysRepo: + def get(self, kegg_id: str): + return GET_ACTION + + def get_all(self): + return GET_ALL_ACTION + + def create(self, kegg_input: KeggOrthologyCreate): + return CREATE_ACTION + + def update(self, kegg_input: KeggOrthologyUpdate): + return UPDATE_ACTION + + def delete(self, kegg_id: str): + return DELETE_ACTION + + +class TestCrudKeggOrthologyUseCase(unittest.TestCase): + def setUp(self) -> None: + self.use_case_test = CrudKeggOrthologyUseCase(keggs_repo=MockKeggOrthologysRepo()) + + def test_create_kegg(self): + # Given + kegg_test = KeggOrthologyCreate(kegg_id="k01", name="test-kegg") + expected_output = CREATE_ACTION + # When + test_output = self.use_case_test.create_kegg(kegg_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_update_kegg(self): + # Given + kegg_test = KeggOrthologyCreate(kegg_id="k01", name="test-kegg") + expected_output = UPDATE_ACTION + # When + test_output = self.use_case_test.update_kegg(kegg_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_delete_kegg(self): + # Given + kegg_id_test = "test-kegg" + expected_output = DELETE_ACTION + # When + test_output = self.use_case_test.delete_kegg(kegg_id_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_kegg(self): + # Given + kegg_id_test = "test-kegg" + expected_output = GET_ACTION + # When + test_output = self.use_case_test.get_kegg(kegg_id_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_all(self): + # Given + expected_output = GET_ALL_ACTION + # When + output = self.use_case_test.get_all() + # Then + self.assertEqual(output, expected_output) diff --git a/backend/app/tests/core/use_cases/crud/test_ncbi_taxonomy.py b/backend/app/tests/core/use_cases/crud/test_ncbi_taxonomy.py new file mode 100644 index 0000000000000000000000000000000000000000..a72c333b1dd0e9fced69eea2247250b4354efce0 --- /dev/null +++ b/backend/app/tests/core/use_cases/crud/test_ncbi_taxonomy.py @@ -0,0 +1,85 @@ +import unittest + +from app.core.use_cases.crud.ncbi_taxonomy import CrudNcbiTaxonomyUseCase +from app.core.schemas.entities.ncbi_taxonomy import ( + NcbiTaxonomyCreate, + NcbiTaxonomyUpdate, +) + +GET_ACTION = "Returning ncbi_taxonomy" +GET_ALL_ACTION = "Returning all ncbi_taxonomy entries" +CREATE_ACTION = "Creating ncbi_taxonomy" +UPDATE_ACTION = "Updating ncbi_taxonomy" +DELETE_ACTION = "Deleting ncbi_taxonomy" + + +class MockNcbiTaxonomysRepo: + def get(self, ncbi_taxonomy_name: str): + return GET_ACTION + + def get_all(self): + return GET_ALL_ACTION + + def create(self, ncbi_taxonomy_input: NcbiTaxonomyCreate): + return CREATE_ACTION + + def update(self, ncbi_taxonomy_input: NcbiTaxonomyUpdate): + return UPDATE_ACTION + + def delete(self, ncbi_taxonomy_name: str): + return DELETE_ACTION + + +class TestCrudNcbiTaxonomyUseCase(unittest.TestCase): + def setUp(self) -> None: + self.use_case_test = CrudNcbiTaxonomyUseCase( + ncbi_taxonomys_repo=MockNcbiTaxonomysRepo() + ) + + def test_create_ncbi_taxonomy(self): + # Given + ncbi_taxonomy_test = NcbiTaxonomyCreate( + tax_id=1, rank="species", name="test-ncbi_taxonomy" + ) + expected_output = CREATE_ACTION + # When + test_output = self.use_case_test.create_ncbi_taxonomy(ncbi_taxonomy_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_update_ncbi_taxonomy(self): + # Given + ncbi_taxonomy_test = NcbiTaxonomyCreate( + tax_id=1, rank="species", name="test-ncbi_taxonomy" + ) + expected_output = UPDATE_ACTION + # When + test_output = self.use_case_test.update_ncbi_taxonomy(ncbi_taxonomy_test) + # Then + self.assertEqual(test_output, expected_output) + + def test_delete_ncbi_taxonomy(self): + # Given + ncbi_taxonomy_test_name = "test-ncbi_taxonomy" + expected_output = DELETE_ACTION + # When + test_output = self.use_case_test.delete_ncbi_taxonomy(ncbi_taxonomy_test_name) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_ncbi_taxonomy(self): + # Given + ncbi_taxonomy_test_name = "test-ncbi_taxonomy" + expected_output = GET_ACTION + # When + test_output = self.use_case_test.get_ncbi_taxonomy(ncbi_taxonomy_test_name) + # Then + self.assertEqual(test_output, expected_output) + + def test_get_all(self): + # Given + expected_output = GET_ALL_ACTION + # When + output = self.use_case_test.get_all() + # Then + self.assertEqual(output, expected_output)