diff --git a/bin/qcms b/bin/qcms
index 9d1da44..f274c5c 100755
--- a/bin/qcms
+++ b/bin/qcms
@@ -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']])
diff --git a/cms/__init__.py b/cms/__init__.py
index 5fe6631..1f00a43 100644
--- a/cms/__init__.py
+++ b/cms/__init__.py
@@ -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"""
+
+ """
+
+ return _data,_code,{'Content-Type':_mimeType}
+ pass
# def _get_config (path) :
# if os.path.exists(path) :
diff --git a/cms/engine/plugins/__init__.py b/cms/engine/plugins/__init__.py
index 6ada10b..86858f0 100644
--- a/cms/engine/plugins/__init__.py
+++ b/cms/engine/plugins/__init__.py
@@ -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
diff --git a/cms/index.py b/cms/index.py
index dfc1abd..7d4f4cd 100644
--- a/cms/index.py
+++ b/cms/index.py
@@ -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//",defaults={'app':'main','key':None})
-@_app.route("//api//",defaults={'key':None})
-@_app.route("////",defaults={'key':None})
+@_app.route("/api//",defaults={'app':'main','key':None},methods=['GET','POST','DELETE','PUT'])
+@_app.route("//api//",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
+@_app.route("////",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//')
-@_app.route("///api//", methods=['GET'])
-@_app.route("/api//",defaults={'app_id':'main','key':None})
-@_app.route("//api//",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('//api//',methods=['POST'],defaults={'key':None})
# @_app.route('///api//',methods=['POST'],defaults={'app_id':'main','key':None})
-@_app.route("///api//", methods=['POST'])
-@_app.route("/api//",defaults={'app_id':'main','key':None},methods=['POST'])
-@_app.route("//api//",defaults={'key':None},methods=['POST'])
+# @_app.route("///api//", methods=['POST'])
+# @_app.route("/api//",defaults={'app_id':'main','key':None},methods=['POST'])
+# @_app.route("//api//",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/')
-# 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('/')
-# 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 (
diff --git a/meta/__init__.py b/meta/__init__.py
index 5a70ba9..c3e7398 100644
--- a/meta/__init__.py
+++ b/meta/__init__.py
@@ -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