Skip to content

Commit 63ee6e0

Browse files
committed
Modify multiple functions that relates to superseding score set and add some related tests.
1 parent 5361fd7 commit 63ee6e0

File tree

6 files changed

+455
-73
lines changed

6 files changed

+455
-73
lines changed

src/mavedb/lib/score_sets.py

Lines changed: 79 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import numpy as np
99
import pandas as pd
1010
from pandas.testing import assert_index_equal
11-
from sqlalchemy import Integer, cast, func, or_, select
11+
from sqlalchemy import and_, Integer, cast, func, or_, select
1212
from sqlalchemy.orm import Session, aliased, contains_eager, joinedload, selectinload
1313

1414
from mavedb.lib.exceptions import ValidationError
@@ -74,13 +74,21 @@ def search_score_sets(db: Session, owner_or_contributor: Optional["UserData"], s
7474
query = db.query(ScoreSet) # \
7575
# .filter(ScoreSet.private.is_(False))
7676

77-
if owner_or_contributor is not None:
78-
query = query.filter(
79-
or_(
80-
ScoreSet.created_by_id == owner_or_contributor.user.id,
81-
ScoreSet.contributors.any(Contributor.orcid_id == owner_or_contributor.user.username),
77+
if owner_or_contributor is not None and search.me is not None:
78+
if search.me:
79+
query = query.filter(
80+
or_(
81+
ScoreSet.created_by_id == owner_or_contributor.user.id,
82+
ScoreSet.contributors.any(Contributor.orcid_id == owner_or_contributor.user.username),
83+
)
84+
)
85+
else:
86+
query = query.filter(
87+
and_(
88+
ScoreSet.created_by_id != owner_or_contributor.user.id,
89+
~ScoreSet.contributors.any(Contributor.orcid_id == owner_or_contributor.user.username),
90+
)
8291
)
83-
)
8492

8593
if search.published is not None:
8694
if search.published:
@@ -256,27 +264,36 @@ def search_score_sets(db: Session, owner_or_contributor: Optional["UserData"], s
256264
.order_by(Experiment.title)
257265
.all()
258266
)
267+
print(score_sets)
259268
# Remove superseded score set
260269
if not score_sets:
270+
print("if no score set")
261271
final_score_sets: list[ScoreSet] = []
262272
else:
263-
published_filter = search.published if search.published is not None else None
264-
print(len(score_sets))
265-
filtered_score_sets = [
266-
find_superseded_score_set_tail(
267-
score_set,
268-
Action.READ,
269-
owner_or_contributor,
270-
published_filter
271-
) for score_set in score_sets
272-
]
273-
print(len(filtered_score_sets))
273+
if search.published:
274+
filtered_score_sets_tail = [
275+
find_publish_or_private_superseded_score_set_tail(
276+
score_set,
277+
Action.READ,
278+
owner_or_contributor,
279+
search.published
280+
) for score_set in score_sets
281+
]
282+
else:
283+
print("filtered_tail")
284+
filtered_score_sets_tail = [
285+
find_superseded_score_set_tail(
286+
score_set,
287+
Action.READ,
288+
owner_or_contributor
289+
) for score_set in score_sets
290+
]
291+
print(len(filtered_score_sets_tail))
274292
# Remove None item.
275-
filtered_score_sets = [score_set for score_set in filtered_score_sets if score_set is not None]
293+
filtered_score_sets = [score_set for score_set in filtered_score_sets_tail if score_set is not None]
276294
print(len(filtered_score_sets))
277295
if filtered_score_sets:
278-
# final_score_sets = sorted(set(filtered_score_sets), key=attrgetter("urn"))
279-
final_score_sets = filtered_score_sets.sort(key=attrgetter("urn"))
296+
final_score_sets = sorted(set(filtered_score_sets), key=attrgetter("urn"))
280297
for f in filtered_score_sets:
281298
print(f.urn)
282299
else:
@@ -335,48 +352,54 @@ def find_meta_analyses_for_experiment_sets(db: Session, urns: list[str]) -> list
335352
def find_superseded_score_set_tail(
336353
score_set: ScoreSet,
337354
action: Optional["Action"] = None,
338-
user_data: Optional["UserData"] = None,
339-
publish: Optional[bool] = None) -> Optional[ScoreSet]:
355+
user_data: Optional["UserData"] = None) -> Optional[ScoreSet]:
340356
from mavedb.lib.permissions import has_permission
341-
if publish is not None:
342-
if publish is True:
343-
while score_set.superseding_score_set is not None:
344-
next_score_set_in_chain = score_set.superseding_score_set
345-
# Find the final published one.
346-
if action is not None and has_permission(user_data, score_set, action).permitted \
347-
and next_score_set_in_chain.published_date is None:
348-
return score_set
349-
score_set = next_score_set_in_chain
350-
else:
351-
# Unpublished score set should not be superseded.
352-
# It should not have superseding score set, but possible have superseded score set.
353-
if action is not None and score_set.published_date is None \
354-
and has_permission(user_data, score_set, action).permitted:
355-
return score_set
357+
while score_set.superseding_score_set is not None:
358+
next_score_set_in_chain = score_set.superseding_score_set
359+
360+
# If we were given a permission to check and the next score set in the chain does not have that permission,
361+
# pretend like we have reached the end of the chain. Otherwise, continue to the next score set.
362+
if action is not None and not has_permission(user_data, next_score_set_in_chain, action).permitted:
363+
return score_set
364+
365+
score_set = next_score_set_in_chain
366+
367+
# Handle unpublished superseding score set case.
368+
# The score set has a published superseded score set but has not superseding score set.
369+
if action is not None and not has_permission(user_data, score_set, action).permitted:
370+
while score_set.superseded_score_set is not None:
371+
next_score_set_in_chain = score_set.superseded_score_set
372+
if has_permission(user_data, next_score_set_in_chain, action).permitted:
373+
return next_score_set_in_chain
356374
else:
357-
return None
358-
else:
375+
score_set = next_score_set_in_chain
376+
return None
377+
378+
return score_set
379+
380+
381+
def find_publish_or_private_superseded_score_set_tail(
382+
score_set: ScoreSet,
383+
action: Optional["Action"] = None,
384+
user_data: Optional["UserData"] = None,
385+
publish: bool = True) -> Optional[ScoreSet]:
386+
from mavedb.lib.permissions import has_permission
387+
if publish:
359388
while score_set.superseding_score_set is not None:
360389
next_score_set_in_chain = score_set.superseding_score_set
361-
362-
# If we were given a permission to check and the next score set in the chain does not have that permission,
363-
# pretend like we have reached the end of the chain. Otherwise, continue to the next score set.
364-
if action is not None and not has_permission(user_data, next_score_set_in_chain, action).permitted:
390+
# Find the final published one.
391+
if action is not None and has_permission(user_data, score_set, action).permitted \
392+
and next_score_set_in_chain.published_date is None:
365393
return score_set
366-
367394
score_set = next_score_set_in_chain
368-
369-
# Handle unpublished superseding score set case.
370-
# The score set has superseded score set but has not superseding score set.
371-
if action is not None and not has_permission(user_data, score_set, action).permitted:
372-
while score_set.superseded_score_set is not None:
373-
next_score_set_in_chain = score_set.superseded_score_set
374-
if has_permission(user_data, next_score_set_in_chain, action).permitted:
375-
return next_score_set_in_chain
376-
else:
377-
score_set = next_score_set_in_chain
395+
else:
396+
# Unpublished score set should not be superseded.
397+
# It should not have superseding score set, but possible have superseded score set.
398+
if action is not None and score_set.published_date is None \
399+
and has_permission(user_data, score_set, action).permitted:
400+
return score_set
401+
else:
378402
return None
379-
380403
return score_set
381404

382405

src/mavedb/routers/experiments.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,25 +171,24 @@ def get_experiment_score_sets(
171171
.all()
172172
)
173173

174-
superseded_score_set_tails = [
174+
filter_superseded_score_set_tails = [
175175
find_superseded_score_set_tail(
176176
score_set,
177177
Action.READ,
178-
user_data,
179-
None
178+
user_data
180179
) for score_set in score_set_result
181180
]
182-
183-
if not superseded_score_set_tails:
181+
filtered_score_sets = [score_set for score_set in filter_superseded_score_set_tails if score_set is not None]
182+
if not filtered_score_sets:
184183
save_to_logging_context({"associated_resources": []})
185184
logger.info(msg="No score sets are associated with the requested experiment.", extra=logging_context())
186185

187186
raise HTTPException(status_code=404, detail="no associated score sets")
188187
else:
189-
superseded_score_set_tails.sort(key=attrgetter("urn"))
188+
filtered_score_sets.sort(key=attrgetter("urn"))
190189
save_to_logging_context({"associated_resources": [item.urn for item in score_set_result]})
191190

192-
return superseded_score_set_tails
191+
return filtered_score_sets
193192

194193

195194
@router.post(

src/mavedb/routers/score_sets.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,15 @@ async def fetch_score_set_by_urn(
119119

120120

121121
@router.post("/score-sets/search", status_code=200, response_model=list[score_set.ShortScoreSet])
122-
def search_score_sets(search: ScoreSetsSearch, db: Session = Depends(deps.get_db)) -> Any: # = Body(..., embed=True),
122+
def search_score_sets(
123+
search: ScoreSetsSearch,
124+
db: Session = Depends(deps.get_db),
125+
user_data: Optional[UserData] = Depends(get_current_user),
126+
) -> Any: # = Body(..., embed=True),
123127
"""
124128
Search score sets.
125129
"""
126-
return _search_score_sets(db, None, search)
130+
return _search_score_sets(db, user_data, search)
127131

128132

129133
@router.post(
@@ -139,6 +143,7 @@ def search_my_score_sets(
139143
"""
140144
Search score sets created by the current user..
141145
"""
146+
search.me = True
142147
return _search_score_sets(db, user_data, search)
143148

144149

src/mavedb/view_models/search.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class ScoreSetsSearch(BaseModel):
2525
publication_identifiers: Optional[list[str]]
2626
keywords: Optional[list[str]]
2727
text: Optional[str]
28+
me: Optional[bool]
2829

2930

3031
class TextSearch(BaseModel):

tests/routers/test_experiments.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
TEST_MEDRXIV_IDENTIFIER,
2525
TEST_MINIMAL_EXPERIMENT,
2626
TEST_MINIMAL_EXPERIMENT_RESPONSE,
27+
TEST_MINIMAL_SEQ_SCORESET,
2728
TEST_ORCID_ID,
2829
TEST_PUBMED_IDENTIFIER,
2930
TEST_USER,
@@ -991,6 +992,112 @@ def test_search_score_sets_for_experiments(session, client, setup_router_db, dat
991992
assert response.json()[0]["urn"] == published_score_set["urn"]
992993

993994

995+
# Creator created a superseding score set but not published it yet.
996+
def test_owner_searches_score_sets_with_unpublished_superseding_score_sets_for_experiments(session, client, setup_router_db, data_files, data_provider):
997+
experiment = create_experiment(client)
998+
unpublished_score_set = create_seq_score_set_with_variants(
999+
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
1000+
)
1001+
publish_score_set_response = client.post(f"/api/v1/score-sets/{unpublished_score_set['urn']}/publish")
1002+
assert publish_score_set_response.status_code == 200
1003+
published_score_set = publish_score_set_response.json()
1004+
score_set_post_payload = deepcopy(TEST_MINIMAL_SEQ_SCORESET)
1005+
score_set_post_payload["experimentUrn"] = published_score_set["experiment"]["urn"]
1006+
score_set_post_payload["supersededScoreSetUrn"] = published_score_set["urn"]
1007+
superseding_score_set_response = client.post("/api/v1/score-sets/", json=score_set_post_payload)
1008+
assert superseding_score_set_response.status_code == 200
1009+
superseding_score_set = superseding_score_set_response.json()
1010+
1011+
# On score set publication, the experiment will get a new urn
1012+
experiment_urn = published_score_set["experiment"]["urn"]
1013+
response = client.get(f"/api/v1/experiments/{experiment_urn}/score-sets")
1014+
assert response.status_code == 200
1015+
assert len(response.json()) == 1
1016+
assert response.json()[0]["urn"] == superseding_score_set["urn"]
1017+
1018+
1019+
def test_non_owner_searches_score_sets_with_unpublished_superseding_score_sets_for_experiments(session, client, setup_router_db, data_files, data_provider):
1020+
experiment = create_experiment(client)
1021+
unpublished_score_set = create_seq_score_set_with_variants(
1022+
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
1023+
)
1024+
publish_score_set_response = client.post(f"/api/v1/score-sets/{unpublished_score_set['urn']}/publish")
1025+
assert publish_score_set_response.status_code == 200
1026+
published_score_set = publish_score_set_response.json()
1027+
score_set_post_payload = deepcopy(TEST_MINIMAL_SEQ_SCORESET)
1028+
score_set_post_payload["experimentUrn"] = published_score_set["experiment"]["urn"]
1029+
score_set_post_payload["supersededScoreSetUrn"] = published_score_set["urn"]
1030+
superseding_score_set_response = client.post("/api/v1/score-sets/", json=score_set_post_payload)
1031+
assert superseding_score_set_response.status_code == 200
1032+
superseding_score_set = superseding_score_set_response.json()
1033+
change_ownership(session, published_score_set["urn"], ScoreSetDbModel)
1034+
change_ownership(session, superseding_score_set["urn"], ScoreSetDbModel)
1035+
# On score set publication, the experiment will get a new urn
1036+
experiment_urn = published_score_set["experiment"]["urn"]
1037+
response = client.get(f"/api/v1/experiments/{experiment_urn}/score-sets")
1038+
assert response.status_code == 200
1039+
assert len(response.json()) == 1
1040+
assert response.json()[0]["urn"] == published_score_set["urn"]
1041+
1042+
1043+
def test_owner_searches_published_superseding_score_sets_for_experiments(session, client, setup_router_db, data_files, data_provider):
1044+
experiment = create_experiment(client)
1045+
unpublished_score_set = create_seq_score_set_with_variants(
1046+
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
1047+
)
1048+
publish_score_set_response = client.post(f"/api/v1/score-sets/{unpublished_score_set['urn']}/publish")
1049+
assert publish_score_set_response.status_code == 200
1050+
published_score_set = publish_score_set_response.json()
1051+
1052+
superseding_score_set = create_seq_score_set_with_variants(
1053+
client,
1054+
session,
1055+
data_provider,
1056+
published_score_set["experiment"]["urn"],
1057+
data_files / "scores.csv",
1058+
update={"supersededScoreSetUrn": published_score_set["urn"]},
1059+
)
1060+
published_superseding_score_set_response = client.post(f"/api/v1/score-sets/{superseding_score_set['urn']}/publish")
1061+
assert published_superseding_score_set_response.status_code == 200
1062+
published_superseding_score_set = published_superseding_score_set_response.json()
1063+
# On score set publication, the experiment will get a new urn
1064+
experiment_urn = published_score_set["experiment"]["urn"]
1065+
response = client.get(f"/api/v1/experiments/{experiment_urn}/score-sets")
1066+
assert response.status_code == 200
1067+
assert len(response.json()) == 1
1068+
assert response.json()[0]["urn"] == published_superseding_score_set["urn"]
1069+
1070+
1071+
def test_non_owner_searches_published_superseding_score_sets_for_experiments(session, client, setup_router_db, data_files, data_provider):
1072+
experiment = create_experiment(client)
1073+
unpublished_score_set = create_seq_score_set_with_variants(
1074+
client, session, data_provider, experiment["urn"], data_files / "scores.csv"
1075+
)
1076+
publish_score_set_response = client.post(f"/api/v1/score-sets/{unpublished_score_set['urn']}/publish")
1077+
assert publish_score_set_response.status_code == 200
1078+
published_score_set = publish_score_set_response.json()
1079+
1080+
superseding_score_set = create_seq_score_set_with_variants(
1081+
client,
1082+
session,
1083+
data_provider,
1084+
published_score_set["experiment"]["urn"],
1085+
data_files / "scores.csv",
1086+
update={"supersededScoreSetUrn": published_score_set["urn"]},
1087+
)
1088+
published_superseding_score_set_response = client.post(f"/api/v1/score-sets/{superseding_score_set['urn']}/publish")
1089+
assert published_superseding_score_set_response.status_code == 200
1090+
published_superseding_score_set = published_superseding_score_set_response.json()
1091+
change_ownership(session, published_score_set["urn"], ScoreSetDbModel)
1092+
change_ownership(session, published_superseding_score_set["urn"], ScoreSetDbModel)
1093+
# On score set publication, the experiment will get a new urn
1094+
experiment_urn = published_score_set["experiment"]["urn"]
1095+
response = client.get(f"/api/v1/experiments/{experiment_urn}/score-sets")
1096+
assert response.status_code == 200
1097+
assert len(response.json()) == 1
1098+
assert response.json()[0]["urn"] == published_superseding_score_set["urn"]
1099+
1100+
9941101
def test_search_score_sets_for_contributor_experiments(session, client, setup_router_db, data_files, data_provider):
9951102
experiment = create_experiment(client)
9961103
score_set_pub = create_seq_score_set_with_variants(

0 commit comments

Comments
 (0)