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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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 Generate fieldtypes for concret classes
  42. # @param ft_dict dict : key = fieldname value = fieldtype __init__ args
  43. # @return (uid_fieldtypes, fieldtypes) designed to be printed in generated code
  44. def concret_fieldtypes(self, ft_dict):
  45. res_ft_l = list()
  46. res_uid_ft = None
  47. for fname, ftargs in ft_dict.items():
  48. ftargs = copy.copy(ftargs)
  49. fieldtype = ftargs['fieldtype']
  50. self.needed_fieldtypes |= set([fieldtype])
  51. del(ftargs['fieldtype'])
  52. constructor = '{ftname}.EmFieldType(**{ftargs})'.format(
  53. ftname = GenericFieldType.module_name(fieldtype),
  54. ftargs = ftargs,
  55. )
  56. if fieldtype == 'pk':
  57. #
  58. # WARNING multiple PK not supported
  59. #
  60. res_uid_ft = "{ %s: %s }"%(repr(fname),constructor)
  61. else:
  62. res_ft_l.append( '%s: %s'%(repr(fname), constructor) )
  63. return (res_uid_ft, res_ft_l)
  64. ## @brief Given a Model generate concrete instances of LeRel2Type classes to represent relations
  65. # @param model : the EditorialModel
  66. # @return python code
  67. def emrel2type_pycode(self, model):
  68. res_code = ""
  69. for field in [ f for f in model.components('EmField') if f.fieldtype == 'rel2type']:
  70. related = model.component(field.rel_to_type_id)
  71. src = field.em_class
  72. cls_name = "Rel_%s2%s"%(self.name2classname(src.name), self.name2classname(related.name))
  73. attr_l = dict()
  74. for attr in [ f for f in model.components('EmField') if f.rel_field_id == field.uid]:
  75. attr_l[attr.name] = LeFactory.fieldtype_construct_from_field(attr)
  76. rel_code = """
  77. class {classname}(LeRel2Type):
  78. _rel_attr_fieldtypes = {attr_dict}
  79. """.format(
  80. classname = cls_name,
  81. attr_dict = "{" + (','.join(['\n %s: %s' % (repr(f), v) for f,v in attr_l.items()])) + "\n}"
  82. )
  83. res_code += rel_code
  84. return res_code
  85. ## @brief Given a Model and an EmClass instances generate python code for corresponding LeClass
  86. # @param model Model : A Model instance
  87. # @param emclass EmClass : An EmClass instance from model
  88. # @return A string representing the python code for the corresponding LeClass child class
  89. def emclass_pycode(self, model, emclass):
  90. cls_fields = dict()
  91. cls_linked_types = list() #Stores authorized LeObject for rel2type
  92. #Populating linked_type attr
  93. for rfield in [ f for f in emclass.fields() if f.fieldtype == 'rel2type']:
  94. fti = rfield.fieldtype_instance()
  95. cls_linked_types.append(LeFactory.name2classname(model.component(fti.rel_to_type_id).name))
  96. # Populating fieldtype attr
  97. for field in emclass.fields(relational = False):
  98. self.needed_fieldtypes |= set([field.fieldtype])
  99. cls_fields[field.name] = LeFactory.fieldtype_construct_from_field(field)
  100. fti = field.fieldtype_instance()
  101. return """
  102. #Initialisation of {name} class attributes
  103. {name}._fieldtypes = {ftypes}
  104. {name}._linked_types = {ltypes}
  105. {name}._classtype = {classtype}
  106. """.format(
  107. name = LeFactory.name2classname(emclass.name),
  108. ftypes = "{" + (','.join(['\n %s: %s' % (repr(f), v) for f, v in cls_fields.items()])) + "\n}",
  109. ltypes = "[" + (','.join(cls_linked_types))+"]",
  110. classtype = repr(emclass.classtype)
  111. )
  112. ## @brief Given a Model and an EmType instances generate python code for corresponding LeType
  113. # @param model Model : A Model instance
  114. # @param emtype EmType : An EmType instance from model
  115. # @return A string representing the python code for the corresponding LeType child class
  116. def emtype_pycode(self, model, emtype):
  117. type_fields = list()
  118. type_superiors = list()
  119. for field in emtype.fields(relational=False):
  120. type_fields.append(field.name)
  121. for nat, sup_l in emtype.superiors().items():
  122. type_superiors.append('%s: [%s]' % (
  123. repr(nat),
  124. ', '.join([LeFactory.name2classname(sup.name) for sup in sup_l])
  125. ))
  126. return """
  127. #Initialisation of {name} class attributes
  128. {name}._fields = {fields}
  129. {name}._superiors = {dsups}
  130. {name}._leclass = {leclass}
  131. """.format(
  132. name=LeFactory.name2classname(emtype.name),
  133. fields=repr(type_fields),
  134. dsups='{' + (', '.join(type_superiors)) + '}',
  135. leclass=LeFactory.name2classname(emtype.em_class.name)
  136. )
  137. ## @brief Generate python code containing the LeObject API
  138. # @param model EditorialModel.model.Model : An editorial model instance
  139. # @param datasource_cls Datasource : A datasource class
  140. # @param datasource_args dict : A dict representing arguments for datasource_cls instanciation
  141. # @return A string representing python code
  142. def generate_python(self, model, datasource_cls, datasource_args):
  143. self.needed_fieldtypes = set() #Stores the list of fieldtypes that will be used by generated code
  144. model = model
  145. result = ""
  146. #result += "#-*- coding: utf-8 -*-\n"
  147. #Putting import directives in result
  148. heading = """## @author LeFactory
  149. import EditorialModel
  150. from EditorialModel import fieldtypes
  151. from EditorialModel.fieldtypes import {needed_fieldtypes_list}
  152. import leapi
  153. import leapi.lecrud
  154. import leapi.leobject
  155. import leapi.lerelation
  156. from leapi.leclass import _LeClass
  157. from leapi.letype import _LeType
  158. """
  159. result += """
  160. import %s
  161. """ % (datasource_cls.__module__)
  162. #Generating the code for LeObject class
  163. leobj_me_uid = dict()
  164. for comp in model.components('EmType') + model.components('EmClass'):
  165. leobj_me_uid[comp.uid] = LeFactory.name2classname(comp.name)
  166. #Building the fieldtypes dict of LeObject
  167. (leobj_uid_fieldtype, leobj_fieldtypes) = self.concret_fieldtypes(EditorialModel.classtypes.common_fields)
  168. """
  169. leobj_fieldtypes = list()
  170. leobj_uid_fieldtype = None
  171. for fname, ftargs in EditorialModel.classtypes.common_fields.items():
  172. ftargs = copy.copy(ftargs)
  173. fieldtype = ftargs['fieldtype']
  174. self.needed_fieldtypes |= set([fieldtype])
  175. del(ftargs['fieldtype'])
  176. constructor = '{ftname}.EmFieldType(**{ftargs})'.format(
  177. ftname = GenericFieldType.module_name(fieldtype),
  178. ftargs = ftargs,
  179. )
  180. if fieldtype == 'pk':
  181. #
  182. # WARNING multiple PK not supported
  183. #
  184. leobj_uid_fieldtype = "{ %s: %s }"%(repr(fname),constructor)
  185. else:
  186. leobj_fieldtypes.append( '%s: %s'%(repr(fname), constructor) )
  187. """
  188. #Building the fieldtypes dict for LeRelation
  189. (lerel_uid_fieldtype, lerel_fieldtypes) = self.concret_fieldtypes(EditorialModel.classtypes.relations_common_fields)
  190. # Fetching superior and subordinate fieldname for LeRelation
  191. lesup = None
  192. lesub = None
  193. for fname, finfo in EditorialModel.classtypes.relations_common_fields.items():
  194. if finfo['fieldtype'] == 'leo':
  195. if finfo['superior']:
  196. lesup = fname
  197. else:
  198. lesub = fname
  199. """
  200. lerel_fieldtypes = list()
  201. lerel_uid_fieldtype = None
  202. for fname, ftargs in EditorialModel.classtypes.relations_common_fields.items():
  203. ftargs = copy.copy(ftargs)
  204. fieldtype = ftargs['fieldtype']
  205. self.needed_fieldtypes |= set([fieldtype])
  206. del(ftargs['fieldtype'])
  207. constructor
  208. """
  209. result += """
  210. ## @brief _LeCrud concret class
  211. # @see leapi.lecrud._LeCrud
  212. class LeCrud(leapi.lecrud._LeCrud):
  213. _datasource = {ds_classname}(**{ds_kwargs})
  214. _uid_fieldtype = None
  215. ## @brief _LeObject concret class
  216. # @see leapi.leobject._LeObject
  217. class LeObject(LeCrud, leapi.leobject._LeObject):
  218. _me_uid = {me_uid_l}
  219. _uid_fieldtype = {leo_uid_fieldtype}
  220. _leo_fieldtypes = {leo_fieldtypes}
  221. ## @brief _LeRelation concret class
  222. # @see leapi.lerelation._LeRelation
  223. class LeRelation(LeCrud, leapi.lerelation._LeRelation):
  224. _uid_fieldtype = {lerel_uid_fieldtype}
  225. _rel_fieldtypes = {lerel_fieldtypes}
  226. _lesup_name = {lesup_name}
  227. _lesub_name = {lesub_name}
  228. class LeHierarch(LeRelation, leapi.lerelation._LeHierarch):
  229. pass
  230. class LeRel2Type(LeRelation, leapi.lerelation._LeRel2Type):
  231. pass
  232. class LeClass(LeObject, _LeClass):
  233. pass
  234. class LeType(LeClass, _LeType):
  235. pass
  236. """.format(
  237. ds_classname = datasource_cls.__module__ + '.' + datasource_cls.__name__,
  238. ds_kwargs = repr(datasource_args),
  239. me_uid_l = repr(leobj_me_uid),
  240. leo_uid_fieldtype = leobj_uid_fieldtype,
  241. leo_fieldtypes = '{\n\t' + (',\n\t'.join(leobj_fieldtypes))+ '\n\t}',
  242. lerel_fieldtypes = '{\n\t' + (',\n\t'.join(lerel_fieldtypes))+ '\n\t}',
  243. lerel_uid_fieldtype = lerel_uid_fieldtype,
  244. lesup_name = repr(lesup),
  245. lesub_name = repr(lesub),
  246. )
  247. emclass_l = model.components(EditorialModel.classes.EmClass)
  248. emtype_l = model.components(EditorialModel.types.EmType)
  249. #LeClass child classes definition
  250. for emclass in emclass_l:
  251. result += """
  252. ## @brief EmClass {name} LeClass child class
  253. # @see leapi.leclass.LeClass
  254. class {name}(LeClass, LeObject):
  255. _class_id = {uid}
  256. """.format(
  257. name=LeFactory.name2classname(emclass.name),
  258. uid=emclass.uid
  259. )
  260. #LeType child classes definition
  261. for emtype in emtype_l:
  262. result += """
  263. ## @brief EmType {name} LeType child class
  264. # @see leobject::letype::LeType
  265. class {name}(LeType, {leclass}):
  266. _type_id = {uid}
  267. """.format(
  268. name=LeFactory.name2classname(emtype.name),
  269. leclass=LeFactory.name2classname(emtype.em_class.name),
  270. uid=emtype.uid
  271. )
  272. #Generating concret class of LeRel2Type
  273. result += self.emrel2type_pycode(model)
  274. #Set attributes of created LeClass and LeType child classes
  275. for emclass in emclass_l:
  276. result += self.emclass_pycode(model, emclass)
  277. for emtype in emtype_l:
  278. result += self.emtype_pycode(model, emtype)
  279. #Populating LeObject._me_uid dict for a rapid fetch of LeType and LeClass given an EM uid
  280. me_uid = {comp.uid: LeFactory.name2classname(comp.name) for comp in emclass_l + emtype_l}
  281. result += """
  282. ## @brief Dict for getting LeClass and LeType child classes given an EM uid
  283. LeObject._me_uid = %s""" % "{" + (', '.join(['%s: %s' % (k, v) for k, v in me_uid.items()])) + "}"
  284. result += "\n"
  285. heading = heading.format(needed_fieldtypes_list = ', '.join(self.needed_fieldtypes))
  286. result = heading + result
  287. del(self.needed_fieldtypes)
  288. return result