Skip to content
Draft
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
110 changes: 110 additions & 0 deletions app/db/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2271,4 +2271,114 @@ class SkeletonizationExecution(Activity, ExecutionActivityMixin):
__mapper_args__ = {"polymorphic_identity": __tablename__} # noqa: RUF012


class Mapping(ScientificArtifact, NameDescriptionVectorMixin):
"""Represents a mapping between two classification schemes.

A mapping defines relationships between two classification schemes, such as
mapping from OBI_mtypes to OBI_etypes. Each mapping contains multiple
mapping relations that define specific relationships between classifications.

Attributes:
id (uuid.UUID): Primary key, inherited from ScientificArtifact.
name (str): Name of mapping, inherited from NameDescriptionVectorMixin.
version (str): Version of mapping.
source_schema_id (uuid.UUID): Foreign key to source EntityClassificationScheme.
target_schema_id (uuid.UUID): Foreign key to target EntityClassificationScheme.
mapping_relations (list[MappingRelation]): Relations within this mapping.
"""

__tablename__ = EntityType.mapping.value
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("scientific_artifact.id"), primary_key=True)

version: Mapped[str]

# Foreign keys to EntityClassificationScheme (placeholder entities for now)
source_schema_id: Mapped[uuid.UUID] = mapped_column(index=True)
target_schema_id: Mapped[uuid.UUID] = mapped_column(index=True)

mapping_relations: Mapped[list["MappingRelation"]] = relationship(
"MappingRelation",
back_populates="mapping",
cascade="all, delete-orphan",
passive_deletes=True,
)

__table_args__ = (
UniqueConstraint("source_schema_id", "target_schema_id", "version", name="uq_mapping_source_target_version"),
)

__mapper_args__ = {"polymorphic_identity": __tablename__} # noqa: RUF012


class MappingRelation(ScientificArtifact):
"""Represents a specific relation within a mapping.

A mapping relation defines the relationship between a specific source classification
and a target classification within a mapping. It can contain data values or
references to parameterisation relations for complex data.

Attributes:
id (uuid.UUID): Primary key, inherited from ScientificArtifact.
mapping_id (uuid.UUID): Foreign key to parent mapping.
mapping (Mapping): The parent mapping this relation belongs to.
source_id (uuid.UUID): Foreign key to source EntityClassification.
target_id (uuid.UUID): Foreign key to target EntityClassification.
data (str | int | float | bool | None): Simple data value or reference to parameterisation.
"""

__tablename__ = EntityType.mapping_relation.value
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("scientific_artifact.id"), primary_key=True)

mapping_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("mapping.id"), index=True
)
mapping: Mapped["Mapping"] = relationship(
"Mapping",
back_populates="mapping_relations",
uselist=False,
)

# Foreign keys to EntityClassification (placeholder entities for now)
source_id: Mapped[uuid.UUID] = mapped_column(index=True)
target_id: Mapped[uuid.UUID] = mapped_column(index=True)

# Can store simple data values (int/float/bool/str)
data: Mapped[str | int | float | bool | None] = mapped_column(JSONB)

parameterisation_relation_id: Mapped[uuid.UUID | None] = mapped_column(
ForeignKey("parameterisation_relation.id"), index=True
)
parameterisation_relation: Mapped["ParameterisationRelation"] = relationship(
"ParameterisationRelation",
foreign_keys=[parameterisation_relation_id],
uselist=False,
)

__table_args__ = (
UniqueConstraint("mapping_id", "source_id", "target_id", name="uq_mapping_relation_source_target"),
)

__mapper_args__ = {"polymorphic_identity": __tablename__} # noqa: RUF012


class ParameterisationRelation(ScientificArtifact):
"""Represents complex parameter data for mapping relations.

A parameterisation relation stores complex data structures as key-value pairs,
used when a mapping relation needs to contain multiple data values
(e.g., synaptic physiology parameters).

Attributes:
id (uuid.UUID): Primary key, inherited from ScientificArtifact.
data (dict): Dictionary of key-value pairs containing the parameter data.
"""

__tablename__ = EntityType.parameterisation_relation.value
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("scientific_artifact.id"), primary_key=True)

data: Mapped[JSON_DICT]

__mapper_args__ = {"polymorphic_identity": __tablename__} # noqa: RUF012


