diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..4ede45d
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,12 @@
+*.md
+app/tests
+__pycache__
+.env
+*example*
+.git
+.vscode
+.venv
+*cache*
+.github
+app/logs
+app/essensfindung.db
\ No newline at end of file
diff --git a/.github/workflows/python-validation.yml b/.github/workflows/python-validation.yml
index aa76008..1f9b60d 100644
--- a/.github/workflows/python-validation.yml
+++ b/.github/workflows/python-validation.yml
@@ -22,6 +22,7 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: 3.9
+
- name: Install dependencies
run: |
python -m pip install --upgrade pip
@@ -30,12 +31,17 @@ jobs:
- uses: psf/black@stable
with:
options: "--line-length 120"
+
- name: Lint with flake8
+ working-directory: ./app
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+
+
- name: Test with pytest
+ working-directory: ./app
run: |
pytest ./tests
diff --git a/.gitignore b/.gitignore
index 55c36bb..9767637 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,7 +134,10 @@ dmypy.json
# Ignore all Configuration Files
*.conf
!example*.conf
-tests/test_db.db
+app/tests/test_db.db
# Ignore Logs
-*log*
\ No newline at end of file
+*log*
+
+# Ignore local data
+app/data
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..751dd3f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,39 @@
+# Base Source https://fastapi.tiangolo.com/deployment/docker
+# This is the first stage, to create the requirements.txt
+FROM python:3.9 as requirements-stage
+
+WORKDIR /tmp
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ libssl-dev \
+ libffi-dev \
+ python3-dev \
+ cargo \
+ && rm -rf /var/lib/apt/lists/*
+RUN pip install poetry
+COPY ["./pyproject.toml", "./poetry.lock", "/tmp/"]
+RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
+
+# Start final stage
+FROM python:3.9
+
+# Install Dependencies
+COPY --from=requirements-stage /tmp/requirements.txt /tmp/requirements.txt
+RUN /usr/local/bin/python -m pip install --upgrade pip && \
+ pip install --no-cache-dir --upgrade -r /tmp/requirements.txt
+
+# Expose port 80
+EXPOSE 80
+
+# Copy the app
+RUN ["mkdir", "-p", "/essensfindung/"]
+COPY ./app /essensfindung/app
+WORKDIR /essensfindung/app
+
+
+# Setup local DB if needed
+RUN ["mkdir", "data"]
+VOLUME [ "/essensfindung/app/data" ]
+
+# Start gunicorn server with 2 workers, uvicorn worker type and use the 0.0.0.0 host with port 80
+ENTRYPOINT ["gunicorn", "-w", "2", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:80", "main:app"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b8dc075
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+build: clean_python
+ docker build -t essensfindung .
+
+clean_python:
+ find . -type d -name __pycache__ -exec rm -r {} \+
+ rm -r -f app/logs
+ rm -f app/essensfindung.db
+
+clean_docker:
+ docker rm --force essensfindung
+ docker container prune --force
+ docker volume prune --force
+
+clean_all: clean_python clean_python clean_docker
diff --git a/README.md b/README.md
index d674704..0ccb2ba 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,11 @@
Student project for DHBW-Friedrichshafen to search for restaurant or recipe suggestions.
# Usage
+If you want to run it make sure you habe a GoogleAPI KEY
+The Application use [place-details](https://developers.google.com/maps/billing-and-pricing/pricing#places-details) and [nearby-search](https://developers.google.com/maps/billing-and-pricing/pricing#nearby-search)
+**!!! The GoogleAPI Requests are not for free, so pay attention on the pricing !!!**
+## Direct with Python
+### Requirements
To run the application install the requirements:
```console
pip install requirements.txt
@@ -10,8 +15,55 @@ OR
```console
poetry install --no-dev
```
-You also need the configuration files in the `configuration` Folder.
-Just copy the examples in the Folder and fill in the Data.
+
+### Configuration
+If you **dont** have a PostgreSQL Database use `.env-example1` with only the GOOGLE_API. With this Configuration it will create a SQL-Lite DB in the app folder. Not Recommended for Production!
+```console
+# Copy the Example
+cp .env-example1 .env
+# Edit the File
+nano .env
+```
+
+If you have a PostgreSQL Database use `.env-example2`
+```console
+# Copy the Example
+cp .env-example2 .env
+# Edit the File
+nano .env
+```
+
+## Docker
+You also can build a docker container
+```console
+git clone https://github.com/DHBW-FN-TIT20/essensfindung.git
+cd essensfindung
+docker build essensfindung .
+```
+If you **dont** have a PostgreSQL Database start the container:
+```console
+docker run -p 8080:80 \
+-v /essensfindung/app/data \
+-e GOOGLE_API_KEY=KEY \
+--name essensfindung \
+essensfindung
+```
+
+If you have a PostgreSQL Database:
+```console
+docker run -p 8080:80 \
+-e GOOGLE_API_KEY=KEY \
+-e POSTGRES_USER=postgres \
+-e POSTGRES_PASSWORD=PASSWORD \
+-e POSTGRES_SERVER=localhost \
+-e POSTGRES_DATABASE=essensfindung \
+-e POSTGRES_PORT=5432 \
+--name essensfindung \
+essensfindung
+```
+
+## Docker-Compose
+See the `docker-compose.yml` File
# Developing
Python-Verison 3.9 or higher
You can use [pyenv](https://github.com/pyenv/pyenv) to manage you virtual envioremnts.
@@ -31,8 +83,10 @@ pre-commit install
```
If you want to manuall start the pre-commit check run:
```console
-pre-commit run --all-files
+pre-commit run
```
+
+
## Dependencies
To develop on this project we recommend to use [venv](https://docs.python.org/3/library/venv.html).
You can use some extra tools like:
diff --git a/api/README.md b/api/README.md
deleted file mode 100644
index 4363514..0000000
--- a/api/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# API
-In this dictonary you add the files for the api interface like `restaurant_api.py` or `rescepe_api.py`
\ No newline at end of file
diff --git a/app/.env-example1 b/app/.env-example1
new file mode 100644
index 0000000..b8a2111
--- /dev/null
+++ b/app/.env-example1
@@ -0,0 +1 @@
+GOOGLE_API_KEY=KEY
\ No newline at end of file
diff --git a/app/.env-example2 b/app/.env-example2
new file mode 100644
index 0000000..94895e6
--- /dev/null
+++ b/app/.env-example2
@@ -0,0 +1,6 @@
+GOOGLE_API_KEY=KEY
+POSTGRES_USER=USER!
+POSTGRES_PASSWORD=PASSWORD!
+POSTGRES_SERVER=DB_SERVER!
+POSTGRES_DATABASE=DATABASE!
+POSTGRES_PORT=5432
\ No newline at end of file
diff --git a/db/__init__.py b/app/db/__init__.py
similarity index 100%
rename from db/__init__.py
rename to app/db/__init__.py
diff --git a/db/base.py b/app/db/base.py
similarity index 100%
rename from db/base.py
rename to app/db/base.py
diff --git a/db/base_class.py b/app/db/base_class.py
similarity index 100%
rename from db/base_class.py
rename to app/db/base_class.py
diff --git a/db/crud/__init__.py b/app/db/crud/__init__.py
similarity index 100%
rename from db/crud/__init__.py
rename to app/db/crud/__init__.py
diff --git a/db/crud/bewertung.py b/app/db/crud/bewertung.py
similarity index 100%
rename from db/crud/bewertung.py
rename to app/db/crud/bewertung.py
diff --git a/db/crud/restaurant.py b/app/db/crud/restaurant.py
similarity index 100%
rename from db/crud/restaurant.py
rename to app/db/crud/restaurant.py
diff --git a/db/crud/user.py b/app/db/crud/user.py
similarity index 100%
rename from db/crud/user.py
rename to app/db/crud/user.py
diff --git a/app/db/database.py b/app/db/database.py
new file mode 100644
index 0000000..ea82cfb
--- /dev/null
+++ b/app/db/database.py
@@ -0,0 +1,27 @@
+"""Create the connection to the Database"""
+from pathlib import Path
+from typing import Generator
+
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from tools.config import settings
+
+if settings.SQL_LITE:
+ Path("./data").mkdir(exist_ok=True)
+ SQLALCHEMY_DATABASE_URL = "sqlite:///./data/essensfindung.db"
+ # Use connect_args parameter only with sqlite
+ engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
+else:
+ SQLALCHEMY_DATABASE_URL = f"postgresql://{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_SERVER}:{settings.POSTGRES_PORT}/{settings.POSTGRES_DATABASE}"
+ engine = create_engine(SQLALCHEMY_DATABASE_URL)
+
+
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+
+def get_db() -> Generator:
+ try:
+ database = SessionLocal()
+ yield database
+ finally:
+ database.close()
diff --git a/db/models/__init__.py b/app/db/models/__init__.py
similarity index 100%
rename from db/models/__init__.py
rename to app/db/models/__init__.py
diff --git a/db/models/allergie.py b/app/db/models/allergie.py
similarity index 100%
rename from db/models/allergie.py
rename to app/db/models/allergie.py
diff --git a/db/models/bewertung.py b/app/db/models/bewertung.py
similarity index 100%
rename from db/models/bewertung.py
rename to app/db/models/bewertung.py
diff --git a/db/models/filter.py b/app/db/models/filter.py
similarity index 100%
rename from db/models/filter.py
rename to app/db/models/filter.py
diff --git a/db/models/person.py b/app/db/models/person.py
similarity index 100%
rename from db/models/person.py
rename to app/db/models/person.py
diff --git a/db/models/restaurant.py b/app/db/models/restaurant.py
similarity index 100%
rename from db/models/restaurant.py
rename to app/db/models/restaurant.py
diff --git a/infrastructure/README.md b/app/infrastructure/README.md
similarity index 100%
rename from infrastructure/README.md
rename to app/infrastructure/README.md
diff --git a/infrastructure/__init__.py b/app/infrastructure/__init__.py
similarity index 100%
rename from infrastructure/__init__.py
rename to app/infrastructure/__init__.py
diff --git a/main.py b/app/main.py
similarity index 91%
rename from main.py
rename to app/main.py
index 3e66be7..c4ff324 100644
--- a/main.py
+++ b/app/main.py
@@ -5,10 +5,10 @@
import fastapi
import uvicorn
-from starlette.staticfiles import StaticFiles
-
from db.base import Base
from db.database import engine
+from sqlalchemy import exc
+from starlette.staticfiles import StaticFiles
from views import index
from views import restaurant
from views import signin
@@ -62,7 +62,11 @@ def configure_routing():
def configure_database():
"""Configure connection to the Database"""
- Base.metadata.create_all(bind=engine)
+ try:
+ Base.metadata.create_all(bind=engine)
+ except exc.OperationalError:
+ # Ignore if Tables already exist
+ pass
if __name__ == "__main__":
diff --git a/schemes/README.md b/app/schemes/README.md
similarity index 100%
rename from schemes/README.md
rename to app/schemes/README.md
diff --git a/schemes/__init__.py b/app/schemes/__init__.py
similarity index 100%
rename from schemes/__init__.py
rename to app/schemes/__init__.py
diff --git a/schemes/exceptions.py b/app/schemes/exceptions.py
similarity index 100%
rename from schemes/exceptions.py
rename to app/schemes/exceptions.py
diff --git a/schemes/scheme_filter.py b/app/schemes/scheme_filter.py
similarity index 100%
rename from schemes/scheme_filter.py
rename to app/schemes/scheme_filter.py
diff --git a/schemes/scheme_rest.py b/app/schemes/scheme_rest.py
similarity index 100%
rename from schemes/scheme_rest.py
rename to app/schemes/scheme_rest.py
diff --git a/schemes/scheme_user.py b/app/schemes/scheme_user.py
similarity index 100%
rename from schemes/scheme_user.py
rename to app/schemes/scheme_user.py
diff --git a/services/README.md b/app/services/README.md
similarity index 100%
rename from services/README.md
rename to app/services/README.md
diff --git a/services/__init__.py b/app/services/__init__.py
similarity index 100%
rename from services/__init__.py
rename to app/services/__init__.py
diff --git a/services/service_res.py b/app/services/service_res.py
similarity index 100%
rename from services/service_res.py
rename to app/services/service_res.py
diff --git a/static/README.md b/app/static/README.md
similarity index 100%
rename from static/README.md
rename to app/static/README.md
diff --git a/static/css/bootstrap.min.css b/app/static/css/bootstrap.min.css
similarity index 100%
rename from static/css/bootstrap.min.css
rename to app/static/css/bootstrap.min.css
diff --git a/static/css/bootstrap.min.css.map b/app/static/css/bootstrap.min.css.map
similarity index 100%
rename from static/css/bootstrap.min.css.map
rename to app/static/css/bootstrap.min.css.map
diff --git a/static/img/glaser.JPG b/app/static/img/glaser.JPG
similarity index 100%
rename from static/img/glaser.JPG
rename to app/static/img/glaser.JPG
diff --git a/static/js/bootstrap.bundle.min.js b/app/static/js/bootstrap.bundle.min.js
similarity index 100%
rename from static/js/bootstrap.bundle.min.js
rename to app/static/js/bootstrap.bundle.min.js
diff --git a/static/js/bootstrap.bundle.min.js.map b/app/static/js/bootstrap.bundle.min.js.map
similarity index 100%
rename from static/js/bootstrap.bundle.min.js.map
rename to app/static/js/bootstrap.bundle.min.js.map
diff --git a/static/js/restaurant.js b/app/static/js/restaurant.js
similarity index 100%
rename from static/js/restaurant.js
rename to app/static/js/restaurant.js
diff --git a/static/text/privacy.txt b/app/static/text/privacy.txt
similarity index 100%
rename from static/text/privacy.txt
rename to app/static/text/privacy.txt
diff --git a/static/text/tos.txt b/app/static/text/tos.txt
similarity index 100%
rename from static/text/tos.txt
rename to app/static/text/tos.txt
diff --git a/templates/README.md b/app/templates/README.md
similarity index 100%
rename from templates/README.md
rename to app/templates/README.md
diff --git a/templates/examples/example.html b/app/templates/examples/example.html
similarity index 100%
rename from templates/examples/example.html
rename to app/templates/examples/example.html
diff --git a/templates/index.html b/app/templates/index.html
similarity index 100%
rename from templates/index.html
rename to app/templates/index.html
diff --git a/templates/restaurant/restaurant_filter_modal.html b/app/templates/restaurant/restaurant_filter_modal.html
similarity index 100%
rename from templates/restaurant/restaurant_filter_modal.html
rename to app/templates/restaurant/restaurant_filter_modal.html
diff --git a/templates/restaurant/restaurant_result.html b/app/templates/restaurant/restaurant_result.html
similarity index 100%
rename from templates/restaurant/restaurant_result.html
rename to app/templates/restaurant/restaurant_result.html
diff --git a/templates/shared/layout.html b/app/templates/shared/layout.html
similarity index 100%
rename from templates/shared/layout.html
rename to app/templates/shared/layout.html
diff --git a/templates/shared/layout_singleform.html b/app/templates/shared/layout_singleform.html
similarity index 100%
rename from templates/shared/layout_singleform.html
rename to app/templates/shared/layout_singleform.html
diff --git a/templates/signin/register.html b/app/templates/signin/register.html
similarity index 100%
rename from templates/signin/register.html
rename to app/templates/signin/register.html
diff --git a/templates/signin/signin.html b/app/templates/signin/signin.html
similarity index 100%
rename from templates/signin/signin.html
rename to app/templates/signin/signin.html
diff --git a/tests/__init__.py b/app/tests/__init__.py
similarity index 100%
rename from tests/__init__.py
rename to app/tests/__init__.py
diff --git a/tests/example_nearby_search.json b/app/tests/example_nearby_search.json
similarity index 100%
rename from tests/example_nearby_search.json
rename to app/tests/example_nearby_search.json
diff --git a/tests/example_place_details.json b/app/tests/example_place_details.json
similarity index 100%
rename from tests/example_place_details.json
rename to app/tests/example_place_details.json
diff --git a/tests/example_restaurants.json b/app/tests/example_restaurants.json
similarity index 100%
rename from tests/example_restaurants.json
rename to app/tests/example_restaurants.json
diff --git a/tests/example_restaurants_with_own_rating.json b/app/tests/example_restaurants_with_own_rating.json
similarity index 100%
rename from tests/example_restaurants_with_own_rating.json
rename to app/tests/example_restaurants_with_own_rating.json
diff --git a/tests/test_db.py b/app/tests/test_db.py
similarity index 100%
rename from tests/test_db.py
rename to app/tests/test_db.py
diff --git a/tests/test_gapi.py b/app/tests/test_gapi.py
similarity index 94%
rename from tests/test_gapi.py
rename to app/tests/test_gapi.py
index 474a187..0ea4227 100644
--- a/tests/test_gapi.py
+++ b/app/tests/test_gapi.py
@@ -5,7 +5,6 @@
import pytest
from pytest_httpx import HTTPXMock
from pytest_mock import MockerFixture
-
from schemes import Cuisine
from schemes.scheme_rest import Restaurant
from tools import gapi
@@ -56,7 +55,7 @@ def test_nearby_search(
httpx_mock.add_response(status_code=status_code, json=fake_nearby_search)
# Mock other functions
- mocker.patch("configuration.config.Setting.GOOGLE_API_KEY", "42")
+ mocker.patch("tools.config.Setting.GOOGLE_API_KEY", "42")
if status_code != 200:
with pytest.raises(httpx.HTTPStatusError):
@@ -78,7 +77,7 @@ def test_place_details(
httpx_mock.add_response(status_code=200, json=fake_place_details[0], url=url)
# Mock other functions
- mocker.patch("configuration.config.Setting.GOOGLE_API_KEY", "42")
+ mocker.patch("tools.config.Setting.GOOGLE_API_KEY", "42")
restaurant = gapi.place_details(fake_nearby_search_restaurants[0])
assert fake_restaurants[0] == restaurant
diff --git a/tests/test_restaurant_service.py b/app/tests/test_restaurant_service.py
similarity index 98%
rename from tests/test_restaurant_service.py
rename to app/tests/test_restaurant_service.py
index 1562969..c01807d 100644
--- a/tests/test_restaurant_service.py
+++ b/app/tests/test_restaurant_service.py
@@ -2,14 +2,10 @@
from typing import List
import pytest
-from pytest_httpx import HTTPXMock
-from pytest_mock import MockerFixture
-from sqlalchemy import create_engine
-from sqlalchemy.orm import Session
-from sqlalchemy.orm import sessionmaker
-
from db.base_class import Base
from db.crud.user import create_user
+from pytest_httpx import HTTPXMock
+from pytest_mock import MockerFixture
from schemes import Allergies
from schemes import Cuisine
from schemes.scheme_filter import FilterRest
@@ -17,6 +13,9 @@
from schemes.scheme_rest import Restaurant
from schemes.scheme_user import UserCreate
from services import service_res
+from sqlalchemy import create_engine
+from sqlalchemy.orm import Session
+from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./tests/test_db.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
@@ -68,7 +67,7 @@ def test_search_for_restaurant(
# ...googleapi
mocker.patch("tools.gapi.search_restaurant", return_value=google_api_restaurants)
- mocker.patch("configuration.config.Setting.GOOGLE_API_KEY", "42")
+ mocker.patch("tools.config.Setting.GOOGLE_API_KEY", "42")
url = f"https://maps.googleapis.com/maps/api/place/details/json?key=42&place_id={random_res.place_id}"
httpx_mock.add_response(status_code=200, json={"result": random_res.dict()}, url=url)
diff --git a/tools/__init__.py b/app/tools/__init__.py
similarity index 100%
rename from tools/__init__.py
rename to app/tools/__init__.py
diff --git a/app/tools/config.py b/app/tools/config.py
new file mode 100644
index 0000000..9574f15
--- /dev/null
+++ b/app/tools/config.py
@@ -0,0 +1,26 @@
+import os
+from pathlib import Path
+
+from dotenv import load_dotenv
+
+ENV_PATH = Path("./.env")
+load_dotenv(dotenv_path=ENV_PATH)
+
+
+class Setting:
+ """Contains all Settings - Loads from the os env"""
+
+ GOOGLE_API_KEY: str = os.getenv("GOOGLE_API_KEY")
+
+ if os.getenv("POSTGRES_SERVER"):
+ SQL_LITE: bool = False
+ POSTGRES_USER: str = os.getenv("POSTGRES_USER")
+ POSTGRES_PASSWORD: str = os.getenv("POSTGRES_PASSWORD")
+ POSTGRES_SERVER: str = os.getenv("POSTGRES_SERVER")
+ POSTGRES_DATABASE: str = os.getenv("POSTGRES_DATABASE")
+ POSTGRES_PORT: str = os.getenv("POSTGRES_PORT")
+ else:
+ SQL_LITE: bool = True
+
+
+settings = Setting()
diff --git a/tools/gapi.py b/app/tools/gapi.py
similarity index 98%
rename from tools/gapi.py
rename to app/tools/gapi.py
index d8547c8..23d36af 100644
--- a/tools/gapi.py
+++ b/app/tools/gapi.py
@@ -3,11 +3,10 @@
from typing import List
import httpx
-
-from configuration.config import settings
from schemes.exceptions import GoogleApiException
from schemes.scheme_filter import FilterRest
from schemes.scheme_rest import Restaurant
+from tools.config import settings
# TODO: Asynchrone Funktionen
# TODO: Asynchrone API Anfragen
diff --git a/tools/hashing.py b/app/tools/hashing.py
similarity index 100%
rename from tools/hashing.py
rename to app/tools/hashing.py
diff --git a/views/README.md b/app/views/README.md
similarity index 100%
rename from views/README.md
rename to app/views/README.md
diff --git a/views/index.py b/app/views/index.py
similarity index 100%
rename from views/index.py
rename to app/views/index.py
diff --git a/views/restaurant.py b/app/views/restaurant.py
similarity index 100%
rename from views/restaurant.py
rename to app/views/restaurant.py
diff --git a/views/signin.py b/app/views/signin.py
similarity index 100%
rename from views/signin.py
rename to app/views/signin.py
diff --git a/configuration/__init__.py b/configuration/__init__.py
deleted file mode 100644
index c3419cc..0000000
--- a/configuration/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import shutil
-from pathlib import Path
-
-PATH_GOOGLE = Path("./configuration/google_api.conf")
-PATH_DB = Path("./configuration/db.conf")
-
-if not PATH_GOOGLE.exists():
- shutil.copy("./configuration/example_google_api.conf", str(PATH_GOOGLE))
-if not PATH_DB.exists():
- shutil.copy("./configuration/example_db.conf", str(PATH_DB))
diff --git a/configuration/config.py b/configuration/config.py
deleted file mode 100644
index aab08ab..0000000
--- a/configuration/config.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from configparser import ConfigParser
-
-from . import PATH_DB
-from . import PATH_GOOGLE
-
-config = ConfigParser()
-config.read((PATH_GOOGLE, PATH_DB))
-
-
-class Setting:
- GOOGLE_API_KEY: str = config["API"]["KEY"]
- POSTGRES_USER: str = config["USER"]["USERNAME"]
- POSTGRES_PASSWORD: str = config["USER"]["PASSWORD"]
- POSTGRES_SERVER: str = config["CONNECTION"]["HOST"]
- POSTGRES_DATABASE: str = config["CONNECTION"]["DATABASE"]
-
-
-settings = Setting()
diff --git a/configuration/example_db.conf b/configuration/example_db.conf
deleted file mode 100644
index 4ebee07..0000000
--- a/configuration/example_db.conf
+++ /dev/null
@@ -1,8 +0,0 @@
-[CONNECTION]
-HOST=192.168.10.200
-PORT=5432
-DATABASE=essensfinder
-
-[USER]
-USERNAME=postgres
-PASSWORD=[PASSWORD]
\ No newline at end of file
diff --git a/configuration/example_google_api.conf b/configuration/example_google_api.conf
deleted file mode 100644
index bb8d4b7..0000000
--- a/configuration/example_google_api.conf
+++ /dev/null
@@ -1,2 +0,0 @@
-[API]
-KEY = [KEY]
\ No newline at end of file
diff --git a/db/database.py b/db/database.py
deleted file mode 100644
index f60c3d8..0000000
--- a/db/database.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""Create the connection to the Database"""
-from typing import Generator
-
-from sqlalchemy import create_engine
-from sqlalchemy.orm import sessionmaker
-
-from configuration.config import settings
-
-SQLALCHEMY_DATABASE_URL = f"postgresql://{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_SERVER}/{settings.POSTGRES_DATABASE}"
-
-engine = create_engine(SQLALCHEMY_DATABASE_URL)
-SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-
-
-def get_db() -> Generator:
- try:
- database = SessionLocal()
- yield database
- finally:
- database.close()
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..d155e70
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,32 @@
+version: '3.5'
+services:
+
+ essensfindung:
+ image: essensfindung
+ container_name: essensfindung
+ hostname: essensfindung
+ depends_on:
+ - db
+ ports:
+ - "8888:80"
+ environment:
+ - TZ=Europe/Berlin
+ - GOOGLE_API_KEY=CHANGE_TO_KEY
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=SAVE_PASSWORD
+ - POSTGRES_SERVER=db
+ - POSTGRES_DATABASE=essensfinder
+ - POSTGRES_PORT=5432
+ volumes:
+ - ./app/data:/essensfindung/app/data:rw
+
+ db:
+ image: postgres
+ container_name: postgresDB_essensfindung
+ hostname: db
+ environment:
+ - TZ=Europe/Berlin
+ - PUID=1000
+ - PGID=1000
+ - POSTGRES_PASSWORD=SAVE_PASSWORD
+ - POSTGRES_DB=essensfindung
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
index cc2099c..e87a4fb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -231,6 +231,20 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
[package.extras]
docs = ["sphinx"]
+[[package]]
+name = "gunicorn"
+version = "20.1.0"
+description = "WSGI HTTP Server for UNIX"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+eventlet = ["eventlet (>=0.24.1)"]
+gevent = ["gevent (>=1.4.0)"]
+setproctitle = ["setproctitle"]
+tornado = ["tornado (>=0.2)"]
+
[[package]]
name = "h11"
version = "0.12.0"
@@ -577,6 +591,17 @@ pytest = ">=5.0"
[package.extras]
dev = ["pre-commit", "tox", "pytest-asyncio"]
+[[package]]
+name = "python-dotenv"
+version = "0.19.2"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
[[package]]
name = "pyyaml"
version = "6.0"
@@ -738,7 +763,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "d53531e2d868d5513f44ed892f1a9d354b6030819599c28c8cdbba2f293d8e05"
+content-hash = "2b62bf788ed5e813d7f73680785c98c909b64f20ea98ee009c3b422a219b81ab"
[metadata.files]
aiofiles = [
@@ -926,6 +951,10 @@ greenlet = [
{file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"},
{file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"},
]
+gunicorn = [
+ {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
+ {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
+]
h11 = [
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
@@ -1175,6 +1204,10 @@ pytest-mock = [
{file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"},
{file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"},
]
+python-dotenv = [
+ {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
+ {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"},
+]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
@@ -1219,6 +1252,11 @@ regex = [
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"},
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"},
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"},
+ {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b"},
+ {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063"},
+ {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03"},
+ {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e"},
+ {file = "regex-2021.11.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b"},
{file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"},
{file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"},
{file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"},
@@ -1228,6 +1266,11 @@ regex = [
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"},
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"},
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"},
+ {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286"},
+ {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264"},
+ {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a"},
+ {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a"},
+ {file = "regex-2021.11.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00"},
{file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"},
{file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"},
{file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"},
@@ -1237,6 +1280,11 @@ regex = [
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"},
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"},
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"},
+ {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da"},
+ {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732"},
+ {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05"},
+ {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942"},
+ {file = "regex-2021.11.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a"},
{file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"},
{file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"},
{file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"},
@@ -1247,6 +1295,11 @@ regex = [
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"},
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"},
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"},
+ {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8"},
+ {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129"},
+ {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a"},
+ {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf"},
+ {file = "regex-2021.11.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0"},
{file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"},
{file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"},
{file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"},
@@ -1257,6 +1310,11 @@ regex = [
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"},
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"},
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"},
+ {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b"},
+ {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296"},
+ {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49"},
+ {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737"},
+ {file = "regex-2021.11.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d"},
{file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"},
{file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"},
{file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"},
diff --git a/pyproject.toml b/pyproject.toml
index f81fd13..0a8f81e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,8 @@ SQLAlchemy = "^1.4.31"
psycopg2 = "^2.9.3"
passlib = "^1.7.4"
bcrypt = "^3.2.0"
+gunicorn = "^20.1.0"
+python-dotenv = "^0.19.2"
[tool.poetry.dev-dependencies]
black = "^21.10b0"
diff --git a/requirements-dev.txt b/requirements-dev.txt
index baab60d..fc1ddb0 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -164,6 +164,9 @@ greenlet==1.1.2; python_version >= "3" and python_full_version < "3.0.0" and (pl
--hash=sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf \
--hash=sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd \
--hash=sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a
+gunicorn==20.1.0; python_version >= "3.5" \
+ --hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \
+ --hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
h11==0.12.0; python_version >= "3.6" \
--hash=sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6 \
--hash=sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042
@@ -383,6 +386,9 @@ pytest-mock==3.6.1; python_version >= "3.6" \
pytest==6.2.5; python_version >= "3.6" \
--hash=sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134 \
--hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89
+python-dotenv==0.19.2; python_version >= "3.5" \
+ --hash=sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f \
+ --hash=sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3
pyyaml==6.0; python_version >= "3.6" and python_full_version >= "3.6.1" \
--hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
--hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
@@ -426,6 +432,11 @@ regex==2021.11.10; python_full_version >= "3.6.2" \
--hash=sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f \
--hash=sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0 \
--hash=sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4 \
+ --hash=sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b \
+ --hash=sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063 \
+ --hash=sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03 \
+ --hash=sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e \
+ --hash=sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b \
--hash=sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a \
--hash=sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12 \
--hash=sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc \
@@ -435,6 +446,11 @@ regex==2021.11.10; python_full_version >= "3.6.2" \
--hash=sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733 \
--hash=sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23 \
--hash=sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e \
+ --hash=sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286 \
+ --hash=sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264 \
+ --hash=sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a \
+ --hash=sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a \
+ --hash=sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00 \
--hash=sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4 \
--hash=sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e \
--hash=sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e \
@@ -444,6 +460,11 @@ regex==2021.11.10; python_full_version >= "3.6.2" \
--hash=sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a \
--hash=sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e \
--hash=sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f \
+ --hash=sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da \
+ --hash=sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732 \
+ --hash=sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05 \
+ --hash=sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942 \
+ --hash=sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a \
--hash=sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec \
--hash=sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4 \
--hash=sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83 \
@@ -454,6 +475,11 @@ regex==2021.11.10; python_full_version >= "3.6.2" \
--hash=sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec \
--hash=sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe \
--hash=sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94 \
+ --hash=sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8 \
+ --hash=sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129 \
+ --hash=sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a \
+ --hash=sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf \
+ --hash=sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0 \
--hash=sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc \
--hash=sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d \
--hash=sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b \
@@ -464,6 +490,11 @@ regex==2021.11.10; python_full_version >= "3.6.2" \
--hash=sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449 \
--hash=sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b \
--hash=sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef \
+ --hash=sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b \
+ --hash=sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296 \
+ --hash=sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49 \
+ --hash=sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737 \
+ --hash=sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d \
--hash=sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a \
--hash=sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29 \
--hash=sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6
diff --git a/requirements.txt b/requirements.txt
index 8d66730..f395a47 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -140,6 +140,9 @@ greenlet==1.1.2; python_version >= "3" and python_full_version < "3.0.0" and (pl
--hash=sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf \
--hash=sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd \
--hash=sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a
+gunicorn==20.1.0; python_version >= "3.5" \
+ --hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \
+ --hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
h11==0.12.0; python_version >= "3.6" \
--hash=sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6 \
--hash=sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042
@@ -279,6 +282,9 @@ pydantic==1.9.0; python_full_version >= "3.6.1" \
--hash=sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16 \
--hash=sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3 \
--hash=sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a
+python-dotenv==0.19.2; python_version >= "3.5" \
+ --hash=sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f \
+ --hash=sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3
rfc3986==1.5.0; python_version >= "3.6" \
--hash=sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97 \
--hash=sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835