From d64c8859af1cdf4372dc26a77855bec19750cf13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torma=20Krist=C3=B3f?= Date: Sun, 13 Feb 2022 20:38:02 +0100 Subject: [PATCH] some stuff donnerino --- .drone.yml | 18 ++++ .gitignore | 134 ++++++++++++++++++++++++++++ Dockerfile | 11 +++ mealAPI/app.py | 36 ++++++++ mealAPI/model/__init__.py | 3 + mealAPI/model/db.py | 4 + mealAPI/model/ingredient.py | 11 +++ mealAPI/model/meal.py | 16 ++++ mealAPI/model/mealingredient.py | 9 ++ mealAPI/resources/__init__.py | 1 + mealAPI/resources/mealresource.py | 28 ++++++ mealAPI/schemas/__init__.py | 3 + mealAPI/schemas/ingredientschema.py | 9 ++ mealAPI/schemas/marshm.py | 5 ++ mealAPI/schemas/mealschema.py | 13 +++ mealAPI/utils/__init__.py | 3 + mealAPI/utils/config.py | 11 +++ mealAPI/utils/error_handlers.py | 18 ++++ mealAPI/utils/healthchecks.py | 14 +++ requirements.txt | 9 ++ 20 files changed, 356 insertions(+) create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 mealAPI/app.py create mode 100644 mealAPI/model/__init__.py create mode 100644 mealAPI/model/db.py create mode 100644 mealAPI/model/ingredient.py create mode 100644 mealAPI/model/meal.py create mode 100644 mealAPI/model/mealingredient.py create mode 100644 mealAPI/resources/__init__.py create mode 100644 mealAPI/resources/mealresource.py create mode 100644 mealAPI/schemas/__init__.py create mode 100644 mealAPI/schemas/ingredientschema.py create mode 100644 mealAPI/schemas/marshm.py create mode 100644 mealAPI/schemas/mealschema.py create mode 100644 mealAPI/utils/__init__.py create mode 100644 mealAPI/utils/config.py create mode 100644 mealAPI/utils/error_handlers.py create mode 100644 mealAPI/utils/healthchecks.py create mode 100644 requirements.txt diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..c75225b --- /dev/null +++ b/.drone.yml @@ -0,0 +1,18 @@ +--- +kind: pipeline +type: docker +name: default + +steps: +- name: kaniko + image: banzaicloud/drone-kaniko + settings: + registry: registry.kmlabz.com + repo: tormachris/${DRONE_REPO_NAME} + username: + from_secret: DOCKER_USERNAME + password: + from_secret: DOCKER_PASSWORD + tags: + - latest + - ${DRONE_BUILD_NUMBER} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13a10e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +#Pycharm +.idea/ +*.iml +*wav \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b02525f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3-slim + +ADD mealapi requirements.txt /mealapi/ +WORKDIR /mealapi/ + +RUN pip3 install -r requirements.txt + +ENV GUNICORN_LOGLEVEL="info" + +EXPOSE 8000 +CMD ["gunicorn", "-b", "0.0.0.0:8000", "--log-level", "${GUNICORN_LOGLEVEL}", "app:app"] \ No newline at end of file diff --git a/mealAPI/app.py b/mealAPI/app.py new file mode 100644 index 0000000..dfb747b --- /dev/null +++ b/mealAPI/app.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +from flask import Flask +from flask_restful import Api +from healthcheck import HealthCheck +from utils import Config, register_all_error_handlers, health_database_status +from model import db +from schemas import ma +from resources import MealBaseResource + +app = Flask(__name__) +app.config.from_object(Config) + +api = Api(app) +health = HealthCheck() +ma.init_app(app) + + +@app.before_first_request +def init_db(): + db.create_all() + + +api.add_resource(MealBaseResource, "/meals") + +health.add_check(health_database_status) + +register_all_error_handlers(app) + +app.add_url_rule("/healthz", "healthcheck", view_func=lambda: health.run()) + +if __name__ != '__main__': + import logging + + gunicorn_logger = logging.getLogger('gunicorn.error') + app.logger.handlers = gunicorn_logger.handlers + app.logger.setLevel(gunicorn_logger.level) diff --git a/mealAPI/model/__init__.py b/mealAPI/model/__init__.py new file mode 100644 index 0000000..fc64209 --- /dev/null +++ b/mealAPI/model/__init__.py @@ -0,0 +1,3 @@ +from .db import db +from .meal import Meal +from .ingredient import Ingredient diff --git a/mealAPI/model/db.py b/mealAPI/model/db.py new file mode 100644 index 0000000..adea5a0 --- /dev/null +++ b/mealAPI/model/db.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() diff --git a/mealAPI/model/ingredient.py b/mealAPI/model/ingredient.py new file mode 100644 index 0000000..42edc6d --- /dev/null +++ b/mealAPI/model/ingredient.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +from .db import db +from .mealingredient import MealIngredient + + +class Ingredient(db.Model): + __tablename__ = 'Ingredient' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String, nullable=False, unique=True) + value = db.Column(db.String, nullable=False) + items = db.relationship('Meal', secondary=MealIngredient, back_populates='Ingredient') diff --git a/mealAPI/model/meal.py b/mealAPI/model/meal.py new file mode 100644 index 0000000..93d6f3d --- /dev/null +++ b/mealAPI/model/meal.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +from .db import db +from .mealingredient import MealIngredient + + +class Meal(db.Model): + __tablename__ = 'Meal' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(255), nullable=False) + description = db.Column(db.Text, nullable=False) + kcal = db.Column(db.Integer, nullable=True) + price = db.Column(db.Integer, nullable=True) + spicy = db.Column(db.Boolean, nullable=False, default=False) + vegan = db.Column(db.Boolean, nullable=False, default=False) + glutenfree = db.Column(db.Boolean, nullable=False, default=False) + ingredients = db.relationship('Ingredient', secondary=MealIngredient, back_populates='Meal') diff --git a/mealAPI/model/mealingredient.py b/mealAPI/model/mealingredient.py new file mode 100644 index 0000000..e851658 --- /dev/null +++ b/mealAPI/model/mealingredient.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +from .db import db + + +class MealIngredient(db.Model): + __tablename__ = 'MealIngredient' + id = db.Column(db.Integer, primary_key=True, index=True) + itemId = db.Column(db.Integer, db.ForeignKey('Meal.id')) + detailId = db.Column(db.Integer, db.ForeignKey('Ingredient.id')) diff --git a/mealAPI/resources/__init__.py b/mealAPI/resources/__init__.py new file mode 100644 index 0000000..ab9c64b --- /dev/null +++ b/mealAPI/resources/__init__.py @@ -0,0 +1 @@ +from .mealresource import MealBaseResource diff --git a/mealAPI/resources/mealresource.py b/mealAPI/resources/mealresource.py new file mode 100644 index 0000000..16211bf --- /dev/null +++ b/mealAPI/resources/mealresource.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +from flask_restful import Resource +from flask import request, current_app, abort +from marshmallow import ValidationError + +from ..model import db, Meal +from ..schemas import MealSchema + + +class MealBaseResource(Resource): + + mealschema = MealSchema(many=False) + mealschemas = MealSchema(many=True) + + def post(self): + body = request.get_json() + + try: + mealobj = self.mealschema.load(body) + db.session.add(mealobj.data) + db.session.commit() + return '', 204 + except ValidationError: + abort(406, "meal validation error") + + def get(self): + meals = Meal.query.all() + return self.mealschemas.dump(list(meals)), 200 diff --git a/mealAPI/schemas/__init__.py b/mealAPI/schemas/__init__.py new file mode 100644 index 0000000..e0a2f21 --- /dev/null +++ b/mealAPI/schemas/__init__.py @@ -0,0 +1,3 @@ +from .marshm import ma +from .mealschema import MealSchema +from .ingredientschema import IngredientSchema diff --git a/mealAPI/schemas/ingredientschema.py b/mealAPI/schemas/ingredientschema.py new file mode 100644 index 0000000..c4f05d4 --- /dev/null +++ b/mealAPI/schemas/ingredientschema.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +from .marshm import ma +from ..model import Ingredient + + +class IngredientSchema(ma.ModelSchema): + + class Meta: + model = Ingredient diff --git a/mealAPI/schemas/marshm.py b/mealAPI/schemas/marshm.py new file mode 100644 index 0000000..deee47e --- /dev/null +++ b/mealAPI/schemas/marshm.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +from flask_marshmallow import Marshmallow + +ma = Marshmallow() diff --git a/mealAPI/schemas/mealschema.py b/mealAPI/schemas/mealschema.py new file mode 100644 index 0000000..2fa19bc --- /dev/null +++ b/mealAPI/schemas/mealschema.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +from .marshm import ma +from ..model import Meal +from .ingredientschema import IngredientSchema + + +class MealSchema(ma.ModelSchema): + + # A list of author objects + authors = ma.Nested(IngredientSchema, many=True) + + class Meta: + model = Meal diff --git a/mealAPI/utils/__init__.py b/mealAPI/utils/__init__.py new file mode 100644 index 0000000..6609036 --- /dev/null +++ b/mealAPI/utils/__init__.py @@ -0,0 +1,3 @@ +from .config import Config +from .error_handlers import register_all_error_handlers +from .healthchecks import health_database_status diff --git a/mealAPI/utils/config.py b/mealAPI/utils/config.py new file mode 100644 index 0000000..7e3fcb7 --- /dev/null +++ b/mealAPI/utils/config.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +import os + + +class Config: + PORT = 8080 + DEBUG = os.environ.get("INPUT_SERVICE_DEBUG", "true").lower() in ["true", "yes", "1"] + + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI') + # disable this for better performance + SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/mealAPI/utils/error_handlers.py b/mealAPI/utils/error_handlers.py new file mode 100644 index 0000000..77b247e --- /dev/null +++ b/mealAPI/utils/error_handlers.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + + +def get_standard_error_handler(code: int): + def error_handler(err): + return {"msg": str(err)}, code + + return error_handler + + +# function to register all handlers + + +def register_all_error_handlers(app): + error_codes_to_override = [404, 403, 401, 405, 400, 409, 422, 500] + + for code in error_codes_to_override: + app.register_error_handler(code, get_standard_error_handler(code)) \ No newline at end of file diff --git a/mealAPI/utils/healthchecks.py b/mealAPI/utils/healthchecks.py new file mode 100644 index 0000000..4159029 --- /dev/null +++ b/mealAPI/utils/healthchecks.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +from model import db + + +def health_database_status(): + is_database_working = True + output = 'database is ok' + try: + db.session.execute('SELECT 1') + except Exception as e: + output = str(e) + is_database_working = False + return is_database_working, output diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bd22824 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +gunicorn +Flask +Flask-RESTful +marshmallow +flask-marshmallow +Flask-SQLAlchemy +marshmallow-sqlalchemy +py-healthcheck +psycopg2-binary \ No newline at end of file