independent modular architecture and integration

master
Steve Nyemba 10 months ago
parent 1973b3533b
commit 64cfe1d630

@ -6,7 +6,7 @@ import copy
from jinja2 import Environment, BaseLoader, FileSystemLoader from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib import importlib
import importlib.util import importlib.util
from cms import disk, cloud from cms import disk, cloud, engine
# import cloud # import cloud
class components : class components :

@ -0,0 +1,309 @@
import json
from genericpath import isdir
import os
import pandas as pd
import transport
import copy
from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib
import importlib.util
from cms import disk, cloud
class Loader :
def __init__(self,**_args):
f = open (_args['path'])
self._config = json.loads(f.read())
#
#
self._location = None
self._caller = None
if 'caller' in _args and _args['caller'] :
self._caller = _args['caller']
self._location = _args['location'].split(os.sep) # needed for plugin loading
self._location = os.sep.join(self._location[:-1])
self._config['system']['portal'] = self._caller != None
self._menu = {}
self._plugins={}
self.load()
def load(self):
"""
This function will load menu (overwrite) and plugins
"""
self.init_menu()
self.init_plugins()
def init_menu(self):
"""
This function will read menu and sub-menu items from disk structure,
The files are loaded will
"""
_config = self._config
if 'source' in _config['system'] and _config['system']['source']['id'] == 'cloud' :
_sourceHandler = cloud
else:
_sourceHandler = disk
_object = _sourceHandler.build(_config)
#
# After building the site's menu, let us add the one from 3rd party apps
#
_layout = copy.deepcopy(_config['layout'])
_overwrite = _layout['overwrite'] if 'overwrite' in _layout else {}
#
# @TODO: Find a way to translate rename/replace keys of the _object (menu) here
#
#-- applying overwrites to the menu items
for _name in _object :
_submenu = _object[_name]
_index = 0
for _item in _submenu :
text = _item['text'].strip()
if text in _overwrite :
if 'uri' in _item and 'url' in 'url' in _overwrite[text] :
del _item['uri']
_item = dict(_item,**_overwrite[text])
if 'uri' in _item and _item['type'] != 'open':
_item['uri'] = _item['uri'].replace(_layout['root'],'')
_submenu[_index] = _item
_index += 1
self.init_apps(_object)
self._menu = _object
self._order()
def init_apps (self,_menu):
"""
Insuring that the apps are loaded into the menu with an approriate label
"""
_system = self._config['system']
_context = _system['context']
if 'routes' in _system :
# _items = []
_overwrite = {} if 'overwrite' not in self._config['layout'] else self._config['layout']['overwrite']
for _text in _system['routes'] :
_item = _system['routes'][_text]
if 'menu' not in _item :
continue
uri = f'{_context}/set/{_text}'
# _items.append ({"text":_text,'uri':uri,'type':'open'})
_label = _item['menu']
if _label not in _menu :
_menu [_label] = []
_menu[_label].append ({"text":_text,'uri':uri,'type':'open'})
# _overwrite[_text] = {'text': _text.replace('-',' ').replace('_',' '),'uri':uri,'type':'open'}
# _menu['products'] = _items
#
# given that the menu items assumes redirecting to a page ...
# This is not the case
#
# self._config['overwrite'] = _overwrite
else:
pass
pass
def _order (self):
_config = self._config
if 'order' in _config['layout'] and 'menu' in _config['layout']['order']:
_sortedmenu = {}
_menu = self._menu
for _name in _config['layout']['order']['menu'] :
if _name in _menu :
_sortedmenu[_name] = _menu[_name]
_menu = _sortedmenu if _sortedmenu else _menu
#
# If there are missing items in the sorting
_missing = list(set(self._menu.keys()) - set(_sortedmenu))
if _missing :
for _name in _missing :
_menu[_name] = self._menu[_name]
_config['layout']['menu'] = _menu #cms.components.menu(_config)
self._menu = _menu
self._config = _config
def init_plugins(self) :
"""
This function looks for plugins in the folder on disk (no cloud support) and attempts to load them
"""
_config = self._config
PATH= os.sep.join([_config['layout']['root'],'_plugins'])
if not os.path.exists(PATH) and self._location and os.path.exists(self._location) :
#
# overriding the location of plugins ...
PATH = self._location
_map = {}
# if not os.path.exists(PATH) :
# return _map
if 'plugins' not in _config :
_config['plugins'] = {}
_conf = _config['plugins']
for _key in _conf :
_path = os.sep.join([PATH,_key+".py"])
if not os.path.exists(_path):
continue
for _name in _conf[_key] :
_pointer = self._load_plugin(path=_path,name=_name)
if _pointer :
_uri = "/".join(["api",_key,_name])
_map[_uri] = _pointer
#
# We are adding some source specific plugins to the user-defined plugins
# This is intended to have out-of the box plugins...
#
if 'source' in _config['system'] and _config['system']['source']['id'] == 'cloud' :
_plugins = cloud.plugins()
else:
_plugins = disk.plugins()
#
# If there are any plugins found, we should load them and use them
if _plugins :
_map = dict(_map,**_plugins)
else:
pass
self._plugins = _map
self._config['plugins'] = self._plugins
def _load_plugin(self,**_args):
"""
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')]
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(_name, _path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return getattr(module,_name) if hasattr(module,_name) else None
class Getter (Loader):
def __init__(self,**_args):
super().__init__(**_args)
_system = self.system()
_logo = _system['logo']
if 'source' in _system and 'id' in _system['source'] and (_system['source']['id'] == 'cloud'):
_icon = f'/api/cloud/download?doc=/{_logo}'
_system['icon'] = _icon
else:
_root = self._config['layout']['root']
_icon = os.sep.join([_root,_icon])
_system['icon'] = _icon
self._config['system'] = _system
def html(self,uri,id,_args={},_system={}) :
"""
This function reads a given uri and returns the appropriate html document, and applies environment context
"""
_system = self._config['system']
if 'source' in _system and _system['source']['id'] == 'cloud':
_html = cloud.html(uri,dict(_args,**{'system':_system}))
else:
_html = disk.html(uri)
# _html = (open(uri)).read()
#return ' '.join(['<div id=":id" class=":id">'.replace(':id',id),_html,'</div>'])
_html = ' '.join(['<div id=":id" class=":id">'.replace(':id',id),_html,'</div>'])
appContext = Environment(loader=BaseLoader()).from_string(_html)
#
# If the rendering of the HTML happens here we should plugin custom functions (at the very least)
#
return appContext.render(**_args)
# return _html
def data (self,_args):
"""
:store data-store parameters (data-transport, github.com/lnyemba/data-transport)
:query query to be applied against the store (expected data-frame)
"""
_store = _args['store']
reader = transport.factory.instance(**_store)
_queries= copy.deepcopy(_store['query'])
_data = reader.read(**_queries)
return _data
def csv(self,uri) :
return pd.read(uri).to_html()
return _map
def menu(self):
return self._config['menu']
def plugins(self):
return copy.deepcopy(self._plugins) if 'plugins' in self._config else {}
def context(self):
"""
adding custom variables functions to Jinja2, this function should be called after plugins are loaded
"""
_plugins = self.plugins()
# if not location:
# env = Environment(loader=BaseLoader())
# else:
location = self._config['layout']['root']
# env = Environment(loader=FileSystemLoader(location))
env = Environment(loader=BaseLoader())
# env.globals['routes'] = _config['plugins']
return env
def config(self):
return copy.deepcopy(self._config)
def system(self,skip=[]):
"""
:skip keys to ignore in the object ...
"""
_data = copy.deepcopy(self._config['system'])
_system = {}
if skip and _system:
for key in _data.keys() :
if key not in skip :
_system[key] = _data[key]
else:
_system= _data
return _system
def get_app(self):
return self._config['system']['app']
class Router :
def __init__(self,**_args) :
path = _args['path']
_app = Getter (path = path)
self._id = 'main'
# _app.load()
self._apps = {}
_system = _app.system()
if 'routes' in _system :
_system = _system['routes']
for _name in _system :
_path = _system[_name]['path']
self._apps[_name] = Getter(path=_path,caller=_app,location=_path)
# self._apps[_name].load()
self._apps['main'] = _app
def set(self,_id):
self._id = _id
def get(self):
return self._apps['main'] if self._id not in self._apps else self._apps[self._id]

@ -2,7 +2,7 @@
These are a few default plugins that will be exported and made available to the calling code These are a few default plugins that will be exported and made available to the calling code
The purpose of plugins is to perform data processing The purpose of plugins is to perform data processing
""" """
import json

@ -2,7 +2,7 @@ __doc__ = """
arguments : arguments :
--config path of the configuration otherwise it will look for the default in the working directory --config path of the configuration otherwise it will look for the default in the working directory
""" """
from flask import Flask,render_template,send_from_directory,request from flask import Flask,render_template,send_from_directory,request, redirect
import flask import flask
import transport import transport
from transport import providers from transport import providers
@ -16,35 +16,42 @@ import base64
from jinja2 import Environment, BaseLoader from jinja2 import Environment, BaseLoader
_app = Flask(__name__) _app = Flask(__name__)
@_app.route('/favicon.ico') @_app.route('/favicon.ico')
def favicon(): def favicon():
global _config global _route
_system = _route.get ().system()
return send_from_directory(os.path.join(_app.root_path, 'static/img'), _logo =_system['icon'] if 'icon' in _system else 'static/img/logo.svg'
'logo.png', mimetype='image/vnd.microsoft.icon') return _logo
# # _root = _route.get().config()['layout']['root']
# # print ([_system])
# # if 'source' in _system and 'id' in _system['source'] and (_system['source']['id'] == 'cloud'):
# # uri = f'/api/cloud/downloads?doc=/{_logo}'
# # print (['****' , uri])
# # return redirect(uri,200) #,{'content-type':'application/image'}
# # else:
# # return send_from_directory(_root, #_app.root_path, 'static/img'),
# _logo, mimetype='image/vnd.microsoft.icon')
@_app.route("/") @_app.route("/")
def _index (): def _index ():
global _config global _config
global _route
_handler = _route.get()
_config = _handler.config()
_system = _handler.system()
_plugins= _handler.plugins()
_args = {} _args = {}
if 'plugins' in _config : # if 'plugins' in _config :
_args['routes']=['plugins'] # _args['routes']=_config['plugins']
_system = cms.components.get_system(_config) #copy.deepcopy(_config['system']) # _system = cms.components.get_system(_config) #copy.deepcopy(_config['system'])
try: try:
_args['layout'] = _config['layout']
# _args = dict(_args,**_config['layout'])
# _args = copy.copy(_config)
uri = os.sep.join([_config['layout']['root'], _config['layout']['index']]) uri = os.sep.join([_config['layout']['root'], _config['layout']['index']])
_html = cms.components.html(uri,'index',_args,_system) _html = _route.get().html(uri,'index',_config,_system)
_args['index'] = _html
# e = Environment(loader=BaseLoader()).from_string(_html)
# e = cms.components.context(_config).from_string(_html)
# _args['index'] = e.render(**_args)
_index_page = "index.html" _index_page = "index.html"
except Exception as e: except Exception as e:
print () print ()
@ -52,9 +59,10 @@ def _index ():
_index_page = "404.html" _index_page = "404.html"
_args['uri'] = request.base_url _args['uri'] = request.base_url
pass pass
if 'source' in _system : # if 'source' in _system :
del _system['source'] # del _system['source']
_args['system'] = _system _args = {'layout':_config['layout'],'index':_html}
_args['system'] = _handler.system(skip=['source','app','route'])
return render_template(_index_page,**_args) return render_template(_index_page,**_args)
@ -68,24 +76,28 @@ def _index ():
@_app.route('/dialog') @_app.route('/dialog')
def _dialog (): def _dialog ():
global _config # global _config
global _route
_handler = _route.get()
_system = _handler.system()
_config = _handler.config()
_uri = os.sep.join([_config['layout']['root'],request.headers['uri']]) _uri = os.sep.join([_config['layout']['root'],request.headers['uri']])
# _uri = request.headers['uri'] # _uri = request.headers['uri']
_id = request.headers['dom'] _id = request.headers['dom']
# _data = cms.components.data(_config) # _data = cms.components.data(_config)
_args = {} #{'system':_config['system']} _args = {} #{'system':_config['system']}
_args['title'] = _id _args['title'] = _id
if 'plugins' in _config : # if 'plugins' in _config :
_args['routes'] = _config['plugins'] # _args['routes'] = _config['plugins']
_system = copy.deepcopy(_config['system']) # _system = copy.deepcopy(_config['system'])
_html = cms.components.html(_uri,_id,_config,_system) # _html = cms.components.html(_uri,_id,_config,_system)
_html = _handler.html(_uri,_id,_config,_system)
e = Environment(loader=BaseLoader()).from_string(_html) e = Environment(loader=BaseLoader()).from_string(_html)
if 'source' in _system : # if 'source' in _system :
del _system['source'] # del _system['source']
_args['system'] = _system _args['system'] = _handler.system(skip=['source','routes','app'])
_args['html'] = _html _args['html'] = _html
_html = ''.join(["<div style='padding:1%'>",str( e.render(**_args)),'</div>']) _html = ''.join(["<div style='padding:1%'>",str( e.render(**_args)),'</div>'])
@ -104,17 +116,20 @@ def _getproxy(module,name) :
:_module entry specified in plugins of the configuration :_module entry specified in plugins of the configuration
:_name name of the function to execute :_name name of the function to execute
""" """
global _config # global _config
global _route
_handler = _route.get()
uri = '/'.join(['api',module,name]) uri = '/'.join(['api',module,name])
_args = dict(request.args,**{}) _args = dict(request.args,**{})
_args['config'] = copy.deepcopy(_config) _args['config'] = _handler.config()
_plugins = _handler.plugins()
if uri not in _config['plugins'] : if uri not in _plugins :
_data = {} _data = {}
_code = 404 _code = 404
else: else:
pointer = _config['plugins'][uri] pointer = _plugins[uri]
if _args : if _args :
_data = pointer (**_args) _data = pointer (**_args)
else: else:
@ -125,15 +140,18 @@ def _getproxy(module,name) :
return _data,_code return _data,_code
@_app.route("/api/<module>/<name>" , methods=['POST']) @_app.route("/api/<module>/<name>" , methods=['POST'])
def _post (module,name): def _post (module,name):
global _config # global _config
global _route
_config = _route.get().config()
_plugins = _route.plugins()
uri = '/'.join(['api',module,name]) uri = '/'.join(['api',module,name])
_args = request.json _args = request.json
code = 404 code = 404
_info = "" _info = ""
if uri in _config['plugins'] and _args: if uri in _plugins and _args:
_pointer = _config['plugins'][uri] _pointer = _plugins[uri]
_info = _pointer(**_args) _info = _pointer(**_args)
if _info: if _info:
code = 200 code = 200
@ -154,7 +172,11 @@ def cms_page():
""" """
return the content of a folder formatted for a menu return the content of a folder formatted for a menu
""" """
global _config # global _config
global _route
_handler = _route.get()
_config = _handler.config()
_uri = os.sep.join([_config['layout']['root'],request.headers['uri']]) _uri = os.sep.join([_config['layout']['root'],request.headers['uri']])
_id = request.headers['dom'] _id = request.headers['dom']
_args = {'layout':_config['layout']} _args = {'layout':_config['layout']}
@ -162,32 +184,38 @@ def cms_page():
_args['routes'] = _config['plugins'] _args['routes'] = _config['plugins']
_system = cms.components.get_system(_config) _system = _handler.system() #cms.components.get_system(_config)
print ([_uri]) _html = _handler.html(_uri,_id,_args,_system) #cms.components.html(_uri,_id,_args,_system)
_html = cms.components.html(_uri,_id,_args,_system)
e = Environment(loader=BaseLoader()).from_string(_html) e = Environment(loader=BaseLoader()).from_string(_html)
# _data = {} #cms.components.data(_config) # _data = {} #cms.components.data(_config)
_system = cms.components.get_system(_config) _system = cms.components.get_system(_config)
_args['system'] = _system _args['system'] = _handler.system(skip=['source','app'])
_html = e.render(**_args) _html = e.render(**_args)
return _html,200 return _html,200
@_app.route('/page') @_app.route('/page')
def _cms_page (): def _cms_page ():
global _config # global _config
global _route
_handler = _route.get()
_config = _handler.config()
_uri = request.args['uri'] _uri = request.args['uri']
_uri = os.sep.join([_config['layout']['root'],_uri]) _uri = os.sep.join([_config['layout']['root'],_uri])
_title = request.args['title'] if 'title' in request.args else '' _title = request.args['title'] if 'title' in request.args else ''
_args = {'system':cms.components.get_system(_config) } _args = {'system':_handler.system()} #cms.components.get_system(_config) }
if 'plugins' in _config: # if 'plugins' in _config:
_args['routes'] = _config['plugins'] # _args['routes'] = _config['plugins']
_html = _handler.html(_uri,_title,_args) # cms.components.html(_uri,_title,_args)
_html = cms.components.html(_uri,_title,_args)
e = Environment(loader=BaseLoader()).from_string(_html) e = Environment(loader=BaseLoader()).from_string(_html)
_args['system'] = _handler.system(skip=['app','source'])
return e.render(**_args),200 return e.render(**_args),200
@_app.route('/reload',methods=['POST'])
def reload(): @_app.route('/set/<id>')
pass def set(id):
global _route
_handler = _route.set(id)
return redirect('/')
# #
# Let us bootup the application # Let us bootup the application
SYS_ARGS = {} SYS_ARGS = {}
@ -210,44 +238,48 @@ if len(sys.argv) > 1:
i += 2 i += 2
if __name__ == '__main__' : if __name__ == '__main__' :
pass
_path = SYS_ARGS['config'] if 'config' in SYS_ARGS else 'config.json' _path = SYS_ARGS['config'] if 'config' in SYS_ARGS else 'config.json'
if os.path.exists(_path): if os.path.exists(_path):
_config = json.loads((open (_path)).read()) _route = cms.engine.Router(path=_path)
if 'theme' not in _config['system'] : _args = _route.get().get_app()
_config['system']['theme'] = 'magazine.css' _app.run(**_args)
# # _config = json.loads((open (_path)).read())
# root can be either on disk or in the cloud ... # if 'theme' not in _config['system'] :
# root: "<path>" reading from disk # _config['system']['theme'] = 'magazine.css'
# root: {uid,token,folder} # #
# # # root can be either on disk or in the cloud ...
# # root: "<path>" reading from disk
_root = _config['layout']['root'] # # root: {uid,token,folder}
_menu = cms.components.menu(_config) # #
if 'order' in _config['layout'] and 'menu' in _config['layout']['order']:
_sortedmenu = {} # _root = _config['layout']['root']
for _name in _config['layout']['order']['menu'] : # _menu = cms.components.menu(_config)
if _name in _menu : # if 'order' in _config['layout'] and 'menu' in _config['layout']['order']:
_sortedmenu[_name] = _menu[_name] # _sortedmenu = {}
# for _name in _config['layout']['order']['menu'] :
_menu = _sortedmenu if _sortedmenu else _menu # if _name in _menu :
_config['layout']['menu'] = _menu #cms.components.menu(_config) # _sortedmenu[_name] = _menu[_name]
# if 'data' in _config :
# _config['data'] = cms.components.data(_config['data']) # _menu = _sortedmenu if _sortedmenu else _menu
# # _config['layout']['menu'] = _menu #cms.components.menu(_config)
_map = cms.components.plugins(_config) # # if 'data' in _config :
_config['plugins'] = _map # # _config['data'] = cms.components.data(_config['data'])
# Let us load the plugins if any are available # #
# if 'plugins' in _config :
# _map = cms.components.plugins(_config) # _map = cms.components.plugins(_config)
# if _map :
# _config['plugins'] = _map # _config['plugins'] = _map
# # # Let us load the plugins if any are available
# register the functions with Jinja2 # # if 'plugins' in _config :
# cms.components.context(_config) # # _map = cms.components.plugins(_config)
# # if _map :
_args = _config['system']['app'] # # _config['plugins'] = _map
_app.run(**_args) # #
else: # # register the functions with Jinja2
print (__doc__) # # cms.components.context(_config)
print ()
# _args = _config['system']['app']
# _app.run(**_args)
# else:
# print (__doc__)
# print ()

