mirror of
https://github.com/yweber/lodel2.git
synced 2026-09-14 13:50:28 +02:00
Compare commits
114 commits
master
...
lodelsites
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a03f6f58ec | ||
|
|
745446f6e3 | ||
|
|
366f56059f | ||
|
|
a9c457d38f | ||
|
|
71213ef58e | ||
|
|
6d8d3fd021 | ||
|
|
3c41d40072 | ||
|
|
e17c4f4468 | ||
|
|
ac75d1cd71 | ||
|
|
56cd806ceb | ||
|
|
bdb7007525 | ||
|
|
ff1baa4467 | ||
|
|
10332f3f87 | ||
|
|
f1eb6ac41a | ||
|
|
f620b4e31b | ||
|
|
82a6ab95c3 | ||
|
|
773f6cdd06 | ||
|
|
ceee53c5bb | ||
|
|
ffd4ef21d5 | ||
|
|
3e3b4740da | ||
|
|
286e3ffe10 | ||
|
|
97b59379f4 | ||
|
|
689d6556c3 | ||
|
|
a567abc187 | ||
|
|
b5f9f97031 | ||
|
|
4cd5be0147 | ||
|
|
2f6cdd279d | ||
|
|
47fa14e566 | ||
|
|
c3aa0aa85c | ||
|
|
cad6962fea | ||
|
|
236861083f | ||
|
|
5467df8184 | ||
|
|
95305a88ba | ||
|
|
d3b88c06b2 | ||
|
|
8006bfb080 | ||
|
|
63f6143003 | ||
|
|
f2a8578981 | ||
|
|
ee6db4f895 | ||
|
|
6e4942c6a9 | ||
|
|
f9ade94a06 | ||
|
|
e877adf9c9 | ||
|
|
c4b37e151a | ||
|
|
52c8be3398 | ||
|
|
373772cf01 | ||
|
|
5eef806aa4 | ||
|
|
9a011fc615 | ||
|
|
4135760ee4 | ||
|
|
ccbb05f5d0 | ||
|
|
3e8b6bc30d | ||
|
|
3bff03424c | ||
|
|
6cb55301cc | ||
|
|
e10d8eb3bc | ||
|
|
83b54cd523 | ||
|
|
5aa1ac55cf | ||
|
|
afea7e74e2 | ||
|
|
4052440dab | ||
|
|
46626b8830 | ||
|
|
0fc3503a12 | ||
|
|
e2c39e7b96 | ||
|
|
87de9d653e | ||
|
|
e640ff96e7 | ||
|
|
cf8a1f488f | ||
|
|
98c72f5d9d | ||
|
|
1e15deaec3 | ||
|
|
26f586ad84 | ||
|
|
15e97bf2bd | ||
|
|
57c21f7f16 | ||
|
|
97ab8b8ea3 | ||
|
|
7d221b499e | ||
|
|
6c721a702c | ||
|
|
e68312098c | ||
|
|
2bd1d3cf48 | ||
|
|
93b713edf0 | ||
|
|
a7874c34c8 | ||
|
|
cb236c6450 | ||
|
|
f4386d9b83 | ||
|
|
e621f4adc5 | ||
|
|
1ea521c971 | ||
|
|
816d99adf6 | ||
|
|
8acd34dfdd | ||
|
|
4c27a6bf87 | ||
|
|
ffd4e1d2b3 | ||
|
|
c117f97c6e | ||
|
|
aadfdac24b | ||
|
|
6586fbebfc | ||
|
|
c6ec0aba2a | ||
|
|
68cd6a0c7d | ||
|
|
ac1d704791 | ||
|
|
0b337fd4b5 | ||
|
|
6953362a6b | ||
|
|
6c642e3a41 | ||
|
|
d3bf43f3ff | ||
|
|
9df0443a8b | ||
|
|
d8b95da6f8 | ||
|
|
738fff7494 | ||
|
|
f2394ba7e1 | ||
|
|
3ede546be4 | ||
|
|
d5cf4cf668 | ||
|
|
f6001dbd20 | ||
|
|
d0ff939544 | ||
|
|
470dbfbca0 | ||
|
|
1c3edfba98 | ||
|
|
3619f66a48 | ||
|
|
bc9a0a0e6b | ||
|
|
d202a55773 | ||
|
|
a33f1daaeb | ||
|
|
695ce80fc5 | ||
|
|
8a8a624cb7 | ||
|
|
a3e048dcd3 | ||
|
|
a3eb448093 | ||
|
|
df2c6810a6 | ||
|
|
819e9f62cf | ||
|
|
5f304cd64e | ||
|
|
94e7cf412a |
59 changed files with 2093 additions and 401 deletions
2
Makefile-common.am.inc
Normal file
2
Makefile-common.am.inc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
lodel2_multisite_data_dirname=sites_data
|
||||
lodel2_multisite_ctx_dirname=.sites_contexts/lodelsites
|
||||
11
Makefile.am
11
Makefile.am
|
|
@ -1,5 +1,5 @@
|
|||
SUBDIRS = lodel progs lodelsites
|
||||
EXTRA_DIST = runtest examples tests debian
|
||||
SUBDIRS = lodel progs
|
||||
EXTRA_DIST = runtest examples tests debian Makefile-common.am.inc
|
||||
CLEANFILES = runtest
|
||||
|
||||
lodel2_localstate_DATA =
|
||||
|
|
@ -33,7 +33,7 @@ runtest: ./runtest.sh
|
|||
|
||||
#Adding logdir creation on install
|
||||
install-data-hook:
|
||||
mkdir -p ${DESTDIR}$(lodel2_localstatedir); mkdir -p ${DESTDIR}$(docdir); cp README ${DESTDIR}$(docdir);
|
||||
$(MKDIR_P) ${DESTDIR}$(lodel2_localstatedir); $(MKDIR_P) ${DESTDIR}$(docdir); cp README ${DESTDIR}$(docdir);
|
||||
|
||||
#Making debian package
|
||||
deb: dist
|
||||
|
|
@ -72,10 +72,9 @@ cleandoc:
|
|||
|
||||
#other cleans
|
||||
distclean-local:
|
||||
-rm -vR debian_package ${PACKAGE}-${VERSION}.tar.gz
|
||||
-find . -name Makefile.in -delete
|
||||
-rm -vR debian_package/${PACKAGE}-${VERSION}.tar.gz
|
||||
|
||||
gitclean: distclean cleandoc
|
||||
-rm -vR autom4te.cache/ aclocal.m4 install-sh missing py-compile configure; find ./ -name Makefile.in |xargs rm -v
|
||||
-rm -vR debian_package autom4te.cache/ aclocal.m4 install-sh missing py-compile configure; find ./ -name Makefile.in |xargs rm -v
|
||||
|
||||
.PHONY: cleandoc tests doc
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ AC_CONFIG_FILES([Makefile \
|
|||
lodel/utils/Makefile \
|
||||
progs/Makefile \
|
||||
progs/slim/Makefile \
|
||||
lodel/plugins/Makefile \
|
||||
lodelsites/Makefile \
|
||||
lodel/plugins/Makefile
|
||||
])
|
||||
|
||||
|
||||
|
|
@ -28,6 +27,8 @@ AC_CONFIG_FILES([Makefile \
|
|||
#pyconfigure macros are prefixed by PC_
|
||||
###
|
||||
AC_PROG_LN_S
|
||||
AC_PROG_MKDIR_P
|
||||
|
||||
m4_include([m4/python.m4]) # Patched version of aclocal python.m4 file
|
||||
m4_include([m4/python_pyconfigure.m4]) #Renamed because we use a patched version of aclocal python.m4 file
|
||||
m4_define(python_min_ver, 3.4.2)
|
||||
|
|
@ -57,6 +58,7 @@ PC_PYTHON_CHECK_MODULE_VERSION([pymongo], [2.7], [2.8], ,
|
|||
AC_SUBST([PYMONGO], 'False')
|
||||
)
|
||||
|
||||
|
||||
#Documentation checks
|
||||
AC_SUBST([DOCOK], 'OK')
|
||||
AC_CHECK_PROGS([DOXYGEN], [doxygen])
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,3 +1,5 @@
|
|||
include $(top_builddir)/Makefile-common.am.inc
|
||||
|
||||
SUBDIRS=auth editorial_model leapi plugin settings utils plugins validator mlnamedobject
|
||||
EXTRA_DIST = plugins
|
||||
lodel_PYTHON = *.py
|
||||
|
|
@ -14,11 +16,14 @@ gitclean:
|
|||
logdir = $(localstatedir)/log/lodel2/
|
||||
|
||||
do_subst = sed -e 's,\[@\]LODEL2_VARDIR\[@\],$(localstatedir)/lodel2/,g' \
|
||||
-e 's,\[@\]LODEL2_LOGDIR\[@\],$(logdir),g'
|
||||
-e 's,\[@\]LODEL2_LOGDIR\[@\],$(logdir),g' \
|
||||
-e 's,\[@\]LODEL2_LODEL_PKG\[@\],$(lodeldir),g' \
|
||||
-e 's,\[@\]MULTISITE_DATA_DIRNAME\[@\],$(lodel2_multisite_data_dirname),g' \
|
||||
-e 's,\[@\]MULTISITE_CTX_DIRNAME\[@\],$(lodel2_multisite_ctx_dirname),g'
|
||||
|
||||
|
||||
install-data-hook:
|
||||
-mkdir -p ${DESTDIR}$(logdir)
|
||||
$(MKDIR_P) ${DESTDIR}$(logdir)
|
||||
|
||||
uninstall-hook:
|
||||
-rmdir ${DESTDIR}$(logdir)
|
||||
|
|
|
|||
281
lodel/bootstrap.py
Normal file
281
lodel/bootstrap.py
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
##@brief Contains functions allowing to bootstrap a lodel instance
|
||||
#
|
||||
#Typically a bootstrap process consist of settings preload ,
|
||||
#plugins start and finally settings loading
|
||||
#
|
||||
#Assertions that are made in this file :
|
||||
#- cwd is an instance directory
|
||||
#- nothing as been done yet (no import, no context etc)
|
||||
#
|
||||
|
||||
import warnings
|
||||
import os, os.path
|
||||
|
||||
import lodel.buildconf
|
||||
from lodel.context import LodelContext
|
||||
|
||||
|
||||
##@brief Hardcoded configuration dirnames given an instance type
|
||||
#
|
||||
#First item is for monosite the second for multisite
|
||||
#First item for multisite is server conf the second the lodelsites instance
|
||||
#conf
|
||||
#
|
||||
#@note obsolete ! We are preparing a merge of server_conf.d and
|
||||
#lodelsites.conf.d
|
||||
CONFS_DIRNAMES = [
|
||||
'conf.d',
|
||||
('server_conf.d', lodel.buildconf.LODELSITE_CONFDIR)]
|
||||
|
||||
##@brief Test if current instance is monosite or multisite
|
||||
#@return if monosite return True else False
|
||||
def _monosite_test():
|
||||
return os.path.isdir('./conf.d')
|
||||
|
||||
##@brief Initialize the context class taking care of the instance type (MONO
|
||||
#or MULTI site)
|
||||
def _context_initialisation():
|
||||
LodelContext.init(
|
||||
LodelContext.MONOSITE if _monosite_test() else LodelContext.MULTISITE)
|
||||
|
||||
##@brief Return confdir name given instance type and context type
|
||||
#@param ctx_type : see @ref boostrap()
|
||||
#@return a configuration directory
|
||||
#@todo return abspath
|
||||
def _get_confdir(ctx_type):
|
||||
if _monosite_test():
|
||||
return CONFS_DIRNAMES[0]
|
||||
return CONFS_DIRNAMES[1][1]
|
||||
|
||||
#elif ctx_type == '__loader__':
|
||||
# return CONFS_DIRNAMES[1][0]
|
||||
#elif ctx_type == 'lodelsites':
|
||||
# return CONFS_DIRNAMES[1][1]
|
||||
#raise ValueError("ctx_type is not one of '__loader__' nor 'lodelsites' \
|
||||
#authorized values")
|
||||
|
||||
##@brief Return confspec associated with current context & context type
|
||||
#@param ctx_type str
|
||||
#@todo delete the argument
|
||||
#@todo delete this function
|
||||
#@return None (for default confspecs) or a confspecs dict
|
||||
def _get_confspec(ctx_type):
|
||||
if not _monosite_test():
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
|
||||
return multisite_confspecs.LODEL2_CONFSPECS
|
||||
return None
|
||||
|
||||
|
||||
##@brief After calling this function you should use your instance as it
|
||||
#
|
||||
#@param ctx_type str : ONLY FOR MULTISITE specify wich multisite context to
|
||||
#bootstrap. The two choices are '__loader__' and 'lodelsites'. Default is
|
||||
#__loader__
|
||||
def bootstrap(ctx_type = None):
|
||||
_context_initialisation()
|
||||
monosite = _monosite_test()
|
||||
if ctx_type is None:
|
||||
#Default value
|
||||
if not _monosite_test():
|
||||
ctx_type = '__loader__'
|
||||
elif monosite:
|
||||
raise RuntimeError("Not allowed to give a value for ctx_type in a \
|
||||
MONOSITE instance")
|
||||
elif ctx_type not in ['__loader__', 'lodelsites']:
|
||||
raise ValueError("ctx_type is not one of '__loader__' nor \
|
||||
'lodelsites' authorized values")
|
||||
|
||||
custom_confspecs = _get_confspec(ctx_type)
|
||||
|
||||
confdir = _get_confdir(ctx_type)
|
||||
if not os.path.isdir(confdir):
|
||||
warnings.warn("Bootstraping seems to fail : unable to find confdir \
|
||||
: %s. Attempt to continue using default values" % confdir)
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.settings': [('Settings', 'settings_loader')],
|
||||
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
|
||||
|
||||
if ctx_type is not None:
|
||||
settings_loader(confdir, custom_confspecs, True) #Append specs
|
||||
else:
|
||||
settings_loader(confdir, custom_confspecs)
|
||||
del(globals()['settings_loader'])
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings']})
|
||||
|
||||
##@brief Preload a site
|
||||
#
|
||||
#Apply a common (as MONOSITE) loading process to a site :
|
||||
#1. Conf preload
|
||||
#2. Plugins preload
|
||||
#3. Conf loading
|
||||
#
|
||||
#4. starting plugins & hooks
|
||||
#@warning At this point we need a uniq identifier for the site (using it
|
||||
#as key for contexts & FAST_APP_EXPOSAL_CACHE). To achieve this we use
|
||||
#the data_path basename. It should works for handled sites and for the
|
||||
#lodelsites instance
|
||||
#@warning may only work for handled sites in a multisite context
|
||||
#@param data_path str : path to the datas directory (containing the confdir)
|
||||
#@param confdir_basename str : the basename of the site confdir
|
||||
#@param lodelsites_instance bool : if true we are loading the lodelsites
|
||||
#instance of the multisite (allow to load the good confspecs)
|
||||
#
|
||||
#@todo For now the interface plugin name for sites is hardcoded (set to
|
||||
#webui). It HAS TO be loaded from settings. But it is a bit complicated,
|
||||
#we have to get the plugin's module name abstracted from context :
|
||||
#lodel.something but if we ask directly to Plugin class the module name
|
||||
#it will return something like : lodelsites.sitename.something...
|
||||
#
|
||||
#@todo there is a quick & dirty workarround with comments saying that it
|
||||
#avoid context escape via hooks. We have to understand why and how and then
|
||||
#replace the workarround by a real solution !
|
||||
#@todo check if it works with monosite context !
|
||||
#@todo change data_path argument to sitename and determine datapath from it
|
||||
def site_preload(data_path, confdir_basename = 'conf.d', lodelsites_instance = False):
|
||||
#args check
|
||||
if confdir_basename != os.path.basename(confdir_basename):
|
||||
LodelFatalError('Bad argument given to site_load(). This really \
|
||||
sux !')
|
||||
#Determining uniq sitename from data_path
|
||||
data_path = data_path.rstrip('/')
|
||||
if lodelsites_instance:
|
||||
data_path = os.path.dirname(data_path).rstrip('/')
|
||||
ctx_name = os.path.basename(data_path)
|
||||
if not os.path.exists(data_path) or not os.path.isdir(data_path):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.exceptions': ['LodelFatalError']})
|
||||
raise LodelFatalError("A site named '%s' was found in the DB but not on the FS (expected to found it in '%s'!!!" % (os.path.basename(data_path), data_path))
|
||||
#Immediately switching to the context
|
||||
LodelContext.new(ctx_name)
|
||||
LodelContext.set(ctx_name)
|
||||
confdir = os.path.join(data_path, confdir_basename)
|
||||
#Loading settings for current site
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.settings': [('Settings', 'settings_preloader')]})
|
||||
if settings_preloader.started():
|
||||
msg = 'Settings seems to be allready started for "%s". \
|
||||
This should not append !' % ctx_name
|
||||
#switch back to loader context in order to log & raise
|
||||
LodelContext.set(None)
|
||||
logger.critical(msg)
|
||||
raise LodelFatalError(msg)
|
||||
if lodelsites_instance:
|
||||
#fetching custom confspec
|
||||
custom_confspec = _get_confspec("dummy_argument_is_obsolete")
|
||||
settings_preloader(os.path.join('./', confdir),
|
||||
custom_confspec, True)
|
||||
else:
|
||||
settings_preloader(os.path.join('./', confdir))
|
||||
LodelContext.set(None)
|
||||
return
|
||||
|
||||
##@brief Load all plugins in a context
|
||||
def site_load_plugins():
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
'lodel.plugin.plugins': ['Plugin']})
|
||||
Plugin.load_all()
|
||||
LodelHook.call_hook('lodel2_bootstraped', 'bootstrap', None)
|
||||
del(globals()['LodelHook'])
|
||||
del(globals()['Plugin'])
|
||||
|
||||
##@brief End a site loading process (load plugins & hooks)
|
||||
#@param data_path str : site data path (used to extract the sitename !!)
|
||||
#@todo change data_path argument to sitename
|
||||
def site_load(data_path):
|
||||
ctx_name = os.path.basename(data_path)
|
||||
LodelContext.set(ctx_name)
|
||||
dyncode_bootstraping()
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin': ['Plugin', 'LodelHook'],
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.plugin.core_hooks': 'core_hooks',
|
||||
'lodel.plugin.core_scripts': 'core_scripts'
|
||||
})
|
||||
Plugin.load_all() #Then all plugins & hooks are loaded
|
||||
#triggering dyncode datasource instanciations
|
||||
LodelHook.call_hook('lodel2_plugins_loaded', 'bootstrap', None)
|
||||
#triggering boostrapped hook
|
||||
LodelHook.call_hook('lodel2_bootstraped', 'bootstrap', None)
|
||||
#Populating FAST_APP_EXPOSAL_CACHE with wsgi application function
|
||||
#
|
||||
#WARNING !!!! Hardcoded interface name ! Here we have to find the
|
||||
#interface plugin name in order to populate the cache properly
|
||||
app = LodelContext.module('lodel.plugins.webui.run')
|
||||
#a dirty & quick attempt to fix context unwanted exite via
|
||||
#hooks
|
||||
for name in ( 'Plugin', 'LodelHook', 'logger', 'core_hooks',
|
||||
'core_scripts'):
|
||||
del(globals()[name])
|
||||
#HAS TO be populated in __loader__ context
|
||||
LodelContext.set(None)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugins.multisite.loader_utils': ['FAST_APP_EXPOSAL_CACHE']})
|
||||
FAST_APP_EXPOSAL_CACHE[ctx_name] = app
|
||||
#site fully loaded, switching back to loader context
|
||||
LodelContext.set(None)
|
||||
|
||||
##@brief Fetch handled sites name
|
||||
#@note Have to be called in __loader__ context. After function call the
|
||||
#loaded context will remain __loader__
|
||||
#@note bootstrap the dyncode and load all plugins
|
||||
#@warning assert that a full __loader__ context is ready and that the
|
||||
#multisite context is preloaded too
|
||||
#@warning hardcoded Lodelsite leo name and shortname fieldname
|
||||
#@todo attempt to delete hardcoded leo name
|
||||
#@todo attempt to delete hardcoded fieldname
|
||||
def get_handled_sites_name():
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings'],
|
||||
'lodel.exceptions': ['LodelException']})
|
||||
lodelsites_name = Settings.sitename
|
||||
LodelContext.set(lodelsites_name)
|
||||
#Loading plugins
|
||||
try:
|
||||
#This is a bit special but the loading process begin to be a bit
|
||||
#messy... Some plugins can ask for the dyncode (in order to make
|
||||
#checks for example ?). In that case the site_load_plugins() will
|
||||
#call first the LodelContext.expose_dyncode() method and will
|
||||
#raise first (it appends when dummy_plugin is activated)
|
||||
site_load_plugins()
|
||||
dyncode_bootstraping()
|
||||
except ImportError:
|
||||
raise LodelException("dyncode not yet imported ! Probably not \
|
||||
generated yet")
|
||||
|
||||
LodelContext.expose_dyncode(globals(), 'leapi_dyncode')
|
||||
lodelsite_leo = leapi_dyncode.Lodelsite #hardcoded leo name
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.leapi.query': ['LeGetQuery'],
|
||||
})
|
||||
handled_sites = LeGetQuery(lodelsite_leo, query_filters = [],
|
||||
field_list = ['shortname']).execute()
|
||||
if handled_sites is None:
|
||||
LodelContext.set(None)
|
||||
return []
|
||||
res = [ s['shortname'] for s in handled_sites]
|
||||
del(globals()['LeGetQuery'])
|
||||
del(globals()['Settings'])
|
||||
LodelContext.set(None)
|
||||
return res
|
||||
|
||||
##@brief This function handles dyncode first exposure and bootstraping
|
||||
#
|
||||
#Call LodelContext.expose_dyncode() and then call the
|
||||
#lodel2_dyncode_bootstraped hook in the current context
|
||||
#@note Tries to avoid side effects by deleting all exposed modules
|
||||
#@todo maybe some checks are usefull (is it allowed to expose dyncode in a
|
||||
#__loader__ context ?)
|
||||
def dyncode_bootstraping():
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
})
|
||||
LodelContext.expose_dyncode(globals(), 'leapi_dyncode')
|
||||
LodelHook.call_hook('lodel2_dyncode_bootstraped', 'boostrap', None)
|
||||
del(globals()['leapi_dyncode'])
|
||||
del(globals()['LodelHook'])
|
||||
|
||||
|
|
@ -7,3 +7,11 @@ WEBUI=@WEBUI@
|
|||
#Populated by make
|
||||
LODEL2VARDIR="[@]LODEL2_VARDIR[@]"
|
||||
LODEL2LOGDIR="[@]LODEL2_LOGDIR[@]"
|
||||
LODEL_PKG_PATH = "[@]LODEL2_LODEL_PKG[@]"
|
||||
|
||||
|
||||
MULTISITE_DATADIR = "[@]MULTISITE_DATA_DIRNAME[@]"
|
||||
MULTISITE_CONTEXTDIR = "[@]MULTISITE_CTX_DIRNAME[@]"
|
||||
MULTISITE_DYNCODE_MODULENAME = 'leapi_dyncode'
|
||||
##@todo to be generated via autotools
|
||||
LODELSITE_CONFDIR = 'lodelsites.conf.d'
|
||||
|
|
|
|||
192
lodel/context.py
192
lodel/context.py
|
|
@ -16,11 +16,13 @@ if 'lodel' not in sys.modules:
|
|||
else:
|
||||
globals()['lodel'] = sys.modules['lodel']
|
||||
|
||||
if 'lodelsites' not in sys.modules:
|
||||
import lodelsites
|
||||
else:
|
||||
if 'lodelsites' in sys.modules:
|
||||
#This should be true since LodelContext init method is called
|
||||
#for a MULTISITE context handling
|
||||
globals()['lodelsites'] = sys.modules['lodelsites']
|
||||
|
||||
from lodel import buildconf
|
||||
|
||||
##@brief Name of the package that will contains all the virtual lodel
|
||||
#packages
|
||||
CTX_PKG = "lodelsites"
|
||||
|
|
@ -45,7 +47,8 @@ class ContextModuleError(ContextError):
|
|||
pass
|
||||
|
||||
def dir_for_context(site_identifier):
|
||||
return os.path.join(lodelsites.__path__[0], site_identifier)
|
||||
_, ctx_path = LodelContext.lodelsites_paths()
|
||||
return os.path.join(os.path.join(ctx_path, site_identifier))
|
||||
|
||||
|
||||
##@brief Designed to permit dynamic packages creation from the lodel package
|
||||
|
|
@ -60,6 +63,7 @@ def dir_for_context(site_identifier):
|
|||
#to represent the lodel package os the FS and then we make python import
|
||||
#files from the symlink.
|
||||
#
|
||||
#
|
||||
#@note Current implementation is far from perfection. In fact no deletion
|
||||
#mechanisms is written and the virtual package cannot be a subpackage of
|
||||
#the lodel package for the moment...
|
||||
|
|
@ -68,22 +72,68 @@ def dir_for_context(site_identifier):
|
|||
#lodelsites/SITENAME/ folder
|
||||
class LodelMetaPathFinder(importlib.abc.MetaPathFinder):
|
||||
|
||||
##@brief implements the find_spec method of MetaPathFinder
|
||||
#
|
||||
#@param fullname str : module fullname
|
||||
#@param path str : with be the value of __path__ of the parent package
|
||||
#@param target module : is a module object that the finder may use to
|
||||
#make a more educated guess about what spec to return
|
||||
#@see https://docs.python.org/3/library/importlib.html#importlib.abc.MetaPathFinder
|
||||
def find_spec(fullname, path, target = None):
|
||||
if fullname.startswith(CTX_PKG):
|
||||
if fullname.startswith(CTX_PKG+'.'):
|
||||
spl = fullname.split('.')
|
||||
site_identifier = spl[1]
|
||||
#creating a symlink to represent the lodel site package
|
||||
mod_path = dir_for_context(site_identifier)
|
||||
if not os.path.exists(mod_path):
|
||||
os.symlink(lodel.__path__[0], mod_path, True)
|
||||
os.mkdir(mod_path)
|
||||
fd = open(os.path.join(mod_path, '__init__.py'), 'w+')
|
||||
fd.close()
|
||||
lodel_pkg_path = os.path.join(mod_path, 'lodel')
|
||||
if not os.path.exists(lodel_pkg_path):
|
||||
os.symlink(src = lodel.__path__[0], dst = lodel_pkg_path,
|
||||
target_is_directory = True)
|
||||
#Cache invalidation after we "created" the new package
|
||||
#importlib.invalidate_caches()
|
||||
return None
|
||||
#def invalidate_caches(): pass
|
||||
|
||||
|
||||
##@brief Class designed to handle context switching and virtual module
|
||||
#exposure
|
||||
#
|
||||
#The main entrypoint of this class is the expose_module method. A kind of
|
||||
#equivalent of the various import X [as Y], from X import Y [as Z] etc.
|
||||
#existing in Python.
|
||||
#The expose_module method add a preffix to the module fullname in order
|
||||
#to make it reconizable by the LodelMetaPathfinder::find_spec() method.
|
||||
#All module names are translated before import. The preffix is set at
|
||||
#__init__ call in __pkg_name. The resulting name is __pkg_name + fullname
|
||||
#
|
||||
#@par examples
|
||||
#When asking for lodel.leapi.leobject :
|
||||
#- in MONOSITE resulting module will be lodel.leapi.leobject
|
||||
#- in MULTISITE resulting module name will be
|
||||
#lodelsites.SITE_ID.lodel.leapi.leobject
|
||||
#
|
||||
#The lodelsites package will be a subdir of buildconf.MULTISITE_CONTEXTDIR
|
||||
#that will be itself added to sys.path in order to be able to import
|
||||
#lodelsites
|
||||
#
|
||||
#@par Notes about dyncode exposure
|
||||
#In MULTISITE mode the dyncode will be stored as a python module in
|
||||
#buildconf.MULTISITE_CONTEXTDIR/SITE_ID/leapi_dyncode.py . The dyncode
|
||||
#exposale process in MULTISITE mode is simply done by asking LodelContext
|
||||
#to expose a module named leapi_dyncode. The LodelContext::_translate()
|
||||
#method is able to produce a correct name for this module.
|
||||
#In MONOSITE mode the dyncode will be stored as a python module in
|
||||
#the site directory. In this case the _translate method will do the same
|
||||
#transformation than for the others modules. But in MONOSITE mode the
|
||||
#module preffix is empty. Resulting in import leapi_dyncode. This will
|
||||
#work asserting that cwd in MONOSITE mode is the instance directory.
|
||||
#
|
||||
#
|
||||
#
|
||||
#@note a dedicated context named LOAD_CTX is used as context for the
|
||||
#loading process
|
||||
class LodelContext(object):
|
||||
|
|
@ -102,7 +152,20 @@ class LodelContext(object):
|
|||
|
||||
##@brief Flag indicating if the classe is initialized
|
||||
__initialized = False
|
||||
|
||||
##@brief Stores path used by MULTISITE instance
|
||||
#
|
||||
#This variable is a tuple with 2 elements (in this order):
|
||||
#- lodelsites datadir (ex: /var/lodel2/MULTISITE_NAME/datadir/)
|
||||
#- lodelsites contextdir (ex: /varL/lodel2/MULTISITE_NAME/.ctx/lodelsites)
|
||||
__lodelsites_paths = None
|
||||
|
||||
##@brief Stores allready imported modules indexed on context name
|
||||
#
|
||||
#The dict contains context name as key and dict as values.
|
||||
#Value dict has module fullname as key and module object as value
|
||||
__imports_cache = dict()
|
||||
|
||||
##@brief Create a new context
|
||||
#@see LodelContext.new()
|
||||
def __init__(self, site_id, instance_path = None):
|
||||
|
|
@ -150,10 +213,14 @@ a context without a path......")
|
|||
else:
|
||||
self.__instance_path = os.path.realpath(instance_path)
|
||||
#Importing the site package to trigger its creation
|
||||
self.__package = importlib.import_module(self.__pkg_name)
|
||||
self.__package = importlib.import_module(
|
||||
self.__pkg_name)
|
||||
self.__class__._contexts[site_id] = self
|
||||
#Designed to be use by with statement
|
||||
self.__previous_ctx = None
|
||||
|
||||
def __repr__(self):
|
||||
return '<LodelContext name="%s">' % self.__id
|
||||
|
||||
##@brief Expose a module from the context
|
||||
#@param globs globals : globals where we have to expose the module
|
||||
|
|
@ -172,7 +239,7 @@ length == 2 but got : %s" % spec)
|
|||
##@brief Return a module from current context
|
||||
def get_module(self, fullname):
|
||||
fullname = self._translate(fullname)
|
||||
module = importlib.import_module(fullname)
|
||||
module = self._import_module(fullname)
|
||||
return module
|
||||
|
||||
|
||||
|
|
@ -194,6 +261,11 @@ site_id set to None when we are in MULTISITE beahavior")
|
|||
raise ContextError("Cannot have a context with \
|
||||
site_id set when we are in MONOSITE beahavior")
|
||||
|
||||
##@return identifier for current context
|
||||
@classmethod
|
||||
def current_id(cls):
|
||||
return cls._current.__id
|
||||
|
||||
##@return True if the class is in MULTISITE mode
|
||||
@classmethod
|
||||
def multisite(cls):
|
||||
|
|
@ -208,20 +280,32 @@ site_id set to None when we are in MULTISITE beahavior")
|
|||
return cls.get(target_ctx_id)
|
||||
|
||||
##@brief Set a context as active
|
||||
#
|
||||
#This method handle the context switching operations. Some static
|
||||
#attributes are set at this step.
|
||||
#@note if not in LOAD_CTX a sys.path update is done
|
||||
#@warning Inconsistency with lodelsites_datasource, we build again the
|
||||
#site context dir path using site_id. This information should come
|
||||
#from only one source
|
||||
#@param site_id str : site identifier (identify a context)
|
||||
#@todo unify the generation of the site specific context dir path
|
||||
@classmethod
|
||||
def set(cls, site_id):
|
||||
if cls._type == cls.MONOSITE:
|
||||
raise ContextError("Context cannot be set in MONOSITE beahvior")
|
||||
|
||||
site_id = LOAD_CTX if site_id is None else site_id
|
||||
if not cls.validate_identifier(site_id):
|
||||
raise ContextError("Given context name is not a valide identifier \
|
||||
: '%s'" % site_id)
|
||||
if site_id not in cls._contexts:
|
||||
raise ContextError("No context named '%s' found." % site_id)
|
||||
if cls.current_id() != LOAD_CTX and site_id != LOAD_CTX:
|
||||
raise ContextError("Not allowed to switch into a site context \
|
||||
from another site context. You have to switch back to LOAD_CTX before")
|
||||
wanted_ctx = cls._contexts[site_id]
|
||||
if hasattr(wanted_ctx, '__instance_path'):
|
||||
os.chdir(self.__instance_path) #May cause problems
|
||||
os.chdir(self.__instance_path) #May cause problems and may be obsolete
|
||||
cls._current = wanted_ctx
|
||||
return cls._current
|
||||
|
||||
|
|
@ -247,7 +331,7 @@ site_id set to None when we are in MULTISITE beahavior")
|
|||
return copy.copy(cls._current.__id)
|
||||
|
||||
|
||||
##@brief Create a new context given a context name
|
||||
##@brief Create a new context given a context name and switch in it
|
||||
#
|
||||
#@note It's just an alias to the LodelContext.__init__ method
|
||||
#@param site_id str : context name
|
||||
|
|
@ -285,6 +369,9 @@ site_id set to None when we are in MULTISITE beahavior")
|
|||
|
||||
##@brief Return a module from current context
|
||||
#@param fullname str : module fullname
|
||||
#@todo check if not globals are set when getting a module ! (if so
|
||||
#checks all calls to this method to check that this assertion was not
|
||||
#made)
|
||||
@classmethod
|
||||
def module(cls, fullname):
|
||||
return cls.get().get_module(fullname)
|
||||
|
|
@ -292,12 +379,13 @@ site_id set to None when we are in MULTISITE beahavior")
|
|||
##@brief Expose leapi_dyncode module
|
||||
@classmethod
|
||||
def expose_dyncode(cls, globs, alias = 'leapi_dyncode'):
|
||||
cls.get()._expose_dyncode(globs, alias)
|
||||
cls.get().expose_modules(globs, { 'leapi_dyncode': alias })
|
||||
|
||||
##@brief Initialize the context manager
|
||||
#
|
||||
#@note Add the LodelMetaPathFinder class to sys.metapath if type is
|
||||
#LodelContext.MULTISITE
|
||||
#@note lodelsites package name is hardcoded and has to be
|
||||
#@param type FLAG : takes value in LodelContext.MONOSITE or
|
||||
#LodelContext.MULTISITE
|
||||
@classmethod
|
||||
|
|
@ -309,11 +397,25 @@ initialize it anymore")
|
|||
raise ContextError("Invalid flag given : %s" % type)
|
||||
cls._type = type
|
||||
if cls._type == cls.MULTISITE:
|
||||
#Woot hardcoded stuff with no idea of what it implies :-P
|
||||
lodelsites_path = os.getcwd() #Same assert in the loader
|
||||
cls.__lodelsites_paths = (
|
||||
os.path.join(lodelsites_path, buildconf.MULTISITE_DATADIR),
|
||||
os.path.join(lodelsites_path,
|
||||
buildconf.MULTISITE_CONTEXTDIR))
|
||||
#Now we are able to import lodelsites package
|
||||
sys.path.append(os.path.dirname(cls.__lodelsites_paths[1]))
|
||||
if 'lodelsites' not in sys.modules:
|
||||
import lodelsites
|
||||
globals()['lodelsites'] = sys.modules['lodelsites']
|
||||
#End of Woot
|
||||
cls._contexts = dict()
|
||||
#Add custom MetaPathFinder allowing implementing custom imports
|
||||
sys.meta_path = [LodelMetaPathFinder] + sys.meta_path
|
||||
#Create and set __loader__ context
|
||||
cls.new(LOAD_CTX)
|
||||
ctx = cls.new(LOAD_CTX)
|
||||
#DIRTY enforcing
|
||||
cls._current = ctx
|
||||
cls.set(LOAD_CTX)
|
||||
else:
|
||||
#Add a single context with no site_id
|
||||
|
|
@ -330,7 +432,8 @@ initialize it anymore")
|
|||
def context_dir(cls):
|
||||
if cls._type == cls.MONOSITE:
|
||||
return './'
|
||||
return dir_for_context(cls._current.__id)
|
||||
return os.path.join(cls.__lodelsites_paths[1],
|
||||
cls._current.__id)
|
||||
|
||||
|
||||
##@brief Validate a context identifier
|
||||
|
|
@ -370,7 +473,7 @@ key '%s' with a different value : %s != %s" % (LodelContext.get_name(), alias, g
|
|||
print("Context '%s' : A module exposure leads in a useless replacement for \
|
||||
key '%s'" % (LodelContext.get_name(),alias))
|
||||
globs[alias] = obj
|
||||
|
||||
|
||||
##@brief Create a context from a path and returns the context name
|
||||
#@param path str : the path from which we extract a sitename
|
||||
#@return the site identifier
|
||||
|
|
@ -386,8 +489,14 @@ MONOSITE mode")
|
|||
"Unable to create a context named '%s'" % site_id)
|
||||
cls.new(site_id, path)
|
||||
return site_id
|
||||
|
||||
|
||||
|
||||
##@brief Return a tuple containing lodelsites datadir & contextdir (
|
||||
#in this order)
|
||||
@classmethod
|
||||
def lodelsites_paths(cls):
|
||||
if cls.__lodelsites_paths is None:
|
||||
raise ContextError('No paths available')
|
||||
return copy.copy(cls.__lodelsites_paths)
|
||||
|
||||
##@brief Utility method to expose a module with an alias name in globals
|
||||
#@param globs globals() : concerned globals dict
|
||||
|
|
@ -395,9 +504,23 @@ MONOSITE mode")
|
|||
#@param alias str : alias name
|
||||
@classmethod
|
||||
def _expose_module(cls, globs, fullname, alias):
|
||||
module = importlib.import_module(fullname)
|
||||
module = cls._import_module(fullname)
|
||||
cls.safe_exposure(globs, module, alias)
|
||||
|
||||
##@brief Utility method to import a module
|
||||
#@param fullname str : module fullname
|
||||
#@param reimport bool : if true import the module even if it was allready
|
||||
#imported in this context
|
||||
@classmethod
|
||||
def _import_module(cls, fullname, reimport = False):
|
||||
ctx_id = cls.current_id()
|
||||
if ctx_id not in cls.__imports_cache:
|
||||
cls.__imports_cache[ctx_id] = dict()
|
||||
if reimport or fullname not in cls.__imports_cache[ctx_id]:
|
||||
cls.__imports_cache[ctx_id][fullname] = importlib.import_module(
|
||||
fullname)
|
||||
return cls.__imports_cache[ctx_id][fullname]
|
||||
|
||||
##@brief Utility mehod to expose objects like in a from x import y,z
|
||||
#form
|
||||
#@param globs globals() : dict of globals
|
||||
|
|
@ -406,7 +529,7 @@ MONOSITE mode")
|
|||
@classmethod
|
||||
def _expose_objects(cls, globs, fullname, objects):
|
||||
errors = []
|
||||
module = importlib.import_module(fullname)
|
||||
module = cls._import_module(fullname)
|
||||
for o_name in objects:
|
||||
if isinstance(o_name, str):
|
||||
alias = o_name
|
||||
|
|
@ -420,27 +543,26 @@ MONOSITE mode")
|
|||
msg = "Module %s does not have any of [%s] as attribute" % (
|
||||
fullname, ','.join(errors))
|
||||
raise ImportError(msg)
|
||||
|
||||
##@brief Implements LodelContext::expose_dyncode()
|
||||
#@todo change hardcoded leapi_dyncode.py filename
|
||||
def _expose_dyncode(self, globs, alias = 'leapi_dyncode'):
|
||||
fullname = '%s.%s.dyncode' % (CTX_PKG, self.__id)
|
||||
if fullname in sys.modules:
|
||||
dyncode = sys.modules[fullname]
|
||||
else:
|
||||
path = os.path.join(self.__instance_path, 'leapi_dyncode.py')
|
||||
sfl = importlib.machinery.SourceFileLoader(fullname, path)
|
||||
dyncode = sfl.load_module()
|
||||
self.safe_exposure(globs, dyncode, alias)
|
||||
|
||||
|
||||
##@brief Translate a module fullname to the context equivalent
|
||||
#
|
||||
#Two transformation are possible :
|
||||
#- we are importing a submodule of the lodel package : resulting module
|
||||
#name will be : self.__pkg_name + module_fullname
|
||||
#- we are importing the dyncode : resulting module name is :
|
||||
#self.__pkg_name + dyncode_modulename
|
||||
#@param module_fullname str : a module fullname
|
||||
#@return The module name in the current context
|
||||
def _translate(self, module_fullname):
|
||||
if not module_fullname.startswith('lodel'):
|
||||
raise ContextModuleError("Given module is not lodel or any \
|
||||
submodule : '%s'" % module_fullname)
|
||||
return module_fullname.replace('lodel', self.__pkg_name)
|
||||
#This test should be obsolete now
|
||||
if module_fullname.startswith('lodel') or \
|
||||
module_fullname.startswith('leapi_dyncode'):
|
||||
if self.multisite():
|
||||
return self.__pkg_name +'.'+ module_fullname
|
||||
else:
|
||||
return module_fullname
|
||||
raise ContextModuleError("Given module is not lodel nor dyncode \
|
||||
or any submodule : '%s'" % module_fullname)
|
||||
|
||||
##@brief Implements the with statement behavior
|
||||
#@see https://www.python.org/dev/peps/pep-0343/
|
||||
|
|
|
|||
|
|
@ -54,6 +54,27 @@ class EmComponent(MlNamedObject):
|
|||
m.update(bytes(data, 'utf-8'))
|
||||
return int.from_bytes(m.digest(), byteorder='big')
|
||||
|
||||
##@brief Type test method
|
||||
#@return bool
|
||||
#@note Those methodes are implemented because we use picklefile to store
|
||||
#EM -> the classes that are stored are not the same than the one loaded
|
||||
#by LodelContext : lodel.editorial_model.components.EmClass !=
|
||||
#lodelsites.INSTANCE_NAME.editorial_model.components.EmClass
|
||||
@classmethod
|
||||
def is_emclass(cls):
|
||||
return False
|
||||
|
||||
##@brief Type test method
|
||||
#@return bool
|
||||
@classmethod
|
||||
def is_emgroup(cls):
|
||||
return False
|
||||
|
||||
##@brief Type test method
|
||||
#@return bool
|
||||
@classmethod
|
||||
def is_emfield(cls):
|
||||
return False
|
||||
|
||||
# @brief Handles editorial model objects classes
|
||||
#@ingroup lodel2_em
|
||||
|
|
@ -116,6 +137,11 @@ class EmClass(EmComponent):
|
|||
data_handler='LeobjectSubclassIdentifier',
|
||||
internal=True,
|
||||
group=group)
|
||||
|
||||
##@todo delete me when support for pickle translator is dropped
|
||||
@classmethod
|
||||
def is_emclass(cls):
|
||||
return True
|
||||
|
||||
# @brief Property that represents a dict of all fields
|
||||
# (the EmField objects defined in this class and all their parents)
|
||||
|
|
@ -251,6 +277,11 @@ class EmField(EmComponent):
|
|||
else:
|
||||
group.add_components([self])
|
||||
|
||||
##@todo delete me when support for pickle translator is dropped
|
||||
@classmethod
|
||||
def is_emfield(cls):
|
||||
return True
|
||||
|
||||
# @brief Returns data_handler_name attribute
|
||||
def get_data_handler_name(self):
|
||||
return copy.copy(self.data_handler_name)
|
||||
|
|
@ -302,6 +333,11 @@ class EmGroup(MlNamedObject):
|
|||
raise ValueError("EmGroup expected in depends argument but %s found" % grp)
|
||||
self.add_dependency(grp)
|
||||
|
||||
##@todo delete me when support for pickle translator is dropped
|
||||
@classmethod
|
||||
def is_emgroup(cls):
|
||||
return True
|
||||
|
||||
# @brief Returns EmGroup dependencies
|
||||
# @param recursive bool : if True returns all dependencies and their own dependencies
|
||||
# @return a dict of EmGroup identified by uid
|
||||
|
|
|
|||
|
|
@ -20,7 +20,16 @@ LodelContext.expose_modules(globals(), {
|
|||
#@ingroup lodel2_em
|
||||
class EditorialModel(MlNamedObject):
|
||||
|
||||
# @brief Create a new editorial model
|
||||
##@brief if true we are in editor mode (all groups visible & loaded)
|
||||
#@note Usually comes from settings but can be overriden at runtime.
|
||||
#It as been implemented this way in order to allow to load EM
|
||||
#in lodelsites context (to generate dyncode for handled sites)
|
||||
_editormode = Settings.editorialmodel.editormode
|
||||
##@brief When non in editor mode indicates the groups that we have to load
|
||||
#in our EM
|
||||
_groups = Settings.editorialmodel.groups
|
||||
|
||||
##@brief Create a new editorial model
|
||||
# @param name MlString|str|dict : the editorial model name
|
||||
# @param description MlString|str|dict : the editorial model description
|
||||
def __init__(self, name, description=None, display_name=None, help_text=None):
|
||||
|
|
@ -123,7 +132,20 @@ class EditorialModel(MlNamedObject):
|
|||
res.append(cls)
|
||||
return set(res)
|
||||
|
||||
# @brief EmGroup getter
|
||||
##@brief Allow to override class attributes _editormode and
|
||||
#_groups in order to control groups exposure
|
||||
#
|
||||
#A typicall usage of this method is for lodelsites when updating
|
||||
#dyncode for handled sites. We have to set both attribute in order to
|
||||
#load the EM as wanted by the handled site.
|
||||
@classmethod
|
||||
def _override_settings(cls, editormode = False, groups = []):
|
||||
logger.warning("EM settings overriding ! If not in lodelsites \
|
||||
context this is a REALLY BAD idea !")
|
||||
cls.__editormode = editormode
|
||||
cls.__groups = groups
|
||||
|
||||
##@brief EmGroup getter
|
||||
# @param uid None | str : give this argument to get a specific EmGroup
|
||||
# @return if uid is given returns an EmGroup else returns an EmGroup iterator
|
||||
def groups(self, uid=None):
|
||||
|
|
@ -141,7 +163,7 @@ class EditorialModel(MlNamedObject):
|
|||
# @brief Update the EditorialModel.__active_groups and
|
||||
# EditorialModel.__active_classes attibutes
|
||||
def __set_actives(self):
|
||||
if Settings.editorialmodel.editormode:
|
||||
if self._editormode:
|
||||
logger.warning("All EM groups active because editormode in ON")
|
||||
# all groups & classes actives because we are in editor mode
|
||||
self.__active_groups = self.__groups
|
||||
|
|
@ -150,13 +172,13 @@ class EditorialModel(MlNamedObject):
|
|||
# determine groups first
|
||||
self.__active_groups = dict()
|
||||
self.__active_classes = dict()
|
||||
for agrp in Settings.editorialmodel.groups:
|
||||
for agrp in self.__groups:
|
||||
if agrp not in self.__groups:
|
||||
raise SettingsError('Invalid group found in settings : %s' % agrp)
|
||||
logger.debug("Set group '%s' as active" % agrp)
|
||||
grp = self.__groups[agrp]
|
||||
self.__active_groups[grp.uid] = grp
|
||||
for acls in [cls for cls in grp.components() if isinstance(cls, EmClass)]:
|
||||
for acls in [cls for cls in grp.components() if cls.is_emclass()]:
|
||||
self.__active_classes[acls.uid] = acls
|
||||
if len(self.__active_groups) == 0:
|
||||
raise RuntimeError("No groups activated, abording...")
|
||||
|
|
@ -238,9 +260,8 @@ class EditorialModel(MlNamedObject):
|
|||
# @brief Raise an error if lodel is not in EM edition mode
|
||||
@staticmethod
|
||||
def raise_if_ro():
|
||||
if not Settings.editorialmodel.editormode:
|
||||
raise EditorialModelError(
|
||||
"Lodel in not in EM editor mode. The EM is in read only state")
|
||||
if not self._editormode:
|
||||
raise EditorialModelError("Lodel in not in EM editor mode. The EM is in read only state")
|
||||
|
||||
# @brief Load a model
|
||||
# @param translator module : The translator module to use
|
||||
|
|
|
|||
13
lodel/editorial_model/translator/externaly_handled.py
Normal file
13
lodel/editorial_model/translator/externaly_handled.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from lodel.context import LodelContext
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.exceptions': ['LodelFatalError'],
|
||||
})
|
||||
|
||||
def save(model, filename= None):
|
||||
raise LodelFatalError('Not allowed to save an EM. EM are handled \
|
||||
externally')
|
||||
|
||||
def load(filename):
|
||||
raise LodelFatalError('Not allowed to load an EM : EM are handled \
|
||||
externally')
|
||||
|
|
@ -31,6 +31,9 @@ class LodelExceptions(LodelException):
|
|||
class LodelFatalError(Exception):
|
||||
pass
|
||||
|
||||
class LodelFatalErrors(LodelFatalError, LodelExceptions):
|
||||
pass
|
||||
|
||||
##@brief Designed to be a catched exception.
|
||||
#
|
||||
#@note Designed to be raised in DataHandler
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class DataHandler(MlNamedObject):
|
|||
self.immutable = False
|
||||
self.primary_key = False
|
||||
self.internal = False
|
||||
|
||||
if 'default' in kwargs:
|
||||
self.default, error = self.check_data_value(kwargs['default'])
|
||||
if error:
|
||||
|
|
@ -74,6 +75,12 @@ class DataHandler(MlNamedObject):
|
|||
del kwargs['default']
|
||||
for argname, argval in kwargs.items():
|
||||
setattr(self, argname, argval)
|
||||
|
||||
if self.is_primary_key():
|
||||
if 'uniq' in kwargs and not self.is_uniq():
|
||||
message = "Parameter 'uniq' has to be set to True when 'primary_key' is not False"
|
||||
raise LodelException(message)
|
||||
self.uniq = True
|
||||
self.check_options()
|
||||
|
||||
display_name = kwargs.get('display_name', MlString(self.display_name))
|
||||
|
|
@ -111,6 +118,9 @@ class DataHandler(MlNamedObject):
|
|||
|
||||
def is_primary_key(self):
|
||||
return self.primary_key
|
||||
|
||||
def is_uniq(self):
|
||||
return self.uniq
|
||||
|
||||
# @brief checks if a fieldtype is internal
|
||||
# @return bool
|
||||
|
|
|
|||
|
|
@ -90,8 +90,6 @@ def get_classes(model):
|
|||
# @brief Given an EmField returns the data_handler constructor suitable for dynamic code
|
||||
# @param a EmField instance
|
||||
# @return a string
|
||||
|
||||
|
||||
def data_handler_constructor(emfield):
|
||||
#dh_module_name = DataHandler.module_name(emfield.data_handler_name)+'.DataHandler'
|
||||
get_handler_class_instr = 'DataField.from_name(%s)' % repr(emfield.data_handler_name)
|
||||
|
|
@ -112,21 +110,25 @@ def data_handler_constructor(emfield):
|
|||
# @brief Return a python repr of option values
|
||||
# @param A value of any type which represents option
|
||||
# @return a string
|
||||
|
||||
|
||||
def forge_optval(optval):
|
||||
if isinstance(optval, dict):
|
||||
return '{' + (', '.join(['%s: %s' % (repr(name), forge_optval(val)) for name, val in optval.items()])) + '}'
|
||||
|
||||
if isinstance(optval, (set, list, tuple)):
|
||||
return '[' + (', '.join([forge_optval(val) for val in optval])) + ']'
|
||||
|
||||
##@todo better class test & name retrieval possible using inspect or type
|
||||
if hasattr(optval, '__class__'):
|
||||
cls_name = optval.__class__.__name__
|
||||
else:
|
||||
cls_name = optval.__name__
|
||||
|
||||
if isinstance(optval, EmField):
|
||||
if cls_name == 'EmField':
|
||||
return "{leobject}.data_handler({fieldname})".format(
|
||||
leobject=LeObject.name2objname(optval._emclass.uid),
|
||||
fieldname=repr(optval.uid)
|
||||
)
|
||||
if isinstance(optval, EmClass):
|
||||
elif cls_name == 'EmClass':
|
||||
return LeObject.name2objname(optval.uid)
|
||||
|
||||
return repr(optval)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ LodelContext.expose_modules(globals(), {
|
|||
'LeApiQueryErrors'],
|
||||
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'lodel.exceptions': ['LodelFatalError'],
|
||||
'lodel.exceptions': ['LodelFatalError', 'LodelFatalErrors'],
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
|
||||
'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
|
||||
|
|
@ -270,26 +270,31 @@ class LeObject(object):
|
|||
rw_ds = ro_ds = cls._datasource_name
|
||||
else:
|
||||
ro_ds, rw_ds = cls._datasource_name
|
||||
errors = []
|
||||
# Read only datasource initialisation
|
||||
cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
|
||||
if cls._ro_datasource is None:
|
||||
log_msg = "No read only datasource set for LeObject %s"
|
||||
log_msg %= cls.__name__
|
||||
logger.debug(log_msg)
|
||||
else:
|
||||
log_msg = "Read only datasource '%s' initialized for LeObject %s"
|
||||
log_msg %= (ro_ds, cls.__name__)
|
||||
logger.debug(log_msg)
|
||||
try:
|
||||
cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
|
||||
if cls._ro_datasource is not None:
|
||||
log_msg = "Read only datasource '%s' initialized for LeObject %s"
|
||||
log_msg %= (ro_ds, cls.__name__)
|
||||
logger.debug(log_msg)
|
||||
except LodelFatalError as e:
|
||||
errors.append(e)
|
||||
|
||||
# Read write datasource initialisation
|
||||
cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
|
||||
if cls._ro_datasource is None:
|
||||
log_msg = "No read/write datasource set for LeObject %s"
|
||||
log_msg %= cls.__name__
|
||||
logger.debug(log_msg)
|
||||
else:
|
||||
log_msg = "Read/write datasource '%s' initialized for LeObject %s"
|
||||
log_msg %= (ro_ds, cls.__name__)
|
||||
logger.debug(log_msg)
|
||||
try:
|
||||
cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
|
||||
if cls._ro_datasource is not None:
|
||||
log_msg = "Read/write datasource '%s' initialized for LeObject %s"
|
||||
log_msg %= (ro_ds, cls.__name__)
|
||||
logger.debug(log_msg)
|
||||
except LodelFatalError as e:
|
||||
errors.append(e)
|
||||
|
||||
if len(errors) > 0:
|
||||
raise LodelFatalErrors(msg = 'Unable to instanciate datasources \
|
||||
for LeObject %s' % cls.__name__, exceptions = errors)
|
||||
|
||||
|
||||
# @brief Return the uid of the current LeObject instance
|
||||
#@return the uid value
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import shutil
|
|||
import tempfile
|
||||
import os, os.path
|
||||
from lodel.context import LodelContext
|
||||
from lodel import buildconf
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.scripts': 'lodel_script',
|
||||
'lodel.plugin.scripts': ['LodelScript'],
|
||||
'lodel.logger': 'logger'})
|
||||
|
||||
##@package lodel.plugin.core_scripts
|
||||
|
|
@ -17,7 +18,7 @@ LodelContext.expose_modules(globals(), {
|
|||
#@ingroup lodel2_plugins
|
||||
#@ingroup lodel2_script
|
||||
#
|
||||
class ListPlugins(lodel_script.LodelScript):
|
||||
class ListPlugins(LodelScript):
|
||||
_action = 'plugins-list'
|
||||
_description = "List all installed plugins"
|
||||
|
||||
|
|
@ -72,7 +73,7 @@ class ListPlugins(lodel_script.LodelScript):
|
|||
|
||||
|
||||
##@brief Handle install & uninstall of lodel plugins
|
||||
class PluginManager(lodel_script.LodelScript):
|
||||
class PluginManager(LodelScript):
|
||||
_action = 'plugins'
|
||||
_description = "Install/Uninstall plugins"
|
||||
|
||||
|
|
@ -276,7 +277,7 @@ oprtions. Use -d --directory instead")
|
|||
##@brief Implements lodel_admin.py **hooks-list** action
|
||||
#@ingroup lodel2_script
|
||||
#@ingroup lodel2_hooks
|
||||
class ListHooks(lodel_script.LodelScript):
|
||||
class ListHooks(LodelScript):
|
||||
_action = 'hooks-list'
|
||||
_description = 'Generate a list of registered hooks once instance started'
|
||||
|
||||
|
|
@ -301,3 +302,104 @@ class ListHooks(lodel_script.LodelScript):
|
|||
priority = priority))
|
||||
print("\n")
|
||||
|
||||
|
||||
##@brief Implements lodel_admin **dyncode** action
|
||||
#@ingroup lodel2_script
|
||||
class RefreshDyncode(LodelScript):
|
||||
_action = 'dyncode'
|
||||
_description = 'Update the dynamic code according to EM and conf'
|
||||
|
||||
@classmethod
|
||||
def argparser_config(cls, parser):
|
||||
parser.add_argument('-m', '--em',
|
||||
help='Specify the emfile to use for dyncode generation',
|
||||
type=str, default='')
|
||||
parser.add_argument('-o', '--dyncode',
|
||||
help='Specify the filename where the dyncode should be written',
|
||||
type=str, default='')
|
||||
if LodelContext.multisite():
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="ONLY VALID FOR MULtisites ! Refresh lodelsites dyncode \
|
||||
+ all handled sites dyncode")
|
||||
return
|
||||
|
||||
##@todo think of a better method to determine if we are in mono or
|
||||
#multisite instance
|
||||
#@todo code factorisation to fetch handled sites list
|
||||
#@todo fetch & use correct em_translator for handled sites
|
||||
@classmethod
|
||||
def run(cls, args):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings'],
|
||||
'lodel.editorial_model.model': ['EditorialModel'],
|
||||
'lodel.leapi.lefactory': 'lefactory'})
|
||||
em_translator = Settings.editorialmodel.emtranslator
|
||||
model_file = args.em
|
||||
if len(args.em.strip()) == 0:
|
||||
#using the default em_file
|
||||
model_file = Settings.editorialmodel.emfile
|
||||
elif LodelContext.multisite() and args.all:
|
||||
raise
|
||||
#Creating dyncode
|
||||
dyncode_file = args.dyncode
|
||||
if len(args.dyncode.strip()) == 0:
|
||||
#using dyncode filename from conf
|
||||
dyncode_file = Settings.editorialmodel.dyncode
|
||||
elif LodelContext.multisite() and args.all:
|
||||
raise
|
||||
if LodelContext.multisite() and args.all:
|
||||
#NOTE the code bellow can be factorised with
|
||||
#plugins/multisite/loader_utils.py
|
||||
|
||||
#Get the lodelsites instance name and the emfile path
|
||||
LodelContext.set(None)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings':['Settings']})
|
||||
lodelsites_name = Settings.sitename
|
||||
emfile_path = Settings.lodelsites.sites_emfile
|
||||
del(globals()['Settings']) #should be useless
|
||||
#Get the list of handled sites name
|
||||
LodelContext.set(None)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.bootstrap': ['get_handled_sites_name'],
|
||||
})
|
||||
handled_sites = get_handled_sites_name()
|
||||
del(globals()['get_handled_sites_name'])
|
||||
lodlesites_path = os.path.join(
|
||||
os.path.join(buildconf.LODEL2VARDIR, lodelsites_name),
|
||||
buildconf.MULTISITE_CONTEXTDIR)
|
||||
if handled_sites is not None:
|
||||
for sitename in handled_sites:
|
||||
LodelContext.set(None)
|
||||
#construct dyncode filename
|
||||
##@todo fetch dyncode filename from handled site conf
|
||||
dyncode_path = os.path.join(
|
||||
os.path.join(lodlesites_path, sitename),
|
||||
'leapi_dyncode.py') #BOO hardcoded dyncode file name
|
||||
LodelContext.set(sitename)
|
||||
cls.refresh_dyncode(emfile_path, dyncode_path,
|
||||
em_translator)
|
||||
LodelContext.set(None)
|
||||
#Refresh only one dyncode
|
||||
#if multisite it's the lodelsites dyncode
|
||||
LodelContext.set(None)
|
||||
cls.refresh_dyncode(model_file, dyncode_file, em_translator)
|
||||
|
||||
|
||||
##@brief Refresh dyncode
|
||||
#@warning you HAVE TO be in handled site context (in order to fetch groups
|
||||
#from conf)
|
||||
#@param model_file str : EM filename
|
||||
#@param dyncode_file str : dyncode output filename
|
||||
#@param em_translator str : translator name
|
||||
@classmethod
|
||||
def refresh_dyncode(cls, model_file, dyncode_file, em_translator):
|
||||
#Model loaded
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings':['Settings']})
|
||||
model = EditorialModel.load(em_translator, filename = model_file)
|
||||
dyncode_content = lefactory.dyncode_from_em(model)
|
||||
with open(dyncode_file, 'w+') as dfp:
|
||||
dfp.write(dyncode_content)
|
||||
print("Dyncode written in %s from em %s" % (dyncode_file, model_file))
|
||||
del(globals()['Settings'])
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.plugins': ['Plugin'],
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'LodelScriptError', 'DatasourcePluginError'],
|
||||
'lodel.validator.validator': ['Validator'],
|
||||
'lodel.exceptions': ['LodelException', 'LodelExceptions',
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
|
||||
'LodelFatalError', 'DataNoneValid', 'FieldValidationError'],
|
||||
})
|
||||
|
||||
_glob_typename = 'datasource'
|
||||
|
||||
|
|
@ -41,8 +43,8 @@ reimplemented by plugin datasource child class")
|
|||
#@param offset int: used with limit to choose the start record
|
||||
#@param instanciate bool : If true, the records are returned as instances, else they are returned as dict
|
||||
#@return list
|
||||
def select(self, target, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0,
|
||||
instanciate=True):
|
||||
def select(self, target, field_list, filters, relational_filters=None,
|
||||
order=None, group=None, limit=None, offset=0, instanciate=True):
|
||||
self._abs_err()
|
||||
|
||||
##@brief Deletes records according to given filters
|
||||
|
|
@ -138,7 +140,11 @@ lodel.plugin.datasource_plugin.AbstractDatasource" % (self.name))
|
|||
plugin_name, ds_identifier = cls.plugin_name(ds_name, ro)
|
||||
ds_conf = cls._get_ds_connection_conf(ds_identifier, plugin_name)
|
||||
ds_cls = cls.get_datasource(plugin_name)
|
||||
return ds_cls(**ds_conf)
|
||||
res = ds_cls(**ds_conf)
|
||||
if res is None:
|
||||
raise LodelFatalError("Unable to instanciate a datasource from \
|
||||
name : '%s'" % ds_name)
|
||||
return res
|
||||
|
||||
##@brief Return an initialized MigrationHandler instance
|
||||
#@param ds_name str : The datasource name
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import os.path
|
|||
import importlib
|
||||
import copy
|
||||
import json
|
||||
from warnings import warn #Because the logger is not able to work at some step
|
||||
from importlib.machinery import SourceFileLoader
|
||||
|
||||
from lodel.context import LodelContext
|
||||
|
|
@ -63,7 +64,14 @@ PLUGIN_DEPS_VARNAME = '__plugin_deps__'
|
|||
##@brief Name of the optionnal activate method
|
||||
ACTIVATE_METHOD_NAME = '_activate'
|
||||
##@brief Default & failover value for plugins path list
|
||||
PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'plugins')
|
||||
if LodelContext.multisite():
|
||||
#When in multisite mode we fetch plugins list from context dir in the
|
||||
#lodel pkg symlink
|
||||
PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'lodel/plugins')
|
||||
else:
|
||||
#When in monosite mode, for the moment, the plugins list is fetched from
|
||||
#a symlink in the instance dir
|
||||
PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'plugins')
|
||||
|
||||
##@brief List storing the mandatory variables expected in a plugin __init__.py
|
||||
#file
|
||||
|
|
@ -431,10 +439,13 @@ name differ from the one found in plugin's init file"
|
|||
|
||||
##@brief Return associated module name
|
||||
def module_name(self):
|
||||
path_array = self.path.split('/')
|
||||
if 'plugins' not in self.path:
|
||||
#Breaks deeper hierarchy for the moment, just a quick & dirty workaround
|
||||
return "lodel.plugins."+os.path.basename(self.path)
|
||||
#if not self.path.startswith('./plugins'):
|
||||
if 'plugins' not in self.path: # startswith != in
|
||||
raise PluginError("Bad path for plugin %s : %s" % (
|
||||
self.name, self.path))
|
||||
path_array = self.path.split('/')
|
||||
return '.'.join(['lodel'] + path_array[path_array.index('plugins'):])
|
||||
|
||||
##@brief Check dependencies of plugin
|
||||
|
|
@ -754,14 +765,16 @@ name differ from the one found in plugin's init file"
|
|||
except PluginError as e:
|
||||
log_msg += "unable to load '%s'. Exception raised : %s"
|
||||
log_msg %= (INIT_FILENAME, e)
|
||||
logger.debug(log_msg)
|
||||
logger.warning(log_msg)
|
||||
warn(log_msg)
|
||||
return False
|
||||
#Checking mandatory init module variables
|
||||
for attr_name in MANDATORY_VARNAMES:
|
||||
if not hasattr(initmod,attr_name):
|
||||
log_msg += " mandatory variable '%s' not found in '%s'"
|
||||
log_msg %= (attr_name, INIT_FILENAME)
|
||||
logger.debug(log_msg)
|
||||
logger.warning(log_msg)
|
||||
warn(log_msg)
|
||||
return False
|
||||
#Fetching plugin's version
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ pluginsdir=$(pkgpythondir)/plugins
|
|||
lodel2confdir=$(sysconfdir)/lodel2
|
||||
|
||||
install-data-hook:
|
||||
mkdir -p ${DESTDIR}$(pluginsdir); cp -R * ${DESTDIR}$(pluginsdir) && rm ${DESTDIR}$(pluginsdir)/Makefile* ;\
|
||||
mkdir -p ${DESTDIR}$(lodel2confdir); \
|
||||
if [ ! -d ${DESTDIR}$(lodel2confdir)/plugins ]; \
|
||||
then \
|
||||
ln -rs -t ${DESTDIR}$(lodel2confdir) $(pluginsdir); \
|
||||
fi
|
||||
-$(MKDIR_P) ${DESTDIR}$(pluginsdir);\
|
||||
cp -R * ${DESTDIR}$(pluginsdir);\
|
||||
rm -v ${DESTDIR}$(pluginsdir)/Makefile*;
|
||||
|
||||
install-exec-hook:
|
||||
- $(MKDIR_P) ${DESTDIR}$(lodel2confdir);\
|
||||
$(LN_S) -rt ${DESTDIR}$(lodel2confdir) ${DESTDIR}$(pluginsdir)
|
||||
|
||||
uninstall-hook:
|
||||
-rm -R ${DESTDIR}$(pluginsdir)
|
||||
-rm -R $(lodel2confdir)
|
||||
-rm -R ${DESTDIR}$(lodel2confdir)
|
||||
|
|
|
|||
49
lodel/plugins/lodelsite_datasource/__init__.py
Normal file
49
lodel/plugins/lodelsite_datasource/__init__.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
##@brief Plugin designed to handle FS site creation
|
||||
#
|
||||
#This plugin has a strong coupling with the multisite EM. It is
|
||||
#designed to handle only 1 EmClass : Lodelsite .
|
||||
#This class must have some mandatory fields :
|
||||
# - shortname
|
||||
# - extensions : list of loaded plugins
|
||||
# - em_groups : selected Em Group from base Em
|
||||
#
|
||||
# Every other fields are ignored by this datasource and forwarded to
|
||||
#childs datasource.
|
||||
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.validator.validator': ['Validator'],
|
||||
'lodel.logger': 'logger'})
|
||||
from .specs import check
|
||||
|
||||
__plugin_type__ = 'datasource'
|
||||
__plugin_name__ = "lodelsite_datasource"
|
||||
__version__ = '0.0.1'
|
||||
__loader__ = 'main.py'
|
||||
__plugin_deps__ = []
|
||||
|
||||
CONFSPEC = {
|
||||
'lodel2.datasource.lodelsite_datasource.*' : {
|
||||
'db_datasource': ( None,
|
||||
Validator('string')),
|
||||
'db_datasource_ro': (None,
|
||||
Validator('string', none_is_valid = True)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
##@brief Hardcoded checks that try to know if the EM fits the requierements
|
||||
def _activate():
|
||||
LodelContext.expose_dyncode(globals())
|
||||
if 'Lodelsite' not in leapi_dyncode.dynclasses_dict:
|
||||
logger.fatal('Unable to activate lodelsite_datasource. Expected a \
|
||||
Lodelsite leapi object')
|
||||
return False #or raise ?
|
||||
LodelSite = leapi_dyncode.dynclasses_dict['Lodelsite']
|
||||
res, reason = check(LodelSite)
|
||||
if not res:
|
||||
logger.fatal('The lodelsite EmClass has some missins mandatory \
|
||||
fields : '+reason)
|
||||
return res
|
||||
|
||||
|
||||
5
lodel/plugins/lodelsite_datasource/exceptions.py
Normal file
5
lodel/plugins/lodelsite_datasource/exceptions.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class LodelSiteDatasourceError(Exception):
|
||||
pass
|
||||
|
||||
class LodelSiteDatasourceInconsistency(LodelSiteDatasourceError):
|
||||
pass
|
||||
218
lodel/plugins/lodelsite_datasource/fs_utils.py
Normal file
218
lodel/plugins/lodelsite_datasource/fs_utils.py
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
##@brief This module contains usefull functions to handle lodelsites on FS
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
from lodel.context import LodelContext
|
||||
from lodel import buildconf #No need to protect it in Contexts
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.logger' : 'logger',
|
||||
'lodel.plugin.datasource_plugin': ['AbstractDatasource', 'DatasourcePlugin'],
|
||||
'lodel.exceptions': ['LodelFatalError'],
|
||||
'lodel.settings': ['Settings']})
|
||||
|
||||
from .exceptions import *
|
||||
|
||||
LODELSITE_DATA_PATH, LODELSITE_CONTEXTS_PATH = LodelContext.lodelsites_paths()
|
||||
|
||||
|
||||
##@brief Define directories architecture
|
||||
#
|
||||
#@note useless because a lot of those stuff would be hardcoded
|
||||
LODELSITE_INSTALL_MODEL = {
|
||||
'datas' : ['uploads', 'conf.d'], #For datadir
|
||||
'ctx': ['leapi_dyncode'] #for context dir
|
||||
}
|
||||
|
||||
CONF_MODELS = os.path.join(os.path.dirname(__file__),'model_conf.d/')
|
||||
CONF_AUTOCONF_FILENAME = 'lodelsites_autoconfig.ini'
|
||||
|
||||
##@brief Util function that returns both datadir and contextdir paths
|
||||
#@param name str : the site shortname
|
||||
#@return a tuple (datadir, contextdir)
|
||||
def name2path(name):
|
||||
return (os.path.join(LODELSITE_DATA_PATH, name),
|
||||
os.path.join(LODELSITE_CONTEXTS_PATH, name))
|
||||
##@brief Util function that indicates if a site exists or not
|
||||
#
|
||||
#This function only checks that both paths returned by name2path are
|
||||
#existing directories
|
||||
#@param name str : site shortname
|
||||
#@return a bool
|
||||
#@throws LodelSiteDatasourceInconsistency if inconsistency detected on FS
|
||||
def site_exists(name):
|
||||
paths = name2path(name)
|
||||
|
||||
if name == Settings.sitename:
|
||||
msg = 'Site shortname "%s" is conflicting with the Lodelsites instance name' % name
|
||||
raise LodelSiteDatasourceError(msg)
|
||||
|
||||
for path in paths:
|
||||
if os.path.isfile(path):
|
||||
msg = 'Will trying to determine if a lodesite "%s" exists we \
|
||||
found that "%s" is a file, but a directory was expected' % (name, path)
|
||||
raise LodelSiteDatasourceInconsistency(msg)
|
||||
|
||||
res = [False, False]
|
||||
res = [os.path.isdir(paths[0]),
|
||||
os.path.isdir(paths[1])]
|
||||
if res[0] != res[1]:
|
||||
msg = 'Inconsistency detected on filesystem will trying to determine \
|
||||
wether a lodelsite exists or not : '
|
||||
if res[0]:
|
||||
msg += 'datadir was found but no contextdir'
|
||||
else:
|
||||
msg += 'contextdir found but no datadir'
|
||||
raise LodelSiteDatasourceInconsistency(msg)
|
||||
return res[0]
|
||||
|
||||
##@brief Create sites directory on filesystem
|
||||
#@param name str : site shortname
|
||||
#@return None
|
||||
#@throws LodelSiteDatasourceError if something fails
|
||||
#@throws LodelSiteDatasourceError if the site already exists
|
||||
#@todo make uploads directory name configurable
|
||||
def site_directories_creation(name):
|
||||
if site_exists(name):
|
||||
raise LodelSiteDatasourceError('This site identified by "%s" \
|
||||
already exists' % name)
|
||||
|
||||
data_path, ctx_path = name2path(name)
|
||||
#Starting by creating both directories
|
||||
#Datadir
|
||||
try:
|
||||
os.mkdir(data_path)
|
||||
except FileExistsError:
|
||||
logger.critical('This should never happen ! We just checked that this \
|
||||
directory does not exist. BAILOUT !')
|
||||
raise LodelFatalError('Unable to create data directory for lodelsite \
|
||||
"%s", file exists')
|
||||
except Exception as e:
|
||||
raise LodelFatalError('Unable to create data directory for lodelsite \
|
||||
"%s" : %s' % (name,e))
|
||||
#Context dir
|
||||
try:
|
||||
os.mkdir(ctx_path)
|
||||
except FileExistsError:
|
||||
logger.critical('This should never happen ! We just checked that this \
|
||||
directory does not exist. BAILOUT !')
|
||||
raise LodelFatalError('Unable to create context directory for \
|
||||
lodelsite "%s", file exists' % name)
|
||||
except Exception as e:
|
||||
raise LodelFatalError('Unable to create context directory for \
|
||||
lodelsite "%s" : %s' % (name, e))
|
||||
|
||||
#Creates lodel site data subdirectories
|
||||
for data_subdirectory in LODELSITE_INSTALL_MODEL['datas']:
|
||||
to_create = os.path.join(data_path, data_subdirectory)
|
||||
try:
|
||||
os.mkdir(to_create)
|
||||
except FileExistsError:
|
||||
logger.critical('This should never happen ! We just checked that this \
|
||||
directory does not exist. BAILOUT !')
|
||||
except Exception as e:
|
||||
raise LodelFatalError('Unable to create %s directory for \
|
||||
lodelsite "%s" : %s' % (d,name, e))
|
||||
|
||||
|
||||
def site_lodelpkg_link_creation(name):
|
||||
dst_path = os.path.join(name2path(name)[1], 'lodel')
|
||||
src_path = buildconf.LODEL_PKG_PATH
|
||||
|
||||
try:
|
||||
os.symlink(src_path, dst_path)
|
||||
except FileExistsError:
|
||||
logger.critical('This should never happen ! We just checked that this \
|
||||
directory does not exist. BAILOUT !')
|
||||
raise LodelFatalError('Unable to create lodel package symlink for \
|
||||
lodelsite "%s", link exists in dir %s' % (name, directory))
|
||||
except Exception as e:
|
||||
raise LodelFatalError('Unable to create lodel package symlink for \
|
||||
lodelsite "%s" : %s' % (name, e))
|
||||
|
||||
def site_context_init_creation(name):
|
||||
directory = name2path(name)[1]
|
||||
try:
|
||||
open(os.path.join(directory, '__init__.py'), 'w+').close()
|
||||
except FileExistsError:
|
||||
logger.critical('This should never happen ! We just checked that this \
|
||||
directory does not exist. BAILOUT !')
|
||||
raise LodelFatalError('Unable to create python __init__ file for \
|
||||
lodelsite context "%s", file exists in dir %s' % (name, directory))
|
||||
except Exception as e:
|
||||
raise LodelFatalError('Unable to create python _init__ file for \
|
||||
lodelsite "%s" : %s' % (name, e))
|
||||
|
||||
|
||||
##@brief Generate conffile containing informations set by lodelsites EM
|
||||
#
|
||||
#@param sitename str : site shortname
|
||||
#@param em_groups list : list of str -> selected em_groups
|
||||
#@param extensions list : list of str -> activated extensions
|
||||
#@return config file content
|
||||
def generate_conf(sitename, groups, extensions):
|
||||
tpl = """
|
||||
[lodel2]
|
||||
sitename = {sitename}
|
||||
extensions = {extensions}
|
||||
|
||||
[lodel2.editorialmodel]
|
||||
groups = {em_groups}
|
||||
"""
|
||||
return tpl.format(
|
||||
sitename = sitename,
|
||||
extensions = ', '.join(extensions),
|
||||
em_groups = ', '.join(groups))
|
||||
|
||||
##@brief Delete generated conf and generate a new one
|
||||
#@param sitename str : site shortname
|
||||
#@param em_groups list : list of str -> selected em_groups
|
||||
#@param extensions list : list of str -> activated extensions
|
||||
#@return None
|
||||
def update_conf(sitename, groups, extensions):
|
||||
data_path, _ = name2path(sitename)
|
||||
conf_dir = os.path.join(data_path, 'conf.d') #Booo hardcoded
|
||||
autoconf = os.path.join(conf_dir, CONF_AUTOCONF_FILENAME)
|
||||
try:
|
||||
os.unlink(autoconf)
|
||||
except Exception as e:
|
||||
#Dropping error on deletion
|
||||
logger.warning('Unable to delete generated conf %s when trying to \
|
||||
update it %s' % (autoconf, e))
|
||||
with open(autoconf, 'w+') as cfp:
|
||||
cfp.write(generate_conf(sitename, groups, extensions))
|
||||
logger.info('Generated configuration file update for %s' % sitename)
|
||||
|
||||
##@brief Copies conffile from model and generates a conffile from given info
|
||||
#@param sitename str : site shortname
|
||||
#@param em_groups list : list of str -> selected em_groups
|
||||
#@param extensions list : list of str -> activated extensions
|
||||
#@return None
|
||||
def make_confs(sitename, groups, extensions):
|
||||
data_path, _ = name2path(sitename)
|
||||
conf_dir = os.path.join(data_path, 'conf.d') #Booo hardcoded
|
||||
#Config copy & forge
|
||||
for conffile in os.listdir(CONF_MODELS):
|
||||
if conffile.endswith('.ini'):
|
||||
conffile = os.path.join(CONF_MODELS, conffile)
|
||||
target_conffile = os.path.join(conf_dir, conffile)
|
||||
logger.info(
|
||||
"Copying conffile %s -> %s" % (conffile, target_conffile))
|
||||
shutil.copyfile(conffile, target_conffile)
|
||||
#Creating generated conf
|
||||
autoconf = os.path.join(conf_dir, CONF_AUTOCONF_FILENAME)
|
||||
with open(autoconf, 'w+') as cfp:
|
||||
cfp.write(generate_conf(sitename, groups, extensions))
|
||||
logger.info("Configuration file %s generated" % (autoconf))
|
||||
|
||||
##@brief Deletes all files related to a site
|
||||
#@warning can lead to dirty bugs if the site is running...
|
||||
def purge(sitename):
|
||||
for todel in name2path(sitename):
|
||||
try:
|
||||
shutil.rmtree(todel)
|
||||
except Exception as e:
|
||||
logger.error('Unable to delete "%s" folder' % todel)
|
||||
logger.info('"%s" files are deleted' % sitename)
|
||||
164
lodel/plugins/lodelsite_datasource/main.py
Normal file
164
lodel/plugins/lodelsite_datasource/main.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import os.path
|
||||
from lodel.context import LodelContext
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.datasource_plugin': ['AbstractDatasource', 'DatasourcePlugin'],
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.exceptions': ['LodelFatalError'],
|
||||
'lodel.settings': 'Settings'})
|
||||
|
||||
from .fs_utils import *
|
||||
from .sites_utils import update_dyncode
|
||||
from .specs import check as check_leo
|
||||
from .exceptions import *
|
||||
|
||||
|
||||
##@brief This datasource handles site creation
|
||||
#@note Filters evaluation is forwarded to child datasource using our select
|
||||
#method. Not optimized but much simple
|
||||
class Datasource(AbstractDatasource):
|
||||
|
||||
##@brief Constructor
|
||||
#
|
||||
#Handles child datasource instanciation
|
||||
#@param db_datasource str : datasource identifier
|
||||
#@param db_datasource_ro str : read only datasource identitifer
|
||||
def __init__(self, db_datasource, db_datasource_ro = None):
|
||||
if db_datasource_ro is None:
|
||||
db_datasource_ro = db_datasource
|
||||
self._child_ds = DatasourcePlugin.init_datasource(
|
||||
db_datasource, False)
|
||||
self._child_ds_ro = DatasourcePlugin.init_datasource(
|
||||
db_datasource, True)
|
||||
pass
|
||||
|
||||
##@brief Checks that given emcomponent is compatible with datasource
|
||||
#behavior
|
||||
#@warning 100% hardcoded checks on leo name fieldnames & types
|
||||
#@param emcomp LeObject subclass (or instance)
|
||||
#@throws LodelFatalError if not compatible
|
||||
@staticmethod
|
||||
def __assert_good_leo(leo):
|
||||
res, reason = check_leo(leo)
|
||||
if not res:
|
||||
msg = 'Bad leo given : %s because %s' % (leo, reason)
|
||||
logger.critical(msg)
|
||||
raise LodelFatalError(msg)
|
||||
|
||||
##@brief Provide a new uniq numeric ID
|
||||
#@param emcomp LeObject subclass (not instance) : To know on wich things we
|
||||
#have to be uniq
|
||||
#@return an integer
|
||||
def new_numeric_id(self, emcomp):
|
||||
self.__assert_good_leo(emcomp)
|
||||
return self._child_ds.new_numeric_id(emcomp)
|
||||
|
||||
##@brief returns a selection of documents from the datasource
|
||||
#@note Simply forwarded to ro child datasource
|
||||
#@param target Emclass
|
||||
#@param field_list list
|
||||
#@param filters list : List of filters
|
||||
#@param rel_filters list : List of relational filters
|
||||
#@param order list : List of column to order. ex: order = [('title', 'ASC'),]
|
||||
#@param group list : List of tupple representing the column to group together. ex: group = [('title', 'ASC'),]
|
||||
#@param limit int : Number of records to be returned
|
||||
#@param offset int: used with limit to choose the start record
|
||||
#@param instanciate bool : If true, the records are returned as instances, else they are returned as dict
|
||||
#@return list
|
||||
def select(self, target, field_list, filters, relational_filters=None,
|
||||
order=None, group=None, limit=None, offset=0, instanciate=True):
|
||||
return self._child_ds_ro.select(
|
||||
target, field_list, relational_filters, order, group, limit, offset)
|
||||
|
||||
##@brief Deletes records according to given filters
|
||||
#@note lazy filters evaluation implementation : to evaluate filters &
|
||||
#rel_filters we run self.select using them
|
||||
#@param target Emclass : class of the record to delete
|
||||
#@param filters list : List of filters
|
||||
#@param relational_filters list : List of relational filters
|
||||
#@return int : number of deleted records
|
||||
def delete(self, target, filters, relational_filters):
|
||||
shortnames = self.select(
|
||||
target,
|
||||
['shortname'],
|
||||
filters,
|
||||
relational_filters)
|
||||
for shortname in [ item['shortname'] for item in shortnames]:
|
||||
purge(shortname)
|
||||
return self._child_ds.delete(target, filters, relational_filters)
|
||||
|
||||
## @brief updates records according to given filters
|
||||
#
|
||||
#@note lazy filters evaluation implementation : to evaluate filters &
|
||||
#rel_filters we run self.select using them
|
||||
#@note shortname updates are forbidden
|
||||
#@param target Emclass : class of the object to update
|
||||
#@param filters list : List of filters
|
||||
#@param relational_filters list : List of relational filters
|
||||
#@param upd_datas dict : datas to update (new values)
|
||||
#@return int : Number of updated records
|
||||
def update(self, target, filters, relational_filters, upd_datas):
|
||||
if 'shortname' in upd_datas:
|
||||
raise LodelSiteDatasourceError('Unable to update the \
|
||||
shortname, it is a site identifier. The right way for doing so is to copy \
|
||||
existing site with a new name')
|
||||
|
||||
datas = self.select(
|
||||
target,
|
||||
['shortname', 'em_groups', 'extensions'],
|
||||
filters,
|
||||
relational_filters)
|
||||
|
||||
for data in datas:
|
||||
if 'em_groups' in upd_datas:
|
||||
#groups update, dyncode refresh
|
||||
groups = upd_datas['em_groups']
|
||||
update_dyncode(data['shortname'], upd_datas['em_groups'])
|
||||
else:
|
||||
groups = data['em_groups']
|
||||
if 'extensions' in upd_datas:
|
||||
extensions = upd_datas['extensions']
|
||||
else:
|
||||
extensions = data['extensions']
|
||||
|
||||
update_conf(**{
|
||||
'sitename': data['shortname'],
|
||||
'em_groups': groups,
|
||||
'extensions': extensions})
|
||||
|
||||
return self._child_ds.update(
|
||||
target, filters, relational_filters, upd_datas)
|
||||
|
||||
##@brief Inserts a record in a given collection
|
||||
#@param target Emclass : class of the object to insert
|
||||
#@param new_datas dict : datas to insert
|
||||
#@return the inserted uid
|
||||
def insert(self, target, new_datas):
|
||||
self.__assert_good_leo(target)
|
||||
if site_exists(new_datas['shortname']):
|
||||
raise LodelSiteDatasourceError('A site with "%s" as shortname \
|
||||
already exists' % (new_datas['shortname']))
|
||||
site_directories_creation(new_datas['shortname'])
|
||||
site_lodelpkg_link_creation(new_datas['shortname'])
|
||||
site_context_init_creation(new_datas['shortname'])
|
||||
generate_conf(
|
||||
new_datas['shortname'],
|
||||
new_datas['em_groups'],
|
||||
new_datas['extensions'])
|
||||
ret = self._child_ds.insert(target, new_datas)
|
||||
update_dyncode(new_datas['shortname'], new_datas['em_groups'])
|
||||
return ret
|
||||
|
||||
## @brief Inserts a list of records in a given collection
|
||||
#@note Here is a simple implementation : a for loop triggering
|
||||
#insert() calls
|
||||
# @param target Emclass : class of the objects inserted
|
||||
# @param datas_list list : list of dict
|
||||
# @return list : list of the inserted records' ids
|
||||
def insert_multi(self, target, datas_list):
|
||||
res = []
|
||||
for new_datas in datas_list:
|
||||
res.append(self.insert(target, datas))
|
||||
return res
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[lodel2]
|
||||
debug=False
|
||||
|
||||
[lodel2.logging.stderr]
|
||||
level = INFO
|
||||
filename = -
|
||||
context = True
|
||||
|
||||
|
||||
42
lodel/plugins/lodelsite_datasource/sites_utils.py
Normal file
42
lodel/plugins/lodelsite_datasource/sites_utils.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
##
|
||||
#@note in itself thos method should be implemented as custom methods
|
||||
#for Lodelsite leobject. The current state of the project do not allow
|
||||
#to do this easily. So you will found them here for the moment....
|
||||
|
||||
import os.path
|
||||
from lodel import buildconf #safe even outside any context
|
||||
|
||||
from lodel.context import LodelContext
|
||||
|
||||
|
||||
##@brief Update the leapi_dyncode.py file for a given site
|
||||
#
|
||||
#Follows expectation of LodelContext :
|
||||
#The leapi_dyncode.py file will be stored in site's context dir
|
||||
#@param site_name str : site shortname
|
||||
#@param em_groups list : list of em groups to enable in the dyncode
|
||||
#@return Nothing
|
||||
def update_dyncode(site_name, em_groups):
|
||||
_, ctx_path = LodelContext.lodelsites_paths()
|
||||
dyncode_path = os.path.join(os.path.join(ctx_path, site_name),
|
||||
buildconf.MULTISITE_DYNCODE_MODULENAME+'.py')
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.settings': ['Settings'],
|
||||
'lodel.editorial_model.model': ['EditorialModel'],
|
||||
'lodel.leapi.lefactory': 'lefactory'})
|
||||
|
||||
EditorialModel._override_settings(False, em_groups)
|
||||
model = EditorialModel.load(
|
||||
Settings.lodelsites.sites_emtranslator,
|
||||
filename = Settings.lodelsites.sites_emfile)
|
||||
logger.info('EditorialModel loaded for handled site %s' % site_name)
|
||||
dyncode = lefactory.dyncode_from_em(model)
|
||||
with open(dyncode_path, 'w+') as dfp:
|
||||
dfp.write(dyncode)
|
||||
EditorialModel._override_settings() #Restoring safe values
|
||||
logger.info('Dyncode generated for handled site %s' % site_name)
|
||||
logger.debug('Dyncode for %s contains those groups : %s' % (
|
||||
site_name, em_groups))
|
||||
|
||||
|
||||
22
lodel/plugins/lodelsite_datasource/specs.py
Normal file
22
lodel/plugins/lodelsite_datasource/specs.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
##@brief Contains some constants in order to check that a LeObject child
|
||||
#is compatible with the datasource
|
||||
#
|
||||
#@note isolated in order to be usable by __init__.py and main.py
|
||||
|
||||
LEO_NAME = 'Lodelsite'
|
||||
MANDATORY_FIELDNAMES = [ 'shortname', 'extensions', 'em_groups' ]
|
||||
|
||||
##@brief Checks that given emcomponent is compatible with datasource
|
||||
#behavior
|
||||
#@warning 100% hardcoded checks on leo name fieldnames & types
|
||||
#@param emcomp LeObject subclass (or instance)
|
||||
#@return a tuple (bool, reason_str)
|
||||
def check(leo):
|
||||
if not hasattr(leo, '__name__'):
|
||||
leo = leo.__class__
|
||||
if leo.__name__ != LEO_NAME:
|
||||
return (False, 'bad name')
|
||||
missings = set(MANDATORY_FIELDNAMES) - set(leo.fieldnames())
|
||||
if len(missings) > 0:
|
||||
return (False, 'missing fields : ' + (', '.join(missings)))
|
||||
return (True, 'ok')
|
||||
|
|
@ -1,3 +1,22 @@
|
|||
##@brief
|
||||
#
|
||||
#@par Notes on FS organisation
|
||||
#
|
||||
#The plan is as follow :
|
||||
#
|
||||
#The autotools deploiment chain or the debian package will create a var
|
||||
#folder dedicated for lodel2 ( for example /var/lodel2 ). This folder will
|
||||
#contains folder named as the lodesites instances (lodelsites is a lodel site
|
||||
#that handles lodel site as content). We will call this folder multisite
|
||||
#folder or lodelsites folder.
|
||||
#The multisite folder contains 2 settings folders :
|
||||
#- lodelsites.conf.d : the lodelsites configuration
|
||||
#- server.conf.d : the multisite process configuration
|
||||
#- datas : a folder containing datas for each site handled by the lodelsites
|
||||
#- .contexts : a folder containing context stuff for each site handlers by
|
||||
#the lodelsites ( the lodel package symlink + dyncode.py)
|
||||
#
|
||||
|
||||
from lodel.context import LodelContext, ContextError
|
||||
try:
|
||||
LodelContext.expose_modules(globals(), {
|
||||
|
|
|
|||
|
|
@ -2,33 +2,19 @@ from lodel.context import LodelContext
|
|||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
#Define a minimal confspec used by multisite loader
|
||||
#Define a minimal confspec used by multisite loader and by lodelsites
|
||||
#instance
|
||||
LODEL2_CONFSPECS = {
|
||||
'lodel2': {
|
||||
'debug': (True, Validator('bool'))
|
||||
'lodel2.lodelsites': {
|
||||
#'name': (None,
|
||||
# Validator('string', none_is_valid = False)), #replaced by lodel2.name
|
||||
'lodelsites_emfile': (None,
|
||||
Validator('string', none_is_valid = False)), #Bad validator
|
||||
'lodelsites_emtranslator': ('picklefile',
|
||||
Validator('strip', none_is_valid = False)), #Bad validator
|
||||
'sites_emfile': (None,
|
||||
Validator('string', none_is_valid = False)), #Bad validator
|
||||
'sites_emtranslator': ('picklefile',
|
||||
Validator('string', none_is_valid = False)), #Bad validator
|
||||
},
|
||||
'lodel2.server': {
|
||||
'listen_address': ('127.0.0.1', Validator('dummy')),
|
||||
#'listen_address': ('', Validator('ip')), #<-- not implemented
|
||||
'listen_port': ( 1337, Validator('int')),
|
||||
'uwsgi_workers': (8, Validator('int')),
|
||||
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
|
||||
'virtualenv': (None, Validator('path', none_is_valid = True)),
|
||||
},
|
||||
'lodel2.logging.*' : {
|
||||
'level': ( 'ERROR',
|
||||
Validator('loglevel')),
|
||||
'context': ( False,
|
||||
Validator('bool')),
|
||||
'filename': ( None,
|
||||
Validator('errfile', none_is_valid = True)),
|
||||
'backupcount': ( 10,
|
||||
Validator('int', none_is_valid = False)),
|
||||
'maxbytes': ( 1024*10,
|
||||
Validator('int', none_is_valid = False)),
|
||||
},
|
||||
'lodel2.datasources.*': {
|
||||
'read_only': (False, Validator('bool')),
|
||||
'identifier': ( None, Validator('string')),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,28 @@ import sys
|
|||
import shlex
|
||||
import warnings
|
||||
|
||||
##@brief Preloader for a multisite process
|
||||
#
|
||||
#This loader is a kind of fake loader. In fact it only read configurations
|
||||
#for the multisite instance and then run a UWSGI process that will run
|
||||
#the run.py file.
|
||||
#
|
||||
#If you want to see the "real" multisite loading process see
|
||||
#@ref lodel/plugins/multisite/run.py file
|
||||
#
|
||||
#@par Implementation details
|
||||
#Here we have to bootstrap a minimal __loader__ context in order
|
||||
#to be able to load the settings
|
||||
#
|
||||
#This file (once bootstraped) start a new process for uWSGI. uWSGI then
|
||||
#run lodel.plugins.multisite.run.application function
|
||||
#@note the uwsgi process in started using the execl function when UWSGI
|
||||
#will exit this process will stop too
|
||||
#
|
||||
from lodel import buildconf
|
||||
from lodel import bootstrap
|
||||
bootstrap.bootstrap('__loader__')
|
||||
|
||||
try:
|
||||
from lodel.context import LodelContext
|
||||
except ImportError:
|
||||
|
|
@ -16,54 +33,28 @@ except ImportError:
|
|||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from lodel.context import LodelContext
|
||||
|
||||
from lodel import buildconf
|
||||
|
||||
LodelContext.init(LodelContext.MULTISITE)
|
||||
LodelContext.set(None) #Loading context creation
|
||||
#Multisite instance settings loading
|
||||
CONFDIR = os.path.join(os.getcwd(), 'conf.d')
|
||||
if not os.path.isdir(CONFDIR):
|
||||
warnings.warn('%s do not exists, default settings used' % CONFDIR)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.settings': [('Settings', 'settings')],
|
||||
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
|
||||
if not settings.started():
|
||||
settings('./conf.d', multisite_confspecs.LODEL2_CONFSPECS)
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings']})
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
})
|
||||
|
||||
##@brief Starts uwsgi in background using settings
|
||||
def uwsgi_fork():
|
||||
|
||||
sockfile = os.path.join(buildconf.LODEL2VARDIR, 'uwsgi_sockets/')
|
||||
if not os.path.isdir(sockfile):
|
||||
os.mkdir(sockfile)
|
||||
sockfile = os.path.join(sockfile, 'uwsgi_lodel2_multisite.sock')
|
||||
logfile = os.path.join(
|
||||
buildconf.LODEL2LOGDIR, 'uwsgi_lodel2_multisite.log')
|
||||
|
||||
cmd='{uwsgi} --plugin python3 --http-socket {addr}:{port} --module \
|
||||
lodel.plugins.multisite.run --socket {sockfile} --logto {logfile} -p {uwsgiworkers}'
|
||||
cmd = cmd.format(
|
||||
addr = Settings.server.listen_address,
|
||||
port = Settings.server.listen_port,
|
||||
uwsgi= Settings.server.uwsgicmd,
|
||||
sockfile=sockfile,
|
||||
logfile = logfile,
|
||||
uwsgiworkers = Settings.server.uwsgi_workers)
|
||||
if Settings.server.virtualenv is not None:
|
||||
cmd += " --virtualenv %s" % Settings.webui.virtualenv
|
||||
#If an interface that execl to run was loaded it will be run by following
|
||||
#hook
|
||||
LodelHook.call_hook('multisite_execl_interface', '__main__', None)
|
||||
|
||||
try:
|
||||
args = shlex.split(cmd)
|
||||
print("Running %s" % cmd)
|
||||
exit(os.execl(args[0], *args))
|
||||
except Exception as e:
|
||||
print("Webui plugin uwsgi execl fails cmd was '%s' error : " % cmd,
|
||||
e, file=sys.stderr)
|
||||
exit(1)
|
||||
#Nothing appened, running default ipython interface
|
||||
#The interface as to be run by execl to go out of this partial context
|
||||
PYTHON_EXC = '/usr/bin/python3'
|
||||
RUNNER_FILE = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'run.py')
|
||||
cmd = '%s "%s"' % (PYTHON_EXC, RUNNER_FILE)
|
||||
try:
|
||||
args = shlex.split(cmd)
|
||||
print("\n\nEND LOADER MULTISITE, execl\n\n")
|
||||
exit(os.execl(args[0], *args))
|
||||
except Exception as e:
|
||||
print("Multisite std interface execl fails. Command was : '%s' error \
|
||||
: %s" % (cmd, e), file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
uwsgi_fork()
|
||||
|
||||
|
|
|
|||
122
lodel/plugins/multisite/loader_utils.py
Normal file
122
lodel/plugins/multisite/loader_utils.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import os
|
||||
import os.path
|
||||
|
||||
|
||||
##@brief A cache allowing a fast application exposure
|
||||
#
|
||||
#This dict contains reference on interface module of each handled site in
|
||||
#order to quickly call the application (PEP 3333) function of concerned site
|
||||
FAST_APP_EXPOSAL_CACHE = dict()
|
||||
|
||||
try:
|
||||
from lodel.context import LodelContext
|
||||
except ImportError:
|
||||
LODEL_BASE_DIR = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from lodel.context import LodelContext, ContextError
|
||||
|
||||
import lodel.buildconf
|
||||
##@brief basename of lodelsites site conf folder
|
||||
LODELSITES_CONFD = lodel.buildconf.LODELSITE_CONFDIR
|
||||
|
||||
##@brief Stores the main function of a multisite loader
|
||||
|
||||
##@brief Function designed to bootstrap a multisite runner
|
||||
#
|
||||
#Handles lodelsites site loading, handled site list fecth & load
|
||||
#@note called at end of file
|
||||
#
|
||||
#@todo evaluate if it is safe to assume that lodelsites_datapath = os.getcwd()
|
||||
#@todo get rid of hardcoded stuff (like shortname fieldname)
|
||||
#@todo use the dyncode getter when it will be available (replaced by
|
||||
#the string SUPERDYNCODE_ACCESSOR.Lodelsite for the moment)
|
||||
#@todo remove hardcoded app name (need a better abstraction or a really
|
||||
#generic multiinstance runner)
|
||||
#@return lodelsites instance name
|
||||
def main(handled_sites_may_not_load = False):
|
||||
#Set current context to reserved loader context
|
||||
from lodel import bootstrap
|
||||
bootstrap.bootstrap('__loader__')
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings']})
|
||||
lodelsites_name = Settings.sitename
|
||||
lodelsites_datapath = os.path.join(
|
||||
os.path.join(lodel.buildconf.LODEL2VARDIR, lodelsites_name),
|
||||
lodel.buildconf.MULTISITE_DATADIR)
|
||||
del(globals()['Settings'])
|
||||
|
||||
#bootstraping the lodelsites instance
|
||||
LodelContext.new(lodelsites_name)
|
||||
LodelContext.set(lodelsites_name)
|
||||
#in lodelsites context
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.settings': [('Settings', 'settings_loader')],
|
||||
'lodel.plugins.multisite.confspecs': 'multisite_confspecs',
|
||||
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
|
||||
|
||||
settings_loader(lodel.buildconf.LODELSITE_CONFDIR,
|
||||
multisite_confspecs.LODEL2_CONFSPECS, True)
|
||||
del(globals()['settings_loader'])
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings']})
|
||||
|
||||
LodelContext.expose_dyncode(globals())
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
'lodel.plugin': ['Plugin']})
|
||||
Plugin.load_all()
|
||||
LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
|
||||
|
||||
lodelsite_leo = leapi_dyncode.Lodelsite #hardcoded leo name
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.leapi.query': ['LeGetQuery'],
|
||||
})
|
||||
#the line bellow you will find another harcoded thing : the shortname
|
||||
#fieldname for a lodelsite
|
||||
handled_sites = LeGetQuery(lodelsite_leo, query_filters = [],
|
||||
field_list = ['shortname']).execute()
|
||||
#Now that we have the handled sitenames list we can go back to
|
||||
#loader context and clean it
|
||||
if handled_sites is not None:
|
||||
LodelContext.set(None)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.bootstrap': ['site_preload', 'site_load']})
|
||||
for mname in ['LeGetQuery', 'Settings', 'LodelHook', 'Plugin', 'logger']:
|
||||
del(globals()[mname])
|
||||
#Loading handled sites
|
||||
for handled_sitename in [s['shortname'] for s in handled_sites]:
|
||||
datapath = os.path.join(lodelsites_datapath, handled_sitename)
|
||||
try:
|
||||
site_preload(datapath) #using default conf.d configuration dirname
|
||||
site_load(datapath)
|
||||
#
|
||||
# HARDCODED APP NAME
|
||||
#
|
||||
populate_fast_app_cache(datapath, 'lodel.plugins.webui.run')
|
||||
except Exception as e:
|
||||
LodelContext.set(None)
|
||||
LodelContext.set(lodelsites_name)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings'],
|
||||
'lodel.logger': 'logger'})
|
||||
if Settings.debug or handled_sites_may_not_load:
|
||||
logger.critical("Unable to load site %s : %s" % (
|
||||
e, handled_sitename))
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
logger.warning("No handled sites !")
|
||||
LodelContext.set(None)
|
||||
return lodelsites_name
|
||||
|
||||
##@brief Add an app to FAST_APP_EXPOSAL_CACHE
|
||||
#@param data_path str : instance data_path (used to extract the sitename !)
|
||||
#@param app_name str : application name (like lodel.plugins.webui.run)
|
||||
def populate_fast_app_cache(data_path, app_name):
|
||||
ctx_name = os.path.basename(data_path)
|
||||
LodelContext.set(ctx_name)
|
||||
FAST_APP_EXPOSAL_CACHE[ctx_name] = LodelContext.module(app_name)
|
||||
LodelContext.set(None)
|
||||
|
||||
|
|
@ -1,18 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import os.path
|
||||
import warnings
|
||||
|
||||
#This file expose common function to process a wsgi request and the
|
||||
#uWSGI application callback
|
||||
|
||||
|
||||
#preloading all instances
|
||||
FAST_APP_EXPOSAL_CACHE = dict()
|
||||
|
||||
LODEL2_INSTANCES_DIR = '.'
|
||||
EXCLUDE_DIR = {'conf.d', '__pycache__'}
|
||||
|
||||
"""
|
||||
try:
|
||||
from lodel.context import LodelContext
|
||||
except ImportError:
|
||||
|
|
@ -20,114 +8,29 @@ except ImportError:
|
|||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from lodel.context import LodelContext, ContextError
|
||||
|
||||
LodelContext.init(LodelContext.MULTISITE)
|
||||
LodelContext.set(None) #Loading context creation
|
||||
import lodel.buildconf #safe even outside contexts
|
||||
"""
|
||||
|
||||
#Multisite instance settings loading
|
||||
CONFDIR = os.path.join(os.getcwd(), 'conf.d')
|
||||
if not os.path.isdir(CONFDIR):
|
||||
warnings.warn('%s do not exists, default settings used' % CONFDIR)
|
||||
from lodel.plugins.multisite.loader_utils import main, FAST_APP_EXPOSAL_CACHE
|
||||
from lodel.bootstrap import site_load
|
||||
from lodel.context import LodelContext
|
||||
|
||||
lodelsites_name = main() #multisite bootstraping
|
||||
|
||||
#Switching back to lodelsites context in order to trigger hooks
|
||||
LodelContext.set(lodelsites_name)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.settings': [('Settings', 'settings')],
|
||||
'lodel.plugins.multisite.confspecs': 'multisite_confspecs'})
|
||||
if not settings.started():
|
||||
settings('./conf.d', multisite_confspecs.LODEL2_CONFSPECS)
|
||||
'lodel.plugin.hooks': ['LodelHook']})
|
||||
LodelHook.call_hook('lodel2_loader_main', '__main__', None)
|
||||
LodelContext.set(None)
|
||||
#If a hook is registered in lodelsites context for lodel2_loader_main
|
||||
#this function never returns
|
||||
|
||||
#Fetching insrtance list from subdirectories
|
||||
lodelsites_list = [ os.path.realpath(os.path.join(LODEL2_INSTANCES_DIR,sitename))
|
||||
for sitename in os.listdir(LODEL2_INSTANCES_DIR)
|
||||
if os.path.isdir(sitename) and sitename not in EXCLUDE_DIR]
|
||||
|
||||
#Bootstraping instances
|
||||
for lodelsite_path in lodelsites_list:
|
||||
ctx_name = LodelContext.from_path(lodelsite_path)
|
||||
#Switch to new context
|
||||
LodelContext.set(ctx_name)
|
||||
os.chdir(lodelsite_path)
|
||||
# Loading settings
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings.settings': [('Settings', 'settings')]})
|
||||
if not settings.started():
|
||||
settings('./conf.d')
|
||||
LodelContext.expose_modules(globals(), {'lodel.settings': ['Settings']})
|
||||
|
||||
# Loading hooks & plugins
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin': ['LodelHook'],
|
||||
'lodel.plugin.core_hooks': 'core_hooks',
|
||||
'lodel.plugin.core_scripts': 'core_scripts'
|
||||
})
|
||||
|
||||
#Load plugins
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.plugin': ['Plugin']})
|
||||
logger.debug("Loader.start() called")
|
||||
Plugin.load_all()
|
||||
#Import & expose dyncode
|
||||
LodelContext.expose_dyncode(globals())
|
||||
#Next hook triggers dyncode datasource instanciations
|
||||
LodelHook.call_hook('lodel2_plugins_loaded', '__main__', None)
|
||||
#Next hook triggers call of interface's main loop
|
||||
LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
|
||||
#FAST_APP_EXPOSAL_CACHE populate
|
||||
FAST_APP_EXPOSAL_CACHE[ctx_name] = LodelContext.module(
|
||||
'lodel.plugins.webui.run')
|
||||
LodelContext
|
||||
#a dirty & quick attempt to fix context unwanted exite via
|
||||
#hooks
|
||||
for name in ( 'LodelHook', 'core_hooks', 'core_scripts',
|
||||
'Settings', 'settings', 'logger', 'Plugin'):
|
||||
del(globals()[name])
|
||||
#switch back to loader context
|
||||
LodelContext.set(None)
|
||||
|
||||
#
|
||||
# From here lodel2 multisite instances are loaded and ready to run
|
||||
#
|
||||
|
||||
|
||||
##@brief Utility function to return quickly an error
|
||||
def http_error(env, start_response, status = '500 internal server error', \
|
||||
extra = None):
|
||||
headers = [('Content-type', 'text/plain; charset=utf-8')]
|
||||
start_response(status, headers)
|
||||
msg = status
|
||||
if extra is not None:
|
||||
msg = extra
|
||||
return [msg.encode('utf-8')]
|
||||
|
||||
|
||||
##@brief utility function to extract site id from an url
|
||||
#@param url str :
|
||||
def site_id_from_url(url):
|
||||
res = ''
|
||||
for c in url[1:]:
|
||||
if c == '/':
|
||||
break
|
||||
res += c
|
||||
if len(res) == 0:
|
||||
return None
|
||||
return res
|
||||
|
||||
##@brief This method is run in a child process by the handler
|
||||
def application(env, start_response):
|
||||
#Attempt to load a context
|
||||
site_id = site_id_from_url(env['PATH_INFO'])
|
||||
if site_id is None:
|
||||
#It can be nice to provide a list of instances here
|
||||
return http_error(env, start_response, '404 Not Found')
|
||||
try:
|
||||
LodelContext.set(site_id)
|
||||
#We are in the good context
|
||||
|
||||
except ContextError as e:
|
||||
print(e)
|
||||
return http_error(env, start_response, '404 Not found',
|
||||
"No site named '%s'" % site_id)
|
||||
#Calling webui
|
||||
return FAST_APP_EXPOSAL_CACHE[site_id].application(env, start_response)
|
||||
#LodelContext.expose_modules(globals(), {
|
||||
# 'lodel.plugins.webui.run': ['application']})
|
||||
#return application(env, start_response)
|
||||
import code
|
||||
print("""
|
||||
Running interactive python in Lodel2 multisite instance env.
|
||||
|
||||
Note : you are in LOAD_CTX environnment.
|
||||
use lodel.context.Lodelcontext.set(CONTEXT_NAME) to switch
|
||||
""")
|
||||
code.interact(local=locals())
|
||||
|
|
|
|||
55
lodel/plugins/multisite_web/__init__.py
Normal file
55
lodel/plugins/multisite_web/__init__.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
##@brief
|
||||
#
|
||||
#Lodel2 multisite uwsgi plugin
|
||||
|
||||
from lodel.context import LodelContext, ContextError
|
||||
try:
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.validator.validator': ['Validator']})
|
||||
|
||||
__plugin_type__ = 'ui' #Warning !!! It's a MULTISITE interface
|
||||
__plugin_name__ = 'multisite_web'
|
||||
__version__ = '0.0.1'
|
||||
__loader__ = 'main.py'
|
||||
#__plugin_deps__ = ['multisite']
|
||||
|
||||
CONFSPEC = {
|
||||
'lodel2.server': {
|
||||
'listen_address': ('127.0.0.1', Validator('dummy')),
|
||||
#'listen_address': ('', Validator('ip')), #<-- not implemented
|
||||
'listen_port': ( 1337, Validator('int')),
|
||||
'uwsgi_workers': (8, Validator('int')),
|
||||
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
|
||||
'virtualenv': (None, Validator('path', none_is_valid = True)),
|
||||
},
|
||||
#A copy from webui confspec
|
||||
##@todo automatic merging of webuig confspecs
|
||||
'lodel2.webui': {
|
||||
'standalone': ( 'False',
|
||||
Validator('string')),
|
||||
'listen_address': ( '127.0.0.1',
|
||||
Validator('dummy')),
|
||||
'listen_port': ( '9090',
|
||||
Validator('int')),
|
||||
'static_url': ( 'http://127.0.0.1/static/',
|
||||
Validator('regex', pattern = r'^https?://[^/].*$')),
|
||||
'virtualenv': (None,
|
||||
Validator('path', none_is_valid=True)),
|
||||
'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
|
||||
'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', Validator('dummy')),
|
||||
'cookie_session_id': ('lodel', Validator('dummy')),
|
||||
'uwsgi_workers': (2, Validator('int'))
|
||||
},
|
||||
'lodel2.webui.sessions': {
|
||||
'directory': ( '/tmp',
|
||||
Validator('path')),
|
||||
'expiration': ( 900,
|
||||
Validator('int')),
|
||||
'file_template': ( 'lodel2_%s.sess',
|
||||
Validator('dummy')),
|
||||
}
|
||||
}
|
||||
|
||||
except ContextError:
|
||||
#This case append when uwsgi call the run.py directly
|
||||
pass
|
||||
1
lodel/plugins/multisite_web/loader_utils.py
Symbolic link
1
lodel/plugins/multisite_web/loader_utils.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../multisite/loader_utils.py
|
||||
50
lodel/plugins/multisite_web/main.py
Normal file
50
lodel/plugins/multisite_web/main.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import os
|
||||
import shlex
|
||||
from lodel.context import LodelContext
|
||||
import lodel.buildconf
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.hooks': ['LodelHook'],
|
||||
})
|
||||
|
||||
##@brief Starts uwsgi in background using settings
|
||||
@LodelHook('lodel2_loader_main')
|
||||
def uwsgi_fork(hook_name, caller, payload):
|
||||
|
||||
sockfile = os.path.join(lodel.buildconf.LODEL2VARDIR, 'uwsgi_sockets/')
|
||||
if not os.path.isdir(sockfile):
|
||||
os.mkdir(sockfile)
|
||||
sockfile = os.path.join(sockfile, 'uwsgi_lodel2_multisite.sock')
|
||||
logfile = os.path.join(
|
||||
lodel.buildconf.LODEL2LOGDIR, 'uwsgi_lodel2_multisite.log')
|
||||
|
||||
#Switching to __loader__ context
|
||||
print("PASS1")
|
||||
LodelContext.set(None)
|
||||
print("PASS2")
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings']})
|
||||
print("PASS3")
|
||||
|
||||
cmd='{uwsgi} --plugin python3 --http-socket {addr}:{port} --module \
|
||||
lodel.plugins.multisite_web.run --socket {sockfile} --logto {logfile} -p {uwsgiworkers}'
|
||||
cmd = cmd.format(
|
||||
addr = Settings.server.listen_address,
|
||||
port = Settings.server.listen_port,
|
||||
uwsgi= Settings.server.uwsgicmd,
|
||||
sockfile=sockfile,
|
||||
logfile = logfile,
|
||||
uwsgiworkers = Settings.server.uwsgi_workers)
|
||||
if Settings.server.virtualenv is not None:
|
||||
cmd += " --virtualenv %s" % Settings.webui.virtualenv
|
||||
|
||||
print("PASS4")
|
||||
try:
|
||||
args = shlex.split(cmd)
|
||||
print("Running %s" % cmd)
|
||||
exit(os.execl(args[0], *args))
|
||||
except Exception as e:
|
||||
print("Webui plugin uwsgi execl fails cmd was '%s' error : " % cmd,
|
||||
e, file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
100
lodel/plugins/multisite_web/run.py
Normal file
100
lodel/plugins/multisite_web/run.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import os.path
|
||||
import warnings
|
||||
|
||||
##@brief File designed to be executed by an UWSGI process in order to
|
||||
#run a multisite instance process
|
||||
#
|
||||
#@warning In this module we have to be VERY carrefull about module exposure !
|
||||
#in fact here we will go through every context at least once and the globals
|
||||
#will be shared all among the module. So ANY exposed module will be accessible
|
||||
#for a short time outside its context !! A good way be protected about that
|
||||
#is to del(globals()[exposed_module_name]) after use. But it's really not
|
||||
#sure that this way of doing is a real safe protection !
|
||||
#
|
||||
#@par Expected context when called
|
||||
#- cwd has to be the lodelsites directory (the directory containing the
|
||||
#conf.d folder)
|
||||
#
|
||||
#This file is divided in two blocks :
|
||||
#
|
||||
#@par Run at load
|
||||
#The main function will be called at import.
|
||||
#This piece of code handles :
|
||||
#- loading the lodelsites site (the site that handles sites ;) )
|
||||
#- fetch the list of handled lodel sites
|
||||
#- loading the whole list of lodel sites
|
||||
#
|
||||
#@par WSGI processing
|
||||
#The other functions are here to handles WSGI request. The entry function
|
||||
#is application(), following the PEP 3333 specifications
|
||||
|
||||
try:
|
||||
from lodel.context import LodelContext
|
||||
except ImportError:
|
||||
LODEL_BASE_DIR = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from lodel.context import LodelContext, ContextError
|
||||
|
||||
import lodel.buildconf #safe even outside contexts
|
||||
import lodel.plugins.multisite.loader_utils as loader_utils
|
||||
lodelsites_name = loader_utils.main()
|
||||
#FAST_APP_EXPOSAL_CACHE has to be loaded and used from __loader__ context
|
||||
LodelContext.set(None)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugins.multisite.loader_utils': ['FAST_APP_EXPOSAL_CACHE']
|
||||
})
|
||||
LodelContext.set(lodelsites_name)
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.bootstrap': ['site_load'],
|
||||
})
|
||||
FAST_APP_EXPOSAL_CACHE[lodelsites_name] = LodelContext.module(
|
||||
'lodel.plugins.webui.run')
|
||||
LodelContext.set(None)
|
||||
|
||||
##@brief Utility function to return quickly an error
|
||||
def http_error(env, start_response, status = '500 internal server error', \
|
||||
extra = None):
|
||||
headers = [('Content-type', 'text/plain; charset=utf-8')]
|
||||
start_response(status, headers)
|
||||
msg = status
|
||||
if extra is not None:
|
||||
msg = extra
|
||||
return [msg.encode('utf-8')]
|
||||
|
||||
|
||||
##@brief utility function to extract site id from an url
|
||||
#@param url str :
|
||||
def site_id_from_url(url):
|
||||
res = ''
|
||||
for c in url[1:]:
|
||||
if c == '/':
|
||||
break
|
||||
res += c
|
||||
if len(res) == 0:
|
||||
return None
|
||||
return res
|
||||
|
||||
##@brief This method is run in a child process by the handler
|
||||
def application(env, start_response):
|
||||
LodelContext.set(None) #Ensure context switching
|
||||
#Attempt to load a context
|
||||
site_id = site_id_from_url(env['PATH_INFO'])
|
||||
if site_id is None:
|
||||
#It can be nice to provide a list of instances here
|
||||
return http_error(env, start_response, '404 Not Found')
|
||||
try:
|
||||
LodelContext.set(site_id)
|
||||
#We are in the good context
|
||||
|
||||
except ContextError as e:
|
||||
print(e)
|
||||
return http_error(env, start_response, '404 Not found',
|
||||
"No site named '%s'" % site_id)
|
||||
#Calling webui
|
||||
return FAST_APP_EXPOSAL_CACHE[site_id].application(env, start_response)
|
||||
#LodelContext.expose_modules(globals(), {
|
||||
# 'lodel.plugins.webui.run': ['application']})
|
||||
#return application(env, start_response)
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ LodelContext.expose_modules(globals(), {
|
|||
'lodel.leapi.exceptions': [],
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.leapi.datahandlers.base_classes': ['MultipleRef'],
|
||||
'lodel.leapi.exceptions': ['LeApiDataCheckErrors'],
|
||||
'lodel.leapi.exceptions': ['LeApiDataCheckErrors', 'LeApiError'],
|
||||
'lodel.exceptions': ['LodelExceptions']})
|
||||
LodelContext.expose_dyncode(globals(), 'dyncode')
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ def admin_update(request):
|
|||
if not(datas is False):
|
||||
if 'lodel_id' not in datas:
|
||||
raise HttpException(400)
|
||||
target_leo = dyncode.Object.name2class(datas['classname'])
|
||||
target_leo = dyncode.dynclasses[0].name2class(datas['classname'])
|
||||
leo = target_leo.get_from_uid(datas['lodel_id'])
|
||||
if leo is None:
|
||||
raise HttpException(404,
|
||||
|
|
@ -66,7 +66,7 @@ def admin_update(request):
|
|||
raise HttpException(400)
|
||||
classname = classname[0]
|
||||
try:
|
||||
target_leo = dyncode.Object.name2class(classname)
|
||||
target_leo = dyncode.dynclasses[0].name2class(classname)
|
||||
except LeApiError:
|
||||
# classname = None
|
||||
raise HttpException(400)
|
||||
|
|
@ -109,7 +109,7 @@ def admin_create(request):
|
|||
|
||||
datas = process_form(request)
|
||||
if not(datas is False):
|
||||
target_leo = dyncode.Object.name2class(datas['classname'])
|
||||
target_leo = dyncode.dynclasses[0].name2class(datas['classname'])
|
||||
if 'lodel_id' in datas:
|
||||
raise HttpException(400)
|
||||
try:
|
||||
|
|
@ -133,7 +133,7 @@ def admin_create(request):
|
|||
raise HttpException(400)
|
||||
classname = classname[0]
|
||||
try:
|
||||
target_leo = dyncode.Object.name2class(classname)
|
||||
target_leo = dyncode.dynclasses[0].name2class(classname)
|
||||
except LeApiError:
|
||||
classname = None
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ def admin_delete(request):
|
|||
raise HttpException(400)
|
||||
classname = classname[0]
|
||||
try:
|
||||
target_leo = dyncode.Object.name2class(classname)
|
||||
target_leo = dyncode.dynclasses[0].name2class(classname)
|
||||
except LeApiError:
|
||||
# classname = None
|
||||
raise HttpException(400)
|
||||
|
|
@ -227,7 +227,7 @@ def admin_class(request):
|
|||
raise HttpException(400)
|
||||
classname = classname[0]
|
||||
try:
|
||||
target_leo = dyncode.Object.name2class(classname)
|
||||
target_leo = dyncode.dynclasses[0].name2class(classname)
|
||||
except LeApiError:
|
||||
classname = None
|
||||
if classname is None or target_leo.is_abstract():
|
||||
|
|
@ -246,7 +246,7 @@ def delete_in_class(request):
|
|||
raise HttpException(400)
|
||||
classname = classname[0]
|
||||
try:
|
||||
target_leo = dyncode.Object.name2class(classname)
|
||||
target_leo = dyncode.dynclasses[0].name2class(classname)
|
||||
except LeApiError:
|
||||
classname = None
|
||||
if classname is None or target_leo.is_abstract():
|
||||
|
|
@ -266,7 +266,7 @@ def search_object(request):
|
|||
classname = request.POST['classname']
|
||||
searchstring = request.POST['searchstring']
|
||||
try:
|
||||
target_leo = dyncode.Object.name2class(classname)
|
||||
target_leo = dyncode.dynclasses[0].name2class(classname)
|
||||
except LeApiError:
|
||||
raise HttpException(400)
|
||||
# TODO The get method must be implemented here
|
||||
|
|
@ -286,7 +286,7 @@ def process_form(request):
|
|||
raise HttpException(400)
|
||||
res['classname'] = classname = request.form['classname']
|
||||
try:
|
||||
target_leo = dyncode.Object.name2class(classname)
|
||||
target_leo = dyncode.dynclasses[0].name2class(classname)
|
||||
except LeApiError:
|
||||
logger.error(
|
||||
"Received a form with an invalid leo name : '%s'" % classname)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {'lodel.logger': 'logger'})
|
||||
LodelContext.expose_modules(globals(), {'lodel.logger': 'logger',
|
||||
'lodel.leapi.exceptions': ['LeApiError']})
|
||||
LodelContext.expose_dyncode(globals(), 'dyncode')
|
||||
|
||||
from .base import get_response
|
||||
from .utils import *
|
||||
from ...exceptions import *
|
||||
|
||||
##@brief These functions are called by the rules defined in ../urls.py
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
import re
|
||||
|
||||
from .controllers import *
|
||||
from .urls import urls
|
||||
from ..main import root_url
|
||||
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.settings': ['Settings']})
|
||||
'lodel.settings': ['Settings'],
|
||||
'lodel.plugins.webui.main': ['root_url'],
|
||||
'lodel.plugins.webui.interface.urls': ['urls'],
|
||||
})
|
||||
|
||||
def format_url_rule(url_rule):
|
||||
if url_rule.startswith('^'):
|
||||
|
|
|
|||
|
|
@ -8,25 +8,25 @@
|
|||
<input id="field_input_{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="checkbox" checked="{% if value %}checked{% endif %}" >
|
||||
{% elif field.base_type == 'password' %}
|
||||
<input id="{{fieldname}}" name="field_input_{{fieldname}}" class="form-control" type="password" value="{{sval}}" >
|
||||
{% elif field.base_type == 'char' or field.base_type == 'int' %}
|
||||
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{value}}" >
|
||||
{% elif field.base_type == 'text' %}
|
||||
<textarea class="form-control" name="field_input_{{fieldname}}">{{value}}</textarea>
|
||||
{% elif field.base_type == 'datetime' %}
|
||||
<input class="form-control" type="date" value="{{value}}" name="field_input_{{fieldname}}">
|
||||
{% elif field.base_type == 'ref' %}
|
||||
{% if value is iterable %}
|
||||
{% set sval=value|join(',') %}
|
||||
{% else %}
|
||||
{% set sval = value %}
|
||||
{% endif %}
|
||||
{% if field.directly_editable %}
|
||||
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}" >
|
||||
{% set l_classe = field.allowed_classes %}
|
||||
<p> Please enter uids to instances of {{ l_classe.__name__ }} separated by commas </p>
|
||||
{% else %}
|
||||
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}">
|
||||
{% endif %}
|
||||
{% elif field.base_type == 'char' or field.base_type == 'int' or field.base_type == 'varchar' %}
|
||||
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{value}}" >
|
||||
{% elif field.base_type == 'text' %}
|
||||
<textarea class="form-control" name="field_input_{{fieldname}}">{{value}}</textarea>
|
||||
{% elif field.base_type == 'datetime' %}
|
||||
<input class="form-control" type="date" value="{{value}}" name="field_input_{{fieldname}}">
|
||||
{% elif field.base_type == 'ref' %}
|
||||
{% if value is iterable %}
|
||||
{% set sval=value|join(',') %}
|
||||
{% else %}
|
||||
{% set sval = value %}
|
||||
{% endif %}
|
||||
{% if field.directly_editable %}
|
||||
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}" >
|
||||
{% set l_classe = field.allowed_classes %}
|
||||
<p> Please enter uids to instances of {{ l_classe.__name__ }} separated by commas </p>
|
||||
{% else %}
|
||||
<input id="{{fieldname}}" class="form-control" name="field_input_{{fieldname}}" type="text" value="{{sval}}">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Unsupported base type "{{field.base_type}}" <br>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -82,17 +82,25 @@ class Settings(object, metaclass=MetaSettings):
|
|||
# @brief Stores the singleton instance
|
||||
instance = None
|
||||
|
||||
# @brief Instanciate the Settings singleton
|
||||
# @param conf_dir str : The configuration directory
|
||||
##@brief Instanciate the Settings singleton
|
||||
#@param conf_dir str : The configuration directory
|
||||
#@param custom_confspecs None | dict : if given overwrite default lodel2
|
||||
# confspecs
|
||||
def __init__(self, conf_dir, custom_confspecs=None):
|
||||
#@param append bool : if true do not replace LODEL2CONFSPECS by
|
||||
#custom_confspecs, append them instead
|
||||
#
|
||||
#@todo append param and __custom_specs_append attributes are quick
|
||||
#workarounds in order to be able to specialise LODEL2_CONF_SPECS for a
|
||||
#multisite lodelsite. This should be done using the standard confspecs
|
||||
#mechanism available using plugins
|
||||
def __init__(self, conf_dir, custom_confspecs=None, append = False):
|
||||
self.singleton_assert() # check that it is the only instance
|
||||
Settings.instance = self
|
||||
# @brief Configuration specification
|
||||
#
|
||||
# Initialized by Settings.__bootstrap() method
|
||||
self.__conf_specs = custom_confspecs
|
||||
self.__conf_specs = custom_confspecs if not append else None
|
||||
self.__conf_specs_append = custom_confspecs if append else None
|
||||
# @brief Stores the configurations in namedtuple tree
|
||||
self.__confs = None
|
||||
self.__conf_dir = conf_dir
|
||||
|
|
@ -185,6 +193,8 @@ class Settings(object, metaclass=MetaSettings):
|
|||
Plugin.start(plugin_list)
|
||||
# Fetching conf specs from plugins
|
||||
specs = [lodel2_specs]
|
||||
if self.__conf_specs_append is not None:
|
||||
specs.append(self.__conf_specs_append)
|
||||
errors = list()
|
||||
for plugin_name in plugin_list:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ class SettingsLoader(object):
|
|||
# @param conf_path str : conf.d path
|
||||
def __init__(self, conf_path):
|
||||
self.__conf_path = conf_path
|
||||
if not os.path.isdir(conf_path):
|
||||
logger.warning("Configuration directory %s do not exists. Using \
|
||||
default configuration values (cwd : %s)" % (conf_path, os.getcwd()))
|
||||
self.__conf_sv = dict()
|
||||
self.__conf = self.__merge()
|
||||
# Stores errors
|
||||
|
|
@ -168,6 +171,7 @@ class SettingsLoader(object):
|
|||
key_id=key_id, \
|
||||
filename =filename))
|
||||
if len(err_l) > 0:
|
||||
raise SettingsErrors(err_l)
|
||||
raise SettingsErrors(err_l, 'Following errors were detected \
|
||||
while loading confs from %s' % self.__conf_path)
|
||||
else:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ class SettingsErrors(Exception):
|
|||
|
||||
##@brief Instanciate an SettingsErrors
|
||||
# @param exceptions list : list of SettingsError instance
|
||||
def __init__(self, exceptions):
|
||||
def __init__(self, exceptions, extra_msg = 'Errors :'):
|
||||
self._extra_msg = extra_msg
|
||||
for expt in exceptions:
|
||||
if not isinstance(expt, SettingsError):
|
||||
raise ValueError("The 'exceptions' argument has to be an array of <class SettingsError>, but a %s was found in the list" % type(expt))
|
||||
|
|
@ -43,7 +44,7 @@ class SettingsErrors(Exception):
|
|||
def __repr__(self): return str(self)
|
||||
|
||||
def __str__(self):
|
||||
res = "Errors :\n"
|
||||
res = "%s\n" % self._extra_msg
|
||||
for expt in self.__exceptions:
|
||||
res += "\t%s\n" % str(expt)
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@ def emfield_val(value):
|
|||
|
||||
@LodelHook('lodel2_dyncode_bootstraped')
|
||||
def emfield_conf_check(hookname, caller, payload):
|
||||
import leapi_dyncode as dyncode # <-- dirty & quick
|
||||
LodelContext.expose_dyncode(globals(), 'dyncode')
|
||||
classnames = {cls.__name__.lower(): cls for cls in dyncode.dynclasses}
|
||||
if value[0].lower() not in classnames:
|
||||
msg = "Following dynamic class do not exists in current EM : %s"
|
||||
|
|
@ -362,35 +362,33 @@ def emfield_val(value):
|
|||
# @brief Validator for plugin name & optionnaly type
|
||||
#
|
||||
# Able to check that the value is a plugin and if it is of a specific type
|
||||
|
||||
|
||||
#@param value the plugin name
|
||||
#@param ptype : plugin type
|
||||
def plugin_validator(value, ptype=None):
|
||||
if value:
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.hooks': ['LodelHook']})
|
||||
value = copy.copy(value.strip())
|
||||
value = None if len(value) == 0 else value
|
||||
|
||||
@LodelHook('lodel2_dyncode_bootstraped')
|
||||
def plugin_type_checker(hookname, caller, payload):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.hooks': ['LodelHook']})
|
||||
value = copy.copy(value)
|
||||
|
||||
@LodelHook('lodel2_dyncode_bootstraped')
|
||||
def plugin_type_checker(hookname, caller, payload):
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.plugin.plugins': ['Plugin'],
|
||||
'lodel.plugin.exceptions': ['PluginError']})
|
||||
if value is None:
|
||||
return
|
||||
try:
|
||||
plugin = Plugin.get(value)
|
||||
except PluginError:
|
||||
msg = "No plugin named %s found"
|
||||
msg %= value
|
||||
raise ValidationError(msg)
|
||||
if plugin._type_conf_name.lower() != ptype.lower():
|
||||
msg = "A plugin of type '%s' was expected but found a plugin \
|
||||
named '%s' that is a '%s' plugin"
|
||||
msg %= (ptype, value, plugin._type_conf_name)
|
||||
raise ValidationError(msg)
|
||||
return value
|
||||
return None
|
||||
|
||||
'lodel.plugin.plugins': ['Plugin'],
|
||||
'lodel.plugin.exceptions': ['PluginError']})
|
||||
if value is None:
|
||||
return
|
||||
try:
|
||||
plugin = Plugin.get(value)
|
||||
except PluginError:
|
||||
msg = "No plugin named %s found"
|
||||
msg %= value
|
||||
raise ValidationError(msg)
|
||||
if plugin._type_conf_name.lower() != ptype.lower():
|
||||
msg = "A plugin of type '%s' was expected but found a plugin \
|
||||
named '%s' that is a '%s' plugin"
|
||||
msg %= (ptype, value, plugin._type_conf_name)
|
||||
raise ValidationError(msg)
|
||||
return value
|
||||
|
||||
Validator.register_validator(
|
||||
'plugin',
|
||||
|
|
@ -406,7 +404,7 @@ Validator.register_validator(
|
|||
# Lodel 2 configuration specification
|
||||
#
|
||||
|
||||
# @brief Append a piece of confspec
|
||||
##@brief Append a piece of confspec
|
||||
#@note orig is modified during the process
|
||||
#@param orig dict : the confspec to update
|
||||
#@param section str : section name
|
||||
|
|
@ -414,8 +412,6 @@ Validator.register_validator(
|
|||
#@param validator Validator : the validator to use to check this configuration key's value
|
||||
#@param default
|
||||
#@return new confspec
|
||||
|
||||
|
||||
def confspec_append(orig, section, key, validator, default):
|
||||
if section not in orig:
|
||||
orig[section] = dict()
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
lodelsites_PYTHON = *.py
|
||||
|
||||
lodelsitesdir=$(pkgpythondir)/../lodelsites/
|
||||
|
|
@ -26,11 +26,12 @@ em_lodel_site = em.new_class(
|
|||
)
|
||||
|
||||
em_lodel_site.new_field(
|
||||
'name',
|
||||
display_name = 'lodelSiteName',
|
||||
help_text = 'Lodel site full name',
|
||||
'id',
|
||||
display_name = 'Site id',
|
||||
help_text = 'Numeric uniq ID for a Lodel site',
|
||||
group = base_group,
|
||||
data_handler = 'varchar'
|
||||
data_handler = 'uniqid',
|
||||
internal = True
|
||||
)
|
||||
|
||||
em_lodel_site.new_field(
|
||||
|
|
@ -38,11 +39,20 @@ em_lodel_site.new_field(
|
|||
display_text = 'lodelSiteShortName',
|
||||
help_text = 'Lodel site short string identifier',
|
||||
group = base_group,
|
||||
data_handler = 'varchar',
|
||||
max_length = 5,
|
||||
data_handler = 'regex',
|
||||
max_length = 16,
|
||||
regex = '^[0-9a-z_-]+$',
|
||||
uniq = True
|
||||
)
|
||||
|
||||
em_lodel_site.new_field(
|
||||
'name',
|
||||
display_name = 'lodelSiteName',
|
||||
help_text = 'Lodel site full name',
|
||||
group = base_group,
|
||||
data_handler = 'varchar'
|
||||
)
|
||||
|
||||
em_lodel_site.new_field(
|
||||
'extensions',
|
||||
display_text = 'lodeSiteExtensions',
|
||||
|
|
@ -60,6 +70,41 @@ em_lodel_site.new_field(
|
|||
data_handler = 'text',
|
||||
)
|
||||
|
||||
|
||||
|
||||
#
|
||||
#This class & fields becomes mandatory when the auth process was integrated
|
||||
#into lodel2 engine
|
||||
#
|
||||
user_group = em.new_group(
|
||||
'users',
|
||||
display_name = 'Lodel users',
|
||||
help_text = 'Group that handle users en perm'
|
||||
)
|
||||
|
||||
user = em.new_class(
|
||||
'User',
|
||||
group = user_group
|
||||
)
|
||||
|
||||
user.new_field(
|
||||
'login',
|
||||
display_name = 'User login',
|
||||
help_text = 'login',
|
||||
group = user_group,
|
||||
data_handler = 'varchar',
|
||||
primary_key = True
|
||||
)
|
||||
|
||||
user.new_field(
|
||||
'password',
|
||||
display_name = 'Password',
|
||||
help_text = 'User password',
|
||||
group = user_group,
|
||||
data_handler = 'password',
|
||||
)
|
||||
|
||||
|
||||
pickle_file_path = 'examples/lodelsites_em.pickle'
|
||||
xml_file_path = 'examples/lodelsites_em.xml'
|
||||
|
||||
|
|
|
|||
2
plugins
2
plugins
|
|
@ -1 +1 @@
|
|||
lodel/plugins/
|
||||
lodel/plugins
|
||||
|
|
@ -1,17 +1,30 @@
|
|||
include $(top_builddir)/Makefile-common.am.inc
|
||||
|
||||
SUBDIRS=slim
|
||||
|
||||
lodel2_scripts_dir = $(datadir)/lodel2/scripts
|
||||
lodel2_scripts__DATA = create_instance mass_deploy
|
||||
CLEANFILES = create_instance mass_deploy mass_deploy.cfg
|
||||
EXTRA_DIST = create_instance.sh mass_deploy.sh
|
||||
lodel2_scripts__DATA = create_instance create_multisite mass_deploy
|
||||
CLEANFILES = create_instance create_multisite mass_deploy mass_deploy.cfg
|
||||
EXTRA_DIST = create_instance.sh create_multisite.sh mass_deploy.sh
|
||||
|
||||
lodel2conf_DATA = mass_deploy.cfg
|
||||
lodel2confdir=$(sysconfdir)/lodel2
|
||||
lodel2statedir=$(localstatedir)/lodel2
|
||||
lodel2datadir = $(datadir)/lodel2
|
||||
lodel_libdir=$(pkgpythondir)/
|
||||
install_model_dir = $(lodel2datadir)/install_model
|
||||
multisite_install_model_dir = $(lodel2datadir)/multisite_install_model
|
||||
|
||||
|
||||
do_subst = sed -e 's,\[@\]PKGPYTHONDIR\[@\],$(pkgpythondir),g' \
|
||||
do_subst = sed -e 's,\[@\]PKGPYTHONDIR\[@\],$(pkgpythondir),g' \
|
||||
-e 's,\[@\]LODEL2_LIBDIR\[@\],$(lodel_libdir),g' \
|
||||
-e 's,\[@\]LODEL2_DATADIR\[@\],$(lodel2datadir),g' \
|
||||
-e 's,\[@\]LODEL2_PROGSDIR\[@\],$(lodel2_scripts_dir),g' \
|
||||
-e 's,\[@\]LODEL2_CONFDIR\[@\],$(lodel2confdir),g'
|
||||
-e 's,\[@\]LODEL2_CONFDIR\[@\],$(lodel2confdir),g' \
|
||||
-e 's,\[@\]LODEL2_VARDIR\[@\],$(lodel2statedir),g' \
|
||||
-e 's,\[@\]INSTALLMODEL_DIR\[@\],$(install_model_dir),g' \
|
||||
-e 's,\[@\]MULTISITE_INSTALLMODEL_DIR\[@\],$(multisite_install_model_dir),g' \
|
||||
-e 's,\[@\]LODELSITES_DATA_DIRNAME\[@\],$(lodel2_multisite_data_dirname),g' \
|
||||
-e 's,\[@\]LODELSITES_CTX_DIRNAME\[@\],$(lodel2_multisite_ctx_dirname),g'
|
||||
|
||||
#There is clearly a way to factorise those rules
|
||||
mass_deploy: mass_deploy.sh
|
||||
|
|
@ -25,6 +38,10 @@ create_instance: create_instance.sh
|
|||
$(do_subst) < $(srcdir)/create_instance.sh > create_instance
|
||||
chmod +x create_instance
|
||||
|
||||
create_multisite: create_multisite.sh
|
||||
$(do_subst) < $(srcdir)/create_multisite.sh > create_multisite
|
||||
chmod +x create_multisite
|
||||
|
||||
install-data-hook:
|
||||
chmod +x ${DESTDIR}$(datadir)/lodel2/scripts/*
|
||||
|
||||
|
||||
|
|
|
|||
84
progs/create_multisite.sh
Normal file
84
progs/create_multisite.sh
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#!/bin/bash
|
||||
|
||||
usage() {
|
||||
cat <<HELP_USAGE
|
||||
Usage: $(basename $0) lodelsites_name [instance_dir] [install_path] [install_tpl]
|
||||
lodelsites_name: name of the lodelsites instance, only lower case alphas, numerics, -, _, are allowed
|
||||
instance_dir: path where the data and context folders will be set (defaults to /var/lodel2)
|
||||
Options:
|
||||
-h, --help give this help list
|
||||
HELP_USAGE
|
||||
exit 1
|
||||
}
|
||||
|
||||
conf_file_subst() {
|
||||
#Make substitution in file given as argument
|
||||
#$1 filename
|
||||
#$2 instance_name
|
||||
sed -i "s/@@@INSTANCE_NAME@@@/$2/g" $1
|
||||
}
|
||||
|
||||
[ $# -lt 1 -o $# -gt 4 ] && usage
|
||||
|
||||
name="$1"
|
||||
install_dir=$2
|
||||
install_dir=${install_dir:=[@]LODEL2_VARDIR[@]}
|
||||
install_tpl="$3"
|
||||
install_tpl=${install_tpl:=[@]MULTISITE_INSTALLMODEL_DIR[@]}
|
||||
context_dir_name="[@]LODELSITES_CTX_DIRNAME[@]"
|
||||
datas_dir_name="[@]LODELSITES_DATA_DIRNAME[@]"
|
||||
libdir="[@]LODEL2_LIBDIR[@]"
|
||||
#Inconsistency with the fact that we can give the install template in argument
|
||||
mono_model=[@]INSTALLMODEL_DIR[@]
|
||||
|
||||
if [[ ! "$name" =~ ^[abcdefghijklmnopqrstuvwxyz0-9_-]+$ ]]
|
||||
then
|
||||
echo -e "Only lowercase alpha, numerics, '-', '_', are allowed in name\n" >&2
|
||||
usage
|
||||
fi
|
||||
if [ -d "$install_dir/$name" ]
|
||||
then
|
||||
echo "A multisite install $name already exists\n" >&2
|
||||
usage
|
||||
fi
|
||||
|
||||
|
||||
instance_dir="$install_dir/$name"
|
||||
lodelsites_ctx_dir="$instance_dir/$context_dir_name"
|
||||
lodelsites_data_dir="$instance_dir/$datas_dir_name"
|
||||
|
||||
echo "Creating multisite instance directory from template"
|
||||
mkdir -pv $instance_dir
|
||||
#Copy both EM for lodelsites and handled sites
|
||||
cp -Rv $install_tpl/*.pickle "$instance_dir"
|
||||
#Creating both conf directory
|
||||
for dname in "lodelsites.conf.d" "server_conf.d"
|
||||
do
|
||||
cp -Rv $install_tpl/$dname $instance_dir
|
||||
#make subst in configuration files
|
||||
for confname in $instance_dir/$dname/*
|
||||
do
|
||||
conf_file_subst $confname $name
|
||||
done
|
||||
done
|
||||
#Using monosite files to populate the instance directory
|
||||
for fname in "Makefile" "lodel_admin.py"
|
||||
do
|
||||
ln -vs $mono_model/$fname $instance_dir/$fname
|
||||
done
|
||||
|
||||
#Dirty ln -s of plugins libdir
|
||||
##@todo find another way to handles plugins in instances
|
||||
ln -vs $libdir/plugins $instance_dir/plugins
|
||||
|
||||
#Dirty again :/
|
||||
ln -vs $libdir/plugins/multisite/loader.py $instance_dir/
|
||||
|
||||
mkdir -pv "$lodelsites_ctx_dir"
|
||||
mkdir -pv "$lodelsites_data_dir"
|
||||
echo "Creating $lodelsites_ctx_dir/__init__.py"
|
||||
> "$lodelsites_ctx_dir/__init__.py"
|
||||
|
||||
|
||||
echo "Multisite $name created in $install_dir"
|
||||
echo -e "Now you can :\ncd $instance_dir && make dyncode"
|
||||
|
|
@ -55,6 +55,9 @@ else
|
|||
random_name="$fixed_name"
|
||||
fi
|
||||
|
||||
random_name=${random_name,,}
|
||||
|
||||
|
||||
#Check for the presence of mongo and its conf
|
||||
if hash mongo 2>/dev/null
|
||||
then
|
||||
|
|
@ -98,7 +101,7 @@ then
|
|||
echo "You didn't answer 'Y' (case matters), exiting"
|
||||
fi
|
||||
exit
|
||||
elif echo $1 | grep -E "[A-Za-z]" &>/dev/null
|
||||
elif ! echo $1 | grep -E "[0-9]" &>/dev/null
|
||||
then
|
||||
usage
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
bin_SCRIPTS = slim
|
||||
CLEANFILES = $(bin_SCRIPTS) slim_instances.json slim_instances_pid.json
|
||||
EXTRA_DIST = slim.py install_model emfile.pickle
|
||||
EXTRA_DIST = slim.py install_model multisite_install_model emfile.pickle
|
||||
|
||||
lodel2_scripts_dir = $(datadir)/lodel2/scripts
|
||||
|
||||
|
|
@ -14,6 +14,16 @@ install_model__DATA = install_model/loader.py \
|
|||
install_model/lodel_admin.py \
|
||||
install_model/Makefile
|
||||
|
||||
multisite_install_model_dir = $(lodel2datadir)/multisite_install_model
|
||||
multisite_install_model__DATA = multisite_install_model/Makefile \
|
||||
install_model/lodel_admin.py \
|
||||
multisite_install_model/sites_em.pickle \
|
||||
multisite_install_model/editorial_model.pickle
|
||||
|
||||
multisite_lodelsites_conf_dir = $(multisite_install_model_dir)/lodelsites.conf.d
|
||||
multisite_lodelsites_conf__DATA = multisite_install_model/lodelsites.conf.d/lodel2.ini
|
||||
|
||||
|
||||
slim_conf_model_dir = $(install_model_dir)/conf.d/
|
||||
slim_conf_model__DATA = install_model/conf.d/lodel2.ini
|
||||
|
||||
|
|
@ -28,6 +38,7 @@ slim_instances_pid.json:
|
|||
do_subst = sed -e 's,\[@\]LODEL2_PROGSDIR\[@\],$(lodel2_scripts_dir),g' \
|
||||
-e 's,\[@\]SLIM_DATADIR\[@\],$(slimdatadir),g' \
|
||||
-e 's,\[@\]INSTALLMODEL_DIR\[@\],$(install_model_dir),g' \
|
||||
-e 's,\[@\]MULTISITE_INSTALLMODEL_DIR\[@\],$(multisite_install_model_dir),g' \
|
||||
-e 's,\[@\]SLIM_VAR_DIR\[@\],$(slim_var_dir),g' \
|
||||
-e 's,\[@\]PKGPYTHONDIR\[@\],$(pkgpythondir),g'
|
||||
|
||||
|
|
@ -37,4 +48,5 @@ slim: slim.py
|
|||
|
||||
install-data-hook:
|
||||
chmod +x ${DESTDIR}$(install_model_dir)/lodel_admin.py
|
||||
chmod +x ${DESTDIR}$(multisite_install_model_dir)/lodel_admin.py
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,14 @@ import argparse
|
|||
#
|
||||
#In order to solve this problem the _simlink_hack() function delete the
|
||||
#LODEL2LIB_FOLDER/install entry from sys.path
|
||||
#@note This problem will be solved once lodel lib dir will be in
|
||||
#/usr/lib/python3/lodel
|
||||
#@todo delete me
|
||||
def _simlink_hack():
|
||||
sys.path[0] = os.getcwd()
|
||||
sys.path[0] = os.getcwd() #In order to override default ./ path
|
||||
|
||||
##@brief Return true if we are in a monosite instance
|
||||
#@todo find a better way to achieve this
|
||||
def _monosite_test():
|
||||
return os.path.isdir('./conf.d')
|
||||
|
||||
## @brief Utility method to generate python code given an emfile and a
|
||||
# translator
|
||||
|
|
@ -28,7 +32,7 @@ def _simlink_hack():
|
|||
def generate_dyncode(model_file, translator):
|
||||
from lodel.editorial_model.model import EditorialModel
|
||||
from lodel.leapi import lefactory
|
||||
|
||||
|
||||
model = EditorialModel.load(translator, filename = model_file)
|
||||
dyncode = lefactory.dyncode_from_em(model)
|
||||
return dyncode
|
||||
|
|
@ -49,8 +53,16 @@ def create_dyncode(model_file, translator, output_filename):
|
|||
|
||||
|
||||
## @brief Refresh dynamic leapi code from settings
|
||||
#@todo make it compatible with multisite : create a "real" progs that takes
|
||||
#conf path in args
|
||||
def refresh_dyncode():
|
||||
import loader
|
||||
#import loader
|
||||
from lodel.context import LodelContext
|
||||
LodelContext.init()
|
||||
from lodel.settings.settings import Settings as settings_loader
|
||||
|
||||
CONFDIR = os.path.join(os.getcwd(), 'conf.d')
|
||||
settings_loader(CONFDIR)
|
||||
from lodel.settings import Settings
|
||||
# EditorialModel update/refresh
|
||||
|
||||
|
|
@ -119,7 +131,7 @@ def update_plugin_discover_cache(path_list = None):
|
|||
os.environ['LODEL2_NO_SETTINGS_LOAD'] = 'True'
|
||||
import loader
|
||||
from lodel.plugin.plugins import Plugin
|
||||
res = Plugin.discover(path_list)
|
||||
res = Plugin.discover()
|
||||
print("Plugin discover result in %s :\n" % res['path_list'])
|
||||
for pname, pinfos in res['plugins'].items():
|
||||
print("\t- %s %s -> %s" % (
|
||||
|
|
@ -127,7 +139,46 @@ def update_plugin_discover_cache(path_list = None):
|
|||
|
||||
if __name__ == '__main__':
|
||||
_simlink_hack()
|
||||
import loader
|
||||
loader.start()
|
||||
from lodel.plugin.scripts import main_run
|
||||
import lodel.buildconf
|
||||
from lodel import bootstrap
|
||||
from lodel.context import LodelContext
|
||||
#to deleted when we known wich action need what kind of MULTISITE API
|
||||
if not bootstrap._monosite_test():
|
||||
bootstrap.bootstrap('lodelsites')
|
||||
LodelContext.set(None)
|
||||
#lodelsites instance preloading
|
||||
#THIS CODE SHOULD BE IN AN UTIL FUNCTION
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.bootstrap': ['get_handled_sites_name', 'site_preload'],
|
||||
'lodel.settings': ['Settings']})
|
||||
lodelsites_name = Settings.sitename
|
||||
lodelsites_datapath = os.path.join(
|
||||
os.path.join(lodel.buildconf.LODEL2VARDIR, lodelsites_name),
|
||||
lodel.buildconf.MULTISITE_DATADIR)
|
||||
del(globals()['Settings'])
|
||||
#preloading lodelsites instance
|
||||
site_preload(lodelsites_datapath, 'lodelsites.conf.d', True)
|
||||
|
||||
LodelContext.expose_modules(globals(), {
|
||||
'lodel.logger': 'logger',
|
||||
'lodel.exceptions': ['LodelException']})
|
||||
#preloading all handled sites
|
||||
try:
|
||||
handled_sites = get_handled_sites_name()
|
||||
except LodelException:
|
||||
handled_sites = []
|
||||
logger.warning("Unable to fetch handled site names without a \
|
||||
leapi_dyncode. Abording handled sites preloading")
|
||||
del(globals()['logger'])
|
||||
del(globals()['LodelException'])
|
||||
for sitename in handled_sites:
|
||||
datapath = os.path.join(lodelsites_datapath, sitename)
|
||||
site_preload(datapath)
|
||||
else:
|
||||
bootstrap.bootstrap()
|
||||
#from lodel.plugin.scripts import main_run
|
||||
LodelContext.expose_modules(globals(),
|
||||
{'lodel.plugin.scripts': ['main_run'],
|
||||
'lodel.plugin.core_scripts': 'core_scripts'})
|
||||
main_run()
|
||||
exit()
|
||||
|
|
|
|||
15
progs/slim/multisite_install_model/Makefile
Normal file
15
progs/slim/multisite_install_model/Makefile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
python=python3
|
||||
|
||||
all: dyncode
|
||||
|
||||
dyncode:
|
||||
$(python) -c 'import lodel_admin; lodel_admin.refresh_dyncode()'
|
||||
|
||||
init_db:
|
||||
$(python) -c 'import lodel_admin; lodel_admin.init_all_dbs()'
|
||||
|
||||
list_hooks: dyncode
|
||||
$(python) -c 'import lodel_admin; lodel_admin.list_registered_hooks()'
|
||||
|
||||
refresh_plugins:
|
||||
$(python) -c 'import lodel_admin; lodel_admin.update_plugin_discover_cache()'
|
||||
BIN
progs/slim/multisite_install_model/editorial_model.pickle
Normal file
BIN
progs/slim/multisite_install_model/editorial_model.pickle
Normal file
Binary file not shown.
|
|
@ -0,0 +1,64 @@
|
|||
[lodel2]
|
||||
debug = False
|
||||
sitename = @@@INSTANCE_NAME@@@
|
||||
datasource_connectors = dummy_datasource lodelsite_datasource
|
||||
session_handler = filesystem_session
|
||||
interface = multisite_web
|
||||
|
||||
[lodel2.auth]
|
||||
login_classfield = lodelsite.shortname
|
||||
pass_classfield = lodelsite.name
|
||||
|
||||
[lodel2.editorialmodel]
|
||||
groups = base_group
|
||||
emfile = editorial_model.pickle
|
||||
#emtranslator = externaly_handled
|
||||
dyncode = leapi_dyncode.py
|
||||
|
||||
[lodel2.lodelsites]
|
||||
sites_emfile = sites_em.pickle
|
||||
lodelsites_emfile = editorial_model.pickle
|
||||
|
||||
[lodel2.datasource.lodelsite_datasource.default]
|
||||
db_datasource = mongodb_datasource.default
|
||||
|
||||
[lodel2.datasources.mongo]
|
||||
identifier = mongodb_datasource.default
|
||||
|
||||
[lodel2.datasources.default]
|
||||
identifier = lodelsite_datasource.default
|
||||
|
||||
[lodel2.datasources.dummy2]
|
||||
identifier = lodelsite_datasource.default
|
||||
|
||||
[lodel2.datasource.dummy_datasource.default]
|
||||
dummy =
|
||||
|
||||
#Example using a real datasource
|
||||
#
|
||||
#[lodel2.lodelsites]
|
||||
#sites_emfile = sites_em.pickle
|
||||
#lodelsites_emfile = editorial_model.pickle
|
||||
#
|
||||
#[lodel2.datasource.lodelsite_datasource.default]
|
||||
#db_datasource = mongo
|
||||
#
|
||||
#[lodel2.datasources.default]
|
||||
#identifier = mongodb_datasource.default
|
||||
#
|
||||
#[lodel2.datasources.dummy2]
|
||||
#identifier = mongodb_datasource.default
|
||||
#
|
||||
#[lodel2.datasource.dummy_datasource.default]
|
||||
#dummy =
|
||||
#
|
||||
#[lodel2.datasource.mongodb_datasource.default]
|
||||
#host = HOSTNAME
|
||||
#username = USERNAME
|
||||
#password = PASSWORD
|
||||
#db_name = DBNAME
|
||||
|
||||
[lodel2.logging.default]
|
||||
context = True
|
||||
filename = -
|
||||
level = DEBUG
|
||||
BIN
progs/slim/multisite_install_model/sites_em.pickle
Normal file
BIN
progs/slim/multisite_install_model/sites_em.pickle
Normal file
Binary file not shown.
|
|
@ -16,28 +16,34 @@ from lodel import buildconf
|
|||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
INSTANCES_ABSPATH="/tmp/lodel2_instances"
|
||||
INSTANCES_ABSPATH="/var/lodel2"
|
||||
LODEL2_INSTALLDIR="/usr/lib/python3/dist-packages"
|
||||
CONFFILE='conf.d/lodel2.ini'
|
||||
try:
|
||||
STORE_FILE = os.path.join("[@]SLIM_VAR_DIR[@]", 'slim_instances.json')
|
||||
PID_FILE = os.path.join("[@]SLIM_VAR_DIR[@]", 'slim_instances_pid.json')
|
||||
CREATION_SCRIPT = os.path.join("[@]LODEL2_PROGSDIR[@]", 'create_instance')
|
||||
MONOSITE_CREATION_SCRIPT = os.path.join("[@]LODEL2_PROGSDIR[@]", 'create_instance')
|
||||
MULTISITE_CREATION_SCRIPT = os.path.join("[@]LODEL2_PROGSDIR[@]", 'create_multisite')
|
||||
INSTALL_TPL = "[@]INSTALLMODEL_DIR[@]"
|
||||
MULTISITE_INSTALL_TPL = "[@]MULTISITE_INSTALLMODEL_DIR[@]"
|
||||
EMFILE = os.path.join("[@]SLIM_DATADIR[@]", 'emfile.pickle')
|
||||
|
||||
except SyntaxError:
|
||||
STORE_FILE='./instances.json'
|
||||
PID_FILE = './slim_instances_pid.json'
|
||||
CREATION_SCRIPT='../scripts/create_instance.sh'
|
||||
MONOSITE_CREATION_SCRIPT='../scripts/create_instance.sh'
|
||||
MULTISITE_CREATION_SCRIPT='../scripts/create_multisite.sh'
|
||||
INSTALL_TPL = './slim_ressources/slim_install_model'
|
||||
MULTISITE_INSTALL_TPL = './slim_ressources/slim_multisite_install_model'
|
||||
EMFILE = './slim_ressources/emfile.pickle'
|
||||
|
||||
|
||||
|
||||
CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), CREATION_SCRIPT)
|
||||
MONOSITE_CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), MONOSITE_CREATION_SCRIPT)
|
||||
MULTISITE_CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), MULTISITE_CREATION_SCRIPT)
|
||||
STORE_FILE=os.path.join(os.path.dirname(__file__), STORE_FILE)
|
||||
INSTALL_TPL=os.path.join(os.path.dirname(__file__), INSTALL_TPL)
|
||||
MULTISITE_INSTALL_TPL=os.path.join(os.path.dirname(__file__), MULTISITE_INSTALL_TPL)
|
||||
EMFILE=os.path.join(os.path.dirname(__file__), EMFILE)
|
||||
|
||||
|
||||
|
|
@ -202,13 +208,22 @@ def new_instance(name):
|
|||
if not os.path.isdir(INSTANCES_ABSPATH):
|
||||
logging.info("Instances directory '%s' don't exists, creating it")
|
||||
os.mkdir(INSTANCES_ABSPATH)
|
||||
instance_path = os.path.join(INSTANCES_ABSPATH, name)
|
||||
|
||||
if args.multisite:
|
||||
creation_script = MULTISITE_CREATION_SCRIPT
|
||||
install_tpl = MULTISITE_INSTALL_TPL
|
||||
instance_path = INSTANCES_ABSPATH
|
||||
else:
|
||||
creation_script = MONOSITE_CREATION_SCRIPT
|
||||
install_tpl = INSTALL_TPL
|
||||
instance_path = os.path.join(INSTANCES_ABSPATH, name)
|
||||
|
||||
creation_cmd = '{script} "{name}" "{path}" "{install_tpl}" \
|
||||
"{emfile}"'.format(
|
||||
script = CREATION_SCRIPT,
|
||||
script = creation_script,
|
||||
name = name,
|
||||
path = instance_path,
|
||||
install_tpl = INSTALL_TPL,
|
||||
install_tpl = install_tpl,
|
||||
emfile = EMFILE)
|
||||
res = os.system(creation_cmd)
|
||||
if res != 0:
|
||||
|
|
@ -444,6 +459,7 @@ server {
|
|||
"""
|
||||
print(ret)
|
||||
|
||||
|
||||
##@brief Returns instanciated parser
|
||||
def get_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
|
|
@ -491,6 +507,12 @@ def get_parser():
|
|||
actions.add_argument('--nginx-conf', action='store_const',
|
||||
default = False, const=True,
|
||||
help="Output a conf for nginx given selected instances")
|
||||
actions.add_argument(
|
||||
'--multisite',
|
||||
default=False,
|
||||
const=True,
|
||||
action='store_const',
|
||||
help="Creates the instance as a multisite one")
|
||||
|
||||
startstop.add_argument('--stop', action='store_const',
|
||||
default=False, const=True, help="Stop instances")
|
||||
|
|
@ -555,6 +577,7 @@ if __name__ == '__main__':
|
|||
print("\nAn instance name expected when creating an instance !",
|
||||
file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
for name in args.name:
|
||||
new_instance(name)
|
||||
elif args.purge:
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ else
|
|||
mkdir $logdir
|
||||
fi
|
||||
|
||||
PYTHON='env python3'
|
||||
export PYTHONPATH="$PYTHONPATH:$(pwd)"
|
||||
PYTHON="env python3"
|
||||
ret_status=0
|
||||
|
||||
$PYTHON ./nocontext_tests.py $logdir $@ || ret_status=1
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@
|
|||
#
|
||||
# https://docs.python.org/3.4/library/unittest.html
|
||||
#
|
||||
export PYTHONPATH="$(pwd)/$(dirname $0)/:$PYTHONPATH"
|
||||
|
||||
if test ! -f lodel/buildconf.py
|
||||
then
|
||||
echo "You have to build the project before running the tests"
|
||||
|
|
@ -52,7 +54,7 @@ then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON='env python3'
|
||||
PYTHON="env python3"
|
||||
testdir=$(mktemp -td "lodel2_unittest_instance_XXXXXXXX")
|
||||
install_model_dir="[@]INSTALLMODEL_DIR[@]"
|
||||
if [ ! -d "$install_model_dir" ]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue