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
21 changes: 21 additions & 0 deletions app/db/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,27 @@ class ValidationResult(Entity):
}


class CalibrationResult(Entity):
__tablename__ = EntityType.calibration_result.value
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), primary_key=True)
value: Mapped[float] = mapped_column(default=False)

name: Mapped[str] = mapped_column(index=True)
description: Mapped[str] = mapped_column(index=True)

calibrated_entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), index=True)
calibrated_entity: Mapped[Entity] = relationship(
"Entity",
uselist=False,
foreign_keys=[calibrated_entity_id],
)

__mapper_args__ = { # noqa: RUF012
"polymorphic_identity": __tablename__,
"inherit_condition": id == Entity.id,
}


class Asset(Identifiable):
"""Asset table."""

Expand Down
1 change: 1 addition & 0 deletions app/db/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class EntityType(StrEnum):
ion_channel_model = auto()
subject = auto()
validation_result = auto()
calibration_result = auto()


class AgentType(StrEnum):
Expand Down
27 changes: 27 additions & 0 deletions app/filters/calibration_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import uuid
from typing import Annotated

from fastapi_filter import FilterDepends

from app.db.model import CalibrationResult
from app.filters.base import CustomFilter
from app.filters.common import EntityFilterMixin


class CalibrationResultFilter(
CustomFilter,
EntityFilterMixin,
):
value: float | None = None
validated_entity_id: uuid.UUID | None = None

order_by: list[str] = ["name"] # noqa: RUF012

class Constants(CustomFilter.Constants):
model = CalibrationResult
ordering_model_fields = ["name"] # noqa: RUF012


CalibrationResultFilterDep = Annotated[
CalibrationResultFilter, FilterDepends(CalibrationResultFilter)
]
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,
calibration_result,
cell_composition,
contribution,
electrical_cell_recording,
Expand Down Expand Up @@ -40,6 +41,7 @@
asset.router,
brain_region.router,
brain_region_hierarchy.router,
calibration_result.router,
cell_composition.router,
contribution.router,
electrical_cell_recording.router,
Expand Down
12 changes: 12 additions & 0 deletions app/routers/calibration_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from fastapi import APIRouter

import app.service.calibration_result

router = APIRouter(
prefix="/calibration-result",
tags=["calibration-result"],
)

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

from pydantic import BaseModel

from app.schemas.agent import CreatedByUpdatedByMixin
from app.schemas.base import (
CreationMixin,
IdentifiableMixin,
)


class CalibrationResultBase(BaseModel):
name: str
description: str
value: float
calibrated_entity_id: uuid.UUID


class CalibrationResultRead(
CalibrationResultBase, CreationMixin, IdentifiableMixin, CreatedByUpdatedByMixin
):
pass


class CalibrationResultCreate(CalibrationResultBase):
pass
82 changes: 82 additions & 0 deletions app/service/calibration_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import uuid

import sqlalchemy as sa
from sqlalchemy.orm import joinedload

from app.db.model import CalibrationResult, Subject
from app.dependencies.auth import UserContextDep, UserContextWithProjectIdDep
from app.dependencies.common import (
FacetsDep,
InBrainRegionDep,
PaginationQuery,
SearchDep,
)
from app.dependencies.db import SessionDep
from app.filters.calibration_result import CalibrationResultFilterDep
from app.queries.common import router_create_one, router_read_many, router_read_one
from app.schemas.calibration import CalibrationResultCreate, CalibrationResultRead
from app.schemas.types import ListResponse


def _load(query: sa.Select):
return query.options(
joinedload(Subject.species),
)


def read_one(
user_context: UserContextDep,
db: SessionDep,
id_: uuid.UUID,
) -> CalibrationResultRead:
return router_read_one(
db=db,
id_=id_,
db_model_class=CalibrationResult,
authorized_project_id=user_context.project_id,
response_schema_class=CalibrationResultRead,
apply_operations=_load,
)


def create_one(
user_context: UserContextWithProjectIdDep,
json_model: CalibrationResultCreate,
db: SessionDep,
) -> CalibrationResultRead:
return router_create_one(
db=db,
user_context=user_context,
db_model_class=CalibrationResult,
json_model=json_model,
response_schema_class=CalibrationResultRead,
)


