Skip to content

Commit

Permalink
Merge pull request #32 from TheJacksonLaboratory/G3-196-gene-mapping-…
Browse files Browse the repository at this point in the history
…endpoint

G3 196 gene mapping endpoint
  • Loading branch information
francastell authored Feb 29, 2024
2 parents 61af93a + c70c624 commit 61e4350
Show file tree
Hide file tree
Showing 9 changed files with 514 additions and 254 deletions.
370 changes: 185 additions & 185 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "geneweaver-api"
version = "0.2.1a1"
version = "0.3.0a0"
description = "The Geneweaver API"
authors = ["Jax Computational Sciences <cssc@jax.org>"]
readme = "README.md"
Expand All @@ -17,7 +17,7 @@ python = "^3.9"
geneweaver-core = "^0.9.0a1"
fastapi = {extras = ["all"], version = "^0.99.1"}
uvicorn = {extras = ["standard"], version = "^0.24.0"}
geneweaver-db = "^0.3.0a4"
geneweaver-db = "^0.3.0a10"
psycopg-pool = "^3.1.7"
requests = "^2.31.0"
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
Expand Down
34 changes: 29 additions & 5 deletions src/geneweaver/api/controller/genes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@

from fastapi import APIRouter, Depends
from geneweaver.api import dependencies as deps
from geneweaver.api.schemas.apimodels import GeneIdMappingReq, GeneIdMappingResp
from geneweaver.api.schemas.apimodels import (
GeneIdHomologReq,
GeneIdHomologResp,
GeneIdMappingReq,
GeneIdMappingResp,
)
from geneweaver.api.services import genes as genes_service

router = APIRouter(prefix="/genes", tags=["genes"])


