No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lefactory.py 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #-*- coding: utf-8 -*-
  2. import os
  3. import os.path
  4. import functools
  5. from lodel.context import LodelContext
  6. LodelContext.expose_modules(globals(), {
  7. 'lodel.editorial_model.components': ['EmComponent', 'EmClass', 'EmField',
  8. 'EmGroup'],
  9. 'lodel.leapi.leobject': ['LeObject'],
  10. 'lodel.leapi.datahandlers.base_classes': ['DataHandler'],
  11. 'lodel.logger': 'logger'})
  12. # @brief Generates python module code from a given model
  13. # @param model lodel.editorial_model.model.EditorialModel
  14. def dyncode_from_em(model):
  15. # Generation of LeObject child classes code
  16. cls_code, bootstrap_instr = generate_classes(model)
  17. # Header
  18. imports = """from lodel.context import LodelContext
  19. LodelContext.expose_modules(globals(), {
  20. 'lodel.leapi.leobject': ['LeObject'],
  21. 'lodel.leapi.datahandlers.base_classes': ['DataField'],
  22. 'lodel.plugin.hooks': ['LodelHook']})
  23. """
  24. # generates the list of all classes in the editorial model
  25. class_list = [LeObject.name2objname(cls.uid) for cls in get_classes(model)]
  26. # formating all components of output
  27. res_code = """#-*- coding: utf-8 -*-
  28. {imports}
  29. {classes}
  30. {bootstrap_instr}
  31. #List of dynamically generated classes
  32. dynclasses = {class_list}
  33. #Dict of dynamically generated classes indexed by name
  34. dynclasses_dict = {class_dict}
  35. {common_code}
  36. """.format(
  37. imports=imports,
  38. classes=cls_code,
  39. bootstrap_instr=bootstrap_instr,
  40. class_list='[' + (', '.join([cls for cls in class_list])) + ']',
  41. class_dict='{' + (', '.join(["'%s': %s" % (cls, cls)
  42. for cls in class_list])) + '}',
  43. common_code=common_code(),
  44. )
  45. return res_code
  46. # @brief Returns the content of lodel.leapi.lefactory_common
  47. #
  48. # @return a string
  49. def common_code():
  50. res = ""
  51. fname = os.path.dirname(__file__)
  52. fname = os.path.join(fname, 'lefactory_common.py')
  53. with open(fname, 'r') as cfp:
  54. for line in cfp:
  55. if not line.startswith('#-'):
  56. res += line
  57. return res
  58. # @brief return A list of EmClass sorted by dependencies
  59. #
  60. # The first elts in the list depend on nothing, etc.
  61. # @param a list of Emclass instances to be sorted
  62. # @return a list of EmClass instances
  63. def emclass_sorted_by_deps(emclass_list):
  64. def emclass_deps_cmp(cls_a, cls_b):
  65. return len(cls_a.parents_recc) - len(cls_b.parents_recc)
  66. ret = sorted(emclass_list, key=functools.cmp_to_key(emclass_deps_cmp))
  67. return ret
  68. # @brief Returns a list of EmClass instances that will be represented as LeObject child classes
  69. # @param model : an EditorialModel instance
  70. # @return a list of EmClass instances
  71. def get_classes(model):
  72. return [cls for cls in emclass_sorted_by_deps(model.classes()) if not cls.pure_abstract]
  73. # @brief Given an EmField returns the data_handler constructor suitable for dynamic code
  74. # @param a EmField instance
  75. # @return a string
  76. def data_handler_constructor(emfield):
  77. #dh_module_name = DataHandler.module_name(emfield.data_handler_name)+'.DataHandler'
  78. get_handler_class_instr = 'DataField.from_name(%s)' % repr(emfield.data_handler_name)
  79. options = []
  80. for name, val in emfield.data_handler_options.items():
  81. if name == 'back_reference' and isinstance(val, tuple):
  82. options.append('{optname}: ({leo_name}, {fieldname})'.format(
  83. optname=repr(name),
  84. leo_name=LeObject.name2objname(val[0]),
  85. fieldname=repr(val[1]),))
  86. else:
  87. options.append(repr(name) + ': ' + forge_optval(val))
  88. return '{handler_instr}(**{{ {options} }})'.format(
  89. handler_instr=get_handler_class_instr,
  90. options=', '.join(options))
  91. # @brief Return a python repr of option values
  92. # @param A value of any type which represents option
  93. # @return a string
  94. def forge_optval(optval):
  95. if isinstance(optval, dict):
  96. return '{' + (', '.join(['%s: %s' % (repr(name), forge_optval(val)) for name, val in optval.items()])) + '}'
  97. if isinstance(optval, (set, list, tuple)):
  98. return '[' + (', '.join([forge_optval(val) for val in optval])) + ']'
  99. if isinstance(optval, EmField):
  100. return "{leobject}.data_handler({fieldname})".format(
  101. leobject=LeObject.name2objname(optval._emclass.uid),
  102. fieldname=repr(optval.uid)
  103. )
  104. if isinstance(optval, EmClass):
  105. return LeObject.name2objname(optval.uid)
  106. return repr(optval)
  107. # @brief Generate dyncode from an EmClass
  108. # @param model EditorialModel :
  109. # @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
  110. def generate_classes(model):
  111. res = ""
  112. bootstrap = ""
  113. # Generating field list for LeObjects generated from EmClass
  114. for em_class in get_classes(model):
  115. logger.info("Generating a dynamic class for %s" % em_class.uid)
  116. uid = list() # List for fieldnames that are part of the EmClass primary key
  117. parents = list() # List for em_class's parents
  118. # Determines primary key
  119. for field in em_class.fields():
  120. if field.data_handler_instance.is_primary_key():
  121. uid.append(field.uid)
  122. # Determines parentsfor inheritance
  123. if len(em_class.parents) > 0:
  124. for parent in em_class.parents:
  125. parents.append(LeObject.name2objname(parent.uid))
  126. else:
  127. parents.append('LeObject')
  128. datasource_name = em_class.datasource
  129. # Dynamic code generation for LeObject child classes
  130. em_cls_code = """
  131. class {clsname}({parents}):
  132. _abstract = {abstract}
  133. _fields = None
  134. _uid = {uid_list}
  135. _ro_datasource = None
  136. _rw_datasource = None
  137. _datasource_name = {datasource_name}
  138. _child_classes = None
  139. """.format(
  140. clsname=LeObject.name2objname(em_class.uid),
  141. parents=', '.join(parents),
  142. abstract='True' if em_class.abstract else 'False',
  143. uid_list=repr(uid),
  144. datasource_name=repr(datasource_name),
  145. )
  146. res += em_cls_code
  147. # Dyncode fields bootstrap instructions
  148. child_classes = model.get_class_childs(em_class.uid)
  149. if len(child_classes) == 0:
  150. child_classes = 'tuple()'
  151. else:
  152. child_classes = '(%s,)' % (', '.join(
  153. [LeObject.name2objname(emcls.uid) for emcls in child_classes]))
  154. bootstrap += """{classname}._set__fields({fields})
  155. {classname}._child_classes = {child_classes}
  156. """.format(
  157. classname=LeObject.name2objname(em_class.uid),
  158. fields='{' + (', '.join(['\n\t%s: %s' % (repr(emfield.uid),
  159. data_handler_constructor(emfield)) for emfield in em_class.fields()])) + '}',
  160. child_classes=child_classes,
  161. )
  162. bootstrap += "\n"
  163. return res, bootstrap