-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Docker setup - Using a Docker Compose file, launch three containers. The first container is the MongoDB instance, the second container is a web application that allows easier interfacing with the instance, and the third is the backend. - The containers are not running on the host network, which is why the host of the Uvicorn server was changed to 0.0.0.0. * feat: MongoDB Demo - /demo shows how to add a user to a MongoDB database and query it * fix: add docker compose down * fix: Docker setup optimizations * fix: component usage cleanup * fix: backend cleanup * fix: type error in page.tsx * feat: unit tests for MongoDB * update: Docker optimizations * fix: change get_user route to GET * feat: set up Docker volume * update: use uvicorn to launch backend, not dev.py * fix: remove unnecessarily exposed port * update: remove motor asyncio workaround * fix: remove unused import * fix: add back event loop workaround * fix: type errors * feat: WORKDIR in Dockerfile * fix: remove type parameters to AgnosticClient * fix: BaseRecord Config deprecation
- Loading branch information
1 parent
cd80b69
commit 3a586cf
Showing
11 changed files
with
390 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
FROM python:3.9 | ||
|
||
RUN apt-get update && apt-get install -y libxml2-dev libxmlsec1-dev libxmlsec1-openssl | ||
|
||
WORKDIR /app | ||
|
||
COPY requirements.txt . | ||
|
||
RUN pip install --no-cache-dir -r requirements.txt "uvicorn[standard]" | ||
|
||
COPY src/ . | ||
|
||
ENV PYTHONPATH=src/ | ||
|
||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Use root/example as user/password credentials | ||
version: "3.1" | ||
|
||
services: | ||
mongo: | ||
image: mongo | ||
restart: always | ||
environment: | ||
MONGO_INITDB_ROOT_USERNAME: root | ||
MONGO_INITDB_ROOT_PASSWORD: example | ||
volumes: | ||
- mongodb_data_volume:/data/db | ||
|
||
mongo-express: | ||
image: mongo-express | ||
restart: always | ||
ports: | ||
- "8081:8081" | ||
environment: | ||
ME_CONFIG_MONGODB_ADMINUSERNAME: root | ||
ME_CONFIG_MONGODB_ADMINPASSWORD: example | ||
ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/ | ||
|
||
fastapi-dev: | ||
build: . | ||
ports: | ||
- "8000:8000" | ||
environment: | ||
MONGODB_URI: mongodb://root:example@mongo:27017 | ||
|
||
volumes: | ||
mongodb_data_volume: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,5 @@ fastapi==0.104.1 | |
httpx==0.25.2 | ||
python-multipart==0.0.5 | ||
python3-saml==1.16.0 | ||
motor==3.3.2 | ||
pydantic[email]==2.5.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
docker compose up --build | ||
docker compose down |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import asyncio | ||
import os | ||
from enum import Enum | ||
from logging import getLogger | ||
from typing import Any, Mapping, Optional, Union | ||
|
||
from bson import CodecOptions | ||
from motor.core import AgnosticClient | ||
from motor.motor_asyncio import AsyncIOMotorClient | ||
from pydantic import BaseModel, Field, ConfigDict | ||
|
||
log = getLogger(__name__) | ||
|
||
STAGING_ENV = os.getenv("DEPLOYMENT") == "STAGING" | ||
|
||
MONGODB_URI = os.getenv("MONGODB_URI") | ||
|
||
# Mypy thinks AgnosticClient is a generic type, but providing type parameters to it | ||
# raises a TypeError. | ||
MONGODB_CLIENT: AgnosticClient = AsyncIOMotorClient(MONGODB_URI) # type: ignore | ||
|
||
# Resolve Vercel runtime issue | ||
MONGODB_CLIENT.get_io_loop = asyncio.get_event_loop # type: ignore | ||
|
||
DATABASE_NAME = "irvinehacks" if STAGING_ENV else "irvinehacks-prod" | ||
DB = MONGODB_CLIENT[DATABASE_NAME].with_options( | ||
codec_options=CodecOptions(tz_aware=True) | ||
) | ||
|
||
|
||
class BaseRecord(BaseModel): | ||
model_config = ConfigDict(populate_by_name=True) | ||
|
||
uid: str = Field(alias="_id") | ||
|
||
def dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: | ||
if "by_alias" in kwargs: | ||
return BaseModel.model_dump(self, *args, **kwargs) | ||
return BaseModel.model_dump(self, by_alias=True, *args, **kwargs) | ||
|
||
|
||
class Collection(str, Enum): | ||
USERS = "users" | ||
TESTING = "testing" | ||
SETTINGS = "settings" | ||
|
||
|
||
async def insert( | ||
collection: Collection, data: Mapping[str, object] | ||
) -> Union[str, bool]: | ||
"""Insert a document into the specified collection of the database""" | ||
COLLECTION = DB[collection.value] | ||
result = await COLLECTION.insert_one(data) | ||
if not result.acknowledged: | ||
log.error("MongoDB document insertion was not acknowledged") | ||
raise RuntimeError("Could not insert document into MongoDB collection") | ||
new_document_id: str = result.inserted_id | ||
return new_document_id | ||
|
||
|
||
async def retrieve_one( | ||
collection: Collection, query: Mapping[str, object], fields: list[str] = [] | ||
) -> Optional[dict[str, Any]]: | ||
"""Search for and retrieve the specified fields of all documents (if any exist) | ||
that satisfy the provided query.""" | ||
COLLECTION = DB[collection.value] | ||
|
||
result: Optional[dict[str, object]] = await COLLECTION.find_one(query, fields) | ||
return result | ||
|
||
|
||
async def retrieve( | ||
collection: Collection, query: Mapping[str, object], fields: list[str] = [] | ||
) -> list[dict[str, object]]: | ||
"""Search for and retrieve the specified fields of a document (if any exist) | ||
that satisfy the provided query.""" | ||
COLLECTION = DB[collection.value] | ||
|
||
result = COLLECTION.find(query, fields) | ||
output: list[dict[str, object]] = await result.to_list(length=None) | ||
return output | ||
|
||
|
||
async def update_one( | ||
collection: Collection, | ||
query: Mapping[str, object], | ||
new_data: Mapping[str, object], | ||
*, | ||
upsert: bool = False, | ||
) -> bool: | ||
"""Search for and set a document's fields using the provided query and data.""" | ||
return await raw_update_one(collection, query, {"$set": new_data}, upsert=upsert) | ||
|
||
|
||
async def raw_update_one( | ||
collection: Collection, | ||
query: Mapping[str, object], | ||
update: Mapping[str, object], | ||
*, | ||
upsert: bool = False, | ||
) -> bool: | ||
"""Search for and update a document using the provided query and raw update.""" | ||
COLLECTION = DB[collection.value] | ||
result = await COLLECTION.update_one(query, update, upsert=upsert) | ||
if not result.acknowledged: | ||
log.error("MongoDB document update was not acknowledged") | ||
raise RuntimeError("Could not update documents in MongoDB collection") | ||
|
||
return result.modified_count > 0 | ||
|
||
|
||
async def update( | ||
collection: Collection, query: Mapping[str, object], new_data: Mapping[str, object] | ||
) -> bool: | ||
"""Search for and update documents (if they exist) using the provided query data.""" | ||
COLLECTION = DB[collection.value] | ||
result = await COLLECTION.update_many(query, {"$set": new_data}) | ||
if not result.acknowledged: | ||
log.error("MongoDB document update was not acknowledged") | ||
raise RuntimeError("Could not update documents in MongoDB collection") | ||
|
||
return result.modified_count > 0 |
Oops, something went wrong.