Added flask support #70 #75 #71

Merged
edraft merged 107 commits from #70 into 0.3 2022-11-05 13:55:42 +01:00
20 changed files with 235 additions and 3 deletions
Showing only changes of commit 0019f868ad - Show all commits

View File

@ -11,13 +11,13 @@
"boot-log": "src/modules/boot_log/boot-log.json",
"database": "src/modules/database/database.json",
"moderator": "src/modules/moderator/moderator.json",
"permission": "src/modules/permission/permission.json"
"permission": "src/modules/permission/permission.json",
"bot-api": "src/bot_api/bot-api.json"
},
"Scripts": {
"prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
"stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
"dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
"build-docker": "cpl b; docker-compose down; docker build -t kdb .",
"compose": "docker-compose up -d",
"docker": "cpl build-docker; cpl compose;"

View File

@ -7,6 +7,10 @@ from cpl_discord.configuration import DiscordBotSettings
from cpl_discord.service import DiscordBotServiceABC, DiscordBotService
from cpl_translation import TranslatePipe, TranslationServiceABC, TranslationSettings
from bot_api.api_thread import ApiThread
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
class Application(DiscordBotApplicationABC):
@ -14,6 +18,7 @@ class Application(DiscordBotApplicationABC):
DiscordBotApplicationABC.__init__(self, config, services)
self._services = services
self._config = config
# cpl-core
self._logger: LoggerABC = services.get_service(LoggerABC)
@ -24,6 +29,12 @@ class Application(DiscordBotApplicationABC):
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
self._t: TranslatePipe = services.get_service(TranslatePipe)
self._feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings)
# api
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
self._api: ApiThread = services.get_service(ApiThread)
self._is_stopping = False
async def configure(self):
@ -32,6 +43,12 @@ class Application(DiscordBotApplicationABC):
async def main(self):
try:
self._logger.debug(__name__, f'Starting...')
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
self._api.start()
if self._feature_flags.get_flag(FeatureFlagsEnum.api_only):
return
self._logger.trace(__name__, f'Try to start {DiscordBotService.__name__}')
await self._bot.start_async()
await self._bot.stop_async()
@ -53,4 +70,4 @@ class Application(DiscordBotApplicationABC):
Console.write_line()
def is_restart(self):
return True if self._configuration.get_configuration('IS_RESTART') == 'true' else False#
return True if self._configuration.get_configuration('IS_RESTART') == 'true' else False #

View File

