Skip to content

Commit d39c58e

Browse files
authored
Merge pull request #379 from VariantEffect/release-2025.0.0
Release 2025.0.0
2 parents ce647e1 + ab7d946 commit d39c58e

File tree

9 files changed

+212
-27
lines changed

9 files changed

+212
-27
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "mavedb"
7-
version = "2024.4.3"
7+
version = "2025.0.0"
88
description = "API for MaveDB, the database of Multiplexed Assays of Variant Effect."
99
license = "AGPL-3.0-only"
1010
readme = "README.md"

src/mavedb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
logger = module_logging.getLogger(__name__)
77

88
__project__ = "mavedb-api"
9-
__version__ = "2024.4.3"
9+
__version__ = "2025.0.0"
1010

1111
logger.info(f"MaveDB {__version__}")

src/mavedb/lib/experiments.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
from typing import Optional
33

4-
from sqlalchemy import func, or_
4+
from sqlalchemy import func, or_, not_
55
from sqlalchemy.orm import Session
66

77
from mavedb.lib.logging.context import logging_context, save_to_logging_context
@@ -99,6 +99,19 @@ def search_experiments(
9999
)
100100
)
101101

102+
if search.meta_analysis is not None:
103+
if not search.meta_analysis:
104+
query = query.filter(
105+
or_(
106+
# Keep experiments without any score sets
107+
not_(Experiment.score_sets.any()),
108+
# Keep experiments where score sets exist but have no meta_analyzes_score_sets
109+
Experiment.score_sets.any(not_(ScoreSet.meta_analyzes_score_sets.any()))
110+
)
111+
)
112+
else:
113+
query = query.filter(Experiment.score_sets.any(ScoreSet.meta_analyzes_score_sets.any()))
114+
102115
items: list[Experiment] = query.order_by(Experiment.urn, Experiment.title).all()
103116
if not items:
104117
items = []

src/mavedb/routers/experiments.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from fastapi import APIRouter, Depends, HTTPException
88
from fastapi.encoders import jsonable_encoder
99
from sqlalchemy.orm import Session
10+
from sqlalchemy import or_
1011

1112
from mavedb import deps
1213
from mavedb.lib.authentication import UserData, get_current_user
@@ -43,7 +44,7 @@
4344
)
4445

4546