@ -18,7 +18,7 @@ body {
.bold {font-weight:bold} .bold {font-weight:bold}
.header img { width:100%;} .header img { width:100%; height: 100%;}
.footer { .footer {
text-align:center; text-align:center;

@ -18,6 +18,8 @@
} }
.main .header .icon {width:40px; height:40px;}
.main .header .icon img {width:40px; height:40px;}
.main .header .title { font-size:32px; text-transform: uppercase; font-weight:bold} .main .header .title { font-size:32px; text-transform: uppercase; font-weight:bold}
.main .header .subtitle {font-style:italic;font-size:14px; color:gray; text-transform: capitalize;} .main .header .subtitle {font-style:italic;font-size:14px; color:gray; text-transform: capitalize;}
@ -37,9 +39,7 @@
blockquote { blockquote {
border-left:8px solid #D3D3D3; padding:4px; border-left:8px solid #D3D3D3; padding:4px;
} }
.main .content table {border-spacing: 2;} .main .content table {border-spacing: 2;}
.main .content table .fa-check {color:green; font-size:20px;}
.main .content table .fa-times {color:RED; font-size:20px;}
.main .content table .active {font-size:14px} .main .content table .active {font-size:14px}
.main .content .banner { .main .content .banner {
height:300px; margin:4px; height:300px; margin:4px;

@ -1,5 +1,7 @@
{% if layout.header.logo == True %} {% if layout.header.logo == True %}
<img src="{{system.context}}/static/img/{{system.logo}}"> <div class="icon">
<img src="{{system.icon}}">
</div>
{% endif %} {% endif %}
<div> <div>

@ -17,7 +17,7 @@ Vanderbilt University Medical Center
<head > <head >
<title>{{layout.header.title}}</title> <title>{{layout.header.title}}</title>
<link rel="shortcut icon" href="{{system.context}}/favicon.ico"> <link rel="shortcut icon" href="{{system.icon}}">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">

@ -1,4 +1,8 @@
<div><i class="fa-solid fa-bars"></i></div> {%if system.portal %}
<div align="center" class="active" onclick="window.open('{{system.context}}/set/main','_self')"><i class="fa-solid fa-house"></i></div>
{% else %}
<div></div>
{% endif %}
{% for _name in layout.menu %} {% for _name in layout.menu %}
<div class="item"> <div class="item">
<div> <div>
@ -8,13 +12,17 @@
<div class="sub-menu border-round border"> <div class="sub-menu border-round border">
{% for _item in layout.menu[_name] %} {% for _item in layout.menu[_name] %}
{%if _item.uri and _item.type not in ['dialog','embed'] %}
{%if _item.type == 'open' %}
<div class="active" onclick="window.open('{{_item.uri}}','_self')">
{%elif _item.uri and _item.type not in ['dialog','embed'] %}
<div class="active" onclick="menu.apply('{{_item.uri}}','{{_item.text}}','{{_name}}')"> <div class="active" onclick="menu.apply('{{_item.uri}}','{{_item.text}}','{{_name}}')">
{%else%} {% else %}
<!-- working on links/widgets --> <!-- working on links/widgets -->
<div class="active" onclick='menu.apply_link({{_item|tojson}})'> <div class="active" onclick='menu.apply_link({{_item|tojson}})'>
{%endif%} {% endif %}
<i class="fa-solid fa-chevron-right"></i> <i class="fa-solid fa-chevron-right" style="margin-right:4px"></i>
{{_item.text}} {{_item.text}}
</div> </div>

Loading…
Cancel
Save