Type-hint Link db attributes more appropriately

This commit is contained in:
Peter Vacho 2024-12-25 19:33:13 +01:00
parent 3689f7d076
commit 3c2bc1c10e
Signed by: school
GPG key ID: 8CFC3837052871B4
8 changed files with 30 additions and 13 deletions

View file

@ -1,5 +1,6 @@
from typing import Annotated, Literal
from beanie import Link
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import Response
from fastapi.security import OAuth2PasswordRequestForm
@ -89,8 +90,11 @@ async def refresh(cur_token: RefreshTokenDep) -> AccessTokenResponseBase:
await invalidate_access_tokens(cur_db_token)
if isinstance(cur_db_token.user, Link):
raise TypeError("The user attribute must be fetched")
if cur_db_token.user.id is None:
raise RuntimeError("Never")
raise ValueError("Got user without id")
# We can now be certain there aren't any existing valid access tokens made from this refresh token.
# Let's create a new access token.

View file

@ -1,5 +1,6 @@
from typing import Annotated, Literal, TypeAlias
from beanie import Link
from fastapi import Depends, HTTPException, Security, status
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
@ -83,6 +84,10 @@ async def get_current_user(
) -> User:
"""FastAPI dependency to get the currently logged in user from the JWT bearer access token."""
_, db_token = await _get_access_token(token, security_scopes=security_scopes)
if isinstance(db_token.user, Link):
raise TypeError("The user attribute must be fetched")
return db_token.user

View file

@ -1,7 +1,7 @@
from datetime import datetime
from typing import Literal, final
from beanie import PydanticObjectId
from beanie import Link, PydanticObjectId
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
@ -33,10 +33,18 @@ class UserSession(BaseModel):
@classmethod
def from_token(cls, token: Token) -> "UserSession":
"""Construct UserSession from database Token object."""
# This can only happen if the attendee wasn't yet committed to the db,
# within the context of this function, that should never happen.
if token.id is None:
raise RuntimeError("Never")
if token.parent_token and token.parent_token.id is None:
raise RuntimeError("Never")
raise ValueError("Got token without id")
if token.parent_token:
# This function expects the parent token links to be fetched already
if isinstance(token.parent_token, Link):
raise TypeError("Parent token must be fetched before calling this function")
if token.parent_token.id is None:
raise ValueError("Got parent token without id")
return cls(
id=str(token.id),

View file

@ -11,7 +11,7 @@ from src.db.models.user import User
class Category(Document):
"""Category table."""
user: Annotated[User, Annotated[Link[User], Indexed()]]
user: Annotated[User | Link[User], Annotated[Link[User], Indexed()]]
name: str # TODO: Should this be unique?
color: str # TODO: Consider using a complex rgb type
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

View file

@ -15,13 +15,13 @@ class Event(Document):
user: Annotated[User, Annotated[Link[User], Indexed()]]
title: str
description: str
categories: Annotated[list[Category], Annotated[list[Link[Category]], Indexed()]]
categories: Annotated[list[Category | Link[Category]], Annotated[list[Link[Category]], Indexed()]]
start_date: Annotated[date, Indexed()]
start_time: time
end_date: Annotated[date, Indexed()]
end_time: time
color: str # TODO: Consider using a complex rgb type
attendees: Annotated[list[Link[User]], Indexed()]
attendees: Annotated[list[User | Link[User]], Annotated[list[Link[User]], Indexed()]]
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
@final

View file

@ -12,8 +12,8 @@ from src.db.models.user import User
class Invitation(Document):
"""Invitation table."""
event: Annotated[Event, Annotated[Link[Event], Indexed()]]
invitee: Annotated[User, Annotated[Link[User], Indexed()]]
event: Annotated[Event | Link[User], Annotated[Link[Event], Indexed()]]
invitee: Annotated[User | Link[User], Annotated[Link[User], Indexed()]]
status: Literal["accepted", "declined", "pending"] = "pending"
sent_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
responded_at: datetime | None = None

View file

@ -11,7 +11,7 @@ from src.db.models.user import User
class Notification(Document):
"""Notification table."""
user: Annotated[User, Annotated[Link[User], Indexed()]]
user: Annotated[User | Link[User], Annotated[Link[User], Indexed()]]
event_type: Annotated[Literal["reminder", "invitation"], Indexed()]
message: str
data: Any

View file

@ -12,9 +12,9 @@ from src.db.models.user import User
class Token(Document):
"""Token table."""
user: Annotated[User, Annotated[Link[User], Indexed()]]
user: Annotated[User | Link[User], Annotated[Link[User], Indexed()]]
tok_type: Literal["access", "refresh"]
parent_token: Annotated["Token | None", Annotated[Link["Token"] | None, Indexed()]] = None
parent_token: Annotated["Token | Link[Token] | None", Annotated[Link["Token"] | None, Indexed()]] = None
revoked: bool = False
issued_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
expires_at: datetime