From 7146c72377c0181d72e981c9f11a46f12184787b Mon Sep 17 00:00:00 2001 From: Lev Date: Tue, 4 Feb 2025 19:05:11 +0200 Subject: [PATCH 1/4] Add CRUD endpoints for movies with pagination and validation Implemented CRUD operations for managing movies, including creation, reading, updating, and deletion. Added pagination to the movie listing endpoint and incorporated robust validation with Pydantic schemas. Updated dependencies in `pyproject.toml` to support new features like extra types and country validation. --- pyproject.toml | 2 + src/routes/movies.py | 217 +++++++++++++++++++++++++++++++++++++++++- src/schemas/movies.py | 109 ++++++++++++++++++++- 3 files changed, 322 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c6cad64..451b97c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,8 @@ fastapi = "^0.115.6" sqlalchemy = "^2.0.36" pytest = "^8.3.4" pydantic-settings = "^2.7.0" +pydantic-extra-types = "^2.10.2" +pycountry = "^24.6.1" pandas = "^2.2.3" tqdm = "^4.67.1" uvicorn = "^0.34.0" diff --git a/src/routes/movies.py b/src/routes/movies.py index e44678a..3a01bca 100644 --- a/src/routes/movies.py +++ b/src/routes/movies.py @@ -1,12 +1,219 @@ -from fastapi import APIRouter, Depends, HTTPException, Query +import logging +from fastapi import ( + APIRouter, + Depends, + HTTPException, + Query, + status, + Request +) from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session, joinedload +from typing import Annotated, Optional +from src.database.models import ( + MovieModel, + CountryModel, + GenreModel, + ActorModel, + LanguageModel +) +from src.database import get_db +from src.schemas.movies import ( + MovieListResponseSchema, + MovieDetailResponseSchema, + MovieCreateResponseSchema, + MovieUpdateResponseSchema, + MovieListItemResponseSchema +) -from database import get_db -from database.models import MovieModel, CountryModel, GenreModel, ActorModel, LanguageModel +router = APIRouter() -router = APIRouter() +@router.get("/movies/",) +def get_movies( + request: Request, + page: Annotated[int, Query(ge=1)] = 1, + per_page: Annotated[int, Query(ge=1, le=20)] = 10, + db: Session = Depends(get_db), +): + start = (page - 1) * per_page + films = (db.query(MovieModel).order_by(MovieModel.id.desc()).offset(start).limit(per_page).all()) + total_items = db.query(MovieModel).count() + total_pages = (total_items + per_page - 1) // per_page + + prev_page = None + if page > 1: + prev_page = f"/theater/movies/?page={page - 1}&per_page={per_page}" + + next_page = None + if page < total_pages: + next_page = f"/theater/movies/?page={page + 1}&per_page={per_page}" + + if not films: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No movies found.") + + return { + "movies": [MovieListItemResponseSchema.model_validate(film) for film in films], + "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.get("/movies/{movie_id}/", response_model=MovieDetailResponseSchema) +def get_movie( + request: Request, + movie_id: int, + db: Session = Depends(get_db), +) -> MovieDetailResponseSchema: + film = db.query(MovieModel).filter(MovieModel.id == movie_id).first() + if not film: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Movie with the given ID was not found.") + return film + + +@router.post( + "/movies/", + response_model=MovieCreateResponseSchema, + status_code=status.HTTP_201_CREATED +) +def create_movie( + movie_data: MovieCreateResponseSchema, + db: Session = Depends(get_db) +): + with db.begin(): + existing_movie = db.query(MovieModel).filter( + MovieModel.name == movie_data.name, + MovieModel.date == movie_data.date + ).first() + if existing_movie: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"A movie with the name '{movie_data.name}' " + f"and release date '{movie_data.date}' already exists." + ) + + country = db.query(CountryModel).filter( + CountryModel.code == movie_data.country + ).first() + if not country: + country = CountryModel(code=movie_data.country, name="Unknown Country") + db.add(country) + db.flush() + + if not movie_data.name or not movie_data.date or not movie_data.overview or not movie_data.status: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Fields 'name', 'date', 'overview' and 'status' is requir to create movie." + ) + + new_movie = MovieModel( + name=movie_data.name, + date=movie_data.date, + score=movie_data.score, + overview=movie_data.overview, + status=movie_data.status, + budget=movie_data.budget, + revenue=movie_data.revenue, + country_id=country.id + ) + + genres_names = movie_data.genres + if genres_names: + genres = [] + for genre_name in genres_names: + 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) + genres.append(genre) + new_movie.genres = genres + + actors_names = movie_data.actors + if actors_names: + actors = [] + for actor_name in actors_names: + 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) + actors.append(actor) + new_movie.actors = actors + + languages_names = movie_data.languages + if languages_names: + languages = [] + for language_name in languages_names: + 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) + languages.append(language) + new_movie.languages = languages + + db.add(new_movie) + db.commit() + db.refresh(new_movie) + + return MovieCreateResponseSchema( + id=new_movie.id, + name=new_movie.name, + date=new_movie.date, + score=new_movie.score, + overview=new_movie.overview, + status=new_movie.status, + budget=new_movie.budget, + revenue=new_movie.revenue, + country=country.code, + genres=[genre.name for genre in new_movie.genres], + actors=[actor.name for actor in new_movie.actors], + languages=[language.name for language in new_movie.languages], + ) + + +@router.patch("/movies/{movie_id}/", response_model=MovieUpdateResponseSchema) +def update_movie( + movie_id: int, + movie_data: MovieUpdateResponseSchema, + 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." + ) + + 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."} -# Write your code here +@router.delete("/movies/{movie_id}/", status_code=status.HTTP_204_NO_CONTENT) +def delete_movie( + request: Request, + movie_id: int, + db: Session = Depends(get_db), +) -> None: + film = db.query(MovieModel).filter(MovieModel.id == movie_id).first() + if not film: + raise HTTPException(status_code=404, detail="Movie with the given ID was not found.") + db.delete(film) + db.commit() + return None diff --git a/src/schemas/movies.py b/src/schemas/movies.py index fabb9be..a92f226 100644 --- a/src/schemas/movies.py +++ b/src/schemas/movies.py @@ -1 +1,108 @@ -# Write your code here +from datetime import date, timedelta +from typing import Optional +from enum import Enum +from pydantic import BaseModel, ConfigDict, constr, Field, field_validator +from pydantic_extra_types.country import CountryAlpha2 + + +class MoviesStatusEnumSchema(str, Enum): + RELEASED = "Released" + POST_PRODUCTION = "Post Production" + IN_PRODUCTION = "In Production" + + +class GenreResponseSchema(BaseModel): + id: int + name: str + model_config = {"from_attributes": True} + + +class ActorResponseSchema(BaseModel): + id: int + name: str + model_config = {"from_attributes": True} + + +class CountryResponseSchema(BaseModel): + id: int + code: CountryAlpha2 + name: Optional[str] + model_config = {"from_attributes": True} + + +class LanguageResponseSchema(BaseModel): + id: int + name: str + model_config = {"from_attributes": True} + + +class MovieCreateResponseSchema(BaseModel): + name: str = Field(max_length=255) + date: date + score: float = Field(ge=0, le=100) + overview: str + status: MoviesStatusEnumSchema + budget: float = Field(gt=0) + revenue: float = Field(gt=0) + country: CountryAlpha2 + genres: list[str] + actors: list[str] + languages: list[str] + + @field_validator("date") + def validate_date_no_more_one_year(cls, movie_date: str | date): + if isinstance(movie_date, str): + try: + movie_date = date.fromisoformat(movie_date) + except ValueError: + raise ValueError(f"Invalid date format: {movie_date}") + if movie_date > date.today() + timedelta(days=365): + raise ValueError("The date must not be more than one year in the future.") + return movie_date + + +class MovieDetailResponseSchema(BaseModel): + id: int + name: str = Field(max_length=255) + date: date + score: float = Field(ge=0, le=100) + overview: str + status: MoviesStatusEnumSchema + budget: float = Field(ge=0) + revenue: float = Field(ge=0) + country: CountryResponseSchema + genres: list[GenreResponseSchema] + actors: list[ActorResponseSchema] + languages: list[LanguageResponseSchema] + + model_config = {"from_attributes": True} + + +class MovieUpdateResponseSchema(BaseModel): + name: Optional[str] = None + date: Optional[date] = None + score: Optional[float] = Field(None, ge=0, le=100) + overview: Optional[str] = None + status: Optional[MoviesStatusEnumSchema] = None + budget: Optional[float] = Field(None, gt=0) + revenue: Optional[float] = Field(None, gt=0) + + +class MovieListItemResponseSchema(BaseModel): + id: int + name: str + date: date + score: float + overview: str + + model_config = {"from_attributes": True} + + +class MovieListResponseSchema(BaseModel): + movies: list[MovieDetailResponseSchema] + prev_page: Optional[str] + next_page: Optional[str] + total_pages: int + total_items: int + + model_config = {"from_attributes": True} From 7030975c32add53036b81104fe44f4a58c440091 Mon Sep 17 00:00:00 2001 From: Lev Date: Tue, 4 Feb 2025 19:07:48 +0200 Subject: [PATCH 2/4] Updated `poetry.lock` --- poetry.lock | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5d0707d..0aaffc9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "alembic" @@ -6,6 +6,8 @@ version = "1.14.0" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25"}, {file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b"}, @@ -25,6 +27,8 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -36,6 +40,8 @@ version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, @@ -58,6 +64,8 @@ version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, @@ -69,6 +77,8 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -83,6 +93,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "python_version <= \"3.11\" and sys_platform == \"win32\" or python_version <= \"3.11\" and platform_system == \"Windows\" or python_version >= \"3.12\" and sys_platform == \"win32\" or python_version >= \"3.12\" and platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -94,6 +106,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -108,6 +122,8 @@ version = "0.115.6" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, @@ -128,6 +144,8 @@ version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, @@ -144,6 +162,8 @@ version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or python_version >= \"3.12\" and python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, @@ -230,6 +250,8 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -241,6 +263,8 @@ version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -262,6 +286,8 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -286,6 +312,8 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -300,6 +328,8 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -311,6 +341,8 @@ version = "1.3.8" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"}, {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"}, @@ -330,6 +362,8 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -400,6 +434,8 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -411,6 +447,8 @@ version = "2.2.1" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "numpy-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440"}, {file = "numpy-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab"}, @@ -475,6 +513,8 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -486,6 +526,8 @@ version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -572,6 +614,8 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -587,6 +631,8 @@ version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, @@ -635,6 +681,7 @@ files = [ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, @@ -663,17 +710,34 @@ version = "2.12.1" description = "Python style guide checker" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] +[[package]] +name = "pycountry" +version = "24.6.1" +description = "ISO country, subdivision, language, currency and script definitions and their translations" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pycountry-24.6.1-py3-none-any.whl", hash = "sha256:f1a4fb391cd7214f8eefd39556d740adcc233c778a27f8942c8dca351d6ce06f"}, + {file = "pycountry-24.6.1.tar.gz", hash = "sha256:b61b3faccea67f87d10c1f2b0fc0be714409e8fcdcc1315613174f6466c10221"}, +] + [[package]] name = "pydantic" version = "2.10.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, @@ -694,6 +758,8 @@ version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -800,12 +866,39 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydantic-extra-types" +version = "2.10.2" +description = "Extra Pydantic types." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +files = [ + {file = "pydantic_extra_types-2.10.2-py3-none-any.whl", hash = "sha256:9eccd55a2b7935cea25f0a67f6ff763d55d80c41d86b887d88915412ccf5b7fa"}, + {file = "pydantic_extra_types-2.10.2.tar.gz", hash = "sha256:934d59ab7a02ff788759c3a97bc896f5cfdc91e62e4f88ea4669067a73f14b98"}, +] + +[package.dependencies] +pydantic = ">=2.5.2" +typing-extensions = "*" + +[package.extras] +all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<4)", "pytz (>=2024.1)", "semver (>=3.0.2)", "semver (>=3.0.2,<3.1.0)", "tzdata (>=2024.1)"] +pendulum = ["pendulum (>=3.0.0,<4.0.0)"] +phonenumbers = ["phonenumbers (>=8,<9)"] +pycountry = ["pycountry (>=23)"] +python-ulid = ["python-ulid (>=1,<2)", "python-ulid (>=1,<4)"] +semver = ["semver (>=3.0.2)"] + [[package]] name = "pydantic-settings" version = "2.7.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic_settings-2.7.0-py3-none-any.whl", hash = "sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5"}, {file = "pydantic_settings-2.7.0.tar.gz", hash = "sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66"}, @@ -826,6 +919,8 @@ version = "3.2.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, @@ -837,6 +932,8 @@ version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -859,6 +956,8 @@ version = "1.1.5" description = "pytest plugin that allows you to add environment variables." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30"}, {file = "pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf"}, @@ -877,6 +976,8 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -891,6 +992,8 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -905,6 +1008,8 @@ version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -916,6 +1021,8 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -927,6 +1034,8 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -938,6 +1047,8 @@ version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, @@ -1033,6 +1144,8 @@ version = "0.41.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, @@ -1050,6 +1163,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1091,6 +1206,8 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -1112,6 +1229,8 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -1123,6 +1242,8 @@ version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, @@ -1134,6 +1255,8 @@ version = "0.34.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, @@ -1148,6 +1271,6 @@ typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.10" -content-hash = "a5cbaeb3603006bac77fc6f90c3bfad7564585078c997ef8ce8967697b36278e" +content-hash = "93bae8ccdc49b0866d1f17552b0f668d2508d57738f767fa398a1090c3332c18" From c9316bae013b76158e94f97e81f17c4636c0e9d5 Mon Sep 17 00:00:00 2001 From: Lev Date: Tue, 4 Feb 2025 19:09:07 +0200 Subject: [PATCH 3/4] Updated flake8 --- .flake8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index cbd72d8..ae04aee 100644 --- a/.flake8 +++ b/.flake8 @@ -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/* extend-exclude = src/tests/* From 0a6dda517f7c5ca8c895edd4744f5f494d80132a Mon Sep 17 00:00:00 2001 From: Lev Date: Tue, 4 Feb 2025 19:20:50 +0200 Subject: [PATCH 4/4] Fix method update_movie response_model=None --- src/routes/movies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/movies.py b/src/routes/movies.py index 3a01bca..157e8e8 100644 --- a/src/routes/movies.py +++ b/src/routes/movies.py @@ -7,6 +7,7 @@ status, Request ) +from pydantic.v1 import NoneStr from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session, joinedload from typing import Annotated, Optional @@ -178,7 +179,7 @@ def create_movie( ) -@router.patch("/movies/{movie_id}/", response_model=MovieUpdateResponseSchema) +@router.patch("/movies/{movie_id}/", response_model=None) def update_movie( movie_id: int, movie_data: MovieUpdateResponseSchema,