123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- #-*- coding: utf-8 -*-
-
- import functools
- from lodel.editorial_model.components import *
- from lodel.leapi.leobject import LeObject
- from lodel.leapi.datahandlers.base_classes import DataHandler
-
- ##@brief Generate python module code from a given model
- # @param model lodel.editorial_model.model.EditorialModel
- def dyncode_from_em(model):
-
- # Generation of LeObject child classes code
- cls_code, modules, bootstrap_instr = generate_classes(model)
-
- # Header
- imports = """from lodel.leapi.leobject import LeObject
- from lodel.leapi.datahandlers.base_classes import DataField
- from lodel.plugin.hooks import LodelHook
- """
- for module in modules:
- imports += "import %s\n" % module
-
- class_list = [ LeObject.name2objname(cls.uid) for cls in get_classes(model) ]
-
- # formating all components of output
- res_code = """#-*- coding: utf-8 -*-
- {imports}
- {classes}
- {bootstrap_instr}
- dynclasses = {class_list}
- {init_hook}
- """.format(
- imports = imports,
- classes = cls_code,
- bootstrap_instr = bootstrap_instr,
- class_list = '[' + (', '.join([cls for cls in class_list]))+']',
- init_hook = datasource_init_hook(),
- )
- return res_code
-
- ##@brief Produce the source code of the LodelHook that initialize datasources
- #in dyncode
- #@return str
- def datasource_init_hook():
- return """
- @LodelHook("lodel2_plugins_loaded")
- def lodel2_dyncode_datasources_init(self, caller, payload):
- for cls in dynclasses:
- cls._init_datasources()
- """
-
- ##@brief return A list of EmClass sorted by dependencies
- #
- # The first elts in the list depends on nothing, etc.
- # @return a list of EmClass instances
- def emclass_sorted_by_deps(emclass_list):
- def emclass_deps_cmp(cls_a, cls_b):
- return len(cls_a.parents_recc) - len(cls_b.parents_recc)
- ret = sorted(emclass_list, key = functools.cmp_to_key(emclass_deps_cmp))
- return ret
-
- ##@brief Returns a list of EmClass that will be represented as LeObject child classes
- def get_classes(model):
- return [ cls for cls in emclass_sorted_by_deps(model.classes()) if not cls.pure_abstract ]
-
- ##@brief Given an EmField returns the data_handler constructor suitable for dynamic code
- 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)
- options = []
- for name, val in emfield.data_handler_options.items():
- if name == 'back_reference' and isinstance(val, tuple):
- options.append('{optname}: ({leo_name}, {fieldname})'.format(
- optname = repr(name),
- leo_name = LeObject.name2objname(val[0]),
- fieldname = repr(val[1]),))
- else:
- options.append(repr(name)+': '+forge_optval(val))
-
- return '{handler_instr}(**{{ {options} }})'.format(
- handler_instr = get_handler_class_instr,
- options = ', '.join(options))
-
- ##@brief Return a python repr of option values
- 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])) + ']'
-
- if isinstance(optval, EmField):
- return "{leobject}.data_handler({fieldname})".format(
- leobject = LeObject.name2objname(optval._emclass.uid),
- fieldname = repr(optval.uid)
- )
- elif isinstance(optval, EmClass):
- return LeObject.name2objname(optval.uid)
- else:
- return repr(optval)
-
- ##@brief Generate dyncode from an EmClass
- # @param model EditorialModel :
- # @param emclass EmClass : EmClass instance
- # @return a tuple with emclass python code, a set containing modules name to import, and a list of python instruction to bootstrap dynamic code, in this order
- def generate_classes(model):
- res = ""
- imports = list()
- bootstrap = ""
- # Generating field list for LeObjects generated from EmClass
- for em_class in get_classes(model):
- uid = list() # List of fieldnames that are part of the EmClass primary key
- parents = list() # List of parents EmClass
- # Determine pk
- for field in em_class.fields():
- if field.data_handler_instance.is_primary_key():
- uid.append(field.uid)
- # Determine parent for inheritance
- if len(em_class.parents) > 0:
- for parent in em_class.parents:
- parents.append(LeObject.name2objname(parent.uid))
- else:
- parents.append('LeObject')
- datasource_name = em_class.datasource
-
- # Dynamic code generation for LeObject childs classes
- em_cls_code = """
- class {clsname}({parents}):
- _abstract = {abstract}
- _fields = None
- _uid = {uid_list}
- _ro_datasource = None
- _rw_datasource = None
- _datasource_name = {datasource_name}
-
- """.format(
- clsname = LeObject.name2objname(em_class.uid),
- parents = ', '.join(parents),
- abstract = 'True' if em_class.abstract else 'False',
- uid_list = repr(uid),
- datasource_name = repr(datasource_name),
- )
- res += em_cls_code
- # Dyncode fields bootstrap instructions
- bootstrap += """{classname}._set__fields({fields})
- """.format(
- classname = LeObject.name2objname(em_class.uid),
- fields = '{' + (', '.join(['\n\t%s: %s' % (repr(emfield.uid),data_handler_constructor(emfield)) for emfield in em_class.fields()])) + '}',
- )
- bootstrap += "\n"
- return res, set(imports), bootstrap
-
|