Skip to content
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

Develop #4

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pytest-env = "^1.1.5"
flake8 = "^7.1.1"
psycopg2-binary = "^2.9.10"
alembic = "^1.14.0"
pydantic-extra-types = "^2.10.2"
pycountry = "^24.6.1"


[build-system]
Expand Down
188 changes: 184 additions & 4 deletions src/routes/movies.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,192 @@
from fastapi import APIRouter, Depends, HTTPException, Query
import math
from typing import Union
import logging
from fastapi import (
APIRouter,
Depends,
HTTPException,
Query,
status
)
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, joinedload
from sqlalchemy.orm import Session

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 (
MovieListSchema,
MovieCreateSchema,
MovieUpdateSchema,
MovieDetailSchema,
)


router = APIRouter()


# Write your code here
def handle_movie_dependencies(
elements: list[str],
model: Union[GenreModel, ActorModel, LanguageModel],
db: Session = Depends(get_db)
):

not_created_elements = []
created_elements = []
for element_name in elements:
element = db.query(model).filter(model.name == element_name).first()
if not element:
element = model(
name=element_name
)
not_created_elements.append(element)
else:
created_elements.append(element)

return not_created_elements, created_elements


@router.get("/movies/")
def get_movies(
page: int = Query(ge=1, default=1),
per_page: int = Query(ge=1, le=20, default=10),
db: Session = Depends(get_db)
):
total_items = db.query(MovieModel).count()
total_pages = math.ceil(total_items / per_page)

first_element = (page - 1) * per_page

movies = db.query(MovieModel).order_by(MovieModel.id.desc()).offset(first_element).limit(per_page).all()

if not movies:
raise HTTPException(status_code=404, detail="No movies found.")

prev_page = f"/theater/movies/?page={page - 1}&per_page={per_page}"
next_page = f"/theater/movies/?page={page + 1}&per_page={per_page}"

Comment on lines +70 to +71

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pagination URLs should match the route defined for movies. Change /theater/movies/ to /movies/ to ensure consistency and correctness.

Comment on lines +70 to +71

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pagination URLs should be updated to /movies/ instead of /theater/movies/ to match the defined route and ensure consistency.

return {
"movies": [MovieListSchema.model_validate(movie) for movie in movies],
"prev_page": prev_page if page > 1 else None,
"next_page": next_page if page < total_pages else None,
"total_pages": total_pages,
"total_items": total_items
}


@router.post("/movies/", response_model=MovieDetailSchema, status_code=status.HTTP_201_CREATED)
def create_movie(movie: MovieCreateSchema, db: Session = Depends(get_db)):

try:
with db.begin():
exciting_movie = db.query(MovieModel).filter(
MovieModel.name == movie.name,
MovieModel.date == movie.date
).first()
if exciting_movie:
raise HTTPException(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo here: exciting_movie should be existing_movie to correctly convey the meaning of checking if a movie already exists.

status_code=status.HTTP_409_CONFLICT,
detail=f"A movie with the name '{movie.name}' and "
f"release date '{movie.date}' already exists."
)

country = db.query(CountryModel).filter(
CountryModel.code == movie.country
).first()
if not country:
country = CountryModel(code=movie.country, name=None)
db.add(country)
db.flush()

not_created_genres, created_genres = handle_movie_dependencies(movie.genres, GenreModel, db)
db.add_all(not_created_genres)
all_genres = not_created_genres + created_genres
db.flush()

not_created_actors, created_actors = handle_movie_dependencies(movie.actors, ActorModel, db)
db.add_all(not_created_actors)
all_actors = not_created_actors + created_actors
db.flush()

not_created_languages, created_languages = handle_movie_dependencies(movie.languages, LanguageModel, db)
db.add_all(not_created_languages)
all_languages = not_created_languages + created_languages
db.flush()

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,
)
db.add(new_movie)
db.flush()

new_movie.genres.extend(all_genres)
new_movie.actors.extend(all_actors)
new_movie.languages.extend(all_languages)

