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.

components.py 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #-*- coding: utf-8 -*-
  2. import itertools
  3. import warnings
  4. import copy
  5. from lodel.utils.mlstring import MlString
  6. from lodel.editorial_model.exceptions import *
  7. ## @brief Abstract class to represent editorial model components
  8. # @see EmClass EmField
  9. class EmComponent(object):
  10. ## @brief Instanciate an EmComponent
  11. # @param uid str : uniq identifier
  12. # @param display_name MlString|str|dict : component display_name
  13. # @param help_text MlString|str|dict : help_text
  14. def __init__(self, uid, display_name = None, help_text = None, group = None):
  15. if self.__class__ == EmComponent:
  16. raise NotImplementedError('EmComponent is an abstract class')
  17. self.uid = uid
  18. self.display_name = None if display_name is None else MlString(display_name)
  19. self.help_text = None if help_text is None else MlString(help_text)
  20. self.group = group
  21. def __str__(self):
  22. if self.display_name is None:
  23. return str(self.uid)
  24. return str(self.display_name)
  25. ## @brief Handles editorial model objects classes
  26. #
  27. # @note The inheritance system allow child classes to overwrite parents EmField. But it's maybe not a good idea
  28. class EmClass(EmComponent):
  29. ## @brief Instanciate a new EmClass
  30. # @param uid str : uniq identifier
  31. # @param display_name MlString|str|dict : component display_name
  32. # @param abstract bool : set the class as asbtract if True
  33. # @param parents list: parent EmClass list or uid list
  34. # @param help_text MlString|str|dict : help_text
  35. def __init__(self, uid, display_name = None, help_text = None, abstract = False, parents = None, group = None):
  36. super().__init__(uid, display_name, help_text, group)
  37. self.abstract = bool(abstract)
  38. if parents is not None:
  39. if not isinstance(parents, list):
  40. parents = [parents]
  41. for parent in parents:
  42. if not isinstance(parent, EmClass):
  43. raise ValueError("<class EmClass> expected in parents list, but %s found" % type(parent))
  44. else:
  45. parents = list()
  46. self.parents = parents
  47. ## @brief Stores EmFields instances indexed by field uid
  48. self.__fields = dict()
  49. ## @brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
  50. @property
  51. def __all_fields(self):
  52. res = dict()
  53. for pfields in [ p.__all_fields for p in self.parents]:
  54. res.update(pfields)
  55. res.update(self.__fields)
  56. return res
  57. ## @brief EmField getter
  58. # @param uid None | str : If None returns an iterator on EmField instances else return an EmField instance
  59. # @param no_parents bool : If True returns only fields defined is this class and not the one defined in parents classes
  60. # @return A list on EmFields instances (if uid is None) else return an EmField instance
  61. def fields(self, uid = None, no_parents = False):
  62. fields = self.__fields if no_parents else self.__all_fields
  63. try:
  64. return list(fields.values()) if uid is None else fields[uid]
  65. except KeyError:
  66. raise EditorialModelError("No such EmField '%s'" % uid)
  67. ## @brief Add a field to the EmClass
  68. # @param emfield EmField : an EmField instance
  69. # @warning do not add an EmField allready in another class !
  70. # @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
  71. def add_field(self, emfield):
  72. if emfield.uid in self.__fields:
  73. raise EditorialModelException("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
  74. self.__fields[emfield.uid] = emfield
  75. emfield._emclass = self
  76. ## @brief Create a new EmField and add it to the EmClass
  77. # @param uid str : the EmField uniq id
  78. # @param **field_kwargs : EmField constructor parameters ( see @ref EmField.__init__() )
  79. def new_field(self, uid, **field_kwargs):
  80. return self.add_field(EmField(uid, **field_kwargs))
  81. ## @brief Handles editorial model classes fields
  82. class EmField(EmComponent):
  83. ## @brief Instanciate a new EmField
  84. # @param uid str : uniq identifier
  85. # @param display_name MlString|str|dict : field display_name
  86. # @param data_handler class|str : A DataHandler class or display_name
  87. # @param help_text MlString|str|dict : help text
  88. # @param group EmGroup :
  89. # @param **handler_kwargs : data handler arguments
  90. def __init__(self, uid, data_handler, display_name = None, help_text = None, group = None, **handler_kwargs):
  91. super().__init__(uid, display_name, help_text, group)
  92. self.data_handler = data_handler
  93. self.data_handler_options = handler_kwargs
  94. ## @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
  95. self._emclass = None
  96. ## @brief Handles functionnal group of EmComponents
  97. class EmGroup(object):
  98. ## @brief Create a new EmGroup
  99. # @note you should NEVER call the constructor yourself. Use Model.add_group instead
  100. # @param uid str : Uniq identifier
  101. # @param depends list : A list of EmGroup dependencies
  102. # @param display_name MlString|str :
  103. # @param help_text MlString|str :
  104. def __init__(self, uid, depends = None, display_name = None, help_text = None):
  105. self.uid = uid
  106. ## @brief Stores the list of groups that depends on this EmGroup indexed by uid
  107. self.required_by = dict()
  108. ## @brief Stores the list of dependencies (EmGroup) indexed by uid
  109. self.require = dict()
  110. ## @brief Stores the list of EmComponent instances contained in this group
  111. self.__components = set()
  112. self.display_name = None if display_name is None else MlString(display_name)
  113. self.help_text = None if help_text is None else MlString(help_text)
  114. if depends is not None:
  115. for grp in depends:
  116. if not isinstance(grp, EmGroup):
  117. raise ValueError("EmGroup expected in depends argument but %s found" % grp)
  118. self.add_dependencie(grp)
  119. ## @brief Returns EmGroup dependencie
  120. # @param recursive bool : if True return all dependencies and their dependencies
  121. # @return a dict of EmGroup identified by uid
  122. def dependencies(self, recursive = False):
  123. res = copy.copy(self.require)
  124. if not recursive:
  125. return res
  126. to_scan = list(res.values())
  127. while len(to_scan) > 0:
  128. cur_dep = to_scan.pop()
  129. for new_dep in cur_dep.require.values():
  130. if new_dep not in res:
  131. to_scan.append(new_dep)
  132. res[new_dep.uid] = new_dep
  133. return res
  134. ## @brief Add components in a group
  135. # @param components list : EmComponent instance list
  136. def add_components(self, components):
  137. for component in components:
  138. if isinstance(component, EmField):
  139. if component._emclass is None:
  140. warnings.warn("Adding an orphan EmField to an EmGroup")
  141. elif not isinstance(component, EmClass):
  142. raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
  143. self.__components |= set(components)
  144. ## @brief Add a dependencie
  145. # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
  146. def add_dependencie(self, grp):
  147. try:
  148. for group in grp:
  149. self.add_dependencie(group)
  150. return
  151. except TypeError: pass
  152. if grp.uid in self.require:
  153. return
  154. if self.__circular_dependencie(grp):
  155. raise EditorialModelError("Circular dependencie detected, cannot add dependencie")
  156. self.require[grp.uid] = grp
  157. grp.required_by[self.uid] = self
  158. ## @brief Search for circular dependencie
  159. # @return True if circular dep found else False
  160. def __circular_dependencie(self, new_dep):
  161. return self.uid in new_dep.dependencies(True)
  162. def __str__(self):
  163. return "<class EmGroup '%s'>" % self.uid
  164. ## @todo better implementation
  165. def __repr__(self):
  166. return self.__str__()