Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/filters/brain_region_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Annotated

from fastapi_filter import FilterDepends

from app.db.model import Mesh as BrainRegionMesh
from app.filters.base import CustomFilter
from app.filters.common import BrainRegionFilterMixin, EntityFilterMixin


class BrainRegionMeshFilter(CustomFilter, BrainRegionFilterMixin, EntityFilterMixin):
order_by: list[str] = ["-creation_date"] # noqa: RUF012

class Constants(CustomFilter.Constants):
model = BrainRegionMesh
ordering_model_fields = ["creation_date", "update_date", "name"] # noqa: RUF012


BrainRegionMeshFilterDep = Annotated[BrainRegionMeshFilter, FilterDepends(BrainRegionMeshFilter)]
7 changes: 4 additions & 3 deletions app/filters/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,10 @@ class SubjectFilterMixin:


class BrainRegionFilter(NameFilterMixin, CustomFilter):
# TODO: Use IdFilterMixin when brain region keys migrate from int to uuid
id: int | None = None
id__in: list[int] | None = None
id: uuid.UUID | None = None
id__in: list[uuid.UUID] | None = None
hierarchy_id: uuid.UUID | None = None
annotation_value: int | None = None
acronym: str | None = None
acronym__in: list[str] | None = None
order_by: list[str] = ["name"] # noqa: RUF012
Expand Down
2 changes: 2 additions & 0 deletions app/routers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
asset,
brain_region,
brain_region_hierarchy,
brain_region_mesh,
cell_composition,
contribution,
electrical_cell_recording,
Expand Down Expand Up @@ -38,6 +39,7 @@
asset.router,
brain_region.router,
brain_region_hierarchy.router,
brain_region_mesh.router,
cell_composition.router,
contribution.router,
electrical_cell_recording.router,
Expand Down
12 changes: 12 additions & 0 deletions app/routers/brain_region_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from fastapi import APIRouter

import app.service.brain_region_mesh

router = APIRouter(
prefix="/brain-region-mesh",
tags=["brain-region-mesh"],
)

read_many = router.get("")(app.service.brain_region_mesh.read_many)
read_one = router.get("/{id_}")(app.service.brain_region_mesh.read_one)
create_one = router.post("")(app.service.brain_region_mesh.create_one)
28 changes: 28 additions & 0 deletions app/schemas/brain_region_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import uuid

from pydantic import BaseModel, ConfigDict

from app.schemas.base import (
AuthorizationMixin,
AuthorizationOptionalPublicMixin,
BrainRegionRead,
CreationMixin,
EntityTypeMixin,
IdentifiableMixin,
)


