diff --git a/src/mavedb/lib/authentication.py b/src/mavedb/lib/authentication.py index b82faf3b..4ff59272 100644 --- a/src/mavedb/lib/authentication.py +++ b/src/mavedb/lib/authentication.py @@ -1,6 +1,5 @@ import logging import os -from dataclasses import dataclass from datetime import datetime from enum import Enum from typing import Optional @@ -19,6 +18,7 @@ from mavedb import deps from mavedb.lib.logging.context import format_raised_exception_info_as_dict, logging_context, save_to_logging_context from mavedb.lib.orcid import fetch_orcid_user_email +from mavedb.lib.types.authentication import UserData from mavedb.models.access_key import AccessKey from mavedb.models.enums.user_role import UserRole from mavedb.models.user import User @@ -45,12 +45,6 @@ class AuthenticationMethod(str, Enum): jwt = "jwt" -@dataclass -class UserData: - user: User - active_roles: list[UserRole] - - #################################################################################################### # JWT authentication #################################################################################################### diff --git a/src/mavedb/lib/authorization.py b/src/mavedb/lib/authorization.py index c9b2ab81..94f011c9 100644 --- a/src/mavedb/lib/authorization.py +++ b/src/mavedb/lib/authorization.py @@ -3,8 +3,9 @@ from fastapi import Depends, HTTPException -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import get_current_user from mavedb.lib.logging.context import logging_context, save_to_logging_context +from mavedb.lib.types.authentication import UserData from mavedb.models.enums.user_role import UserRole logger = logging.getLogger(__name__) diff --git a/src/mavedb/lib/experiments.py b/src/mavedb/lib/experiments.py index ed02b701..a200e93b 100644 --- a/src/mavedb/lib/experiments.py +++ b/src/mavedb/lib/experiments.py @@ -1,13 +1,13 @@ import logging from typing import Optional -from sqlalchemy import func, or_, not_ +from sqlalchemy import func, not_, or_ 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.lib.types.authentication import UserData from mavedb.models.contributor import Contributor from mavedb.models.controlled_keyword import ControlledKeyword from mavedb.models.experiment import Experiment diff --git a/src/mavedb/lib/permissions/collection.py b/src/mavedb/lib/permissions/collection.py index 916db06b..a629a45e 100644 --- a/src/mavedb/lib/permissions/collection.py +++ b/src/mavedb/lib/permissions/collection.py @@ -1,10 +1,10 @@ from typing import Optional -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import save_to_logging_context from mavedb.lib.permissions.actions import Action from mavedb.lib.permissions.models import PermissionResponse from mavedb.lib.permissions.utils import deny_action_for_entity, roles_permitted +from mavedb.lib.types.authentication import UserData from mavedb.models.collection import Collection from mavedb.models.enums.contribution_role import ContributionRole from mavedb.models.enums.user_role import UserRole diff --git a/src/mavedb/lib/permissions/core.py b/src/mavedb/lib/permissions/core.py index c14190ea..df6facc8 100644 --- a/src/mavedb/lib/permissions/core.py +++ b/src/mavedb/lib/permissions/core.py @@ -1,10 +1,10 @@ from typing import Any, Callable, Optional -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import save_to_logging_context from mavedb.lib.permissions.actions import Action from mavedb.lib.permissions.exceptions import PermissionException from mavedb.lib.permissions.models import PermissionResponse +from mavedb.lib.types.authentication import UserData from mavedb.lib.types.permissions import EntityType from mavedb.models.collection import Collection from mavedb.models.experiment import Experiment diff --git a/src/mavedb/lib/permissions/experiment.py b/src/mavedb/lib/permissions/experiment.py index 834de45b..91f8f617 100644 --- a/src/mavedb/lib/permissions/experiment.py +++ b/src/mavedb/lib/permissions/experiment.py @@ -1,10 +1,10 @@ from typing import Optional -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import save_to_logging_context from mavedb.lib.permissions.actions import Action from mavedb.lib.permissions.models import PermissionResponse from mavedb.lib.permissions.utils import deny_action_for_entity, roles_permitted +from mavedb.lib.types.authentication import UserData from mavedb.models.enums.user_role import UserRole from mavedb.models.experiment import Experiment diff --git a/src/mavedb/lib/permissions/experiment_set.py b/src/mavedb/lib/permissions/experiment_set.py index 13497fb3..d8cbecc3 100644 --- a/src/mavedb/lib/permissions/experiment_set.py +++ b/src/mavedb/lib/permissions/experiment_set.py @@ -1,10 +1,10 @@ from typing import Optional -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import save_to_logging_context from mavedb.lib.permissions.actions import Action from mavedb.lib.permissions.models import PermissionResponse from mavedb.lib.permissions.utils import deny_action_for_entity, roles_permitted +from mavedb.lib.types.authentication import UserData from mavedb.models.enums.user_role import UserRole from mavedb.models.experiment_set import ExperimentSet diff --git a/src/mavedb/lib/permissions/score_calibration.py b/src/mavedb/lib/permissions/score_calibration.py index 08c27068..4c068c7c 100644 --- a/src/mavedb/lib/permissions/score_calibration.py +++ b/src/mavedb/lib/permissions/score_calibration.py @@ -1,10 +1,10 @@ from typing import Optional -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import save_to_logging_context from mavedb.lib.permissions.actions import Action from mavedb.lib.permissions.models import PermissionResponse from mavedb.lib.permissions.utils import deny_action_for_entity, roles_permitted +from mavedb.lib.types.authentication import UserData from mavedb.models.enums.user_role import UserRole from mavedb.models.score_calibration import ScoreCalibration diff --git a/src/mavedb/lib/permissions/score_set.py b/src/mavedb/lib/permissions/score_set.py index 6a992240..6e580669 100644 --- a/src/mavedb/lib/permissions/score_set.py +++ b/src/mavedb/lib/permissions/score_set.py @@ -1,10 +1,10 @@ from typing import Optional -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import save_to_logging_context from mavedb.lib.permissions.actions import Action from mavedb.lib.permissions.models import PermissionResponse from mavedb.lib.permissions.utils import deny_action_for_entity, roles_permitted +from mavedb.lib.types.authentication import UserData from mavedb.models.enums.user_role import UserRole from mavedb.models.score_set import ScoreSet diff --git a/src/mavedb/lib/permissions/user.py b/src/mavedb/lib/permissions/user.py index 908c84d6..cee817ca 100644 --- a/src/mavedb/lib/permissions/user.py +++ b/src/mavedb/lib/permissions/user.py @@ -1,10 +1,10 @@ from typing import Optional -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import save_to_logging_context from mavedb.lib.permissions.actions import Action from mavedb.lib.permissions.models import PermissionResponse from mavedb.lib.permissions.utils import deny_action_for_entity, roles_permitted +from mavedb.lib.types.authentication import UserData from mavedb.models.enums.user_role import UserRole from mavedb.models.user import User diff --git a/src/mavedb/lib/permissions/utils.py b/src/mavedb/lib/permissions/utils.py index 4d3a32bf..3d92ce1d 100644 --- a/src/mavedb/lib/permissions/utils.py +++ b/src/mavedb/lib/permissions/utils.py @@ -1,9 +1,9 @@ import logging from typing import Optional, Union, overload -from mavedb.lib.authentication import UserData from mavedb.lib.logging.context import logging_context, save_to_logging_context from mavedb.lib.permissions.models import PermissionResponse +from mavedb.lib.types.authentication import UserData from mavedb.lib.types.permissions import EntityType from mavedb.models.enums.contribution_role import ContributionRole from mavedb.models.enums.user_role import UserRole diff --git a/src/mavedb/lib/score_sets.py b/src/mavedb/lib/score_sets.py index 190d7b42..91613365 100644 --- a/src/mavedb/lib/score_sets.py +++ b/src/mavedb/lib/score_sets.py @@ -23,6 +23,8 @@ VARIANT_SCORE_DATA, ) from mavedb.lib.mave.utils import is_csv_null +from mavedb.lib.permissions import Action, has_permission +from mavedb.lib.types.authentication import UserData from mavedb.lib.validation.constants.general import null_values_list from mavedb.lib.validation.utilities import is_null as validate_is_null from mavedb.lib.variants import get_digest_from_post_mapped, get_hgvs_from_post_mapped, is_hgvs_g, is_hgvs_p @@ -55,7 +57,6 @@ from mavedb.view_models.search import ScoreSetsSearch if TYPE_CHECKING: - from mavedb.lib.authentication import UserData from mavedb.lib.permissions import Action VariantData = dict[str, Optional[dict[str, dict]]] @@ -298,21 +299,40 @@ def score_set_search_filter_options_from_counter(counter: Counter): return [{"value": value, "count": count} for value, count in counter.items()] -def fetch_score_set_search_filter_options(db: Session, owner_or_contributor: Optional[User], search: ScoreSetsSearch): +def fetch_score_set_search_filter_options( + db: Session, requester: Optional[UserData], owner_or_contributor: Optional[User], search: ScoreSetsSearch +): save_to_logging_context({"score_set_search_criteria": search.model_dump()}) query = db.query(ScoreSet) query = build_search_score_sets_query_filter(db, query, owner_or_contributor, search) - score_sets: list[ScoreSet] = query.all() if not score_sets: score_sets = [] + # Target related counters target_category_counter: Counter[str] = Counter() target_name_counter: Counter[str] = Counter() target_organism_name_counter: Counter[str] = Counter() target_accession_counter: Counter[str] = Counter() + # Publication related counters + publication_author_name_counter: Counter[str] = Counter() + publication_db_name_counter: Counter[str] = Counter() + publication_journal_counter: Counter[str] = Counter() + + # --- PERFORMANCE NOTE --- + # The following counter construction loop is a bottleneck for large score set queries. + # Practical future optimizations might include: + # - Batch permission checks and attribute access outside the loop if possible + # - Use parallelization (e.g., multiprocessing or concurrent.futures) for large datasets + # - Pre-fetch or denormalize target/publication data in the DB query + # - Profile and refactor nested attribute lookups to minimize Python overhead for score_set in score_sets: + # Check read permission for each score set, skip if no permission + if not has_permission(requester, score_set, Action.READ).permitted: + continue + + # Target related options for target in getattr(score_set, "target_genes", []): category = getattr(target, "category", None) if category: @@ -335,10 +355,7 @@ def fetch_score_set_search_filter_options(db: Session, owner_or_contributor: Opt if accession: target_accession_counter[accession] += 1 - publication_author_name_counter: Counter[str] = Counter() - publication_db_name_counter: Counter[str] = Counter() - publication_journal_counter: Counter[str] = Counter() - for score_set in score_sets: + # Publication related options for publication_association in getattr(score_set, "publication_identifier_associations", []): publication = getattr(publication_association, "publication", None) @@ -443,8 +460,6 @@ def find_meta_analyses_for_experiment_sets(db: Session, urns: list[str]) -> list def find_superseded_score_set_tail( score_set: ScoreSet, action: Optional["Action"] = None, user_data: Optional["UserData"] = None ) -> Optional[ScoreSet]: - from mavedb.lib.permissions import has_permission - while score_set.superseding_score_set is not None: next_score_set_in_chain = score_set.superseding_score_set diff --git a/src/mavedb/lib/types/authentication.py b/src/mavedb/lib/types/authentication.py new file mode 100644 index 00000000..748f6f90 --- /dev/null +++ b/src/mavedb/lib/types/authentication.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mavedb.models.enums.user_role import UserRole + from mavedb.models.user import User + + +@dataclass +class UserData: + user: "User" + active_roles: list["UserRole"] diff --git a/src/mavedb/routers/access_keys.py b/src/mavedb/routers/access_keys.py index c584dcb2..275fc437 100644 --- a/src/mavedb/routers/access_keys.py +++ b/src/mavedb/routers/access_keys.py @@ -12,10 +12,10 @@ from sqlalchemy.orm import Session from mavedb import deps -from mavedb.lib.authentication import UserData from mavedb.lib.authorization import require_current_user from mavedb.lib.logging import LoggedRoute from mavedb.lib.logging.context import logging_context, save_to_logging_context +from mavedb.lib.types.authentication import UserData from mavedb.models.access_key import AccessKey from mavedb.models.enums.user_role import UserRole from mavedb.routers.shared import ACCESS_CONTROL_ERROR_RESPONSES, PUBLIC_ERROR_RESPONSES, ROUTER_BASE_PREFIX diff --git a/src/mavedb/routers/collections.py b/src/mavedb/routers/collections.py index cf215a69..48f38964 100644 --- a/src/mavedb/routers/collections.py +++ b/src/mavedb/routers/collections.py @@ -9,7 +9,7 @@ from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound from mavedb import deps -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import get_current_user from mavedb.lib.authorization import require_current_user, require_current_user_with_email from mavedb.lib.logging import LoggedRoute from mavedb.lib.logging.context import ( @@ -18,6 +18,7 @@ save_to_logging_context, ) from mavedb.lib.permissions import Action, assert_permission, has_permission +from mavedb.lib.types.authentication import UserData from mavedb.models.collection import Collection from mavedb.models.collection_user_association import CollectionUserAssociation from mavedb.models.enums.contribution_role import ContributionRole diff --git a/src/mavedb/routers/experiment_sets.py b/src/mavedb/routers/experiment_sets.py index 1166fb7f..6bc5214c 100644 --- a/src/mavedb/routers/experiment_sets.py +++ b/src/mavedb/routers/experiment_sets.py @@ -6,11 +6,12 @@ from sqlalchemy.orm import Session from mavedb import deps -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import 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, assert_permission, has_permission +from mavedb.lib.types.authentication import UserData from mavedb.models.experiment_set import ExperimentSet from mavedb.routers.shared import ACCESS_CONTROL_ERROR_RESPONSES, PUBLIC_ERROR_RESPONSES, ROUTER_BASE_PREFIX from mavedb.view_models import experiment_set diff --git a/src/mavedb/routers/experiments.py b/src/mavedb/routers/experiments.py index 2064196b..827833f5 100644 --- a/src/mavedb/routers/experiments.py +++ b/src/mavedb/routers/experiments.py @@ -9,7 +9,7 @@ from sqlalchemy.orm import Session from mavedb import deps -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import get_current_user 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 @@ -25,6 +25,7 @@ from mavedb.lib.logging.context import logging_context, save_to_logging_context from mavedb.lib.permissions import Action, assert_permission, has_permission from mavedb.lib.score_sets import find_superseded_score_set_tail +from mavedb.lib.types.authentication import UserData from mavedb.lib.validation.exceptions import ValidationError from mavedb.lib.validation.keywords import validate_keyword_list from mavedb.models.contributor import Contributor diff --git a/src/mavedb/routers/mapped_variant.py b/src/mavedb/routers/mapped_variant.py index 5657fd3a..52830f92 100644 --- a/src/mavedb/routers/mapped_variant.py +++ b/src/mavedb/routers/mapped_variant.py @@ -17,7 +17,6 @@ variant_study_result, ) from mavedb.lib.annotation.exceptions import MappingDataDoesntExistException -from mavedb.lib.authentication import UserData from mavedb.lib.authorization import get_current_user from mavedb.lib.logging import LoggedRoute from mavedb.lib.logging.context import ( @@ -25,6 +24,7 @@ save_to_logging_context, ) from mavedb.lib.permissions import Action, assert_permission, has_permission +from mavedb.lib.types.authentication import UserData from mavedb.models.mapped_variant import MappedVariant from mavedb.models.variant import Variant from mavedb.routers.shared import ACCESS_CONTROL_ERROR_RESPONSES, PUBLIC_ERROR_RESPONSES, ROUTER_BASE_PREFIX diff --git a/src/mavedb/routers/permissions.py b/src/mavedb/routers/permissions.py index c100cfa2..39833ec7 100644 --- a/src/mavedb/routers/permissions.py +++ b/src/mavedb/routers/permissions.py @@ -6,10 +6,11 @@ from sqlalchemy.orm import Session from mavedb import deps -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import get_current_user 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 +from mavedb.lib.types.authentication import UserData from mavedb.models.collection import Collection from mavedb.models.experiment import Experiment from mavedb.models.experiment_set import ExperimentSet diff --git a/src/mavedb/routers/score_calibrations.py b/src/mavedb/routers/score_calibrations.py index d5bceb88..77551cf0 100644 --- a/src/mavedb/routers/score_calibrations.py +++ b/src/mavedb/routers/score_calibrations.py @@ -5,7 +5,7 @@ from sqlalchemy.orm import Session, selectinload from mavedb import deps -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import get_current_user from mavedb.lib.authorization import require_current_user from mavedb.lib.logging import LoggedRoute from mavedb.lib.logging.context import ( @@ -21,6 +21,7 @@ promote_score_calibration_to_primary, publish_score_calibration, ) +from mavedb.lib.types.authentication import UserData from mavedb.models.score_calibration import ScoreCalibration from mavedb.models.score_set import ScoreSet from mavedb.view_models import score_calibration diff --git a/src/mavedb/routers/score_sets.py b/src/mavedb/routers/score_sets.py index 959f9133..b49260be 100644 --- a/src/mavedb/routers/score_sets.py +++ b/src/mavedb/routers/score_sets.py @@ -26,7 +26,6 @@ variant_study_result, ) from mavedb.lib.annotation.exceptions import MappingDataDoesntExistException -from mavedb.lib.authentication import UserData from mavedb.lib.authorization import ( get_current_user, require_current_user, @@ -61,6 +60,7 @@ ) from mavedb.lib.target_genes import find_or_create_target_gene_by_accession, find_or_create_target_gene_by_sequence from mavedb.lib.taxonomies import find_or_create_taxonomy +from mavedb.lib.types.authentication import UserData from mavedb.lib.urns import ( generate_experiment_set_urn, generate_experiment_urn, @@ -598,8 +598,9 @@ def search_score_sets( def get_filter_options_for_search( search: ScoreSetsSearch, db: Session = Depends(deps.get_db), + user_data: Optional[UserData] = Depends(get_current_user), ) -> Any: - return fetch_score_set_search_filter_options(db, None, search) + return fetch_score_set_search_filter_options(db, user_data, None, search) @router.get( diff --git a/src/mavedb/routers/target_genes.py b/src/mavedb/routers/target_genes.py index 29f91c5e..a304ee89 100644 --- a/src/mavedb/routers/target_genes.py +++ b/src/mavedb/routers/target_genes.py @@ -4,13 +4,14 @@ from sqlalchemy.orm import Session, selectinload from mavedb import deps -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import get_current_user from mavedb.lib.authorization import require_current_user from mavedb.lib.permissions import Action, has_permission from mavedb.lib.score_sets import find_superseded_score_set_tail from mavedb.lib.target_genes import ( search_target_genes as _search_target_genes, ) +from mavedb.lib.types.authentication import UserData from mavedb.models.score_set import ScoreSet from mavedb.models.target_gene import TargetGene from mavedb.routers.shared import ACCESS_CONTROL_ERROR_RESPONSES, PUBLIC_ERROR_RESPONSES, ROUTER_BASE_PREFIX diff --git a/src/mavedb/routers/users.py b/src/mavedb/routers/users.py index 79c9cb88..7aafc0ab 100644 --- a/src/mavedb/routers/users.py +++ b/src/mavedb/routers/users.py @@ -5,11 +5,11 @@ from starlette.convertors import Convertor, register_url_convertor from mavedb import deps -from mavedb.lib.authentication import UserData from mavedb.lib.authorization import RoleRequirer, require_current_user from mavedb.lib.logging import LoggedRoute from mavedb.lib.logging.context import logging_context, save_to_logging_context from mavedb.lib.permissions import Action, assert_permission +from mavedb.lib.types.authentication import UserData from mavedb.models.enums.user_role import UserRole from mavedb.models.user import User from mavedb.routers.shared import ACCESS_CONTROL_ERROR_RESPONSES, PUBLIC_ERROR_RESPONSES, ROUTER_BASE_PREFIX diff --git a/src/mavedb/routers/variants.py b/src/mavedb/routers/variants.py index 4de1de1d..c195f903 100644 --- a/src/mavedb/routers/variants.py +++ b/src/mavedb/routers/variants.py @@ -10,10 +10,11 @@ from sqlalchemy.sql import or_ from mavedb import deps -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.lib.authentication import get_current_user from mavedb.lib.logging import LoggedRoute from mavedb.lib.logging.context import logging_context, save_to_logging_context from mavedb.lib.permissions import Action, assert_permission, has_permission +from mavedb.lib.types.authentication import UserData from mavedb.models.mapped_variant import MappedVariant from mavedb.models.score_set import ScoreSet from mavedb.models.variant import Variant diff --git a/tests/conftest_optional.py b/tests/conftest_optional.py index 8597c4f9..a07607a7 100644 --- a/tests/conftest_optional.py +++ b/tests/conftest_optional.py @@ -1,9 +1,10 @@ import os +import shutil +import tempfile from concurrent import futures from inspect import getsourcefile from posixpath import abspath -import shutil -import tempfile +from unittest.mock import patch import cdot.hgvs.dataproviders import pytest @@ -12,15 +13,14 @@ from biocommons.seqrepo import SeqRepo from fastapi.testclient import TestClient from httpx import AsyncClient -from unittest.mock import patch -from mavedb.lib.authentication import UserData, get_current_user +from mavedb.deps import get_db, get_seqrepo, get_worker, hgvs_data_provider +from mavedb.lib.authentication import get_current_user from mavedb.lib.authorization import require_current_user +from mavedb.lib.types.authentication import UserData from mavedb.models.user import User from mavedb.server_main import app -from mavedb.deps import get_db, get_worker, hgvs_data_provider, get_seqrepo -from mavedb.worker.settings import BACKGROUND_FUNCTIONS, BACKGROUND_CRONJOBS - +from mavedb.worker.settings import BACKGROUND_CRONJOBS, BACKGROUND_FUNCTIONS from tests.helpers.constants import ADMIN_USER, EXTRA_USER, TEST_SEQREPO_INITIAL_STATE, TEST_USER #################################################################################################### diff --git a/tests/lib/test_score_set.py b/tests/lib/test_score_set.py index a260599a..d9f7fa39 100644 --- a/tests/lib/test_score_set.py +++ b/tests/lib/test_score_set.py @@ -7,6 +7,10 @@ import pytest from sqlalchemy import select +from mavedb.models.enums.target_category import TargetCategory +from mavedb.models.user import User +from mavedb.view_models.search import ScoreSetsSearch + arq = pytest.importorskip("arq") cdot = pytest.importorskip("cdot") fastapi = pytest.importorskip("fastapi") @@ -17,7 +21,9 @@ create_variants, create_variants_data, csv_data_to_df, + fetch_score_set_search_filter_options, ) +from mavedb.lib.types.authentication import UserData from mavedb.lib.validation.constants.general import ( hgvs_nt_column, hgvs_pro_column, @@ -33,7 +39,7 @@ from mavedb.models.target_sequence import TargetSequence from mavedb.models.taxonomy import Taxonomy from mavedb.models.variant import Variant -from tests.helpers.constants import TEST_EXPERIMENT, TEST_ACC_SCORESET, TEST_SEQ_SCORESET +from tests.helpers.constants import TEST_ACC_SCORESET, TEST_EXPERIMENT, TEST_SEQ_SCORESET, TEST_USER from tests.helpers.util.experiment import create_experiment from tests.helpers.util.score_set import create_seq_score_set @@ -377,3 +383,170 @@ def test_create_null_score_range(setup_lib_db, client, session): assert not score_set.score_calibrations assert score_set is not None + + +def test_fetch_score_set_search_filter_options_no_score_sets(setup_lib_db, session): + score_set_search = ScoreSetsSearch() + filter_options = fetch_score_set_search_filter_options(session, None, None, score_set_search) + + assert filter_options == { + "target_gene_categories": [], + "target_gene_names": [], + "target_organism_names": [], + "target_accessions": [], + "publication_author_names": [], + "publication_db_names": [], + "publication_journals": [], + } + + +def test_fetch_score_set_search_filter_options_with_score_set(setup_lib_db, session): + requesting_user = session.query(User).filter(User.username == TEST_USER["username"]).first() + user_data = UserData(user=requesting_user, active_roles=[]) + + experiment = Experiment(**TEST_EXPERIMENT) + session.add(experiment) + session.commit() + session.refresh(experiment) + + target_accessions = [TargetAccession(**seq["target_accession"]) for seq in TEST_ACC_SCORESET["target_genes"]] + target_genes = [ + TargetGene(**{**gene, **{"target_accession": target_accessions[idx]}}) + for idx, gene in enumerate(TEST_ACC_SCORESET["target_genes"]) + ] + + score_set = ScoreSet( + **{ + **TEST_ACC_SCORESET, + **{ + "experiment_id": experiment.id, + "target_genes": target_genes, + "extra_metadata": {}, + "license": session.scalars(select(License)).first(), + }, + "created_by_id": requesting_user.id, + "modified_by_id": requesting_user.id, + } + ) + session.add(score_set) + session.commit() + session.refresh(score_set) + + score_set_search = ScoreSetsSearch() + filter_options = fetch_score_set_search_filter_options(session, user_data, None, score_set_search) + + assert filter_options == { + "target_gene_categories": [{"value": TargetCategory.protein_coding, "count": 1}], + "target_gene_names": [{"value": "TEST2", "count": 1}], + "target_organism_names": [], + "target_accessions": [{"value": "NM_001637.3", "count": 1}], + "publication_author_names": [], + "publication_db_names": [], + "publication_journals": [], + } + + +def test_fetch_score_set_search_filter_options_with_partial_filtered_score_sets(setup_lib_db, session): + requesting_user = session.query(User).filter(User.username == TEST_USER["username"]).first() + user_data = UserData(user=requesting_user, active_roles=[]) + + experiment = Experiment(**TEST_EXPERIMENT) + session.add(experiment) + session.commit() + session.refresh(experiment) + + target_sequences = [ + TargetSequence(**{**seq["target_sequence"], **{"taxonomy": session.scalars(select(Taxonomy)).first()}}) + for seq in TEST_SEQ_SCORESET["target_genes"] + ] + target_genes = [ + TargetGene(**{**gene, **{"target_sequence": target_sequences[idx]}}) + for idx, gene in enumerate(TEST_SEQ_SCORESET["target_genes"]) + ] + + score_set = ScoreSet( + **{ + **TEST_SEQ_SCORESET, + **{ + "experiment_id": experiment.id, + "target_genes": target_genes, + "extra_metadata": {}, + "license": session.scalars(select(License)).first(), + }, + "created_by_id": requesting_user.id, + "modified_by_id": requesting_user.id, + } + ) + session.add(score_set) + session.commit() + session.refresh(score_set) + + target_accessions = [TargetAccession(**seq["target_accession"]) for seq in TEST_ACC_SCORESET["target_genes"]] + target_genes = [ + TargetGene(**{**gene, **{"target_accession": target_accessions[idx]}}) + for idx, gene in enumerate(TEST_ACC_SCORESET["target_genes"]) + ] + + score_set = ScoreSet( + **{ + **TEST_ACC_SCORESET, + **{ + "experiment_id": experiment.id, + "target_genes": target_genes, + "extra_metadata": {}, + "license": session.scalars(select(License)).first(), + }, + "created_by_id": requesting_user.id, + "modified_by_id": requesting_user.id, + } + ) + session.add(score_set) + session.commit() + + session.refresh(score_set) + + score_set_search = ScoreSetsSearch(targets=["TEST1"]) + requesting_user = session.query(User).filter(User.username == TEST_USER["username"]).first() + user_data = UserData(user=requesting_user, active_roles=[]) + filter_options = fetch_score_set_search_filter_options(session, user_data, None, score_set_search) + assert filter_options == { + "target_gene_categories": [{"value": TargetCategory.protein_coding, "count": 1}], + "target_gene_names": [{"value": "TEST1", "count": 1}], + "target_organism_names": [{"count": 1, "value": "Organism name"}], + "target_accessions": [], + "publication_author_names": [], + "publication_db_names": [], + "publication_journals": [], + } + + +def test_fetch_score_set_search_filter_options_with_no_matching_score_sets(setup_lib_db, session): + score_set_search = ScoreSetsSearch(publication_journals=["Non Existent Journal"]) + requesting_user = session.query(User).filter(User.username == TEST_USER["username"]).first() + user_data = UserData(user=requesting_user, active_roles=[]) + filter_options = fetch_score_set_search_filter_options(session, user_data, None, score_set_search) + + assert filter_options == { + "target_gene_categories": [], + "target_gene_names": [], + "target_organism_names": [], + "target_accessions": [], + "publication_author_names": [], + "publication_db_names": [], + "publication_journals": [], + } + + +def test_fetch_score_set_search_filter_options_with_no_permitted_score_sets(setup_lib_db, session): + score_set_search = ScoreSetsSearch() + filter_options = fetch_score_set_search_filter_options(session, None, None, score_set_search) + + assert filter_options == { + "target_gene_categories": [], + "target_gene_names": [], + "target_organism_names": [], + "target_accessions": [], + "publication_author_names": [], + "publication_db_names": [], + "publication_journals": [], + }