register_model_events()
3 changes: 3 additions & 0 deletions app/db/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ class EntityType(StrEnum):
analysis_notebook_result = auto()
skeletonization_config = auto()
skeletonization_campaign = auto()
mapping = auto()
mapping_relation = auto()
parameterisation_relation = auto()


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

from app.db.model import Mapping
from app.dependencies.filter import FilterDepends
from app.filters.common import ILikeSearchFilterMixin, NameFilterMixin
from app.filters.scientific_artifact import ScientificArtifactFilter


class MappingFilter(ScientificArtifactFilter, NameFilterMixin, ILikeSearchFilterMixin):
version: str | None = None
source_schema_id: uuid.UUID | None = None
target_schema_id: uuid.UUID | None = None

order_by: list[str] = ["-creation_date"] # noqa: RUF012

class Constants(ScientificArtifactFilter.Constants):
model = Mapping
ordering_model_fields = ["creation_date", "update_date", "name", "version"] # noqa: RUF012


MappingFilterDep = Annotated[MappingFilter, FilterDepends(MappingFilter)]
23 changes: 23 additions & 0 deletions app/filters/mapping_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import uuid
from typing import Annotated

from app.db.model import MappingRelation
from app.dependencies.filter import FilterDepends
from app.filters.common import ILikeSearchFilterMixin
from app.filters.scientific_artifact import ScientificArtifactFilter


class MappingRelationFilter(ScientificArtifactFilter, ILikeSearchFilterMixin):
mapping_id: uuid.UUID | None = None
source_id: uuid.UUID | None = None
target_id: uuid.UUID | None = None
parameterisation_relation_id: uuid.UUID | None = None

order_by: list[str] = ["-creation_date"] # noqa: RUF012

class Constants(ScientificArtifactFilter.Constants):
model = MappingRelation
ordering_model_fields = ["creation_date", "update_date"] # noqa: RUF012


MappingRelationFilterDep = Annotated[MappingRelationFilter, FilterDepends(MappingRelationFilter)]
20 changes: 20 additions & 0 deletions app/filters/parameterisation_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import Annotated

from app.db.model import ParameterisationRelation
from app.dependencies.filter import FilterDepends
from app.filters.common import ILikeSearchFilterMixin
from app.filters.scientific_artifact import ScientificArtifactFilter


class ParameterisationRelationFilter(ScientificArtifactFilter, ILikeSearchFilterMixin):
order_by: list[str] = ["-creation_date"] # noqa: RUF012

class Constants(ScientificArtifactFilter.Constants):
model = ParameterisationRelation
ordering_model_fields = ["creation_date", "update_date"] # noqa: RUF012


ParameterisationRelationFilterDep = Annotated[
ParameterisationRelationFilter,
FilterDepends(ParameterisationRelationFilter),
]
6 changes: 6 additions & 0 deletions app/routers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
license,
measurement_annotation,
measurement_label,
mapping,
mapping_relation,
memodel,
memodel_calibration_result,
mtype,
Expand All @@ -59,6 +61,7 @@
root,
scientific_artifact_external_url_link,
scientific_artifact_publication_link,
parameterisation_relation,
simulation,
simulation_campaign,
simulation_execution,
Expand Down Expand Up @@ -127,6 +130,8 @@
license.router,
measurement_annotation.router,
measurement_label.router,
mapping.router,
mapping_relation.router,
memodel.router,
memodel_calibration_result.router,
mtype.router,
Expand All @@ -137,6 +142,7 @@
role.router,
scientific_artifact_external_url_link.router,
scientific_artifact_publication_link.router,
parameterisation_relation.router,
skeletonization_campaign.router,
skeletonization_config.router,
skeletonization_config_generation.router,
Expand Down
20 changes: 20 additions & 0 deletions app/routers/mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fastapi import APIRouter

import app.service.mapping
from app.routers.admin import router as admin_router

ROUTE = "mapping"

router = APIRouter(
prefix=f"/{ROUTE}",
tags=[ROUTE],
)

read_many = router.get("")(app.service.mapping.read_many)
read_one = router.get("/{id_}")(app.service.mapping.read_one)
create_one = router.post("")(app.service.mapping.create_one)
update_one = router.patch("/{id_}")(app.service.mapping.update_one)
delete_one = router.delete("/{id_}")(app.service.mapping.delete_one)

admin_read_one = admin_router.get(f"/{ROUTE}/{{id_}}")(app.service.mapping.admin_read_one)
admin_update_one = admin_router.patch(f"/{ROUTE}/{{id_}}")(app.service.mapping.admin_update_one)
24 changes: 24 additions & 0 deletions app/routers/mapping_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from fastapi import APIRouter

import app.service.mapping_relation
from app.routers.admin import router as admin_router

ROUTE = "mapping-relation"

router = APIRouter(
prefix=f"/{ROUTE}",
tags=[ROUTE],
)

read_many = router.get("")(app.service.mapping_relation.read_many)
read_one = router.get("/{id_}")(app.service.mapping_relation.read_one)
create_one = router.post("")(app.service.mapping_relation.create_one)
update_one = router.patch("/{id_}")(app.service.mapping_relation.update_one)
delete_one = router.delete("/{id_}")(app.service.mapping_relation.delete_one)

admin_read_one = admin_router.get(f"/{ROUTE}/{{id_}}")(
app.service.mapping_relation.admin_read_one
)
admin_update_one = admin_router.patch(f"/{ROUTE}/{{id_}}")(
app.service.mapping_relation.admin_update_one
)
24 changes: 24 additions & 0 deletions app/routers/parameterisation_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from fastapi import APIRouter

import app.service.parameterisation_relation
from app.routers.admin import router as admin_router

ROUTE = "parameterisation-relation"

router = APIRouter(
prefix=f"/{ROUTE}",
tags=[ROUTE],
)

read_many = router.get("")(app.service.parameterisation_relation.read_many)
read_one = router.get("/{id_}")(app.service.parameterisation_relation.read_one)
create_one = router.post("")(app.service.parameterisation_relation.create_one)
update_one = router.patch("/{id_}")(app.service.parameterisation_relation.update_one)
delete_one = router.delete("/{id_}")(app.service.parameterisation_relation.delete_one)

admin_read_one = admin_router.get(f"/{ROUTE}/{{id_}}")(
app.service.parameterisation_relation.admin_read_one
)
admin_update_one = admin_router.patch(f"/{ROUTE}/{{id_}}")(
app.service.parameterisation_relation.admin_update_one
)
36 changes: 36 additions & 0 deletions app/schemas/mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import uuid

from pydantic import BaseModel, ConfigDict

from app.schemas.base import NameDescriptionMixin
from app.schemas.scientific_artifact import ScientificArtifactCreate, ScientificArtifactRead
from app.schemas.utils import make_update_schema


class MappingBase(BaseModel, NameDescriptionMixin):
model_config = ConfigDict(from_attributes=True)
version: str
source_schema_id: uuid.UUID
target_schema_id: uuid.UUID


class MappingCreate(
MappingBase,
ScientificArtifactCreate,
):
pass


MappingUserUpdate = make_update_schema(MappingCreate, "MappingUserUpdate") # pyright: ignore [reportInvalidTypeForm]
MappingAdminUpdate = make_update_schema(
MappingCreate,
"MappingAdminUpdate",
excluded_fields=set(),
) # pyright : ignore [reportInvalidTypeForm]


class MappingRead(
MappingBase,
ScientificArtifactRead,
):
pass
43 changes: 43 additions & 0 deletions app/schemas/mapping_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import uuid
from typing import Any

from pydantic import BaseModel, ConfigDict

from app.schemas.parameterisation_relation import NestedParameterisationRelationRead
from app.schemas.scientific_artifact import ScientificArtifactCreate, ScientificArtifactRead
from app.schemas.utils import make_update_schema


class MappingRelationBase(BaseModel):
model_config = ConfigDict(from_attributes=True)

mapping_id: uuid.UUID
source_id: uuid.UUID
target_id: uuid.UUID
data: Any | None = None
parameterisation_relation_id: uuid.UUID | None = None


class MappingRelationCreate(
MappingRelationBase,
ScientificArtifactCreate,
):
pass


MappingRelationUserUpdate = make_update_schema(
MappingRelationCreate,
"MappingRelationUserUpdate",
) # pyright: ignore [reportInvalidTypeForm]
MappingRelationAdminUpdate = make_update_schema(
MappingRelationCreate,
"MappingRelationAdminUpdate",
excluded_fields=set(),
) # pyright : ignore [reportInvalidTypeForm]


class MappingRelationRead(
MappingRelationBase,
ScientificArtifactRead,
):
parameterisation_relation: NestedParameterisationRelationRead | None = None
Loading
Loading