@ -12,6 +12,12 @@
"FileLogLevel": "TRACE"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/",
"Filename": "api.log",
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
},
"Command": {
"Path": "logs/",
"Filename": "commands.log",

View File

@ -1,5 +1,6 @@
{
"FeatureFlags": {
"ApiModule": true,
"AdminModule": true,
"AutoRoleModule": true,
"BaseModule": true,

View File

@ -1,5 +1,6 @@
from cpl_query.extension import List
from bot_api.api_module import ApiModule
from bot_core.core_extension.core_extension_module import CoreExtensionModule
from bot_core.core_module import CoreModule
from bot_data.data_module import DataModule
@ -20,6 +21,7 @@ class ModuleList:
return List(type, [
CoreModule, # has to be first!
DataModule,
ApiModule,
AdminModule,
AutoRoleModule,
BaseModule,

View File

@ -9,6 +9,7 @@ from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.environment import ApplicationEnvironment
from cpl_core.logging import LoggerABC
from bot_api.logging.api_logger import ApiLogger
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
@ -40,6 +41,9 @@ class Startup(StartupABC):
services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
services.add_singleton(CustomFileLoggerABC, MessageLogger)
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
services.add_singleton(CustomFileLoggerABC, ApiLogger)
services.add_translation()
services.add_db_context(DBContext, self._config.get_configuration(DatabaseSettings))

1
src/bot_api/__init__.py Normal file
View File

@ -0,0 +1 @@
# imports:

52
src/bot_api/api.py Normal file
View File

@ -0,0 +1,52 @@
import sys
from functools import partial
from cpl_core.dependency_injection import ServiceProviderABC
from flask import Flask, request
from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route
class Api(Flask):
def __init__(
self,
logger: ApiLogger,
services: ServiceProviderABC,
*args, **kwargs
):
if not args:
kwargs.setdefault('import_name', __name__)
Flask.__init__(self, *args, **kwargs)
self._logger = logger
self._services = services
# register before request
self.before_request_funcs.setdefault(None, []).append(self.before_request)
def _register_routes(self):
for path, f in Route.registered_routes.items():
cls = None
qual_name_split = f.__qualname__.split('.')
if len(qual_name_split) > 0:
cls_type = vars(sys.modules[f.__module__])[qual_name_split[0]]
cls = self._services.get_service(cls_type)
partial_f = partial(f, self if cls is None else cls)
partial_f.__name__ = f.__name__
self.route(path)(partial_f)
def before_request(self, *args, **kwargs):
self._logger.debug(__name__, f'Received GET @{request.url}')
headers = str(request.headers).replace("\n", "\n\t")
self._logger.trace(__name__, f'Headers: \n\t{headers}')
self._logger.trace(__name__, f'Body: {request.get_json(force=True, silent=True)}')
def start(self):
self._logger.info(__name__, f'Starting API')
self._register_routes()
from waitress import serve
serve(self, host="0.0.0.0", port=5000)

26
src/bot_api/api_module.py Normal file
View File

@ -0,0 +1,26 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from flask import Flask
from bot_api.api import Api
from bot_api.api_thread import ApiThread
from bot_api.controller.api_controller import ApiController
from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
class ApiModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.api_module)
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_singleton(ApiThread)
services.add_singleton(Flask, Api)
services.add_transient(ApiController)

27
src/bot_api/api_thread.py Normal file
View File

@ -0,0 +1,27 @@
import threading
from bot_api.api import Api
from bot_api.logging.api_logger import ApiLogger
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
class ApiThread(threading.Thread):
def __init__(
self,
logger: ApiLogger,
api: Api,
feature_flags: FeatureFlagsSettings
):
threading.Thread.__init__(self, daemon=not feature_flags.get_flag(FeatureFlagsEnum.api_only))
self._logger = logger
self._api = api
def run(self) -> None:
try:
self._logger.trace(__name__, f'Try to start {type(self._api).__name__}')
self._api.start()
except Exception as e:
self._logger.error(__name__, 'Start failed', e)

48
src/bot_api/bot-api.json Normal file
View File

@ -0,0 +1,48 @@
{
"ProjectSettings": {
"Name": "bot-api",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core==2022.10.0.post6",
"Flask==2.2.2",
"Flask-Classful==0.14.2"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "bot_api.main",
"EntryPoint": "bot-api",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

View File

@ -0,0 +1,22 @@
from cpl_translation import TranslatePipe
from bot_api.api import Api
from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route
class ApiController:
def __init__(
self,
logger: ApiLogger,
t: TranslatePipe,
api: Api
):
self._logger = logger
self._t = t
self._api = api
@Route.route('/api/hello-world')
def hello_world(self):
return self._t.transform('common.hello_world')

View File

View File

View File

@ -0,0 +1,11 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_core.time import TimeFormatSettings
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
class ApiLogger(CustomFileLoggerABC):
def __init__(self, config: ConfigurationABC, time_format: TimeFormatSettings, env: ApplicationEnvironmentABC):
CustomFileLoggerABC.__init__(self, 'Api', config, time_format, env)

View File

View File

@ -0,0 +1,11 @@
class Route:
registered_routes = {}
@classmethod
def route(cls, path=None):
# simple decorator for class based views
def inner(fn):
cls.registered_routes[path] = fn
return fn
return inner

View File

@ -4,6 +4,7 @@ from enum import Enum
class FeatureFlagsEnum(Enum):
# modules
api_module = 'ApiModule'
admin_module = 'AdminModule'
auto_role_module = 'AutoRoleModule'
base_module = 'BaseModule'
@ -15,4 +16,5 @@ class FeatureFlagsEnum(Enum):
moderator_module = 'ModeratorModule'
permission_module = 'PermissionModule'
# features
api_only = 'ApiOnly'
presence = 'Presence'

View File

@ -14,6 +14,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
self._flags = {
# modules
FeatureFlagsEnum.api_module.value: False, # 13.10.2022 #70
FeatureFlagsEnum.admin_module.value: False, # 02.10.2022 #48
FeatureFlagsEnum.auto_role_module.value: True, # 03.10.2022 #54
FeatureFlagsEnum.base_module.value: True, # 02.10.2022 #48
@ -25,6 +26,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
FeatureFlagsEnum.moderator_module.value: False, # 02.10.2022 #48
FeatureFlagsEnum.permission_module.value: True, # 02.10.2022 #48
# features
FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70
FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56
}