46-
# TODO: Rewrite this function.
47+
# None of any part calls this function. Feel free to modify it if we need it in the future.
4748
@router.get(
4849
"/experiments/",
4950
status_code=200,
@@ -53,30 +54,26 @@
5354
def list_experiments(
5455
*,
5556
editable: Optional[bool] = None,
56-
q: Optional[str] = None,
5757
db: Session = Depends(deps.get_db),
5858
user_data: Optional[UserData] = Depends(get_current_user),
5959
) -> list[Experiment]:
6060
"""
6161
List experiments.
6262
"""
6363
query = db.query(Experiment)
64-
if q is not None:
65-
save_to_logging_context({"query_string": q})
6664

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

71-
if len(q) > 0:
72-
logger.debug(msg="Listing experiments for the current user.", extra=logging_context())
73-
query = query.filter(
74-
Experiment.created_by_id == user_data.user.id
75-
) # .filter(Experiment.published_date is None)
76-
# else:
77-
# query = query.filter(Experiment.created_by_id == user.id).filter(Experiment.published_date is None)
78-
else:
79-
logger.debug(msg="No query string was provided; Listing all experiments.", extra=logging_context())
70+
logger.debug(msg="Listing experiments for the current user.", extra=logging_context())
71+
query = query.filter(
72+
or_(
73+
Experiment.created_by_id == user_data.user.id,
74+
Experiment.contributors.any(Contributor.orcid_id == user_data.user.username)
75+
)
76+
)
8077

8178
items = query.order_by(Experiment.urn).all()
8279
return items

src/mavedb/routers/hgvs.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
router = APIRouter(
1313
prefix="/api/v1/hgvs",
14-
tags=["hgvs"],
14+
tags=["transcripts"],
1515
responses={404: {"description": "Not found"}},
1616
)
1717

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

8787

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

100100

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

113113

114-
@router.get("/transcripts/protein/{transcript}", status_code=200, response_model=str)
114+
@router.get("/protein/{transcript}", status_code=200, response_model=str)
115115
def convert_to_protein(transcript: str, hdp: RESTDataProvider = Depends(hgvs_data_provider)) -> str:
116116
"""
117117
Convert a provided transcript from it's nucleotide accession identifier to its protein accession identifier

src/mavedb/view_models/search.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ExperimentsSearch(BaseModel):
1111
publication_identifiers: Optional[list[str]]
1212
keywords: Optional[list[str]]
1313
text: Optional[str]
14+
meta_analysis: Optional[bool]
1415

1516

1617
class ScoreSetsSearch(BaseModel):

tests/routers/test_experiments.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,58 @@ def test_search_my_experiments(session, client, setup_router_db):
973973
assert response.json()[0]["title"] == experiment["title"]
974974

975975

976+
def test_search_meta_analysis_experiment(session, data_provider, client, setup_router_db, data_files):
977+
experiment = create_experiment(client)
978+
score_set = create_seq_score_set_with_variants(
979+
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
980+
)
981+
982+
score_set = (client.post(f"/api/v1/score-sets/{score_set['urn']}/publish")).json()
983+
meta_score_set = create_seq_score_set_with_variants(
984+
client,
985+
session,
986+
data_provider,
987+
None,
988+
data_files / "scores.csv",
989+
update={"title": "Test Meta Analysis", "metaAnalyzesScoreSetUrns": [score_set["urn"]]},
990+
)
991+
992+
meta_score_set = (client.post(f"/api/v1/score-sets/{meta_score_set['urn']}/publish")).json()
993+
score_set_refresh = (client.get(f"/api/v1/score-sets/{score_set['urn']}")).json()
994+
search_payload = {"metaAnalysis": True}
995+
response = client.post("/api/v1/me/experiments/search", json=search_payload)
996+
assert response.status_code == 200
997+
response_data = response.json()
998+
assert any(item["urn"] == meta_score_set["experiment"]["urn"] for item in response_data)
999+
assert all(item["urn"] != score_set_refresh["experiment"]["urn"] for item in response_data)
1000+
1001+
1002+
def test_search_exclude_meta_analysis_experiment(session, data_provider, client, setup_router_db, data_files):
1003+
experiment = create_experiment(client)
1004+
score_set = create_seq_score_set_with_variants(
1005+
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
1006+
)
1007+
1008+
score_set = (client.post(f"/api/v1/score-sets/{score_set['urn']}/publish")).json()
1009+
meta_score_set = create_seq_score_set_with_variants(
1010+
client,
1011+
session,
1012+
data_provider,
1013+
None,
1014+
data_files / "scores.csv",
1015+
update={"title": "Test Meta Analysis", "metaAnalyzesScoreSetUrns": [score_set["urn"]]},
1016+
)
1017+
1018+
meta_score_set = (client.post(f"/api/v1/score-sets/{meta_score_set['urn']}/publish")).json()
1019+
score_set_refresh = (client.get(f"/api/v1/score-sets/{score_set['urn']}")).json()
1020+
search_payload = {"metaAnalysis": False}
1021+
response = client.post("/api/v1/me/experiments/search", json=search_payload)
1022+
assert response.status_code == 200
1023+
response_data = response.json()
1024+
assert any(item["urn"] == score_set_refresh["experiment"]["urn"] for item in response_data)
1025+
assert all(item["urn"] != meta_score_set["experiment"]["urn"] for item in response_data)
1026+
1027+
9761028
def test_search_score_sets_for_experiments(session, client, setup_router_db, data_files, data_provider):
9771029
experiment = create_experiment(client)
9781030
score_set_pub = create_seq_score_set_with_variants(

tests/routers/test_hgvs.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def test_hgvs_gene_transcript_valid(client, setup_router_db):
122122
json={"results": [{"hgnc": f"{VALID_GENE}", "tx_ac": VALID_TRANSCRIPT}]},
123123
)
124124

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

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

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

136136
assert m.called
137137
assert response.status_code == 404
138138

139139

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

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

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

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

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

168168
assert m.called
169169

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

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

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

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

190190
assert m.called
191191
assert response.status_code == 404

0 commit comments

Comments
 (0)