Skip to content

Commit

Permalink
Release v1.10.0
Browse files Browse the repository at this point in the history
  • Loading branch information
gfrn committed Dec 10, 2024
1 parent acb69fe commit 18a8847
Show file tree
Hide file tree
Showing 30 changed files with 413 additions and 37 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@
Changelog
==========

+++++++++
v1.10.0 (10/12/2024)
+++++++++

**Added**

- Add GET /foil-holes/{foilHoleId}/movies endpoint
- Add GET /grid-squares/{gridSquareId}/foil-holes endpoint
- Add GET /grid-squares/{gridSquareId}/image endpoint
- Add GET /dataGroups/{groupId}/atlas/image endpoint
- Add GET /dataGroups/{groupId}/atlas endpoint
- Add GET /dataGroups/{groupId}/grid-squares endpoint

+++++++++
v1.9.0 (09/10/2024)
+++++++++
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ dependencies = [
"python-multipart~=0.0.9",
"pika~=1.3.2",
"SQLAlchemy~=2.0.34",
"fastapi~=0.112.2",
"fastapi~=0.115.5",
"uvicorn[standard]~=0.30.1",
"requests~=2.32.3",
"mysqlclient~=2.2.4",
"mysql-connector-python~=8.2.0",
"pydantic~=2.8.2",
"pydantic~=2.9.2",
"types-requests",
"lims-utils~=0.2.3"
"lims-utils~=0.2.5"
]
dynamic = ["version"]
license.file = "LICENSE"
Expand Down
89 changes: 80 additions & 9 deletions src/pato/auth/micro.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import requests
from fastapi import Depends, HTTPException, Request
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials
from lims_utils.auth import CookieOrHTTPBearer, GenericUser
from lims_utils.tables import (
Atlas,
BLSession,
DataCollectionGroup,
FoilHole,
GridSquare,
)
from sqlalchemy import select

from ..utils.config import Config
from ..utils.database import db
from ..utils.generic import parse_proposal
from .template import GenericPermissions

Expand Down Expand Up @@ -33,16 +42,10 @@ def __init__(
super().__init__(**user)


def _check_perms(data_id: str | int, endpoint: str, token: str):
def _check_perms(data_id: str | int, endpoint: str, token: str, options: str = ""):
response = requests.get(
"".join(
[
Config.auth.endpoint,
"permission/",
endpoint,
"/",
str(data_id),
]
[Config.auth.endpoint, "permission/", endpoint, "/", str(data_id), options]
),
headers={"Authorization": f"Bearer {token}"},
)
Expand Down Expand Up @@ -96,3 +99,71 @@ def processing_job(
token: HTTPAuthorizationCredentials = Depends(oauth2_scheme),
):
return _check_perms(processingJobId, "processingJob", token.credentials)

@staticmethod
def data_collection_group(
groupId: int, token: HTTPAuthorizationCredentials = Depends(oauth2_scheme)
):
session_id = db.session.scalar(
select(BLSession.sessionId)
.select_from(DataCollectionGroup)
.filter(DataCollectionGroup.dataCollectionGroupId == groupId)
.join(BLSession)
)

if session_id is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Data collection group not found",
)

_check_perms(session_id, "session", token.credentials, "?useId=true")

return groupId

@staticmethod
def grid_square(
gridSquareId: int, token: HTTPAuthorizationCredentials = Depends(oauth2_scheme)
):
session_id = db.session.scalar(
select(BLSession.sessionId)
.select_from(GridSquare)
.join(Atlas)
.join(DataCollectionGroup)
.join(BLSession)
.filter(GridSquare.gridSquareId == gridSquareId)
)

if session_id is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Grid square not found",
)

_check_perms(session_id, "session", token.credentials, "?useId=true")

return gridSquareId

@staticmethod
def foil_holes(
foilHoleId: int, token: HTTPAuthorizationCredentials = Depends(oauth2_scheme)
):
session_id = db.session.scalar(
select(BLSession.sessionId)
.select_from(FoilHole)
.join(GridSquare)
.join(Atlas)
.join(DataCollectionGroup)
.join(BLSession)
.filter(FoilHole.foilHoleId == foilHoleId)
)

if session_id is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Foil hole not found",
)

_check_perms(session_id, "session", token.credentials, "?useId=true")

return foilHoleId
12 changes: 12 additions & 0 deletions src/pato/auth/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@ def processing_job(processingJobId: int) -> int:
@staticmethod
def session(proposalReference: str, visitNumber: int):
return parse_proposal(proposalReference, visitNumber)

@staticmethod
def data_collection_group(groupId: int) -> int:
return groupId

@staticmethod
def grid_square(gridSquareId: int) -> int:
return gridSquareId

@staticmethod
def foil_hole(foilHoleId: int) -> int:
return foilHoleId
12 changes: 12 additions & 0 deletions src/pato/crud/foil_holes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from lims_utils.tables import Movie
from sqlalchemy import select

from ..utils.database import db


def get_movies(foil_hole_id: int, page: int, limit: int):
query = select(Movie).filter(Movie.foilHoleId == foil_hole_id)

return db.paginate(
query=query, limit=limit, page=page, slow_count=False, scalar=False
)
23 changes: 23 additions & 0 deletions src/pato/crud/grid_squares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from lims_utils.tables import FoilHole, GridSquare
from sqlalchemy import select

from ..utils.database import db


def get_foil_holes(grid_square_id: int, page: int, limit: int):
query = select(
FoilHole.pixelLocationX,
FoilHole.pixelLocationY,
FoilHole.diameter,
FoilHole.foilHoleId,
).filter(FoilHole.gridSquareId == grid_square_id)

