Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "mavedb"
version = "2024.4.3"
version = "2025.0.0"
description = "API for MaveDB, the database of Multiplexed Assays of Variant Effect."
license = "AGPL-3.0-only"
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion src/mavedb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
logger = module_logging.getLogger(__name__)

__project__ = "mavedb-api"
__version__ = "2024.4.3"
__version__ = "2025.0.0"

logger.info(f"MaveDB {__version__}")
15 changes: 14 additions & 1 deletion src/mavedb/lib/experiments.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from typing import Optional

from sqlalchemy import func, or_
from sqlalchemy import func, or_, not_
from sqlalchemy.orm import Session

from mavedb.lib.logging.context import logging_context, save_to_logging_context
Expand Down Expand Up @@ -99,6 +99,19 @@ def search_experiments(
)
)

if search.meta_analysis is not None:
if not search.meta_analysis:
query = query.filter(
or_(
# Keep experiments without any score sets
not_(Experiment.score_sets.any()),
# Keep experiments where score sets exist but have no meta_analyzes_score_sets
Experiment.score_sets.any(not_(ScoreSet.meta_analyzes_score_sets.any()))
)
)
else:
query = query.filter(Experiment.score_sets.any(ScoreSet.meta_analyzes_score_sets.any()))

items: list[Experiment] = query.order_by(Experiment.urn, Experiment.title).all()
if not items:
items = []
Expand Down
23 changes: 10 additions & 13 deletions src/mavedb/routers/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.encoders import jsonable_encoder
from sqlalchemy.orm import Session
from sqlalchemy import or_

from mavedb import deps
from mavedb.lib.authentication import UserData, get_current_user
Expand Down Expand Up @@ -43,7 +44,7 @@
)


