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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #-*- coding: utf-8 -*-
  2. import importlib
  3. import copy
  4. import os.path
  5. import EditorialModel
  6. from EditorialModel.model import Model
  7. from EditorialModel.fieldtypes.generic import GenericFieldType
  8. ## @brief This class is designed to generated the leobject API given an EditorialModel.model
  9. # @note Contains only static methods
  10. #
  11. # The name is not good but i've no other ideas for the moment
  12. class LeFactory(object):
  13. output_file = 'dyn.py'
  14. modname = None
  15. def __init__(self, code_filename = 'leapi/dyn.py'):
  16. self._code_filename = code_filename
  17. self._dyn_file = os.path.basename(code_filename)
  18. self._modname = os.path.dirname(code_filename).strip('/').replace('/', '.') #Warning Windaube compatibility
  19. ## @brief Convert an EmType or EmClass name in a python class name
  20. # @param name str : The name
  21. # @return name.title()
  22. @staticmethod
  23. def name2classname(name):
  24. if not isinstance(name, str):
  25. raise AttributeError("Argument name should be a str and not a %s" % type(name))
  26. return name.title()
  27. ## @brief Return a call to a FieldType constructor given an EmField
  28. # @param emfield EmField : An EmField
  29. # @return a string representing the python code to instanciate a EmFieldType
  30. @staticmethod
  31. def fieldtype_construct_from_field(emfield):
  32. return '%s.EmFieldType(**%s)' % (
  33. GenericFieldType.module_name(emfield.fieldtype),
  34. repr(emfield._fieldtype_args),
  35. )
  36. ## @brief Write generated code to a file
  37. # @todo better options/params for file creation
  38. def create_pyfile(self, model, datasource_cls, datasource_args):
  39. with open(self._code_filename, "w+") as dynfp:
  40. dynfp.write(self.generate_python(model, datasource_cls, datasource_args))
  41. ## @brief Given a Model and an EmClass instances generate python code for corresponding LeClass
  42. # @param model Model : A Model instance
  43. # @param emclass EmClass : An EmClass instance from model
  44. # @return A string representing the python code for the corresponding LeClass child class
  45. def emclass_pycode(self, model, emclass):
  46. cls_fields = dict()
  47. cls_linked_types = dict() #keys are LeType classnames and values are tuples (attr_fieldname, attr_fieldtype)
  48. #Populating linked_type attr
  49. for rfield in [ f for f in emclass.fields() if f.fieldtype == 'rel2type']:
  50. fti = rfield.fieldtype_instance()
  51. cls_linked_types[LeFactory.name2classname(model.component(fti.rel_to_type_id).name)] = [
  52. (f.name, LeFactory.fieldtype_construct_from_field(f)) for f in model.components('EmField') if f.rel_field_id == rfield.uid
  53. ]
  54. # Populating fieldtype attr
  55. for field in emclass.fields(relational = False):
  56. self.needed_fieldtypes |= set([field.fieldtype])
  57. cls_fields[field.name] = LeFactory.fieldtype_construct_from_field(field)
  58. fti = field.fieldtype_instance()
  59. return """
  60. #Initialisation of {name} class attributes
  61. {name}._fieldtypes = {ftypes}
  62. {name}._linked_types = {ltypes}
  63. {name}._classtype = {classtype}
  64. """.format(
  65. name = LeFactory.name2classname(emclass.name),
  66. ftypes = "{" + (','.join(['\n %s: %s' % (repr(f), v) for f, v in cls_fields.items()])) + "\n}",
  67. ltypes = '{'+ (','.join(
  68. [
  69. '\n {ltname}: {ltattr_list}'.format(
  70. ltname = lt,
  71. ltattr_list = '['+(', '.join([
  72. '(%s, %s)'%(repr(ltname), ltftype) for ltname, ltftype in ltattr
  73. ]))+']'
  74. ) for lt, ltattr in cls_linked_types.items()
  75. ]))+'}',
  76. classtype = repr(emclass.classtype)
  77. )
  78. ## @brief Given a Model and an EmType instances generate python code for corresponding LeType
  79. # @param model Model : A Model instance
  80. # @param emtype EmType : An EmType instance from model
  81. # @return A string representing the python code for the corresponding LeType child class
  82. def emtype_pycode(self, model, emtype):
  83. type_fields = list()
  84. type_superiors = list()
  85. for field in emtype.fields(relational=False):
  86. type_fields.append(field.name)
  87. for nat, sup_l in emtype.superiors().items():
  88. type_superiors.append('%s: [%s]' % (
  89. repr(nat),
  90. ', '.join([LeFactory.name2classname(sup.name) for sup in sup_l])
  91. ))
  92. return """
  93. #Initialisation of {name} class attributes
  94. {name}._fields = {fields}
  95. {name}._superiors = {dsups}
  96. {name}._leclass = {leclass}
  97. """.format(
  98. name=LeFactory.name2classname(emtype.name),
  99. fields=repr(type_fields),
  100. dsups='{' + (', '.join(type_superiors)) + '}',
  101. leclass=LeFactory.name2classname(emtype.em_class.name)
  102. )
  103. ## @brief Generate python code containing the LeObject API
  104. # @param model EditorialModel.model.Model : An editorial model instance
  105. # @param datasource_cls Datasource : A datasource class
  106. # @param datasource_args dict : A dict representing arguments for datasource_cls instanciation
  107. # @return A string representing python code
  108. def generate_python(self, model, datasource_cls, datasource_args):
  109. self.needed_fieldtypes = set() #Stores the list of fieldtypes that will be used by generated code
  110. model = model
  111. result = ""
  112. #result += "#-*- coding: utf-8 -*-\n"
  113. #Putting import directives in result
  114. heading = """## @author LeFactory
  115. import EditorialModel
  116. from EditorialModel import fieldtypes
  117. from EditorialModel.fieldtypes import {needed_fieldtypes_list}
  118. import leapi
  119. import leapi.lecrud
  120. import leapi.leobject
  121. from leapi.leclass import LeClass
  122. from leapi.letype import LeType
  123. """
  124. result += """
  125. import %s
  126. """ % (datasource_cls.__module__)
  127. #Generating the code for LeObject class
  128. leobj_me_uid = dict()
  129. for comp in model.components('EmType') + model.components('EmClass'):
  130. leobj_me_uid[comp.uid] = LeFactory.name2classname(comp.name)
  131. #Building the fieldtypes dict of LeObject
  132. leobj_fieldtypes = list()
  133. leobj_uid_fieldtype = None
  134. for fname, ftargs in EditorialModel.classtypes.common_fields.items():
  135. ftargs = copy.copy(ftargs)
  136. fieldtype = ftargs['fieldtype']
  137. self.needed_fieldtypes |= set([fieldtype])
  138. del(ftargs['fieldtype'])
  139. constructor = '{ftname}.EmFieldType(**{ftargs})'.format(
  140. ftname = GenericFieldType.module_name(fieldtype),
  141. ftargs = ftargs,
  142. )
  143. if fieldtype == 'pk':
  144. #
  145. # WARNING multiple PK not supported
  146. #
  147. leobj_uid_fieldtype = "{ 'lodel_id': %s }"%constructor
  148. else:
  149. leobj_fieldtypes.append( '%s: %s'%(repr(fname), constructor) )
  150. result += """
  151. ## @brief _LeCrud concret class
  152. # @see leapi.lecrud._LeCrud
  153. class LeCrud(leapi.lecrud._LeCrud):
  154. _datasource = {ds_classname}(**{ds_kwargs})
  155. _uid_fieldtype = None
  156. ## @brief _LeObject concret class
  157. # @see leapi.leobject._LeObject
  158. class LeObject(leapi.leobject._LeObject, LeCrud):
  159. _me_uid = {me_uid_l}
  160. _uid_fieldtype = {leo_uid_fieldtype}
  161. _leo_fieldtypes = {leo_fieldtypes}
  162. """.format(
  163. ds_classname = datasource_cls.__module__ + '.' + datasource_cls.__name__,
  164. ds_kwargs = repr(datasource_args),
  165. me_uid_l = repr(leobj_me_uid),
  166. leo_uid_fieldtype = leobj_uid_fieldtype,
  167. leo_fieldtypes = '{\n\t' + (',\n\t'.join(leobj_fieldtypes))+ '\n\t}',
  168. )
  169. emclass_l = model.components(EditorialModel.classes.EmClass)
  170. emtype_l = model.components(EditorialModel.types.EmType)
  171. #LeClass child classes definition
  172. for emclass in emclass_l:
  173. result += """
  174. ## @brief EmClass {name} LeClass child class
  175. # @see leapi.leclass.LeClass
  176. class {name}(LeClass, LeObject):
  177. _class_id = {uid}
  178. """.format(
  179. name=LeFactory.name2classname(emclass.name),
  180. uid=emclass.uid
  181. )
  182. #LeType child classes definition
  183. for emtype in emtype_l:
  184. result += """
  185. ## @brief EmType {name} LeType child class
  186. # @see leobject::letype::LeType
  187. class {name}(LeType, {leclass}):
  188. _type_id = {uid}
  189. """.format(
  190. name=LeFactory.name2classname(emtype.name),
  191. leclass=LeFactory.name2classname(emtype.em_class.name),
  192. uid=emtype.uid
  193. )
  194. #Set attributes of created LeClass and LeType child classes
  195. for emclass in emclass_l:
  196. result += self.emclass_pycode(model, emclass)
  197. for emtype in emtype_l:
  198. result += self.emtype_pycode(model, emtype)
  199. #Populating LeObject._me_uid dict for a rapid fetch of LeType and LeClass given an EM uid
  200. me_uid = {comp.uid: LeFactory.name2classname(comp.name) for comp in emclass_l + emtype_l}
  201. result += """
  202. ## @brief Dict for getting LeClass and LeType child classes given an EM uid
  203. LeObject._me_uid = %s""" % "{" + (', '.join(['%s: %s' % (k, v) for k, v in me_uid.items()])) + "}"
  204. result += "\n"
  205. heading = heading.format(needed_fieldtypes_list = ', '.join(self.needed_fieldtypes))
  206. result = heading + result
  207. del(self.needed_fieldtypes)
  208. return result