-
Notifications
You must be signed in to change notification settings - Fork 58
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
solution #34
base: main
Are you sure you want to change the base?
solution #34
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,110 @@ | ||
from fastapi import APIRouter, Depends, HTTPException, Query | ||
from typing import Annotated | ||
from fastapi import APIRouter, Depends, HTTPException, Query, status | ||
from sqlalchemy.exc import IntegrityError | ||
from sqlalchemy.orm import Session, joinedload | ||
|
||
from database import get_db | ||
from database.models import MovieModel, CountryModel, GenreModel, ActorModel, LanguageModel | ||
|
||
from database.models import ( | ||
MovieModel, | ||
CountryModel, | ||
GenreModel, | ||
ActorModel, | ||
LanguageModel | ||
) | ||
from schemas.movies import ( | ||
PaginatedMovieResponseSchema, | ||
CreateMovieSchema, | ||
CreateResponseMovieSchema, | ||
MovieDetailSchema, | ||
UpdateMovieRequest | ||
) | ||
from services.crud import ( | ||
delete_movie, | ||
get_movies, | ||
create_movie, | ||
get_movie_by_id, | ||
update_movie, | ||
) | ||
|
||
router = APIRouter() | ||
|
||
|
||
# Write your code here | ||
@router.get( | ||
"/movies/", | ||
response_model=PaginatedMovieResponseSchema, | ||
status_code=status.HTTP_200_OK | ||
) | ||
def read_movies( | ||
db: Session = Depends(get_db), | ||
page: Annotated[int, Query(ge=1)] = 1, | ||
per_page: Annotated[int, Query(ge=1, le=20)] = 10 | ||
): | ||
total_items = db.query(MovieModel).count() | ||
total_pages = (total_items + per_page - 1) // per_page | ||
|
||
offset = (page - 1) * per_page | ||
limit = per_page | ||
|
||
movies = get_movies(db, offset, limit) | ||
|
||
if not movies: | ||
raise HTTPException( | ||
status_code=status.HTTP_404_NOT_FOUND, | ||
detail="No movies found." | ||
) | ||
|
||
prev_page = f"/theater/movies/?page={page - 1}&per_page={per_page}" if page > 1 else None | ||
next_page = f"/theater/movies/?page={page + 1}&per_page={per_page}" if page < total_pages else None | ||
|
||
return PaginatedMovieResponseSchema( | ||
movies=movies, | ||
prev_page=prev_page, | ||
next_page=next_page, | ||
total_pages=total_pages, | ||
total_items=total_items | ||
) | ||
|
||
|
||
@router.post( | ||
"/movies/", | ||
response_model=CreateResponseMovieSchema, | ||
status_code=status.HTTP_201_CREATED | ||
) | ||
def add_movie( | ||
movie: CreateMovieSchema, | ||
db: Session = Depends(get_db) | ||
): | ||
movie_exists = db.query(MovieModel).filter_by(name=movie.name, date=movie.date).first() | ||
|
||
if movie_exists: | ||
raise HTTPException( | ||
status_code=status.HTTP_409_CONFLICT, | ||
detail=f"A movie with the name '{movie.name}' and release date '{movie.date}' already exists." | ||
) | ||
|
||
try: | ||
new_movie = create_movie(movie, db) | ||
return CreateResponseMovieSchema.model_validate(new_movie) | ||
except ValueError: | ||
raise HTTPException( | ||
status_code=status.HTTP_400_BAD_REQUEST, | ||
detail="Invalid input data." | ||
) | ||
|
||
Comment on lines
+88
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Catching a generic
Comment on lines
+88
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider catching more specific exceptions related to the |
||
|
||
@router.get("/movies/{movie_id}/", response_model=MovieDetailSchema) | ||
def get_movie_details(movie_id: int, db: Session = Depends(get_db)): | ||
return get_movie_by_id(db, movie_id) | ||
|
||
|
||
@router.delete("/movies/{movie_id}/", status_code=204) | ||
def delete_movie_by_id(movie_id: int, db: Session = Depends(get_db)): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using |
||
delete_movie(db, movie_id) | ||
return {} | ||
|
||
Comment on lines
+100
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that the delete operation returns a status code of 204 explicitly to indicate successful deletion. |
||
|
||
@router.patch("/movies/{movie_id}/") | ||
def update_movie_by_id(movie_id: int, updated_data: UpdateMovieRequest, db: Session = Depends(get_db)): | ||
update_data_dict = updated_data.model_dump(exclude_unset=True) | ||
update_movie(db, movie_id, update_data_dict) | ||
return {"detail": "Movie updated successfully."} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,96 @@ | ||
# Write your code here | ||
from datetime import date, timedelta | ||
from typing import Optional | ||
|
||
from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator | ||
|
||
from database.models import MovieStatusEnum | ||
|
||
|
||
class MovieSchema(BaseModel): | ||
id: int | ||
name: str | ||
date: date | ||
score: float | ||
overview: str | ||
|
||
class Config: | ||
from_attributes = True | ||
|
||
|
||
class PaginatedMovieResponseSchema(BaseModel): | ||
movies: list[MovieSchema] | ||
prev_page: Optional[str] | ||
next_page: Optional[str] | ||
total_pages: int | ||
total_items: int | ||
|
||
|
||
class CreateMovieSchema(BaseModel): | ||
name: str = Field(max_length=255) | ||
date: date | ||
score: float = Field(ge=0, le=100) | ||
overview: str | ||
status: MovieStatusEnum | ||
budget: float = Field(ge=0) | ||
revenue: float = Field(ge=0) | ||
country: str | ||
genres: list[str] | ||
actors: list[str] | ||
languages: list[str] | ||
|
||
|
||
class BaseSchema(BaseModel): | ||
id: int | ||
name: str | ||
|
||
class Config: | ||
from_attributes = True | ||
|
||
|
||
class CountrySchema(BaseSchema): | ||
code: str | ||
name: Optional[str] | ||
|
||
|
||
class GenreSchema(BaseSchema): | ||
pass | ||
|
||
|
||
class ActorSchema(BaseSchema): | ||
pass | ||
|
||
|
||
class LanguageSchema(BaseSchema): | ||
pass | ||
|
||
|
||
class CreateResponseMovieSchema(BaseModel): | ||
id: int | ||
name: str | ||
date: date | ||
score: float | ||
overview: str | ||
status: MovieStatusEnum | ||
budget: float | ||
revenue: float | ||
country: CountrySchema | ||
genres: list[GenreSchema] | ||
actors: list[ActorSchema] | ||
languages: list[LanguageSchema] | ||
|
||
class Config: | ||
from_attributes = True | ||
|
||
|
||
class MovieDetailSchema(CreateResponseMovieSchema): | ||
pass | ||
|
||
|
||
class UpdateMovieRequest(BaseModel): | ||
name: str = None | ||
date: str = None | ||
score: float = None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider changing the type of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change the type of the |
||
overview: str = None | ||
status: str = None | ||
budget: float = None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change the type of the |
||
revenue: float = None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
from typing import Annotated, Optional | ||
|
||
from fastapi import Depends, Query, HTTPException | ||
from pydantic import ValidationError | ||
from sqlalchemy.orm import Session | ||
|
||
from database import get_db | ||
from database.models import MovieModel, CountryModel, GenreModel, ActorModel, LanguageModel | ||
from schemas.movies import ( | ||
MovieSchema, | ||
CreateMovieSchema, | ||
MovieDetailSchema, | ||
) | ||
|
||
|
||
def get_movies( | ||
db: Session = Depends(get_db), | ||
offset: Optional[int] = None, | ||
limit: Optional[int] = None | ||
): | ||
query = db.query(MovieModel).order_by(MovieModel.id.desc()) | ||
|
||
if offset: | ||
query = query.offset(offset) | ||
if limit: | ||
query = query.limit(limit) | ||
|
||
return query.all() | ||
|
||
|
||
def create_movie( | ||
movie: CreateMovieSchema, | ||
db: Session = Depends(get_db) | ||
): | ||
try: | ||
country = get_or_create_country(movie.country, db) | ||
|
||
genres = [get_or_create_genre(genre, db) for genre in movie.genres] | ||
actors = [get_or_create_actor(actor, db) for actor in movie.actors] | ||
languages = [get_or_create_language(language, db) for language in movie.languages] | ||
|
||
new_movie = MovieModel( | ||
name=movie.name, | ||
date=movie.date, | ||
score=movie.score, | ||
overview=movie.overview, | ||
status=movie.status, | ||
budget=movie.budget, | ||
revenue=movie.revenue, | ||
country=country, | ||
genres=genres, | ||
actors=actors, | ||
languages=languages | ||
) | ||
|
||
db.add(new_movie) | ||
db.commit() | ||
db.refresh(new_movie) | ||
|
||
return new_movie | ||
except Exception as e: | ||
db.rollback() | ||
raise e | ||
Comment on lines
+61
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider logging the exception There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Raising the exception without additional context might make it harder to diagnose issues. Consider wrapping the exception in a custom error message or logging it for better traceability.
Comment on lines
+61
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider catching more specific exceptions related to database operations instead of a generic |
||
|
||
|
||
def get_or_create_country(country_code: str, db: Session): | ||
country = db.query(CountryModel).filter_by(code=country_code).first() | ||
if not country: | ||
country = CountryModel(code=country_code) | ||
db.add(country) | ||
db.flush() | ||
return country | ||
|
||
|
||
def get_or_create_genre(genre_name: str, db: Session): | ||
genre = db.query(GenreModel).filter_by(name=genre_name).first() | ||
if not genre: | ||
genre = GenreModel(name=genre_name) | ||
db.add(genre) | ||
db.flush() | ||
return genre | ||
|
||
|
||
def get_or_create_actor(actor_name: str, db: Session): | ||
actor = db.query(ActorModel).filter_by(name=actor_name).first() | ||
if not actor: | ||
actor = ActorModel(name=actor_name) | ||
db.add(actor) | ||
db.flush() | ||
return actor | ||
|
||
|
||
def get_or_create_language(language_name: str, db: Session): | ||
language = db.query(LanguageModel).filter_by(name=language_name).first() | ||
if not language: | ||
language = LanguageModel(name=language_name) | ||
db.add(language) | ||
db.flush() | ||
return language | ||
|
||
|
||
def get_movie_by_id(db: Session, movie_id: int) -> MovieModel | None: | ||
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first() | ||
|
||
if movie is None: | ||
raise HTTPException( | ||
status_code=404, | ||
detail="Movie with the given ID was not found." | ||
) | ||
return movie | ||
|
||
|
||
def delete_movie(db: Session, movie_id: int) -> None: | ||
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first() | ||
|
||
if movie is None: | ||
raise HTTPException( | ||
status_code=404, | ||
detail="Movie with the given ID was not found." | ||
) | ||
|
||
db.delete(movie) | ||
db.commit() | ||
|
||
|
||
def update_movie(db: Session, movie_id: int, updated_data: dict) -> None: | ||
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first() | ||
|
||
if movie is None: | ||
raise HTTPException( | ||
status_code=404, | ||
detail="Movie with the given ID was not found." | ||
) | ||
|
||
for key, value in updated_data.items(): | ||
if hasattr(movie, key): | ||
setattr(movie, key, value) | ||
|
||
db.commit() |
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
status
module is used here but not imported. Consider addingfrom fastapi import status
to the import statements.