# TODO: Rewrite this function.
# None of any part calls this function. Feel free to modify it if we need it in the future.
@router.get(
"/experiments/",
status_code=200,
Expand All @@ -53,30 +54,26 @@
def list_experiments(
*,
editable: Optional[bool] = None,
q: Optional[str] = None,
db: Session = Depends(deps.get_db),
user_data: Optional[UserData] = Depends(get_current_user),
) -> list[Experiment]:
"""
List experiments.
"""
query = db.query(Experiment)
if q is not None:
save_to_logging_context({"query_string": q})

if editable:
if user_data is None or user_data.user is None:
logger.debug(msg="User is anonymous; Cannot list their experiments.", extra=logging_context())
return []

if len(q) > 0:
logger.debug(msg="Listing experiments for the current user.", extra=logging_context())
query = query.filter(
Experiment.created_by_id == user_data.user.id
) # .filter(Experiment.published_date is None)
# else:
# query = query.filter(Experiment.created_by_id == user.id).filter(Experiment.published_date is None)
else:
logger.debug(msg="No query string was provided; Listing all experiments.", extra=logging_context())
logger.debug(msg="Listing experiments for the current user.", extra=logging_context())
query = query.filter(
or_(
Experiment.created_by_id == user_data.user.id,
Experiment.contributors.any(Contributor.orcid_id == user_data.user.username)
)
)

items = query.order_by(Experiment.urn).all()
return items
Expand Down
8 changes: 4 additions & 4 deletions src/mavedb/routers/hgvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

router = APIRouter(
prefix="/api/v1/hgvs",
tags=["hgvs"],
tags=["transcripts"],
responses={404: {"description": "Not found"}},
)

Expand Down Expand Up @@ -85,7 +85,7 @@ def gene_info(gene: str, hdp: RESTDataProvider = Depends(hgvs_data_provider)) ->
return gene_info


@router.get("/transcripts/gene/{gene}", status_code=200, response_model=list[str])
@router.get("/gene/{gene}", status_code=200, response_model=list[str])
def list_transcripts_for_gene(gene: str, hdp: RESTDataProvider = Depends(hgvs_data_provider)) -> list[str]:
"""
List transcripts associated with a particular gene
Expand All @@ -98,7 +98,7 @@ def list_transcripts_for_gene(gene: str, hdp: RESTDataProvider = Depends(hgvs_da
return list(transcripts)


@router.get("/transcripts/{transcript}", status_code=200, response_model=dict[str, Any])
@router.get("/{transcript}", status_code=200, response_model=dict[str, Any])
def transcript_info(transcript: str, hdp: RESTDataProvider = Depends(hgvs_data_provider)) -> dict[str, Any]:
"""
List transcript information for a particular transcript
Expand All @@ -111,7 +111,7 @@ def transcript_info(transcript: str, hdp: RESTDataProvider = Depends(hgvs_data_p
return transcript_info


@router.get("/transcripts/protein/{transcript}", status_code=200, response_model=str)
@router.get("/protein/{transcript}", status_code=200, response_model=str)
def convert_to_protein(transcript: str, hdp: RESTDataProvider = Depends(hgvs_data_provider)) -> str:
"""
Convert a provided transcript from it's nucleotide accession identifier to its protein accession identifier
Expand Down
1 change: 1 addition & 0 deletions src/mavedb/view_models/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class ExperimentsSearch(BaseModel):
publication_identifiers: Optional[list[str]]
keywords: Optional[list[str]]
text: Optional[str]
meta_analysis: Optional[bool]


class ScoreSetsSearch(BaseModel):
Expand Down
52 changes: 52 additions & 0 deletions tests/routers/test_experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,58 @@ def test_search_my_experiments(session, client, setup_router_db):
assert response.json()[0]["title"] == experiment["title"]


def test_search_meta_analysis_experiment(session, data_provider, client, setup_router_db, data_files):
experiment = create_experiment(client)
score_set = create_seq_score_set_with_variants(
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
)

score_set = (client.post(f"/api/v1/score-sets/{score_set['urn']}/publish")).json()
meta_score_set = create_seq_score_set_with_variants(
client,
session,
data_provider,
None,
data_files / "scores.csv",
update={"title": "Test Meta Analysis", "metaAnalyzesScoreSetUrns": [score_set["urn"]]},
)

meta_score_set = (client.post(f"/api/v1/score-sets/{meta_score_set['urn']}/publish")).json()
score_set_refresh = (client.get(f"/api/v1/score-sets/{score_set['urn']}")).json()
search_payload = {"metaAnalysis": True}
response = client.post("/api/v1/me/experiments/search", json=search_payload)
assert response.status_code == 200
response_data = response.json()
assert any(item["urn"] == meta_score_set["experiment"]["urn"] for item in response_data)
assert all(item["urn"] != score_set_refresh["experiment"]["urn"] for item in response_data)


def test_search_exclude_meta_analysis_experiment(session, data_provider, client, setup_router_db, data_files):
experiment = create_experiment(client)
score_set = create_seq_score_set_with_variants(
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
)

score_set = (client.post(f"/api/v1/score-sets/{score_set['urn']}/publish")).json()
meta_score_set = create_seq_score_set_with_variants(
client,
session,
data_provider,
None,
data_files / "scores.csv",
update={"title": "Test Meta Analysis", "metaAnalyzesScoreSetUrns": [score_set["urn"]]},
)

meta_score_set = (client.post(f"/api/v1/score-sets/{meta_score_set['urn']}/publish")).json()
score_set_refresh = (client.get(f"/api/v1/score-sets/{score_set['urn']}")).json()
search_payload = {"metaAnalysis": False}
response = client.post("/api/v1/me/experiments/search", json=search_payload)
assert response.status_code == 200
response_data = response.json()
assert any(item["urn"] == score_set_refresh["experiment"]["urn"] for item in response_data)
assert all(item["urn"] != meta_score_set["experiment"]["urn"] for item in response_data)


def test_search_score_sets_for_experiments(session, client, setup_router_db, data_files, data_provider):
experiment = create_experiment(client)
score_set_pub = create_seq_score_set_with_variants(
Expand Down
14 changes: 7 additions & 7 deletions tests/routers/test_hgvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def test_hgvs_gene_transcript_valid(client, setup_router_db):
json={"results": [{"hgnc": f"{VALID_GENE}", "tx_ac": VALID_TRANSCRIPT}]},
)

response = client.get(f"/api/v1/hgvs/transcripts/gene/{VALID_GENE}")
response = client.get(f"/api/v1/hgvs/gene/{VALID_GENE}")
assert response.status_code == 200
assert VALID_TRANSCRIPT in response.json()

Expand All @@ -131,15 +131,15 @@ def test_hgvs_gene_transcript_invalid(client, setup_router_db):
with requests_mock.mock() as m:
m.get(f"https://cdot.cc/transcripts/gene/{INVALID_GENE}", status_code=404)

response = client.get(f"/api/v1/hgvs/transcripts/gene/{INVALID_GENE}")
response = client.get(f"/api/v1/hgvs/gene/{INVALID_GENE}")

assert m.called
assert response.status_code == 404


def test_hgvs_transcript_valid(client, setup_router_db):
with patch.object(cdot.hgvs.dataproviders.RESTDataProvider, "_get_transcript", return_value=TEST_CDOT_TRANSCRIPT):
response = client.get(f"/api/v1/hgvs/transcripts/{VALID_TRANSCRIPT}")
response = client.get(f"/api/v1/hgvs/{VALID_TRANSCRIPT}")

assert response.status_code == 200
assert response.json()["hgnc"] == VALID_GENE
Expand All @@ -149,7 +149,7 @@ def test_hgvs_transcript_invalid(client, setup_router_db):
with requests_mock.mock() as m:
m.get(f"https://cdot.cc/transcript/{INVALID_TRANSCRIPT}", status_code=404)

response = client.get(f"/api/v1/hgvs/transcripts/{INVALID_TRANSCRIPT}")
response = client.get(f"/api/v1/hgvs/{INVALID_TRANSCRIPT}")

assert m.called
assert response.status_code == 404
Expand All @@ -163,7 +163,7 @@ def test_hgvs_transcript_protein_valid(client, setup_router_db):
json={"biotype": ["protein_coding"], "gene_name": "A2M", "gene_vesion": "2", "protein": "NP_000005.2"},
)

response = client.get(f"/api/v1/hgvs/transcripts/protein/{HAS_PROTEIN_ACCESSION}")
response = client.get(f"/api/v1/hgvs/protein/{HAS_PROTEIN_ACCESSION}")

assert m.called

Expand All @@ -175,7 +175,7 @@ def test_hgvs_transcript_protein_no_protein(client, setup_router_db):
with requests_mock.mock() as m:
m.get(f"https://cdot.cc/transcript/{SMALL_ACCESSION}", status_code=404)

response = client.get(f"/api/v1/hgvs/transcripts/protein/{SMALL_ACCESSION}")
response = client.get(f"/api/v1/hgvs/protein/{SMALL_ACCESSION}")

assert m.called
assert response.status_code == 404
Expand All @@ -185,7 +185,7 @@ def test_hgvs_transcript_protein_invalid(client, setup_router_db):
with requests_mock.mock() as m:
m.get(f"https://cdot.cc/transcript/{INVALID_ACCESSION}", status_code=404)

response = client.get(f"/api/v1/hgvs/transcripts/protein/{INVALID_ACCESSION}")
response = client.get(f"/api/v1/hgvs/protein/{INVALID_ACCESSION}")

assert m.called
assert response.status_code == 404
Loading
Loading