@router.post("/homologs", response_model=GeneIdMappingResp)
@router.post("/homologs", response_model=GeneIdHomologResp)
def get_related_gene_ids(
gene_id_mapping: GeneIdMappingReq,
gene_id_mapping: GeneIdHomologReq,
cursor: Optional[deps.Cursor] = Depends(deps.cursor),
) -> GeneIdMappingResp:
) -> GeneIdHomologResp:
"""Get homologous gene ids given list of gene ids."""
response = genes_service.get_gene_mapping(
response = genes_service.get_homolog_ids(
cursor,
gene_id_mapping.source_ids,
gene_id_mapping.target_gene_id_type,
Expand All @@ -24,6 +29,25 @@ def get_related_gene_ids(
gene_id_mapping.source_species,
)

resp_id_map = response.get("ids_map")
gene_id_mapping_resp = GeneIdHomologResp(gene_ids_map=resp_id_map)

return gene_id_mapping_resp


@router.post("/mapping", response_model=GeneIdMappingResp)
def get_genes_mapping(
gene_id_mapping: GeneIdMappingReq,
cursor: Optional[deps.Cursor] = Depends(deps.cursor),
) -> GeneIdHomologResp:
"""Get gene ids mapping."""
response = genes_service.get_gene_mapping(
cursor,
gene_id_mapping.source_ids,
gene_id_mapping.species,
gene_id_mapping.target_gene_id_type,
)

resp_id_map = response.get("ids_map")
gene_id_mapping_resp = GeneIdMappingResp(gene_ids_map=resp_id_map)

Expand Down
24 changes: 19 additions & 5 deletions src/geneweaver/api/schemas/apimodels.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
"""Models for API requests."""
from typing import Iterable, Optional
from typing import Iterable, List, Optional

from geneweaver.core.enum import GeneIdentifier, Species
from pydantic import BaseModel


class GeneIdMappingResp(BaseModel):
"""Model for gene id mapping."""
class GeneIdHomologResp(BaseModel):
"""Model for homolog gene id mapping."""

gene_ids_map: list[dict]


class GeneIdMappingReq(BaseModel):
"""Model for gene id mapping request."""
class GeneIdHomologReq(BaseModel):
"""Model for homolog gene id mapping request."""

source_ids: Iterable[str]
target_gene_id_type: GeneIdentifier
source_gene_id_type: Optional[GeneIdentifier] = None
target_species: Optional[Species] = None
source_species: Optional[Species] = None


class GeneIdMappingResp(BaseModel):
"""Model for gene id mapping."""

gene_ids_map: list[dict]


class GeneIdMappingReq(BaseModel):
"""Model for gene id mapping request."""

source_ids: List[str]
target_gene_id_type: GeneIdentifier
species: Species
31 changes: 29 additions & 2 deletions src/geneweaver/api/services/genes.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Service methods for genes."""

from typing import Iterable, Optional
from typing import Iterable, List, Optional

from fastapi.logger import logger
from geneweaver.core.enum import GeneIdentifier, Species
from geneweaver.db import gene as db_gene
from psycopg import Cursor


def get_gene_mapping(
def get_homolog_ids(
cursor: Cursor,
gene_id_list: Iterable,
target_gene_id_type: GeneIdentifier,
Expand Down Expand Up @@ -45,3 +45,30 @@ def get_gene_mapping(
raise err

return {"ids_map": ids_map}


def get_gene_mapping(
cursor: Cursor,
source_ids: List[str],
species: Species,
target_gene_id_type: GeneIdentifier,
) -> dict:
"""Get gene identifier mappings.
Get gene id mappings based on target GeneIdentifier id.
@param cursor: DB Cursor
@param source_ids: list of gene ids to search
@param target_gene_id_type: gene identifier
@param species: target species identifier
@return: dictionary with id mappings.
"""
ids_map = None
try:
ids_map = db_gene.mapping(cursor, source_ids, species, target_gene_id_type)

except Exception as err:
logger.error(err)
raise err

return {"ids_map": ids_map}
83 changes: 65 additions & 18 deletions tests/controllers/test_genes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,120 @@
import json
from unittest.mock import patch

from tests.data import test_gene_data
from tests.data import test_gene_homolog_data, test_gene_mapping_data

gene_ids_map_req_1 = test_gene_data.get("gene_ids_map_req_1_gene_ids_species")
gene_ids_map_resp_1 = test_gene_data.get("gene_ids_map_resp_1")
# gene homolog ids test data
gene_ids_homolog_req_1 = test_gene_homolog_data.get(
"gene_ids_map_req_1_gene_ids_species"
)
gene_ids_homolog_resp_1 = test_gene_homolog_data.get("gene_ids_map_resp_1")

# gene id mapping test data
gene_id_mapping_req_1 = test_gene_mapping_data.get("gene_mapping_req_1")
gene_id_mapping_resp_1 = test_gene_mapping_data.get("gene_mapping_resp_1")

@patch("geneweaver.api.services.genes.get_gene_mapping")

@patch("geneweaver.api.services.genes.get_homolog_ids")
def test_gene_id_mapping_response_post_req(mock_gene_id_mapping, client):
"""Test genes homologous ids url and post request."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_ids_map_resp_1.get("gene_ids_map")
"ids_map": gene_ids_homolog_resp_1.get("gene_ids_map")
}

response = client.post(
url="/api/genes/homologs", data=json.dumps(gene_ids_map_req_1)
url="/api/genes/homologs", data=json.dumps(gene_ids_homolog_req_1)
)
print(response)
assert response.status_code == 200
assert response.json() == gene_ids_map_resp_1
assert response.json() == gene_ids_homolog_resp_1


@patch("geneweaver.api.services.genes.get_gene_mapping")
@patch("geneweaver.api.services.genes.get_homolog_ids")
def test_gene_id_mapping_invalid_url(mock_gene_id_mapping, client):
"""Test genes homologous ids invalid url."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_ids_map_resp_1.get("gene_ids_map")
"ids_map": gene_ids_homolog_resp_1.get("gene_ids_map")
}

response = client.post(
url="/api/genes/homologous-ids", data=json.dumps(gene_ids_map_req_1)
url="/api/genes/homologous-ids", data=json.dumps(gene_ids_homolog_req_1)
)
print(response)
assert response.status_code == 404


@patch("geneweaver.api.services.genes.get_gene_mapping")
@patch("geneweaver.api.services.genes.get_homolog_ids")
def test_gene_id_mapping_invalid_post_data_(mock_gene_id_mapping, client):
"""Test genes homologous ids url and invalid post data request."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_ids_map_resp_1.get("gene_ids_map")
"ids_map": gene_ids_homolog_resp_1.get("gene_ids_map")
}

response = client.post(url="/api/genes/homologs", data=json.dumps({"test": "test"}))
assert response.status_code == 422


@patch("geneweaver.api.services.genes.get_gene_mapping")
@patch("geneweaver.api.services.genes.get_homolog_ids")
def test_gene_id_mapping_missing_target_gene_identifier(mock_gene_id_mapping, client):
"""Test genes homologous ids url and invalid post data request."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_ids_map_resp_1.get("gene_ids_map")
"ids_map": gene_ids_homolog_resp_1.get("gene_ids_map")
}

req_gene_map = gene_ids_map_req_1.copy()
req_gene_map = gene_ids_homolog_req_1.copy()
req_gene_map.pop("target_gene_id_type")
response = client.post(url="/api/genes/homologs", data=json.dumps(req_gene_map))
assert response.status_code == 422


@patch("geneweaver.api.services.genes.get_gene_mapping")
@patch("geneweaver.api.services.genes.get_homolog_ids")
def test_gene_id_mapping_missing_gene_list(mock_gene_id_mapping, client):
"""Test genes homologous ids url and invalid post data request."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_ids_map_resp_1.get("gene_ids_map")
"ids_map": gene_ids_homolog_resp_1.get("gene_ids_map")
}