class BrainRegionMeshBase(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
description: str


class BrainRegionMeshCreate(BrainRegionMeshBase, AuthorizationOptionalPublicMixin):
brain_region_id: uuid.UUID


class BrainRegionMeshRead(
BrainRegionMeshBase, CreationMixin, IdentifiableMixin, AuthorizationMixin, EntityTypeMixin
):
brain_region: BrainRegionRead
87 changes: 87 additions & 0 deletions app/service/brain_region_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import uuid

from sqlalchemy.orm import joinedload, raiseload, selectinload

from app.db.model import BrainRegion, Mesh as BrainRegionMesh
from app.dependencies.auth import UserContextDep, UserContextWithProjectIdDep
from app.dependencies.common import (
FacetQueryParams,
FacetsDep,
InBrainRegionDep,
PaginationQuery,
SearchDep,
)
from app.dependencies.db import SessionDep
from app.filters.brain_region_mesh import BrainRegionMeshFilterDep
from app.queries import facets as fc
from app.queries.common import router_create_one, router_read_many, router_read_one
from app.schemas.brain_region_mesh import BrainRegionMeshCreate, BrainRegionMeshRead
from app.schemas.types import ListResponse


def read_one(
user_context: UserContextDep,
db: SessionDep,
id_: uuid.UUID,
) -> BrainRegionMeshRead:
return router_read_one(
db=db,
id_=id_,
db_model_class=BrainRegionMesh,
authorized_project_id=user_context.project_id,
response_schema_class=BrainRegionMeshRead,
apply_operations=lambda q: q.options(
joinedload(BrainRegionMesh.brain_region),
joinedload(BrainRegionMesh.assets),
raiseload("*"),
),
)


def create_one(
user_context: UserContextWithProjectIdDep,
json_model: BrainRegionMeshCreate,
db: SessionDep,
) -> BrainRegionMeshRead:
return router_create_one(
db=db,
json_model=json_model,
db_model_class=BrainRegionMesh,
authorized_project_id=user_context.project_id,
response_schema_class=BrainRegionMeshRead,
)


def read_many(
user_context: UserContextDep,
db: SessionDep,
pagination_request: PaginationQuery,
filter_model: BrainRegionMeshFilterDep,
with_search: SearchDep,
in_brain_region: InBrainRegionDep,
facets: FacetsDep,
) -> ListResponse[BrainRegionMeshRead]:
name_to_facet_query_params: dict[str, FacetQueryParams] = fc.brain_region
apply_filter_query = lambda query: (
query.join(BrainRegion, BrainRegionMesh.brain_region_id == BrainRegion.id)
)
apply_data_options = lambda query: (
query.options(joinedload(BrainRegionMesh.brain_region))
.options(selectinload(BrainRegionMesh.assets))
.options(raiseload("*"))
)
return router_read_many(
db=db,
filter_model=filter_model,
db_model_class=BrainRegionMesh,
with_search=with_search,
with_in_brain_region=in_brain_region,
facets=facets,
name_to_facet_query_params=name_to_facet_query_params,
apply_filter_query_operations=apply_filter_query,
apply_data_query_operations=apply_data_options,
aliases={},
pagination_request=pagination_request,
response_schema_class=BrainRegionMeshRead,
authorized_project_id=user_context.project_id,
)
103 changes: 103 additions & 0 deletions tests/test_brain_region_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import pytest

from .utils import (
assert_request,
check_authorization,
check_missing,
check_pagination,
)

ROUTE = "/brain-region-mesh"


@pytest.fixture
def json_data(
brain_region_id,
):
return {
"brain_region_id": str(brain_region_id),
"description": "my-description",
"name": "my-name",
"authorized_public": False,
}


def _assert_read_response(data, json_data):
assert data["brain_region"]["id"] == json_data["brain_region_id"]
assert data["description"] == json_data["description"]
assert data["name"] == json_data["name"]
assert data["type"] == "mesh"


@pytest.fixture
def create_id(client, json_data):
def _create_id(**kwargs):
return assert_request(client.post, url=ROUTE, json=json_data | kwargs).json()["id"]

return _create_id


@pytest.fixture
def model_id(create_id):
return create_id()


def test_create_one(client, json_data):
data = assert_request(client.post, url=ROUTE, json=json_data).json()
_assert_read_response(data, json_data)


def test_read_one(client, model_id, json_data):
data = assert_request(client.get, url=f"{ROUTE}/{model_id}").json()
_assert_read_response(data, json_data)

data = assert_request(client.get, url=ROUTE).json()
assert len(data["data"]) == 1


def test_missing(client):
check_missing(ROUTE, client)


def test_authorization(
client_user_1,
client_user_2,
client_no_project,
json_data,
):
check_authorization(ROUTE, client_user_1, client_user_2, client_no_project, json_data)


def test_pagination(client, create_id):
check_pagination(ROUTE, client, create_id)


def test_filtering(client, brain_region_id, brain_region_hierarchy_id, model_id):
data = assert_request(
client.get,
url=ROUTE,
params={"brain_region__id": str(model_id)},
).json()["data"]

assert len(data) == 0

data = assert_request(
client.get,
url=ROUTE,
params={"brain_region__id": str(brain_region_id)},
).json()["data"]

assert len(data) == 1
assert data[0]["brain_region"]["id"] == str(brain_region_id)

data = assert_request(
client.get,
url=ROUTE,
params={
"brain_region__name": "RedRegion",
"brain_region__hierarchy_id": str(brain_region_hierarchy_id),
},
).json()["data"]

assert len(data) == 1
assert data[0]["brain_region"]["id"] == str(brain_region_id)