Skip to content

Commit

Permalink
Merge pull request #29 from KnowledgeSeed/webpush
Browse files Browse the repository at this point in the history
support long running task
  • Loading branch information
ote82 authored Nov 8, 2023
2 parents 03c0507 + 6472dea commit b95ada9
Show file tree
Hide file tree
Showing 24 changed files with 193 additions and 32 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ analogic/tests/apps/camemptyrepo/app.json
analogic/tests/apps/camsecure/app.json
analogic/tests/apps/default/app.json
analogic/tests/apps/logincam/app.json
long_running_tasks
7 changes: 6 additions & 1 deletion analogic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
from .exceptions import AnalogicProxyException
from .exceptions import AnalogicTM1ServiceException
from .exceptions import AnalogicAccessDeniedException
from .logged_in_signal import logged_in
from .exceptions import AnalogicAcceptedException
from .signals import logged_in
from .signals import before_call_do_proxy
from .multi_authentication_provider_interface import MultiAuthenticationProviderInterface
from .session_handler import SessionHandler
from .signal_receiver import SignalReceiver
from .default_signal_receiver import DefaultSignalReceiver
from .long_running_task_executor import LongRunningTaskExecutor
46 changes: 38 additions & 8 deletions analogic/analogic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import glob
import os
from flask import Flask, Blueprint, request, send_file, session, render_template
import typing as t
Expand All @@ -10,12 +9,14 @@
from importlib import resources
from analogic.core_endpoints import core_endpoints
from analogic.authentication_provider import AuthenticationProvider
from analogic.signal_receiver import SignalReceiver
import inspect
from analogic.setting import SettingManager
from datetime import timedelta
import importlib
from analogic.task import scheduler
import atexit
from analogic.default_signal_receiver import DefaultSignalReceiver

APPLICATIONS_DIR = 'apps'
APPLICATIONS_DIR_EXTRA = os.environ.get('APPLICATIONS_DIR_EXTRA', '')
Expand All @@ -40,11 +41,12 @@ def __init__(self, *args, **kwargs):
'MultiAuthenticationProvider': 'analogic',
'NoLogin': 'analogic'
}
self.conditions = {}
self.signal_receivers = {}
self.extension_assets = {}
self.add_url_rule('/extension_asset', methods=['GET'], view_func=self.extension_asset)
self.analogic_applications = {}
self.initialize_auth_providers = True
self.long_running_tasks = {}

def register_analogic_url_rules(self, instance):
for url_rule in self.endpoint_rules:
Expand All @@ -62,11 +64,24 @@ def register_analogic_endpoint(self, endpoint: "AnalogicEndpoint", **options: t.
def register_authentication_provider(self, name, module_name):
self.authentication_providers[name] = module_name

def register_condition(self, name, module_name):
self.conditions[name] = module_name
def register_signal_receiver(self, name, module_name):
self.signal_receivers[name] = module_name

def evaluate_signal_receivers(self):
for k,v in self.signal_receivers.items():

class_name = k
module_name = v

if module_name in sys.modules:
module = sys.modules[module_name]
else:
module = importlib.import_module(module_name)

signal_receiver_class = getattr(module, class_name)
signal_receiver = signal_receiver_class()
signal_receiver.initialize()

def get_condition_module_name(self, name):
return self.conditions[name]

def get_authentication_provider_module_name(self, name):
return self.authentication_providers[name]
Expand Down Expand Up @@ -208,6 +223,10 @@ def create_app(instance_path, start_scheduler=True, initialize_auth_providers=Tr
app.register_error_handler(404, page_not_found)
app.register_error_handler(500, page_error)

with app.app_context():
app.evaluate_signal_receivers()
DefaultSignalReceiver().initialize()

scheduler.init_app(app)

if start_scheduler:
Expand All @@ -227,8 +246,12 @@ def _load_logging(app):
log_config = json.load(file)
for h in log_config['handlers']:
if 'filename' in log_config['handlers'][h]:
log_config['handlers'][h]['filename'] = os.path.join(app.instance_path,
log_config['handlers'][h]['filename'])
if h == 'scheduler_file_handler':
file_name = f"logs/scheduler_{os.getpid()}.log"
else:
file_name = log_config['handlers'][h]['filename']

log_config['handlers'][h]['filename'] = os.path.join(app.instance_path, file_name)

logging.config.dictConfig(log_config)

Expand Down Expand Up @@ -273,6 +296,13 @@ def _register_extension_components(app, extension_name, files):
logging.getLogger(__name__).info('Registering authentication provider ' + extension_name + "." + name)
app.register_authentication_provider(name, extension_name)

if inspect.isclass(obj) and \
not inspect.isabstract(obj) and \
issubclass(obj, SignalReceiver) and \
app.signal_receivers.get(name) is None:
logging.getLogger(__name__).info('Registering signal receiver ' + extension_name + "." + name)
app.register_signal_receiver(name, extension_name)

if isinstance(obj, AnalogicEndpoint):
logging.getLogger(__name__).info('Registering analogic endpoing ' + name)
app.register_analogic_endpoint(obj)
Expand Down
20 changes: 17 additions & 3 deletions analogic/authentication_provider.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from flask import session, current_app, send_file, request, jsonify, Response
from analogic.loader import ClassLoader
import analogic.pivot as PivotApi
from analogic.exceptions import AnalogicProxyException, AnalogicAccessDeniedException
from analogic.exceptions import AnalogicProxyException, AnalogicAccessDeniedException, AnalogicException, \
AnalogicAcceptedException
from analogic.session_handler import SessionHandler
import logging
import pandas as pd
from abc import ABC, abstractmethod
from analogic.signals import before_call_do_proxy
from functools import wraps
import orjson

Expand Down Expand Up @@ -65,6 +67,7 @@ def __init__(self, setting):
self.logged_in_user_session_name = '_logged_in_user_name'
self._logger = logging.getLogger(self.setting.get_instance())
self.session_handler = SessionHandler(self.setting.get_instance_and_name())
self.user_subscriptions = {}

def initialize(self):
self.setting.initialize()
Expand Down Expand Up @@ -156,7 +159,8 @@ def _get_check_access_mdx(self, force_server_side_query=False):
mdx = self._set_custom_mdx_data(mdx)

for k in body:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))
if type(body[k]) is not dict:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))