return new_movie

except IntegrityError as exc:
db.rollback()
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(exc)
)


@router.get("/movies/{movie_id}/", response_model=MovieDetailSchema)
def get_movie_by_id(movie_id: int, db: Session = Depends(get_db)):
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first()
if not movie:
raise HTTPException(
status_code=404,
detail="Movie with the given ID was not found."
)
return movie


@router.delete("/movies/{movie_id}/", status_code=status.HTTP_204_NO_CONTENT)
def delete_movie(movie_id: int, db: Session = Depends(get_db)):
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first()
if not movie:
raise HTTPException(
status_code=404,
detail="Movie with the given ID was not found."
)
db.delete(movie)
db.commit()
return None


@router.patch("/movies/{movie_id}/", status_code=status.HTTP_200_OK)
def update_movie(movie_id: int, movie_data: MovieUpdateSchema, db: Session = Depends(get_db)):
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first()
logging.info(movie_data)
if not movie:
raise HTTPException(
status_code=404,
detail="Movie with the given ID was not found."
)

movie.name = movie_data.name if movie_data.name else movie.name
movie.date = movie_data.date if movie_data.date else movie.date
movie.score = movie_data.score if movie_data.score else movie.score
movie.overview = movie_data.overview if movie_data.overview else movie.overview
movie.status = movie_data.status if movie_data.status else movie.status
movie.budget = movie_data.budget if movie_data.budget else movie.budget
movie.revenue = movie_data.revenue if movie_data.revenue else movie.revenue

db.commit()

return {"detail": "Movie updated successfully."}
101 changes: 100 additions & 1 deletion src/schemas/movies.py
Original file line number Diff line number Diff line change
@@ -1 +1,100 @@
# Write your code here
from datetime import date, timedelta
from enum import Enum
from typing import Optional

from pydantic import (
BaseModel,
Field,
field_validator,
ValidationError
)
from pydantic_extra_types.country import CountryAlpha3, CountryAlpha2


class CountrySchema(BaseModel):
id: int
code: CountryAlpha3 | CountryAlpha2
name: str | None


class GenreSchema(BaseModel):
id: int
name: str


class ActorSchema(BaseModel):
id: int
name: str


class LanguageSchema(BaseModel):
id: int
name: str


class StatusEnum(str, Enum):
Released = "Released"
PostProduction = "Post Production"
InProduction = "In Production"


class MovieCreateSchema(BaseModel):
name: str = Field(max_length=255)
date: date
score: float = Field(ge=0, le=100)
overview: str
status: StatusEnum
budget: float = Field(gt=0)
revenue: float = Field(gt=0)
country: CountryAlpha3 | CountryAlpha2
genres: list[str]
actors: list[str]
languages: list[str]

@field_validator("date")
@classmethod
def check_date_on_the_future(cls, date: date):
if date > date.today() + timedelta(days=365):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter date in the check_date_on_the_future method shadows the date module imported from datetime. Consider renaming this parameter to avoid conflicts, such as movie_date.

raise ValidationError(
"The date must not be more than one year in the future."
)
return date


class MovieDetailSchema(BaseModel):
id: int
name: str
date: date
score: float = Field(ge=0, le=100)
overview: str
status: StatusEnum
budget: float = Field(gt=0)
revenue: float = Field(gt=0)
country: CountrySchema
genres: list[GenreSchema]
actors: list[ActorSchema]
languages: list[LanguageSchema]

class Config:
from_attributes = True


class MovieUpdateSchema(BaseModel):
name: Optional[str] = None
date: Optional[date] = None
score: Optional[float] = Field(None, ge=0, le=100)
overview: Optional[str] = None
status: Optional[StatusEnum] = None
budget: Optional[float] = Field(None, gt=0)
revenue: Optional[float] = Field(None, gt=0)


class MovieListSchema(BaseModel):
id: int
name: str
date: date
score: float
overview: str

class Config:
from_attributes = True
Loading