Skip to content

Commit ab5ceab

Browse files
authored
Merge pull request #243 from VariantEffect/release2024.2.1
Release 2024.2.1
2 parents 8c27b33 + 9c47105 commit ab5ceab

File tree

9 files changed

+273
-42
lines changed

9 files changed

+273
-42
lines changed

src/mavedb/lib/authentication.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,8 @@ async def get_current_user(
158158
email=email,
159159
is_first_login=True,
160160
)
161-
logger.info(f"Creating new user with username {user.username}")
162161

163-
db.add(user)
164-
db.commit()
165-
db.refresh(user)
162+
logger.info(f"Creating new user with username {user.username}")
166163

167164
elif not user.is_active:
168165
return None

src/mavedb/lib/score_sets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def search_score_sets(db: Session, owner: Optional[User], search: ScoreSetsSearc
7171
query = query.filter(ScoreSet.published_date.is_(None))
7272

7373
if search.text:
74-
lower_search_text = search.text.lower()
74+
lower_search_text = search.text.lower().strip()
7575
query = query.filter(
7676
or_(
7777
ScoreSet.urn.icontains(lower_search_text),

src/mavedb/routers/experiments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def get_experiment_score_sets(
125125
#
126126
# TODO(#182): A side effect of this implementation is that only the user who has created the experiment may view all the Score sets
127127
# associated with a given experiment. This could be solved with user impersonation for certain user roles.
128-
score_sets = db.query(ScoreSet).filter(ScoreSet.experiment_id == experiment.id)
128+
score_sets = db.query(ScoreSet).filter(ScoreSet.experiment_id == experiment.id).filter(~ScoreSet.superseding_score_set.has())
129129
if user_data is not None:
130130
score_set_result = score_sets.filter(
131131
or_(ScoreSet.private.is_(False), and_(ScoreSet.private.is_(True), ScoreSet.created_by == user_data.user))

src/mavedb/routers/publication_identifiers.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
from enum import Enum
12
from typing import Any, List
23

34
from fastapi import APIRouter, Depends, HTTPException
45
from sqlalchemy import func, or_
56
from sqlalchemy.exc import MultipleResultsFound
67
from sqlalchemy.orm import Session
8+
from starlette.convertors import Convertor, register_url_convertor
79

810
from mavedb import deps
9-
from mavedb.lib.identifiers import find_generic_article, find_or_create_publication_identifier
11+
from mavedb.lib.identifiers import find_generic_article
12+
from mavedb.lib.validation.constants.publication import valid_dbnames
1013
from mavedb.models.publication_identifier import PublicationIdentifier
1114
from mavedb.view_models import publication_identifier
1215
from mavedb.view_models.search import TextSearch
1316

17+
# I don't think we can escape the type: ignore hint here on a dynamically created enumerated type.
18+
PublicationDatabases = Enum("PublicationDataBases", ((x, x) for x in valid_dbnames)) # type: ignore
19+
20+
1421
router = APIRouter(
1522
prefix="/api/v1/publication-identifiers",
1623
tags=["publication identifiers"],
@@ -27,8 +34,31 @@ def list_publications(*, db: Session = Depends(deps.get_db)) -> Any:
2734
return items
2835

2936

37+
# See https://github.com/tiangolo/fastapi/discussions/7328, which describes that slashes are currently un-escapable in FastAPI/Starlette
38+
# implementations.
39+
#
40+
# Workaround here by defining a custom type convertor (see https://www.starlette.io/routing/#path-parameters) whose
41+
# RegEx match matches our accepted publication identifiers. When the API is queried and the string matches the convertor, we enter the
42+
# route on which the convertor captured a match, use the match as our variable of interest, and resolve the route. By capturing the match,
43+
# we retain the ability to add routes such as /db_name/identifier or /identifier/title since we can now match to these unescapable slashes
44+
# without matching the rest of the path as a path type, as the `:path` convertor would have. In the OpenAPI spec, this type will still be
45+
# represented as a 'path'. -capodb 2024.05.30
46+
class PublicationIdentifierConverter(Convertor):
47+
# RegEx structure: "DOI | PubMed | bioRxiv | medRxiv"
48+
regex = "10[.][0-9]{4,9}\/[-._;()\/:A-z0-9]+|[0-9]{0,8}|[0-9]{4}[.][0-9]{2}[.][0-9]{2}[.][0-9]{6}|[0-9]{4}[.][0-9]{2}[.][0-9]{2}[.][0-9]{8}"
49+
50+
def convert(self, value: str) -> str:
51+
return value
52+
53+
def to_string(self, value: str) -> str:
54+
return str(value)
55+
56+
57+
register_url_convertor("publication", PublicationIdentifierConverter())
58+
59+
3060
@router.get(
31-
"/{identifier}",
61+
"/{identifier:publication}",
3262
status_code=200,
3363
response_model=publication_identifier.PublicationIdentifier,
3464
responses={404: {}},
@@ -51,14 +81,14 @@ def fetch_publication_by_identifier(*, identifier: str, db: Session = Depends(de
5181

5282

5383
@router.get(
54-
"/{db_name}/{identifier}",
84+
"/{db_name:str}/{identifier:publication}",
5585
status_code=200,
5686
response_model=publication_identifier.PublicationIdentifier,
5787
responses={404: {}},
5888
)
5989
def fetch_publication_by_dbname_and_identifier(
6090
*,
61-
db_name: str,
91+
db_name: PublicationDatabases,
6292
identifier: str,
6393
db: Session = Depends(deps.get_db),
6494
) -> PublicationIdentifier:
@@ -68,18 +98,19 @@ def fetch_publication_by_dbname_and_identifier(
6898
try:
6999
item = (
70100
db.query(PublicationIdentifier)
71-
.filter(PublicationIdentifier.identifier == identifier and PublicationIdentifier.db_name == db_name)
101+
.filter(PublicationIdentifier.identifier == identifier)
102+
.filter(PublicationIdentifier.db_name == db_name.name)
72103
.one_or_none()
73104
)
74105
except MultipleResultsFound:
75106
raise HTTPException(
76107
status_code=500,
77-
detail=f"Multiple publications with identifier {identifier} and database name {db_name} were found.",
108+
detail=f"Multiple publications with identifier {identifier} and database name {db_name.name} were found.",
78109
)
79110
if not item:
80111
raise HTTPException(
81112
status_code=404,
82-
detail=f"No publication with identifier {identifier} and database name {db_name} were found.",
113+
detail=f"No publication with identifier {identifier} and database name {db_name.name} were found.",
83114
)
84115
return item
85116

@@ -191,7 +222,7 @@ async def search_publications_by_identifier(*, identifier: str, db: Session = De
191222
@router.get(
192223
"/search/{db_name}/{identifier}",
193224
status_code=200,
194-
response_model=publication_identifier.PublicationIdentifier,
225+
response_model=list[publication_identifier.PublicationIdentifier],
195226
responses={404: {}, 500: {}},
196227
)
197228
async def search_publications_by_identifier_and_db(
@@ -201,7 +232,7 @@ async def search_publications_by_identifier_and_db(
201232
db: Session = Depends(deps.get_db),
202233
) -> Any:
203234
"""
204-
Search publication identifiers via their identifier and database.
235+
Search all of the publication identifiers via their identifier and database.
205236
"""
206237
query = (
207238
db.query(PublicationIdentifier)

tests/helpers/constants.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
"is_first_login": True,
2222
}
2323

24+
TEST_USER_DECODED_JWT = {
25+
"sub": TEST_USER["username"],
26+
"given_name": TEST_USER["first_name"],
27+
"family_name": TEST_USER["last_name"],
28+
}
29+
2430
EXTRA_USER = {
2531
"username": "1234-5678-8765-4321",
2632
"first_name": "Extra",
@@ -32,6 +38,12 @@
3238
"is_first_login": True,
3339
}
3440

41+
EXTRA_USER_DECODED_JWT = {
42+
"sub": EXTRA_USER["username"],
43+
"given_name": EXTRA_USER["first_name"],
44+
"family_name": EXTRA_USER["last_name"],
45+
}
46+
3547
ADMIN_USER = {
3648
"username": "9999-9999-9999-9999",
3749
"first_name": "Admin",
@@ -43,6 +55,12 @@
4355
"is_first_login": True,
4456
}
4557

58+
ADMIN_USER_DECODED_JWT = {
59+
"sub": ADMIN_USER["username"],
60+
"given_name": ADMIN_USER["first_name"],
61+
"family_name": ADMIN_USER["last_name"],
62+
}
63+
4664
TEST_EXPERIMENT = {
4765
"title": "Test Title",
4866
"short_description": "Test experiment",

tests/helpers/util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,26 @@ def publish_score_set(client, score_set_urn):
175175
response_data = response.json()
176176
jsonschema.validate(instance=response_data, schema=ScoreSet.schema())
177177
return response_data
178+
179+
180+
def create_api_key_for_current_user(client):
181+
response = client.post("api/v1/users/me/access-keys")
182+
assert response.status_code == 200
183+
return response.json()["keyId"]
184+
185+
186+
def create_admin_key_for_current_user(client):
187+
response = client.post("api/v1/users/me/access-keys/admin")
188+
assert response.status_code == 200
189+
return response.json()["keyId"]
190+
191+
192+
def mark_user_inactive(session, username):
193+
user = session.query(User).where(User.username == username).one()
194+
user.is_active = False
195+
196+
session.add(user)
197+
session.commit()
198+
session.refresh(user)
199+
200+
return user

tests/lib/conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from mavedb.models.license import License
44
from mavedb.models.taxonomy import Taxonomy
55
from mavedb.models.user import User
6+
from mavedb.models.role import Role
7+
from mavedb.models.enums.user_role import UserRole
68

7-
from tests.helpers.constants import EXTRA_USER, TEST_LICENSE, TEST_TAXONOMY, TEST_USER
9+
from tests.helpers.constants import ADMIN_USER, EXTRA_USER, TEST_LICENSE, TEST_TAXONOMY, TEST_USER
810

911

1012
@pytest.fixture
@@ -16,6 +18,7 @@ def setup_lib_db(session):
1618
db = session
1719
db.add(User(**TEST_USER))
1820
db.add(User(**EXTRA_USER))
21+
db.add(User(**ADMIN_USER, role_objs=[Role(name=UserRole.admin)]))
1922
db.add(Taxonomy(**TEST_TAXONOMY))
2023
db.add(License(**TEST_LICENSE))
2124
db.commit()

0 commit comments

Comments
 (0)