return mdx.encode('utf-8')

Expand All @@ -183,7 +187,8 @@ def _get_server_side_mdx(self, force_server_side_query=False):

mdx = self._set_custom_mdx_data(mdx)
for k in body:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))
if type(body[k]) is not dict:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))

return mdx.encode('utf-8')
else:
Expand Down Expand Up @@ -324,10 +329,19 @@ def proxy(self, sub_path, encode_content=False, method=None, force_server_side_q

try:
mdx = self._get_server_side_mdx(force_server_side_query)

before_call_do_proxy.send(self, url=url, method=meth, data=mdx, headers=headers, cookies=cookies,
encode_content=encode_content)

response = self.do_proxy_request(url, meth, mdx, headers, cookies, encode_content)
except AnalogicProxyException as e:
self._logger.error(e, exc_info=True)
return {'message': 'Something went wrong {}'.format(e)}, 500, {'Content-Type': 'application/json'}
except AnalogicAcceptedException as e:
return {'message': str(e)}, 202, {'Content-Type': 'application/json'}
except AnalogicException as e:
self._logger.error(e, exc_info=True)
return {'message': str(e)}, 500, {'Content-Type': 'application/json'}
except AnalogicAccessDeniedException as e:
self._logger.error(e, exc_info=True)
return {'message': 'Something went wrong {}'.format(e)}, 403, {'Content-Type': 'application/json'}
Expand Down
5 changes: 3 additions & 2 deletions analogic/cam.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from analogic.exceptions import AnalogicTM1ServiceException
from analogic.loader import ClassLoader
from analogic.authentication_provider import login_required
from analogic.logged_in_signal import logged_in
from analogic.signals import logged_in
from analogic.multi_authentication_provider_interface import MultiAuthenticationProviderInterface


Expand Down Expand Up @@ -88,7 +88,8 @@ def _get_server_side_mdx(self, force_server_side_query=False):
mdx = self.setting.get_mdx(key)
mdx = self._set_custom_mdx_data(mdx)
for k in body:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))
if type(body[k]) is not dict:
mdx = mdx.replace('$' + k, body[k].replace('"', '\\"'))

return mdx.encode('utf-8')

Expand Down
2 changes: 1 addition & 1 deletion analogic/camsecure.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from analogic.cam import Cam
from flask import request, make_response, redirect
from analogic.logged_in_signal import logged_in
from analogic.signals import logged_in


class CamSecure(Cam):
Expand Down
8 changes: 2 additions & 6 deletions analogic/core_endpoints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from analogic.endpoint import AnalogicEndpoint
from analogic.authentication_provider import get_authentication_provider


core_endpoints = AnalogicEndpoint('core_endpoints', __name__)


Expand Down Expand Up @@ -43,12 +44,6 @@ def export():
def clear_cache():
return get_authentication_provider().clear_cache()


# @core_endpoints.analogic_endpoint_route('/ping', methods=['GET'])
# def ping():
# return get_authentication_provider().ping()


@core_endpoints.analogic_endpoint_route('/pivot', methods=['GET', 'POST'])
def pivot():
return get_authentication_provider().pivot()
Expand All @@ -57,3 +52,4 @@ def pivot():
@core_endpoints.analogic_endpoint_route('/middleware', methods=['GET', 'POST'])
def middleware():
return get_authentication_provider().middleware()

52 changes: 52 additions & 0 deletions analogic/default_signal_receiver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from analogic.signal_receiver import SignalReceiver
from flask import current_app
import os
import sys
import logging
class DefaultSignalReceiver(SignalReceiver):

