Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Default migration message

Revision ID: 9730c55381f3
Revises: b8490e92310f
Create Date: 2025-05-21 13:18:02.105223

"""

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 = "9730c55381f3"
down_revision: Union[str, None] = "b8490e92310f"
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(
"validation_result",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("passed", sa.Boolean(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("validated_entity_id", sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(["id"], ["entity.id"], name=op.f("fk_validation_result_id_entity")),
sa.ForeignKeyConstraint(
["validated_entity_id"],
["entity.id"],
name=op.f("fk_validation_result_validated_entity_id_entity"),
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_validation_result")),
)
op.create_index(op.f("ix_validation_result_name"), "validation_result", ["name"], unique=False)
op.create_index(
op.f("ix_validation_result_validated_entity_id"),
"validation_result",
["validated_entity_id"],
unique=False,
)
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=[],
)
# ### 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",
],
affected_columns=[
TableReference(table_schema="public", table_name="entity", column_name="type")
],
enum_values_to_rename=[],
)
op.drop_index(op.f("ix_validation_result_validated_entity_id"), table_name="validation_result")
op.drop_index(op.f("ix_validation_result_name"), table_name="validation_result")
op.drop_table("validation_result")
# ### end Alembic commands ###
20 changes: 20 additions & 0 deletions app/db/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,26 @@ class IonChannelModelToEModel(Base):
)


class ValidationResult(Entity):
__tablename__ = EntityType.validation_result.value
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), primary_key=True)
passed: Mapped[bool] = mapped_column(default=False)

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

validated_entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), index=True)
validated_entity: Mapped[Entity] = relationship(
"Entity",
uselist=False,
foreign_keys=[validated_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 @@ -66,6 +66,7 @@ class EntityType(StrEnum):
single_neuron_synaptome_simulation = auto()
ion_channel_model = auto()
subject = auto()
validation_result = auto()


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

from fastapi_filter import FilterDepends

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


class ValidationResultFilter(
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 = ValidationResult
ordering_model_fields = ["name"] # noqa: RUF012


ValidationResultFilterDep = Annotated[ValidationResultFilter, FilterDepends(ValidationResultFilter)]
4 changes: 3 additions & 1 deletion app/routers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
species,
strain,
subject,
validation_result,
)

router = APIRouter()
Expand All @@ -47,6 +48,7 @@
experimental_bouton_density.router,
experimental_neuron_density.router,
experimental_synapses_per_connection.router,
ion_channel_model.router,
license.router,
measurement_annotation.router,
memodel.router,
Expand All @@ -61,7 +63,7 @@
species.router,
strain.router,
subject.router,
ion_channel_model.router,
validation_result.router,
]
for r in authenticated_routers:
router.include_router(r, dependencies=[Depends(user_verified)])
12 changes: 12 additions & 0 deletions app/routers/validation_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from fastapi import APIRouter

import app.service.validation_result

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

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

from pydantic import BaseModel

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


class ValidationResultBase(BaseModel):
name: str
passed: bool
validated_entity_id: uuid.UUID


class ValidationResultRead(
ValidationResultBase, CreationMixin, IdentifiableMixin, CreatedByUpdatedByMixin
):
pass


class ValidationResultCreate(ValidationResultBase):
pass
82 changes: 82 additions & 0 deletions app/service/validation_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 Subject, ValidationResult
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.validation_result import ValidationResultFilterDep
from app.queries.common import router_create_one, router_read_many, router_read_one
from app.schemas.types import ListResponse
from app.schemas.validation import ValidationResultCreate, ValidationResultRead


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


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


def create_one(
user_context: UserContextWithProjectIdDep,
json_model: ValidationResultCreate,
db: SessionDep,
) -> ValidationResultRead:
return router_create_one(
db=db,
user_context=user_context,
db_model_class=ValidationResult,
json_model=json_model,
response_schema_class=ValidationResultRead,
)


def read_many(
user_context: UserContextDep,
db: SessionDep,
pagination_request: PaginationQuery,
filter_model: ValidationResultFilterDep,
with_search: SearchDep,
facets: FacetsDep,
in_brain_region: InBrainRegionDep,
) -> ListResponse[ValidationResultRead]:
aliases = {}
name_to_facet_query_params = {}
return router_read_many(
db=db,
filter_model=filter_model,
db_model_class=ValidationResult,
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=ValidationResultRead,
authorized_project_id=user_context.project_id,
filter_joins=None,
)
14 changes: 14 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,20 @@ def mtype_class_id(db):
)


@pytest.fixture
def validation_result_id(client, morphology_id):
return assert_request(
client.post,
url="/validation-result",
json={
"name": "test_validation_result",
"passed": True,
"validated_entity_id": str(morphology_id),
"authorized_public": False,
},
).json()["id"]


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


Expand Down
Loading