From 2f36891bc502ce9999ddc0065f5cc0a3c120709b Mon Sep 17 00:00:00 2001
From: Pavel Kirilin <win10@list.ru>
Date: Mon, 27 Sep 2021 18:47:58 +0400
Subject: [PATCH] Added tests for generated project. (#16)

* Added tests for generated project.
* Added tests for template generator.
* Bumped version.

Closes #15, #7.
---
 .github/workflows/test.yml                    |  19 +
 .gitignore                                    |   5 +
 fastapi_template/__main__.py                  |  21 +-
 fastapi_template/cli.py                       |  32 +-
 fastapi_template/input_model.py               |  23 +
 .../{{cookiecutter.project_name}}/.env        |   5 +
 .../{{cookiecutter.project_name}}/.flake8     |  28 +-
 .../.github/workflows/tests.yml               |   2 +-
 .../.gitlab-ci.yml                            |   4 +-
 .../.pre-commit-config.yaml                   | 119 ++-
 .../{{cookiecutter.project_name}}/README.md   |  44 +-
 .../conditional_files.json                    |   9 +-
 .../deploy/Dockerfile                         |   2 +-
 .../deploy/docker-compose.yml                 |  91 ++-
 .../pyproject.toml                            |  23 +-
 .../{{cookiecutter.project_name}}/conftest.py | 142 +++-
 .../db/dao/dummy_dao.py                       |  19 +-
 .../db/migrations/env.py                      |   5 +-
 .../{{cookiecutter.project_name}}/db/utils.py |  87 +++
 .../{{cookiecutter.project_name}}/settings.py |   2 +-
 .../tests/test_dummy.py                       |  65 ++
 .../tests/test_echo.py                        |  21 +
 .../tests/test_redis.py                       |  57 ++
 .../test_{{cookiecutter.project_name}}.py     |  17 +-
 .../web/api/docs/views.py                     |  23 +-
 .../web/api/dummy/views.py                    |   4 +-
 .../web/api/monitoring/__init__.py            |   4 +
 .../web/api/monitoring/views.py               |  11 +
 .../web/api/redis/views.py                    |   4 +-
 .../web/api/router.py                         |   3 +-
 .../web/lifetime.py                           |   4 +-
 fastapi_template/tests/conftest.py            | 119 +++
 fastapi_template/tests/test_mysql.py          |  47 ++
 fastapi_template/tests/test_postgres.py       |  47 ++
 fastapi_template/tests/test_sqlite.py         |  47 ++
 fastapi_template/tests/utils.py               |  43 ++
 poetry.lock                                   | 705 ------------------
 pyproject.toml                                |  28 +-
 38 files changed, 1043 insertions(+), 888 deletions(-)
 create mode 100644 .github/workflows/test.yml
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/utils.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_echo.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_redis.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/__init__.py
 create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/views.py
 create mode 100644 fastapi_template/tests/conftest.py
 create mode 100644 fastapi_template/tests/test_mysql.py
 create mode 100644 fastapi_template/tests/test_postgres.py
 create mode 100644 fastapi_template/tests/test_sqlite.py
 create mode 100644 fastapi_template/tests/utils.py
 delete mode 100644 poetry.lock

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..b218488
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,19 @@
+name: Testing fastapi-template
+
+on: push
+
+jobs:
+  pytest:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: '3.9'
+      - name: Install deps
+        uses: knowsuchagency/poetry-install@v1
+        env:
+          POETRY_VIRTUALENVS_CREATE: false
+      - name: Run tests
+        run: poetry run pytest -vv
diff --git a/.gitignore b/.gitignore
index 6f47b3e..10676df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,9 @@
 .idea
+.vscode
+
+{%- if cookiecutter.db_info.name == "sqlite" %}
+*.sqlite3
+{%- endif %}
 
 # Byte-compiled / optimized / DLL files
 __pycache__/
diff --git a/fastapi_template/__main__.py b/fastapi_template/__main__.py
index 8e52abe..818ed6e 100644
--- a/fastapi_template/__main__.py
+++ b/fastapi_template/__main__.py
@@ -10,12 +10,12 @@ from fastapi_template.input_model import BuilderContext
 script_dir = Path(__file__).parent
 
 
-def main():
-    try:
-        context = get_context()
-    except KeyboardInterrupt:
-        print("Goodbye!")
-        return
+def generate_project(context: BuilderContext) -> None:
+    """
+    Generate actual project with given context.
+
+    :param context: builder_context
+    """
     try:
         cookiecutter(
             template=f"{script_dir}/template",
@@ -33,5 +33,14 @@ def main():
     )
 
 
+def main() -> None:
+    """Starting point."""
+    try:
+        context = get_context()
+    except KeyboardInterrupt:
+        print("Goodbye!")
+        return
+    generate_project(context)
+
 if __name__ == "__main__":
     main()
diff --git a/fastapi_template/cli.py b/fastapi_template/cli.py
index 81bd204..ff9715f 100644
--- a/fastapi_template/cli.py
+++ b/fastapi_template/cli.py
@@ -7,7 +7,7 @@ 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, Database, DatabaseType, CIType
+from fastapi_template.input_model import BuilderContext, DB_INFO, DatabaseType, CIType
 
 
 class SnakeCaseValidator(Validator):
@@ -17,30 +17,6 @@ class SnakeCaseValidator(Validator):
             raise ValidationError(message="Must be a valid snake_case name.")
 
 
-DB_INFO = {
-    DatabaseType.none: Database(
-        name="none",
-        image="none",
-        driver=None,
-        port=None,
-    ),
-    DatabaseType.postgresql: Database(
-        name=DatabaseType.postgresql.value,
-        image="postgres:13.4-buster",
-        driver="postgresql+asyncpg",
-        port=5432,
-    ),
-    DatabaseType.mysql: Database(
-        name=DatabaseType.mysql.value,
-        image="bitnami/mysql:8.0.26",
-        driver="mysql+aiomysql",
-        port=3306,
-    ),
-    DatabaseType.sqlite: Database(
-        name=DatabaseType.sqlite.value, image=None, driver="sqlite+aiosqlite", port=None
-    ),
-}
-
 def parse_args():
     parser = ArgumentParser(
         prog="FastAPI template",
@@ -157,7 +133,6 @@ def ask_features(current_context: BuilderContext) -> BuilderContext:
             "name": "add_dummy",
             "value": current_context.add_dummy
         }
-
     checkbox_values = []
     for feature_name, feature in features.items():
         if feature["value"] is None:
@@ -192,8 +167,9 @@ def read_user_input(current_context: BuilderContext) -> BuilderContext:
         ).run()
         if current_context.db is None:
             raise KeyboardInterrupt()
