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 7.6KB

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