Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions fastapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,39 @@ By default it runs:

You can read more about pre-commit here: https://pre-commit.com/

## Migrations

If you want to migrate your database, you should run following commands:
```bash
# To run all migrations until the migration with revision_id.
alembic upgrade "<revision_id>"

# To perform all pending migrations.
alembic upgrade "head"
```

### Reverting migrations

If you want to revert migrations, you should run:
```bash
# revert all migrations up to: revision_id.
alembic downgrade <revision_id>

# Revert everything.
alembic downgrade base
```

### Migration generation

To generate migrations you should run:
```bash
# For automatic change detection.
alembic revision --autogenerate

# For empty file generation.
alembic revision
```


## Running tests

Expand Down
54 changes: 54 additions & 0 deletions fastapp/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[alembic]
script_location = fastapp/db/migrations
file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d-%%(minute).2d_%%(rev)s
prepend_sys_path = .
output_encoding = utf-8
# truncate_slug_length = 40


[post_write_hooks]
hooks = black,autoflake,isort

black.type = console_scripts
black.entrypoint = black

autoflake.type = console_scripts
autoflake.entrypoint = autoflake

isort.type = console_scripts
isort.entrypoint = isort

# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
9 changes: 9 additions & 0 deletions fastapp/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ services:
volumes:
- fastapp-db-data:/db_data/

migrator:
image: fastapp:${FASTAPP_VERSION:-latest}
restart: "no"
command: alembic upgrade head
environment:
FASTAPP_DB_FILE: /db_data/db.sqlite3
volumes:
- fastapp-db-data:/db_data/



volumes:
Expand Down
1 change: 1 addition & 0 deletions fastapp/fastapp/db/dao/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""DAO classes."""
50 changes: 50 additions & 0 deletions fastapp/fastapp/db/dao/dummy_dao.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from typing import List, Optional

from fastapi import Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from fastapp.db.dependencies import get_db_session
from fastapp.db.models.dummy_model import DummyModel


class DummyDAO:
"""Class for accessing dummy table."""

def __init__(self, session: AsyncSession = Depends(get_db_session)) -> None:
self.session = session

async def create_dummy_model(self, name: str) -> None:
"""
Add single dummy to session.

:param name: name of a dummy.
"""
self.session.add(DummyModel(name=name))

async def get_all_dummies(self, limit: int, offset: int) -> List[DummyModel]:
"""
Get all dummy models with limit/offset pagination.

:param limit: limit of dummies.
:param offset: offset of dummies.
:return: stream of dummies.
"""
raw_dummies = await self.session.execute(
select(DummyModel).limit(limit).offset(offset),
)

return list(raw_dummies.scalars().fetchall())

async def filter(self, name: Optional[str] = None) -> List[DummyModel]:
"""
Get specific dummy model.

:param name: name of dummy instance.
:return: dummy models.
"""
query = select(DummyModel)
if name:
query = query.where(DummyModel.name == name)
rows = await self.session.execute(query)
return list(rows.scalars().fetchall())
1 change: 1 addition & 0 deletions fastapp/fastapp/db/migrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Alembic migrations."""
88 changes: 88 additions & 0 deletions fastapp/fastapp/db/migrations/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import asyncio
from logging.config import fileConfig

from alembic import context
from sqlalchemy.ext.asyncio.engine import create_async_engine
from sqlalchemy.future import Connection
from fastapp.db.meta import meta
from fastapp.db.models import load_all_models
from fastapp.settings import settings

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config


load_all_models()
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
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 = meta

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


async 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.

"""
context.configure(
url=str(settings.db_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:
"""
Run actual sync migrations.

:param connection: connection to the database.
"""
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 = create_async_engine(str(settings.db_url))

async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)


loop = asyncio.get_event_loop()
if context.is_offline_mode():
task = run_migrations_offline()
else:
task = run_migrations_online()

loop.run_until_complete(task)
24 changes: 24 additions & 0 deletions fastapp/fastapp/db/migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Initial migration.

Revision ID: 819cbf6e030b
Revises:
Create Date: 2021-08-16 16:53:05.484024

"""

# revision identifiers, used by Alembic.
revision = "819cbf6e030b"
down_revision = None
branch_labels = None
depends_on = None


def upgrade() -> None:
pass


def downgrade() -> None:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Created Dummy Model.

Revision ID: 2b7380507a71
Revises: 819cbf6e030b
Create Date: 2021-08-16 16:55:25.157309

"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "2b7380507a71"
down_revision = "819cbf6e030b"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"dummy_model",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("name", sa.String(length=200), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("dummy_model")
# ### end Alembic commands ###
Empty file.
13 changes: 13 additions & 0 deletions fastapp/fastapp/db/models/dummy_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql.sqltypes import String

from fastapp.db.base import Base


class DummyModel(Base):
"""Model for demo purpose."""

__tablename__ = "dummy_model"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(length=200))
5 changes: 5 additions & 0 deletions fastapp/fastapp/web/api/dummy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Dummy model API."""

from fastapp.web.api.dummy.views import router

__all__ = ["router"]
20 changes: 20 additions & 0 deletions fastapp/fastapp/web/api/dummy/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pydantic import BaseModel, ConfigDict


class DummyModelDTO(BaseModel):
"""
DTO for dummy models.

It returned when accessing dummy models from the API.
"""

id: int
name: str

model_config = ConfigDict(from_attributes=True)


class DummyModelInputDTO(BaseModel):
"""DTO for creating new dummy model."""

name: str
Loading