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
56 changes: 56 additions & 0 deletions app/filters/circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import uuid
from datetime import datetime
from typing import Annotated

from fastapi_filter import FilterDepends

from app.db.model import Circuit
from app.filters.base import CustomFilter
from app.filters.common import (
BrainRegionFilterMixin,
EntityFilterMixin,
NameFilterMixin,
SubjectFilterMixin,
)


class ScientificArtifactFilter(
CustomFilter, SubjectFilterMixin, BrainRegionFilterMixin, EntityFilterMixin
):
experiment_date__lte: datetime | None = None
experiment_date__gte: datetime | None = None
contact_id: uuid.UUID | None = None


class CircuitFilter(ScientificArtifactFilter, NameFilterMixin):
atlas_id: uuid.UUID | None = None
root_circuit_id: uuid.UUID | None = None

has_morphologies: bool | None = None
has_point_neurons: bool | None = None
has_electrical_cell_models: bool | None = None
has_spines: bool | None = None

number_neurons__lte: int | None = None
number_neurons__gte: int | None = None

number_synapses__lte: int | None = None
number_synapses__gte: int | None = None

number_connections__lte: int | None = None
number_connections__gte: int | None = None

build_category: str | None = None
build_category__in: list[str] | None = None

scale: str | None = None
scale__in: list[str] | None = None

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

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


CircuitFilterDep = Annotated[CircuitFilter, FilterDepends(CircuitFilter)]
2 changes: 2 additions & 0 deletions app/routers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
brain_region,
brain_region_hierarchy,
cell_composition,
circuit,
contribution,
derivation,
electrical_cell_recording,
Expand Down Expand Up @@ -45,6 +46,7 @@
brain_region.router,
brain_region_hierarchy.router,
cell_composition.router,
circuit.router,
contribution.router,
derivation.router,
electrical_cell_recording.router,
Expand Down
12 changes: 12 additions & 0 deletions app/routers/circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from fastapi import APIRouter

import app.service.circuit

router = APIRouter(
prefix="/circuit",
tags=["circuit"],
)

read_many = router.get("")(app.service.circuit.read_many)
read_one = router.get("/{id_}")(app.service.circuit.read_one)
create_one = router.post("")(app.service.circuit.create_one)
8 changes: 8 additions & 0 deletions app/schemas/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ class LicenseRead(LicenseCreate, CreationMixin, IdentifiableMixin):
pass


class LicenseReadMixin:
license: LicenseRead | None = None


class LicenseCreateMixin:
license_id: uuid.UUID | None = None


class BrainRegionRead(IdentifiableMixin, CreationMixin):
model_config = ConfigDict(from_attributes=True)

Expand Down
36 changes: 36 additions & 0 deletions app/schemas/circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import uuid

from pydantic import BaseModel, ConfigDict

from app.db.types import CircuitBuildCategory, CircuitScale
from app.schemas.scientific_artifact import ScientificArtifactCreate, ScientificArtifactRead


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

name: str
description: str

has_morphologies: bool = False
has_point_neurons: bool = False
has_electrical_cell_models: bool = False
has_spines: bool = False

number_neurons: int
number_synapses: int
number_connections: int | None

scale: CircuitScale
build_category: CircuitBuildCategory

root_circuit_id: uuid.UUID | None = None
atlas_id: uuid.UUID | None = None


class CircuitRead(CircuitBase, ScientificArtifactRead):
pass


class CircuitCreate(CircuitBase, ScientificArtifactCreate):
pass
52 changes: 52 additions & 0 deletions app/schemas/scientific_artifact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import uuid
from datetime import datetime

from pydantic import BaseModel, ConfigDict

from app.schemas.agent import CreatedByUpdatedByMixin
from app.schemas.asset import AssetsMixin
from app.schemas.base import (
AuthorizationMixin,
AuthorizationOptionalPublicMixin,
BrainRegionCreateMixin,
BrainRegionReadMixin,
CreationMixin,
EntityTypeMixin,
IdentifiableMixin,
LicenseCreateMixin,
LicenseReadMixin,
)
from app.schemas.subject import SubjectCreateMixin, SubjectReadMixin


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

experiment_date: datetime | None = None
contact_id: uuid.UUID | None = None
atlas_id: uuid.UUID | None = None


class ScientificArtifactRead(
ScientificArtifactBase,
SubjectReadMixin,
BrainRegionReadMixin,
CreatedByUpdatedByMixin,
CreationMixin,
LicenseReadMixin,
EntityTypeMixin,
IdentifiableMixin,
AuthorizationMixin,
AssetsMixin,
):
pass


class ScientificArtifactCreate(
ScientificArtifactBase,
SubjectCreateMixin,
BrainRegionCreateMixin,
LicenseCreateMixin,
AuthorizationOptionalPublicMixin,
):
pass
4 changes: 4 additions & 0 deletions app/schemas/subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@ class SubjectRead(

class SubjectReadMixin:
subject: NestedSubjectRead


class SubjectCreateMixin:
subject_id: uuid.UUID
122 changes: 122 additions & 0 deletions app/service/circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import uuid
from typing import TYPE_CHECKING

import sqlalchemy as sa
from sqlalchemy.orm import aliased, joinedload, raiseload, selectinload

from app.db.model import (
Agent,
Circuit,
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.circuit import CircuitFilterDep
from app.queries.common import router_create_one, router_read_many, router_read_one
from app.queries.factory import query_params_factory
from app.schemas.circuit import (
CircuitCreate,
CircuitRead,
)
from app.schemas.types import ListResponse

if TYPE_CHECKING:
from app.filters.base import Aliases


def _load(query: sa.Select):
return query.options(
joinedload(Circuit.license),
joinedload(Circuit.subject).joinedload(Subject.species),
joinedload(Circuit.brain_region),
joinedload(Circuit.created_by),
joinedload(Circuit.updated_by),
selectinload(Circuit.assets),
raiseload("*"),
)


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


def create_one(
db: SessionDep,
json_model: CircuitCreate,
user_context: UserContextWithProjectIdDep,
) -> CircuitRead:
return router_create_one(
db=db,
json_model=json_model,
user_context=user_context,
db_model_class=Circuit,
response_schema_class=CircuitRead,
apply_operations=_load,
)


def read_many(
user_context: UserContextDep,
db: SessionDep,
pagination_request: PaginationQuery,
filter_model: CircuitFilterDep,
with_search: SearchDep,
facets: FacetsDep,
in_brain_region: InBrainRegionDep,
) -> ListResponse[CircuitRead]:
agent_alias = aliased(Agent, flat=True)
created_by_alias = aliased(Agent, flat=True)
updated_by_alias = aliased(Agent, flat=True)

aliases: Aliases = {
Agent: {
"contribution": agent_alias,
"created_by": created_by_alias,
"updated_by": updated_by_alias,
}
}
facet_keys = filter_keys = [
"brain_region",
"created_by",
"updated_by",
"contribution",
]
name_to_facet_query_params, filter_joins = query_params_factory(
db_model_class=Circuit,
facet_keys=facet_keys,
filter_keys=filter_keys,
aliases=aliases,
)
return router_read_many(
db=db,
filter_model=filter_model,
db_model_class=Circuit,
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=CircuitRead,
authorized_project_id=user_context.project_id,
filter_joins=filter_joins,
)
Loading