Ei kuvausta
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.5KB

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