From cbd303056547f18650058c4f9cbb4a00839ae338 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Mon, 25 Jul 2022 22:58:02 +0200 Subject: [PATCH] Create initial wordle methods --- database/crud/wordle.py | 23 ++++++++ database/mongo_types.py | 6 ++ database/schemas/mongo.py | 58 +++++++++++++++++++- tests/test_database/test_crud/test_wordle.py | 42 ++++++++++++++ 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 database/crud/wordle.py create mode 100644 database/mongo_types.py create mode 100644 tests/test_database/test_crud/test_wordle.py diff --git a/database/crud/wordle.py b/database/crud/wordle.py new file mode 100644 index 0000000..5a2211c --- /dev/null +++ b/database/crud/wordle.py @@ -0,0 +1,23 @@ +from typing import Optional + +from database.mongo_types import MongoCollection +from database.schemas.mongo import WordleGame + +__all__ = ["get_active_wordle_game", "make_wordle_guess", "start_new_wordle_game"] + + +async def get_active_wordle_game(collection: MongoCollection, user_id: int) -> Optional[WordleGame]: + """Find a player's active game""" + return await collection.find_one({"user_id": user_id}) + + +async def start_new_wordle_game(collection: MongoCollection, user_id: int, word: str) -> WordleGame: + """Start a new game""" + game = WordleGame(user_id=user_id, word=word) + await collection.insert_one(game.dict(by_alias=True)) + return game + + +async def make_wordle_guess(collection: MongoCollection, user_id: int, guess: str): + """Make a guess in your current game""" + await collection.update_one({"user_id": user_id}, {"$push": {"guesses": guess}}) diff --git a/database/mongo_types.py b/database/mongo_types.py new file mode 100644 index 0000000..11f5b7a --- /dev/null +++ b/database/mongo_types.py @@ -0,0 +1,6 @@ +import motor.motor_asyncio + +# Type aliases for the Motor types, which are way too long +MongoClient = motor.motor_asyncio.AsyncIOMotorClient +MongoDatabase = motor.motor_asyncio.AsyncIOMotorDatabase +MongoCollection = motor.motor_asyncio.AsyncIOMotorCollection diff --git a/database/schemas/mongo.py b/database/schemas/mongo.py index 95d2a2a..6560b95 100644 --- a/database/schemas/mongo.py +++ b/database/schemas/mongo.py @@ -1,7 +1,12 @@ -from bson import ObjectId -from pydantic import BaseModel, Field +import datetime +from abc import ABC, abstractmethod +from typing import Optional -__all__ = ["MongoBase"] +from bson import ObjectId +from overrides import overrides +from pydantic import BaseModel, Field, conlist + +__all__ = ["MongoBase", "WordleGame"] class PyObjectId(str): @@ -36,3 +41,50 @@ class MongoBase(BaseModel): arbitrary_types_allowed = True json_encoders = {ObjectId: str, PyObjectId: str} use_enum_values = True + + +class MongoCollection(MongoBase, ABC): + """Base model for the 'main class' in a collection + + This field stores the name of the collection to avoid making typos against it + """ + + @staticmethod + @abstractmethod + def collection() -> str: + raise NotImplementedError + + +class WordleStats(BaseModel): + """Model that holds stats about a player's Wordle performance""" + + guess_distribution: conlist(int, min_items=6, max_items=6) = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0]) + last_guess: Optional[datetime.date] = None + win_rate: float = 0 + current_streak: int = 0 + max_streak: int = 0 + + +class GameStats(MongoCollection): + """Collection that holds stats about how well a user has performed in games""" + + user_id: int + wordle: Optional[WordleStats] = None + + @staticmethod + @overrides + def collection() -> str: + return "game_stats" + + +class WordleGame(MongoCollection): + """Collection that holds people's active Wordle games""" + + user_id: int + word: str + guesses: conlist(str, min_items=0, max_items=6) = Field(default_factory=list) + + @staticmethod + @overrides + def collection() -> str: + return "wordle" diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py new file mode 100644 index 0000000..6b85d7e --- /dev/null +++ b/tests/test_database/test_crud/test_wordle.py @@ -0,0 +1,42 @@ +import pytest + +from database.crud import wordle as crud +from database.mongo_types import MongoCollection, MongoDatabase +from database.schemas.mongo import WordleGame + + +@pytest.fixture +async def wordle_collection(mongodb: MongoDatabase) -> MongoCollection: + """Fixture to get a reference to the wordle collection""" + yield mongodb[WordleGame.collection()] + + +@pytest.fixture +async def wordle_game(wordle_collection: MongoCollection, test_user_id: int) -> WordleGame: + """Fixture to create a new game""" + game = WordleGame(user_id=test_user_id, word="test") + await wordle_collection.insert_one(game.dict(by_alias=True)) + yield game + + +async def test_start_new_game(wordle_collection: MongoCollection, test_user_id: int): + """Test starting a new game""" + result = await wordle_collection.find_one({"user_id": test_user_id}) + assert result is None + + await crud.start_new_wordle_game(wordle_collection, test_user_id, "test") + + result = await wordle_collection.find_one({"user_id": test_user_id}) + assert result is not None + + +async def test_get_active_wordle_game_none(wordle_collection: MongoCollection, test_user_id: int): + """Test getting an active game when there is none""" + result = await crud.get_active_wordle_game(wordle_collection, test_user_id) + assert result is None + + +async def test_get_active_wordle_game(wordle_collection: MongoCollection, wordle_game: WordleGame): + """Test getting an active game when there is none""" + result = await crud.get_active_wordle_game(wordle_collection, wordle_game.user_id) + assert result == wordle_game.dict(by_alias=True)