initial movie project

parents
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
media/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
data/
.idea
\ No newline at end of file
FROM python:3.10-slim-buster
RUN pip3 install --upgrade pip && pip3 install poetry
WORKDIR /app
COPY pyproject.toml poetry.lock /app/
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi
COPY . .
version: '3'
services:
core:
build:
context: .
dockerfile: Dockerfile
image: movie:1.0
restart: always
ports:
- "8000:8000"
environment:
- database_url=postgres://postgres:postgres@db:5432/postgres
networks:
- movienetwork
volumes:
- .:/app
command: bash -c "poetry run python3 src/app/main.py"
depends_on:
- db
db:
image: postgres:14-alpine
environment:
- POSTGRES_PASSWORD=postgres
ports:
- "5435:5432"
volumes:
- ./data/postgres:/var/lib/postgresql/data/
networks:
- movienetwork
networks:
movienetwork:
driver: bridge
This diff is collapsed.
[tool.poetry]
name = "movie"
version = "0.1.0"
description = "Pet project!"
authors = ["beka <beka1990.30.10@gmail.com>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.104.1"
uvicorn = "^0.24.0.post1"
tortoise-orm = {extras = ["asyncodbc"], version = "^0.20.0"}
aerich = "^0.7.2"
asyncpg = "^0.29.0"
[tool.aerich]
tortoise_orm = "src.app.db.conf.TORTOISE_ORM"
location = "src/migrations"
src_folder = "./."
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
from fastapi import APIRouter
router = APIRouter(prefix='/api')
@router.get('/greetings')
def get_greetings():
return {'text': 'Greetings!'}
import os
import pydantic
database_url = pydantic.PostgresDsn(os.getenv('database_url'))
import os
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
TORTOISE_ORM = {
'connections': {'default': os.environ.get('database_url')},
'apps': {
'models': {
'models': ['src.app.db.models.movie', 'aerich.models'],
'default_connection': 'default',
},
},
}
def init_db(app: FastAPI) -> None:
register_tortoise(
app,
config=TORTOISE_ORM,
generate_schemas=True,
add_exception_handlers=True,
)
from tortoise import fields, models
class IdMixin(models.Model):
id = fields.IntField(pk=True)
class Meta:
abstract = True
class TimestampMixin(models.Model):
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
class Meta:
abstract = True
class BaseModel(IdMixin, TimestampMixin):
class Meta:
ordering = ('-created_at', )
from tortoise import fields
from .base import BaseModel
class Director(BaseModel):
first_name = fields.CharField(max_length=255)
last_name = fields.CharField(max_length=255)
born = fields.DateField()
def __str__(self):
return self.first_name
class Genre(BaseModel):
name = fields.CharField(max_length=255)
def __str__(self):
return self.name
class Movie(BaseModel):
name = fields.CharField(max_length=255)
year = fields.IntField()
country = fields.CharField(max_length=255)
director = fields.ForeignKeyField(
'models.Director', related_name='director_movies', on_delete=fields.OnDelete.CASCADE
)
genre = fields.ForeignKeyField(
'models.Genre', related_name='genre_movies', on_delete=fields.OnDelete.CASCADE
)
description = fields.TextField()
duration = fields.TimeField()
def __str__(self):
return self.name
class NotFoundException(Exception):
pass
class CreateException(Exception):
pass
class UpdateException(Exception):
pass
class DeleteException(Exception):
pass
import fastapi
import pydantic
from starlette import status
from fastapi.responses import JSONResponse
from .http import BaseHTTPException
async def query_params_exc_handler(
request: fastapi.Request, exc: pydantic.ValidationError,
) -> JSONResponse:
return JSONResponse(
{'detail': exc.errors()}, status.HTTP_422_UNPROCESSABLE_ENTITY,
)
async def request_exc_handler(
request: fastapi.Request, exc: BaseHTTPException,
) -> JSONResponse:
return JSONResponse(
{'detail': exc.detail}, exc.status_code,
)
async def internal_exc_handler(
request: fastapi.Request, exc: Exception,
) -> JSONResponse:
return JSONResponse(
{'detail': 'Internal Server Error'}, status.HTTP_500_INTERNAL_SERVER_ERROR,
)
from typing import Any, Dict
from fastapi.exceptions import HTTPException
from starlette import status
class BaseHTTPException(HTTPException):
pass
class HTTPBadRequestException(BaseHTTPException):
def __init__(
self, status_code: int, detail: Any = None, headers: Dict[str, str] = None
) -> None:
super().__init__(status.HTTP_400_BAD_REQUEST, detail, headers)
class HTTPNotFoundException(BaseHTTPException):
def __init__(
self, status_code: int, detail: Any = None, headers: Dict[str, str] = None
) -> None:
super().__init__(status.HTTP_404_NOT_FOUND, detail, headers)
import fastapi
import pydantic
from api.router import router
from exceptions import handlers as exc_handlers, http as http_exc
def setup():
app = fastapi.FastAPI()
app.include_router(router)
app.exception_handler(pydantic.ValidationError)(exc_handlers.query_params_exc_handler)
app.exception_handler(http_exc.BaseHTTPException)(exc_handlers.request_exc_handler)
app.exception_handler(500)(exc_handlers.internal_exc_handler)
return app
app = setup()
if __name__ == '__main__':
import uvicorn
uvicorn.run('main:app', host='0.0.0.0', port=8000, reload=True)
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "director" (
"id" SERIAL NOT NULL PRIMARY KEY,
"first_name" VARCHAR(255) NOT NULL,
"last_name" VARCHAR(255) NOT NULL,
"born" DATE NOT NULL
);
CREATE TABLE IF NOT EXISTS "genre" (
"id" SERIAL NOT NULL PRIMARY KEY,
"name" VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS "movie" (
"id" SERIAL NOT NULL PRIMARY KEY,
"name" VARCHAR(255) NOT NULL,
"year" INT NOT NULL,
"country" VARCHAR(255) NOT NULL,
"description" TEXT NOT NULL,
"time" TIMETZ NOT NULL,
"director_id" INT NOT NULL REFERENCES "director" ("id") ON DELETE CASCADE,
"genre_id" INT NOT NULL REFERENCES "genre" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "aerich" (
"id" SERIAL NOT NULL PRIMARY KEY,
"version" VARCHAR(255) NOT NULL,
"app" VARCHAR(100) NOT NULL,
"content" JSONB NOT NULL
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "basemodel" (
"id" SERIAL NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE "director" ADD "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE "director" ADD "updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE "genre" ADD "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE "genre" ADD "updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE "movie" RENAME COLUMN "time" TO "duration";
ALTER TABLE "movie" ADD "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE "movie" ADD "updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP;"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "genre" DROP COLUMN "created_at";
ALTER TABLE "genre" DROP COLUMN "updated_at";
ALTER TABLE "movie" RENAME COLUMN "duration" TO "time";
ALTER TABLE "movie" DROP COLUMN "created_at";
ALTER TABLE "movie" DROP COLUMN "updated_at";
ALTER TABLE "director" DROP COLUMN "created_at";
ALTER TABLE "director" DROP COLUMN "updated_at";
DROP TABLE IF EXISTS "basemodel";"""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment