-
Couldn't load subscription status.
- Fork 4
feat: crud functionality and aggregations for python backend #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a couple comments/questions. nice job!
server/python/src/routers/movies.py
Outdated
| @router.get("/{id}", response_model=SuccessResponse[Movie]) | ||
| async def get_movie_by_id(id: str): | ||
| # Validate ObjectId format | ||
| object_id = ObjectId(id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we wrap this in a try-except? i.e. what happens if id isn't valid?
| # Use findOne() to get a single document by _id | ||
| movie = await db.movies.find_one({"_id": object_id}) | ||
|
|
||
| movie["_id"] = str(movie["_id"]) # Convert ObjectId to string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same - what happens if movie is None?
server/python/src/routers/movies.py
Outdated
| print(f"Database name: {db.name if hasattr(db, 'name') else 'unknown'}") | ||
| print(f"Collection name: movies") | ||
|
|
||
| # For motor (async MongoDB driver), we need to await the aggregate call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we weren't using Motor (instead using PyMongo's native async)
| # For motor (async MongoDB driver), we need to await the aggregate call | |
| # For async PyMongo driver, we need to await the aggregate call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, we aren't using motor. Just async within the PyMongo driver.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am not, i think copilot added this comment for some reason despite not using motor 😓
server/python/src/routers/movies.py
Outdated
|
|
||
| # For motor (async MongoDB driver), we need to await the aggregate call | ||
| cursor = await db.movies.aggregate(pipeline) | ||
| results = await cursor.to_list(length=None) # Convert cursor to list |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to point out why we're using to_list() for aggregations vs. async for for find queries (what you did in lines 105-108)
| cursor = await db.movies.aggregate(pipeline) | ||
| results = await cursor.to_list(length=None) # Convert cursor to list | ||
|
|
||
| print(f"Aggregation returned {len(results)} results") # Debug logging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a ticket to add proper logging?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe there's an official logging ticket, this was just my logging for my own testing purposes locally. I can remove it if that makes the code cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job!
These are some minor changes. Mostly to keep the code similar and adding in validation. I didn't get to the end of the file. I will get to find and delete on Monday.
I havent written an aggregation yet, so I might leave those for now. I will ping you, if I get to them.
server/python/src/routers/movies.py
Outdated
| object_id = ObjectId(id) | ||
|
|
||
| # Use findOne() to get a single document by _id | ||
| movie = await db.movies.find_one({"_id": object_id}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To grab the db, I added a function called get_collection from mongo_client.py file to make unit testing easier later. I am calling the db like this in the rest of the functions:
movies_collection = get_collection("movies")
server/python/src/routers/movies.py
Outdated
| genre (str): The genre of the movie. | ||
| year (int): The year the movie was released. | ||
| min_rating (float): The minimum IMDB rating. | ||
| max_rating (float): The maximum IMDB rating. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The request body for this the CreateMovieRequest object.
server/python/src/routers/movies.py
Outdated
| result = await db.movies.insert_one(movie_data) | ||
|
|
||
| # Retrieve the created document to return complete data | ||
| created_movie = await db.movies.find_one({"_id": result.inserted_id}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to verify that the document was created before querying it. A check that result is acknowledged.
server/python/src/routers/movies.py
Outdated
| SuccessResponse[Movie]: A response object containing the created movie data. | ||
| """ | ||
|
|
||
| @router.post("/", response_model=SuccessResponse[CreateMovieRequest], status_code=201) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be:
response_model=SuccessResponse[Movie]
We are returning the movie
server/python/src/routers/movies.py
Outdated
|
|
||
| @router.delete("/{id}", response_model=SuccessResponse[dict]) | ||
| async def delete_movie_by_id(id: str): | ||
| object_id = ObjectId(id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap in a try/catch. Id might not be valid.
server/python/src/routers/movies.py
Outdated
| result = await db.movies.delete_one({"_id": object_id}) | ||
|
|
||
| if result.deleted_count == 0: | ||
| raise HTTPException(status_code=404, detail="Movie not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets use the create_error_response() to keep the errors consistent.
server/python/src/routers/movies.py
Outdated
| object_id = ObjectId(id) | ||
|
|
||
| # Use deleteOne() to remove a single document | ||
| result = await db.movies.delete_one({"_id": object_id}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above about accessing the db.
server/python/src/routers/movies.py
Outdated
|
|
||
| @router.delete("/{id}/find-and-delete", response_model=SuccessResponse[Movie]) | ||
| async def find_and_delete_movie(id: str): | ||
| object_id = ObjectId(id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap in try /except
server/python/src/routers/movies.py
Outdated
| deleted_movie = await db.movies.find_one_and_delete({"_id": object_id}) | ||
|
|
||
| if deleted_movie is None: | ||
| raise HTTPException(status_code=404, detail="Movie not found") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
convert to our standard error response
server/python/src/routers/movies.py
Outdated
| SuccessResponse[List[dict]]: A response object containing aggregated genre statistics. | ||
| """ | ||
|
|
||
| @router.get("/aggregate/by-genre", response_model=SuccessResponse[List[dict]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we are doing by genre?
Either way the endpoint should be /api/movies/reportingByGenre
I'm not against using /aggregate. I think that would look nicer, but its a change we all have to agree on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, i will remove
server/python/src/routers/movies.py
Outdated
| } | ||
| ] | ||
|
|
||
| # Execute the aggregation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try/except
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed this code
server/python/src/routers/movies.py
Outdated
| SuccessResponse[List[dict]]: A response object containing movies with their most recent comments. | ||
| """ | ||
|
|
||
| @router.get("/aggregate/recent-commented", response_model=SuccessResponse[List[dict]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above.
I think the endpoint is /api/movies/reportingByYear
server/python/src/routers/movies.py
Outdated
| object_id = ObjectId(movie_id) | ||
| pipeline[0]["$match"]["_id"] = object_id | ||
| except Exception: | ||
| raise HTTPException(status_code=400, detail="Invalid movie_id format") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Standardize error response.
server/python/src/routers/movies.py
Outdated
| ]) | ||
|
|
||
| # Execute the aggregation | ||
| results = await execute_aggregation(pipeline) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try / except.
server/python/src/routers/movies.py
Outdated
| "$sort": {"mostRecentCommentDate": -1} | ||
| }, | ||
| { | ||
| "$limit": 50 if movie_id else 20 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use limit? You defined it earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, i think this is some copilot weirdness i should've caught! fixing
server/python/src/routers/movies.py
Outdated
| SuccessResponse[List[dict]]: A response object containing yearly movie statistics. | ||
| """ | ||
|
|
||
| @router.get("/aggregate/by-year", response_model=SuccessResponse[List[dict]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/api/movies/reportingByYear
server/python/src/routers/movies.py
Outdated
| ] | ||
|
|
||
| # Execute the aggregation | ||
| results = await execute_aggregation(pipeline) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try/ except
server/python/src/routers/movies.py
Outdated
| ] | ||
|
|
||
| # Execute the aggregation | ||
| results = await execute_aggregation(pipeline) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try / except
server/python/src/routers/movies.py
Outdated
| SuccessResponse[List[dict]]: A response object containing director statistics. | ||
| """ | ||
|
|
||
| @router.get("/aggregate/directors", response_model=SuccessResponse[List[dict]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/api/movies/reportingByDirector
Basic crud functionality and aggregation pipelines for python backend
This PR introduces basic CRUD functionality and aggregation pipelines for the FastAPI application. It sets up find, insert, delete, and find and delete operations. It also adds aggregations for movies by year, most recent comments (joining the movies collection with the comments collection), and by director.
Key Changes
Testing