-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ENH] Add new query param for nodes that defaults to a prepopulated index #30
Changes from all commits
73d57d7
3f56fa6
ca2c113
d621bee
0b5277c
40627d6
945a3ef
a0b4777
02d2ba7
98343a8
0b5e6d4
cff42cb
6d81496
2a801a9
3a6451d
84f98a2
763ff30
b4e6b75
292752c
e11c0f7
302657c
2171604
3cbe7dc
92e7d95
6dd2df9
f8b366a
d6e8112
40daf15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,8 +1,8 @@ | ||||||||||||
"""Data models.""" | ||||||||||||
|
||||||||||||
from typing import Optional, Union | ||||||||||||
|
||||||||||||
from pydantic import BaseModel | ||||||||||||
from fastapi import Query | ||||||||||||
from pydantic import BaseModel, Field | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isort? mind opening an issue to add this to the CI? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isort is already included in the precommit hooks for this repo! 🙂 federation-api/.pre-commit-config.yaml Lines 25 to 29 in f85c44b
I think this is the expected import order following
|
||||||||||||
|
||||||||||||
CONTROLLED_TERM_REGEX = r"^[a-zA-Z]+[:]\S+$" | ||||||||||||
|
||||||||||||
|
@@ -18,6 +18,9 @@ class QueryModel(BaseModel): | |||||||||||
min_num_sessions: int = None | ||||||||||||
assessment: str = None | ||||||||||||
image_modal: str = None | ||||||||||||
# TODO: Replace default value with union of local and public nodes once https://github.com/neurobagel/federation-api/issues/28 is merged | ||||||||||||
# syntax from https://github.com/tiangolo/fastapi/issues/4445#issuecomment-1117632409 | ||||||||||||
node_url: list[str] | None = Field(Query(default=[])) | ||||||||||||
|
||||||||||||
|
||||||||||||
class CohortQueryResponse(BaseModel): | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Replace the value of LOCAL_NB_NODES below with the URL-name pairs of | ||
# any locally hosted nodes you wish to make available for federation | ||
LOCAL_NB_NODES=(https://myfirstnode.org/,First Node)(https://mysecondnode.org/,Second Node) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import os | ||
|
||
import httpx | ||
import pytest | ||
|
||
from app.api import utility as util | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"local_nodes", | ||
[ | ||
"(https://mylocalnode.org, Local Node)", | ||
"(https://mylocalnode.org/, Local Node) (https://firstpublicnode.org/, First Public Node)", | ||
], | ||
) | ||
def test_nodes_discovery_endpoint(test_app, monkeypatch, local_nodes): | ||
"""Test that a federation node index is correctly created from locally set and remote node lists.""" | ||
monkeypatch.setenv("LOCAL_NB_NODES", local_nodes) | ||
monkeypatch.setattr( | ||
util, | ||
"LOCAL_NODES", | ||
os.environ.get( | ||
"LOCAL_NB_NODES", "(https://api.neurobagel.org/, OpenNeuro)" | ||
), | ||
alyssadai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
def mock_httpx_get(**kwargs): | ||
return httpx.Response( | ||
status_code=200, | ||
json=[ | ||
{ | ||
"NodeName": "First Public Node", | ||
"ApiURL": "https://firstpublicnode.org", | ||
}, | ||
{ | ||
"NodeName": "Second Public Node", | ||
"ApiURL": "https://secondpublicnode.org", | ||
}, | ||
], | ||
) | ||
|
||
monkeypatch.setattr(httpx, "get", mock_httpx_get) | ||
|
||
with test_app: | ||
response = test_app.get("/nodes/") | ||
assert util.FEDERATION_NODES == { | ||
"https://firstpublicnode.org/": "First Public Node", | ||
"https://secondpublicnode.org/": "Second Public Node", | ||
"https://mylocalnode.org/": "Local Node", | ||
} | ||
assert response.json() == [ | ||
{ | ||
"NodeName": "First Public Node", | ||
"ApiURL": "https://firstpublicnode.org/", | ||
}, | ||
{ | ||
"NodeName": "Second Public Node", | ||
"ApiURL": "https://secondpublicnode.org/", | ||
}, | ||
{"NodeName": "Local Node", "ApiURL": "https://mylocalnode.org/"}, | ||
] | ||
|
||
|
||
def test_failed_public_nodes_fetching_raises_warning(test_app, monkeypatch): | ||
"""Test that when request for remote list of public nodes fails, an informative warning is raised and the federation node index only includes local nodes.""" | ||
monkeypatch.setenv( | ||
"LOCAL_NB_NODES", "(https://mylocalnode.org, Local Node)" | ||
) | ||
monkeypatch.setattr( | ||
util, | ||
"LOCAL_NODES", | ||
os.environ.get( | ||
"LOCAL_NB_NODES", "(https://api.neurobagel.org/, OpenNeuro)" | ||
alyssadai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
), | ||
) | ||
|
||
def mock_httpx_get(**kwargs): | ||
return httpx.Response( | ||
status_code=404, json={}, text="Some error message" | ||
) | ||
|
||
monkeypatch.setattr(httpx, "get", mock_httpx_get) | ||
|
||
with pytest.warns(UserWarning) as w: | ||
with test_app: | ||
response = test_app.get("/nodes/") | ||
assert util.FEDERATION_NODES == { | ||
"https://mylocalnode.org/": "Local Node" | ||
} | ||
assert response.json() == [ | ||
{ | ||
"NodeName": "Local Node", | ||
"ApiURL": "https://mylocalnode.org/", | ||
} | ||
] | ||
|
||
for warn_substr in [ | ||
"Unable to fetch directory of public Neurobagel nodes", | ||
"The federation API will only register the nodes defined locally for this API: {'https://mylocalnode.org/': 'Local Node'}", | ||
]: | ||
assert warn_substr in w[0].message.args[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this mean that the
get_terms
request will always go out to all known nodes, regardless of any subselection the user might have made? in other words, wouldn't we also want to restrict this byas above in
async def get
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've opened a separate issue for this
/{data_element_URI}
request based on specified nodes #36as I think this could benefit from some further discussion.
Right now, I think only the query tool uses this endpoint (
/attributes/{data_element_URI}
) to get all the instances of a given controlled term likenb:Assessment
(across the known graphs) to populate the dropdown options for the UI. I imagine this would be separate from/before the user actually selects specific nodes in the app to send a query to - not sure if we want the query tool to have dynamically changing dropdown options as a result.