Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 0 additions & 2 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ services:
- "8002:8000"
volumes:
- .:/code
- mavedb-seqrepo-dev:/usr/local/share/seqrepo

worker:
image: mavedb-api/mavedb-worker:dev
Expand All @@ -42,7 +41,6 @@ services:
LOG_CONFIG: dev
volumes:
- .:/code
- mavedb-seqrepo-dev:/usr/local/share/seqrepo
depends_on:
- db
- redis
Expand Down
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 = "2025.1.1"
version = "2025.1.2"
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__ = "2025.1.1"
__version__ = "2025.1.2"

logger.info(f"MaveDB {__version__}")
33 changes: 32 additions & 1 deletion src/mavedb/lib/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
from sqlalchemy import func, or_, not_
from sqlalchemy.orm import Session

from mavedb.lib.authentication import UserData
from mavedb.lib.logging.context import logging_context, save_to_logging_context
from mavedb.lib.permissions import Action
from mavedb.lib.score_sets import find_superseded_score_set_tail
from mavedb.models.contributor import Contributor
from mavedb.models.controlled_keyword import ControlledKeyword
from mavedb.models.experiment import Experiment
Expand All @@ -14,6 +17,7 @@
from mavedb.models.publication_identifier import PublicationIdentifier
from mavedb.models.score_set import ScoreSet
from mavedb.models.user import User
from mavedb.view_models import experiment
from mavedb.view_models.search import ExperimentsSearch

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -108,7 +112,7 @@ def search_experiments(
# 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()))
Experiment.score_sets.any(not_(ScoreSet.meta_analyzes_score_sets.any())),
)
)
else:
Expand All @@ -125,3 +129,30 @@ def search_experiments(
)

return items


def enrich_experiment_with_num_score_sets(
item_update: Experiment, user_data: Optional[UserData]
) -> experiment.Experiment:
"""
Validate and update the number of score set in experiment. The superseded score set is excluded.
Data structure: experiment{score_set_urns, num_score_sets}
"""
filter_superseded_score_set_tails = [
find_superseded_score_set_tail(score_set, Action.READ, user_data) for score_set in item_update.score_sets
]
filtered_score_set_urns = sorted(
{
score_set.urn
for score_set in filter_superseded_score_set_tails
if score_set is not None and score_set.urn is not None
}
)