req_gene_map = gene_ids_map_req_1.copy()
req_gene_map = gene_ids_homolog_req_1.copy()
req_gene_map.pop("source_ids")
response = client.post(url="/api/genes/homologs", data=json.dumps(req_gene_map))
assert response.status_code == 422


@patch("geneweaver.api.services.genes.get_gene_mapping")
def test_gene_mapping_valid_post_req(mock_gene_id_mapping, client):
"""Test genes mapping ids url and post request."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_id_mapping_resp_1.get("gene_ids_map")
}

response = client.post(
url="/api/genes/mapping", data=json.dumps(gene_id_mapping_req_1)
)
print(response)
assert response.status_code == 200
assert response.json() == gene_id_mapping_resp_1


@patch("geneweaver.api.services.genes.get_gene_mapping")
def test_gene_mapping_invalid_url(mock_gene_id_mapping, client):
"""Test genes mapping ids invalid url."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_id_mapping_resp_1.get("gene_ids_map")
}

response = client.post(
url="/api/genes/mappings", data=json.dumps(gene_id_mapping_req_1)
)
print(response)
assert response.status_code == 404


@patch("geneweaver.api.services.genes.get_gene_mapping")
def test_gene_mapping_invalid_post_data_(mock_gene_id_mapping, client):
"""Test genes ids mapping url and invalid post data request."""
mock_gene_id_mapping.return_value = {
"ids_map": gene_id_mapping_resp_1.get("gene_ids_map")
}

response = client.post(url="/api/genes/mapping", data=json.dumps({"test": "test"}))
assert response.status_code == 422
24 changes: 21 additions & 3 deletions tests/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"tests.data", "homologus_ids.json"
)

gene_id_mapping_json = importlib.resources.read_text("tests.data", "gene_maping.json")

publications_json = importlib.resources.read_text("tests.data", "publications.json")

jwt_test_keys_json = importlib.resources.read_text(
Expand All @@ -34,8 +36,8 @@
),
}

# Gene test data
test_gene_data = {
# Gene homolog ids test data
test_gene_homolog_data = {
"gene_ids_map_req_1_gene_ids_species": json.loads(gene_homologus_ids_json).get(
"gene_ids_map_req_1_gene_ids_species"
),
Expand All @@ -56,7 +58,23 @@
),
}

# publication test data
# Gene mapping test data
test_gene_mapping_data = {
"gene_mapping_req_1": json.loads(gene_id_mapping_json).get(
"gene_mapping_request_1"
),
"gene_mapping_resp_1": json.loads(gene_id_mapping_json).get(
"gene_mapping_response_1"
),
"gene_mapping_req_2": json.loads(gene_id_mapping_json).get(
"gene_mapping_request_2"
),
"gene_mapping_resp_2": json.loads(gene_id_mapping_json).get(
"gene_mapping_response_2"
),
}

# Publication test data
test_publication_data = {
"publication_by_id": json.loads(publications_json).get("publication_by_id"),
"publication_by_pubmed_id": json.loads(publications_json).get(
Expand Down
43 changes: 43 additions & 0 deletions tests/data/gene_maping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"gene_mapping_request_1": {
"source_ids": [
"DAB1",
"SLC6A9"
],
"target_gene_id_type": "Ensemble Gene",
"species": "Mus Musculus"
},
"gene_mapping_response_1": {
"gene_ids_map": []
},
"gene_mapping_request_2": {
"source_ids": [
"ENSG00000000003",
"ENSG00000001497",
"ENSG00000002586",
"ENSG00000003096"
],
"target_gene_id_type": "Ensemble Gene",
"species": "Homo Sapiens"
},
"gene_mapping_response_2": {
"gene_ids_map": [
{
"original_ref_id": "ENSG00000000003",
"mapped_ref_id": "TSPAN6"
},
{
"original_ref_id": "ENSG00000001497",
"mapped_ref_id": "LAS1L"
},
{
"original_ref_id": "ENSG00000002586",
"mapped_ref_id": "CD99"
},
{
"original_ref_id": "ENSG00000003096",
"mapped_ref_id": "KLHL13"
}
]
}
}
Loading

0 comments on commit 61e4350

Please sign in to comment.