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 #27

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ ignore = E203, E266, W503, ANN002, ANN003, ANN101, ANN102, ANN401, N807, N818, V
max-line-length = 119
max-complexity = 18
select = B,C,E,F,W,T4,B9,ANN,Q0,N8,VNE
exclude = .venv
exclude = .venv, src/database/migrations
extend-exclude = src/tests/*
Empty file added src/crud/__init__.py
Empty file.
81 changes: 81 additions & 0 deletions src/crud/movies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from datetime import datetime

from sqlalchemy.orm import Session

from database.models import (ActorModel, CountryModel, GenreModel,
LanguageModel, MovieModel)
from schemas.movies import MovieCreateRequest


def get_movies_pagination(offset: int, per_page: int, db: Session) -> tuple[list[type[MovieModel]], int]:
movies = db.query(MovieModel).order_by(MovieModel.id.desc()).offset(offset).limit(per_page).all()
movies_count = db.query(MovieModel).count()
return movies, movies_count


def get_movie_by_id(db: Session, movie_id: int) -> MovieModel | None:
return db.query(MovieModel).filter(MovieModel.id == movie_id).first()


def get_create(name: str, date: datetime.date, db: Session) -> MovieModel | None:
return db.query(MovieModel).filter(MovieModel.name == name, MovieModel.date == date).first()
Comment on lines +20 to +21

Choose a reason for hiding this comment

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

The function name get_create is misleading. It suggests both retrieval and creation, but it only retrieves a movie. Consider renaming it to something like get_movie_by_name_and_date for clarity.



def crud_create_movie(movie: MovieCreateRequest, db: Session) -> MovieModel:
country = db.query(CountryModel).filter(CountryModel.code == movie.country).first()
if not country:
country = CountryModel(code=movie.country)
db.add(country)
db.commit()
db.refresh(country)

genres = []
for genre_name in movie.genres:
genre = db.query(GenreModel).filter(GenreModel.name == genre_name).first()
if not genre:
genre = GenreModel(name=genre_name)
db.add(genre)
db.commit()
db.refresh(genre)
Comment on lines +38 to +39

Choose a reason for hiding this comment

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

Committing within a loop can be inefficient. Consider batching these operations and committing once after the loop to reduce the number of database transactions.

genres.append(genre)

actors = []
for actor_name in movie.actors:
actor = db.query(ActorModel).filter(ActorModel.name == actor_name).first()
if not actor:
actor = ActorModel(name=actor_name)
db.add(actor)
db.commit()
db.refresh(actor)
Comment on lines +48 to +49

Choose a reason for hiding this comment

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

Similar to the genres loop, committing within the actors loop can be optimized by batching the operations and committing once after the loop.

actors.append(actor)

languages = []
for language_name in movie.languages:
language = db.query(LanguageModel).filter(LanguageModel.name == language_name).first()
if not language:
language = LanguageModel(name=language_name)
db.add(language)
db.commit()
db.refresh(language)
Comment on lines +58 to +59

Choose a reason for hiding this comment

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

As with the previous loops, consider batching the language operations and committing once after the loop to improve efficiency.

languages.append(language)

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_id=country.id,
)
db.add(new_movie)
db.commit()
db.refresh(new_movie)

new_movie.genres = genres
new_movie.actors = actors
new_movie.languages = languages
db.commit()

return new_movie
19 changes: 7 additions & 12 deletions src/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import os

from database.models import (
Base,
MovieModel
)
from database.models import Base, MovieModel
from database.session_sqlite import reset_sqlite_database as reset_database

environment = os.getenv("ENVIRONMENT", "developing")

if environment == "testing":
from database.session_sqlite import (
get_sqlite_db_contextmanager as get_db_contextmanager,
get_sqlite_db as get_db
)
from database.session_sqlite import get_sqlite_db as get_db
from database.session_sqlite import \
get_sqlite_db_contextmanager as get_db_contextmanager
else:
from database.session_postgresql import (
get_postgresql_db_contextmanager as get_db_contextmanager,
get_postgresql_db as get_db
)
from database.session_postgresql import get_postgresql_db as get_db
from database.session_postgresql import \
get_postgresql_db_contextmanager as get_db_contextmanager
3 changes: 1 addition & 2 deletions src/database/migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from alembic import context

from database import models # noqa: F401
from database import models # noqa: F401
from database.models import Base
from database.session_postgresql import postgresql_engine


# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = 'ea3a65568bd9'
Expand Down
5 changes: 3 additions & 2 deletions src/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from enum import Enum
from typing import Optional

from sqlalchemy import String, Float, Text, DECIMAL, UniqueConstraint, Date, ForeignKey, Table, Column
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped, relationship
from sqlalchemy import DECIMAL, Column, Date
from sqlalchemy import Enum as SQLAlchemyEnum
from sqlalchemy import Float, ForeignKey, String, Table, Text, UniqueConstraint
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


class Base(DeclarativeBase):
Expand Down
8 changes: 5 additions & 3 deletions src/database/populate.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import pandas as pd
from sqlalchemy import insert
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from tqdm import tqdm

from config import get_settings
from database import MovieModel, get_db_contextmanager
from database.models import CountryModel, GenreModel, ActorModel, MoviesGenresModel, ActorsMoviesModel, LanguageModel, \
MoviesLanguagesModel
from database.models import (ActorModel, ActorsMoviesModel, CountryModel,
GenreModel, LanguageModel, MoviesGenresModel,
MoviesLanguagesModel)


class CSVDatabaseSeeder:
Expand Down Expand Up @@ -139,6 +140,7 @@ def seed(self):
print(f"Unexpected error: {e}")
raise


def main():
settings = get_settings()
with get_db_contextmanager() as db_session:
Expand Down
2 changes: 1 addition & 1 deletion src/database/session_postgresql.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.orm import Session, sessionmaker

from config import get_settings

Expand Down
2 changes: 1 addition & 1 deletion src/database/session_sqlite.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.orm import Session, sessionmaker

from config import get_settings
from database import Base
Expand Down
132 changes: 128 additions & 4 deletions src/routes/movies.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,136 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, joinedload
from starlette.requests import Request

from crud.movies import (crud_create_movie, get_create, get_movie_by_id,
get_movies_pagination)
from database import get_db
from database.models import MovieModel, CountryModel, GenreModel, ActorModel, LanguageModel

from database.models import (ActorModel, CountryModel, GenreModel,
LanguageModel, MovieModel)
from schemas.movies import (ActorResponse, CountryResponse, GenreResponse,
LanguageResponse, MovieCreateRequest,
MovieCreateResponse, MovieDetailResponse,
MovieListResponse, MovieSchema, MovieUpdateRequest)

router = APIRouter()


# Write your code here
@router.get("/movies/", response_model=MovieListResponse)
def get_movies(
page: int = Query(1, ge=1, description="Page number"),
per_page: int = Query(10, ge=1, le=20, description="Items per page"),
db: Session = Depends(get_db),
):
total_items = db.query(MovieModel).count()

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

total_pages = (total_items + per_page - 1) // per_page

if page > total_pages:
raise HTTPException(status_code=404, detail="Page not found.")

offset = (page - 1) * per_page
movies, total_items = get_movies_pagination(offset, per_page, db)

movies_list = [
MovieSchema(
id=movie.id,
name=movie.name,
date=str(movie.date),
score=movie.score,
overview=movie.overview
)
for movie in movies
]

base_url = "/theater/movies/"
prev_page = f"{base_url}?page={page - 1}&per_page={per_page}" if page > 1 else None
next_page = f"{base_url}?page={page + 1}&per_page={per_page}" if page < total_pages else None

return MovieListResponse(
movies=movies_list,
prev_page=prev_page,
next_page=next_page,
total_pages=total_pages,
total_items=total_items
)


@router.post("/movies/", response_model=MovieCreateResponse, status_code=201)
def create_movie(movie: MovieCreateRequest, db: Session = Depends(get_db)):
existing_movie = get_create(movie.name, movie.date, db)

Choose a reason for hiding this comment

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

The function get_create is misleadingly named as it only retrieves a movie. Consider renaming it to get_movie_by_name_and_date for clarity.

if existing_movie:
raise HTTPException(
status_code=409,
detail=f"A movie with the name '{movie.name}' and release date '{movie.date}' already exists."
)

try:
new_movie = crud_create_movie(movie, db)
return MovieCreateResponse.model_validate(new_movie)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid input data."
)


@router.get("/movies/{movie_id}/", response_model=MovieDetailResponse)
def get_movie_details(movie_id: int, db: Session = Depends(get_db)):
movie_details = get_movie_by_id(db, movie_id)
if not movie_details:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Movie with the given ID was not found."
)
return movie_details


@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=status.HTTP_404_NOT_FOUND,
detail="Movie with the given ID was not found."
)

db.delete(movie)
db.commit()
return None


@router.patch("/movies/{movie_id}/")
def update_movie(movie_id: int, movie_data: MovieUpdateRequest, db: Session = Depends(get_db)):
movie = db.query(MovieModel).filter(MovieModel.id == movie_id).first()

if not movie:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Movie with the given ID was not found."
)

if movie_data.name:
movie.name = movie_data.name
if movie_data.date:
movie.date = movie_data.date
if movie_data.score is not None:
movie.score = movie_data.score
if movie_data.overview:
movie.overview = movie_data.overview
if movie_data.status:
movie.status = movie_data.status
if movie_data.budget is not None:
movie.budget = movie_data.budget
if movie_data.revenue is not None:
movie.revenue = movie_data.revenue

db.commit()

return {"detail": "Movie updated successfully."}
Loading
Loading