From ec5336742dfb07bdd508909cd6289dc4b129a67c Mon Sep 17 00:00:00 2001 From: Alexandre Busquets Date: Wed, 9 Aug 2023 17:17:11 +0200 Subject: [PATCH] feat: migrations by boundary (#14) --- README.md | 3 + pyproject.toml | 1 + src/alembic.ini | 2 +- src/app/app_container.py | 3 +- src/app/asgi.py | 3 +- src/core/adapters/spi/repositories/country.py | 2 +- src/core/adapters/spi/repositories/user.py | 2 +- .../domain/ports => core/infra}/__init__.py | 0 src/core/infra/database/__init__.py | 0 src/core/infra/database/alembic/env.py | 101 ++++++++++++++++++ .../infra/database/alembic/script.py.mako | 24 +++++ .../0bfd3f8f6bdf_add_model_core_country.py | 0 .../11d9c7e4df5e_add_model_filmin_genre.py | 0 ...03a0661222_add_model_filmin_movie_genre.py | 0 ...a20_add_model_filmin_production_company.py | 0 ...83331ff4239_add_model_filmin_collection.py | 0 .../d812f9899efb_add_model_filmin_movie.py | 0 .../e484f9d6a6af_add_model_core_user.py | 0 .../infra/database/sqlalchemy/__init__.py | 0 .../database/sqlalchemy/models}/__init__.py | 0 .../database/sqlalchemy/models}/country.py | 0 .../infra/database/sqlalchemy/models}/user.py | 0 src/core/infra/database/sqlalchemy/session.py | 80 ++++++++++++++ .../infra/database/sqlalchemy/sqlalchemy.py | 12 +++ src/filmin/api/genre.py | 7 +- src/filmin/api/movie.py | 3 +- src/filmin/api/production_company.py | 3 +- src/filmin/data/repositories/genre.py | 5 +- src/filmin/data/repositories/movie.py | 7 +- src/filmin/data/repositories/ports/genre.py | 1 - src/filmin/data/repositories/ports/movie.py | 1 - .../repositories/ports/production_company.py | 1 - .../data/repositories/production_company.py | 5 +- src/filmin/di/mixins/genre.py | 1 - src/filmin/di/mixins/movie.py | 1 - src/filmin/di/mixins/production_company.py | 1 - src/filmin/infra/__init__.py | 0 src/filmin/infra/database/__init__.py | 0 src/filmin/infra/database/alembic/__init__.py | 0 .../11d9c7e4df5e_add_model_filmin_genre.py | 35 ++++++ ...03a0661222_add_model_filmin_movie_genre.py | 41 +++++++ ...a20_add_model_filmin_production_company.py | 35 ++++++ ...83331ff4239_add_model_filmin_collection.py | 35 ++++++ .../d812f9899efb_add_model_filmin_movie.py | 47 ++++++++ .../infra/database/sqlalchemy/__init__.py | 0 .../database/sqlalchemy/models}/__init__.py | 0 .../database/sqlalchemy/models}/collection.py | 0 .../database/sqlalchemy/models}/genre.py | 0 .../database/sqlalchemy/models}/movie.py | 0 .../sqlalchemy/models}/production_company.py | 0 .../database/sqlalchemy/models/__init__.py | 2 - src/tests/conftest.py | 4 +- src/tests/core/conftest.py | 2 +- src/tests/filmin/api/conftest.py | 6 +- 54 files changed, 438 insertions(+), 38 deletions(-) rename src/{auth/domain/ports => core/infra}/__init__.py (100%) create mode 100644 src/core/infra/database/__init__.py create mode 100644 src/core/infra/database/alembic/env.py create mode 100644 src/core/infra/database/alembic/script.py.mako rename src/{ => core}/infra/database/alembic/versions/0bfd3f8f6bdf_add_model_core_country.py (100%) rename src/{ => core}/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py (100%) rename src/{ => core}/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py (100%) rename src/{ => core}/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py (100%) rename src/{ => core}/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py (100%) rename src/{ => core}/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py (100%) rename src/{ => core}/infra/database/alembic/versions/e484f9d6a6af_add_model_core_user.py (100%) create mode 100644 src/core/infra/database/sqlalchemy/__init__.py rename src/{infra/database/sqlalchemy/models/core => core/infra/database/sqlalchemy/models}/__init__.py (100%) rename src/{infra/database/sqlalchemy/models/core => core/infra/database/sqlalchemy/models}/country.py (100%) rename src/{infra/database/sqlalchemy/models/core => core/infra/database/sqlalchemy/models}/user.py (100%) create mode 100644 src/core/infra/database/sqlalchemy/session.py create mode 100644 src/core/infra/database/sqlalchemy/sqlalchemy.py create mode 100644 src/filmin/infra/__init__.py create mode 100644 src/filmin/infra/database/__init__.py create mode 100644 src/filmin/infra/database/alembic/__init__.py create mode 100644 src/filmin/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py create mode 100644 src/filmin/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py create mode 100644 src/filmin/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py create mode 100644 src/filmin/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py create mode 100644 src/filmin/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py create mode 100644 src/filmin/infra/database/sqlalchemy/__init__.py rename src/{infra/database/sqlalchemy/models/filmin => filmin/infra/database/sqlalchemy/models}/__init__.py (100%) rename src/{infra/database/sqlalchemy/models/filmin => filmin/infra/database/sqlalchemy/models}/collection.py (100%) rename src/{infra/database/sqlalchemy/models/filmin => filmin/infra/database/sqlalchemy/models}/genre.py (100%) rename src/{infra/database/sqlalchemy/models/filmin => filmin/infra/database/sqlalchemy/models}/movie.py (100%) rename src/{infra/database/sqlalchemy/models/filmin => filmin/infra/database/sqlalchemy/models}/production_company.py (100%) diff --git a/README.md b/README.md index ecd983e..ca5afd5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ python manage.py {user_email} {name} # Migrations +:warning: **WIP, not tested with multiple folders** + + ```bash alembic init -t async migrations ``` diff --git a/pyproject.toml b/pyproject.toml index 967422a..3679af5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ known-local-folder = [ "auth", "config", "core", + "filmin", "infra", "shared", "utils", diff --git a/src/alembic.ini b/src/alembic.ini index 775d180..ea22ef7 100644 --- a/src/alembic.ini +++ b/src/alembic.ini @@ -2,7 +2,7 @@ [alembic] # path to migration scripts -script_location = infra/database/alembic +script_location = infra/database/alembic core/infra/database/alembic/migrations/ filmin/infra/database/alembic/migrations/ # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s # Uncomment the line below if you want the files to be prepended with date and time diff --git a/src/app/app_container.py b/src/app/app_container.py index e6b238e..9a7d06c 100644 --- a/src/app/app_container.py +++ b/src/app/app_container.py @@ -1,8 +1,7 @@ -from filmin.di.mixins import FilminContainerMixin - from auth.di.mixins.token import TokenContainerMixin from config import settings from core.di.mixins import CoreContainerMixin +from filmin.di.mixins import FilminContainerMixin from infra.cache.ports import AbstractCacheRepository from infra.database.sqlalchemy.session import AbstractDatabase from utils.di import DIContainer, di_singleton diff --git a/src/app/asgi.py b/src/app/asgi.py index b6d9804..67103f6 100644 --- a/src/app/asgi.py +++ b/src/app/asgi.py @@ -6,13 +6,12 @@ from fastapi import FastAPI, Request from fastapi.responses import JSONResponse -from filmin.api.router import router as filmin_router - from .app_container import AppContainer from app.setup_logging import setup_logging from auth.adapters.api.http.router import router as auth_router from config import settings from core.adapters.api.http.router import router as core_router +from filmin.api.router import router as filmin_router from shared.exceptions import APPExceptionError diff --git a/src/core/adapters/spi/repositories/country.py b/src/core/adapters/spi/repositories/country.py index 551607f..4f54c28 100644 --- a/src/core/adapters/spi/repositories/country.py +++ b/src/core/adapters/spi/repositories/country.py @@ -4,7 +4,7 @@ from core.domain.dtos.country.update_country import UpdatePartialCountryInDTO from core.domain.entities.country import Country from core.domain.ports.repositories.country import AbstractCountryRepository -from infra.database.sqlalchemy.models.core.country import countries +from core.infra.database.sqlalchemy.models.country import countries from shared.repository.sqlalchemy import SqlAlchemyRepository diff --git a/src/core/adapters/spi/repositories/user.py b/src/core/adapters/spi/repositories/user.py index cdd69f8..07f90b5 100644 --- a/src/core/adapters/spi/repositories/user.py +++ b/src/core/adapters/spi/repositories/user.py @@ -3,7 +3,7 @@ from core.domain.entities.user import User from core.domain.ports.repositories.user import AbstractUserRepository, CreateUserInDTO, UpdatePartialUserInDTO -from infra.database.sqlalchemy.models.core.user import users +from core.infra.database.sqlalchemy.models.user import users from shared.exceptions import AlreadyExistsError from shared.repository.sqlalchemy import SqlAlchemyRepository diff --git a/src/auth/domain/ports/__init__.py b/src/core/infra/__init__.py similarity index 100% rename from src/auth/domain/ports/__init__.py rename to src/core/infra/__init__.py diff --git a/src/core/infra/database/__init__.py b/src/core/infra/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/infra/database/alembic/env.py b/src/core/infra/database/alembic/env.py new file mode 100644 index 0000000..b8ab661 --- /dev/null +++ b/src/core/infra/database/alembic/env.py @@ -0,0 +1,101 @@ +import asyncio + +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import AsyncEngine + +from config import settings +import infra.database.sqlalchemy.models.core +import infra.database.sqlalchemy.models.filmin # noqa + +from infra.database.sqlalchemy.sqlalchemy import metadata + + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +config.set_main_option('sqlalchemy.url', settings.DATABASE_URL) + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +# target_metadata = None + + +target_metadata = metadata + + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option('sqlalchemy.url') + + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={'paramstyle': 'named'}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = AsyncEngine( + engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + future=True, + ) + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + asyncio.run(run_migrations_online()) diff --git a/src/core/infra/database/alembic/script.py.mako b/src/core/infra/database/alembic/script.py.mako new file mode 100644 index 0000000..55df286 --- /dev/null +++ b/src/core/infra/database/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/src/infra/database/alembic/versions/0bfd3f8f6bdf_add_model_core_country.py b/src/core/infra/database/alembic/versions/0bfd3f8f6bdf_add_model_core_country.py similarity index 100% rename from src/infra/database/alembic/versions/0bfd3f8f6bdf_add_model_core_country.py rename to src/core/infra/database/alembic/versions/0bfd3f8f6bdf_add_model_core_country.py diff --git a/src/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py b/src/core/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py similarity index 100% rename from src/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py rename to src/core/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py diff --git a/src/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py b/src/core/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py similarity index 100% rename from src/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py rename to src/core/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py diff --git a/src/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py b/src/core/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py similarity index 100% rename from src/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py rename to src/core/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py diff --git a/src/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py b/src/core/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py similarity index 100% rename from src/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py rename to src/core/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py diff --git a/src/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py b/src/core/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py similarity index 100% rename from src/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py rename to src/core/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py diff --git a/src/infra/database/alembic/versions/e484f9d6a6af_add_model_core_user.py b/src/core/infra/database/alembic/versions/e484f9d6a6af_add_model_core_user.py similarity index 100% rename from src/infra/database/alembic/versions/e484f9d6a6af_add_model_core_user.py rename to src/core/infra/database/alembic/versions/e484f9d6a6af_add_model_core_user.py diff --git a/src/core/infra/database/sqlalchemy/__init__.py b/src/core/infra/database/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/database/sqlalchemy/models/core/__init__.py b/src/core/infra/database/sqlalchemy/models/__init__.py similarity index 100% rename from src/infra/database/sqlalchemy/models/core/__init__.py rename to src/core/infra/database/sqlalchemy/models/__init__.py diff --git a/src/infra/database/sqlalchemy/models/core/country.py b/src/core/infra/database/sqlalchemy/models/country.py similarity index 100% rename from src/infra/database/sqlalchemy/models/core/country.py rename to src/core/infra/database/sqlalchemy/models/country.py diff --git a/src/infra/database/sqlalchemy/models/core/user.py b/src/core/infra/database/sqlalchemy/models/user.py similarity index 100% rename from src/infra/database/sqlalchemy/models/core/user.py rename to src/core/infra/database/sqlalchemy/models/user.py diff --git a/src/core/infra/database/sqlalchemy/session.py b/src/core/infra/database/sqlalchemy/session.py new file mode 100644 index 0000000..5156f08 --- /dev/null +++ b/src/core/infra/database/sqlalchemy/session.py @@ -0,0 +1,80 @@ +import abc + +from asyncio import current_task +from contextlib import asynccontextmanager +import contextvars +import logging +from typing import TYPE_CHECKING, Any, AsyncContextManager, AsyncIterator, Dict, Optional, cast + +from sqlalchemy.ext.asyncio import AsyncSession, async_scoped_session, create_async_engine +from sqlalchemy.orm import sessionmaker + +from config import settings + + +if TYPE_CHECKING: + from sqlalchemy.ext.asyncio.engine import AsyncEngine + + +logger = logging.getLogger(__name__) + + +db_session_context: contextvars.ContextVar = contextvars.ContextVar('db_ctx', default={'session': None, 'level': 0}) + + +class AbstractDatabase(abc.ABC): + @abc.abstractmethod + def session(self) -> AsyncContextManager[AsyncSession]: + ... + + +def _get_current_task_id() -> int: + return id(current_task()) + + +# @singleton +class Database(AbstractDatabase): + def __init__(self) -> None: + self.engine: AsyncEngine = create_async_engine(settings.DATABASE_URL, echo=True, future=True) + session_local: sessionmaker = sessionmaker( + self.engine, class_=AsyncSession, autocommit=False, autoflush=False, expire_on_commit=False + ) + # SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False, class_=AsyncSession) + self._session_factory = async_scoped_session(session_local, scopefunc=_get_current_task_id) + + @asynccontextmanager + async def session(self) -> AsyncIterator[AsyncSession]: + db_session: Optional[Dict[str, Any]] = None + db_session = db_session_context.get() or {'session': None, 'level': 0} + if db_session['level'] == 0: + session: AsyncSession = cast(AsyncSession, self._session_factory()) + db_session['session'] = session + # await session.begin() + logger.debug('session begin', extra={'level': db_session['level']}) + + else: + session = db_session['session'] + db_session['level'] = (db_session['level'] or 0) + 1 + db_session_context.set(db_session) + + try: + yield session + except Exception: + logger.exception('Session rollback because of exception') + await session.rollback() + logger.debug('session rollback') + raise + else: + # db_session = db_session_context.get() or {'session': None, 'level': 0} + if db_session['level'] == 0: + await session.commit() + logger.debug('session commit', extra={'level': db_session['level']}) + finally: + # db_session = db_session_context.get() or {'session': None, 'level': 0} + if db_session['level'] == 0: + await session.close() + logger.debug('session close', extra={'level': db_session['level']}) + db_session_context.set(None) + else: + db_session['level'] = (db_session['level'] or 0) - 1 + db_session_context.set(db_session) diff --git a/src/core/infra/database/sqlalchemy/sqlalchemy.py b/src/core/infra/database/sqlalchemy/sqlalchemy.py new file mode 100644 index 0000000..452c7d8 --- /dev/null +++ b/src/core/infra/database/sqlalchemy/sqlalchemy.py @@ -0,0 +1,12 @@ +from sqlalchemy.schema import MetaData + + +metadata = MetaData( + naming_convention={ + 'ix': 'ix_%(column_0_label)s', + 'uq': 'uq_%(table_name)s_%(column_0_name)s', + 'ck': 'ck_%(table_name)s_%(constraint_name)s', + 'fk': 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s', + 'pk': 'pk_%(table_name)s', + } +) diff --git a/src/filmin/api/genre.py b/src/filmin/api/genre.py index 05c037d..35d438b 100644 --- a/src/filmin/api/genre.py +++ b/src/filmin/api/genre.py @@ -4,6 +4,9 @@ from fastapi.responses import JSONResponse from fastapi.routing import APIRouter +from app.exceptions import EmptyPayloadExceptionError +from app.schemas import Session +from app.session_deps import check_access_token, is_admin_session from filmin.api.schemas.genre import ( CreateGenreRequestDTO, CreateGenreResponseDTO, @@ -13,10 +16,6 @@ from filmin.data.repositories.ports.genre import AbstractGenreRepository from filmin.domain.schemas.genre import Genre from filmin.schemas.genre import CreateGenreInDTO, UpdatePartialGenreInDTO - -from app.exceptions import EmptyPayloadExceptionError -from app.schemas import Session -from app.session_deps import check_access_token, is_admin_session from shared.api.schemas.page import PagedResponseSchema, PageParams diff --git a/src/filmin/api/movie.py b/src/filmin/api/movie.py index 06cdd9b..250e457 100644 --- a/src/filmin/api/movie.py +++ b/src/filmin/api/movie.py @@ -4,6 +4,7 @@ from fastapi.responses import JSONResponse from fastapi.routing import APIRouter +from app.exceptions import EmptyPayloadExceptionError from filmin.api.schemas.movie import ( CreateMovieRequestDTO, CreateMovieResponseDTO, @@ -12,8 +13,6 @@ from filmin.data.repositories.ports.movie import AbstractMovieRepository from filmin.domain.schemas.movie import Movie from filmin.schemas.movie import CreateMovieInDTO, UpdatePartialMovieInDTO - -from app.exceptions import EmptyPayloadExceptionError from shared.api.schemas.page import PagedResponseSchema, PageParams diff --git a/src/filmin/api/production_company.py b/src/filmin/api/production_company.py index f5c4410..537ab99 100644 --- a/src/filmin/api/production_company.py +++ b/src/filmin/api/production_company.py @@ -4,6 +4,7 @@ from fastapi.responses import JSONResponse from fastapi.routing import APIRouter +from app.exceptions import EmptyPayloadExceptionError from filmin.api.schemas.production_company import ( CreateProductionCompanyRequestDTO, CreateProductionCompanyResponseDTO, @@ -13,8 +14,6 @@ from filmin.data.repositories.ports.production_company import AbstractProductionCompanyRepository from filmin.domain.schemas.production_company import ProductionCompany from filmin.schemas.production_company import CreateProductionCompanyInDTO, UpdatePartialProductionCompanyInDTO - -from app.exceptions import EmptyPayloadExceptionError from shared.api.schemas.page import PagedResponseSchema, PageParams diff --git a/src/filmin/data/repositories/genre.py b/src/filmin/data/repositories/genre.py index 8b5a844..f4cba6e 100644 --- a/src/filmin/data/repositories/genre.py +++ b/src/filmin/data/repositories/genre.py @@ -1,10 +1,9 @@ from sqlalchemy.orm import registry +from .ports.genre import AbstractGenreRepository from filmin.domain.schemas.genre import Genre +from filmin.infra.database.sqlalchemy.models.genre import genres from filmin.schemas.genre import CreateGenreInDTO, UpdatePartialGenreInDTO - -from .ports.genre import AbstractGenreRepository -from infra.database.sqlalchemy.models.filmin.genre import genres from shared.repository.sqlalchemy import SqlAlchemyRepository diff --git a/src/filmin/data/repositories/movie.py b/src/filmin/data/repositories/movie.py index 086237d..5c6a00e 100644 --- a/src/filmin/data/repositories/movie.py +++ b/src/filmin/data/repositories/movie.py @@ -3,15 +3,14 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import registry, relationship +from .ports.movie import AbstractMovieRepository from filmin.data.repositories.ports.genre import AbstractGenreRepository from filmin.domain.schemas.genre import Genre from filmin.domain.schemas.movie import Movie from filmin.domain.schemas.movie_collection import MovieCollection +from filmin.infra.database.sqlalchemy.models.collection import movie_collection +from filmin.infra.database.sqlalchemy.models.movie import movie, movie_genre from filmin.schemas.movie import CreateMovieInDTO, UpdatePartialMovieInDTO - -from .ports.movie import AbstractMovieRepository -from infra.database.sqlalchemy.models.filmin.collection import movie_collection -from infra.database.sqlalchemy.models.filmin.movie import movie, movie_genre from shared.repository.sqlalchemy import SqlAlchemyRepository diff --git a/src/filmin/data/repositories/ports/genre.py b/src/filmin/data/repositories/ports/genre.py index b713678..b69446c 100644 --- a/src/filmin/data/repositories/ports/genre.py +++ b/src/filmin/data/repositories/ports/genre.py @@ -1,6 +1,5 @@ from filmin.domain.schemas.genre import Genre from filmin.schemas.genre import CreateGenreInDTO, UpdatePartialGenreInDTO - from shared.repository.ports.generic import AbstractRepository diff --git a/src/filmin/data/repositories/ports/movie.py b/src/filmin/data/repositories/ports/movie.py index 7a57ac0..cc6c194 100644 --- a/src/filmin/data/repositories/ports/movie.py +++ b/src/filmin/data/repositories/ports/movie.py @@ -1,6 +1,5 @@ from filmin.domain.schemas.movie import Movie from filmin.schemas.movie import CreateMovieInDTO, UpdatePartialMovieInDTO - from shared.repository.ports.generic import AbstractRepository diff --git a/src/filmin/data/repositories/ports/production_company.py b/src/filmin/data/repositories/ports/production_company.py index d3bd6c6..5845f25 100644 --- a/src/filmin/data/repositories/ports/production_company.py +++ b/src/filmin/data/repositories/ports/production_company.py @@ -1,6 +1,5 @@ from filmin.domain.schemas.production_company import ProductionCompany from filmin.schemas.production_company import CreateProductionCompanyInDTO, UpdatePartialProductionCompanyInDTO - from shared.repository.ports.generic import AbstractRepository diff --git a/src/filmin/data/repositories/production_company.py b/src/filmin/data/repositories/production_company.py index bd36b00..29cebdf 100644 --- a/src/filmin/data/repositories/production_company.py +++ b/src/filmin/data/repositories/production_company.py @@ -1,10 +1,9 @@ from sqlalchemy.orm import registry +from .ports.production_company import AbstractProductionCompanyRepository from filmin.domain.schemas.production_company import ProductionCompany +from filmin.infra.database.sqlalchemy.models import production_company from filmin.schemas.production_company import CreateProductionCompanyInDTO, UpdatePartialProductionCompanyInDTO - -from .ports.production_company import AbstractProductionCompanyRepository -from infra.database.sqlalchemy.models.filmin import production_company from shared.repository.sqlalchemy import SqlAlchemyRepository diff --git a/src/filmin/di/mixins/genre.py b/src/filmin/di/mixins/genre.py index d50e635..8bc123b 100644 --- a/src/filmin/di/mixins/genre.py +++ b/src/filmin/di/mixins/genre.py @@ -1,5 +1,4 @@ from filmin.data.repositories.ports.genre import AbstractGenreRepository - from infra.database.sqlalchemy.session import AbstractDatabase diff --git a/src/filmin/di/mixins/movie.py b/src/filmin/di/mixins/movie.py index 3f5eebd..9403d3b 100644 --- a/src/filmin/di/mixins/movie.py +++ b/src/filmin/di/mixins/movie.py @@ -1,6 +1,5 @@ from filmin.data.repositories.ports.genre import AbstractGenreRepository from filmin.data.repositories.ports.movie import AbstractMovieRepository - from infra.database.sqlalchemy.session import AbstractDatabase diff --git a/src/filmin/di/mixins/production_company.py b/src/filmin/di/mixins/production_company.py index d5ae376..e2519dc 100644 --- a/src/filmin/di/mixins/production_company.py +++ b/src/filmin/di/mixins/production_company.py @@ -1,5 +1,4 @@ from filmin.data.repositories.ports.production_company import AbstractProductionCompanyRepository - from infra.database.sqlalchemy.session import AbstractDatabase diff --git a/src/filmin/infra/__init__.py b/src/filmin/infra/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/filmin/infra/database/__init__.py b/src/filmin/infra/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/filmin/infra/database/alembic/__init__.py b/src/filmin/infra/database/alembic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/filmin/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py b/src/filmin/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py new file mode 100644 index 0000000..98f7e95 --- /dev/null +++ b/src/filmin/infra/database/alembic/versions/11d9c7e4df5e_add_model_filmin_genre.py @@ -0,0 +1,35 @@ +"""add model filmin.genre + +Revision ID: 11d9c7e4df5e +Revises: b83331ff4239 +Create Date: 2023-06-27 06:08:56.183993 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '11d9c7e4df5e' +down_revision = 'b83331ff4239' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'filmin_genre', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('code', sa.String(length=255), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('id', name=op.f('pk_filmin_genre')), + sa.UniqueConstraint('code', name=op.f('uq_filmin_genre_code')), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('filmin_genre') + # ### end Alembic commands ### diff --git a/src/filmin/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py b/src/filmin/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py new file mode 100644 index 0000000..b80d2e8 --- /dev/null +++ b/src/filmin/infra/database/alembic/versions/3c03a0661222_add_model_filmin_movie_genre.py @@ -0,0 +1,41 @@ +"""add model filmin.movie_genre + +Revision ID: 3c03a0661222 +Revises: d812f9899efb +Create Date: 2023-07-03 08:07:22.598933 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3c03a0661222' +down_revision = 'd812f9899efb' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'filmin_movie_genre', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('movie_id', sa.UUID(), nullable=True), # type: ignore + sa.Column('genre_id', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint( + ['genre_id'], ['filmin_genre.code'], name=op.f('fk_filmin_movie_genre_genre_id_filmin_genre') + ), + sa.ForeignKeyConstraint( + ['movie_id'], ['filmin_movie.uuid'], name=op.f('fk_filmin_movie_genre_movie_id_filmin_movie') + ), + sa.PrimaryKeyConstraint('id', name=op.f('pk_filmin_movie_genre')), + sa.UniqueConstraint('movie_id', 'genre_id', name='unique_filmin_movie_genre'), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('filmin_movie_genre') + # ### end Alembic commands ### diff --git a/src/filmin/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py b/src/filmin/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py new file mode 100644 index 0000000..fb5d36c --- /dev/null +++ b/src/filmin/infra/database/alembic/versions/4226432f4a20_add_model_filmin_production_company.py @@ -0,0 +1,35 @@ +"""add model filmin.production_company + +Revision ID: 4226432f4a20 +Revises: e484f9d6a6af +Create Date: 2023-06-27 04:44:52.438170 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4226432f4a20' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'filmin_production_company', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uuid', sa.UUID(), nullable=False), # type: ignore + sa.Column('name', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('id', name=op.f('pk_filmin_production_company')), + sa.UniqueConstraint('uuid', name=op.f('uq_filmin_production_company_uuid')), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('filmin_production_company') + # ### end Alembic commands ### diff --git a/src/filmin/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py b/src/filmin/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py new file mode 100644 index 0000000..7ad450f --- /dev/null +++ b/src/filmin/infra/database/alembic/versions/b83331ff4239_add_model_filmin_collection.py @@ -0,0 +1,35 @@ +"""add model filmin.collection + +Revision ID: b83331ff4239 +Revises: 4226432f4a20 +Create Date: 2023-06-27 04:47:56.336385 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b83331ff4239' +down_revision = '4226432f4a20' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'filmin_collection', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uuid', sa.UUID(), nullable=False), # type: ignore + sa.Column('name', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('id', name=op.f('pk_filmin_collection')), + sa.UniqueConstraint('uuid', name=op.f('uq_filmin_collection_uuid')), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('filmin_collection') + # ### end Alembic commands ### diff --git a/src/filmin/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py b/src/filmin/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py new file mode 100644 index 0000000..b8a448c --- /dev/null +++ b/src/filmin/infra/database/alembic/versions/d812f9899efb_add_model_filmin_movie.py @@ -0,0 +1,47 @@ +"""add model filmin.movie + +Revision ID: d812f9899efb +Revises: 11d9c7e4df5e +Create Date: 2023-06-30 14:25:03.793749 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd812f9899efb' +down_revision = '11d9c7e4df5e' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'filmin_movie', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uuid', sa.UUID(), nullable=False), # type: ignore + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('release_date', sa.Date(), nullable=True), + sa.Column('budget', sa.BigInteger(), nullable=True), + sa.Column('revenue', sa.BigInteger(), nullable=True), + sa.Column('popularity', sa.Float(), nullable=True), + sa.Column('runtime', sa.BigInteger(), nullable=True), + sa.Column('rating', sa.Float(), nullable=True), + sa.Column('original_language', sa.String(length=2), nullable=True), + sa.Column('collection_id', sa.UUID(), nullable=True), # type: ignore + sa.Column('overview', sa.Text(), nullable=True), + sa.ForeignKeyConstraint( + ['collection_id'], ['filmin_collection.uuid'], name=op.f('fk_filmin_movie_collection_id_filmin_collection') + ), + sa.PrimaryKeyConstraint('id', name=op.f('pk_filmin_movie')), + sa.UniqueConstraint('uuid', name=op.f('uq_filmin_movie_uuid')), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('filmin_movie') + # ### end Alembic commands ### diff --git a/src/filmin/infra/database/sqlalchemy/__init__.py b/src/filmin/infra/database/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/database/sqlalchemy/models/filmin/__init__.py b/src/filmin/infra/database/sqlalchemy/models/__init__.py similarity index 100% rename from src/infra/database/sqlalchemy/models/filmin/__init__.py rename to src/filmin/infra/database/sqlalchemy/models/__init__.py diff --git a/src/infra/database/sqlalchemy/models/filmin/collection.py b/src/filmin/infra/database/sqlalchemy/models/collection.py similarity index 100% rename from src/infra/database/sqlalchemy/models/filmin/collection.py rename to src/filmin/infra/database/sqlalchemy/models/collection.py diff --git a/src/infra/database/sqlalchemy/models/filmin/genre.py b/src/filmin/infra/database/sqlalchemy/models/genre.py similarity index 100% rename from src/infra/database/sqlalchemy/models/filmin/genre.py rename to src/filmin/infra/database/sqlalchemy/models/genre.py diff --git a/src/infra/database/sqlalchemy/models/filmin/movie.py b/src/filmin/infra/database/sqlalchemy/models/movie.py similarity index 100% rename from src/infra/database/sqlalchemy/models/filmin/movie.py rename to src/filmin/infra/database/sqlalchemy/models/movie.py diff --git a/src/infra/database/sqlalchemy/models/filmin/production_company.py b/src/filmin/infra/database/sqlalchemy/models/production_company.py similarity index 100% rename from src/infra/database/sqlalchemy/models/filmin/production_company.py rename to src/filmin/infra/database/sqlalchemy/models/production_company.py diff --git a/src/infra/database/sqlalchemy/models/__init__.py b/src/infra/database/sqlalchemy/models/__init__.py index 3ac6c89..e69de29 100644 --- a/src/infra/database/sqlalchemy/models/__init__.py +++ b/src/infra/database/sqlalchemy/models/__init__.py @@ -1,2 +0,0 @@ -from .core import * # noqa -from .filmin import * # noqa diff --git a/src/tests/conftest.py b/src/tests/conftest.py index 0f12be8..28d4d83 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -16,10 +16,12 @@ from config import settings from core.domain.entities.user import User from core.domain.ports.repositories.user import AbstractUserRepository, CreateUserInDTO +import core.infra.database.sqlalchemy.models # noqa +import filmin.infra.database.sqlalchemy.models # noqa + from infra.cache.memory_cache import MemoryCache from infra.cache.ports import AbstractCacheRepository import infra.database.sqlalchemy.models # noqa - from infra.database.sqlalchemy.sqlalchemy import metadata from utils.di import di_singleton diff --git a/src/tests/core/conftest.py b/src/tests/core/conftest.py index 014e0e4..b7fdc5e 100644 --- a/src/tests/core/conftest.py +++ b/src/tests/core/conftest.py @@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession -from infra.database.sqlalchemy.models.core.country import countries +from core.infra.database.sqlalchemy.models.country import countries AsyncSessionCtxT = Callable[[], AsyncContextManager[AsyncSession]] diff --git a/src/tests/filmin/api/conftest.py b/src/tests/filmin/api/conftest.py index 331bef4..63a5ebb 100644 --- a/src/tests/filmin/api/conftest.py +++ b/src/tests/filmin/api/conftest.py @@ -6,9 +6,9 @@ from sqlalchemy.ext.asyncio import AsyncSession -from infra.database.sqlalchemy.models.filmin.collection import movie_collection -from infra.database.sqlalchemy.models.filmin.genre import genres -from infra.database.sqlalchemy.models.filmin.movie import movie, movie_genre +from filmin.infra.database.sqlalchemy.models.collection import movie_collection +from filmin.infra.database.sqlalchemy.models.genre import genres +from filmin.infra.database.sqlalchemy.models.movie import movie, movie_genre AsyncSessionCtxT = Callable[[], AsyncContextManager[AsyncSession]]