From 09ab372d273651887fcbfd988b9483f612c1b470 Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Thu, 22 May 2025 09:40:53 +0200 Subject: [PATCH 01/18] me model calibration result proposal --- app/db/model.py | 19 +++++++++++++++++++ app/db/types.py | 1 + 2 files changed, 20 insertions(+) diff --git a/app/db/model.py b/app/db/model.py index 9df07198..1bbdffeb 100644 --- a/app/db/model.py +++ b/app/db/model.py @@ -832,6 +832,25 @@ class ValidationResult(Entity): } +class MEModelCalibrationResult(Entity): + __tablename__ = EntityType.memodel_calibration_result.value + id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), primary_key=True) + holding_current: Mapped[float] + threshold_current: Mapped[float] + rin: Mapped[float] + calibrated_entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("MEModel.id"), index=True) + calibrated_entity: Mapped[Entity] = relationship( + "MEModel", + uselist=False, + foreign_keys=[calibrated_entity_id], + ) + + __mapper_args__ = { # noqa: RUF012 + "polymorphic_identity": __tablename__, + "inherit_condition": id == Entity.id, + } + + class Asset(Identifiable): """Asset table.""" diff --git a/app/db/types.py b/app/db/types.py index 1250b004..4eb0b02a 100644 --- a/app/db/types.py +++ b/app/db/types.py @@ -53,6 +53,7 @@ class EntityType(StrEnum): brain_atlas_region = auto() emodel = auto() cell_composition = auto() + memodel_calibration_result = auto() experimental_bouton_density = auto() experimental_neuron_density = auto() experimental_synapses_per_connection = auto() From 19dc825100795efa4348689dbbfdd5776aa0673f Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Thu, 22 May 2025 16:39:57 +0200 Subject: [PATCH 02/18] make rin optional, remove holding/threshold current from MEModel --- app/db/model.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/db/model.py b/app/db/model.py index 1bbdffeb..b798b9a2 100644 --- a/app/db/model.py +++ b/app/db/model.py @@ -483,9 +483,6 @@ class MEModel( "ReconstructionMorphology", foreign_keys=[morphology_id], uselist=False ) - holding_current: Mapped[float | None] - threshold_current: Mapped[float | None] - emodel_id: Mapped[uuid.UUID] = mapped_column(ForeignKey(f"{EntityType.emodel}.id")) emodel = relationship("EModel", foreign_keys=[emodel_id], uselist=False) @@ -837,7 +834,7 @@ class MEModelCalibrationResult(Entity): id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), primary_key=True) holding_current: Mapped[float] threshold_current: Mapped[float] - rin: Mapped[float] + rin: Mapped[float | None] calibrated_entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("MEModel.id"), index=True) calibrated_entity: Mapped[Entity] = relationship( "MEModel", From ed14f1282012948c0f9cc454ec83275b5263b766 Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 09:27:18 +0200 Subject: [PATCH 03/18] MEModelCalibrationResult schema --- app/schemas/memodel_calibration_result.py | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/schemas/memodel_calibration_result.py diff --git a/app/schemas/memodel_calibration_result.py b/app/schemas/memodel_calibration_result.py new file mode 100644 index 00000000..e7a9b044 --- /dev/null +++ b/app/schemas/memodel_calibration_result.py @@ -0,0 +1,25 @@ +import uuid + +from pydantic import BaseModel + +from app.schemas.entity import EntityRead + + +class MEModelCalibrationResultBase(BaseModel): + """Base model for MEModel calibration results.""" + + holding_current: float + threshold_current: float + rin: float | None = None + + +class MEModelCalibrationResultRead(MEModelCalibrationResultBase, EntityRead): + """Read model for MEModel calibration results, including entity metadata.""" + + calibrated_entity_id: uuid.UUID + + +class MEModelCalibrationResultCreate(MEModelCalibrationResultBase): + """Create model for MEModel calibration results.""" + + calibrated_entity_id: uuid.UUID From b45627490a7af155e6add5515cc14fe27c8a8c0e Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 11:13:25 +0200 Subject: [PATCH 04/18] add filter and router --- app/filters/memodel_calibration_result.py | 25 +++++++ app/routers/memodel_calibration_result.py | 9 +++ app/service/memodel_calibration_result.py | 85 +++++++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 app/filters/memodel_calibration_result.py create mode 100644 app/routers/memodel_calibration_result.py create mode 100644 app/service/memodel_calibration_result.py diff --git a/app/filters/memodel_calibration_result.py b/app/filters/memodel_calibration_result.py new file mode 100644 index 00000000..5a0a5159 --- /dev/null +++ b/app/filters/memodel_calibration_result.py @@ -0,0 +1,25 @@ +import uuid +from typing import Annotated + +from fastapi_filter import FilterDepends + +from app.db.model import MEModelCalibrationResult +from app.filters.base import CustomFilter +from app.filters.common import EntityFilterMixin + + +class MEModelCalibrationResultFilter( + CustomFilter, + EntityFilterMixin, +): + passed: bool | None = None + validated_entity_id: uuid.UUID | None = None + + order_by: list[str] = ["name"] # noqa: RUF012 + + class Constants(CustomFilter.Constants): + model = MEModelCalibrationResult + ordering_model_fields = ["name"] # noqa: RUF012 + + +MEModelCalibrationResultFilterDep = Annotated[MEModelCalibrationResultFilter, FilterDepends(MEModelCalibrationResultFilter)] diff --git a/app/routers/memodel_calibration_result.py b/app/routers/memodel_calibration_result.py new file mode 100644 index 00000000..315ac120 --- /dev/null +++ b/app/routers/memodel_calibration_result.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter +import app.service.memodel_calibration_result +router = APIRouter( + prefix="/memodel-calibration-result", + tags=["memodel-calibration-result"], +) +read_many = router.get("")(app.service.memodel_calibration_result.read_many) +read_one = router.get("/{id_}")(app.service.memodel_calibration_result.read_one) +create_one = router.post("")(app.service.memodel_calibration_result.create_one) \ No newline at end of file diff --git a/app/service/memodel_calibration_result.py b/app/service/memodel_calibration_result.py new file mode 100644 index 00000000..01603b1e --- /dev/null +++ b/app/service/memodel_calibration_result.py @@ -0,0 +1,85 @@ +import uuid + +import sqlalchemy as sa +from sqlalchemy.orm import joinedload + +from app.db.model import MEModelCalibrationResult, 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.memodel_calibration_result import MEModelCalibrationResultFilterDep +from app.queries.common import router_create_one, router_read_many, router_read_one +from app.schemas.memodel_calibration_result import ( + MEModelCalibrationResultCreate, + MEModelCalibrationResultRead, +) +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, +) -> MEModelCalibrationResultRead: + return router_read_one( + db=db, + id_=id_, + db_model_class=MEModelCalibrationResult, + authorized_project_id=user_context.project_id, + response_schema_class=MEModelCalibrationResultRead, + apply_operations=_load, + ) + + +def create_one( + user_context: UserContextWithProjectIdDep, + json_model: MEModelCalibrationResultCreate, + db: SessionDep, +) -> MEModelCalibrationResultRead: + return router_create_one( + db=db, + user_context=user_context, + db_model_class=MEModelCalibrationResult, + json_model=json_model, + response_schema_class=MEModelCalibrationResultRead, + ) + + +def read_many( + user_context: UserContextDep, + db: SessionDep, + pagination_request: PaginationQuery, + filter_model: MEModelCalibrationResultFilterDep, + with_search: SearchDep, + facets: FacetsDep, + in_brain_region: InBrainRegionDep, +) -> ListResponse[MEModelCalibrationResultRead]: + aliases = {} + name_to_facet_query_params = {} + return router_read_many( + db=db, + filter_model=filter_model, + db_model_class=MEModelCalibrationResult, + 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=MEModelCalibrationResultRead, + authorized_project_id=user_context.project_id, + filter_joins=None, + ) From 6ec4c6922d9f4fa61efcc49dfe873870310d47db Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 16:21:35 +0200 Subject: [PATCH 05/18] fixing test --- ..._a864888b6a39_default_migration_message.py | 136 ++++++++++++++++++ app/db/model.py | 2 +- app/filters/memodel_calibration_result.py | 6 +- app/routers/__init__.py | 2 + app/routers/memodel_calibration_result.py | 2 + app/schemas/me_model.py | 2 - app/schemas/memodel_calibration_result.py | 8 +- tests/conftest.py | 19 ++- tests/test_memodel.py | 6 - tests/test_memodel_calibration_result.py | 114 +++++++++++++++ tests/test_single_neuron_simulation.py | 8 -- tests/test_single_neuron_synaptome.py | 6 - ...test_single_neuron_synaptome_simulation.py | 2 - 13 files changed, 279 insertions(+), 34 deletions(-) create mode 100644 alembic/versions/20250526_124351_a864888b6a39_default_migration_message.py create mode 100644 tests/test_memodel_calibration_result.py diff --git a/alembic/versions/20250526_124351_a864888b6a39_default_migration_message.py b/alembic/versions/20250526_124351_a864888b6a39_default_migration_message.py new file mode 100644 index 00000000..766c0bd1 --- /dev/null +++ b/alembic/versions/20250526_124351_a864888b6a39_default_migration_message.py @@ -0,0 +1,136 @@ +"""Default migration message + +Revision ID: a864888b6a39 +Revises: 9730c55381f3 +Create Date: 2025-05-26 12:43:51.408647 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from alembic_postgresql_enum import TableReference + +from sqlalchemy import Text +import app.db.types + +# revision identifiers, used by Alembic. +revision: str = "a864888b6a39" +down_revision: Union[str, None] = "9730c55381f3" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "memodel_calibration_result", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("holding_current", sa.Float(), nullable=False), + sa.Column("threshold_current", sa.Float(), nullable=False), + sa.Column("rin", sa.Float(), nullable=True), + sa.Column("calibrated_entity_id", sa.Uuid(), nullable=False), + sa.ForeignKeyConstraint( + ["calibrated_entity_id"], + ["memodel.id"], + name=op.f("fk_memodel_calibration_result_calibrated_entity_id_memodel"), + ), + sa.ForeignKeyConstraint( + ["id"], ["entity.id"], name=op.f("fk_memodel_calibration_result_id_entity") + ), + sa.PrimaryKeyConstraint("id", name=op.f("pk_memodel_calibration_result")), + ) + op.create_index( + op.f("ix_memodel_calibration_result_calibrated_entity_id"), + "memodel_calibration_result", + ["calibrated_entity_id"], + unique=False, + ) + op.drop_column("memodel", "holding_current") + op.drop_column("memodel", "threshold_current") + op.sync_enum_values( + enum_schema="public", + enum_name="entitytype", + new_values=[ + "analysis_software_source_code", + "brain_atlas", + "emodel", + "cell_composition", + "memodel_calibration_result", + "experimental_bouton_density", + "experimental_neuron_density", + "experimental_synapses_per_connection", + "memodel", + "mesh", + "me_type_density", + "reconstruction_morphology", + "electrical_cell_recording", + "electrical_recording_stimulus", + "single_neuron_simulation", + "single_neuron_synaptome", + "single_neuron_synaptome_simulation", + "ion_channel_model", + "subject", + "validation_result", + ], + affected_columns=[ + TableReference(table_schema="public", table_name="entity", column_name="type") + ], + enum_values_to_rename=[], + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.sync_enum_values( + enum_schema="public", + enum_name="entitytype", + new_values=[ + "analysis_software_source_code", + "brain_atlas", + "emodel", + "cell_composition", + "experimental_bouton_density", + "experimental_neuron_density", + "experimental_synapses_per_connection", + "memodel", + "mesh", + "me_type_density", + "reconstruction_morphology", + "electrical_cell_recording", + "electrical_recording_stimulus", + "single_neuron_simulation", + "single_neuron_synaptome", + "single_neuron_synaptome_simulation", + "ion_channel_model", + "subject", + "validation_result", + ], + affected_columns=[ + TableReference(table_schema="public", table_name="entity", column_name="type") + ], + enum_values_to_rename=[], + ) + op.add_column( + "memodel", + sa.Column( + "threshold_current", + sa.DOUBLE_PRECISION(precision=53), + autoincrement=False, + nullable=True, + ), + ) + op.add_column( + "memodel", + sa.Column( + "holding_current", sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True + ), + ) + op.drop_index( + op.f("ix_memodel_calibration_result_calibrated_entity_id"), + table_name="memodel_calibration_result", + ) + op.drop_table("memodel_calibration_result") + # ### end Alembic commands ### diff --git a/app/db/model.py b/app/db/model.py index b798b9a2..dca0d295 100644 --- a/app/db/model.py +++ b/app/db/model.py @@ -835,7 +835,7 @@ class MEModelCalibrationResult(Entity): holding_current: Mapped[float] threshold_current: Mapped[float] rin: Mapped[float | None] - calibrated_entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("MEModel.id"), index=True) + calibrated_entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("memodel.id"), index=True) calibrated_entity: Mapped[Entity] = relationship( "MEModel", uselist=False, diff --git a/app/filters/memodel_calibration_result.py b/app/filters/memodel_calibration_result.py index 5a0a5159..a7afd09b 100644 --- a/app/filters/memodel_calibration_result.py +++ b/app/filters/memodel_calibration_result.py @@ -13,13 +13,13 @@ class MEModelCalibrationResultFilter( EntityFilterMixin, ): passed: bool | None = None - validated_entity_id: uuid.UUID | None = None + calibrated_entity_id: uuid.UUID | None = None - order_by: list[str] = ["name"] # noqa: RUF012 + order_by: list[str] = ["calibrated_entity_id"] # noqa: RUF012 class Constants(CustomFilter.Constants): model = MEModelCalibrationResult - ordering_model_fields = ["name"] # noqa: RUF012 + ordering_model_fields = ["calibrated_entity_id"] # noqa: RUF012 MEModelCalibrationResultFilterDep = Annotated[MEModelCalibrationResultFilter, FilterDepends(MEModelCalibrationResultFilter)] diff --git a/app/routers/__init__.py b/app/routers/__init__.py index 255dd5b0..1d8d7c79 100644 --- a/app/routers/__init__.py +++ b/app/routers/__init__.py @@ -20,6 +20,7 @@ license, measurement_annotation, memodel, + memodel_calibration_result, morphology, mtype, organization, @@ -54,6 +55,7 @@ license.router, measurement_annotation.router, memodel.router, + memodel_calibration_result.router, morphology.router, mtype.router, organization.router, diff --git a/app/routers/memodel_calibration_result.py b/app/routers/memodel_calibration_result.py index 315ac120..eed953ac 100644 --- a/app/routers/memodel_calibration_result.py +++ b/app/routers/memodel_calibration_result.py @@ -1,5 +1,7 @@ from fastapi import APIRouter + import app.service.memodel_calibration_result + router = APIRouter( prefix="/memodel-calibration-result", tags=["memodel-calibration-result"], diff --git a/app/schemas/me_model.py b/app/schemas/me_model.py index f407c443..29088ef4 100644 --- a/app/schemas/me_model.py +++ b/app/schemas/me_model.py @@ -25,8 +25,6 @@ class MEModelBase(BaseModel): name: str description: str validation_status: ValidationStatus = ValidationStatus.created - holding_current: float | None = None - threshold_current: float | None = None # To be used by entities who reference MEModel diff --git a/app/schemas/memodel_calibration_result.py b/app/schemas/memodel_calibration_result.py index e7a9b044..6ab56f55 100644 --- a/app/schemas/memodel_calibration_result.py +++ b/app/schemas/memodel_calibration_result.py @@ -2,7 +2,11 @@ from pydantic import BaseModel -from app.schemas.entity import EntityRead +from app.schemas.agent import CreatedByUpdatedByMixin +from app.schemas.base import ( + CreationMixin, + IdentifiableMixin, +) class MEModelCalibrationResultBase(BaseModel): @@ -13,7 +17,7 @@ class MEModelCalibrationResultBase(BaseModel): rin: float | None = None -class MEModelCalibrationResultRead(MEModelCalibrationResultBase, EntityRead): +class MEModelCalibrationResultRead(MEModelCalibrationResultBase, CreationMixin, IdentifiableMixin, CreatedByUpdatedByMixin): """Read model for MEModel calibration results, including entity metadata.""" calibrated_entity_id: uuid.UUID diff --git a/tests/conftest.py b/tests/conftest.py index 38173532..92acbdb1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -385,6 +385,21 @@ def validation_result_id(client, morphology_id): }, ).json()["id"] +@pytest.fixture +def memodel_calibration_result_id(client, memodel_id): + return assert_request( + client.post, + url="/memodel-calibration-result", + json={ + "name": "test_memodel_calibration_result", + "calibrated_entity_id": str(memodel_id), + "authorized_public": False, + "threshold_current": 0.8, + "holding_current": 0.2, + "rin": 100.0, # Optional field, can be None + }, + ).json()["id"] + CreateIds = Callable[[int], list[str]] @@ -475,8 +490,6 @@ def _create_memodel_ids(count: int): emodel_id=emodel_id, authorized_public=False, authorized_project_id=PROJECT_ID, - holding_current=0, - threshold_current=0, ), ).id @@ -654,8 +667,6 @@ def faceted_memodels(db: Session, client: TestClient, agents: tuple[Agent, Agent emodel_id=emodel_id, authorized_public=False, authorized_project_id=PROJECT_ID, - holding_current=0, - threshold_current=0, ), ) diff --git a/tests/test_memodel.py b/tests/test_memodel.py index d7a933fa..497baa05 100644 --- a/tests/test_memodel.py +++ b/tests/test_memodel.py @@ -57,8 +57,6 @@ def test_create_memodel( "name": "Test MEModel Name", "morphology_id": morphology_id, "emodel_id": emodel_id, - "holding_current": 0, - "threshold_current": 0, }, ) assert response.status_code == 200, f"Failed to create memodel: {response.text}" @@ -490,8 +488,6 @@ def test_authorization( "strain_id": strain_id, "emodel_id": emodel_id, "morphology_id": morphology_id, - "holding_current": 0, - "threshold_current": 0, } public_obj = client_user_1.post( @@ -645,8 +641,6 @@ def create_model_function(_db, name, brain_region_id): morphology_id=morphology_id, emodel_id=emodel_id, authorized_project_id=PROJECT_ID, - holding_current=0, - threshold_current=0, ) check_brain_region_filter(ROUTE, client, db, brain_region_hierarchy_id, create_model_function) diff --git a/tests/test_memodel_calibration_result.py b/tests/test_memodel_calibration_result.py new file mode 100644 index 00000000..3f7c08ff --- /dev/null +++ b/tests/test_memodel_calibration_result.py @@ -0,0 +1,114 @@ + +import uuid + +import pytest +from fastapi.testclient import TestClient + +from app.db.model import MEModelCalibrationResult + +from .utils import assert_request + +MODEL = MEModelCalibrationResult +ROUTE = "/memodel-calibration-result" + + +@pytest.fixture +def json_data(memodel_id): + return { + "calibrated_entity_id": str(memodel_id), + "authorized_public": False, + "threshold_current": 0.8, + "holding_current": 0.2, + "rin": 100.0, # Optional field, can be None + } + + +@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["threshold_current"] == json_data["threshold_current"] + assert data["holding_current"] == json_data["holding_current"] + assert data["rin"] == json_data.get("rin") + 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, memodel_calibration_result_id, json_data): + data = assert_request(client.get, url=f"{ROUTE}/{memodel_calibration_result_id}").json() + _assert_read_response(data, json_data) + assert data["id"] == memodel_calibration_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, memodel_calibration_result_id, memodel_id, morphology_id): + # no results expected for unrelated id + data = assert_request( + client.get, + url=ROUTE, + params={"calibrated_entity_id": str(morphology_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) + assert data[0]["id"] == str(memodel_calibration_result_id) + + +@pytest.fixture +def models(create, memodel_id): + return [ + create( + threshold_current=0.5, + holding_current=0.1, + calibrated_entity_id=str(memodel_id), + ), + create( + threshold_current=0.5, + holding_current=0.1, + calibrated_entity_id=str(memodel_id), + ), + ] + + +def test_filtering__many_entries(client, models, memodel_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"] diff --git a/tests/test_single_neuron_simulation.py b/tests/test_single_neuron_simulation.py index 769059dc..7f7bdfa0 100644 --- a/tests/test_single_neuron_simulation.py +++ b/tests/test_single_neuron_simulation.py @@ -176,8 +176,6 @@ def test_pagination(db, client, brain_region_id, emodel_id, morphology_id, speci emodel_id=emodel_id, morphology_id=morphology_id, species_id=species_id, - holding_current=0, - threshold_current=0, ), ) me_model_2 = add_db( @@ -190,8 +188,6 @@ def test_pagination(db, client, brain_region_id, emodel_id, morphology_id, speci emodel_id=emodel_id, morphology_id=morphology_id, species_id=species_id, - holding_current=0, - threshold_current=0, ), ) @@ -244,8 +240,6 @@ def faceted_ids(db, brain_region_hierarchy_id, emodel_id, morphology_id, species "emodel_id": emodel_id, "morphology_id": morphology_id, "species_id": species_id, - "holding_current": 0, - "threshold_current": 0, }, ) for i in range(2) @@ -343,8 +337,6 @@ def create_model_function(db, name, brain_region_id): "emodel_id": emodel_id, "morphology_id": morphology_id, "species_id": species_id, - "holding_current": 0, - "threshold_current": 0, }, ) ) diff --git a/tests/test_single_neuron_synaptome.py b/tests/test_single_neuron_synaptome.py index 5adf1be8..4b5c735e 100644 --- a/tests/test_single_neuron_synaptome.py +++ b/tests/test_single_neuron_synaptome.py @@ -194,8 +194,6 @@ def test_pagination(db, client, brain_region_id, emodel_id, morphology_id, speci emodel_id=emodel_id, morphology_id=morphology_id, species_id=species_id, - holding_current=0, - threshold_current=0, ), ) me_model_2 = add_db( @@ -208,8 +206,6 @@ def test_pagination(db, client, brain_region_id, emodel_id, morphology_id, speci emodel_id=emodel_id, morphology_id=morphology_id, species_id=species_id, - holding_current=0, - threshold_current=0, ), ) @@ -355,8 +351,6 @@ def create_model_function(db, name, brain_region_id): emodel_id=emodel_id, morphology_id=morphology_id, species_id=species_id, - holding_current=0, - threshold_current=0, ), ).id ) diff --git a/tests/test_single_neuron_synaptome_simulation.py b/tests/test_single_neuron_synaptome_simulation.py index 209efb19..5abf5ddc 100644 --- a/tests/test_single_neuron_synaptome_simulation.py +++ b/tests/test_single_neuron_synaptome_simulation.py @@ -383,8 +383,6 @@ def create_model_function(db, name, brain_region_id): emodel_id=emodel_id, morphology_id=morphology_id, species_id=species_id, - holding_current=0.0, - threshold_current=0.0, ), ).id ) From f17031480e579c56036638542ef99f5f50d319a4 Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 16:30:23 +0200 Subject: [PATCH 06/18] fix ruff --- app/filters/memodel_calibration_result.py | 4 +++- app/routers/memodel_calibration_result.py | 2 +- app/schemas/memodel_calibration_result.py | 4 +++- tests/conftest.py | 1 + tests/test_memodel_calibration_result.py | 1 - 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/filters/memodel_calibration_result.py b/app/filters/memodel_calibration_result.py index a7afd09b..3fc62974 100644 --- a/app/filters/memodel_calibration_result.py +++ b/app/filters/memodel_calibration_result.py @@ -22,4 +22,6 @@ class Constants(CustomFilter.Constants): ordering_model_fields = ["calibrated_entity_id"] # noqa: RUF012 -MEModelCalibrationResultFilterDep = Annotated[MEModelCalibrationResultFilter, FilterDepends(MEModelCalibrationResultFilter)] +MEModelCalibrationResultFilterDep = Annotated[ + MEModelCalibrationResultFilter, FilterDepends(MEModelCalibrationResultFilter) +] diff --git a/app/routers/memodel_calibration_result.py b/app/routers/memodel_calibration_result.py index eed953ac..9c67f3a1 100644 --- a/app/routers/memodel_calibration_result.py +++ b/app/routers/memodel_calibration_result.py @@ -8,4 +8,4 @@ ) read_many = router.get("")(app.service.memodel_calibration_result.read_many) read_one = router.get("/{id_}")(app.service.memodel_calibration_result.read_one) -create_one = router.post("")(app.service.memodel_calibration_result.create_one) \ No newline at end of file +create_one = router.post("")(app.service.memodel_calibration_result.create_one) diff --git a/app/schemas/memodel_calibration_result.py b/app/schemas/memodel_calibration_result.py index 6ab56f55..1c7d1b93 100644 --- a/app/schemas/memodel_calibration_result.py +++ b/app/schemas/memodel_calibration_result.py @@ -17,7 +17,9 @@ class MEModelCalibrationResultBase(BaseModel): rin: float | None = None -class MEModelCalibrationResultRead(MEModelCalibrationResultBase, CreationMixin, IdentifiableMixin, CreatedByUpdatedByMixin): +class MEModelCalibrationResultRead( + MEModelCalibrationResultBase, CreationMixin, IdentifiableMixin, CreatedByUpdatedByMixin +): """Read model for MEModel calibration results, including entity metadata.""" calibrated_entity_id: uuid.UUID diff --git a/tests/conftest.py b/tests/conftest.py index 92acbdb1..8c160734 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -385,6 +385,7 @@ def validation_result_id(client, morphology_id): }, ).json()["id"] + @pytest.fixture def memodel_calibration_result_id(client, memodel_id): return assert_request( diff --git a/tests/test_memodel_calibration_result.py b/tests/test_memodel_calibration_result.py index 3f7c08ff..950600f3 100644 --- a/tests/test_memodel_calibration_result.py +++ b/tests/test_memodel_calibration_result.py @@ -1,4 +1,3 @@ - import uuid import pytest From 2646facb8f3e4a9f63cb504193c89ce43bf87352 Mon Sep 17 00:00:00 2001 From: MikeG Date: Mon, 26 May 2025 13:28:15 +0200 Subject: [PATCH 07/18] Initial implementation of `brain-atlas` endpoints (#195) See https://github.com/openbraininstitute/entitycore/pull/168 for the specification. Briefly; a `BrainAtlas` is an named entity that has an asset `annotation.nrrd` attached to it. It is associated with a particular `BrainRegionHierarchy` In addition, `BrainAtlasRegion` gives metadata for all the regions within a `BrainAtlas`; * their volume in the `annotation.nrrd` if they are a leaf, None otherwise. * an attached asset with the .obj mesh --- ...c1693694ffe7_default_migration_message.py} | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) rename alembic/versions/{20250526_124351_a864888b6a39_default_migration_message.py => 20250526_220145_c1693694ffe7_default_migration_message.py} (94%) diff --git a/alembic/versions/20250526_124351_a864888b6a39_default_migration_message.py b/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py similarity index 94% rename from alembic/versions/20250526_124351_a864888b6a39_default_migration_message.py rename to alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py index 766c0bd1..6f3e645f 100644 --- a/alembic/versions/20250526_124351_a864888b6a39_default_migration_message.py +++ b/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py @@ -1,8 +1,8 @@ """Default migration message -Revision ID: a864888b6a39 -Revises: 9730c55381f3 -Create Date: 2025-05-26 12:43:51.408647 +Revision ID: c1693694ffe7 +Revises: 634224e88212 +Create Date: 2025-05-26 22:01:45.625516 """ @@ -16,8 +16,8 @@ import app.db.types # revision identifiers, used by Alembic. -revision: str = "a864888b6a39" -down_revision: Union[str, None] = "9730c55381f3" +revision: str = "c1693694ffe7" +down_revision: Union[str, None] = "634224e88212" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -47,14 +47,15 @@ def upgrade() -> None: ["calibrated_entity_id"], unique=False, ) - op.drop_column("memodel", "holding_current") op.drop_column("memodel", "threshold_current") + op.drop_column("memodel", "holding_current") op.sync_enum_values( enum_schema="public", enum_name="entitytype", new_values=[ "analysis_software_source_code", "brain_atlas", + "brain_atlas_region", "emodel", "cell_composition", "memodel_calibration_result", @@ -90,6 +91,7 @@ def downgrade() -> None: new_values=[ "analysis_software_source_code", "brain_atlas", + "brain_atlas_region", "emodel", "cell_composition", "experimental_bouton_density", @@ -116,16 +118,16 @@ def downgrade() -> None: op.add_column( "memodel", sa.Column( - "threshold_current", - sa.DOUBLE_PRECISION(precision=53), - autoincrement=False, - nullable=True, + "holding_current", sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True ), ) op.add_column( "memodel", sa.Column( - "holding_current", sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True + "threshold_current", + sa.DOUBLE_PRECISION(precision=53), + autoincrement=False, + nullable=True, ), ) op.drop_index( From 86362bee9ce01991464b4cb620fcd55b26711119 Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 17:02:41 +0200 Subject: [PATCH 08/18] recreate migration after rebase --- ...26_220145_c1693694ffe7_default_migration_message.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py b/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py index 6f3e645f..667a9c93 100644 --- a/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py +++ b/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py @@ -1,8 +1,14 @@ """Default migration message +<<<<<<<< HEAD:alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py Revision ID: c1693694ffe7 Revises: 634224e88212 Create Date: 2025-05-26 22:01:45.625516 +======== +Revision ID: 6e5778b48a1c +Revises: 634224e88212 +Create Date: 2025-05-26 17:02:23.140215 +>>>>>>>> 1d6f518 (recreate migration after rebase):alembic/versions/20250526_170223_6e5778b48a1c_default_migration_message.py """ @@ -16,7 +22,11 @@ import app.db.types # revision identifiers, used by Alembic. +<<<<<<<< HEAD:alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py revision: str = "c1693694ffe7" +======== +revision: str = "6e5778b48a1c" +>>>>>>>> 1d6f518 (recreate migration after rebase):alembic/versions/20250526_170223_6e5778b48a1c_default_migration_message.py down_revision: Union[str, None] = "634224e88212" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None From 49769b95eed9b2d27ab1dddbc1edc78e9d0b0ad3 Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 17:13:36 +0200 Subject: [PATCH 09/18] fix tests due to rebase --- tests/test_memodel_calibration_result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_memodel_calibration_result.py b/tests/test_memodel_calibration_result.py index 950600f3..e90d9145 100644 --- a/tests/test_memodel_calibration_result.py +++ b/tests/test_memodel_calibration_result.py @@ -35,7 +35,7 @@ def _assert_read_response(data, json_data): assert data["holding_current"] == json_data["holding_current"] assert data["rin"] == json_data.get("rin") assert data["calibrated_entity_id"] == json_data["calibrated_entity_id"] - assert data["createdBy"]["id"] == data["updatedBy"]["id"] + assert data["created_by"]["id"] == data["updated_by"]["id"] assert data["creation_date"] == data["update_date"] From 1efe91c85ef02bccb9ce6174058c720e15150e1c Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 22:06:15 +0200 Subject: [PATCH 10/18] fix merge --- ...26_220145_c1693694ffe7_default_migration_message.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py b/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py index 667a9c93..66104fc2 100644 --- a/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py +++ b/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py @@ -1,14 +1,8 @@ """Default migration message -<<<<<<<< HEAD:alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py -Revision ID: c1693694ffe7 -Revises: 634224e88212 -Create Date: 2025-05-26 22:01:45.625516 -======== Revision ID: 6e5778b48a1c Revises: 634224e88212 Create Date: 2025-05-26 17:02:23.140215 ->>>>>>>> 1d6f518 (recreate migration after rebase):alembic/versions/20250526_170223_6e5778b48a1c_default_migration_message.py """ @@ -22,11 +16,7 @@ import app.db.types # revision identifiers, used by Alembic. -<<<<<<<< HEAD:alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py -revision: str = "c1693694ffe7" -======== revision: str = "6e5778b48a1c" ->>>>>>>> 1d6f518 (recreate migration after rebase):alembic/versions/20250526_170223_6e5778b48a1c_default_migration_message.py down_revision: Union[str, None] = "634224e88212" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None From 9c094e0da8500a4d5a82fe6c59397265f0734d07 Mon Sep 17 00:00:00 2001 From: Eleftherios Zisis Date: Mon, 26 May 2025 17:38:43 +0200 Subject: [PATCH 11/18] Remove unique constraint from Person (#199) --- ..._8d1610d7c882_default_migration_message.py | 34 +++++++++++++++++++ app/db/model.py | 1 - 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 alembic/versions/20250526_173512_8d1610d7c882_default_migration_message.py diff --git a/alembic/versions/20250526_173512_8d1610d7c882_default_migration_message.py b/alembic/versions/20250526_173512_8d1610d7c882_default_migration_message.py new file mode 100644 index 00000000..26ab1466 --- /dev/null +++ b/alembic/versions/20250526_173512_8d1610d7c882_default_migration_message.py @@ -0,0 +1,34 @@ +"""Default migration message + +Revision ID: 8d1610d7c882 +Revises: 634224e88212 +Create Date: 2025-05-26 17:35:12.743948 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +from sqlalchemy import Text +import app.db.types + +# revision identifiers, used by Alembic. +revision: str = "8d1610d7c882" +down_revision: Union[str, None] = "634224e88212" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("unique_person_name_1", "person", type_="unique") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint("unique_person_name_1", "person", ["given_name", "family_name"]) + # ### end Alembic commands ### diff --git a/app/db/model.py b/app/db/model.py index dca0d295..5800bbe2 100644 --- a/app/db/model.py +++ b/app/db/model.py @@ -231,7 +231,6 @@ class Person(Agent): "polymorphic_identity": __tablename__, "polymorphic_load": "selectin", } - __table_args__ = (UniqueConstraint("given_name", "family_name", name="unique_person_name_1"),) class Organization(Agent): From a9564d74e3eaa94856e4d4f8f37de92e3b11801c Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Mon, 26 May 2025 22:44:58 +0200 Subject: [PATCH 12/18] new migration file --- ...1589bff44728_default_migration_message.py} | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) rename alembic/versions/{20250526_220145_c1693694ffe7_default_migration_message.py => 20250526_222351_1589bff44728_default_migration_message.py} (96%) diff --git a/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py b/alembic/versions/20250526_222351_1589bff44728_default_migration_message.py similarity index 96% rename from alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py rename to alembic/versions/20250526_222351_1589bff44728_default_migration_message.py index 66104fc2..4750c11a 100644 --- a/alembic/versions/20250526_220145_c1693694ffe7_default_migration_message.py +++ b/alembic/versions/20250526_222351_1589bff44728_default_migration_message.py @@ -1,8 +1,8 @@ """Default migration message -Revision ID: 6e5778b48a1c -Revises: 634224e88212 -Create Date: 2025-05-26 17:02:23.140215 +Revision ID: 1589bff44728 +Revises: 8d1610d7c882 +Create Date: 2025-05-26 22:23:51.082630 """ @@ -16,8 +16,8 @@ import app.db.types # revision identifiers, used by Alembic. -revision: str = "6e5778b48a1c" -down_revision: Union[str, None] = "634224e88212" +revision: str = "1589bff44728" +down_revision: Union[str, None] = "8d1610d7c882" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -47,8 +47,8 @@ def upgrade() -> None: ["calibrated_entity_id"], unique=False, ) - op.drop_column("memodel", "threshold_current") op.drop_column("memodel", "holding_current") + op.drop_column("memodel", "threshold_current") op.sync_enum_values( enum_schema="public", enum_name="entitytype", @@ -118,16 +118,16 @@ def downgrade() -> None: op.add_column( "memodel", sa.Column( - "holding_current", sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True + "threshold_current", + sa.DOUBLE_PRECISION(precision=53), + autoincrement=False, + nullable=True, ), ) op.add_column( "memodel", sa.Column( - "threshold_current", - sa.DOUBLE_PRECISION(precision=53), - autoincrement=False, - nullable=True, + "holding_current", sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True ), ) op.drop_index( From efc04bd716560f9efc5d9a095d365fab505ca32a Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Tue, 27 May 2025 09:53:07 +0200 Subject: [PATCH 13/18] move calibrated_entity_id to base class --- app/schemas/memodel_calibration_result.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/schemas/memodel_calibration_result.py b/app/schemas/memodel_calibration_result.py index 1c7d1b93..9c536631 100644 --- a/app/schemas/memodel_calibration_result.py +++ b/app/schemas/memodel_calibration_result.py @@ -15,6 +15,7 @@ class MEModelCalibrationResultBase(BaseModel): holding_current: float threshold_current: float rin: float | None = None + calibrated_entity_id: uuid.UUID class MEModelCalibrationResultRead( @@ -22,10 +23,6 @@ class MEModelCalibrationResultRead( ): """Read model for MEModel calibration results, including entity metadata.""" - calibrated_entity_id: uuid.UUID - class MEModelCalibrationResultCreate(MEModelCalibrationResultBase): """Create model for MEModel calibration results.""" - - calibrated_entity_id: uuid.UUID From 42f4c3a5eedf27e0405bac3ebf843c2085215fe5 Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Tue, 27 May 2025 10:36:09 +0200 Subject: [PATCH 14/18] import calibration results --- app/cli/import_data.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/cli/import_data.py b/app/cli/import_data.py index 1b8e49ce..731639e4 100644 --- a/app/cli/import_data.py +++ b/app/cli/import_data.py @@ -49,6 +49,7 @@ Measurement, MeasurementAnnotation, MEModel, + MEModelCalibrationResult, METypeDensity, MTypeClass, MTypeClassification, @@ -901,13 +902,23 @@ def ingest(db, project_context, data_list, all_data_by_id, hierarchy_name: str): strain_id=morphology.strain_id, creation_date=createdAt, update_date=updatedAt, - holding_current=data.get("holding_current"), - threshold_current=data.get("threshold_current"), ) db.add(db_item) db.flush() - + db_calibration = MEModelCalibrationResult( + calibrated_entity_id=db_item.id, + holding_current=data.get("holding_current", 0), + threshold_current=data.get("threshold_current", 0), + authorized_project_id=project_context.project_id, + authorized_public=AUTHORIZED_PUBLIC, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + creation_date=createdAt, + update_date=updatedAt, + ) + db.add(db_calibration) + db.flush() utils.import_contribution(data, db_item.id, db) for annotation in ensurelist(data.get("annotation", [])): From 92b446dd45326d46377b10b6ef627bee46536bb8 Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Wed, 28 May 2025 13:18:07 +0200 Subject: [PATCH 15/18] adding memodel to calibrationresult relationship --- app/db/model.py | 7 +++++++ app/schemas/me_model.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/app/db/model.py b/app/db/model.py index 5800bbe2..e353d37b 100644 --- a/app/db/model.py +++ b/app/db/model.py @@ -486,6 +486,13 @@ class MEModel( emodel = relationship("EModel", foreign_keys=[emodel_id], uselist=False) + calibration_result = relationship( + "MEModelCalibrationResult", + uselist=False, + foreign_keys="MEModelCalibrationResult.calibrated_entity_id", + lazy="joined", + ) + __mapper_args__ = {"polymorphic_identity": __tablename__} # noqa: RUF012 diff --git a/app/schemas/me_model.py b/app/schemas/me_model.py index 29088ef4..1283a3bd 100644 --- a/app/schemas/me_model.py +++ b/app/schemas/me_model.py @@ -17,6 +17,7 @@ ) from app.schemas.contribution import ContributionReadWithoutEntity from app.schemas.emodel import EModelRead +from app.schemas.memodel_calibration_result import MEModelCalibrationResultRead from app.schemas.morphology import ReconstructionMorphologyRead @@ -57,3 +58,4 @@ class MEModelRead( etypes: list[ETypeClassRead] | None morphology: ReconstructionMorphologyRead emodel: EModelRead + calibration_result: MEModelCalibrationResultRead | None From 3e9f70771650b05ca12ccd33baf0eed35316a415 Mon Sep 17 00:00:00 2001 From: Gil Arturo Barrios del Villar Date: Wed, 28 May 2025 13:27:47 +0200 Subject: [PATCH 16/18] Add joinedload --- app/service/memodel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/service/memodel.py b/app/service/memodel.py index d4ee0cfd..5ae18814 100644 --- a/app/service/memodel.py +++ b/app/service/memodel.py @@ -70,6 +70,7 @@ def _load(select: Select): joinedload(MEModel.etypes), joinedload(MEModel.created_by), joinedload(MEModel.updated_by), + joinedload(MEModel.calibration_result), raiseload("*"), ) From a72f42c7963e27f0e7e9c9e0fcf229a1f7c361fa Mon Sep 17 00:00:00 2001 From: Gil Arturo Barrios del Villar Date: Wed, 28 May 2025 13:34:45 +0200 Subject: [PATCH 17/18] Add nested created_by, updated_by loader --- app/service/memodel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/service/memodel.py b/app/service/memodel.py index 5ae18814..2e45e62f 100644 --- a/app/service/memodel.py +++ b/app/service/memodel.py @@ -14,6 +14,7 @@ Contribution, EModel, MEModel, + MEModelCalibrationResult, ReconstructionMorphology, ) from app.dependencies.auth import UserContextDep, UserContextWithProjectIdDep @@ -70,7 +71,8 @@ def _load(select: Select): joinedload(MEModel.etypes), joinedload(MEModel.created_by), joinedload(MEModel.updated_by), - joinedload(MEModel.calibration_result), + joinedload(MEModel.calibration_result).joinedload(MEModelCalibrationResult.created_by), + joinedload(MEModel.calibration_result).joinedload(MEModelCalibrationResult.updated_by), raiseload("*"), ) From 6c39b767478261c45e0bdc9fad637e666001d07b Mon Sep 17 00:00:00 2001 From: Jean-Denis Courcol Date: Wed, 28 May 2025 13:50:20 +0200 Subject: [PATCH 18/18] add test for the memodel to calibration relationship --- tests/test_memodel_calibration_result.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_memodel_calibration_result.py b/tests/test_memodel_calibration_result.py index e90d9145..25e3e907 100644 --- a/tests/test_memodel_calibration_result.py +++ b/tests/test_memodel_calibration_result.py @@ -85,6 +85,12 @@ def test_filtering__one_entry(client, memodel_calibration_result_id, memodel_id, assert data[0]["id"] == str(memodel_calibration_result_id) +def test_memodel_relationship(client, memodel_id, memodel_calibration_result_id): + data = assert_request(client.get, url=f"memodel/{memodel_id}").json() + assert data["calibration_result"]["id"] == str(memodel_calibration_result_id) + assert data["calibration_result"]["calibrated_entity_id"] == str(memodel_id) + + @pytest.fixture def models(create, memodel_id): return [