From 456c29baff1413a15159966d253eed9f9812ad1e Mon Sep 17 00:00:00 2001
From: Pavel Kirilin <win10@list.ru>
Date: Wed, 29 Sep 2021 14:38:21 +0400
Subject: [PATCH] TortoiseORM support. (#19)

* TortoiseORM support.

Closes #17.

Signed-off-by: Pavel Kirilin <win10@list.ru>
---
 README.md                                     | 32 +++++----
 fastapi_template/__main__.py                  |  4 +-
 fastapi_template/cli.py                       | 50 ++++++++++----
 fastapi_template/input_model.py               | 24 +++++--
 fastapi_template/template/cookiecutter.json   |  5 +-
 .../template/hooks/post_gen_project.py        | 32 +++++++--
 .../{{cookiecutter.project_name}}/.flake8     |  2 +
 .../{{cookiecutter.project_name}}/README.md   |  2 +-
 .../{{cookiecutter.project_name}}/aerich.ini  |  4 ++
 .../conditional_files.json                    | 65 ++++++++++++++----
 .../deploy/docker-compose.yml                 |  8 ++-
 .../deploy/kube/db.yml                        |  5 ++
 .../pyproject.toml                            | 30 +++++++--
 .../replaceable_files.json                    |  6 ++
 .../{{cookiecutter.project_name}}/conftest.py | 66 +++++++++++++++----
 .../{db => db_sa}/base.py                     |  0
 .../{db => db_sa}/dao/__init__.py             |  0
 .../{db => db_sa}/dao/dummy_dao.py            | 10 +--
 .../{db => db_sa}/dependencies.py             |  0
 .../{db => db_sa}/meta.py                     |  0
 .../{db => db_sa}/migrations/__init__.py      |  0
 .../{db => db_sa}/migrations/env.py           |  0
 .../{db => db_sa}/migrations/script.py.mako   |  0
 .../versions/2021-08-16-16-53_819cbf6e030b.py |  0
 .../versions/2021-08-16-16-55_2b7380507a71.py |  0
 .../migrations/versions/__init__.py           |  0
 .../{db => db_sa}/models/__init__.py          |  0
 .../{db => db_sa}/models/dummy_model.py       |  0
 .../{db => db_sa}/utils.py                    |  0
 .../db_tortoise/config.py                     | 16 +++++
 .../db_tortoise/dao/__init__.py               |  1 +
 .../db_tortoise/dao/dummy_dao.py              | 38 +++++++++++
 .../models/0_20210928165300_init_mysql.sql    |  7 ++
 .../models/0_20210928165300_init_pg.sql       |  7 ++
 .../models/0_20210928165300_init_sqlite.sql   |  7 ++
 .../1_20210928165300_init_dummy_mysql.sql     |  5 ++
 .../models/1_20210928165300_init_dummy_pg.sql |  6 ++
 .../1_20210928165300_init_dummy_sqlite.sql    |  5 ++
 .../db_tortoise/models/__init__.py            |  1 +
 .../db_tortoise/models/dummy_model.py         | 11 ++++
 .../{{cookiecutter.project_name}}/settings.py | 10 ++-
 .../tests/test_dummy.py                       | 42 +++++-------
 .../web/api/dummy/views.py                    | 12 ++--
 .../web/application.py                        | 11 ++++
 .../web/lifetime.py                           |  8 +--
 fastapi_template/tests/conftest.py            | 18 +++--
 fastapi_template/tests/test_generator.py      | 66 +++++++++++++++++++
 fastapi_template/tests/test_mysql.py          | 47 -------------
 fastapi_template/tests/test_postgres.py       | 47 -------------
 fastapi_template/tests/test_sqlite.py         | 47 -------------
 pyproject.toml                                |  2 +-
 51 files changed, 506 insertions(+), 253 deletions(-)
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/aerich.ini
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/base.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/dao/__init__.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/dao/dummy_dao.py (85%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/dependencies.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/meta.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/migrations/__init__.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/migrations/env.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/migrations/script.py.mako (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/migrations/versions/2021-08-16-16-53_819cbf6e030b.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/migrations/versions/2021-08-16-16-55_2b7380507a71.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/migrations/versions/__init__.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/models/__init__.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/models/dummy_model.py (100%)
 rename fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/{db => db_sa}/utils.py (100%)
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/__init__.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/dummy_dao.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_mysql.sql
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_pg.sql
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_sqlite.sql
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_mysql.sql
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_pg.sql
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_sqlite.sql
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/__init__.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/dummy_model.py
 create mode 100644 fastapi_template/tests/test_generator.py
 delete mode 100644 fastapi_template/tests/test_mysql.py
 delete mode 100644 fastapi_template/tests/test_postgres.py
 delete mode 100644 fastapi_template/tests/test_sqlite.py

diff --git a/README.md b/README.md
index 74ee8f3..3b58385 100644
--- a/README.md
+++ b/README.md
@@ -30,42 +30,50 @@ python3 -m fastapi_template
 
 ## Features
 
-Template is made with SQLAlchemy1.4 and uses sqlalchemy orm and sessions,
-instead of raw drivers.
+One of the coolest features is that this project is extremely small and handy.
+You can choose between different databases and even ORMs. 
+Currently SQLAlchemy1.4 and TortoiseORM are supported.
 
-It has minimum to start new excellent project.
-
-Pre-commit integrations and excellent code documentation.
+TUI and CLI and excellent code documentation.
 
 Generator features:
-- Different databases to choose from.
-- Alembic integration;
+- Different databases support;
+- Different ORMs support;
+- Optional migrations for each ORM;
 - redis support;
-- different CI\CD templates;
-- Kubernetes config generation.
+- different CI\CD;
+- Kubernetes config generation;
+- Demo routers and models;
+- Pre-commit integrations;
+- Generated tests;
+- Tests for the generator itself.
 
 This project can handle arguments passed through command line.
 
 ```shell
 $ python -m fastapi_template --help
 
-usage: FastAPI template [-h] [--name PROJECT_NAME]
+usage: FastAPI template [-h] [--version] [--name PROJECT_NAME]
                         [--description PROJECT_DESCRIPTION]
                         [--db {none,sqlite,mysql,postgresql}]
-                        [--ci {none,gitlab,github}] [--redis] [--alembic]
+                        [--orm {sqlalchemy,tortoise}]
+                        [--ci {none,gitlab,github}] [--redis] [--migrations]
                         [--kube] [--dummy] [--routers] [--swagger] [--force]
 
 optional arguments:
   -h, --help            show this help message and exit
+  --version, -V         Prints current version
   --name PROJECT_NAME   Name of your awesome project
   --description PROJECT_DESCRIPTION
                         Project description
   --db {none,sqlite,mysql,postgresql}
                         Database
+  --orm {sqlalchemy,tortoise}
+                        ORM
   --ci {none,gitlab,github}
                         Choose CI support
   --redis               Add redis support
-  --alembic             Add alembic support
+  --migrations          Add migrations support
   --kube                Add kubernetes configs
   --dummy, --dummy-model
                         Add dummy model
diff --git a/fastapi_template/__main__.py b/fastapi_template/__main__.py
index 818ed6e..1ae52d7 100644
--- a/fastapi_template/__main__.py
+++ b/fastapi_template/__main__.py
@@ -1,3 +1,4 @@
+import json
 from pathlib import Path
 
 from cookiecutter.exceptions import FailedHookException, OutputDirExistsException
@@ -19,7 +20,7 @@ def generate_project(context: BuilderContext) -> None:
     try:
         cookiecutter(
             template=f"{script_dir}/template",
-            extra_context=context.dict(),
+            extra_context=json.loads(context.json()),
             default_config=BuilderContext().dict(),
             no_input=True,
             overwrite_if_exists=context.force,
@@ -42,5 +43,6 @@ def main() -> None:
         return
     generate_project(context)
 
+
 if __name__ == "__main__":
     main()
diff --git a/fastapi_template/cli.py b/fastapi_template/cli.py
index ff9715f..ac21503 100644
--- a/fastapi_template/cli.py
+++ b/fastapi_template/cli.py
@@ -7,7 +7,14 @@ from prompt_toolkit.document import Document
 from prompt_toolkit.shortcuts import checkboxlist_dialog, radiolist_dialog
 from prompt_toolkit.validation import ValidationError, Validator
 
-from fastapi_template.input_model import BuilderContext, DB_INFO, DatabaseType, CIType
+from fastapi_template.input_model import (
+    ORM,
+    BuilderContext,
+    DB_INFO,
+    DatabaseType,
+    CIType,
+)
+from importlib.metadata import version
 
 
 class SnakeCaseValidator(Validator):
@@ -21,6 +28,9 @@ def parse_args():
     parser = ArgumentParser(
         prog="FastAPI template",
     )
+    parser.add_argument(
+        "--version", "-V", action="store_true", help="Prints current version"
+    )
     parser.add_argument(
         "--name",
         type=str,
@@ -42,6 +52,14 @@ def parse_args():
         default=None,
         dest="db",
     )
+    parser.add_argument(
+        "--orm",
+        help="ORM",
+        type=str,
+        choices=list(map(attrgetter("value"), ORM)),
+        default=None,
+        dest="orm",
+    )
     parser.add_argument(
         "--ci",
         help="Choose CI support",
@@ -58,11 +76,11 @@ def parse_args():
         dest="enable_redis",
     )
     parser.add_argument(
-        "--alembic",
-        help="Add alembic support",
+        "--migrations",
+        help="Add migrations support",
         action="store_true",
         default=None,
-        dest="enable_alembic",
+        dest="enable_migrations",
     )
     parser.add_argument(
         "--kube",
@@ -122,16 +140,15 @@ def ask_features(current_context: BuilderContext) -> BuilderContext:
             "name": "self_hosted_swagger",
             "value": current_context.self_hosted_swagger,
         },
-
     }
     if current_context.db != DatabaseType.none:
-        features["Alembic migrations"] = {
-            "name": "enable_alembic",
-            "value": current_context.enable_alembic,
+        features["Migrations support"] = {
+            "name": "enable_migrations",
+            "value": current_context.enable_migrations,
         }
         features["Add dummy model"] = {
             "name": "add_dummy",
-            "value": current_context.add_dummy
+            "value": current_context.add_dummy,
         }
     checkbox_values = []
     for feature_name, feature in features.items():
@@ -156,7 +173,7 @@ def read_user_input(current_context: BuilderContext) -> BuilderContext:
         current_context.project_name = prompt(
             "Project name: ", validator=SnakeCaseValidator()
         )
-    current_context.kube_name = current_context.project_name.replace('_', '-')
+    current_context.kube_name = current_context.project_name.replace("_", "-")
     if current_context.project_description is None:
         current_context.project_description = prompt("Project description: ")
     if current_context.db is None:
@@ -168,8 +185,16 @@ def read_user_input(current_context: BuilderContext) -> BuilderContext:
         if current_context.db is None:
             raise KeyboardInterrupt()
     if current_context.db == DatabaseType.none:
-        current_context.enable_alembic = False
+        current_context.enable_migrations = False
         current_context.add_dummy = False
+    elif current_context.orm is None:
+        current_context.orm = radiolist_dialog(
+            "ORM",
+            text="Which ORM do you want?",
+            values=[(orm, orm.value) for orm in list(ORM)],
+        ).run()
+        if current_context.orm is None:
+            raise KeyboardInterrupt()
     if current_context.ci_type is None:
         current_context.ci_type = radiolist_dialog(
             "CI",
@@ -184,6 +209,9 @@ def read_user_input(current_context: BuilderContext) -> BuilderContext:
 
 def get_context() -> BuilderContext:
     args = parse_args()
+    if args.version:
+        print(version("fastapi_template"))
+        exit(0)
     context = BuilderContext.from_orm(args)
     context = read_user_input(context)
     context.db_info = DB_INFO[context.db]
diff --git a/fastapi_template/input_model.py b/fastapi_template/input_model.py
index edd41cd..3c5c369 100644
--- a/fastapi_template/input_model.py
+++ b/fastapi_template/input_model.py
@@ -18,37 +18,52 @@ class CIType(enum.Enum):
     gitlab_ci = "gitlab"
     github = "github"
 
+@enum.unique
+class ORM(enum.Enum):
+    sqlalchemy = "sqlalchemy"
+    tortoise = "tortoise"
+
 
 class Database(BaseModel):
     name: str
     image: Optional[str]
     driver: Optional[str]
+    async_driver: Optional[str]
     port: Optional[int]
 
+
 DB_INFO = {
     DatabaseType.none: Database(
         name="none",
         image=None,
         driver=None,
+        async_driver=None,
         port=None,
     ),
     DatabaseType.postgresql: Database(
         name=DatabaseType.postgresql.value,
         image="postgres:13.4-buster",
-        driver="postgresql+asyncpg",
+        async_driver="postgresql+asyncpg",
+        driver="postgres",
         port=5432,
     ),
     DatabaseType.mysql: Database(
         name=DatabaseType.mysql.value,
         image="bitnami/mysql:8.0.26",
-        driver="mysql+aiomysql",
+        async_driver="mysql+aiomysql",
+        driver="mysql",
         port=3306,
     ),
     DatabaseType.sqlite: Database(
-        name=DatabaseType.sqlite.value, image=None, driver="sqlite+aiosqlite", port=None
+        name=DatabaseType.sqlite.value,
+        image=None,
+        async_driver="sqlite+aiosqlite",
+        driver="sqlite",
+        port=None,
     ),
 }
 
+
 class BuilderContext(BaseModel):
     """Options for project generation."""
 
@@ -59,7 +74,8 @@ class BuilderContext(BaseModel):
     db_info: Optional[Database]
     enable_redis: Optional[bool]
     ci_type: Optional[CIType]
-    enable_alembic: Optional[bool]
+    orm: Optional[ORM]
+    enable_migrations: Optional[bool]
     enable_kube: Optional[bool]
     enable_routers: Optional[bool]
     add_dummy: Optional[bool] = False
diff --git a/fastapi_template/template/cookiecutter.json b/fastapi_template/template/cookiecutter.json
index 6bdfe0f..c1ea080 100644
--- a/fastapi_template/template/cookiecutter.json
+++ b/fastapi_template/template/cookiecutter.json
@@ -14,7 +14,7 @@
   "ci_type": {
     "type": "string"
   },
-  "enable_alembic": {
+  "enable_migrations": {
     "type": "bool"
   },
   "enable_kube": {
@@ -29,6 +29,9 @@
   "add_dummy": {
     "type": "bool"
   },
+  "orm": {
+    "type": "str"
+  },
   "self_hosted_swagger": {
     "type": "bool"
   },
diff --git a/fastapi_template/template/hooks/post_gen_project.py b/fastapi_template/template/hooks/post_gen_project.py
index 6165a08..76f2399 100644
--- a/fastapi_template/template/hooks/post_gen_project.py
+++ b/fastapi_template/template/hooks/post_gen_project.py
@@ -6,16 +6,16 @@ import subprocess
 
 from pygit2 import init_repository
 from termcolor import cprint, colored
+from pathlib import Path
 
 MANIFEST = "conditional_files.json"
+REPLACE_MANIFEST = "replaceable_files.json"
 
 
 def delete_resource(resource):
     if os.path.isfile(resource):
-        print("removing file: {}".format(resource))
         os.remove(resource)
     elif os.path.isdir(resource):
-        print("removing directory: {}".format(resource))
         shutil.rmtree(resource)
 
 
@@ -23,18 +23,39 @@ def delete_resources_for_disabled_features():
     with open(MANIFEST) as manifest_file:
         manifest = json.load(manifest_file)
         for feature_name, feature in manifest.items():
-            if feature['enabled'].lower() != "true":
+            if feature["enabled"].lower() != "true":
                 text = "{} resources for disabled feature {}...".format(
                     colored("Removing", color="red"),
-                    colored(feature_name, color="magenta", attrs=['underline'])
+                    colored(feature_name, color="magenta", attrs=["underline"]),
                 )
                 print(text)
-                for resource in feature['resources']:
+                for resource in feature["resources"]:
                     delete_resource(resource)
     delete_resource(MANIFEST)
     cprint("cleanup complete!", color="green")
 
 
+def replace_resources():
+    print(
+        "⭐ Placing {} nicely in your {} ⭐".format(
+            colored("resources", color="green"), colored("new project", color="blue")
+        )
+    )
+    with open(REPLACE_MANIFEST) as replace_manifest:
+        manifest = json.load(replace_manifest)
+        for target, replaces in manifest.items():
+            target_path = Path(target)
+            delete_resource(target_path)
+            for src_file in map(Path, replaces):
+                if src_file.exists():
+                    shutil.move(src_file, target_path)
+    print(
+        "Resources are happy to be where {}.".format(
+            colored("they are needed the most", color="green", attrs=["underline"])
+        )
+    )
+
+
 def init_repo():
     repo_path = os.getcwd()
     repo = init_repository(repo_path)
@@ -52,4 +73,5 @@ def init_repo():
 
 if __name__ == "__main__":
     delete_resources_for_disabled_features()
+    replace_resources()
     init_repo()
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.flake8 b/fastapi_template/template/{{cookiecutter.project_name}}/.flake8
index a4b9f06..e724cd5 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/.flake8
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/.flake8
@@ -72,6 +72,8 @@ per-file-ignores =
   WPS210,
   ; Found magic number
   WPS432,
+  ; Missing parameter(s) in Docstring
+  DAR101,
 
   ; all init files
   __init__.py:
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/README.md b/fastapi_template/template/{{cookiecutter.project_name}}/README.md
index 8357b6c..1145b5a 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/README.md
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/README.md
@@ -31,7 +31,7 @@ docker save --output {{cookiecutter.project_name}}.tar {{cookiecutter.project_na
 ```
 
 {%- endif %}
-{%- if cookiecutter.enable_alembic == 'True' %}
+{%- if cookiecutter.enable_migrations == 'True' %}
 
 ## Migrations
 
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/aerich.ini b/fastapi_template/template/{{cookiecutter.project_name}}/aerich.ini
new file mode 100644
index 0000000..3ee2b52
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/aerich.ini
@@ -0,0 +1,4 @@
+[aerich]
+tortoise_orm = {{cookiecutter.project_name}}.db.config.TORTOISE_CONFIG
+location = ./{{cookiecutter.project_name}}/db/migrations
+src_folder = ./{{cookiecutter.project_name}}
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json
index 0b3b443..78ebd23 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json
@@ -17,11 +17,12 @@
     "Database support": {
         "enabled": "{{cookiecutter.db_info.name != 'none'}}",
         "resources": [
+            "aerich.ini",
+            "alembic.ini",
             "{{cookiecutter.project_name}}/web/api/dummy",
-            "{{cookiecutter.project_name}}/db",
+            "{{cookiecutter.project_name}}/db_sa",
             "{{cookiecutter.project_name}}/tests/test_dummy.py",
-            "deploy/kube/db.yml",
-            "alembic.ini"
+            "deploy/kube/db.yml"
         ]
     },
     "Postgres and MySQL support": {
@@ -30,21 +31,23 @@
             "deploy/kube/db.yml"
         ]
     },
-    "Alembic": {
-        "enabled": "{{cookiecutter.enable_alembic}}",
+    "Migrations": {
+        "enabled": "{{cookiecutter.enable_migrations}}",
         "resources": [
+            "aerich.ini",
             "alembic.ini",
-            "{{cookiecutter.project_name}}/db/migrations"
+            "{{cookiecutter.project_name}}/db_sa/migrations",
+            "{{cookiecutter.project_name}}/db_tortoise/migrations"
         ]
     },
     "Gitlab CI": {
-        "enabled": "{{cookiecutter.ci_type == 'CIType.gitlab_ci'}}",
+        "enabled": "{{cookiecutter.ci_type == 'gitlab_ci'}}",
         "resources": [
             ".gitlab-ci.yml"
         ]
     },
     "Github CI": {
-        "enabled": "{{cookiecutter.ci_type == 'CIType.github'}}",
+        "enabled": "{{cookiecutter.ci_type == 'github'}}",
         "resources": [
             ".github"
         ]
@@ -64,10 +67,15 @@
         "enabled": "{{cookiecutter.add_dummy}}",
         "resources": [
             "{{cookiecutter.project_name}}/web/api/dummy",
-            "{{cookiecutter.project_name}}/db/dao",
-            "{{cookiecutter.project_name}}/db/models/dummy_model.py",
+            "{{cookiecutter.project_name}}/db_sa/dao",
+            "{{cookiecutter.project_name}}/db_sa/models/dummy_model.py",
+            "{{cookiecutter.project_name}}/db_tortoise/dao",
+            "{{cookiecutter.project_name}}/db_tortoise/models/dummy_model.py",
             "{{cookiecutter.project_name}}/tests/test_dummy.py",
-            "{{cookiecutter.project_name}}/db/migrations/versions/2021-08-16-16-55_2b7380507a71.py"
+            "{{cookiecutter.project_name}}/db_sa/migrations/versions/2021-08-16-16-55_2b7380507a71.py",
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_pg.sql",
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_mysql.sql",
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_sqlite.sql"
         ]
     },
     "Self-hosted swagger": {
@@ -76,5 +84,40 @@
             "{{cookiecutter.project_name}}/web/static",
             "{{cookiecutter.project_name}}/web/api/docs"
         ]
+    },
+    "SQLAlchemy ORM": {
+        "enabled": "{{cookiecutter.orm == 'sqlalchemy'}}",
+        "resources": [
+            "alembic.ini",
+            "{{cookiecutter.project_name}}/db_sa"
+        ]
+    },
+    "Tortoise ORM": {
+        "enabled": "{{cookiecutter.orm == 'tortoise'}}",
+        "resources": [
+            "aerich.ini",
+            "{{cookiecutter.project_name}}/db_tortoise"
+        ]
+    },
+    "Postgresql DB": {
+        "enabled": "{{cookiecutter.db_info.name == 'postgresql'}}",
+        "resources": [
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_pg.sql",
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_pg.sql"
+        ]
+    },
+    "MySQL DB": {
+        "enabled": "{{cookiecutter.db_info.name == 'mysql'}}",
+        "resources": [
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_mysql.sql",
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_mysql.sql"
+        ]
+    },
+    "SQLite DB": {
+        "enabled": "{{cookiecutter.db_info.name == 'sqlite'}}",
+        "resources": [
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_sqlite.sql",
+            "{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_sqlite.sql"
+        ]
     }
 }
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml
index 80d1bf2..c93d33a 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml
@@ -76,17 +76,21 @@ services:
         - "-p{{cookiecutter.project_name}}"
         - "-e"
         - "SELECT 1"
-      interval: 2s
+      interval: 3s
       timeout: 3s
       retries: 40
     volumes:
       - {{cookiecutter.project_name}}-db-data:/bitnami/mysql/data
   {%- endif %}
 
-  {% if cookiecutter.enable_alembic == 'True' -%}
+  {% if cookiecutter.enable_migrations == 'True' -%}
   migrator:
     image: {{cookiecutter.project_name}}:{{"${" }}{{cookiecutter.project_name | upper }}_VERSION:-latest{{"}"}}
+    {%- if cookiecutter.orm == 'sqlalchemy' %}
     command: alembic upgrade head
+    {%- elif cookiecutter.orm == 'tortoise' %}
+    command: aerich upgrade
+    {%- endif %}
     {%- if cookiecutter.db_info.name == "sqlite" %}
     environment:
       {{cookiecutter.project_name | upper }}_DB_FILE: /db_data/db.sqlite3
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml
index cd337fb..e488897 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml
@@ -68,9 +68,14 @@ spec:
           - "180"
           - "{{cookiecutter.kube_name}}-db-service:{{cookiecutter.db_info.port}}"
           - "--"
+          {%- if cookiecutter.orm == 'sqlalchemy' %}
           - "alembic"
           - "upgrade"
           - "head"
+          {% elif cookiecutter.orm == 'tortoise' %}
+          - "aerich"
+          - "upgrade"
+          {%- endif %}
         env:
           - name: {{cookiecutter.project_name | upper }}_DB_HOST
             value: {{cookiecutter.kube_name}}-db-service
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml
index 12490b7..42060ce 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml
@@ -16,20 +16,33 @@ fastapi = "^0.68.0"
 uvicorn = "^0.15.0"
 pydantic = {version = "^1.8.2", extras = ["dotenv"]}
 yarl = "^1.6.3"
-{%- if cookiecutter.db_info.name != "none" %}
+{%- if cookiecutter.orm == "sqlalchemy" %}
 SQLAlchemy = {version = "^1.4", extras = ["mypy", "asyncio"]}
+{%- elif cookiecutter.orm == "tortoise" %}
+tortoise-orm = "^0.17.7"
 {%- endif %}
-{%- if cookiecutter.enable_alembic == "True" %}
+{%- if cookiecutter.enable_migrations == "True" %}
+{%- if cookiecutter.orm == "sqlalchemy" %}
 alembic = "^1.6.5"
+{%- elif cookiecutter.orm == "tortoise" %}
+aerich = "^0.5.8"
+{%- endif %}
 {%- endif %}
 {%- if cookiecutter.db_info.name == "postgresql" %}
+{%- if cookiecutter.orm == "sqlalchemy" %}
 asyncpg = {version = "^0.24.0", extras = ["sa"]}
+{%- elif cookiecutter.orm == "tortoise" %}
+asyncpg = {version = "^0.24.0"}
+{%- endif %}
 {%- endif %}
 {%- if cookiecutter.db_info.name == "sqlite" %}
 aiosqlite = "^0.17.0"
 {%- endif %}
 {%- if cookiecutter.db_info.name == "mysql" %}
 aiomysql = "^0.0.21"
+{%- if cookiecutter.orm == "tortoise" %}
+cryptography = "^3.4.8"
+{%- endif %}
 {%- endif %}
 {%- if cookiecutter.enable_redis == "True" %}
 aioredis = {version = "^2.0.0", extras = ["hiredis"]}
@@ -51,7 +64,7 @@ pre-commit = "^2.11.0"
 wemake-python-styleguide = "^0.15.3"
 black = "^21.7b0"
 autoflake = "^1.4"
-{%- if cookiecutter.db_info.name != "none" %}
+{%- if cookiecutter.orm == "sqlalchemy" %}
 SQLAlchemy = {version = "^1.4", extras = ["mypy"]}
 {%- endif %}
 pytest-cov = "^2.12.1"
@@ -62,6 +75,10 @@ pytest-env = "^0.6.2"
 fakeredis = "^1.6.1"
 {%- endif %}
 requests = "^2.26.0"
+{%- if cookiecutter.orm == "tortoise" %}
+asynctest = "^0.13.0"
+{%- endif %}
+
 
 [tool.isort]
 profile = "black"
@@ -76,11 +93,16 @@ pretty = true
 show_error_codes = true
 implicit_reexport = true
 allow_untyped_decorators = true
-{%- if cookiecutter.db_info.name != "none" %}
+warn_return_any = false
+{%- if cookiecutter.orm == "sqlalchemy" %}
 plugins = ["sqlalchemy.ext.mypy.plugin"]
 {%- endif %}
 
 [tool.pytest.ini_options]
+filterwarnings = [
+    "error",
+    "ignore::DeprecationWarning",
+]
 {%- if cookiecutter.db_info.name != "none" %}
 env = [
     {%- if cookiecutter.db_info.name == "sqlite" %}
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json b/fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json
new file mode 100644
index 0000000..81b49ba
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json
@@ -0,0 +1,6 @@
+{
+    "{{cookiecutter.project_name}}/db": [
+        "{{cookiecutter.project_name}}/db_sa",
+        "{{cookiecutter.project_name}}/db_tortoise"
+    ]
+}
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py
index 61da6c4..8ab5548 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py
@@ -1,6 +1,7 @@
 import asyncio
+from asyncio.events import AbstractEventLoop
 import sys
-from typing import Any, Generator, AsyncGenerator
+from typing import Generator, AsyncGenerator
 
 import pytest
 from fastapi import FastAPI
@@ -11,14 +12,22 @@ from {{cookiecutter.project_name}}.services.redis.dependency import get_redis_co
 {%- endif %}
 
 from {{cookiecutter.project_name}}.settings import settings
+from {{cookiecutter.project_name}}.web.application import get_app
+
 
 {%- if cookiecutter.db_info.name != "none" %}
+{%- if cookiecutter.orm == "sqlalchemy" %}
 from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, AsyncEngine, AsyncConnection
 from sqlalchemy.orm import sessionmaker
 from {{cookiecutter.project_name}}.db.dependencies import get_db_session
 from {{cookiecutter.project_name}}.db.utils import create_database, drop_database
+{%- elif cookiecutter.orm == "tortoise" %}
+from tortoise.contrib.test import finalizer, initializer, _restore_default  # noqa: WPS450
+from tortoise import Tortoise
+from {{cookiecutter.project_name}}.db.config import MODELS_MODULES
 {%- endif %}
-from {{cookiecutter.project_name}}.web.application import get_app
+{%- endif %}
+
 import nest_asyncio
 
 nest_asyncio.apply()
@@ -44,8 +53,8 @@ def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
     yield loop
     loop.close()
 
-
 {%- if cookiecutter.db_info.name != "none" %}
+{%- if cookiecutter.orm == "sqlalchemy" %}
 @pytest.fixture(scope="session")
 @pytest.mark.asyncio
 async def _engine() -> AsyncGenerator[AsyncEngine, None]:
@@ -113,6 +122,47 @@ async def transaction(_engine: AsyncEngine) -> AsyncGenerator[AsyncConnection, N
         yield conn
     finally:
         await conn.rollback()
+{%- elif cookiecutter.orm == "tortoise" %}
+
+@pytest.fixture(scope="session", autouse=True)
+def initialize_db(event_loop: AbstractEventLoop) -> Generator[None, None, None]:
+    """
+    Initialize models and database.
+
+    :param event_loop: Session-wide event loop.
+    :yields: Nothing.
+    """
+    initializer(
+        MODELS_MODULES,
+        db_url=str(settings.db_url),
+        app_label="models",
+        loop=event_loop,
+    )
+
+    yield
+
+    finalizer()
+
+@pytest.fixture(autouse=True)
+@pytest.mark.asyncio
+async def clean_db() -> AsyncGenerator[None, None]:
+    """
+    Removes all data from database after test.
+
+    :yields: Nothing.
+    """
+    yield
+
+    _restore_default()
+    for app in Tortoise.apps.values():
+        for model in app.values():
+            meta = model._meta  # noqa: WPS437
+            quote_char = meta.db.query_class._builder().QUOTE_CHAR  # noqa: WPS437
+            await meta.db.execute_script(
+                f"DELETE FROM {quote_char}{meta.db_table}{quote_char}" # noqa: S608
+            )
+
+{%- endif %}
 {%- endif %}
 
 
@@ -129,7 +179,7 @@ def fake_redis() -> FakeRedis:
 
 @pytest.fixture()
 def fastapi_app(
-    {%- if cookiecutter.db_info.name != "none" %}
+    {%- if cookiecutter.db_info.name != "none" and cookiecutter.orm == "sqlalchemy" %}
     dbsession: AsyncSession,
     {%- endif %}
     {% if cookiecutter.enable_redis == "True" -%}
@@ -139,16 +189,10 @@ def fastapi_app(
     """
     Fixture for creating FastAPI app.
 
-    {% if cookiecutter.db_info.name != "none" -%}
-    :param dbsession: test db session.
-    {% endif -%}
-    {% if cookiecutter.enable_redis == "True" -%}
-    :param fake_redis: Fake redis instance.
-    {% endif -%}
     :return: fastapi app with mocked dependencies.
     """
     application = get_app()
-    {%- if cookiecutter.db_info.name != "none" %}
+    {% if cookiecutter.db_info.name != "none" and cookiecutter.orm == "sqlalchemy" -%}
     application.dependency_overrides[get_db_session] = lambda: dbsession
     {%- endif %}
     {%- if cookiecutter.enable_redis == "True" %}
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/base.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/base.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/base.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/base.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/dao/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/__init__.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/dao/__init__.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/__init__.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/dao/dummy_dao.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/dummy_dao.py
similarity index 85%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/dao/dummy_dao.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/dummy_dao.py
index 03a51db..4bc6ca5 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/dao/dummy_dao.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/dummy_dao.py
@@ -1,7 +1,7 @@
 from typing import List, Optional
 from fastapi import Depends
 from sqlalchemy import select
-from sqlalchemy.ext.asyncio import AsyncScalarResult, AsyncSession
+from sqlalchemy.ext.asyncio import AsyncSession
 
 from {{cookiecutter.project_name}}.db.dependencies import get_db_session
 from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel
@@ -13,7 +13,7 @@ class DummyDAO:
     def __init__(self, session: AsyncSession = Depends(get_db_session)):
         self.session = session
 
-    def create_dummy_model(self, name: str) -> None:
+    async def create_dummy_model(self, name: str) -> None:
         """
         Add single dummy to session.
 
@@ -21,7 +21,7 @@ class DummyDAO:
         """
         self.session.add(DummyModel(name=name))
 
-    async def get_all_dummies(self, limit: int, offset: int) -> AsyncScalarResult:
+    async def get_all_dummies(self, limit: int, offset: int) -> List[DummyModel]:
         """
         Get all dummy models with limit/offset pagination.
 
@@ -29,11 +29,11 @@ class DummyDAO:
         :param offset: offset of dummies.
         :return: stream of dummies.
         """
-        raw_stream = await self.session.stream(
+        raw_dummies = await self.session.execute(
             select(DummyModel).limit(limit).offset(offset),
         )
 
-        return raw_stream.scalars()
+        return raw_dummies.scalars().fetchall()
 
     async def filter(
         self,
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/dependencies.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dependencies.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/dependencies.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dependencies.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/meta.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/meta.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/meta.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/meta.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/__init__.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/__init__.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/__init__.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/env.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/env.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/env.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/env.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/script.py.mako b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/script.py.mako
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/script.py.mako
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/script.py.mako
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/versions/2021-08-16-16-53_819cbf6e030b.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/versions/2021-08-16-16-53_819cbf6e030b.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/versions/2021-08-16-16-53_819cbf6e030b.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/versions/2021-08-16-16-53_819cbf6e030b.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/versions/2021-08-16-16-55_2b7380507a71.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/versions/2021-08-16-16-55_2b7380507a71.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/versions/2021-08-16-16-55_2b7380507a71.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/versions/2021-08-16-16-55_2b7380507a71.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/versions/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/versions/__init__.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/migrations/versions/__init__.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/migrations/versions/__init__.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/models/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/models/__init__.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/models/__init__.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/models/__init__.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/models/dummy_model.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/models/dummy_model.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/models/dummy_model.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/models/dummy_model.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/utils.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/utils.py
similarity index 100%
rename from fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/utils.py
rename to fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/utils.py
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py
new file mode 100644
index 0000000..e7e3737
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py
@@ -0,0 +1,16 @@
+from typing import List
+from {{cookiecutter.project_name}}.settings import settings
+
+MODELS_MODULES: List[str] = [{%- if cookiecutter.add_dummy == 'True' %}"{{cookiecutter.project_name}}.db.models.dummy_model"{%- endif %}]  # noqa: WPS407
+
+TORTOISE_CONFIG = {  # noqa: WPS407
+    "connections": {
+        "default": str(settings.db_url),
+    },
+    "apps": {
+        "models": {
+            "models": ["aerich.models"] + MODELS_MODULES,
+            "default_connection": "default",
+        },
+    },
+}
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/__init__.py
new file mode 100644
index 0000000..db62a0a
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/__init__.py
@@ -0,0 +1 @@
+"""DAO classes."""
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/dummy_dao.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/dummy_dao.py
new file mode 100644
index 0000000..7190725
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/dao/dummy_dao.py
@@ -0,0 +1,38 @@
+from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel
+from typing import List, Optional
+
+
+class DummyDAO:
+    """Class for accessing dummy table."""
+
+    async def create_dummy_model(self, name: str) -> None:
+        """
+        Add single dummy to session.
+
+        :param name: name of a dummy.
+        """
+        await DummyModel.create(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.
+        """
+        return (
+            await DummyModel.all().offset(offset).limit(limit)
+        )
+
+    async def filter(self, name: Optional[str] = None) -> List[DummyModel]:
+        """
+        Get specific dummy model.
+
+        :param name: name of dummy instance.
+        :return: dummy models.
+        """
+        query = DummyModel.all()
+        if name:
+            query = query.filter(name=name)
+        return await query
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_mysql.sql b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_mysql.sql
new file mode 100644
index 0000000..5bc531c
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_mysql.sql
@@ -0,0 +1,7 @@
+-- upgrade --
+CREATE TABLE IF NOT EXISTS `aerich` (
+    `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
+    `version` VARCHAR(255) NOT NULL,
+    `app` VARCHAR(20) NOT NULL,
+    `content` JSON NOT NULL
+) CHARACTER SET utf8mb4;
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_pg.sql b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_pg.sql
new file mode 100644
index 0000000..6b1ec93
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_pg.sql
@@ -0,0 +1,7 @@
+-- upgrade --
+CREATE TABLE IF NOT EXISTS "aerich" (
+    "id" SERIAL NOT NULL PRIMARY KEY,
+    "version" VARCHAR(255) NOT NULL,
+    "app" VARCHAR(20) NOT NULL,
+    "content" JSONB NOT NULL
+);
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_sqlite.sql b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_sqlite.sql
new file mode 100644
index 0000000..8644433
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/0_20210928165300_init_sqlite.sql
@@ -0,0 +1,7 @@
+-- upgrade --
+CREATE TABLE IF NOT EXISTS "aerich" (
+    "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    "version" VARCHAR(255) NOT NULL,
+    "app" VARCHAR(20) NOT NULL,
+    "content" JSON NOT NULL
+);
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_mysql.sql b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_mysql.sql
new file mode 100644
index 0000000..ea5fa9e
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_mysql.sql
@@ -0,0 +1,5 @@
+-- upgrade --
+CREATE TABLE IF NOT EXISTS `dummymodel` (
+    `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
+    `name` VARCHAR(200) NOT NULL
+) CHARACTER SET utf8mb4 COMMENT='Model for demo purpose.';
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_pg.sql b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_pg.sql
new file mode 100644
index 0000000..b84401d
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_pg.sql
@@ -0,0 +1,6 @@
+-- upgrade --
+CREATE TABLE IF NOT EXISTS "dummymodel" (
+    "id" SERIAL NOT NULL PRIMARY KEY,
+    "name" VARCHAR(200) NOT NULL
+);
+COMMENT ON TABLE "dummymodel" IS 'Model for demo purpose.';
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_sqlite.sql b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_sqlite.sql
new file mode 100644
index 0000000..23fc3e5
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_sqlite.sql
@@ -0,0 +1,5 @@
+-- upgrade --
+CREATE TABLE IF NOT EXISTS "dummymodel" (
+    "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    "name" VARCHAR(200) NOT NULL
+) /* Model for demo purpose. */;
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/__init__.py
new file mode 100644
index 0000000..f115875
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/__init__.py
@@ -0,0 +1 @@
+"""Models for {{cookiecutter.project_name}}."""
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/dummy_model.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/dummy_model.py
new file mode 100644
index 0000000..495cb8e
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/models/dummy_model.py
@@ -0,0 +1,11 @@
+from tortoise import models, fields
+
+
+class DummyModel(models.Model):
+    """Model for demo purpose."""
+
+    id = fields.IntField(pk=True)
+    name = fields.CharField(max_length=200)  # noqa: WPS432
+
+    def __str__(self) -> str:
+        return self.name
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py
index 11d9a82..89e199c 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py
@@ -49,12 +49,20 @@ class Settings(BaseSettings):
         """
         {%- if cookiecutter.db_info.name == "sqlite" %}
         return URL.build(
+            {%- if cookiecutter.orm == "sqlalchemy" %}
+            scheme="{{cookiecutter.db_info.async_driver}}",
+            {%- elif cookiecutter.orm == "tortoise" %}
             scheme="{{cookiecutter.db_info.driver}}",
+            {%- endif %}
             path=f"///{self.db_file}"
         )
-        {% else %}
+        {%- else %}
         return URL.build(
+            {%- if cookiecutter.orm == "sqlalchemy" %}
+            scheme="{{cookiecutter.db_info.async_driver}}",
+            {%- elif cookiecutter.orm == "tortoise" %}
             scheme="{{cookiecutter.db_info.driver}}",
+            {%- endif %}
             host=self.db_host,
             port=self.db_port,
             user=self.db_user,
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py
index 0d7e71e..bd518fe 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py
@@ -2,9 +2,7 @@ import uuid
 import pytest
 from fastapi.testclient import TestClient
 from fastapi import FastAPI
-from sqlalchemy import select
 from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.sql.functions import count
 from starlette import status
 from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel
 from {{cookiecutter.project_name}}.db.dao.dummy_dao import DummyDAO
@@ -13,26 +11,22 @@ from {{cookiecutter.project_name}}.db.dao.dummy_dao import DummyDAO
 async def test_creation(
     fastapi_app: FastAPI,
     client: TestClient,
-    dbsession: AsyncSession
+    {%- if cookiecutter.orm == "sqlalchemy" %}
+    dbsession: AsyncSession,
+    {%- endif %}
 ) -> None:
-    """
-    Tests dummy instance creation.
-
-    :param fastapi_app: current app fixture.
-    :param client: client for app.
-    :param dbsession: current db session.
-    """
+    """Tests dummy instance creation."""
     url = fastapi_app.url_path_for('create_dummy_model')
     test_name = uuid.uuid4().hex
     response = client.put(url, json={
         "name": test_name
     })
     assert response.status_code == status.HTTP_200_OK
-    instance_count = await dbsession.scalar(
-        select(count()).select_from(DummyModel)
-    )
-    assert instance_count == 1
+    {%- if cookiecutter.orm == "sqlalchemy" %}
     dao = DummyDAO(dbsession)
+    {%- elif cookiecutter.orm == "tortoise" %}
+    dao = DummyDAO()
+    {%- endif %}
     instances = await dao.filter(name=test_name)
     assert instances[0].name == test_name
 
@@ -41,19 +35,18 @@ async def test_creation(
 async def test_getting(
     fastapi_app: FastAPI,
     client: TestClient,
-    dbsession: AsyncSession
+    {%- if cookiecutter.orm == "sqlalchemy" %}
+    dbsession: AsyncSession,
+    {%- endif %}
 ) -> None:
-    """
-    Tests dummy instance retrieval.
-
-    :param fastapi_app: current app fixture.
-    :param client: client for app.
-    :param dbsession: current db session.
-    """
+    """Tests dummy instance retrieval."""
+    {%- if cookiecutter.orm == "sqlalchemy" %}
     dao = DummyDAO(dbsession)
+    {%- elif cookiecutter.orm == "tortoise" %}
+    dao = DummyDAO()
+    {%- endif %}
     test_name = uuid.uuid4().hex
-    dao.create_dummy_model(name=test_name)
-    await dbsession.commit()
+    await dao.create_dummy_model(name=test_name)
 
     url = fastapi_app.url_path_for('get_dummy_models')
     response = client.get(url)
@@ -62,4 +55,3 @@ async def test_getting(
     assert len(response_json) == 1
     assert response_json[0]['name'] == test_name
 
-
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py
index 44662fd..80fef78 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py
@@ -1,9 +1,10 @@
-from typing import Any, List
+from typing import List
 
 from fastapi import APIRouter
 from fastapi.param_functions import Depends
 
 from {{cookiecutter.project_name}}.db.dao.dummy_dao import DummyDAO
+from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel
 from {{cookiecutter.project_name}}.web.api.dummy.schema import DummyModelDTO, DummyModelInputDTO
 
 router = APIRouter()
@@ -14,7 +15,7 @@ async def get_dummy_models(
     limit: int = 10,
     offset: int = 0,
     dummy_dao: DummyDAO = Depends(),
-) -> Any:
+) -> List[DummyModel]:
     """
     Retrieve all dummy objects from database.
 
@@ -23,12 +24,11 @@ async def get_dummy_models(
     :param dummy_dao: DAO for dummy models.
     :return: list of dummy obbjects from database.
     """
-    dummies = await dummy_dao.get_all_dummies(limit=limit, offset=offset)
-    return await dummies.fetchall()
+    return await dummy_dao.get_all_dummies(limit=limit, offset=offset)
 
 
 @router.put("/")
-def create_dummy_model(
+async def create_dummy_model(
     new_dummy_object: DummyModelInputDTO,
     dummy_dao: DummyDAO = Depends(),
 ) -> None:
@@ -38,4 +38,4 @@ def create_dummy_model(
     :param new_dummy_object: new dummy model item.
     :param dummy_dao: DAO for dummy models.
     """
-    dummy_dao.create_dummy_model(**new_dummy_object.dict())
+    await dummy_dao.create_dummy_model(**new_dummy_object.dict())
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py
index 1f3d486..0731def 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py
@@ -4,10 +4,17 @@ from {{cookiecutter.project_name}}.web.api.router import api_router
 from {{cookiecutter.project_name}}.web.lifetime import shutdown, startup
 from importlib import metadata
 
+{%- if cookiecutter.orm == 'tortoise' %}
+from tortoise.contrib.fastapi import register_tortoise
+from {{cookiecutter.project_name}}.db.config import TORTOISE_CONFIG
+{%- endif %}
+
+
 {%- if cookiecutter.self_hosted_swagger == 'True' %}
 from fastapi.staticfiles import StaticFiles
 from pathlib import Path
 
+
 APP_ROOT = Path(__file__).parent.parent
 {%- endif %}
 
@@ -47,4 +54,8 @@ def get_app() -> FastAPI:
     )
     {% endif %}
 
+    {%- if cookiecutter.orm == 'tortoise' %}
+    register_tortoise(app, config=TORTOISE_CONFIG, add_exception_handlers=True)
+    {%- endif %}
+
     return app
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py
index 22ac7be..b69107a 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py
@@ -8,7 +8,7 @@ from {{cookiecutter.project_name}}.settings import settings
 import aioredis
 {%- endif %}
 
-{%- if cookiecutter.db_info.name != "none" %}
+{%- if cookiecutter.db_info.name != "none" and cookiecutter.orm == "sqlalchemy" %}
 from asyncio import current_task
 from sqlalchemy.ext.asyncio import (
     AsyncSession,
@@ -16,10 +16,8 @@ from sqlalchemy.ext.asyncio import (
     create_async_engine,
 )
 from sqlalchemy.orm import sessionmaker
-{%- endif %}
 
 
-{% if cookiecutter.db_info.name != "none" %}
 def _setup_db(app: FastAPI) -> None:
     """
     Create connection to the database.
@@ -63,7 +61,7 @@ def startup(app: FastAPI) -> Callable[[], Awaitable[None]]:
     """
 
     async def _startup() -> None:  # noqa: WPS430
-        {%- if cookiecutter.db_info.name != "none" %}
+        {%- if cookiecutter.db_info.name != "none" and cookiecutter.orm == "sqlalchemy" %}
         _setup_db(app)
         {%- endif %}
         {%- if cookiecutter.enable_redis == "True" %}
@@ -83,7 +81,7 @@ def shutdown(app: FastAPI) -> Callable[[], Awaitable[None]]:
     """
 
     async def _shutdown() -> None:  # noqa: WPS430
-        {%- if cookiecutter.db_info.name != "none" %}
+        {%- if cookiecutter.db_info.name != "none" and cookiecutter.orm == "sqlalchemy" %}
         await app.state.db_engine.dispose()
         {%- endif %}
         {%- if cookiecutter.enable_redis == "True" %}
diff --git a/fastapi_template/tests/conftest.py b/fastapi_template/tests/conftest.py
index 320dbaa..b54a50d 100644
--- a/fastapi_template/tests/conftest.py
+++ b/fastapi_template/tests/conftest.py
@@ -5,7 +5,7 @@ import tempfile
 import shutil
 from faker import Faker
 from pathlib import Path
-from fastapi_template.input_model import BuilderContext, CIType
+from fastapi_template.input_model import DB_INFO, BuilderContext, CIType, DatabaseType
 from fastapi_template.tests.utils import run_docker_compose_command
 
 
@@ -18,7 +18,7 @@ def project_name() -> str:
     """
     fake = Faker()
     raw_name: str = fake.name_female()
-    return raw_name.lower().replace(" ", "_").replace("-", "_")
+    return raw_name.lower().replace(" ", "_").replace("-", "_").replace(".", "_")
 
 
 @pytest.fixture(scope="session", autouse=True)
@@ -39,7 +39,7 @@ def generator_start_dir() -> str:
 
 
 @pytest.fixture()
-def defautl_context(project_name: str) -> None:
+def default_context(project_name: str) -> None:
     """
     Default builder context without features.
 
@@ -51,11 +51,13 @@ def defautl_context(project_name: str) -> None:
         kube_name=project_name.replace("_", "-"),
         project_description="Generated by pytest.",
         ci_type=CIType.none,
+        db=DatabaseType.none,
+        db_info=DB_INFO[DatabaseType.none],
         enable_redis=False,
-        enable_alembic=True,
+        enable_migrations=False,
         enable_kube=False,
         enable_routers=True,
-        add_dummy=True,
+        add_dummy=False,
         self_hosted_swagger=False,
         force=True,
     )
@@ -87,7 +89,7 @@ def default_dir(generator_start_dir: str) -> None:
         os.chdir(generator_start_dir)
 
 
-@pytest.fixture(scope="module", autouse=True)
+@pytest.fixture(autouse=True)
 def docker_module_shutdown(generator_start_dir: str, project_name: str) -> None:
     """
     Cleans up docker context.
@@ -98,6 +100,8 @@ def docker_module_shutdown(generator_start_dir: str, project_name: str) -> None:
     yield
     cwd = os.getcwd()
     project_dir = Path(generator_start_dir) / project_name
+    if not project_dir.exists():
+        return
     os.chdir(project_dir)
     run_docker_compose_command("down -v")
     os.chdir(cwd)
@@ -114,6 +118,8 @@ def docker_shutdown(generator_start_dir: str, project_name: str) -> None:
     yield
     cwd = os.getcwd()
     project_dir = Path(generator_start_dir) / project_name
+    if not project_dir.exists():
+        return
     os.chdir(project_dir)
     run_docker_compose_command("down -v --rmi=all")
     os.chdir(cwd)
diff --git a/fastapi_template/tests/test_generator.py b/fastapi_template/tests/test_generator.py
new file mode 100644
index 0000000..8fc80c4
--- /dev/null
+++ b/fastapi_template/tests/test_generator.py
@@ -0,0 +1,66 @@
+from typing import Optional
+from fastapi_template.tests.utils import run_default_check
+import pytest
+
+from fastapi_template.input_model import ORM, BuilderContext, DatabaseType, DB_INFO
+
+
+def init_context(
+    context: BuilderContext, db: DatabaseType, orm: Optional[ORM]
+) -> BuilderContext:
+    context.db = db
+    context.db_info = DB_INFO[db]
+    context.orm = orm
+
+    context.enable_migrations = db != DatabaseType.none
+    context.add_dummy = db != DatabaseType.none    
+
+    return context
+
+
+def test_default_without_db(default_context: BuilderContext):
+    run_default_check(init_context(default_context, DatabaseType.none, None))
+
+
+@pytest.mark.parametrize(
+    "db",
+    [
+        DatabaseType.postgresql,
+        DatabaseType.sqlite,
+        DatabaseType.mysql,
+    ],
+)
+@pytest.mark.parametrize("orm", [ORM.sqlalchemy, ORM.tortoise])
+def test_default_with_db(default_context: BuilderContext, db: DatabaseType, orm: ORM):
+    run_default_check(init_context(default_context, db, orm))
+
+
+@pytest.mark.parametrize("orm", [ORM.sqlalchemy, ORM.tortoise])
+def test_without_routers(default_context: BuilderContext, orm: ORM):
+    context = init_context(default_context, DatabaseType.postgresql, orm)
+    context.enable_routers = False
+    run_default_check(context)
+
+
+@pytest.mark.parametrize("orm", [ORM.sqlalchemy, ORM.tortoise])
+def test_without_migrations(default_context: BuilderContext, orm: ORM):
+    context = init_context(default_context, DatabaseType.postgresql, orm)
+    context.enable_migrations = False
+    run_default_check(context)
+
+
+def test_with_selfhosted_swagger(default_context: BuilderContext):
+    default_context.self_hosted_swagger = True
+    run_default_check(default_context)
+
+
+@pytest.mark.parametrize("orm", [ORM.sqlalchemy, ORM.tortoise])
+def test_without_dummy(default_context: BuilderContext, orm: ORM):
+    context = init_context(default_context, DatabaseType.postgresql, orm)
+    context.add_dummy = False
+    run_default_check(context)
+
+
+def test_redis(default_context: BuilderContext):
+    default_context.enable_redis = True
+    run_default_check(default_context)
diff --git a/fastapi_template/tests/test_mysql.py b/fastapi_template/tests/test_mysql.py
deleted file mode 100644
index ae54671..0000000
--- a/fastapi_template/tests/test_mysql.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from fastapi_template.tests.utils import run_default_check
-import pytest
-
-from fastapi_template.input_model import BuilderContext, DatabaseType, DB_INFO
-
-
-@pytest.fixture()
-def mysql_context(defautl_context: BuilderContext) -> BuilderContext:
-    defautl_context.db = DatabaseType.mysql
-    defautl_context.db_info = DB_INFO[DatabaseType.mysql]
-
-    return defautl_context
-
-
-@pytest.mark.mysql
-def test_default_mysql(mysql_context: BuilderContext):
-    run_default_check(mysql_context)
-
-
-@pytest.mark.mysql
-def test_mysql_without_routers(mysql_context: BuilderContext):
-    mysql_context.enable_routers = False
-    run_default_check(mysql_context)
-
-
-@pytest.mark.mysql
-def test_mysql_without_alembic(mysql_context: BuilderContext):
-    mysql_context.enable_alembic = False
-    run_default_check(mysql_context)
-
-
-@pytest.mark.mysql
-def test_mysql_with_selfhosted_swagger(mysql_context: BuilderContext):
-    mysql_context.self_hosted_swagger = True
-    run_default_check(mysql_context)
-
-
-@pytest.mark.mysql
-def test_mysql_without_dummy(mysql_context: BuilderContext):
-    mysql_context.add_dummy = False
-    run_default_check(mysql_context)
-
-
-@pytest.mark.mysql
-def test_mysql_and_redis(mysql_context: BuilderContext):
-    mysql_context.enable_redis = True
-    run_default_check(mysql_context)
diff --git a/fastapi_template/tests/test_postgres.py b/fastapi_template/tests/test_postgres.py
deleted file mode 100644
index 8fbc28f..0000000
--- a/fastapi_template/tests/test_postgres.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from fastapi_template.tests.utils import run_default_check
-import pytest
-
-from fastapi_template.input_model import BuilderContext, DatabaseType, DB_INFO
-
-
-@pytest.fixture()
-def pg_context(defautl_context: BuilderContext) -> BuilderContext:
-    defautl_context.db = DatabaseType.postgresql
-    defautl_context.db_info = DB_INFO[DatabaseType.postgresql]
-
-    return defautl_context
-
-
-@pytest.mark.pg
-def test_default_pg(pg_context: BuilderContext):
-    run_default_check(pg_context)
-
-
-@pytest.mark.pg
-def test_pg_without_routers(pg_context: BuilderContext):
-    pg_context.enable_routers = False
-    run_default_check(pg_context)
-
-
-@pytest.mark.pg
-def test_pg_without_alembic(pg_context: BuilderContext):
-    pg_context.enable_alembic = False
-    run_default_check(pg_context)
-
-
-@pytest.mark.pg
-def test_pg_with_selfhosted_swagger(pg_context: BuilderContext):
-    pg_context.self_hosted_swagger = True
-    run_default_check(pg_context)
-
-
-@pytest.mark.pg
-def test_pg_without_dummy(pg_context: BuilderContext):
-    pg_context.add_dummy = False
-    run_default_check(pg_context)
-
-
-@pytest.mark.pg
-def test_pg_and_redis(pg_context: BuilderContext):
-    pg_context.enable_redis = True
-    run_default_check(pg_context)
diff --git a/fastapi_template/tests/test_sqlite.py b/fastapi_template/tests/test_sqlite.py
deleted file mode 100644
index e7c5145..0000000
--- a/fastapi_template/tests/test_sqlite.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from fastapi_template.tests.utils import run_default_check
-import pytest
-
-from fastapi_template.input_model import BuilderContext, DatabaseType, DB_INFO
-
-
-@pytest.fixture()
-def sqlite_context(defautl_context: BuilderContext) -> BuilderContext:
-    defautl_context.db = DatabaseType.sqlite
-    defautl_context.db_info = DB_INFO[DatabaseType.sqlite]
-
-    return defautl_context
-
-
-@pytest.mark.sqlite
-def test_default_sqlite(sqlite_context: BuilderContext):
-    run_default_check(sqlite_context)
-
-
-@pytest.mark.sqlite
-def test_sqlite_without_routers(sqlite_context: BuilderContext):
-    sqlite_context.enable_routers = False
-    run_default_check(sqlite_context)
-
-
-@pytest.mark.sqlite
-def test_sqlite_without_alembic(sqlite_context: BuilderContext):
-    sqlite_context.enable_alembic = False
-    run_default_check(sqlite_context)
-
-
-@pytest.mark.sqlite
-def test_sqlite_with_selfhosted_swagger(sqlite_context: BuilderContext):
-    sqlite_context.self_hosted_swagger = True
-    run_default_check(sqlite_context)
-
-
-@pytest.mark.sqlite
-def test_sqlite_without_dummy(sqlite_context: BuilderContext):
-    sqlite_context.add_dummy = False
-    run_default_check(sqlite_context)
-
-
-@pytest.mark.sqlite
-def test_sqlite_and_redis(sqlite_context: BuilderContext):
-    sqlite_context.enable_redis = True
-    run_default_check(sqlite_context)
diff --git a/pyproject.toml b/pyproject.toml
index 1257b57..b944f93 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "fastapi_template"
-version = "2.5.0"
+version = "3.0.0"
 description = "Feature-rich robust FastAPI template"
 authors = ["Pavel Kirilin <win10@list.ru>"]
 packages = [{ include = "fastapi_template" }]
-- 
GitLab