updated_experiment = experiment.Experiment.from_orm(item_update).copy(
update={
"num_score_sets": len(filtered_score_set_urns),
"score_set_urns": filtered_score_set_urns,
}
)
return updated_experiment
11 changes: 10 additions & 1 deletion src/mavedb/routers/experiment_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from mavedb import deps
from mavedb.lib.authentication import UserData, get_current_user
from mavedb.lib.experiments import enrich_experiment_with_num_score_sets
from mavedb.lib.logging import LoggedRoute
from mavedb.lib.logging.context import logging_context, save_to_logging_context
from mavedb.lib.permissions import Action, has_permission
Expand Down Expand Up @@ -51,5 +52,13 @@ def fetch_experiment_set(

# Filter experiment sub-resources to only those experiments readable by the requesting user.
item.experiments[:] = [exp for exp in item.experiments if has_permission(user_data, exp, Action.READ).permitted]
enriched_experiments = [
enrich_experiment_with_num_score_sets(exp, user_data)
for exp in item.experiments
]
enriched_item = experiment_set.ExperimentSet.from_orm(item).copy(update={
"experiments": enriched_experiments,
"numExperiments": len(enriched_experiments)
})

return item
return enriched_item
27 changes: 21 additions & 6 deletions src/mavedb/routers/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from mavedb.lib.authorization import require_current_user, require_current_user_with_email
from mavedb.lib.contributors import find_or_create_contributor
from mavedb.lib.exceptions import NonexistentOrcidUserError
from mavedb.lib.experiments import search_experiments as _search_experiments
from mavedb.lib.experiments import search_experiments as _search_experiments, enrich_experiment_with_num_score_sets
from mavedb.lib.identifiers import (
find_or_create_doi_identifier,
find_or_create_publication_identifier,
Expand Down Expand Up @@ -89,7 +89,11 @@ def search_experiments(search: ExperimentsSearch, db: Session = Depends(deps.get
"""
Search experiments.
"""
return _search_experiments(db, None, search)
items = _search_experiments(db, None, search)
return [
enrich_experiment_with_num_score_sets(exp, None)
for exp in items
]


@router.post(
Expand All @@ -105,7 +109,11 @@ def search_my_experiments(
"""
Search experiments created by the current user..
"""
return _search_experiments(db, user_data.user, search)
items = _search_experiments(db, user_data.user, search)
return [
enrich_experiment_with_num_score_sets(exp, user_data)
for exp in items
]


@router.get(
Expand All @@ -120,7 +128,7 @@ def fetch_experiment(
urn: str,
db: Session = Depends(deps.get_db),
user_data: Optional[UserData] = Depends(get_current_user),
) -> Experiment:
) -> experiment.Experiment:
"""
Fetch a single experiment by URN.
"""
Expand All @@ -133,7 +141,7 @@ def fetch_experiment(
raise HTTPException(status_code=404, detail=f"Experiment with URN {urn} not found")

assert_permission(user_data, item, Action.READ)
return item
return enrich_experiment_with_num_score_sets(item, user_data)


@router.get(
Expand Down Expand Up @@ -184,6 +192,13 @@ def get_experiment_score_sets(
else:
filtered_score_sets.sort(key=attrgetter("urn"))
save_to_logging_context({"associated_resources": [item.urn for item in score_set_result]})
enriched_score_sets = []
for fs in filtered_score_sets:
enriched_experiment = enrich_experiment_with_num_score_sets(fs.experiment, user_data)
response_item = score_set.ScoreSet.from_orm(fs).copy(update={"experiment": enriched_experiment})
enriched_score_sets.append(response_item)

return enriched_score_sets

return filtered_score_sets

Expand Down Expand Up @@ -429,7 +444,7 @@ async def update_experiment(
db.refresh(item)

save_to_logging_context({"updated_resource": item.urn})
return item
return enrich_experiment_with_num_score_sets(item, user_data)


@router.delete("/experiments/{urn}", response_model=None, responses={422: {}})
Expand Down
2 changes: 1 addition & 1 deletion src/mavedb/routers/mapped_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async def fetch_mapped_variant_by_variant_urn(db, urn: str) -> Optional[MappedVa
db.query(MappedVariant)
.filter(Variant.urn == urn)
.filter(MappedVariant.variant_id == Variant.id)
.filter(MappedVariant.current is True)
.filter(MappedVariant.current) # filter current is true
.one_or_none()
)
except MultipleResultsFound:
Expand Down
45 changes: 36 additions & 9 deletions src/mavedb/routers/score_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from mavedb.lib.contributors import find_or_create_contributor
from mavedb.lib.exceptions import MixedTargetError, NonexistentOrcidUserError, ValidationError
from mavedb.lib.experiments import enrich_experiment_with_num_score_sets
from mavedb.lib.identifiers import (
create_external_gene_identifier_offset,
find_or_create_doi_identifier,
Expand Down Expand Up @@ -72,7 +73,7 @@

async def fetch_score_set_by_urn(
db, urn: str, user: Optional[UserData], owner_or_contributor: Optional[UserData], only_published: bool
) -> Optional[ScoreSet]:
) -> ScoreSet:
"""
Fetch one score set by URN, ensuring that the user has read permission.

Expand Down Expand Up @@ -135,7 +136,15 @@ def search_score_sets(
Search score sets.
"""
score_sets = _search_score_sets(db, None, search)
return fetch_superseding_score_set_in_search_result(score_sets, user_data, search)
updated_score_sets = fetch_superseding_score_set_in_search_result(score_sets, user_data, search)
enriched_score_sets = []
if updated_score_sets:
for u in updated_score_sets:
enriched_experiment = enrich_experiment_with_num_score_sets(u.experiment, user_data)
response_item = score_set.ScoreSet.from_orm(u).copy(update={"experiment": enriched_experiment})
enriched_score_sets.append(response_item)

return enriched_score_sets


@router.get("/score-sets/mapped-genes", status_code=200, response_model=dict[str, list[str]])
Expand Down Expand Up @@ -183,7 +192,15 @@ def search_my_score_sets(
Search score sets created by the current user..
"""
score_sets = _search_score_sets(db, user_data.user, search)
return fetch_superseding_score_set_in_search_result(score_sets, user_data, search)
updated_score_sets = fetch_superseding_score_set_in_search_result(score_sets, user_data, search)
enriched_score_sets = []
if updated_score_sets:
for u in updated_score_sets:
enriched_experiment = enrich_experiment_with_num_score_sets(u.experiment, user_data)
response_item = score_set.ScoreSet.from_orm(u).copy(update={"experiment": enriched_experiment})
enriched_score_sets.append(response_item)

return enriched_score_sets


@router.get(
Expand All @@ -203,7 +220,9 @@ async def show_score_set(
Fetch a single score set by URN.
"""
save_to_logging_context({"requested_resource": urn})
return await fetch_score_set_by_urn(db, urn, user_data, None, False)
item = await fetch_score_set_by_urn(db, urn, user_data, None, False)
enriched_experiment = enrich_experiment_with_num_score_sets(item.experiment, user_data)
return score_set.ScoreSet.from_orm(item).copy(update={"experiment": enriched_experiment})


@router.get(
Expand Down Expand Up @@ -647,7 +666,9 @@ async def create_score_set(
db.refresh(item)

save_to_logging_context({"created_resource": item.urn})
return item

enriched_experiment = enrich_experiment_with_num_score_sets(item.experiment, user_data)
return score_set.ScoreSet.from_orm(item).copy(update={"experiment": enriched_experiment})


@router.post(
Expand Down Expand Up @@ -711,7 +732,8 @@ async def upload_score_set_variant_data(
db.add(item)
db.commit()
db.refresh(item)
return item
enriched_experiment = enrich_experiment_with_num_score_sets(item.experiment, user_data)
return score_set.ScoreSet.from_orm(item).copy(update={"experiment": enriched_experiment})


@router.post(
Expand Down Expand Up @@ -748,7 +770,8 @@ async def update_score_set_calibration_data(
db.refresh(item)

save_to_logging_context({"updated_resource": item.urn})
return item
enriched_experiment = enrich_experiment_with_num_score_sets(item.experiment, user_data)
return score_set.ScoreSet.from_orm(item).copy(update={"experiment": enriched_experiment})


@router.put(
Expand Down Expand Up @@ -1004,7 +1027,9 @@ async def update_score_set(
db.refresh(item)

save_to_logging_context({"updated_resource": item.urn})
return item

enriched_experiment = enrich_experiment_with_num_score_sets(item.experiment, user_data)
return score_set.ScoreSet.from_orm(item).copy(update={"experiment": enriched_experiment})


@router.delete("/score-sets/{urn}", responses={422: {}})
Expand Down Expand Up @@ -1142,7 +1167,8 @@ async def publish_score_set(
msg="Failed to enqueue published variant materialized view refresh job.", extra=logging_context()
)

return item
enriched_experiment = enrich_experiment_with_num_score_sets(item.experiment, user_data)
return score_set.ScoreSet.from_orm(item).copy(update={"experiment": enriched_experiment})


@router.get(
Expand Down Expand Up @@ -1276,3 +1302,4 @@ async def get_clinical_controls_options_for_score_set(
dict(zip(("db_name", "available_versions"), (db_name, db_versions)))
for db_name, db_versions in clinical_control_options.items()
]

1 change: 1 addition & 0 deletions src/mavedb/view_models/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def publication_identifiers_validator(cls, value, values, field) -> list[Publica

# Properties to return to non-admin clients
class Experiment(SavedExperiment):
num_score_sets: Optional[int] = None
score_set_urns: list[str]
processing_state: Optional[str]
doi_identifiers: Sequence[DoiIdentifier]
Expand Down
1 change: 1 addition & 0 deletions src/mavedb/view_models/experiment_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class ExperimentSet(SavedExperimentSet):
created_by: Optional[User]
modified_by: Optional[User]
experiments: Sequence[Experiment]
num_experiments: Optional[int] = None


# Properties to return to admin clients
Expand Down
3 changes: 3 additions & 0 deletions tests/helpers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@
"urn": None,
"experimentSetUrn": None,
"officialCollections": [],
"numScoreSets": 0,
}

TEST_EXPERIMENT_WITH_KEYWORD_RESPONSE = {
Expand Down Expand Up @@ -426,6 +427,7 @@
"urn": None,
"experimentSetUrn": None,
"officialCollections": [],
"numScoreSets": 0, # NOTE: This is context-dependent and may need overriding per test
}

TEST_EXPERIMENT_WITH_KEYWORD_HAS_DUPLICATE_OTHERS_RESPONSE = {
Expand Down Expand Up @@ -475,6 +477,7 @@
"urn": None,
"experimentSetUrn": None,
"officialCollections": [],
"numScoreSets": 0, # NOTE: This is context-dependent and may need overriding per test
}

TEST_TAXONOMY = {
Expand Down
Loading