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
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")
def _info():
@ -174,6 +181,7 @@ def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file
"""
Manage plugins list loaded plugins,
"""
manifest = get_manifest(manifest)
_config = config.get(manifest)
_root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins'])
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 :
_msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
else:
_data = plugins.stats(_plugins)
# _data = plugins.stats(_plugins)
_data = cms.Plugin.stats(_plugins)
print (_data)
_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 :
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 file not in _plugins :
_plugins[file] = []
@ -269,7 +279,7 @@ def reload (
"""
Reload a site/portal given the manifest ...
"""
_config = config.get(path)
_config = config.get( get_manifest(path))
if 'key' in _config['system']['source'] :
f = open(_config['system']['source']['key'])
key = f.read()
@ -293,9 +303,10 @@ def bootup (
"""
This function will launch a site/project given the location of the manifest file
"""
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 = os.sep.join([manifest,'qcms-manifest.json'])
# 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 = os.sep.join([manifest,'qcms-manifest.json'])
manifest = get_manifest(manifest)
index.start(manifest,port)
@cli.command(name='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
"""
manifest = get_manifest(manifest)
_config = config.get(manifest)
_root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']])

@ -6,8 +6,118 @@ import copy
from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib
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) :
# if os.path.exists(path) :

@ -4,6 +4,11 @@ import importlib
import importlib.util
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) :
"""
Returns the statistics of the plugins
@ -37,6 +42,9 @@ def load(_path,_filename,_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

@ -7,6 +7,7 @@ import flask
import transport
from transport import providers
import cms
from cms import Plugin
import sys
import os
import json
@ -144,28 +145,16 @@ def _dialog (app):
_args['title'] = _id
return render_template('dialog.html',**_args) #title=_id,html=_html)
@_app.route("/api/<module>/<name>",defaults={'app':'main','key':None})
@_app.route("/<app>/api/<module>/<name>",defaults={'key':None})
@_app.route("/<app>/<key>/<module>/<name>",defaults={'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},methods=['GET','POST','DELETE','PUT'])
@_app.route("/<app>/<key>/<module>/<name>",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
def _delegate_call(app,key,module,name):
_handler = _getHandler(app,key)
return _delegate(_handler,module,name)
# @_app.route('/api/<module>/<name>')
@_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['GET'])
@_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)
# print (_handler.config()/)
uri = f'api/{module}/{name}'
return Plugin.call(uri=uri,handler=_handler,request=request)
# return _delegate(_handler,module,name)
def _delegate(_handler,module,name):
global _route
@ -186,10 +175,13 @@ def _delegate(_handler,module,name):
# _data = pointer (**_args)
# else:
# _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 :
_data = _data.to_dict(orient='records')
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>/<key>/api/<module>/<name>',methods=['POST'],defaults={'app_id':'main','key':None})
@_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("/<app_id>/api/<module>/<name>",defaults={'key':None},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("/<app_id>/api/<module>/<name>",defaults={'key':None},methods=['POST'])
def _post (app_id,key,module,name):
# global _config
# global _route
# _handler = _route.get()
# app_id = '/'.join([app_id,key]) if key else app_id
# def _post (app_id,key,module,name):
# # global _config
# # global _route
# # _handler = _route.get()
# # app_id = '/'.join([app_id,key]) if key else app_id
_handler = _getHandler(app_id,key)
return _delegate(_handler,module,name)
# _handler = _getHandler(app_id,key)
# return _delegate(_handler,module,name)
@_app.route('/version')
def _version ():
@ -310,31 +302,6 @@ def _cms_page (app_id,resource):
_args = _route.render(_uri,_title,session.get(app_id,'main'))
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()
def start (

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

Loading…
Cancel
Save