def initialize(self):
for name, analogic_app in current_app.analogic_applications.items():

if analogic_app.get_setting().get_config().get('authenticationMode') == 'MultiAuthenticationProvider':

for n, sub_analogic_app in analogic_app.authentication_providers.items():

self._connect_signal_and_handler(sub_analogic_app)
else:

self._connect_signal_and_handler(analogic_app)

def _connect_signal_and_handler(self, analogic_app):

setting = analogic_app.get_setting()

path = os.path.join(setting.site_root, 'server', 'configs', 'signal_subscriptions.json')

if os.path.exists(path):

sub = setting._get_json_setting(os.path.join('server', 'configs', 'signal_subscriptions'))

for subscription in sub.get('subscriptions'):
try:
signal_params = subscription.get('signal')

handler_params = subscription.get('handler')

named_signal = getattr(sys.modules[signal_params.get('namespace')], signal_params.get('name'))

if handler_params.get('class') is None:

handler = getattr(sys.modules[handler_params.get('namespace')], handler_params.get('method'))

else:

handler = getattr(getattr(sys.modules[handler_params.get('namespace')], handler_params.get('class')), handler_params.get('method'))

getattr(named_signal, 'connect')(handler, analogic_app)

except Exception as e:
logger = logging.getLogger(__name__)
logger.error(f'Unable to connect signal {setting.get_instance()}')
logger.error(e, exc_info=True)

5 changes: 4 additions & 1 deletion analogic/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
class EmailManager:

def send_email(self, request, tm1_service, setting, authentication_provider):
post_data = request.json if request.method == 'POST' else request.values.to_dict()
return self.send_email_by_params(setting, post_data)

def send_email_by_params(self, setting, post_data):
logger = logging.getLogger(setting.get_instance())

post_data = request.json if request.method == 'POST' else request.values.to_dict()

if 'email_template' not in post_data:
return self._error('missing email_template parameter from request', logger)
Expand Down
7 changes: 7 additions & 0 deletions analogic/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,10 @@ def __init__(self, message):

def __str__(self):
return self.message

class AnalogicAcceptedException(Exception):
def __init__(self, message):
self.message = message

def __str__(self):
return self.message
4 changes: 0 additions & 4 deletions analogic/logged_in_signal.py

This file was deleted.

2 changes: 1 addition & 1 deletion analogic/loginbasic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from analogic.analogic_tm1_service import AnalogicTM1Service
from analogic.exceptions import AnalogicTM1ServiceException
from flask import render_template, request, make_response, redirect, session, Response
from analogic.logged_in_signal import logged_in
from analogic.signals import logged_in
from analogic.multi_authentication_provider_interface import MultiAuthenticationProviderInterface


Expand Down
18 changes: 18 additions & 0 deletions analogic/long_running_task_executor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from abc import ABC, abstractmethod
class LongRunningTaskExecutor(ABC):

@abstractmethod
def send_long_running_request(self, url, method, data, headers, cookies, encode_content) -> dict:
pass

@abstractmethod
def on_long_running_request_canceled(self, **kwargs):
pass

@abstractmethod
def on_long_running_request_completed(self, **kwargs):
pass

@abstractmethod
def check_long_running_request(self, **kwargs) -> bool:
pass
2 changes: 1 addition & 1 deletion analogic/multi_authentication_provider.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from analogic.authentication_provider import AuthenticationProvider
from analogic.multi_setting import MultiSettingManager
from flask import current_app, request
from analogic.logged_in_signal import logged_in
from analogic.signals import logged_in
from analogic.multi_authentication_provider_interface import MultiAuthenticationProviderInterface
from rich.prompt import Prompt

Expand Down
5 changes: 5 additions & 0 deletions analogic/signal_receiver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from abc import ABC, abstractmethod
class SignalReceiver(ABC):
@abstractmethod
def initialize(self):
pass
7 changes: 7 additions & 0 deletions analogic/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from blinker import Namespace

my_signals = Namespace()

logged_in = my_signals.signal('logged_in')

before_call_do_proxy = my_signals.signal('before_call_do_proxy')
6 changes: 6 additions & 0 deletions analogic/static/assets/js/framework/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ Auth.getAjaxRequest = (url, data, type, widgetId = '', resent = false, eventMapI
statusCode: {
401: function () {
Auth.handle401();
},
202: function(resp) {
if (resp.message) {
Api.showPopup(resp.message, 400);
}
}
}
});
Expand Down Expand Up @@ -93,6 +98,7 @@ Auth.handle401 = () => {
Extensions.authenticationProviders.forEach(ext => ext.handle401());
};


Auth.getHeader = (contentType = 'application/json; charset=utf-8', accept = 'application/json; charset=utf-8') => {
let headers = {};

Expand Down
Loading

0 comments on commit b95ada9

Please sign in to comment.