return db.paginate(query=query, limit=limit, page=page, slow_count=False)


def get_grid_square_image(grid_square_id: int):
return db.session.scalar(
select(GridSquare.gridSquareImage).filter(
GridSquare.gridSquareId == grid_square_id
)
)
51 changes: 48 additions & 3 deletions src/pato/crud/groups.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from typing import Optional

from fastapi import HTTPException, status
from lims_utils.auth import GenericUser
from lims_utils.models import Paged
from lims_utils.tables import (
Atlas,
BLSession,
DataCollection,
DataCollectionGroup,
ExperimentType,
GridSquare,
Proposal,
Tomogram,
)
Expand All @@ -16,7 +19,7 @@
from ..models.parameters import DataCollectionSortTypes
from ..models.response import DataCollectionGroupSummaryResponse, DataCollectionSummary
from ..utils.auth import check_session
from ..utils.database import db, paginate, unravel
from ..utils.database import db, unravel
from ..utils.generic import parse_proposal


Expand Down Expand Up @@ -72,7 +75,7 @@ def get_collection_groups(
db.session.execute(session_id_query).all()
)
)
return paginate(check_session(query, user), limit, page, slow_count=True)
return db.paginate(check_session(query, user), limit, page, slow_count=True)


def get_collections(
Expand Down Expand Up @@ -121,4 +124,46 @@ def get_collections(
if search is not None and search != "":
query = query.filter(sub_result.c.comments.contains(search))

return paginate(query, limit, page, slow_count=True)
return db.paginate(query, limit, page, slow_count=True)


def get_grid_squares(dcg_id: int, limit: int, page: int, hide_uncollected: bool = True):
query = (
select(GridSquare)
.select_from(Atlas)
.filter(Atlas.dataCollectionGroupId == dcg_id)
.join(GridSquare)
)

if hide_uncollected:
query = query.filter(GridSquare.gridSquareImage.is_not(None))

return db.paginate(query, limit, page, slow_count=True, scalar=False)


def get_atlas(dcg_id: int):
atlas = db.session.scalar(
select(Atlas).filter(Atlas.dataCollectionGroupId == dcg_id)
)

if atlas is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Data collection group has no atlas",
)

return atlas


def get_atlas_image(dcg_id: int):
atlas_image = db.session.scalar(
select(Atlas.atlasImage).filter(Atlas.dataCollectionGroupId == dcg_id)
)

if atlas_image is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Data collection group has no atlas",
)

return atlas_image
5 changes: 5 additions & 0 deletions src/pato/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
autoproc,
collections,
feedback,
foil_holes,
grid_squares,
groups,
movies,
procjob,
Expand Down Expand Up @@ -57,6 +59,7 @@ async def lifespan(app: FastAPI):

app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5)


@app.middleware("http")
async def get_session_as_middleware(request, call_next):
with get_session(session_factory):
Expand All @@ -75,3 +78,5 @@ async def get_session_as_middleware(request, call_next):
app.include_router(autoproc.router)
app.include_router(feedback.router)
app.include_router(procjob.router)
app.include_router(grid_squares.router)
app.include_router(foil_holes.router)
23 changes: 23 additions & 0 deletions src/pato/models/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,26 @@ class ReprocessingResponse(BaseModel):
class BFactorFitOut(BaseModel):
numberOfParticles: float
resolution: float


class GridSquare(BaseModel):
gridSquareId: int
x: int = Field(validation_alias="pixelLocationX")
y: int = Field(validation_alias="pixelLocationY")
height: int
width: int
angle: float


class Atlas(BaseModel):
atlasId: int
pixelSize: float
cassetteSlot: int
dataCollectionGroupId: int


class FoilHole(BaseModel):
diameter: int
foilHoleId: int
x: int = Field(validation_alias="pixelLocationX")
y: int = Field(validation_alias="pixelLocationY")
20 changes: 20 additions & 0 deletions src/pato/routes/foil_holes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fastapi import APIRouter, Depends
from lims_utils.models import Paged, pagination

from ..auth import Permissions
from ..crud import foil_holes as crud
from ..models.response import Movie

router = APIRouter(
tags=["Foil Holes"],
prefix="/foil-holes",
)


@router.get("/{foilHoleId}/movies", response_model=Paged[Movie])
def get_movies(
foilHoleId: int = Depends(Permissions.foil_hole),
page: dict[str, int] = Depends(pagination),
):
"""Get movies in a foil hole"""
return crud.get_movies(foil_hole_id=foilHoleId, **page)
27 changes: 27 additions & 0 deletions src/pato/routes/grid_squares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from fastapi import APIRouter, Depends
from fastapi.responses import FileResponse
from lims_utils.models import Paged, pagination

from ..auth import Permissions
from ..crud import grid_squares as crud
from ..models.response import FoilHole

router = APIRouter(
tags=["Grid Squares"],
prefix="/grid-squares",
)


@router.get("/{gridSquareId}/foil-holes", response_model=Paged[FoilHole])
def get_foil_holes(
gridSquareId: int = Depends(Permissions.grid_square),
page: dict[str, int] = Depends(pagination),
):
"""Get foil holes in a grid square"""
return crud.get_foil_holes(grid_square_id=gridSquareId, **page)


@router.get("/{gridSquareId}/image", response_class=FileResponse)
def get_grid_square_image(gridSquareId: int = Depends(Permissions.grid_square)):
"""Get image of grid square"""
return crud.get_grid_square_image(grid_square_id=gridSquareId)
Loading

0 comments on commit 18a8847

Please sign in to comment.