bug fix: plugin functions, streamline cli runner

pull/12/head
Steve Nyemba 2 months ago
parent a0cf018edf
commit 763787ea02

@ -43,6 +43,13 @@ INVALID_FOLDER = """
# handling cli interface # handling cli interface
cli = typer.Typer() cli = typer.Typer()
def get_manifest (manifest):
if not manifest.endswith('json') and os.path.isdir(manifest):
manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1])
return os.sep.join([manifest,'qcms-manifest.json'])
else:
return manifest
@cli.command(name="info") @cli.command(name="info")
def _info(): def _info():
@ -174,6 +181,7 @@ def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file
""" """
Manage plugins list loaded plugins, Manage plugins list loaded plugins,
""" """
manifest = get_manifest(manifest)
_config = config.get(manifest) _config = config.get(manifest)
_root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins']) _root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins'])
if os.path.exists(_root) : if os.path.exists(_root) :
@ -185,13 +193,15 @@ def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file
if not _plugins : if not _plugins :
_msg = f"""{FAILED} no plugins are loaded\n\t{manifest}""" _msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
else: else:
_data = plugins.stats(_plugins) # _data = plugins.stats(_plugins)
_data = cms.Plugin.stats(_plugins)
print (_data) print (_data)
_msg = f"""{PASSED} found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}""" _msg = f"""{PASSED} found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}"""
if add in [True,False] and pointer : if add in [True,False] and pointer :
file,fnName = pointer.split('.') file,fnName = pointer.split('.')
_fnpointer = plugins.load(_root,file+'.py',fnName) # _fnpointer = plugins.load(_root,file+'.py',fnName)
_fnpointer = cms.Plugin.load(_root,file+'.py',fnName)
if _fnpointer and add: if _fnpointer and add:
if file not in _plugins : if file not in _plugins :
_plugins[file] = [] _plugins[file] = []
@ -269,7 +279,7 @@ def reload (
""" """
Reload a site/portal given the manifest ... Reload a site/portal given the manifest ...
""" """
_config = config.get(path) _config = config.get( get_manifest(path))
if 'key' in _config['system']['source'] : if 'key' in _config['system']['source'] :
f = open(_config['system']['source']['key']) f = open(_config['system']['source']['key'])
key = f.read() key = f.read()
@ -293,9 +303,10 @@ def bootup (
""" """
This function will launch a site/project given the location of the manifest file This function will launch a site/project given the location of the manifest file
""" """
if not manifest.endswith('json') and os.path.isdir(manifest): # if not manifest.endswith('json') and os.path.isdir(manifest):
manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1]) # manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1])
manifest = os.sep.join([manifest,'qcms-manifest.json']) # manifest = os.sep.join([manifest,'qcms-manifest.json'])
manifest = get_manifest(manifest)
index.start(manifest,port) index.start(manifest,port)
@cli.command(name='theme') @cli.command(name='theme')
def handle_theme ( def handle_theme (
@ -307,6 +318,7 @@ def handle_theme (
This function will show the list available themes and can also set a theme in a manifest This function will show the list available themes and can also set a theme in a manifest
""" """
manifest = get_manifest(manifest)
_config = config.get(manifest) _config = config.get(manifest)
_root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']]) _root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']])

@ -6,8 +6,118 @@ import copy
from jinja2 import Environment, BaseLoader, FileSystemLoader from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib import importlib
import importlib.util import importlib.util
import json
class Plugin :
#
# decorator for plugin functions, this is a preliminary to enable future developement
#
def __init__(self,**_args):
self._mimetype = _args['mimetype']
if 'method' in _args :
_method = _args['method']
if type(_method) != list :
_method = [_method]
else:
_method = ['POST','GET']
self._method = _method
def __call__(self,_callback):
def wrapper(**_args):
return _callback(**_args)
setattr(wrapper,'method',self._method)
setattr(wrapper,'mimetype',self._mimetype)
return wrapper
@staticmethod
def load(_path,_filename,_name) :
"""
This function will load external module form a given location and return a pointer to a function in a given module
:path absolute path of the file (considered plugin) to be loaded
:name name of the function to be applied
"""
# _path = _args['path'] #os.sep.join([_args['root'],'plugin'])
if os.path.isdir(_path):
files = os.listdir(_path)
if files :
files = [name for name in files if name.endswith('.py') and name == _filename]
if files:
_path = os.sep.join([_path,files[0]])
else:
return None
else:
return None
#-- We have a file ...
# _name = _args['name']
spec = importlib.util.spec_from_file_location(_filename, _path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
#
# we need to make sure we have the plugin decorator here to make sure
return getattr(module,_name) if hasattr(module,_name) else None
@staticmethod
def stats (_config) :
"""
Returns the statistics of the plugins
"""
_data = []
for _name in _config :
_log = {"files":_name,"loaded":len(_config[_name]),"logs":json.dumps(_config[_name])}
_data.append(_log)
return pd.DataFrame(_data)
@staticmethod
def call(**_args):
"""
This function will execute a plugged-in function given
"""
# _uri = _args['uri'] if 'uri' in _args else '/'.join([_args['module'],_args['name']])
_handler= _args['handler'] #-- handler
_request= _args['request']
_uri = _args['uri'] #_request.path[1:]
#
# we need to update the _uri (if context/embeded apps)
#
_context = _handler.system()['context']
if _context :
_uri = f'{_context}/{_uri}'
_plugins = _handler.plugins()
_code = 200
if _uri in _plugins :
_config = _handler.config() #_args['config']
_pointer = _plugins[_uri]
if hasattr(_pointer,'mimetype') and _request.method in _pointer.method:
#
# we constraint the methods given their execution ...
_mimeType = _pointer.mimetype
_data = _pointer(request=_request,config=_config)
else:
_mimeType = 'application/octet-stream'
try:
_data,_mimeType = _pointer(request=_request,config=_config)
except Exception as e:
_data = _pointer(request=_request,config=_config)
pass
else:
_code = 404
#
# We should generate a 500 error in this case with a message ...
#
_mimeType = 'plain/html'
_data = f"""
<script>
</script>
"""
return _data,_code,{'Content-Type':_mimeType}
pass
# def _get_config (path) : # def _get_config (path) :
# if os.path.exists(path) : # if os.path.exists(path) :

@ -4,6 +4,11 @@ import importlib
import importlib.util import importlib.util
import os import os
#
# Defining the decorator to be used in plugins, this will enable simple loading and assigning mimetype to the output (if any)
#
def stats (_config) : def stats (_config) :
""" """
Returns the statistics of the plugins Returns the statistics of the plugins
@ -37,6 +42,9 @@ def load(_path,_filename,_name) :
spec = importlib.util.spec_from_file_location(_filename, _path) spec = importlib.util.spec_from_file_location(_filename, _path)
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) spec.loader.exec_module(module)
#
# we need to make sure we have the plugin decorator here to make sure
return getattr(module,_name) if hasattr(module,_name) else None return getattr(module,_name) if hasattr(module,_name) else None

@ -7,6 +7,7 @@ import flask
import transport import transport
from transport import providers from transport import providers
import cms import cms
from cms import Plugin
import sys import sys
import os import os
import json import json
@ -144,28 +145,16 @@ def _dialog (app):
_args['title'] = _id _args['title'] = _id
return render_template('dialog.html',**_args) #title=_id,html=_html) return render_template('dialog.html',**_args) #title=_id,html=_html)
@_app.route("/api/<module>/<name>",defaults={'app':'main','key':None}) @_app.route("/api/<module>/<name>",defaults={'app':'main','key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("/<app>/api/<module>/<name>",defaults={'key':None}) @_app.route("/<app>/api/<module>/<name>",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("/<app>/<key>/<module>/<name>",defaults={'key':None}) @_app.route("/<app>/<key>/<module>/<name>",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
def _delegate_call(app,key,module,name): def _delegate_call(app,key,module,name):
_handler = _getHandler(app,key) _handler = _getHandler(app,key)
return _delegate(_handler,module,name) # print (_handler.config()/)
uri = f'api/{module}/{name}'
# @_app.route('/api/<module>/<name>') return Plugin.call(uri=uri,handler=_handler,request=request)
@_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['GET']) # return _delegate(_handler,module,name)
@_app.route("/api/<module>/<name>",defaults={'app_id':'main','key':None})
@_app.route("/<app_id>/api/<module>/<name>",defaults={'key':None})
def _api(app_id,key,module,name) :
"""
This endpoint will load a module and make a function call
:_module entry specified in plugins of the configuration
:_name name of the function to execute
"""
_handler = _getHandler( app_id,key)
return _delegate(_handler,module,name)
def _delegate(_handler,module,name): def _delegate(_handler,module,name):
global _route global _route
@ -186,10 +175,13 @@ def _delegate(_handler,module,name):
# _data = pointer (**_args) # _data = pointer (**_args)
# else: # else:
# _data = pointer() # _data = pointer()
if hasattr(pointer,'mimetype') :
_data = pointer(request=request,config=_handler.config())
_mimeType = pointer.mimetype
else:
_data,_mimeType = pointer(request=request,config=_handler.config())
_data,_mimeType = pointer(request=request,config=_handler.config()) _mimeType = 'application/octet-stream' if not _mimeType else _mimeType
_mimeType = 'application/octet-stream' if not _mimeType else _mimeType
if type(_data) == pd.DataFrame : if type(_data) == pd.DataFrame :
_data = _data.to_dict(orient='records') _data = _data.to_dict(orient='records')
if type(_data) == list: if type(_data) == list:
@ -201,18 +193,18 @@ def _delegate(_handler,module,name):
# @_app.route('/<app_id>/api/<module>/<name>',methods=['POST'],defaults={'key':None}) # @_app.route('/<app_id>/api/<module>/<name>',methods=['POST'],defaults={'key':None})
# @_app.route('/<app_id>/<key>/api/<module>/<name>',methods=['POST'],defaults={'app_id':'main','key':None}) # @_app.route('/<app_id>/<key>/api/<module>/<name>',methods=['POST'],defaults={'app_id':'main','key':None})
@_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['POST']) # @_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['POST'])
@_app.route("/api/<module>/<name>",defaults={'app_id':'main','key':None},methods=['POST']) # @_app.route("/api/<module>/<name>",defaults={'app_id':'main','key':None},methods=['POST'])
@_app.route("/<app_id>/api/<module>/<name>",defaults={'key':None},methods=['POST']) # @_app.route("/<app_id>/api/<module>/<name>",defaults={'key':None},methods=['POST'])
def _post (app_id,key,module,name): # def _post (app_id,key,module,name):
# global _config # # global _config
# global _route # # global _route
# _handler = _route.get() # # _handler = _route.get()
# app_id = '/'.join([app_id,key]) if key else app_id # # app_id = '/'.join([app_id,key]) if key else app_id
_handler = _getHandler(app_id,key) # _handler = _getHandler(app_id,key)
return _delegate(_handler,module,name) # return _delegate(_handler,module,name)
@_app.route('/version') @_app.route('/version')
def _version (): def _version ():
@ -310,31 +302,6 @@ def _cms_page (app_id,resource):
_args = _route.render(_uri,_title,session.get(app_id,'main')) _args = _route.render(_uri,_title,session.get(app_id,'main'))
return _args[_title],200 return _args[_title],200
# @_app.route('/set/<id>')
# def set(id):
# global _route
# _setHandler(id)
# # _route.set(id)
# # _handler = _route.get()
# _handler = _getHandler()
# _context = _handler.system()['context']
# _uri = f'/{_context}'.replace('//','/')
# return redirect(_uri)
# @_app.route('/<id>')
# def _open(id):
# global _route
# # _handler = _route.get()
# _handler = _getHandler()
# if id not in _route._apps :
# _args = {'config':_handler.config(), 'layout':_handler.layout(),'system':_handler.system(skip=['source','app'])}
# return render_template("404.html",**_args)
# else:
# _setHandler(id)
# # _route.set(id)
# return _index()
@cli.command() @cli.command()
def start ( def start (

@ -1,5 +1,5 @@
__author__ = "Steve L. Nyemba" __author__ = "Steve L. Nyemba"
__version__= "2.1.6" __version__= "2.2.0"
__email__ = "steve@the-phi.com" __email__ = "steve@the-phi.com"
__license__=""" __license__="""
Copyright 2010 - 2024, Steve L. Nyemba, Vanderbilt University Medical Center Copyright 2010 - 2024, Steve L. Nyemba, Vanderbilt University Medical Center

Loading…
Cancel
Save