-        if current_context.db == DatabaseType.none:
-            current_context.enable_alembic = False
+    if current_context.db == DatabaseType.none:
+        current_context.enable_alembic = False
+        current_context.add_dummy = False
     if current_context.ci_type is None:
         current_context.ci_type = radiolist_dialog(
             "CI",
diff --git a/fastapi_template/input_model.py b/fastapi_template/input_model.py
index 5b2c7c2..edd41cd 100644
--- a/fastapi_template/input_model.py
+++ b/fastapi_template/input_model.py
@@ -25,6 +25,29 @@ class Database(BaseModel):
     driver: Optional[str]
     port: Optional[int]
 
+DB_INFO = {
+    DatabaseType.none: Database(
+        name="none",
+        image=None,
+        driver=None,
+        port=None,
+    ),
+    DatabaseType.postgresql: Database(
+        name=DatabaseType.postgresql.value,
+        image="postgres:13.4-buster",
+        driver="postgresql+asyncpg",
+        port=5432,
+    ),
+    DatabaseType.mysql: Database(
+        name=DatabaseType.mysql.value,
+        image="bitnami/mysql:8.0.26",
+        driver="mysql+aiomysql",
+        port=3306,
+    ),
+    DatabaseType.sqlite: Database(
+        name=DatabaseType.sqlite.value, image=None, driver="sqlite+aiosqlite", port=None
+    ),
+}
 
 class BuilderContext(BaseModel):
     """Options for project generation."""
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.env b/fastapi_template/template/{{cookiecutter.project_name}}/.env
index 8d89e98..0a784e0 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/.env
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/.env
@@ -1 +1,6 @@
 {{cookiecutter.project_name | upper}}_RELOAD=True
+{%- if cookiecutter.db_info.name == "sqlite" %}
+{{cookiecutter.project_name | upper}}_DB_FILE=db.sqlite3
+{%- else %}
+{{cookiecutter.project_name | upper}}_DB_HOST=localhost
+{%- endif %}
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.flake8 b/fastapi_template/template/{{cookiecutter.project_name}}/.flake8
index 6572ef1..a4b9f06 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/.flake8
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/.flake8
@@ -14,6 +14,8 @@ ignore =
   D105,
   ; Missing docstring in __init__
   D107,
+  ; Found `__init__.py` module with logic
+  WPS412,
   ; Found class without a base class
   WPS306,
   ; Missing docstring in public nested class
@@ -21,16 +23,12 @@ ignore =
   ; First line should be in imperative mood
   D401,
   ; Found `__init__.py` module with logic
-  WPS412,
-  ; Found implicit string concatenation
   WPS326,
   ; Found string constant over-use
   WPS226,
   ; Found upper-case constant in a class
   WPS115,
   ; Found nested function
-  WPS430,
-  ; Found using `@staticmethod`
   WPS602,
   ; Found method without arguments
   WPS605,
@@ -44,8 +42,6 @@ ignore =
   W503,
   ; Found module with too many imports
   WPS201,
-  ; Found vague import that may cause confusion: X
-  WPS347,
   ; Inline strong start-string without end-string.
   RST210,
   ; Found nested class
@@ -65,6 +61,18 @@ ignore =
   ;  not perform function calls in argument defaults (for dependency injection)
   B008,
 
+per-file-ignores =
+  ; all tests
+  test_*.py,tests.py,tests_*.py,*/tests/*,conftest.py:
+  ; Use of assert detected
+  S101,
+  ; Found outer scope names shadowing
+  WPS442,
+  ; Found too many local variables
+  WPS210,
+  ; Found magic number
+  WPS432,
+
   ; all init files
   __init__.py:
   ; ignore not used imports
@@ -74,14 +82,6 @@ ignore =
   ; Found wrong metadata variable
   WPS410,
 
-per-file-ignores =
-  ; all tests
-  test_*.py,tests.py,tests_*.py,*/tests/*:
-  ; Use of assert detected
-  S101,
-  ; Found outer scope names shadowing
-  WPS442,
-
 exclude =
   ./.git,
   ./venv,
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml b/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml
index f4a9a18..a869b8d 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml
@@ -85,7 +85,7 @@ jobs:
         env:
           POETRY_VIRTUALENVS_CREATE: false
       - name: Run pytest check
-        run: poetry run pytest -vv --cov="{{cookiecutter.project_name}}" . {% if cookiecutter.enable_alembic %}--test-alembic{%- endif %}
+        run: poetry run pytest -vv --cov="{{cookiecutter.project_name}}" .
         {%- if cookiecutter.db_info.name != "none" %}
         {%- if cookiecutter.db_info.name != "sqlite" %}
         env:
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml b/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml
index b568e79..094d90e 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml
@@ -39,7 +39,6 @@ pytest:
   {%- if cookiecutter.db_info.name != "sqlite" %}
   services:
     - name: {{ cookiecutter.db_info.image }}
-  {%- endif %}
   variables:
     {%- if cookiecutter.db_info.name == "postgresql" %}
     {{ cookiecutter.project_name | upper }}_DB_HOST: localhost
@@ -55,6 +54,7 @@ pytest:
     ALLOW_EMPTY_PASSWORD: yes
     {%- endif %}
   {%- endif %}
+  {%- endif %}
   script:
     {%- if cookiecutter.db_info.name != "none" %}
     {%- if cookiecutter.db_info.name != "sqlite" %}
@@ -63,4 +63,4 @@ pytest:
     - wait-for-it -t 180 ${{ cookiecutter.project_name | upper }}_DB_HOST:{{cookiecutter.db_info.port}}
     {%- endif %}
     {%- endif %}
-    - pytest -vv --cov="{{cookiecutter.project_name}}" . {% if cookiecutter.enable_alembic %}--test-alembic{%- endif %}
+    - pytest -vv --cov="{{cookiecutter.project_name}}" .
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml
index 294b534..2dac1b2 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml
@@ -1,63 +1,62 @@
+---
 # See https://pre-commit.com for more information
 # See https://pre-commit.com/hooks.html for more hooks
 repos:
-  - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v2.4.0
-    hooks:
-      - id: check-ast
-      - id: trailing-whitespace
-      - id: check-toml
-      - id: end-of-file-fixer
-
-  - repo: https://github.com/asottile/add-trailing-comma
-    rev: v2.1.0
-    hooks:
-    -   id: add-trailing-comma
-
-  - repo: local
-    hooks:
-      - id: black
-        name: Format with Black
-        entry: poetry run black
-        language: system
-        types: [python]
-
-      - id: autoflake
-        name: autoflake
-        entry: poetry run autoflake
-        language: system
-        types: [ python ]
-        args: [ --in-place, --remove-all-unused-imports, --remove-duplicate-keys ]
-
-      - id: isort
-        name: isort
-        entry: poetry run isort
-        language: system
-        types: [ python ]
-
-      - id: flake8
-        name: Check with Flake8
-        entry: poetry run flake8
-        language: system
-        pass_filenames: false
-        types: [ python ]
-        args: [--count, .]
-
-      - id: mypy
-        name: Validate types with MyPy
-        entry: poetry run mypy
-        language: system
-        types: [ python ]
-
-      - id: yesqa
-        name: Remove usless noqa
-        entry: poetry run yesqa
-        language: system
-        types: [ python ]
-
-      - id: pytest
-        name: pytest
-        entry: poetry run pytest
-        language: system
-        pass_filenames: false
-        types: [ python ]
+    - repo: https://github.com/pre-commit/pre-commit-hooks
+      rev: v2.4.0
+      hooks:
+          - id: check-ast
+          - id: trailing-whitespace
+          - id: check-toml
+          - id: end-of-file-fixer
+
+    - repo: https://github.com/asottile/add-trailing-comma
+      rev: v2.1.0
+      hooks:
+          - id: add-trailing-comma
+
+    - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt
+      rev: 0.0.11 # or specific tag
+      hooks:
+          - id: yamlfmt
+
+    - repo: local
+      hooks:
+          - id: black
+            name: Format with Black
+            entry: poetry run black
+            language: system
+            types: [python]
+
+          - id: autoflake
+            name: autoflake
+            entry: poetry run autoflake
+            language: system
+            types: [python]
+            args: [--in-place, --remove-all-unused-imports, --remove-duplicate-keys]
+
+          - id: isort
+            name: isort
+            entry: poetry run isort
+            language: system
+            types: [python]
+
+          - id: flake8
+            name: Check with Flake8
+            entry: poetry run flake8
+            language: system
+            pass_filenames: false
+            types: [python]
+            args: [--count, .]
+
+          - id: mypy
+            name: Validate types with MyPy
+            entry: poetry run mypy
+            language: system
+            types: [python]
+
+          - id: yesqa
+            name: Remove usless noqa
+            entry: poetry run yesqa
+            language: system
+            types: [python]
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/README.md b/fastapi_template/template/{{cookiecutter.project_name}}/README.md
index a0ea83d..8357b6c 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/README.md
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/README.md
@@ -2,14 +2,14 @@
 
 Start a project with:
 
-```
+```bash
 docker-compose -f deploy/docker-compose.yml --project-directory . up
 ```
 
 ## Pre-commit
 
 To install pre-commit simply run inside of the shell:
-```
+```bash
 pre-commit install
 ```
 {%- if cookiecutter.enable_kube == 'True' %}
@@ -17,7 +17,7 @@ pre-commit install
 ## Kubernetes
 To run your app in kubernetes
 just run:
-```
+```bash
 kubectl apply -f deploy/kube
 ```
 
@@ -25,7 +25,7 @@ It will create needed components.
 
 If you hasn't pushed to docker registry yet, you can build image locally.
 
-```
+```bash
 docker-compose -f deploy/docker-compose.yml --project-directory . build
 docker save --output {{cookiecutter.project_name}}.tar {{cookiecutter.project_name}}:latest
 ```
@@ -33,7 +33,7 @@ docker save --output {{cookiecutter.project_name}}.tar {{cookiecutter.project_na
 {%- endif %}
 {%- if cookiecutter.enable_alembic == 'True' %}
 
-# Migrations
+## Migrations
 
 If you want to migrate your database, you should run following commands:
 ```bash
@@ -67,3 +67,37 @@ alembic revision
 ```
 
 {%- endif %}
+
+
+## Running tests
+
+If you want to run it in docker, simply run:
+
+```bash
+docker-compose -f deploy/docker-compose.yml --project-directory . run --rm api pytest -vv .
+docker-compose -f deploy/docker-compose.yml --project-directory . down
+```
+
+For running tests on your local machine.
+
+{%- if cookiecutter.db_info.name != "none" %}
+{%- if cookiecutter.db_info.name != "sqlite" %}
+1. you need to start a database.
+
+I prefer doing it with docker:
+```
+{%- if cookiecutter.db_info.name == "postgresql" %}
+docker run -p "{{cookiecutter.db_info.port}}:{{cookiecutter.db_info.port}}" -e "POSTGRES_PASSWORD={{cookiecutter.project_name}}" -e "POSTGRES_USER={{cookiecutter.project_name}}" -e "POSTGRES_DB={{cookiecutter.project_name}}" {{cookiecutter.db_info.image}}
+{%- endif %}
+{%- if cookiecutter.db_info.name == "mysql" %}
+docker run -p "{{cookiecutter.db_info.port}}:{{cookiecutter.db_info.port}}" -e "MYSQL_PASSWORD={{cookiecutter.project_name}}" -e "MYSQL_USER={{cookiecutter.project_name}}" -e "MYSQL_DATABASE={{cookiecutter.project_name}}" -e ALLOW_EMPTY_PASSWORD=yes {{cookiecutter.db_info.image}}
+{%- endif %}
+```
+{%- endif %}
+{%- endif %}
+
+
+2. Run the pytest.
+```bash
+pytest -vv .
+```
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json
index bc3495c..0b3b443 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json
@@ -4,6 +4,7 @@
         "resources": [
             "{{cookiecutter.project_name}}/web/api/redis",
             "{{cookiecutter.project_name}}/services/redis",
+            "{{cookiecutter.project_name}}/tests/test_redis.py",
             "deploy/kube/redis.yml"
         ]
     },
@@ -18,7 +19,7 @@
         "resources": [
             "{{cookiecutter.project_name}}/web/api/dummy",
             "{{cookiecutter.project_name}}/db",
-            "{{cookiecutter.project_name}}/conftest.py",
+            "{{cookiecutter.project_name}}/tests/test_dummy.py",
             "deploy/kube/db.yml",
             "alembic.ini"
         ]
@@ -53,7 +54,10 @@
         "resources": [
             "{{cookiecutter.project_name}}/web/api/echo",
             "{{cookiecutter.project_name}}/web/api/dummy",
-            "{{cookiecutter.project_name}}/web/api/redis"
+            "{{cookiecutter.project_name}}/web/api/redis",
+            "{{cookiecutter.project_name}}/tests/test_echo.py",
+            "{{cookiecutter.project_name}}/tests/test_dummy.py",
+            "{{cookiecutter.project_name}}/tests/test_redis.py"
         ]
     },
     "Dummy model": {
@@ -62,6 +66,7 @@
             "{{cookiecutter.project_name}}/web/api/dummy",
             "{{cookiecutter.project_name}}/db/dao",
             "{{cookiecutter.project_name}}/db/models/dummy_model.py",
+            "{{cookiecutter.project_name}}/tests/test_dummy.py",
             "{{cookiecutter.project_name}}/db/migrations/versions/2021-08-16-16-55_2b7380507a71.py"
         ]
     },
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile
index 7c1e824..9a5c226 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile
@@ -12,7 +12,7 @@ RUN poetry config virtualenvs.create false
 COPY pyproject.toml poetry.lock /app/src/
 WORKDIR /app/src
 
-RUN poetry install --no-dev
+RUN poetry install
 
 # Copying actuall application
 COPY . /app/src/
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 318833b..80d1bf2 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml
@@ -8,11 +8,34 @@ services:
     image: {{cookiecutter.project_name}}:{{"${" }}{{cookiecutter.project_name | upper }}_VERSION:-latest{{"}"}}
     env_file:
       - .env
+    {%- if ((cookiecutter.db_info.name != "none" and cookiecutter.db_info.name != "sqlite") or
+            (cookiecutter.enable_redis == "True")) %}
+    depends_on:
+      {%- if cookiecutter.db_info.name != "none" %}
+      {%- if cookiecutter.db_info.name != "sqlite" %}
+      db:
+        condition: service_healthy
+      {%- endif %}
+      {%- endif %}
+      {%- if cookiecutter.enable_redis == "True" %}
+      redis:
+        condition: service_healthy
+      {%- endif %}
+    {%- endif %}
     environment:
-      - {{cookiecutter.project_name | upper }}_HOST=0.0.0.0
+      {{cookiecutter.project_name | upper }}_HOST: 0.0.0.0
+      {%- if cookiecutter.db_info.name != "none" %}
       {%- if cookiecutter.db_info.name == "sqlite" %}
-      - {{cookiecutter.project_name | upper }}_DB_FILE=/db_data/db.sqlite3
+      {{cookiecutter.project_name | upper }}_DB_FILE: /db_data/db.sqlite3
+      {%- else %}
+      {{cookiecutter.project_name | upper}}_DB_HOST: {{cookiecutter.project_name}}-db
+      {{cookiecutter.project_name | upper}}_DB_PORT: {{cookiecutter.db_info.port}}
+      {{cookiecutter.project_name | upper}}_DB_USER: {{cookiecutter.project_name}}
+      {{cookiecutter.project_name | upper}}_DB_PASS: {{cookiecutter.project_name}}
+      {{cookiecutter.project_name | upper}}_DB_BASE: {{cookiecutter.project_name}}
+      {%- endif %}
       {%- endif %}
+
     {%- if cookiecutter.db_info.name == "sqlite" %}
     volumes:
       - {{cookiecutter.project_name}}-db-data:/db_data/
@@ -20,49 +43,81 @@ services:
 
   {%- if cookiecutter.db_info.name == "postgresql" %}
   db:
-    image: postgres:13.4-buster
+    image: {{cookiecutter.db_info.image}}
     hostname: {{cookiecutter.project_name}}-db
     environment:
-      - POSTGRES_PASSWORD={{cookiecutter.project_name}}
-      - POSTGRES_USER={{cookiecutter.project_name}}
-      - POSTGRES_DB={{cookiecutter.project_name}}
+      - "POSTGRES_PASSWORD={{cookiecutter.project_name}}"
+      - "POSTGRES_USER={{cookiecutter.project_name}}"
+      - "POSTGRES_DB={{cookiecutter.project_name}}"
     volumes:
       - {{cookiecutter.project_name}}-db-data:/var/lib/postgresql/data
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready"]
+      interval: 2s
+      timeout: 3s
+      retries: 40
   {%- endif %}
 
-  {%- if cookiecutter.db_info.name == "mysql" %}
+  {% if cookiecutter.db_info.name == "mysql" -%}
   db:
-    image: bitnami/mysql:8.0.26
+    image: {{cookiecutter.db_info.image}}
     hostname: {{cookiecutter.project_name}}-db
     environment:
-      - MYSQL_PASSWORD={{cookiecutter.project_name}}
-      - MYSQL_USER={{cookiecutter.project_name}}
-      - MYSQL_DATABASE={{cookiecutter.project_name}}
-      - ALLOW_EMPTY_PASSWORD=yes
+      - "ALLOW_EMPTY_PASSWORD=yes"
+      - "MYSQL_ROOT_PASSWORD={{cookiecutter.project_name}}"
+      - "MYSQL_ROOT_USER={{cookiecutter.project_name}}"
+      - "MYSQL_DATABASE={{cookiecutter.project_name}}"
+    healthcheck:
+      test:
+        - "CMD"
+        - "mysql"
+        - "-u"
+        - "{{cookiecutter.project_name}}"
+        - "-p{{cookiecutter.project_name}}"
+        - "-e"
+        - "SELECT 1"
+      interval: 2s
+      timeout: 3s
+      retries: 40
     volumes:
       - {{cookiecutter.project_name}}-db-data:/bitnami/mysql/data
   {%- endif %}
 
-  {% if cookiecutter.enable_alembic == 'True' %}
+  {% if cookiecutter.enable_alembic == 'True' -%}
   migrator:
     image: {{cookiecutter.project_name}}:{{"${" }}{{cookiecutter.project_name | upper }}_VERSION:-latest{{"}"}}
-    {%- if cookiecutter.db_info.name == "sqlite" %}
     command: alembic upgrade head
+    {%- if cookiecutter.db_info.name == "sqlite" %}
     environment:
-      - {{cookiecutter.project_name | upper }}_DB_FILE=/db_data/db.sqlite3
+      {{cookiecutter.project_name | upper }}_DB_FILE: /db_data/db.sqlite3
     volumes:
       - {{cookiecutter.project_name}}-db-data:/db_data/
     {%- else %}
-    command: wait-for-it -t 180 {{cookiecutter.project_name}}-db:{{cookiecutter.db_info.port}} -- alembic upgrade head
+    environment:
+      {{cookiecutter.project_name | upper}}_DB_HOST: {{cookiecutter.project_name}}-db
+      {{cookiecutter.project_name | upper}}_DB_PORT: {{cookiecutter.db_info.port}}
+      {{cookiecutter.project_name | upper}}_DB_USER: {{cookiecutter.project_name}}
+      {{cookiecutter.project_name | upper}}_DB_PASS: {{cookiecutter.project_name}}
+      {{cookiecutter.project_name | upper}}_DB_BASE: {{cookiecutter.project_name}}
+    {%- endif %}
+    {%- if cookiecutter.db_info.name != "sqlite" %}
+    depends_on:
+      db:
+        condition: service_healthy
     {%- endif %}
   {%- endif %}
 
-  {%- if cookiecutter.enable_redis == "True" %}
+  {% if cookiecutter.enable_redis == "True" -%}
   redis:
     image: bitnami/redis:6.2.5
     hostname: {{cookiecutter.project_name}}-redis
     environment:
-      - ALLOW_EMPTY_PASSWORD=yes
+      - "ALLOW_EMPTY_PASSWORD=yes"
+    healthcheck:
+      test: ["CMD", "redis-cli", "ping"]
+      interval: 1s
+      timeout: 3s
+      retries: 30
   {%- endif %}
 
 {% if cookiecutter.db_info.name != 'none' %}
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml
index fa9def7..12490b7 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml
@@ -50,18 +50,18 @@ yesqa = "^1.2.3"
 pre-commit = "^2.11.0"
 wemake-python-styleguide = "^0.15.3"
 black = "^21.7b0"
-{%- if cookiecutter.db_info.name != "none" %}
-pytest-async-sqlalchemy = "^0.1.3"
-{%- endif %}
 autoflake = "^1.4"
 {%- if cookiecutter.db_info.name != "none" %}
 SQLAlchemy = {version = "^1.4", extras = ["mypy"]}
 {%- endif %}
-{%- if cookiecutter.enable_alembic %}
-pytest-alembic = "^0.3.3"
-{%- endif %}
 pytest-cov = "^2.12.1"
 pytest-asyncio = "^0.15.1"
+nest-asyncio = "^1.5.1"
+pytest-env = "^0.6.2"
+{%- if cookiecutter.enable_redis == "True" %}
+fakeredis = "^1.6.1"
+{%- endif %}
+requests = "^2.26.0"
 
 [tool.isort]
 profile = "black"
@@ -80,6 +80,17 @@ allow_untyped_decorators = true
 plugins = ["sqlalchemy.ext.mypy.plugin"]
 {%- endif %}
 
+[tool.pytest.ini_options]
+{%- if cookiecutter.db_info.name != "none" %}
+env = [
+    {%- if cookiecutter.db_info.name == "sqlite" %}
+    "{{cookiecutter.project_name | upper}}_DB_FILE=test_db.sqlite3",
+    {%- else %}
+    "{{cookiecutter.project_name | upper}}_DB_BASE={{cookiecutter.project_name}}_test",
+    {%- endif %}
+]
+{%- endif %}
+
 [build-system]
 requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
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 86b6683..61da6c4 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,11 +1,27 @@
 import asyncio
 import sys
-from typing import Any, Generator
+from typing import Any, Generator, AsyncGenerator
 
 import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+{%- if cookiecutter.enable_redis == "True" %}
+from fakeredis.aioredis import FakeRedis
+from {{cookiecutter.project_name}}.services.redis.dependency import get_redis_connection
+{%- endif %}
 
 from {{cookiecutter.project_name}}.settings import settings
 
+{%- if cookiecutter.db_info.name != "none" %}
+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
+{%- endif %}
+from {{cookiecutter.project_name}}.web.application import get_app
+import nest_asyncio
+
+nest_asyncio.apply()
 
 @pytest.fixture(scope="session")
 def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
@@ -29,26 +45,126 @@ def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
     loop.close()
 
 
+{%- if cookiecutter.db_info.name != "none" %}
 @pytest.fixture(scope="session")
-def database_url() -> str:
+@pytest.mark.asyncio
+async def _engine() -> AsyncGenerator[AsyncEngine, None]:
     """
-    Возвращает ссылку на тестовю базу данных.
+    Create engine and databases.
 
-    :return: URL.
+    :yield: new engine.
     """
-    return str(settings.db_url)
+    from {{cookiecutter.project_name}}.db.meta import meta  # noqa: WPS433
+    from {{cookiecutter.project_name}}.db.models import load_all_models  # noqa: WPS433
+
+    load_all_models()
 
+    await create_database()
 
-@pytest.fixture(scope="session")
-def init_database() -> Any:
+    engine = create_async_engine(str(settings.db_url))
+    async with engine.begin() as conn:
+        await conn.run_sync(meta.create_all)
+
+    try:
+        yield engine
+    finally:
+        await engine.dispose()
+        await drop_database()
+
+@pytest.fixture()
+@pytest.mark.asyncio
+async def dbsession(_engine: AsyncEngine) -> AsyncGenerator[AsyncSession, None]:
     """
-    Create a database for tests.
+    Get session to database.
 
-    :return: ничего
+    Fixture that returns a SQLAlchemy session with a SAVEPOINT, and the rollback to it
+    after the test completes.
+
+    :param _engine: current engine.
+    :yields: async session.
     """
-    from {{cookiecutter.project_name}}.db.meta import meta  # noqa: WPS433
-    from {{cookiecutter.project_name}}.db.models import load_all_models  # noqa: WPS433
+    connection = await _engine.connect()
+    trans = await connection.begin()
 
-    load_all_models()
+    session_maker = sessionmaker(
+        connection, expire_on_commit=False, class_=AsyncSession,
+    )
+    session = session_maker()
+
+    try:
+        yield session
+    finally:
+        await session.close()
+        await trans.rollback()
+        await connection.close()
+
+
+@pytest.fixture()
+@pytest.mark.asyncio
+async def transaction(_engine: AsyncEngine) -> AsyncGenerator[AsyncConnection, None]:
+    """
+    Create and obtain a transaction.
 
-    return meta.create_all
+    :param _engine: current database engine.
+    :yield: connection.
+    """
+    conn = await _engine.begin()
+    try:
+        yield conn
+    finally:
+        await conn.rollback()
+{%- endif %}
+
+
+{% if cookiecutter.enable_redis == "True" -%}
+@pytest.fixture()
+def fake_redis() -> FakeRedis:
+    """
+    Get instance of a fake redis.
+
+    :return: FakeRedis instance.
+    """
+    return FakeRedis(decode_responses=True)
+{%- endif %}
+
+@pytest.fixture()
+def fastapi_app(
+    {%- if cookiecutter.db_info.name != "none" %}
+    dbsession: AsyncSession,
+    {%- endif %}
+    {% if cookiecutter.enable_redis == "True" -%}
+    fake_redis: FakeRedis,
+    {%- endif %}
+) -> FastAPI:
+    """
+    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" %}
+    application.dependency_overrides[get_db_session] = lambda: dbsession
+    {%- endif %}
+    {%- if cookiecutter.enable_redis == "True" %}
+    application.dependency_overrides[get_redis_connection] = lambda: fake_redis
+    {%- endif %}
+
+    return application  # noqa: WPS331
+
+@pytest.fixture(scope="function")
+def client(
+    fastapi_app: FastAPI,
+) -> TestClient:
+    """
+    Fixture that creates client for requesting server.
+
+    :param fastapi_app: the application.
+    :return: client for the app.
+    """
+    return TestClient(app=fastapi_app)
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/dao/dummy_dao.py
index 2180ffd..03a51db 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/dao/dummy_dao.py
@@ -1,3 +1,4 @@
+from typing import List, Optional
 from fastapi import Depends
 from sqlalchemy import select
 from sqlalchemy.ext.asyncio import AsyncScalarResult, AsyncSession
@@ -12,7 +13,7 @@ class DummyDAO:
     def __init__(self, session: AsyncSession = Depends(get_db_session)):
         self.session = session
 
-    async def create_dummy_model(self, name: str) -> None:
+    def create_dummy_model(self, name: str) -> None:
         """
         Add single dummy to session.
 
@@ -33,3 +34,19 @@ class DummyDAO:
         )
 
         return raw_stream.scalars()
+
+    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 rows.scalars().fetchall()
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/migrations/env.py
index 4e58cd7..792e21d 100644
--- 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/migrations/env.py
@@ -11,13 +11,14 @@ from {{cookiecutter.project_name}}.settings import settings
 
 # this is the Alembic Config object, which provides
 # access to the values within the .ini file in use.
-config = context.config  # type: ignore
+config = context.config
 
 
 load_all_models()
 # Interpret the config file for Python logging.
 # This line sets up loggers basically.
-fileConfig(config.config_file_name)
+if config.config_file_name is not None:
+    fileConfig(config.config_file_name)
 
 # add your model's MetaData object here
 # for 'autogenerate' support
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/utils.py
new file mode 100644
index 0000000..2f353b2
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db/utils.py
@@ -0,0 +1,87 @@
+import os
+from sqlalchemy import text
+from sqlalchemy.engine import URL, make_url
+from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, AsyncEngine
+from sqlalchemy.orm import sessionmaker
+from {{cookiecutter.project_name}}.settings import settings
+
+{% if cookiecutter.db_info.name == "postgresql" -%}
+async def create_database() -> None:
+    """Create a databse."""
+    db_url = make_url(str(settings.db_url.with_path('/postgres')))
+    engine = create_async_engine(db_url, isolation_level="AUTOCOMMIT")
+
+    async with engine.connect() as conn:
+        database_existance = await conn.execute(
+            text(
+                f"SELECT 1 FROM pg_database WHERE datname='{settings.db_base}'",  # noqa: E501, S608
+            )
+        )
+        database_exists = database_existance.scalar() == 1
+
+    if database_exists:
+        await drop_database()
+
+    async with engine.connect() as conn:  # noqa: WPS440
+        await conn.execute(
+            text(
+                f'CREATE DATABASE "{settings.db_base}" ENCODING "utf8" TEMPLATE template1',  # noqa: E501
+            )
+        )
+
+async def drop_database() -> None:
+    """Drop current database."""
+    db_url = make_url(str(settings.db_url.with_path('/postgres')))
+    engine = create_async_engine(db_url, isolation_level="AUTOCOMMIT")
+    async with engine.connect() as conn:
+        disc_users = (
+        "SELECT pg_terminate_backend(pg_stat_activity.pid) "  # noqa: S608
+        "FROM pg_stat_activity "
+        f"WHERE pg_stat_activity.datname = '{settings.db_base}' "
+        "AND pid <> pg_backend_pid();"
+        )
+        await conn.execute(text(disc_users))
+        await conn.execute(text(f'DROP DATABASE "{settings.db_base}"'))
+
+
+{%- endif %}
+{%- if cookiecutter.db_info.name == "mysql" %}
+async def create_database() -> None:
+    """Create a databse."""
+    engine = create_async_engine(str(settings.db_url.with_path("/mysql")))
+
+    async with engine.connect() as conn:
+        database_existance = await conn.execute(
+            text(
+                "SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA"  # noqa: S608
+                f" WHERE SCHEMA_NAME='{settings.db_base}';",
+            )
+        )
+        database_exists = database_existance.scalar() == 1
+
+    if database_exists:
+        await drop_database()
+
+    async with engine.connect() as conn:  # noqa: WPS440
+        await conn.execute(
+            text(
+                f'CREATE DATABASE {settings.db_base};'
+            )
+        )
+
+async def drop_database() -> None:
+    """Drop current database."""
+    engine = create_async_engine(str(settings.db_url.with_path("/mysql")))
+    async with engine.connect() as conn:
+        await conn.execute(text(f'DROP DATABASE {settings.db_base};'))
+{%- endif %}
+{%- if cookiecutter.db_info.name == "sqlite" %}
+async def create_database() -> None:
+    """Create a databse."""
+
+async def drop_database() -> None:
+    """Drop current database."""
+    if settings.db_file.exists():
+        os.remove(settings.db_file)
+
+{%- endif %}
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 11f8691..11d9a82 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
@@ -22,7 +22,7 @@ class Settings(BaseSettings):
     {%- if cookiecutter.db_info.name == "sqlite" %}
     db_file: Path = TEMP_DIR / "db.sqlite3"
     {%- else %}
-    db_host: str = "{{cookiecutter.project_name}}-db"
+    db_host: str = "localhost"
     db_port: int = {{cookiecutter.db_info.port}}
     db_user: str = "{{cookiecutter.project_name}}"
     db_pass: str = "{{cookiecutter.project_name}}"
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
new file mode 100644
index 0000000..0d7e71e
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py
@@ -0,0 +1,65 @@
+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
+
+@pytest.mark.asyncio
+async def test_creation(
+    fastapi_app: FastAPI,
+    client: TestClient,
+    dbsession: AsyncSession
+) -> None:
+    """
+    Tests dummy instance creation.
+
+    :param fastapi_app: current app fixture.
+    :param client: client for app.
+    :param dbsession: current db session.
+    """
+    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
+    dao = DummyDAO(dbsession)
+    instances = await dao.filter(name=test_name)
+    assert instances[0].name == test_name
+
+
+@pytest.mark.asyncio
+async def test_getting(
+    fastapi_app: FastAPI,
+    client: TestClient,
+    dbsession: AsyncSession
+) -> None:
+    """
+    Tests dummy instance retrieval.
+
+    :param fastapi_app: current app fixture.
+    :param client: client for app.
+    :param dbsession: current db session.
+    """
+    dao = DummyDAO(dbsession)
+    test_name = uuid.uuid4().hex
+    dao.create_dummy_model(name=test_name)
+    await dbsession.commit()
+
+    url = fastapi_app.url_path_for('get_dummy_models')
+    response = client.get(url)
+    assert response.status_code == status.HTTP_200_OK
+    response_json = response.json()
+    assert len(response_json) == 1
+    assert response_json[0]['name'] == test_name
+
+
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_echo.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_echo.py
new file mode 100644
index 0000000..fee5395
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_echo.py
@@ -0,0 +1,21 @@
+import uuid
+
+from fastapi.testclient import TestClient
+from fastapi import FastAPI
+from starlette import status
+
+def test_echo(fastapi_app: FastAPI, client: TestClient) -> None:
+    """
+    Tests that echo route works.
+
+    :param fastapi_app: current application.
+    :param client: clien for the app.
+    """
+    url = fastapi_app.url_path_for('send_echo_message')
+    message = uuid.uuid4().hex
+    response = client.post(url, json={
+        "message": message
+    })
+    assert response.status_code == status.HTTP_200_OK
+    assert response.json()['message'] == message
+
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_redis.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_redis.py
new file mode 100644
index 0000000..3eb6f43
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_redis.py
@@ -0,0 +1,57 @@
+import uuid
+import fakeredis
+import pytest
+
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from starlette import status
+from fakeredis.aioredis import FakeRedis
+
+
+@pytest.mark.asyncio
+async def test_setting_value(
+    fastapi_app: FastAPI,
+    fake_redis: FakeRedis,
+    client: TestClient,
+) -> None:
+    """
+    Tests that you can set value in redis.
+
+    :param fastapi_app: current application fixture.
+    :param fake_redis: fake redis instance.
+    :param client: client fixture.
+    """
+    url = fastapi_app.url_path_for('set_redis_value')
+    test_key = uuid.uuid4().hex
+    test_val = uuid.uuid4().hex
+    response = client.put(url, json={
+        "key": test_key,
+        "value": test_val
+    })
+    assert response.status_code == status.HTTP_200_OK
+    actual_value = await fake_redis.get(test_key)
+    assert actual_value == test_val
+
+
+@pytest.mark.asyncio
+async def test_getting_value(
+    fastapi_app: FastAPI,
+    fake_redis: FakeRedis,
+    client: TestClient,
+) -> None:
+    """
+    Tests that you can get value from redis by key.
+
+    :param fastapi_app: current application fixture.
+    :param fake_redis: fake redis instance.
+    :param client: client fixture.
+    """
+    test_key = uuid.uuid4().hex
+    test_val = uuid.uuid4().hex
+    await fake_redis.set(test_key, test_val)
+
+    url = fastapi_app.url_path_for('get_redis_value')
+    response = client.get(url, params={"key": test_key})
+    assert response.status_code == status.HTTP_200_OK
+    assert response.json()['key'] == test_key
+    assert response.json()['value'] == test_val
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_{{cookiecutter.project_name}}.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_{{cookiecutter.project_name}}.py
index 23a09e9..e09e5e2 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_{{cookiecutter.project_name}}.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_{{cookiecutter.project_name}}.py
@@ -1,8 +1,15 @@
-def test_stub() -> None:
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from starlette import status
+
+
+def test_health(client: TestClient, fastapi_app: FastAPI) -> None:
     """
-    Test stub.
+    Checks the health endpoint.
 
-    It must be removed in real applications.
+    :param client: client for the app.
+    :param fastapi_app: current FastAPI application.
     """
-    test_val = 3
-    assert test_val == 3
+    url = fastapi_app.url_path_for('health_check')
+    response = client.get(url)
+    assert response.status_code == status.HTTP_200_OK
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/docs/views.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/docs/views.py
index 709cdb4..f5a95a9 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/docs/views.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/docs/views.py
@@ -10,9 +10,16 @@ router = APIRouter()
 
 @router.get("/docs", include_in_schema=False)
 async def custom_swagger_ui_html(request: Request) -> HTMLResponse:
+    """
+    Swagger UI.
+
+    :param request: current request.
+    :return: rendered swagger UI.
+    """
+    title = request.app.title
     return get_swagger_ui_html(
         openapi_url=request.app.openapi_url,
-        title=request.app.title + " - Swagger UI",
+        title=f"{title} - Swagger UI",
         oauth2_redirect_url=request.url_for("swagger_ui_redirect"),
         swagger_js_url="/static/docs/swagger-ui-bundle.js",
         swagger_css_url="/static/docs/swagger-ui.css",
@@ -21,13 +28,25 @@ async def custom_swagger_ui_html(request: Request) -> HTMLResponse:
 
 @router.get("/swagger-redirect", include_in_schema=False)
 async def swagger_ui_redirect() -> HTMLResponse:
+    """
+    Redirect to swagger.
+
+    :return: redirect.
+    """
     return get_swagger_ui_oauth2_redirect_html()
 
 
 @router.get("/redoc", include_in_schema=False)
 async def redoc_html(request: Request) -> HTMLResponse:
+    """
+    Redoc UI.
+
+    :param request: current request.
+    :return: rendered redoc UI.
+    """
+    title = request.app.title
     return get_redoc_html(
         openapi_url=request.app.openapi_url,
-        title=request.app.title + " - ReDoc",
+        title=f"{title} - ReDoc",
         redoc_js_url="/static/docs/redoc.standalone.js",
     )
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 955c9c6..44662fd 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
@@ -28,7 +28,7 @@ async def get_dummy_models(
 
 
 @router.put("/")
-async def create_dummy_model(
+def create_dummy_model(
     new_dummy_object: DummyModelInputDTO,
     dummy_dao: DummyDAO = Depends(),
 ) -> None:
@@ -38,4 +38,4 @@ async def create_dummy_model(
     :param new_dummy_object: new dummy model item.
     :param dummy_dao: DAO for dummy models.
     """
-    await dummy_dao.create_dummy_model(**new_dummy_object.dict())
+    dummy_dao.create_dummy_model(**new_dummy_object.dict())
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/__init__.py
new file mode 100644
index 0000000..c9bc60d
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/__init__.py
@@ -0,0 +1,4 @@
+"""API for checking project status."""
+from {{cookiecutter.project_name}}.web.api.monitoring.views import router
+
+__all__ = ['router']
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/views.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/views.py
new file mode 100644
index 0000000..c951b09
--- /dev/null
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/monitoring/views.py
@@ -0,0 +1,11 @@
+from fastapi import APIRouter
+
+router = APIRouter()
+
+@router.get('/health')
+def health_check() -> None:
+    """
+    Checks the health of a project.
+
+    It returns 200 if the project is healthy.
+    """
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/redis/views.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/redis/views.py
index 5c2ab6b..e2aa629 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/redis/views.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/redis/views.py
@@ -9,7 +9,7 @@ router = APIRouter()
 
 
 @router.get("/", response_model=RedisValueDTO)
-async def get_value(
+async def get_redis_value(
     key: str,
     redis: Redis = Depends(get_redis_connection),
 ) -> RedisValueDTO:
@@ -28,7 +28,7 @@ async def get_value(
 
 
 @router.put("/")
-async def set_value(
+async def set_redis_value(
     redis_value: RedisValueDTO,
     redis: Redis = Depends(get_redis_connection),
 ) -> None:
diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py
index 03dc240..63f736f 100644
--- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py
+++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py
@@ -14,9 +14,10 @@ from {{cookiecutter.project_name}}.web.api import redis
 {%- if cookiecutter.self_hosted_swagger == "True" %}
 from {{cookiecutter.project_name}}.web.api import docs
 {%- endif %}
-
+from {{cookiecutter.project_name}}.web.api import monitoring
 
 api_router = APIRouter()
+api_router.include_router(monitoring.router)
 {%- if cookiecutter.self_hosted_swagger == "True" %}
 api_router.include_router(docs.router)
 {%- endif %}
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 56a383a..22ac7be 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
@@ -62,7 +62,7 @@ def startup(app: FastAPI) -> Callable[[], Awaitable[None]]:
     :return: function that actually performs actions.
     """
 
-    async def _startup() -> None:
+    async def _startup() -> None:  # noqa: WPS430
         {%- if cookiecutter.db_info.name != "none" %}
         _setup_db(app)
         {%- endif %}
@@ -82,7 +82,7 @@ def shutdown(app: FastAPI) -> Callable[[], Awaitable[None]]:
     :return: function that actually performs actions.
     """
 
-    async def _shutdown() -> None:
+    async def _shutdown() -> None:  # noqa: WPS430
         {%- if cookiecutter.db_info.name != "none" %}
         await app.state.db_engine.dispose()
         {%- endif %}
diff --git a/fastapi_template/tests/conftest.py b/fastapi_template/tests/conftest.py
new file mode 100644
index 0000000..320dbaa
--- /dev/null
+++ b/fastapi_template/tests/conftest.py
@@ -0,0 +1,119 @@
+import pytest
+import os
+import uuid
+import tempfile
+import shutil
+from faker import Faker
+from pathlib import Path
+from fastapi_template.input_model import BuilderContext, CIType
+from fastapi_template.tests.utils import run_docker_compose_command
+
+
+@pytest.fixture(scope="session")
+def project_name() -> str:
+    """
+    Generate name for test project.
+
+    :return: project name.
+    """
+    fake = Faker()
+    raw_name: str = fake.name_female()
+    return raw_name.lower().replace(" ", "_").replace("-", "_")
+
+
+@pytest.fixture(scope="session", autouse=True)
+def generator_start_dir() -> str:
+    """
+    Generate directory to work into
+
+    :yield: this fixture generates dir for all test projects.
+    """
+    old_cwd = os.getcwd()
+    newpath = tempfile.mkdtemp()
+    os.chdir(newpath)
+    try:
+        yield newpath
+    finally:
+        os.chdir(old_cwd)
+        shutil.rmtree(newpath, ignore_errors=True)
+
+
+@pytest.fixture()
+def defautl_context(project_name: str) -> None:
+    """
+    Default builder context without features.
+
+    :param project_name: current project name.
+    :return: context.
+    """
+    return BuilderContext(
+        project_name=project_name,
+        kube_name=project_name.replace("_", "-"),
+        project_description="Generated by pytest.",
+        ci_type=CIType.none,
+        enable_redis=False,
+        enable_alembic=True,
+        enable_kube=False,
+        enable_routers=True,
+        add_dummy=True,
+        self_hosted_swagger=False,
+        force=True,
+    )
+
+
+@pytest.fixture(autouse=True)
+def lock_remover(project_name: str) -> None:
+    """
+    Automatically removes lock file
+    after each test.
+
+    :param project_name: generated project.
+    """
+    yield
+
+    Path("poetry.lock").unlink(missing_ok=True)
+
+
+@pytest.fixture(autouse=True)
+def default_dir(generator_start_dir: str) -> None:
+    """
+    Change directory to generator_start_dir.
+
+    :param generator_start_dir: start_dir.
+    """
+    yield
+    cwd = os.getcwd()
+    if cwd != generator_start_dir:
+        os.chdir(generator_start_dir)
+
+
+@pytest.fixture(scope="module", autouse=True)
+def docker_module_shutdown(generator_start_dir: str, project_name: str) -> None:
+    """
+    Cleans up docker context.
+
+    :param generator_start_dir: generator dir.
+    :param project_name: name of the project.
+    """
+    yield
+    cwd = os.getcwd()
+    project_dir = Path(generator_start_dir) / project_name
+    os.chdir(project_dir)
+    run_docker_compose_command("down -v")
+    os.chdir(cwd)
+
+
+@pytest.fixture(scope="session", autouse=True)
+def docker_shutdown(generator_start_dir: str, project_name: str) -> None:
+    """
+    Cleans up docker context.
+
+    :param generator_start_dir: generator dir.
+    :param project_name: name of the project.
+    """
+    yield
+    cwd = os.getcwd()
+    project_dir = Path(generator_start_dir) / project_name
+    os.chdir(project_dir)
+    run_docker_compose_command("down -v --rmi=all")
+    os.chdir(cwd)
diff --git a/fastapi_template/tests/test_mysql.py b/fastapi_template/tests/test_mysql.py
new file mode 100644
index 0000000..ae54671
--- /dev/null
+++ b/fastapi_template/tests/test_mysql.py
@@ -0,0 +1,47 @@
+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
new file mode 100644
index 0000000..8fbc28f
--- /dev/null
+++ b/fastapi_template/tests/test_postgres.py
@@ -0,0 +1,47 @@
+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
new file mode 100644
index 0000000..e7c5145
--- /dev/null
+++ b/fastapi_template/tests/test_sqlite.py
@@ -0,0 +1,47 @@
+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/fastapi_template/tests/utils.py b/fastapi_template/tests/utils.py
new file mode 100644
index 0000000..6fcdfaf
--- /dev/null
+++ b/fastapi_template/tests/utils.py
@@ -0,0 +1,43 @@
+import os
+import subprocess
+from typing import Optional
+from fastapi_template.input_model import BuilderContext
+from fastapi_template.__main__ import generate_project
+
+
+def generate_project_and_chdir(context: BuilderContext):
+    generate_project(context)
+    os.chdir(context.project_name)
+
+
+def run_pre_commit() -> int:
+    results = subprocess.run(["pre-commit", "run", "-a"])
+    return results.returncode
+
+
+def run_docker_compose_command(command: Optional[str] = None) -> subprocess.CompletedProcess:
+    docker_command = [
+        "docker-compose",
+        "-f",
+        "deploy/docker-compose.yml",
+        "--project-directory",
+        ".",
+    ]
+    if command:
+        docker_command.extend(command.split())
+    else:
+        docker_command.extend(
+            [
+                "build",
+            ]
+        )
+    return subprocess.run(docker_command)
+
+
+def run_default_check(context: BuilderContext):
+    generate_project_and_chdir(context)
+    assert run_pre_commit() == 0
+    build = run_docker_compose_command("build")
+    assert build.returncode == 0
+    tests = run_docker_compose_command("run --rm api pytest -vv .")
+    assert tests.returncode == 0
diff --git a/poetry.lock b/poetry.lock
deleted file mode 100644
index c8baf6c..0000000
--- a/poetry.lock
+++ /dev/null
@@ -1,705 +0,0 @@
-[[package]]
-name = "arrow"
-version = "1.1.1"
-description = "Better dates & times for Python"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-python-dateutil = ">=2.7.0"
-
-[[package]]
-name = "backports.entry-points-selectable"
-version = "1.1.0"
-description = "Compatibility shim providing selectable entry points for older implementations"
-category = "main"
-optional = false
-python-versions = ">=2.7"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"]
-
-[[package]]
-name = "binaryornot"
-version = "0.4.4"
-description = "Ultra-lightweight pure Python package to check if a file is binary or text."
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-chardet = ">=3.0.2"
-
-[[package]]
-name = "certifi"
-version = "2021.5.30"
-description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "cffi"
-version = "1.14.6"
-description = "Foreign Function Interface for Python calling C code."
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-pycparser = "*"
-
-[[package]]
-name = "cfgv"
-version = "3.3.1"
-description = "Validate configuration and produce human readable error messages."
-category = "main"
-optional = false
-python-versions = ">=3.6.1"
-
-[[package]]
-name = "chardet"
-version = "4.0.0"
-description = "Universal encoding detector for Python 2 and 3"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "charset-normalizer"
-version = "2.0.4"
-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
-optional = false
-python-versions = ">=3.5.0"
-
-[package.extras]
-unicode_backport = ["unicodedata2"]
-
-[[package]]
-name = "click"
-version = "8.0.1"
-description = "Composable command line interface toolkit"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-colorama = {version = "*", markers = "platform_system == \"Windows\""}
-
-[[package]]
-name = "colorama"
-version = "0.4.4"
-description = "Cross-platform colored terminal text."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "cookiecutter"
-version = "1.7.3"
-description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[package.dependencies]
-binaryornot = ">=0.4.4"
-click = ">=7.0"
-Jinja2 = ">=2.7,<4.0.0"
-jinja2-time = ">=0.2.0"
-poyo = ">=0.5.0"
-python-slugify = ">=4.0.0"
-requests = ">=2.23.0"
-six = ">=1.10"
-
-[[package]]
-name = "distlib"
-version = "0.3.2"
-description = "Distribution utilities"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "filelock"
-version = "3.0.12"
-description = "A platform independent file lock."
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "identify"
-version = "2.2.13"
-description = "File identification library for Python"
-category = "main"
-optional = false
-python-versions = ">=3.6.1"
-
-[package.extras]
-license = ["editdistance-s"]
-
-[[package]]
-name = "idna"
-version = "3.2"
-description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-
-[[package]]
-name = "jinja2"
-version = "3.0.1"
-description = "A very fast and expressive template engine."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-MarkupSafe = ">=2.0"
-
-[package.extras]
-i18n = ["Babel (>=2.7)"]
-
-[[package]]
-name = "jinja2-time"
-version = "0.2.0"
-description = "Jinja2 Extension for Dates and Times"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-arrow = "*"
-jinja2 = "*"
-
-[[package]]
-name = "markupsafe"
-version = "2.0.1"
-description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[[package]]
-name = "nodeenv"
-version = "1.6.0"
-description = "Node.js virtual environment builder"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "platformdirs"
-version = "2.3.0"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.extras]
-docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
-
-[[package]]
-name = "poyo"
-version = "0.5.0"
-description = "A lightweight YAML Parser for Python. 🐓"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[[package]]
-name = "pre-commit"
-version = "2.15.0"
-description = "A framework for managing and maintaining multi-language pre-commit hooks."
-category = "main"
-optional = false
-python-versions = ">=3.6.1"
-
-[package.dependencies]
-cfgv = ">=2.0.0"
-identify = ">=1.0.0"
-nodeenv = ">=0.11.1"
-pyyaml = ">=5.1"
-toml = "*"
-virtualenv = ">=20.0.8"
-
-[[package]]
-name = "prompt-toolkit"
-version = "3.0.20"
-description = "Library for building powerful interactive command lines in Python"
-category = "main"
-optional = false
-python-versions = ">=3.6.2"
-
-[package.dependencies]
-wcwidth = "*"
-
-[[package]]
-name = "pycparser"
-version = "2.20"
-description = "C parser in Python"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[[package]]
-name = "pydantic"
-version = "1.8.2"
-description = "Data validation and settings management using python 3.6 type hinting"
-category = "main"
-optional = false
-python-versions = ">=3.6.1"
-
-[package.dependencies]
-typing-extensions = ">=3.7.4.3"
-
-[package.extras]
-dotenv = ["python-dotenv (>=0.10.4)"]
-email = ["email-validator (>=1.0.3)"]
-
-[[package]]
-name = "pygit2"
-version = "1.6.1"
-description = "Python bindings for libgit2."
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-cffi = ">=1.4.0"
-
-[[package]]
-name = "python-dateutil"
-version = "2.8.2"
-description = "Extensions to the standard Python datetime module"
-category = "main"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-
-[package.dependencies]
-six = ">=1.5"
-
-[[package]]
-name = "python-slugify"
-version = "5.0.2"
-description = "A Python Slugify application that handles Unicode"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-text-unidecode = ">=1.3"
-
-[package.extras]
-unidecode = ["Unidecode (>=1.1.1)"]
-
-[[package]]
-name = "pyyaml"
-version = "5.4.1"
-description = "YAML parser and emitter for Python"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
-
-[[package]]
-name = "requests"
-version = "2.26.0"
-description = "Python HTTP for Humans."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
-
-[package.dependencies]
-certifi = ">=2017.4.17"
-charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
-idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
-urllib3 = ">=1.21.1,<1.27"
-
-[package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
-use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
-
-[[package]]
-name = "six"
-version = "1.16.0"
-description = "Python 2 and 3 compatibility utilities"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-
-[[package]]
-name = "termcolor"
-version = "1.1.0"
-description = "ANSII Color formatting for output in terminal."
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "text-unidecode"
-version = "1.3"
-description = "The most basic Text::Unidecode port"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "toml"
-version = "0.10.2"
-description = "Python Library for Tom's Obvious, Minimal Language"
-category = "main"
-optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-
-[[package]]
-name = "typing-extensions"
-version = "3.10.0.2"
-description = "Backported and Experimental Type Hints for Python 3.5+"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "urllib3"
-version = "1.26.6"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-
-[package.extras]
-brotli = ["brotlipy (>=0.6.0)"]
-secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
-
-[[package]]
-name = "virtualenv"
-version = "20.7.2"
-description = "Virtual Python Environment builder"
-category = "main"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-
-[package.dependencies]
-"backports.entry-points-selectable" = ">=1.0.4"
-distlib = ">=0.3.1,<1"
-filelock = ">=3.0.0,<4"
-platformdirs = ">=2,<3"
-six = ">=1.9.0,<2"
-
-[package.extras]
-docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
-testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
-
-[[package]]
-name = "wcwidth"
-version = "0.2.5"
-description = "Measures the displayed width of unicode strings in a terminal"
-category = "main"
-optional = false
-python-versions = "*"
-
-[metadata]
-lock-version = "1.1"
-python-versions = "^3.8"
-content-hash = "f75e0608a162229e1e7cf160a4adcbbe2e1cb11621073400604d5d5086504c71"
-
-[metadata.files]
-arrow = [
-    {file = "arrow-1.1.1-py3-none-any.whl", hash = "sha256:77a60a4db5766d900a2085ce9074c5c7b8e2c99afeaa98ad627637ff6f292510"},
-    {file = "arrow-1.1.1.tar.gz", hash = "sha256:dee7602f6c60e3ec510095b5e301441bc56288cb8f51def14dcb3079f623823a"},
-]
-"backports.entry-points-selectable" = [
-    {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"},
-    {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"},
-]
-binaryornot = [
-    {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"},
-    {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"},
-]
-certifi = [
-    {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
-    {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
-]
-cffi = [
-    {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"},
-    {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"},
-    {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"},
-    {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"},
-    {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"},
-    {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"},
-    {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"},
-    {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"},
-    {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"},
-    {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"},
-    {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"},
-    {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"},
-    {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"},
-    {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"},
-    {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"},
-    {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"},
-    {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"},
-    {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"},
-    {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"},
-    {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"},
-    {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"},
-    {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"},
-    {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"},
-    {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"},
-    {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"},
-    {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"},
-    {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"},
-    {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"},
-    {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"},
-    {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"},
-    {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"},
-    {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"},
-    {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"},
-    {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"},
-    {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"},
-    {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"},
-    {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"},
-    {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"},
-    {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"},
-    {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"},
-    {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"},
-    {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"},
-    {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"},
-    {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"},
-    {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
-]
-cfgv = [
-    {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
-    {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
-]
-chardet = [
-    {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
-    {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
-]
-charset-normalizer = [
-    {file = "charset-normalizer-2.0.4.tar.gz", hash = "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"},
-    {file = "charset_normalizer-2.0.4-py3-none-any.whl", hash = "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b"},
-]
-click = [
-    {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
-    {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
-]
-colorama = [
-    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
-    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
-]
-cookiecutter = [
-    {file = "cookiecutter-1.7.3-py2.py3-none-any.whl", hash = "sha256:f8671531fa96ab14339d0c59b4f662a4f12a2ecacd94a0f70a3500843da588e2"},
-    {file = "cookiecutter-1.7.3.tar.gz", hash = "sha256:6b9a4d72882e243be077a7397d0f1f76fe66cf3df91f3115dbb5330e214fa457"},
-]
-distlib = [
-    {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"},
-    {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"},
-]
-filelock = [
-    {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
-    {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
-]
-identify = [
-    {file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"},
-    {file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"},
-]
-idna = [
-    {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
-    {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
-]
-jinja2 = [
-    {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
-    {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
-]
-jinja2-time = [
-    {file = "jinja2-time-0.2.0.tar.gz", hash = "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40"},
-    {file = "jinja2_time-0.2.0-py2.py3-none-any.whl", hash = "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa"},
-]
-markupsafe = [
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
-    {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
-    {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
-    {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
-    {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
-    {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
-]
-nodeenv = [
-    {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
-    {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
-]
-platformdirs = [
-    {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"},
-    {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"},
-]
-poyo = [
-    {file = "poyo-0.5.0-py2.py3-none-any.whl", hash = "sha256:3e2ca8e33fdc3c411cd101ca395668395dd5dc7ac775b8e809e3def9f9fe041a"},
-    {file = "poyo-0.5.0.tar.gz", hash = "sha256:e26956aa780c45f011ca9886f044590e2d8fd8b61db7b1c1cf4e0869f48ed4dd"},
-]
-pre-commit = [
-    {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"},
-    {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"},
-]
-prompt-toolkit = [
-    {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"},
-    {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"},
-]
-pycparser = [
-    {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
-    {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
-]
-pydantic = [
-    {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
-    {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
-    {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
-    {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
-    {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
-    {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
-    {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
-    {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
-    {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
-    {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
-    {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
-    {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
-    {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
-    {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
-    {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
-    {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
-    {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
-    {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
-    {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
-    {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
-    {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
-    {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
-]
-pygit2 = [
-    {file = "pygit2-1.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:547429774c11f5bc9d20a49aa86e4bd13c90a55140504ef05f55cf424470ee34"},
-    {file = "pygit2-1.6.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e75865d7b6fc161d93b16f10365eaad353cd546e302a98f2de2097ddea1066b"},
-    {file = "pygit2-1.6.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4a64b6090308ffd1c82e2dd4316cb79483715387b13818156d516134a5b17c"},
-    {file = "pygit2-1.6.1-cp36-cp36m-win32.whl", hash = "sha256:2666a3970b2ea1222a9f0463b466f98c8d564f29ec84cf0a58d9b0d3865dbaaf"},
-    {file = "pygit2-1.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2de12ca2d3b7eb86106223b40b2edc0c61103c71e7962e53092c6ddef71a194"},
-    {file = "pygit2-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9c1d96c66fb6e69ec710078a73c19edff420bc1db430caa9e03a825eede3f25c"},
-    {file = "pygit2-1.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:454d42550fa6a6cd0e6a6ad9ab3f3262135fd157f57bad245ce156c36ee93370"},
-    {file = "pygit2-1.6.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce0827b77dd2f8a3465bdc181c4e65f27dd12dbd92635c038e58030cc90c2de0"},
-    {file = "pygit2-1.6.1-cp37-cp37m-win32.whl", hash = "sha256:b0161a141888d450eb821472fdcdadd14a072ddeda841fee9984956d34d3e19d"},
-    {file = "pygit2-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:af2fa259b6f7899227611ab978c600695724e85965836cb607d8b1e70cfea9b3"},
-    {file = "pygit2-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0e1e02c28983ddc004c0f54063f3e46fca388225d468e32e16689cfb750e0bd6"},
-    {file = "pygit2-1.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5dadc4844feb76cde5cc9a37656326a361dd8b5c8e8f8674dcd4a5ecf395db3"},
-    {file = "pygit2-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef07458e4172a31318663295083b43f957d611145738ff56aa76db593542a6e8"},
-    {file = "pygit2-1.6.1-cp38-cp38-win32.whl", hash = "sha256:7a0c0a1f11fd41f57e8c6c64d903cc7fa4ec95d15592270be3217ed7f78eb023"},
-    {file = "pygit2-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:2fd5c1b2d84dc6084f1bda836607afe37e95186a53a5a827a69083415e57fe4f"},
-    {file = "pygit2-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9b88b7e9a5286a71be0b6c307f0523c9606aeedff6b61eb9c440e18817fa641"},
-    {file = "pygit2-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac12d32b714c3383ebccffee5eb6aff0b69a2542a40a664fd5ad370afcb28ee7"},
-    {file = "pygit2-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe682ed6afd2ab31127f6a502cf3e002dc1cc8d26c36a5d49dfd180250351eb6"},
-    {file = "pygit2-1.6.1-cp39-cp39-win32.whl", hash = "sha256:dbbf66a23860aa899949068ac9b503b4bc21e6063e8f53870440adbdc909405e"},
-    {file = "pygit2-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:f90775afb11f69376e2af21ab56fcfbb52f6bc84117059ddf0355f81e5e36352"},
-    {file = "pygit2-1.6.1.tar.gz", hash = "sha256:c3303776f774d3e0115c1c4f6e1fc35470d15f113a7ae9401a0b90acfa1661ac"},
-]
-python-dateutil = [
-    {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
-    {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
-]
-python-slugify = [
-    {file = "python-slugify-5.0.2.tar.gz", hash = "sha256:f13383a0b9fcbe649a1892b9c8eb4f8eab1d6d84b84bb7a624317afa98159cab"},
-    {file = "python_slugify-5.0.2-py2.py3-none-any.whl", hash = "sha256:6d8c5df75cd4a7c3a2d21e257633de53f52ab0265cd2d1dc62a730e8194a7380"},
-]
-pyyaml = [
-    {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
-    {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
-    {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
-    {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
-    {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
-    {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
-    {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
-    {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
-    {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
-    {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
-    {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
-    {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
-    {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
-    {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
-    {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
-]
-requests = [
-    {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
-    {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
-]
-six = [
-    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
-    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-termcolor = [
-    {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
-]
-text-unidecode = [
-    {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
-    {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
-]
-toml = [
-    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
-    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
-]
-typing-extensions = [
-    {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
-    {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
-    {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
-]
-urllib3 = [
-    {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
-    {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
-]
-virtualenv = [
-    {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"},
-    {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"},
-]
-wcwidth = [
-    {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
-    {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
-]
diff --git a/pyproject.toml b/pyproject.toml
index d265a47..1257b57 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,19 +1,13 @@
 [tool.poetry]
 name = "fastapi_template"
-version = "2.4.1"
+version = "2.5.0"
 description = "Feature-rich robust FastAPI template"
 authors = ["Pavel Kirilin <win10@list.ru>"]
-packages = [
-    { include = "fastapi_template" },
-]
+packages = [{ include = "fastapi_template" }]
 repository = "https://github.com/s3rius/FastAPI-template"
 homepage = "https://github.com/s3rius/FastAPI-template"
 readme = "README.md"
-keywords = [
-    "FastAPI",
-    "Cookiecutter",
-    "Template"
-]
+keywords = ["FastAPI", "Cookiecutter", "Template"]
 
 
 [tool.poetry.dependencies]
@@ -26,6 +20,22 @@ pydantic = "^1.8.2"
 prompt-toolkit = "^3.0.19"
 
 [tool.poetry.dev-dependencies]
+pytest = "^6.2.5"
+pytest-env = "^0.6.2"
+Faker = "^8.14.0"
+
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+markers = [
+    "pg: tests for postgresql.",
+    "mysql: tests for mysql.",
+    "sqlite: tests for sqlite3.",
+]
+env = [
+    "POETRY_VIRTUALENVS_IN_PROJECT=True"
+]
+testpaths = ["fastapi_template/tests"]
 
 [tool.poetry.scripts]
 fastapi_template = "fastapi_template.__main__:main"
-- 
GitLab