Skip to content

Commit 2e16114

Browse files
Add experimental graphql endpoint
1 parent 4840c3c commit 2e16114

22 files changed

+484
-0
lines changed

app/application.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from app.db.session import configure_database_session_manager
1818
from app.dependencies.common import forbid_extra_query_params
1919
from app.errors import ApiError, ApiErrorCode
20+
from app.graphql.router import graphql_router
2021
from app.logger import L
2122
from app.routers import router
2223
from app.schemas.api import ErrorResponse
@@ -106,3 +107,4 @@ async def validation_exception_handler(
106107
},
107108
dependencies=[Depends(forbid_extra_query_params)],
108109
)
110+
app.include_router(graphql_router)

app/graphql/__init__.py

Whitespace-only changes.

app/graphql/filters/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from app.graphql.filters import common, species, morphology # noqa: I001
2+
3+
__all__ = [
4+
"common",
5+
"morphology",
6+
"species",
7+
]

app/graphql/filters/common.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import strawberry
2+
3+
from app.filters.common import AgentFilter, MTypeClassFilter, StrainFilter
4+
5+
6+
@strawberry.experimental.pydantic.input(model=MTypeClassFilter, all_fields=True)
7+
class MTypeClassFilterInput:
8+
pass
9+
10+
11+
@strawberry.experimental.pydantic.input(model=StrainFilter, all_fields=True)
12+
class StrainFilterInput:
13+
pass
14+
15+
16+
@strawberry.experimental.pydantic.input(model=AgentFilter, all_fields=True)
17+
class AgentFilterInput:
18+
pass

app/graphql/filters/morphology.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import strawberry
2+
3+
from app.filters.morphology import MorphologyFilter
4+
5+
6+
@strawberry.experimental.pydantic.input(model=MorphologyFilter, all_fields=True)
7+
class MorphologyFilterInput:
8+
pass

app/graphql/filters/species.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import strawberry
2+
3+
from app.filters.common import SpeciesFilter
4+
5+
6+
@strawberry.experimental.pydantic.input(model=SpeciesFilter, all_fields=True)
7+
class SpeciesFilterInput:
8+
pass

app/graphql/resolvers/__init__.py

Whitespace-only changes.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import uuid
2+
from typing import TYPE_CHECKING
3+
4+
import strawberry
5+
6+
import app.service.morphology
7+
from app.dependencies.auth import user_with_project_id
8+
from app.filters.morphology import MorphologyFilter
9+
from app.graphql.filters.morphology import MorphologyFilterInput
10+
from app.graphql.types.morphology import MorphologyInput, MorphologyType
11+
from app.graphql.types.pagination import ListResponseType, PaginationRequestInput
12+
from app.schemas.morphology import ReconstructionMorphologyCreate, ReconstructionMorphologyRead
13+
from app.schemas.types import PaginationRequest
14+
15+
if TYPE_CHECKING:
16+
from sqlalchemy.orm import Session
17+
18+
from app.schemas.auth import UserContext, UserContextWithProjectId
19+
20+
21+
@strawberry.type
22+
class MorphologyQuery:
23+
@strawberry.field
24+
def read_many_morphologies(
25+
self,
26+
*,
27+
info: strawberry.Info,
28+
pagination_request: PaginationRequestInput,
29+
morphology_filter: MorphologyFilterInput,
30+
search: str | None = None,
31+
with_facets: bool = False,
32+
) -> ListResponseType[MorphologyType, ReconstructionMorphologyRead]:
33+
# for proper validation, validate the input and return a pydantic model
34+
db: Session = info.context.db
35+
user_context: UserContext = info.context.user_context
36+
validated_pagination_request = PaginationRequest.model_validate(
37+
pagination_request, from_attributes=True
38+
)
39+
validated_morphology_filter = MorphologyFilter.model_validate(
40+
morphology_filter, from_attributes=True
41+
)
42+
result = app.service.morphology.read_many(
43+
user_context=user_context,
44+
db=db,
45+
pagination_request=validated_pagination_request,
46+
morphology_filter=validated_morphology_filter,
47+
search=search,
48+
with_facets=with_facets,
49+
)
50+
return ListResponseType[MorphologyType, ReconstructionMorphologyRead].from_pydantic(result)
51+
52+
@strawberry.field
53+
def read_morphology(self, id_: uuid.UUID, info: strawberry.Info) -> MorphologyType | None:
54+
# for proper validation, validate the input and return a pydantic model
55+
db: Session = info.context.db
56+
user_context: UserContext = info.context.user_context
57+
result = app.service.morphology.read_one(user_context=user_context, db=db, id_=id_)
58+
return MorphologyType.from_pydantic(result) if result else None
59+
60+
61+
@strawberry.type
62+
class MorphologyMutation:
63+
@strawberry.mutation
64+
def create_morphology(
65+
self, morphology: MorphologyInput, info: strawberry.Info
66+
) -> MorphologyType:
67+
# for proper validation, validate the input and return a pydantic model
68+
db: Session = info.context.db
69+
user_context: UserContextWithProjectId = user_with_project_id(info.context.user_context)
70+
validated = ReconstructionMorphologyCreate.model_validate(morphology)
71+
result = app.service.morphology.create(
72+
user_context=user_context, db=db, reconstruction=validated
73+
)
74+
return MorphologyType.from_pydantic(result)

