Skip to content

Commit b3a3a9c

Browse files
Add BrainRegionMesh
1 parent 56fb881 commit b3a3a9c

File tree

7 files changed

+254
-3
lines changed

7 files changed

+254
-3
lines changed

app/filters/brain_region_mesh.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from typing import Annotated
2+
3+
from fastapi_filter import FilterDepends
4+
5+
from app.db.model import Mesh as BrainRegionMesh
6+
from app.filters.base import CustomFilter
7+
from app.filters.common import BrainRegionFilterMixin, EntityFilterMixin
8+
9+
10+
class BrainRegionMeshFilter(CustomFilter, BrainRegionFilterMixin, EntityFilterMixin):
11+
order_by: list[str] = ["-creation_date"] # noqa: RUF012
12+
13+
class Constants(CustomFilter.Constants):
14+
model = BrainRegionMesh
15+
ordering_model_fields = ["creation_date", "update_date", "name"] # noqa: RUF012
16+
17+
18+
BrainRegionMeshFilterDep = Annotated[BrainRegionMeshFilter, FilterDepends(BrainRegionMeshFilter)]

app/filters/common.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,10 @@ class SubjectFilterMixin:
140140

141141

142142
class BrainRegionFilter(NameFilterMixin, CustomFilter):
143-
# TODO: Use IdFilterMixin when brain region keys migrate from int to uuid
144-
id: int | None = None
145-
id__in: list[int] | None = None
143+
id: uuid.UUID | None = None
144+
id__in: list[uuid.UUID] | None = None
145+
hierarchy_id: uuid.UUID | None = None
146+
annotation_value: int | None = None
146147
acronym: str | None = None
147148
acronym__in: list[str] | None = None
148149
order_by: list[str] = ["name"] # noqa: RUF012

app/routers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
asset,
88
brain_region,
99
brain_region_hierarchy,
10+
brain_region_mesh,
1011
cell_composition,
1112
contribution,
1213
electrical_cell_recording,
@@ -38,6 +39,7 @@
3839
asset.router,
3940
brain_region.router,
4041
brain_region_hierarchy.router,
42+
brain_region_mesh.router,
4143
cell_composition.router,
4244
contribution.router,
4345
electrical_cell_recording.router,

app/routers/brain_region_mesh.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from fastapi import APIRouter
2+
3+
import app.service.brain_region_mesh
4+
5+
router = APIRouter(
6+
prefix="/brain-region-mesh",
7+
tags=["brain-region-mesh"],
8+
)
9+
10+
read_many = router.get("")(app.service.brain_region_mesh.read_many)
11+
read_one = router.get("/{id_}")(app.service.brain_region_mesh.read_one)
12+
create_one = router.post("")(app.service.brain_region_mesh.create_one)

app/schemas/brain_region_mesh.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import uuid
2+
3+
from pydantic import BaseModel, ConfigDict
4+
5+
from app.schemas.base import (
6+
AuthorizationMixin,
7+
AuthorizationOptionalPublicMixin,
8+
BrainRegionRead,
9+
CreationMixin,
10+
EntityTypeMixin,
11+
IdentifiableMixin,
12+
)
13+
14+
15+
class BrainRegionMeshBase(BaseModel):
16+
model_config = ConfigDict(from_attributes=True)
17+
name: str
18+
description: str
19+
20+
21+
class BrainRegionMeshCreate(BrainRegionMeshBase, AuthorizationOptionalPublicMixin):
22+
brain_region_id: uuid.UUID
23+
24+
25+
class BrainRegionMeshRead(
26+
BrainRegionMeshBase, CreationMixin, IdentifiableMixin, AuthorizationMixin, EntityTypeMixin
27+
):
28+
brain_region: BrainRegionRead

