From abafdd40f99e2a11ecdebea765131586e8c6feca Mon Sep 17 00:00:00 2001 From: Mawoka Date: Thu, 11 Jul 2024 00:27:45 +0200 Subject: [PATCH] :bug: Fix Pydantic breaking changes --- classquiz/db/models.py | 42 ++++++------- classquiz/db/quiztivity.py | 6 +- classquiz/kahoot_importer/__init__.py | 80 ++++++++++++------------- classquiz/kahoot_importer/search.py | 4 +- classquiz/routers/box_controller/web.py | 2 +- classquiz/routers/login.py | 4 +- classquiz/routers/moderation.py | 2 +- classquiz/routers/quiz.py | 14 +++-- classquiz/routers/quiztivity/shares.py | 8 +-- classquiz/routers/users/__init__.py | 2 +- classquiz/socket_server/__init__.py | 16 ++--- 11 files changed, 91 insertions(+), 89 deletions(-) diff --git a/classquiz/db/models.py b/classquiz/db/models.py index 8a9d63c..569573f 100644 --- a/classquiz/db/models.py +++ b/classquiz/db/models.py @@ -84,7 +84,7 @@ class UserSession(ormar.Model): class ABCDQuizAnswer(BaseModel): right: bool answer: str - color: str | None + color: str | None = None class RangeQuizAnswer(BaseModel): @@ -97,7 +97,7 @@ class RangeQuizAnswer(BaseModel): class VotingQuizAnswer(BaseModel): answer: str image: str | None = None - color: str | None + color: str | None = None class QuizQuestionType(str, Enum): @@ -145,10 +145,10 @@ class QuizInput(BaseModel): public: bool = False title: str description: str - cover_image: str | None - background_color: str | None + cover_image: str | None = None + background_color: str | None = None questions: list[QuizQuestion] - background_image: str | None + background_image: str | None = None class Quiz(ormar.Model): @@ -207,18 +207,18 @@ class PlayGame(BaseModel): game_pin: str started: bool = False captcha_enabled: bool = False - cover_image: str | None - game_mode: str | None + cover_image: str | None = None + game_mode: str | None = None current_question: int = -1 - background_color: str | None - background_image: str | None - custom_field: str | None + background_color: str | None = None + background_image: str | None = None + custom_field: str | None = None question_show: bool = False class GamePlayer(BaseModel): username: str - sid: str | None + sid: str | None = None class GameAnswer2(BaseModel): @@ -320,8 +320,8 @@ class OnlyId(BaseModel): class PublicQuizTivityShare(BaseModel): id: uuid.UUID - name: str | None - expire_in: int | None + name: str | None = None + expire_in: int | None = None quiztivity: OnlyId user: OnlyId @@ -363,13 +363,13 @@ class PublicStorageItem(BaseModel): id: uuid.UUID uploaded_at: datetime mime_type: str - hash: str | None + hash: str | None = None size: int - deleted_at: datetime | None - alt_text: str | None - filename: str | None - thumbhash: str | None - server: str | None + deleted_at: datetime | None = None + alt_text: str | None = None + filename: str | None = None + thumbhash: str | None = None + server: str | None = None imported: bool @classmethod @@ -425,8 +425,8 @@ class PrivateStorageItem(PublicStorageItem): class UpdateStorageItem(BaseModel): - filename: str | None - alt_text: str | None + filename: str | None = None + alt_text: str | None = None class Controller(ormar.Model): diff --git a/classquiz/db/quiztivity.py b/classquiz/db/quiztivity.py index 4eb283d..191c823 100644 --- a/classquiz/db/quiztivity.py +++ b/classquiz/db/quiztivity.py @@ -12,8 +12,8 @@ class Pdf(BaseModel): class _MemoryCard(BaseModel): - image: str | None - text: str | None + image: str | None = None + text: str | None = None id: str @@ -53,6 +53,6 @@ TYPE_CLASS_LIST = { class QuizTivityPage(BaseModel): - title: str | None + title: str | None = None type: QuizTivityTypes data: Pdf | Memory | Markdown | Abcd diff --git a/classquiz/kahoot_importer/__init__.py b/classquiz/kahoot_importer/__init__.py index 63056c7..5285068 100644 --- a/classquiz/kahoot_importer/__init__.py +++ b/classquiz/kahoot_importer/__init__.py @@ -10,19 +10,19 @@ from pydantic import BaseModel class _CoverMetadata(BaseModel): - id: UUID | None - resources: str | None + id: UUID | None = None + resources: str | None = None class _CreatorAvatar(BaseModel): - url: str | None - id: UUID | None - type: str | None - bitmojiAvatarId: str | None - altText: str | None - contentType: str | None - width: int | None - height: int | None + url: str | None = None + id: UUID | None = None + type: str | None = None + bitmojiAvatarId: str | None = None + altText: str | None = None + contentType: str | None = None + width: int | None = None + height: int | None = None class _LastEdit(BaseModel): @@ -32,19 +32,19 @@ class _LastEdit(BaseModel): class _ImageMetadata(BaseModel): - id: UUID | None - content_type: Optional[str] - width: Optional[int] - height: Optional[int] - resources: Optional[str] + id: UUID | None = None + content_type: Optional[str] = None + width: Optional[int] = None + height: Optional[int] = None + resources: Optional[str] = None class _SampleQuestion(BaseModel): - image: str | None - imageMetadata: _ImageMetadata | None + image: str | None = None + imageMetadata: _ImageMetadata | None = None title: str type: str - time: int | None + time: int | None = None class _Access(BaseModel): @@ -57,19 +57,19 @@ class _Card(BaseModel): title: str description: str slug: str - cover: str | None - coverMetadata: _CoverMetadata | dict[None, None] | None + cover: str | None = None + coverMetadata: _CoverMetadata | dict[None, None] | None = None draftExists: bool - inventoryItemIds: List[Any] + inventoryItemIds: List[Any] = None number_of_questions: int creator: UUID creator_username: str - creator_avatar: _CreatorAvatar | dict[None, None] | None + creator_avatar: _CreatorAvatar | dict[None, None] | None = None badges: List[str] visibility: int locked: bool writeProtection: bool - lastEdit: _LastEdit | None + lastEdit: _LastEdit | None = None featured: bool young_featured: bool sponsored: bool @@ -111,8 +111,8 @@ class _LanguageInfo(BaseModel): class _Metadata(BaseModel): access: _Access - duplicationProtection: bool | None - lastEdit: _LastEdit | None + duplicationProtection: bool | None = None + lastEdit: _LastEdit | None = None class _Parent(BaseModel): @@ -123,15 +123,15 @@ class _Parent(BaseModel): class _Choice(BaseModel): answer: str correct: bool - languageInfo: _LanguageInfo | None + languageInfo: _LanguageInfo | None = None class _Video(BaseModel): startTime: float endTime: float service: str - full_url: Optional[str] - id: Optional[str] + full_url: Optional[str] = None + id: Optional[str] = None class _Question(BaseModel): @@ -141,12 +141,12 @@ class _Question(BaseModel): points: bool pointsMultiplier: int choices: List[_Choice] - image: str | None - imageMetadata: _ImageMetadata | None - resources: Optional[str] + image: str | None = None + imageMetadata: _ImageMetadata | None = None + resources: Optional[str] = None video: _Video questionFormat: int - languageInfo: _LanguageInfo | None + languageInfo: _LanguageInfo | None = None media: List[Any] @@ -157,23 +157,23 @@ class _Kahoot(BaseModel): creator_username: str compatibilityLevel: int creator_primary_usage: str - folderId: UUID | None + folderId: UUID | None = None visibility: int - difficulty: int | None + difficulty: int | None = None audience: str audience: str title: str description: str quizType: str - tags: str | None | List[str] - cover: str | None - coverMetadata: _CoverMetadata | dict[None, None] | None + tags: str | None | List[str] = None + cover: str | None = None + coverMetadata: _CoverMetadata | dict[None, None] | None = None questions: List[_Question] metadata: _Metadata - parent: _Parent | None - resources: str | None + parent: _Parent | None = None + resources: str | None = None slug: str - languageInfo: _LanguageInfo | None + languageInfo: _LanguageInfo | None = None inventoryItemIds: List[Any] type: str created: int diff --git a/classquiz/kahoot_importer/search.py b/classquiz/kahoot_importer/search.py index 201dac7..989438b 100644 --- a/classquiz/kahoot_importer/search.py +++ b/classquiz/kahoot_importer/search.py @@ -15,12 +15,12 @@ from classquiz.kahoot_importer import _Entity class _Response(BaseModel): entities: List[_Entity] totalHits: int - cursor: int | None + cursor: int | None = None pageTimestamp: int async def search( - query: str | None, + query: str | None = None, limit: int | None = 9, cursor: int | None = 1, search_cluster: int | None = 1, diff --git a/classquiz/routers/box_controller/web.py b/classquiz/routers/box_controller/web.py index 14c60ad..e20e851 100644 --- a/classquiz/routers/box_controller/web.py +++ b/classquiz/routers/box_controller/web.py @@ -18,7 +18,7 @@ router = APIRouter() class SetControllerUpInput(BaseModel): - player_name: str | None + player_name: str | None = None name: str diff --git a/classquiz/routers/login.py b/classquiz/routers/login.py index a88d33e..1b95cec 100644 --- a/classquiz/routers/login.py +++ b/classquiz/routers/login.py @@ -50,7 +50,7 @@ class LoginSession(BaseModel): user_id: str step_1: set[StartLoginResponseTypes] step_2: set[StartLoginResponseTypes] - webauthn_challenge: str | None + webauthn_challenge: str | None = None step1_success: bool = False @@ -58,7 +58,7 @@ class StartLoginResponse(BaseModel): step_1: set[StartLoginResponseTypes] step_2: set[StartLoginResponseTypes] session_id: str - webauthn_data: None | str + webauthn_data: None | str = None def verify_webauthn(data, fidocredentialss: list[FidoCredentials], login_session: LoginSession): diff --git a/classquiz/routers/moderation.py b/classquiz/routers/moderation.py index 0ef4d16..6b1ff73 100644 --- a/classquiz/routers/moderation.py +++ b/classquiz/routers/moderation.py @@ -47,7 +47,7 @@ async def get_newest_quizzes( class SetModRatingForQuizInput(BaseModel): - rating: int | None + rating: int | None = None @router.post("/rating/set/{quiz_id}") diff --git a/classquiz/routers/quiz.py b/classquiz/routers/quiz.py index 8de848c..c6dcf11 100644 --- a/classquiz/routers/quiz.py +++ b/classquiz/routers/quiz.py @@ -15,13 +15,14 @@ import ormar.exceptions from classquiz.helpers import generate_spreadsheet, handle_import_from_excel from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from fastapi.responses import JSONResponse, StreamingResponse -from pydantic import ValidationError, BaseModel, Field +from pydantic import ValidationError, BaseModel from classquiz.auth import get_current_user from classquiz.config import redis, settings, storage, meilisearch from classquiz.db.models import Quiz, User, PlayGame, GameInLobby, QuizQuestion, QuizQuestionType from classquiz.helpers.box_controller import generate_code from classquiz.kahoot_importer.import_quiz import import_quiz +from uuid import UUID import urllib.parse settings = settings() @@ -54,9 +55,9 @@ class PublicQuizResponseUser(BaseModel): id: uuid.UUID -class PublicQuizResponse(Quiz.get_pydantic()): +class PublicQuizResponse(Quiz.get_pydantic(exclude={"questions"})): user_id: PublicQuizResponseUser - var_questions: list[QuizQuestion] = Field(..., alias="questions") + questions: list[QuizQuestion] likes: int dislikes: int views: int @@ -143,8 +144,8 @@ async def start_quiz( class CheckIfCaptchaEnabledResponse(BaseModel): enabled: bool - game_mode: str | None - custom_field: str | None + game_mode: str | None = None + custom_field: str | None = None @router.get("/play/check_captcha/{game_pin}", response_model=CheckIfCaptchaEnabledResponse) @@ -229,7 +230,8 @@ async def export_quiz_answers(export_token: str, game_pin: str): data = json.loads(data) data2 = await redis.get(f"game:{game_pin}") game_data = PlayGame.parse_raw(data2) - quiz = await Quiz.objects.get_or_none(id=game_data.quiz_id) + print(type(game_data.quiz_id)) + quiz = await Quiz.objects.get_or_none(id=UUID(game_data.quiz_id)) if quiz is None: raise HTTPException(status_code=404, detail="quiz not found") diff --git a/classquiz/routers/quiztivity/shares.py b/classquiz/routers/quiztivity/shares.py index ffa740b..47e1213 100644 --- a/classquiz/routers/quiztivity/shares.py +++ b/classquiz/routers/quiztivity/shares.py @@ -25,9 +25,9 @@ async def get_shares(user: User = Depends(get_current_user)) -> list[PublicQuizT class CreateShareInput(BaseModel): - name: str | None + name: str | None = None quiztivity: UUID - expire_in: int | None + expire_in: int | None = None @router.post("/") @@ -55,8 +55,8 @@ async def delete_share(uuid: UUID, user: User = Depends(get_current_user)): class UpdateShareInput(BaseModel): - name: str | None - expire_in: int | None + name: str | None = None + expire_in: int | None = None @router.put("/{uuid}") diff --git a/classquiz/routers/users/__init__.py b/classquiz/routers/users/__init__.py index 10be527..1a3f4e8 100644 --- a/classquiz/routers/users/__init__.py +++ b/classquiz/routers/users/__init__.py @@ -297,7 +297,7 @@ async def get_other_avatar(respo: Response, user_id: uuid.UUID): class InternalAuthData(BaseModel): rememberme: str - jwt: str | None + jwt: str | None = None @router.post("/auth/internal") diff --git a/classquiz/socket_server/__init__.py b/classquiz/socket_server/__init__.py index e39c846..83b6139 100644 --- a/classquiz/socket_server/__init__.py +++ b/classquiz/socket_server/__init__.py @@ -61,10 +61,10 @@ def calculate_score(z: float, t: int) -> int: async def set_answer(answers, game_pin: str, q_index: int, data: AnswerData) -> AnswerDataList: if answers is None: - answers = AnswerDataList(__root__=[data]) + answers = AnswerDataList([data]) else: answers = AnswerDataList.parse_raw(answers) - answers.__root__.append(data) + answers.root.append(data) await redis.set( f"game_session:{game_pin}:{q_index}", answers.json(), @@ -76,8 +76,8 @@ async def set_answer(answers, game_pin: str, q_index: int, data: AnswerData) -> class _JoinGameData(BaseModel): username: str game_pin: str - captcha: str | None - custom_field: str | None + captcha: str | None = None + custom_field: str | None = None class _RejoinGameData(BaseModel): @@ -280,7 +280,7 @@ async def get_question_results(sid: str, data: dict): if redis_res is None: redis_res = [] else: - redis_res = AnswerDataList.parse_raw(redis_res).dict()["__root__"] + redis_res = AnswerDataList.parse_raw(redis_res).model_dump() game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}")) game_data.question_show = False await redis.set(f"game:{session['game_pin']}", game_data.json()) @@ -291,7 +291,7 @@ async def get_question_results(sid: str, data: dict): class ABCDQuizAnswerWithoutSolution(BaseModel): answer: str - color: str | None + color: str | None = None class RangeQuizAnswerWithoutSolution(BaseModel): @@ -360,7 +360,7 @@ class _SubmitAnswerDataOrderType(BaseModel): class _SubmitAnswerData(BaseModel): question_index: int answer: str - complex_answer: list[_SubmitAnswerDataOrderType] | None + complex_answer: list[_SubmitAnswerDataOrderType] | None = None @sio.event @@ -448,7 +448,7 @@ async def submit_answer(sid: str, data: dict): ) player_count = await redis.scard(f"game_session:{session['game_pin']}:players") await sio.emit("player_answer", {}) - if len(answers.__root__) == player_count: + if len(answers.root) == player_count: # await sio.emit( # "question_results", # await redis.get(f"game_session:{session['game_pin']}:{data.question_index}"),