@ -18,29 +18,60 @@ import pandas as pd
class RequestController :
def __init__ ( self , _route ) :
# self._plugins = _plugins
self . _routes = _route
#
# let's build a map of routes i.e ends with / or not they should reference the same objects
# This allows us to gain has map performance without writing much code
self . _map = { }
for _name in self . _routes :
self . _map [ _name ] = _name
self . _map [ f ' / { _name } / ' . replace ( ' // ' , ' / ' ) ] = _name
self . _map [ f ' / { _name } ' . replace ( ' // ' , ' / ' ) ] = _name
def isfile ( self , request ) :
#
# call self.exists to determin if this is an actual file or not
return ' . ' in request . path
def isroute ( self , request ) :
# return '/'.join(request.path[1:].split('/')[:2]) in self._routes
return self . get_route ( request ) in self . _routes
return False
return self . get_route ( request )
# p = self.get_route(request) in self._routes
# q = request.path != '' and request.path.split('/')[1] in self._routes
# _context = '' if request.path.strip() == '' else request.path.split('/')[1]
# print ([' ## ',q, q, request.path,_context])
# return p or q
def isapi ( self , request ) :
return ' api ' in request . path
def get_route ( self , request ) :
_items = request . path [ 1 : ] . split ( ' / ' )
_items = _items if _items [ - 1 ] != ' ' else _items [ : - 1 ]
N = len ( _items ) + 1
_names = [ ]
for i in range ( 1 , len ( _items ) + 1 ) :
# _items = request.path[1:].split('/')
_names . append ( ' / ' . join ( _items [ : i ] ) )
# if not '.' in request.path :
# print (self._routes)
# print (_items)
# if _items[0] in self._routes :
# return _items[0]
# else:
# return None
#
# We should find the appropriate route for any resource access
_uri = request . path
if _uri not in self . _map :
_items = _uri . split ( ' / ' )
_x = [ _items [ 0 ] ]
for _name in _items [ 1 : ] :
if ' / ' . join ( _x + [ _name ] ) in self . _map :
_x . append ( _name )
else :
break
#
#
if ' / ' . join ( _x ) in self . _map :
self . _map [ _uri ] = self . _map [ ' / ' . join ( _x ) ]
_uri = ' / ' . join ( _x )
_names = [ _item for _item in _names if _item in self . _routes ]
return _names [ - 1 ] if _names else None
return self . _map . get ( _uri , None )
class IOConfig :
"""
@ -99,7 +130,15 @@ class Initialization (IOConfig):
# self._config = self.read_config(**_args)
self . _args = _args
#
# in case the path provided does NOT have the manifest
if ' path ' in self . _args and not os . path . isfile ( self . _args [ ' path ' ] ) :
#
#
self . _args [ ' path ' ] = f " { self . _args [ ' path ' ] } { os . sep } qcms-manifest.json "
#
# Invoke initialization
self . reload ( )
#
if self . _caller and self . _caller . secure :
@ -146,6 +185,53 @@ class Initialization (IOConfig):
self . log ( action = ' init.config ' , module = ' read_config ' , input = { ' caller ' : _name } )
return _config
def context ( self , * * _args ) :
"""
updating context and parent context
"""
_m = { }
_context = _args [ ' context ' ] if ' context ' in _args else self . get ( ' system.context ' )
_parentContext = self . get ( ' system.parentContext ' ) if not self . _caller else self . get ( ' system.parentContext ' )
#
# Logging what we've done ...
#
if self . _caller :
_x = self . _caller . get ( ' system.context ' ) . split ( ' / ' ) + [ _context ]
_context = f ' { self . _caller . get ( " system.context " ) } / { _context } / ' . replace ( ' // ' , ' / ' )
_parentContext = f ' / { self . _caller . get ( " system.context " ) } ' . replace ( ' // ' , ' / ' )
if _parentContext == ' / ' :
#
# This is the case when the site is the actual website (e.g: https://foo.com)
_parentContext = ' '
_parentIcon = self . _caller . get ( ' system.icon ' )
_parentIcon = _parentIcon if _parentIcon . startswith ( ' / ' ) else f ' / { _parentIcon } '
self . set ( ' system.parentContext ' , _parentContext )
self . set ( ' system.caller ' , { ' icon ' : _parentIcon , ' context ' : _parentContext } )
else :
_context = f ' / { _context } / ' if _context not in [ ' ' , ' / ' ] else _context
_parentContext = _context
_context = _context . replace ( ' // ' , ' / ' )
_parentContext = _parentContext . replace ( ' // ' , ' / ' ) if _parentContext else ' '
self . set ( ' system.context ' , _context )
self . set ( ' system.parentContext ' , _parentContext )
# self.set('system.parentContext',_parentContext)
#
# If for whatever reason the main page has context and ends with forward slash
# we need to update the expression of context
#
# request = _args['request']
self . log ( action = ' init.context ' , module = ' context ' , input = { ' context ' : _context , ' parentContext ' : _parentContext } )
#
# loosly context to a certain extent involves locations of icons and document root
#
self . locations ( * * _args )
def _context ( self , * * _args ) :
"""
Updating the context so that we can have a consistent representation
"""
@ -174,6 +260,8 @@ class Initialization (IOConfig):
self . _config [ ' system ' ] [ ' onport ' ] = 0
else :
_iconURI = _iconURI . replace ( " /// " , " / " )
self . set ( ' system.onport ' , 1 )
self . _config [ ' system ' ] [ ' onport ' ] = 1
self . set ( ' system.caller ' , { ' icon ' : _iconURI , ' context ' : self . _caller . get ( ' system.context ' ) } )
@ -193,6 +281,12 @@ class Initialization (IOConfig):
# self._config['system']['context'] = _context.strip()
self . _config [ ' system ' ] [ ' parentContext ' ] = _parentContext
p = { ' has_caller ' : self . _caller != None , ' context ' : _context }
#
# If for whatever reason the main page has context and ends with forward slash
# we need to update the expression of context
#
# request = _args['request']
self . log ( action = ' init.context ' , module = ' context ' , input = p )
#
# loosly context to a certain extent involves locations of icons and document root
@ -219,15 +313,20 @@ class Initialization (IOConfig):
# else:
# _icon = f'{_context}/api/disk/read?uri={_logo}'
# _icon = f'{_context}/{_logo}'.replace(_root,'')
_icon = f ' { _context } / { _logo } ' if _context else _logo
_icon = f ' / { _context } / { _logo } ' if _context else _logo
self . set ( ' system.icon ' , _icon )
self . set ( ' system.logo ' , _icon )
# self.set('system.icon',_icon)
# self.set('system.logo',_icon)
self . set ( ' system.icon ' , f ' { _context } { _logo } ' ) #f'/{_context}/{_logo}'.replace('//','/'))
self . set ( ' system.logo ' , _logo )
#
# from the path provided we can determine the location of the project
_homefolder = os . sep . join ( _args [ ' path ' ] . split ( os . sep ) [ : - 1 ] )
if _homefolder and os . path . exists ( os . sep . join ( [ _homefolder , _root ] ) ) :
self . set ( ' layout.location ' , _homefolder )
@ -310,6 +409,20 @@ class Initialization (IOConfig):
# self._config['layout']['menu'] = _menu
else :
pass
def _api ( self ) :
"""
This function will update public facing api provided
"""
# _context = self.get('system.context')
_plugins = self . get ( ' plugins ' )
for _key in [ ' debug ' , ' log ' ] :
_pointer = getattr ( self , _key )
_uri = f ' api/system/ { _key } '
# if _uri[0] == '/' :
# _uri = _uri[1:]
_plugins [ _uri ] = _pointer
self . set ( ' plugins ' , _plugins )
def plugins ( self , * * _args ) :
"""
This function will load the plugins from disk ( only )
@ -319,15 +432,18 @@ class Initialization (IOConfig):
_parentContext = self . get ( ' system.parentContext ' )
_map = { }
_plugins = { }
# if self.get('system.source.id') == 'cloud' :
# _plugins = cloud.plugins(_context)
# else:
# _plugins = disk.plugins(context=_context)
_uri = f ' { _context } /api/system/debug '
if _uri . startswith ( ' / ' ) :
_uri = _uri [ 1 : ]
# _uri = _uri if not _context else f'{_context}/{_uri}'
_plugins [ _uri ] = self . debug
# _uri = f'{_context}/api/system/debug'
# if _uri.startswith('/') :
# _uri = _uri[1:]
# # _uri = _uri if not _context else f'{_context}/{_uri}'
# _plugins[_uri] = self.debug
self . _api ( ) #-- adding default api calls
if os . path . exists ( _folder ) and self . get ( ' plugins ' ) :
_items = self . get ( ' plugins ' )
@ -340,10 +456,10 @@ class Initialization (IOConfig):
_pointer = disk . plugins ( path = _path , name = _module , context = _context )
if _pointer :
_uri = f " api/ { _filename } / { _module } "
_uri = f " { _context } / { _uri } " if _context else _uri
# _uri = f"{_context}/{_uri}" if _context else _uri
if ( _uri . startswith ( " / " ) ) :
_uri = _uri [ 1 : ]
_uri . replace ( ' // ' , ' / ' )
_map [ _uri ] = _pointer
if _parentContext :
# _uri = f"{_parentContext}/{_context}"
@ -360,11 +476,14 @@ class Initialization (IOConfig):
#
# Updating plugins from disk/cloud
_plugins = _map if not _plugins else dict ( _plugins , * * _map )
#
# if we have login enabled we should add them as a plugins
#
self . set ( ' plugins ' , _plugins )
self . _api ( ) #-- updating with system level api
self . log ( action = ' init.plugins ' , module = ' plugins ' , input = list ( _plugins . keys ( ) ) )
class Site ( Initialization ) :
@ -374,6 +493,7 @@ class Site(Initialization) :
self . _routes = [ ]
if self . get ( ' system.routes ' ) :
self . _routes = list ( self . get ( ' system.routes ' ) . keys ( ) )
self . log ( action = ' init.routes ' , modules = ' site ' , input = self . _routes )
if self . _caller :
self . _routes = self . _caller . _routes
self . inspect = RequestController ( self . _routes )
@ -386,24 +506,17 @@ class Site(Initialization) :
_plugins [ ' api/oauth2/final ' ] = cms . oauthFinalize
self . set ( ' plugins ' , _plugins )
def exists ( self , uri ) :
path = [ ]
if self . get ( ' layout.location ' ) :
path . append ( self . get ( ' layout.location ' ) )
if self . get ( ' layout.root ' ) not in uri :
path . append ( self . get ( ' layout.root ' ) )
# path = []
# if self.get('layout.location'):
# path.append(self.get('layout.location'))
# if self.get('layout.root') not in uri :
# path.append(self.get('layout.root'))
# path.append(uri)
# return os.path.exists( os.sep.join(path))
return os . path . exists ( uri )
path . append ( uri )
return os . path . exists ( os . sep . join ( path ) )
# def _html(self,_uri,_id,request) :
# if self.secure.allow(request=request):
# _handler = cloud if self.get('system.source.id') == 'cloud' else disk
# _html = _handler.html(_uri, self.get(None))
# #
# # maybe apply the environment here ??
# return " ".join([f'<div id="{_id}"> ',_html,"</div>"])
# else:
# None
def mimeType ( self , uri ) :
_extension = uri . split ( ' . ' ) [ - 1 ] . strip ( ) . lower ( )
_mimeType = ' application/octet-stream '
@ -419,13 +532,13 @@ class Site(Initialization) :
return _mimeType
def path ( self , _uri ) :
path = [ ]
if self . get ( ' layout.location ' ) :
path . append ( self . get ( ' layout.location ' ) )
if self . get ( ' layout.root ' ) not in _uri :
path . append ( self . get ( ' layout.root ' ) )
path . append ( _uri )
return os . sep . join ( path )
def html ( self , _request ) :
_uri = self . uri ( _request )
@ -439,11 +552,12 @@ class Site(Initialization) :
# _content = f'<div>{_content}</div>'
if _uri . split ( ' . ' ) [ - 1 ] . strip ( ) . lower ( ) == ' md ' :
_content = mistune . html ( _content ) . replace ( " " " , ' " ' ) . replace ( " < " , " < " ) . replace ( " > " , " > " ) if _uri [ - 2 : ] in [ ' md ' , ' MD ' , ' Md ' , ' mD ' ] else _content
_content = f " <div> { _content } </div> " #if 'dom' not in _request.headers else f'<div id="{_request.headers['dom']}">{_content}</div>'
# _content = f"<div>{_content}</div>" #if 'dom' not in _request.headers else f'<div id="{_request.headers['dom']}">{_content}</div>'
else :
_env = Environment ( loader = BaseLoader ( ) ) . from_string ( _content )
_env = Environment ( loader = BaseLoader ( ) ) . from_string ( f " <div> { _content } </div> " )
_content = str ( _env . render ( * * self . get ( None ) ) )
pass
return _content , _mimeType
def uri ( self , request ) :
if ' uri ' in request . headers or ' uri ' in request . args :
@ -455,6 +569,7 @@ class Site(Initialization) :
#
file = request . path . replace ( f ' { _route } ' , ' ' )
#
if file . startswith ( ' // ' ) or file . startswith ( ' / ' ) :
# NOTE: false positive if file.startswith('/') is used
file = file [ 2 : ] if file . startswith ( ' // ' ) else file [ 1 : ]
@ -482,9 +597,14 @@ class Site(Initialization) :
#
# We can NOT serve python files over the web (possible security issue)
return None
f = open ( _uri , _mode )
_content = f . read ( )
f . close ( )
#
# If the file doesn't exists we should return 404
if self . exists ( _uri ) :
f = open ( _uri , _mode )
_content = f . read ( )
f . close ( )
else :
_content = """ <div align= " center " ><div class= " large-text " >404</div><div>File Not Found</div></div> """
return _content
def read ( self , request ) :
_kwargs = { ' allow ' : 0 }
@ -505,6 +625,10 @@ class Site(Initialization) :
# _content = io.BytesIO(f.read())
# f.close()
#
# maybe the path needs some massage ...
_content = self . open ( uri = self . path ( _uri ) , mode = ' rb ' )
_content = io . BytesIO ( _content )
_kwargs = { ' allow ' : 1 , ' mimeType ' : _mimeType , ' extension ' : _extension ,
@ -513,7 +637,7 @@ class Site(Initialization) :
self . log ( action = ' file.read ' , module = ' site.read ' , input = _kwargs )
if _content :
return _content , _mimeType
return None , ' plain /html'
return None , ' text /html'
# if 'html' in _mimetype :
# _args = {'layout':self.get('layout'),'system':self.get('system')}
@ -537,6 +661,17 @@ class Site(Initialization) :
_kwargs = { ' layout ' : self . get ( ' layout ' ) }
_system = self . get ( ' system ' )
#
# we are going to correct an issue with context given that the initial url (portal) can end with forward slash
# This happens when using proxy-forwarding
_context = _system [ ' context ' ]
# if not self._caller :
# _system['context'] = f'/{_context}/'.replace('//','/')
# _system['parentContext'] = f'/{_context}/'.replace('//','/')
# else:
# _system['parentContext'] = f'/{self._caller.get("system.context")}/'
for k in [ ' source ' , ' app ' ] :
if k in _system :
del _system [ k ]
@ -557,6 +692,7 @@ class Site(Initialization) :
return None
def apply_tags ( self , _html ) :
_kwargs = { ' layout ' : self . get ( ' layout ' ) , ' system ' : self . get ( ' system ' ) }
_env = Environment ( loader = BaseLoader ( ) ) . from_string ( f ' <div> { _html } </div> ' )
return _env . render ( * * _kwargs )
def run ( self , _request ) :
@ -564,12 +700,15 @@ class Site(Initialization) :
_data = f " <div align= ' center ' style= ' padding:20% ' ><div style= ' font-size:32px; font-weight:bold ' >404</div style= ' font-weight:lighter ' ><div> { _request . path } Not Found</div></div> "
_mimeType = ' text/html '
_code = 404
_key = _request . path [ 1 : ] #if self.get('system.context') != '' else _request.path[1:]
# print ([_key,_key in list(_plugins.keys())])
# print (list (_plugins.keys()))
# _key = _request.path[1:] #if self.get('system.context') != '' else _request.path[1:]
_index = _request . path . index ( ' api ' )
_key = _request . path [ _index : ]
if _plugins and _key in _plugins :
_mimeType = ' application/octet-stream '
_pointer = _plugins . get ( _key )
if hasattr ( _pointer , ' mimetype ' ) :
_mimeType = _pointer . mimetype
_data = _pointer ( request = _request , config = self . get ( None ) )
@ -585,6 +724,7 @@ class Site(Initialization) :
elif type ( _data ) in [ dict , list ] :
_data = json . dumps ( _data )
# #
# # return the ata
_code = _code if not _data else 200
@ -600,23 +740,25 @@ class QCMS:
self . _id = _app . get ( ' system.context ' ) #if _app.get('system.context') else 'main'
# if self._id == '' :
# self._id = '/'
self . _sites = { self . _id : _app , ' / ' : _app }
self . _sites = { self . _id : _app , ' ' : _app , ' / ' : _app }
self . _routes = [ ]
if _app . get ( ' system.routes ' ) :
_routes = _app . get ( ' system.routes ' )
for _name in _routes :
self . _routes . append ( _name )
_path = _routes [ _name ] [ ' path ' ]
self . _sites [ _name ] = Site ( context = _name , path = _path , caller = _app )
# self._sites[f' {_name}/'] = self._sites[_name]
# self._sites[f' / {_name}/'.replace('//','/') ] = self._sites[_name]
self . inspect = RequestController ( self . _routes )
def _render ( self , request ) :
_site = self . get ( request )
_args = _site . render ( request = request , id = ' index ' )
return render_template ( ' index.html ' , * * _args )
def _read ( self , request ) :
@ -626,6 +768,7 @@ class QCMS:
isfile = self . inspect . isfile ( request )
isapi = self . inspect . isapi ( request )
isroute = self . inspect . isroute ( request )
if not isapi and not isfile :
return self . _render ( request )
#
@ -637,7 +780,10 @@ class QCMS:
return _content , 200 , { ' Content-Type ' : _mimeType }
if isapi :
_site = self . get ( request )
return _site . run ( request )
return _site . run ( request )
def get ( self , request = None ) :
if request :