app/service/brain_region_mesh.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import uuid
2+
3+
from sqlalchemy.orm import joinedload, raiseload, selectinload
4+
5+
from app.db.model import BrainRegion, Mesh as BrainRegionMesh
6+
from app.dependencies.auth import UserContextDep, UserContextWithProjectIdDep
7+
from app.dependencies.common import (
8+
FacetQueryParams,
9+
FacetsDep,
10+
InBrainRegionDep,
11+
PaginationQuery,
12+
SearchDep,
13+
)
14+
from app.dependencies.db import SessionDep
15+
from app.filters.brain_region_mesh import BrainRegionMeshFilterDep
16+
from app.queries import facets as fc
17+
from app.queries.common import router_create_one, router_read_many, router_read_one
18+
from app.schemas.brain_region_mesh import BrainRegionMeshCreate, BrainRegionMeshRead
19+
from app.schemas.types import ListResponse
20+
21+
22+
def read_one(
23+
user_context: UserContextDep,
24+
db: SessionDep,
25+
id_: uuid.UUID,
26+
) -> BrainRegionMeshRead:
27+
return router_read_one(
28+
db=db,
29+
id_=id_,
30+
db_model_class=BrainRegionMesh,
31+
authorized_project_id=user_context.project_id,
32+
response_schema_class=BrainRegionMeshRead,
33+
apply_operations=lambda q: q.options(
34+
joinedload(BrainRegionMesh.brain_region),
35+
joinedload(BrainRegionMesh.assets),
36+
raiseload("*"),
37+
),
38+
)
39+
40+
41+
def create_one(
42+
user_context: UserContextWithProjectIdDep,
43+
json_model: BrainRegionMeshCreate,
44+
db: SessionDep,
45+
) -> BrainRegionMeshRead:
46+
return router_create_one(
47+
db=db,
48+
json_model=json_model,
49+
db_model_class=BrainRegionMesh,
50+
authorized_project_id=user_context.project_id,
51+
response_schema_class=BrainRegionMeshRead,
52+
)
53+
54+
55+
def read_many(
56+
user_context: UserContextDep,
57+
db: SessionDep,
58+
pagination_request: PaginationQuery,
59+
filter_model: BrainRegionMeshFilterDep,
60+
with_search: SearchDep,
61+
in_brain_region: InBrainRegionDep,
62+
facets: FacetsDep,
63+
) -> ListResponse[BrainRegionMeshRead]:
64+
name_to_facet_query_params: dict[str, FacetQueryParams] = fc.brain_region
65+
apply_filter_query = lambda query: (
66+
query.join(BrainRegion, BrainRegionMesh.brain_region_id == BrainRegion.id)
67+
)
68+
apply_data_options = lambda query: (
69+
query.options(joinedload(BrainRegionMesh.brain_region))
70+
.options(selectinload(BrainRegionMesh.assets))
71+
.options(raiseload("*"))
72+
)
73+
return router_read_many(
74+
db=db,
75+
filter_model=filter_model,
76+
db_model_class=BrainRegionMesh,
77+
with_search=with_search,
78+
with_in_brain_region=in_brain_region,
79+
facets=facets,
80+
name_to_facet_query_params=name_to_facet_query_params,
81+
apply_filter_query_operations=apply_filter_query,
82+
apply_data_query_operations=apply_data_options,
83+
aliases={},
84+
pagination_request=pagination_request,
85+
response_schema_class=BrainRegionMeshRead,
86+
authorized_project_id=user_context.project_id,
87+
)

tests/test_brain_region_mesh.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import pytest
2+
3+
from .utils import (
4+
assert_request,
5+
check_authorization,
6+
check_missing,
7+
check_pagination,
8+
)
9+
10+
ROUTE = "/brain-region-mesh"
11+
12+
13+
@pytest.fixture
14+
def json_data(
15+
brain_region_id,
16+
):
17+
return {
18+
"brain_region_id": str(brain_region_id),
19+
"description": "my-description",
20+
"name": "my-name",
21+
"authorized_public": False,
22+
}
23+
24+
25+
def _assert_read_response(data, json_data):
26+
assert data["brain_region"]["id"] == json_data["brain_region_id"]
27+
assert data["description"] == json_data["description"]
28+
assert data["name"] == json_data["name"]
29+
assert data["type"] == "mesh"
30+
31+
32+
@pytest.fixture
33+
def create_id(client, json_data):
34+
def _create_id(**kwargs):
35+
return assert_request(client.post, url=ROUTE, json=json_data | kwargs).json()["id"]
36+
37+
return _create_id
38+
39+
40+
@pytest.fixture
41+
def model_id(create_id):
42+
return create_id()
43+
44+
45+
def test_create_one(client, json_data):
46+
data = assert_request(client.post, url=ROUTE, json=json_data).json()
47+
_assert_read_response(data, json_data)
48+
49+
50+
def test_read_one(client, model_id, json_data):
51+
data = assert_request(client.get, url=f"{ROUTE}/{model_id}").json()
52+
_assert_read_response(data, json_data)
53+
54+
data = assert_request(client.get, url=ROUTE).json()
55+
assert len(data["data"]) == 1
56+
57+
58+
def test_missing(client):
59+
check_missing(ROUTE, client)
60+
61+
62+
def test_authorization(
63+
client_user_1,
64+
client_user_2,
65+
client_no_project,
66+
json_data,
67+
):
68+
check_authorization(ROUTE, client_user_1, client_user_2, client_no_project, json_data)
69+
70+
71+
def test_pagination(client, create_id):
72+
check_pagination(ROUTE, client, create_id)
73+
74+
75+
def test_filtering(client, brain_region_id, brain_region_hierarchy_id, model_id):
76+
data = assert_request(
77+
client.get,
78+
url=ROUTE,
79+
params={"brain_region__id": str(model_id)},
80+
).json()["data"]
81+
82+
assert len(data) == 0
83+
84+
data = assert_request(
85+
client.get,
86+
url=ROUTE,
87+
params={"brain_region__id": str(brain_region_id)},
88+
).json()["data"]
89+
90+
assert len(data) == 1
91+
assert data[0]["brain_region"]["id"] == str(brain_region_id)
92+
93+
data = assert_request(
94+
client.get,
95+
url=ROUTE,
96+
params={
97+
"brain_region__name": "RedRegion",
98+
"brain_region__hierarchy_id": str(brain_region_hierarchy_id),
99+
},
100+
).json()["data"]
101+
102+
assert len(data) == 1
103+
assert data[0]["brain_region"]["id"] == str(brain_region_id)

0 commit comments

Comments
 (0)