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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
67 changes: 67 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
annotated-types==0.7.0
anyio==4.7.0
build==1.2.2.post1
CacheControl==0.14.2
certifi==2024.12.14
charset-normalizer==3.4.1
cleo==2.1.0
click==8.1.8
colorama==0.4.6
crashtest==0.4.1
distlib==0.3.9
dulwich==0.22.7
fastapi==0.115.6
fastjsonschema==2.21.1
filelock==3.16.1
flake8==7.1.1
greenlet==3.1.1
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
idna==3.10
iniconfig==2.0.0
installer==0.7.0
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
keyring==25.6.0
mccabe==0.7.0
more-itertools==10.5.0
msgpack==1.1.0
numpy==2.2.1
packaging==24.2
pandas==2.2.3
pkginfo==1.12.0
platformdirs==4.3.6
pluggy==1.5.0
poetry==2.0.1
poetry-core==2.0.1
pycodestyle==2.12.1
pydantic==2.10.4
pydantic-settings==2.7.0
pydantic_core==2.27.2
pyflakes==3.2.0
pyproject_hooks==1.2.0
pytest==8.3.4
pytest-env==1.1.5
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
pytz==2024.2
pywin32-ctypes==0.2.3
RapidFuzz==3.11.0
requests==2.32.3
requests-toolbelt==1.0.0
shellingham==1.5.4
six==1.17.0
sniffio==1.3.1
SQLAlchemy==2.0.36
-e git+https://github.com/TsvetanKichuk/py-fastapi-homework-1-task.git@2ae84ac4ba422cd86910b73070391e7184667237#egg=src
starlette==0.41.3
tomlkit==0.13.2
tqdm==4.67.1
trove-classifiers==2025.1.10.15
typing_extensions==4.12.2
tzdata==2024.2
urllib3==2.3.0
uvicorn==0.34.0
virtualenv==20.28.1
Empty file added src/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion src/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from config.settings import get_settings
from src.config.settings import get_settings
4 changes: 2 additions & 2 deletions src/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from database.models import (
from src.database.models import (
Base,
MovieModel
)
from database.session import (
from src.database.session import (
get_db_contextmanager,
get_db,
reset_sqlite_database
Expand Down
22 changes: 22 additions & 0 deletions src/database/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import datetime
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from contextlib import contextmanager

from sqlalchemy import String, Float, Text, DECIMAL, UniqueConstraint, Date
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped

DATABASE_URL = "sqlite:///movies.db"
engine = create_engine(DATABASE_URL)
Session = sessionmaker(bind=engine)

class Base(DeclarativeBase):
pass
Expand Down Expand Up @@ -31,3 +37,19 @@ class MovieModel(Base):

def __repr__(self):
return f"<Movie(name='{self.name}', release_date='{self.date}', score={self.score})>"

@contextmanager
def get_db_contextmanager(self):
"""
Context manager for database sessions.
Automatically handles session commit/rollback and closing.
"""
session = Session()
try:
yield session
session.commit()
except Exception as e:
session.rollback()
raise e
finally:
session.close()
7 changes: 4 additions & 3 deletions src/database/populate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from sqlalchemy.exc import SQLAlchemyError
from tqdm import tqdm

from config import get_settings
from database import MovieModel, get_db_contextmanager
from src.config.settings import get_settings
from src.database.models import MovieModel, get_db_contextmanager
from src.database.session import DATABASE_URL


class CSVDatabaseSeeder:
Expand Down Expand Up @@ -65,7 +66,7 @@ def seed(self):

def main():
settings = get_settings()
with get_db_contextmanager() as db_session:
with get_db_contextmanager(DATABASE_URL) as db_session:

Choose a reason for hiding this comment

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

The get_db_contextmanager function is being called with DATABASE_URL, but according to its definition, it does not accept any parameters. You should remove DATABASE_URL from the call to fix this issue.

seeder = CSVDatabaseSeeder(settings.PATH_TO_MOVIES_CSV, db_session)

if not seeder.is_db_populated():
Expand Down
4 changes: 2 additions & 2 deletions src/database/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

from config import get_settings
from database import Base
from src.config import get_settings
from src.database import Base

settings = get_settings()

Expand Down
35 changes: 34 additions & 1 deletion src/routes/movies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,41 @@

from database import get_db, MovieModel

Choose a reason for hiding this comment

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

The import statement for get_db and MovieModel should be prefixed with src. to match the project structure. Change from database import get_db, MovieModel to from src.database import get_db, MovieModel.


from src.schemas.movies import MovieListResponseSchema, MovieDetailResponseSchema

router = APIRouter()


# Write your code here

@router.get("/movies/", response_model=MovieListResponseSchema)
def get_movies(
page: int = Query(default=1, ge=1, description="The page number(>= 1)"),
per_page: int = Query(default=10, ge=1, le=20, description="Film quantity at the page (>= 1 и <= 20)"),
db: Session = Depends(get_db)
):
total_items = db.query(MovieModel).count()
if total_items == 0:
raise HTTPException(status_code=404, detail="No movies found.")
offset = (page - 1) * per_page
movies = db.query(MovieModel).offset(offset).limit(per_page).all()
base_url = "/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 offset + per_page < total_items else None
if page is None or per_page is None:
raise HTTPException(status_code=422, detail="Ensure this value is greater than or equal to 1")
return {
"movies": movies,
"prev_page": prev_page,
"next_page": next_page,
"total_pages": (total_items + per_page - 1) // per_page,
"total_items": total_items,
}


@router.get("/movies/{movie_id}/", response_model=MovieDetailResponseSchema)
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
32 changes: 31 additions & 1 deletion src/schemas/movies.py
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
# Write your code here
import datetime

from pydantic import BaseModel
from typing import List, Optional


class MovieDetailResponseSchema(BaseModel):
id: int
name: str
date: datetime.date
score: Optional[float]
genre: Optional[str]
overview: Optional[str]
crew: Optional[str]
orig_title: Optional[str]
status: Optional[str]
orig_lang: Optional[str]
budget: Optional[int]
revenue: Optional[float]
country: Optional[str]

class Config:
from_attributes: True


class MovieListResponseSchema(BaseModel):
movies: List[MovieDetailResponseSchema]
prev_page: Optional[str]
next_page: Optional[str]
total_pages: int
total_items: int
8 changes: 4 additions & 4 deletions src/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import pytest
from fastapi.testclient import TestClient

from config import get_settings
from database import (
from src.config import get_settings
from src.database import (
reset_sqlite_database,
get_db_contextmanager,
)
from database.populate import CSVDatabaseSeeder
from main import app
from src.database.populate import CSVDatabaseSeeder
from src.main import app


@pytest.fixture(scope="function", autouse=True)
Expand Down
2 changes: 1 addition & 1 deletion src/tests/test_integration/test_movies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from database import MovieModel
from src.database import MovieModel


def test_get_movies_empty_database(client):
Expand Down
Empty file added src/uvicorn
Empty file.
Loading