def read_many(
user_context: UserContextDep,
db: SessionDep,
pagination_request: PaginationQuery,
filter_model: CalibrationResultFilterDep,
with_search: SearchDep,
facets: FacetsDep,
in_brain_region: InBrainRegionDep,
) -> ListResponse[CalibrationResultRead]:
aliases = {}
name_to_facet_query_params = {}
return router_read_many(
db=db,
filter_model=filter_model,
db_model_class=CalibrationResult,
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=None,
apply_data_query_operations=_load,
aliases=aliases,
pagination_request=pagination_request,
response_schema_class=CalibrationResultRead,
authorized_project_id=user_context.project_id,
filter_joins=None,
)
15 changes: 15 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,21 @@ def validation_result_id(client, morphology_id):
).json()["id"]


@pytest.fixture
def calibration_result_id(client, memodel_id):
return assert_request(
client.post,
url="/calibration-result",
json={
"name": "threshold_current",
"description": "threshold_current (mV) of the memodel for testing",
"value": 0.0654321,
"calibrated_entity_id": str(memodel_id),
"authorized_public": False,
},
).json()["id"]


CreateIds = Callable[[int], list[str]]


Expand Down
136 changes: 136 additions & 0 deletions tests/test_calibration_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import uuid

import pytest
from fastapi.testclient import TestClient

from app.db.model import CalibrationResult

from .utils import assert_request

MODEL = CalibrationResult
ROUTE = "/calibration-result"


@pytest.fixture
def json_data(memodel_id):
return {
"value": 0.0654321,
"calibrated_entity_id": str(memodel_id),
"name": "threshold_current",
"description": "threshold_current (mV) of the memodel for testing",
"authorized_public": False,
}


@pytest.fixture
def create(client, json_data):
def _create(**kwargs):
return assert_request(client.post, url=ROUTE, json=json_data | kwargs).json()

return _create


def _assert_read_response(data, json_data):
assert data["name"] == json_data["name"]
assert data["description"] == json_data["description"]
assert data["value"] == json_data["value"]
assert data["calibrated_entity_id"] == json_data["calibrated_entity_id"]
assert data["createdBy"]["id"] == data["updatedBy"]["id"]
assert data["creation_date"] == data["update_date"]


def test_read_one(client: TestClient, calibrated_result_id, json_data):
data = assert_request(client.get, url=f"{ROUTE}/{calibrated_result_id}").json()
_assert_read_response(data, json_data)
assert data["id"] == calibrated_result_id


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

data = assert_request(client.get, url=f"{ROUTE}/{data['id']}").json()
_assert_read_response(data, json_data)

data = assert_request(client.get, url=ROUTE).json()["data"][0]
_assert_read_response(data, json_data)


def test_missing(client):
response = client.get(f"{ROUTE}/{uuid.uuid4()}")
assert response.status_code == 404

response = client.get(f"{ROUTE}/notauuid")
assert response.status_code == 422


def test_filtering__one_entry(client, calibration_result_id, memodel_id):
# no results expected for unrelated id
data = assert_request(
client.get,
url=ROUTE,
params={"calibrated_entity_id": str(calibration_result_id)},
).json()["data"]

assert len(data) == 0

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

assert len(data) == 1
assert data[0]["calibrated_entity_id"] == str(memodel_id)


@pytest.fixture
def models(create, memodel_id, emodel_id):
return [
create(
name="me1",
description="test memodel 1",
value=0.1234567,
validated_entity_id=str(memodel_id),
),
create(
name="me2",
description="test memodel 2",
value=0.0,
validated_entity_id=str(memodel_id),
),
create(
name="e1",
description="test emodel 1",
value=0.1234567,
validated_entity_id=str(emodel_id),
),
create(
name="e2",
description="test emodel 2",
value=0.0,
validated_entity_id=str(emodel_id),
),
]


def test_filtering__many_entries(client, models, memodel_id, emodel_id):
data = assert_request(
client.get,
url=ROUTE,
params={"calibrated_entity_id": str(memodel_id)},
).json()["data"]

assert len(data) == 2
assert data[0]["calibrated_entity_id"] == models[0]["calibrated_entity_id"]
assert data[1]["calibrated_entity_id"] == models[1]["calibrated_entity_id"]

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

assert len(data) == 2
assert data[0]["calibrated_entity_id"] == models[2]["calibrated_entity_id"]
assert data[1]["calibrated_entity_id"] == models[3]["calibrated_entity_id"]
Loading