From 8bff435ef342fd29339f9ad38115377f2f2cd86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torma=20Krist=C3=B3f?= Date: Thu, 11 Nov 2021 18:52:56 +0100 Subject: [PATCH] complete easy endpoints --- requirements.txt | 3 +- src/app.py | 16 +++++-- src/config.py | 2 +- src/errorhandlers.py | 27 +++++++++++ src/healthchecks.py | 17 +++++++ src/resources.py | 112 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/errorhandlers.py create mode 100644 src/healthchecks.py diff --git a/requirements.txt b/requirements.txt index aac4cc2..2ee2556 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ Flask Flask-RESTful marshmallow flask-marshmallow -flask-redis \ No newline at end of file +flask-redis +py-healthcheck diff --git a/src/app.py b/src/app.py index 2965c02..96c3c2e 100644 --- a/src/app.py +++ b/src/app.py @@ -2,12 +2,16 @@ from flask import Flask from flask_restful import Api import sentry_sdk +from healthcheck import HealthCheck from sentry_sdk.integrations.flask import FlaskIntegration from redis_client import redis_client from config import Config from marshm import ma -from resources import SampleResource +from errorhandlers import register_all_error_handlers +from healthchecks import redis_available +from resources import ServiceDiscoveryResource, ServiceLocationResource, ServiceDatabaseResource, \ + ServiceDatabaseItemResource """ Main Flask RESTful API @@ -33,12 +37,18 @@ app.config.from_object(Config) api = Api(app) ma.init_app(app) - +health = HealthCheck() redis_client.init_app(app) +api.add_resource(ServiceDiscoveryResource, "/service/directory") +api.add_resource(ServiceLocationResource, "/service/location/") +api.add_resource(ServiceDatabaseResource, "/service") +api.add_resource(ServiceDatabaseItemResource, "/service/") -api.add_resource(SampleResource, "/input") +health.add_check(redis_available) +app.add_url_rule("/healthz", "healthcheck", view_func=lambda: health.run()) +register_all_error_handlers(app) if __name__ != '__main__': import logging diff --git a/src/config.py b/src/config.py index 0654aef..8a8bb08 100644 --- a/src/config.py +++ b/src/config.py @@ -19,4 +19,4 @@ class Config: RELEASE_ID = os.environ.get("RELEASE_ID", "test") RELEASEMODE = os.environ.get("RELEASEMODE", "dev") - REDIS_URL = os.environ['CACHE_REDIS_URL'] + REDIS_URL = os.environ['REDIS_URL'] diff --git a/src/errorhandlers.py b/src/errorhandlers.py new file mode 100644 index 0000000..a8fa3c1 --- /dev/null +++ b/src/errorhandlers.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +""" +Flask error handler functions +""" + +__author__ = '@tormakris' +__copyright__ = "Copyright 2021, KMLabz Team" +__module_name__ = "errorhandlers" +__version__text__ = "1" + + +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] + + 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/src/healthchecks.py b/src/healthchecks.py new file mode 100644 index 0000000..4bf2f89 --- /dev/null +++ b/src/healthchecks.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +from redis_client import redis_client + +""" +Healthchek functions +""" + +__author__ = "@tormakris" +__copyright__ = "Copyright 2021, KMLabz Team" +__module_name__ = "healthchecks" +__version__text__ = "1" + + +def redis_available(): + redisstatus = redis_client.info() + return True, redisstatus diff --git a/src/resources.py b/src/resources.py index e69de29..a57828f 100644 --- a/src/resources.py +++ b/src/resources.py @@ -0,0 +1,112 @@ +""" +Endpoinds to serve location API +""" + +__author__ = '@tormakris' +__copyright__ = "Copyright 2021, KMLabz Team" +__module_name__ = "resources" +__version__text__ = "1" + +import json +import random + +from flask import request, current_app, abort +from flask_restful import Resource + +from redis_client import redis_client +from schemas import ServiceLocatorSchema, ServiceDirectorySchema + + +class ServiceDiscoveryResource(Resource): + """ + Service Discovery endpoint + """ + servicedirectoryschema = ServiceDirectorySchema(many=True) + + def get(self): + try: + servicelist = self.servicedirectoryschema.load(json.load(redis_client.get('servicelist').decode('UTF-8'))) + except Exception as e: + current_app.logger.info(e) + abort(404, "not found") + return self.servicedirectoryschema.dump(servicelist), 200 + + +class ServiceLocationResource(Resource): + """ + Service location endpoint + """ + servicelocatorschema = ServiceLocatorSchema(many=False) + + def get(self, serviceid: str): + try: + serviceobject: dict = self.servicelocatorschema.load(json.load(redis_client.get(serviceid).decode('UTF-8'))) + servicelocation: str = random.choice(serviceobject['servicearray'])['location'] + except Exception as e: + current_app.logger.info(e) + abort(404, "not found") + return {"location": servicelocation}, 200 + + +class ServiceDatabaseResource(Resource): + """ + Service database endpoint + """ + servicelocatorschema = ServiceLocatorSchema(many=False) + servicedirectoryschema = ServiceDirectorySchema(many=True) + + def post(self): + try: + body = request.get_json() + except Exception as e: + current_app.logger.info(e) + abort(400, "JSON parse error") + try: + servicelocation = self.servicelocatorschema.load(body) + except Exception as e: + current_app.logger.info(e) + abort(417, "invalid JSON schema") + try: + redis_client.set(servicelocation['id'], self.servicelocatorschema.dump(servicelocation).encode('UTF-8')) + servicelist = self.servicedirectoryschema.load(json.load(redis_client.get('servicelist').decode('UTF-8'))) + servicelist.append({"name": servicelocation['name'], "id": servicelocation['id']}) + redis_client.set('servicelist', self.servicedirectoryschema.dump(servicelist).encode('UTF-8')) + except Exception as e: + current_app.logger.info(e) + abort(404, "not found") + return 200 + + +class ServiceDatabaseItemResource(Resource): + """ + Service database endpoint interacting with individual items + """ + servicelocatorschema = ServiceLocatorSchema(many=False) + servicedirectoryschema = ServiceDirectorySchema(many=True) + + def put(self, serviceid: str): + try: + body = request.get_json() + except Exception as e: + current_app.logger.info(e) + abort(400, "JSON parse error") + try: + servicelocation = self.servicelocatorschema.load(body) + except Exception as e: + current_app.logger.info(e) + abort(417, "invalid JSON schema") + try: + redis_client.get(serviceid) + redis_client.set(serviceid, self.servicelocatorschema.dump(servicelocation).encode('UTF-8')) + except Exception as e: + current_app.logger.info(e) + abort(404, "not found") + return 200 + + def get(self, serviceid: str): + try: + servicelocation = self.servicelocatorschema.load(json.load(redis_client.get(serviceid).decode('UTF-8'))) + except Exception as e: + current_app.logger.info(e) + abort(404, "not found") + return servicelocation, 200