app/graphql/resolvers/species.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import uuid
2+
from typing import TYPE_CHECKING
3+
4+
import strawberry
5+
6+
import app.service.species
7+
from app.filters.common import SpeciesFilter
8+
from app.graphql.filters.species import SpeciesFilterInput
9+
from app.graphql.types.common import SpeciesInput, SpeciesType
10+
from app.graphql.types.pagination import ListResponseType, PaginationRequestInput
11+
from app.schemas.base import SpeciesCreate, SpeciesRead
12+
from app.schemas.types import PaginationRequest
13+
14+
if TYPE_CHECKING:
15+
from sqlalchemy.orm import Session
16+
17+
18+
@strawberry.type
19+
class SpeciesQuery:
20+
@strawberry.field
21+
def read_many_species(
22+
self,
23+
*,
24+
pagination_request: PaginationRequestInput,
25+
species_filter: SpeciesFilterInput,
26+
info: strawberry.Info,
27+
) -> ListResponseType[SpeciesType, SpeciesRead]:
28+
# for proper validation, validate the input and return a pydantic model
29+
db: Session = info.context.db
30+
validated_pagination_request = PaginationRequest.model_validate(
31+
pagination_request, from_attributes=True
32+
)
33+
validated_species_filter = SpeciesFilter.model_validate(
34+
species_filter, from_attributes=True
35+
)
36+
result = app.service.species.read_many(
37+
db=db,
38+
pagination_request=validated_pagination_request,
39+
species_filter=validated_species_filter,
40+
)
41+
return ListResponseType[SpeciesType, SpeciesRead].from_pydantic(result)
42+
43+
@strawberry.field
44+
def read_species(self, *, id_: uuid.UUID, info: strawberry.Info) -> SpeciesType | None:
45+
# for proper validation, validate the input and return a pydantic model
46+
db: Session = info.context.db
47+
result = app.service.species.read_one(db=db, id_=id_)
48+
return SpeciesType.from_pydantic(result) if result else None
49+
50+
51+
@strawberry.type
52+
class SpeciesMutation:
53+
@strawberry.mutation
54+
def create_species(self, *, species: SpeciesInput, info: strawberry.Info) -> SpeciesType:
55+
# for proper validation, validate the input and return a pydantic model
56+
db: Session = info.context.db
57+
validated = SpeciesCreate.model_validate(species)
58+
result = app.service.species.create(db=db, species=validated)
59+
return SpeciesType.from_pydantic(result)

app/graphql/router.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from fastapi import Depends
2+
from sqlalchemy.orm import Session
3+
from strawberry.fastapi import BaseContext, GraphQLRouter
4+
5+
from app.dependencies.auth import UserContextDep, user_with_service_admin_role
6+
from app.dependencies.db import SessionDep
7+
from app.graphql.schema import schema
8+
from app.schemas.auth import UserContext
9+
10+
11+
class Context(BaseContext):
12+
def __init__(self, *, db: Session, user_context: UserContext) -> None:
13+
"""Initialize a new Context."""
14+
super().__init__()
15+
self.db = db
16+
self.user_context = user_context
17+
18+
19+
def get_context(db: SessionDep, user_context: UserContextDep) -> Context:
20+
return Context(db=db, user_context=user_context)
21+
22+
23+
graphql_router = GraphQLRouter(
24+
schema,
25+
prefix="/graphql",
26+
graphql_ide="apollo-sandbox",
27+
context_getter=get_context,
28+
dependencies=[Depends(user_with_service_admin_role)],
29+
include_in_schema=False,
30+
)

0 commit comments

Comments
 (0)