Merge branch 'prototype/graphql/ariadne' into #162

# Conflicts:
#	kdb-bot/cpl-workspace.json
#	kdb-bot/src/bot/config
#	kdb-bot/src/bot/module_list.py
#	kdb-bot/src/bot_api/api.py
#	kdb-bot/src/bot_data/service/client_repository_service.py
#	kdb-bot/src/bot_data/service/server_repository_service.py
This commit is contained in:
2023-01-15 02:25:05 +01:00
28 changed files with 721 additions and 79 deletions

View File

@@ -22,21 +22,23 @@ from bot_api.exception.service_exception import ServiceException
from bot_api.logging.api_logger import ApiLogger
from bot_api.model.error_dto import ErrorDTO
from bot_api.route.route import Route
from bot_graphql.graphql_service import GraphQLService
class Api(Flask):
def __init__(
self,
logger: ApiLogger,
services: ServiceProviderABC,
api_settings: ApiSettings,
frontend_settings: FrontendSettings,
auth_settings: AuthenticationSettings,
*args,
**kwargs,
self,
logger: ApiLogger,
services: ServiceProviderABC,
api_settings: ApiSettings,
frontend_settings: FrontendSettings,
auth_settings: AuthenticationSettings,
graphql: GraphQLService,
*args, **kwargs
):
if not args:
kwargs.setdefault("import_name", __name__)
kwargs.setdefault('import_name', __name__)
Flask.__init__(self, *args, **kwargs)
@@ -56,21 +58,17 @@ class Api(Flask):
self.register_error_handler(exc_class, self.handle_exception)
# websockets
self._socketio = SocketIO(self, cors_allowed_origins="*", path="/api/socket.io")
self._socketio.on_event("connect", self.on_connect)
self._socketio.on_event("disconnect", self.on_disconnect)
self._socketio = SocketIO(self, cors_allowed_origins='*', path='/api/socket.io')
self._socketio.on_event('connect', self.on_connect)
self._socketio.on_event('disconnect', self.on_disconnect)
self._requests = {}
@staticmethod
def _get_methods_from_registered_route() -> Union[list[str], str]:
methods = ["Unknown"]
if (
request.path in Route.registered_routes
and len(Route.registered_routes[request.path]) >= 1
and "methods" in Route.registered_routes[request.path][1]
):
methods = Route.registered_routes[request.path][1]["methods"]
methods = ['Unknown']
if request.path in Route.registered_routes and len(Route.registered_routes[request.path]) >= 1 and 'methods' in Route.registered_routes[request.path][1]:
methods = Route.registered_routes[request.path][1]['methods']
if len(methods) == 1:
return methods[0]
@@ -81,7 +79,7 @@ class Api(Flask):
route = f[0]
kwargs = f[1]
cls = None
qual_name_split = route.__qualname__.split(".")
qual_name_split = route.__qualname__.split('.')
if len(qual_name_split) > 0:
cls_type = vars(sys.modules[route.__module__])[qual_name_split[0]]
cls = self._services.get_service(cls_type)
@@ -91,7 +89,7 @@ class Api(Flask):
self.route(path, **kwargs)(partial_f)
def handle_exception(self, e: Exception):
self._logger.error(__name__, f"Caught error", e)
self._logger.error(__name__, f'Caught error', e)
if isinstance(e, ServiceException):
ex: ServiceException = e
@@ -104,7 +102,7 @@ class Api(Flask):
return jsonify(error.to_dict()), 404
else:
tracking_id = uuid.uuid4()
user_message = f"Tracking Id: {tracking_id}"
user_message = f'Tracking Id: {tracking_id}'
self._logger.error(__name__, user_message, e)
error = ErrorDTO(None, user_message)
return jsonify(error.to_dict()), 400
@@ -114,42 +112,34 @@ class Api(Flask):
self._requests[request] = request_id
method = request.access_control_request_method
self._logger.info(
__name__,
f"Received {request_id} @ {self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}",
)
self._logger.info(__name__, f'Received {request_id} @ {self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}')
headers = str(request.headers).replace("\n", "\n\t\t")
headers = str(request.headers).replace('\n', '\n\t\t')
data = request.get_data()
data = "" if len(data) == 0 else str(data.decode(encoding="utf-8"))
data = '' if len(data) == 0 else str(data.decode(encoding="utf-8"))
text = textwrap.dedent(
f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tUser-Agent: {request.user_agent.string}\n\tBody: {data}"
)
text = textwrap.dedent(f'Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tUser-Agent: {request.user_agent.string}\n\tBody: {data}')
self._logger.trace(__name__, text)
def after_request_hook(self, response: Response):
method = request.access_control_request_method
request_id = f"{self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}"
request_id = f'{self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}'
if request in self._requests:
request_id = self._requests[request]
self._logger.info(__name__, f"Answered {request_id}")
self._logger.info(__name__, f'Answered {request_id}')
headers = str(request.headers).replace("\n", "\n\t\t")
headers = str(request.headers).replace('\n', '\n\t\t')
data = request.get_data()
data = "" if len(data) == 0 else str(data.decode(encoding="utf-8"))
data = '' if len(data) == 0 else str(data.decode(encoding="utf-8"))
text = textwrap.dedent(f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tResponse: {data}")
text = textwrap.dedent(f'Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tResponse: {data}')
self._logger.trace(__name__, text)
return response
def start(self):
self._logger.info(
__name__,
f"Starting API {self._api_settings.host}:{self._api_settings.port}",
)
self._logger.info(__name__, f'Starting API {self._api_settings.host}:{self._api_settings.port}')
self._register_routes()
self.secret_key = CredentialManager.decrypt(self._auth_settings.secret_key)
# from waitress import serve
@@ -158,11 +148,11 @@ class Api(Flask):
wsgi.server(
eventlet.listen((self._api_settings.host, self._api_settings.port)),
self,
log_output=False,
log_output=False
)
def on_connect(self):
self._logger.info(__name__, f"Client connected")
self._logger.info(__name__, f'Client connected')
def on_disconnect(self):
self._logger.info(__name__, f"Client disconnected")
self._logger.info(__name__, f'Client disconnected')

View File

@@ -14,6 +14,7 @@ from bot_api.api_thread import ApiThread
from bot_api.controller.auth_controller import AuthController
from bot_api.controller.auth_discord_controller import AuthDiscordController
from bot_api.controller.discord.server_controller import ServerController
from bot_api.controller.grahpql_controller import GraphQLController
from bot_api.controller.gui_controller import GuiController
from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent
from bot_api.service.auth_service import AuthService
@@ -46,6 +47,7 @@ class ApiModule(ModuleABC):
services.add_transient(GuiController)
services.add_transient(DiscordService)
services.add_transient(ServerController)
services.add_transient(GraphQLController)
# cpl-discord
self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent)

View File

@@ -0,0 +1,44 @@
from ariadne import graphql_sync
from ariadne.constants import PLAYGROUND_HTML
from cpl_core.configuration import ConfigurationABC
from cpl_core.environment import ApplicationEnvironmentABC
from flask import request, jsonify
from graphql import MiddlewareManager
from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route
from bot_graphql.schema import Schema
class GraphQLController:
BasePath = f'/api/graphql'
def __init__(
self,
config: ConfigurationABC,
env: ApplicationEnvironmentABC,
logger: ApiLogger,
schema: Schema,
):
self._config = config
self._env = env
self._logger = logger
self._schema = schema
@Route.get(f'{BasePath}/playground')
async def playground(self):
return PLAYGROUND_HTML, 200
@Route.post(f'{BasePath}')
async def graphql(self):
data = request.get_json()
# Note: Passing the request to the context is optional.
# In Flask, the current request is always accessible as flask.request
success, result = graphql_sync(
self._schema.schema,
data,
context_value=request
)
return jsonify(result), 200 if success else 400